HTML文档差异对比实现

在RainbowPortal中,当工作流开启时,需要为编辑器和/或审批者提供一种方式,以直观地比较当前生产版本与暂存版本的HTML文档的差异。这种代码不会发布在RainbowPortal中。

经过一些研究,选择了Eugene W. Myers在其论文《An O(ND) Difference Algorithm and its Variation》中提出的算法,这也是Unix Diff使用的算法。可以在此处找到文件的副本。Unix Diff的实现可以在此处找到。

设计

总体要求可以简要概括为:

要比较的是可能包含HTML标签、HTML注释、空白符和特殊字符(如" "、"{"等)的HTML文本。文档应该作为一个整体进行比较,而不是逐行比较。重要的是文档内容的差异,因为格式已经直观显示。差异应该被突出显示。

由于是文档级比较,为了性能和方便起见,只比较文档内容,决定逐词而不是逐字符比较文档。因此,过程可以分为两部分。第一部分是解析文档以找出标签、自然词、标点符号、特殊字符和空白符等。第二部分是比较单词、标点符号和一些特殊字符以找出差异。由于比较过程中忽略的内容仍然需要用于重建文档,因此必须通过将其与相邻的单词关联起来来保留它们。

定义了一个名为Word的类来表示解析出的单词以及它前面的标签、空白符和特殊字符。Word类的定义如下:

internal class Word : IComparable { private string _word = string.Empty; private string _prefix = string.Empty; private string _suffix = string.Empty; public Word() { ... } public Word(string word, string prefix, string suffix) { ... } ... // 用于重建无变化的单词 public string reconstruct() { ... } // 用于重建已更改的单词 public string reconstruct(string beginTag, string endTag) { ... } public int CompareTo(object obj) { ... } }

解析遵循以下规则:

  • 以'<'开头并以'>'结尾的任何内容都被视为HTML标签。
  • HTML标签和空白符被视为相邻单词的前缀或后缀,并放入Word对象的前缀或后缀字段中。
  • 由空格、" "、"&#xxx;"、尾随标点分隔的英文单词被视为单词,并放入Word类的word字段中。
  • 空白符 == {' ', '\t', '\n'}。

还定义了一个强类型集合WordsCollection来保存解析出的单词。

定义了一个名为HtmlTextParser的类,其中包含一个静态方法用于解析。

internal class HtmlTextParser { static public WordsCollection parse(string s) { ... } }

最后,定义了一个名为Merger的类来执行比较和合并的繁重工作:

class Merger { private WordsCollection _original; private WordsCollection _modified; private IntVector fwdVector; private IntVector bwdVector; public Merger(string original, string modified) { .. } ... private MiddleSnake findMiddleSnake(Sequence src, Sequence des) { ... } private string doMerge(Sequence src, Sequence des) { ... } ... public string merge() { ... } }

Merger类的构造函数将接受HTML文本字符串,并调用HtmlTextParser.parse()将它们解析成两个单词集合。当调用公共方法merge()时,它将从集合中构建Sequence对象并调用私有方法doMerge(),而doMerge将递归比较和合并整个集合。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485