id() in XPATH with .NET
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">
<book id="ISBN9789170375033">
<title>I väntan på talibanerna</title>
<cover>http://image.bokus.com/images2/9789170375033_large</cover>
<author-ref id="A1" />
</book>
<book id="ISBN9789170372063">
<title>Sista resan till Phnom Penh</title>
<cover>http://image.bokus.com/images2/9789170372063_large</cover>
<author-ref id="A1" />
</book>
<book id="ISBN9789127121867">
<title>V开发者_如何学Cårt bröllop : Kronprinsessan Victoria och Prins Daniel 19 juni 2010</title>
<cover>http://image.bokus.com/images2/9789127121867_large</cover>
<author-ref id="A2 A3" />
</book>
<book id="ISBN9789189204966">
<title>Människa, människa</title>
<cover>http://image.bokus.com/images2/9789189204966_large</cover>
<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
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"><name>Susanna Popova</name></author>
<author id="A3" xmlns="http://litemedia.se/BookStore.xsd"><name>Paul Hansen</name></author>
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">
<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)
Iirc (from long back) the id handling works for xdr but not xsd.
Maybe just use //*[@id='foo']
精彩评论