Как создать элемент из двух окружающих элементов? - PullRequest
1 голос
/ 02 октября 2019

Я застрял с преобразованием XML в XML с использованием XSLT 2.0, где мне нужно преобразовать это:

<p>some mixed content <x h="">START:attr="value"</x> more mixed content <x h="">END</x> other mixed content</p>

В это:

<p>some mixed content <ph attr="value"> more mixed content </ph> other mixed content</p>

Так что в основном я хотел бызамените <x h="">START:attr="value"</x> на <ph attr="value">

и <x h="">END</x> на </ph> и обработайте все остальное как обычно.

Кто-нибудь знает, возможно ли это?

Моя основная проблема заключается в том, что я не могу понять, как найти элемент со значением END, а затем указать процессору XSLT (я использую saxon) обработать содержимое между первым и вторым появлением и, наконец,написать конечный элемент. Я знаком с тем, как создать элемент (включая атрибуты).

У меня есть специальный шаблон, соответствующий начальному элементу START: attr = "value". Поскольку обрабатываемый мной XML-документ содержит много других элементов, я бы предпочел рекурсивное решение, поэтому продолжайте обработку найденного содержимого между START и END, используя другие существующие шаблоны.

Образец XML (обратите внимание, что я не знаю заранее, будет ли родитель элементом ap)

<p> my sample text <b>mixed</b> more
  <x h="">START:attr="value"</x>
  This is mixed content <i>REALLY</i>, process it normally
  <x h="">END</x>
</p>

Моя таблица стилей

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0">

<xsl:output method="xml" indent="yes"/>

<xsl:template match="x[@h][starts-with(., 'START:')]">
    <ph>

       <xsl:for-each-group select="../*" group-starting-with="x[@h][. = 'START:']">
            <xsl:for-each-group select="current-group()" group-ending-with="x[@h][. = 'END']">

               <xsl:apply-templates select="@*|node()|text()"/>

            </xsl:for-each-group>
       </xsl:for-each-group>    
    </ph>
</xsl:template>

<xsl:template match="x[@h][starts-with(., 'END')]"/>

<xsl:template match="node()|@*">
    <xsl:copy copy-namespaces="no">
        <xsl:apply-templates select="node()|@*" /> 
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

Результат

<?xml version="1.0" encoding="UTF-8"?>
<p> my sample text <b>mixed</b> more
  <ph>mixed</ph>
  This is mixed content <i>REALLY</i>, process it normally

</p>

Я не могу понять, как поместить весь контент между START и END в тегах. Есть идеи?

1 Ответ

0 голосов
/ 04 октября 2019

Я бы сопоставил родительский элемент, содержащий эти маркеры, и использовал бы вложенную группу для каждой группы, конечно, все на основе шаблона преобразования идентификаторов в качестве базовой обработки:

  <xsl:template match="p[x[@h][starts-with(., 'START:')]]">
      <xsl:copy>
          <xsl:apply-templates select="@*"/>
          <xsl:for-each-group select="node()" group-starting-with="x[@h][starts-with(., 'START:')]">
              <xsl:choose>
                  <xsl:when test="self::x[@h][starts-with(., 'START:')]">
                      <xsl:variable name="value" select="replace(., '(START:attr=&quot;)([^&quot;]*)&quot;', '$2')"/>
                      <xsl:for-each-group select="current-group()[position() gt 1]" group-ending-with="x[@h][. = 'END']">
                          <xsl:choose>
                              <xsl:when test="current-group()[last()][self::x[@h][. = 'END']]">
                                  <ph attr="{$value}">
                                      <xsl:apply-templates select="current-group()[position() ne last()]"/>
                                  </ph>
                              </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>

Пример XSLT 3 в https://xsltfiddle.liberty -development.net / pPJ8LV4 , для XSLT 2 необходимо заменить используемое объявление xsl:mode на <xsl:template match="@* | node()"><xsl:copy><xsl:apply-templates select="@* | node()"/></xsl:copy></xsl:template>.

Поскольку Saxon также поддерживает XQuery с использованием окна поворота, где вы можетепроверить, что и начальное, и конечное условие вместе, может быть немного более кратким (хотя в XQuery вам нужно проделать дополнительную работу, чтобы удостовериться, что вы пропускаете материал, который не переносится, так как окно обычно отфильтровывает элементы, для которых условия не выполняются):

p ! <p>
{
    for tumbling window $group in node()
    start $s 
      when $s[self::x[@h][starts-with(., 'START:')]] or true()
    end $e 
      when $e[self::x[@h][. = 'END']] and $s[self::x[@h][starts-with(., 'START:')]] or not($s[self::x[@h][starts-with(., 'START:')]])
    return 
        if ($s[self::x[@h][starts-with(., 'START:')]])
        then
            <ph value="{replace($group[1], '(START:attr=&quot;)([^&quot;]*)&quot;', '$2')}">
            {
                tail($group)[not(position() = last())]
            }
            </ph>
        else $group
}
</p>

https://xqueryfiddle.liberty -development.net / 948Fn5s / 2

...