2017-01-02 4 views
0

Я пытаюсь сделать что-то сложное в xslt У меня есть xml-файл, который содержит большой список братьев и сестер и в зависимости от имени, которое я хочу преобразовать их детям Общие правила для моего преобразования:XSLT трансформирует родственных отношений детям

  1. Если метка/имя «БЛОК», а затем открыть элемент «Block» с тегом/значение в качестве атрибута
  2. Если метка/имя «BLOCK_END» закрыть элемент «Block» (
  3. В все остальные случаи создают тег/имя элемента, помещают тег/значение и немедленно закрывают его

Так для следующего XML:

<message> 
    <tag> 
     <name>BLOCK</name> 
     <value>first</value> 
    </tag> 
    <tag> 
     <name>FOO</name> 
     <value>BAR</value> 
    </tag> 
    <tag> 
     <name>BLOCK</name> 
     <value>second</value> 
    </tag> 
    <tag> 
     <name>FOO2</name> 
     <value>BAR2</value> 
    </tag> 
    <tag> 
     <name>BLOCK_END</name> 
    </tag> 
    <tag> 
     <name>BLOCK_END</name> 
    </tag> 
    <tag> 
     <name>BLOCK</name> 
     <value>third</value> 
    </tag> 
    <tag> 
     <name>FOO3</name> 
     <value>BAR3</value> 
    </tag> 
    <tag> 
     <name>BLOCK_END</name> 
    </tag> 
</message> 

это результат Я надеюсь на:

<message> 
    <BLOCK id="first"> 
     <FOO>BAR</FOO> 
     <BLOCK id="second"> 
      <FOO2>BAR2</FOO2> 
     </BLOCK> 
    </BLOCK> 
    <BLOCK id="third"> 
     <FOO3>BAR3</FOO3> 
    </BLOCK">  
</message> 

Я использовал следующий XSLT. Это работает хорошо, но к сожалению, он заканчивает выполнение после encoutering первого BLOCK_END тега

<xsl:template match="/"> 
    <message> 
     <xsl:apply-templates select="message/tag[1]" /> 
    </message> 
</xsl:template> 

<xsl:template match="tag"> 
    <xsl:variable name="tagName" select="name"/> 
    <xsl:variable name="tagValue" select="value"/> 
    <xsl:choose> 
     <xsl:when test="$tagName = 'BLOCK'"> 
      <xsl:element name="{$tagName}"> 
       <xsl:attribute name="id"> 
        <xsl:value-of select="$tagValue"/> 
       </xsl:attribute> 
       <xsl:apply-templates select="./following-sibling::*[1]" /> 
      </xsl:element> 
     </xsl:when> 
     <xsl:when test="$tagName = 'BLOCK_END'"> 
      <!-- DO NOTHING--> 
     </xsl:when> 
     <xsl:otherwise> 
      <xsl:element name="{$tagName}"> 
       <xsl:value-of select="$tagValue"/> 
      </xsl:element> 
      <xsl:apply-templates select="./following-sibling::*[1]" /> 
     </xsl:otherwise> 
    </xsl:choose> 
</xsl:template> 

UPDATE: Благодаря BitTickler Я получаю ближе, но до сих пор не совсем там ,

+0

Вы можете использовать в XSLT 2.0 процессора как Saxon 9, Altova, XmlPrime? –

+0

@MartinHonnen К сожалению нет, я застрял в ядре XSLT 1.0 – DannyBoy

+0

Вместо «НИЧЕГО» в BLOCK_END я бы попробовал что-то вроде '' ' ', как вы хотите вернуться из текущей рекурсии, а затем продолжить с остальными тегами. – BitTickler

ответ

1

Проблема возникает из-за того, что рекурсивные вызовы шаблонов служат 2 целей (1 слишком много):

  1. протекал курсор в элементы ввода тегов один к концу.
  2. Управлять уровнем вложенности для вывода.

Для этого необходимо «вернуть» как текущее состояние выхода, так и состояние «итерации» из рекурсивной функции (шаблона).

На функциональном языке это может быть продемонстрировано, например. со следующим коротким кодом, подражающим ситуации.

type Node = 
    | Simple of string * string 
    | Nested of string * string * Node list 

let input = 
    [ 
     Simple ("BLOCK","first") 
     Simple ("FOO","BAR") 
     Simple ("BLOCK","second") 
     Simple ("FOO2","BAR2") 
     Simple ("BLOCK_END","") 
     Simple ("FOO3","BAR3") 
     Simple ("BLOCK_END","") 
    ] 

let rec transform (result,remaining) = 
    match remaining with 
    | [] -> result,remaining 
    | x::xs -> 
     match x with 
     | Simple (n,v) when n = "BLOCK" -> 
      let below,remaining' = transform ([],xs) 
      transform (result @ [Nested(n,v,below)],remaining') 
     | Simple (n,v) when n = "BLOCK_END" -> 
      result,xs 
     | Simple (n,v) -> 
      transform (result @[x],xs) 

transform ([],input) 

Теперь, когда есть одна стратегия решение, которое работает, остальные только вопрос в том, как применить эту стратегию преобразований XSLT.

Чтобы начать все, возможно, первый элемент <tag> необходимо преобразовать. И в его трансформации происходит рекурсия.

BLOCK_END должен каким-то образом вернуться из рекурсии, так что текущая позиция известна, поэтому секция BLOCK может возобновиться в этот момент позже.

Моя догадка до сих пор выглядит следующим образом:

<xsl:template match="/"> 
    <xsl:element name="message"> 
    <xsl:apply-templates select="/message/tag[1]" /> 
    </xsl:element> 
</xsl:template> 

<xsl:template name="nest" match="tag"> 
    <xsl:variable name="tagName" select="name"/> 
    <xsl:variable name="tagValue" select="value"/> 

    <xsl:choose> 
    <xsl:when test="./name='BLOCK'"> 
     <xsl:element name="{$tagName}"> 
     <xsl:attribute name="id"> 
      <xsl:value-of select="$tagValue"/> 
     </xsl:attribute> 
     <xsl:apply-templates select="./following-sibling::tag[1]" /> 
     </xsl:element> 
     <!--TODO: We must continue here with the remaining nodes. But we do not know how many 
     Nodes the block contained... Our cursor (.) is unaffected by previous recursion. --> 
     <!--<xsl:apply-templates select="./following-sibling::tag[1]" />--> 
    </xsl:when> 
    <xsl:when test="./name='BLOCK_END'"> 
     <!--No nothing--> 
    </xsl:when> 
    <xsl:otherwise> 
     <xsl:element name="{$tagName}"> 
     <xsl:value-of select="$tagValue"/> 
     </xsl:element> 
     <xsl:apply-templates select="./following-sibling::tag[1]" /> 
    </xsl:otherwise> 
    </xsl:choose> 

</xsl:template> 

Производство выход:

<message> 
    <BLOCK id="first"> 
     <FOO>BAR</FOO> 
     <BLOCK id="second"> 
     <FOO2>BAR2</FOO2> 
     </BLOCK> 
    </BLOCK> 
</message> 
+0

Многие справедливые моменты - особенно те, что указаны в комментарии. Благодаря вашим предложениям я чувствую, что приближаюсь. У меня возникают сомнения по поводу вызова шаблона после «END_BLOCK». Все дело в том, чтобы «выйти из рекурсивного вызова», чтобы сделал это волшебство. Если я сделаю еще один рекурсивный вызов после «END_BLOCK», тогда я перебираю весь документ перед закрытием элемента . Пожалуйста, уточните мое обновленное описание. – DannyBoy

+0

@ DannyBoy. Да, вы не хотите вводить другую рекурсию. Но вы хотите как-то продвигаться '' .''. Возможно, вам нужно переключиться на '' ... '' полностью. Если нет другого способа сделать это. Параметр затем содержит, например, следующий брат или около того. – BitTickler

+0

взял меня больше, чем ожидалось, но мне удалось найти решение. В разделе TODO я ищу «закрывающий» тег и повторно инициализирую цикл для следующего брата после закрывающего тега. Работает как шарм. Это была довольно крутая проблема и не могла сделать это без вашей помощи! Большое спасибо! – DannyBoy

2

Там может быть более короткий путь, но это по-прежнему кажется мне самой страйкой ghtforward подход:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xmlns:exsl="http://exslt.org/common" 
extension-element-prefixes="exsl"> 
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/> 
<xsl:strip-space elements="*"/> 

<xsl:key name="child-item" match="tag[not(name='BLOCK')]" use="generate-id(preceding-sibling::tag[name='BLOCK'][@level=current()/@level][1])" /> 

<xsl:key name="child-block" match="tag[name='BLOCK']" use="generate-id(preceding-sibling::tag[name='BLOCK'][@level=current()/@level - 1][1])" /> 

<xsl:template match="/message"> 
    <xsl:variable name="first-pass-rtf"> 
     <xsl:apply-templates select="tag[1]" mode="first-pass" /> 
    </xsl:variable> 
    <xsl:variable name="first-pass" select="exsl:node-set($first-pass-rtf)/tag" /> 
    <!-- output --> 
    <message> 
     <xsl:apply-templates select="$first-pass[name='BLOCK'][@level=1]"/> 
    </message> 
</xsl:template> 

<!-- first-pass templates --> 
<xsl:template match="tag[name='BLOCK']" mode="first-pass"> 
    <xsl:param name="level" select="0"/> 
    <tag level="{$level + 1}"> 
     <xsl:copy-of select="*"/> 
    </tag> 
    <xsl:apply-templates select="following-sibling::tag[1]" mode="first-pass"> 
     <xsl:with-param name="level" select="$level + 1"/> 
    </xsl:apply-templates> 
</xsl:template> 

<xsl:template match="tag" mode="first-pass"> 
    <xsl:param name="level"/> 
    <tag level="{$level}"> 
     <xsl:copy-of select="*"/> 
    </tag> 
    <xsl:apply-templates select="following-sibling::tag[1]" mode="first-pass"> 
     <xsl:with-param name="level" select="$level"/> 
    </xsl:apply-templates> 
</xsl:template> 

<xsl:template match="tag[name='BLOCK_END']" mode="first-pass"> 
    <xsl:param name="level"/> 
    <xsl:apply-templates select="following-sibling::tag[1]" mode="first-pass"> 
     <xsl:with-param name="level" select="$level - 1"/> 
    </xsl:apply-templates> 
</xsl:template> 

<!-- output templates --> 
<xsl:template match="tag[name='BLOCK']"> 
    <BLOCK id="{value}"> 
     <xsl:apply-templates select="key('child-item', generate-id()) | key('child-block', generate-id())" /> 
    </BLOCK> 
</xsl:template> 

<xsl:template match="tag"> 
    <xsl:element name="{name}"> 
     <xsl:value-of select="value"/> 
    </xsl:element> 
</xsl:template> 

</xsl:stylesheet> 

Это запустит переписав вас вход как:

<xsl:variable name="first-pass-rtf"> 
    <tag level="1"> 
     <name>BLOCK</name> 
     <value>first</value> 
    </tag> 
    <tag level="1"> 
     <name>FOO</name> 
     <value>BAR</value> 
    </tag> 
    <tag level="2"> 
     <name>BLOCK</name> 
     <value>second</value> 
    </tag> 
    <tag level="2"> 
     <name>FOO2</name> 
     <value>BAR2</value> 
    </tag> 
    <tag level="1"> 
     <name>BLOCK</name> 
     <value>third</value> 
    </tag> 
    <tag level="1"> 
     <name>FOO3</name> 
     <value>BAR3</value> 
    </tag> 
</xsl:variable> 

Тогда становится простым (г), чтобы связать каждый tag на родительский блок.

0

Конечный раствор имеет расширение на идее BitTickler в:

Я должен был twick исходного XML, так что конечный блок тег также содержит идентификатор (мы используем его для поиска соответствующего END_BLOCK тег для BLOCK тега)

<message> 
    <tag> 
     <name>BLOCK</name> 
     <value>first</value> 
    </tag> 
    <tag> 
     <name>FOO</name> 
     <value>BAR</value> 
    </tag> 
    <tag> 
     <name>BLOCK</name> 
     <value>second</value> 
    </tag> 
    <tag> 
     <name>FOO2</name> 
     <value>BAR2</value> 
    </tag> 
    <tag> 
     <name>BLOCK_END</name> 
     <value>second</value> 
    </tag> 
    <tag> 
     <name>BLOCK_END</name> 
     <value>first</value> 
    </tag> 
    <tag> 
     <name>BLOCK</name> 
     <value>third</value> 
    </tag> 
    <tag> 
     <name>FOO3</name> 
     <value>BAR3</value> 
    </tag> 
    <tag> 
     <name>BLOCK_END</name> 
     <value>third</value> 
    </tag> 
</message> 

Тогда хитрость заключается в том, чтобы сделать повторяющийся вызов шаблона для END_BLOCK + 1 тег (см «магия» комментарий). Там нет никакой выгоды делать «вызов-шаблон» (только некоторые остатки проб и ошибок на моей стороне), и я вернуться обратно к «Применить-шаблоны»

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

    <xsl:template match="/"> 
     <message> 
      <xsl:call-template name="transformTag"> 
       <xsl:with-param name="tag" select="message/tag[1]"/> 
      </xsl:call-template> 
     </message> 
    </xsl:template> 

    <xsl:template name="transformTag"> 
     <xsl:param name="tag"/> 
     <xsl:variable name="tagName" select="$tag/name"/> 
     <xsl:variable name="tagValue" select="$tag/value"/> 
     <xsl:choose> 
      <xsl:when test="$tagName = 'BLOCK'"> 
       <!-- OPEN A BLOCK, RECURENTLY CALL FOR THE NEXT ELEMENT AND THEN MAKE A RECURENT CALL ONCE AGAIN FOR THE NEXT BLOCK--> 
       <xsl:element name="{$tagName}"> 
        <xsl:attribute name="id"> 
         <xsl:value-of select="$tagValue"/> 
        </xsl:attribute> 
        <xsl:call-template name="transformTag"> 
         <xsl:with-param name="tag" select="$tag/following-sibling::*[1]"/> 
        </xsl:call-template> 
       </xsl:element> 
       <!-- THIS IS WHERE THE MAGIC HAPPENS--> 
       <xsl:variable name="closingTag" select="$tag/following-sibling::*[name='END_BLOCK' and substring-before(value,'@@')=$tagValue][1]"/> 

       <xsl:if test="$closingTag/name='END_BLOCK'"> 
        <xsl:variable name="nextTag" select="$closingTag/following-sibling::*[1]"/> 
        <xsl:if test="$nextTag[name() = 'tag']"> 
         <xsl:call-template name="transformTag"> 
          <xsl:with-param name="tag" select="$nextTag"/> 
         </xsl:call-template> 
        </xsl:if> 
       </xsl:if> 
      </xsl:when> 
      <xsl:when test="$tagName = 'END_BLOCK'"> 
       <!-- DO NOTHING AND EXIT THE RECURENT CALL (THIS CLOSES THE TAG)--> 
      </xsl:when> 
      <xsl:otherwise> 
       <!-- PRINT THE REGULAR TAG AND RECURENTLY CALL FOR THE NEXT TAG--> 
       <xsl:element name="_{$tagName}"> 
        <xsl:value-of select="$tagValue"/> 
       </xsl:element> 
       <xsl:call-template name="transformTag"> 
        <xsl:with-param name="tag" select="$tag/following-sibling::*[1]"/> 
       </xsl:call-template> 
      </xsl:otherwise> 
     </xsl:choose> 
    </xsl:template> 
</xsl:stylesheet> 
+0

«* пришлось обмануть исходный xml *» Это делает совершенно другой вопрос.Когда мы отвечаем на вопросы XSLT здесь, мы предполагаем, что вводится входная XML-схема. Если у вас есть возможность изменить его по своему вкусу, почему бы не сделать его фактически простым в обработке? –

+0

«пришлось обмануть исходный xml» не означает «Я тот, кто генерирует xml и может создать структуру, как мне нравится». Я имел в виду, что мой оригинальный xml намного сложнее и имеет гораздо больше данных. В этом вопросе SO я отфильтровал xml, чтобы поймать суть моей проблемы. К счастью, «идентификатор закрывающего блока» был тем, что я имел доступным, но я его опустил изначально, поскольку я (ошибочно) считал, что это не имеет значения – DannyBoy

Смежные вопросы