athom
athom

Reputation: 1277

How to parse XML using c# and XDocument into an object when multiple elements have the same name?

I'm using SharePoint 2013's REST API to get XML to parse, but I'm having trouble parsing a certain chunk of XML. Here's a truncated example of some XML I might get back that I have no problem parsing:

<feed xml:base="http://server/DesktopApplications/_api/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml">
  <id>2dff4586-6b7d-4186-ae63-4d048f74a112</id>
  <title />
  <updated>2014-03-19T15:32:15Z</updated>
  <entry m:etag=""19"">
    <id>http://server/DesktopApplications/_api/Web/Lists(guid'0ac46109-38d9-4070-96b7-f90085b56f1e')</id>
    <category term="SP.List" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <link rel="edit" href="Web/Lists(guid'0ac46109-38d9-4070-96b7-f90085b56f1e')" />
    <title />
    <updated>2014-03-19T15:32:15Z</updated>
    <author>
      <name />
    </author>
    <content type="application/xml">
      <m:properties>
        <d:BaseTemplate m:type="Edm.Int32">101</d:BaseTemplate>
        <d:BaseType m:type="Edm.Int32">1</d:BaseType>
        <d:Id m:type="Edm.Guid">0ac46109-38d9-4070-96b7-f90085b56f1e</d:Id>
        <d:ListItemEntityTypeFullName>SP.Data.Shared_x0020_DocumentsItem</d:ListItemEntityTypeFullName>
        <d:Title>Documents</d:Title>
      </m:properties>
    </content>
  </entry>
  ...
</feed>

I can parse this just fine using XDocument with the following:

private static readonly XNamespace d = "http://schemas.microsoft.com/ado/2007/08/dataservices";
private static readonly XNamespace m = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata";

...    

List<KLList> lists = doc.Descendants(m + "properties").Select(
    list => new KLList() 
    { 
        Id = list.Element(d + "Id").Value, 
        Title = list.Element(d + "Title").Value,
        ListItemEntityTypeFullName = list.Element(d + "ListItemEntityTypeFullName").Value,
        BaseType = (BaseType)Convert.ToInt32(list.Element(d + "BaseType").Value),
        ListTemplateType = (ListTemplateType)Convert.ToInt32(list.Element(d + "BaseTemplate").Value)
    }).ToList();

When I expand my query to get the server relative url of the list's root folder, I get XML like this that I have trouble with:

<feed xml:base="http://server/DesktopApplications/_api/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml">
  <id>a01c22dc-a87f-4253-91d1-0a6ef8efa6d0</id>
  <title />
  <updated>2014-03-19T15:27:11Z</updated>
  <entry m:etag=""19"">
    <id>http://server/DesktopApplications/_api/Web/Lists(guid'0ac46109-38d9-4070-96b7-f90085b56f1e')</id>
    <category term="SP.List" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <link rel="edit" href="Web/Lists(guid'0ac46109-38d9-4070-96b7-f90085b56f1e')" />
    <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/RootFolder" type="application/atom+xml;type=entry" title="RootFolder" href="Web/Lists(guid'0ac46109-38d9-4070-96b7-f90085b56f1e')/RootFolder">
      <m:inline>
        <entry>
          <id>http://server/DesktopApplications/_api/Web/Lists(guid'0ac46109-38d9-4070-96b7-f90085b56f1e')/RootFolder</id>
          <category term="SP.Folder" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
          <link rel="edit" href="Web/Lists(guid'0ac46109-38d9-4070-96b7-f90085b56f1e')/RootFolder" />
          <title />
          <updated>2014-03-19T15:27:11Z</updated>
          <author>
            <name />
          </author>
          <content type="application/xml">
            <m:properties>
              <d:ServerRelativeUrl>/DesktopApplications/Shared Documents</d:ServerRelativeUrl>
            </m:properties>
          </content>
        </entry>
      </m:inline>
    </link>
    <title />
    <updated>2014-03-19T15:27:11Z</updated>
    <author>
      <name />
    </author>
    <content type="application/xml">
      <m:properties>
        <d:BaseTemplate m:type="Edm.Int32">101</d:BaseTemplate>
        <d:BaseType m:type="Edm.Int32">1</d:BaseType>
        <d:Id m:type="Edm.Guid">0ac46109-38d9-4070-96b7-f90085b56f1e</d:Id>
        <d:ListItemEntityTypeFullName>SP.Data.Shared_x0020_DocumentsItem</d:ListItemEntityTypeFullName>
        <d:Title>Documents</d:Title>
      </m:properties>
    </content>
  </entry>
  ...
</feed>

I tried using basically the same code because I want the ServerRelativeUrl to be on the same object:

List<KLList> lists = doc.Descendants(m + "properties").Select(
    list => new KLList() 
    { 
        Id = list.Element(d + "Id").Value, 
        Title = list.Element(d + "Title").Value,
        ListItemEntityTypeFullName = list.Element(d + "ListItemEntityTypeFullName").Value,
        BaseType = (BaseType)Convert.ToInt32(list.Element(d + "BaseType").Value),
        ListTemplateType = (ListTemplateType)Convert.ToInt32(list.Element(d + "BaseTemplate").Value),
        RelativeUrl = list.Element(d + "ServerRelativeUrl").Value
    }).ToList();

But this no longer works. The first thing doc.Descendants(m + "properties") returns is

<m:properties>
    <d:ServerRelativeUrl>/DesktopApplications/Shared Documents</d:ServerRelativeUrl>
</m:properties>

so trying to set the other properties at the same time throws an object reference exception, because the other elements aren't here.

How can I parse this XML so that all of the values I want go into one object? I'd rather not have to make a separate call to get the root folder url for each list.

Update:

I posted an answer below, but I feel like there is a better way out there somewhere. Feel free to post an answer if it's better than mine and I'll mark yours as the answer.

Upvotes: 1

Views: 872

Answers (2)

athom
athom

Reputation: 1277

I figured out a way to get me what I need but I feel like there has to be a better way...

private readonly XNamespace a = "http://www.w3.org/2005/Atom";
private readonly XNamespace d = "http://schemas.microsoft.com/ado/2007/08/dataservices";
private readonly XNamespace m = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata";

List<KLList> lists = doc.Descendants(a + "entry").Where(element => element.Attribute(m + "etag") != null).Select(
    list => new KLList()
    {
        Id = list.Descendants(d + "Id").FirstOrDefault().Value,
        Title = list.Descendants(d + "Title").FirstOrDefault().Value,
        ListItemEntityTypeFullName = list.Descendants(d + "ListItemEntityTypeFullName").FirstOrDefault().Value,
        BaseType = (BaseType)Convert.ToInt32(list.Descendants(d + "BaseType").FirstOrDefault().Value),
        ListTemplateType = (ListTemplateType)Convert.ToInt32(list.Descendants(d + "BaseTemplate").FirstOrDefault().Value),
        RelativeUrl = list.Descendants(d + "ServerRelativeUrl").FirstOrDefault().Value
    }).ToList();

Upvotes: 1

Carl Burnett
Carl Burnett

Reputation: 181

What you want to do is to check that the element exists before getting it's value because at the moment you are just assuming that they will always exist.

This has been asked before here (I haven't got enough reputation to post this as a comment): Check if an element exists when parsing XML

Upvotes: 0

Related Questions