我正在尝试对 IntelliJ IDEA 配置文件进行版本控制。这是一个小样本:
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<ignored path="tilde.iws" />
<ignored path=".idea/workspace.xml" />
<ignored path=".idea/dataSources.local.xml" />
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="TRACKING_ENABLED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ToolWindowManager">
<frame x="1201" y="380" width="958" height="1179" extended-state="0" />
<editor active="false" />
<layout>
<window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
<window_info id="Palette	" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
</layout>
</component>
</project>
/project/component[@name='ToolWindowManager']/layout/window_info
每次 IDE 保存配置时,某些元素似乎都会以任意顺序保存。同一类型的所有元素似乎总是以相同的顺序具有相同的属性。考虑到元素的顺序与 IDE 的功能无关,如果元素按元素名称排序,然后按属性值排序,并且属性和空格保留在原处。
<stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform">
<output method="xml" indent="yes" encoding="UTF-8"/>
<strip-space elements="*"/>
<template match="processing-instruction()|@*">
<copy>
<apply-templates select="node()|@*"/>
</copy>
</template>
<template match="*">
<copy>
<apply-templates select="@*"/>
<apply-templates>
<sort select="name()"/>
<sort select="@*[1]"/>
<sort select="@*[2]"/>
<sort select="@*[3]"/>
<sort select="@*[4]"/>
<sort select="@*[5]"/>
<sort select="@*[6]"/>
</apply-templates>
</copy>
</template>
</stylesheet>
已经差不多了,但是有一些问题:
- 它不排序每一个属性值(并且
@*
不起作用) - 它删除空元素末尾之前的空间(
<foo />
成为<foo/>
)。 - 它在 EOF 处添加了一个换行符(在我看来这不是一个错误,但使生成的文件与原始文件不太相似)。
答案1
我不确定规范 xml 排序的详细信息以及它是否与您所描述的相符,但是我建议xmllint
在将文件保存到源代码管理之前使用进行规范 xml 排序。如果您对此保持一致,那么您的版本控制应该非常干净且有用。您可以将下面的内容修改为脚本,如果您使用 git,则可以设置 agithook
来为您启动脚本。
$ xmllint --c14n originalConfig.xml > sortedConfig.xml
$ mv sortedConfig.xml originalConfig.xml
如果您使用的是 Linux 或 Mac,以上内容应该适合您。如果您使用的是 Windows,则可能需要安装 cygwin 之类的东西。
答案2
我会使用perl
和来解决它XML::Twig
。
perl 有一个sort
函数,允许您指定任意标准来比较一系列值。只要您的函数根据相对顺序返回正值、负值或零。
这就是神奇发生的地方 - 我们指定一个排序标准:
- 根据节点名称(标签)进行比较
- 然后根据属性是否存在进行比较
- 然后比较属性值。
它也需要在整个结构中递归地执行此操作以对子节点进行排序。
所以:
#!/usr/bin/env perl
use strict;
use warnings;
use XML::Twig;
my $xml = XML::Twig -> new -> parsefile ('sample.xml');
sub compare_elements {
## perl sort uses $a and $b to compare.
## in this case, it's nodes we expect;
#tag is the node name.
my $compare_by_tag = $a -> tag cmp $b -> tag;
#conditional return - this works because cmp returns zero
#if the values are the same.
return $compare_by_tag if $compare_by_tag;
#bit more complicated - extract all the attributes of both a and b, and then compare them sequentially:
#This is to handle case where you've got mismatched attributes.
#this may be irrelevant based on your input.
my %all_atts;
foreach my $key ( keys %{$a->atts}, keys %{$b->atts}) {
$all_atts{$key}++;
}
#iterate all the attributes we've seen - in either element.
foreach my $key_to_compare ( sort keys %all_atts ) {
#test if this attribute exists. If it doesn't in one, but does in the other, then that gets sorted to the top.
my $exists = ($a -> att($key_to_compare) ? 1 : 0) <=> ($b -> att($key_to_compare) ? 1 : 0);
return $exists if $exists;
#attribute exists in both - extract value, and compare them alphanumerically.
my $comparison = $a -> att($key_to_compare) cmp $b -> att($key_to_compare);
return $comparison if $comparison;
}
#we have fallen through all our comparisons, we therefore assume the nodes are the same and return zero.
return 0;
}
#recursive sort - traverses to the lowest node in the tree first, and then sorts that, before
#working back up.
sub sort_children {
my ( $node ) = @_;
foreach my $child ( $node -> children ) {
#sort this child if is has child nodes.
if ( $child -> children ) {
sort_children ( $child )
}
}
#iterate each of the child nodes of this one, sorting based on above criteria
foreach my $element ( sort { compare_elements } $node -> children ) {
#cut everything, then append to the end.
#because we've ordered these, then this will work as a reorder operation.
$element -> cut;
$element -> paste ( last_child => $node );
}
}
#set off recursive sort.
sort_children ( $xml -> root );
#set output formatting. indented_a implicitly sorts attributes.
$xml -> set_pretty_print ( 'indented_a');
$xml -> print;
给定你的输入,输出:
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<ignored path=".idea/dataSources.local.xml" />
<ignored path=".idea/workspace.xml" />
<ignored path="tilde.iws" />
<option
name="EXCLUDED_CONVERTED_TO_IGNORED"
value="true"
/>
<option
name="HIGHLIGHT_CONFLICTS"
value="true"
/>
<option
name="HIGHLIGHT_NON_ACTIVE_CHANGELIST"
value="false"
/>
<option
name="LAST_RESOLUTION"
value="IGNORE"
/>
<option
name="SHOW_DIALOG"
value="false"
/>
<option
name="TRACKING_ENABLED"
value="true"
/>
</component>
<component name="ToolWindowManager">
<editor active="false" />
<frame
extended-state="0"
height="1179"
width="958"
x="1201"
y="380"
/>
<layout>
<window_info
active="false"
anchor="bottom"
auto_hide="false"
content_ui="tabs"
id="TODO"
internal_type="DOCKED"
order="6"
show_stripe_button="true"
sideWeight="0.5"
side_tool="false"
type="DOCKED"
visible="false"
weight="0.33"
/>
<window_info
active="false"
anchor="left"
auto_hide="false"
content_ui="tabs"
id="Palette	"
internal_type="DOCKED"
order="2"
show_stripe_button="true"
sideWeight="0.5"
side_tool="false"
type="DOCKED"
visible="false"
weight="0.33"
/>
</layout>
</component>
</project>
无论各个子节点的顺序如何。
我个人喜欢,indented_a
因为它将属性包装到新行中,而且我认为这更清晰。但indented
输出格式可以起到同样的作用。