7

我在 Flex/AS3 中工作(为简单起见)一个 XML 编辑器。我需要提供撤消/重做功能。

当然,一种解决方案是在每次编辑时存储整个源文本。但是,为了节省内存,我想存储差异(这些差异也将用于将更新传输到服务器以进行自动保存)。


我的问题是 - 我可以使用纯文本差异算法来跟踪这些 XML 更改吗?

我在互联网上的研究表明我不能这样做。但是,我显然错过了一些东西。明文 diff 提供的功能据称是:

diff(text, text') -> diffs
patch(text, diffs) -> text'

XML 只是文本,那么为什么我不能只使用 diff() 和 patch() 来可靠地转换文本呢?

例如:假设我是一个诗人。当我写诗时,我会使用很多时髦的标点符号……你知道,像 <、/ 和 >。(您可能会看到我将如何处理...)如果我在使用差异来提供撤消/重做功能的应用程序中写诗,那么当我撤消/重做我的编辑时,我的诗歌会变得乱码吗?这只是文字!为什么它会对算法产生影响?

我显然在这里没有得到任何东西......感谢您的解释!:)

更新:

我遇到的一些关于使用纯文本算法区分 XML 的讨论:


另外,我知道命令模式可能是实现撤消/重做的更好方法。为了简单起见,我已经简化了我的用例,我仍然认为 XML diffing 是最好的方法。

4

4 回答 4

14

我是 Google 的纯文本 diff/match/patch 库的作者。

关键问题是你的补丁是否准确。在理想世界中:

  diff(old_text, new_text) -> edits
  patch(edits, old_text) -> new_text

请注意,两个操作中的基本文本 (old_text) 是相同的。在这种理想情况下,无论内容的类型如何,一个简单的纯文本差异和补丁都可以完美运行。如果这种情况适用于您,那么您就完成了。

问题在于模糊修补。这是相应的示例:

  diff(old_text, new_text) -> edits
  patch(edits, old_forked_text) -> new_forked_text

请注意,两个操作中的基本文本不同。它们应该是相似的,但是补丁操作现在必须使用“判断”来判断它应该做什么。一些补丁可能完全符合编辑中的规定,其他补丁可能需要调整位置,其他补丁可能需要调整以适应变化的上下文,其他补丁可能根本不适合,应该被删除。如果您的修补算法在做出决定时不了解 XML 的结构,那么您很可能最终会得到错误的 XML。这是一个示例:

  old_text = Jabberwock<SPAN>Hello<SPAN>World</SPAN></SPAN>
  new_text = Jabberwock<DIV>Hello<SPAN>World</SPAN></DIV>
  diff(old_text, new_text) -> edits
  edits = ["SPAN" -> "DIV" @ character 11,
           "SPAN" -> "DIV" @ character 41]
  old_forked_text = <SPAN>Hello<SPAN>World</SPAN></SPAN>
  patch(edits, old_forked_text) -> new_forked_text
  new_forked_text = <SPAN>Hello<DIV>World</SPAN></DIV>

让我们仔细看看这个。原始差异返回两个编辑,将最外面的 SPAN 更改为 DIV。简单的改变。不幸的是,正在应用此编辑的文本已与原始文本不同。“Jabberwock”一词已被删除。现在第一个 SPAN->DIV 更改与第二个 SPAN 标记匹配,而不是第一个。由于补丁算法不了解 XML 的规则,因此会导致标签的非法嵌套。

有一些技巧可以让您在使用纯文本补丁时保证有效的 XML,但它们会导致一些灵活性的损失(原始问题已经有一个指向我写的关于此的 wiki 页面的链接)。修补 XML 的最终解决方案当然是使用可识别 XML 的差异和修补算法。这些明显更加复杂和昂贵,但它们存在。谷歌一下 Tancred Lindholm 和 Sebastian Rönnau 在 XML 领域(尤其是关于 DocEng)所做的出色工作。

让我知道是否还有其他可以添加的内容。

——尼尔·弗雷泽

于 2010-03-12T09:15:28.603 回答
1

我一直使用Beyond Compare来比较 XML 文档。它在一定程度上理解 XML。

您可能需要对这两个文档进行预处理,以使文本比较能够做到最好。例如,在某些 XML 文档中,某些元素的顺序可能无关紧要。这对您的差异工具肯定很重要!在比较两个排序的文件之前,您可能需要使用 XML 转换对 XML 进行预处理,该转换将这些元素排序为两个文件中的公共顺序。

您还需要对两个文档使用相同的缩进。我发现在新行开始每个元素并为每个级别使用相同数量的缩进和空格很有用。如果您的文档变得非常深,您可能希望每个级别只使用一两个空格,以便比较适合屏幕。您甚至可能希望每行使用一个属性(并将属性排序为公共顺序)。

于 2010-03-12T02:31:02.597 回答
1

如果您是撤消/重做点之间数据的唯一“所有者”,那么您当然可以为它们使用纯文本差异。正如您所指出的,它相当于一组转换。

但是,根据您提供的操作,纯文本差异可能不是远程记录撤消/重做的最佳选择,您可能需要专门处理某些情况。想象一下只记录一个 ReplaceAll 命令,它可能只有几个字节的开销加上搜索和替换字符串。这可能会产生大量的明文差异。

在更广泛的背景下,如果您允许对这些文档进行外部编辑,并且您正在更多地考虑如何在服务器上存储增量,那么您就是在模仿 git 或其他版本控制系统。您必须使用某种差异算法,因为仅记录您的命令显然不是转换的唯一来源。此时,您开始将撤消/重做与版本控制混为一谈,您可能需要认真考虑是否让您的用户混淆这些概念。

我会在编辑会话中保留撤消/重做,并在文件打开时禁止外部编辑。如上所述,这使您可以针对广泛的情况优化命令记录。

除此之外,要么使用传统的版本控制(考虑包装 git),要么实现自己的方式来处理在编辑器之外更改的文件。

于 2010-03-12T02:43:44.577 回答
-1

我认为您可以对 xml 使用文本差异,尤其是在人类将逐行编写 xml 的情况下。我不知道你得到什么信息说你不能这样做,但我猜这个声明是基于这样一个事实,即空格字符(空格、制表符、换行符......)与它们在纯文本文件中有些不同,这可能导致两个不同的文本文件从 XML 的角度来看是相同的。但同样,对于一个以人类为目标的编辑,我不明白为什么你不能。

于 2010-03-12T02:27:42.620 回答