2

我需要从 C# 中的 XML 文件中获取信息。

这是来自 XML 的一些片段。

<Surface id="su-62" surfaceType="InteriorWall">
    <Name>S-3-7-I-W-62</Name>
    <AdjacentSpaceId spaceIdRef="sp-3-TUIN">
    </AdjacentSpaceId>
    <AdjacentSpaceId spaceIdRef="sp-7-huizen">
    </AdjacentSpaceId>
    <CADObjectId>Basic Wall: _omgevingsmuur [184610]</CADObjectId>
</Surface>
...
<Surface id="su-63" surfaceType="ExteriorWall">
    <Name>N-4-E-W-63</Name>
    <AdjacentSpaceId spaceIdRef="sp-4-onthaal">
    </AdjacentSpaceId>
    <Opening id="su-63-op-1" openingType="NonSlidingDoor">
    </Opening>
    <CADObjectId>Basic Wall: _detentiemuur [193431]</CADObjectId>
</Surface>  
...
<Surface id="su-282" surfaceType="Shade">
    <Name>X-S-282</Name>
    <CADObjectId>Basic Roof: Generic - 400mm [190976]</CADObjectId>
</Surface>

如您所见,有些表面没有其他表面的所有信息。我必须知道哪些表面与哪个空间相邻,以及是否有开口。

(最终目标是制作一个 2d 阵列,您可以在其中看到哪个 SPACE 与哪个 SPACE 相邻,另一个阵列可以查看是否有连接的开口。)

4

1 回答 1

0

假设您拥有 .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 个属性:SurfaceIdSurfaceNameAdjacentSpace。我将解释其中一项作业,它应该让你理解所有这些:

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().

于 2012-11-14T10:30:25.327 回答