获取 2 个处理指令之间的文本

Get text between 2 processing instructions

我的目标是使用 xslt 在 2 个处理指令之间获取所有文本,包括元素内的文本。

输入文件是具有标准 XML 结构的 DITA。我正在搜索 <?PI start?><?PI end?> 的 2 个处理指令。我在 <?PI start?> 之后和 <?PI end?> 之前搜索文本。可以只有文本或其中包含文本的元素。

输入

<concept id="testcase" >
  <title> Introduction</title>
  <conbody>
    <p>
      <p>text01</p>
      <?PI start 1?> text02 <?PI end  1?>
      <b>   text03   </b> 
      <?PI start 2?> text04 text05 <?PI end  2?> text06
      <?PI start 3?> text07 <?PI end  3?>
    </p>
    <p>
      <?PI start 4?>text11 <?PI end 4?>
      <?PI start 5?><b>text12</b><?PI end 5?>
      <?PI start 6?> text13<?PI end 6?>
    </p>
  </conbody>
</concept>

我的方法是:

xsl

<xsl:template match="//processing-instruction('PI')[contains(.,'start')]"> 
    
  <xsl:variable name='text1' select="following-sibling::text()[preceding::processing-instruction('PI')[1][contains(.,'start')]][following::processing-instruction('PI')[1][contains(., 'end ')]] "/>
  <xsl:variable name='text2' select="following-sibling::*[preceding::processing-instruction('PI')[1][contains(.,'start')]][following::processing-instruction('PI')[1][contains(., 'end')]]/text() "/>
  <xsl:variable name="text" select="concat($text1,$text2)"/>
    
  <xsl:value-of select="$text"/>
    
  <xsl:copy>
    <xsl:apply-templates select="@* | node()" />
  </xsl:copy>
</xsl:template>

输出

<concept xmlns:ditaarch="http://dita.oasis-open.org/architecture/2005/" id="testcase">
  <title> Introduction</title>
  <conbody>
    <p>
      <p>text01</p>
       text02 <?PI start 1?> text02 <?PI end  1?>
      <b>   text03   </b> 
       text04 text05 <?PI start 2?> text04 text05 <?PI end  2?> text06
       text07 <?PI start 3?> text07 <?PI end  3?>
    </p>
    <p>
      text11 text12<?PI start 4?>text11 <?PI end 4?>
       text13text12<?PI start 5?>
      <b>text12</b><?PI end 5?>
       text13<?PI start 6?> text13<?PI end 6?>
    </p>
  </conbody>
</concept>

xsl

<xsl:template match="//processing-instruction('PI')[contains(.,'start')]"> 
  <xsl:for-each select="following-sibling::*">  
    <xsl:value-of select="./text()"/>  
  </xsl:for-each>
  
  <xsl:for-each select="following-sibling::text()">  
    <xsl:value-of select="."/>  
  </xsl:for-each>
  
  <xsl:copy>
    <xsl:apply-templates select="@* | node()" />
  </xsl:copy>
</xsl:template>

输出

<concept xmlns:ditaarch="http://dita.oasis-open.org/architecture/2005/" id="testcase">
  <title> Introduction</title>
  <conbody>
    <p>
      <p>text01</p>
         text03    text02 
       
       text04 text05  text06
       text07 
    <?PI start 1?> text02 <?PI end  1?>
      <b>   text03   </b> 
       text04 text05  text06
       text07 
    <?PI start 2?> text04 text05 <?PI end  2?> text06
       text07 
    <?PI start 3?> text07 <?PI end  3?>
    </p>
    <p>
      text12text11 
      
       text13
    <?PI start 4?>text11 <?PI end 4?>
      text12
       text13
    <?PI start 5?>
      <b>text12</b><?PI end 5?>
       text13
    <?PI start 6?> text13<?PI end 6?>
    </p>
  </conbody>
</concept>

问题是它甚至匹配不在 2 个处理指令之间的元素。例如下面的 text03,从技术上讲它确实有 preceding-sibling <?PI start?>following-sibling <?PI end?>:

 <?PI start 1?> text02 <?PI end  1?>
 <b>   text03   </b> 
 <?PI start 2?> text04 text05 <?PI end  2?>

XSLT 版本:1.0

XSLT 处理器:Saxon-HE

我会感谢任何意见、想法和建议

如果 PI 总是兄弟姐妹那么做

<xsl:template match="processing-instruction('PI')[starts-with(.,'start')]"> 
  <xsl:variable name="end-pi" select="following-sibling::processing-instruction('PI')[starts-with(., 'end')][1]"/>
  <xsl:variable name="nodes-before-end"
    select="following-sibling::node()[. &lt;&lt; $end-pi]"/>
</xsl:template>

应该足以select两个PI之间的节点。根据需要输出它们,我不太清楚where/how你想输出它们。

我知道这不是您要的,但也许会有用。

我的这段代码几乎与您要求的完全相同,只是略有不同:如果 2 条处理指令之间的节点内容是空白集合或 2 条处理指令之间的节点文本是空白的集合,它用空节点替换它们。 我正在使用 pythons 标准库,不需要安装任何额外的东西。只需确保 运行 与 python3(我 运行 与 python3.6,但任何 python3+ 都应该没问题)

from pprint import pprint
from typing import List
from xml.dom import minidom
from xml.dom import Node
import re


def get_all_processing_instruction_nodes(child_nodes: List):
    start_end_pairs = []
    current_pair = {}
    for index, node in enumerate(child_nodes):
        if node.nodeType == Node.PROCESSING_INSTRUCTION_NODE:
            if "start" in node.nodeValue:
                current_pair["start"] = {
                    "node": node,
                    "index": index
                }
            if "end" in node.nodeValue:
                if "start" not in current_pair:
                    raise ValueError("End detected before start")
                current_pair['end'] = {
                    "node": node,
                    "index": index
                }
                start_end_pairs.append(current_pair)
                current_pair = {}
    return start_end_pairs


def process_all_paired_child_nodes_recursively(node):
    pi_pairs = get_all_processing_instruction_nodes(node.childNodes)
    if pi_pairs:
        print(node.nodeName, "::", node.nodeValue)
        pprint(pi_pairs)
    for pair in pi_pairs:
        start_index = pair['start']['index']
        end_index = pair['end']['index']
        interesting_nodes = node.childNodes[start_index + 1: end_index]
        process_interesting_nodes(interesting_nodes)
    for child_node in node.childNodes:
        process_all_paired_child_nodes_recursively(child_node)


def process_interesting_nodes(interesting_nodes: List):
    for i_node in interesting_nodes:
        if i_node.nodeValue:
            i_node.nodeValue = re.sub(r"\s+", "", i_node.nodeValue)


def process_xml_file(input_file_path: str, output_file_path: str):
    document_node = minidom.parse(input_file_path)
    process_all_paired_child_nodes_recursively(document_node)
    with open(output_file_path, "w") as f:
        f.write(document_node.toxml(document_node._get_encoding()).decode())

您可以轻松修改 process_interesting 节点函数以对匹配的节点执行任何操作(请注意,2 个处理指令之间的纯文本在 python 中被解析为文本节点,因此您将其视为作为常规节点)。

希望这对您有所帮助。我还建议您查看 python 的 xml 库,尤其是 minidom 部分 (https://docs.python.org/3/library/xml.dom.minidom.html)。 Minidom 模块,与常规 xml 解析器不同,允许您将处理指令和注释视为常规节点。