tags:

views:

60

answers:

3

I'm trying to query XML through XPATH but is having problem getting id() to work. I would like to get all the authors for a book, specifying the book ID.

Here's the XML.

<?xml version="1.0" encoding="utf-8" ?>
<bookstore xmlns="http://litemedia.se/BookStore.xsd"&gt;
  <book id="ISBN9789170375033">
    <title>I väntan på talibanerna</title>
    <cover>http://image.bokus.com/images2/9789170375033_large&lt;/cover&gt;
    <author-ref id="A1" />
  </book>

  <book id="ISBN9789170372063">
    <title>Sista resan till Phnom Penh</title>
    <cover>http://image.bokus.com/images2/9789170372063_large&lt;/cover&gt;
    <author-ref id="A1" />
  </book>

  <book id="ISBN9789127121867">
    <title>Vårt bröllop : Kronprinsessan Victoria och Prins Daniel 19 juni 2010</title>
    <cover>http://image.bokus.com/images2/9789127121867_large&lt;/cover&gt;
    <author-ref id="A2 A3" />    
  </book>

  <book id="ISBN9789189204966">
    <title>Människa, människa</title>
    <cover>http://image.bokus.com/images2/9789189204966_large&lt;/cover&gt;
    <author-ref id="A3" />
  </book>

  <author id="A1">
    <name>Jesper Huor</name>
  </author>
  <author id="A2">
    <name>Susanna Popova</name>
  </author>
  <author id="A3">
    <name>Paul Hansen</name>
  </author>
</bookstore>

And this is my schema.

<?xml version="1.0" encoding="utf-8"?>
<xs:schema id="BookStore"
    targetNamespace="http://litemedia.se/BookStore.xsd"
    elementFormDefault="qualified"
    xmlns="http://litemedia.se/BookStore.xsd"
    xmlns:mstns="http://litemedia.se/BookStore.xsd"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
>
  <xs:element name="bookstore">
    <xs:complexType mixed="true">
      <xs:sequence>
        <xs:element name="book" maxOccurs="unbounded">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="title" type="xs:string" />
              <xs:element name="cover" type="xs:anyURI" />
              <xs:element name="author-ref">
                <xs:complexType>
                  <xs:attribute name="id" type="xs:IDREFS"/>
                </xs:complexType>
              </xs:element>
            </xs:sequence>

            <xs:attribute name="id" type="xs:ID" />
          </xs:complexType>
        </xs:element>

        <xs:element name="author" maxOccurs="unbounded">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="name" type="xs:string" />
            </xs:sequence>

            <xs:attribute name="id" type="xs:ID" />
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

This is my code.

public IList<Author> GetAuthorsForBook(string isbn)
{
    using (var xmlStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(XmlDataSourcePath))
    using (var xsdStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(XsdDataSourcePath))
    {
        var doc = new XmlDocument();

        // Load schema
        var schema = XmlSchema.Read(xsdStream, SchemaEvents);
        doc.Schemas.Add(schema);

        // Load document
        doc.Load(xmlStream);

        // Load default namespace: bs
        var nsmgr = new XmlNamespaceManager(doc.NameTable);
        nsmgr.AddNamespace("bs", "http://litemedia.se/BookStore.xsd");

        // We should be able to do this
        // var path = string.Format("id(id('{0}')/bs:author-ref/@id)", isbn);

        // but this will have to do
        var path = string.Format("/bs:bookstore/bs:author/@id[contains(/bs:bookstore/bs:book[@id='{0}']/bs:author-ref/@id,.)]", isbn);

        return doc.SelectNodes(path, nsmgr).Cast<XmlNode>()
            .Select(node =>
            new Author
            {
                Name = node.FirstChild.Value
            }).ToList();
    }
}

Since I've specified bookId and authorId as ID type in the schema I would like to be able to do the following.

var path = string.Format("id(id('{0}')/bs:author-ref/@id)", isbn);

This query always returns 0 elements. If I reduce it to id('ISBN9789127121867') that will also return 0 result, which indicates that id() doesn't work in my scenario. :(

At the moment I'm going for the following query, even if it's not as efficient as using id() would be, it does give me the results I need.

var path = string.Format("/bs:bookstore/bs:author/@id[contains(/bs:bookstore/bs:book[@id='{0}']/bs:author-ref/@id,.)]", isbn);

Have you ever had a similiar problem or any clue to what I might have done wrong? Thank you!

Mikael Lundin

A: 

Iirc (from long back) the id handling works for xdr but not xsd.

Maybe just use //*[@id='foo']

Marc Gravell
+1  A: 

AFAIK the XPath id() function is not implemented in XslCompiledTransform.

For example:

XSLT stylesheet:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/">
  <xsl:copy-of select="id('a22')"/>
 </xsl:template>
</xsl:stylesheet>

Source XML document:

<!DOCTYPE test [
  <!ELEMENT test (x+)>
  <!ELEMENT x (x+| y+)>
  <!ATTLIST x
     a ID #REQUIRED>
  <!ELEMENT y ANY>
]>
<test>
    <x a="a11">
      <x a="a21">
        <x a="a31">
          <y>y31</y>
          <y>y32</y>
        </x>
      </x>
    </x>
    <x a="a12">
      <x a="a22">
        <y>y21</y>
        <y>y22</y>
      </x>
    </x>
    <x a="a13">
      <y>y11</y>
      <y>y12</y>
    </x>
    <x a="a14">
      <y>y03</y>
      <y>y04</y>
    </x>
</test>

Results with MSXML3/4:

<x a="a22">
        <y>y21</y>
        <y>y22</y>
      </x>

Result with XslCompiledTransform or with XslTransform:

(Nothing)
Dimitre Novatchev
Dimitre, how did you run your stylesheet? I simply added your two samples, the XML file and the stylesheet, to a VS 2008 .NET 3.5 C# project, associated the stylesheet with the XML in the IDE and then used the "show XSLT output" button and the transformation result shown has the "x" element with attribute a="a22".
Martin Honnen
@Martin-Honnen: I am using nxslt2.exe.
Dimitre Novatchev
+2  A: 

As explained in my comment, schema defined ids don't matter to XPath 1.0. To give you an example how it works with a schema aware XPath 2.0 implementation, here is some code using the XQSharp extension methods on XmlNodes:

    XmlDocument doc = new XmlDocument();
    XmlReaderSettings xrs = new XmlReaderSettings()
    {
        ValidationType = ValidationType.Schema
    };
    xrs.Schemas.Add(null, "schema.xsd");
    using (XmlReader xr = XmlReader.Create("input.xml", xrs))
    {
        doc.Load(xr);
    }

    string isbn = "ISBN9789127121867";

    XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
    nsmgr.AddNamespace("bs", "http://litemedia.se/BookStore.xsd");

    string path = string.Format("id(id('{0}')/bs:author-ref/@id)", isbn);
    foreach (XmlElement author in doc.XPathSelectNodes(path, nsmgr))
    {
        Console.WriteLine(author.OuterXml);
    }

When I run that in a project where I have added references to XQSharp and XQSharp.ExtensionMethods and have added a using XQSharp.ExtensionMethods; it outputs the two author elements as e.g.

<author id="A2" xmlns="http://litemedia.se/BookStore.xsd"&gt;&lt;name&gt;Susanna Popova</name></author>
<author id="A3" xmlns="http://litemedia.se/BookStore.xsd"&gt;&lt;name&gt;Paul Hansen</name></author>
Martin Honnen
+1 for a good explanation.
Dimitre Novatchev
I see, thank you for your detailed answer. I did not know about the XQSharp project. That's worth taking a closer look at.
Mikael Lundin