【问题标题】:Parsing XML into a list将 XML 解析为列表
【发布时间】:2014-12-25 04:53:37
【问题描述】:

我有一个非常精细的 XML,我已经能够解析其中的大部分内容,但是我遇到了一棵让我难过的树,我担心我会变得更加困难。 这是我指的 XML。

<Codes>
            <CustomFieldValueSet name="Account" label="Account" distributionType="PercentOfPrice">
                <CustomFieldValue distributionValue="10.00" splitindex="0">
                    <Value>7200</Value>
                    <Description>General Supplies</Description>
                </CustomFieldValue>
                <CustomFieldValue distributionValue="45.00" splitindex="1">
                    <Value>7200</Value>
                    <Description>General Supplies</Description>
                </CustomFieldValue>
                <CustomFieldValue distributionValue="45.00" splitindex="2">
                    <Value>7200</Value>
                    <Description>General Supplies</Description>
                </CustomFieldValue>
            </CustomFieldValueSet>
            <CustomFieldValueSet name="Activity" label="Activity" distributionType="PercentOfPrice" />
            <CustomFieldValueSet name="Chart" label="Chart" distributionType="PercentOfPrice">
                <CustomFieldValue distributionValue="10.00" splitindex="0">
                    <Value>T</Value>
                    <Description>University</Description>
                </CustomFieldValue>
                <CustomFieldValue distributionValue="45.00" splitindex="1">
                    <Value>T</Value>
                    <Description>University</Description>
                </CustomFieldValue>
                <CustomFieldValue distributionValue="45.00" splitindex="2">
                    <Value>T</Value>
                    <Description>University</Description>
                </CustomFieldValue>
            </CustomFieldValueSet>
            <CustomFieldValueSet name="Fund" label="Fund" distributionType="PercentOfPrice">
                <CustomFieldValue distributionValue="10.00" splitindex="0">
                    <Value>360806</Value>
                    <Description>National Institutes of Health</Description>
                </CustomFieldValue>
                <CustomFieldValue distributionValue="45.00" splitindex="1">
                    <Value>360903</Value>
                    <Description>National  Institutes of Health</Description>
                </CustomFieldValue>
                <CustomFieldValue distributionValue="45.00" splitindex="2">
                    <Value>360957</Value>
                    <Description>National Institutes of Health</Description>
                </CustomFieldValue>
            </CustomFieldValueSet>
            <CustomFieldValueSet name="Program" label="Program" distributionType="PercentOfPrice">
                <CustomFieldValue distributionValue="10.00" splitindex="0">
                    <Value>02</Value>
                    <Description>Research</Description>
                </CustomFieldValue>
                <CustomFieldValue distributionValue="45.00" splitindex="1">
                    <Value>02</Value>
                    <Description>Research</Description>
                </CustomFieldValue>
                <CustomFieldValue distributionValue="45.00" splitindex="2">
                    <Value>02</Value>
                    <Description>Research</Description>
                </CustomFieldValue>
            </CustomFieldValueSet>
            <CustomFieldValueSet name="Location" label="Location" distributionType="PercentOfPrice">
                <CustomFieldValue distributionValue="10.00" splitindex="0">
                    <Value>015</Value>
                    <Description>Biology - Life Science</Description>
                </CustomFieldValue>
                <CustomFieldValue distributionValue="45.00" splitindex="1">
                    <Value>015</Value>
                    <Description>Biology - Life Science</Description>
                </CustomFieldValue>
                <CustomFieldValue distributionValue="45.00" splitindex="2">
                    <Value>015</Value>
                    <Description>Biology - Life Science</Description>
                </CustomFieldValue>
            </CustomFieldValueSet>
            <CustomFieldValueSet name="Organization" label="Organization" distributionType="PercentOfPrice">
                <CustomFieldValue distributionValue="10.00" splitindex="0">
                    <Value>04400</Value>
                    <Description>TUSM:Neuroscience</Description>
                </CustomFieldValue>
                <CustomFieldValue distributionValue="45.00" splitindex="1">
                    <Value>04400</Value>
                    <Description>TUSM:Neuroscience</Description>
                </CustomFieldValue>
                <CustomFieldValue distributionValue="45.00" splitindex="2">
                    <Value>04400</Value>
                    <Description>TUSM:Neuroscience</Description>
                </CustomFieldValue>
            </CustomFieldValueSet>
        </Codes>

我试图最终得到一个看起来像这样的列表。

Account distributionType   Activity   distributionValue  Fund
7200     PercentOfPrice     ""        10                 360806
7200     PercentOfPrice     ""        45                 360903
7200     PercentOfPrice     ""        45                 360957

等等……

我编写的代码看起来像这样。这是一个sn-p。请注意,我想我把这件事复杂化了。

if (tagName == "Codes")
                                {
                                  // Create another reader that contains just the accounting elements.
                                    XmlReader inner = reader.ReadSubtree();
                                    //inner.ReadToDescendant("Codes");
                                    //printOutXML(inner);
                                    while (inner.Read())
                                    {
                                        switch (inner.NodeType)
                                        {       
                                            //walk down the xml hiearchy then simply  fill in the values.
                                            case XmlNodeType.Element:

                                                switch (reader.Name)
                                                {
                                                    case "CustomFieldValueSet":
                                                       //get the attribute that we are currently working with such as account and  
                                                        innerTagName=inner.GetAttribute("name");

                                                        // activity and location can potentially be blank therefore i will check here if it is 
                                                        //and if it is i will immediate assign the activity list a set of empty quotes.
                                                        if (innerTagName == "Activity")
                                                        {
                                                            if (inner.IsEmptyElement)
                                                            {   //quickly put fillers in .
                                                                for (int i = 0; i < thisInvoice.account.Count; i++)
                                                                {
                                                                    thisInvoice.activity.Add("");
                                                                }
                                                            }         
                                                        }

                                                        if (innerTagName == "Location")
                                                        {
                                                            if (inner.IsEmptyElement)
                                                            {   //quickly put fillers in .
                                                                for (int i = 0; i < thisInvoice.account.Count; i++)
                                                                {
                                                                    thisInvoice.location.Add("");
                                                                }
                                                                //thisInvoice.activity.Add("");
                                                            }
                                                        }

                                                        if (null == inner.GetAttribute("distributionType"))
                                                        {
                                                            distType = null;
                                                        }
                                                       else if
                                                       (distributionSwitch == false)
                                                        {
                                                            thisInvoice.distributionType.Add(inner.GetAttribute("distributionType") ?? "");
                                                            distType = inner.GetAttribute("distributionType") ?? "";
                                                       }
                                                        //Console.WriteLine(inner.Value);
                                                        //Console.WriteLine(inner.Name);
                                                        break;

                                                    case "CustomFieldValue":
                                                        if(null == inner.GetAttribute("distributionValue"))
                                                        //thisInvoice.distributionValue.Add(inner.GetAttribute("distributionValue") ?? "");
                                                        {/*do nothing*/}
                                                    else if
                                                        (distributionSwitch == false)
                                                        {
                                                            thisInvoice.distributionValue.Add(inner.GetAttribute("distributionValue") ?? "");
                                                        }
                                                        //check the length of the current distribution  if the lenght is less than the curren distribution value
                                                       // then we must then add the values to the new location.
                                                        if (thisInvoice.distributionValue.Count > thisInvoice.distributionType.Count)
                                                        {
                                                            for (int i = 0; i < thisInvoice.distributionValue.Count - thisInvoice.distributionType.Count; i++)
                                                            {
                                                                thisInvoice.distributionType.Add(distType);
                                                            }



                                                        }

                                                        break;

                                                    case "Value":
                                                         // XmlNodeType.Text
                                                        if (innerTagName == "Account"/*&& inner.NodeType ==XmlNodeType.Text*/)
                                                        {
                                                            inner.MoveToContent();// move to the text 
                                                            inner.Read();
                                                            thisInvoice.account.Add(inner.Value);
                                                        }


                                                        if (innerTagName == "Activity")
                                                        {
                                                            // activitiy is not a mandartory field so it could be empty therefore we need 
                                                            // to check if its  a self closing tag and if it is then we need to assign and 
                                                            if (inner.IsEmptyElement)
                                                            {
                                                                thisInvoice.activity.Add("");
                                                            }
                                                            else
                                                            {
                                                                inner.MoveToContent();// move to the text 
                                                                inner.Read();
                                                                thisInvoice.activity.Add(inner.Value);
                                                            }
                                                        }

                                                        if (innerTagName == "Location")
                                                        {
                                                            if (inner.IsEmptyElement)
                                                            {
                                                                thisInvoice.location.Add("");
                                                            }
                                                            else
                                                            {
                                                                inner.MoveToContent();// move to the text 
                                                                inner.Read();
                                                                thisInvoice.location.Add(inner.Value);
                                                            }
                                                        }

                                                        if (innerTagName == "Fund")
                                                        {
                                                            inner.MoveToContent();// move to the text 
                                                            inner.Read();
                                                            thisInvoice.fund.Add(inner.Value);
                                                        }

                                                        if (innerTagName == "Organization")
                                                        {
                                                            inner.MoveToContent();// move to the text 
                                                            inner.Read();
                                                            thisInvoice.org.Add(inner.Value);
                                                        }

                                                        if (innerTagName == "Program")
                                                        {
                                                            inner.MoveToContent();// move to the text 
                                                            inner.Read();
                                                            thisInvoice.prog.Add(inner.Value);
                                                        }

                                                       break;



                                                }//end switch
                                                break;//brake the outside case.
                                            case XmlNodeType.EndElement:
                                                if (inner.Name == "CustomFieldValueSet" || inner.Value == "CustomFieldValueSet")
                                                {
                                                    distributionSwitch = true;
                                                    Console.WriteLine(reader.Value);
                                                    Console.WriteLine(reader.Name);
                                                }
                                                if (inner.Name == "Codes")
                                                {
                                                    distributionSwitch = false;
                                                    distType = null;
                                                    inner.Close();
                                                }

                                                break;
                                        }//end switch
                                    }//end while
                                }//end the if;

在标签 distributionType 的情况下,我需要使列表长度与帐户列表一样长,换句话说,一旦我将它放在变量上,我需要将其用作填充物,以使分布类型列表为大如帐户列表。 我无法想象没有更简单的方法可以做到这一点,我一直在查看 linq to xml,但这没有多大意义。我很想听听你们中的一些专家将如何解决这个问题。我正在尝试用更少的代码组合出一个优雅的解决方案。 任何帮助将不胜感激。

【问题讨论】:

  • 作为第一个问题,你为什么不走反序列化XML成类的路线,而是自己解析XML?
  • bernd 我是一个使用 xml 的菜鸟。我得到一个 xml 文件,然后打开并处理该文件。最终我必须制作一个数组列表以插入到 DB 表中。反序列化 xml 可能是最好的选择,但在这一点上我不知道更好。
  • 给我一点,我会为你输入一些东西。同时查看this post中的答案和链接
  • 谢谢 Bernd 我会仔细查看帖子的。
  • 我可能错了(因此只是将其作为评论发布),但我看到您的 XML 具有以下结构:CustomFieldValueSet 带有 nameCustomFieldValue 孩子一个唯一的splitindex 和用于抓取的数据。为什么不以相同的方式解析每个CustomFieldValueSet 并根据子项的splitindex 值将其抓取的数据添加到对象列表中?它会自动有一个与之关联的帐户并获取所有其他相应的信息......简单地说,Account 不应该是你的“主键”,而是,splitindex 应该是......

标签: c# xml-parsing


【解决方案1】:

如 cmets 部分所述,作为 Mihai 使用 LINQ to XML 的解决方案的替代方案,您还可以使用预定义的类结构将您的 XML 反序列化为类型化的类和属性。

这样做的好处是您将拥有一个表示 XML 的对象(希望如此),并允许您更轻松地处理 XML 中的数据

使用提供的 XML 示例并使用 Visual Studio 中的 Edit -> Paste Special -> Paste XML as Classes 菜单选项,您会得到一个类似于下面的类结构(为了便于阅读,这个类结构做了一些细化)

using System.Xml.Serialization;

[XmlTypeAttribute(AnonymousType = true)]
[XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class Codes
{
  [XmlElementAttribute("CustomFieldValueSet")]
  public List<CodesCustomFieldValueSet> CustomFieldValueSet { get; set; }
}

[XmlTypeAttribute(AnonymousType = true)]
public partial class CodesCustomFieldValueSet
{
  [XmlElementAttribute("CustomFieldValue")]
  public List<CodesCustomFieldValueSetCustomFieldValue> CustomFieldValue { get; set; }

  [XmlAttributeAttribute(AttributeName="name")]
  public string Name { get; set; }

  [XmlAttributeAttribute(AttributeName = "label")]
  public string Label { get; set; }

  [XmlAttributeAttribute(AttributeName = "distributionType")]
  public string DistributionType { get; set; }
}

[XmlTypeAttribute(AnonymousType = true)]
public partial class CodesCustomFieldValueSetCustomFieldValue
{
  public string Value { get; set; }

  public string Description { get; set; }

  [XmlAttributeAttribute(AttributeName = "distributionValue")]
  public decimal DistributionValue { get; set; }

  [XmlAttributeAttribute(AttributeName = "splitindex")]
  public byte SplitIndex { get; set; }
}

有了这个类结构,你就可以用下面的代码来反序列化你的 XML
(其中txtInput.Text 是我用来保存示例 XML 数据的 TextBox)

XmlSerializer serializer = new XmlSerializer(typeof(Codes));
Codes codesInput = serializer.Deserialize(new StringReader(txtInput.Text)) as Codes;

if (codesInput != null)
{
  // Do something with the data
}

注意:
根据您所需的输出和您提供的示例 XML 的结构,您需要将反序列化对象中的信息转换为您想要的内容/方式,为此我建议创建一个额外的类结构,并结合List&lt;T&gt;,用于保存所需输出中显示的所有信息。

如果您能控制 XML 的结构并以一种更好的方式构建它以使其比当前更易于解释,那就更好了,因为似乎每个 CustomFieldValueSet 之间的链接是 splitindex ,这是子节点的一个属性,比较复杂。

进一步阅读 XML 序列化:
MSDN: Introducing XML Serialization
The XmlSerializer Class

【讨论】:

  • 我喜欢你的解决方案。我已经对其进行了测试,并且效果很好。所以我要 +1。
  • 谢谢,它可能唯一的问题是与 LINQ 一起使用它比直接从 XML 中使用要困难一些,但我认为使用 List 仍然可能
【解决方案2】:

您可以为此使用Linq to XML

using System.Xml;
using System.Xml.Linq;

static void Main(string[] args) {

// This txt file contains your xml.
var xml_sample = File.ReadAllText("xml_sample.txt");
var doc = XDocument.Parse(xml_sample);

// Get all <CustomFieldValueSet> that have the label attribute `Account`
var accounts = from item in doc.Descendants("Codes").Descendants("CustomFieldValueSet")
               where (item.HasAttributes) && 
                     (item.Attribute("label").Value == "Account")
               select item;

// Create an anonymous type containing the value of the 
// distributionValue attribute and the <Value> node.
var accountValue = from el in accounts.Descendants("CustomFieldValue")
                   let distAttribute = el.Attribute("distributionValue")
                   select new
                   {
                       distValue = distAttribute != null ? distAttribute.Value : "0",
                       value = el.Descendants("Value").First().Value,
                   };

// Display stuff here just to make sure we got it right.
accounts.ToList().ForEach(el => 
    Console.WriteLine(el.Name + " " + el.Attribute("distributionType").Value));

accountValue.ToList().ForEach(el => 
    Console.WriteLine(el.distValue + ":"+ el.value));
}

您应该能够根据需要使用这些想法来解析您的 XML 文件。

【讨论】:

  • 感谢 Mihai 我也会尝试一下,感谢您的回复。
  • Mihai 只是一个问题分布类型并不总是 xml 的一部分,在这种情况下我会得到一个错误。有没有办法解决这个问题。抱歉,我应该在最初的问题中提到这一点。
  • 您可以在访问之前检查该属性是否存在:例如XML parse check if attribute exist。我确定还有其他方法存在。
  • 是的,我明白这一点,但我想在链接查询中这样做,所以每当我运行这行代码`Console.WriteLine(el.distValue + ":"+ el. value));` 我得到一个对象未​​设置为引用错误。
  • 米格尔,请检查更新后的答案。正如我所说,您可以检查该属性是否存在。在这里,我定义了一个新变量distAttribute,它在select new 子句中进行了检查。当您的 xml 中缺少 distributionValue 属性时,您应该将“0”字符串替换为您认为应该采用的任何值
猜你喜欢
  • 2015-04-13
  • 2017-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多