XML 字母排序器






4.53/5 (12投票s)
这个项目允许用户对任何XML文件进行字母排序,无需使用XSLT转换文件,也无需以任何方式修改文件,只需根据节点名称本身或任何节点的属性对节点进行排序。
引言
此项目允许用户对任何 XML 文件进行字母排序,无需使用 XSLT 转换文件。
我搜索了相当长的时间来寻找一种按字母顺序对 XML 文档进行排序的方法,我得到的大多数结果都基于过于复杂的 XSLT 转换。我使用的一些用于对 XML 文档进行排序的模板对拼图的一部分有效,但在花费大量时间后,我决定使用 Linq 和XDocument
重新开始。
我最终创建了一个解决方案,可以根据 XML 节点的名称、XML 节点的任何属性以及 XML 文档中的特定深度对任何 XML 文档进行排序。此实用程序还可以按字母顺序(升序或降序)从左到右对 XML 节点的所有属性进行排序。
示例
原始 XML - 未排序
<Collection>
<CollectionB>
<B2 Text="1" Stat="5"></B2>
<A2 Text="2" Type="3" Stat="7"></A2>
<A1></A1>
<B1 typeName="2">
<B22></B22>
<A22></A22>
<A12></A12>
</B1>
<B1 typeName="1"></B1>
</CollectionB>
<CollectionA>
<B2 Echo="1" Bravo="1" Tango="1" Alpha="1"></B2>
<A2></A2>
<A1></A1>
<B1></B1>
</CollectionA>
</Collection>
默认排序
<Collection>
<CollectionA>
<A1 />
<A2 />
<B1 />
<B2 Echo="1" Bravo="1" Tango="1" Alpha="1" />
</CollectionA>
<CollectionB>
<A1 />
<A2 Text="2" Type="3" Stat="7" />
<B1 typeName="2">
<A12 />
<A22 />
<B22 />
</B1>
<B1 typeName="1" />
<B2 Text="1" Stat="5" />
</CollectionB>
</Collection>
按“文本”属性排序
<Collection>
<CollectionA>
<A1 />
<A2 />
<B1 />
<B2 Echo="1" Bravo="1" Tango="1" Alpha="1" />
</CollectionA>
<CollectionB>
<B2 Text="1" Stat="5" />
<A2 Text="2" Type="3" Stat="7" />
<A1 />
<B1 typeName="2">
<A12 />
<A22 />
<B22 />
</B1>
<B1 typeName="1" />
</CollectionB>
</Collection>
按 XML 树中第 2 级以下的“文本”属性排序
<Collection>
<CollectionB>
<B2 Text="1" Stat="5" />
<A2 Text="2" Type="3" Stat="7" />
<A1 />
<B1 typeName="2">
<A12 />
<A22 />
<B22 />
</B1>
<B1 typeName="1" />
</CollectionB>
<CollectionA>
<B2 Echo="1" Bravo="1" Tango="1" Alpha="1" />
<A2 />
<A1 />
<B1 />
</CollectionA>
</Collection>
默认排序 - 按属性升序排序
<Collection>
<CollectionB>
<B2 Stat="5" Text="1" />
<A2 Stat="7" Text="2" Type="3" />
<A1 />
<B1 typeName="2">
<A12 />
<A22 />
<B22 />
</B1>
<B1 typeName="1" />
</CollectionB>
<CollectionA>
<B2 Alpha="1" Bravo="1" Echo="1" Tango="1" />
<A2 />
<A1 />
<B1 />
</CollectionA>
</Collection>
背景
Lambda 表达式
我在这段代码中大量依赖 Lambda 表达式来减少代码行数并以最有效的方式使用 Linq。与使用委托相比,使用 Lambda 表达式可以实现更简洁的代码。以下是一个 Lambda 表达式的快速示例,让您在深入研究代码之前了解其语法。
在此示例中,让我们来看一个Customer
对象的强类型集合。
List<Customer> myCustomers;
一个Customer
具有一个公共属性FirstName
。
一旦此集合填充了Customer
对象,我们就可以根据每个Customer.FirstName
对该集合进行排序。
list = list.OrderBy(x => x.FirstName).toList(); --Sort ascending
list = list.OrderByDescending(x => x.FirstName).toList(); --Sort descending
在上方的语法中,“x
”代表一个Customer
对象。您可以使用任何您选择的变量名。
提示:当您键入 Lambda 表达式并到达在“x”之后键入“.”的语法部分时,您应该会获得代码完成提示,并能够从该对象的任何可见属性中进行选择。
条件运算符
该orderby
子句中的条件运算符。我找不到其他支持的执行内联排序并同时应用逻辑的方法。在此示例中,我使用了一个两级条件语句来验证当前子节点是否具有所选级别以下的子节点,并验证当前子节点是否具有有效的属性;如果满足这些条件,则按我们在 UI 中指定的名称的属性值进行排序。
当尝试仅在特定条件为true
时将一个变量赋值给另一个变量,如果不是,则将其赋值给另一个值时,此运算符非常有用。该运算符使用 ? 和 :,基本上是 (如果为真) ? 那么 : 否则。
普通的“if
”语法
if (myValue == "something")
{
otherValue = myValue;
}
else
{
otherValue = "defaultValue";
}
使用条件运算符的简化语法
otherValue = (myValue == "something") ? myValue : "defaultValue";
使用该实用工具
在此示例(图 1)中,我加载 XML 文件“Test.xml”,从节点级别 2 开始排序,按属性“Text
”排序,并将所有 XML 属性按字母顺序升序从左到右排序。
所有结果都保存到工作目录中的名为“result.xml”的文件中(每次运行都覆盖),并在表单窗口底部的TextArea
中显示。
注意:如果未为源 XML 文件指定完整路径,我会在工作目录中查找,如果文件名不存在则附加“.xml”。
Using the Code
此实用程序的核心利用XDocument
Linq 对象加载所有 XML、对其进行操作,然后按请求的顺序输出。
XDocument doc = XDocument.Load(sourceDoc);
//Add these two hard-coded values as options from within the UI
//so we can apply it to any XML file.
XDocument sortedDoc = Sort(doc, level, attribute, sortAttributes);
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
settings.Indent = true;
using (XmlWriter writer = XmlWriter.Create(resultDoc, settings))
{
sortedDoc.WriteTo(writer);
}
要对 XML 树执行排序,我们使用 Linq 查询 XML 对象的集合,根据从 UI 中选择的选项应用“orderby
”子句,然后递归调用所有找到的节点的Sort
方法。
如果我们选择对所有 XML 节点的属性进行排序,那么我们将进入switch
语句并对与该特定 XML 节点关联的XAttribute
对象的集合应用排序。
#region Private Method - Sort
/// <summary>
/// Sort an XML Element based on a minimum level to perform the
/// sort from and either based on the value
/// of an attribute of an XML Element or by the name of the XML Element.
/// </summary>
/// <param name="file">File to load and sort</param>
/// <param name="level">Minimum level to apply the sort from.
/// 0 for root level.</param>
/// <param name="attribute">Name of the attribute to sort by.
/// "" for no sort</param>
/// <param name="sortAttributes">Sort attributes none,
/// ascending or descending for all sorted XML nodes</param>
/// <returns>Sorted XElement based on the criteria passed in.</returns>
private static XDocument Sort(XDocument file,
int level, string attribute, int sortAttributes)
{
return new XDocument(Sort(file.Root, level, attribute, sortAttributes));
}
/// <summary>
/// Sort an XML Element based on a minimum level to perform the
/// sort from and either based on the value
/// of an attribute of an XML Element or by the name of the XML Element.
/// </summary>
/// <param name="element">Element to sort</param>
/// <param name="level">Minimum level to apply the sort from.
/// 0 for root level.</param>
/// <param name="attribute">Name of the attribute to sort by.
/// "" for no sort</param>
/// <param name="sortAttributes">Sort attributes none,
/// ascending or descending for all sorted XML nodes</param>
/// <returns>Sorted XElement based on the criteria passed in.</returns>
private static XElement Sort(XElement element,
int level, string attribute, int sortAttributes)
{
XElement newElement = new XElement(element.Name,
from child in element.Elements()
orderby
(child.Ancestors().Count() > level)
? (
(child.HasAttributes &&
!string.IsNullOrEmpty(attribute)
&& child.Attribute(attribute) != null)
? child.Attribute(attribute).
Value.ToString()
: child.Name.ToString()
)
: "" //End of the orderby clause
select Sort(child, level, attribute, sortAttributes));
if (element.HasAttributes)
{
switch (sortAttributes)
{
case 0: //None
foreach (XAttribute attrib in element.Attributes())
{
newElement.SetAttributeValue
(attrib.Name, attrib.Value);
}
break;
case 1: //Ascending
foreach (XAttribute attrib in element.Attributes().
OrderBy(a => a.Name.ToString()))
{
newElement.SetAttributeValue
(attrib.Name, attrib.Value);
}
break;
case 2: //Descending
foreach (XAttribute attrib in element.Attributes().
OrderByDescending(a => a.Name.ToString()))
{
newElement.SetAttributeValue
(attrib.Name, attrib.Value);
}
break;
default:
break;
}
}
return newElement;
}
#endregion
关注点
这是我第一次需要使用 Linq 查询,在网上找到一个好例子后,我能够成功地使用它来迭代整个集合,对对象执行orderby
,然后根据返回的每个项目执行操作。
我学到的一件事是,如果需要在 Linq 查询的orderby
子句中添加一些逻辑,则必须使用
XElement newElement = new XElement(element.Name,
from child in element.Elements()
orderby
(child.Ancestors().Count() > level)
? (
(child.HasAttributes &&
!string.IsNullOrEmpty(attribute) &&
child.Attribute(attribute) != null)
? child.Attribute(attribute).Value.ToString()
: child.Name.ToString()
)
: "" //End of the orderby clause
select Sort(child, level, attribute, sortAttributes));
历史
- 版本 1.0 - 初始提交