diff --git a/cli/.gitrepo b/cli/.gitrepo
index 4a32282e79..d920e3f90b 100644
--- a/cli/.gitrepo
+++ b/cli/.gitrepo
@@ -6,6 +6,6 @@
[subrepo]
remote = git@github.com:daisy/pipeline-cli-go.git
branch = master
- commit = 080925324c4dbd37d141261f537bb2a5e6fadde3
- parent = 0d87113b214274de885978a9efe04f6c878fa94c
+ commit = 9e483116d0ffc78d5bdeb73ca00918c1b0c00e14
+ parent = 7f81547a070c3fbfbc4bc2ba15c2a7196c396913
cmdver = 0.3.1
diff --git a/cli/pom.xml b/cli/pom.xml
index 2c8c8701aa..ae37eeb013 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -12,7 +12,7 @@
org.daisy.pipeline
cli
- 2.2.0
+ 2.2.1-SNAPSHOT
pom
DAISY Pipeline 2 :: Command Line Interface
Command Line Interface for the DAISY Pipeline 2.
@@ -101,8 +101,4 @@
-
-
- v2.2.0
-
diff --git a/framework/.gitrepo b/framework/.gitrepo
index a2153503a5..b6eb6624fa 100644
--- a/framework/.gitrepo
+++ b/framework/.gitrepo
@@ -6,6 +6,6 @@
[subrepo]
remote = git@github.com:daisy/pipeline-framework.git
branch = master
- commit = 1614556e78510068f584bba4ba44ef383007a117
- parent = 3e05d8023a313a9082b95541ac98886b0e04d6da
+ commit = e9a0e7138b6a1e508fe0734413ab6b6136a0a3da
+ parent = 6ab8727d70af90110701604c630ed8e280a28b46
cmdver = 0.3.1
diff --git a/framework/bom/pom.xml b/framework/bom/pom.xml
index 74222e6977..9e54f82a2d 100644
--- a/framework/bom/pom.xml
+++ b/framework/bom/pom.xml
@@ -70,7 +70,7 @@
org.daisy.pipeline
modules-registry
- 4.1.2
+ 5.0.0-SNAPSHOT
org.daisy.pipeline
@@ -80,7 +80,7 @@
org.daisy.pipeline
saxon-adapter
- 5.0.0
+ 5.5.0-SNAPSHOT
org.daisy.pipeline
diff --git a/framework/common-utils/src/main/java/org/daisy/common/file/URLs.java b/framework/common-utils/src/main/java/org/daisy/common/file/URLs.java
index d72871d146..4c96077ea4 100644
--- a/framework/common-utils/src/main/java/org/daisy/common/file/URLs.java
+++ b/framework/common-utils/src/main/java/org/daisy/common/file/URLs.java
@@ -143,7 +143,7 @@ public static URI relativize(URI base, URI url) {
return base.relativize(url);
}
- private static Map fsEnv = Collections.emptyMap();
+ private static final Map fsEnv = Collections.emptyMap();
/**
* @param resource The (not URL-encoded) path of a resource inside the specified JAR or class directory
diff --git a/framework/modules-registry/pom.xml b/framework/modules-registry/pom.xml
index 1fa76fce25..bf20eb81a7 100644
--- a/framework/modules-registry/pom.xml
+++ b/framework/modules-registry/pom.xml
@@ -11,7 +11,7 @@
modules-registry
- 4.1.3-SNAPSHOT
+ 5.0.0-SNAPSHOT
bundle
DAISY Pipeline 2 :: Module Registry
@@ -30,10 +30,10 @@
slf4j-api
-
+
org.osgi
org.osgi.core
- provided
org.osgi
@@ -71,8 +71,8 @@
<_dsannotations>
- org.daisy.pipeline.modules.impl.resolver.ModuleUriResolver,
- org.daisy.pipeline.modules.impl.tracker.DefaultModuleRegistry,
+ org.daisy.pipeline.modules.impl.ModuleUriResolver,
+ org.daisy.pipeline.modules.impl.DefaultModuleRegistry,
org.daisy.pipeline.xmlcatalog.impl.StaxXmlCatalogParser
!org.daisy.common.spi,*
@@ -97,8 +97,8 @@
- org.daisy.pipeline.modules.impl.resolver.ModuleUriResolver,
- org.daisy.pipeline.modules.impl.tracker.DefaultModuleRegistry,
+ org.daisy.pipeline.modules.impl.ModuleUriResolver,
+ org.daisy.pipeline.modules.impl.DefaultModuleRegistry,
org.daisy.pipeline.xmlcatalog.impl.StaxXmlCatalogParser
diff --git a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/Component.java b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/Component.java
index 68715a6d44..5a2001a019 100644
--- a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/Component.java
+++ b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/Component.java
@@ -2,48 +2,35 @@
import java.net.URI;
import java.net.URL;
+import java.nio.file.NoSuchFileException;
import org.daisy.common.file.URLs;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
/**
* Module component now based on expath package components.
*/
-public class Component {
-
-
+public class Component implements Dependency {
- /** The uri. */
+ private final Module module;
private final URI uri;
-
- /** The path. */
- private final String path;
- // private Space space;
- /** The loader. */
- private final ResourceLoader loader;
-
- /** The module. */
- private Module module;
-
- /** The m logger. */
- Logger mLogger = LoggerFactory.getLogger(getClass().getName());
+ private final URL resource;
+ private final String version;
/**
* Instantiates a new component.
- *
- * @param uri
- * the uri
- * @param path
- * the path
- * @param loader
- * the loader which actually able to load the resource.
*/
- public Component(URI uri, String path, ResourceLoader loader) {
+ public Component(Module module, URI uri, String path) throws NoSuchFileException {
+ this.module = module;
this.uri = uri;
- this.path = path;
- this.loader = loader;
+ resource = module.getResource(path);
+ version = module.getVersion().replaceAll("-SNAPSHOT$", "");
+ }
+
+ public Component(Module module, URI uri, URL resource) {
+ this.module = module;
+ this.uri = uri;
+ this.resource = resource;
+ version = module.getVersion().replaceAll("-SNAPSHOT$", "");
}
/**
@@ -55,30 +42,30 @@ public URI getURI() {
return uri;
}
- /*
- * public Space getSpace() { return space; }
- */
-
/**
* Gets the resource's real uri.
*
* @return the resource
*/
public URI getResource() {
- try {
+ return URLs.asURI(resource);
+ }
- mLogger.trace("Getting resource from component " + this + ": " + path);
- URL url= loader.loadResource(path);
- if(url!=null) {
- return URLs.asURI(url);
- } else {
- return null;
- }
+ /**
+ * Get the version of this component.
+ *
+ * Defaults to the version number of the module. Method should be overridden by components that
+ * have their own versioning.
+ */
+ public String getVersion() {
+ return version;
+ }
- } catch (Exception e) {
- mLogger.debug("Resource " + path + " does not exist", e);
- return null;
- }
+ /**
+ * Gets the module owner of this component.
+ */
+ public Module getModule() {
+ return module;
}
/*
@@ -91,22 +78,28 @@ public String toString() {
return "[" + uri + "]";
}
- /**
- * Gets the module owner of this component.
- *
- * @return the module
- */
- public Module getModule() {
- return module;
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null)
+ return false;
+ if (!(o instanceof Component))
+ return false;
+ Component that = (Component)o;
+ if (!module.equals(that.module))
+ return false;
+ if (!uri.equals(that.uri))
+ return false;
+ return true;
}
- /**
- * Sets the module.
- *
- * @param module
- * the new module
- */
- public void setModule(Module module) {
- this.module = module;
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + module.hashCode();
+ result = prime * result + uri.hashCode();
+ return result;
}
}
diff --git a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/Dependency.java b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/Dependency.java
new file mode 100644
index 0000000000..db8aad3237
--- /dev/null
+++ b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/Dependency.java
@@ -0,0 +1,4 @@
+package org.daisy.pipeline.modules;
+
+public interface Dependency {
+}
\ No newline at end of file
diff --git a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/Entity.java b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/Entity.java
index bb57e81b17..7992111d18 100644
--- a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/Entity.java
+++ b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/Entity.java
@@ -2,63 +2,34 @@
import java.net.URI;
import java.net.URL;
+import java.nio.file.NoSuchFileException;
import org.daisy.common.file.URLs;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-
-// TODO: Auto-generated Javadoc
/**
* Entity represents a public id and its path inside the module. Only handles
* publicId as the privates one will be loaded as components.
*/
public class Entity {
- /** The Public id. */
- private final String mPublicId;
-
- /** The Path. */
- private final String mPath;
+ private final Module module;
+ private final String publicId;
+ private final URL resource;
- /** The loader. */
- private final ResourceLoader mLoader;
-
- /** The module. */
- private Module mModule;
-
- private static Logger mLogger = LoggerFactory.getLogger(Entity.class);
/**
* Instantiates a new entity.
- *
- * @param publicId the public id
- * @param path the path
- * @param loader the loader
*/
- public Entity(String publicId, String path, ResourceLoader loader) {
- super();
- mPublicId = publicId;
- mPath = path;
- mLoader = loader;
+ public Entity(Module module, String publicId, String path) throws NoSuchFileException {
+ this.module = module;
+ this.publicId = publicId;
+ resource = module.getResource(path);
}
/**
- * Gets the module.
- *
- * @return the module
+ * Gets the module owner of this entity.
*/
public Module getModule() {
- return mModule;
- }
-
- /**
- * Sets the module.
- *
- * @param module the new module
- */
- public void setModule(Module module) {
- mModule = module;
+ return module;
}
/**
@@ -67,7 +38,7 @@ public void setModule(Module module) {
* @return the public id
*/
public String getPublicId() {
- return mPublicId;
+ return publicId;
}
/**
@@ -76,30 +47,6 @@ public String getPublicId() {
* @return the path
*/
public URI getResource() {
-try {
-
- mLogger.trace("Getting resource from entity " + this + ": " + mPath);
- URL url= mLoader.loadResource(mPath);
- if(url!=null) {
- return URLs.asURI(url);
- } else {
- return null;
- }
-
- } catch (Exception e) {
- mLogger.debug("Resource " + mPath + " does not exist", e);
- return null;
- }
- }
-
- /**
- * Gets the loader.
- *
- * @return the loader
- */
- public ResourceLoader getLoader() {
- return mLoader;
+ return URLs.asURI(resource);
}
-
-
}
diff --git a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/JavaDependency.java b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/JavaDependency.java
new file mode 100644
index 0000000000..1ec709821f
--- /dev/null
+++ b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/JavaDependency.java
@@ -0,0 +1,52 @@
+package org.daisy.pipeline.modules;
+
+/**
+ * Java dependency of an XSLT resource.
+ */
+public class JavaDependency implements Dependency {
+
+ private final Module module;
+ private final String className;
+
+ /**
+ * @param className The fully qualified name of the class.
+ */
+ public JavaDependency(Module module, String className) {
+ this.module = module;
+ this.className = className;
+ }
+
+ public String getClassName() {
+ return className;
+ }
+
+ @Override
+ public String toString() {
+ return className;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null)
+ return false;
+ if (!(o instanceof JavaDependency))
+ return false;
+ JavaDependency that = (JavaDependency)o;
+ //if (!module.equals(that.module))
+ // return false;
+ if (!className.equals(that.className))
+ return false;
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ //result = prime * result + module.hashCode();
+ result = prime * result + className.hashCode();
+ return result;
+ }
+}
diff --git a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/Module.java b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/Module.java
index fc78b77875..f920cf3453 100644
--- a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/Module.java
+++ b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/Module.java
@@ -1,15 +1,27 @@
package org.daisy.pipeline.modules;
import java.io.File;
+import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.function.Supplier;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import javax.xml.stream.XMLInputFactory;
import com.google.common.collect.Iterators;
@@ -32,71 +44,109 @@ public abstract class Module {
private String name;
private String version;
private String title;
- private final Map components = new HashMap<>();
- private final Map entities = new HashMap<>();
+ protected final Map> components = new HashMap<>();
+ private final Map> entities = new HashMap<>();
+ private final Map xsltPackages = new HashMap<>();
+ private ResourceLoader loader;
private static final Logger mLogger = LoggerFactory.getLogger(Module.class);
+ private static final Map fsEnv = Collections.emptyMap();
+
/**
* Instantiate a new module
*/
protected Module(String name, String version, String title) {
- this.name = name;
- this.version = version;
- this.title = title;
- }
-
- /**
- * Initialize the module
- */
- protected void init(XmlCatalogParser parser) {
- Class> thisClass = this.getClass();
- try {
- URI jarFileURI = thisClass.getProtectionDomain().getCodeSource().getLocation().toURI();
+ if (OSGiHelper.inOSGiContext())
+ OSGiHelper.populate(this);
+ else {
+ this.name = name;
+ this.version = version;
+ this.title = title;
try {
+ URI jarFileURI = getClass().getProtectionDomain().getCodeSource().getLocation().toURI();
+ if (!jarFileURI.toString().startsWith("file:"))
+ throw new RuntimeException("unexpected code source location: " + jarFileURI);
File jarFile = new File(jarFileURI);
- mLogger.trace("Creating module from JAR: " + jarFile);
- parseCatalog(
- parser,
- new ResourceLoader() {
+ if (!jarFile.exists())
+ throw new RuntimeException("coding error");
+ getLogger().trace("Creating module from JAR: " + jarFile);
+ this.loader = new ResourceLoader() {
// Can't use ClassLoader.getResource() because there can be name
// clashes between resources in different JARs. Alternative
// solution would be to have a ClassLoader for each JAR.
@Override
- public URL loadResource(String path) {
+ public URL loadResource(String path) throws NoSuchFileException {
// Paths are assumed to be relative to META-INF
if (!path.startsWith("../")) {
throw new RuntimeException("Paths must start with '../' but got '" + path + "'");
}
- path = path.substring(2);
- try {
- return jarFile.isDirectory() ?
- new URL(jarFile.toURI().toASCIIString() + path) :
- new URL("jar:" + jarFile.toURI().toASCIIString() + "!" + path);
- } catch (MalformedURLException e) {
- throw new RuntimeException(e);
+ path = path.substring(3);
+ if (jarFile.isDirectory()) {
+ File f = new File(jarFile, path);
+ if (!f.exists())
+ throw new NoSuchFileException("file does not exist: " + f);
+ return URLs.asURL(f);
+ } else {
+ FileSystem fs; {
+ try {
+ fs = FileSystems.newFileSystem(URLs.asURI("jar:" + jarFileURI), fsEnv); }
+ catch (IOException e) {
+ throw new RuntimeException(e); }}
+ try {
+ Path f = fs.getPath("/" + path);
+ if (!Files.exists(f))
+ throw new NoSuchFileException("file does not exist: " + f);
+ try {
+ return new URL("jar:" + jarFileURI + "!/" + path); }
+ catch (MalformedURLException e) {
+ throw new RuntimeException(e); }}
+ finally {
+ try {
+ fs.close(); }
+ catch (IOException e) {
+ throw new RuntimeException(e); }}
}
}
@Override
public Iterable loadResources(final String path) {
throw new UnsupportedOperationException("Not supported without OSGi.");
}
- });
- } catch (IllegalArgumentException e) {
- // Could be because we are running in OSGi context
- OSGiHelper.init(this, parser);
+ };
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
}
- } catch (URISyntaxException e) {
- throw new RuntimeException(e);
}
}
+ protected Module(String name, String version, String title, ResourceLoader loader) {
+ if (OSGiHelper.inOSGiContext())
+ throw new RuntimeException("Constructor not supported in OSGi");
+ this.name = name;
+ this.version = version;
+ this.title = title;
+ this.loader = loader;
+ }
+
+ protected Logger getLogger() {
+ return mLogger;
+ }
+
// static nested class in order to delay class loading
private static abstract class OSGiHelper {
- static void init(Module thiz, XmlCatalogParser parser) {
+
+ static boolean inOSGiContext() {
+ try {
+ return FrameworkUtil.getBundle(OSGiHelper.class) != null;
+ } catch (NoClassDefFoundError e) {
+ return false;
+ }
+ }
+
+ static void populate(Module thiz) {
Bundle bundle = FrameworkUtil.getBundle(thiz.getClass());
- mLogger.trace("Creating module from OSGi bundle: " + bundle);
- ResourceLoader loader = new ResourceLoader() {
+ thiz.getLogger().trace("Creating module from OSGi bundle: " + bundle);
+ thiz.loader = new ResourceLoader() {
@Override
public URL loadResource(String path) {
// Paths are assumed to be relative to META-INF
@@ -118,7 +168,6 @@ public Iterator iterator() {
};
}
};
- thiz.parseCatalog(parser, loader);
// these fields are already set, but now get the values from the bundle metadata
thiz.name = bundle.getHeaders().get("Bundle-Name").toString();
thiz.version = bundle.getVersion().toString();
@@ -126,55 +175,103 @@ public Iterator iterator() {
}
}
+ /**
+ * Called by {@link ModuleRegistry} to resolve dependencies of components. If a dependency of a
+ * component can not be resolved, this does not result in an exception, but the component will
+ * not be created (and consequently will not be available through the {@link #getComponent()} or
+ * {@link #getEntity} method).
+ */
+ public abstract void resolveDependencies();
+
/**
* Parse catalog.xml file
*/
- private void parseCatalog(XmlCatalogParser parser, ResourceLoader loader) {
- URL catalogURL = loader.loadResource("../META-INF/catalog.xml");
- if (catalogURL == null)
- throw new RuntimeException("/META-INF/catalog.xml file not found");
+ public static void parseCatalog(Module module, XmlCatalogParser parser) {
+ URL catalogURL; {
+ try {
+ catalogURL = module.loader.loadResource("../META-INF/catalog.xml");
+ } catch (NoSuchFileException e) {
+ throw new RuntimeException("/META-INF/catalog.xml file not found", e);
+ }
+ }
XmlCatalog catalog = parser.parse(URLs.asURI(catalogURL));
- parseCatalog(catalog, loader);
+ try {
+ parseCatalog(module, catalog);
+ } catch (Throwable e) {
+ throw new RuntimeException("catalog.xml can not be parsed", e);
+ }
}
- // package private for unit tests
- void parseCatalog(XmlCatalog catalog, ResourceLoader loader) {
+ public static void parseCatalog(Module module, XmlCatalog catalog) throws NoSuchFileException {
for (Map.Entry entry : catalog.getSystemIdMappings().entrySet()) {
- addComponent(entry.getKey(), entry.getValue().toString(), loader);
+ URI path = entry.getValue();
+ if (path.isAbsolute() || path.getSchemeSpecificPart().startsWith("/"))
+ throw new NoSuchFileException("Expected a relative path, but got: " + path);
+ module.addComponent(entry.getKey(), path.getPath());
}
for (Map.Entry entry : catalog.getUriMappings().entrySet()) {
- addComponent(entry.getKey(), entry.getValue().toString(), loader);
+ URI path = entry.getValue();
+ if (path.isAbsolute() || path.getSchemeSpecificPart().startsWith("/"))
+ throw new NoSuchFileException("Expected a relative path, but got: " + path);
+ module.addComponent(entry.getKey(), path.getPath());
}
for (Map.Entry entry : catalog.getPublicMappings().entrySet()) {
- addEntity(entry.getKey(), entry.getValue().toString(), loader);
+ URI path = entry.getValue();
+ if (path.isAbsolute() || path.getSchemeSpecificPart().startsWith("/"))
+ throw new NoSuchFileException("Expected a relative path, but got: " + path);
+ module.addEntity(entry.getKey(), path.getPath());
}
for (Map.Entry rule : catalog.getRewriteUris().entrySet()) {
- Iterable entries = loader.loadResources(rule.getValue().toString());
+ URI path = rule.getValue();
+ if (path.isAbsolute() || path.getSchemeSpecificPart().startsWith("/"))
+ throw new NoSuchFileException("Expected a relative path, but got: " + path);
+ Iterable entries = module.loader.loadResources(path.getPath());
for (URL url : entries) {
try {
// get tail of the path i.e. ../static/css/ -> /css/
- String path = url.toURI().getPath().toString().replace(rule.getValue().toString().replace("..",""),"");
- addComponent(rule.getKey().resolve(URI.create(path)), url.toString(), loader);
+ String rewritten = url.toURI().getPath().replace(path.getPath().replace("..", ""), "");
+ module.addComponent(new Component(module, rule.getKey().resolve(URI.create(rewritten)), url));
} catch (URISyntaxException e) {
- mLogger.warn("Exception while generating paths");
+ module.getLogger().warn("Exception while generating paths");
}
}
}
}
- private void addComponent(URI uri, String path, ResourceLoader loader) {
- mLogger.trace("add component:" + uri.toString() + ", path: " + path);
- Component component = new Component(uri, path, loader);
- component.setModule(this);
- components.put(component.getURI(), component);
+ /**
+ * This method can be overridden to exclude certain components that have unmet dependencies.
+ */
+ protected boolean addComponent(URI uri, String path) throws NoSuchFileException {
+ getLogger().trace("Adding component: " + uri.toString() + ", path: " + path);
+ return addComponent(new Component(this, uri, path));
+ }
+
+ protected boolean addComponent(Component component) {
+ components.put(component.getURI(), () -> component);
+ return true;
}
-
-
- private void addEntity(String publicId, String path, ResourceLoader loader) {
- mLogger.trace("add entity:" + publicId.toString() + ", path: " + path);
- Entity entity = new Entity(publicId, path, loader);
- entity.setModule(this);
- entities.put(entity.getPublicId(), entity);
+
+ protected boolean addEntity(String publicId, String path) throws NoSuchFileException {
+ getLogger().trace("Adding entity: " + publicId.toString() + ", path: " + path);
+ return addEntity(new Entity(this, publicId, path));
+ }
+
+ protected boolean addEntity(Entity entity) {
+ entities.put(entity.getPublicId(), () -> entity);
+ return true;
+ }
+
+ protected boolean addXSLTPackage(String path, XMLInputFactory parser) throws NoSuchFileException, IllegalArgumentException {
+ return addXSLTPackage(new XSLTPackage(this, path, parser));
+ }
+
+ protected boolean addXSLTPackage(String name, String version, String path) throws NoSuchFileException {
+ return addXSLTPackage(new XSLTPackage(this, name, version, path));
+ }
+
+ protected boolean addXSLTPackage(XSLTPackage pack) {
+ xsltPackages.put(pack.getName(), pack);
+ return true;
}
/**
@@ -199,35 +296,128 @@ public String getTitle() {
}
/**
- * Gets the components.
+ * Get the specified resource from the module. This includes all resources, also the ones that
+ * are not exposed as components. The method can be overridden to exclude certain resources that
+ * have unmet dependencies.
+ *
+ * @param path The (not URL-encoded) path, relative to catalog.xml, of a resource inside the JAR
+ * or class directory of the module.
+ * @return An encoded absolute URL
+ * @throws NoSuchFileException if the resource at {@code path} is not available
*/
- public Iterable getComponents() {
- return components.values();
+ public URL getResource(String path) throws NoSuchFileException {
+ return loader.loadResource(path);
}
/**
- * Gets the component identified by the given uri.
+ * Whether this module provides a component with the given URI. This assumes that the component
+ * can be resolved.
+ *
+ * If the component could not be resolved (e.g. due to a missing dependency), this will result
+ * in a {@link ResolutionException} when the {@link #getComponent} method is called.
*/
- public Component getComponent(URI uri) {
- return components.get(uri);
+ public boolean hasComponent(URI uri) {
+ return components.containsKey(uri);
}
/**
- * Gets the list of entities.
+ * Gets all the component URIs.
*/
- public Iterable getEntities() {
- return entities.values();
+ public Set getComponents() {
+ return components.keySet();
}
/**
- * Gets the entity identified by the given public id.
+ * Gets the component identified by the given URI.
+ *
+ * @throws NoSuchElementException if this module provides a component with the given URI.
+ * @throws ResolutionException if the component could not be resolved, e.g. due to a missing dependency.
*/
- public Entity getEntity(String publicId) {
- return entities.get(publicId);
+ public Component getComponent(URI uri) throws NoSuchElementException, ResolutionException {
+ Supplier s = components.get(uri);
+ if (s == null)
+ throw new NoSuchElementException();
+ Component c = s.get();
+ if (c == null)
+ throw new ResolutionException();
+ return c;
+ }
+
+ /**
+ * Gets the list of entity IDs.
+ */
+ public Set getEntities() {
+ return entities.keySet();
+ }
+
+ /**
+ * Whether this module provides a entity with the given ID. This assumes that the entity can be
+ * resolved.
+ *
+ * If the entity could not be resolved (e.g. due to a missing dependency), this will result in a
+ * {@link ResolutionException} when the {@link #getEntity} method is called.
+ */
+ public boolean hasEntity(String publicId) {
+ return entities.containsKey(publicId);
+ }
+
+ /**
+ * Gets the entity identified by the given ID.
+ *
+ * @throws NoSuchElementException if this module provides an entity with the given ID.
+ * @throws ResolutionException if the entity could not be resolved, e.g. due to a missing dependency.
+ */
+ public Entity getEntity(String publicId) throws NoSuchElementException, ResolutionException {
+ Supplier s = entities.get(publicId);
+ if (s == null)
+ throw new NoSuchElementException();
+ Entity e = s.get();
+ if (e == null)
+ throw new ResolutionException();
+ return e;
+ }
+
+ /**
+ * Gets the list of XSLT packages.
+ */
+ public Iterable getXSLTPackages() {
+ return xsltPackages.values();
+ }
+
+ /**
+ * Gets the XSLT package identified by the given name.
+ */
+ public XSLTPackage getXSLTPackage(String name) {
+ return xsltPackages.get(name);
}
@Override
public String toString() {
return getName() + " [" + getVersion() + "]";
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null)
+ return false;
+ if (!(o instanceof Module))
+ return false;
+ Module that = (Module)o;
+ if (!name.equals(that.name))
+ return false;
+ if (!version.equals(that.version))
+ return false;
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + name.hashCode();
+ result = prime * result + version.hashCode();
+ return result;
+ }
}
diff --git a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/ModuleRegistry.java b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/ModuleRegistry.java
index b4b3586da9..6ac3e12944 100644
--- a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/ModuleRegistry.java
+++ b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/ModuleRegistry.java
@@ -2,9 +2,6 @@
import java.net.URI;
-
-
-// TODO: Auto-generated Javadoc
/**
* ModuleRegistry offers the functionality to enregister and query for the modules loaded.
*/
@@ -12,42 +9,43 @@ public interface ModuleRegistry extends Iterable {
/**
* Gets the module which has a component identified by the unique systemId.
- *
- * @param uri the uri
- * @return the module by component
*/
public Module getModuleByComponent(URI uri);
/**
- * Gets the module which has declared the entity with the given public id.
+ * Gets the module which has a component identified by the unique systemId and a version
+ * matching the given version range.
*
- * @param publicId the public id
- * @return the module by entity
+ * @param versionRange the version range, expressed as a mathematical interval notation, using the
+ * OSGi syntax.
+ */
+ public Module getModuleByComponent(URI uri, String versionRange);
+
+ /**
+ * Gets the module which has declared the entity with the given public id.
*/
public Module getModuleByEntity(String publicId);
/**
- * Gets the module handler which solves the dependency of the module source
- * with the returned module component uri.
- *
- * @param component the component
- * @param source the source
- * @return the module
+ * Gets the module which has an XSLT package identified by the given name.
*/
- public Module resolveDependency(URI component, Module source);
+ public Module getModuleByXSLTPackage(String name);
+
+ /**
+ * Gets the module which has the specified class among its resources, or {@code null} if no such
+ * module can be found.
+ */
+ public Module getModuleByClass(Class> clazz);
/**
* Returns the list of available components.
- *
- * @return an iterable with all the available components
*/
public Iterable getComponents();
/**
* Returns the list of available entities.
- *
- * @return the entities
*/
public Iterable getEntities();
-}
\ No newline at end of file
+}
diff --git a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/RelaxNGResource.java b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/RelaxNGResource.java
new file mode 100644
index 0000000000..be37da5f97
--- /dev/null
+++ b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/RelaxNGResource.java
@@ -0,0 +1,118 @@
+package org.daisy.pipeline.modules;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.nio.file.NoSuchFileException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.XMLEvent;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLInputFactory;
+
+import org.daisy.common.file.URLs;
+
+public class RelaxNGResource {
+
+ private static final String RNG_NS = "http://relaxng.org/ns/structure/1.0";
+ static final QName RNG_GRAMMAR = new QName(RNG_NS, "grammar");
+ private static final QName RNG_INCLUDE = new QName(RNG_NS, "include");
+ private static final QName _HREF = new QName("href");
+
+ private final URL resource;
+
+ public RelaxNGResource(Module module, String path) throws NoSuchFileException {
+ this.resource = module.getResource(path);
+ }
+
+ public RelaxNGResource(Component component) {
+ try {
+ this.resource = component.getResource().toURL();
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("coding error");
+ }
+ }
+
+ /**
+ * List the dependencies of this resource.
+ *
+ * This recursively resolves all include
that reference a component (either in the
+ * same module or in another one). Other dependencies are ignored.
+ *
+ * The dependencies are (external) {@link Component}. Included resources from the same module
+ * are handled recursively.
+ */
+ public Set listDependencies(ModuleRegistry resolver, XMLInputFactory parser) {
+ return listDependencies(resource, resolver, parser);
+ }
+
+ /**
+ * @param rngFile The RNG resource.
+ * @param resolver The module registry, for resolving dependency URIs.
+ * @param parser The StAX input factory for parsing the XML.
+ */
+ static Set listDependencies(URL rngFile, ModuleRegistry resolver, XMLInputFactory parser) {
+ if (cache.containsKey(rngFile)) {
+ return cache.get(rngFile);
+ } else {
+ Set dependencies = new HashSet<>();
+ try (InputStream is = rngFile.openStream()) {
+ XMLEventReader reader = parser.createXMLEventReader(is);
+ try {
+ int depth = 0;
+ while (reader.hasNext()) {
+ XMLEvent event = reader.peek();
+ if (event.isStartElement()) {
+ if (depth == 0 && !RNG_GRAMMAR.equals(event.asStartElement().getName())) {
+ throw new IllegalArgumentException("File is not RNG: " + rngFile);
+ } else if (depth == 1 && RNG_INCLUDE.equals(event.asStartElement().getName())) {
+ Attribute href = event.asStartElement().getAttributeByName(_HREF);
+ if (href != null) {
+ URI uri = URI.create(href.getValue());
+ if (uri.isAbsolute()) {
+ Module m = resolver.getModuleByComponent(uri);
+ if (m != null) {
+ Component component = m.getComponent(uri);
+ if (component == null)
+ throw new IllegalStateException("coding error"); // can not happen
+ dependencies.add(component);
+ } else {
+ throw new RuntimeException("" + rngFile + ": Couldn't resolve href " + uri);
+ }
+ } else {
+ // resolve relative path
+ dependencies.addAll(
+ listDependencies(URLs.resolve(URLs.asURI(rngFile), uri).toURL(), resolver, parser));
+ }
+ }
+ }
+ depth++;
+ } else if (event.isEndElement()) {
+ depth--;
+ }
+ reader.next();
+ }
+ } finally {
+ reader.close();
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Couldn't access RelaxNG file " + rngFile, e);
+ } catch (XMLStreamException e) {
+ throw new RuntimeException("Couldn't parse RelaxNG file " + rngFile, e);
+ }
+ cache.put(rngFile, dependencies);
+ return dependencies;
+ }
+ }
+
+ private final static Map> cache = new HashMap<>();
+
+}
diff --git a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/ResolutionException.java b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/ResolutionException.java
new file mode 100644
index 0000000000..02cbf4fcfa
--- /dev/null
+++ b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/ResolutionException.java
@@ -0,0 +1,23 @@
+package org.daisy.pipeline.modules;
+
+/**
+ * Exception thrown when a resource can not be resolved.
+ */
+public class ResolutionException extends RuntimeException {
+
+ public ResolutionException() {
+ super();
+ }
+
+ public ResolutionException(String message) {
+ super(message);
+ }
+
+ public ResolutionException(Throwable cause) {
+ super(cause);
+ }
+
+ public ResolutionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/ResourceLoader.java b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/ResourceLoader.java
index 8eb0babc12..bc5136b855 100644
--- a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/ResourceLoader.java
+++ b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/ResourceLoader.java
@@ -1,7 +1,7 @@
package org.daisy.pipeline.modules;
import java.net.URL;
-
+import java.nio.file.NoSuchFileException;
/**
* The Interface ResourceLoader allows to get an accessible URI from the path provided. This is used for loading {@link Component} objects.
@@ -11,15 +11,15 @@ public interface ResourceLoader {
/**
* Loads the resource.
*
- * @param path the path
- * @return the uRL
+ * @param path the (not URL-encoded) path, relative to catalog.xml
+ * @return An encoded absolute URL
+ * @throws NoSuchFileException if the resource is not available
*/
- URL loadResource(String path);
+ URL loadResource(String path) throws NoSuchFileException;
+
/**
* Loads a list of resources recursively.
- *
- * @param path the path
- * @return the uRL
*/
Iterable loadResources(String path);
+
}
diff --git a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/UseXSLTPackage.java b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/UseXSLTPackage.java
new file mode 100644
index 0000000000..af45bb853d
--- /dev/null
+++ b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/UseXSLTPackage.java
@@ -0,0 +1,52 @@
+package org.daisy.pipeline.modules;
+
+/**
+ * A {@code xsl:use-package} dependency.
+ */
+public class UseXSLTPackage implements Dependency {
+
+ private final String name;
+ private final String version;
+
+ public UseXSLTPackage(String name) {
+ this(name, "*");
+ }
+
+ public UseXSLTPackage(String name, String version) {
+ this.name = name;
+ this.version = version;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null)
+ return false;
+ if (!(o instanceof UseXSLTPackage))
+ return false;
+ UseXSLTPackage that = (UseXSLTPackage)o;
+ if (!name.equals(that.name))
+ return false;
+ if (!version.equals(that.version))
+ return false;
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + name.hashCode();
+ result = prime * result + version.hashCode();
+ return result;
+ }
+}
diff --git a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/XProcResource.java b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/XProcResource.java
new file mode 100644
index 0000000000..1457bf283a
--- /dev/null
+++ b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/XProcResource.java
@@ -0,0 +1,177 @@
+package org.daisy.pipeline.modules;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.nio.file.NoSuchFileException;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLInputFactory;
+
+import org.daisy.common.file.URLs;
+
+public class XProcResource {
+
+ private static final String XPROC_NS = "http://www.w3.org/ns/xproc";
+ private static final String CALABASH_EX_NS = "http://xmlcalabash.com/ns/extensions";
+ private static final QName P_LIBRARY = new QName(XPROC_NS, "library");
+ private static final QName P_DECLARE_STEP = new QName(XPROC_NS, "declare-step");
+ private static final QName P_PIPELINE = new QName(XPROC_NS, "pipeline");
+ private static final QName P_IMPORT = new QName(XPROC_NS, "import");
+ private static final QName P_DOCUMENT = new QName(XPROC_NS, "document");
+ private static final QName CX_IMPORT = new QName(CALABASH_EX_NS, "import");
+ private static final QName _HREF = new QName("href");
+ private static final QName _TYPE = new QName("type");
+
+ private final URL resource;
+ private final Module module;
+
+ public XProcResource(Module module, String path) throws NoSuchFileException {
+ this.resource = module.getResource(path);
+ this.module = module;
+ }
+
+ public XProcResource(Component component) {
+ try {
+ this.resource = component.getResource().toURL();
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("coding error");
+ }
+ this.module = component.getModule();
+ }
+
+ /**
+ * List the dependencies of this resource.
+ *
+ * This recursively resolves all p:import
, cx:import
,
+ * p:document
, xsl:import
, xsl:include
and
+ * rng:include
that reference a component (either in the same module or in another
+ * one). It also resolves xsl:use-package
and it resolves XPath extension functions
+ * defined in Java. Other dependencies are ignored.
+ *
+ * The dependencies may include (external) {@link Component}, {@link UseXSLTPackage} and
+ * (internal) {@link JavaDependency}. XSLT/XProc/RelaxNG resources from the same module are
+ * handled recursively.
+ */
+ public Set listDependencies(ModuleRegistry resolver, Set sourceRoots, ClassLoader compileClassPath,
+ XMLInputFactory parser) {
+ return listDependencies(resource, true, false, module, resolver, sourceRoots, compileClassPath, parser, new LinkedList<>());
+ }
+
+ /**
+ * @param file The resource.
+ * @param ensureXProc Assert that the resource is XProc.
+ * @param ensureXSLT Assert that the resource is XSLT.
+ * @param module The module that contains the resource.
+ * @param resolver The module registry, for resolving dependency URIs.
+ * @param sourceRoots Set of root directories containing source files. This is used to resolve
+ * Java source files when this method is called during compilation of the
+ * module.
+ * @param parser The StAX input factory for parsing the XML.
+ */
+ private static Set listDependencies(URL file, boolean ensureXProc, boolean ensureXSLT, Module module,
+ ModuleRegistry resolver, Set sourceRoots, ClassLoader compileClassPath,
+ XMLInputFactory parser, Deque stack) {
+ if (cache.containsKey(file)) {
+ return cache.get(file);
+ } else {
+ Set dependencies = new HashSet<>();
+ if (stack.contains(file)) {
+ // cyclic dependencies within the same module are supported
+ return dependencies;
+ }
+ try (InputStream is = file.openStream()) {
+ XMLEventReader reader = parser.createXMLEventReader(is);
+ try {
+ int depth = 0;
+ while (reader.hasNext()) {
+ XMLEvent event = reader.peek();
+ if (event.isStartElement()) {
+ StartElement elem = event.asStartElement();
+ QName name = elem.getName();
+ if (depth == 0) {
+ if (!(P_LIBRARY.equals(name) || P_DECLARE_STEP.equals(name) || P_PIPELINE.equals(name))) {
+ if (ensureXProc)
+ throw new IllegalArgumentException(
+ "File is not XProc: " + file + ": found root element " + name);
+ else if (XSLTResource.XSL_STYLESHEET.equals(name) || XSLTResource.XSL_PACKAGE.equals(name))
+ return XSLTResource.listDependencies(file, module, resolver, sourceRoots,
+ compileClassPath, parser);
+ else if (ensureXSLT)
+ throw new IllegalArgumentException(
+ "File is not XSLT: " + file + ": found root element " + name);
+ else if (RelaxNGResource.RNG_GRAMMAR.equals(name))
+ return RelaxNGResource.listDependencies(file, resolver, parser);
+ else
+ break;
+ }
+ } else if ((P_IMPORT.equals(name) ||
+ (CX_IMPORT.equals(name)
+ && "type='application/xml+xslt'".equals("" + elem.getAttributeByName(_TYPE)))) ||
+ P_DOCUMENT.equals(name)) {
+ Attribute href = elem.getAttributeByName(_HREF);
+ if (href == null)
+ throw new IllegalArgumentException(
+ "" + file + ": Invalid XProc: missing href attribute on " + name);
+ URI uri = URI.create(href.getValue());
+ if (P_IMPORT.equals(name)
+ && "http://xmlcalabash.com/extension/steps/library-1.0.xpl".equals(uri.toString()))
+ ; // FIXME: create dependency on XMLCalabash
+ else if (uri.isAbsolute()) {
+ Module m = resolver.getModuleByComponent(uri);
+ if (m != null) {
+ Component component = m.getComponent(uri);
+ if (component == null)
+ throw new IllegalStateException("coding error"); // can not happen
+ dependencies.add(component);
+ } else {
+ throw new RuntimeException("" + file + ": Couldn't resolve href " + uri);
+ }
+ } else {
+ // resolve relative path
+ stack.push(file);
+ dependencies.addAll(
+ listDependencies(URLs.resolve(URLs.asURI(file), uri).toURL(), P_IMPORT.equals(name),
+ CX_IMPORT.equals(name), module, resolver, sourceRoots, compileClassPath,
+ parser, stack));
+ stack.pop();
+ }
+ }
+ depth++;
+ } else if (event.isEndElement()) {
+ depth--;
+ }
+ reader.next();
+ }
+ } finally {
+ reader.close();
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Couldn't access "
+ + (ensureXProc ? "XProc " : ensureXSLT ? "XSLT " : "") + "file " + file, e);
+ } catch (XMLStreamException e) {
+ throw new RuntimeException("Couldn't parse "
+ + (ensureXProc ? "XProc " : ensureXSLT ? "XSLT " : "") + "file " + file, e);
+ }
+ cache.put(file, dependencies);
+ return dependencies;
+ }
+ }
+
+ private final static Map> cache = new HashMap<>();
+
+}
diff --git a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/XSLTPackage.java b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/XSLTPackage.java
new file mode 100644
index 0000000000..be4375c612
--- /dev/null
+++ b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/XSLTPackage.java
@@ -0,0 +1,78 @@
+package org.daisy.pipeline.modules;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.NoSuchFileException;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+public class XSLTPackage extends XSLTResource {
+
+ private String name;
+ private String version;
+
+ public XSLTPackage(Module module, String name, String version, String path) throws NoSuchFileException {
+ super(module, path);
+ this.name = name;
+ this.version = version != null
+ ? version
+ : module.getVersion().replaceAll("-SNAPSHOT$", "");
+ }
+
+ public XSLTPackage(Module module, String path, XMLInputFactory parser) throws NoSuchFileException, IllegalArgumentException {
+ super(module, path);
+ getPackageNameAndVersion(getResource(), parser);
+ if (version == null)
+ version = module.getVersion().replaceAll("-SNAPSHOT$", "");
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ private void getPackageNameAndVersion(URL xsltFile, XMLInputFactory parser) throws IllegalArgumentException {
+ try (InputStream is = xsltFile.openStream()) {
+ XMLEventReader reader = parser.createXMLEventReader(is);
+ try {
+ while (reader.hasNext()) {
+ XMLEvent event = reader.peek();
+ if (event.isStartElement()) {
+ StartElement elem = event.asStartElement();
+ QName elemName = elem.getName();
+ if (!XSL_PACKAGE.equals(elemName))
+ throw new IllegalArgumentException(
+ "File is not a XSLT package: " + xsltFile + ": found root element " + elemName);
+ Attribute name = elem.getAttributeByName(_NAME);
+ if (name == null)
+ throw new IllegalArgumentException(
+ "" + xsltFile + ": Invalid XSLT: missing name attribute on " + elemName);
+ this.name = name.getValue();
+ Attribute version = elem.getAttributeByName(_PACKAGE_VERSION);
+ if (version != null)
+ this.version = version.getValue();
+ return;
+ }
+ reader.next();
+ }
+ throw new IllegalStateException("coding error"); // document without a root element: can not happen
+ } finally {
+ reader.close();
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Couldn't access XSLT file " + xsltFile, e);
+ } catch (XMLStreamException e) {
+ throw new RuntimeException("Couldn't parse XSLT file " + xsltFile, e);
+ }
+ }
+}
diff --git a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/XSLTResource.java b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/XSLTResource.java
new file mode 100644
index 0000000000..e97494f636
--- /dev/null
+++ b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/XSLTResource.java
@@ -0,0 +1,208 @@
+package org.daisy.pipeline.modules;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.nio.file.NoSuchFileException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.Namespace;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLInputFactory;
+
+import org.daisy.common.file.URLs;
+
+public class XSLTResource {
+
+ private static final String XSLT_NS = "http://www.w3.org/1999/XSL/Transform";
+ static final QName XSL_STYLESHEET = new QName(XSLT_NS, "stylesheet");
+ static final QName XSL_PACKAGE = new QName(XSLT_NS, "package");
+ private static final QName XSL_IMPORT = new QName(XSLT_NS, "import");
+ private static final QName XSL_INCLUDE = new QName(XSLT_NS, "include");
+ private static final QName XSL_USE_PACKAGE = new QName(XSLT_NS, "use-package");
+ private static final QName _HREF = new QName("href");
+ protected static final QName _NAME = new QName("name");
+ protected static final QName _PACKAGE_VERSION = new QName("package-version");
+
+ private final URL resource;
+ private final Module module;
+
+ public XSLTResource(Module module, String path) throws NoSuchFileException {
+ this.resource = module.getResource(path);
+ this.module = module;
+ }
+
+ public XSLTResource(XSLTResource resource) {
+ this.resource = resource.resource;
+ this.module = resource.module;
+ }
+
+ public XSLTResource(Component component) {
+ try {
+ this.resource = component.getResource().toURL();
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("coding error");
+ }
+ this.module = component.getModule();
+ }
+
+ public URL getResource() {
+ return resource;
+ }
+
+ /**
+ * List the dependencies of this resource.
+ *
+ * This recursively resolves all xsl:import
and xsl:include
that
+ * reference a component (either in the same module or in another one). It also resolves
+ * xsl:use-package
and it resolves XPath extension functions defined in Java. Other
+ * dependencies are ignored.
+ *
+ * The dependencies may include (external) {@link Component}, {@link UseXSLTPackage} and
+ * (internal) {@link JavaDependency}. Imported/included resources from the same module are
+ * handled recursively.
+ */
+ public Set listDependencies(ModuleRegistry resolver, Set sourceRoots, ClassLoader compileClassPath,
+ XMLInputFactory parser) {
+ return listDependencies(resource, module, resolver, sourceRoots, compileClassPath, parser);
+ }
+
+ /**
+ * @param xsltFile The XSLT resource.
+ * @param module The module that contains the XSLT resource.
+ * @param resolver The module registry, for resolving dependency URIs.
+ * @param sourceRoots Set of root directories containing source files. This is used to resolve
+ * Java source files when this method is called during compilation of the
+ * module.
+ * @param parser The StAX input factory for parsing the XML.
+ */
+ static Set listDependencies(URL xsltFile, Module module, ModuleRegistry resolver, Set sourceRoots,
+ ClassLoader compileClassPath, XMLInputFactory parser) {
+ if (cache.containsKey(xsltFile)) {
+ return cache.get(xsltFile);
+ } else {
+ Set dependencies = new HashSet<>();
+ try (InputStream is = xsltFile.openStream()) {
+ XMLEventReader reader = parser.createXMLEventReader(is);
+ try {
+ int depth = 0;
+ while (reader.hasNext()) {
+ XMLEvent event = reader.peek();
+ if (event.isStartElement()) {
+ StartElement elem = event.asStartElement();
+ QName elemName = elem.getName();
+ if (depth == 0 && !(XSL_STYLESHEET.equals(elemName) || XSL_PACKAGE.equals(elemName))) {
+ throw new IllegalArgumentException(
+ "File is not XSLT: " + xsltFile + ": found root element " + elemName);
+ } else if (depth == 1 && (XSL_IMPORT.equals(elemName) || XSL_INCLUDE.equals(elemName))) {
+ Attribute href = elem.getAttributeByName(_HREF);
+ if (href == null)
+ throw new IllegalArgumentException(
+ "" + xsltFile + ": Invalid XSLT: missing href attribute on " + elemName);
+ URI uri = URI.create(href.getValue());
+ if (uri.isAbsolute()) {
+ Module m = resolver.getModuleByComponent(uri);
+ if (m != null) {
+ Component component = m.getComponent(uri);
+ if (component == null)
+ throw new IllegalStateException("coding error"); // can not happen
+ dependencies.add(component);
+ } else {
+ throw new RuntimeException("" + xsltFile + ": Couldn't resolve href " + uri);
+ }
+ } else {
+ // resolve relative path
+ dependencies.addAll(
+ listDependencies(URLs.resolve(URLs.asURI(xsltFile), uri).toURL(), module,
+ resolver, sourceRoots, compileClassPath, parser));
+ }
+ } else if (depth == 1 && XSL_USE_PACKAGE.equals(elemName)) {
+ Attribute name = elem.getAttributeByName(_NAME);
+ if (name == null)
+ throw new IllegalArgumentException(
+ "" + xsltFile + ": Invalid XSLT: missing name attribute on " + elemName);
+ Attribute version = elem.getAttributeByName(_PACKAGE_VERSION);
+ dependencies.add(
+ version != null ? new UseXSLTPackage(name.getValue(), version.getValue())
+ : new UseXSLTPackage(name.getValue()));
+ }
+ @SuppressWarnings("unchecked")
+ Iterator ns = elem.getNamespaces();
+ while (ns.hasNext()) {
+ String nsUri = ns.next().getNamespaceURI();
+ // In addition to checking that the namespace has the form of a
+ // fully qualified Java class name, use the heuristic that
+ // ExtensionFunctionProvider classes are not in the default package.
+ if (FQCN.matcher(nsUri).matches() && nsUri.contains(".")) {
+ // assuming it is the namespace of a XPath extension function, and the function
+ // is used within this element
+ String className = nsUri;
+ // try to resolve the class
+ String javaSourceFile = className.split("\\$")[0].replace('.', '/') + ".java";
+ boolean internal = false; {
+ if (sourceRoots != null)
+ for (File root : sourceRoots)
+ if (new File(root, javaSourceFile).exists()) {
+ // Java source file is inside the module we are compiling
+ internal = true;
+ break; }}
+ if (!internal) {
+ // if the function is not implemented in the same module, the class must be
+ // on the class path and resolvable through ModuleRegistry
+ Class c; {
+ try {
+ c = Class.forName(className, true, compileClassPath);
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "" + xsltFile
+ + ": Java dependency could not be resolved: class not on class path: "
+ + className);
+ }
+ }
+ if (resolver.getModuleByClass(c) == null)
+ throw new RuntimeException(
+ "" + xsltFile
+ + ": Java dependency could not be resolved: no module provides class: "
+ + className);
+
+ }
+ dependencies.add(new JavaDependency(module, className));
+ }
+ }
+ depth++;
+ } else if (event.isEndElement()) {
+ depth--;
+ }
+ reader.next();
+ }
+ } finally {
+ reader.close();
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Couldn't access XSLT file " + xsltFile, e);
+ } catch (XMLStreamException e) {
+ throw new RuntimeException("Couldn't parse XSLT file " + xsltFile, e);
+ }
+ cache.put(xsltFile, dependencies);
+ return dependencies;
+ }
+ }
+
+ private final static Map> cache = new HashMap<>();
+
+ private final static Pattern FQCN = Pattern.compile("([a-zA-Z_$][a-zA-Z\\d_$]*\\.)*[a-zA-Z_$][a-zA-Z\\d_$]*");
+
+}
diff --git a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/impl/DefaultModuleRegistry.java b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/impl/DefaultModuleRegistry.java
new file mode 100644
index 0000000000..23e042bde4
--- /dev/null
+++ b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/impl/DefaultModuleRegistry.java
@@ -0,0 +1,441 @@
+package org.daisy.pipeline.modules.impl;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import com.google.common.collect.Lists;
+
+import org.daisy.common.spi.ServiceLoader;
+import org.daisy.pipeline.modules.Component;
+import org.daisy.pipeline.modules.Entity;
+import org.daisy.pipeline.modules.Module;
+import org.daisy.pipeline.modules.ModuleRegistry;
+import org.daisy.pipeline.modules.ResolutionException;
+import org.daisy.pipeline.modules.XSLTPackage;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
+import org.osgi.framework.VersionRange;
+
+@org.osgi.service.component.annotations.Component(
+ name = "module-registry",
+ service = { ModuleRegistry.class }
+)
+public class DefaultModuleRegistry implements ModuleRegistry {
+
+ private static final Logger logger = LoggerFactory.getLogger(DefaultModuleRegistry.class);
+
+ private final HashMap componentsMap = new HashMap<>();
+ private final HashMap entityMap = new HashMap<>();
+ private final HashMap xsltPackageMap = new HashMap<>();
+ private final HashMap codeSourceLocationMap = new HashMap<>();
+ private final List modules = new ArrayList<>();
+ private Iterator nextModules;
+ private final LinkedList modulesBeingInitialized = new LinkedList<>();
+ /*
+ * Note that thanks to SaxonConfigurator.setModuleRegistry(), which calls
+ * ModuleRegistry.iterator(), the module registry and all modules will be fully initialized
+ * before the web service goes up.
+ */
+ private boolean initialized = false;
+ private int detectEndlessRecursion = 0;
+
+ public DefaultModuleRegistry() {
+ if (OSGiHelper.inOSGiContext())
+ nextModules = OSGiHelper.getModules();
+ else
+ nextModules = SPIHelper.getModules();
+ }
+
+ private Set components;
+ private Set entities;
+
+ /**
+ * Whether some module satisfies {@code module.hasComponent(uri)}.
+ */
+ private boolean hasComponent(URI uri) {
+ if (components == null) {
+ components = new HashSet<>();
+ entities = new HashSet<>();
+ for (Module m : modules) {
+ components.addAll(m.getComponents());
+ entities.addAll(m.getEntities());
+ }
+ for (Module m : modulesBeingInitialized) {
+ components.addAll(m.getComponents());
+ entities.addAll(m.getEntities());
+ }
+ if (nextModules.hasNext()) {
+ // load modules only after DefaultModuleRegistry has been created,
+ // because some module activator methods may use DefaultModuleRegistry
+ List modules = Lists.newArrayList(nextModules);
+ for (Module m : modules) {
+ components.addAll(m.getComponents());
+ entities.addAll(m.getEntities());
+ }
+ nextModules = modules.iterator();
+ }
+ }
+ return components.contains(uri);
+ }
+
+ /**
+ * Whether some module satisfies {@code module.hasEntity(uri)}.
+ */
+ private boolean hasEntity(String publicId) {
+ if (entities == null)
+ hasComponent(null); // initialize entities
+ return entities.contains(publicId);
+ }
+
+ private void addNextModule() throws NoSuchElementException {
+ if (initialized)
+ throw new NoSuchElementException();
+ do {
+ Module module; {
+ if (nextModules.hasNext())
+ module = nextModules.next();
+ else if (!modulesBeingInitialized.isEmpty()) {
+ if (detectEndlessRecursion > 2 * modulesBeingInitialized.size())
+ // An endless recursion is detected. This can happen either because a
+ // dependency is missing, or because there is a circular dependency. We
+ // assume that the recursive call comes from getModuleByComponent(),
+ // getModuleByEntity(), getModuleByXSLTPackage() or getModuleByClass(). All
+ // of these methods first check that the component/entity/package/class in
+ // question is provided by one the modules (with unresolved dependencies),
+ // before calling addNextModule(). So we can say that we've detected a
+ // circular dependency.
+ throw new RuntimeException("Circular dependency detected");
+ module = modulesBeingInitialized.poll();
+ } else {
+ initialized = true;
+ throw new NoSuchElementException();
+ }
+ }
+ detectEndlessRecursion++;
+ modulesBeingInitialized.add(module);
+ try {
+ // Resolve dependencies of the module. We do it here in ModuleRegistry so that a
+ // module does not need to do it during construction. This allows module components
+ // to depend on components of other modules. Because modules that make use of
+ // ModuleRegistry may recursively call their own resolveDependencies() method, a
+ // module may already be partly initialized when module.resolveDependencies() is
+ // called.
+ module.resolveDependencies();
+ } catch (Throwable e) {
+ logger.warn("An error happened while resolving dependencies of module " + module, e);
+ continue;
+ } finally {
+ modulesBeingInitialized.remove(module);
+ }
+ if (modules.contains(module))
+ // already added in a recursive call
+ return;
+ detectEndlessRecursion = 0;
+ logger.debug("Adding module {}", module);
+ modules.add(module);
+ for (URI component : module.getComponents()) {
+ try {
+ module.getComponent(component);
+ logger.debug(" - {}", component);
+ componentsMap.put(component, module);
+ } catch (ResolutionException e) {
+ // ignore components that can not be resolved
+ }
+ }
+ for (String entity: module.getEntities()) {
+ try {
+ module.getEntity(entity);
+ logger.debug(" - {}", entity);
+ entityMap.put(entity, module);
+ } catch (ResolutionException e) {
+ // ignore entities that can not be resolved
+ }
+ }
+ for (XSLTPackage pack : module.getXSLTPackages()) {
+ xsltPackageMap.put(pack.getName(), module);
+ }
+ URL codeSourceLocation = getCodeSourceLocation(module.getClass());
+ if (codeSourceLocationMap.containsKey(codeSourceLocation))
+ throw new IllegalStateException("Not more than one module is allowed in one bundle");
+ codeSourceLocationMap.put(codeSourceLocation, module);
+ logger.trace("Added module: "
+ + module.getClass().getName()
+ + "@" + Integer.toHexString(System.identityHashCode(module)));
+ return;
+ } while (true);
+ }
+
+ /**
+ * {@link Module} objects that call this method should be aware that this may call their own
+ * {@link Module#resolveDependencies} method.
+ */
+ @Override
+ public synchronized Iterator iterator() {
+ if (initialized)
+ return modules.iterator();
+ return new Iterator() {
+ private int index = 0;
+ public boolean hasNext() {
+ synchronized (DefaultModuleRegistry.this) {
+ if (index < modules.size())
+ return true;
+ return nextModules.hasNext() || !modulesBeingInitialized.isEmpty();
+ }
+ }
+ public Module next() throws NoSuchElementException {
+ synchronized (DefaultModuleRegistry.this) {
+ if (index >= modules.size())
+ addNextModule();
+ return modules.get(index++);
+ }
+ }
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ /**
+ * {@link Module} objects that call this method should be aware that this may call their own
+ * {@link Module#resolveDependencies} method.
+ */
+ @Override
+ public synchronized Module getModuleByComponent(URI uri) {
+ if (!hasComponent(uri))
+ return null;
+ do {
+ Module module = componentsMap.get(uri);
+ if (module != null)
+ return module;
+ for (Module m : modules)
+ if (m.hasComponent(uri))
+ // This means that a module provides the component, but
+ // the component could not be resolved. It's no use
+ // checking other modules.
+ try {
+ // Just to be sure check whether the component can
+ // really not be resolved. This should result in a
+ // ResolutionException.
+ m.getComponent(uri);
+ return m;
+ } catch (ResolutionException e) {
+ return null;
+ }
+ try {
+ addNextModule();
+ } catch (NoSuchElementException e) {
+ break;
+ }
+ } while (true);
+ return null;
+ }
+
+ /**
+ * {@link Module} objects that call this method should be aware that this may call their own
+ * {@link Module#resolveDependencies} method.
+ */
+ @Override
+ public synchronized Module getModuleByComponent(URI uri, String versionRange) {
+ Module module = getModuleByComponent(uri);
+ if (module != null && versionRange != null) {
+ Component component = module.getComponent(uri);
+ if (component == null)
+ throw new IllegalStateException("coding error"); // can not happen
+ Version version; {
+ try {
+ version = Version.parseVersion(component.getVersion());
+ } catch (IllegalArgumentException e) {
+ logger.debug("Version of " + uri + " can not be parsed: " + component.getVersion());
+ return null;
+ }
+ }
+ try {
+ if (!(new VersionRange(versionRange).includes(version))) {
+ logger.debug("Version of " + uri + " (" + version + ") not in requested range (" + versionRange + ")");
+ return null;
+ }
+ } catch (IllegalArgumentException e) {
+ logger.debug("Version range can not be parsed: " + versionRange);
+ return null;
+ }
+ }
+ return module;
+ }
+
+ /**
+ * Returns only the components that could be resolved (unlike {@link Module#getComponents}).
+ *
+ * {@link Module} objects that call this method should be aware that this may call their own
+ * {@link Module#resolveDependencies} method.
+ */
+ @Override
+ public synchronized Iterable getComponents() {
+ if (!initialized)
+ while (true)
+ try {
+ addNextModule();
+ } catch (NoSuchElementException e) {
+ break;
+ }
+ return componentsMap.keySet();
+ }
+
+ /**
+ * {@link Module} objects that call this method should be aware that this may call their own
+ * {@link Module#resolveDependencies} method.
+ */
+ @Override
+ public synchronized Module getModuleByEntity(String publicId) {
+ if (!hasEntity(publicId))
+ return null;
+ do {
+ Module module = entityMap.get(publicId);
+ if (module != null)
+ return module;
+ for (Module m : modules)
+ if (m.hasEntity(publicId))
+ // This means that a module provides the entity, but the
+ // entity could not be resolved. It's no use checking
+ // other modules.
+ try {
+ // Just to be sure check whether the entity can
+ // really not be resolved. This should result in a
+ // ResolutionException.
+ m.getEntity(publicId);
+ return m;
+ } catch (ResolutionException e) {
+ return null;
+ }
+ try {
+ addNextModule();
+ } catch (NoSuchElementException e) {
+ break;
+ }
+ } while (true);
+ return null;
+ }
+
+ /**
+ * Returns only the entities that could be resolved (unlike {@link Module#getEntities}).
+ *
+ * {@link Module} objects that call this method should be aware that this may call their own
+ * {@link Module#resolveDependencies} method.
+ */
+ @Override
+ public synchronized Iterable getEntities() {
+ if (!initialized)
+ while (true)
+ try {
+ addNextModule();
+ } catch (NoSuchElementException e) {
+ break;
+ }
+ return entityMap.keySet();
+ }
+
+ /**
+ * {@link Module} objects that call this method should be aware that this may call their own
+ * {@link Module#resolveDependencies} method.
+ */
+ @Override
+ public synchronized Module getModuleByXSLTPackage(String name) {
+ do {
+ Module module = xsltPackageMap.get(name);
+ if (module != null)
+ return module;
+ try {
+ addNextModule();
+ } catch (NoSuchElementException e) {
+ break;
+ }
+ } while (true);
+ return null;
+ }
+
+ /**
+ * {@link Module} objects that call this method should be aware that this may call their own
+ * {@link Module#resolveDependencies} method.
+ */
+ @Override
+ public synchronized Module getModuleByClass(Class> clazz) {
+ URL location = getCodeSourceLocation(clazz);
+ do {
+ Module module = codeSourceLocationMap.get(location);
+ if (module != null)
+ return module;
+ try {
+ addNextModule();
+ } catch (NoSuchElementException e) {
+ break;
+ }
+ } while (true);
+ return null;
+ }
+
+ private static URL getCodeSourceLocation(Class> clazz) throws IllegalArgumentException {
+ URL location = clazz.getProtectionDomain().getCodeSource().getLocation();
+ if (!(location.toString().startsWith("file:") || location.toString().startsWith("bundle:") || location.toString().startsWith("mvn:")))
+ throw new RuntimeException("unexpected code source location: " + location);
+ return location;
+ }
+
+ // static nested class in order to delay class loading
+ private static abstract class OSGiHelper {
+
+ static boolean inOSGiContext() {
+ try {
+ return FrameworkUtil.getBundle(OSGiHelper.class) != null;
+ } catch (NoClassDefFoundError e) {
+ return false;
+ }
+ }
+
+ static Iterator getModules() {
+ return new Iterator() {
+ private BundleContext bc = FrameworkUtil.getBundle(DefaultModuleRegistry.class).getBundleContext();
+ private ServiceReference[] refs;
+ private int index = 0; {
+ try {
+ refs = bc.getServiceReferences(Module.class.getName(), null);
+ } catch (InvalidSyntaxException e) {
+ throw new IllegalStateException(e); // should not happen
+ }
+ }
+ public boolean hasNext() {
+ return refs != null && index < refs.length;
+ }
+ public Module next() throws NoSuchElementException {
+ if (!hasNext())
+ throw new NoSuchElementException();
+ return (Module)bc.getService(refs[index++]);
+ }
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ }
+
+ // static nested class in order to delay class loading
+ private static abstract class SPIHelper {
+ static Iterator getModules() {
+ return ServiceLoader.load(Module.class).iterator();
+ }
+ }
+}
diff --git a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/impl/resolver/ModuleUriResolver.java b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/impl/ModuleUriResolver.java
similarity index 71%
rename from framework/modules-registry/src/main/java/org/daisy/pipeline/modules/impl/resolver/ModuleUriResolver.java
rename to framework/modules-registry/src/main/java/org/daisy/pipeline/modules/impl/ModuleUriResolver.java
index 49df686545..04cda54add 100644
--- a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/impl/resolver/ModuleUriResolver.java
+++ b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/impl/ModuleUriResolver.java
@@ -1,9 +1,10 @@
-package org.daisy.pipeline.modules.impl.resolver;
+package org.daisy.pipeline.modules.impl;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URI;
+import java.util.NoSuchElementException;
import javax.xml.transform.Source;
import javax.xml.transform.URIResolver;
@@ -11,8 +12,11 @@
import org.daisy.pipeline.modules.Module;
import org.daisy.pipeline.modules.ModuleRegistry;
+import org.daisy.pipeline.modules.ResolutionException;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
@@ -97,13 +101,16 @@ public Source resolve(String href, String base) {
private Source resolveFromModules(URI href) {
Module mod = mRegistry.getModuleByComponent(href);
if (mod == null) {
- mLogger.trace("No module found for uri:" + href);
+ mLogger.trace("No module found for URI: " + href);
return null;
}
- URI resource = mod.getComponent(href).getResource();
- if (resource == null) {
- mLogger.trace("No resource found in module " + mod.getName() + " for uri :" + href);
- return null;
+ mLogger.trace(href.toString() + " found in module " + mod.getName());
+ URI resource; {
+ try {
+ resource = mod.getComponent(href).getResource();
+ } catch (NoSuchElementException|ResolutionException e) {
+ throw new IllegalStateException("coding error");
+ }
}
SAXSource source = new SAXSource(new InputSource(resource.toString()));
return source;
@@ -116,26 +123,30 @@ private Source resolveFromModules(URI href) {
* java.lang.String)
*/
@Override
- public InputSource resolveEntity(String publicId, String systemId)
- throws SAXException, IOException {
- InputSource src=null;
+ public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
Module mod = mRegistry.getModuleByEntity(publicId);
- //by public id
- if (mod != null &&mod.getEntity(publicId)!=null && mod.getEntity(publicId).getResource()!=null) {
- src=new InputSource(mod.getEntity(publicId).getResource().toString());
-
- }else{
- //by systemId
- mLogger.trace("No module found for publicId:" + publicId);
- URI uriSystem=URI.create(systemId);
- mod = mRegistry.getModuleByComponent(uriSystem);
- if (mod != null) {
- src=new InputSource(mod.getComponent(uriSystem).getResource().toString());
- }else{
- mLogger.trace("No module found for uri:" + systemId);
+ // by public id
+ if (mod != null) {
+ mLogger.trace("Entity " + publicId + " found in module " + mod.getName());
+ URI resource; {
+ try {
+ resource = mod.getEntity(publicId).getResource();
+ } catch (NoSuchElementException|ResolutionException e) {
+ throw new IllegalStateException("coding error");
+ }
}
+ return new InputSource(resource.toString());
+ }
+ // by systemId
+ mLogger.trace("No module found for publicId:" + publicId);
+ URI uriSystem=URI.create(systemId);
+ mod = mRegistry.getModuleByComponent(uriSystem);
+ if (mod != null) {
+ return new InputSource(mod.getComponent(uriSystem).getResource().toString());
+ } else {
+ mLogger.trace("No module found for publicId: " + systemId);
+ return null;
}
- return src;
}
}
diff --git a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/impl/resolver/package-info.java b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/impl/resolver/package-info.java
deleted file mode 100644
index b31a2b8af0..0000000000
--- a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/impl/resolver/package-info.java
+++ /dev/null
@@ -1,4 +0,0 @@
-/**
- * URI resolver which uses the tracked modules' components to translate a public URI to a Source object
- */
-package org.daisy.pipeline.modules.impl.resolver;
diff --git a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/impl/tracker/DefaultModuleRegistry.java b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/impl/tracker/DefaultModuleRegistry.java
deleted file mode 100644
index 452fb1de45..0000000000
--- a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/impl/tracker/DefaultModuleRegistry.java
+++ /dev/null
@@ -1,104 +0,0 @@
-package org.daisy.pipeline.modules.impl.tracker;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-
-import org.daisy.pipeline.modules.Component;
-import org.daisy.pipeline.modules.Entity;
-import org.daisy.pipeline.modules.Module;
-import org.daisy.pipeline.modules.ModuleRegistry;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import org.osgi.service.component.annotations.Activate;
-import org.osgi.service.component.annotations.Deactivate;
-import org.osgi.service.component.annotations.Reference;
-import org.osgi.service.component.annotations.ReferenceCardinality;
-import org.osgi.service.component.annotations.ReferencePolicy;
-
-@org.osgi.service.component.annotations.Component(
- name = "module-registry",
- service = { ModuleRegistry.class }
-)
-public class DefaultModuleRegistry implements ModuleRegistry {
-
- private static final Logger logger = LoggerFactory
- .getLogger(DefaultModuleRegistry.class);
-
- private final HashMap componentsMap = new HashMap();
- private final HashMap entityMap = new HashMap();
- private final HashSet modules = new HashSet();
-
- public DefaultModuleRegistry() {
- }
-
- @Activate
- public void init() {
- logger.trace("Module registry up");
- }
-
- @Deactivate
- public void close() {
- }
-
- @Override
- public Iterator iterator() {
- return modules.iterator();
- }
-
- @Override
- public Module getModuleByComponent(URI uri) {
- return componentsMap.get(uri);
- }
-
- @Override
- public Module resolveDependency(URI component, Module source) {
- // TODO check cache, otherwise delegate to resolver
- return null;
- }
-
- @Override
- public Iterable getComponents() {
- return componentsMap.keySet();
- }
-
- @Reference(
- name = "Module",
- unbind = "-",
- service = Module.class,
- cardinality = ReferenceCardinality.MULTIPLE,
- policy = ReferencePolicy.STATIC
- )
- public void addModule(Module module) {
- logger.debug("Registring module {}", module.getName());
- modules.add(module);
- for (Component component : module.getComponents()) {
- logger.debug(" - {}", component.getURI());
- componentsMap.put(component.getURI(), module);
- }
- for (Entity entity: module.getEntities()) {
- logger.debug(" - {}", entity.getPublicId());
- entityMap.put(entity.getPublicId(), module);
- }
- }
-
- public void removeModule(Module module) {
- // FIXME: remove module
- }
-
- @Override
- public Module getModuleByEntity(String publicId) {
- return entityMap.get(publicId);
- }
-
- @Override
- public Iterable getEntities() {
- return entityMap.keySet();
- }
-
-
-}
diff --git a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/impl/tracker/package-info.java b/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/impl/tracker/package-info.java
deleted file mode 100644
index c22648a164..0000000000
--- a/framework/modules-registry/src/main/java/org/daisy/pipeline/modules/impl/tracker/package-info.java
+++ /dev/null
@@ -1,4 +0,0 @@
-/**
- * Module tracking used to manage any module loaded into the framework.
- */
-package org.daisy.pipeline.modules.impl.tracker;
diff --git a/framework/modules-registry/src/test/java/org/daisy/pipeline/modules/ModuleTest.java b/framework/modules-registry/src/test/java/org/daisy/pipeline/modules/ModuleTest.java
index 7946314ec1..364fec6710 100644
--- a/framework/modules-registry/src/test/java/org/daisy/pipeline/modules/ModuleTest.java
+++ b/framework/modules-registry/src/test/java/org/daisy/pipeline/modules/ModuleTest.java
@@ -4,11 +4,11 @@
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
+import java.nio.file.NoSuchFileException;
import java.util.Iterator;
import javax.xml.stream.XMLInputFactory;
-import org.daisy.pipeline.modules.Component;
import org.daisy.pipeline.modules.Module;
import org.daisy.pipeline.modules.ResourceLoader;
import org.daisy.pipeline.xmlcatalog.impl.StaxXmlCatalogParser;
@@ -22,7 +22,7 @@
public class ModuleTest {
@Test
- public void testBuildModule() {
+ public void testBuildModule() throws NoSuchFileException {
StaxXmlCatalogParser catalogParser = new StaxXmlCatalogParser();
catalogParser.setFactory(XMLInputFactory.newInstance());
catalogParser.activate();
@@ -42,12 +42,15 @@ public Iterable loadResources(String path) {
throw new UnsupportedOperationException("not implemented");
}
};
- Module module = new Module("name", "version", "title") {};
- module.parseCatalog(catalog, resourceLoader);
- Iterator components = module.getComponents().iterator();
+ Module module = new Module("name", "version", "title", resourceLoader) {
+ @Override
+ public void resolveDependencies() {}
+ };
+ Module.parseCatalog(module, catalog);
+ Iterator components = module.getComponents().iterator();
assertTrue(components.hasNext());
- Component c = components.next();
- assertEquals("http://example-module/hello.xml", c.getURI().toString());
+ URI c = components.next();
+ assertEquals("http://example-module/hello.xml", c.toString());
assertFalse(components.hasNext());
}
}
diff --git a/framework/parent/pom.xml b/framework/parent/pom.xml
index 4023c91ba6..9adff035f4 100644
--- a/framework/parent/pom.xml
+++ b/framework/parent/pom.xml
@@ -158,7 +158,7 @@
org.daisy.pipeline
ds-to-spi-runtime
- 1.1.0
+ 1.2.1-SNAPSHOT
org.daisy.pipeline.build
diff --git a/framework/saxon-adapter/pom.xml b/framework/saxon-adapter/pom.xml
index ffa92a2ffa..ee1f51f72d 100644
--- a/framework/saxon-adapter/pom.xml
+++ b/framework/saxon-adapter/pom.xml
@@ -11,7 +11,7 @@
saxon-adapter
- 5.0.1-SNAPSHOT
+ 5.5.0-SNAPSHOT
bundle
DAISY Pipeline 2 :: Adapter for Saxon
@@ -21,6 +21,10 @@
org.daisy.pipeline
common-utils
+
+ org.daisy.pipeline
+ modules-registry
+
com.google.guava
guava
@@ -43,14 +47,6 @@
ds-to-spi-runtime
provided
-
-
- org.daisy.pipeline
- modules-registry
- runtime
-
diff --git a/framework/saxon-adapter/src/main/java/org/daisy/common/saxon/SaxonConfigurator.java b/framework/saxon-adapter/src/main/java/org/daisy/common/saxon/SaxonConfigurator.java
index d888d324de..52cb7ee0f7 100644
--- a/framework/saxon-adapter/src/main/java/org/daisy/common/saxon/SaxonConfigurator.java
+++ b/framework/saxon-adapter/src/main/java/org/daisy/common/saxon/SaxonConfigurator.java
@@ -2,12 +2,16 @@
import java.io.ByteArrayInputStream;
import java.io.InputStream;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.xml.transform.sax.SAXSource;
+import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.URIResolver;
import net.sf.saxon.Configuration;
@@ -22,6 +26,9 @@
import net.sf.saxon.xpath.XPathFactoryImpl;
import org.daisy.common.xpath.saxon.XPathFunctionRegistry;
+import org.daisy.pipeline.modules.Module;
+import org.daisy.pipeline.modules.ModuleRegistry;
+import org.daisy.pipeline.modules.XSLTPackage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -144,6 +151,32 @@ public void addPackageDetails(PackageDetails pack) {
packages.add(pack);
}
+ @Reference(
+ name = "ModuleRegistry",
+ unbind = "-",
+ service = ModuleRegistry.class,
+ cardinality = ReferenceCardinality.OPTIONAL,
+ policy = ReferencePolicy.STATIC
+ )
+ public void setModuleRegistry(ModuleRegistry moduleRegistry) {
+ for (Module module : moduleRegistry) {
+ for (XSLTPackage pack : module.getXSLTPackages()) {
+ addPackageDetails(
+ new PackageDetails() {{
+ try {
+ nameAndVersion = new VersionedPackageName(pack.getName(), pack.getVersion());
+ URL url = pack.getResource();
+ sourceLocation = new StreamSource(url.openStream(), url.toURI().toASCIIString());
+ } catch (XPathException|IOException|URISyntaxException e) {
+ throw new IllegalStateException(
+ "Failed to create PackageDetails object for package '" + pack.getName() + "'", e);
+ }
+ }}
+ );
+ }
+ }
+ }
+
public void removePackageDetails(PackageDetails pack) {
if (packages != null)
packages.remove(pack);
diff --git a/framework/saxon-adapter/src/main/java/org/daisy/common/saxon/SaxonOutputValue.java b/framework/saxon-adapter/src/main/java/org/daisy/common/saxon/SaxonOutputValue.java
index f482ea4b31..f79f192870 100644
--- a/framework/saxon-adapter/src/main/java/org/daisy/common/saxon/SaxonOutputValue.java
+++ b/framework/saxon-adapter/src/main/java/org/daisy/common/saxon/SaxonOutputValue.java
@@ -142,6 +142,60 @@ public void writeStartElement(String prefix, String localName, String namespaceU
elementDepth++;
}
@Override
+ public void writeAttribute(String localName, String value) throws XMLStreamException {
+ if (elementDepth == 0) {
+ try {
+ super.writeStartElement("_");
+ super.writeAttribute(localName, value);
+ super.writeEndElement();
+ receiver.close();
+ XdmNode doc = destination.getXdmNode();
+ itemConsumer.accept(((XdmNode)doc.axisIterator(Axis.CHILD).next()).axisIterator(Axis.ATTRIBUTE).next());
+ } catch (XPathException e) {
+ throw new XMLStreamException(e);
+ }
+ seenStartDocument = false;
+ writer = null;
+ } else
+ super.writeAttribute(localName, value);
+ }
+ @Override
+ public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException {
+ if (elementDepth == 0) {
+ try {
+ super.writeStartElement("_");
+ super.writeAttribute(namespaceURI, localName, value);
+ super.writeEndElement();
+ receiver.close();
+ XdmNode doc = destination.getXdmNode();
+ itemConsumer.accept(((XdmNode)doc.axisIterator(Axis.CHILD).next()).axisIterator(Axis.ATTRIBUTE).next());
+ } catch (XPathException e) {
+ throw new XMLStreamException(e);
+ }
+ seenStartDocument = false;
+ writer = null;
+ } else
+ super.writeAttribute(namespaceURI, localName, value);
+ }
+ @Override
+ public void writeAttribute(String prefix, String namespaceURI, String localName, String value) throws XMLStreamException {
+ if (elementDepth == 0) {
+ try {
+ super.writeStartElement("_");
+ super.writeAttribute(prefix, namespaceURI, localName, value);
+ super.writeEndElement();
+ receiver.close();
+ XdmNode doc = destination.getXdmNode();
+ itemConsumer.accept(((XdmNode)doc.axisIterator(Axis.CHILD).next()).axisIterator(Axis.ATTRIBUTE).next());
+ } catch (XPathException e) {
+ throw new XMLStreamException(e);
+ }
+ seenStartDocument = false;
+ writer = null;
+ } else
+ super.writeAttribute(prefix, namespaceURI, localName, value);
+ }
+ @Override
public void writeEndElement() throws XMLStreamException {
if (elementDepth == 0)
throw new XMLStreamException();
diff --git a/framework/saxon-adapter/src/main/java/org/daisy/common/xpath/saxon/ReflexiveExtensionFunctionProvider.java b/framework/saxon-adapter/src/main/java/org/daisy/common/xpath/saxon/ReflexiveExtensionFunctionProvider.java
index ab4b0ed739..60590e301e 100644
--- a/framework/saxon-adapter/src/main/java/org/daisy/common/xpath/saxon/ReflexiveExtensionFunctionProvider.java
+++ b/framework/saxon-adapter/src/main/java/org/daisy/common/xpath/saxon/ReflexiveExtensionFunctionProvider.java
@@ -55,6 +55,33 @@ public Collection getDefinitions() {
}
protected ReflexiveExtensionFunctionProvider(Class> definition) {
+ this(new Class>[]{definition});
+ }
+
+ /**
+ * @param object When non-null, use this instance of the {@code definition} class to invoke all instance methods
+ * on. In other words, it is used as the implicit first argument of all functions that correspond to
+ * instance methods.
+ */
+ protected ReflexiveExtensionFunctionProvider(Class definition, T object) {
+ this.definitions = new ArrayList<>();
+ addExtensionFunctionDefinitionsFromClass(definition, object);
+ }
+
+ protected ReflexiveExtensionFunctionProvider(Class>... definitions) {
+ this.definitions = new ArrayList<>();
+ for (Class> definition : definitions)
+ addExtensionFunctionDefinitionsFromClass(definition, null);
+ }
+
+ /**
+ * This method can be used to add function definitions if for some reason it can not be done at construction time.
+ */
+ protected void addExtensionFunctionDefinitionsFromClass(Class definition, T object) {
+ definitions.addAll(extensionFunctionDefinitionsFromClass(definition, object));
+ }
+
+ private Collection extensionFunctionDefinitionsFromClass(Class> definition, Object object) {
Map> methods = new HashMap<>();
for (Constructor> constructor : definition.getConstructors()) {
if (Modifier.isPublic(constructor.getModifiers())) {
@@ -85,11 +112,12 @@ protected ReflexiveExtensionFunctionProvider(Class> definition) {
methods.put(method.getName(), list);
}
}
- definitions = new ArrayList<>();
+ Collection definitions = new ArrayList<>();
for (List m : methods.values()) {
Collections.sort(m, (a, b) -> new Integer(a.getParameterCount()).compareTo(b.getParameterCount()));
- definitions.add(extensionFunctionDefinitionFromMethods(m));
+ definitions.add(extensionFunctionDefinitionFromMethods(m, object));
}
+ return definitions;
}
/**
@@ -97,11 +125,11 @@ protected ReflexiveExtensionFunctionProvider(Class> definition) {
* with the same name. Assumed to be sorted by number of parameters (from least
* to most number of parameters).
*/
- private ExtensionFunctionDefinition extensionFunctionDefinitionFromMethods(Collection methods)
+ private ExtensionFunctionDefinition extensionFunctionDefinitionFromMethods(Collection methods, Object object)
throws IllegalArgumentException {
ExtensionFunctionDefinition ret = null;
for (Executable m : methods) {
- ExtensionFunctionDefinition def = extensionFunctionDefinitionFromMethod(m);
+ ExtensionFunctionDefinition def = extensionFunctionDefinitionFromMethod(m, object);
if (ret == null)
ret = def;
else {
@@ -176,7 +204,7 @@ public Sequence call(XPathContext ctxt, Sequence[] args) throws XPathException {
return ret;
}
- private ExtensionFunctionDefinition extensionFunctionDefinitionFromMethod(Executable method)
+ private ExtensionFunctionDefinition extensionFunctionDefinitionFromMethod(Executable method, Object object)
throws IllegalArgumentException {
assert method instanceof Constructor || method instanceof Method;
if (method.isVarArgs())
@@ -215,13 +243,13 @@ private ExtensionFunctionDefinition extensionFunctionDefinitionFromMethod(Execut
}
SequenceType[] argumentTypes = new SequenceType[ // arguments of XPath function
javaArgumentTypes.length
- + (isStatic ? 0 : 1)
+ + ((isStatic || object != null) ? 0 : 1)
- (requiresXMLStreamWriter ? 1 : 0)
- (requiresXPath ? 1 : 0)
- (requiresDocumentBuilder ? 1 : 0)
];
int i = 0;
- if (!isStatic)
+ if (!isStatic && object == null)
argumentTypes[i++] = SequenceType.SINGLE_ITEM; // must be special wrapper item
for (Type t : javaArgumentTypes)
if (!t.equals(XMLStreamWriter.class) &&
@@ -269,12 +297,16 @@ public Sequence call(XPathContext ctxt, Sequence[] args) throws XPathException {
int i = 0;
Object instance = null;
if (!isStatic) {
- Item item = SaxonHelper.getSingleItem(args[i++]);
- try {
- instance = SaxonHelper.objectFromItem(item, declaringClass);
- } catch (IllegalArgumentException e) {
- throw new IllegalArgumentException(
- "Expected ObjectValue<" + declaringClass.getSimpleName() + ">" + ", but got: " + item, e);
+ if (object != null)
+ instance = object;
+ else {
+ Item item = SaxonHelper.getSingleItem(args[i++]);
+ try {
+ instance = SaxonHelper.objectFromItem(item, declaringClass);
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException(
+ "Expected ObjectValue<" + declaringClass.getSimpleName() + ">" + ", but got: " + item, e);
+ }
}
}
List nodeListFromXMLStreamWriter = null;
diff --git a/libs/braille-css/.gitrepo b/libs/braille-css/.gitrepo
index 77db15d9a8..81f0e79c6e 100644
--- a/libs/braille-css/.gitrepo
+++ b/libs/braille-css/.gitrepo
@@ -6,6 +6,6 @@
[subrepo]
remote = git@github.com:daisy/braille-css.git
branch = master
- commit = 5fab2e8bdcec44603eaeb3efc7d7ced0099a2d3c
- parent = 2ce8d3e9986e013a1e53ed47dcd6ae41b7ce9dc1
+ commit = 736f91081e0290d80dc5e7f704fa02f6f1ff2d4e
+ parent = f3eb86656b125cb243df7567702ca9d7bfa64f45
cmdver = 0.3.1
diff --git a/libs/braille-css/README.md b/libs/braille-css/README.md
index 2d5ea2b61f..fe8776a7b0 100644
--- a/libs/braille-css/README.md
+++ b/libs/braille-css/README.md
@@ -1,7 +1,7 @@
-[braille-css][]
-===============
+[braille-css-java][]
+====================
-Implementation of [Braille CSS][braille-css-spec] in Java/ANTLR.
+Implementation of [Braille CSS][braille-css] in Java/ANTLR.
Release procedure
-----------------
@@ -52,5 +52,5 @@ Release procedure
```
-[braille-css-spec]: http://snaekobbi.github.io/braille-css-spec
-[braille-css]: http://snaekobbi.github.io/braille-css
+[braille-css]: http://braillespecs.github.io/braille-css
+[braille-css-java]: http://github.com/daisy/braille-css-java
diff --git a/libs/braille-css/pom.xml b/libs/braille-css/pom.xml
index fa091e39db..c753c7fba2 100644
--- a/libs/braille-css/pom.xml
+++ b/libs/braille-css/pom.xml
@@ -12,7 +12,7 @@
org.daisy.braille
braille-css
- 1.24.1
+ 1.25.0-SNAPSHOT
bundle
braille-css
@@ -21,7 +21,7 @@
scm:git:git@github.com:snaekobbi/braille-css.git
scm:git:git@github.com:snaekobbi/braille-css.git
scm:git:git@github.com:snaekobbi/braille-css.git
- 1.24.1
+ HEAD
@@ -29,7 +29,7 @@
org.daisy.libs
jstyleparser
- 1.20-p21
+ 1.20-p22-SNAPSHOT
com.google.guava
@@ -78,8 +78,8 @@
org.apache.maven.plugins
maven-compiler-plugin
-
- 1.6
+
+ 1.8
diff --git a/libs/braille-css/src/main/antlr3/org/daisy/braille/css/BrailleCSSLexer.g b/libs/braille-css/src/main/antlr3/org/daisy/braille/css/BrailleCSSLexer.g
index 76dfc223a5..8b5fa44cf4 100644
--- a/libs/braille-css/src/main/antlr3/org/daisy/braille/css/BrailleCSSLexer.g
+++ b/libs/braille-css/src/main/antlr3/org/daisy/braille/css/BrailleCSSLexer.g
@@ -15,6 +15,11 @@ import CSSLexer;
emit(t);
return t;
}
+
+ @Override
+ public void displayRecognitionError(String[] tokenNames, RecognitionException e) {
+ gCSSLexer.displayRecognitionError(tokenNames, e);
+ }
}
VOLUME : '@volume' ;
diff --git a/libs/braille-css/src/main/antlr3/org/daisy/braille/css/BrailleCSSParser.g b/libs/braille-css/src/main/antlr3/org/daisy/braille/css/BrailleCSSParser.g
index 41dad93f65..dd61d1e08e 100644
--- a/libs/braille-css/src/main/antlr3/org/daisy/braille/css/BrailleCSSParser.g
+++ b/libs/braille-css/src/main/antlr3/org/daisy/braille/css/BrailleCSSParser.g
@@ -14,6 +14,11 @@ import CSSParser;
public void init() {
gCSSParser.init();
}
+
+ @Override
+ public void displayRecognitionError(String[] tokenNames, RecognitionException e) {
+ gCSSParser.displayRecognitionError(tokenNames, e);
+ }
}
// @Override
@@ -24,7 +29,6 @@ unknown_atrule
| hyphenation_resource_def
| counter_style_def
| vendor_atrule
- | ATKEYWORD S* LCURLY any* RCURLY -> INVALID_ATSTATEMENT
;
volume
@@ -57,14 +61,10 @@ counter_style_def
;
vendor_atrule
- : VENDOR_ATRULE S* LCURLY S* declarations any_atrule* RCURLY
- -> ^(VENDOR_ATRULE declarations ^(SET any_atrule*))
- ;
-
-// not using atstatement because that does not include as much
-any_atrule
- : ATKEYWORD S* LCURLY S* declarations any_atrule* RCURLY S*
- -> ^(ATKEYWORD declarations ^(SET any_atrule*))
+ : VENDOR_ATRULE S* LCURLY S* declarations (vendor_atrule S*)* RCURLY
+ -> ^(VENDOR_ATRULE declarations ^(SET vendor_atrule*))
+ | ATKEYWORD S* LCURLY S* declarations (vendor_atrule S*)* RCURLY
+ -> ^(ATKEYWORD declarations ^(SET vendor_atrule*))
;
// @Override
@@ -173,10 +173,10 @@ inline_hyphenation_resourcestyle
;
inline_vendor_atrulestyle
- : S* declarations any_atrule*
+ : S* declarations (vendor_atrule S*)*
-> ^(INLINESTYLE
^(RULE declarations)
- any_atrule*
+ vendor_atrule*
)
;
diff --git a/libs/braille-css/src/main/antlr3/org/daisy/braille/css/BrailleCSSTreeParser.g b/libs/braille-css/src/main/antlr3/org/daisy/braille/css/BrailleCSSTreeParser.g
index fac568206a..dfd4307c9f 100644
--- a/libs/braille-css/src/main/antlr3/org/daisy/braille/css/BrailleCSSTreeParser.g
+++ b/libs/braille-css/src/main/antlr3/org/daisy/braille/css/BrailleCSSTreeParser.g
@@ -47,18 +47,22 @@ import cz.vutbr.web.csskit.antlr.SimplePreparator;
public List getImportPaths() {
return gCSSTreeParser.getImportPaths();
}
+
+ @Override
+ public void displayRecognitionError(String[] tokenNames, RecognitionException e) {
+ gCSSTreeParser.displayRecognitionError(tokenNames, e);
+ }
}
// @Override
-// Added volume, text_transform_def, hyphenation_resource_def and counter_style_def
+// Added volume, text_transform_def, hyphenation_resource_def, counter_style_def and vendor_atrule
unknown_atrule returns [RuleBlock> stmnt]
@init { $stmnt = null; }
: (v=volume) { $stmnt = v; }
| (tt=text_transform_def) { $stmnt = tt; }
| (hr=hyphenation_resource_def) { $stmnt = hr; }
| (cs=counter_style_def) { $stmnt = cs; }
- | (aar=any_atrule) { $stmnt = aar; }
- | INVALID_ATSTATEMENT { gCSSTreeParser.debug("Skipping invalid at statement"); }
+ | (var=vendor_atrule) { $stmnt = var; }
;
volume returns [RuleVolume stmnt]
@@ -167,23 +171,28 @@ counter_style_def returns [RuleCounterStyle def]
}
;
-any_atrule returns [AnyAtRule stmnt]
+vendor_atrule returns [RuleBlock extends Rule>> stmnt]
@init {
- List childrules = new ArrayList();
+ List>> childrules = new ArrayList>>();
}
- : ^( at=(VENDOR_ATRULE|ATKEYWORD)
+ : ^( at=(VENDOR_ATRULE|UNPREFIXED_VENDOR_ATRULE|ATKEYWORD)
decl=declarations
- ^( SET ( r=any_atrule { childrules.add(r); } )* )
+ ^( SET ( r=vendor_atrule { childrules.add(r); } )* )
) {
String name = at.getText().substring(1);
- AnyAtRule aar = new AnyAtRule(name);
- if (decl != null) aar.addAll(decl);
- if (childrules != null) aar.addAll(childrules);
- if (aar.isEmpty())
- gCSSTreeParser.debug("Empty AnyAtRule was omited");
- else {
- gCSSTreeParser.debug("Create @" + name + " as with:\n{}", aar);
- $stmnt = aar;
+ List> content = new ArrayList>();
+ if (decl != null) content.addAll(decl);
+ if (childrules != null) content.addAll(childrules);
+ try {
+ RuleBlock extends Rule>> var = ((BrailleCSSRuleFactory)gCSSTreeParser.rf).createRuleVendor(name, content);
+ if (var.isEmpty())
+ gCSSTreeParser.debug("Empty @" + name + " rule was omited");
+ else {
+ gCSSTreeParser.debug("Create @" + name + " as with:\n{}", var);
+ $stmnt = var;
+ }
+ } catch (Exception e) {
+ gCSSTreeParser.error(at, e.getMessage());
}
}
;
@@ -476,7 +485,7 @@ inlineblock returns [RuleBlock> b]
| v=volume { $b = v; }
| pm=margin { $b = pm; }
| va=volume_area { $b = va; }
- | aa=any_atrule { $b = aa; }
+ | var=vendor_atrule { $b = var; }
| ^(AMPERSAND
(rr=relative_rule { $b = rr; }
|p=page { $b = new InlineStyle.RuleRelativePage(p); } // relative @page pseudo rule
diff --git a/libs/braille-css/src/main/java/org/daisy/braille/css/BrailleCSSDeclarationTransformer.java b/libs/braille-css/src/main/java/org/daisy/braille/css/BrailleCSSDeclarationTransformer.java
deleted file mode 100644
index 30f8f04a49..0000000000
--- a/libs/braille-css/src/main/java/org/daisy/braille/css/BrailleCSSDeclarationTransformer.java
+++ /dev/null
@@ -1,1062 +0,0 @@
-package org.daisy.braille.css;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Pattern;
-import java.util.Set;
-
-import org.daisy.braille.css.BrailleCSSProperty.AbsoluteMargin;
-import org.daisy.braille.css.BrailleCSSProperty.BorderAlign;
-import org.daisy.braille.css.BrailleCSSProperty.BorderPattern;
-import org.daisy.braille.css.BrailleCSSProperty.BorderStyle;
-import org.daisy.braille.css.BrailleCSSProperty.BorderWidth;
-import org.daisy.braille.css.BrailleCSSProperty.BrailleCharset;
-import org.daisy.braille.css.BrailleCSSProperty.Content;
-import org.daisy.braille.css.BrailleCSSProperty.Display;
-import org.daisy.braille.css.BrailleCSSProperty.Flow;
-import org.daisy.braille.css.BrailleCSSProperty.GenericVendorCSSPropertyProxy;
-import org.daisy.braille.css.BrailleCSSProperty.HyphenateCharacter;
-import org.daisy.braille.css.BrailleCSSProperty.Hyphens;
-import org.daisy.braille.css.BrailleCSSProperty.LetterSpacing;
-import org.daisy.braille.css.BrailleCSSProperty.LineHeight;
-import org.daisy.braille.css.BrailleCSSProperty.ListStyleType;
-import org.daisy.braille.css.BrailleCSSProperty.Margin;
-import org.daisy.braille.css.BrailleCSSProperty.MaxHeight;
-import org.daisy.braille.css.BrailleCSSProperty.MaxLength;
-import org.daisy.braille.css.BrailleCSSProperty.MinLength;
-import org.daisy.braille.css.BrailleCSSProperty.Padding;
-import org.daisy.braille.css.BrailleCSSProperty.Page;
-import org.daisy.braille.css.BrailleCSSProperty.RenderTableBy;
-import org.daisy.braille.css.BrailleCSSProperty.Size;
-import org.daisy.braille.css.BrailleCSSProperty.StringSet;
-import org.daisy.braille.css.BrailleCSSProperty.TableHeaderPolicy;
-import org.daisy.braille.css.BrailleCSSProperty.TextIndent;
-import org.daisy.braille.css.BrailleCSSProperty.TextTransform;
-import org.daisy.braille.css.BrailleCSSProperty.VolumeBreak;
-import org.daisy.braille.css.BrailleCSSProperty.VolumeBreakInside;
-import org.daisy.braille.css.BrailleCSSProperty.WhiteSpace;
-import org.daisy.braille.css.BrailleCSSProperty.WordSpacing;
-
-import cz.vutbr.web.css.CSSProperty;
-import cz.vutbr.web.css.Declaration;
-import cz.vutbr.web.css.SupportedCSS;
-import cz.vutbr.web.css.Term;
-import cz.vutbr.web.css.TermFunction;
-import cz.vutbr.web.css.TermIdent;
-import cz.vutbr.web.css.TermInteger;
-import cz.vutbr.web.css.TermList;
-import cz.vutbr.web.css.TermNumber;
-import cz.vutbr.web.css.TermPair;
-import cz.vutbr.web.css.TermPercent;
-import cz.vutbr.web.css.TermString;
-import cz.vutbr.web.domassign.DeclarationTransformer;
-import cz.vutbr.web.domassign.Repeater;
-import cz.vutbr.web.domassign.Variator;
-
-public class BrailleCSSDeclarationTransformer extends DeclarationTransformer {
-
- public BrailleCSSDeclarationTransformer() {
- this(new SupportedBrailleCSS());
- }
-
- public BrailleCSSDeclarationTransformer(SupportedCSS css) {
- super(css);
- }
-
- protected Map parsingMethods() {
- Map map = new HashMap(css.getTotalProperties(), 1.0f);
- for (String property : css.getDefinedPropertyNames()) {
- try {
- Method m = BrailleCSSDeclarationTransformer.class.getDeclaredMethod(
- camelCase("process-" + property),
- Declaration.class, Map.class, Map.class);
- map.put(property, m);
- } catch (Exception e) {
- try {
- Method m = DeclarationTransformer.class.getDeclaredMethod(
- DeclarationTransformer.camelCase("process-" + property),
- Declaration.class, Map.class, Map.class);
- map.put(property, m);
- } catch (Exception e2) {
- log.debug("Unable to find method for property {}.", property);
- }
- }
- }
- log.debug("Totally found {} parsing methods", map.size());
- return map;
- }
-
- @Override
- public boolean parseDeclaration(Declaration d, Map properties, Map> values) {
- String property = d.getProperty().toLowerCase();
- if (!css.isSupportedCSSProperty(property)) {
- if (property.startsWith("-")) {
- // vendor extension
- if (d.size() == 1) {
- Term> term = d.get(0);
- if (term instanceof TermIdent)
- return genericProperty(GenericVendorCSSPropertyProxy.class, (TermIdent)term,
- true, properties, property);
- else if (term instanceof TermInteger)
- return genericTerm(TermInteger.class, term, d.getProperty(),
- GenericVendorCSSPropertyProxy.valueOf(null), false, properties, values);
- else if (term instanceof TermFunction)
- return genericTerm(TermFunction.class, term, d.getProperty(),
- GenericVendorCSSPropertyProxy.valueOf(null), false, properties, values);
- }
- log.warn("Ignoring unsupported declaration: " + declarationToString(d));
- } else {
- log.debug("Ignoring unsupported property: " + property);
- }
- } else {
- try {
- Method m = methods.get(property);
- if (m != null)
- try {
- return (Boolean)m.invoke(this, d, properties, values);
- } catch (IllegalAccessException e) {
- if (super.parseDeclaration(d, properties, values))
- return true;
- } catch (IllegalArgumentException e) {
- if (super.parseDeclaration(d, properties, values))
- return true;
- } catch (InvocationTargetException e) {
- }
- } catch (Exception e) {
- }
- log.warn("Ignoring unsupported declaration: " + declarationToString(d));
- }
- return false;
- }
-
- private static String declarationToString(Declaration d) {
- StringBuilder b = new StringBuilder();
- b.append(d.getProperty()).append(":");
- for (Term> t : d)
- b.append(" ").append(t);
- b.append(";");
- return b.toString();
- }
-
- /****************************************************************
- * PROCESSING METHODS
- ****************************************************************/
-
- @SuppressWarnings("unused")
- private boolean processBorder(Declaration d,
- Map properties, Map> values) {
- Variator border = new BorderVariator();
- border.assignTermsFromDeclaration(d);
- border.assignDefaults(properties, values);
- return border.vary(properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderAlign(Declaration d,
- Map properties, Map> values) {
- Repeater r = new BorderAlignRepeater();
- return r.repeatOverFourTermDeclaration(d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderBottom(Declaration d,
- Map properties, Map> values) {
- Variator borderSide = new BorderSideVariator("bottom");
- borderSide.assignTermsFromDeclaration(d);
- borderSide.assignDefaults(properties, values);
- return borderSide.vary(properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderBottomAlign(Declaration d,
- Map properties, Map> values) {
- return genericOneIdent(BorderAlign.class, d, properties);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderBottomPattern(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrDotPattern(BorderPattern.class, BorderPattern.dot_pattern,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderBottomStyle(Declaration d,
- Map properties, Map> values) {
- return genericOneIdent(BorderStyle.class, d, properties);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderBottomWidth(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrInteger(BorderWidth.class, BorderWidth.integer, true,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderLeft(Declaration d,
- Map properties, Map> values) {
- Variator borderSide = new BorderSideVariator("left");
- borderSide.assignTermsFromDeclaration(d);
- borderSide.assignDefaults(properties, values);
- return borderSide.vary(properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderLeftAlign(Declaration d,
- Map properties, Map> values) {
- return genericOneIdent(BorderAlign.class, d, properties);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderLeftPattern(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrDotPattern(BorderPattern.class, BorderPattern.dot_pattern,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderLeftStyle(Declaration d,
- Map properties, Map> values) {
- return genericOneIdent(BorderStyle.class, d, properties);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderLeftWidth(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrInteger(BorderWidth.class, BorderWidth.integer, true,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderRight(Declaration d,
- Map properties, Map> values) {
- Variator borderSide = new BorderSideVariator("right");
- borderSide.assignTermsFromDeclaration(d);
- borderSide.assignDefaults(properties, values);
- return borderSide.vary(properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderRightAlign(Declaration d,
- Map properties, Map> values) {
- return genericOneIdent(BorderAlign.class, d, properties);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderRightPattern(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrDotPattern(BorderPattern.class, BorderPattern.dot_pattern,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderRightStyle(Declaration d,
- Map properties, Map> values) {
- return genericOneIdent(BorderStyle.class, d, properties);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderRightWidth(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrInteger(BorderWidth.class, BorderWidth.integer, true,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderStyle(Declaration d,
- Map properties, Map> values) {
- Repeater r = new BorderStyleRepeater();
- return r.repeatOverFourTermDeclaration(d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderTop(Declaration d,
- Map properties, Map> values) {
- Variator borderSide = new BorderSideVariator("top");
- borderSide.assignTermsFromDeclaration(d);
- borderSide.assignDefaults(properties, values);
- return borderSide.vary(properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderTopAlign(Declaration d,
- Map properties, Map> values) {
- return genericOneIdent(BorderAlign.class, d, properties);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderTopPattern(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrDotPattern(BorderPattern.class, BorderPattern.dot_pattern,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderTopStyle(Declaration d,
- Map properties, Map> values) {
- return genericOneIdent(BorderStyle.class, d, properties);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderTopWidth(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrInteger(BorderWidth.class, BorderWidth.integer, true,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processBorderWidth(Declaration d,
- Map properties, Map> values) {
- Repeater r = new BorderWidthRepeater();
- return r.repeatOverFourTermDeclaration(d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processBrailleCharset(Declaration d,
- Map properties, Map> values) {
- return genericOneIdent(BrailleCharset.class, d, properties);
- }
-
- private final static Set validContentFuncNames
- = new HashSet(Arrays.asList("content", "attr", "counter", "counters", "string", "leader", "flow",
- "target-text", "target-string", "target-counter", "target-content"));
-
- private final static Pattern customContentFuncName = Pattern.compile("^-.*"); // is the rest handled in ANTLR?
-
- @SuppressWarnings("unused")
- protected boolean processContent(Declaration d,
- Map properties, Map> values) {
-
- if (d.size() == 1 && genericOneIdent(Content.class, d, properties))
- return true;
-
- TermList list = tf.createList();
- for (Term> t : d.asList()) {
- if (t instanceof TermString)
- list.add(t);
- else if (t instanceof TermFunction) {
- String funcName = ((TermFunction)t).getFunctionName();
- if (validContentFuncNames.contains(funcName.toLowerCase())
- || customContentFuncName.matcher(funcName).matches())
- list.add(t);
- else
- return false; }
- else
- return false;
- }
- if (list.isEmpty())
- return false;
-
- properties.put("content", Content.content_list);
- values.put("content", list);
- return true;
- }
-
- private final static Pattern customDisplayIdent = Pattern.compile("^-.*"); // is the rest handled in ANTLR?
-
- @SuppressWarnings("unused")
- private boolean processDisplay(Declaration d,
- Map properties, Map> values) {
- if (d.size() != 1)
- return false;
- Term t = d.get(0);
- String prop = d.getProperty();
- if (genericTermIdent(Display.class, t, ALLOW_INH, prop, properties))
- return true;
- if (t instanceof TermIdent) {
- if (customDisplayIdent.matcher(((TermIdent)t).getValue()).matches()) {
- properties.put(prop, Display.custom);
- values.put(prop, t);
- return true; }}
- return false;
- }
-
- @SuppressWarnings("unused")
- private boolean processFlow(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrIdentifier(Flow.class, Flow.identifier, true,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processHyphenateCharacter(Declaration d,
- Map properties, Map> values) {
- if (d.size() != 1)
- return false;
- Term> term = d.get(0);
- String prop = d.getProperty();
- if (genericTermIdent(HyphenateCharacter.class, term, ALLOW_INH, prop, properties))
- return true;
- else if (TermString.class.isInstance(term)) {
- properties.put(prop, HyphenateCharacter.braille_string);
- values.put(prop, term);
- return true; }
- return false;
- }
-
- @SuppressWarnings("unused")
- private boolean processHyphens(Declaration d,
- Map properties, Map> values) {
- return genericOneIdent(Hyphens.class, d, properties);
- }
-
- @SuppressWarnings("unused")
- private boolean processLeft(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrInteger(AbsoluteMargin.class, AbsoluteMargin.integer, true,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processLetterSpacing(Declaration d,
- Map properties, Map> values) {
- if (genericOneIdentOrInteger(LetterSpacing.class, LetterSpacing.length, true,
- d, properties, values))
- return true;
- else {
- log.warn("{} not supported, illegal number", d);
- return false; }
- }
-
- @SuppressWarnings("unused")
- private boolean processLineHeight(Declaration d,
- Map properties, Map> values) {
- if (d.size() != 1)
- return false;
- Term> term = d.get(0);
- return genericTermIdent(LineHeight.class, term, ALLOW_INH, d.getProperty(),
- properties)
- || genericTerm(TermInteger.class, term, d.getProperty(), LineHeight.number,
- true, properties, values)
- || genericTerm(TermNumber.class, term, d.getProperty(), LineHeight.number,
- true, properties, values)
- || genericTerm(TermPercent.class, term, d.getProperty(), LineHeight.percentage,
- true, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processListStyle(Declaration d,
- Map properties, Map> values) {
- return processListStyleType(d, properties, values);
- }
-
- private boolean processListStyleType(Declaration d,
- Map properties, Map> values) {
- String propertyName = "list-style-type";
- if (d.size() != 1)
- return false;
- Term> term = d.get(0);
- if (genericTermIdent(ListStyleType.class, term, ALLOW_INH, propertyName, properties))
- return true;
- else
- try {
- if (term instanceof TermIdent) {
- properties.put(propertyName, ListStyleType.counter_style_name);
- values.put(propertyName, term);
- return true; }
- else if (TermString.class.isInstance(term)) {
- properties.put(propertyName, ListStyleType.braille_string);
- values.put(propertyName, term);
- return true; }
- else if (TermFunction.class.isInstance(term)
- && "symbols".equals(((TermFunction)term).getFunctionName().toLowerCase())) {
- properties.put(propertyName, ListStyleType.symbols_fn);
- values.put(propertyName, term);
- return true; }}
- catch (Exception e) {}
- return false;
- }
-
- @SuppressWarnings("unused")
- private boolean processMarginBottom(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrInteger(Margin.class, Margin.integer, true,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processMarginLeft(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrInteger(Margin.class, Margin.integer, false,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processMarginRight(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrInteger(Margin.class, Margin.integer, false,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processMarginTop(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrInteger(Margin.class, Margin.integer, true,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processMargin(Declaration d,
- Map properties, Map> values) {
- Repeater r = new MarginRepeater();
- return r.repeatOverFourTermDeclaration(d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processMaxHeight(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrInteger(MaxHeight.class, MaxHeight.integer, false,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processMaxLength(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrInteger(MaxLength.class, MaxLength.integer, false,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processMinLength(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrInteger(MinLength.class, MinLength.integer, false,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processPaddingBottom(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrInteger(Padding.class, Padding.integer, true,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processPaddingLeft(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrInteger(Padding.class, Padding.integer, true,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processPaddingRight(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrInteger(Padding.class, Padding.integer, true,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processPaddingTop(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrInteger(Padding.class, Padding.integer, true,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processPadding(Declaration d,
- Map properties, Map> values) {
- Repeater r = new PaddingRepeater();
- return r.repeatOverFourTermDeclaration(d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processPage(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrIdentifier(Page.class, Page.identifier, true,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processRenderTableBy(Declaration d,
- Map properties, Map> values) {
-
- if (d.size() == 1 && genericOneIdent(RenderTableBy.class, d, properties))
- return true;
- TermList list = tf.createList();
- for (Term> t : d.asList())
- if (t instanceof TermIdent)
- list.add(t);
- else
- return false;
- if (list.isEmpty())
- return false;
- properties.put("render-table-by", RenderTableBy.axes);
- values.put("render-table-by", list);
- return true;
- }
-
- @SuppressWarnings("unused")
- private boolean processRight(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrInteger(AbsoluteMargin.class, AbsoluteMargin.integer, true,
- d, properties, values);
- }
-
- @SuppressWarnings("unused")
- private boolean processSize(Declaration d,
- Map properties, Map> values) {
- if (d.size() == 1 && genericOneIdent(StringSet.class, d, properties))
- return true;
- TermInteger width = null;
- TermInteger height = null;
- for (Term> t : d.asList()) {
- if (height != null || !(t instanceof TermInteger)) {
- return false;
- } else if (width == null) {
- width = (TermInteger)t;
- } else {
- height = (TermInteger)t;
- }
- }
- if (height == null) {
- return false;
- }
- TermList size = tf.createList(2);
- size.add(width);
- size.add(height);
- properties.put("size", Size.integer_pair);
- values.put("size", size);
- return true;
- }
-
- @SuppressWarnings("unused")
- protected boolean processStringSet(Declaration d,
- Map properties, Map> values) {
-
- if (d.size() == 1 && genericOneIdent(StringSet.class, d, properties))
- return true;
-
- final Set validFuncNames = new HashSet(Arrays.asList("content", "attr"));
-
- TermList list = tf.createList();
- TermList contentList = tf.createList();
- String stringName = null;
- boolean first = true;
- for (Term> t : d.asList()) {
- if (stringName == null) {
- if (t instanceof TermIdent)
- stringName = ((TermIdent)t).getValue();
- else
- return false;
- } else if (t instanceof TermIdent) {
- if (contentList.isEmpty())
- return false;
- TermPair pair = tf.createPair(stringName, contentList);
- if (!first) pair.setOperator(Term.Operator.COMMA);
- list.add(pair);
- stringName = ((TermIdent)t).getValue();
- contentList = tf.createList();
- first = false;
- } else if (t instanceof TermString)
- contentList.add(t);
- else if (t instanceof TermFunction
- && validFuncNames.contains(((TermFunction)t).getFunctionName().toLowerCase()))
- contentList.add(t);
- else
- return false;
- }
-
- if (contentList.isEmpty())
- return false;
- TermPair pair = tf.createPair(stringName, contentList);
- if (!first) pair.setOperator(Term.Operator.COMMA);
- list.add(pair);
-
- properties.put("string-set", StringSet.list_values);
- values.put("string-set", list);
-
- return true;
- }
-
- @SuppressWarnings("unused")
- private boolean processTableHeaderPolicy(Declaration d,
- Map properties, Map> values) {
- return genericOneIdent(TableHeaderPolicy.class, d, properties);
- }
-
- @SuppressWarnings("unused")
- private boolean processTextIndent(Declaration d,
- Map properties, Map> values) {
- return genericOneIdentOrInteger(TextIndent.class, TextIndent.integer, false,
- d, properties, values);
- }
-
- protected boolean processTextTransform(Declaration d,
- Map properties, Map> values) {
-
- if (d.size() == 1 && genericOneIdent(TextTransform.class, d, properties))
- return true;
-
- TermList list = tf.createList();
- for (Term> t : d.asList()) {
- if (t instanceof TermIdent) {
- String value = ((TermIdent)t).getValue().toLowerCase();
- if (!value.equals("auto"))
- list.add(t);
- }
- else
- return false;
- }
-
- if (list.isEmpty())
- return false;
-
- properties.put("text-transform", TextTransform.list_values);
- values.put("text-transform", list);
- return true;
- }
-
- @SuppressWarnings("unused")
- private boolean processVolumeBreakAfter(Declaration d,
- Map properties, Map> values) {
- return genericOneIdent(VolumeBreak.class, d, properties);
- }
-
- @SuppressWarnings("unused")
- private boolean processVolumeBreakBefore(Declaration d,
- Map properties, Map> values) {
- return genericOneIdent(VolumeBreak.class, d, properties);
- }
-
- @SuppressWarnings("unused")
- private boolean processVolumeBreakInside(Declaration d,
- Map properties, Map> values) {
- if (genericOneIdent(VolumeBreakInside.class, d, properties))
- return true;
- if (d.size() == 1) {
- Term> term = d.get(0);
- if (term instanceof TermFunction) {
- TermFunction fun = (TermFunction)term;
- if ("-obfl-keep".equals(fun.getFunctionName())
- && fun.size() == 1
- && fun.get(0) instanceof TermInteger) {
- properties.put("volume-break-inside", VolumeBreakInside.obfl_keep);
- values.put("volume-break-inside", fun);
- return true;
- }
- }
- }
- return false;
- }
-
- @SuppressWarnings("unused")
- private boolean processWhiteSpace(Declaration d,
- Map properties, Map> values) {
- return genericOneIdent(WhiteSpace.class, d, properties);
- }
-
- @SuppressWarnings("unused")
- private boolean processWordSpacing(Declaration d,
- Map properties, Map> values) {
- if (genericOneIdentOrInteger(WordSpacing.class, WordSpacing.length, true,
- d, properties, values))
- return true;
- else {
- log.warn("{} not supported, illegal number", d);
- return false; }
- }
-
- /****************************************************************
- * GENERIC METHODS
- ****************************************************************/
-
- private boolean genericOneIdentOrDotPattern(
- Class type, T dotPatternIdentification, Declaration d,
- Map properties, Map> values) {
-
- if (d.size() != 1)
- return false;
-
- Term> term = d.get(0);
-
- if (genericTermIdent(type, term, ALLOW_INH, d.getProperty(),
- properties))
- return true;
-
- try {
- if (TermIdent.class.isInstance(term)) {
- String propertyName = d.getProperty();
- TermDotPattern value = TermDotPattern.createDotPattern((TermIdent)term);
- properties.put(propertyName, dotPatternIdentification);
- values.put(propertyName, value);
- return true;
- }
- } catch (Exception e) {
- }
- return false;
- }
-
- private boolean genericOneIdentOrIdentifier(
- Class type, T identifierIdentification, boolean sanify,
- Declaration d, Map properties,
- Map> values) {
-
- if (d.size() != 1)
- return false;
-
- return genericTermIdent(type, d.get(0), ALLOW_INH, d.getProperty(),
- properties)
- || genericTerm(TermIdent.class, d.get(0), d.getProperty(),
- identifierIdentification, sanify, properties, values);
- }
-
-
- /****************************************************************
- * REPEATER CLASSES
- ****************************************************************/
-
- private final class MarginRepeater extends Repeater {
-
- public MarginRepeater() {
- super(4, css);
- type = Margin.class;
- names.add("margin-top");
- names.add("margin-right");
- names.add("margin-bottom");
- names.add("margin-left");
- }
-
- protected boolean operation(int i,
- Map properties,
- Map> values) {
- return genericTermIdent(type, terms.get(i), AVOID_INH, names.get(i), properties)
- || genericTerm(TermInteger.class, terms.get(i), names.get(i),
- Margin.integer, false, properties, values);
- }
- }
-
- private final class PaddingRepeater extends Repeater {
-
- public PaddingRepeater() {
- super(4, css);
- type = Padding.class;
- names.add("padding-top");
- names.add("padding-right");
- names.add("padding-bottom");
- names.add("padding-left");
- }
-
- protected boolean operation(int i,
- Map properties,
- Map> values) {
- return genericTermIdent(type, terms.get(i), AVOID_INH, names.get(i), properties)
- || genericTerm(TermInteger.class, terms.get(i), names.get(i),
- Padding.integer, false, properties, values);
- }
- }
-
- private final class BorderStyleRepeater extends Repeater {
-
- public BorderStyleRepeater() {
- super(4, css);
- this.type = BorderStyle.class;
- names.add("border-top-style");
- names.add("border-right-style");
- names.add("border-bottom-style");
- names.add("border-left-style");
- }
-
- @Override
- protected boolean operation(int i, Map properties,
- Map> values) {
- return genericTermIdent(BorderStyle.class, terms.get(i), ALLOW_INH, names.get(i),
- properties);
- }
- }
-
- private final class BorderAlignRepeater extends Repeater {
-
- public BorderAlignRepeater() {
- super(4, css);
- this.type = BorderAlign.class;
- names.add("border-top-align");
- names.add("border-right-align");
- names.add("border-bottom-align");
- names.add("border-left-align");
- }
-
- @Override
- protected boolean operation(int i, Map properties,
- Map> values) {
- return genericTermIdent(BorderAlign.class, terms.get(i), ALLOW_INH, names.get(i),
- properties);
- }
- }
-
- private final class BorderWidthRepeater extends Repeater {
-
- public BorderWidthRepeater() {
- super(4, css);
- this.type = BorderWidth.class;
- names.add("border-top-width");
- names.add("border-right-width");
- names.add("border-bottom-width");
- names.add("border-left-width");
- }
-
- @Override
- protected boolean operation(int i, Map properties,
- Map> values) {
- return genericTermIdent(type, terms.get(i), ALLOW_INH, names.get(i), properties)
- || genericTerm(TermInteger.class, terms.get(i), names.get(i), BorderWidth.integer,
- false, properties, values);
- }
- }
-
-
- /****************************************************************
- * VARIATOR CLASSES
- ****************************************************************/
-
- private final class BorderVariator extends Variator {
-
- public static final int WIDTH = 0;
- public static final int STYLE = 1;
- public static final int ALIGN = 2;
-
- private List repeaters;
-
- public BorderVariator() {
- super(3, css);
- types.add(BorderWidth.class);
- types.add(BorderStyle.class);
- types.add(BorderAlign.class);
- repeaters = new ArrayList(variants);
- repeaters.add(new BorderWidthRepeater());
- repeaters.add(new BorderStyleRepeater());
- repeaters.add(new BorderAlignRepeater());
- }
-
- @Override
- protected boolean variant(int variant, IntegerRef iteration,
- Map properties, Map> values) {
- int i = iteration.get();
- Term> term = terms.get(i);
- Repeater r;
- switch (variant) {
- case WIDTH:
- case STYLE:
- case ALIGN:
- r = repeaters.get(variant);
- r.assignTerms(term, term, term, term);
- return r.repeat(properties, values);
- default:
- return false;
- }
- }
-
- @Override
- protected boolean checkInherit(int variant, Term> term, Map