Оптимизировать или нет - на примере XSLT.
От: Дм.Григорьев  
Дата: 03.07.08 18:35
Оценка:
Всем привет.

Долго думал, в какой форум — решил в профильный. Хотя любители покричать про тупых программистов, пишущих тормозные программы, тусуются в основном в КСВ. Сам я тоже склонен сочувствовать юзерам, не понимающим, почему компьютеры всё мощнее и мощнее, а софт всё тормознее и тормознее. Однако вчера, занимаясь причёсыванием своих PHP+XSLT наработок, почувствовал всю глубину диалектического дуализма ситуации. Дело в том, что формы в админке у меня теперь генерируются в ТРИ прохода XSLT-процессора.

Прежде чем я расскажу, как дошёл до такой жизни, хочу заметить, что убедить меня отказаться от этого решения можно только одним способом: предложив не менее компактную на уровне исходников и не менее удобную для использования альтернативу. Оптимизировать в ущерб компактноти кода я не намерен. Кто заговорит про требования по производительности, первым делом получит предложение переехать с php на java, а вторым — пойти куда подальше. И кстати, я не занимался замерами производительности XSLT, но склонен полагать, что его загрузка и интерпретация сжирает как минимум не меньше времени, чем собственно исполнение. А сами формы, обрабатывающиеся в несколько проходов, составляют лишь малую часть всего кода страницы.

Так вот, откуда три прохода.




Первый (с конца) проход. Если никаких форм на странице нет, он мог бы быть и единственным. На входе XML-дерево, содержащее полный неупорядоченный набор данных, необходимых для генерации HTML-кода страницы. На выходе — HTML. Я выделил жирным тот момент, который радикально расходится со всеми виденными мною примерами использования XSLT. В примерах логическая структура входного дерева заточена под требуемую структуру результата, например:

<!-- SOURCE -->
<goods>
    <r id="1"><title>DVD Player</title><price>$200</price></r>
    <r id="2"><title>Condom</title><price>$2</price></r>
</goods>

<!-- OUTPUT -->
<table>
    <thead>
        <tr><th>Title</th><th>Price</th></tr>
    </thead>
    <tbody>
        <tr><td>DVDPlayer</td><td>$200</td></tr>
        <tr><td>Condom</td><td>$2</td></tr>
    </tbody>
</table>


Ясно, что трансформацию здесь напишет и ребёнок. Но при этом накладываются непомерные требования на код, генерирующий входной XML: фактически, он ставится в зависимость от XSLT-слоя в частности и от дизайна вообще. Поэтому на все эти "good practices" я блогополучно забил, и чаще предпочитаю импортировать и наследовать <template name=""/>, чем <template match=""/> (к счастью, это возможно):

<!-- Overridable -->
<x:template name="title"/>
<x:template name="content"/>

<x:template match="/">
    <html>
        <head><title><x:call-template name="title"/></title></head>
        <body>
            ... <!-- header -->
            <x:call-template name="content"/>
            ... <!-- footer -->
        </body>
    </html>
</x:template>





Второй проход. Что меня постоянно напрягало в html-формах, это необходимость вручную прописывать значения по умолчанию в полях <input>. Далее, CSS селекторы невозможно настроить на input/@type, поэтому хотелось бы дублировать атрибут type в class. Также, атрибуты disabled="disabled" и readonly="readonly" раздражают своей громоздкостью и также напрашиваются на дублирование в class (в частности, я подсвечиваю readonly text field как disabled, чтобы народ не путался). И наконец, хотелось бы сделать автоматическую подсветку ошибочных полей, не накладывая ограничений на дизайн/вёрстку. Отсюда возникает следующая трансформация (привожу в несколько схематичном виде):

<!-- SOURCE -->
<f:form name="editpage"><table>
<tr>
    <th>URI:</th>
    <td><f:text name="uri" class="w100" readonly="1"/></td>
</tr>
<tr>
    <f:errclass/>  <!-- разворачивается в атрибут class="formerror" если count(/response/messages/error[@form='editpage' and @param='title'])!=0.
    <th>Title:<f:required/></th>
    <td><f:text name="title" class="w100"/></td>
</tr>
<tr>
    <th>Text:</th>
    <td><f:fckeditor name="text" class="w100"/></td>
</tr>
</f:form>

<!-- RESULT -->
<form name="editpage" method="post" action="{/response/paths/requestUri}"/><table>
<tr>
    <th>URI:</th>
    <td><input type="text" class="w100 text readonly" readonly="readonly" value="{/response/forms/editpage/uri}"/></td>
</tr>
<tr class="formerror">
    <th>Title:<span class="formrequired">*</span></th>
    <td><input type="text" class="w100 text" value="{/response/forms/editpage/title}"/></td>
</tr>
<tr>
    <th>Text:</th>
    <td><div class="fckeditor"><script type="text/javascript"> <!-- FCKEditor initialization code --></script></div></td>
</tr>
</form>


Здесь CSS-класс w100 — сокращение для {width:100%}. Как видите, трансформация достаточно прямолинейная, по крайней мере для стандартных полей: один элемент на входе, один на выходе. Так

Почему отдельный проход: из неструктурированного входного XML-дерева здесь получается HTML, но с вкраплениями элементов <f:*>, которые должны быть ещё раз оттранслированы. Здесь я поступаю совершенно грубо: поскольку форм у меня полно на самых разных страницах (а частенько формы логина и поиска присутствуют в основном шаблоне), я прогоняю повторно весь контент, вместо того, чтобы оборачивать каждую форму в аналогичную конструкцию:

<x:template match="/">
    <x:variable name="x">
        <x:copy-of select="/response"/>  <!-- обеспечивает доступ к полному входному XML-дереву изнутри вложенной обработки -->
        <x:apply-templates select="/" mode="ex"/>
    </x:variable>
    <x:apply-templates select="ex:node-set($x)/html"/>
</x:template>

<x:template match="/" mode="ex">
    <html>...</html>
</x:template/>





Третий (с конца) проход. Выписывать эти <table>, <tr>, <th>, <td> напрягает. Большинство форм имеют такую табличную структуру (а уж админские в моём праве сделать все такими), поэтому нужно бы автоматизировать. Однако для сохранения гибкости для всяких нетривиальных случаев дизайна необходимо оставить вышеописанные примитивы. Отсюда возникает следующая трансформация:

<!-- SOURCE -->
<ft:form name="editpage">
    <ft:text th="URI:" name="uri" class="w100" readonly="1"/>
    <ft:text th="Title:" required="1" name="title" class="w100"/>
    <ft:fckeditor th="Text:" name="text" class="w100"/>
</ft:form>

<!-- RESULT -->
<!-- См. SOURCE с предыдущего прохода. -->


Тупо свести <ft:form> к <x:call-template name="f:form"> не выходит, потому как внутри <f:form> генерируется дополнительный контент: <table>. Отсюда получаем следующее (несколько упрощённо, но "основано на реальных событиях"):

<x:template match="ft:form">
    <!-- Здесь я не только генерирую <f:*>, но и сразу же транслирую их в HTML. 
             Поэтому второй проход переведёт <ft:*> сразу в HTML. -->
    <x:variable name="x">
        <x:copy-of select="/response"/>
        <f:form>
            <x:apply-templates select="descendant::ft:hidden" mode="ft:_field"/>
            <x:copy-of select="@*"/>
            <table>
                <x:apply-templates/>
            </table>
        </f:form>
    </x:variable>
    <x:apply-templates select="ex:node-set($x)/f:form"/>
</x:template>

<!-- Already processed by <ft:form>, outside generated <table>. -->
<x:template match="ft:hidden"/>

<!-- Any ft:field syntax matches f:field syntax, including element name, attributes and content. 
     Added special attributes: @th, [@required=1]. -->
<x:template match="ft:*">
    <tr>
        <f:errclass/>
        <th>
            <x:value-of select="@th"/>
            <x:if test="@required=1">
                <f:required/>
            </x:if>
        </th>
        <td><x:apply-templates select="." mode="ft:_field"/></td>
    </tr>
</x:template>
    
<!-- INTERNAL. -->
<x:template match="ft:*" mode="ft:_field">
    <x:element name="f:{local-name()}">
        <x:copy-of select="@*[name()!='th' and name()!='required']"/>
        <x:apply-templates/>
    </x:element>
</x:template>





Комментарии?
http://dimgel.ru/lib.web — thin, stateless, strictly typed Scala web framework.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.