如何获取字符偏移处的 XPath?

问题描述 投票:0回答:2

我使用 xmlstarlet 来验证 XML 文件。

当失败时,我将获得错误的数字位置。但我想要 XPath。有没有一个工具可以将位置转换为 XPath?

对于格式良好的 XML 文件,应该始终有一个与该位置相对应的 XPath。

问题:在 Linux 命令行上:给定 XML 文件内的行+字符位置,如何以编程方式获取相应的 XPath?

示例:我的模式表示苹果必须是红色或绿色。但我有一个棕色的苹果。所以我在位置“2.27”处收到错误。

$ cat schema.xsd
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="fruit">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="apples">
                    <xs:simpleType>
                        <xs:restriction base="xs:string">
                            <xs:enumeration value="green"/>
                            <xs:enumeration value="red"/>
                        </xs:restriction>
                    </xs:simpleType>
                </xs:element>
                <xs:element name="bananas"/>
                <xs:element name="cherries"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>
✓

$ cat badfruit.xml
<fruit>
    <apples>brown</apples>
    <bananas>yellow</bananas>
    <cherries>red</cherries>
</fruit>
✓

$ xmlstarlet validate --well-formed badfruit.xml
badfruit.xml - valid
✓

$ xmlstarlet validate --err --xsd schema.xsd badfruit.xml
badfruit.xml:2.27: Element 'apples': [facet 'enumeration'] The value 'brown' is not an element of the set {'green', 'red'}.
badfruit.xml - invalid

$ xmlstarlet elements badfruit.xml
fruit
fruit/apples
fruit/bananas
fruit/cherries
✓

想要:执行此操作的命令:

$ magiccommand "badfruit.xml:2.27"
/fruit/apples

有这样的工具吗?

背景信息:我现实生活中的例子有点复杂。我正在处理一个 10k 行的文件,其中 XML 元素名称已在多个不同的层次结构级别上重复使用。因此,如果验证因“Element 'x' invalid”而失败,那么这对我来说意义不大,因为“x”可能会出现在几个不同的级别。而这些都是由不同的Java代码生成的。因此,准确了解 XPath 受到的影响对我来说非常有帮助。目前,我只是在图形 XML 编辑器(带有 XML 工具插件的 Notepad++)中重新打开该文件并在其中重新验证。然后就会直接跳到错误位置。光标下的当前 XPath 将位于状态栏的左下角。但我想避免这个额外的步骤并留在命令行上。

更新2023年2月6日。 NPP 内部等效功能的屏幕截图

这是两张截图。

带有

XML 工具 插件的 Notepad++ 在屏幕左下角的光标处显示 XPath。所以我基本上想要这个功能,但不是在 Notepad++ 内,而是在命令行上。

2.27 处的光标显示 XPath

/fruit

因此 xmlstarlet 输出

2.27
作为错误位置,但在 Notepad++ 中,您实际上必须位于
2.26
位置才能获得我想要的 XPath。

2.27 处的光标显示 XPath

/fruit/apples

xml xpath
2个回答
1
投票

从错误消息中构建错误元素 xpath,然后使用 xml2xpath 获取 xpath 可能是一个选项

echo "badfruit.xml:2.27: Element 'apples': [facet 'enumeration'] The value 'brown' is not an element of the set {'green', 'red'}." | sed -rne "s@.* Element '([^']+)': .*value '([^']+)' .*@//\1[.='\2']@p"

结果

//apples[.='brown']

使用 xml2xpath

获取其 XPath
xml2xpath.sh -a -s "//apples[.='brown']" -x tmp.xml

结果(已编辑)

...
XPath expressions found: 1 (absolute, unique elements, use -r to override)
================================================================================ (2022-12-28 12:51:34 -03)

/fruit/apples[1]

鉴于此 XML

<fruit>
    <apples>brown</apples>
    <bananas>yellow</bananas>
    <cherries>red</cherries>
    <apples>brown</apples>
</fruit>

它会回来

/fruit/apples[1]
/fruit/apples[2]

注意

xml2xpath
需要
xmllint
,但它可以在大多数Linux发行版上访问。


1
投票

使用列号可能会很棘手,因为可能会出现错误和警告消息 报告 (1) 开始标记、(2) 结束标记的开始/结束列 标签、(3) 节点名称或 (4) 节点值。 但由于您想要格式化 XML 文件的 XPath 表达式,可以使 事情变得更简单。

通过打印 max 的格式化程序运行 XML 文件。一个元素标签 每行属性位于同一行,然后将 XSLT 与 XML 结合使用 维护行号的解析器。 使用撒克逊语

lineNumber
扩展 可以在给定行号的情况下提取 XPath 表达式。 它由 Saxon 6.5.5 XSLT 1.0 处理器支持,并且通过
libexslt
(可能还有其他 XSLT 处理器)。 更新的 Saxon-PE 和 Saxon-EE 处理器(自版本 9.9.1 起) 支持 行号和列号。也是如此 SAX2 API。

一些快速测试表明 Saxon 6.5.5 处理了 106MB 电子表格 XML 文件包含预期的 200 万行。

libexslt
saxon:line-number()
, 但是,从未返回超过 65535 的值,从而限制了范围 可用行号。该限制可能与内存相关,但事实并非如此 可通过
xsltproc
xmlstarlet
命令行选项进行配置 (两者都是基于 )。

更新2024-04-28:

libxml2
有一个
XML_PARSE_BIG_LINES
解析器选项,允许行号 大于 65535 才能在错误消息和中正确报告
saxon:line-number
扩展的输出。 我做了一个补丁 对于
xmlstarlet
1.6.1 添加了
--big-lines
命令行选项 申请
XML_PARSE_BIG_LINES
(和
--huge
申请
XML_PARSE_HUGE
允许文本节点大于 10MB)。 (更新结束


这是 Saxon 6.5.5 的 XSLT 转换。

文件:

ln2xpath.xsl

<?xml version="1.0"?>
<xsl:transform version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:saxon="http://icl.com/saxon" 
  extension-element-prefixes="saxon"
>
  <xsl:output method="text"/>

  <xsl:param name="pathname"/>
  <xsl:param name="lines"/><!-- list of line numbers -->
  <xsl:param name="delim" select="'&#9;'"/>
  <xsl:param name="newline" select="'&#10;'"/>

  <xsl:template match="/">
    <xsl:variable name="root" select="/"/>

    <xsl:value-of select="concat('# File: ',$pathname,$newline)"/>

    <xsl:for-each select="saxon:tokenize($lines,' ')">
      <xsl:value-of select="concat('# Line: ',.,$newline)"/>
      <xsl:for-each select="$root//*[saxon:line-number() = current()]">

        <xsl:for-each select="ancestor-or-self::*">
          <xsl:value-of select="concat('/',name(),'['
          ,1 + count(preceding-sibling::*[name() = name(current())])
          ,']')"/>
        </xsl:for-each>
        <xsl:value-of select="$newline"/>

        <xsl:for-each select="attribute::*">
          <xsl:value-of select="concat(
            '@',name(),$delim,normalize-space(),$newline
          )"/>
        </xsl:for-each>
        <xsl:value-of select="$newline"/>

      </xsl:for-each>
    </xsl:for-each>

  </xsl:template>

</xsl:transform>

地点:

  • 最外面的
    xsl:for-each
    迭代传递的行号 作为参数使用
    saxon:tokenize()
    扩展功能; 它还会更改 当前节点 所以
    $root
    已保存以供稍后使用
  • 第二个
    xsl:for-each
    处理元素节点,其
    saxon:line-number()
    $lines
     中的 
    current
  • 行号匹配
  • 第三个
    xsl:for-each
    从 上的元素构建 XPath 表达式
    ancestor-or-self
  • 第四个
    xsl:for-each
    列出属性,每行一个,值在 标准化形式

示例:

ln2xpath() {
  # Arguments:
  # $1 pathname of file output by `xmlstarlet format file.xml`
  # $2 list of line numbers (min. 1)

  java -jar saxon655.jar -l "${1}" ln2xpath.xsl pathname="${1}" lines="${2:?line numbers?}"
}

xmlstarlet format /usr/share/*/xslt/docbook/common/db-common.xsl | 
ln2xpath /dev/stdin '17 53 132 54321'
  • -l
    选项启用行编号
  • ${2:?…}
    扩展第二个位置 参数,或者如果丢失会导致 shell 发出嗡嗡声并失败

输出:

# File: /dev/stdin
# Line: 17
/xsl:stylesheet[1]
@exclude-result-prefixes        db str msg
@version        1.0

# Line: 53
/xsl:stylesheet[1]/xsl:key[3]
@name   db.biblio.label.key
@match  biblioentry[@id and @xreflabel] | bibliomixed[@id and @xreflabel] | db:biblioentry[@xml:id and @xreflabel] | db:bibliomixed[@xml:id and @xreflabel]
@use    string(@xreflabel)

# Line: 132
/xsl:stylesheet[1]/xsl:template[4]/xsl:choose[1]/xsl:when[2]/xsl:variable[1]
@name   prev
@select $node/preceding::*[name(.) = name($node)][1]

# Line: 54321

这是等效的

xmlstarlet
版本(最多 65535 行输入)。

# shellcheck shell=sh disable=SC2016
ln2xpath() {
  xmlstarlet select --text -t \
    --var lines -o "${2:?wot, no line numbers?}" -b \
    --var delim -o "$(printf '\t')" -b \
    --var root='/' \
    -o '# File: ' -f -n \
    -m 'str:tokenize($lines)' \
      -o '# Line: ' -v '.' -n \
      -m '$root//*[saxon:line-number() = current()]' \
        -m 'ancestor-or-self::*' \
          --var pos='1 + count(preceding-sibling::*[name() = name(current())])' \
          -v 'concat("/",name(),"[",$pos,"]")' \
        -b \
        -n \
        -m 'attribute::*' \
          -v 'concat("@",name(),$delim,normalize-space())' \
          -n \
        -b \
        -n \
  "${1}"
}
© www.soinside.com 2019 - 2024. All rights reserved.