Xpoint
   [напомнить пароль]

Динамическое использование подгружаемых шаблонов

Метки: [без меток]
2011-02-04 12:39:30 [обр] micolo[досье]

Доброго времени суток!
У меня такая проблема. Есть общий шаблон который обрабатывает данные из XML в простую HTML таблицу. В самом шаблоне это реализовано посредством xsl:for-each и никаких проблем нет. Но появилась необходимость чтобы данные из последнего <td> в строке обрабатывались исходя из конкретных настроек своим преобразованием (т.е. своим шаблоном). Причём это должно быть реализовано динамически - т.е. имя шаблона который должен подгрузиться по условию должно быть не статично прописано в самом шаблоне - а доставаться непосредственно из XML. Умные люди посоветовали использовать apply-templates с вызовом нужных шаблонов через mode - но как выяснилось в сам mode нельзя динамично вставить название а использование choose или ему подобных тож проблематично - так как подгружаемых шаблонов может быть и 100 и 200. В итоге должно получиться что-то похожее на ниже стоящую конструкцию(понятное дело этот вариант не рабочий):

<test test_item="s1">
<current>
....
</current>
</test>
<test test_item="s2">
<current>
....
</current>
</test>
<xsl:for-each select="//test">
<xsl:apply-templates select="current" mode="@test_item"/>
</xsl:for-each>

Исходя из вышесказанного прошу помочь в решении моей проблемы, возможно есть другие способы про которые я не знаю! Спасибо!

спустя 55 минут [обр] Jared(0/26)[досье]
Приведите пожалуйста минимальный пример исходного XML и того HTML что должен из него получиться.
спустя 57 минут [обр] micolo[досье]

xml

<rows>
<row temp_type="t1">
<row_1>test1</row_1>
<row_2>test2</row_2>
<row_3>test3</row_3>
<row_4>
<row_content>
<c_row1>1</c_row1>
<c_row2>2</c_row2>
<c_row3>3</c_row3>
</row_content>
</row_4>
</row>
<row temp_type="t2">
<row_1>test4</row_1>
<row_2>test5</row_2>
<row_3>test6</row_3>
<row_4>
<row_content>
<c_row1>4</c_row1>
<c_row2>5</c_row2>
<c_row3>6</c_row3>
</row_content>
</row_4>
</row>
</rows>

html

<table>
<tr>
<td>test1</td>
<td>test2</td>
<td>test3</td>
<td>
<table>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>test4</td>
<td>test5</td>
<td>test6</td>
<td>
<table>
<tr><td>4</td></tr>
<tr><td>5</td></tr>
<tr><td>6</td></tr>
</table>
</td>
</tr>
</table>

Подгружаемые шаблоны надо использовать для row_4, а так как связка делается с PHP я должен просто положить новый шаблон в нужное место и вставить в выводе temp_type его имя. Такая вот легенда )).

спустя 55 минут [обр] Jared(0/26)[досье]

Динамически подгружать внешние XSLT шаблоны с помощью XSLT-процессора нельзя by-design.
Вообще, если что-то с XSLT не получается, значит либо заходите не с того конца, либо пытаетесь использовать технологию не по назначению.

Примерный фрагмент кода для вашего примера:

<!-- Поймали row -->
<xsl:template match='row'>
   <tr>
      <!-- Заворачиваем всех детей в шаблоны в режиме td -->
      <xsl:apply-templates mode='td'/>
   </tr>
</xsl:template>

<!-- ============================================================================= -->
<!-- !!! Режим td             Заворачиваем в элементы TD  всякого пойманного
<!-- ============================================================================= -->

<!-- Попался кто-то в режиме td -->
<xsl:template match='node()' mode='td'>
   <td>
      <!-- Завернем в <td> и продолжим в режиме subtd -->
      <xsl:apply-templates select='.' mode="subtd"/>
   </td>
</xsl:template>


<!-- ============================================================================= -->
<!-- !!! Режим subtd     Обработка того, что появится в ячейках топовой таблицы. -->
<!-- !!! ИМЕННО ТУТ шаблоны для row_4 -->
<!-- ============================================================================== -->

<!-- Попался row_4 subtd - проверим, а не надо ли его вывести как для @temp_type t1 -->
<xsl:template match='row[@temp_type="t1"]/row_4' mode='subtd'>
   <table><tr>
      <xsl:apply-templates mode='td' select='row_content/node()'/>
   </tr></table>
</xsl:template>

<!-- Попался row_4 subtd - проверим, а не надо ли его вывести как для @temp_type t2 -->
<xsl:template match='row[@temp_type="t2"]/row_4' mode='subtd'>
   <table>
      <xsl:apply-templates mode='tr' select='row_content/node()'/>
   </table>
</xsl:template>

<!-- Попался какой то row_4 без определенного шаблона для его @temp_type -->
<xsl:template match='row_4' mode='subtd'>
   <xsl:apply-templates mode='subtd' select='row_content/node()'/>
</xsl:template>

<!-- Попался непонятный зверь в режиме subtd -->
<xsl:template match='node()' mode='subtd'>
   <!-- Скопируем его значение -->
   <xsl:value-of select='.'/>
</xsl:template>

<!-- ============================================================================================================ -->
<!-- !!! Режим tr             Заворачиваем в элементы TR-TD  всякого пойманного
<!-- ============================================================================================================ -->
<xsl:template match='node()' mode='tr'>
   <!-- Скопируем его значение -->
   <tr><td><xsl:value-of select='.'/></tr></td>
</xsl:template>

Как только появляется новый вариант верстки для нового значения temp_type, добавляете новый template который матчит row_4, который является ребенком row с этим новым значением в @temp_type:

<xsl:template match='row[@temp_type="NEW_T_VALUE"]/row_4' mode='subtd'></xsl:template>

Если не хочется руками ползать по 9000 строчкам xslt файла - сделайте скрипт сборки большого шаблона из нескольких небольших файлов.

спустя 25 минут [обр] Jared(0/26)[досье]

Да, и еще...
Если так уж хочется подцеплять нужный шаблон для row_4 в зависимости от значения родительского @temp_type динамически, то решается это только с помощью определения пользовательской функции. Не знаю, как это делается в PHP, использовал в свое время с точки зрения perl:

XML::LibXSLT->register_function(
   "urn:myfunctions", #namespace
   "subtemplate", #имя функции с точки зрения XSLT
   sub 
   {
      my $node = shift;  # Принимаем node из шаблона
      my $template_pointer = shift;  # Принимаем нечто, что даст нам понять, какой файл шаблона брать

      my $result_node; # Сюда положим результат
      
      # тут идет подцпляние-парсинг-отработка шаблона, результат кладем в $result_node
      
      return $result_node;
   }
);

Использование:

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

<!-- skip [..] -->

<xsl:template match="row">
   <!-- skip [..] -->

   <!-- !!! Вызыываем нашу внешнюю функцию, передаем ноду row_4 и собственный аттрибут @temp_type,
   по которому она определит, как обрабатывать row_4 -->
   <xsl:copy-of select="myfunctions:subtemplate(row_4, @temp_type)"/>

   <!-- skip [..] -->
</xsl:template>

<!-- skip [..] -->
</xsl:stylesheet>

Вероятно, для PHP аналогичная функциональность обязана быть.

спустя 1 час 10 минут [обр] micolo[досье]
Спасибо большое за такие подробности! Особенно заинтересовало использовать register_function. Такое тоже есть в PHP, только немного не понятно как эта моя функция должна отработать внутри перед тем как положить результат в $result_node. Если вам не трудно простой примерчик вашей функции на перле с вызовом шаблона и как это должно выглядеть в самом xslt.
спустя 1 день 11 часов [обр] Jared(0/26)[досье]

Исходный XML:

<?xml version='1.0' encoding='UTF-8'?>
<root>
    <node>
        <data attr='value1'>somedata1</data>
    </node>
    
    <node transform-me-with='subxslt'>
        <data attr='value2'>somedata2</data>
    </node>    
</root>

Основной XSLT:

<?xml version='1.0' encoding='UTF-8'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:myfunctions="urn:myfunctions">

<xsl:template match="node[@transform-me-with]">
    <xsl:copy-of select="myfunctions:subtemplate(., @transform-me-with)"/>
</xsl:template>
      
<xsl:template match="/">
        <xsl:apply-templates select='node()|@*'/>
</xsl:template>

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

</xsl:stylesheet>

Дополнительный xslt, файл subxslt.xslt

<?xml version='1.0' encoding='UTF-8'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:myfunctions="urn:myfunctions">
      
<xsl:template match="/">
    <xsl:apply-templates/>
</xsl:template>

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

<xsl:template match='data'>
    <otherdata>
        <xsl:apply-templates select='node()|@*' mode='edit'/>
    </otherdata>
</xsl:template>

<xsl:template match='@attr' mode='edit'>
    <xsl:attribute name='otherattr'>old data: <xsl:value-of select="."/></xsl:attribute>
</xsl:template>

<xsl:template match='text()' mode='edit'>
Old node value was:: <xsl:value-of select="."/>
Here comes new one!;
</xsl:template>

</xsl:stylesheet>

Ну и наконец, перл функция, доступная из XSLT:

our $parser = XML::LibXML->new();
our $xslt = XML::LibXSLT->new();

XML::LibXSLT->register_function(
    "urn:myfunctions", #namespace
    "subtemplate", #имя функции с точки зрения XSLT
    sub 
    {
        my $node_list = shift;  # Принимаем node list из шаблона
        my $template_pointer = shift;  # Принимаем нечто, что даст нам понять, какой файл шаблона брать
        my $result_dom; # Сюда положим результат        
        
        # Берем файл, имя которого хранится в $template_pointer 
        if(-f $template_pointer.'.xslt')
        {
                # Создаем новый XML документ и наполняем его пришедшим из XSLT node-list'ом
                my $dom = new XML::LibXML::Document('1.0','UTF-8'); 
                my $root = $dom->createElement('root');
                $dom->setDocumentElement($root);
                $root = $dom->documentElement();                
                $root->addChild($node_list->get_node($i)) for(my $i = 0; $i <= $node_list->size(); $i++)
                
                # Прогоняем получившийся DOM через вторичное преобразование                
                my $subxslt = $xslt->parse_stylesheet( $parser->parse_file($template_pointer.'.xslt') );
                $result_dom = $subxslt->transform($dom);
                
                return $result_dom;     
        }
        else
        {
                die('Substylesheet not found!');
        }
    }
);
спустя 1 день 6 часов [обр] micolo[досье]
Большое спасибо!
спустя 1 час 45 минут [обр] micolo[досье]

Хм. Вроде все получилось, данные обработались, шаблон подгрузился и сработал - только этот вывод почему то не в HTML - все теги из подгружаемого шаблона в виде

&lt;/td&gt;

хотя в шаблоне стоит output method="html"

спустя 1 час 10 минут [обр] micolo[досье]
Вопрос решен! Спасибо!
Powered by POEM™ Engine Copyright © 2002-2005