假设您拥有 .NET 3.5 或更高版本,那么这对于LINQ2XML来说是一个很好的用途。
您可以编写一个查询,该查询将获取相关区域并确定哪些空间彼此相邻。
// Load your XML File into an XDocument object
var xDoc = XDocument.Load(xmlPath);
// this is your query, in the end result with my a Dictionary with the Surface
// Id attribute as the key and the AdjacentSpaceId as the value
var result = (from x in xDoc.Descendants("AdjacentSpaceId")
select new
{
SurfaceId = (String)x.Parent.Attribute("id"),
SurfaceName = (String)x.Parent.Element("Name"),
AdjacentSpace = (String)x
}).GroupBy(sp => sp.SurfaceId)
.ToDictionary(grp => grp.Key,
grp => grp.Select(value => value.AdjacentSpace));
如果您不熟悉任何 LINQ 变体,我将尝试更详细地解释该查询。包含查询结果的本地字段被称为results
. 该var
类型是另一个 .NET 3.5 添加的内容,它告诉编译器在表达式的右侧识别类型。在这种情况下,result
将是一个Dictionary<String, IEnumerable<String>>
.
在查询的第一行:
from x in xDoc.Descendants("AdjacentSpaceId")
您基本上是在告诉 LINQ 遍历名为AdjacentSpaceId
. 这些节点的位置或父节点的名称无关紧要。由于这个原因,LINQ 中的Descendants()
方法可能非常强大,因为这意味着您不需要知道特定节点的确切 XML 路径来选择它,您只需要名称(请参阅最后的注释)
查询的下一行准确定义了您希望从查询中返回的内容:
select new
{
SurfaceId = (String)x.Parent.Attribute("id"),
SurfaceName = (String)x.Parent.Element("Name"),
AdjacentSpace = (String)x
})
所有使用这种语法的 LINQ 查询都必须有一个select
语句(还有一种方法语法可以代替或另外使用,它也不需要 select,但我认为这种语法更容易学习)。
在 select 子句中,您基本上是在定义一个新的匿名类型,它具有 3 个属性:SurfaceId
、SurfaceName
和AdjacentSpace
。我将解释其中一项作业,它应该让你理解所有这些:
SurfaceId = (String)x.Parent.Attribute("id")
在此行中,x
指的是您迭代所有AdjacentSpaceId
节点的 LINQ 查询的初始行,因此x
类型为XElement
.
的Parent
属性x
将选择父节点,在本例中为Surface
。(注意:如果您的 XML 中的根节点恰好被调用AdjacentSpaceId
,那么这一行将引发异常,因为根节点的父节点将为空,但似乎是一个安全的假设,它不会成为问题在这种情况下)。上的Parent
属性XElement
也将是另一个XElement
。
下一部分是 的Attribute
方法XElement
,您正在选择节点的第一个属性,称为id
。
因此,您正在id
为每个节点选择所有父节点的属性AdjacentSpaceId
。
最后,我将其转换为字符串。有一个Value
可以代替使用的属性,但我个人的偏好是强制转换为字符串,因为如果Attribute("id")
方法失败(如果没有名为“id”的属性可能会发生这种情况),调用该Value
属性将引发异常,而强制转换在这些情况下,字符串只会返回 null 。
此 select 子句的其他部分几乎相同。
该查询的其余部分实际上是单独的查询。我只是把它们拴在一起,但它们很容易被拆开并自己穿上。
下一段:
GroupBy(sp => sp.SurfaceId)
SurfaceId
正在按属性对 LINQ 查询的结果进行分组。这是一个关键步骤,因为听起来您想知道每个表面与空间相邻的位置,这将有效地将所有相邻空间按表面组合在一起。
如果您不熟悉,sp => sp.SurfaceId
这是一个用于快速轻松地创建匿名函数或委托的Lambda 表达式。
最后一部分将把你的分组结果转换成更有用的东西,通常是 a Dictionary<>
,或者在这种情况下是 aDictionary<String, IEnumerable<String>>
ToDictionary(grp => grp.Key,
grp => grp.Select(value => value.AdjacentSpace));
只是关于 Linq-to-Xml 对象的扩展方法的一个侧面说明Descendants()
,虽然正如我上面提到的它可能非常有用,但它也可能非常危险。当您在同一个 XML 中有多个具有相同名称但用途不同的节点或在树的不同父级中时,Descendants()
将返回 ALL 对象。如果你只想返回一些匹配特定名称的节点,你需要先使用Element()
orElements()
扩展方法过滤它,然后选择合适的父节点,然后你可以安全地调用Descendants()
.