Как внедрить XSLT-преобразование DocBook в веб-приложение Java?

Я нахожусь на этапе проверки концепции создания некоторого преобразования DocBook → PDF в веб-приложение. Основные требования:

  • Он должен запускаться «из JAR» — установка таблицы стилей в виде файлов в файловой системе сервера приложений — это не то, что мне нужно.
  • Он не основан на Spring, поэтому мне нужно более общее решение для Java.
  • В настоящее время мы используем таблицы стилей DocBook 1.79.2 , хотя, возможно, могли бы использовать таблицы стилей xslt20, если это более уместно.
  • В настоящее время мы используем Saxon-HE 12.3 для проверки концепции, но определенно можем обновить ее до коммерческой версии.

TLDR: Как мне инкапсулировать таблицы стилей DocBook XSLT в JAR (что не требует разбивки JAR на файлы в файловой системе)?

Как недавно обсуждалось в списке рассылки docbook-apps, я могу многое сделать, начав с таблиц стилей в src/main/resources/xsl (с некоторыми настройками на этом уровне, а затем таблиц стилей DocBook в src/main/resources/xsl/docbook-xsl-1.79.2), каталога, который начинается так:

<?xml version = "1.0" encoding = "utf-8"?>
<catalog xmlns = "urn:oasis:names:tc:entity:xmlns:xml:catalog">
   <uri name = "file:/xsl/juno-driver.xsl"
         uri = "classpath:/xsl/juno-driver.xsl" />
   <uri name = "file:/xsl/header-footer.xsl"
         uri = "classpath:/xsl/header-footer.xsl" />
   <uri name = "file:/xsl/table.xsl"
         uri = "classpath:/xsl/table.xsl" />
   <uri name = "file:/xsl/titlepage.xsl"
         uri = "classpath:/xsl/titlepage.xsl" />
   <uri name = "file:/xsl/docbook-xsl-1.79.2/fo/docbook.xsl"
         uri = "classpath:/xsl/docbook-xsl-1.79.2/fo/docbook.xsl" />
   <uri name = "file:/xsl/docbook-xsl-1.79.2/VERSION.xsl"
         uri = "classpath:/xsl/docbook-xsl-1.79.2/VERSION.xsl" />
   <uri name = "file:/xsl/docbook-xsl-1.79.2/fo/param.xsl"
         uri = "classpath:/xsl/docbook-xsl-1.79.2/fo/param.xsl" />

(и продолжает сопоставлять каждый файл .xsl, .xml, .ent и .dtd с его эквивалентом classpath: URI) и некоторый код, подобный этому:

DOMResult result = new DOMResult();
TransformerFactory factory = TransformerFactory.newInstance();
InputStream is = XmlTest.class.getResourceAsStream("/xsl/juno-driver.xsl");
Source source = new StreamSource(is, "file:/xsl/juno-driver.xsl");
Transformer transformer = factory.newTransformer(source);
transformer.transform(new DOMSource(document), result);
return (Document) result.getNode();

Это почти приводит нас туда, но терпит неудачу:

Error at char 9 in expression in xsl:param/@select on line 18 column 57 of l10n.xsl:
  FODC0002  I/O error reported by XML parser processing
  file:///xsl/docbook-xsl-1.79.2/common/l10n.xsl. Caused by java.io.FileNotFoundException:
  /xsl/docbook-xsl-1.79.2/common/l10n.xsl (No such file or directory)
at parameter local.l10n.xml on line 18 column 57 of l10n.xsl:
     invoked by global parameter local.l10n.xml at file:///xsl/docbook-xsl-1.79.2/common/l10n.xsl#18

Где эта строка включает вызов document(''):

<xsl:param name = "local.l10n.xml" select = "document('')"/>

Похоже, он настаивает на загрузке себя из файла, а затем (очевидно) не может найти его по этому URI. Как мы сообщаем тому, кто обрабатывает вызовы функции document(), использовать путь к классам?

Я отправил минимальный пример проблемы на GitHub: вы можете клонировать репозиторий и запустить mvn clean test для воспроизведения.

Я бы также согласился на совет относительно любого другого подхода к выполнению этого, который соответствует списку ограничений в верхней части поста!

Можете ли вы обновить свой вопрос, включив в него полный минимальный пример, включая pom.xml, который воспроизводит проблему?

tgdavies 07.08.2023 03:56

Вместо того, чтобы использовать URI пути к классам и перечислять их все в каталоге, рассматривали ли вы использование jar: URI, например. new StreamSource(is, "jar:file:///docbook-xsl!/xsl/foo.xsl")

Michael Kay 07.08.2023 11:23

Кроме того, может случиться так, что написанный пользователем URIResolver (или Saxon ResourceResolver) может обрабатывать вызовы document("") и делегировать все остальное.

Michael Kay 07.08.2023 11:26

Я могу опубликовать минимальный пример, и я попробую jar: URI — спасибо.

Paul A. Hoadley 08.08.2023 01:03

Судя по pom.xml, ваш минимальный пример, похоже, ориентирован на Java 8. Требуется ли для вас работа на Java 8?

Jukka Matilainen 08.08.2023 19:17
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
5
84
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я думаю, что есть несколько способов сделать это. Один из способов сделать это — добавить поддержку доступа к ресурсам в пути к классам по URL-адресам. Таким образом, вы можете указать на таблицы стилей в своем пути к классам с помощью URL-адреса без необходимости иметь каталог.

Вы можете сделать это, например, зарегистрировав приведенный ниже класс как реализацию URLStreamHandlerProvider. Реализация адаптирована из этого ответа, но изменена для поддержки необязательной косой черты в начале URL-адреса, а также изменена для использования имени схемы cp: вместо более обычного classpath:.

  • Начальная косая черта полезна в URL-адресах, чтобы они обрабатывались как иерархические URL-адреса, чтобы можно было разрешить относительные ссылки.
  • Изменение имени схемы (протокола) на cp: связано с тем, что Saxon-HE (по крайней мере, версия 12.3), по-видимому, имеет обходной путь, специфичный для URL-адресов classpath:, что вызывает проблему с удалением начального слэша из пути, когда он разрешает относительные classpath: URL-адреса.

В Java 9 и выше вы можете зарегистрировать провайдера, указав полное имя класса в файле конфигурации META-INF/services/java.net.spi.URLStreamHandlerProvider.

С этим вы сможете указывать на свои таблицы стилей с помощью URL-адреса, например cp:/xsl/docbook-xsl-1.79.2/html/docbook.xsl, и заставить его работать без каталога, включая относительный импорт, до тех пор, пока ваш XSLT-процессор использует (или, по крайней мере, возвращается) этот метод разыменования. URL-адреса. Судя по быстрому тесту, этот подход работает по крайней мере с XSLT-процессорами Xalan-Java и Saxon-HE. (Я думаю, что процессор XSLT по умолчанию, включенный в Java, может иметь некоторые проблемы при использовании таблиц стилей docbook-xsl.)

package com.stackoverflow.q76848364;

import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.net.spi.URLStreamHandlerProvider;

/**
 * URL stream handler for "cp:/" URLs for accessing resources in the classpath.
 * Supports a leading slash in the the path so that the scheme is treated as a
 * hierarchical scheme for resolving relative URL references.
 * 
 * <p>
 * Register this provider by putting the fully qualified name of this class in
 * the configuration file
 * META-INF/services/java.net.spi.URLStreamHandlerProvider.
 */
public class ClasspathURLStreamHandlerProvider extends URLStreamHandlerProvider {

    private static final String PROTOCOL = "cp";

    @Override
    public URLStreamHandler createURLStreamHandler(String protocol) {
        if (PROTOCOL.equals(protocol)) {
            return new URLStreamHandler() {
                @Override
                protected URLConnection openConnection(URL url) throws IOException {
                    String urlPath = url.getPath();
                    String resourcePath = urlPath.startsWith("/") ? urlPath.substring(1) : urlPath;
                    return ClassLoader.getSystemClassLoader().getResource(resourcePath).openConnection();
                }
            };
        }
        return null;
    }

}

Отредактировано для добавления: Предупреждение о разрешении относительных ссылок URI в Java.

При работе с относительными ссылками URI в Java обратите внимание, что в методе java.net.URI.resolve() есть ошибка, которая влияет на разрешение ссылок относительных URI, когда относительный URI пуст (баг JDK-8218962 в базе данных ошибок Java). Таблицы стилей docbook-xsl полагаются на то, что это работает правильно, поэтому возникнут проблемы, если кто-то попытается использовать что-либо, зависящее от класса java.net.URI для этой функциональности. Поскольку и Xalan-Java, и Saxon-HE работают нормально, они должны использовать что-то другое.

Отредактировано для добавления (2): Демонстрация

Я создал пул реквест здесь, демонстрируя это решение на предоставленном минимальном примере. (Исходный пример был настроен на Java 8. Поскольку метод регистрации реализации URLStreamHandler отличается для Java 8 и Java 9+, вместо этого я изменил цель компиляции на Java 9, чтобы продемонстрировать новый подход.)

Спасибо за этот исчерпывающий ответ, включая запрос на включение примера проекта! Я подтвердил, что это работает. Java 8 — это всего лишь мягкое требование. Еще раз спасибо — превосходно.

Paul A. Hoadley 09.08.2023 02:24

Другие вопросы по теме