XSL-преобразование в пространствах имен - PullRequest
1 голос
/ 06 мая 2009

У меня есть XML, который выглядит примерно так:

<Root xmlns="http://widgetspecA.com/ns">
  ...any...
  <WidgetBox>
    <A/>
    <B/>
    <SmallWidget> <!-- minOccurs='0' -->
      ...any...
    </SmallWidget>
    <Widgets> <!-- minOccurs='0' -->
      ...any...
    </Widgets>
    ...any...
  </WidgetBox>
  ...any...
</Root>

и я хочу преобразовать это в это:

<Root xmlns="http://widgetspecB/ns">
  ...any...
  <WidgetBox>
    <A/>
    <B/>
    <Widgets>
      <Atom>
        ...any...
      </Atom>
      <Molecule>
        ...any...
      </Molecule>
    </Widgets>
    ...any...
  </WidgetBox>
  ...any...
</Root>

Другими словами:

<SmallWidget> в specA означает то же самое, что и <Atom> в specB, поэтому просто переименуйте элемент.

<Widgets> в specA означает то же самое, что и <Molecule> в specB, поэтому просто переименуйте элемент.

Оберните <Atom> и <Molecule> в элемент с именем <Widgets>, что означает нечто отличное от <Widgets>.

в specA

Все остальное копируется как есть, но в новом пространстве имен.

Каким будет XSLT для этого?

РЕШЕНИЕ ?: В конце концов я пошел с этим:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:old="http://widgetspecA.com/ns"
    xmlns="http://widgetspecB.com/ns"
    exclude-result-prefixes="old">

  <xsl:output method="xml"/>

  <xsl:template match="*">
    <xsl:element name="{name()}">
      <xsl:copy-of select="@*"/>
      <xsl:apply-templates/>
    </xsl:element>
  </xsl:template>

  <xsl:template match="old:SmallWidget" mode="single">
    <Atom>
      <xsl:apply-templates/>
    </Atom>
  </xsl:template>

  <xsl:template match="old:Widgets" mode="single">
      <Molecule>
        <xsl:apply-templates/>
      </Molecule>
  </xsl:template>

  <xsl:template match="old:SmallWidget[following-sibling::old:Widgets]">
      <Widgets>
       <xsl:apply-templates select="self::node()" mode="single"/>
       <xsl:apply-templates select="following-sibling::old:Widgets" mode="single"/>
      </Widgets>
  </xsl:template>

  <xsl:template match="old:Widgets[preceding-sibling::old:SmallWidget]"/>

  <xsl:template match="old:SmallWidget[not(following-sibling::old:Widgets)]">
      <Widgets>
       <xsl:apply-templates select="self::node()" mode="single"/>
      </Widgets>
  </xsl:template>

  <xsl:template match="old:Widgets[not(preceding-sibling::old:SmallWidget)]">
      <Widgets>
       <xsl:apply-templates select="self::node()" mode="single"/>
      </Widgets>
  </xsl:template>

</xsl:stylesheet>

1 Ответ

1 голос
/ 10 мая 2009

Хорошее решение XSLT сопоставит ваши понятные человеку правила с простыми шаблонными правилами. Вот правила, по вашим словам:

  1. <SmallWidget> в specA означает то же самое, что и <Atom> в specB, поэтому просто переименуйте элемент.
  2. <Widgets> в specA означает то же самое, что и <Molecule> в specB, поэтому просто переименуйте элемент.
  3. Оберните <Atom> и <Molecule> в элемент с именем <Widgets>, что означает что-то отличное от <Widgets>.
  4. Все остальное копируется как есть, но в новом пространстве имен.

Давайте попробуем:

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:in="http://widgetspecA.com/ns"
  xmlns="http://widgetspecB.com/ns"
  exclude-result-prefixes="in">

  <!-- 1. Rename <SmallWidget> -->
  <xsl:template mode="rename" match="in:SmallWidget">Atom</xsl:template>

  <!-- 2. Rename <Widgets> -->
  <xsl:template mode="rename" match="in:Widgets">Molecule</xsl:template>

  <!-- 3. Wrap <Atom> & <Molecule> with <Widgets> -->
  <xsl:template match="in:SmallWidget">
    <!-- ASSUMPTION: in:Widgets immediately follows in:SmallWidget -->
    <Widgets>
      <xsl:apply-templates mode="convert" select="."/>
      <xsl:apply-templates mode="convert" select="following-sibling::in:Widgets"/>
    </Widgets>
  </xsl:template>

          <!-- Skip by this in regular processing;
               it gets explicitly converted inside <Widgets> (see above) -->
          <xsl:template match="in:Widgets"/>

          <!-- Also, don't copy whitespace appearing
               immediately before in:Widgets -->
          <xsl:template match="text()
                               [following-sibling::node()[1][self::in:Widgets]]"/>


  <!-- 4: Everything copied as is, but in the new namespace -->

    <!-- Copy non-element nodes as is -->
    <xsl:template match="@* | text() | comment() | processing-instruction()">
      <xsl:copy/>
    </xsl:template>

    <!-- By default, just convert elements to new namespace
         (exceptions under #3 above) -->
    <xsl:template match="*">
      <xsl:apply-templates mode="convert" select="."/>
    </xsl:template>

            <xsl:template mode="convert" match="*">
              <!-- Optionally rename the element -->
              <xsl:variable name="name">
                <xsl:apply-templates mode="rename" select="."/>
              </xsl:variable>
              <xsl:element name="{$name}">
                <xsl:apply-templates select="@* | node()"/>
              </xsl:element>
            </xsl:template>

                    <!-- By default, just use the same local
                         name as in the input document -->
                    <xsl:template mode="rename" match="*">
                      <xsl:value-of select="local-name()"/>
                    </xsl:template>

</xsl:stylesheet>

Обратите внимание, что важно использовать функцию local-name(), а не функцию name(). Если вы используете name(), ваша таблица стилей сломается, если ваш входной документ начинает использовать префикс пространства имен, который явно не объявлен в вашей таблице стилей (если только вы не добавите атрибут namespace в <xsl:element>, чтобы принудительно применить пространство имен, даже когда префикс появляется). Однако, если мы используем local-name(), мы в безопасности; он никогда не будет включать префикс, поэтому элемент result примет пространство имен нашей таблицы стилей по умолчанию.

Запуск приведенной выше таблицы стилей для вашего образца входного документа дает именно то, что вы запрашивали:

<Root xmlns="http://widgetspecB.com/ns">...any...<WidgetBox>...any...
  <Widgets><Atom>
    ...any...
  </Atom><Molecule>
    ...any...
  </Molecule></Widgets>...any...
</WidgetBox>...any...</Root>

Дайте мне знать, если у вас есть какие-либо вопросы. Разве XSLT не мощный!

P.S. Если бы я хотел быть очень точным в репликации пробелов, как в вашем примере, я мог бы использовать пошаговую «цепную» обработку, где я применяю шаблоны только к одному узлу за раз, и каждое правило шаблона отвечает за продолжение обработки на его следующий родной брат. Но это казалось излишним для этой ситуации.

UPDATE: Новое решение, которое вы разместили, очень разумно. Некоторые могут быть упрощены, хотя. Я взял ваше новое решение и внес некоторые рекомендуемые изменения ниже, вместе с комментариями, указывающими, что я изменил и почему я сделал эти изменения.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:old="http://widgetspecA.com/ns"
    xmlns="http://widgetspecB.com/ns"
    exclude-result-prefixes="old">

  <!-- "xml" is the default; no real need for this
  <xsl:output method="xml"/>
  -->

  <!-- This works fine if you only want to copy elements, attributes,
       and text. Just be aware that comments and PIs will get
       effectively stripped out, because the default template rule
       for those is to do nothing.
  -->
  <xsl:template match="*">
    <xsl:element name="{name()}">
      <xsl:copy-of select="@*"/>
      <xsl:apply-templates/>
    </xsl:element>
  </xsl:template>

  <xsl:template match="old:SmallWidget" mode="single">
    <Atom>
      <xsl:apply-templates/>
    </Atom>
  </xsl:template>

  <xsl:template match="old:Widgets" mode="single">
      <Molecule>
        <xsl:apply-templates/>
      </Molecule>
  </xsl:template>

  <!-- You actually only need one rule for <old:SmallWidget>.
       Why? Because the behavior of this rule will always
       be exactly the same as the behavior of the other rule
       you supplied below.
  -->
  <xsl:template match="old:SmallWidget"> <!--[following-sibling::old:Widgets]">-->
      <Widgets>
                      <!-- "." means exactly the same thing as "self::node()" -->
       <xsl:apply-templates select="." mode="single"/>

       <!-- If the node-set is empty, then this will be a no-op anyway,
            so it's safe to have it here even for the case when
            <old:Widgets> is not present in the source tree. -->
                                    <!-- This XPath expression ensures
                                         that you only process the next
                                         sibling element - and then only
                                         if it's name is <old:Widgets>.
                                         Your schema might not allow it,
                                         but this is a clearer communication
                                         of your intention, and it will also
                                         work correctly if another
                                         old:SmallWidget/old:Widget pair
                                         appeared later in the document.
                                    -->
       <xsl:apply-templates select="following-sibling::*[1][self::old:Widgets]"
                            mode="single"/>
      </Widgets>
  </xsl:template>

                                  <!-- updated this predicate for the
                                       same reason as above. Answers the
                                       question: Is the element right before
                                       this one a SmallWidget? (as opposed to:
                                       Are there any SmallWidget elements
                                       before this one?) -->
  <xsl:template match="old:Widgets[preceding-sibling::*[1][self::old:SmallWidget]]"/>

  <!-- Removed, because this rule effectively has the same behavior as the other one above
  <xsl:template match="old:SmallWidget[not(following-sibling::old:Widgets)]">
      <Widgets>
       <xsl:apply-templates select="self::node()" mode="single"/>
      </Widgets>
  </xsl:template>
  -->

  <!-- no need for the predicate. The format of this pattern (just a name)
       causes this template rule's priority to be 0. Your other rule
       for <old:Widgets> above has priority of .5, which means that it
       will override this one automatically. You don't need to repeat
       the constraint. Alternatively, you could keep this predicate
       and remove the other one. Either way it will work. (It's probably
       a good idea to place these rules next to each other though,
       so you can read it like an if/else statement) -->
  <xsl:template match="old:Widgets">  <!--[not(preceding-sibling::*[1][self::old:SmallWidget])]">-->
      <Widgets>
       <xsl:apply-templates select="." mode="single"/>
      </Widgets>
  </xsl:template>

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