如何将直引号转换为弯引号的想法

15
我有一个包含“直引号”(普通的ASCII)的文件,并且我想将它们转换为真正的引号符号字符(“卷曲”的引号,U+2018到U+201D)。由于从两个不同的引号字符转换为一个字符本身就是有损失的,显然没有办法自动执行此转换;尽管如此,我仍然认为一些启发式算法将涵盖大部分情况。因此,计划编写一个脚本(在Emacs中),执行以下操作:对于每个直引号字符,
  1. 如果可能,猜测使用哪个卷曲引号字符
  2. 要求用户(即我)确认或进行选择
这个问题是关于第一步:对于正常英文文本(例如小说),什么样的算法(更像是一组启发式方法)可以使用呢?以下是一些初步的想法,我相信对于双引号是有效的(欢迎提出反例!):
  1. 如果双引号位于行首,则猜测其为开口引号。
  2. 如果双引号位于行末,则猜测为闭合引号。
  3. 如果双引号前面有空格,则猜测为开口引号。
  4. 如果双引号后面有空格,则猜测为闭合引号。
  5. 如果双引号不符合上述任何一种类别,则猜测它是最近使用的另一种双引号类型的“相反”。
单引号比较棘手,因为一个 ' 可能既是开头引号、结尾引号,或者撇号,我们需要保留撇号(不应该写成“mustn’t”)。一些和上面相同的规则适用,但有可能撇号在单词(或行)的开头,虽然这种情况比过去少了。我想不出规则来处理类似 ["I like 'That '70s show'", she said] 这样的片段。它可能需要查看不止相邻字符,并计算引号之间的距离,例如……

还有更多想法吗?如果没有覆盖所有可能的情况也没关系,目标是尽可能智能,但不要过度。:-)

编辑:还有一些可能值得考虑的事情(或可能与此无关,不确定):

  • 引号可能不总是成对出现:对于单引号,如上所述,很明显为什么。但即使对于双引号,当引语延伸到多个段落时,通常的印刷惯例(不要问我为什么)是在每个段落开头加上引号,即使它在前一个段落中没有关闭。因此,简单地保持在两种状态之间交替的状态机将无法起作用!
  • 嵌套引用(在上面的“我喜欢‘那个‘70年代秀’”示例中提到):这可能会使任一类型的引用在前面或后面跟随空格。
  • 英式/美式标点风格:逗号是在引号内还是外面?
  • 许多文字处理器(例如Microsoft Word)已经执行了某种类似于此的转换。虽然它们并不完美,而且经常很烦人,但学习它们的工作方式可能是有益的...

我最终完成了实际文档的转换。前四条规则涵盖了所有双引号。对于单引号,“紧随逗号或句号”处理了许多闭合引号,其余的我不得不手动处理。 - ShreevatsaR
10个回答

3
你不能使用正则表达式解析英文引号,因为英文引号无法被正则表达式解析。正则表达式不足以表达英文引号的解析。在一些情况下可以使用,但无法使用正则表达式创建通用解决方案。请参见测试用例以了解我的解决方案
给定:
  • 一个词法分析器,用于从字符流中创建词元。
  • 一个发射器,用于发布各种类型的引号。
  • 一个消歧器,用于创建嵌套树。
  • 已知的有歧义和无歧义缩写集合。
  • 长度为4的循环缓冲区。
然后,广义地说,可能会有一种算法:
使用词法分析器迭代文档。 将从词法分析器获得的词元传递给发射器。 将词元推入发射器的循环缓冲区中。 在发射器中每次解析4个词元以分类卷曲部分: - 开始/结束双引号/单引号 - 撇号 - 直引号 - 不明确的开始单引号 - 不明确的结束单引号 - 不明确的单引号 - 不明确的双引号 将分类后的引号作为标记发送到歧义解析器。 让解析器创建树(用于跟踪嵌套引号): - 为开头的引号标记(单/双)打开一个树 - 对于关闭引号标记(单/双),关闭该树 - 否则,在当前树中跟踪任何不明确的标记 在所有标记都在嵌套树中之后: - 从根开始 - 解决标记歧义 - 对标记列表进行排序 - 解决剩余的标记 - 再次解决标记歧义 - 将标记转发给文档解析器。
消歧义需要用可解析的等价物替换模棱两可的引号。基本上,您需要计算模棱两可的前导、后续和不确定单引号的数量。根据当前树的级别是否已经包含一些前导/后缀引号的组合,您可以确定模棱两可的引号是:闭合单引号、开放引号或撇号。
这不是一个简单的算法,它可能需要:
- 循环缓冲区 - 词法分析器(标记化程序) - 解析器(发射器) - 解决程序(歧义) - 树 - 一组缩写词(模棱两可和明确的)
以下是KeenQuotes的一些屏幕截图,它被集成到我的文本编辑器KeenWrite中:

keenquotes 01

提示:应该写成“70年代”,而不是“70的年代”,因为年代本身没有所有格。

keenquotes 02


对于你关于“Nit: It's '70s, not '70's because decades cannot possess anything.” 的过于学究的注释,这里有一个异常情况需要注意,即当假设“’s”中的撇号表示所有权时:尽管包含撇号的“it's”并不表示所有权,而同样,“its”则在没有撇号的情况下表示所有权。编写处理此问题的代码时需要考虑到这个例外。 - pbarney

2
一个很好的起点是使用状态机:
  • 从位置0开始迭代字符
  • 遇到引号时,进入“Quoted”状态(开放引号)
  • 如果在“Quoted”状态下遇到引号,则返回到“Starting”状态(关闭引号)
在每个状态转换时,您可以做出附加决策。
例如,您可以尝试通过识别已知连词并在处理之前将其转换为不同的非文本字符来规范化单引号。
我的价值观是 $0.02。

1
这只是假设引号字符交替作为开头引号和结尾引号,这显然是正确的。 - ShreevatsaR
这就是规范化发挥作用的地方。如果您知道有段落分隔符,则可以将无序引号更改为其他内容。状态机是处理经过规范化的文本的工具。通常,找到所有“奇怪”的情况比考虑所有“好”的情况要容易得多。 - Ryan Emerle
引号交替是比较容易处理的情况,有很多种方法可以处理,包括你使用的方法。我正在尝试找到一个更大的启发式集合(不仅仅是“交替”),以尽可能地处理更多的情况。问题中的启发式已经涵盖了比这个答案(5)更多的情况。 - ShreevatsaR
重点是状态机可以在实现中使用。我发布了一个例子,但更复杂的状态机可以轻松处理99%的情况。这个讨论是关于英语语言复杂性还是解决问题的方法? - Ryan Emerle
实际的“机制”(保持状态、回溯、编写递归下降解析器等)是实现细节,我认为我可以处理它们。问题确实是关于基于英语语言的高层次思想...如果这不清楚,对不起。我该如何更好地表达呢? - ShreevatsaR
抱歉,我误解了。我认为您描述问题的方式很好,只是网站的背景让我走上了实现路径。 - Ryan Emerle

2

猜测应该使用哪种卷曲引号字符,如果可能的话

一般情况下是无法确定的。

大多数自动转换器使用的简单算法只是查看在 ' 或 " 前键入的前一个字母。如果它是空格、行首、开放括号或其他开放引号,则选择开放引号,否则选择闭合引号。这种方法的优点是可以实时运行,因此当它选择错误时,您通常可以进行更正。

我们想保留撇号不变

我同意!但并不是很多人都这样做。将撇号转换为向左的单引号是正常的排版惯例。个人而言,我更喜欢保持它们原样,以区分它们与封闭引号,使文本更易于阅读,并且可以自动处理。

然而,这确实只是我的个人喜好,并不能仅仅因为该字符被 Unicode 标准定义为 APOSTROPHE 就被广泛认可。

可能会有撇号出现在单词开头

确实如此。在像 Fish 'n' Chips 这样的情况下,除了巨大量的文化背景外,无法区分撇号和潜在的开放引号。

(更不用说 prime、okina、喉塞音和其他使用撇号的情况了...)

当然,最好的方法是安装可以直接输入智能引号的键盘布局。我在 AltGr+[] 上有 ‘’,在 AltGr+Shift+[] 上有 “”,在 AltGr+[Shift]+dash 上有 –—,等等。


好的观点!不幸的是,我已经完成了这个文件的3/4(重新格式化一个OCR公共领域的书),尽管我试图手动进行一些更改,但我发现大部分情况下都可以自动化处理...这也引出了这个问题。 :) - ShreevatsaR
哦,我也遇到过这种情况!是的,我通常使用上面介绍的简单方法来处理,但要在单词内部保留撇号。仍然需要手动校对才能发现被错误转换的起始撇号和复数所有格。 - bobince

2
看起来你的初稿已经涵盖了我要写的大部分内容,以下是我留下的:
对于撇号的例子(“我喜欢 'That'70s show'”,她说),不太可能在同一类型的引号内嵌套引号。你可以利用这一点。
我认为最好的方法是只处理明确的情况(双引号非常简单)。对于有多个可能选择的情况,将它们的位置存储在列表中,在完成时进行检查。你可能会发现其中还有几种易于编码的情况,或者你可能决定手动修复它们。

1
  1. 基本的事情是始终尝试找到匹配的引号。鉴于每个引号都有一个匹配的引号,您可以让程序仅在不确定哪个是匹配引号时请求您的帮助。

  2. 开头的引号总是在行首或前面有一个空格。结束引号总是在后面有一个空格。如果您发现冒号后面跟着引号,则可能是结束引号。

  3. 如果引号后面的字母是大写字母,则很可能是开头的引号。

  4. 如果引号前面有标点符号,则很可能是结束引号。

  5. 尝试进行迭代。程序应该首先询问您所有可以明确分配给函数的引号。(只是为了确保它没有犯任何错误。)

  6. 在第二轮中,例如所有不确定是开头引号还是撇号的引号。对于所有开头的引号,它必须自动找到结束引号。

另一个也许不太复杂的想法是:

  1. 通过询问用户,找出所有可能是引用或非引用的非引号

  2. 剩下的引用应该很容易转换。开头的引号前面有空格或换行符,结束后有空格或换行符。

最后一点思考:

你应该将这个过程分解成逐段处理。如果你的程序出错了,鉴于语言的复杂性,更容易纠正错误,并且程序可以从新段落开始重新运行。


1

我不想这么说,但最好的做法可能是学习Word的操作,并复制它。即使在某些情况下它是错误的,但它代表了许多人已经习惯的标准。一个值得模仿的行为是,在你替换弯引号后,撤销(Ctrl-Z)立即恢复成直引号。


是的,我在上面的问题中提到了那个。那么,如何学习Word的操作呢? :) - ShreevatsaR
1
获取最新版本的Word并尝试不同的条件。您已经创建了一个很好的例外情况列表,我相信您会在以后产生更多的例外情况。 - Mark Ransom
1
更具体地说,生成一个他们正在使用的算法的假设,并提出可以证明该假设不成立的测试用例。如果您失败了,那么您很可能已经猜对了算法。 - Mark Ransom

1

计算语言学 有人感兴趣吗?

有人提到如果你拥有大量的文化背景,这可能是可行的。因此,解决问题的过度但最准确的自动化解决方案是浅层分析。这需要一个与您处理的语言和模式相对应的语料库(例如,布朗语料库 用于一般英语)。

基于语料库中花括号引号出现的句法上下文开发分类器。最后,将带有直引号的任意句法上下文输入分类器,最可能的引号字符就会出现!


如果你想朝这个方向发展,http://en.wikipedia.org/wiki/Natural_Language_Toolkit 是一个很好的学习和实现自然语言处理工具的地方。它同时提供了自然语言处理和Python的教程。 - dkretz

1

这里是一个正则表达式,可能有助于处理双引号:

/([^\s\(]?)"(\s*)([^\\]*?(\\.[^\\]*)*)(\s*)("|\n\n)([^\s\)\.\,;]?)/gms

它将在每个段落重新启动,并识别引号对(如果有必要,还允许您检查引号前后的间距是否正确)。

Numbered element    identification  
  1               non-white-space before quote quote  
  2               white-space after leading quote  
  5               white-space before trailing quote  
  6               trailing quote (or double-newline, i.e. start of a paragraph  
  7               character after trailing quote if not whitespace or right   paren                     

我认为将其扩展到其他情况是合理的(我只是还没有需要)。

这是JavaScript语法。它非常快,但我还没有进行更多的优化,只是达到了“足够好”的水平。它可以在大约一秒钟内处理400页的书。我认为要在过程中匹配它的速度是很困难的。


0

她说:“我喜欢《那70年代秀》”

我最初认为多次通过文本以获得上下文洞察力可能会有所帮助,但这并不能解决所有情况。

你可以做的最好的事情是列出可能的单词组/表达式列表,如'twas、'tis、'70's等,并将它们放入字典中,并打开自动更正功能,以将直线转换为卷曲线,反之亦然。拼写检查不是每个单词都运行吗?(对不起,这不能解决你的emacs问题)

从我所知道的情况来看,OO忽略了单引号的弯曲。

维基百科上有一些关于这些讨厌的东西的信息。


0

尝试使用 Shift + Ctrl + "(双引号键),这在我使用名为Kalipso的程序时在Windows 10上有效。


抱歉,这并没有回答我的问题,也不是我想问的。我没有问题插入 字符; 问题是关于提出一个算法/启发式/一组规则来确定何时插入哪个字符。 - ShreevatsaR

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接