Extrai os filhos de um tipo de elemento XML específico

2

Dado um elemento XML específico (ou seja, um nome de tag específico) e um snippet de dados XML, quero extrair os filhos de cada ocorrência desse elemento. Mais especificamente, tenho o seguinte trecho de dados XML (não bastante válidos):

<!-- 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>

Eu gostaria de um script ou comando que tome esses dados como entrada e produza a seguinte saída:

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

Eu gostaria que a solução usasse ferramentas de processamento de texto padrão, como sed ou awk .

Eu tentei usar o seguinte comando sed , mas não funcionou:

sed -n '/<Sample/,/<\/Sample/p' data.xml
    
por Abhi S 11.11.2017 / 15:10

2 respostas

7

Se você realmente deseja sed - ou awk - como processamento de linha de comando para arquivos XML, provavelmente deve considerar o uso de uma ferramenta de linha de comando de processamento XML. Aqui estão algumas das ferramentas que eu mais vi usadas:

Você também deve estar ciente de que existem várias linguagens de programação / consulta específicas para XML:

Observe que (para ser um XML válido) seus dados XML precisam de um nó raiz e que seus valores de atributo devem ser citados, ou seja, seu arquivo de dados deve ficar mais parecido com isto:

<!-- 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>

Se os seus dados estiverem formatados como XML válido, você poderá usar o XPath com xmlstarlet para obter exatamente o que você quer com um comando muito conciso:

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

Isso produz a seguinte saída:

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

Ou você pode usar o Python (minha escolha favorita). Aqui está um script Python que realiza a mesma tarefa:

#!/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]))

E aqui está como você pode executar o script:

python extract_instance_children.py data.xml

Isso usa o pacote xml da Biblioteca Padrão do Python , que também é um rígido Analisador XML.

Se você não está preocupado em ter XML formatado corretamente e quer apenas analisar um arquivo de texto que parece mais ou menos aquele que você apresentou, então você pode definitivamente realizar o que você quer usando apenas shell-scripting e comando padrão ferramentas de linha. Aqui está um script awk (conforme solicitado):

#!/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;
    }
}

Para executar o script de um arquivo, você usaria um comando como este:

awk -f extract_instance_children.awk data.xml

E aqui está um script Bash que produz a saída desejada:

#!/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}"

Você executaria assim:

bash extract_instance_children.bash data.xml

Ou, voltando ao Python mais uma vez, você poderia usar o pacote Beautiful Soup . O Beautiful Soup é muito mais flexível em sua capacidade de analisar XML inválido do que o módulo XML Python padrão (e todos os outros analisadores XML que eu encontrei). Aqui está um script Python que usa Beautiful Soup para alcançar o resultado desejado:

#!/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()]))
    
por 11.11.2017 / 16:07
3

Isso pode ajudar:

#!/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 

Supondo que o texto Sample no seu exemplo seja um erro e o mantenha simples.

A variável p decide quando imprimir. Um $1=$1 remove os espaços iniciais.

    
por 11.11.2017 / 15:44