Skip to content

Commit

Permalink
Fix extends resolution
Browse files Browse the repository at this point in the history
  • Loading branch information
jelovirt committed Nov 10, 2024
1 parent 1243d7b commit c794436
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 47 deletions.
5 changes: 4 additions & 1 deletion src/generator/com/elovirta/pdf/merge.xsl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
<xsl:variable name="current" select="x:normalize(x:flatten($base), (), $url)"/>
<xsl:choose>
<xsl:when test="map:contains($base, 'extends') and $base ?extends eq 'default'">
<xsl:sequence select="json-doc(xs:anyURI('classpath:/com/elovirta/pdf/default.json'))"/>
<xsl:variable name="extends-url" select="xs:anyURI('classpath:/com/elovirta/pdf/default.json')"/>
<xsl:variable name="extends" select="x:normalize(x:flatten(json-doc($extends-url)), (), $url)"/>
<xsl:sequence select="map:merge(($current, $extends),
map{ 'duplicates': 'use-first' })"/>
</xsl:when>
<xsl:when test="map:contains($base, 'extends')">
<xsl:variable name="extends-url" select="resolve-uri($base ?extends, $url)"/>
Expand Down
90 changes: 48 additions & 42 deletions src/main/java/com/elovirta/pdf/ant/StylesheetGeneratorTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@

import javax.xml.transform.TransformerException;
import javax.xml.transform.URIResolver;
import java.io.*;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;
Expand All @@ -28,7 +30,9 @@ public class StylesheetGeneratorTask extends Task {
private static final QName ATTR = QName.fromClarkName("{}attr");
private static final QName VERSION = QName.fromClarkName("{}version");
private static final QName FORMATTER = QName.fromClarkName("{}formatter");
/** Magic extends for build-in theme. */
/**
* Magic extends for build-in theme.
*/
private static final String DEFAULT_EXTENDS = "default";
private static final URI DEFAULT_EXTENDS_URI = URI.create("classpath:/com/elovirta/pdf/default.json");

Expand All @@ -38,6 +42,7 @@ public class StylesheetGeneratorTask extends Task {
private URIResolver resolver;
private Processor processor;
private XsltCompiler compiler;
private Xslt30Transformer transformer;
private XPathCompiler xpathCompiler;
private ObjectMapper yamlReader;
private ObjectMapper jsonWriter;
Expand All @@ -53,6 +58,12 @@ public void init() {
processor = xmlUtils.getProcessor();
compiler = processor.newXsltCompiler();
compiler.setURIResolver(resolver);
try {
final XsltExecutable executable = compiler.compile(resolver.resolve("classpath:/com/elovirta/pdf/merge.xsl", null));
transformer = executable.load30();
} catch (SaxonApiException | TransformerException e) {
throw new BuildException(String.format("Failed to parse template %s", template), e);
}
xpathCompiler = processor.newXPathCompiler();
yamlReader = new ObjectMapper(new YAMLFactory());
jsonWriter = new ObjectMapper();
Expand All @@ -69,6 +80,7 @@ public void execute() throws BuildException {
}

final XdmItem xdmItem = parseTemplate();
getProject().log("Template:" + xdmItem, Project.MSG_INFO);
generate(xdmItem, "front-matter.xsl", "xsl/fo/front-matter.xsl", null);
generate(xdmItem, "commons.xsl", "xsl/fo/commons.xsl", null);
generate(xdmItem, "tables.xsl", "xsl/fo/tables.xsl", null);
Expand Down Expand Up @@ -150,24 +162,18 @@ File generate(final XdmItem xdmItem,
XdmItem parseTemplate() {
getProject().log(this, String.format("Reading %s", template.toURI()), Project.MSG_INFO);
final String name = template.getName().toLowerCase();
try {
final XsltExecutable executable = compiler.compile(resolver.resolve("classpath:/com/elovirta/pdf/merge.xsl", null));
final XdmItem merged;
// XXX: Saxon's JSON functions don't use URIResolver, so have to parse manually
if (name.endsWith(".yml") || name.endsWith(".yaml")) {
merged = parseYamlTemplate(executable, parseYaml(template.toURI()), template.toURI());
} else {
merged = parseJsonTemplate();
}
return resolveVariables(executable, merged);
} catch (SaxonApiException | TransformerException e) {
throw new BuildException(String.format("Failed to parse template %s", template), e);
final XdmItem merged;
// XXX: Saxon's JSON functions don't use URIResolver, so have to parse manually
if (name.endsWith(".yml") || name.endsWith(".yaml")) {
merged = parseYamlTemplate(parseYaml(template.toURI()), template.toURI());
} else {
merged = parseJsonTemplate();
}
return resolveVariables(merged);
}

private XdmItem resolveVariables(final XsltExecutable executable, final XdmItem base) {
private XdmItem resolveVariables(final XdmItem base) {
try {
final Xslt30Transformer transformer = executable.load30();
final XdmValue resolved = transformer.callFunction(QName.fromClarkName("{x}resolve"), new XdmValue[]{
base
});
Expand All @@ -182,6 +188,7 @@ private XdmItem resolveVariables(final XsltExecutable executable, final XdmItem

private XdmItem parseJsonTemplate() {
try {
new JsonBuilder(xmlUtils.getProcessor().getUnderlyingConfiguration());
final XdmItem theme = xpathCompiler.evaluateSingle("json-doc(.)", new XdmAtomicValue(template.toURI()));
final XsltExecutable executable = compiler.compile(resolver.resolve("classpath:/com/elovirta/pdf/merge.xsl", null));
final Xslt30Transformer transformer = executable.load30();
Expand All @@ -196,54 +203,53 @@ private XdmItem parseJsonTemplate() {
}
}

private XdmItem parseYamlTemplate(final XsltExecutable executable, final XdmItem base, final URI url) {
private XdmItem parseYamlTemplate(final XdmItem base, final URI url) {
try {
final Xslt30Transformer transformer = executable.load30();
final XdmItem extendsValue = xpathCompiler.evaluateSingle(". ?extends", base);
if (extendsValue != null && extendsValue.getStringValue().equals(DEFAULT_EXTENDS)) {
final XdmItem extendsRes = xpathCompiler.evaluateSingle("json-doc(.)", new XdmAtomicValue(DEFAULT_EXTENDS_URI));
final XPathSelector selector = xpathCompiler.compile("json-doc(.)").load();
selector.setURIResolver(resolver);
selector.setContextItem(new XdmAtomicValue(DEFAULT_EXTENDS_URI));
final XdmItem extendsRes = selector.evaluateSingle();
// final XdmItem extendsRes = xpathCompiler.evaluateSingle("json-doc(.)", new XdmAtomicValue(DEFAULT_EXTENDS_URI));

final XdmValue flattened = transformer.callFunction(QName.fromClarkName("{x}flatten"), new XdmValue[]{
base
});
final XdmValue normalized = transformer.callFunction(QName.fromClarkName("{x}normalize"), new XdmValue[]{
flattened, XdmEmptySequence.getInstance(), new XdmAtomicValue(DEFAULT_EXTENDS_URI)
});
return normalized.itemAt(0);
return transformer.callFunction(QName.fromClarkName("{x}merge"), new XdmValue[]{
normalize(extendsRes),
normalize(base)
}).itemAt(0);
} else if (extendsValue != null) {
final URI extendsUri = url.resolve(extendsValue.getStringValue());
final XdmItem extendsRes = parseYamlTemplate(executable, parseYaml(extendsUri), url);
final XdmItem extendsRes = parseYamlTemplate(parseYaml(extendsUri), url);

final XdmValue flattened = transformer.callFunction(QName.fromClarkName("{x}flatten"), new XdmValue[]{
base
});
final XdmValue normalized = transformer.callFunction(QName.fromClarkName("{x}normalize"), new XdmValue[]{
flattened, XdmEmptySequence.getInstance(), new XdmAtomicValue(extendsUri)
});
return transformer.callFunction(QName.fromClarkName("{x}merge"), new XdmValue[]{
extendsRes.stream().asXdmValue(), normalized
extendsRes, normalize(base)
}).itemAt(0);
} else {
final XdmValue flattened = transformer.callFunction(QName.fromClarkName("{x}flatten"), new XdmValue[]{
base
});
final XdmValue normalized = transformer.callFunction(QName.fromClarkName("{x}normalize"), new XdmValue[]{
flattened, XdmEmptySequence.getInstance(), new XdmAtomicValue(url)
});
return normalized.itemAt(0);
return normalize(base).itemAt(0);
}
} catch (SaxonApiException e) {
throw new BuildException(String.format("Failed to parse template %s", template), e);
}
}

private XdmItem normalize(XdmItem in) {
try {
return transformer.callFunction(QName.fromClarkName("{x}normalize"), new XdmValue[]{
transformer.callFunction(QName.fromClarkName("{x}flatten"), new XdmValue[]{
in
}), XdmEmptySequence.getInstance(), new XdmAtomicValue(DEFAULT_EXTENDS_URI)
}).itemAt(0);
} catch (SaxonApiException e) {
throw new RuntimeException(e);
}
}

private XdmItem parseYaml(final URI abs) {
try {
final Object yaml = yamlReader.readValue(abs.toURL(), Object.class);
final String json = jsonWriter.writeValueAsString(yaml);
return xpathCompiler.evaluateSingle("parse-json(.)", new XdmAtomicValue(json));
} catch (SaxonApiException | IOException e) {
e.printStackTrace();
throw new BuildException("Failed to convert YAML to JSON: " + e.getMessage(), e);
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/resources/com/elovirta/pdf/default.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"style": {
"toc-1": {
"color": "pink"
"h1": {
"font-weight": "bold"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@
import org.dita.dost.log.DITAOTAntLogger;
import org.dita.dost.util.XMLUtils;
import org.json.JSONException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.skyscreamer.jsonassert.JSONCompareMode;

import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
Expand All @@ -34,6 +32,7 @@ public class StylesheetGeneratorTaskTest {
@BeforeEach
public void setUp() {
xmlUtils = new XMLUtils();
xmlUtils.getProcessor().getUnderlyingConfiguration().setURIResolver(resolver);
xpathCompiler = xmlUtils.getProcessor().newXPathCompiler();
final Project project = new Project();
xmlUtils.setLogger(new DITAOTAntLogger(project));
Expand Down Expand Up @@ -174,6 +173,20 @@ public void getTemplate_normalize_single(final String template) throws URISyntax
JSONCompareMode.STRICT);
}

@ParameterizedTest
@ValueSource(strings = {
"default.json",
"default.yaml"
})
public void getTemplate_default(final String template) throws URISyntaxException, SaxonApiException, JSONException {
task.setTemplate(new File(getClass().getClassLoader().getResource("src/" + template).toURI()).getAbsolutePath());

final XdmValue act = task.parseTemplate();

assertEquals(readToString("exp/" + template.substring(0, template.indexOf(".")) + ".json"), toString(act),
JSONCompareMode.STRICT);
}

private String readToString(final String name) {
final InputStream baseInput = getClass().getClassLoader().getResourceAsStream(name);
return new BufferedReader(new InputStreamReader(baseInput, StandardCharsets.UTF_8))
Expand Down
7 changes: 7 additions & 0 deletions src/test/resources/com/elovirta/pdf/default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"style": {
"h1": {
"font-weight": "bold",
}
}
}
10 changes: 10 additions & 0 deletions src/test/resources/exp/default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"style": {
"toc-1": {
"color": "blue"
},
"toc-2": {
"color": "green"
}
}
}
8 changes: 8 additions & 0 deletions src/test/resources/src/default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "default",
"style": {
"h1": {
"background-color": "pink"
}
}
}
4 changes: 4 additions & 0 deletions src/test/resources/src/default.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
extends: default
style:
h1:
background-color: pink

0 comments on commit c794436

Please sign in to comment.