获取 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>
我的方法是:
- 匹配
<?PI start?>
,并尝试获得 following-sibling
,直到我到达 <?PI end?>
。问题是 xslt 中的循环没有中断,也没有办法改变变量的值,所以我不知道如何停止。
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>
- 匹配文本或具有
preceding-sibling
<?PI start?>
和 following-sibling
<?PI end?>
. 的任何元素
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()[. << $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 解析器不同,允许您将处理指令和注释视为常规节点。
我的目标是使用 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>
我的方法是:
- 匹配
<?PI start?>
,并尝试获得following-sibling
,直到我到达<?PI end?>
。问题是 xslt 中的循环没有中断,也没有办法改变变量的值,所以我不知道如何停止。
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>
- 匹配文本或具有
preceding-sibling
<?PI start?>
和following-sibling
<?PI end?>
. 的任何元素
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()[. << $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 解析器不同,允许您将处理指令和注释视为常规节点。