Преобразование XML в указанный порядок (DTD -> XSD)

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

Существует ли простой процесс преобразования (например, XSLT), который может взять старый XML-файл и упорядочить его элементы определенным образом, чтобы мы могли использовать новый XSD для его проверки?

Пример:

<Top>
  <A/>
  <D/>
  <B/>
  <C/>
  <A/>
</TOP>

В

<Top>
  <A/>
  <A/>
  <B/>
  <C/>
  <D/>
</TOP>

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


person Scanningcrew    schedule 25.11.2009    source источник


Ответы (2)


Вместо того, чтобы указывать все элементы для заказа в шаблоне, вы можете более декларативно использовать «список поиска», встроенный в таблицу стилей:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
   xmlns:my="my-namespace" 
   exclude-result-prefixes="my">
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
  <my:Top>
    <my:A>
      <my:AA/>
      <my:AB/>
      <my:AC/>
    </my:A>
    <my:B/>
    <my:C/>
    <my:D/>
  </my:Top>
  <xsl:template match="my:*">
    <xsl:param name="source"/>
    <xsl:variable name="current-lookup-elem" select="current()"/>
    <xsl:for-each select="$source/*[name()=local-name($current-lookup-elem)]">
      <xsl:copy>
        <xsl:apply-templates select="$current-lookup-elem/*">
          <xsl:with-param name="source" select="current()"/>
        </xsl:apply-templates>
        <xsl:copy-of select="text()"/>
      </xsl:copy>
    </xsl:for-each>
  </xsl:template>
  <xsl:template match="/Top">
    <xsl:apply-templates select="document('')/*/my:*">
      <xsl:with-param name="source" select="/"/>
    </xsl:apply-templates>
  </xsl:template>
</xsl:stylesheet>

Этот образец:

<Top>
  <A>
    <AC/>
    <AA/>
  </A>
  <D/>
  <B/>
  <C>yyy</C>
  <A>
    <AB/>
    <AC/>
    <AA>xxx</AA>
  </A>
</Top>

вернусь:

<Top>
    <A>
        <AA>xxx</AA>
        <AC/>
    </A>
    <A>
        <AA/>
        <AB/>
        <AC/>
    </A>
    <B/>
    <C>yyy</C>
    <D/>
</Top>
person Erlock    schedule 26.11.2009
comment
Мне нравится эта идея, я тоже попробую ее воплотить. - person Scanningcrew; 26.11.2009
comment
Я пытался использовать этот метод, но он только копирует значения (мой плохой пример, но каждый элемент имеет #PCDATA не пусто). Есть ли способ заставить его также печатать теги элементов? - person Scanningcrew; 01.12.2009
comment
Добавьте ‹xsl:copy-of select=text()/› непосредственно перед ‹/xsl:copy›. Я отредактировал приведенный выше код, чтобы включить его. - person Erlock; 01.12.2009
comment
Хорошая мысль, но он говорит, что у него есть XSD - окончательным решением здесь было бы отогнать его :) - person Pavel Minaev; 01.12.2009
comment
Конечно, но окончательное решение, о котором вы говорите, выходит далеко за рамки простого фрагмента кода, подобного этому. :) - person Erlock; 01.12.2009
comment
Возможно, это процессор netbeans (XSL Transform...), который я использую, но кажется, что шаблон внутри таблицы стилей не считывается и не обрабатывается с помощью первого шаблона применения с вводом. Даже с этим изменением я все еще получаю пустой выходной файл, используя ваш точный пример XSL и пример ввода. - person Scanningcrew; 01.12.2009
comment
Странно... Я протестировал его с двумя разными процессорами, и он работал хорошо. Возможно, вам следует проверить это с помощью Saxon или Xalan. - person Erlock; 02.12.2009
comment
Он также работает с w3schools.com/Xsl/. - person Erlock; 02.12.2009
comment
Так странно. работает в Xalan, но не w3schools.com/Xsl/ для меня?? - person Scanningcrew; 02.12.2009

Я предполагаю, что вы не хотите расположить элементы в алфавитном порядке, а расположите их в указанном вами порядке. Попробуйте это — вам понадобится процессор XSLT (например, Saxon), и сохраните этот файл как * .xsl.

<?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" method="xml" version="1.0" />

<xsl:template match="Top">
   <xsl:copy>
      <xsl:for-each select="A">
         <xsl:copy-of select="." />  
      </xsl:for-each> 
      <xsl:for-each select="B">
         <xsl:copy-of select="." />  
      </xsl:for-each>  
      <xsl:for-each select="C">
         <xsl:copy-of select="." />  
      </xsl:for-each>  
      <xsl:for-each select="D">
         <xsl:copy-of select="." />  
      </xsl:for-each>
   </xsl:copy>  
</xsl:template>

</xsl:stylesheet>

БОЛЬШОЕ предостережение: XML чувствителен к регистру, поэтому ваши теги <Top> и </TOP> не совпадают, поэтому у вас нет правильно сформированного XML, поэтому процессор XSLT выдаст ошибку и завершит работу.

<xsl:copy-of> копирует соответствующий элемент и ВСЕ его дочерние элементы (включая атрибуты). Чтобы изменить порядок более глубоких уровней, вы можете заменить xsl:copy-of на xsl:copy, а затем вызвать оттуда аналогичный шаблон для вывода следующего уровня по порядку.

person carillonator    schedule 26.11.2009
comment
Это очень... странный способ сделать это. Почему бы не просто <xsl:copy-of select="A"/><xsl:copy-of select="B"/>...? - person Pavel Minaev; 01.12.2009