粘贴富文本至emacs

前言

在使用org-mode记录笔记时遇到一个麻烦:
复制网页的内容后,直接粘贴到普通文本编辑器,gedit,emacs等,均丢失了格式
特定的应用比如typora中,复制可以不丢失格式
此处通过以下方法解决

  • shell命令:xclip复制内容,tidy整理html,pandoc转换格式,其他工具润色
  • emacs调用shell命令,并做后续润色

原因

据说X display会在粘贴内容时才通知原先的应用格式化数据,
会根据粘贴到的应用选择格式.
不过不能解释原先的应用关闭后仍然可以工作.

从剪贴板提取html格式内容

1
xclip -selection clipboard -o -t text/html
  • xclip的-t选项一般不能使用text/html类别,需要加 -selection clipboard 才能使用
    • xclip的官方说明一点也不讲究

对html内容的处理

刚提取出来的html文件有很多毛病:

  • 全部在一行,结尾有字符串结束符 ^@ ,会被pandoc传递到最后的org文本中
  • 会被pandoc解析出行尾有两个空行
  • 一部分网页使用禁止换行空格,pandoc照搬
  • 许多inline style,会被pandoc使用 BEGIN_HTML 环境包围
    • 删除后还需要将空行也删除

这里使用tidy对html文件做基本的处理

1
tidy -q --show-errors 0 --show-body-only yes --clean yes --bare yes

选项含义分别是:

  • 不显示tidy的广告
  • 提示错误级别为0(不提示)
  • 只显示body的部分
  • 将style部分从inline变为head信息,即从tag中删除style
  • 将禁止换行空格替换为普通空格

格式转换

然后将html文件转换为org文件

1
pandoc --lua-filter=/pathe/to/remove-attr.lua -f html-auto_identitfiers-native_spans-native-divs -t org

pandoc做了三件事情:

  • 格式转换
    • 注意pandoc默认为 <h1> 等标签建立id,需要使用 -auto_identifiers 关闭
  • 删除对org来说无意义的span与div,否则将变成 BEGIN_HTML 的一部分
    • 最新版 的pandoc中自动进行,无需使用 -native-spans-native-divs 两个参数
  • 删除多余的attributes,防止变成 BEGIN_HTML 内容
    • 如果使用latex中转,确实会丢掉class,id,style等信息,不过latex对表格内换行支持不好会导致表格内容部分丢失
    • 如果使用 markdown_github 中转,也可以丢掉这些信息,不过在成为 markdown_github 后部分表格会使用html语法,在变回org后仍然被保留,难看

使用的lua文件如下

1
2
3
4
5
6
7
8
function remove_attr (x)
if x.attr then
x.attr = pandoc.Attr()
return x
end
end

return {{Inline = remove_attr, Block = remove_attr}}

公共润色项目

若使用tidy,也不使用pandoc的配置,
则所有的润色工作交给sed来解决.
也是可以办到的.
同时也有一部分是使用预处理后还需要做的公共项目.

latex风格换行的处理 \\

出现原因:

org转换到html时,需要两个换行,或者 \\ 符号才可以在html中显示一个换行,
所以在html转换到org的过程中,html中看不见的换行以某种形式出现在org文件中.
可惜这种形式是 \\ 而不是 换行

  • 删除
    • 如果考虑将org-mode作为最终查看的文件,可以将这两个符号删除.
    • 如果考虑将org导出为markdown或者html查看,可以指定pandoc使用 硬换行,
      不需要双反斜杠也可以在输出到markdown时做到所见即所得.
  • 替换为回车有一定的问题
    • 部分网页会在表格内使用换行,这是org-mode的表格无法使用正常手段(使用换行或 \\ )做到的
      • 可以使用inline的html元素 @@html:<br>@@ 作为表格内换行
    • 因此如果换成换行则会导致表格结构破坏
1
sed 's/\\\\$//g'
  • 正则表达式引擎得到 \\\\ 则进行转义, 成为 \\,合适
  • 如果使用双引号,则在专递给正则表达式引擎之前先进行一次转义,因此用双引号需要8个 \

中文标点的半角化

1
sed 's/。/./g;s/,/,/g;s/、/,/g;s/?/?/g;s/!/!/;s/(/(/g;s/)/)/g;s/;/;/g;s/:/:/g'
  • 可以看出分号的替换与分割命令的分号没有产生歧义,这是分隔符的功劳
  • 还有引号和中括号的问题,但这两个可能会造成org-mode的歧义,暂时放下

TODO emcas自身问题

pandoc转换结果中,格式化部分与正文部分是紧密挨在一起的,
此时emacs无法渲染出结果.
可以通过设置使紧挨时可以渲染

不做预处理时的润色

去除结尾的两个空行

1
head -n -2

去除 ^@

实际上这里是一个空字符
emacs中使用 C-q C-@ 或者 C-q 0 RET 获取
对应的unicode编码是 \x0
多余的空字符会对文档的保存造成影响,比如emacs无法以utf-8编码保存,不过还是可以显示.

1
sed 's/\x0//g'

禁止换行空格的替换

  • 在emcas的GUI模式下看起来难看
  • 需要使用对应的utf-8编码的16进制来替换
    • U+00A0对应的uft-8编码的16进制为C2 A0
1
sed 's/\xC2\xA0/ /g'

多余HTML内容的删除

BEGIN_HTMLEND_HTML 严格地独立占据一行,使用sed删除即可

1
sed '/#+BEGIN_HTML/,/#+END_HTML/d'

行尾空格与多余空白行的处理

  1. emacs内处理

    emacs内处理遇到的最大问题就是处理范围,
    由于是先粘贴后处理,
    光标的位置导致emacs不方便只对刚粘贴来的部分进行处理.
    因此只能全部处理,
    尽管去除行尾空格和删除多余空格是一个全局使用也基本无害的操作,
    但严格上讲有过多处理的问题.

  2. emacs外处理

    1. 可以使用sed删除多余的行尾空格

      1
      sed -E 's/[[:space:]]+$//g'
    2. 然后删除多余的空行

      1
      sed -e '/^$/{N;/\n$/D};'

      注意在配置文件中写时 \n 写成 \\n

emacs的后续处理

可选:emacs内处理行尾空格与多余空白行的处理

结果仅仅在re-builder中验证过.

  1. 若分两步

    1. 删除多余行尾空格

      1
      (delete-trailing-whitespace)
    2. 删除多余空行

      使用的emacs内正则表达式为

      1
      ^^J+$  ->

      需要使用命令表达正则表达式替换,参考
      https://www.emacswiki.org/emacs/RegularExpression

      命令如下

      1
      2
      (while (re-search-forward "^\n+$" nil t)
      (replace-match ""))
  2. 若一步完成

    使用的emacs内正则表达式为

    1
    \(^[[:blank:]]*^J\)+  ->  ^J

    命令如下

    1
    2
    (while (re-search-forward "\\(^[[:blank:]]*\n\\)+" nil t)
    (replace-match "\n"))

表格对齐

有可能转换出的结果中表格就没有对齐,
也有可能是替换了字符造成了长短的变化.
总之表格错乱了.
需要调整

1
(org-table-map-tables 'org-table-align)

内容多时会有进度显示

手动处理

pandoc下划线转换的不足

原文件中的下划线(或者链接?)被pandoc解释为 /content/, 成了斜体

歧义的部分可能有很多,需要自行手动替换

其他问题

  • 待发现

写成emacs函数

函数框架参考:
https://emacs.stackexchange.com/questions/12121/org-mode-parsing-rich-html-directly-when-pasting

原文中写到由json格式中介可以简化html,但目前遇到的情况中两者输出都一样,倒是latex和 markown_github 可以

tidy,pandoc,sed润色

1
2
3
4
5
6
7
;; org-mode的粘贴函数
(defun mypaste-html2org-clipboard()
"Convert clipboard contents from HTML to Org and then past."
(interactive)
(kill-new (shell-command-to-string "xclip -selection clipboard -o -t text/html |tidy -q --show-errors 0 --show-body-only yes --clean yes --bare yes | pandoc --lua-filter=/home/chougousui/.emacs.d/remove-attr.lua -f html-auto_identifiers -t org | sed 's/\\\\\\\\$//g;s/。/./g;s/,/,/g;s/、/,/g;s/?/?/g;s/!/!/;s/(/(/g;s/)/)/g;s/:/:/g;s/;/;/g;'"))
(yank)
(org-table-map-tables 'org-table-align))
  • emacs不可以为某个模式定义mode
    • 不过可以检测函数,在其他模式是返回no-op
    • 或者人为控制在其他模式不用
  • (interactive)
    为了定义为交互函数,否则像 shell-command-to-string 一样不能被 M-x 调用
  • kill-new
    剪切
  • sed的调整
    这里 xclip... 等命令为了避免与sed的 ' 冲突使用了双引号,
    导致sed中的反斜杠还是被转义了,
    因此需要输入共计8个,传递给shell后变为4个,sed解读为2个

全部由sed润色的方案

1
2
3
4
5
6
7
;; org-mode的粘贴函数
(defun mypaste-html2org-clipboard()
"Convert clipboard contents from HTML to Org and then past."
(interactive)
(kill-new (shell-command-to-string "xclip -selection clipboard -o -t text/html | pandoc -f html -t org | sed 's/\\\\\\\\$//g;s/\\x0//g;s/。/./g;s/,/,/g;s/、/,/g;s/?/?/g;s/!/!/;s/(/(/g;s/)/)/g;s/:/:/g;s/;/;/g;s/\xC2\xA0/ /g' | sed '/#+BEGIN_HTML/,/#+END_HTML/d' | head -n -2 | sed -E 's/[[:space:]]+$//g' | sed -e '/^$/{N;/\\n$/D};'"))
(yank)
(org-table-map-tables 'org-table-align))

mac系统

提取方法的变化

xclip是针对X window的工具,
mac下有自己的工具将剪贴板的内容转换为html格式
不过由于提取后的格式为16进制,需要进一步转换

1
osascript -e 'the clipboard as "HTML"' | perl -ne 'print chr foreach unpack("C*",pack("H*",substr($_,11,-3)))'

润色情况的不同

由于没有采用xclip处理而是使用了perl进一步处理,在结果上较linux张更为方便

  • 没有了style标签等
  • 也意味着不需要删除多余的空行(尽管网页本身可能有多余的空行)
  • 没有了结尾的两个空行和 \0

需要处理的也仅仅有三个

  • 去除行尾 \\
  • 中文标点的半角化
  • 非换行空格到普通空格的转换

需要注意:
mac上的sed是BSD的版本,在正则表达式的支持上与GNU的版本不同,
为了使用同样的正则表达式,建议使用gsed作为替代

emacs函数

1
2
3
4
5
6
7
;; org-mode的粘贴函数
(defun mypaste-html2org-clipboard()
"Convert clipboard contents from HTML to Org and then past."
(interactive)
(kill-new (shell-command-to-string "osascript -e 'the clipboard as \"HTML\"' | perl -ne 'print chr foreach unpack(\"C*\",pack(\"H*\",substr($_,11,-3)))' | pandoc -f html -t org | gsed 's/\\\\\\\\$//g;s/。/./g;s/,/,/g;s/、/,/g;s/?/?/g;s/!/!/;s/(/(/g;s/)/)/g;s/:/:/g;s/;/;/g;s/\xC2\xA0/ /g'"))
(yank)
(org-table-map-tables 'org-table-align))