Поиск XML с использованием XPath с JDOM/JAXEN/SAXON
У меня есть документ XML, который я анализирую с помощью JDOM-2.0.5
, Следующий код работает нормально и bookNodes
список содержит все узлы книги из моего файла XML:
SAXBuilder builder = new SAXBuilder();
// @see http://xerces.apache.org/xerces-j/features.html
// Disable namespace validation
builder.setFeature("http://xml.org/sax/features/namespaces", false);
Document doc = null;
try {
doc = builder.build(xmlURL);
} catch (JDOMException | IOException e) {
e.printStackTrace();
return null;
}
// get browse elmt
Element browse = doc.getRootElement().getChild("browse");
// Get all browse's chlidren
List<Element> bookNodes = browse.getChildren("book");
for (Element book : bookNodes) {
// Do things with the selected nodes
//...
}
И вот пример моих данных XML:
<?xml version="1.0" encoding="utf-8"?>
<Books xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.example.com/XMLSchema" version="1">
<status code="0"/>
<link>http://www.example.com/books</link>
<description>Browse, search and ....</description>
<language>en-us</language>
<pubDate>Sun, 09 Nov 2014 00:00:02 +0000</pubDate>
<copyright>Copyright 2014, XXX</copyright>
<category>Books</category>
<browse>
<book id="bk101">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<price>44.95</price>
<publish_date>2000-10-01</publish_date>
<description>An in-depth look at creating applications
with XML.</description>
</book>
<book id="bk102">
<author>Ralls, Kim</author>
<title>The Midnight Rain</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2000-12-16</publish_date>
<description>A former architect battles corporate zombies,
an evil sorceress, and her own childhood to become queen
of the world.</description>
</book>
<book id="bk105">
<author>Corets, Eva</author>
<title>The Sundered Grail</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2001-09-10</publish_date>
<description>The two daughters of Maeve, half-sisters,
battle one another for control of England. Sequel to
Oberon's Legacy.</description>
</book>
<book id="bk106">
<author>Randall, Cynthia</author>
<title>Lover Birds</title>
<genre>Romance</genre>
<price>4.95</price>
<publish_date>2000-09-02</publish_date>
<description>When Carla meets Paul at an ornithology
conference, tempers fly as feathers get ruffled.</description>
</book>
</browse>
</Books>
Вопрос 1:
Я хочу выбрать только узлы книги, содержащие текст. Итак, я использовал запрос XPath //book[contains(./title, 'The')]
а также jaxen-1.1.6
со следующим кодом:
filter = "//book[contains(./title, 'The')]"; // should return 2 elements (2nd and 3rd nodes)
// use the default implementation
XPathFactory xFactory = XPathFactory.instance();
XPathExpression<Element> expr = xFactory.compile(filter, Filters.element());
List<Element> bookNodes = expr.evaluate(doc);
Но bookNodes
список был пуст!
Что не так с моим кодом?
Вопрос 2:
Мне понадобятся более продвинутые функции для поиска в моих полях xml, например:
filter = "//book[matches(./title, '^ *XML.*?Developer.*?Guide *$', 'i')]"; // should return 1 element (1st node)
Я тогда использую saxon9he
который поддерживает XPath 2.0+, но я не мог понять, как заставить его работать с JDOM2 и моим кодом выше.
Так что, если вы можете приучить меня к тому, как сделать это на основе моего кода (я уже гуглил на помощь, но я не мог найти какой-либо)
Ответ на вопрос 1 поможет мне понять, что я сделал не так. Но ответ на вопрос 2 поможет мне продвинуться вперед с моим небольшим личным приложением.
Спасибо
2 ответа
Язык XPath определяется только в правильно сформированном XML-пространстве имен и может давать неожиданные результаты, если вы попытаетесь использовать его в дереве XML, которое было проанализировано без пространств имен. Вместо того, чтобы игнорировать пространство имен, вы должны использовать их правильно:
SAXBuilder builder = new SAXBuilder();
Document doc = null;
try {
doc = builder.build(xmlURL);
} catch (JDOMException | IOException e) {
e.printStackTrace();
return null;
}
Namespace ns = Namespace.getNamespace("http://www.example.com/XMLSchema");
// get browse elmt
Element browse = doc.getRootElement().getChild("browse", ns);
// Get all browse's chlidren
List<Element> bookNodes = browse.getChildren("book", ns);
for (Element book : bookNodes) {
// Do things with the selected nodes
//...
}
Для XPath вам необходимо привязать URI пространства имен к префиксу:
filter = "//ns:book[contains(./ns:title, 'The')]";
// use the default implementation
XPathFactory xFactory = XPathFactory.instance();
XPathBuilder<Element> builder = new XPathBuilder(filter, Filters.element());
builder.setNamespace("ns", "http://www.example.com/XMLSchema");
XPathExpression<Element> expr = builder.compileWith(xFactory);
List<Element> bookNodes = expr.evaluate(doc);
Что касается вопроса 2, движок Saxon XPath может работать с деревьями JDOM2, но вы должны использовать Saxon XPath API вместо JDOM, что, в свою очередь, означает, что вы должны использовать стандартный javax.xml.xpath
способ связывания префиксов пространства имен с URI, что гораздо более громоздко, чем JDOM - вам нужно определить собственную реализацию NamespaceContext
или используйте сторонний, такой как SimpleNamespaceContext Spring.
JDOM2DocumentWrapper docw =
new JDOM2DocumentWrapper(doc, config); // net.sf.saxon.option.jdom2
XPathEvaluator xpath = new XPathEvaluator(); // net.sf.saxon.xpath
SimpleNamespaceContext nsCtx = new SimpleNamespaceContext();
nsCtx.bindNamespaceUri("ns", "http://www.example.com/XMLSchema");
xpath.setNamespaceContext(nsCtx);
List<?> bookNodes = (List<?>)xpath.evaluate(
"//ns:book[matches(./ns:title, '^ *XML.*?Developer.*?Guide *$', 'i')]", docw,
XPathConstants.NODESET);
(адаптировано из саксонского JDOM2Example.java)
Для полноты вот как это сделать с помощью интерфейса S9api Saxon:
Processor proc = new Processor();
XdmNode docw = proc.newDocumentBuilder().wrap(doc);
XPathCompiler xpath = proc.newXPathCompiler();
xpath.declareNamespace("ns", "http://www.example.com/XMLSchema");
XdmValue bookNodes = xpath.evaluate(
"//ns:book[matches(./ns:title, '^ *XML.*?Developer.*?Guide *$', 'i')]", docw);
for (XdmItem book : bookNodes) {
....
}