15

我有以下 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

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

4

5 回答 5

10

您好,从 2020 年开始!

从 libxml v2.9.9 开始,此行为本身已得到修复xmllint

但是,如果您使用的是比这更旧的任何东西,并且不想从源代码构建 libxml 只是为了获得 fixed xmllint,那么您将需要此处的其他解决方法之一。在撰写本文时,例如,最新的 CentOS 8 仍在使用运行 OP 所描述方式的 libxml (2.9.7) 版本。

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


XMLStarlet(如果可用)提供了另一种解决方案,但您需要xmlstarlet fo先将 HTML 片段格式化为有效的 XML,然后再xmlstarlet sel用于提取节点:

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

如果Attempt to load network entity来自第二次xmlstarlet调用的消息让您烦恼,只需2>/dev/null在最后添加以抑制它(冒着抑制其他消息打印到标准错误的风险)。

XMLStarlet 选项解释(另见用户指南):

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

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

于 2018-01-17T23:15:26.523 回答
5

试试这个补丁,它提供了 2 个选项:

  • --xpath: 与 old 相同--xpath,节点之间用 . 分隔\n

  • --xpath0: 与 old 相同--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
于 2018-07-30T14:41:12.433 回答
1

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

</textarea>通过\n</textarea>使用以下命令替换 HTML 代码:

sed 's/\<\/textarea/\'$'\n\<\\/textarea/g' f
于 2013-08-30T13:05:46.763 回答
0

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

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

./xmllint2.sh --xpath --html '//textarea[@name="command"]/text()' 2>/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系统崩溃的风险)

于 2020-06-28T23:13:10.680 回答
0

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

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

@Cyker 的合并请求包括xmllint添加一个-xpath0选项,该选项将用 NUL 分隔 xpath 结果。此功能的新功能请求也已打开。

希望xmllint很快会获得此功能。

另一个 xpath 命令行工具,xmlstarlet现在可以被哄骗来实现这个目标。xmlstarlet目前不支持直接输出 NUL,但我们可以将其设为 output U+FFFF,与 NUL 一样,保证不会出现在 XML 数据中。(我假设通用编码字符集的 UTF-8 编码。)然后我们只需要转换U+FFFFU+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

以下是如何使用xmlstarlet单行perl符来使用 NUL 分隔 xpath 结果:

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

注意:我运行了 HTML xmlstarlet fo -H -R,如@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')
于 2021-07-09T01:38:46.607 回答