提取特定 XML 元素类型的子元素

提取特定 XML 元素类型的子元素

给定一个特定的 XML 元素(即特定的标签名称)和 XML 数据片段,我想从该元素的每次出现中提取子元素。更具体地说,我有以下(不完全有效)XML 数据片段:

<!-- data.xml -->

<instance ab=1 >
    <a1>aa</a1>
    <a2>aa</a2>
</instance>
<instance ab=2 >
    <b1>bb</b1>
    <b2>bb</b2>
</instance>
<instance ab=3 >
    <c1>cc</c1>
    <c2>cc</c2>
</instance>

我想要一个脚本或命令,它将这些数据作为输入并产生以下输出:

<a1>aa</a1><a2>aa</a2>
<b1>bb</b1><b2>bb</b2>
<c1>cc</c1><c2>cc</c2>

我希望解决方案使用标准文本处理工具,例如sedawk

我尝试使用以下sed命令,但它不起作用:

sed -n '/<Sample/,/<\/Sample/p' data.xml

答案1

如果您确实想要对 XML 文件sed进行awk类似命令行处理,那么您可能应该考虑使用 XML 处理命令行工具。以下是我见过的一些更常用的工具:

您还应该知道有几种特定于 XML 的编程/查询语言:

请注意(为了成为有效的 XML)您的 XML 数据需要一个根节点,并且您的属性值应该被引用,即您的数据文件应该看起来更像这样:

<!-- data.xml -->

<instances>

    <instance ab='1'>
        <a1>aa</a1>
        <a2>aa</a2>
    </instance>

    <instance ab='2'>
        <b1>bb</b1>
        <b2>bb</b2>
    </instance>

    <instance ab='3'>
        <c1>cc</c1>
        <c2>cc</c2>
    </instance>

</instances>

如果您的数据格式为有效的 XML,那么您可以使用X路径xmlstarlet用一个非常简洁的命令得到你想要的东西:

xmlstarlet sel -t -m '//instance' -c "./*" -n data.xml

这会产生以下输出:

<a1>aa</a1><a2>aa</a2>
<b1>bb</b1><b2>bb</b2>
<c1>cc</c1><c2>cc</c2>

或者你可以使用Python(我个人最喜欢的选择)。下面是一个完成相同任务的 Python 脚本:

#!/usr/bin/env python2
# -*- encoding: ascii -*-
"""extract_instance_children.bash"""

import sys
import xml.etree.ElementTree

# Load the data
tree = xml.etree.ElementTree.parse(sys.argv[1])
root = tree.getroot()

# Extract and output the child elements
for instance in root.iter("instance"):
    print(''.join([xml.etree.ElementTree.tostring(child).strip() for child in instance]))

以下是运行脚本的方法:

python extract_instance_children.py data.xml

这使用来自 Python 标准库的 xml 包这也是一个严格的 XML 解析器。

如果您不关心 XML 的格式是否正确,而只想解析一个与您所呈现的文件大致相似的文本文件,那么您绝对可以使用 shell 脚本和标准命令行工具来完成您想要的任务。这是一个awk脚本(根据要求):

#!/usr/bin/env awk

# extract_instance_children.awk

BEGIN {
    addchild=0;
    children="";
}

{
    # Opening tag for "instance" element - set the "addchild" flag
    if($0 ~ "^ *<instance[^<>]+>") {
        addchild=1;
    }

    # Closing tag for "instance" element - reset "children" string and "addchild" flag, print children
    else if($0 ~ "^ *</instance>" && addchild == 1) {
        addchild=0;
        printf("%s\n", children);
        children="";
    }

    # Concatenating child elements - strip whitespace
    else if (addchild == 1) {
        gsub(/^[ \t]+/,"",$0);
        gsub(/[ \t]+$/,"",$0);
        children=children $0;
    }
}

要从文件执行脚本,您可以使用如下命令:

awk -f extract_instance_children.awk data.xml

这是一个产生所需输出的 ​​Bash 脚本:

#!/bin/bash

# extract_instance_children.bash

# Keep track of whether or not we're inside of an "instance" element
instance=0

# Loop through the lines of the file
while read line; do

    # Set the instance flag to true if we come across an opening tag
    if echo "${line}" | grep -q '<instance.*>'; then
        instance=1

    # Set the instance flag to false and print a newline if we come across a closing tag
    elif echo "${line}" | grep -q '</instance>'; then
        instance=0
        echo

    # If we're inside an instance tag then print the child element
    elif [[ ${instance} == 1 ]]; then
        printf "${line}"
    fi

done < "${1}"

你可以像这样执行它:

bash extract_instance_children.bash data.xml

或者,再次回到 Python,您可以使用美丽的汤包裹。 Beautiful Soup 在解析无效 XML 方面比标准 Python XML 模块(以及我遇到的所有其他 XML 解析器)灵活得多。这是一个使用 Beautiful Soup 来实现所需结果的 Python 脚本:

#!/usr/bin/env python2
# -*- encoding: ascii -*-
"""extract_instance_children.bash"""

import sys
from bs4 import BeautifulSoup as Soup

with open(sys.argv[1], 'r') as xmlfile:
    soup = Soup(xmlfile.read(), "html.parser")
    for instance in soup.findAll('instance'):
        print(''.join([str(child) for child in instance.findChildren()]))

答案2

这可能会有所帮助:

#!/bin/bash

awk -vtag=instance -vp=0 '{
if($0~("^<"tag)){p=1;next}
if($0~("^</"tag)){p=0;printf("\n");next}
if(p==1){$1=$1;printf("%s",$0)}
}' infile 

假设Sample示例中的文本是错误的并保持简单。

p 变量决定何时打印。 A$1=$1删除前导空格。

相关内容