有没有一种简单的方法,可以使用XPath查询(或在每台linux/osx机器中都可以轻松找到的任何其他命令行工具)从大型XML文件中提取原始文件的子集?
具体来说,我有一个大的xml文件,格式为:
<root>
<header>...<>
<item name="1">...<>
<item name="2">...<>
...
<item name="1000000">..<>
</root>
并且我希望输出一个较小的XML文件,其中包括头的前k个项目(比如10个)。附带说明一下,请考虑文件可能已损坏。从本质上讲,我正在寻找一个类似于head
的命令,该命令使用SAX解析器解析XML文件(为了不占用内存,并对文件的过早终止具有弹性)。
我认为xsl:iterate
的流式处理允许在XSLT3.0中实现这一点,正如Saxon 9.7 EE目前所实现的那样(这显然不是一个在LINUX上很容易获得的命令行工具,但由于它确实解决了这个问题,我认为值得一提):假设一个格式不好的名为test2015122701.xml
的XML,格式为
<root>
<header>...</header>
<item name="1">...</item>
<item name="2">...</item>
<item name="3">...</item>
<item name="4">...</item>
<item>
</root>
和带有代码的XSLT3.0样式表
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:param name="items-to-copy" as="xs:integer" select="4"/>
<xsl:variable name="children-to-copy" as="xs:integer" select="$items-to-copy + 1"/>
<xsl:param name="input-uri" as="xs:string" select="'test2015122701.xml'"/>
<xsl:output indent="yes"/>
<xsl:template name="main" match="/">
<root>
<xsl:stream href="{$input-uri}">
<xsl:iterate select="root/*">
<xsl:copy-of select="."/>
<xsl:if test="position() eq $children-to-copy">
<xsl:break/>
</xsl:if>
</xsl:iterate>
</xsl:stream>
</root>
</xsl:template>
</xsl:stylesheet>
Saxon 9.7 EE,当与java -jar saxon9ee.jar -it:main -xsl:sheet.xsl
一起运行时,会产生以下输出:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<header>...</header>
<item name="1">...</item>
<item name="2">...</item>
<item name="3">...</item>
<item name="4">...</item>
</root>
如果我们使用-t
命令行选项来检查处理的一些细节,我们会看到:
Streaming file:/C:/Users/Martin%20Honnen/Documents/xslt/test2015122701.xml
URIResolver.resolve href="test2015122701.xml" base="file:/C:/Users/Martin%20Honnen/Documents/xslt/test2015122702.xsl"
Using parser com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser
Streaming test2015122701.xml : early exit
因此Saxon实际上只处理文件的开头,并在遇到第一个元素之后格式不好的标记之前退出。
作为替代方案,这里有一个使用Perl和XML::Twig:的示例
use strict;
use XML::Twig;
my $itemCount = 0;
my $breakCount = 4;
sub count_items {
my ($t, $item) = @_;
$itemCount++;
if ($itemCount == $breakCount) {
$t->finish_now();
}
}
my $input = 'input.xml';
my $result = 'output.xml';
my $twig = XML::Twig->new(
twig_handlers => { item => &count_items},
pretty_print => 'indented'
);
$twig->parsefile($input);
$twig->print_to_file($result);
恐怕我不知道Perl和XML::Twig在LINUX上的支持程度,我已经测试了上面的内容,以便在带有XML::twg 3.49的Windows上使用Perl5.20.3。