如何获取包含嵌套元素的 XML 文件并从中获取一组 C# 类
本文关键字:获取 一组 包含 何获取 嵌套 元素 XML 文件 | 更新日期: 2023-09-27 18:20:13
首先,我在XML方面不是很有经验。我知道阅读和写入它的基本知识,但在大多数情况下,像模式这样的东西开始让我的眼睛很快交叉。如果看起来我对 XML 的工作方式做出了不正确的假设,那么我很有可能是。
撇开免责声明不谈,这是我多次遇到但找不到令人满意的解决方案的问题。我有一个定义数据的XML,包括嵌套条目(举个例子,一个文件可能有一个"Power"元素,该元素有一个"AlternatePowers"的子节点,而"AlternatePowers"又包含"Power"元素(。理想情况下,我希望能够从此 XML 文件快速生成一组类来存储我正在读取的数据。我看到的一般解决方案是使用 Microsoft 的 XSD.exe 工具从 XML 文件生成 XSD 文件,然后使用相同的工具将架构转换为类。问题是,如果有嵌套元素,工具就会阻塞。例:
- A column named 'Power' already belongs to this DataTable: cannot set
a nested table name to the same name.
有没有一种很好的简单方法可以做到这一点?我在这里搜索了几次类似的问题,但我发现处理使用具有相同名称的嵌套元素生成模式的唯一问题没有答案。
或者,我也有可能完全误解了 XML 和 XSD 的工作原理,并且不可能有这样的嵌套......
更新
例如,我想解析的一件事是特定字符生成器程序的 XML 输出。公平的警告,尽管我删除了除权力部分以外的任何内容,但这有点罗嗦。
<?xml version="1.0" encoding="ISO-8859-1"?>
<document>
<product name="Hero Lab" url="http://www.wolflair.com" versionmajor="3" versionminor="7" versionpatch=" " versionbuild="256">Hero Lab® and the Hero Lab logo are Registered Trademarks of LWD Technology, Inc. Free download at http://www.wolflair.com
Mutants & Masterminds, Second Edition is ©2005-2011 Green Ronin Publishing, LLC. All rights reserved.</product>
<hero active="yes" name="Pretty Deadly" playername="">
<size name="Medium"/>
<powers>
<power name="Enhanced Trait 16" info="" ranks="16" cost="16" range="" displaylevel="0" summary="Traits: Constitution +6 (18, +4), Dexterity +8 (20, +5), Charisma +2 (12, +1)" active="yes">
<powerdesc>You have an enhancement to a non-effect trait, such as an ability (including saving throws) or skill (including attack or defense bonus). Since Toughness save cannot be increased on its own,use the Protection effect instead of Enhanced Toughness (see Protection later in this chapter).</powerdesc>
<descriptors/>
<elements/>
<options/>
<traitmods>
<traitmod name="Constitution" bonus="+6"/>
<traitmod name="Dexterity" bonus="+8"/>
<traitmod name="Charisma" bonus="+2"/>
</traitmods>
<flaws/>
<powerfeats/>
<powerdrawbacks/>
<usernotes/>
<alternatepowers/>
<chainedpowers/>
<otherpowers/>
</power>
<power name="Sailor Suit (Device 2)" info="" ranks="2" cost="8" range="" displaylevel="0" summary="Hard to lose" active="yes">
<powerdesc>A device that has one or more powers and can be equipped and un-equipped.</powerdesc>
<descriptors/>
<elements/>
<options/>
<traitmods/>
<flaws/>
<powerfeats/>
<powerdrawbacks/>
<usernotes/>
<alternatepowers/>
<chainedpowers/>
<otherpowers>
<power name="Protection 6" info="+6 Toughness" ranks="6" cost="10" range="" displaylevel="1" summary="+6 Toughness; Impervious [4 ranks only]" active="yes">
<powerdesc>You're particularly resistant to harm. You gain a bonus on your Toughness saving throws equal to your Protection rank.</powerdesc>
<descriptors/>
<elements/>
<options/>
<traitmods/>
<extras>
<extra name="Impervious" info="" partialranks="2">Your Protection stops some damage completely. If an attack has a damage bonus less than your Protection rank, it inflicts no damage (you automatically succeed on your Toughness saving throw). Penetrating damage (see page 112) ignores this modifier; you must save against it normally.</extra>
</extras>
<flaws/>
<powerfeats/>
<powerdrawbacks/>
<usernotes/>
<alternatepowers/>
<chainedpowers/>
<otherpowers/>
</power>
</otherpowers>
</power>
</powers>
</hero>
</document>
是的,那里有许多不必要的标签,但这是我希望能够插入并获得合理内容的 XML 类型的示例。此 XML 在发送到 XSD 时,将生成以下错误:
- A column named 'traitmods' already belongs to this DataTable: cannot set
a nested table name to the same name.
我刚刚帮助完某人。 尝试在此处阅读此线程:https://stackoverflow.com/a/8840309/353147
从你的例子和我的链接来看,你会有这样的类。
public class Power
{
XElement self;
public Power(XElement power) { self = power; }
public AlternatePowers AlternatePowers
{ get { return new AlternatePowers(self.Element("AlternatePowers")); } }
}
public class AlternatePowers
{
XElement self;
public AlternatePowers(XElement power) { self = power; }
public Power2[] Powers
{
get
{
return self.Elements("Power").Select(e => new Power2(e)).ToArray();
}
}
}
public class Power2
{
XElement self;
public Power2(XElement power) { self = power; }
}
在不知道 xml 的其余部分的情况下,我无法创建构成每个类/节点级别的属性,但您应该从此处和链接中获取要点。
然后,您可以像这样引用它:
Power power = new Power(XElement.Load("file"));
foreach(Power2 power2 in power.AlternatePowers.Powers)
{
...
}
错误消息暗示您正在尝试从架构(/d
开关(生成DataSet
,而不是一组使用 XML 序列化程序属性(/c
开关(修饰的任意类。
我自己没有尝试过生成这样的DataSet
,但我可以看到它是如何失败的。DataSet
是DataTable
的集合,而又包含DataRow
的集合。这是一个固定的 3 级层次结构。如果您的 XML 架构深度大于或小于 3 级,则它不适合所需的结构。尝试在设计器中创建测试DataSet
并检查生成的.xsd
文件;这将向您展示适合哪种架构结构。
我可以从个人经验中向您保证,如果您将模式转换为一组任意类,那么它将处理您愿意抛出的几乎所有模式结构。
所以,它并不漂亮,但以下是我最终提出的解决方案。我在基节点上运行 processElement,然后遍历现有元素并导出类代码。
namespace XMLToClasses
{
public class Element
{
public string Name;
public HashSet<string> attributes;
public HashSet<string> children;
public bool hasText;
public Element()
{
Name = "";
attributes = new HashSet<string>();
children = new HashSet<string>();
hasText = false;
}
public string getSource()
{
StringBuilder sourceSB = new StringBuilder();
sourceSB.AppendLine("[Serializable()]");
sourceSB.AppendLine("public class cls_" + Name);
sourceSB.AppendLine("{");
sourceSB.AppendLine("'t// Attributes" );
if (hasText)
{
sourceSB.AppendLine("'tstring InnerText;");
}
foreach(string attribute in attributes)
{
sourceSB.AppendLine("'tpublic string atr_" + attribute + ";");
}
sourceSB.AppendLine("");
sourceSB.AppendLine("'t// Children");
foreach (string child in children)
{
sourceSB.AppendLine("'tpublic List<cls_" + child + "> list" + child + ";");
}
sourceSB.AppendLine("");
sourceSB.AppendLine("'t// Constructor");
sourceSB.AppendLine("'tpublic cls_" + Name + "()");
sourceSB.AppendLine("'t{");
foreach (string child in children)
{
sourceSB.AppendLine("'t'tlist" + child + " = new List<cls_" + child + ">()" + ";");
}
sourceSB.AppendLine("'t}");
sourceSB.AppendLine("");
sourceSB.AppendLine("'tpublic cls_" + Name + "(XmlNode xmlNode) : this ()");
sourceSB.AppendLine("'t{");
if (hasText)
{
sourceSB.AppendLine("'t't'tInnerText = xmlNode.InnerText;");
sourceSB.AppendLine("");
}
foreach (string attribute in attributes)
{
sourceSB.AppendLine("'t'tif (xmlNode.Attributes['"" + attribute + "'"] != null)");
sourceSB.AppendLine("'t't{");
sourceSB.AppendLine("'t't'tatr_" + attribute + " = xmlNode.Attributes['"" + attribute + "'"].Value;");
sourceSB.AppendLine("'t't}");
}
sourceSB.AppendLine("");
foreach (string child in children)
{
sourceSB.AppendLine("'t'tforeach (XmlNode childNode in xmlNode.SelectNodes('"./" + child + "'"))");
sourceSB.AppendLine("'t't{");
sourceSB.AppendLine("'t't'tlist" + child + ".Add(new cls_" + child + "(childNode));");
sourceSB.AppendLine("'t't}");
}
sourceSB.AppendLine("'t}");
sourceSB.Append("}");
return sourceSB.ToString();
}
}
public class XMLToClasses
{
public Hashtable extantElements;
public XMLToClasses()
{
extantElements = new Hashtable();
}
public Element processElement(XmlNode xmlNode)
{
Element element;
if (extantElements.Contains(xmlNode.Name))
{
element = (Element)extantElements[xmlNode.Name];
}
else
{
element = new Element();
element.Name = xmlNode.Name;
extantElements.Add(element.Name, element);
}
if (xmlNode.Attributes != null)
{
foreach (XmlAttribute attribute in xmlNode.Attributes)
{
if (!element.attributes.Contains(attribute.Name))
{
element.attributes.Add(attribute.Name);
}
}
}
if (xmlNode.ChildNodes != null)
{
foreach (XmlNode node in xmlNode.ChildNodes)
{
if (node.Name == "#text")
{
element.hasText = true;
}
else
{
Element childNode = processElement(node);
if (!element.children.Contains(childNode.Name))
{
element.children.Add(childNode.Name);
}
}
}
}
return element;
}
}
}
我确信有办法让它看起来更漂亮或效果更好,但这对我来说已经足够了。
编辑:并添加了丑陋但功能强大的反序列化代码,以获取包含对象的XMLNode并对其进行解码。
后来的想法:两年后,我有机会重用这段代码。我不仅没有在这里保持最新状态(我进行了更改以更好地规范项目的名称(,而且我认为评论者说我以错误的方式这样做是对的。我仍然认为这可能是为 XML 文件生成模板类的一种便捷方式,其中给定类型的元素可以以不同的深度显示,但它不灵活(您必须每次重新运行代码并重新提取类(并且不能很好地处理版本控制的更改(从我第一次创建此代码以允许我快速创建字符文件转换器到现在, 格式改变了,所以我有人抱怨它停止工作。回想起来,使用 XPaths 搜索正确的元素,然后从那里提取数据会更有意义(。
尽管如此,这是一次宝贵的经验,我怀疑我可能会时不时地回到这段代码中,以便快速粗略地处理 XML 数据,至少在我找到更好的数据之前。