Настолько вдохновил, что захотелось написать собственное решение, чем я собственно и занялся. Сразу оговорюсь, что я не любитель изобретать велосипеды (хотя нет... на самом деле любитель), но варианты, которые существовали на тот момент меня не очень привлекали. И написаны они были хорошо, и работали быстро и надежно, но лично мне работать с ними было неудобно - неудобно вызывать, неудобно обращаться к результатам обработки и т.д. и т.п.
В качестве технологической базы выбрал XSLT, хотя JS шаблонизация во многих случая предпочтительней, но уж больно давно мне полюбилась эта технология (благодаря, кстати, Mozart Framework). В результате этого моего увлечения появился плагин для jQuery:
XSLT Templater - плагин для фреймворка jQuery, реализующий следующие задачи:
- XML/XSLT процессинг
Плагин позволяет произвести XSLT преобразования для предаваемых в качестве аргументов xml и xsl. Причем xml и xsl могут предоставляться в трех формах:
- Строковое представление
В этом случае плагин самостоятельно распарсит строковое представление XML, и произведет необходимые вычисления. Такой вариант представления удобен прежде всего в тех ситуациях, когда необходим самостоятельный контроль за доставкой или генерацией исходного xml или xsl шаблона.
- Ссылка на файл/ресурс
Плагин с помощью AJAX загрузит необходимые данные из внешнего источника (в пределах домена) и произведит необходимые преобразования. Все просто - указываем путь к xml и xsl на сервере, а получаем результат преобразований.
- XMLDocument
В такой ситуации плагин не будет производить каких-либо дополнительных операций, а будет сразу использовать аргумент в виде XMLDocument при XSLT процессинге. Этот вариант мне понадобился, когда я попробовал написать специфическую систему кэширования поверх плагина, после чего появился и один из нижеописанных уровней кэширования.
- Строковое представление
- Кэширование XML/XSLT на двух уровнях
Кэширование позволяет организовать хранение xml/xsl для повторного использования, что и реализует базовые возможности для шаблонизации с вычислительными возможностями XSLT. Кэширование организуется на двух уровнях:
- Пользовательский уровень
Пользователь самостоятельно управляет кэшированием с доступом только к результатам последнего преобразования. Это очень удобно, когда при ручной обработке нужно повторно использовать предыдущие аргументы. А так как кэшируется сразу XMLDocument, то наглядно получаем некоторую прибавку в скорости... Хотя нужно отметить, что самой тяжелой операцией по-прежнему является сама трансформация.
- Базовый уровень
Внутреннее кэширование плагина, включенное по умолчанию на кэширование только xsl. Можно переключать внутренний кэш между четырьмя различными уровнями, но на практике из четырех режимов понадобятся всего 2-3.
Внутренне кэширование предполагает, что пользователь контролирует порядок вызовов нескольких преобразований над одинаковыми входными данными. Иными словами, два идентичных последовательных вызова преобразования будут работать (в силу асинхронности JavaScript) с идентичным состоянием кэша, что означает, что одинаковые ресурсы могут быть загружены несколько раз. При этом, существует механизм позволяющий организовывать цепочки преобразований, на основе обратных вызовов. Этот механизм позволяет установить четкую очередь обработки вызовов, что в свою очередь позволяет использовать актуальное состояние кэша при каждом отдельном преобразовании.
- Пользовательский уровень
Различные способы вызова
Преобразования осуществляются контекстно - относительно элемента контейнера для результирующего представления. И исходный xml и xsl могут представляться в трех различных формах.
- Строковые представления
Пусть существуют следующие строковые представления:
"<xml-message>"+
и
"<message>It working with string representation of xml</message>"+
"</xml-message>"
'<?xml version="1.0"?>'+
Тогда вызов xml/xsl трансформации будет выглядеть следующим образом:
'<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">'+
'<xsl:template match="xml-message">'+
'<p>Result: <xsl:value-of select="message/text()"/>!</p>'+
'</xsl:template>'+
'</xsl:stylesheet>'
$('#test1').xslt("<xml-message>"+
или
"<message>It working with string representation of xml</message>"+
"</xml-message>",
'<?xml version="1.0"?>'+
'<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">'+
'<xsl:template match="xml-message">'+
'<p>Result: <xsl:value-of select="message/text()"/>!</p>'+
'</xsl:template>'+
'</xsl:stylesheet>'
);
var xmlString = "<xml-message>"+
"<message>It working with string representation of xml</message>"+
"</xml-message>";
var xslString = '<?xml version="1.0"?>'+
'<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">'+
'<xsl:template match="xml-message">'+
'<p>Result: <xsl:value-of select="message/text()"/>!</p>'+
'</xsl:template>'+
'</xsl:stylesheet>';
$('#test1').xslt(xmlString,xslString);
- Ссылка на ресурс
Пусть существует следующий файл или скрипт возвращающий следующую структуру:
/tests/data/message.xml
<?xml version="1.0"?>
и
<xml-message>
<message>It working with loading xml file</message>
</xml-message>
/tests/xslt/message-template.xsl
<?xml version="1.0"?>
В этом случае вызов будет осуществляться следующим образом:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="xml-message">
<p>Result: <xsl:value-of select="message/text()"/> and loading xslt file!</p>
</xsl:template>
</xsl:stylesheet>
$('#test2').xslt("/tests/data/message.xml","/tests/xslt/message-template.xsl");
- В виде XMLDocument (см. Работа с пользовательским кешем)
Комплексный пример:
var xslString = '<?xml version="1.0"?>'+
'<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">'+
'<xsl:template match="xml-message">'+
'<p>Result: <xsl:value-of select="message/text()"/>!</p>'+
'</xsl:template>'+
'</xsl:stylesheet>';
$('#test2').xslt("/tests/data/message.xml",xslString);
Обратные вызовы
Для синхронизации работы нескольких вызовов и организации работы с кэшом существует механизм обратных вызовов:
$('#test2').xslt("/tests/data/message.xml","/tests/xslt/message-template.xsl", function(){Этот механизм позволяет организовывать цепочки вызовов, причем каждый обратный вызов уже будет иметь доступ к состоянию кеша после предыдущего вызова:
console.debug("Text message after transformation!");
});
var xslString = '<?xml version="1.0"?>'+
'<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">'+
'<xsl:template match="xml-message">'+
'<p>Result: <xsl:value-of select="message/text()"/>!</p>'+
'</xsl:template>'+
'</xsl:stylesheet>';
$('#test2').xslt("/tests/data/message.xml",xslString, function() {
$('#test3').xslt("/tests/data/message.xml","/tests/xslt/message-template.xsl", function() {
$('#test4').xslt("/tests/data/message.xml","/tests/xslt/message-template.xsl",function(){
console.debug("Text message after all transformations ");
});
});
});
Работа с пользовательским кэшем
Пользовательский (локальный) кэш необходим для самостоятельного контроля кэширования внутри шаблонизатора. Локальный кэш передается непосредственно в функцию преобразования, поэтому с его помощью существует доступ только к результатам кэширования последнего выполненного преобразования. Если локальный кэш передается в качестве аргумента, то необходимо получить экземпляр локального кэша:
var xsltCache = $.getXSLTLocalCache({Теперь этот экземпляр можно использовать в вызовах:
onUnlock: function(state) {
console.debug ('New value in cache');
}
});
$('#test2').xslt("/tests/data/message.xml","/tests/xslt/message-template.xsl", function() {В приведенном примере стоит обратить внимание на следующие особенности:
$('#test3').xslt(xsltCache.getXmlDoc(),"/tests/xslt/message-template.xsl",null,xsltCache,false);
// using result of caching xml representation in callback function
},xsltCache, false);// insert local cache to transformation function and switch off base cache
- xsltCache.getXmlDoc() – получение XMLDocument исходного xml из предыдущего вызова
- …null,xsltCache,false… во внутреннем вызове - null вместо функции обратного вызова, повторное использование локального кэша, полное отключение внутреннего (базового) кэширования плагина
Методы пользовательского кэша
Методы | Описание |
---|---|
checkXmlDoc() | Проверяет наличие в xml кэше значения. Возвращает true, если значение установлено, false – в обратном случае |
checkXslDoc() | Проверяет наличие в xsl кэше значения. Возвращает true, если значение установлено, false – в обратном случае |
getXmlDoc() | Возвращает значение xml кэша. Если значение не было установлено, то возвращает false |
getXslDoc() | Возвращает значение xsl кэша. Если значение не было установлено, то возвращает false |
lock() | Сервисный метод для проверки возможности работы с кэшом. Имеет только информативный смысл. |
setXmlDoc(xml) | Устанавливает новое значение xml кэша. Используется внутри плагина. |
setXslDoc(xml) | Устанавливает новое значение xsl кэша. Используется внутри плагина. |
reset | Сбрасывает все значения кэша |
unlock | Сервисный метод для проверки возможности работы с кэшом. Имеет только информативный смысл. |
Работа с базовым кэшом
Базовый кэш используется внутри плагина для оптимизации преобразований. Кэширование происходит на основе строкового ключа (строковое представление xml или ссылка на ресурс) Возможны четыре состояния внутреннего кэша:
- Кэшируется только xsl – режим по умолчанию
Этот режим предполагает ситуацию, когда шаблон загружается только один раз, а данные периодически обновляются. В этом случае подготовка шаблона к преобразованиям будет произведена только один раз.
- Кэшируется только xml
В этом режиме предполагается, что для одних, статических данных меняется только форма представления (шаблон)
- Кешируется и xml, и xsl
В этом случае предполагается, что и данные и представление являются статическим. Как правило, данный вариант используется, когда существует неопределенность о возможности повторной загрузки статического контента.
- Кеширование не происходит
Настройка базового кэша
Для четырех различных случаев кэширование настраивается по-разному:
- Кэшируется только xsl – режим по умолчанию
$('#test1').xslt(xmlString,xslString);
или
var cache = {
"xml":false,
"xsl":true
}
$('#test1').xslt(xmlString,xslString,null,null,cache);
- Кэшируется только xml
var cache = {
"xml":true,
"xsl":false
}
$('#test1').xslt(xmlString,xslString,null,null,cache);
- Кешируется и xml, и xsl
$('#test1').xslt(xmlString,xslString,null,null,true);
или
var cache = {
"xml":true,
"xsl":true
}
$('#test1').xslt(xmlString,xslString,null,null,cache);
- Кеширование не происходит
$('#test1').xslt(xmlString,xslString,null,null,false);
или
var cache = {
"xml":false,
"xsl":false
}
$('#test1').xslt(xmlString,xslString,null,null,cache);
Комплексный пример
$(document).ready(function() {
var time = new Date();
var xmlString = "<xml-message>"+
"<message>It working with string representation of xml</message>"+
"</xml-message>";
var xslString = '<?xml version="1.0"?>'+
'<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">'+
'<xsl:template match="xml-message">'+
'<p>Result: <xsl:value-of select="message/text()"/>!</p>'+
'</xsl:template>'+
'</xsl:stylesheet>';
$('#test1').xslt(xmlString,xslString);
var xsltCache = $.getXSLTLocalCache({
onUnlock: function(state) {
console.debug("Text message after setting new value in local cache!");
}
});
$('#test2').xslt("/tests/data/message.xml",xslString, function() {
$('#test3').xslt(xsltCache.getXmlDoc(),"/tests/xslt/message-template.xsl", function() {
$('#test4').xslt(xsltCache.getXmlDoc(),xsltCache.getXslDoc(),function () {
var time2 = new Date();
$("#time").text("Calculating time:"+(time2-time));
},xsltCache,false);
},xsltCache);
},xsltCache, false);
});
Официальный сайт: http://www.xslt-templater.com
Страница проекта: http://code.google.com/p/xslt-templater/
Страница на jQuery.com: http://plugins.jquery.com/project/XSLTTemplater
Вот так, интересно ваше мнение...
в свое время (2005-ый год) для внутреннего приложения сделал похожий шаблонизатор на основе XML (помесь html и собственных тэгов) + XSLT. Собственные тэги конвертировались в контролы типа полей ввода, таблиц и т.п - ессно в виде web2.0 . Работало очень быстро - по сравнению со всеми на тот момент имеющимися web 2.0 вариантами (GWT тогда еще не было и в помине). Поддерживался ИЕ и файрфокс
ОтветитьУдалитьЖаль, проект так и остался внутренним...
Это всё здорово до тех пор пока не упрёшься в "особенности" реализации XSLT в разных браузерах, а они там есть и весьма заковыристые порой. Одна из таких проблем это то что все браузеры на базе webkit не обрабатывают xsl:include, xsl:import и document(). Тема не новая правда, но малоизученная и зачастую о разного рода подводных камнях там узнаешь лишь когда сам спотыкаешься о них.
ОтветитьУдалитьПривет!
ОтветитьУдалитьВдохновение это большая сила )
В тему xslt я таки не вижу аргументов в пользу использования на клиенте. На клиенте рулит json. В коде минимум лишнего и повторяющихся строк. В этом смысле xml не самое лучшее решение, имхо.
Т.е. конкуренты этого решения на мой взгляд не другие xslt шаблонизаторы, а например:
http://github.com/jquery/jquery-tmpl
с кучей очевидных преимуществ, хотя бы в меньшем количестве кода.
PS: Надеюсь не очень далеко от темы.
Уже вышел новый релиз, в котором сделаны значительные изменения, поэтому пост описывает не актуальную версию! Желательно сверяться с документацией на официальном сайте...
ОтветитьУдалитьОлег, спасибо! Удобный плагин и хорошая реализация.
ОтветитьУдалитьЕдинственное - столкнулся с проблемой в Google Chrome (5.0.375.125). Если в подгружаемом xsl есть include или import второго xsl-файла, то рендера не происходит. Не сталкивались с подобной проблемой?
Рад, что понравилось) Скоро будет готова новая версия.
ОтветитьУдалитьС проблемой импорта и инклуда сталкивался... К сожалению, здесь поделать ничего не получиться, так как это особенности "понимания" xsl в конкретном браузере... Такое происходит не только в Chrome. В качестве решения могу предложить использовать цепочку вызовов с различными xsl...
С xslt на стороне клиента, к сожалению, есть проблемы кроссбраузерности,но в целом они обходимы в самом xsl).
Хорошо, что проект развивается! Буду ждать обновленную версию)
ОтветитьУдалитьС Chrome смутило то, что если документ рендерится "обычным" способом, то все инклюды работают. Но если я пытаюсь динамически через ява-скрипт отрендерить шаблон, то возникает проблема с импортом.
Под "обычным" способом я подразумеваю следующее: в строке запроса браузера я обращаюсь к некоему URL на сайте, который мне возвращает XML и в заголовке указан основной XSL-файл которым нужно преобразовать дерево. Браузер подгружает основной XSL, а также все XSL которые указаны как импортируемые.
Действительно, странное поведение... Постараюсь разобраться в этом вопросе)
ОтветитьУдалитьОлег, привет.
ОтветитьУдалитьСтолкнулся с необходимостью получать результаты преобразования, не вкладывая их в какой-либо контейнер. Хотелось бы, чтоб $.xslt(xml,xsl) возвращал не пусто, а результаты рендеринга, как текст. В некоторых момента действительно необходимо - например .append()
Заранее спасибо :)
Такая возможность есть, $.xslt(xml,xsl) возвращает пустоту не с проста, так как наложение происходит асинхронно. Ох уж этот JavaScript!)
ОтветитьУдалитьВ данной ситуации необходимо использовать пользовательский кэш, через который вернется результат преобразования:
var xsltCache = $.getXSLTLocalCache();
$.xslt('data.xml','view.xsl',function() {
alert('Получаем строку:'+xsltCache.getResultDoc());
// и что-то с ней делаем
},xsltCache);