Сортировка XML с помощью XSLT - вся XML-схема неизвестна - PullRequest
7 голосов
/ 08 июля 2011

Мне интересно, позволяет ли XSLT сортировать XML-файл, если я не знаю всей XML-схемы.

Например, я хотел бы отсортировать следующий XML-файл.
Сортировать / CATALOG / CD элементы по / CATALOG / CD / TITLE

<CATALOG attrib1="value1">
  <DVD2>
    <TITLE>The Godfather2</TITLE>
  </DVD2>
  <CD>
    <TITLE>Hide your heart</TITLE>
    <ARTIST>Bonnie Tyler</ARTIST>
    <COUNTRY>UK</COUNTRY>
    <COMPANY>CBS Records</COMPANY>
    <PRICE>9.90</PRICE>
    <YEAR>1988</YEAR>
  </CD>
  <CD attrib4="value4">
    <TITLE>Empire Burlesque</TITLE>
    <ARTIST>Bob Dylan</ARTIST>
    <COUNTRY>USA</COUNTRY>
    <COMPANY>Columbia</COMPANY>
    <PRICE>
      <CATALOG>
        <CD><TITLE>E</TITLE></CD>
        <CD><TITLE>I</TITLE></CD>
        <CD><TITLE>D</TITLE></CD>
      </CATALOG>
    </PRICE>
    <YEAR>1985</YEAR>
  </CD>
  <CD attrib2="value2">
    <TITLE attrib3="value3">Greatest Hits</TITLE>
    <ARTIST>Dolly Parton</ARTIST>
    <COUNTRY>USA</COUNTRY>
    <COMPANY>RCA</COMPANY>
    <PRICE>9.90</PRICE>
    <YEAR>1982</YEAR>
  </CD>
  <DVD>
    <TITLE>The Godfather1</TITLE>
  </DVD>
</CATALOG>

Вывод должен быть:

<CATALOG attrib1="value1">
  <CD attrib4="value4">
    <TITLE>Empire Burlesque</TITLE>
    <ARTIST>Bob Dylan</ARTIST>
    <COUNTRY>USA</COUNTRY>
    <COMPANY>Columbia</COMPANY>
    <PRICE>
      <CATALOG>
        <CD><TITLE>E</TITLE></CD>
        <CD><TITLE>I</TITLE></CD>
        <CD><TITLE>D</TITLE></CD>
      </CATALOG>
    </PRICE>
    <YEAR>1985</YEAR>
  </CD>
  <CD attrib2="value2">
    <TITLE attrib3="value3">Greatest Hits</TITLE>
    <ARTIST>Dolly Parton</ARTIST>
    <COUNTRY>USA</COUNTRY>
    <COMPANY>RCA</COMPANY>
    <PRICE>9.90</PRICE>
    <YEAR>1982</YEAR>
  </CD>
  <CD>
    <TITLE>Hide your heart</TITLE>
    <ARTIST>Bonnie Tyler</ARTIST>
    <COUNTRY>UK</COUNTRY>
    <COMPANY>CBS Records</COMPANY>
    <PRICE>9.90</PRICE>
    <YEAR>1988</YEAR>
  </CD>
  <DVD2>
    <TITLE>The Godfather2</TITLE>
  </DVD2>
  <DVD>
    <TITLE>The Godfather1</TITLE>
  </DVD>
</CATALOG>

Вот одна из многих попыток, которые я сделал:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <!--<CATALOG>-->
    <xsl:for-each select="CATALOG/CD">
      <xsl:sort select="TITLE" />
      <xsl:copy-of select="."/>
    </xsl:for-each>
    <!--</CATALOG>-->
  </xsl:template>
</xsl:stylesheet>

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

Эту функцию легко реализовать, например, используя .NET (с объектами XmlDocument и XmlNode) или библиотеку Python lxmx, но возможно ли это с XSLT?

Спасибо!

Примечание. Нелегко найти образец входного XML, который позволит избежать неправильного понимания вопроса во всех случаях. Но я постараюсь максимально подробно описать проблему:

  • должны быть отсортированы только элементы CD прямо под CATALOG (например, элементы CD в разделе Боба Дилана должны быть оставлены нетронутыми)
  • все равно, находятся ли элементы, кроме CD (например, DVD и DVD2), в начале или конце списка
  • без элементов, атрибутов, значений, комментариев, поэтому в выходном XML ничего не должно быть пропущено
  • не-CD элементы (например, DVD и DVD2) не должны сортироваться подэлементом TITLE

Ответы [ 4 ]

2 голосов
/ 08 июля 2011

Это работа для преобразования личности? Это можно использовать для копирования XML, схема которого неизвестна

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

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

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:template match="@*|node()">
      <xsl:copy>
         <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
   </xsl:template>

   <xsl:template match="CATALOG">
      <xsl:copy>
         <xsl:apply-templates select="@*" />
         <xsl:apply-templates select="CD">
            <xsl:sort select="TITLE"/>
         </xsl:apply-templates>
         <xsl:apply-templates select="*[local-name() != 'CD']" />
      </xsl:copy>
   </xsl:template>
</xsl:stylesheet>

Таким образом, при сопоставлении CATALOG вы все равно можете копировать любые атрибуты и любые дочерние элементы, не относящиеся к CD, в схеме, не зная явно их имен. Обратите внимание, что если до CATALOG есть, например, элементы DVD, они все будут перемещены после отсортированных элементов CD в этом случае.

1 голос
/ 08 июля 2011

Продолжая просто изменять преобразование идентичности (что может быть небезопасно), я думаю, что следующее должно быть эквивалентно ответу @ Тим.

ПРИМЕЧАНИЕ Я вообще не продвигаю эту технику, если вы не понимаете, каково общее поведение преобразования идентичности.

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

    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* 
                | node()[not(self::CD[parent::CATALOG])]"/>
            <xsl:apply-templates select="CD[parent::CATALOG]">
                <xsl:sort select="TITLE"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

или, если вам небезразличны другие элементы DVD и DVD2, вы можете сделать:

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

    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:apply-templates select="CD[parent::CATALOG]">
                <xsl:sort select="TITLE"/>
            </xsl:apply-templates>
            <xsl:apply-templates select="node()
                [not(self::CD[parent::CATALOG])]"/>
        </xsl:copy>
    </xsl:template>

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

Чтобы получить все атрибуты в элементе CATALOG, вы можете написать:

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

    <xsl:template match="CATALOG">
        <xsl:copy>
            <xsl:copy-of select="@*"/>

            <xsl:copy-of select="CD[1]/preceding-sibling::*"/>
            <xsl:for-each select="CD">
                <xsl:sort select="TITLE"/>
                <xsl:copy-of select="."/>
            </xsl:for-each>
            <xsl:copy-of select="CD[last()]/following-sibling::*"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

Результат:

<CATALOG atr1="value1" atr2="value2">
    <DVD>
        <FORMAT>DVD-9</FORMAT>
    </DVD>
    <CD>
        <TITLE>1999 Grammy Nominees</TITLE>
        <ARTIST>Many</ARTIST>
        <COUNTRY>USA</COUNTRY>
        <COMPANY>Grammy</COMPANY>
        <PRICE>10.20</PRICE>
        <YEAR>1999</YEAR>
    </CD>
    <CD>
        <TITLE>Big Willie style</TITLE>
        <ARTIST>Will Smith</ARTIST>
        <COUNTRY>USA</COUNTRY>
        <COMPANY>Columbia</COMPANY>
        <PRICE>9.90</PRICE>
        <YEAR>1997</YEAR>
    </CD>
    ...
    <BLUERAY>
        <TITLE>Contact</TITLE>
        <YEAR>1997</YEAR>
    </BLUERAY>
</CATALOG>
0 голосов
/ 08 июля 2011

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

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
  <xsl:output method="xml" indent="yes" />

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

  <xsl:template match="CATALOG">
    <xsl:copy>
      <xsl:apply-templates select="@* | *">
        <xsl:sort select="TITLE" />
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

Это стандартный шаблон копирования match="@* | node()" плюс особый случай для узла CATALOG, где указаны критерии сортировки.

Обратите внимание, что ясделал селектор для правила apply-templates внутри шаблона secod, немного отличающийся от стандартного шаблона копирования (@* | *).Это связано с тем, что стандартный селектор шаблона копирования также включает в себя текстовые узлы, однако у текстовых узлов нет элемента TITLE, и поэтому директива sort в конечном итоге ставит их все сначала, что выглядит немного странно (попробуйте и посмотрите).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...