title: Using IXMLDOMDocument and XPath to parse Dynamics 365 XML data
body: <p>Continuing on from my other questions about consuming REST services from Dynamics 365, I found that by setting the <em>Accept</em> property of the <em>TRESTRequest</em> appropriately, I can get the odata query result as XML (since trying to parse the odata response itself was just an exercise in frustration at Delphi).</p>
  
  <pre><code>RESTRequest.Accept := 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8';
  </code></pre>
  
  <p>Leads to this result (sorry for the heavy block of XML, but this is what I am dealing with, not some nice shallow easy to use XML):</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="utf-8" standalone="yes"?&gt;
  &lt;feed xml:base="https://mycrm.dynamics.com/XRMServices/2011/OrganizationData.svc/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom"&gt;
    &lt;title type="text"&gt;BookableResourceSet&lt;/title&gt;
    &lt;id&gt;https://mycrm.dynamics.com/XRMServices/2011/OrganizationData.svc/BookableResourceSet&lt;/id&gt;
    &lt;updated&gt;2018-05-29T02:47:48Z&lt;/updated&gt;
    &lt;link rel="self" title="BookableResourceSet" href="BookableResourceSet" /&gt;
    &lt;entry&gt;
      &lt;id&gt;https://mycrm.dynamics.com/XRMServices/2011/OrganizationData.svc/BookableResourceSet(guid'f2060c99-0653-e811-8162-e0071b659e71')&lt;/id&gt;
      &lt;title type="text"&gt;John Smith&lt;/title&gt;
      &lt;updated&gt;2018-05-29T02:47:48Z&lt;/updated&gt;
      &lt;author&gt;
        &lt;name /&gt;
      &lt;/author&gt;
      &lt;link rel="edit" title="BookableResource" href="BookableResourceSet(guid'f2060c99-0653-e811-8162-e0071b659e71')" /&gt;
      &lt;link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/systemuser_bookableresource_UserId" type="application/atom+xml;type=entry" title="systemuser_bookableresource_UserId" href="BookableResourceSet(guid'f2060c99-0653-e811-8162-e0071b659e71')/systemuser_bookableresource_UserId"&gt;
        &lt;m:inline&gt;
          &lt;entry&gt;
            &lt;id&gt;https://mycrm.dynamics.com/XRMServices/2011/OrganizationData.svc/SystemUserSet(guid'1648251b-9bff-e611-8107-c4346bc50748')&lt;/id&gt;
            &lt;title type="text"&gt;John Smith&lt;/title&gt;
            &lt;updated&gt;2018-05-29T02:47:48Z&lt;/updated&gt;
            &lt;author&gt;
              &lt;name /&gt;
            &lt;/author&gt;
            &lt;link rel="edit" title="SystemUser" href="SystemUserSet(guid'1648251b-9bff-e611-8107-c4346bc50748')" /&gt;
            &lt;category term="Microsoft.Crm.Sdk.Data.Services.SystemUser" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /&gt;
            &lt;content type="application/xml"&gt;
              &lt;m:properties&gt;
                &lt;d:FirstName&gt;John&lt;/d:FirstName&gt;
                &lt;d:TerritoryId m:type="Microsoft.Crm.Sdk.Data.Services.EntityReference"&gt;
                  &lt;d:Id m:type="Edm.Guid"&gt;c10a3010-0087-e711-8113-c4346bc540c4&lt;/d:Id&gt;
                  &lt;d:LogicalName&gt;territory&lt;/d:LogicalName&gt;
                  &lt;d:Name&gt;District 9&lt;/d:Name&gt;
                  &lt;d:RowVersion m:null="true" /&gt;
                &lt;/d:TerritoryId&gt;
                &lt;d:int_StaffNumber&gt;A111111&lt;/d:int_StaffNumber&gt;
                &lt;d:int_ServiceAreaId m:type="Microsoft.Crm.Sdk.Data.Services.EntityReference"&gt;
                  &lt;d:Id m:type="Edm.Guid" m:null="true" /&gt;
                  &lt;d:LogicalName m:null="true" /&gt;
                  &lt;d:Name m:null="true" /&gt;
                  &lt;d:RowVersion m:null="true" /&gt;
                &lt;/d:int_ServiceAreaId&gt;
                &lt;d:int_Title m:type="Microsoft.Crm.Sdk.Data.Services.OptionSetValue"&gt;
                  &lt;d:Value m:type="Edm.Int32" m:null="true" /&gt;
                &lt;/d:int_Title&gt;
                &lt;d:LastName&gt;Smith&lt;/d:LastName&gt;
              &lt;/m:properties&gt;
            &lt;/content&gt;
          &lt;/entry&gt;
        &lt;/m:inline&gt;
      &lt;/link&gt;
      &lt;category term="Microsoft.Crm.Sdk.Data.Services.BookableResource" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /&gt;
      &lt;content type="application/xml"&gt;
        &lt;m:properties&gt;
          &lt;d:BookableResourceId m:type="Edm.Guid"&gt;f2060c99-0653-e811-8162-e0071b659e71&lt;/d:BookableResourceId&gt;
          &lt;d:int_cmsobjectid m:null="true" /&gt;
        &lt;/m:properties&gt;
      &lt;/content&gt;
    &lt;/entry&gt;
    &lt;entry&gt;
      &lt;id&gt;https://mycrm.dynamics.com/XRMServices/2011/OrganizationData.svc/BookableResourceSet(guid'5f7767db-0653-e811-8162-e0071b659e71')&lt;/id&gt;
      &lt;title type="text"&gt;Joe Bloggs&lt;/title&gt;
      &lt;updated&gt;2018-05-29T02:47:48Z&lt;/updated&gt;
      &lt;author&gt;
        &lt;name /&gt;
      &lt;/author&gt;
  </code></pre>
  
  <p>Originally I had done this:</p>
  
  <pre><code>  AXMLDoc := TXMLDocument.Create(nil);
  
    AXMLDoc.LoadFromXML(AResponseContent);
    AXMLDoc.Active := true;
  
    ARootNode := AXMLDoc.DocumentElement;
  
    APropertyNode := ARootNode.ChildNodes.FindNode('m:properties');
  </code></pre>
  
  <p>Or a variation of, and considered looping through all the nodes to find the ones I wanted, but the <em>FindNodes</em> function only checks the immediate children, so I would need to write a recursive method to basically search through all the child nodes until I found the one I wanted. This led me to the <em>selectNodes</em> function, which can do this for me.</p>
  
  <p>However, it was a game and a half just to find out how to even use that function, and after going through a number of examples that didn't work due to deprecated methods, I finally tried this:</p>
  
  <pre><code>  AXMLDOMDoc := TMSXMLDOMDocumentFactory.CreateDOMDocument;
  
    AXMLDOMDoc.loadXML(AResponseContent);
  
    AXMLNodeList := AXMLDOMDoc.selectNodes('/feed/entry/link/m:inline/entry/content/m:properties');
  </code></pre>
  
  <p>But that gave me a <strong>"reference to undeclared namespace prefix: m"</strong> error, which then led me to a SO question/answer about setting the DOM document properties, so I ended up with a descendant class of the <em>TMSXMLDOMDocumentFactory</em> in order to not have the <em>selectNodes</em> function throw the error by ensuring the namespaces are included.</p>
  
  <pre><code>  type
      TDynamics365XMLResponseDocument = class(TMSXMLDOMDocumentFactory)
      public
        class function CreateDOMDocument: IXMLDOMDocument; override;
      end;
  
    class function TDynamics365XMLResponseDocument.CreateDOMDocument: IXMLDOMDocument;
    begin
      Result := inherited;
  
      AddDOMProperty('SelectionNamespaces',
                     'xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" '+
                     'xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" '+
                       'xmlns="http://www.w3.org/2005/Atom"');
  
      SetDOMProperties(Result as IXMLDOMDocument2);
    end;
  </code></pre>
  
  <p>So, what I am after is an <em>IXMLDOMNodeList</em> containing all the <strong>"m:properties"</strong> nodes, as they are the ones that actually contain the data I am after. To achieve this, I do what I did above, but create the document from the descendant factory class:</p>
  
  <pre><code>  // Because the XML back from Dynamics 365 contains namespaces, we need to specify the URI's for them as part
    // of the document factory
  
    AXMLDOMDoc := TDynamics365XMLResponseDocument.CreateDOMDocument;
  
    // Load the document with the raw XML
  
    AXMLDOMDoc.loadXML(AResponseContent);
  
    // Using a XPath query, get all the entry property nodes that have the data we need
  
    AXMLDOMNodeList := AXMLDOMDoc.selectNodes('//feed/entry/link/m:inline/entry/content/m:properties');
  </code></pre>
  
  <p>I no longer get namespace errors but the AXMLDOMNodeList is always empty. I tried with a number of different query values, and they all return a list of 0 items, so it seems like the <em>selectNodes</em> of the <em>IXMLDOMDocument</em> isn't working, and I can't step into the source code to see why as the interfaces are just generated from a Microsoft type library.</p>
  
  <p>Can anyone identify what I am doing wrong? Or a better way to do this?</p>