Отчет PDF со встроенным HTML

У нас есть Java-система, которая считывает данные из базы данных, объединяет отдельные поля данных с предустановленными тегами XSL-FO и преобразует результат в PDF с помощью Apache FOP.

В формате XSL-FO это выглядит так:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE Html [
<!ENTITY nbsp  "&#160;"> 
    <!-- all other entities -->
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
    <xsl:output method="xml" indent="yes" />
    <xsl:template match="/">

        <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:svg="http://www.w3.org/2000/svg" font-family="..." font-size="...">
            <fo:layout-master-set>          
                <fo:simple-page-master master-name="Letter Page" page-width="8.500in" page-height="11.000in">

                    <!-- appropriate settings -->

                </fo:simple-page-master>
            </fo:layout-master-set>
            <fo:page-sequence master-reference="Letter Page">

                <!-- some static content -->

            <fo:flow flow-name="xsl-region-body">
                    <fo:block>
                        <fo:table ...>
                            <fo:table-column ... />
                            <fo:table-body>
                                <fo:table-row>
                                    <fo:table-cell ...>
                                        <fo:block text-align="...">
                                            <fo:inline font-size="..." font-weight="...">
                                                <!-- Header / Title -->
                                            </fo:inline>
                                        </fo:block>
                                    </fo:table-cell>
                                </fo:table-row>
                            </fo:table-body>
                        </fo:table>
                    </fo:block>

                    <fo:block>

                        <fo:table ...>
                            <fo:table-column ... />
                            <fo:table-body> 
                                <fo:table-row>
                                    <fo:table-cell>
                                        <fo:block ...>
                                            <!-- Field A -->                                
                                        </fo:block>
                                    </fo:table-cell>
                                </fo:table-row>
                            </fo:table-body>
                        </fo:table>

                        <!-- Other fields in a very similar fashion as the above "Field A" -->

                    </fo:block>

                </fo:flow>      

            </fo:page-sequence>

        </fo:root>              

    </xsl:template>

</xsl:stylesheet>

Теперь я ищу способ разрешить некоторым полям содержать статический HTML-формат. Этот контент будет создан нашим редактором HTML (что-то вроде строк CLEditor, CKEditor и т.д.) Или вставлено снаружи.

Мой план состоит в том, чтобы следовать рецепту из этой статьи JavaWorld:

  • используйте JTidy для преобразования строки в формате HTML в соответствующий XHTML
  • дополнительно измените xhtml2fo.xsl из Antenna House, чтобы удалить все преобразования для всего документа и всего веб-сайта.
  • применить этот модифицированный XSLT к моей строке XHTML (javax.xml.transform)
  • извлеките все узлы под корнем с помощью XPath (javax.xml.xpath)
  • передать результат непосредственно в существующий документ XSL-FO

У меня есть голосовой вариант такого кода и получил следующую ошибку:

(Место ошибки неизвестно) org.apache.fop1.fo.ValidationException: "{http://www.w3.org/1999/XSL/Format} table-body" не является допустимым дочерним "fo: block"! (Отсутствует контекстная информация)

Мои вопросы:

  • Каким образом можно устранить эту проблему?
  • Может ли <fo:block> использоваться как общий контейнер с другими объектами (включая таблицы), вложенными внутри?
  • Это общий разумный подход к решению задачи?

Если кто-то уже "был там, это сделал", пожалуйста, поделитесь своим опытом.

Ответ 1

Лучшим способом устранения неполадок является использование проверяющего средства просмотра/редактора для проверки XSL FO. Многие (например, oXygen) покажут вам ошибки в структуре XSL FO при их открытии, и они расскажут о проблеме (точно так же, как сообщалось об ошибке).

В вашем случае у вас, очевидно, есть fo: table-body как дочерний элемент fo: block. Не может быть. Fo: table-body имеет только один действительный родительский элемент fo: table. Вам либо не хватает тега fo: table, либо вы ошибочно ввели fo: block в этой позиции.

По-моему, я мог бы сделать что-то совсем другое. Я бы поставил содержимое XHTML в линейку XSL FO там, где вы этого хотите. Затем я создам преобразование идентичности, которое копирует все содержимое, основанное на fo, но преобразует части XHTML с помощью XSL. Таким образом, вы можете направить это преобразование в XSL-редактор, например oXygen, и посмотреть, где возникают ошибки и почему именно. Как и любой другой отладчик.

Примечание. Возможно, вы захотите взглянуть и на другие XSL, особенно если ваш HTML может иметь атрибуты CSS стиля style = ". Если это так, то это не простой HTML, тогда вам понадобится лучший метод обработки HTML с CSS для FO.

http://www.cloudformatter.com/css2pdf основан на этом полном преобразовании. Эта общая таблица стилей доступна здесь: http://xep.cloudformatter.com/doc/XSL/xeponline-fo-translate-2.xsl

Я автор этой таблицы стилей. Он делает гораздо больше, чем вы просите, но имеет довольно сложную рекурсию парсинга для преобразования стилей CSS в атрибуты XSL FO.

Ответ 2

  • Если вы используете отладчик XSLT, например, в oXygen или XML Spy, вы можете пройти через преобразование. С oXygen - не уверен в XML-шпионах или других редакторах - если вы нажмете на разметку на выходе отладчика, oXygen выделяет разметку как из источника, так и из таблицы стилей, которая создала этот node.

    Как только у вас есть FO, структура focheck (https://github.com/AntennaHouse/focheck) имеет самую полную проверку FO в настоящее время.

  • fo:block может содержать таблицы и т.д. В спецификации XSL 1.1 определение каждого FO включает подраздел "Содержание", в котором указан его разрешенный контент. См., Например, http://www.w3.org/TR/xsl11/#fo_block. Определения "объектов параметров" в моделях контента находятся в http://www.w3.org/TR/xsl11/#d0e6532, но некоторые ОС имеют дополнительные ограничения в тексте их определений.
  • В статье, которую вы цитируете, по-видимому, нет "извлечь все узлы под root с шагом XPath", и я не уверен, зачем вам это нужно. Помимо этого, он выглядит как разумный подход для выполнения задания с использованием Java.

Вместо того, чтобы вставлять FO, преобразованный из вашего JTidy-ed HTML в статическую FO, вы можете заменить свою <!-- Field A --> на не-FO-разметку, которая предоставляет достаточную информацию, чтобы сделать ссылку на поле для вставки. Затем вы можете создать таблицу стилей XSLT, которая преобразует документ шаблона + ссылки в прямой FO, выполнив преобразование идентичности на частях FO - как в ответе от @kevin-brown - и используя информацию в ссылочной разметке, чтобы построить URI для использования с функцией document() (http://www.w3.org/TR/xslt#document), чтобы найти разметку для вставки.

Если FO для содержимого поля находится на диске, то использование document() является простым. Если это не так, вам придется сделать что-то вроде переопределения URIResolver, используемого процессором XSLT, чтобы вместо поиска на диске он делал правильные действия для извлечения содержимого. Вы даже можете иметь JTidying как часть URIResolver, получая HTML. Вы также можете сделать преобразование в "внутри" URIResolver или, как предложили @kevin-brown, сделать это как отдельный режим. Если преобразование выполняется до или во время URIResolver, получающего FO, тогда "основное" преобразование шаблона + ссылки на FO просто необходимо извлечь правую часть поддокумента FO, например. document('constructed-URI')/fo:root/fo:page-sequence/*. Однако, если вы изменяете таблицу стилей из Antenna House, тогда вы должны иметь возможность модифицировать ее, чтобы в любом случае не создавать внешние fo:root и т.д.

Я сделал что-то подобное много лет назад с переопределением URI-резольвера для процессора XSLT libxslt для сервера на основе XSLT: контекст для последовательных прогонов внутреннего XSLT-процессора был сохранен как документы на специальных URI и не обязательно был записан в файловая система вообще.

Вместо этого вы могли бы написать функцию расширения, которая выполняет поиск ссылок на поля. Например, группа сообщества печати и макета страницы @W3C выпустила функции расширения для нескольких процессоров XSLT, которые запускают процессор FO в середине преобразования XSLT, чтобы вернуть XML для дерева области для форматированного результата. См. http://www.w3.org/community/ppl/wiki/XSLTExtensions