XSLT:分析字符串并保留子节点

Posted

技术标签:

【中文标题】XSLT:分析字符串并保留子节点【英文标题】:XSLT: analyze-string and retain child nodes 【发布时间】:2021-04-29 07:56:33 【问题描述】:

我正在尝试使用正则表达式文本匹配来查找引用其他语句的语句。它适用于文本位于同一节点中的情况,但我正在努力处理作为子节点或跨节点拆分的文本。此外,我想忽略 del 标记内的任何文本。

从这样的文档开始:

<doc>
    <sectionA>
        <statement id="1">
            <title>Titlle A</title>
            <statementtext id="a">This is referring to statement 2 about the stuff</statementtext>
            <!-- This is referring to statement <ref statementNumber="2">2</ref> about the stuff -->
        </statement>
        <statement id="2">
            <title>Title B</title>
            <statementtext id="b">This is <b>my</b> statement <b>1</b> referring to something else</statementtext>
            <!-- This is <b>my</b> statement <ref statementNumber="1"><b>1</b></ref> referring to something else -->
        </statement>
        <statement id="3">
            <title>Title 3</title>
            <statementtext id="c">This is another statement <b>1</b><i>2</i> about the stuff</statementtext>
            <!-- This is another statement <ref statementNumber="12"><b>1</b><i>2</i></ref> about the stuff -->
        </statement>
        <statement id="4">
            <title>Title 4</title>
            <statementtext id="d">This is corrected statement <del>1</del><ins>2</ins> about the stuff</statementtext>
            <!-- This is corrected statement <ref statementNumber="2"><del>1</del><ins>2</ins></ref> about the stuff -->
        </statement>        
        <statement id="5">
            <title>Title 5</title>
            <statementtext id="e">This is partially corrected statement 1<del>1</del><ins>5</ins> about the stuff</statementtext>
            <!-- This is partially corrected statement <ref statementNumber="15">1<del>1</del><ins>5</ins></ref> about the stuff -->
        </statement>
                <statement id="6">
            <title>Title 6</title>
            <statementtext  id="f">This is another
            <statementtext  id="g"> that contains a nested satementtext for statement <b>1</b><i>3</i> about </statementtext>
            the stuff</statementtext>
            <!-- This is another <statementtext id="g"> that contains a nested satementtext for statement <ref statementNumber="13"><b>1</b><i>3</i></ref> about </statementtext> -->
        </statement>
        <statement id="7">
            <title>Title 7</title>
            <statementtext id="h">This is <i>statement</i> <b>1</b> referring to something else</statementtext>
            <!-- This is my <i>statement</i> <ref statementNumber="1"><b>1</b></ref> referring to something else -->
        </statement>
        <statement id="8">
            <title>Title 8</title>
            <statementtext id="i">This is has no reference to another statement</statementtext>
            <!-- his is has no reference to another statement -->
        </statement>        
    </sectionA>     
</doc>

使用我当前的模板

  <xsl:template match="statementtext">
      <statementtext>
          <xsl:copy-of select="./@*" />
        <xsl:variable name="thisText">
            <xsl:value-of select="./descendant-or-self::text()"/>
        </xsl:variable>

        <xsl:variable name="thisTextFiltered">
            <xsl:value-of select="./descendant-or-self::text()[not(descendant-or-self::del and comment())]"/>
        </xsl:variable>       

        <xsl:choose>
            <xsl:when test="matches($thisTextFiltered,'(statement\s*)(\d+)','i')">
                    <xsl:analyze-string select="$thisTextFiltered"
                                    regex="(statement\s*)(\d+)"
                                    flags="ix">
                        <xsl:matching-substring>
                        <xsl:value-of select="regex-group(1)"/>
                        <xsl:variable name="statementNumber">
                            <xsl:value-of select="regex-group(2)"></xsl:value-of>
                        </xsl:variable>
                            <ref>
                                <xsl:attribute name="statementNumber">
                                    <xsl:value-of select="$statementNumber" />
                                </xsl:attribute>
                                <xsl:value-of select="regex-group(2)"/>
                            </ref> 
                            </xsl:matching-substring>
                            <xsl:non-matching-substring>
                              <xsl:value-of select="."/>
                            </xsl:non-matching-substring>
                        </xsl:analyze-string>
                </xsl:when>
            <xsl:otherwise>
                <xsl:apply-templates />
            </xsl:otherwise>           
            </xsl:choose>
        </statementtext>
   </xsl:template>

      <xsl:template match="@*|*|processing-instruction()|comment()">
        <xsl:copy>
            <xsl:apply-templates select="*|@*|text()|processing-instruction()|comment()" mode="#current"/>
        </xsl:copy>
    </xsl:template

这是我的输出:

<!DOCTYPE html>
<doc>
   <sectionA>
      <statement id="1"><title>Titlle A</title><statementtext id="a">This is referring to statement 
            <ref statementNumber="2">2</ref> about the stuff
         </statementtext>
         <!-- This is referring to statement <ref statementNumber="2">2</ref> about the stuff -->
      </statement>
      <statement id="2"><title>Title B</title><statementtext id="b">This is my statement 
            <ref statementNumber="1">1</ref> referring to something else
         </statementtext>
         <!-- This is <b>my</b> statement <b><ref statementNumber="1">1</ref></b> referring to something else -->
      </statement>
      <statement id="3"><title>Title 3</title><statementtext id="c">This is another statement 
            <ref statementNumber="12">12</ref> about the stuff
         </statementtext>
         <!-- This is another statement <ref statementNumber="12"><b>1</b><i>2</i></ref> about the stuff -->
      </statement>
      <statement id="4"><title>Title 4</title><statementtext id="d">This is corrected statement 
            <ref statementNumber="12">12</ref> about the stuff
         </statementtext>
         <!-- This is corrected statement <ref statementNumber="2"><del>1</del><ins>2</ins></ref> about the stuff -->
      </statement>
      <statement id="5"><title>Title 5</title><statementtext id="e">This is partially corrected statement 
            <ref statementNumber="115">115</ref> about the stuff
         </statementtext>
         <!-- This is partially corrected statement <ref statementNumber="15">1<del>1</del><ins>5</ins></ref> about the stuff -->
      </statement>
      <statement id="6"><title>Title 6</title><statementtext id="f">This is another
                         that contains a nested satementtext for statement 
            <ref statementNumber="13">13</ref> about 
                        the stuff
         </statementtext>
         <!-- This is another <statementtext id="g"> that contains a nested satementtext for statement <ref statementNumber="13"><b>1</b><i>3</i></ref> about </statementtext> -->
      </statement>
      <statement id="7"><title>Title 7</title><statementtext id="h">This is statement
            <ref statementNumber="1">1</ref> referring to something else
         </statementtext>
         <!-- This is my <i>statement</i> <b><ref statementNumber="1">1</ref></b> referring to something else -->
      </statement>
      <statement id="8"><title>Title 8</title><statementtext id="i">This is has no reference to another statement</statementtext>
         <!-- his is has no reference to another statement -->
      </statement>
   </sectionA>
</doc>

我是接近还是我完全改变了我的方法

【问题讨论】:

【参考方案1】:

我尝试使用预处理步骤来包装数字,然后混合使用 group-starting-with/group-adjacent,我认为它现在涵盖了您提供的所有示例,但它相当复杂且嵌套很深分组代码:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:fn="http://www.w3.org/2005/xpath-functions"
    exclude-result-prefixes="#all"
    expand-text="yes"
    version="3.0">

  <xsl:mode on-no-match="shallow-copy"/>
  
  <xsl:template match="text()" mode="analyze">
      <xsl:apply-templates select="analyze-string(., 'statement\s*([0-9]+)')" mode="wrap"/>
  </xsl:template>
  
  <xsl:mode name="analyze" on-no-match="shallow-copy"/>
  
  <xsl:template match="fn:group[@nr = 1]" mode="wrap">
      <n>.</n>
  </xsl:template>

  <xsl:template match="statementtext">
      <xsl:copy>
          <xsl:variable name="wrapped" as="node()*">
              <xsl:apply-templates mode="analyze"/>
          </xsl:variable>
 
          <xsl:for-each-group select="$wrapped" group-starting-with="node()[matches(., 'statement\s*$', 'i')]">
              <xsl:choose>
                  <xsl:when test="matches(., 'statement\s*$', 'i')">
                      <xsl:apply-templates select="."/>
                      <xsl:for-each-group select="tail(current-group())" group-adjacent="matches(., '^[0-9 ]+$')">
                          <xsl:choose>
                              <xsl:when test="current-grouping-key() and position() = 1 and matches(., '^\s+$')">
                                  <xsl:apply-templates select="."/>
                                  <ref statementNumber="string-join(tail(current-group())[not(self::del)])">
                                      <xsl:apply-templates select="tail(current-group())"/>
                                  </ref>
                              </xsl:when>
                              <xsl:when test="current-grouping-key() and position() = 1">
                                  <ref statementNumber="string-join(current-group()[not(self::del)])">
                                      <xsl:apply-templates select="current-group()"/>
                                  </ref>
                              </xsl:when>
                              <xsl:otherwise>
                                  <xsl:apply-templates select="current-group()"/>
                              </xsl:otherwise>
                          </xsl:choose>
                      </xsl:for-each-group>
                  </xsl:when>
                  <xsl:otherwise>
                      <xsl:apply-templates select="current-group()"/>
                  </xsl:otherwise>
              </xsl:choose>
          </xsl:for-each-group>
      </xsl:copy>
  </xsl:template>
  
  <xsl:template match="n">
      <xsl:apply-templates/>
  </xsl:template>
  
</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/bEJbVrL

【讨论】:

谢谢。这是一个很大的帮助。我还有一个我没有包括在内的场景,其中语句本身包含在标签中,例如 statement 1statement 1 @MatthewSrickler,因为显示的代码已经是保持简单的失败尝试,但需要各种补丁和复杂性来涵盖所有情况我将在今天晚些时候尝试重新考虑是否有更简单的方法,然后再尝试进一步修补现有的。【参考方案2】:

我不会花费时间来生成有效的解决方案,但您应该在代码中解决以下问题:

<xsl:variable name="statementNumber">
       <xsl:value-of select="regex-group(2)"></xsl:value-of>
</xsl:variable>
<ref>
    <xsl:attribute name="statementNumber">
      <xsl:value-of select="$statementNumber" />
    </xsl:attribute>
    <xsl:value-of select="regex-group(2)"/>
</ref> 

这可以简化为

<ref statementNumber="regex-group(2)">regex-group(2)</ref>

还有这个:

<xsl:variable name="thisTextFiltered">
    <xsl:value-of select="./descendant-or-self::text()[not(descendant-or-self::del and comment())]"/>
</xsl:variable> 

不可能是正确的,因为文本节点没有后代(撒克逊人应该给你一个警告)。但我不确定你的真正意图。

【讨论】:

以上是关于XSLT:分析字符串并保留子节点的主要内容,如果未能解决你的问题,请参考以下文章

使用应用模板在 XSLT 输出中保留   和其他特殊字符

如何获取具有相同名称的元素并根据 XSLT 中的子节点值对它们进行排序

通过保留第一次出现的节点来删除 XSLT 属性中的重复项

XSLT - 如何将节点内的内联/转义 XML 视为嵌套节点

XSLT 将来自多个节点的属性连接成单个值

XSLT 过滤掉节点并生成新的 xml