过滤与某些 id 匹配的 xml 文档

过滤与某些 id 匹配的 xml 文档

假设您有一个包含许多 xml 文档的文件,例如

<a>
  <b>
  ...
</a>
in between xml documents there may be plain text log messages
<x>
  ...
</x>

...

我如何过滤此文件以仅显示给定正则表达式与该 xml 文档的任何一行匹配的 xml 文档?我在这里讨论的是简单的文本匹配,因此正则表达式匹配部分可能完全不了解底层格式 - xml。

您可以假设根元素的开始和结束标记始终位于其自己的行上(尽管可能是空白填充),并且它们仅用作根元素,即具有相同名称的标记不会出现在下面根元素。这样就可以完成工作,而不必求助于 xml 感知工具。

答案1

概括

我编写了一个 Python 解决方案、一个 Bash 解决方案和一个 Awk 解决方案。所有脚本的想法都是相同的:逐行浏览并使用标志变量来跟踪状态(即我们当前是否在 XML 子文档中以及我们是否找到了匹配的行) )。

在 Python 脚本中,我将所有行读入列表中,并跟踪当前 XML 子文档开始的列表索引,以便在到达结束标记时可以打印出当前子文档。我检查每一行的正则表达式模式,并使用一个标志来跟踪处理完成后是否输出当前子文档。

在 Bash 脚本中,我使用临时文件作为缓冲区来存储当前的 XML 子文档,并等待其写入完成,然后再grep检查它是否包含与给定正则表达式匹配的行。

Awk 脚本与 Base 脚本类似,但我使用 Awk 数组作为缓冲区而不是文件。

测试数据文件

data.xml我根据您问题中给出的示例数据对照以下数据文件 ( ) 检查了这两个脚本:

<a>
  <b>
    string to search for: stuff
  </b>
</a>
in between xml documents there may be plain text log messages
<x>
    unicode string: øæå
</x>

Python解决方案

这是一个简单的 Python 脚本,可以完成您想要的操作:

#!/usr/bin/env python2
# -*- encoding: ascii -*-
"""xmlgrep.py"""

import sys
import re

invert_match = False

if sys.argv[1] == '-v' or sys.argv[1] == '--invert-match':
    invert_match = True
    sys.argv.pop(0)

regex = sys.argv[1]

# Open the XML-ish file
with open(sys.argv[2], 'r') if len(sys.argv) > 2 else sys.stdin as xmlfile:

    # Read all of the data into a list
    lines = xmlfile.readlines()

    # Use flags to keep track of which XML subdocument we're in
    # and whether or not we've found a match in that document
    start_index = closing_tag = regex_match = False

    # Iterate through all the lines
    for index, line in enumerate(lines):

        # Remove trailing and leading white-space
        line = line.strip()

        # If we have a start_index then we're inside an XML document
        if start_index is not False:

            # If this line is a closing tag then reset the flags
            # and print the document if we found a match
            if line == closing_tag:
                if regex_match != invert_match:
                    print(''.join(lines[start_index:index+1]))
                start_index = closing_tag = regex_match = False

            # If this line is NOT a closing tag then we
            # search the current line for a match
            elif re.search(regex, line):
                regex_match = True

        # If we do NOT have a start_index then we're either at the
        # beginning of a new XML subdocument or we're inbetween
        # XML subdocuments
        else:

            # Check for an opening tag for a new XML subdocument
            match = re.match(r'^<(\w+)>$', line)
            if match:

                # Store the current line number
                start_index = index

                # Construct the matching closing tag
                closing_tag = '</' + match.groups()[0] + '>'

以下是运行脚本来搜索字符串“stuff”的方法:

python xmlgrep.py stuff data.xml

这是输出:

<a>
  <b>
    string to search for: stuff
  </b>
</a>

以下是运行脚本来搜索字符串“øæå”的方法:

python xmlgrep.py øæå data.xml

这是输出:

<x>
    unicode string: øæå
</x>

您还可以指定-v--invert-match来搜索不匹配的文档,并在标准输入上工作:

cat data.xml | python xmlgrep.py -v stuff

重击解决方案

这是相同基本算法的 bash 实现。它使用标志来跟踪当前行是否属于 XML 文档,并使用临时文件作为缓冲区来存储正在处理的每个 XML 文档。

#!/usr/bin/env bash
# xmlgrep.sh

# Get the filename and search pattern from the command-line
FILENAME="$1"
REGEX="$2"

# Use flags to keep track of which XML subdocument we're in
XML_DOC=false
CLOSING_TAG=""

# Use a temporary file to store the current XML subdocument
TEMPFILE="$(mktemp)"

# Reset the internal field separator to preserver white-space
export IFS=''

# Iterate through all the lines of the file
while read LINE; do

    # If we're already in an XML subdocument then update
    # the temporary file and check to see if we've reached
    # the end of the document
    if "${XML_DOC}"; then

        # Append the line to the temp-file
        echo "${LINE}" >> "${TEMPFILE}"

        # If this line is a closing tag then reset the flags
        if echo "${LINE}" | grep -Pq '^\s*'"${CLOSING_TAG}"'\s*$'; then
            XML_DOC=false
            CLOSING_TAG=""

            # Print the document if it contains the match pattern 
            if grep -Pq "${REGEX}" "${TEMPFILE}"; then
                cat "${TEMPFILE}"
            fi
        fi

    # Otherwise we check to see if we've reached
    # the beginning of a new XML subdocument
    elif echo "${LINE}" | grep -Pq '^\s*<\w+>\s*$'; then

        # Extract the tag-name
        TAG_NAME="$(echo "${LINE}" | sed 's/^\s*<\(\w\+\)>\s*$/\1/;tx;d;:x')"

        # Construct the corresponding closing tag
        CLOSING_TAG="</${TAG_NAME}>"

        # Set the XML_DOC flag so we know we're inside an XML subdocument
        XML_DOC=true

        # Start storing the subdocument in the temporary file 
        echo "${LINE}" > "${TEMPFILE}"
    fi
done < "${FILENAME}"

以下是运行脚本来搜索字符串“stuff”的方法:

bash xmlgrep.sh data.xml 'stuff'

这是相应的输出:

<a>
  <b>
    string to search for: stuff
  </b>
</a>

以下是运行脚本来搜索字符串“øæå”的方法:

bash xmlgrep.sh data.xml 'øæå'

这是相应的输出:

<x>
    unicode string: øæå
</x>

awk解决方案

这是一个awk解决方案 -awk虽然我的不是很好,所以它很粗糙。它使用与 Bash 和 Python 脚本相同的基本思想。它将每个 XML 文档存储在缓冲区(一个awk数组)中,并使用标志来跟踪状态。当它完成处理文档时,如果文档包含与给定正则表达式匹配的任何行,则会打印该文档。这是脚本:

#!/usr/bin/env gawk
# xmlgrep.awk

# Variables:
#
#   XML_DOC
#       XML_DOC=1 if the current line is inside an XML document.
#
#   CLOSING_TAG
#       Stores the closing tag for the current XML document.
#
#   BUFFER_LENGTH
#       Stores the number of lines in the current XML document.
#
#   MATCH
#       MATCH=1 if we found a matching line in the current XML document.
#
#   PATTERN
#       The regular expression pattern to match against (given as a command-line argument).
#

# Initialize Variables
BEGIN{
    XML_DOC=0;
    CLOSING_TAG="";
    BUFFER_LENGTH=0;
    MATCH=0;
}
{
    if (XML_DOC==1) {

        # If we're inside an XML block, add the current line to the buffer
        BUFFER[BUFFER_LENGTH]=$0;
        BUFFER_LENGTH++;

        # If we've reached a closing tag, reset the XML_DOC and CLOSING_TAG flags
        if ($0 ~ CLOSING_TAG) {
            XML_DOC=0;
            CLOSING_TAG="";

            # If there was a match then output the XML document
            if (MATCH==1) {
                for (i in BUFFER) {
                    print BUFFER[i];
                }
            }
        }
        # If we found a matching line then update the MATCH flag
        else {
            if ($0 ~ PATTERN) {
                MATCH=1;
            }
        }
    }
    else {

        # If we reach a new opening tag then start storing the data in the buffer
        if ($0 ~ /<[a-z]+>/) {

            # Set the XML_DOC flag
            XML_DOC=1;

            # Reset the buffer
            delete BUFFER;
            BUFFER[0]=$0;
            BUFFER_LENGTH=1;

            # Reset the match flag
            MATCH=0;

            # Compute the corresponding closing tag
            match($0, /<([a-z]+)>/, match_groups);
            CLOSING_TAG="</" match_groups[1] ">";
        }
    }
}

您可以这样称呼它:

gawk -v PATTERN="øæå" -f xmlgrep.awk data.xml

这是相应的输出:

<x>
    unicode string: øæå
</x>

相关内容