【问题标题】:How to append a newline after every match using xmlint --xpath如何使用 xmllint --xpath 在每次匹配后附加换行符
【发布时间】:2013-09-03 04:27:23
【问题描述】:

我有以下 HTML 代码:

<textarea name="command" class="setting-input   fixed-width" rows="9">1</textarea><textarea name="command" class="setting-input   fixed-width" rows="5">2</textarea>

我想解析它以接收这样的输出:

1
2

目前我正在使用:

xmllint --xpath '//textarea[@name="command"]/text()' --html

但它不会在每次匹配后添加换行符。

【问题讨论】:

  • 你现在如何获得输出?你在哪里测试的?
  • @Babai 假设上面的 HTML 代码在文件 f 中可用,xmllint --xpath '//textarea[@name="command"]/text()' --html f
  • 其实我确实在在线工具中测试过,文本换了一行。所以试着了解你想在哪里打印它..
  • @Babai 在这种情况下,我猜您使用的工具的行为与 xmllint 不同。
  • 来自未来的你好!此行为已在 libxml2 版本 2.9.9 中得到修复,--xpath 现在(终于)在转储 XPath 节点时完成了您所期望的事情。如果您使用旧的 libxml2,请参阅下面的 my answer,了解使用 XMLStarlet 的替代解决方案。

标签: xml shell xpath xmllint


【解决方案1】:

我做了以下丑陋的伎俩,请随时提供更好的解决方案。

通过使用以下命令将 &lt;/textarea&gt; 替换为 \n&lt;/textarea&gt; 来更改 HTML 代码:

sed 's/\<\/textarea/\'$'\n\<\\/textarea/g' f

【讨论】:

  • 您可以使用其他字符作为 sed 的分隔符,例如%,所以你不需要转义斜线。
  • 如果丑,就别贴了。 'sed' 不是 XML 解析器
【解决方案2】:

您好,从 2020 年开始!

从 libxml 的 v2.9.9 开始,此行为 has been fixed in xmllint itself

但是,如果您使用的是比这更早的东西,并且不想从源代码构建 libxml 只是为了获得固定的xmllint,您将需要这里的其他解决方法之一。在撰写本文时,例如,最新的 CentOS 8 仍在使用与 OP 描述方式相同的 libxml (2.9.7) 版本。

正如我从this SO answer 收集到的,理论上可以将命令输入到xmllint 的旧(--shell 选项中,这将在单独的行上生成每个节点。但是,您最终不得不使用sedgrep 对其进行后处理,以消除shell 模式(面向人的)输出的视觉碎片。这并不理想。


XMLStarlet(如果可用)提供了另一种解决方案,但在使用xmlstarlet sel 提取节点之前,您确实需要使用xmlstarlet fo 将您的HTML 片段格式化为有效的XML:

echo '
<textarea name="command" class="setting-input fixed-width"
 rows="9">1</textarea>
<textarea name="command" class="setting-input fixed-width"
 rows="5">2</textarea>' \
  | xmlstarlet fo -H -R \
  | xmlstarlet sel -T -t -v '//textarea[@name="command"]' -n

如果来自第二个xmlstarlet 调用的Attempt to load network entity 消息让您烦恼,只需在最后添加2&gt;/dev/null 即可抑制它(有可能抑制其他消息打印到标准错误)。

XMLStarlet 选项解释(另见user's guide):

  • fo -H -Rformat 输出,期待 HTML 输入,并尽可能多地恢复错误输入
    • 这将添加一个 &lt;html&gt; 根节点,使 OP 示例中的片段有效 XML
  • sel -T -t -v //xpath -nselect 节点基于 XPath //xpath
    • 输出纯文本 (-T) 而不是 XML
    • 使用给定模板 (-t) 返回节点的值 (-v) 而不是节点本身(允许您放弃在 XPath 表达式中使用 text()
    • 最后,添加一个换行符 (-n)

编辑:删除了半实现的xmllint --shell 解决方案,因为它太糟糕了。添加了一个实际使用 OP 数据的 XMLStarlet 示例。

【讨论】:

  • 提供的链接指向此页面上接受的答案。
  • 哈。接得好。我的剪贴板里肯定有别的东西。现在修好了。 :)
  • 如果您正在解析别人的 HTML,同样值得注意的是,xmllintxmlstartlet 可以无错误解析的各种“格式良好”(X)HTML 似乎是 . ..这些天很少见。您可以按照here 的建议尝试xmllint --html,这对输入格式稍微宽容一些。有时即使 that 也不起作用,我会先通过tidy 将输入的HTML 传递一遍。或者放弃并使用正则表达式,此时我遇到了两个问题。
  • v2.9.10 又坏了?我有xmllint: using libxml version 20910,但没有换行。
【解决方案3】:

试试this patch,它提供了2个选项:

  • --xpath:与旧的--xpath相同,节点由\n分隔。

  • --xpath0:与旧的--xpath相同,节点由\0分隔。

测试输入(a.html):

<textarea name="command" class="setting-input   fixed-width" rows="9">1</textarea><textarea name="command" class="setting-input   fixed-width" rows="5">2</textarea>

测试命令1:

# xmllint --xpath '//textarea[@name="command"]/text()' --html a.html

测试输出1:

 1
 2

测试命令2:

# xmllint --xpath0 '//textarea[@name="command"]/text()' --html a.html | xargs -0 -n1

测试输出2:

 1
 2

【讨论】:

  • 如果能合并这个功能就好了
  • @AdamSiemion 不确定他们是否有权访问他们的 gnome git repo。如果他们在 github 上托管他们的源代码,我很乐意发送拉取请求。另外,需要他们团队中的某个人来做一些健全性检查。
  • @Cyker 您的merge request 只是在那里停滞不前,仍然打开,但您的--xpath 修复为v2.9.9 添加换行符were basically implemented。所以谢谢!
  • @TheDudeAbides 感谢您的提醒。这种更改在字符串中硬编码\n,这使得与\0 分开几乎是不可能的。所以这个补丁不能再被合并,我不会变基。我会把它留在那里,以防有人不需要最新功能,而是\0
  • @Cyker Bummer。尽管如此,您的努力还是值得赞赏的。
【解决方案4】:

下面是一个包装脚本,完全用于换行分隔输出(对于xmllint 的旧版本)。

使用内容创建一个文件xmllint2.sh。然后执行chmod u+x xmllint2.sh,最后运行如下:

./xmllint2.sh --xpath --html '//textarea[@name="command"]/text()' 2&gt;/dev/null

(命令的最后一部分是隐藏html出现的警告输出)

#!/bin/bash

# wrapper script to
# - have newline delimited output on Xpath querys
# - implements --xpath on very old releases

/usr/bin/xmllint --xpath &>/dev/null
implements_xpath=$?

newlines_delimited_xmllint_version=20909
current_version=$(xmllint --version |& awk 'NR==1{print $NF;exit}')

args=( "$@" )
if [[ $@ == *--xpath* ]]; then
    # iterate over positional parameters
    for ((i=0; i<${#args}; i++)); do
        if [[ ${args[i]} == --xpath ]]; then
            xpath="${args[i+1]}"
            unset args[i+1]
            unset args[i]
            break
        fi
    done
    if [[ ($implements_xpath==0 && $current_version>=20909) || $file == - || $file == /dev/stdin || $xpath == / || $xpath == string\(* ]]
    then
        exec /usr/bin/xmllint "$@"
    else
        exec /usr/bin/xmllint "${args[@]}" --shell <<< "cat $xpath" | sed '1d;$d;s/^ ------- *$//;/^$/d'
    fi
else
    exec /usr/bin/xmllint "$@"
fi

查看最新版本:https://github.com/sputnick-dev/xmllint

2020 年 6 月 29 日的 Debian Buster 有 4 年历史的 2.9.4 版本。
Debian testing/experimental 有2.9.10,是固定版本。

在 Debian last stable 中安装 2.9.10 的另一种方法:https://serverfault.com/a/1022826/120473(不冒apt 系统崩溃的风险)

【讨论】:

  • 我尝试了 20910,但它似乎没有换行修复,但我编辑了您的脚本以删除版本检查并且能够继续我正在做的事情。
【解决方案5】:

换行符可以合法地出现在 xml 数据中。一种更健壮的方法将通过保证不会出现在 XML 数据中的字符来分隔 xpath 结果。 Null characterUniversal Coded Character Set中的U+0000,就是这样一个字符。

注意代码点U+0000,分配给空控件 字符,是唯一以 Unicode 和 ISO/IEC 10646 编码的字符 这在任何 XML 1.0 和 1.1 文档中始终无效。
https://en.wikipedia.org/wiki/Valid_characters_in_XML

@Cyker 的merge requestxmllint 添加了一个-xpath0 选项,该选项将用NUL 分隔xpath 结果。 A new feature request 这个功能也被打开了。

希望xmllint 能尽快获得此功能。

另一个 xpath 命令行工具,xmlstarlet,现在可以用来实现这个目标。 xmlstarlet目前不支持直接输出NUL,但是我们可以让它输出U+FFFF,和NUL一样,保证不会出现在XML数据中。 (我假设Universal Coded Character Set 是UTF-8 编码。)然后我们只需要将U+FFFF 转换为U+0000,我们就会得到NUL 分隔的xpath 结果。

在以下示例中,我将使用以下部分 html 文件。这是来自问题的相同数据,除了我添加了换行符用于测试目的。

cat > data.html <<'EOF'
<textarea name="command" class="setting-input fixed-width" rows="9">1 
 newline</textarea>
<textarea name="command" class="setting-input fixed-width" rows="5">2 
 newline</textarea>
EOF

下面是如何使用xmlstarletperl 单行符来用NUL 分隔xpath 结果:

xmlstarlet fo -H -R data.html \
| xmlstarlet sel -t -m '//textarea[@name="command"]' -v '.' -o $'\uffff' \
| perl -C -0xFFFF -l0 -pe ''

注意:我通过xmlstarlet fo -H -R 运行HTML,如@TheDudeAbides 回答中所示。

现在 xpath 结果由 NUL 分隔,我们可以在 xargs -0 的帮助下处理结果。示例:

xmlstarlet fo -H -R data.html \
| xmlstarlet sel -t -m '//textarea[@name="command"]' -v '.' -o $'\uffff' \
| perl -C -0xFFFF -l0 -pe '' \
| xargs -0 -n 1 printf '%q\n'

结果:

'1 '$'\n'' newline'
'2 '$'\n'' newline'

或将其加载到 bash 数组中:

mapfile -t -d '' a < <(
 xmlstarlet fo -H -R data.html \
 | xmlstarlet sel -t -m '//textarea[@name="command"]' -v '.' -o $'\uffff' \
 | perl -C -0xFFFF -l0 -pe ''
)

declare -p a

结果:

declare -a a=([0]=$'1 \n newline' [1]=$'2 \n newline')

【讨论】:

    猜你喜欢
    • 2020-11-26
    • 1970-01-01
    • 2022-11-22
    • 2011-11-14
    • 1970-01-01
    • 1970-01-01
    • 2014-07-17
    • 2020-01-06
    • 1970-01-01
    相关资源
    最近更新 更多