從 PDF 複製的文字有奇怪的空格和換行符
PDF 裡複製出來的中文,有時候文字之間會出現不自然的空格和換行符,還有數字和英文字母都是全形的、標點符號全是半形的。 例如:
1969 年,阿 波 羅11
號(Apollo 11)登
陸 月 球。
看起來很詭異。
我需要一個工具,在貼上的那一刻自動把文字「洗」乾淨,變成:
1969年,阿波羅11號(Apollo 11)登陸月球。
要做的事情包括:
- 刪除漢字之間的空格和換行符
- 把全形數字(0123)轉換為半形數字(0123)
- 把全形字母(Apollo)轉換為半形數字(Apollo)
- 把半形標點(!?"":;)轉換為全形標點(!?“”:;)
我的電腦是 Windows。不知道 Mac 是不是有更方便的方法解決這個問題。 總之用 AutoHotkey v2 寫了個快捷方式。 AutoHotkey 是一個輕量級自動化工具,可以開機自啟、常駐後臺執行。
詳細來說的話就是安裝 AutoHotkey,然後随便在哪里新建一個 .ahk 文件1,寫入程式碼。(if 有人想要完整程式碼,請看後面。) 雙擊執行該 .ahk 檔案。該工具會在背景執行。也可以把這個 .ank 檔案設定為開機自啟,跳過雙擊執行這一步。
現在我只需要 複製 PDF 中的文本,貼上的時候不要按 Ctrl+V,而是按我自己設定的快捷鍵(Ctrl+Shift+Q),剪貼簿裡的文字會被自動“清洗”、自動貼上。 貼上的速度和用 Ctrl+V 一樣快,但貼出來的文字變“乾淨”了。
今天改進了一下。 之前的設計是遇到空格就刪掉:
text := RegExReplace(text, "\s+")
然後發現正常英文之間的空格它也刪除,比如
漢字排版測試(Typography Test)在轉換之後會變成漢字排版測試(TypographyTest),Typography Test中間的空格消失了。阿波羅11號(Apollo 11)在轉換之後會變成阿波羅11號(Apollo11),Apollo 11之間的空格消失了。
遂改成 只刪除「空格兩側至少有一側是中日韓字元」的空格。 如果空格兩側都是純 ASCII 字母或數字(說明是正常的英文/數字間隔),就保留。
現在唯一沒做的是小數點.轉句號。
因為需要考慮的特殊條件有點太多了。
曾經嘗試設定轉換規則為,只有當小數點.前面緊接著中文/全形字元,而且後面不是緊接數字、英文字母或另一個小數點時,才轉換成全形句號 。比如:
阿波羅11號登上月球.小數點前是中文字元,轉換成阿波羅11號登上月球。0.99cm小數點前是數字,不轉換1.第一項小數點前是數字,不轉換www.wikipedia.org,小數點前是字母,不轉換这样啊...,小數點後是小數點,不轉換一.1990年度工作報告,小數點後是數字,不轉換
但是中文文章也會出現句尾是英文,或者句首是英文的情況。比如 不可使用生成式AI. ,小數點前是英文,不轉換,但實際上應該使用中文句號。
情況太複雜了!所以沒有在標點轉換中寫入轉換小數點。
完整 AHK 代码
#Requires AutoHotkey v2.0
; 觸發這個程式的快捷鍵 是 ^+q
; ^+q 的意思是 shift+ctrl+q
^+q::
{
text := A_Clipboard
if (text = "")
return
; 1. 全形數字轉半形
fullwidthNum := "0123456789"
halfwidthNum := "0123456789"
Loop Parse, fullwidthNum
text := StrReplace(text, A_LoopField, SubStr(halfwidthNum, A_Index, 1))
; 2. 全形字母轉半形字母(A-Z, a-z):引用函式
text := ConvertFullwidthLetters(text)
; =========================
; 3. 半形標點轉全形標點
; =========================
; 一般標點:一對一直接替換
text := StrReplace(text, ",", ",")
text := StrReplace(text, "!", "!")
text := StrReplace(text, "?", "?")
text := StrReplace(text, ":", ":")
text := StrReplace(text, ";", ";")
text := StrReplace(text, "~", "~")
text := StrReplace(text, "(", "(")
text := StrReplace(text, ")", ")")
text := StrReplace(text, "%", "%")
; 雙引號 " 和 單引號 ' :需要根據出現順序交替變成左/右引號
text := ConvertQuotes(text, '"', "“", "”")
text := ConvertQuotes(text, "'", "‘", "’")
; =========================
; 4. 刪除空白字元
; 只刪除「與中文/全形字元相鄰」的空白
; =========================
; 中日韓統一表意文字 + 中日韓擴展A + 中日韓兼容表意文字 + 中文標點區(、。《》「」等)
CJK := "\x{4E00}-\x{9FFF}\x{3400}-\x{4DBF}\x{F900}-\x{FAFF}\x{3000}-\x{303F}"
text := RegExReplace(text, "(?<=[" CJK "])\s+") ; 空白前面是中文/全形字元 → 刪除
text := RegExReplace(text, "\s+(?=[" CJK "])") ; 空白後面是中文/全形字元 → 刪除
; 寫回剪貼簿並貼上
A_Clipboard := text
ClipWait(1)
Send("^v")
}
; =========================
; 全形字母轉半形字母函式
; 全形 A-Z: U+FF21-FF3A → 半形 A-Z: U+0041-005A
; 全形 a-z: U+FF41-FF5A → 半形 a-z: U+0061-007A
; =========================
ConvertFullwidthLetters(text) {
result := ""
Loop Parse, text
{
code := Ord(A_LoopField)
if (code >= 0xFF21 && code <= 0xFF3A) {
; 全形大寫字母
result .= Chr(code - 0xFF21 + 0x41)
} else if (code >= 0xFF41 && code <= 0xFF5A) {
; 全形小寫字母
result .= Chr(code - 0xFF41 + 0x61)
} else {
result .= A_LoopField
}
}
return result
}
; =========================
; 引號轉換輔助函式
; 把半形引號 quoteChar 按出現順序交替替換為 leftChar / rightChar
; =========================
ConvertQuotes(text, quoteChar, leftChar, rightChar) {
result := ""
isLeft := true
Loop Parse, text
{
if (A_LoopField = quoteChar) {
result .= isLeft ? leftChar : rightChar
isLeft := !isLeft
} else {
result .= A_LoopField
}
}
return result
}
如果不知道怎樣新建 .ahk 文件:新建一個純文字檔案(新建文件.txt),把程式碼寫在裡面;儲存檔案並關閉檔案;然後將該檔案的副檔名從 .txt 更改為 .ahk。好了。 ↩︎