Выравнивание полей на основе позиции с использованием XSLT

У меня есть XML-документ с записями, которые имеют отдельные поля для предметных тегов на английском и испанском языках. Отдельные теги разделяются точкой с запятой.

<collections>
  <collection name="anyCollection">
    <record>
      <field name="materia">comida; bebida; fiesta</field>
      <field name="subject">food; drink; party</field>
      <field name="recordid">abc0001</field>
    </record>
    <record>
      <field name="materia">comida; bebida; fiesta</field>
      <field name="subject">food; drink; party</field>
      <field name="recordid">abc0002</field>
    </record>
    <record>
      <field name="materia">comida; bebida; fiesta</field>
      <field name="subject">food; drink; party</field>
      <field name="recordid">abc0003</field>
    </record>
    <record>
      <field name="materia">fiesta; sombreros; música; baile; agua; cerveza; sopa</field>
      <field name="subject">party; hats; music; dance; water; beer; soup</field>
      <field name="recordid">abc0004</field>
    </record>
    <record>
      <field name="materia">comida; bebida; fiesta; sombreros; música</field>
      <field name="subject">food; drink; party; hats; music</field>
      <field name="recordid">abc0005</field>
    </record>
    <record>
      <field name="materia">comida; bebida; cerveza; agua</field>
      <field name="subject">food; drink; beer; water</field>            
      <field name="recordid">abc0006</field>
    </record>        
    <record>
      <field name="materia">fiesta; sombreros; música; baile; agua; cerveza</field>
      <field name="subject">party; hats; music; dance; water; beer</field>
      <field name="recordid">abc0007</field>
    </record>       
  </collection>
</collections>

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

<?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" exclude-result-prefixes="xs"    
  version="2.0">

  <xsl:variable name="field">
    <xsl:for-each  
      select="collections/collection[@name='anyCollection']/record">
      <record>
        <xsl:for-each select="field">
          <field>
            <xsl:for-each select="tokenize(.[@name='materia'],';')">
              <materia>
                <xsl:value-of select="."/>
              </materia>
            </xsl:for-each>
            <xsl:for-each select="tokenize(.[@name='subject'],';')">
              <subject>
                <xsl:value-of select="."/>
              </subject>
            </xsl:for-each>
          </field>
        </xsl:for-each>
      </record>
    </xsl:for-each>
  </xsl:variable>

  <xsl:variable name="align">        
    <xsl:for-each select="$field/record/field">           
      <languagePair1>                
        <xsl:for-each select="materia[1]">                    
          <xsl:value-of select="."/> 
          <xsl:text>_</xsl:text>    
        </xsl:for-each>
        <xsl:for-each select="subject[1]">                    
          <xsl:value-of select="."/> 
          <xsl:text>&#10;</xsl:text>    
        </xsl:for-each>
      </languagePair1>
      <languagePair2>
        <xsl:for-each select="materia[2]">                    
          <xsl:value-of select="."/> 
          <xsl:text>_</xsl:text>    
        </xsl:for-each>                                               
        <xsl:for-each select="subject[2]">                    
          <xsl:value-of select="."/> 
          <xsl:text>&#10;</xsl:text>    
        </xsl:for-each>                             
      </languagePair2>
    </xsl:for-each>
  </xsl:variable>

  <xsl:template match="/">             
    <xsl:for-each-group select="$align/languagePair1" group-by=".">           
      <xsl:value-of select="current-grouping-key()"/>           
    </xsl:for-each-group>
    <xsl:for-each-group select="$align/languagePair2" group-by=".">           
      <xsl:value-of select="current-grouping-key()"/>            
    </xsl:for-each-group>
  </xsl:template>

</xsl:stylesheet>

Вот основной вывод, который я хочу:

comida_food

bebida_drink

fiesta_party

sombreros_hats

música_music

Мне также нужно вывести recordidс каждым тегом, но я еще не смог включить это в таблицу стилей.

При добавлении этой информации желаемый результат будет выглядеть так:

comida_food
abc0001
abc0002
abc0003
abc0005
abc0006

bebida_drink
abc0001
abc0002
abc0003
abc0005
abc0006

fiesta_party
abc0001
abc0002
abc0003
abc0004
abc0005
abc0007

sombreros_hats
abc0004
abc0005
abc0007

música_music
abc0004
abc0005
abc0007

3 ответа

Решение

Хороший вариант использования fn: для каждой пары в XPath 3.0:

for-each-pair(
  tokenize($materia, '; '), 
  tokenize($subject, '; '),
  function($x, $y) { $x || '_' || $y || '&#xa;' })

Доступен в Saxon-PE 9.5.1.1.

Используя fn: для каждой пары в XPath 3.0 с Saxon-PE 9.5.1.1, требуемый вывод генерируется следующей таблицей стилей:

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

  <xsl:output method="text"/>
  <xsl:template match="/">
    <xsl:for-each-group select="collections/collection[@name = 'anyCollection']/record"
      group-by="for-each-pair(
      tokenize(field[@name = 'materia'], '; '), 
      tokenize(field[@name = 'subject'], '; '),
      function($x, $y) { $x || '_' || $y || '&#xa;' })">

      <xsl:value-of select="current-grouping-key()"/>

      <xsl:for-each-group select="current-group()" group-by="field[@name='recordid']">
        <xsl:sort select="substring(translate(current-grouping-key(),'ÁÉÍÓÚÜáéíóúü','AEIOUUaeiouu'),4)" data-type="number"/>                
        <xsl:value-of select="current-grouping-key()"/>
        <xsl:text>&#10;</xsl:text>    
      </xsl:for-each-group>

      <xsl:text>&#10;</xsl:text>                       
    </xsl:for-each-group>
  </xsl:template>
</stylesheet>

Я могу получить желаемый результат с помощью следующего кода:

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


<xsl:output method="text"/>

<xsl:template match="/">
  <xsl:for-each-group select="collections/collection[@name = 'anyCollection']/record" group-by="tokenize(field[@name = 'materia'], '; ')">
    <xsl:variable name="pos" select="position()"/>
    <xsl:variable name="subjects" select="tokenize(field[@name = 'subject'], '; ')"/>
    <xsl:value-of select="concat(current-grouping-key(), '_', $subjects[$pos]), current-group()/field[@name = 'recordid']" separator="&#10;"/>
    <xsl:text>&#10;&#10;</xsl:text>
  </xsl:for-each-group>
</xsl:template>

</xsl:stylesheet>

Этого достаточно? Я не уверен, каким образом вы хотите, чтобы код был динамическим, я предполагал, что вы знаете name атрибуты field элементы, которые вас интересуют (т.е. materia, subject).

Другие вопросы по тегам