Java с использованием Saxon (s9api) для преобразования XML: Как добавить входные файлы в ресурсы? - PullRequest
0 голосов
/ 31 января 2020

Я использую saxon (HE 9.9.1-6) для преобразования XML в HTML файл. Saxon используется потому, что XSLT версии 2 и классы java по умолчанию не выполнены.
XSLT содержит два оператора для копирования в содержимом других файлов:

<xsl:value-of select="unparsed-text('file.ext')"/>

Это работает до тех пор, пока Xslt и эти файлы находятся в одном каталоге, а xslt указан как источник файла

Source xslt = new StreamSource(new File("c:/somedir/file.xsl"));

Но мой xslt находится в каталоге ресурсов (позже он должен быть упакован в jar). Если я использую его в этом контексте, saxon не сможет найти включенные файлы, потому что он смотрит в каталог root моего проекта:

Source xslt = new StreamSource(getClass().getClassLoader().getResourceAsStream("file.xsl"));

приводит к:

Error evaluating (fn:unparsed-text(...)) in xsl:value-of/@select on line 22 column 66
FOUT1170: Failed to read input file: <project root directory>\included_file.css (File not found)

Is Есть ли способ, чтобы я мог предоставить saxon дополнительные StreamSources для файлов, которые он должен включать? Я не смог ничего найти.

В идеале я хотел бы что-то вроде этого:

transformer.addInput(new StreamSource(getClass().getClassLoader().getResourceAsStream("inputfile.css")));

Единственное решение, которое я нашел, было довольно уродливым: скопируйте xslt и нужные ему файлы из ресурсов в временный каталог, а затем выполните преобразование, используя его в качестве источника.


Пример кода

Я не разбираюсь в написании XSLT, поэтому могу предложить только неминимальные файлы примеров.
xslt и два его необходимых файла (css и js) можно найти здесь . Вам нужны три "xrechnung". Прямые ссылки: xrechnung- html .xsl , xrechnung-viewer. css, xrechnung-viewer. js.
Пожалуйста, поставьте их в каталоге ресурсов (на всякий случай в eclipse: создайте папку ресурсов и добавьте ее в качестве исходного каталога в свойствах-> путь сборки).

xml был сгенерирован первым шагом Вышеупомянутый проект, использующий его собственные файлы примеров, я поместил его на pastebin здесь
(изначально включен, но получил ошибку ограничения символов)

Наконец, код Java, включая уродливый Обходной путь:

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Comparator;

import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;

import org.xml.sax.SAXException;

import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.Serializer;
import net.sf.saxon.s9api.Xslt30Transformer;
import net.sf.saxon.s9api.XsltCompiler;
import net.sf.saxon.s9api.XsltExecutable;

public class SaxonProblem {
    public static void main(String[] args) throws IOException, SaxonApiException, SAXException {
        Path xml = Paths.get("path/to/the.xml");
        //working(xml);
        notWorking(xml);
    }

    public static void working(Path xmlFile) throws IOException, SaxonApiException, SAXException {
        Path dir = Files.createTempDirectory("saxon");
        System.out.println("Temp dir: " + dir.toString());
        Path xsltFile = dir.resolve("xrechnung-html.xsl");

        Files.copy(SaxonProblem.class.getClassLoader().getResourceAsStream("xrechnung-html.xsl"),
                xsltFile, StandardCopyOption.REPLACE_EXISTING);
        Files.copy(SaxonProblem.class.getClassLoader().getResourceAsStream("xrechnung-viewer.css"),
                dir.resolve("xrechnung-viewer.css"), StandardCopyOption.REPLACE_EXISTING);
        Files.copy(SaxonProblem.class.getClassLoader().getResourceAsStream("xrechnung-viewer.js"),
                dir.resolve("xrechnung-viewer.js"), StandardCopyOption.REPLACE_EXISTING);

        // for the sake of brevity, the html is made where the xml was
        Path html = xmlFile.resolveSibling(xmlFile.getFileName().toString() + ".html");
        Source xslt = new StreamSource(xsltFile.toFile());
        Source xml = new StreamSource(xmlFile.toFile());

        transformXml(xml, xslt, html);

        // cleanup
        Files.walk(dir).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
    }

    public static void notWorking(Path xmlFile) throws SaxonApiException, SAXException, IOException {
        // for the sake of brevity, the html is made where the xml was
        Path html = xmlFile.resolveSibling(xmlFile.getFileName().toString() + ".html");

        Source xslt = new StreamSource(SaxonProblem.class.getClassLoader().getResourceAsStream("xrechnung-html.xsl"));
        Source xml = new StreamSource(xmlFile.toFile());
        transformXml(xml, xslt, html);
    }

    public static void transformXml(Source xml, Source xslt, Path output) throws SaxonApiException, SAXException, IOException {
        Processor processor = new Processor(false);
        XsltCompiler compiler = processor.newXsltCompiler();
        XsltExecutable stylesheet = compiler.compile(xslt);
        Serializer out = processor.newSerializer(output.toFile());
        out.setOutputProperty(Serializer.Property.METHOD, "html");
        out.setOutputProperty(Serializer.Property.INDENT, "yes");
        Xslt30Transformer transformer = stylesheet.load30();
        transformer.transform(xml, out);
    }
}

Решение

Благодаря комментарию Мартина Хоннена и ответу Майкла Кея у меня есть решение с использованием UnparsedTextURIResolver. Это больше похоже на взлом, но это работает и лучше, чем мой предыдущий обходной путь:

Processor processor = new Processor(false);
UnparsedTextURIResolver defaultUtur = processor.getUnderlyingConfiguration().getUnparsedTextURIResolver();
processor.getUnderlyingConfiguration().setUnparsedTextURIResolver(new UnparsedTextURIResolver() {
    @Override
    public Reader resolve(URI arg0, String arg1, Configuration arg2) throws XPathException {
        if (arg0.toString().endsWith("myfilename.css")) {
            InputStream css = SaxonProblem.class.getClassLoader().getResourceAsStream("myfilename.css");
            return new InputStreamReader(css);
        }
        return defaultUtur.resolve(arg0, arg1, arg2);
    }
});
//[...]

1 Ответ

1 голос
/ 31 января 2020

Некоторые предложения:

  • Использовать URI со схемой classpath: (довольно недавнее добавление и может поддерживаться не на всех путях, где используются URI)

  • Зарегистрируйте UnparsedTextResolver в конфигурации; Saxon будет делегировать задачу поиска ресурса этому распознавателю

  • Предоставить имя содержащей директории в качестве параметра для таблицы стилей и использовать функцию resolve-uri() для получения абсолютного URI

...