XSLT 2.0: объединить узлы-братья, если они разделены только пунктуацией - PullRequest
0 голосов
/ 02 марта 2019

Учитывая этот (упрощенный) XML:

<p>
    <hi rend="italic">Some text</hi>, <hi rend="italic">and some more</hi>: <hi rend="italic"
        >followed by some more.</hi>
    <hi rend="bold">This text is fully in bold.</hi> Here we have plain text, which should't be
    touched. <hi rend="bold">Here we go with bold</hi>, <hi rend="bold">yet again.</hi>
</p>

Я хотел бы объединить все узлы, которые имеют одинаковые имена и атрибуты, вместе со всеми текстовыми узлами между ними, но только если normalize-space()текстовых узлов можно сократить до знаков препинания.

Другими словами, если два или более узлов hi[@rend='italic'] или hi[@rend='bold'] разделены текстовыми узлами, содержащими только знаки препинания и пробелы, они должны быть объединены.

Если, с другой стороны, текстовый узел между двумя hi[@rend='italic'] или двумя hi[@rend='bold'] узлами не сводится к пунктуации, его не следует трогать.

Я хотел бы узнатькак это сделать без жесткого элемента hi и атрибута @rend, т.е. я бы хотел, чтобы таблица стилей объединяла любые идентичные комбинации элементов / атрибутов, разделенные узлами пунктуации.

Символы пунктуации должны соответствовать регулярному выражению \p{P}.

Вывод должен выглядеть следующим образом:

<p>
    <hi rend="italic">Some text, and some more: followed by some more.</hi>
    <hi rend="bold">This text is fully in bold.</hi> Here we have plain text, which should't be
    touched. <hi rend="bold">Here we go with bold, yet again.</hi>
</p>

Большое спасибо заранее.

1 Ответ

0 голосов
/ 03 марта 2019

Я не уверен, что существует одношаговое решение, один подход, о котором я мог бы подумать, - это двухэтапное преобразование, при котором на первом шаге текстовые узлы между пунктами преобразуются в элементы, так что на втором шаге преобразования можно использовать group-adjacent.Далее я использовал XSLT 3 и составной группирующий ключ, состоящий из node-name() элемента и последовательности отсортированных значений атрибута node-name ():

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="#all"
    version="3.0">

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:mode name="text-to-el" on-no-match="shallow-copy"/>

  <xsl:function name="mf:match" as="xs:boolean">
      <xsl:param name="e1" as="element()"/>
      <xsl:param name="e2" as="element()"/>
      <xsl:sequence
        select="deep-equal(($e1!(node-name(), mf:sort(@* except @mf:punctuation)!data())), ($e2!(node-name(), mf:sort(@* except @mf:punctuation)!data())))"/>
  </xsl:function>

  <xsl:function name="mf:sort" as="attribute()*">
      <xsl:param name="attributes" as="attribute()*"/>
      <xsl:perform-sort select="$attributes">
          <xsl:sort select="node-name()"/>
      </xsl:perform-sort>
  </xsl:function>

  <xsl:template match="text()[matches(normalize-space(.), '^\p{P}+$') and mf:match(preceding-sibling::node()[1], following-sibling::node()[1])]" mode="text-to-el">
      <xsl:element name="{node-name(preceding-sibling::node()[1])}" namespace="{namespace-uri(preceding-sibling::node()[1])}">
          <xsl:apply-templates select="preceding-sibling::node()[1]/@*" mode="#current"/>
          <xsl:attribute name="mf:punctuation">true</xsl:attribute>
          <xsl:value-of select="."/>
      </xsl:element>
  </xsl:template>

  <xsl:variable name="punctuation-text-to-element">
      <xsl:apply-templates mode="text-to-el"/>
  </xsl:variable>

  <xsl:template match="/">
      <xsl:apply-templates select="$punctuation-text-to-element/node()"/>
  </xsl:template>

  <xsl:template match="*[*]">
      <xsl:copy>
          <xsl:apply-templates select="@*"/>
          <xsl:for-each-group select="node()" composite="yes" group-adjacent="if (. instance of element()) then (node-name(), mf:sort(@* except @mf:punctuation)!data()) else false()">
              <xsl:choose>
                  <xsl:when test="current-grouping-key() instance of xs:boolean and not(current-grouping-key())">
                      <xsl:apply-templates select="current-group()"/>
                  </xsl:when>
                  <xsl:otherwise>
                      <xsl:copy>
                          <xsl:apply-templates select="@*, current-group()/node()"/>
                      </xsl:copy>
                  </xsl:otherwise>
              </xsl:choose>
          </xsl:for-each-group>
      </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

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

В XSLT 2 у вас нет составных ключей группировки, но, конечно, можно string-join последовательность, использованную в примере XSLT 3, в качестве ключа группировки в одну строкуключ группировки, вам просто нужно убедиться, что вы используете символ-разделитель с string-join, который не встречается в именах элементов и значениях атрибутов.

Вместо использования xsl:mode преобразование идентичности должно бытьпрописано, и использование ! должно быть заменено на for .. return выражений или / шагов, где это возможно:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="#all"
    version="2.0">

    <xsl:param name="sep" as="xs:string">|</xsl:param>

    <xsl:template match="@*|node()" mode="#all">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" mode="#current"/>
        </xsl:copy>
    </xsl:template>

  <xsl:function name="mf:match" as="xs:boolean">
      <xsl:param name="e1" as="element()"/>
      <xsl:param name="e2" as="element()"/>
      <xsl:sequence
        select="deep-equal(($e1/(node-name(.), for $att in mf:sort(@* except @mf:punctuation) return data($att))), ($e2/(node-name(.), for $att in mf:sort(@* except @mf:punctuation) return data($att))))"/>
  </xsl:function>

  <xsl:function name="mf:sort" as="attribute()*">
      <xsl:param name="attributes" as="attribute()*"/>
      <xsl:perform-sort select="$attributes">
          <xsl:sort select="node-name(.)"/>
      </xsl:perform-sort>
  </xsl:function>

  <xsl:template match="text()[matches(normalize-space(.), '^\p{P}+$') and mf:match(preceding-sibling::node()[1], following-sibling::node()[1])]" mode="text-to-el">
      <xsl:element name="{node-name(preceding-sibling::node()[1])}" namespace="{namespace-uri(preceding-sibling::node()[1])}">
          <xsl:apply-templates select="preceding-sibling::node()[1]/@*" mode="#current"/>
          <xsl:attribute name="mf:punctuation">true</xsl:attribute>
          <xsl:value-of select="."/>
      </xsl:element>
  </xsl:template>

  <xsl:variable name="punctuation-text-to-element">
      <xsl:apply-templates mode="text-to-el"/>
  </xsl:variable>

  <xsl:template match="/">
      <xsl:apply-templates select="$punctuation-text-to-element/node()"/>
  </xsl:template>

  <xsl:template match="*[*]">
      <xsl:copy>
          <xsl:apply-templates select="@*"/>
          <xsl:for-each-group select="node()" group-adjacent="if (. instance of element()) then string-join((string(node-name(.)), for $att in mf:sort(@* except @mf:punctuation) return data($att)), $sep) else false()">
              <xsl:choose>
                  <xsl:when test="current-grouping-key() instance of xs:boolean and not(current-grouping-key())">
                      <xsl:apply-templates select="current-group()"/>
                  </xsl:when>
                  <xsl:otherwise>
                      <xsl:copy>
                          <xsl:apply-templates select="@*, current-group()/node()"/>
                      </xsl:copy>
                  </xsl:otherwise>
              </xsl:choose>
          </xsl:for-each-group>
      </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

http://xsltransform.net/asnmyS

...