是否有适用于 Ubuntu 和/或 CentOS 的软件包,它有一个命令行工具,可以执行像 foo //element@attribute filename.xml
或 foo //element@attribute < filename.xml
这样的 XPath 单行程序并逐行返回结果?
我正在寻找可以让我只使用 apt-get install foo
或 yum install foo
的东西,然后就可以开箱即用,不需要包装器或其他改编。
以下是一些接近的例子:
诺科切里。如果我编写这个包装器,我可以按上述方式调用包装器:
#!/usr/bin/ruby
require 'nokogiri'
Nokogiri::XML(STDIN).xpath(ARGV[0]).each do |row|
puts row
end
XML::XPath。可以使用这个包装器:
#!/usr/bin/perl
use strict;
use warnings;
use XML::XPath;
my $root = XML::XPath->new(ioref => 'STDIN');
for my $node ($root->find($ARGV[0])->get_nodelist) {
print($node->getData, "\n");
}
来自 XML::XPath 的 xpath
返回太多噪音,-- NODE --
和 attribute = "value"
。
来自 XML::Twig 的 xml_grep
无法处理不返回元素的表达式,因此不能用于提取属性值而无需进一步处理。
编辑:
echo cat //element/@attribute | xmllint --shell filename.xml
返回类似于 xpath
的噪声。
xmllint --xpath //element/@attribute filename.xml
返回 attribute = "value"
。
xmllint --xpath 'string(//element/@attribute)' filename.xml
返回我想要的,但仅适用于第一个匹配项。
对于几乎满足该问题的另一个解决方案,这里是一个 XSLT,可用于评估任意 XPath 表达式(需要 XSLT 处理器中的 dyn:evaluate 支持):
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:dyn="http://exslt.org/dynamic" extension-element-prefixes="dyn">
<xsl:output omit-xml-declaration="yes" indent="no" method="text"/>
<xsl:template match="/">
<xsl:for-each select="dyn:evaluate($pattern)">
<xsl:value-of select="dyn:evaluate($value)"/>
<xsl:value-of select="' '"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
使用 xsltproc --stringparam pattern //element/@attribute --stringparam value . arbitrary-xpath.xslt filename.xml
运行。
xpath
的“噪音”在 STDERR 而不是 STDOUT 上。
你应该试试这些工具:
xmlstarlet : 可以编辑、选择、转换... 默认未安装,xpath1
xmllint : 通常默认安装 libxml2-utils, xpath1 (检查我的包装器有 --xpath 开关非常旧的版本和换行符分隔输出 (v < 2.9.9)
xpath : 通过 perl 的模块 XML::XPath, xpath1 安装
xml_grep : 通过 perl 的模块 XML::Twig, xpath1 安装(有限的 xpath 使用)
西德尔:xpath3
saxon-lint :我自己的项目,包装@Michael Kay 的 Saxon-HE Java 库,xpath3
xmllint
带有 libxml2-utils
(可以用作带有 --shell
开关的交互式 shell)
xmlstarlet
是 xmlstarlet
。
xpath
带有 perl 的模块 XML::Xpath
xml_grep
带有 perl 的模块 XML::Twig
xidel
是 xidel
saxon-lint
使用 SaxonHE 9.6 ,XPath 3.x(+复古兼容性)
前任 :
xmllint --xpath '//element/@attribute' file.xml
xmlstarlet sel -t -v "//element/@attribute" file.xml
xpath -q -e '//element/@attribute' file.xml
xidel -se '//element/@attribute' file.xml
saxon-lint --xpath '//element/@attribute' file.xml
xmlstarlet 页面
人 xmllint
xpath 页面
xml_grep
西德尔
撒克逊林特
.
你也可以试试我的Xidel。它不在存储库中的包中,但您可以从网页下载它(它没有依赖项)。
它具有用于此任务的简单语法:
xidel filename.xml -e '//element/@attribute'
它是支持 XPath 2 的少数工具之一。
find . -name "*.xml" -printf '%p : ' -exec xidel {} -s -e 'expr' \;
一个很可能已经安装在系统上的软件包是 python-lxml
。如果是这样,则无需安装任何额外的软件包即可:
python -c "from lxml.etree import parse; from sys import stdin; print('\n'.join(parse(stdin).xpath('//element/@attribute')))"
stdin
。这消除了在已经相当冗长的单行中包含 open()
和 close()
的需要。要解析文件,只需运行 python -c "from lxml.etree import parse; from sys import stdin; print '\n'.join(parse(stdin).xpath('//element/@attribute'))" < my_file.xml
并让您的 shell 处理文件查找、打开和关闭。
在我搜索查询 maven pom.xml 文件时,我遇到了这个问题。但是我有以下限制:
必须跨平台运行。
必须存在于所有主要的 linux 发行版上,无需任何额外的模块安装
必须处理复杂的 xml 文件,例如 maven pom.xml 文件
简单的语法
我已经尝试了上述许多方法但没有成功:
python lxml.etree 不是标准 python 发行版的一部分
xml.etree 是但不能很好地处理复杂的 maven pom.xml 文件,还没有深入挖掘
python xml.etree 不处理 maven pom.xml 文件,原因不明
xmllint 也不起作用,核心转储经常在 ubuntu 12.04 “xmllint:使用 libxml 版本 20708”
我遇到的稳定、简短且可在许多平台上工作且成熟的解决方案是 ruby 中内置的 rexml lib:
ruby -r rexml/document -e 'include REXML;
puts XPath.first(Document.new($stdin), "/project/version/text()")' < pom.xml
启发我找到这篇文章的是以下文章:
Ruby/XML、XSLT 和 XPath 教程
IBM:Ruby on Rails 和 XML
xmlstarlet
作为接受的答案,因为它符合我更广泛的标准,而且它非常简洁。但我可能会不时使用您的解决方案。
puts
而不是 p
。
Saxon 不仅对 XPath 2.0 这样做,而且对 XQuery 1.0 和(在商业版本中)3.0 也这样做。它不是作为 Linux 软件包提供的,而是作为 jar 文件提供的。语法(您可以轻松地将其包装在一个简单的脚本中)是
java net.sf.saxon.Query -s:source.xml -qs://element/attribute
2020 更新
Saxon 10.0 包括 Gizmo 工具,可以从命令行交互或批量使用。例如
java net.sf.saxon.Gizmo -s:source.xml
/>show //element/@attribute
/>quit
libsaxonb-java
,但如果我运行 saxonb-xquery -qs://element/@attribute -s:filename.xml
,我会得到 SENR0001: Cannot serialize a free-standing attribute node
,与例如 xml_grep
的问题相同。
-qs
,如下所示:'-qs:declare namespace mets="http://www.loc.gov/METS/";/mets:mets/mets:dmdSec'
您可能还对 xsh 感兴趣。它具有交互模式,您可以在其中对文档进行任何您喜欢的操作:
open 1.xml ;
ls //element/@id ;
for //p[@class="first"] echo text() ;
cpan XML::XSH2
从 CPAN 安装。
cpan XML::XSH2
无法安装任何东西。
clacke’s answer 很好,但我认为只有当您的源代码是格式良好的 XML,而不是普通的 HTML 时才有效。
所以要对普通的 Web 内容做同样的事情——HTML 文档不一定是格式良好的 XML:
echo "<p>foo<div>bar</div><p>baz" | python -c "from sys import stdin; \
from lxml import html; \
print '\n'.join(html.tostring(node) for node in html.parse(stdin).xpath('//p'))"
并且改为使用 html5lib(以确保您获得与 Web 浏览器相同的解析行为——因为与浏览器解析器一样,html5lib 符合 HTML 规范中的解析要求)。
echo "<p>foo<div>bar</div><p>baz" | python -c "from sys import stdin; \
import html5lib; from lxml import html; \
doc = html5lib.parse(stdin, treebuilder='lxml', namespaceHTMLElements=False); \
print '\n'.join(html.tostring(node) for node in doc.xpath('//p'))
与 Mike 和 clacke 的答案类似,这里是 python one-liner(使用 python >= 2.5)从 pom.xml 文件中获取构建版本,它绕过了 pom.xml 文件通常没有 dtd 或默认命名空间,所以在 libxml 中看起来格式不正确:
python -c "import xml.etree.ElementTree as ET; \
print(ET.parse(open('pom.xml')).getroot().find('\
{http://maven.apache.org/POM/4.0.0}version').text)"
在 Mac 和 Linux 上测试,不需要安装任何额外的软件包。
lxml
也没有 xmllint
,甚至没有 Ruby。本着 my own answer 中格式的精神,我在 bash 中将其写为 python3 -c "from xml.etree.ElementTree import parse; from sys import stdin; print(parse(stdin).find('.//element[subelement=\"value\"]/othersubelement').text)" <<< "$variable_containing_xml"
。 .getroot()
似乎没有必要。
除了 XML::XSH 和 XML::XSH2 之外,还有一些类似 grep
的实用程序不如 App::xml_grep2
和 XML::Twig
(其中包括 xml_grep
而不是 xml_grep2
)。在处理大型或大量 XML 文件以用于快速单行器或 Makefile
目标时,这些可能非常有用。当您想要比您的 $SHELL
和 xmllint
xstlproc
提供更多的处理时,XML::Twig
特别适合用于 perl
脚本方法。
应用程序名称中的编号方案表明“2”版本是本质上相同工具的更新/更新版本,可能需要其他模块(或 perl
本身)的更新版本。
xml_grep2 -t //element@attribute filename.xml
可以正常工作并按照我的预期运行(xml_grep --root //element@attribute --text_only filename.xml
仍然没有,返回“无法识别的表达式”错误)。伟大的!
xml_grep --pretty_print --root '//element[@attribute]' --text_only filename.xml
呢?不确定在这种情况下发生了什么或 XPath 对 []
的描述,但用方括号括起来的 @attribute
适用于 xml_grep
和 xml_grep2
。
//element/@attribute
,而不是 //element@attribute
。显然无法对其进行编辑,但将其留在那里而不是删除+替换,以免混淆此讨论的历史。
//element[@attribute]
选择具有属性 attribute
的 element
类型元素。我不想要元素,只想要属性。 <element attribute='foo'/>
应该给我 foo
,而不是完整的 <element attribute='foo'/>
。
--text_only
在像 <element attribute='foo'/>
这样的元素内部没有文本节点的情况下为我提供了空字符串。
我尝试了几个命令行 XPath 实用程序,当我意识到我花了太多时间在谷歌上搜索并弄清楚它们是如何工作的,所以我用 Python 编写了最简单的 XPath 解析器,它可以满足我的需要。
如果 XPath 表达式计算为字符串,则下面的脚本显示字符串值,如果结果是节点,则显示整个 XML 子节点:
#!/usr/bin/env python
import sys
from lxml import etree
tree = etree.parse(sys.argv[1])
xpath = sys.argv[2]
for e in tree.xpath(xpath):
if isinstance(e, str):
print(e)
else:
print((e.text and e.text.strip()) or etree.tostring(e))
它使用 lxml
— 一个用 C 语言编写的快速 XML 解析器,它不包含在标准 python 库中。使用 pip install lxml
安装它。在 Linux/OSX 上可能需要使用 sudo
作为前缀。
用法:
python xmlcat.py file.xml "//mynode"
lxml 也可以接受一个 URL 作为输入:
python xmlcat.py http://example.com/file.xml "//mynode"
提取附件节点下的 url 属性,即 <enclosure url="http:...""..>)
:
python xmlcat.py xmlcat.py file.xml "//enclosure/@url"
谷歌浏览器中的 Xpath
作为一个不相关的附注:如果您想对网页的标记运行 XPath 表达式,那么您可以直接从 Chrome 开发工具中执行此操作:右键单击 Chrome 中的页面 >选择 Inspect,然后在 DevTools 控制台中将 XPath 表达式粘贴为 $x("//spam/eggs")
。
获取此页面上的所有作者:
$x("//*[@class='user-details']/a/text()")
这是一个 xmlstarlet 用例,用于从嵌套元素 elem1、elem2 中提取数据到此类 XML 中的一行文本(还展示了如何处理命名空间):
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<mydoctype xmlns="http://xml-namespace-uri" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xml-namespace-uri http://xsd-uri" format="20171221A" date="2018-05-15">
<elem1 time="0.586" length="10.586">
<elem2 value="cue-in" type="outro" />
</elem1>
</mydoctype>
输出将是
0.586 10.586 cue-in outro
在这个片段中,-m 匹配嵌套的 elem2,-v 输出属性值(带有表达式和相对寻址),-o 文字文本,-n 添加一个换行符:
xml sel -N ns="http://xml-namespace-uri" -t -m '//ns:elem1/ns:elem2' \
-v ../@time -o " " -v '../@time + ../@length' -o " " -v @value -o " " -v @type -n file.xml
如果需要来自 elem1 的更多属性,可以这样做(也显示 concat() 函数):
xml sel -N ns="http://xml-namespace-uri" -t -m '//ns:elem1/ns:elem2/..' \
-v 'concat(@time, " ", @time + @length, " ", ns:elem2/@value, " ", ns:elem2/@type)' -n file.xml
请注意命名空间(ns,用 -N 声明)的(IMO 不必要的)复杂性,这让我几乎放弃了 xpath 和 xmlstarlet,并编写了一个快速的临时转换器。
我的 Python 脚本 xgrep.py 正是这样做的。为了在文件 filename.xml ...
中搜索元素 element
的所有属性 attribute
,您可以按如下方式运行它:
xgrep.py "//element/@attribute" filename.xml ...
有各种控制输出的开关,例如 -c
用于计算匹配项,-i
用于缩进匹配部分,-l
用于仅输出文件名。
该脚本不能作为 Debian 或 Ubuntu 软件包使用,但它的所有依赖项都是可用的。
安装 BaseX 数据库,然后像这样使用它的 "standalone command-line mode":
basex -i - //element@attribute < filename.xml
或者
basex -i filename.xml //element@attribute
查询语言实际上是 XQuery (3.0),而不是 XPath,但由于 XQuery 是 XPath 的超集,您可以使用 XPath 查询而无需注意。
由于这个项目显然是相当新的,请查看 https://github.com/jeffbr13/xq ,它似乎是 lxml
的包装,但这就是您真正需要的(并且在其他答案中也使用 lxml 发布了临时解决方案)
我对用于 HTML XPath 查询的 Python 单行代码不满意,所以我自己编写了代码。假设您安装了 python-lxml
软件包或运行了 pip install --user lxml
:
function htmlxpath() { python -c 'for x in __import__("lxml.html").html.fromstring(__import__("sys").stdin.read()).xpath(__import__("sys").argv[1]): print(x)' $1 }
一旦你有了它,你可以像在这个例子中一样使用它:
> curl -s https://slashdot.org | htmlxpath '//title/text()'
Slashdot: News for nerds, stuff that matters
很抱歉成为战斗中的另一个声音。我尝试了这个线程中的所有工具,发现它们都不能满足我的需求,所以我自己写了。您可以在这里找到它:https://github.com/charmparticle/xpe
它已上传到 pypi,因此您可以使用 pip3 轻松安装它,如下所示:
sudo pip3 install xpe
安装后,您可以使用它来针对各种输入运行 xpath 表达式,其灵活性与在 selenium 或 javascript 中使用 xpath 所获得的灵活性相同。是的,你可以使用 xpaths 来对抗 HTML。
即使在顶部存在命名空间声明时也可以使用的解决方案:
如果 xml 在顶部声明了命名空间,则答案中提出的大多数命令都不能开箱即用。考虑一下:
输入xml:
<elem1 xmlns="urn:x" xmlns:prefix="urn:y">
<elem2 attr1="false" attr2="value2">
elem2 value
</elem2>
<elem2 attr1="true" attr2="value2.1">
elem2.1 value
</elem2>
<prefix:elem3>
elem3 value
</prefix:elem3>
</elem1>
不工作:
xmlstarlet sel -t -v "/elem1" input.xml
# nothing printed
xmllint -xpath "/elem1" input.xml
# XPath set is empty
解决方案:
# Requires >=java11 to run like below (but the code requires >=java17 for case syntax to be recognized)
# Prints the whole document
java ExtractXpath.java "/" example-inputs/input.xml
# Prints the contents and self of "elem1"
java ExtractXpath.java "/elem1" input.xml
# Prints the contents and self of "elem2" whose attr2 value is: 'value2'
java ExtractXpath.java "//elem2[@attr2='value2']" input.xml
# Prints the value of the attribute 'attr2': "value2", "value2.1"
java ExtractXpath.java "/elem1/elem2/@attr2" input.xml
# Prints the text inside elem3: "elem3 value"
java ExtractXpath.java "/elem1/elem3/text()" input.xml
# Prints the name of the matched element: "prefix:elem3"
java ExtractXpath.java "name(/elem1/elem3)" input.xml
# Same as above: "prefix:elem3"
java ExtractXpath.java "name(*/elem3)" input.xml
# Prints the count of the matched elements: 2.0
java ExtractXpath.java "count(/elem2)" input.xml
# known issue: while "//elem2" works. "//elem3" does not (it works only with: '*/elem3' )
提取Xpath.java:
import java.io.File;
import java.io.FileInputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathEvaluationResult;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class ExtractXpath {
public static void main(String[] args) throws Exception {
assertThat(args.length==2, "Wrong number of args");
String xpath = args[0];
File file = new File(args[1]);
assertThat(file.isFile(), file.getAbsolutePath()+" is not a file.");
FileInputStream fileIS = new FileInputStream(file);
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder();
Document xmlDocument = builder.parse(fileIS);
XPath xPath = XPathFactory.newInstance().newXPath();
String expression = xpath;
XPathExpression xpathExpression = xPath.compile(expression);
XPathEvaluationResult xpathEvalResult = xpathExpression.evaluateExpression(xmlDocument);
System.out.println(applyXpathExpression(xmlDocument, xpathExpression, xpathEvalResult.type().name()));
}
private static String applyXpathExpression(Document xmlDocument, XPathExpression expr, String xpathTypeName) throws TransformerConfigurationException, TransformerException, XPathExpressionException {
// see: https://www.w3.org/TR/1999/REC-xpath-19991116/#corelib
List<String> retVal = new ArrayList();
if(xpathTypeName.equals(XPathConstants.NODESET.getLocalPart())){ //e.g. xpath: /elem1/*
NodeList nodeList = (NodeList)expr.evaluate(xmlDocument, XPathConstants.NODESET);
for (int i = 0; i < nodeList.getLength(); i++) {
retVal.add(convertNodeToString(nodeList.item(i)));
}
}else if(xpathTypeName.equals(XPathConstants.STRING.getLocalPart())){ //e.g. xpath: name(/elem1/*)
retVal.add((String)expr.evaluate(xmlDocument, XPathConstants.STRING));
}else if(xpathTypeName.equals(XPathConstants.NUMBER.getLocalPart())){ //e.g. xpath: count(/elem1/*)
retVal.add(((Number)expr.evaluate(xmlDocument, XPathConstants.NUMBER)).toString());
}else if(xpathTypeName.equals(XPathConstants.BOOLEAN.getLocalPart())){ //e.g. xpath: contains(elem1, 'sth')
retVal.add(((Boolean)expr.evaluate(xmlDocument, XPathConstants.BOOLEAN)).toString());
}else if(xpathTypeName.equals(XPathConstants.NODE.getLocalPart())){ //e.g. xpath: fixme: find one
System.err.println("WARNING found xpathTypeName=NODE");
retVal.add(convertNodeToString((Node)expr.evaluate(xmlDocument, XPathConstants.NODE)));
}else{
throw new RuntimeException("Unexpected xpath type name: "+xpathTypeName+". This should normally not happen");
}
return retVal.stream().map(str->"==MATCH_START==\n"+str+"\n==MATCH_END==").collect(Collectors.joining ("\n"));
}
private static String convertNodeToString(Node node) throws TransformerConfigurationException, TransformerException {
short nType = node.getNodeType();
switch (nType) {
case Node.ATTRIBUTE_NODE , Node.TEXT_NODE -> {
return node.getNodeValue();
}
case Node.ELEMENT_NODE, Node.DOCUMENT_NODE -> {
StringWriter writer = new StringWriter();
Transformer trans = TransformerFactory.newInstance().newTransformer();
trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
trans.setOutputProperty(OutputKeys.INDENT, "yes");
trans.transform(new DOMSource(node), new StreamResult(writer));
return writer.toString();
}
default -> {
System.err.println("WARNING: FIXME: Node type:"+nType+" could possibly be handled in a better way.");
return node.getNodeValue();
}
}
}
private static void assertThat(boolean b, String msg) {
if(!b){
System.err.println(msg+"\n\nUSAGE: program xpath xmlFile");
System.exit(-1);
}
}
}
@SuppressWarnings("unchecked")
class NamespaceResolver implements NamespaceContext {
//Store the source document to search the namespaces
private final Document sourceDocument;
public NamespaceResolver(Document document) {
sourceDocument = document;
}
//The lookup for the namespace uris is delegated to the stored document.
@Override
public String getNamespaceURI(String prefix) {
if (prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)) {
return sourceDocument.lookupNamespaceURI(null);
} else {
return sourceDocument.lookupNamespaceURI(prefix);
}
}
@Override
public String getPrefix(String namespaceURI) {
return sourceDocument.lookupPrefix(namespaceURI);
}
@SuppressWarnings("rawtypes")
@Override
public Iterator getPrefixes(String namespaceURI) {
return null;
}
}
为简单起见:
xpath-extract
命令:
#!/bin/bash
java ExtractXpath.java "$1" "$2"
xmlstarlet sel -T -t -m '//element/@attribute' -v '.' -n filename.xml
完全符合我的要求!xmllint
不支持命令行参数--xpath
,但大多数似乎支持--shell
。稍微脏一点的输出,但在绑定中仍然有用。sel -t -m ... -v ...
示例:arstechnica.com/information-technology/2005/11/linux-20051115/2,匹配所有但是最后一个节点并将其保存为像我的用例一样的值表达式,我似乎仍然无法得到它,我只是得到空白输出..