ResX 合并工具
用于在 GitHub 合并时帮助合并 ResX 文件的工具
引言
我尝试过的各种合并工具会将 ResX 文件视为文本文件进行合并,而此工具则会将 ResX 文件视为 ResX 文件。它使用 ResXResourceReader
加载它们,然后使用 ResXResourceWriter
进行比较并合并成一个 ResX 文件。
背景
我使用 ResX 文件(不进行代码生成)来存储/检索本地化字符串。每当我从该项目的其他开发人员那里合并各种分支进行构建时,我都会花费越来越多的时间处理我们的主要 ResX 文件并使其正确合并。随着时间的推移,这个问题只会越来越严重,以至于我花费在合并此文件上的时间超过了构建过程中所有其他工作加起来的时间。
我发现的问题是,各种合并工具都将 ResX 文件视为纯文本文件,并尝试逐行合并,而不是逐项合并。我发现的一种建议的解决方案是加载每个 ResX 文件,按键排序,然后以排序后的形式输出,然后让各种合并工具比较和合并排序后的 ResX 文件。
我尝试了这样做 - 实际上使我的问题变得更糟。我从大约 500 个“合并冲突”(合并工具不知道使用哪一行,需要人工输入)增加到大约 3,500 个合并冲突(这是从一个已经增长到约 4,000 个条目的 ResX 文件中得到的)。
由于找不到解决我日益增长的问题的好方法,并且我不想尝试研究一种不同的本地化方案,所以我选择开发自己的 ResX 合并工具。我已经在最近几次构建中使用了它,它在近 12,000 个条目(每个文件 4,000 个条目)上运行良好且快速。现在,我不再处理 500 个合并冲突,而是快速处理大约 10 个 - 只需不到一分钟的时间即可选择我想要保留的条目并删除不好的条目。
合并冲突的一个例子见附图
注意冲突的行是 '</data>' 与 '<data name="Axillary" xml:space="preserve">'。其中一个原因是并非每个项目都有注释 - 因此行条目在代码的不同分支之间会不匹配。另一个原因是合并工具将 resX 文件视为文本文件并逐行合并。
如果我在合并时不够小心,我可能会遇到一系列问题,导致 resX 文件损坏。
- 我遇到过多余的 '</data>' 标签的情况。
- 我也遇到过丢失 '</data>' 标签的情况。
- 另一个问题是,我遇到过注释被合并到错误条目的情况。
- 我丢失了整个条目。
- 我得到了条目的完整重复。
例如,在上面的例子中,如果我合并不正确,我可能会在条目之前有一个额外的 '</data>' 标签,并在之后缺少一个。如果我只选择两个条目中的一个,我也可能会丢失一个条目。我采取的策略是选择两个条目,结果就会出现重复条目。例如,“集成时间”可能在右侧的文件中,只是位置靠后,由于我正在合并两个来源的条目,因此我得到了两个“集成时间”条目。
自动冲突/合并的效果也好不到哪里去。我曾遇到过类似的错误,行条目不匹配。
对于较小的文件,有 20 几个冲突不是大问题。但对于我这样的大文件,每次合并分支进行构建时,我都会遇到 500 多个合并冲突。
在我遇到这个问题之前,我尝试过 Tom Clement 的解决方案(Solving the .resx Merge Problem),其中我让它在合并前对每个 resX 文件进行排序,然后让 KDiff3 进行实际合并。这使我的问题变得更糟。我从大约 500 个合并冲突增加到几乎整个文件都是合并冲突。
例如,假设第一个文件(排序后)的开头是
<data name="A" xml:space="preserve">
<value>The letter 'A'.</value>
</data>
<data name="B" xml:space="preserve">
<value>The letter 'B'.</value>
</data>
<!-- ...more entries... -->
现在第二个文件(排序后)是
<data name="A" xml:space="preserve">
<value>The letter 'A'.</value>
</data>
<data name="A1" xml:space="preserve">
<value>'A1' is a steak sauce.</value>
</data>
<data name="B" xml:space="preserve">
<value>The letter 'B'.</value>
</data>
<!-- ...more entries... -->
在上述情况下,以 A1 开头的所有条目及其下方都会成为合并冲突,需要我手动选择左文件、右文件或两者。同样,对于 20 几个条目,这不成问题。但是当你有大约 4,000 个条目并且还在增长时,每次合并分支时都要手动合并 12,000 行(每个条目有 3 到 4 行)是一个巨大的问题。
我的解决方案
- 我使用
ResXResourceReader
解析每个 ResX 文件。 - 一旦我加载了一个
ResXDataNode
,我就会检查它的键(小写)是否在我的排序冲突列表中(private SortedList<string, ResXConflictNode> mConflicts = new SortedList<string, ResXConflictNode>();
)。- ResXConflictNode 是一个简单的类
public ResXDataNode BaseNode; public ResXDataNode LocalNode; public ResXDataNode RemoteNode; public ResXConflictNode(ResXDataNode @base, ResXDataNode local, ResXDataNode remote) { BaseNode = @base; LocalNode = local; RemoteNode = remote; }
-
这个类用于存储 3 个节点:base、local 和 remote 的副本。
- ResXConflictNode 是一个简单的类
-
如果键不在冲突列表中,它会检查它是否存在于我的另一个排序列表中(
private SortedList<string, ResXSourceNode> mOutput = new SortedList<string, ResXSourceNode>();
)-
ResXSourceNode
也是一个简单的类public ResXSource Source; public ResXDataNode Node; public ResXSourceNode(ResXSource source, ResXDataNode node) { this.Source = source; this.Node = node; }
-
ResXSource
是一个简单的枚举,它具有 Flags() 属性和 3 个主要条目:base = 1,local = 2,remote = 4。 -
我使用 source 来跟踪节点是从哪里加载的。此外,只要它从一个源到下一个源完全相同,source 就可以设置为多个源。例如,“3”表示 base 和 local,但不表示 remote,而 7 表示该节点在所有 3 个文件中都相同。
-
-
如果节点不存在,我只是添加它。如果存在,我会确保键/名称、值和注释都相等(区分大小写)。如果不相等,我将从列表中删除该条目,并在另一个列表中开始一个新的冲突条目。
-
解析完所有三个文件后,我将排序后的冲突节点加载到显示的 DataGridView 中,然后是未冲突的节点。因此,您将始终首先看到冲突节点。
-
此外,非冲突节点在 DataGridView 中每条目只有一行。冲突节点最多有 3 行(每种来源 1 行)。用户可以决定保留哪个条目并删除其他冲突条目。
-
-
当用户点击“保存”时,DataGridView 将按键/名称对所有条目进行排序,然后使用
ResXResourceWriter
将每个条目写入输出文件。-
一个好处是,如果 DataGridView 中的条目无法转换为有效的
ResXDataNode
,它会抛出错误(并显示给用户)。这可能是由于重复的键/名称。或者条目包含无效字符。
-
基本上,我的工具不是尝试将每个 ResX 文件解析和合并为文本文件,而是将其作为 ResX 文件进行解析和合并。您无需担心多余或丢失的 </data> 标签,或者删除/重复的条目。
列表节点可能有一种更优化的处理方式 - 您可以根据需要进行调整/修改。
Using the Code
此工具的功能:合并 3 个 ResX 文件(base、local 和 remote)并将它们合并到一个输出 ResX 文件中。
此工具的功能:不合并其他任何东西。您仍然需要一个更健壮/通用的合并工具来处理您所有其他文件。
其他关键信息:我使用 GitHub,以及 GitExtensions(Visual Studio 插件)和 KDiff3 作为我的主要合并工具。此工具仅在此设置下进行了测试 - 任何其他设置可能无法立即使用。但是,源代码应该能为您指明方向。
我的使用方式
- 使用 Visual Studio 中的 GitExtensions,我将其配置为使用我的 ResX 合并工具而不是 KDiff3。我也没有将其设置为 difftool,只设置为我的主要合并工具。
- 我将我的 ResX 合并工具放在 KDiff3 的安装目录“C:\Program Files\KDiff3\”中。
- 该工具不智能也不可配置。它硬编码为查找同一文件夹中的 KDiff3.exe 文件。如果您想让它与不同的合并工具一起使用,则需要进行修改和重新编译。
- 当我运行 Visual Studio 中的合并时,它会启动我的合并工具。我的合并工具会检查它收到的命令行参数。它期望 5 个参数
- Base 文件的完整路径
- Local 文件的完整路径
- Remote 文件的完整路径
- "-o"
- 输出文件的相对路径
- 如果它没有收到 5 个参数,它会中止,并将命令行参数传递给 KDiff3.exe 并启动它。
- 它可能只有一个指向 base 文件和 local 文件,或 base 文件和 remote 文件的路径。无论哪种情况,该文件在 local 分支或 remote 分支中都不存在。在这种情况下,我们不需要进行文件内容比较 - 用户只需要决定保留或删除该文件。这可以由 KDiff3 处理。
- 如果输出文件的扩展名不是“resx”,它也会中止。同样,启动 KDiff3 并将命令行参数传递给它 - 如果它不是 ResX 文件,那么该工具就不关心它。
- 一旦通过了这些基本要求,它就会加载和解析这 3 个文件,然后填充一个可以直接编辑的 DataGridView。
- 可以添加新行
- 现有行可以被修改
- 行可以被删除
- 填充 DataGridView 后,它会按以下顺序对行进行排序:冲突在前,然后是仅在 base 文件中存在的条目(这通常意味着该行在分支中被删除),然后是仅在 local 或 remote 中存在的条目(应该是新项目),最后是存在于所有文件中的条目。
- 对于冲突,它比较键(区分大小写)、值和注释以确定一行是否不同。
- 对于仅 base 文件,有一个复选框(默认选中 - 但下次运行工具时它应该记住您是打开还是关闭它),指示从输出中排除仅 base 文件条目。
- 我将冲突解决保持简单:每个冲突将有 3 行:base、local 和 remote。您选择哪个是“正确”的,然后删除其他 2 行。
- 最后,我点击“保存”。如果保存成功,它会关闭,并且不会启动 KDiff3。
- 如果保存失败,它会显示异常错误消息。这时,您可以尝试纠正问题,或者点击“取消”按钮。一个常见的错误可能是键不唯一 - 这是由于没有删除冲突行集中的所有不需要的行。
- 如果我选择取消或关闭工具,它将默认为启动 KDiff3 并传递命令行参数。
一点小提示:我没有构建任何撤销/重做功能。您弄乱了并删除了一行或修改了一行,并且忘记了它原来的样子,您将需要重新开始(或构建您自己的撤销/重做功能)。不过,我包含了一个“重置”按钮。它只是清空 DataGridView 并用它开始时拥有的条目重新填充(所以基本上有一个“全部撤销”,但没有撤销/重做)。
总之,如果您在合并 ResX 文件作为文本时遇到麻烦,请下载源代码并试用。您可以通过使用上面 5 个参数从命令行启动它来手动测试 3 个 ResX 文件。
关注点
我不确定为什么,但 Git 将前 3 个路径(base、local 和 remote)作为完整/绝对路径传递,但对于输出路径,当它启动我的工具时,它会覆盖默认的 CurrentDirectory (System.IO.Directory.GetCurrentDirectory()),然后只为输出文件传递一个相对路径。
对我来说,将所有 4 个路径都作为绝对路径传递会更有意义。但没关系。它能正常工作。
42!
历史
- 2016 年 10 月 24 日:初始版本
- Deeksha Shenoy 为我纠正了一些问题。
- 我在背景部分添加了更多内容,试图更好地解释问题所在以及我的工具如何更好地工作。