基于 Java 中的另一个 XML 创建一个 XML
Creating an XML based on another XML in Java
我想使用一个 XML 文件,结构复杂,大小约为一半,并从中创建另一个 XML 文件,仅包含原始文件的选定元素。
1) 我该怎么做?
2) DOM Parser 可以吗? DOM 解析器的大小限制是多少?
谢谢!
如果您有一个非常大的源 XML(比如您的 0.5 GB 文件),并希望从中提取信息,可能会创建一个新的 XML,您可以考虑使用事件-基于解析器,不需要将整个 XML 加载到内存中。这些实现中最简单的是 SAX 解析器,它要求您编写一个事件侦听器,它将捕获文档开始、元素开始、元素结束等事件,您可以在其中检查正在读取的数据(名称元素、属性等)并决定是要忽略它还是对数据做一些事情。
搜索使用 JAXP 的 SAX 教程,您应该会找到几个示例。您可能要考虑的另一种策略是 StAX,具体取决于您要执行的操作。
这是一个使用 SAX 从 XML 文件中读取数据并根据搜索条件提取一些信息的简单示例。这是我用来教授 SAX 处理的一个非常简单的示例。我认为这可能有助于您了解其工作原理。搜索条件是固定的,由电影导演的名字组成,可以在一个巨大的 XML 中搜索,并根据 IMDB 数据生成电影选择。
XML 源代码示例("source.xml" ~300MB 文件)
<Movies>
...
<Movie>
<Imdb>tt1527186</Imdb>
<Title>Melancholia</Title>
<Director>Lars von Trier</Director>
<Year>2011</Year>
<Duration>136</Duration>
</Movie>
<Movie>
<Imdb>tt0060390</Imdb>
<Title>Fahrenheit 451</Title>
<Director>François Truffaut</Director>
<Year>1966</Year>
<Duration>112</Duration>
</Movie>
<Movie>
<Imdb>tt0062622</Imdb>
<Title>2001: A Space Odyssey</Title>
<Director>Stanley Kubrick</Director>
<Year>1968</Year>
<Duration>160</Duration>
</Movie>
...
</Movies>
这是事件处理程序的示例。它通过匹配字符串来选择 Movie
元素。我扩展了 DefaultHandler
并实现了 startElement()
(在找到开始标记时调用),characters()
(在读取字符块时调用),endElement()
(在结束时调用找到标签)和 endDocument()
(在文档完成时调用一次)。由于读取的数据并没有保留在内存中,所以你必须自己保存你感兴趣的数据。我使用了一些布尔标志和实例变量来保存当前标签、当前数据等
class ExtractMovieSaxHandler extends DefaultHandler {
// These are some parameters for the search which will select
// the subtrees (they will receive data when we set up the parser)
private String tagToMatch;
private String tagContents; // OR match
private boolean strict = false; // if strict matches will be exact
/**
* Sets criteria to select and copy Movie elements from source XML.
*
* @param tagToMatch Must contain text only
* @param tagContents Text contents of the tag
* @param strict If true, match must be exact
*/
public void setSearchCriteria(String tagToMatch, String tagContents, boolean strict) {
this.tagToMatch = tagToMatch;
this.tagContents = tagContents;
this.strict = strict;
}
// These are the temporary values we store as we parse the file
private String currentElement;
private StringBuilder contents = null; // if not null we are in Movie tag
private String currentData;
List<String> result = new ArrayList<String>(); // store resulting nodes here
private boolean skip = false;
...
这些方法都执行了ContentHandler
。第一个检测到找到一个元素(开始标记)。我们将标签名称(Movie
的子项)保存在一个变量中,因为它可能是我们在搜索中使用的名称:
...
@Override
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
// Store the current element that started now
currentElement = qName;
// If this is a Movie tag, save the contents because we might need it
if (qName.equals("Movie")) {
contents = new StringBuilder();
}
}
...
每次调用一段字符时都会调用这个。我们检查这些字符是否出现在我们感兴趣的元素内。如果是,我们就匹配内容,如果匹配就保存
...
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
// if we discovered that we don't need this data, we skip it
if (skip || currentElement == null) {
return;
}
// If we are inside the tag we want to search, save the contents
currentData = new String(ch, start, length);
if (currentElement.equals(tagToMatch)) {
boolean discard = true;
if (strict) {
if (currentData.equals(tagContents)) { // exact match
discard = false;
}
} else {
if (currentData.toLowerCase().indexOf(tagContents.toLowerCase()) >= 0) { // matches occurrence of substring
discard = false;
}
}
if (discard) {
skip = true;
}
}
}
...
找到结束标记时调用此方法。如果愿意,我们现在可以将其附加到我们正在内存中构建的文档中。
...
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
// Rebuild the XML if it's a node we didn't skip
if (qName.equals("Movie")) {
if (!skip) {
result.add(contents.insert(0, "<Movie>").append("</Movie>").toString());
}
// reset the variables so we can check the next node
contents = null;
skip = false;
} else if (contents != null && !skip) {
contents.append("<").append(qName).append(">")
.append(currentData)
.append("</").append(qName).append(">");
}
currentElement = null;
}
...
最后,这个在文档结束时被调用。最后我也用它来打印结果。
...
@Override
public void endDocument() throws SAXException {
StringBuilder resultFile = new StringBuilder();
resultFile.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
resultFile.append("<Movies>");
for (String childNode : result) {
resultFile.append(childNode.toString());
}
resultFile.append("</Movies>");
System.out.println("=== Resulting XML containing Movies where " + tagToMatch + " is one of " + tagContents + " ===");
System.out.println(resultFile.toString());
}
}
这是一个小型 Java 应用程序,它加载该文件并使用事件处理程序提取数据。
public class SAXReaderExample {
public static final String PATH = "src/main/resources"; // this is where I put the XML file
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
// Obtain XML Reader
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
XMLReader reader = sp.getXMLReader();
// Instantiate SAX handler
ExtractMovieSaxHandler handler = new ExtractMovieSaxHandler();
// set search criteria
handler.setSearchCriteria("Director", "Kubrick", false);
// Register handler with XML reader
reader.setContentHandler(handler);
// Parse the XML
reader.parse(new InputSource(new FileInputStream(new File(PATH, "source.xml"))));
}
}
这是处理后的结果文件:
<?xml version="1.0" encoding="UTF-8"?>
<Movies>
<Movie>
<Imdb>tt0062622</Imdb>
<Title>2001: A Space Odyssey</Title>
<Director>Stanley Kubrick</Director>
<Year>1968</Year>
<Duration>160</Duration>
</Movie>
<Movie>
<Imdb>tt0066921</Imdb>
<Title>A Clockwork Orange</Title>
<Director>Stanley Kubrick</Director>
<Year>1972</Year>
<Duration>136</Duration>
</Movie>
<Movie>
<Imdb>tt0081505</Imdb>
<Title>The Shining</Title>
<Director>Stanley Kubrick</Director>
<Year>1980</Year>
<Duration>144</Duration>
</Movie>
...
</Movies>
您的情况可能有所不同,但此示例显示了一个通用解决方案,您可能可以根据自己的问题进行调整。您可以在有关 SAX 和 JAXP 的教程中找到更多信息。
500Mb 完全在使用 XSLT 所能达到的极限之内。这在一定程度上取决于您要花费多少精力来开发最佳解决方案:即,您的时间还是机器的时间哪个更昂贵?
我想使用一个 XML 文件,结构复杂,大小约为一半,并从中创建另一个 XML 文件,仅包含原始文件的选定元素。
1) 我该怎么做?
2) DOM Parser 可以吗? DOM 解析器的大小限制是多少?
谢谢!
如果您有一个非常大的源 XML(比如您的 0.5 GB 文件),并希望从中提取信息,可能会创建一个新的 XML,您可以考虑使用事件-基于解析器,不需要将整个 XML 加载到内存中。这些实现中最简单的是 SAX 解析器,它要求您编写一个事件侦听器,它将捕获文档开始、元素开始、元素结束等事件,您可以在其中检查正在读取的数据(名称元素、属性等)并决定是要忽略它还是对数据做一些事情。
搜索使用 JAXP 的 SAX 教程,您应该会找到几个示例。您可能要考虑的另一种策略是 StAX,具体取决于您要执行的操作。
这是一个使用 SAX 从 XML 文件中读取数据并根据搜索条件提取一些信息的简单示例。这是我用来教授 SAX 处理的一个非常简单的示例。我认为这可能有助于您了解其工作原理。搜索条件是固定的,由电影导演的名字组成,可以在一个巨大的 XML 中搜索,并根据 IMDB 数据生成电影选择。
XML 源代码示例("source.xml" ~300MB 文件)
<Movies>
...
<Movie>
<Imdb>tt1527186</Imdb>
<Title>Melancholia</Title>
<Director>Lars von Trier</Director>
<Year>2011</Year>
<Duration>136</Duration>
</Movie>
<Movie>
<Imdb>tt0060390</Imdb>
<Title>Fahrenheit 451</Title>
<Director>François Truffaut</Director>
<Year>1966</Year>
<Duration>112</Duration>
</Movie>
<Movie>
<Imdb>tt0062622</Imdb>
<Title>2001: A Space Odyssey</Title>
<Director>Stanley Kubrick</Director>
<Year>1968</Year>
<Duration>160</Duration>
</Movie>
...
</Movies>
这是事件处理程序的示例。它通过匹配字符串来选择 Movie
元素。我扩展了 DefaultHandler
并实现了 startElement()
(在找到开始标记时调用),characters()
(在读取字符块时调用),endElement()
(在结束时调用找到标签)和 endDocument()
(在文档完成时调用一次)。由于读取的数据并没有保留在内存中,所以你必须自己保存你感兴趣的数据。我使用了一些布尔标志和实例变量来保存当前标签、当前数据等
class ExtractMovieSaxHandler extends DefaultHandler {
// These are some parameters for the search which will select
// the subtrees (they will receive data when we set up the parser)
private String tagToMatch;
private String tagContents; // OR match
private boolean strict = false; // if strict matches will be exact
/**
* Sets criteria to select and copy Movie elements from source XML.
*
* @param tagToMatch Must contain text only
* @param tagContents Text contents of the tag
* @param strict If true, match must be exact
*/
public void setSearchCriteria(String tagToMatch, String tagContents, boolean strict) {
this.tagToMatch = tagToMatch;
this.tagContents = tagContents;
this.strict = strict;
}
// These are the temporary values we store as we parse the file
private String currentElement;
private StringBuilder contents = null; // if not null we are in Movie tag
private String currentData;
List<String> result = new ArrayList<String>(); // store resulting nodes here
private boolean skip = false;
...
这些方法都执行了ContentHandler
。第一个检测到找到一个元素(开始标记)。我们将标签名称(Movie
的子项)保存在一个变量中,因为它可能是我们在搜索中使用的名称:
...
@Override
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
// Store the current element that started now
currentElement = qName;
// If this is a Movie tag, save the contents because we might need it
if (qName.equals("Movie")) {
contents = new StringBuilder();
}
}
...
每次调用一段字符时都会调用这个。我们检查这些字符是否出现在我们感兴趣的元素内。如果是,我们就匹配内容,如果匹配就保存
...
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
// if we discovered that we don't need this data, we skip it
if (skip || currentElement == null) {
return;
}
// If we are inside the tag we want to search, save the contents
currentData = new String(ch, start, length);
if (currentElement.equals(tagToMatch)) {
boolean discard = true;
if (strict) {
if (currentData.equals(tagContents)) { // exact match
discard = false;
}
} else {
if (currentData.toLowerCase().indexOf(tagContents.toLowerCase()) >= 0) { // matches occurrence of substring
discard = false;
}
}
if (discard) {
skip = true;
}
}
}
...
找到结束标记时调用此方法。如果愿意,我们现在可以将其附加到我们正在内存中构建的文档中。
...
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
// Rebuild the XML if it's a node we didn't skip
if (qName.equals("Movie")) {
if (!skip) {
result.add(contents.insert(0, "<Movie>").append("</Movie>").toString());
}
// reset the variables so we can check the next node
contents = null;
skip = false;
} else if (contents != null && !skip) {
contents.append("<").append(qName).append(">")
.append(currentData)
.append("</").append(qName).append(">");
}
currentElement = null;
}
...
最后,这个在文档结束时被调用。最后我也用它来打印结果。
...
@Override
public void endDocument() throws SAXException {
StringBuilder resultFile = new StringBuilder();
resultFile.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
resultFile.append("<Movies>");
for (String childNode : result) {
resultFile.append(childNode.toString());
}
resultFile.append("</Movies>");
System.out.println("=== Resulting XML containing Movies where " + tagToMatch + " is one of " + tagContents + " ===");
System.out.println(resultFile.toString());
}
}
这是一个小型 Java 应用程序,它加载该文件并使用事件处理程序提取数据。
public class SAXReaderExample {
public static final String PATH = "src/main/resources"; // this is where I put the XML file
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
// Obtain XML Reader
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
XMLReader reader = sp.getXMLReader();
// Instantiate SAX handler
ExtractMovieSaxHandler handler = new ExtractMovieSaxHandler();
// set search criteria
handler.setSearchCriteria("Director", "Kubrick", false);
// Register handler with XML reader
reader.setContentHandler(handler);
// Parse the XML
reader.parse(new InputSource(new FileInputStream(new File(PATH, "source.xml"))));
}
}
这是处理后的结果文件:
<?xml version="1.0" encoding="UTF-8"?>
<Movies>
<Movie>
<Imdb>tt0062622</Imdb>
<Title>2001: A Space Odyssey</Title>
<Director>Stanley Kubrick</Director>
<Year>1968</Year>
<Duration>160</Duration>
</Movie>
<Movie>
<Imdb>tt0066921</Imdb>
<Title>A Clockwork Orange</Title>
<Director>Stanley Kubrick</Director>
<Year>1972</Year>
<Duration>136</Duration>
</Movie>
<Movie>
<Imdb>tt0081505</Imdb>
<Title>The Shining</Title>
<Director>Stanley Kubrick</Director>
<Year>1980</Year>
<Duration>144</Duration>
</Movie>
...
</Movies>
您的情况可能有所不同,但此示例显示了一个通用解决方案,您可能可以根据自己的问题进行调整。您可以在有关 SAX 和 JAXP 的教程中找到更多信息。
500Mb 完全在使用 XSLT 所能达到的极限之内。这在一定程度上取决于您要花费多少精力来开发最佳解决方案:即,您的时间还是机器的时间哪个更昂贵?