提问人:cachius 提问时间:10/27/2022 更新时间:10/27/2022 访问量:41
将一组简单的 xpath 与 SAX 匹配
Match set of simple xpaths with SAX
问:
我有一组简单的 xpath,只涉及标签和属性,没有谓词。我的 XML 输入大小为几 MB,因此我想使用流式 XML 解析器。
如何将流式 XML 解析器与一组 xapths 进行匹配,以便为每个 xpath 检索一个值?
关键似乎是从 xpath 集合中构建正确的数据结构,以便可以根据 xml 事件对其进行评估。
这似乎是一个相当常见的任务,但我找不到任何现成的解决方案。
答:
若要将流式处理 XML 分析器与一组简单的 xpath 进行匹配,可以使用以下步骤:
- 创建一个 来存储 xpath 及其相应的值。将值初始化为 。
Map<String, String>
null
- 创建一个 以跟踪 XML 元素的当前路径。
Stack<String>
- 创建 a 和 a 以分析 XML 输入。
SAXParser
DefaultHandler
- 在处理程序的方法中,将元素名称推送到堆栈并将其追加到当前路径。然后,检查当前路径是否与地图中的任何 xpath 匹配。如果是,请设置一个标志以指示应提取该值。
startElement
- 在处理程序的方法中,从堆栈中弹出元素名称并将其从当前路径中删除。然后,重置该标志以指示不应提取该值。
endElement
- 在处理程序的方法中,检查是否设置了标志。如果是,请将字符数据附加到映射中匹配的 xpath 的值。
characters
- 解析 XML 输入后,返回包含 xpath 及其值的映射。
解释
流式 XML 解析器(如 )按顺序读取 XML 输入,并在遇到文档的不同部分(如开始标记、结束标记、文本等)时触发事件。它不会在内存中构建文档的树结构,这使得它对于大型 XML 输入更有效。SAXParser
xpath 是一种用于从 XML 文档中选择节点的语法。它由一系列步骤组成,用斜杠分隔,用于描述所需节点的位置。例如,选择 bookstore 元素的 book 元素的 title 元素。/bookstore/book/title
一个简单的 xpath 只涉及标签和属性,不涉及谓词。例如,选择具有 value 的属性的 book 元素的 title 元素。/bookstore/book[@lang='en']/title
lang
en
为了将流式 XML 解析器与一组简单的 xpath 进行匹配,我们需要在解析输入时跟踪 XML 元素的当前路径,并将其与集合中的 xpath 进行比较。如果我们找到匹配项,我们需要提取节点的值并将其存储在地图中。我们还需要处理节点值跨越多个字符事件的情况,或者节点在文档中有多个实例的情况。
例
假设我们有以下 XML 输入:
<bookstore>
<book lang="en">
<title>Harry Potter and the Philosopher's Stone</title>
<author>J. K. Rowling</author>
<price>10.99</price>
</book>
<book lang="fr">
<title>Le Petit Prince</title>
<author>Antoine de Saint-Exupéry</author>
<price>8.50</price>
</book>
</bookstore>
以及以下一组简单的 xpath:
/bookstore/book/title
/bookstore/book/author
/bookstore/book[@lang='fr']/price
我们可以使用以下 Java 代码将流式 XML 解析器与一组 xpath 进行匹配:
import java.io.*;
import java.util.*;
import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
public class XPathMatcher {
public static Map<String, String> match(InputStream xmlInput, Set<String> xpaths) throws Exception {
// Create a map to store the xpaths and their values
Map<String, String> map = new HashMap<>();
for (String xpath : xpaths) {
map.put(xpath, null);
}
// Create a stack to keep track of the current path
Stack<String> stack = new Stack<>();
// Create a SAXParser and a DefaultHandler to parse the XML input
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
DefaultHandler handler = new DefaultHandler() {
// A flag to indicate if the value should be extracted
boolean extract = false;
// A variable to store the current path
String currentPath = "";
// A variable to store the matching xpath
String matchingXPath = "";
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
// Push the element name to the stack and append it to the current path
stack.push(qName);
currentPath += "/" + qName;
// Check if the current path matches any of the xpaths in the map
for (String xpath : map.keySet()) {
// If the xpath has an attribute, extract the attribute name and value
String attrName = "";
String attrValue = "";
if (xpath.contains("[@")) {
int start = xpath.indexOf("[@") + 2;
int end = xpath.indexOf("=");
attrName = xpath.substring(start, end);
start = end + 2;
end = xpath.indexOf("]");
attrValue = xpath.substring(start, end - 1);
}
// If the xpath matches the current path, and either has no attribute or has a matching attribute, set the flag and the matching xpath
if (xpath.startsWith(currentPath) && (attrName.isEmpty() || attrValue.equals(attributes.getValue(attrName)))) {
extract = true;
matchingXPath = xpath;
break;
}
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
// Pop the element name from the stack and remove it from the current path
stack.pop();
currentPath = currentPath.substring(0, currentPath.length() - qName.length() - 1);
// Reset the flag and the matching xpath
extract = false;
matchingXPath = "";
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
// Check if the flag is set
if (extract) {
// Append the character data to the value of the matching xpath in the map
String value = map.get(matchingXPath);
if (value == null) {
value = "";
}
value += new String(ch, start, length);
map.put(matchingXPath, value);
}
}
};
// Parse the XML input
parser.parse(xmlInput, handler);
// Return the map with the xpaths and their values
return map;
}
public static void main(String[] args) throws Exception {
// Create an input stream from the XML file
InputStream xmlInput = new FileInputStream("bookstore.xml");
// Create a set of simple xpaths
Set<String> xpaths = new HashSet<>();
xpaths.add("/bookstore/book/title");
xpaths.add("/bookstore/book/author");
xpaths.add("/bookstore/book[@lang='fr']/price");
// Match the streaming XML parser against the set of xpaths
Map<String, String> map = match(xmlInput, xpaths);
// Print the results
for (String xpath : map.keySet()) {
System.out.println(xpath + " = " + map.get(xpath));
}
}
}
代码的输出为:
/bookstore/book/title = Harry Potter and the Philosopher's StoneLe Petit Prince
/bookstore/book/author = J. K. RowlingAntoine de Saint-Exupéry
/bookstore/book[@lang='fr']/price = 8.50
下一个:筛选复杂 XML 文档中的元素
评论