XSL: добавить родственный узел при создании значения через рекурсию - PullRequest
0 голосов
/ 11 сентября 2018

Ввод:

<root>
  <name>a,b,c,d,e,f,g,h,i,j,k,</name>
  <value>1,,3,,5,,7,,,,11,<value>
</root>

Ожидаемый результат

<root>
  <out>a:1|c:3|e:5|g:7|k:11</out>
  <e>5</e>
  <j/>
</root>

Мне удалось получить ожидаемый результат через рекурсию.Но мне нужно напечатать два значения среди тех, которые отдельно, как «е» и «J».во время цикла, если имена 'e' или 'j', эти элементы должны быть созданы. Я не смог этого сделать.

Код рекурсии

<xsl:template match="//root">
      <xsl:param name="columnName" select="a,b,c,d,e,f,g,h,i,j,k"></xsl:param>

        <root>
           <out>
            <xsl:call-template name="merge">
                <xsl:with-param name="name" select="normalize-space(name)" />
                 <xsl:with-param name="value" select="normalize-space(value)" />
            </xsl:call-template>                    
          </out>
       </root>
     </xsl:template>

    <xsl:template name="merge">
        <xsl:param name="name" />
        <xsl:param name="value" />
        <xsl:param name="separator" select="','" />  
           <xsl:variable name="currentValue"  select="substring-before($value, $separator)"/>
            <xsl:if test="$currentValue!=''">
                <xsl:value-of select="substring-before($name, $separator)" /><xsl:text>:</xsl:text>
                <xsl:value-of select="$currentValue" /><xsl:text>|</xsl:text>
            </xsl:if>
            <xsl:call-template name="merge">
                <xsl:with-param name="value" select="normalize-space(substring-after($value, $separator))" />
                 <xsl:with-param name="name" select="normalize-space(substring-after($name, $separator))" />
            </xsl:call-template>
    </xsl:template> 

Как я могудобавьте два элемента в цикле для получения 'out'.?

Ответы [ 3 ]

0 голосов
/ 11 сентября 2018

Если у вас есть хотя бы поддержка XSLT 2, вы можете использовать tokenize и преобразовать две последовательности значений в некоторый XML, который затем можете обработать:

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

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

  <xsl:template match="root">
      <xsl:copy>
          <xsl:variable name="pairs">
              <xsl:variable name="values" select="tokenize(value, ',')"/>
              <xsl:for-each select="tokenize(name, ',')[. castable as xs:QName]">
                <xsl:element name="{.}">
                  <xsl:variable name="pos" select="position()"/>
                  <xsl:value-of select="$values[$pos]"/>
                </xsl:element>
              </xsl:for-each>
          </xsl:variable>
          <out>
              <xsl:value-of select="$pairs/(* except (e, j))[normalize-space()]/concat(name(), ':', .)" separator="|"/>
          </out>
          <xsl:copy-of select="$pairs/(e, j)"/>
      </xsl:copy>
  </xsl:template>

</xsl:transform>

http://xsltransform.hikmatu.com/gWcDMez

С XSLT 3 вы можете даже передать последовательность строк, возвращаемых из tokenize, в шаблон для создания промежуточного XML (хотя я должен признать, что мне нравится предложение использовать карты, которые Майкл Кей уже опубликовал больше, чем это использование XML):

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

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

  <xsl:template match=".[. instance of xs:string]">
      <xsl:param name="values"/>
      <xsl:element name="{.}">{ let $pos := position() return $values[$pos] }</xsl:element>
  </xsl:template>

  <xsl:template match="root">
      <xsl:copy>
          <xsl:variable name="pairs">
              <xsl:apply-templates select="tokenize(name, ',')[. castable as xs:QName]">
                  <xsl:with-param name="values" select="tokenize(value, ',')"/>
              </xsl:apply-templates>
          </xsl:variable>
          <out>
              <xsl:value-of select="$pairs/(* except (e, j))[normalize-space()]!(name() || ':' || .)" separator="|"/>
          </out>
          <xsl:copy-of select="$pairs/(e, j)"/>
      </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty -development.net / bdxtqz / 1

0 голосов
/ 11 сентября 2018

Другой подход заключается в изменении шаблона «слияния» для вывода элементов формы <a>1</a>, которые можно хранить в переменной и манипулировать ими для получения нужного результата.

Попробуйте это XSLT

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
  <xsl:output method="xml" indent="yes" />

  <xsl:template match="//root">
    <xsl:variable name="nodes">
      <xsl:call-template name="merge">
        <xsl:with-param name="name" select="normalize-space(name)" />
        <xsl:with-param name="value" select="normalize-space(value)" />
      </xsl:call-template>              
    </xsl:variable>
    <root>
      <out>
        <xsl:value-of select="$nodes/*[normalize-space()]/concat(name(), ':', .)" separator="|" />
        <!-- Alternate approach if above does not work
        <xsl:for-each select="$nodes/*[normalize-space()]">
          <xsl:if test="position() > 1">|</xsl:if>
          <xsl:value-of select="concat(name(), ':', .)" />
        </xsl:for-each>
        -->
      </out>
      <xsl:copy-of select="$nodes/e" />
      <xsl:copy-of select="$nodes/j" />
    </root>
  </xsl:template>

  <xsl:template name="merge">
    <xsl:param name="name" />
    <xsl:param name="value" />
    <xsl:param name="separator" select="','" />  
    <xsl:variable name="currentName" select="substring-before($name, $separator)"/>
    <xsl:if test="$currentName!=''">
      <xsl:element name="{$currentName}">
        <xsl:value-of select="substring-before($value, $separator)" />  
      </xsl:element>
      <xsl:call-template name="merge">
        <xsl:with-param name="value" select="normalize-space(substring-after($value, $separator))" />
        <xsl:with-param name="name" select="normalize-space(substring-after($name, $separator))" />
      </xsl:call-template>
    </xsl:if>
  </xsl:template> 
</xsl:stylesheet>

См. Это в действии на http://xsltfiddle.liberty -development.net / 6qVRKwD / 1

0 голосов
/ 11 сентября 2018

Когда вы исходите из процедурного мышления, очень естественно думать такими словами, как «пока я перебираю значения, я вычислю что-то еще, что мне понадобится позже, как побочный эффект».Вообще с функциональным программированием лучше думать иначе.Рассчитайте каждую вещь, которая вам нужна в выходных данных, не пытайтесь вычислить несколько вещей за один проход ввода.

Однако вы можете предварительно вычислить переменные как функции входных данных, еслиони будут полезны не раз.Таким образом, в XSLT 2.0 вы могли бы сделать

<xsl:variable name="names" select="tokenize(name, ',')"/>
<xsl:variable name="values" select="tokenize(value, ',')"/>

Затем вы можете написать функцию

<xsl:function name="f:value" as="xs:string">
  <xsl:param name="key" as="xs:string"/>
  <xsl:sequence select="$values[index-of($names, $key)]"/>
</xsl:function>

, а затем вы можете сделать, например:

<e><xsl:value-of select="f:value('e')"/></e>
<j><xsl:value-of select="f:value('j')"/></j>

ЕслиВы можете использовать XSLT 3.0, тогда естественным решением для всего этого является использование карт.Как это:

<xsl:variable name="names" select="tokenize(name, ',')"/>
<xsl:variable name="map" as="map(*)"
  select="map:merge(
           for-each-pair($names, 
                         tokenize(value, ','),
                         function($k, $v) {map{$k, $v}}))"/>
</xsl:variable>
<out>
  <xsl:value-of select="$names ! (. || ':' || $map(.))" separator="|"/>
</out>
<e>{$map?e}</e>
<j>{$map?j}</j>
...