简体   繁体   English

如何使用LINQ to XML读取此XML

[英]how to read this XML using LINQ to XML

I am new to LINQ to XML, and currently working with the following XML: 我是LINQ to XML的新手,目前使用以下XML:

<invoices>
  <invoice>
    <order_id>85</order_id>
    <time>02:52 PM</time>
    <date>24-05-2013</date>
    <order>
      <item>
        <Main>
          <id>343</id>
          <Qty>1</Qty>
        </Main>
        <Add />
      </item>
      <item>
        <Main>
          <id>3</id>
          <Qty>1</Qty>
        </Main>
        <Add>
          <Extra id="1">
            <Qty>1</Qty>
            <Desc>Regular</Desc>
          </Extra>
        </Add>
      </item>
    </order>
  </invoice>
  <invoice>
    <order_id>88</order_id>
    <time>03:10 PM</time>
    <date>24-05-2013</date>
    <order>
      <item>
        <Main>
          <id>345</id>
          <Qty>1</Qty>
        </Main>
        <Add />
      </item>
      <item>
        <Main>
          <id>2</id>
          <Qty>2</Qty>
        </Main>
        <Add>
          <Extra id="1">
            <Qty>1</Qty>
            <Desc>Regular</Desc>
          </Extra>
        </Add>
      </item>
    </order>
  </invoice>
</invoices>

So far I have written the following code: 到目前为止,我已经编写了以下代码:

void queryData(XDocument doc)
{
        var data = from item in doc.Descendants("invoice")
                   select new
                   {
                       orderId = item.Element("order_id").Value,
                       orderDate = item.Element("date").Value,
                       orderTime = item.Element("time").Value
                   };
        foreach(var p in data)
            Console.WriteLine(p.ToString());

        //...

}

I having trouble reading the nested tags in the "order" tag. 我无法在“order”标签中阅读嵌套标签。 Also the element/tag "Add" sometimes has the "Extra" no. 元素/标签“添加”有时也有“额外”号。 of tags/elements and sometimes not. 标签/元素,有时不是。

I don't have access to the code where this xml is generated so have to read this pattern. 我无法访问生成此xml的代码,因此必须阅读此模式。

So far I have tried working with grouping, but I am not able to work with 2nd and 3rd level elements. 到目前为止,我已经尝试过分组,但我无法使用二级和三级元素。

After reading I would save these values to the database. 阅读后,我会将这些值保存到数据库中。

Thanks, 谢谢,

For the nested elements, just keep going with .Element("name") : 对于嵌套元素,只需继续使用.Element("name")

orderQuantities = item.Element("order").Elements("item")
    .Select(orderItem => new { 
        id = orderItem.Element("Main").Element("id")),
        qty = orderItem.Element("Main").Element("Qty"))
     }).ToArray(),

For the elements that you are not sure exist, you can always write a helper method: 对于您不确定存在的元素,您始终可以编写辅助方法:

extraQty = GetExtra(item),

Where GetExtra would be something like: GetExtra会是这样的:

public int GetExtra(XElement element)
{
    XElement extra = element.Element("Add").Element("Extra");
    if (extra != null) return int.Parse(extra.Element("Qty").Value);
    else return 0;
}

(Needs more error handling of course, but you get the idea.) (当然需要更多的错误处理,但你明白了。)

Let me know if I am off by something, I didn't get a chance to test this, and also had to assume some of the elements where going to be duplicated 让我知道,如果我有所作为,我没有机会测试这一点,并且还必须假设一些要重复的元素

var data = from item in doc.Descendants ( "invoice" )
    select new {
        orderId = item.Element ( "order_id" ).Value ,
        orderDate = item.Element ( "date" ).Value ,
        orderTime = item.Element ( "time" ).Value ,
        items = 
            from order in item.Element ( "order" ).Descendants ( "item" )
            let main = order.Element ( "Main" )
            let adds = order.Elements ( "Add" )
            select new {
                Main = new {
                    id = main.Element ( "id" ).Value ,
                    Qty = main.Element ( "Qty" ).Value
                } ,
                Add = 
                (from add in adds
                    let extras = add.Elements ( "Extra" )
                    select new {
                                Extra = ( from extra in extras
                                        select new {
                                                extraId = extra.Attribute("id").Value,
                                                Qty = extra.Element ( "Qty" ).Value ,
                                                Desc = extra.Element ( "Desc" ).Value
                                            }).FirstOrDefault ( )
                            }).FirstOrDefault()
            }
};

Here is parsing of your xml: 这是解析你的xml:

var parser = new Parser();
XDocument xdoc = XDocument.Load(path_to_xml);
var orders = from invoice in xdoc.Root.Elements()
             select parser.ParseOrderFrom(invoice);

Thats all. 就这样。 I have created following classes. 我创建了以下类。 Order, which holds collection of order items and have nice parsed date: 订单,它包含订单商品的集合并具有很好的解析日期:

public class Order
{
    public int Id { get; set; }
    public DateTime Date { get; set; }
    public List<OrderItem> Items { get; set; }
}

Order item, which is your main dish. 订购商品,这是您的主菜。 Also it has list of extras inside (if any): 它还有内部附加列表(如果有的话):

public class OrderItem
{
    public int Id { get; set; }
    public int Quantity { get; set; }
    public List<Extra> Extras { get; set; }
}

And extras class: 和额外课程:

public class Extra
{
    public int Id { get; set; }
    public int Quantity { get; set; }
    public string Description { get; set; }
}

All parsing occurs in separate parser class, if you want (this will keep domain classes clean): 如果需要,所有解析都在单独的解析器类中进行(这将保持域类清洁):

public class Parser
{
    public Order ParseOrderFrom(XElement invoice)
    {
        string time = (string)invoice.Element("time");
        string date = (string)invoice.Element("date");

        return new Order {
           Id = (int)invoice.Element("order_id"),
           Date = DateTime.ParseExact(date + time, "dd-MM-yyyyhh:mm tt", null),
           Items = invoice.Element("order").Elements("item")
                          .Select(i => ParseOrderItemFrom(i)).ToList()
        };
    }

    public OrderItem ParseOrderItemFrom(XElement item)
    {
        var main = item.Element("Main");

        return new OrderItem {
            Id = (int)main.Element("id"),
            Quantity = (int)main.Element("Qty"),
            Extras = item.Element("Add").Elements("Extra")
                         .Select(e => ParseExtraFrom(e)).ToList()
        };
    }

    public Extra ParseExtraFrom(XElement extra)
    {
        return new Extra {
            Id = (int)extra.Attribute("id"),
            Quantity = (int)extra.Element("Qty"),
            Description = (string)extra.Element("Desc")
        };
    }
}

Tested an working. 测试工作。 This is impossible to do in one shot without defining some extra classes. 如果不定义一些额外的类,这是不可能一次完成的。 Here I have a pivot-interface Item and then two classes which implement the interface Additem and MainItem . 这里我有一个pivot-interface Item ,然后是两个实现AdditemMainItem接口的类。

Feel free to ask about an explanation on any portion. 随意询问有关任何部分的解释。

// Since there are different types of items, we need an interface/abstact
// class to pivot.
public interface Item {
}

// The information neccesary for storing the 'Extra' element.
public class Extra {
    public Int32 ID { get; private set; }
    public Int32 Quantity { get; private set; }
    public String Description { get; private set; }

    public Extra(XElement extra) {

        // Here we load up all of the details from the 'extra' element
        this.ID = Int32.Parse(extra.Attribute("id").Value);
        this.Quantity = Int32.Parse(extra.Element("Qty").Value); ;
        this.Description = extra.Element("Desc").Value;
    }
}

// The 'add-item' is associated with the 'add' tag in the actual XML.
public class AddItem : Item {

    public IEnumerable<Extra> Extras { get; private set; }

    // The 'extras' is a collection of many items, so we require
    // an ienumerable.
    public AddItem(IEnumerable<Extra> extras) {
        this.Extras = extras;
    }

}

// The storage for the 'main-item'
public class MainItem : Item {
    public Int32 ID { get; private set; }
    public Int32 Quantity { get; private set; }

    public MainItem(Int32 id, Int32 quantity) {
        this.ID = id;
        this.Quantity = quantity;
    }
}

class Program {
    static void Main(string[] args) {
        String data = File.ReadAllText("File.txt");

        XElement tree = XElement.Parse(data);


        var projection = tree.Elements()
            .Select(invoice => new {
                // Project the main details of the invoice { OrderID, Time, Date, Order }
                // The order itself needs to be projected again though because it too is a 
                // collection of sub items.
                OrderID = invoice.Element("order_id").Value,
                Time = invoice.Element("time").Value,
                Date = invoice.Element("date").Value,
                Order = invoice.Element("order")
                    .Elements()
                    .Elements()
                    .Select(item => {

                        // First, we need to know what type of item this 'order' is.
                        String itemType = item.Name.ToString();

                        // If its a 'main' item, then return that type.
                        if (itemType == "Main") {
                            Int32 id = Int32.Parse(item.Element("id").Value);
                            Int32 quantity = Int32.Parse(item.Element("Qty").Value);

                            return (Item)new MainItem(id, quantity);
                        }

                        // If it's an 'Add' item. Then we have to:
                        if (itemType == "Add") {
                            // (1) Capture all of the extras.
                            IEnumerable<Extra> extras = item.Elements()
                                .Select(extra => new Extra(extra))
                                .ToList();

                            // (2) Add the extras to a new AddItem. Then return the 'add'-item.
                            // Notice that we have to cast to 'Item' because we are returning 
                            // a 'Main'-item sometimes and an 'add' item other times.
                            // Select requires the return type to be the same regardless.
                            return (Item)new AddItem(extras);
                        }

                        // Hopefully this path never hits.
                        throw new NotImplementedException("This path not defined");

                    }).ToList()

            }).ToList();

        Console.WriteLine(projection);
    }
}

You can make things more manageable if you use some xpath in your query. 如果在查询中使用某个xpath,则可以使事情更易于管理。 Using pure LINQ to XML here can get too verbose if you asked me. 如果你问我,在这里使用纯LINQ to XML会变得太冗长。

var query =
    from invoice in doc.XPathSelectElements("/invoices/invoice")
    select new
    {
        OrderId = (int)invoice.Element("order_id"),
        Time = (string)invoice.Element("time"),
        Date = (string)invoice.Element("date"),
        Items =
            from item in invoice.XPathSelectElements("./order/item")
            select new
            {
                Id = (int)item.XPathSelectElement("./Main/id"),
                Quantity = (int)item.XPathSelectElement("./Main/Qty"),
                Extras =
                    from extra in item.XPathSelectElements("./Add/Extra")
                    select new
                    {
                        Id = (int)extra.Attribute("id"),
                        Quantity = (int)extra.Element("Qty"),
                        Description = (string)extra.Element("Desc"),
                    },
            },
    };

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM