Как сгруппировать элементы по содержимому (XSLT 2.0)? - PullRequest
3 голосов
/ 27 июня 2011

- измененный вопрос -

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

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

Каждая строка содержит основной английский (который может повторяться в файле десятки раз) и варианты перевода.В некоторых случаях это просто, так как все переведенные версии одинаковы, поэтому я бы закончил одной строкой, но в других случаях это могло бы быть более сложным.

Итак, предположим, что сегодня у меня есть 10 строк, содержащихтот же английский контент (# 1), 2 разных немецких варианта, 3 разных французских варианта, а остальные локали только один вариант, который мне нужно получить:

1 Пара с: 1 EN / 2 DE (v1 и v2) / 3 FR (v1, v2 и v3) / ...

И это повторяется для каждого сгруппированного уникального английского значения в моем списке

Модифицированный XML:

<Books>
<!--First English String (#1) with number of potential translations -->
<Para>
    <EN>English Content #1</EN>
    <DE>German Trans of #1 v1</DE>
    <FR>French Trans of #1 v1</FR>
    <!-- More locales here -->
</Para>
<Para>
    <EN>English Content #1</EN>
    <DE>German Trans of #1 v2</DE>
    <FR>French Trans of #1 v1</FR>
    <!-- More locales here -->
</Para>
<Para>
    <EN>English Content #1</EN>
    <DE>German Trans of #1 v1</DE>
    <FR>French Trans of #1 v2</FR>
    <!-- More locales here -->
</Para>
<!--Second English String (#2) with number of potential translations -->
<Para>
    <EN>English Content #2</EN>
    <DE>German Trans of #2 v1</DE>
    <FR>French Trans of #2 v1</FR>
    <!-- More locales here -->
</Para>
<Para>
    <EN>English Content #2</EN>
    <DE>German Trans of #2 v3</DE>
    <FR>French Trans of #2 v1</FR>
    <!-- More locales here -->
</Para>
<Para>
    <EN>English Content #2</EN>
    <DE>German Trans of #2 v2</DE>
    <FR>French Trans of #2 v1</FR>
    <!-- More locales here -->
</Para>
<!--Loads of additional English Strings (#3 ~ #n) with number of potential    translations -->

Текущие решения предлагают мне следующий вывод

<Books>
<Para>
    <EN>English Content #1</EN>
    <DE>German Trans of #1 v1</DE>
    <DE>German Trans of #1 v2</DE>
    <DE>German Trans of #2 v1</DE>
    <DE>German Trans of #2 v3</DE>
    <DE>German Trans of #2 v2</DE>
    <FR>French Trans of #1 v1</FR>
    <FR>French Trans of #1 v1</FR>
    <FR>French Trans of #1 v2</FR>
    <FR>French Trans of #2 v1</FR>
</Para>
</Books>

Таким образом, беря только первый тег EN, а затем группируя все остальные, независимо от различий между основными английскими строками.Хотя я стремлюсь получить следующее:

<Books>
<!-- First Grouped EN string and linked grouped translations -->
<Para>
    <EN>English Content #1</EN>
    <DE>German Trans of #1 v1</DE>
    <DE>German Trans of #1 v2</DE>
    <FR>French Trans of #1 v1</FR>
    <FR>French Trans of #1 v2</FR>
</Para>
<!-- Second Grouped EN string and linked grouped translations -->
<Para>
    <EN>English Content #2</EN>
    <DE>German Trans of #2 v1</DE>
    <DE>German Trans of #2 v3</DE>
    <DE>German Trans of #2 v2</DE>
    <FR>French Trans of #2 v1</FR>
</Para>
<!-- 3d to n Grouped EN string and linked grouped translations -->
</Books>

Ответы [ 4 ]

2 голосов
/ 27 июня 2011

Расширенный ответ XSLT 2.0 для выполнения обновления в запросах вопросов

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="Books">
        <xsl:copy>
            <xsl:for-each-group select="*" 
                group-by="EN">
                <xsl:copy>
                   <xsl:copy-of select="EN"/>
                   <xsl:for-each-group select="current-group()/*[not(local-name()='EN')]"
                        group-by=".">
                        <xsl:sort select="local-name()"/>
                        <xsl:copy-of select="."/>
                    </xsl:for-each-group>
                </xsl:copy>
            </xsl:for-each-group>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

Расширенный ответ XSLT 1.0 для выполнения обновления в запросах вопросов

Вы все еще можете использовать одно и то же решение, даже если вам нужны два разных типа ключей. Это первое простое решение, которое приходит на ум:

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:key name="main" match="Para" use="EN"/>
    <xsl:key name="locale" match="Para/*[not(self::EN)]" use="concat(../EN,.)"/>

    <xsl:template match="Books">
        <xsl:copy>
            <xsl:apply-templates select="Para[
                generate-id()
                = generate-id(key('main',EN)[1])]" mode="EN"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="*" mode="EN">
        <xsl:copy>
            <xsl:copy-of select="EN"/>
            <xsl:apply-templates select="../Para/*[
                generate-id()
                = generate-id(key('locale',concat(current()/EN,.))[1])]" mode="locale">
                <xsl:sort select="local-name()"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="*" mode="locale">
        <xsl:copy>
            <xsl:value-of select="."/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

При применении o

n the new provided input, produces:

<Books>
    <Para>
        <EN>English Content #1</EN>
        <DE>German Trans of #1 v1</DE>
        <DE>German Trans of #1 v2</DE>
        <FR>French Trans of #1 v1</FR>
        <FR>French Trans of #1 v2</FR>
    </Para>
    <Para>
        <EN>English Content #2</EN>
        <DE>German Trans of #2 v1</DE>
        <DE>German Trans of #2 v3</DE>
        <DE>German Trans of #2 v2</DE>
        <FR>French Trans of #2 v1</FR>
    </Para>
</Books>

Это преобразование XSLT 1.0 делает именно то, что вы просите, и его можно использовать в качестве отправной точки для создания более значимого дерева результатов, если вам нравится:

 <xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:strip-space elements="*"/>


    <xsl:key name="locale" match="Para/*[not(local-name()='EN')]" use="text()"/>

    <xsl:template match="Books">
        <xsl:copy>
            <Para>
                <xsl:copy-of select="Para[1]/EN"/>
                <xsl:apply-templates select="Para/*[
                    generate-id()
                    = generate-id(key('locale',text())[1])]" mode="group">
                    <xsl:sort select="local-name()"/>
                </xsl:apply-templates>
            </Para>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="*" mode="group">
        <xsl:copy>
            <xsl:value-of select="."/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

Пояснение:

  • xsl:key используется для группировки всех элементов по содержимому (но EN)
  • Простая прямая копия первого PARA/EN узла
  • Meunchian метод группировки с xsl:sort для вывода других элементов, сгруппированных по запросу (элементы с одинаковым содержимым сообщаются один раз)

Применительно к вводу, предоставленному в вопросе, дерево результатов выглядит так:

<Books>
   <Para>
      <EN>Some English Content</EN>
      <DE>German Trans v1</DE>
      <DE>German Trans v2</DE>
      <FR>French Trans v1</FR>
      <FR>French Trans v2</FR>
   </Para>
</Books>

Тот же результат (и более короткое преобразование) с XSLT 2.0 xsl:for-each-group:

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="Books">
        <xsl:copy>
            <Para>
                <xsl:copy-of select="Para[1]/EN"/>
                <xsl:for-each-group select="Para/*[not(local-name()='EN')]" 
                            group-by=".">
                    <xsl:sort select="local-name()"/>
                    <xsl:copy>
                        <xsl:value-of select="."/>
                    </xsl:copy>
                </xsl:for-each-group>
            </Para>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>
1 голос
/ 27 июня 2011

Это преобразование :

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:key name="kLangByValAndText"
  match="Para/*[not(self::EN)]"
  use="concat(name(), '+++', .)"/>

 <xsl:template match="/">
  <Books>
   <Para>
    <xsl:copy-of select="/*/Para[1]/EN"/>
    <xsl:for-each select=
    "/*/*/*[generate-id()
           =
            generate-id(key('kLangByValAndText',
                            concat(name(), '+++', .)
                            )
                            [1]
                       )
           ]
    ">
     <xsl:sort select="name()"/>
     <xsl:copy-of select="."/>
    </xsl:for-each>
   </Para>
  </Books>
 </xsl:template>
</xsl:stylesheet>

при применении к этому документу XML (расширенная версия предоставлена, чтобы сделать его более интересным):

<Books>
    <Para>
        <EN>Some English Content</EN>
        <DE>German Trans v1</DE>
        <FR>French Trans v1</FR>
        <!-- More locales here -->
    </Para>
    <Para>
        <EN>Some English Content</EN>
        <EN-US>Some English Content</EN-US>
        <DE>German Trans v1</DE>
        <FR>French Trans v1</FR>
        <!-- More locales here -->
    </Para>
    <Para>
        <EN>Some English Content</EN>
        <Australian>Some English Content</Australian>
        <DE>German Trans v1</DE>
        <FR>French Trans v2</FR>
        <!-- More locales here -->
    </Para>
    <!-- Much more para's hereafter containing variety of <EN> Content -->
</Books>

Дает нужный, правильный результат :

<Books>
   <Para>
      <EN>Some English Content</EN>
      <Australian>Some English Content</Australian>
      <DE>German Trans v1</DE>
      <EN-US>Some English Content</EN-US>
      <FR>French Trans v1</FR>
      <FR>French Trans v2</FR>
   </Para>
</Books>

Пояснение : мюнхенская группировка по составному (из 2-х частей) ключу.

Заметка : группировка только по переводу (как сделано в другом ответе на этот вопрос) теряет перевод <Australian> - примените решение @empo к этому же документу, и результат (<Australian> потеряно!):

<Books>
   <Para>
      <EN>Some English Content</EN>
      <DE>German Trans v1</DE>
      <EN-US>Some English Content</EN-US>
      <FR>French Trans v1</FR>
      <FR>French Trans v2</FR>
   </Para>
</Books>
0 голосов
/ 27 июня 2011

В Saxon 9, когда я применяю таблицу стилей

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

  <xsl:strip-space elements="*"/>
  <xsl:output indent="yes"/>

  <xsl:template match="Books">
    <xsl:copy>
      <xsl:for-each-group select="Para" group-by="EN">
        <xsl:apply-templates select="."/>
      </xsl:for-each-group>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="Para">
    <xsl:copy>
      <xsl:copy-of select="EN"/>
      <xsl:for-each-group select="current-group()/(* except EN)" group-by="node-name(.)">
        <xsl:for-each-group select="current-group()" group-by=".">
          <xsl:copy-of select="."/>
        </xsl:for-each-group>
      </xsl:for-each-group>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

на вход

<Books>
<!--First English String (#1) with number of potential translations -->
<Para>
    <EN>English Content #1</EN>
    <DE>German Trans of #1 v1</DE>
    <FR>French Trans of #1 v1</FR>
    <!-- More locales here -->
</Para>
<Para>
    <EN>English Content #1</EN>
    <DE>German Trans of #1 v2</DE>
    <FR>French Trans of #1 v1</FR>
    <!-- More locales here -->
</Para>
<Para>
    <EN>English Content #1</EN>
    <DE>German Trans of #1 v1</DE>
    <FR>French Trans of #1 v2</FR>
    <!-- More locales here -->
</Para>
<!--Second English String (#2) with number of potential translations -->
<Para>
    <EN>English Content #2</EN>
    <DE>German Trans of #2 v1</DE>
    <FR>French Trans of #2 v1</FR>
    <!-- More locales here -->
</Para>
<Para>
    <EN>English Content #2</EN>
    <DE>German Trans of #2 v3</DE>
    <FR>French Trans of #2 v1</FR>
    <!-- More locales here -->
</Para>
<Para>
    <EN>English Content #2</EN>
    <DE>German Trans of #2 v2</DE>
    <FR>French Trans of #2 v1</FR>
    <!-- More locales here -->
</Para>
</Books>

Я получаю результат

<Books>
   <Para>
      <EN>English Content #1</EN>
      <DE>German Trans of #1 v1</DE>
      <DE>German Trans of #1 v2</DE>
      <FR>French Trans of #1 v1</FR>
      <FR>French Trans of #1 v2</FR>
   </Para>
   <Para>
      <EN>English Content #2</EN>
      <DE>German Trans of #2 v1</DE>
      <DE>German Trans of #2 v3</DE>
      <DE>German Trans of #2 v2</DE>
      <FR>French Trans of #2 v1</FR>
   </Para>
</Books>
0 голосов
/ 27 июня 2011

Еще одна мюнхенская группировка с составными ключами для подуровня:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes" />
  <xsl:key name="english" match="EN" use="." />
  <xsl:key name="others" match="Para/*[not(self::EN)]" use="concat(../EN, '&#160;', ., '&#160;', name())" />
  <xsl:template match="/Books">
    <Books>
      <xsl:for-each select="Para/EN[generate-id() = generate-id(key('english', .)[1])]">
        <Para>
          <xsl:copy-of select=".|key('english', .)/../*[not(self::EN)][generate-id() = generate-id(key('others', concat(current(), '&#160;', ., '&#160;', name()))[1])]" />
        </Para>
      </xsl:for-each>
    </Books>
  </xsl:template>
</xsl:stylesheet>
...