ChatGPT解决这个技术问题 Extra ChatGPT

如何从 shell 执行 XPath 单行程序?

是否有适用于 Ubuntu 和/或 CentOS 的软件包,它有一个命令行工具,可以执行像 foo //element@attribute filename.xmlfoo //element@attribute < filename.xml 这样的 XPath 单行程序并逐行返回结果?

我正在寻找可以让我只使用 apt-get install fooyum 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="'&#10;'"/>
    </xsl:for-each> 
  </xsl:template>
</xsl:stylesheet>

使用 xsltproc --stringparam pattern //element/@attribute --stringparam value . arbitrary-xpath.xslt filename.xml 运行。

+1 提出好问题并集思广益,寻找一种简单可靠的方法在换行符上打印多个结果
请注意,来自 xpath 的“噪音”在 STDERR 而不是 STDOUT 上。
@miken32 不。我只想要输出的值。 hastebin.com/ekarexumeg.bash

G
Gilles Quenot

你应该试试这些工具:

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)

xmlstarletxmlstarlet

xpath 带有 perl 的模块 XML::Xpath

xml_grep 带有 perl 的模块 XML::Twig

xidelxidel

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

西德尔

撒克逊林特

.


出色的! xmlstarlet sel -T -t -m '//element/@attribute' -v '.' -n filename.xml 完全符合我的要求!
注意:传闻 xmlstarlet 已被废弃,但现在再次处于积极开发中。
注意:一些旧版本的 xmllint 不支持命令行参数 --xpath,但大多数似乎支持 --shell。稍微脏一点的输出,但在绑定中仍然有用。
我似乎仍然无法查询节点内容,而不是属性。任何人都可以为此提供一个例子吗?出于某种原因,我仍然发现 xmlstarlet 很难弄清楚并在匹配、值、根目录之间正确查看文档结构等。即使使用此页面中的第一个 sel -t -m ... -v ... 示例:arstechnica.com/information-technology/2005/11/linux-20051115/2,匹配所有但是最后一个节点并将其保存为像我的用例一样的值表达式,我似乎仍然无法得到它,我只是得到空白输出..
xpath 版本上的一个不错的版本-我只是遇到了其他出色的 xmllint 的限制
G
Gilles Quenot

你也可以试试我的Xidel。它不在存储库中的包中,但您可以从网页下载它(它没有依赖项)。

它具有用于此任务的简单语法:

xidel filename.xml -e '//element/@attribute' 

它是支持 XPath 2 的少数工具之一。


Xidel 看起来很酷,尽管您可能应该提到您也是您推荐的这个工具的作者。
Saxon 和 saxon-lint 使用 xpath3 ;)
Xidel (0..8.win32.zip) 在 Virustotal 上显示为恶意软件。因此,请自行承担风险virustotal.com/#/file/…
太棒了 - 我要在我的个人扳手工具箱中添加 xidel
好的!我必须对具有与给定 xpath 查询匹配的节点的 XML 文件运行递归搜索。将 xidel 与 find 一起使用,如下所示:find . -name "*.xml" -printf '%p : ' -exec xidel {} -s -e 'expr' \;
H
Heath Borders

一个很可能已经安装在系统上的软件包是 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 处理文件查找、打开和关闭。
M
Mike

在我搜索查询 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 作为接受的答案,因为它符合我更广泛的标准,而且它非常简洁。但我可能会不时使用您的解决方案。
我会将其添加到避免在结果周围使用引号,在 Ruby 命令中使用 puts 而不是 p
M
Michael Kay

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

SaxonB 在 Ubuntu 中,包 libsaxonb-java,但如果我运行 saxonb-xquery -qs://element/@attribute -s:filename.xml,我会得到 SENR0001: Cannot serialize a free-standing attribute node,与例如 xml_grep 的问题相同。
如果要查看此查询选择的属性节点的完整详细信息,请在命令行上使用 -wrap 选项。如果您只想要属性的字符串值,请将 /string() 添加到查询中。
谢谢。添加 /string() 更接近。但它输出一个 XML 标头并将所有结果放在一行中,所以仍然没有雪茄。
如果您不想要 XML 标头,请添加选项 !method=text。
要使用命名空间,请将其添加到 -qs,如下所示:'-qs:declare namespace mets="http://www.loc.gov/METS/";/mets:mets/mets:dmdSec'
c
choroba

您可能还对 xsh 感兴趣。它具有交互模式,您可以在其中对文档进行任何您喜欢的操作:

open 1.xml ;
ls //element/@id ;
for //p[@class="first"] echo text() ;

它似乎没有作为一个包提供,至少在 Ubuntu 中没有。
@clacke:不是,但可以通过 cpan XML::XSH2 从 CPAN 安装。
@choroba,我在 OS X 上尝试过,但安装失败,出现某种 makefile 错误。
@cnst:你安装了 XML::LibXML 吗?
@choroba,我不知道;但我的意思是,cpan XML::XSH2 无法安装任何东西。
2
2 revs, 2 users 98%

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'))

是的,我在这个问题中相信了我自己的假设,即 XPath 意味着 XML。这个答案是对这里其他答案的一个很好的补充,感谢您让我了解 html5lib!
p
pdr

与 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() 似乎没有必要。
G
G. Cito

除了 XML::XSHXML::XSH2 之外,还有一些类似 grep 的实用程序不如 App::xml_grep2XML::Twig(其中包括 xml_grep 而不是 xml_grep2)。在处理大型或大量 XML 文件以用于快速单行器或 Makefile 目标时,这些可能非常有用。当您想要比您的 $SHELLxmllint 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_grepxml_grep2
我的意思是 //element/@attribute,而不是 //element@attribute。显然无法对其进行编辑,但将其留在那里而不是删除+替换,以免混淆此讨论的历史。
//element[@attribute] 选择具有属性 attributeelement 类型元素。我不想要元素,只想要属性。 <element attribute='foo'/> 应该给我 foo,而不是完整的 <element attribute='foo'/>
...并且在这种情况下,--text_only 在像 <element attribute='foo'/> 这样的元素内部没有文本节点的情况下为我提供了空字符串。
G
Geoff Nixon

值得一提的是,nokogiri 本身附带了一个命令行工具,该工具应与 gem install nokogiri 一起安装。

您可能会找到 this blog post useful


c
ccpizza

我尝试了几个命令行 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()")

不是单行字,而且 lxml 在您之前的 two 其他 answers 年中已经被提及。
d
diemo

这是一个 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,并编写了一个快速的临时转换器。


xmlstarlet 很棒,但是公认的主要排名答案已经提到了它。如果有的话,关于如何处理命名空间的信息可能与评论相关。任何遇到命名空间和 xmlstarlet 问题的人都可以找到出色的 discussion in the documentation
当然,@clacke,xmlstarlet 已被多次提及,但也很难掌握,而且文档不足。我猜测了一个小时如何从嵌套元素中获取信息。我希望我有那个例子,这就是为什么我在这里发布它以避免其他人浪费时间(并且这个例子太长了,无法评论)。
A
Andreas Nolda

我的 Python 脚本 xgrep.py 正是这样做的。为了在文件 filename.xml ... 中搜索元素 element 的所有属性 attribute,您可以按如下方式运行它:

xgrep.py "//element/@attribute" filename.xml ...

有各种控制输出的开关,例如 -c 用于计算匹配项,-i 用于缩进匹配部分,-l 用于仅输出文件名。

该脚本不能作为 Debian 或 Ubuntu 软件包使用,但它的所有依赖项都是可用的。


而你在 sourcehut 上托管!好的!
i
igneus

安装 BaseX 数据库,然后像这样使用它的 "standalone command-line mode"

basex -i - //element@attribute < filename.xml

或者

basex -i filename.xml //element@attribute

查询语言实际上是 XQuery (3.0),而不是 XPath,但由于 XQuery 是 XPath 的超集,您可以使用 XPath 查询而无需注意。


m
mgrandi

由于这个项目显然是相当新的,请查看 https://github.com/jeffbr13/xq ,它似乎是 lxml 的包装,但这就是您真正需要的(并且在其他答案中也使用 lxml 发布了临时解决方案)


d
d33tah

我对用于 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

b
brotatochip

很抱歉成为战斗中的另一个声音。我尝试了这个线程中的所有工具,发现它们都不能满足我的需求,所以我自己写了。您可以在这里找到它:https://github.com/charmparticle/xpe

它已上传到 pypi,因此您可以使用 pip3 轻松安装它,如下所示:

sudo pip3 install xpe

安装后,您可以使用它来针对各种输入运行 xpath 表达式,其灵活性与在 selenium 或 javascript 中使用 xpath 所获得的灵活性相同。是的,你可以使用 xpaths 来对抗 HTML。


M
Marinos An

即使在顶部存在命名空间声明时也可以使用的解决方案:

如果 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"