Преобразование XML с несколькими XSL файлами

У меня есть XML, который я бы хотел преобразовать в HTML, используя несколько XSL файлов. Эти XSL файлы связаны между собой через команды xsl: import и xsl: include и все необходимые для завершения преобразования.

Я знаю, что XSL работает, потому что использование директивы <?xml-stylesheet type="text/xsl" href="transform.xsl"?> в предварительно созданном XML файле, открываемом браузером, отображает результат, который я хочу. Проблема в том, что я хочу иметь возможность реплицировать эту функциональность на динамически генерируемый XML.

Есть два способа увидеть, что это можно сделать, но у обоих, похоже, есть ограничения, с которыми я не мог работать.

Первое решение - использовать Javascript для преобразования XML. Из того, что я могу сказать, для этого потребуется объект XSLTProcessor для загрузки нескольких XSL файлов, но Chrome (и, возможно, другие браузеры) не поддерживают xsl: import очень хорошо - http://code.google.com/p/chromium/issues/detail?id=8441

Я также посмотрел на запись XML в iFrame или в новое окно, но директивы <?xml-stylesheet type="text/xsl" href="transform.xsl"?> закомментированы в появившемся окне. На самом деле все, что написано в новом окне, равно HTML - я не нашел способ записать XML в новое окно.

Итак, как я могу получить окно браузера для отображения результата XML файла, преобразованного с помощью набора XSL файлов?

UPDATE

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

Возможное обходное решение: скомпилируйте инструмент, например xsltproc, в JavaScript с помощью emscripten. Я действительно это сделал - см. https://github.com/mcasperson/xsltproc.js

Проблема: это невероятно медленно в firefox (что занимает 5 секунд в Chrome, занимает 30+ в firefox), и вы не можете запускать код в веб-клиенте Chrome - https://code.google.com/p/chromium/issues/detail?id=252492

Возможное обходное решение: вообще не используйте XSL, но отобразите XML с помощью таблиц стилей CSS.

Проблема: пока браузеры не начнут внедрять функцию css attr(atrributename, url), невозможно трактовать ссылку на файл в атрибуте XML как нечто, отличное от строки, что делает невозможным отображение изображений.

Возможное обходное решение: объединить все XSL файлы в одну таблицу стилей

Проблема. Это несколько возможно (см. Объединить несколько таблиц стилей xslt), но xsl: import и xsl: include имеют определенную семантику, которая не переносятся, просто заменяя содержимое файлов вместо оператора xsl: import или xsl: include. Для больших преобразований XSL, разбитых на несколько файлов, это решение потребует много ручной работы.

Возможное обходное решение. Запишите содержимое XML в iframe или в новое окно.

Проблема. Невозможно записать XML в новое окно или iframe. Содержимое, записанное в эти элементы, всегда считается HTML и вставляется в элемент HTML- > BODY.

Возможное обходное решение. Создайте службу на стороне сервера, которая принимает XML, а затем возвращает этот XML с помощью директивы таблицы стилей XSL. Затем URL-адрес службы можно использовать как атрибут src для iframe или нового окна.

Проблема. Служба должна быть конечной точкой GET, а это означает, что возвращаемый XML должен быть включен как параметр запроса, что означает, что вы в конечном итоге столкнетесь с проблемами с длина URL-адреса.

Возможное обходное решение. Используйте библиотеку javascript XSL, например Saxonica CE.

Проблема. Это может работать (я не пробовал), но Saxonica CE не является открытым исходным кодом (что является требованием нашего проекта).

Ответы

Ответ 1

Я бы рекомендовал использовать jquery для импорта и стиля XML.

Что-то вроде этого позволит вам импортировать XML всякий раз, когда вызывается функция (функция, связанная с нажатием клавиши или кнопкой обновления или даже таймером.)

$.ajax({
    type: "GET",
    url: "FILENAME.xml",
    dataType: "xml",
    success: function(xml) {
        $(xml).find('site').each(function(){ //finds parent node
            var id = $(this).attr('id'); //id of parent node
            var title= $(this).find('title').text();  //finds title node within parent node
            var url = $(this).find('url').text(); //finds URL node within parent node
            var description = $(this).find('descr').text(); //etc...
            var img = $(this).find('img').text(); //etc...

            // Creates div with id of parent node (for individual styling)
            $('<div id="link_'+id+'">')
                .addClass('add a div class')
            //sets css of div
                .css({set css})
            // Sets the inner HTML of this XML allocated div to hyperlinked 'title' with 'description' and an 'img'
                .html('<a href="'+url+'">'+title+'</a>'+description+'<img src="'+img+'">')                  
            // Append the newly created element to body
                .appendTo('#holder');
    }
    }
})

И XML будет выглядеть примерно так:

<site id="0">
 <url>http://blah.com</url>
 <img>imgs/image1.png</img>
 <description>this is a description</description>
 <title>Title</title>
</site>

<site id="1">
 <url>http://filler.com</url>
 <img>imgs/image2.jpg</img>
 <description>this is another description</description>
 <title>Title 2</title>
</site>

Конечно, вместо импорта в div вы можете импортировать XML в таблицу или любой другой тип HTML-элемента.

Ответ 2

Если вы хотите решение только для браузера, я бы сделал это следующим образом:

Статический xml

Сделайте простой статический xml, содержащий только вызов xsl. Это xml, который открывается в браузере - всегда. Этот xml файл может содержать параметры свойств для управления потоком или вообще ничего в этом примере.

<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="cartoon2html.xsl"?>
<xml/>

Динамический простой xml

Создайте динамический XML в своем любимом ключе с использованием определенного имени - в моем случае cartoons.xml.

<?xml version="1.0" encoding="utf-8"?>
<cartoons>
    <cartoon name="Donald Duck" publisher="Walt Disney" />
    <cartoon name="Mickey Mouse" publisher="Walt Disney" />
    <cartoon name="Batman" publisher="DC Comics" />
    <cartoon name="Superman" publisher="DC Comics" />
    <cartoon name="Iron Man" publisher="Marvel Comics" />
    <cartoon name="Spider-Man" publisher="Marvel Comics" />
</cartoons>

XSLT с загрузкой документа

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

Внимательно посмотрите на ссылку на переменную сверху и дальше в коде. Здесь выполняется волшебство.

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

    <xsl:variable name="cartoons" select="document('cartoons.xml')/cartoons" />

    <xsl:template match="/">
        <html>
            <head>
                <title>Cartoons</title>
                <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
            </head>
            <body>
                <xsl:apply-templates select="$cartoons" />
            </body>
        </html>
    </xsl:template>

    <xsl:template match="cartoons">
        <table>
            <xsl:apply-templates />
        </table>
    </xsl:template>

    <xsl:template match="cartoon">
        <tr>
            <td><xsl:value-of select="@name" /></td>
            <td><xsl:value-of select="@publisher" /></td>
        </tr>
    </xsl:template>

</xsl:stylesheet>

Вы можете сохранить эти три файла в выбранный каталог и открыть статический файл xml в firefox. (Chrome и, возможно, Safari должен иметь файл, обслуживаемый через веб-сервер для выполнения преобразования).

Ответ 3

У меня была такая же проблема только сейчас. Мое решение заключалось в распространении включений непосредственно в XML. Этот код был протестирован в Chrome, Firefox и IE 10,9,8 и 7.

function propegateIncludes(dname,xml, isExplorer) {
    var preTag = isExplorer ? "xsl:" : "";

    var TAG_STYLESHEET = preTag + "stylesheet", TAG_INCLUDE = preTag
        + "include", TAG_TEMPLATE = preTag + "template";

    var stylesheets = xml.getElementsByTagName(TAG_STYLESHEET);
    if (stylesheets.length == 0) {
        return;
    }

    var includes = xml.getElementsByTagName(TAG_INCLUDE);
    if (includes.length == 0) {
        return;
    }

    var stylesheet = stylesheets[0];
    var path = dname.substring(0, dname.lastIndexOf('/'));

    for ( var i = 0; i < includes.length; i++) {
        var args = includes[i].attributes;
        var includeXml = null;

        for ( var a = 0; a < args.length; a++) {
            if (args[a].nodeName == "href") {
                includeXml = loadXMLDoc(path + "/" + args[a].nodeValue);
                break;
            }
        }

        stylesheet.removeChild(includes[i]);

        if (includeXml != null) {
            var templates = includeXml.getElementsByTagName(TAG_TEMPLATE);

            for (var t = 0; t < templates.length; ++t) {
                stylesheet.appendChild(templates[t].cloneNode(true));
            }
        }
    }
}