2010-11-17 3 views
8

не уверен, если это возможно без того, чтобы пройти через несколько проходов, но я все равно спрошу (мой XSL немного ржавый)XSLT пропустить уже «посещаемые» узлы

У меня есть документ XML, который содержит узлы следующим образом: (! реальный файл содержит множество структурных тегов, взаимозависимостей, ни один из которых циркулярный)

<structures> 
<structure id="STRUCT_A"> 
    <field idref="STRUCT_B" name="b"/> 
    <field idref="STRUCT_C" name="c"/> 
    <field idref="FIELD_D" name="d"/> 
</structure> 

<structure id="STRUCT_B"> 
    <field idref="STRUCT_C" name="c"/> 
    <field idref="FIELD_E" name="e"/> 
</structure> 

<structure id="STRUCT_C"> 
    <field idref="FIELD_E" name="e"/> 
    <field idref="FIELD_F" name="f"/> 
    <field idref="FIELD_G" name="g"/> 
</structure> 
</structures> 

То, что я хочу сделать, это произвести некоторый текст (в данном случае C++ struct), и очевидным требованием является порядок o е в struct с, так что мой идеальным выходом будет

struct STRUCT_C 
{ 
    FIELD_E e; 
    FIELD_F f; 
    FIELD_G g; 
}; 

struct STRUCT_B 
{ 
    STRUCT_C c; 
    FIELD_E e; 
}; 

struct STRUCT_A 
{ 
    STRUCT_B b; 
    STRUCT_C c; 
    FIELD_D d; 
}; 

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

До сих пор я могу обнаружить, чтобы увидеть, если structure имеет никаких зависимостей, со следующим битом XSL:

<xsl:for-each select="descendant::*/@idref"> 
    <xsl:variable name="name" select="."/> 
    <xsl:apply-templates select="//structure[@id = $name]" mode="struct.dep"/> 
</xsl:for-each> 

(это происходит внутри <xsl:template match="structure">)

Теперь, теоретически, я мог бы тогда следуйте этой «цепочке» зависимостей и генерируйте struct s для каждой записи сначала, а затем тот, который я нахожу сейчас, однако, как вы можете себе представить, это создает копии экземпляров той же структуры, что является болью.

Есть ли все-таки, чтобы избежать копий? В принципе, как только структура была посещена, и если мы снова заходим, чтобы не беспокоить вывод кода для нее ... Мне не нужен полный xslt для этого (если только это не тривиально!), А просто любые идеи о подходах ...

Если нет, то я мог бы теоретически обернуть struct с #ifdef/#define/#endif настороже, так что компилятор использует только первое определение, однако это действительно NASTY! :(

(ПРИМЕЧАНИЯ: XSLT 1.0, xsltproc на Linux: Использование LibXML 20623, 10115 и LibXSLT libexslt 812)

+0

Отличный вопрос, +1. См. Мой ответ для полного и короткого решения. :) –

ответ

7

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

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

<xsl:variable name="vLeafs" select="/*/structure[not(field/@idref = /*/structure/@id)]"/> 

<xsl:template match="/*"> 
    <xsl:apply-templates select="$vLeafs[1]"> 
    <xsl:with-param name="pVisited" select="'|'"/> 
    </xsl:apply-templates> 

</xsl:template> 

<xsl:template match="structure"> 
    <xsl:param name="pVisited"/> 

struct <xsl:value-of select="@id"/> 
{<xsl:text/> 
    <xsl:apply-templates/> 
}; 
    <xsl:variable name="vnewVisited" 
     select="concat($pVisited, @id, '|')"/> 
    <xsl:apply-templates select= 
    "../structure[not(contains($vnewVisited, concat('|', @id, '|'))) 
       and 
       not(field/@idref 
          [not(contains($vnewVisited, concat('|', ., '|'))) 
          and 
          . = ../../../structure/@id 
          ] 
        ) 
       ] [1] 
    "> 
    <xsl:with-param name="pVisited" select="$vnewVisited"/> 
    </xsl:apply-templates> 
</xsl:template> 

<xsl:template match="field"> 
    <xsl:value-of select="concat('&#xA; ', @idref, ' ', @name, ';')"/> 
</xsl:template> 
</xsl:stylesheet> 

при нанесении на поставленном XML документа:

<structures> 
<structure id="STRUCT_A"> 
    <field idref="STRUCT_B" name="b"/> 
    <field idref="STRUCT_C" name="c"/> 
    <field idref="FIELD_D" name="d"/> 
</structure> 

<structure id="STRUCT_B"> 
    <field idref="STRUCT_C" name="c"/> 
    <field idref="FIELD_E" name="e"/> 
</structure> 

<structure id="STRUCT_C"> 
    <field idref="FIELD_E" name="e"/> 
    <field idref="FIELD_F" name="f"/> 
    <field idref="FIELD_G" name="g"/> 
</structure> 
</structures> 

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

struct STRUCT_C 
{ 
    FIELD_E e; 
    FIELD_F f; 
    FIELD_G g; 
}; 


struct STRUCT_B 
{ 
    STRUCT_C c; 
    FIELD_E e; 
}; 


struct STRUCT_A 
{ 
    STRUCT_B b; 
    STRUCT_C c; 
    FIELD_D d; 
}; 

Объяснение: structure элементы обрабатываются строго по одному. В любой момент мы обрабатываем первый элемент structure, чей id еще не зарегистрирован в параметре pVisited и который не имеет значения field/@idref, которое еще не находится в параметре и относится к существующему элементу structure.

+0

+1 хороший подход: процесс из листьев, чтобы вам не пришлось изменять и передавать информацию о состоянии. Я все еще перевариваю ваш ответ ... – LarsH

+0

+1, работает для меня, теперь нужно сопоставить его с реальным XML-документом, который у меня есть, это будет весело! Спасибо, большое спасибо! – Nim

+0

@ Dimitre: +1 Очень хороший ответ! – 2010-11-17 18:16:24

2

Ох, это гораздо сложнее, чем казалось на первый взгляд +1 за хороший вопрос

..

Я думаю, что лучший способ выполнить это в XSLT 1.0 - это передать накопительный параметр всякий раз, когда вы применяете шаблоны к структуре. Параметр (назовите его «$ visited-structure») представляет собой список имен с пробелами структуры, которые вы уже обработали.

Обновление: наконец-то получилось. :-)

В шаблоне для обработки структуры проверьте, не зависят ли какие-либо другие структуры, от которых это зависит, в $ visited-структурах. Если нет, сгенерируйте код для этой структуры и запишите шаблон, выбрав следующую структуру без посещения, добавив текущее имя структуры к параметру $ Посещенные-структуры. В противном случае не создавайте код для структуры, но рекурсируйте по шаблону, выбирая первую структуру зависимостей, передавая параметр $ Посещенные-структуры немодифицированным.

Вот код ...

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    version="1.0"> 
    <xsl:key name="structuresById" match="/*/structure" use="@id" /> 

    <xsl:template match="structures"> 
     <xsl:apply-templates select="structure[1]" > 
     <!-- a space-delimited list of id's of structures already processed, with space 
      at beginning and end. Could contain duplicates. --> 
     <xsl:with-param name="visited-structures" select="' '"/> 
     </xsl:apply-templates> 
    </xsl:template> 

    <xsl:template match="structure"> 
     <xsl:param name="visited-structures" select="' '" /> 
     <xsl:variable name="dependencies" select="key('structuresById', field/@idref) 
        [not(contains($visited-structures, @id))]"/> 
     <xsl:choose> 
     <xsl:when test="$dependencies"> 
      <xsl:apply-templates select="$dependencies[1]"> 
       <xsl:with-param name="visited-structures" select="$visited-structures"/> 
      </xsl:apply-templates>    
     </xsl:when> 
     <xsl:otherwise> 
      <!-- Now generate code for this structure ... ... --> 
struct <xsl:value-of select="@id"/> 
{ 
<xsl:apply-templates select="field"/>}; 
      <xsl:variable name="new-visited" select="concat(' ', @id, $visited-structures)"/> 
      <xsl:apply-templates select="/*/structure[not(contains($new-visited, @id))][1]" > 
       <xsl:with-param name="visited-structures" select="$new-visited"/> 
      </xsl:apply-templates> 
     </xsl:otherwise> 
     </xsl:choose>  
    </xsl:template> 

    <xsl:template match="field"> 
     <xsl:value-of select="concat(' ', @idref, ' ', @name, ';&#xa;')"/>  
    </xsl:template> 

</xsl:stylesheet> 

И выход:

<?xml version="1.0" encoding="utf-8"?> 

struct STRUCT_C 
{ 
    FIELD_E e; 
    FIELD_F f; 
    FIELD_G g; 
}; 


struct STRUCT_B 
{ 
    STRUCT_C c; 
    FIELD_E e; 
}; 


struct STRUCT_A 
{ 
    STRUCT_B b; 
    STRUCT_C c; 
    FIELD_D d; 
}; 
+0

P.S. Вы также должны передать дополнительный параметр шаблону для '', указав, следует ли его повторять в следующем браке. Это будет справедливо только на верхнем уровне, то есть при применении в шаблоне для обработки ''. – LarsH

+0

переменная, которая передается, звучит как идея, однако не будет проблем с областью? т. е. каждый корневой узел 'structure' вызывается с пустым' $ visited-structure'? Следовательно, все еще будет несколько копий узлов структуры? – Nim

+0

Ничего себе, похоже, вам даже нужно пройти «продолжение»! Или, по крайней мере, список структур, которые еще предстоит обработать. – LarsH

3

Просто для удовольствия, другой подход (уровень по уровню) и Ussing ключей:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:output method="text"/> 
    <xsl:key name="kStructById" match="structure" use="@id"/> 
    <xsl:key name="kStructByIdref" match="structure" use="field/@idref"/> 
    <xsl:template match="/"> 
     <xsl:param name="pParents" select="/.."/> 
     <xsl:param name="pChilds" 
        select="structures/structure[not(key('kStructById', 
                 field/@idref))]"/> 
     <xsl:variable name="vParents" select="$pParents|$pChilds"/> 
     <xsl:variable name="vChilds" 
         select="key('kStructByIdref',$pChilds/@id) 
           [count(key('kStructById', 
              field/@idref) | 
             $vParents) = 
            count($vParents)]"/> 
     <xsl:apply-templates select="$pChilds"/> 
     <xsl:apply-templates select="current()[$vChilds]"> 
      <xsl:with-param name="pParents" select="$vParents"/> 
      <xsl:with-param name="pChilds" select="$vChilds"/> 
     </xsl:apply-templates> 
    </xsl:template> 
    <xsl:template match="structure"> 
     <xsl:value-of select="concat('struct ',@id,'&#xA;{&#xA;')"/> 
     <xsl:apply-templates/> 
     <xsl:text>};&#xA;</xsl:text> 
    </xsl:template> 
    <xsl:template match="field"> 
     <xsl:value-of select="concat('&#x9;',@idref,' ',@name,';&#xA;')"/> 
    </xsl:template> 
</xsl:stylesheet> 

Выход:

struct STRUCT_C 
{ 
    FIELD_E e; 
    FIELD_F f; 
    FIELD_G g; 
}; 
struct STRUCT_B 
{ 
    STRUCT_C c; 
    FIELD_E e; 
}; 
struct STRUCT_A 
{ 
    STRUCT_B b; 
    STRUCT_C c; 
    FIELD_D d; 
}; 
+0

Ницца. Я не уверен, что когда-либо видел шаблон «/», применяя шаблоны к «/» рекурсивно. :-) – LarsH

+0

@LarsH: Ja! Это то же самое, что и использование ссылок на шаблоны. Но в этом случае это просто эзотерическая форма, чтобы избежать «xsl: if» ... – 2010-11-18 12:42:39

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