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.6 + 1.8 + 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> 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> 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 properties) { - if (!(term instanceof TermIdent) - || !CSSProperty.INHERIT_KEYWORD.equalsIgnoreCase(((TermIdent) term).getValue())) { - return false; - } - if (variant == ALL_VARIANTS) { - for (int i = 0; i < variants; i++) { - Repeater r = repeaters.get(i); - r.assignTerms(term, term, term, term); - r.repeat(properties, null); - } - return true; - } - Repeater r = repeaters.get(variant); - r.assignTerms(term, term, term, term); - r.repeat(properties, null); - return true; - } - - @Override - public void assignDefaults(Map properties, Map> values) { - for (Repeater r : repeaters) - r.assignDefaults(properties, values); - } - } - - private final class BorderSideVariator extends Variator { - - public static final int ALIGN = 0; - public static final int STYLE = 1; - public static final int WIDTH = 2; - - private final String borderPatternName; - - public BorderSideVariator(String side) { - super(3, css); - names.add("border-" + side + "-align"); - types.add(BorderAlign.class); - names.add("border-" + side + "-style"); - types.add(BorderStyle.class); - names.add("border-" + side + "-width"); - types.add(BorderWidth.class); - borderPatternName = "border-" + side + "-pattern"; - } - - @Override - public void assignDefaults(Map properties, Map> values) { - super.assignDefaults(properties, values); - assignDefault(borderPatternName, properties, values); - } - - @Override - protected boolean variant(int variant, IntegerRef iteration, - Map properties, Map> values) { - int i = iteration.get(); - switch (variant) { - case WIDTH: - return genericTermIdent(types.get(variant), terms.get(i), AVOID_INH, names.get(variant), - properties) - || genericTerm(TermInteger.class, terms.get(i), names.get(variant), BorderWidth.integer, - false, properties, values); - case ALIGN: - case STYLE: - return genericTermIdent(types.get(variant), terms.get(i), AVOID_INH, names.get(variant), - properties); - default: - return false; - } - } - - private boolean patternVariant(Map properties, Map> values) { - if (terms.size() != 1) - return false; - Term t = terms.get(0); - if (genericTermIdent(BorderPattern.class, t, ALLOW_INH, borderPatternName, properties)) - return true; - try { - if (TermIdent.class.isInstance(t)) { - TermDotPattern value = TermDotPattern.createDotPattern((TermIdent)t); - properties.put(borderPatternName, BorderPattern.dot_pattern); - values.put(borderPatternName, value); - return true; - } - } catch (Exception e) { - } - return false; - } - - @Override - public boolean vary(Map properties, Map> values) { - boolean rv = false; - if (super.vary(properties, values)) - rv = true; - if (patternVariant(properties, values)) - rv = true; - return rv; - } - } - - private final void assignDefault(String propertyName, Map properties, Map> values) { - CSSProperty dp = css.getDefaultProperty(propertyName); - if (dp != null) - properties.put(propertyName, dp); - Term dv = css.getDefaultValue(propertyName); - if (dv != null) - values.put(propertyName, dv); - } -} diff --git a/libs/braille-css/src/main/java/org/daisy/braille/css/BrailleCSSExtension.java b/libs/braille-css/src/main/java/org/daisy/braille/css/BrailleCSSExtension.java new file mode 100644 index 0000000000..296e9179ab --- /dev/null +++ b/libs/braille-css/src/main/java/org/daisy/braille/css/BrailleCSSExtension.java @@ -0,0 +1,129 @@ +package org.daisy.braille.css; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import cz.vutbr.web.css.CSSProperty; +import cz.vutbr.web.css.Declaration; +import cz.vutbr.web.css.Rule; +import cz.vutbr.web.css.RuleBlock; +import cz.vutbr.web.css.Selector.PseudoClass; +import cz.vutbr.web.css.Selector.PseudoElement; +import cz.vutbr.web.css.SupportedCSS; +import cz.vutbr.web.css.Term; +import cz.vutbr.web.css.TermIdent; +import cz.vutbr.web.css.TermList; +import cz.vutbr.web.csskit.OutputUtil; +import cz.vutbr.web.domassign.DeclarationTransformer; + +public abstract class BrailleCSSExtension extends DeclarationTransformer implements SupportedCSS { + + protected BrailleCSSExtension(SupportedCSS supportedProperties) { + super(supportedProperties); + } + + /** + * Must not be {@code null} and must start and end with a '{@code -}'. + * + * The {@link DeclarationTransformer} and {@link SupportedCSS} methods expect that property names + * are always prefixed with this prefix. In addition, {@link #parseDeclaration} may also support + * non-prefixed properties and values, but the {@link SupportedCSS} methods shouldn't. + */ + public abstract String getPrefix(); + + @Override + protected final Map parsingMethods() { + return null; + } + + @Override + public boolean parseDeclaration(Declaration d, Map properties, Map> values) { + return false; + } + + public boolean parseContentTerm(Term term, TermList list) { + return false; + } + + public TermIdent parseCounterName(Term term) { + throw new IllegalArgumentException("Unknown counter name " + term); + } + + public TermIdent parseTextTransform(Term term) { + throw new IllegalArgumentException("Unknown text-transform value " + term); + } + + public PseudoClass createPseudoClass(String name) { + throw new IllegalArgumentException("Unknown pseudo-class :" + name); + } + + public PseudoClass createPseudoClassFunction(String name, String... args) { + StringBuilder msg = new StringBuilder(); + msg.append(":").append(name).append("("); + if (args.length > 0) + OutputUtil.appendList(msg, Arrays.asList(args), ", "); + msg.append(")"); + throw new IllegalArgumentException("Unknown pseudo-class " + msg.toString()); + } + + public PseudoElement createPseudoElement(String name) { + throw new IllegalArgumentException("Unknown pseudo-element ::" + name); + } + + public PseudoElement createPseudoElementFunction(String name, String... args) { + StringBuilder msg = new StringBuilder(); + msg.append("::").append(name).append("("); + if (args.length > 0) + OutputUtil.appendList(msg, Arrays.asList(args), ", "); + msg.append(")"); + throw new IllegalArgumentException("Unknown pseudo-element " + msg.toString()); + } + + public VendorAtRule> createAtRule(String name, List> content) { + throw new IllegalArgumentException("Unknown at-rule @" + name); + } + + /////////////////////////////////////////////////////////////// + // SupportedCSS + /////////////////////////////////////////////////////////////// + + @Override + public boolean isSupportedMedia(String media) { + return css.isSupportedMedia(media); + } + @Override + public final boolean isSupportedCSSProperty(String property) { + return css.isSupportedCSSProperty(property); + } + @Override + public final CSSProperty getDefaultProperty(String property) { + return css.getDefaultProperty(property); + } + @Override + public final Term getDefaultValue(String property) { + return css.getDefaultValue(property); + } + @Override + public final int getTotalProperties() { + return css.getTotalProperties(); + } + @Override + public final Set getDefinedPropertyNames() { + return css.getDefinedPropertyNames(); + } + @Override + public String getRandomPropertyName() { + return css.getRandomPropertyName(); + } + @Override + public int getOrdinal(String propertyName) { + return css.getOrdinal(propertyName); + } + @Override + public String getPropertyName(int o) { + return css.getPropertyName(o); + } +} diff --git a/libs/braille-css/src/main/java/org/daisy/braille/css/BrailleCSSParserFactory.java b/libs/braille-css/src/main/java/org/daisy/braille/css/BrailleCSSParserFactory.java index deae6fbf24..208802c228 100644 --- a/libs/braille-css/src/main/java/org/daisy/braille/css/BrailleCSSParserFactory.java +++ b/libs/braille-css/src/main/java/org/daisy/braille/css/BrailleCSSParserFactory.java @@ -14,12 +14,14 @@ import cz.vutbr.web.css.RuleFactory; import cz.vutbr.web.css.RuleList; import cz.vutbr.web.css.RuleMedia; +import cz.vutbr.web.css.SourceLocator; import cz.vutbr.web.css.StyleSheet; import cz.vutbr.web.csskit.antlr.CSSInputStream; import cz.vutbr.web.csskit.antlr.CSSParserFactory; import cz.vutbr.web.csskit.antlr.CSSSource; import cz.vutbr.web.csskit.antlr.CSSSource.SourceType; import cz.vutbr.web.csskit.antlr.CSSSourceReader; +import cz.vutbr.web.csskit.antlr.DefaultCSSSourceReader; import cz.vutbr.web.csskit.antlr.TreeUtil; import org.antlr.runtime.CommonTokenStream; @@ -34,7 +36,16 @@ public class BrailleCSSParserFactory extends CSSParserFactory { - private static final BrailleCSSRuleFactory ruleFactory = new BrailleCSSRuleFactory(); + private static final BrailleCSSRuleFactory defaultRuleFactory = new BrailleCSSRuleFactory(); + private final BrailleCSSRuleFactory ruleFactory; + + public BrailleCSSParserFactory() { + this(defaultRuleFactory); + } + + public BrailleCSSParserFactory(BrailleCSSRuleFactory ruleFactory) { + this.ruleFactory = ruleFactory; + } @Override public StyleSheet parse(CSSSource source, CSSSourceReader cssReader, boolean inlinePriority) @@ -135,8 +146,14 @@ public enum Context { } public RuleList parseInlineStyle(String style, Context context) { + return parseInlineStyle(style, context, null); + } + + private static final CSSSourceReader defaultSourceReader = new DefaultCSSSourceReader(); + + public RuleList parseInlineStyle(String style, Context context, SourceLocator location) { try { - CSSInputStream input = CSSInputStream.newInstance(style, null); + CSSInputStream input = CSSInputStream.newInstance(defaultSourceReader.read(new CSSSource(style, "text/css", location))); CommonTokenStream tokens = feedLexer(input); CommonTree ast = feedParser(tokens, SourceType.INLINE, context); // OK to pass null for context element because it is only used in Analyzer.evaluateDOM() @@ -210,8 +227,8 @@ public Declaration parseDeclaration(String declaration) { return null; } } - private static BrailleCSSTreeParser createTreeParser(CSSSource source, CSSSourceReader cssReader, - Preparator preparator) + private BrailleCSSTreeParser createTreeParser(CSSSource source, CSSSourceReader cssReader, + Preparator preparator) throws IOException, CSSException { CSSInputStream input = getInput(source, cssReader); CommonTokenStream tokens = feedLexer(input); @@ -238,8 +255,8 @@ private static CommonTree feedParser(CommonTokenStream source, SourceType type, return getAST(parser, type, context); } - private static BrailleCSSTreeParser feedAST(CommonTokenStream source, CommonTree ast, - Preparator preparator, Map namespaces) { + private BrailleCSSTreeParser feedAST(CommonTokenStream source, CommonTree ast, + Preparator preparator, Map namespaces) { if (log.isTraceEnabled()) log.trace("Feeding tree parser with AST:\n{}", TreeUtil.toStringTree(ast)); CommonTreeNodeStream nodes = new CommonTreeNodeStream(ast); diff --git a/libs/braille-css/src/main/java/org/daisy/braille/css/BrailleCSSProperty.java b/libs/braille-css/src/main/java/org/daisy/braille/css/BrailleCSSProperty.java index 87430cfa83..29b856bcfb 100644 --- a/libs/braille-css/src/main/java/org/daisy/braille/css/BrailleCSSProperty.java +++ b/libs/braille-css/src/main/java/org/daisy/braille/css/BrailleCSSProperty.java @@ -9,7 +9,7 @@ public interface BrailleCSSProperty extends CSSProperty { ************************************************************************/ public enum AbsoluteMargin implements BrailleCSSProperty { - integer(""), INHERIT("inherit"), INITIAL("initial"); + integer(""), AUTO("auto"), INHERIT("inherit"), INITIAL("initial"); private String text; @@ -18,7 +18,7 @@ private AbsoluteMargin(String text) { } public boolean inherited() { - return true; + return false; } public boolean equalsInherit() { @@ -502,6 +502,33 @@ public String toString() { } } + public enum Orphans implements BrailleCSSProperty { + integer(""), INHERIT("inherit"), INITIAL("initial"); + + private String text; + + private Orphans(String text) { + this.text = text; + } + + public boolean inherited() { + return false; + } + + public boolean equalsInherit() { + return this == INHERIT; + } + + public boolean equalsInitial() { + return this == INITIAL; + } + + @Override + public String toString() { + return text; + } + } + public enum Padding implements BrailleCSSProperty { integer(""), component_values(""), INHERIT("inherit"), INITIAL("initial"); @@ -750,7 +777,7 @@ public String toString() { } public enum VolumeBreakInside implements BrailleCSSProperty { - AUTO("auto"), AVOID("avoid"), obfl_keep(""), INHERIT("inherit"), + AUTO("auto"), AVOID("avoid"), custom(""), INHERIT("inherit"), INITIAL("initial"); private String text; @@ -804,7 +831,34 @@ public String toString() { return text; } } - + + public enum Widows implements BrailleCSSProperty { + integer(""), INHERIT("inherit"), INITIAL("initial"); + + private String text; + + private Widows(String text) { + this.text = text; + } + + public boolean inherited() { + return false; + } + + public boolean equalsInherit() { + return this == INHERIT; + } + + public boolean equalsInitial() { + return this == INITIAL; + } + + @Override + public String toString() { + return text; + } + } + public enum WordSpacing implements BrailleCSSProperty { length(""), INHERIT("inherit"), INITIAL("initial"); diff --git a/libs/braille-css/src/main/java/org/daisy/braille/css/BrailleCSSRuleFactory.java b/libs/braille-css/src/main/java/org/daisy/braille/css/BrailleCSSRuleFactory.java index 3d5e5d7d6f..fbbfba70eb 100644 --- a/libs/braille-css/src/main/java/org/daisy/braille/css/BrailleCSSRuleFactory.java +++ b/libs/braille-css/src/main/java/org/daisy/braille/css/BrailleCSSRuleFactory.java @@ -1,16 +1,42 @@ package org.daisy.braille.css; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + import cz.vutbr.web.css.CombinedSelector; +import cz.vutbr.web.css.Rule; +import cz.vutbr.web.css.RuleBlock; import cz.vutbr.web.css.RuleMargin; import cz.vutbr.web.css.Selector; import cz.vutbr.web.css.Selector.PseudoClass; import cz.vutbr.web.css.Selector.PseudoElement; import cz.vutbr.web.csskit.RuleFactoryImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class BrailleCSSRuleFactory extends RuleFactoryImpl { - public BrailleCSSRuleFactory() {} - + public BrailleCSSRuleFactory() { + this(Collections.emptyList(), true); + } + + private final List extensions; + private final boolean allowUnknownVendorExtensions; + + public BrailleCSSRuleFactory(Collection extensions, + boolean allowUnknownVendorExtensions) { + this.extensions = new ArrayList<>(); + for (BrailleCSSExtension x : extensions) + if (x.getPrefix() == null || !x.getPrefix().matches("-.+-")) + log.warn("CSS extension without prefix ignored: " + x); + else + this.extensions.add(x); + this.allowUnknownVendorExtensions = allowUnknownVendorExtensions; + } + @Override public RuleMargin createMargin(String area) { return new RuleMarginImpl(area); @@ -27,22 +53,126 @@ public CombinedSelector createCombinedSelector() { } @Override - public PseudoClass createPseudoClass(String name) { - return new SelectorImpl.PseudoClassImpl(name); + public PseudoClass createPseudoClass(String name) throws IllegalArgumentException { + if (name.startsWith("-")) { + for (BrailleCSSExtension x : extensions) + if (name.startsWith(x.getPrefix())) + return x.createPseudoClass(name); + if (!allowUnknownVendorExtensions) + throw new IllegalArgumentException(name + " is not a valid pseudo-class name"); + } + try { + return new SelectorImpl.PseudoClassImpl(name); + } catch (IllegalArgumentException e) { + if (!name.startsWith("-")) + // might be an unprefixed extension pseudo-class (which the extension's parser may or may not support) + for (BrailleCSSExtension x : extensions) + try { + return x.createPseudoClass(name); + } catch (IllegalArgumentException ee) { + // ignore + } + throw e; + } } @Override - public PseudoClass createPseudoClassFunction(String name, String... args) { - return new SelectorImpl.PseudoClassImpl(name, args); + public PseudoClass createPseudoClassFunction(String name, String... args) throws IllegalArgumentException { + if (name.startsWith("-")) { + for (BrailleCSSExtension x : extensions) + if (name.startsWith(x.getPrefix())) + return x.createPseudoClassFunction(name, args); + if (!allowUnknownVendorExtensions) + throw new IllegalArgumentException(name + " is not a valid pseudo-class name"); + } + try { + return new SelectorImpl.PseudoClassImpl(name, args); + } catch (IllegalArgumentException e) { + if (!name.startsWith("-")) + // might be an unprefixed extension pseudo-class (which the extension's parser may or may not support) + for (BrailleCSSExtension x : extensions) + try { + return x.createPseudoClassFunction(name, args); + } catch (IllegalArgumentException ee) { + // ignore + } + throw e; + } } @Override - public PseudoElement createPseudoElement(String name) { - return new SelectorImpl.PseudoElementImpl(name); + public PseudoElement createPseudoElement(String name) throws IllegalArgumentException { + String n = name; + if (n.startsWith(":")) + n = n.substring(1); + if (n.startsWith("-")) { + for (BrailleCSSExtension x : extensions) + if (n.startsWith(x.getPrefix())) + return x.createPseudoElement(name); + if (!allowUnknownVendorExtensions) + throw new IllegalArgumentException(n + " is not a valid pseudo-element name"); + } + try { + return new SelectorImpl.PseudoElementImpl(name); + } catch (IllegalArgumentException e) { + if (!n.startsWith("-")) + // might be an unprefixed extension pseudo-element (which the extension's parser may or may not support) + for (BrailleCSSExtension x : extensions) + try { + return x.createPseudoElement(name); + } catch (IllegalArgumentException ee) { + // ignore + } + throw e; + } } @Override - public PseudoElement createPseudoElementFunction(String name, String... args) { - return new SelectorImpl.PseudoElementImpl(name, args); + public PseudoElement createPseudoElementFunction(String name, String... args) throws IllegalArgumentException { + String n = name; + if (n.startsWith(":")) + n = n.substring(1); + if (n.startsWith("-")) { + for (BrailleCSSExtension x : extensions) + if (n.startsWith(x.getPrefix())) + return x.createPseudoElementFunction(name, args); + if (!allowUnknownVendorExtensions) + throw new IllegalArgumentException(n + " is not a valid pseudo-element name"); + } + try { + return new SelectorImpl.PseudoElementImpl(name, args); + } catch (IllegalArgumentException e) { + if (!n.startsWith("-")) + // might be an unprefixed extension pseudo-element (which the extension's parser may or may not support) + for (BrailleCSSExtension x : extensions) + try { + return x.createPseudoElementFunction(name, args); + } catch (IllegalArgumentException ee) { + // ignore + } + throw e; + } } + + public VendorAtRule> createRuleVendor(String name, List> content) + throws IllegalArgumentException { + if (name.startsWith("-")) { + for (BrailleCSSExtension x : extensions) + if (name.startsWith(x.getPrefix())) + return x.createAtRule(name, content); + if (!allowUnknownVendorExtensions) + throw new IllegalArgumentException(name + " is not a valid at-rule name"); + } + if (!name.startsWith("-")) + // might be an unprefixed extension pseudo-element (which the extension's parser may or may not support) + for (BrailleCSSExtension x : extensions) + try { + return x.createAtRule(name, content); + } catch (IllegalArgumentException ee) { + // ignore + } + return new VendorAtRule>(name, content); + } + + private static final Logger log = LoggerFactory.getLogger(BrailleCSSRuleFactory.class); } diff --git a/libs/braille-css/src/main/java/org/daisy/braille/css/InlineStyle.java b/libs/braille-css/src/main/java/org/daisy/braille/css/InlineStyle.java index e1b9bf029f..62ffff869f 100644 --- a/libs/braille-css/src/main/java/org/daisy/braille/css/InlineStyle.java +++ b/libs/braille-css/src/main/java/org/daisy/braille/css/InlineStyle.java @@ -12,12 +12,11 @@ import cz.vutbr.web.css.Declaration; import cz.vutbr.web.css.Rule; import cz.vutbr.web.css.RuleBlock; -import cz.vutbr.web.css.RuleFactory; -import cz.vutbr.web.css.RuleMargin; import cz.vutbr.web.css.RulePage; import cz.vutbr.web.css.RuleSet; import cz.vutbr.web.css.Selector; import cz.vutbr.web.css.Selector.SelectorPart; +import cz.vutbr.web.css.SourceLocator; import cz.vutbr.web.css.StyleSheet; import cz.vutbr.web.csskit.AbstractRuleBlock; @@ -29,11 +28,12 @@ */ public class InlineStyle implements Cloneable, Iterable> { - private final static BrailleCSSParserFactory parserFactory = new BrailleCSSParserFactory(); + private static final BrailleCSSParserFactory defaultParserFactory; private static final SelectorPart dummyElementSelectorPart; static { - RuleFactory ruleFactory = new BrailleCSSRuleFactory(); - dummyElementSelectorPart = ruleFactory.createElementDOM(null, true); + BrailleCSSRuleFactory defaultRuleFactory = new BrailleCSSRuleFactory(); + defaultParserFactory = new BrailleCSSParserFactory(defaultRuleFactory); + dummyElementSelectorPart = defaultRuleFactory.createElementDOM(null, true); } private final static RuleMainBlock emptyBlock = new RuleMainBlock(); @@ -46,9 +46,20 @@ public InlineStyle(String style) { } public InlineStyle(String style, BrailleCSSParserFactory.Context context) { + this(style, context, null); + } + + // FIXME: could also pass SourceMap instead of SourceLocator + // => source map can also be included in serialized form within special comment in CSS and parsed + public InlineStyle(String style, BrailleCSSParserFactory.Context context, SourceLocator location) { + this(style, context, location, defaultParserFactory); + } + + public InlineStyle(String style, BrailleCSSParserFactory.Context context, SourceLocator location, + BrailleCSSParserFactory parserFactory) { nestedStyles = new ArrayList>(); List mainDeclarations = new ArrayList(); - for (RuleBlock block : parserFactory.parseInlineStyle(style, context)) { + for (RuleBlock block : parserFactory.parseInlineStyle(style, context, location)) { if (block == null) {} else if (block instanceof RuleSet) { RuleSet set = (RuleSet)block; @@ -69,22 +80,8 @@ else if (block instanceof RuleSet) { relativeSelector.addAll(combinedSelector.subList(1, combinedSelector.size())); nestedStyles.add(new RuleRelativeBlock(relativeSelector, set)); } - } else if (block instanceof RuleTextTransform - || block instanceof RuleHyphenationResource - || block instanceof RuleCounterStyle - || block instanceof RulePage - || block instanceof RuleVolume - || block instanceof RuleMargin - || block instanceof RuleVolumeArea - || block instanceof RuleRelativeBlock - || block instanceof RuleRelativePage - || block instanceof RuleRelativeVolume - || block instanceof RuleRelativeHyphenationResource - || block instanceof AnyAtRule - ) { - nestedStyles.add(block); } else { - throw new RuntimeException("coding error"); + nestedStyles.add(block); } } mainStyle = new Optional(new RuleMainBlock(mainDeclarations)); diff --git a/libs/braille-css/src/main/java/org/daisy/braille/css/PropertyValue.java b/libs/braille-css/src/main/java/org/daisy/braille/css/PropertyValue.java index 074a95a958..cb97c8192a 100644 --- a/libs/braille-css/src/main/java/org/daisy/braille/css/PropertyValue.java +++ b/libs/braille-css/src/main/java/org/daisy/braille/css/PropertyValue.java @@ -1,29 +1,211 @@ package org.daisy.braille.css; +import java.util.AbstractList; import java.util.HashMap; +import java.util.List; import java.util.Map; import cz.vutbr.web.css.CSSProperty; import cz.vutbr.web.css.Declaration; +import cz.vutbr.web.css.Rule; +import cz.vutbr.web.css.SourceLocator; import cz.vutbr.web.css.Term; +import cz.vutbr.web.css.TermIdent; +import cz.vutbr.web.csskit.TermIdentImpl; +import cz.vutbr.web.domassign.SingleMapNodeData.Quadruple; -public class PropertyValue implements Cloneable { +public class PropertyValue extends AbstractList> implements Cloneable, Declaration { - private CSSProperty property; - private Term value; + private final String propertyName; + protected Quadruple propertyValue; // not final for clone() + SupportedBrailleCSS css; // not final for clone() + private CSSProperty property; // not final for clone() + private Term value; // not final for clone() + private Term declaration; // not final for clone() + private Declaration sourceDeclaration; // not final for clone() - PropertyValue(CSSProperty property, Term value) { - this.property = property; - this.value = value; + public PropertyValue(String propertyName, + final CSSProperty property, + final Term value, + final Declaration sourceDeclaration, + SupportedBrailleCSS css) { + this(propertyName, new Quadruple(css, propertyName) {{ + curProp = property; + curValue = value; + curSource = sourceDeclaration; + }}, css); } - public CSSProperty getProperty() { + public PropertyValue(PropertyValue propertyValue) { + this(propertyValue.propertyName, propertyValue.propertyValue, propertyValue.css); + } + + PropertyValue(String propertyName, Quadruple propertyValue, SupportedBrailleCSS css) { + if (propertyValue.isEmpty()) + throw new IllegalArgumentException(); + this.propertyName = propertyName; + this.propertyValue = propertyValue; + this.css = css; + init(); + } + + // called from constructor and from clone() + private void init() { + property = propertyValue.getProperty(true); + value = propertyValue.getValue(true); + sourceDeclaration = propertyValue.getSourceDeclaration(true); + if (value != null) + declaration = value; + else if (sourceDeclaration != null && sourceDeclaration.getProperty().equals(propertyName)) + declaration = sourceDeclaration.get(0); // assuming sourceDeclaration is a single TermIdent + else + // repeater and/or variator was applied + declaration = new TermIdentImpl() {{ + value = property.toString(); + operator = null; } + @Override public TermIdent setValue(String value) { throw new UnsupportedOperationException("Unmodifiable"); } + @Override public TermIdent setOperator(Operator operator) { throw new UnsupportedOperationException("Unmodifiable"); } + }; + } + + /** + * {@link SupportedBrailleCSS} instance that was used to create this {@link PropertyValue} + */ + public SupportedBrailleCSS getSupportedBrailleCSS() { + return css; + } + + public CSSProperty getCSSProperty() { return property; } + /** + * Returned object is not immutable. + */ public Term getValue() { return value; } + + /** + * Returned object can be assumed to be immutable. + */ + public Declaration getSourceDeclaration() { + return sourceDeclaration; + } + + /** + * Returns null if the property is unknown. + */ + public PropertyValue getDefault() { + Quadruple q = propertyValue.getDefault(); + if (q == propertyValue) + return this; + else if (q.isEmpty()) + // unknown property + return null; + else + return new PropertyValue(propertyName, q, css); + } + + private boolean concretizedInherit = false; + private boolean concretizedInitial = false; + + /** + * Does not mutate the object. Returns a new object if needed. + */ + public PropertyValue concretize(boolean concretizeInherit, boolean concretizeInitial) { + if ((concretizedInherit || !concretizeInherit) && (concretizedInitial || !concretizeInitial)) + return this; + else { + Quadruple q = (Quadruple)propertyValue.clone(); + q.concretize(concretizeInherit, concretizeInitial); + PropertyValue concretized = new PropertyValue(propertyName, q, css); + concretized.concretizedInherit = concretizeInherit; + concretized.concretizedInitial = concretizeInitial; + return concretized; + } + } + + private boolean inherited = false; + + /** + * Does not mutate the object. Returns a new object if needed. + */ + public PropertyValue inheritFrom(PropertyValue parent) { + if (inherited) + throw new UnsupportedOperationException("Can not inherit from more than one parent style"); + else if (concretizedInherit) + throw new UnsupportedOperationException("Can not inherit from a parent style: 'inherit' values were already concretized."); + Quadruple q = (Quadruple)propertyValue.clone(); + q.inheritFrom(parent.concretize(true, false).propertyValue); + q.concretize(true, false); + PropertyValue inherited = new PropertyValue(propertyName, q, css); + inherited.inherited = inherited.concretizedInherit = true; + return inherited; + } + + /* ===== Declaration ===== */ + + @Override + public String getProperty() { + return propertyName; + } + + @Override + public boolean isImportant() { + return sourceDeclaration != null ? sourceDeclaration.isImportant() : false; + } + + @Override + public SourceLocator getSource() { + return sourceDeclaration != null ? sourceDeclaration.getSource() : null; + } + + @Override public void setProperty(String p) { throw new UnsupportedOperationException("immutable"); } + @Override public void setImportant(boolean i) { throw new UnsupportedOperationException("immutable"); } + @Override public void setSource(SourceLocator sl) { throw new UnsupportedOperationException("immutable"); } + + // List> + + @Override + public int size() { + return 1; + } + + @Override + public Term get(int i) { + return i == 0 ? declaration : null; + } + + // Rule> + + @Override + public List> asList() { + return this; + } + + @Override public Rule> replaceAll(List> replacement) { throw new UnsupportedOperationException("immutable"); } + @Override public Rule> unlock() { throw new UnsupportedOperationException("immutable"); } + + // PrettyOutput + + @Override + public String toString(int depth) { + return toString(); + } + + // Comparable + + @Override + public int compareTo(Declaration o) { + if (this.isImportant() && !o.isImportant()) + return 1; + else if (o.isImportant() && !this.isImportant()) + return -1; + return 0; + } + + /* ============================= */ @Override public Object clone() { @@ -32,34 +214,36 @@ public Object clone() { clone = (PropertyValue)super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError("coding error"); }} - clone.property = property; - if (value != null) - clone.value = (Term)value.clone(); + clone.propertyValue = (Quadruple)propertyValue.clone(); + clone.init(); return clone; } @Override public String toString() { if (value != null) - return value.toString(); + return propertyName + ": " + value.toString(); else - return property.toString(); + return propertyName + ": " + property.toString(); } - private static BrailleCSSDeclarationTransformer transformerInstance - = new BrailleCSSDeclarationTransformer(new SupportedBrailleCSS(true, false)); - private final static BrailleCSSParserFactory parserFactoryInstance = new BrailleCSSParserFactory(); + /* ============================= */ public static PropertyValue parse(String property, String value) { - return parse(parserFactoryInstance.parseDeclaration(property + ":" + value)); + return parse(SimpleInlineStyle.defaultParserFactory.parseDeclaration(property + ":" + value)); } - public static PropertyValue parse(Declaration declaration) { - Map properties = new HashMap(); - Map> terms = new HashMap>(); - if (!transformerInstance.parseDeclaration(declaration, properties, terms)) + public static PropertyValue parse(final Declaration declaration) { + final Map properties = new HashMap(); + final Map> terms = new HashMap>(); + if (!SimpleInlineStyle.defaultCSS.parseDeclaration(declaration, properties, terms)) return null; - String property = declaration.getProperty(); - return new PropertyValue(properties.get(property), terms.get(property)); + final String propertyName = declaration.getProperty(); + return new PropertyValue( + propertyName, + properties.get(propertyName), + terms.get(propertyName), + declaration, + SimpleInlineStyle.defaultCSS); } } diff --git a/libs/braille-css/src/main/java/org/daisy/braille/css/SelectorImpl.java b/libs/braille-css/src/main/java/org/daisy/braille/css/SelectorImpl.java index 74e929da10..8dbba9fb69 100644 --- a/libs/braille-css/src/main/java/org/daisy/braille/css/SelectorImpl.java +++ b/libs/braille-css/src/main/java/org/daisy/braille/css/SelectorImpl.java @@ -69,7 +69,7 @@ public static class PseudoClassImpl extends cz.vutbr.web.csskit.SelectorImpl.Pse private final String name; private final String[] args; - public PseudoClassImpl(String name, String... args) { + public PseudoClassImpl(String name, String... args) throws IllegalArgumentException { super(name, args); this.name = name; this.args = args; diff --git a/libs/braille-css/src/main/java/org/daisy/braille/css/SimpleInlineStyle.java b/libs/braille-css/src/main/java/org/daisy/braille/css/SimpleInlineStyle.java index d5c2ebcff6..88d81fc575 100644 --- a/libs/braille-css/src/main/java/org/daisy/braille/css/SimpleInlineStyle.java +++ b/libs/braille-css/src/main/java/org/daisy/braille/css/SimpleInlineStyle.java @@ -2,54 +2,104 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import cz.vutbr.web.css.CSSProperty; import cz.vutbr.web.css.Declaration; import cz.vutbr.web.css.NodeData; -import cz.vutbr.web.css.SupportedCSS; import cz.vutbr.web.css.Term; -import cz.vutbr.web.domassign.DeclarationTransformer; import cz.vutbr.web.domassign.SingleMapNodeData; public class SimpleInlineStyle extends SingleMapNodeData implements NodeData, Cloneable, Iterable { - private final static SupportedCSS cssInstance = new SupportedBrailleCSS(true, false); - private static DeclarationTransformer transformerInstance = new BrailleCSSDeclarationTransformer(cssInstance); - private final static BrailleCSSParserFactory parserFactory = new BrailleCSSParserFactory(); + final static SupportedBrailleCSS defaultCSS = new SupportedBrailleCSS(true, false); + final static BrailleCSSParserFactory defaultParserFactory = new BrailleCSSParserFactory(); public static final SimpleInlineStyle EMPTY = new SimpleInlineStyle((List)null); + private SupportedBrailleCSS css; + public SimpleInlineStyle(String style) { this(style, null); } + /** + * @param parentStyle if a parent style is provided (not null), all inherited + * properties are included, and all ")null, + (style != null && !"".equals(style)) ? defaultParserFactory.parseSimpleInlineStyle(style) : (List)null, parentStyle); } - public SimpleInlineStyle(List declarations) { + public SimpleInlineStyle(Iterable declarations) { this(declarations, null); } - public SimpleInlineStyle(List declarations, SimpleInlineStyle parentStyle) { - this(declarations, parentStyle, transformerInstance, cssInstance); + public SimpleInlineStyle(Iterable declarations, SimpleInlineStyle parentStyle) { + this(declarations, parentStyle, null); } - public SimpleInlineStyle(List declarations, SimpleInlineStyle parentStyle, - DeclarationTransformer transformer, SupportedCSS css) { - super(transformer, css); - if (declarations != null) - for (Declaration d : declarations) - push(d); - if (parentStyle != null) - inheritFrom(parentStyle); + /** + * @param css {@link SupportedBrailleCSS} to be used to process declarations that are not {@link + * PropertyValue} instances. If non-{@code null}, assert that declarations that are + * {@link PropertyValue} were created using the given {@link SupportedBrailleCSS}. If + * {@code null}, assert that all {@link PropertyValue} declarations were created with + * the same {@link SupportedBrailleCSS}. + * @throws IllegalArgumentException if a parent style or declaration is provided that is not + * compatible with the provided {@code SupportedBrailleCSS} + */ + public SimpleInlineStyle(Iterable declarations, SimpleInlineStyle parentStyle, SupportedBrailleCSS css) { + super(css == null ? defaultCSS : css, + css == null ? defaultCSS : css); + if (declarations != null) { + for (Declaration d : declarations) { + if (!(d instanceof PropertyValue)) { + if (css == null) + css = defaultCSS; + super.push(d); + } + } + for (Declaration d : declarations) { + if (d instanceof PropertyValue) { + PropertyValue v = (PropertyValue)d; + if (css == null) + css = v.css; + if (v.css.equals(css)) + map.put(v.getProperty(), v.propertyValue); + else + throw new IllegalArgumentException(); + } + } + } + if (parentStyle != null) { + if (declarations != null) + for (Declaration d : declarations) + for (PropertyValue dd : parentStyle) { + if (!css.equals(dd.css)) + throw new IllegalArgumentException(); + break; } + super.inheritFrom(parentStyle.concretize()); + super.concretize(true, true); + inherited = concretizedInherit = concretizedInitial = true; + if (css == null) + css = parentStyle.css; + } else { + super.concretize(false, true); + concretizedInitial = true; + } + this.css = css; } - + public Term getValue(String name) { return getValue(name, true); } @@ -73,10 +123,7 @@ public boolean hasNext() { return props.hasNext(); } public PropertyValue next() { - String prop = props.next(); - return new PropertyValue( - SimpleInlineStyle.this.getProperty(prop), - SimpleInlineStyle.this.getValue(prop)); + return SimpleInlineStyle.this.get(props.next()); } public void remove() { props.remove(); @@ -85,10 +132,12 @@ public void remove() { } public PropertyValue get(String property) { - CSSProperty p = getProperty(property); - if (p == null) + Quadruple q = map.get(property); + if (q == null) return null; - return new PropertyValue(p, getValue(property)); + if (css == null) + throw new IllegalStateException(); // can not happen + return new PropertyValue(property, q, css); } @Override @@ -104,17 +153,19 @@ public String toString() { if (value != null) sb.append(value); else - sb.append(getProperty(key)); } + sb.append(getProperty(key).toString()); } return sb.toString(); } @Override - public boolean equals(Object other) { + public final boolean equals(Object other) { if (other instanceof SimpleInlineStyle) { SimpleInlineStyle that = (SimpleInlineStyle)other; Set keys = map.keySet(); if (!keys.equals(that.map.keySet())) return false; + if (!isEmpty() && css != that.css) + return false; for (String key : keys) { Term value = getValue(key); if ((value == null && that.getValue(key) != null) @@ -129,4 +180,59 @@ public boolean equals(Object other) { } return false; } + + private boolean concretizedInherit = false; + private boolean concretizedInitial = false; + + @Override + public SimpleInlineStyle concretize() { + return (SimpleInlineStyle)super.concretize(); + } + + @Override + public SimpleInlineStyle concretize(boolean concretizeInherit, boolean concretizeInitial) { + if ((concretizedInherit || !concretizeInherit) && (concretizedInitial || !concretizeInitial)) + return this; + else { + SimpleInlineStyle copy = (SimpleInlineStyle)clone(); + copy.noCopyConcretize(concretizeInherit, concretizeInitial); + return copy; + } + } + + private void noCopyConcretize(boolean concretizeInherit, boolean concretizeInitial) { + super.concretize(concretizeInherit, concretizeInitial); + concretizedInherit = concretizeInherit; + concretizedInitial = concretizeInitial; + } + + private boolean inherited = false; + + @Override + public SimpleInlineStyle inheritFrom(NodeData parent) throws ClassCastException { + if (inherited) + throw new UnsupportedOperationException("Can not inherit from more than one parent style"); + else if (concretizedInherit) + throw new UnsupportedOperationException("Can not inherit from a parent style: 'inherit' values were already concretized."); + else if (!(parent instanceof SimpleInlineStyle)) + throw new UnsupportedOperationException("Can only inherit from another SimpleInlineStyle"); + else { + SimpleInlineStyle copy = (SimpleInlineStyle)clone(); + copy.noCopyInheritFrom(((SimpleInlineStyle)parent).concretize()); + copy.noCopyConcretize(true, false); + if (copy.css == null) + copy.css = ((SimpleInlineStyle)parent).css; + return copy; + } + } + + private void noCopyInheritFrom(NodeData parent) { + super.inheritFrom(parent); + inherited = true; + } + + @Override + public NodeData push(Declaration d) { + throw new UnsupportedOperationException(); + } } diff --git a/libs/braille-css/src/main/java/org/daisy/braille/css/SupportedBrailleCSS.java b/libs/braille-css/src/main/java/org/daisy/braille/css/SupportedBrailleCSS.java index 785b72ef55..ca035b4ef0 100644 --- a/libs/braille-css/src/main/java/org/daisy/braille/css/SupportedBrailleCSS.java +++ b/libs/braille-css/src/main/java/org/daisy/braille/css/SupportedBrailleCSS.java @@ -1,25 +1,45 @@ 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.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Random; +import java.util.regex.Pattern; import java.util.Set; +import com.google.common.collect.ForwardingMap; + import cz.vutbr.web.css.CSSFactory; import cz.vutbr.web.css.CSSProperty; import cz.vutbr.web.css.CSSProperty.Border; import cz.vutbr.web.css.CSSProperty.CounterIncrement; import cz.vutbr.web.css.CSSProperty.CounterReset; import cz.vutbr.web.css.CSSProperty.CounterSet; -import cz.vutbr.web.css.CSSProperty.Orphans; import cz.vutbr.web.css.CSSProperty.PageBreak; import cz.vutbr.web.css.CSSProperty.PageBreakInside; import cz.vutbr.web.css.CSSProperty.TextAlign; -import cz.vutbr.web.css.CSSProperty.Widows; +import cz.vutbr.web.css.Declaration; import cz.vutbr.web.css.SupportedCSS; import cz.vutbr.web.css.Term; import cz.vutbr.web.css.TermFactory; +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; import org.daisy.braille.css.BrailleCSSProperty.AbsoluteMargin; import org.daisy.braille.css.BrailleCSSProperty.BorderAlign; @@ -30,6 +50,7 @@ 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; @@ -39,6 +60,7 @@ 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.Orphans; import org.daisy.braille.css.BrailleCSSProperty.Padding; import org.daisy.braille.css.BrailleCSSProperty.Page; import org.daisy.braille.css.BrailleCSSProperty.RenderTableBy; @@ -50,56 +72,1238 @@ 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.Widows; import org.daisy.braille.css.BrailleCSSProperty.WordSpacing; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** +/** * @author bert */ -public class SupportedBrailleCSS implements SupportedCSS { +public class SupportedBrailleCSS extends DeclarationTransformer implements SupportedCSS { + + public SupportedBrailleCSS() { + this(false, true); + } + + public SupportedBrailleCSS(boolean allowComponentProperties, boolean allowShorthandProperties) { + this(allowComponentProperties, allowShorthandProperties, Collections.emptyList(), true); + } + + /** + * @param allowUnknownVendorExtensions Whether to allow unknown vendor extensions. + */ + public SupportedBrailleCSS(boolean allowComponentProperties, + boolean allowShorthandProperties, + Collection extensions, + boolean allowUnknownVendorExtensions) { + // SupportedCSSImpl is defined at the bottom of this file + // it is a separate class because we need an argument to pass to the constructor of DeclarationTransformer + super(new SupportedCSSImpl(allowComponentProperties, allowShorthandProperties, extensions)); + this.methods = parsingMethods(extensions); + this.extensions = new ArrayList<>(); + for (BrailleCSSExtension x : extensions) + if (x.getPrefix() == null || !x.getPrefix().matches("-.+-")) + log.warn("CSS extension without prefix ignored: " + x); + else + this.extensions.add(x); + this.allowUnknownVendorExtensions = allowUnknownVendorExtensions; + } + + protected SupportedBrailleCSS(SupportedCSS css) { + super(css); + this.extensions = null; + this.allowUnknownVendorExtensions = false; + } + + protected final Collection extensions; + protected final boolean allowUnknownVendorExtensions; + + /////////////////////////////////////////////////////////////// + // DeclarationTransformer + /////////////////////////////////////////////////////////////// + + @Override + protected final Map parsingMethods() { + return null; + } + + private Map parsingMethods(Collection extensions) { + Map map = new HashMap(css.getTotalProperties(), 1.0f); + for (String property : css.getDefinedPropertyNames()) { + boolean isExtensionProperty = false; { + for (BrailleCSSExtension x : extensions) + if (property.startsWith(x.getPrefix())) { + isExtensionProperty = true; + break; }} + if (isExtensionProperty) + continue; // will be handled by extension + try { + Method m = SupportedBrailleCSS.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) { + if (methods == null || extensions == null) + throw new IllegalStateException("parseDeclaration() method must be overridden"); + String property = d.getProperty().toLowerCase(); + if (property.startsWith("-")) { + for (BrailleCSSExtension x : extensions) + if (property.startsWith(x.getPrefix())) { + if (!x.isSupportedCSSProperty(property)) { + log.debug("Ignoring unsupported property: " + property); + return false; + } else if (x.parseDeclaration(d, properties, values)) { + return true; + } else { + log.warn("Ignoring unsupported declaration: " + declarationToString(d)); + return false; + } + } + if (!allowUnknownVendorExtensions) { + log.debug("Ignoring unsupported property: " + property); + return false; + } + if (d.size() == 1) { + Term term = d.get(0); + if (term instanceof TermIdent) + return genericProperty(GenericVendorCSSPropertyProxy.class, (TermIdent)term, + ALLOW_INH, 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)); + return false; + } + if (css.isSupportedCSSProperty(property)) { + try { + Method m = methods.get(property); + if (m != null) + try { + if ((Boolean)m.invoke(this, d, properties, values)) + return true; + } 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) { + } + // might be a declaration with a non-standard value that an extension can parse + for (BrailleCSSExtension x : extensions) + if (x.parseDeclaration(d, properties, values)) + return true; + } catch (Exception e) { + } + } else { + // might be an unprefixed extension property (which the extension's parser may or may not support) + for (BrailleCSSExtension x : extensions) + if (x.isSupportedCSSProperty(x.getPrefix() + property)) + if (x.parseDeclaration(d, + new NormalizingMap(x, properties), + new NormalizingMap>(x, values))) + return true; + } + log.warn("Ignoring unsupported declaration: " + declarationToString(d)); + return false; + } + + /** + * Normalize property names by adding a prefix if needed. No warnings are issued. + */ + private class NormalizingMap extends ForwardingMap { + + private final BrailleCSSExtension extension; + private final Map map; + + public NormalizingMap(BrailleCSSExtension extension, Map map) { + this.extension = extension; + this.map = map; + } + + @Override + protected Map delegate() { + return map; + } + + @Override + public T put(String propertyName, T value) { + if (!extension.isSupportedCSSProperty(propertyName) + && !propertyName.startsWith(extension.getPrefix()) + && extension.isSupportedCSSProperty(extension.getPrefix() + propertyName)) + propertyName = extension.getPrefix() + propertyName; + return super.put(propertyName, value); + } + } + + 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 customIdentOrFuncName = Pattern.compile("^-.*"); + + @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); + continue; + } else if (t instanceof TermFunction) { + String funcName = ((TermFunction)t).getFunctionName(); + if (validContentFuncNames.contains(funcName.toLowerCase())) { + list.add(t); + continue; + } + } + boolean parsedByExtension = false; + for (BrailleCSSExtension x : extensions) { + if (x.parseContentTerm(t, list)) { + parsedByExtension = true; + break; + } + } + if (parsedByExtension) + continue; + if (allowUnknownVendorExtensions) + if (t instanceof TermFunction) { + String funcName = ((TermFunction)t).getFunctionName(); + if (customIdentOrFuncName.matcher(funcName).matches()) { + for (BrailleCSSExtension x : extensions) + if (funcName.startsWith(x.getPrefix())) + return false; // x.parseContentTerm() above should have returned true + list.add(t); + continue; + } + } + return false; + } + if (list.isEmpty()) + return false; + properties.put("content", Content.content_list); + values.put("content", list); + return true; + } + + @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 (allowUnknownVendorExtensions) + if (t instanceof TermIdent) { + String ident = ((TermIdent)t).getValue(); + if (customIdentOrFuncName.matcher(ident).matches()) { + for (BrailleCSSExtension x : extensions) + if (ident.startsWith(x.getPrefix())) + return false; + 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); + } + + private static final Pattern HYPHENATE_CHARACTER_RE = Pattern.compile("[\u2800-\u28ff]+"); + + @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)) { + if (!HYPHENATE_CHARACTER_RE.matcher("" + term.getValue()).matches()) { + return false; + } + 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, true, + d, properties, values); + } + + @SuppressWarnings("unused") + private boolean processMinLength(Declaration d, + Map properties, Map> values) { + return genericOneIdentOrInteger(MinLength.class, MinLength.integer, true, + d, properties, values); + } + + @SuppressWarnings("unused") + private boolean processOrphans(Declaration d, + Map properties, Map> values) { + return genericOneIdentOrInteger(Orphans.class, Orphans.integer, true, + 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(Size.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()) { + TermIdent textTransform = null; + if (t instanceof TermIdent && ((TermIdent)t).getValue().startsWith("-")) { + // if the counter name starts with an extension prefix, let the extension parse it + for (BrailleCSSExtension x : extensions) + if (((TermIdent)t).getValue().startsWith(x.getPrefix())) + try { + textTransform = x.parseTextTransform(t); + break; + } catch (IllegalArgumentException e) { + return false; + } + } else + // other, give extensions the chance to normalize names + for (BrailleCSSExtension x : extensions) + try { + textTransform = x.parseTextTransform(t); + break; + } catch (IllegalArgumentException e) { + // continue + } + if (textTransform == null && t instanceof TermIdent) + textTransform = (TermIdent)t; + if (textTransform == null) + return false; + if (!"auto".equalsIgnoreCase(textTransform.getValue())) + list.add(textTransform); + } + 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 (allowUnknownVendorExtensions) + if (d.size() == 1) { + Term term = d.get(0); + if (term instanceof TermFunction) { + String funcName = ((TermFunction)term).getFunctionName(); + if (customIdentOrFuncName.matcher(funcName).matches()) { + for (BrailleCSSExtension x : extensions) + if (funcName.startsWith(x.getPrefix())) + return false; + properties.put("volume-break-inside", VolumeBreakInside.custom); + values.put("volume-break-inside", term); + 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 processWidows(Declaration d, + Map properties, Map> values) { + return genericOneIdentOrInteger(Widows.class, Widows.integer, true, d, + properties, values); + } + + @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) + && !((TermIdent)d.get(0)).getValue().startsWith("-")); + } + + /**************************************************************** + * 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 properties) { + if (!(term instanceof TermIdent) + || !CSSProperty.INHERIT_KEYWORD.equalsIgnoreCase(((TermIdent) term).getValue())) { + return false; + } + if (variant == ALL_VARIANTS) { + for (int i = 0; i < variants; i++) { + Repeater r = repeaters.get(i); + r.assignTerms(term, term, term, term); + r.repeat(properties, null); + } + return true; + } + Repeater r = repeaters.get(variant); + r.assignTerms(term, term, term, term); + r.repeat(properties, null); + return true; + } + + @Override + public void assignDefaults(Map properties, Map> values) { + for (Repeater r : repeaters) + r.assignDefaults(properties, values); + } + } + + private final class BorderSideVariator extends Variator { + + public static final int ALIGN = 0; + public static final int STYLE = 1; + public static final int WIDTH = 2; + + private final String borderPatternName; + + public BorderSideVariator(String side) { + super(3, css); + names.add("border-" + side + "-align"); + types.add(BorderAlign.class); + names.add("border-" + side + "-style"); + types.add(BorderStyle.class); + names.add("border-" + side + "-width"); + types.add(BorderWidth.class); + borderPatternName = "border-" + side + "-pattern"; + } + + @Override + public void assignDefaults(Map properties, Map> values) { + super.assignDefaults(properties, values); + assignDefault(borderPatternName, properties, values); + } + + @Override + protected boolean variant(int variant, IntegerRef iteration, + Map properties, Map> values) { + int i = iteration.get(); + switch (variant) { + case WIDTH: + return genericTermIdent(types.get(variant), terms.get(i), AVOID_INH, names.get(variant), + properties) + || genericTerm(TermInteger.class, terms.get(i), names.get(variant), BorderWidth.integer, + false, properties, values); + case ALIGN: + case STYLE: + return genericTermIdent(types.get(variant), terms.get(i), AVOID_INH, names.get(variant), + properties); + default: + return false; + } + } + + private boolean patternVariant(Map properties, Map> values) { + if (terms.size() != 1) + return false; + Term t = terms.get(0); + if (genericTermIdent(BorderPattern.class, t, ALLOW_INH, borderPatternName, properties)) + return true; + try { + if (TermIdent.class.isInstance(t)) { + TermDotPattern value = TermDotPattern.createDotPattern((TermIdent)t); + properties.put(borderPatternName, BorderPattern.dot_pattern); + values.put(borderPatternName, value); + return true; + } + } catch (Exception e) { + } + return false; + } + + @Override + public boolean vary(Map properties, Map> values) { + boolean rv = false; + if (super.vary(properties, values)) + rv = true; + if (patternVariant(properties, values)) + rv = true; + return rv; + } + } + + private final void assignDefault(String propertyName, Map properties, Map> values) { + CSSProperty dp = css.getDefaultProperty(propertyName); + if (dp != null) + properties.put(propertyName, dp); + Term dv = css.getDefaultValue(propertyName); + if (dv != null) + values.put(propertyName, dv); + } + /////////////////////////////////////////////////////////////// + // SupportedCSS + /////////////////////////////////////////////////////////////// + + @Override + public boolean isSupportedMedia(String media) { + return css.isSupportedMedia(media); + } + @Override + public final boolean isSupportedCSSProperty(String property) { + return css.isSupportedCSSProperty(property); + } + @Override + public final CSSProperty getDefaultProperty(String property) { + return css.getDefaultProperty(property); + } + @Override + public final Term getDefaultValue(String property) { + return css.getDefaultValue(property); + } + @Override + public final int getTotalProperties() { + return css.getTotalProperties(); + } + @Override + public final Set getDefinedPropertyNames() { + return css.getDefinedPropertyNames(); + } + @Override + public String getRandomPropertyName() { + return css.getRandomPropertyName(); + } + @Override + public int getOrdinal(String propertyName) { + return css.getOrdinal(propertyName); + } + @Override + public String getPropertyName(int o) { + return css.getPropertyName(o); + } +} + +class SupportedCSSImpl implements SupportedCSS { + private static Logger log = LoggerFactory.getLogger(SupportedBrailleCSS.class); - - private static final int TOTAL_SUPPORTED_DECLARATIONS = 70; - + private static final TermFactory tf = CSSFactory.getTermFactory(); - + private static final CSSProperty DEFAULT_UA_TEXT_ALIGN = TextAlign.LEFT; private static final Term DEFAULT_UA_TEXT_IDENT = tf.createInteger(0); private static final Term DEFAULT_UA_MARGIN = tf.createInteger(0); private static final Term DEFAULT_UA_PADDING = tf.createInteger(0); private static final Term DEFAULT_UA_BORDER_WIDTH = tf.createInteger(1); - private static final Term DEFAULT_UA_ORPHANS = tf.createInteger(2); - private static final Term DEFAULT_UA_WIDOWS = tf.createInteger(2); + private static final Term DEFAULT_UA_ORPHANS = tf.createInteger(0); + private static final Term DEFAULT_UA_WIDOWS = tf.createInteger(0); private static final Term DEFAULT_UA_LINE_HEIGHT = tf.createInteger(1); - private static final Term DEFAULT_UA_LETTER_SPACING = tf.createInteger(1); + private static final Term DEFAULT_UA_LETTER_SPACING = tf.createInteger(0); private static final Term DEFAULT_UA_WORD_SPACING = tf.createInteger(1); - + + private final int TOTAL_SUPPORTED_DECLARATIONS; + private Set supportedCSSproperties; private Map defaultCSSproperties; private Map> defaultCSSvalues; - private Map ordinals; private Map ordinalsRev; - - public SupportedBrailleCSS() { - this(false, true); - } - - public SupportedBrailleCSS(boolean allowComponentProperties, boolean allowShorthandProperties) { - this.setSupportedCSS(allowComponentProperties, allowShorthandProperties); + + SupportedCSSImpl(boolean allowComponentProperties, boolean allowShorthandProperties, Collection extensions) { + this.TOTAL_SUPPORTED_DECLARATIONS = 70 + 2 * extensions.stream().mapToInt(BrailleCSSExtension::getTotalProperties).sum(); + this.setSupportedCSS(allowComponentProperties, allowShorthandProperties, extensions); this.setOridinals(); } - + @Override public boolean isSupportedMedia(String media) { if (media == null) return false; return media.toLowerCase().equals("embossed"); } - + @Override public final boolean isSupportedCSSProperty(String property) { if (supportedCSSproperties.contains(property)) @@ -109,59 +1313,59 @@ public final boolean isSupportedCSSProperty(String property) { log.info("Shorthand or component property not supported: {}"); return false; } } - + @Override public final CSSProperty getDefaultProperty(String property) { CSSProperty value = defaultCSSproperties.get(property); log.debug("Asked for property {}'s default value: {}", property, value); return value; } - + @Override public final Term getDefaultValue(String property) { return defaultCSSvalues.get(property); } - + @Override public final int getTotalProperties() { return defaultCSSproperties.size(); } - + @Override public final Set getDefinedPropertyNames() { return defaultCSSproperties.keySet(); } - + @Override public String getRandomPropertyName() { final Random generator = new Random(); int o = generator.nextInt(getTotalProperties()); return getPropertyName(o); } - + @Override public int getOrdinal(String propertyName) { Integer i = ordinals.get(propertyName); return (i == null) ? -1 : i.intValue(); } - + @Override public String getPropertyName(int o) { return ordinalsRev.get(o); } - + private void setProperty(String name, CSSProperty defaultProperty) { setProperty(name, true, defaultProperty, null); } - + private void setProperty(String name, boolean allow, CSSProperty defaultProperty) { setProperty(name, allow, defaultProperty, null); } - + private void setProperty(String name, CSSProperty defaultProperty, Term defaultValue) { setProperty(name, true, defaultProperty, defaultValue); } - + private void setProperty(String name, boolean allow, CSSProperty defaultProperty, Term defaultValue) { if (allow) supportedCSSproperties.add(name); @@ -170,73 +1374,73 @@ private void setProperty(String name, boolean allow, CSSProperty defaultProperty if (defaultValue != null) defaultCSSvalues.put(name, defaultValue); } - - private void setSupportedCSS(boolean allowComponentProperties, boolean allowShorthandProperties) { - + + private void setSupportedCSS(boolean allowComponentProperties, boolean allowShorthandProperties, Collection extensions) { + supportedCSSproperties = new HashSet(TOTAL_SUPPORTED_DECLARATIONS, 1.0f); defaultCSSproperties = new HashMap(TOTAL_SUPPORTED_DECLARATIONS, 1.0f); defaultCSSvalues = new HashMap>(TOTAL_SUPPORTED_DECLARATIONS, 1.0f);; - + // text spacing setProperty("text-align", DEFAULT_UA_TEXT_ALIGN); setProperty("text-indent", TextIndent.integer, DEFAULT_UA_TEXT_IDENT); setProperty("line-height", LineHeight.number, DEFAULT_UA_LINE_HEIGHT); - + // layout box - setProperty("left", AbsoluteMargin.integer, DEFAULT_UA_MARGIN); - setProperty("right", AbsoluteMargin.integer, DEFAULT_UA_MARGIN); - + setProperty("left", AbsoluteMargin.AUTO); + setProperty("right", AbsoluteMargin.AUTO); + setProperty("margin-top", Margin.integer, DEFAULT_UA_MARGIN); setProperty("margin-right", Margin.integer, DEFAULT_UA_MARGIN); setProperty("margin-bottom", Margin.integer, DEFAULT_UA_MARGIN); setProperty("margin-left", Margin.integer, DEFAULT_UA_MARGIN); setProperty("margin", allowShorthandProperties, Margin.component_values); - + setProperty("padding-top", Padding.integer, DEFAULT_UA_PADDING); setProperty("padding-right", Padding.integer, DEFAULT_UA_PADDING); setProperty("padding-bottom", Padding.integer, DEFAULT_UA_PADDING); setProperty("padding-left", Padding.integer, DEFAULT_UA_PADDING); setProperty("padding", allowShorthandProperties, Padding.component_values); - + setProperty("border-top-pattern", allowComponentProperties, BorderPattern.NONE); setProperty("border-right-pattern", allowComponentProperties, BorderPattern.NONE); setProperty("border-bottom-pattern", allowComponentProperties, BorderPattern.NONE); setProperty("border-left-pattern", allowComponentProperties, BorderPattern.NONE); - + setProperty("border-top-style", allowComponentProperties, BorderStyle.NONE); setProperty("border-right-style", allowComponentProperties, BorderStyle.NONE); setProperty("border-bottom-style", allowComponentProperties, BorderStyle.NONE); setProperty("border-left-style", allowComponentProperties, BorderStyle.NONE); setProperty("border-style", allowShorthandProperties, BorderStyle.component_values); - + setProperty("border-top-width", allowComponentProperties, BorderWidth.integer, DEFAULT_UA_BORDER_WIDTH); setProperty("border-right-width", allowComponentProperties, BorderWidth.integer, DEFAULT_UA_BORDER_WIDTH); setProperty("border-bottom-width", allowComponentProperties, BorderWidth.integer, DEFAULT_UA_BORDER_WIDTH); setProperty("border-left-width", allowComponentProperties, BorderWidth.integer, DEFAULT_UA_BORDER_WIDTH); setProperty("border-width", allowShorthandProperties, BorderWidth.component_values); - + setProperty("border-top-align", allowComponentProperties, BorderAlign.CENTER); setProperty("border-right-align", allowComponentProperties, BorderAlign.CENTER); setProperty("border-bottom-align", allowComponentProperties, BorderAlign.CENTER); setProperty("border-left-align", allowComponentProperties, BorderAlign.CENTER); setProperty("border-align", allowShorthandProperties, BorderAlign.component_values); - + setProperty("border-top", allowShorthandProperties, Border.component_values); setProperty("border-right", allowShorthandProperties, Border.component_values); setProperty("border-bottom", allowShorthandProperties, Border.component_values); setProperty("border-left", allowShorthandProperties, Border.component_values); setProperty("border", allowShorthandProperties, Border.component_values); - + // positioning setProperty("display", Display.INLINE); - + // elements setProperty("list-style-type", ListStyleType.NONE); setProperty("list-style", allowShorthandProperties, ListStyleType.NONE); - + // @page rule setProperty("size", Size.AUTO); - + // paged setProperty("page", Page.AUTO); setProperty("page-break-before", PageBreak.AUTO); @@ -244,23 +1448,23 @@ private void setSupportedCSS(boolean allowComponentProperties, boolean allowShor setProperty("page-break-inside", PageBreakInside.AUTO); setProperty("orphans", Orphans.integer, DEFAULT_UA_ORPHANS); setProperty("widows", Widows.integer, DEFAULT_UA_WIDOWS); - + // @footnotes rule setProperty("max-height", MaxHeight.NONE); - + // @volume rule setProperty("min-length", MinLength.AUTO); setProperty("max-length", MaxLength.AUTO); - + // volume breaking setProperty("volume-break-before", VolumeBreak.AUTO); setProperty("volume-break-after", VolumeBreak.AUTO); setProperty("volume-break-inside", VolumeBreakInside.AUTO); - + // tables setProperty("render-table-by", RenderTableBy.AUTO); setProperty("table-header-policy", TableHeaderPolicy.ONCE); - + // misc setProperty("counter-reset", CounterReset.NONE); setProperty("counter-set", CounterSet.NONE); @@ -275,23 +1479,24 @@ private void setSupportedCSS(boolean allowComponentProperties, boolean allowShor setProperty("letter-spacing", LetterSpacing.length, DEFAULT_UA_LETTER_SPACING); setProperty("word-spacing", WordSpacing.length, DEFAULT_UA_WORD_SPACING); setProperty("flow", Flow.NORMAL); - + + // vendor extensions + for (BrailleCSSExtension x : extensions) + for (String p : x.getDefinedPropertyNames()) + // p includes prefix + setProperty(p, x.getDefaultProperty(p), x.getDefaultValue(p)); } - + private void setOridinals() { - Map ords = new HashMap(getTotalProperties(), 1.0f); Map ordsRev = new HashMap(getTotalProperties(), 1.0f); - int i = 0; for (String key : defaultCSSproperties.keySet()) { ords.put(key, i); ordsRev.put(i, key); i++; } - this.ordinals = ords; this.ordinalsRev = ordsRev; - } } diff --git a/libs/braille-css/src/main/java/org/daisy/braille/css/TermDotPattern.java b/libs/braille-css/src/main/java/org/daisy/braille/css/TermDotPattern.java index 4939da6a00..8b0e9b4d39 100644 --- a/libs/braille-css/src/main/java/org/daisy/braille/css/TermDotPattern.java +++ b/libs/braille-css/src/main/java/org/daisy/braille/css/TermDotPattern.java @@ -1,5 +1,7 @@ package org.daisy.braille.css; +import java.util.regex.Pattern; + import cz.vutbr.web.css.TermIdent; import cz.vutbr.web.csskit.TermImpl; @@ -7,6 +9,8 @@ public class TermDotPattern extends TermImpl { + private static final Pattern DOT_PATTERN_RE = Pattern.compile("[\u2800-\u28ff]"); + private TermDotPattern() {} @Override @@ -15,7 +19,9 @@ public TermDotPattern setValue(Character value) { throw new IllegalArgumentException( "Invalid value for TermDotPattern(null)"); } - //if (value != braille) { throw new IllegalArgumentException("Invalid value for TermDotPattern(" + value + ")"); } + if (!DOT_PATTERN_RE.matcher("" + value).matches()) { + throw new IllegalArgumentException("Invalid value for TermDotPattern(" + value + ")"); + } this.value = value; return this; } diff --git a/libs/braille-css/src/main/java/org/daisy/braille/css/AnyAtRule.java b/libs/braille-css/src/main/java/org/daisy/braille/css/VendorAtRule.java similarity index 54% rename from libs/braille-css/src/main/java/org/daisy/braille/css/AnyAtRule.java rename to libs/braille-css/src/main/java/org/daisy/braille/css/VendorAtRule.java index 0f46b673a2..075065d356 100644 --- a/libs/braille-css/src/main/java/org/daisy/braille/css/AnyAtRule.java +++ b/libs/braille-css/src/main/java/org/daisy/braille/css/VendorAtRule.java @@ -1,6 +1,5 @@ package org.daisy.braille.css; -import java.util.ArrayList; import java.util.List; import cz.vutbr.web.css.Declaration; @@ -9,13 +8,16 @@ import cz.vutbr.web.csskit.AbstractRuleBlock; import cz.vutbr.web.csskit.OutputUtil; -public class AnyAtRule extends AbstractRuleBlock> implements PrettyOutput { +public class VendorAtRule> extends AbstractRuleBlock implements PrettyOutput { private final String name; - public AnyAtRule(String name) { + public VendorAtRule(String name, List content) { super(); - replaceAll(new ArrayList>()); + for (Rule r : content) + if (!(r instanceof Declaration || r instanceof VendorAtRule)) + throw new IllegalArgumentException("Rule must be either a Declaration or an at-rule"); + replaceAll(content); this.name = name; } @@ -23,21 +25,12 @@ public String getName() { return name; } - @Override - public boolean add(Rule element) { - if (element instanceof Declaration || element instanceof AnyAtRule) - return super.add(element); - else - throw new IllegalArgumentException("Rule must be either a Declaration or an at rule"); - } - public String toString(int depth) { StringBuilder sb = new StringBuilder(); sb.append("@").append(name); sb.append(" "); sb.append(OutputUtil.RULE_OPENING); - List rules = (List)list; - sb = OutputUtil.appendList(sb, rules, OutputUtil.EMPTY_DELIM, depth + 1); + sb = OutputUtil.appendList(sb, (List)(List)list, OutputUtil.EMPTY_DELIM, depth + 1); sb.append(OutputUtil.RULE_CLOSING); return sb.toString(); } diff --git a/libs/braille-css/src/test/java/PseudoClassTest.java b/libs/braille-css/src/test/java/PseudoClassTest.java index 9e9c0ccd2b..789b43989c 100644 --- a/libs/braille-css/src/test/java/PseudoClassTest.java +++ b/libs/braille-css/src/test/java/PseudoClassTest.java @@ -24,7 +24,6 @@ import org.apache.xerces.parsers.DOMParser; -import org.daisy.braille.css.BrailleCSSDeclarationTransformer; import org.daisy.braille.css.BrailleCSSParserFactory; import org.daisy.braille.css.BrailleCSSRuleFactory; import org.daisy.braille.css.SelectorImpl.NegationPseudoClassImpl; @@ -48,8 +47,9 @@ public class PseudoClassTest { private static final RuleFactory rf = new BrailleCSSRuleFactory(); public PseudoClassTest() { - CSSFactory.registerSupportedCSS(new SupportedBrailleCSS()); - CSSFactory.registerDeclarationTransformer(new BrailleCSSDeclarationTransformer()); + SupportedBrailleCSS css = new SupportedBrailleCSS(); + CSSFactory.registerSupportedCSS(css); + CSSFactory.registerDeclarationTransformer(css); } @Test diff --git a/libs/braille-css/src/test/java/PseudoElementsTest.java b/libs/braille-css/src/test/java/PseudoElementsTest.java index 9a94a62c32..cf76242836 100644 --- a/libs/braille-css/src/test/java/PseudoElementsTest.java +++ b/libs/braille-css/src/test/java/PseudoElementsTest.java @@ -15,7 +15,6 @@ import cz.vutbr.web.csskit.antlr.CSSSource; import cz.vutbr.web.csskit.antlr.DefaultCSSSourceReader; -import org.daisy.braille.css.BrailleCSSDeclarationTransformer; import org.daisy.braille.css.BrailleCSSParserFactory; import org.daisy.braille.css.BrailleCSSRuleFactory; import org.daisy.braille.css.SelectorImpl.PseudoElementImpl; @@ -30,8 +29,9 @@ public class PseudoElementsTest { private static final RuleFactory rf = new BrailleCSSRuleFactory(); public PseudoElementsTest() { - CSSFactory.registerSupportedCSS(new SupportedBrailleCSS()); - CSSFactory.registerDeclarationTransformer(new BrailleCSSDeclarationTransformer()); + SupportedBrailleCSS css = new SupportedBrailleCSS(); + CSSFactory.registerSupportedCSS(css); + CSSFactory.registerDeclarationTransformer(css); } @Test diff --git a/libs/braille-css/src/test/java/VendorExtensionsTest.java b/libs/braille-css/src/test/java/VendorExtensionsTest.java index efb4cb9853..6092c37672 100644 --- a/libs/braille-css/src/test/java/VendorExtensionsTest.java +++ b/libs/braille-css/src/test/java/VendorExtensionsTest.java @@ -15,10 +15,10 @@ import cz.vutbr.web.csskit.antlr.CSSSource; import cz.vutbr.web.csskit.antlr.DefaultCSSSourceReader; -import org.daisy.braille.css.AnyAtRule; import org.daisy.braille.css.BrailleCSSParserFactory; import org.daisy.braille.css.BrailleCSSRuleFactory; import org.daisy.braille.css.SimpleInlineStyle; +import org.daisy.braille.css.VendorAtRule; import org.junit.Assert; import org.junit.Test; @@ -75,10 +75,10 @@ public void testVendorAtRule() throws IOException, CSSException { 0, 0), new DefaultCSSSourceReader()); Assert.assertEquals(1, sheet.size()); - AnyAtRule rule = (AnyAtRule)sheet.get(0); + VendorAtRule rule = (VendorAtRule)sheet.get(0); Assert.assertEquals("-obfl-volume-transition", rule.getName()); Assert.assertEquals(1, rule.size()); - rule = (AnyAtRule)rule.get(0); + rule = (VendorAtRule)rule.get(0); Assert.assertEquals("any-interrupted", rule.getName()); Assert.assertEquals(1, rule.size()); Declaration decl = (Declaration)rule.get(0); diff --git a/libs/braille-css/src/test/java/VolumesTest.java b/libs/braille-css/src/test/java/VolumesTest.java index 8bd555d88b..7226549c7b 100644 --- a/libs/braille-css/src/test/java/VolumesTest.java +++ b/libs/braille-css/src/test/java/VolumesTest.java @@ -10,7 +10,6 @@ import cz.vutbr.web.csskit.antlr.CSSSource; import cz.vutbr.web.csskit.antlr.DefaultCSSSourceReader; -import org.daisy.braille.css.BrailleCSSDeclarationTransformer; import org.daisy.braille.css.BrailleCSSParserFactory; import org.daisy.braille.css.RuleVolume; import org.daisy.braille.css.RuleVolumeArea; @@ -24,8 +23,9 @@ public class VolumesTest { public VolumesTest() { - CSSFactory.registerSupportedCSS(new SupportedBrailleCSS()); - CSSFactory.registerDeclarationTransformer(new BrailleCSSDeclarationTransformer()); + SupportedBrailleCSS css = new SupportedBrailleCSS(); + CSSFactory.registerSupportedCSS(css); + CSSFactory.registerDeclarationTransformer(css); } @Test diff --git a/libs/jstyleparser/.gitrepo b/libs/jstyleparser/.gitrepo index c155453c36..9809bb49b1 100644 --- a/libs/jstyleparser/.gitrepo +++ b/libs/jstyleparser/.gitrepo @@ -6,6 +6,6 @@ [subrepo] remote = git@github.com:daisy/jStyleParser.git branch = org.daisy.libs - commit = bb4ea87b6115c0d69c20ef518c75ea81df368e7b - parent = 5e400531ba1114f98a5a628c10624b088473ca4e + commit = 5072a3b4c936afbad9adc0135a2c43c3b570bef7 + parent = e8afbb7b8dfb53a7609dc33a25dde68aeb5741ea cmdver = 0.3.1 diff --git a/libs/jstyleparser/pom.xml b/libs/jstyleparser/pom.xml index e87da2fdc3..28cc9153d0 100644 --- a/libs/jstyleparser/pom.xml +++ b/libs/jstyleparser/pom.xml @@ -12,7 +12,7 @@ org.daisy.libs jstyleparser - 1.20-p21 + 1.20-p22-SNAPSHOT bundle jStyleParser jStyleParser is a CSS parser written in Java. It has its own application interface that is designed to allow an efficient CSS processing in Java and mapping the values to the Java data types. It parses CSS 2.1 style sheets into structures that can be efficiently assigned to DOM elements. It is intended be the primary CSS parser for the CSSBox library. While handling errors, it is user agent conforming according to the CSS specification. @@ -76,7 +76,7 @@ https://github.com/radkovo/jStyleParser scm:git:git@github.com:radkovo/jStyleParser.git scm:git:git@github.com:radkovo/jStyleParser.git - jStyleParser-1.20-p21 + HEAD diff --git a/libs/jstyleparser/src/main/antlr3/CSSLexer.g b/libs/jstyleparser/src/main/antlr3/CSSLexer.g index 4261022d85..c936f6e83d 100644 --- a/libs/jstyleparser/src/main/antlr3/CSSLexer.g +++ b/libs/jstyleparser/src/main/antlr3/CSSLexer.g @@ -124,7 +124,7 @@ tokens { @Override public void emitErrorMessage(String msg) { - log.info("ANTLR: {}", msg); + log.warn("ANTLR: {}", msg); } /** diff --git a/libs/jstyleparser/src/main/antlr3/CSSParser.g b/libs/jstyleparser/src/main/antlr3/CSSParser.g index bd9fbc87fe..c35205780b 100644 --- a/libs/jstyleparser/src/main/antlr3/CSSParser.g +++ b/libs/jstyleparser/src/main/antlr3/CSSParser.g @@ -50,11 +50,27 @@ options { this.tnr = new cz.vutbr.web.csskit.antlr.CSSTreeNodeRecovery(this, input, state, adaptor, log); } + private boolean emitErrorAsDebugMessage = false; + @Override public void emitErrorMessage(String msg) { - log.info("ANTLR: {}", msg); + if (emitErrorAsDebugMessage) + log.debug("ANTLR: {}", msg); + else + log.warn("ANTLR: {}", msg); } - + + @Override + public void displayRecognitionError(String[] tokenNames, RecognitionException e) { + cz.vutbr.web.csskit.antlr.CSSToken location = null; { + if (e.token instanceof cz.vutbr.web.csskit.antlr.CSSToken) + location = (cz.vutbr.web.csskit.antlr.CSSToken)e.token; + } + emitErrorMessage( + (location != null ? ("at " + cz.vutbr.web.css.SourceLocator.toString(location.getSourceLocator()) + ": ") : "") + + getErrorMessage(e, tokenNames)); + } + /** * Obtains the current lexer state from current token */ @@ -118,9 +134,13 @@ atstatement | unknown_atrule ; catch [RecognitionException re] { - final BitSet follow = BitSet.of(RCURLY, SEMICOLON); - retval.tree = tnr.invalidFallbackGreedy(INVALID_ATSTATEMENT, - "INVALID_ATSTATEMENT", follow, re); + final BitSet follow = BitSet.of(RCURLY, SEMICOLON); + try { + emitErrorAsDebugMessage = true; + retval.tree = tnr.invalidFallbackGreedy(INVALID_ATSTATEMENT, "INVALID_ATSTATEMENT", follow, re); + } finally { + emitErrorAsDebugMessage = false; + } } import_uri @@ -172,7 +192,12 @@ media ; catch [RecognitionException re] { final BitSet follow = BitSet.of(COMMA, LCURLY, SEMICOLON); - retval.tree = tnr.invalidFallback(INVALID_STATEMENT, "INVALID_STATEMENT", follow, cz.vutbr.web.csskit.antlr.CSSLexerState.RecoveryMode.BALANCED, null, re); + try { + emitErrorAsDebugMessage = true; + retval.tree = tnr.invalidFallback(INVALID_STATEMENT, "INVALID_STATEMENT", follow, cz.vutbr.web.csskit.antlr.CSSLexerState.RecoveryMode.BALANCED, null, re); + } finally { + emitErrorAsDebugMessage = false; + } } media_query @@ -185,7 +210,12 @@ media_term ; catch [RecognitionException re] { final BitSet follow = BitSet.of(COMMA, LCURLY, SEMICOLON); - retval.tree = tnr.invalidFallback(INVALID_STATEMENT, "INVALID_STATEMENT", follow, cz.vutbr.web.csskit.antlr.CSSLexerState.RecoveryMode.RULE, null, re); + try { + emitErrorAsDebugMessage = true; + retval.tree = tnr.invalidFallback(INVALID_STATEMENT, "INVALID_STATEMENT", follow, cz.vutbr.web.csskit.antlr.CSSLexerState.RecoveryMode.RULE, null, re); + } finally { + emitErrorAsDebugMessage = false; + } } media_expression @@ -194,8 +224,12 @@ media_expression ; catch [RecognitionException re] { final BitSet follow = BitSet.of(RPAREN, SEMICOLON); - retval.tree = tnr.invalidFallbackGreedy(INVALID_STATEMENT, - "INVALID_STATEMENT", follow, re); + try { + emitErrorAsDebugMessage = true; + retval.tree = tnr.invalidFallbackGreedy(INVALID_STATEMENT, "INVALID_STATEMENT", follow, re); + } finally { + emitErrorAsDebugMessage = false; + } } media_rule @@ -208,8 +242,12 @@ unknown_atrule ; catch [RecognitionException re] { final BitSet follow = BitSet.of(RCURLY); - retval.tree = tnr.invalidFallbackGreedy(INVALID_ATSTATEMENT, - "INVALID_ATSTATEMENT", follow, cz.vutbr.web.csskit.antlr.CSSLexerState.RecoveryMode.BALANCED, null, re); + try { + emitErrorAsDebugMessage = true; + retval.tree = tnr.invalidFallbackGreedy(INVALID_ATSTATEMENT, "INVALID_ATSTATEMENT", follow, cz.vutbr.web.csskit.antlr.CSSLexerState.RecoveryMode.BALANCED, null, re); + } finally { + emitErrorAsDebugMessage = false; + } } ruleset @@ -241,8 +279,13 @@ declaration ; catch [RecognitionException re] { final BitSet follow = BitSet.of(SEMICOLON, RCURLY); //recover on the declaration end or rule end - //not greedy - the final ; or } must remain for properly finishing the declaration/rule - retval.tree = tnr.invalidFallback(INVALID_DECLARATION, "INVALID_DECLARATION", follow, cz.vutbr.web.csskit.antlr.CSSLexerState.RecoveryMode.DECL, begin, re); + try { + emitErrorAsDebugMessage = true; + //not greedy - the final ; or } must remain for properly finishing the declaration/rule + retval.tree = tnr.invalidFallback(INVALID_DECLARATION, "INVALID_DECLARATION", follow, cz.vutbr.web.csskit.antlr.CSSLexerState.RecoveryMode.DECL, begin, re); + } finally { + emitErrorAsDebugMessage = false; + } } important @@ -250,7 +293,12 @@ important ; catch [RecognitionException re] { final BitSet follow = BitSet.of(RCURLY, SEMICOLON); - retval.tree = tnr.invalidFallback(INVALID_DIRECTIVE, "INVALID_DIRECTIVE", follow, cz.vutbr.web.csskit.antlr.CSSLexerState.RecoveryMode.RULE, null, re); + try { + emitErrorAsDebugMessage = true; + retval.tree = tnr.invalidFallback(INVALID_DIRECTIVE, "INVALID_DIRECTIVE", follow, cz.vutbr.web.csskit.antlr.CSSLexerState.RecoveryMode.RULE, null, re); + } finally { + emitErrorAsDebugMessage = false; + } } property @@ -265,13 +313,22 @@ terms if (functLevel == 0) { final BitSet follow = BitSet.of(RCURLY, SEMICOLON); - retval.tree = tnr.invalidFallbackGreedy(INVALID_STATEMENT, - "INVALID_STATEMENT", follow, re); + try { + emitErrorAsDebugMessage = true; + retval.tree = tnr.invalidFallbackGreedy(INVALID_STATEMENT, "INVALID_STATEMENT", follow, re); + } finally { + emitErrorAsDebugMessage = false; + } } else { final BitSet follow = BitSet.of(RPAREN, RCURLY, SEMICOLON); - retval.tree = tnr.invalidFallbackGreedy(INVALID_STATEMENT, "INVALID_STATEMENT", follow, cz.vutbr.web.csskit.antlr.CSSLexerState.RecoveryMode.FUNCTION, null, re); + try { + emitErrorAsDebugMessage = true; + retval.tree = tnr.invalidFallbackGreedy(INVALID_STATEMENT, "INVALID_STATEMENT", follow, cz.vutbr.web.csskit.antlr.CSSLexerState.RecoveryMode.FUNCTION, null, re); + } finally { + emitErrorAsDebugMessage = false; + } } } @@ -329,8 +386,13 @@ combined_selector : selector ((combinator) selector)* ; catch [RecognitionException re] { - log.warn("INVALID COMBINED SELECTOR"); - reportError(re); + log.debug("INVALID COMBINED SELECTOR"); + try { + emitErrorAsDebugMessage = true; + reportError(re); + } finally { + emitErrorAsDebugMessage = false; + } recover(input,re); } @@ -358,7 +420,12 @@ selector -> ^(SELECTOR selpart+) ; catch [RecognitionException re] { - retval.tree = tnr.invalidFallback(INVALID_SELECTOR, "INVALID_SELECTOR", re); + try { + emitErrorAsDebugMessage = true; + retval.tree = tnr.invalidFallback(INVALID_SELECTOR, "INVALID_SELECTOR", re); + } finally { + emitErrorAsDebugMessage = false; + } } selpart @@ -369,7 +436,12 @@ selpart | INVALID_SELPART ; catch [RecognitionException re] { - retval.tree = tnr.invalidFallback(INVALID_SELPART, "INVALID_SELPART", re); + try { + emitErrorAsDebugMessage = true; + retval.tree = tnr.invalidFallback(INVALID_SELPART, "INVALID_SELPART", re); + } finally { + emitErrorAsDebugMessage = false; + } } attribute @@ -381,7 +453,12 @@ pseudo : pseudocolon^ (IDENT | FUNCTION S!* (IDENT | MINUS? NUMBER | MINUS? INDEX) S!* RPAREN!) ; catch [RecognitionException re] { - retval.tree = tnr.invalidFallback(INVALID_SELPART, "INVALID_SELPART", re); + try { + emitErrorAsDebugMessage = true; + retval.tree = tnr.invalidFallback(INVALID_SELPART, "INVALID_SELPART", re); + } finally { + emitErrorAsDebugMessage = false; + } } pseudocolon diff --git a/libs/jstyleparser/src/main/antlr3/CSSTreeParser.g b/libs/jstyleparser/src/main/antlr3/CSSTreeParser.g index 76df55bef0..2b52096ccb 100644 --- a/libs/jstyleparser/src/main/antlr3/CSSTreeParser.g +++ b/libs/jstyleparser/src/main/antlr3/CSSTreeParser.g @@ -66,7 +66,27 @@ options { public void emitErrorMessage(String msg) { warn("ANTLR: {}", msg); } - + + @Override + public void displayRecognitionError(String[] tokenNames, RecognitionException e) { + cz.vutbr.web.csskit.antlr.CSSToken location = null; { + if (e.token instanceof cz.vutbr.web.csskit.antlr.CSSToken) + location = (cz.vutbr.web.csskit.antlr.CSSToken)e.token; + else if (e.node instanceof CommonErrorNode) { + Token start = ((CommonErrorNode)e.node).start; + if (start instanceof cz.vutbr.web.csskit.antlr.CSSToken) + location = (cz.vutbr.web.csskit.antlr.CSSToken)start; + } else if (e.node instanceof CommonTree) { + if (((CommonTree)e.node).getToken() instanceof cz.vutbr.web.csskit.antlr.CSSToken) { + location = (cz.vutbr.web.csskit.antlr.CSSToken)((CommonTree)e.node).getToken(); + } + } + } + emitErrorMessage( + (location != null ? ("at " + cz.vutbr.web.css.SourceLocator.toString(location.getSourceLocator()) + ": ") : "") + + getErrorMessage(e, tokenNames)); + } + private String extractText(CommonTree token) { return token.getText(); } @@ -158,8 +178,7 @@ options { private void mdcPutPosition(CommonTree token) { if (token != null) { if (token.getToken() instanceof cz.vutbr.web.csskit.antlr.CSSToken) { - cz.vutbr.web.csskit.antlr.CSSToken t = (cz.vutbr.web.csskit.antlr.CSSToken)token.getToken(); - mdcPutPosition(t.getBase(), t.getLine(), t.getCharPositionInLine()); + mdcPutPosition((cz.vutbr.web.csskit.antlr.CSSToken)token.getToken()); } else { mdcRemovePosition(); } @@ -169,6 +188,14 @@ options { curToken = token; } + private void mdcPutPosition(cz.vutbr.web.csskit.antlr.CSSToken token) { + mdcPutPosition(token.getSourceLocator()); + } + + private void mdcPutPosition(cz.vutbr.web.css.SourceLocator loc) { + mdcPutPosition(loc.getURL(), loc.getLineNumber(), loc.getColumnNumber()); + } + private void mdcPutPosition(java.net.URL url, int line, int position) { org.slf4j.MDC.put(MDC_KEY_URL, ""+url); org.slf4j.MDC.put(MDC_KEY_LINE, ""+line); diff --git a/libs/jstyleparser/src/main/java/cz/vutbr/web/css/CSSFactory.java b/libs/jstyleparser/src/main/java/cz/vutbr/web/css/CSSFactory.java index e2cd0cae06..8ea0eaa5d5 100644 --- a/libs/jstyleparser/src/main/java/cz/vutbr/web/css/CSSFactory.java +++ b/libs/jstyleparser/src/main/java/cz/vutbr/web/css/CSSFactory.java @@ -764,14 +764,17 @@ protected void processNode(StyleSheet result, Node current, Object source) try { // embedded style-sheet if (isEmbeddedStyleSheet(elem, media)) { - SourceLocator loc = nodeLocator.apply(elem); - String mediaType = getMediaType(elem); - if (cssReader.supportsMediaType(mediaType, null)) { - result = pf.append( - new CSSSource(extractElementText(elem), mediaType, base, loc.getLineNumber(), loc.getColumnNumber()), - cssReader, - result); - log.debug("Matched embedded CSS style"); + Node firstChild = elem.getFirstChild(); + if (firstChild != null) { + SourceLocator loc = nodeLocator.apply(firstChild); + String mediaType = getMediaType(elem); + if (cssReader.supportsMediaType(mediaType, null)) { + result = pf.append( + new CSSSource(extractElementText(elem), mediaType, base, loc.getLineNumber(), loc.getColumnNumber()), + cssReader, + result); + log.debug("Matched embedded CSS style"); + } } } // linked style-sheet @@ -790,8 +793,9 @@ else if (isLinkedStyleSheet(elem, media)) { else { if (cssReader.supportsMediaType(null, null)) { if (elem.getAttribute("style") != null && elem.getAttribute("style").length() > 0) { + SourceLocator loc = nodeLocator.apply(elem.getAttributeNode("style")); result = pf.append( - new CSSSource(elem.getAttribute("style"), elem, base), + new CSSSource(elem.getAttribute("style"), elem, base, loc.getLineNumber(), loc.getColumnNumber()), cssReader, true, result); @@ -799,8 +803,9 @@ else if (isLinkedStyleSheet(elem, media)) { } } if (elem.getAttribute("XDefaultStyle") != null && elem.getAttribute("XDefaultStyle").length() > 0) { + SourceLocator loc = nodeLocator.apply(elem.getAttributeNode("XDefaultStyle")); result = pf.append( - new CSSSource(elem.getAttribute("XDefaultStyle"), elem, base), + new CSSSource(elem.getAttribute("XDefaultStyle"), elem, base, loc.getLineNumber(), loc.getColumnNumber()), cssReader, false, result); diff --git a/libs/jstyleparser/src/main/java/cz/vutbr/web/css/CSSProperty.java b/libs/jstyleparser/src/main/java/cz/vutbr/web/css/CSSProperty.java index 72b0caa90c..9a4ab9a6f7 100644 --- a/libs/jstyleparser/src/main/java/cz/vutbr/web/css/CSSProperty.java +++ b/libs/jstyleparser/src/main/java/cz/vutbr/web/css/CSSProperty.java @@ -1739,7 +1739,7 @@ private PageBreakInside(String text) { } public boolean inherited() { - return true; + return false; } public boolean equalsInherit() { diff --git a/libs/jstyleparser/src/main/java/cz/vutbr/web/css/SourceLocator.java b/libs/jstyleparser/src/main/java/cz/vutbr/web/css/SourceLocator.java index 90e3a09510..a17b627bb2 100644 --- a/libs/jstyleparser/src/main/java/cz/vutbr/web/css/SourceLocator.java +++ b/libs/jstyleparser/src/main/java/cz/vutbr/web/css/SourceLocator.java @@ -10,15 +10,33 @@ */ public interface SourceLocator { /** - * The URL. + * The URL or {@code null} if not available. */ public URL getURL(); /** - * The line number. + * The line number (0-based) or {@code -1} if not available. */ public int getLineNumber(); /** - * The character position within the line. + * The character position (0-based) within the line or {@code -1} if not available. */ public int getColumnNumber(); + + public static String toString(SourceLocator locator) { + String s = ""; + URL base = locator.getURL(); + if (base != null) + s += base; + else + s += ""; + int line = locator.getLineNumber(); + if (line >= 0) { + s += (":" + line); + int column = locator.getColumnNumber(); + if (column >= 0) { + s += (":" + column); + } + } + return s; + } } diff --git a/libs/jstyleparser/src/main/java/cz/vutbr/web/csskit/antlr/CSSInputStream.java b/libs/jstyleparser/src/main/java/cz/vutbr/web/csskit/antlr/CSSInputStream.java index fdb19f6e2f..884b5ecdc1 100644 --- a/libs/jstyleparser/src/main/java/cz/vutbr/web/csskit/antlr/CSSInputStream.java +++ b/libs/jstyleparser/src/main/java/cz/vutbr/web/csskit/antlr/CSSInputStream.java @@ -193,10 +193,6 @@ public SourceLocator getSourceLocator() { public URL getURL() { return base; } public int getLineNumber() { return line; } public int getColumnNumber() { return column; } - @Override - public String toString() { - return ((base == null) ? "" : base.toString()) + ":" + line + ":" + column; - } }; return loc; } diff --git a/libs/jstyleparser/src/main/java/cz/vutbr/web/csskit/antlr/CSSSource.java b/libs/jstyleparser/src/main/java/cz/vutbr/web/csskit/antlr/CSSSource.java index 51174d6bd1..f4bd1bc867 100644 --- a/libs/jstyleparser/src/main/java/cz/vutbr/web/csskit/antlr/CSSSource.java +++ b/libs/jstyleparser/src/main/java/cz/vutbr/web/csskit/antlr/CSSSource.java @@ -3,6 +3,8 @@ import java.net.URL; import java.nio.charset.Charset; +import cz.vutbr.web.css.SourceLocator; + import org.w3c.dom.Element; /** @@ -45,14 +47,14 @@ public static enum SourceType { public URL base; /** - * Line number of the first character. + * Line number (0-based) of the first character, or {@code -1} if unknown. */ - public int lineOffset = 0; + public int lineOffset = -1; // unknown /** - * Column number of the first character. + * Column number (0-based) of the first character, or {@code -1} if unknown. */ - public int columnOffset = 0; + public int columnOffset = -1; // unknown /** * The media type as specified on the style or link element @@ -60,11 +62,31 @@ public static enum SourceType { */ public String mediaType; - public CSSSource(String source, Element inlineElement, URL base) { + public CSSSource(String source, Element inlineElement, SourceLocator location) { + this(source, + inlineElement, + location != null ? location.getURL() : null, + location != null ? location.getLineNumber() : -1, + location != null ? location.getColumnNumber() : -1); + } + + public CSSSource(String source, Element inlineElement, URL base, int lineOffset, int columnOffset) { this.type = SourceType.INLINE; this.source = source; this.inlineElement = inlineElement; this.base = base; + if (lineOffset >= 0) + this.lineOffset = lineOffset; + if (columnOffset >= 0) + this.columnOffset = columnOffset; + } + + public CSSSource(String source, String mediaType, SourceLocator location) { + this(source, + mediaType, + location != null ? location.getURL() : null, + location != null ? location.getLineNumber() : -1, + location != null ? location.getColumnNumber() : -1); } public CSSSource(String source, String mediaType, URL base, int lineOffset, int columnOffset) { @@ -72,9 +94,9 @@ public CSSSource(String source, String mediaType, URL base, int lineOffset, int this.source = source; this.mediaType = mediaType; this.base = base; - if (lineOffset > 0) + if (lineOffset >= 0) this.lineOffset = lineOffset; - if (columnOffset > 0) + if (columnOffset >= 0) this.columnOffset = columnOffset; } diff --git a/libs/jstyleparser/src/main/java/cz/vutbr/web/csskit/antlr/DefaultCSSSourceReader.java b/libs/jstyleparser/src/main/java/cz/vutbr/web/csskit/antlr/DefaultCSSSourceReader.java index 5a5e5dce28..90ad6d15f8 100644 --- a/libs/jstyleparser/src/main/java/cz/vutbr/web/csskit/antlr/DefaultCSSSourceReader.java +++ b/libs/jstyleparser/src/main/java/cz/vutbr/web/csskit/antlr/DefaultCSSSourceReader.java @@ -43,7 +43,7 @@ public CSSInputStream read(CSSSource source) throws IOException { if (!supportsMediaType(source.mediaType, null)) throw new IllegalArgumentException(); SourceMap sourceMap = null; - if (source.lineOffset != 0 && source.columnOffset != 0) + if (source.lineOffset != 0 || source.columnOffset != 0) sourceMap = new SourceMap() { public SourceLocator get(int line, int column) { return new SourceLocator() { @@ -51,12 +51,21 @@ public URL getURL() { return source.base; } public int getLineNumber() { + if (line < 0) + return -1; + else if (source.lineOffset < 0) + return -1; // offset is unknown so resulting line number is also unknown return line + source.lineOffset; } public int getColumnNumber() { - if (line == 0) - return column + source.columnOffset; - else + if (column < 0) + return -1; + else if (line == 0) { // line numbers are 0-based + if (source.columnOffset < 0) + return -1; // offset is unknown so resulting line number is also unknown + else + return column + source.columnOffset; + } else return column; } }; diff --git a/libs/jstyleparser/src/main/java/cz/vutbr/web/domassign/DeclarationTransformer.java b/libs/jstyleparser/src/main/java/cz/vutbr/web/domassign/DeclarationTransformer.java index 2033490b89..a95d8f1b47 100644 --- a/libs/jstyleparser/src/main/java/cz/vutbr/web/domassign/DeclarationTransformer.java +++ b/libs/jstyleparser/src/main/java/cz/vutbr/web/domassign/DeclarationTransformer.java @@ -436,16 +436,16 @@ protected boolean genericTerm( // sanity check if (sanify) { // check for integer - if (term.getValue() instanceof Integer) { + if (term instanceof TermInteger) { final Integer zero = new Integer(0); - if (zero.compareTo((Integer) term.getValue()) > 0) { + if (zero.compareTo((Integer) ((TermInteger)term).getIntValue()) > 0) { // return false is also possibility // but we will change to zero ((TermInteger) term).setValue(zero); } } // check for float - else if (term.getValue() instanceof Float) { + else if (term instanceof TermNumber) { final Float zero = new Float(0.0f); if (zero.compareTo((Float) term.getValue()) > 0) { // return false is also possibility diff --git a/libs/jstyleparser/src/main/java/cz/vutbr/web/domassign/SingleMapNodeData.java b/libs/jstyleparser/src/main/java/cz/vutbr/web/domassign/SingleMapNodeData.java index b68c469a39..d52badb268 100644 --- a/libs/jstyleparser/src/main/java/cz/vutbr/web/domassign/SingleMapNodeData.java +++ b/libs/jstyleparser/src/main/java/cz/vutbr/web/domassign/SingleMapNodeData.java @@ -4,6 +4,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -38,7 +39,7 @@ public SingleMapNodeData() { public SingleMapNodeData(DeclarationTransformer transformer, SupportedCSS css) { this.transformer = transformer; this.css = css; - this.map = new HashMap(css.getTotalProperties(), 1.0f); + this.map = new HashMap(css != null ? css.getTotalProperties() : 16, 1.0f); } public T getProperty(String name) { @@ -52,39 +53,14 @@ public T getProperty(String name, Quadruple q = map.get(name); if(q==null) return null; - - CSSProperty tmp; - - if(includeInherited) { - if(q.curProp!=null) tmp = q.curProp; - else tmp = q.inhProp; - } - else { - tmp = q.curProp; - } - - // this will cast to inferred type - // if there is no inferred type, cast to CSSProperty is safe - // otherwise the possibility having wrong left side of assignment - // is roughly the same as use wrong dynamic class cast - @SuppressWarnings("unchecked") - T retval = (T) tmp; - return retval; - + return q.getProperty(includeInherited); } public Term getValue(String name, boolean includeInherited) { Quadruple q = map.get(name); - if(q==null) return null; - - if(includeInherited) { - if(q.curValue!=null) return q.curValue; - if(q.curProp!=null) return null; - return q.inhValue; - } - - return q.curValue; + if (q==null) return null; + else return q.getValue(includeInherited); } public > T getValue(Class clazz, String name) { @@ -119,7 +95,7 @@ public NodeData push(Declaration d) { for(String key: properties.keySet()) { Quadruple q = map.get(key); - if(q==null) q = new Quadruple(); + if(q==null) q = new Quadruple(css, key); q.curProp = properties.get(key); q.curValue = terms.get(key); q.curSource = d; @@ -134,22 +110,19 @@ public NodeData push(Declaration d) { } public NodeData concretize() { - - for(String key: map.keySet()) { - Quadruple q = map.get(key); - - // replace current with inherited or defaults - if(q.curProp!=null && q.curProp.equalsInherit()) { - if(q.inhProp==null) q.curProp = css.getDefaultProperty(key); - else { - q.curProp = q.inhProp; - q.curSource = q.inhSource; - } - - if(q.inhValue==null) q.curValue = css.getDefaultValue(key); - else q.curValue = q.inhValue; + return concretize(true, false); + } + + public NodeData concretize(boolean concretizeInherit, boolean concretizeInitial) { + if (concretizeInherit || concretizeInitial) { + Iterator> entries = map.entrySet().iterator(); + while (entries.hasNext()) { + Quadruple q = entries.next().getValue(); + q.concretize(concretizeInherit, concretizeInitial); + if (q.isEmpty()) + // default unknown + entries.remove(); } - map.put(key, q); } return this; } @@ -173,23 +146,10 @@ public NodeData inheritFrom(NodeData parent) throws ClassCastException{ // create new quadruple if this do not contain one // for this property - if(q==null) q = new Quadruple(); - - boolean forceInherit = (q.curProp != null && q.curProp.equalsInherit()); + if(q==null) q = new Quadruple(qp.getDefault()); - //try the inherited value of the parent - if(qp.inhProp!=null && (qp.inhProp.inherited() || forceInherit)) { - q.inhProp = qp.inhProp; - q.inhValue = qp.inhValue; - q.inhSource = qp.inhSource; - } + q.inheritFrom(qp); - //try the declared property of the parent - if(qp.curProp!=null && (qp.curProp.inherited() || forceInherit)) { - q.inhProp = qp.curProp; - q.inhValue = qp.curValue; - q.inhSource = qp.curSource; - } // insert/replace only if contains inherited/original // value if(!q.isEmpty()) @@ -249,14 +209,7 @@ public Declaration getSourceDeclaration(String name, boolean includeInherited) if (q == null) return null; else - { - if(includeInherited) { - if(q.curSource!=null) return q.curSource; - return q.inhSource; - } - else - return q.curSource; - } + return q.getSourceDeclaration(includeInherited); } @Override @@ -268,21 +221,153 @@ public Object clone() { throw new InternalError("coding error"); } } - clone.map = new HashMap(css.getTotalProperties(), 1.0f); + clone.map = new HashMap(css != null ? css.getTotalProperties() : 16, 1.0f); for (String key : map.keySet()) clone.map.put(key, (Quadruple)map.get(key).clone()); return clone; } - static class Quadruple implements Cloneable { - CSSProperty inhProp = null; - CSSProperty curProp = null; - Term inhValue = null; - Term curValue = null; - Declaration inhSource = null; - Declaration curSource = null; + public static class Quadruple implements Cloneable { + protected CSSProperty inhProp = null; + protected CSSProperty curProp = null; + protected Term inhValue = null; + protected Term curValue = null; + protected Declaration inhSource = null; + protected Declaration curSource = null; + private Quadruple defaultValue = null; + private final SupportedCSS css; + private final String key; + + public Quadruple() { + this.css = null; + this.key = null; + } + + public Quadruple(SupportedCSS css, String key) { + if (css == null || key == null) + throw new IllegalArgumentException(); + this.css = css; + this.key = key; + } - public Quadruple() { + /** + * @param defaultValue assumed to be immutable + */ + public Quadruple(Quadruple defaultValue) { + if (defaultValue == null) + throw new IllegalArgumentException(); + this.defaultValue = defaultValue; + // these variables will not be used + this.css = null; + this.key = null; + } + + public T getProperty(boolean includeInherited) { + CSSProperty prop; { + if (curProp != null) + prop = curProp; + else if (includeInherited) + prop = inhProp; + else + prop = null; + } + // this will cast to inferred type + // if there is no inferred type, cast to CSSProperty is safe + // otherwise the possibility having wrong left side of assignment + // is roughly the same as use wrong dynamic class cast + @SuppressWarnings("unchecked") + T retval = (T)prop; + return retval; + } + + public Term getValue(boolean includeInherited) { + if (curValue != null) + return curValue; + else if (curProp != null) + return null; + else if (includeInherited) + return inhValue; + else + return null; + } + + public Declaration getSourceDeclaration(boolean includeInherited) { + if (curSource != null) + return curSource; + else if (includeInherited) + return inhSource; + else + return null; + } + + public void concretize() { + concretize(true, false); + } + + public void concretize(boolean concretizeInherit, boolean concretizeInitial) { + if (curProp != null) { + if (concretizeInherit && curProp.equalsInherit()) { + // replace current with inherited or defaults + if (inhProp != null) { + curProp = inhProp; + curValue = inhValue; + curSource = inhSource; + } else if (defaultValue != null) { + curProp = defaultValue.curProp; + curValue = defaultValue.curValue; + } else if (css != null) { + curProp = css.getDefaultProperty(key); + curValue = css.getDefaultValue(key); + defaultValue = this; + } else { + // default not known + curProp = null; + defaultValue = this; + } + } else if (concretizeInitial && curProp.equalsInitial()) { + // replace current with defaults + if (css != null) { + curProp = css.getDefaultProperty(key); + curValue = css.getDefaultValue(key); + defaultValue = this; + } else { + // default not known + curProp = null; + defaultValue = this; + } + } + } + } + + public void inheritFrom(Quadruple parent) { + boolean forceInherit = curProp != null && curProp.equalsInherit(); + // try the inherited value of the parent + if (parent.inhProp != null && (parent.inhProp.inherited() || forceInherit)) { + inhProp = parent.inhProp; + inhValue = parent.inhValue; + inhSource = parent.inhSource; + } + // try the declared property of the parent + if (parent.curProp != null && (parent.curProp.inherited() || forceInherit)) { + inhProp = parent.curProp; + inhValue = parent.curValue; + inhSource = parent.curSource; + } + } + + public Quadruple getDefault() { + if (defaultValue == null) { + if (css != null) { + defaultValue = new Quadruple(css, key); + defaultValue.curProp = css.getDefaultProperty(key); + defaultValue.curValue = css.getDefaultValue(key); + } else { + defaultValue = new Quadruple(); + } + defaultValue.curSource = null; + defaultValue.defaultValue = defaultValue; + } + return defaultValue; } public boolean isEmpty() { diff --git a/modules/.gitrepo b/modules/.gitrepo index c8f316dc63..263b2cdcf7 100644 --- a/modules/.gitrepo +++ b/modules/.gitrepo @@ -7,5 +7,5 @@ remote = git@github.com:daisy/pipeline-modules.git branch = master commit = a22831f2190433aa801f9ff15e78c66eb19a06c0 - parent = 7be99c50f698a26c4fb0062ac80835cdf66a7597 + parent = 3a2a8a73260f37a841df4e835bd88fb8b08e1870 cmdver = 0.3.1 diff --git a/modules/audio/audio-common/src/main/resources/META-INF/catalog.xml b/modules/audio/audio-common/src/main/resources/META-INF/catalog.xml index 659f0161ca..d39344e74d 100644 --- a/modules/audio/audio-common/src/main/resources/META-INF/catalog.xml +++ b/modules/audio/audio-common/src/main/resources/META-INF/catalog.xml @@ -1,7 +1,4 @@ - - - diff --git a/modules/bom/pom.xml b/modules/bom/pom.xml index 8bc9cf4075..ac9ccc4983 100644 --- a/modules/bom/pom.xml +++ b/modules/bom/pom.xml @@ -33,7 +33,7 @@ org.daisy.pipeline.modules audio-common - 5.1.4 + 5.1.5-SNAPSHOT org.daisy.pipeline.modules @@ -47,17 +47,17 @@ org.daisy.pipeline.modules.braille braille-common - 5.0.1 + 5.0.2-SNAPSHOT org.daisy.pipeline.modules.braille braille-css-utils - 3.1.1 + 4.0.0-SNAPSHOT org.daisy.pipeline.modules.braille dotify-utils - 6.2.3 + 6.2.4-SNAPSHOT @@ -87,7 +87,7 @@ org.daisy.pipeline.modules.braille liblouis-utils - 6.2.1 + 6.2.2-SNAPSHOT org.daisy.pipeline.modules.braille @@ -110,7 +110,7 @@ org.daisy.pipeline.modules.braille pef-utils - 6.0.1 + 6.0.2-SNAPSHOT org.daisy.pipeline.modules.braille @@ -124,47 +124,47 @@ org.daisy.pipeline.modules common-utils - 3.1.1 + 3.1.2-SNAPSHOT org.daisy.pipeline.modules file-utils - 4.3.0 + 4.3.1-SNAPSHOT org.daisy.pipeline.modules fileset-utils - 6.1.3 + 6.1.4-SNAPSHOT org.daisy.pipeline.modules html-utils - 6.4.0 + 6.4.1-SNAPSHOT org.daisy.pipeline.modules image-utils - 1.0.7 + 1.0.8-SNAPSHOT org.daisy.pipeline.modules mathml-utils - 1.0.2 + 1.0.3-SNAPSHOT org.daisy.pipeline.modules mediatype-utils - 2.0.1 + 2.0.2-SNAPSHOT org.daisy.pipeline.modules validation-utils - 2.0.0 + 2.0.1-SNAPSHOT org.daisy.pipeline.modules zip-utils - 2.1.7 + 2.1.8-SNAPSHOT @@ -212,67 +212,67 @@ org.daisy.pipeline.modules ace-adapter - 1.0.8 + 1.0.9-SNAPSHOT org.daisy.pipeline.modules asciimath-utils - 1.0.10 + 1.0.11-SNAPSHOT org.daisy.pipeline.modules css-utils - 5.4.0 + 6.0.0-SNAPSHOT org.daisy.pipeline.modules daisy202-utils - 1.6.3 + 1.6.4-SNAPSHOT org.daisy.pipeline.modules daisy3-utils - 4.1.5 + 4.1.6-SNAPSHOT org.daisy.pipeline.modules dtbook-utils - 5.1.0 + 5.1.1-SNAPSHOT org.daisy.pipeline.modules epub-utils - 2.2.0 + 2.2.1-SNAPSHOT org.daisy.pipeline.modules epubcheck-adapter - 1.1.12 + 1.1.13-SNAPSHOT org.daisy.pipeline.modules epub3-to-html - 1.0.0 + 1.0.1-SNAPSHOT org.daisy.pipeline.modules smil-utils - 4.0.1 + 4.0.2-SNAPSHOT org.daisy.pipeline.modules metadata-utils - 2.0.1 + 2.0.2-SNAPSHOT org.daisy.pipeline.modules odf-utils - 1.0.3 + 1.0.4-SNAPSHOT org.daisy.pipeline.modules zedai-utils - 1.2.1 + 1.2.2-SNAPSHOT - - - - - diff --git a/modules/braille/braille-common/src/main/resources/xml/abstract-block-translator.xsl b/modules/braille/braille-common/src/main/resources/xml/abstract-block-translator.xsl index 0e92b73b40..2936ceac7e 100644 --- a/modules/braille/braille-common/src/main/resources/xml/abstract-block-translator.xsl +++ b/modules/braille/braille-common/src/main/resources/xml/abstract-block-translator.xsl @@ -2,6 +2,8 @@ @@ -21,94 +23,81 @@ + + + + - - - - - - - - + + + + - + - - - - + + + + + + + + - - - - + + + + + + + + "/> + + + + + + + - - - - - - - - + + + + + + + + "/> + + + - - - - - - + + + + + - - - - - - - + + - - - - - - - - - - - - - - - - - @@ -119,7 +108,9 @@ - + + + @@ -135,12 +126,6 @@ - - - - @@ -150,9 +135,10 @@ - - + + + @@ -165,350 +151,119 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - + + + + + + + + - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - string() function not supported in string-set property - - - - counter() function not supported in string-set property - - - - target-text() function not supported in string-set property - - - - target-string() function not supported in string-set property - - - - target-counter() function not supported in string-set property - - - - target-content() function not supported in string-set property - - - - leader() function not supported in string-set property - - - - () function not supported in string-set property - - - - - - - + - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - - + + + + + - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - + + + + + + + + "/> + + + + + + + + + - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - @@ -516,47 +271,31 @@ - - - + + + + + + + + + + + + - - - - - - - - - - - - <_> - - - - - - - - - - - - + + - + <_> + + + + @@ -564,75 +303,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <_/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/modules/braille/braille-common/src/main/resources/xml/block-translator-from-text-transform.xsl b/modules/braille/braille-common/src/main/resources/xml/block-translator-from-text-transform.xsl index 7babe2c2ef..063a2c6072 100644 --- a/modules/braille/braille-common/src/main/resources/xml/block-translator-from-text-transform.xsl +++ b/modules/braille/braille-common/src/main/resources/xml/block-translator-from-text-transform.xsl @@ -2,65 +2,24 @@ - + - + + - + - - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/modules/braille/braille-common/src/main/resources/xml/block-translator.xpl b/modules/braille/braille-common/src/main/resources/xml/block-translator.xpl index 8c64113a3b..a30fc78884 100644 --- a/modules/braille/braille-common/src/main/resources/xml/block-translator.xpl +++ b/modules/braille/braille-common/src/main/resources/xml/block-translator.xpl @@ -1,19 +1,31 @@ - + exclude-inline-prefixes="#all" + type="px:block-translate"> - + + + px:css-parse-properties + + - + + + Make css:before and css:after elements from pseudo-element rules. + + + + + + + - + @@ -21,4 +33,13 @@ + + + Convert css:before and css:after elements back to pseudo-element rules. + + + + + + diff --git a/modules/braille/braille-common/src/main/resources/xml/collapse-pseudo-elements.xsl b/modules/braille/braille-common/src/main/resources/xml/collapse-pseudo-elements.xsl new file mode 100644 index 0000000000..3b44816cea --- /dev/null +++ b/modules/braille/braille-common/src/main/resources/xml/collapse-pseudo-elements.xsl @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/braille/braille-common/src/main/resources/xml/expand-pseudo-elements.xsl b/modules/braille/braille-common/src/main/resources/xml/expand-pseudo-elements.xsl new file mode 100644 index 0000000000..dc4e309dcc --- /dev/null +++ b/modules/braille/braille-common/src/main/resources/xml/expand-pseudo-elements.xsl @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_> + + + + + + + + + + + + + + + + diff --git a/modules/braille/braille-common/src/main/resources/xml/library.xsl b/modules/braille/braille-common/src/main/resources/xml/library.xsl index 39b26c2192..7c91870b4c 100644 --- a/modules/braille/braille-common/src/main/resources/xml/library.xsl +++ b/modules/braille/braille-common/src/main/resources/xml/library.xsl @@ -1,8 +1,7 @@ + xmlns:pf="http://www.daisy.org/ns/pipeline/functions"> - + + + + + + + diff --git a/modules/braille/braille-common/src/test/resources/phony.xsl b/modules/braille/braille-common/src/test/resources/phony.xsl deleted file mode 100644 index daea49a78f..0000000000 --- a/modules/braille/braille-common/src/test/resources/phony.xsl +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/modules/braille/braille-common/src/test/resources/uppercase-block-translator.xsl b/modules/braille/braille-common/src/test/resources/uppercase-block-translator.xsl index 73c6d9e47d..6a212d8c29 100644 --- a/modules/braille/braille-common/src/test/resources/uppercase-block-translator.xsl +++ b/modules/braille/braille-common/src/test/resources/uppercase-block-translator.xsl @@ -2,63 +2,58 @@ - + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/modules/braille/braille-common/src/test/xprocspec/test_abstract-block-translator.xprocspec b/modules/braille/braille-common/src/test/xprocspec/test_abstract-block-translator.xprocspec new file mode 100644 index 0000000000..11f009d7eb --- /dev/null +++ b/modules/braille/braille-common/src/test/xprocspec/test_abstract-block-translator.xprocspec @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + foobar +

foo

+ ii +
+
+
+
+ + + + + + + FOOBAR +

FOO

+ II +
+
+
+
+ + + + + + +
+
+
+
+
+ + + + + + +
+
+
+
+
+ + + + + +
+

foobar

+

busstopp

+

+

+
+
+
+ + + + + +
+

FOO=BAR

+

<_ style="braille-charset: unicode; hyphens: auto; text-transform: auto">busstopp

+

+

+
+
+
+ + + + + + +
+ + + + + + + + + + +
+ + + + + + + + + + +
+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

foo⠃⠁⠗

+

fooBAR

+

foo

+

foo

+
+
+
+
+ + + + + + +

FOOBAR

+

FOOBAR

+

FOO

+

FOO

+
+
+
+
+ + diff --git a/modules/braille/braille-common/src/test/xspec/test_abstract-block-translator.xspec b/modules/braille/braille-common/src/test/xspec/test_abstract-block-translator.xspec deleted file mode 100644 index e8b03f7dab..0000000000 --- a/modules/braille/braille-common/src/test/xspec/test_abstract-block-translator.xspec +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - - - foobar -

foo

- ii -
-
- - - FOOBAR -

FOO

- II -
-
-
- - - - -
-
-
- - -
-
-
-
- - - -
-

foobar

-
-
- -
-

FOO=BAR

-
-
-
- - - - -
- - - - -
- - - - - - - -
- - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

foo⠃⠁⠗

-

fooBAR

-

foo

-

foo

-
-
- - -

FOOBAR

-

FOOBAR

-

FOO

-

FOO

-
-
-
- - diff --git a/modules/braille/braille-common/src/test/xspec/test_text-transform.xspec b/modules/braille/braille-common/src/test/xspec/test_text-transform.xspec index ca045ead8a..96a44b0796 100644 --- a/modules/braille/braille-common/src/test/xspec/test_text-transform.xspec +++ b/modules/braille/braille-common/src/test/xspec/test_text-transform.xspec @@ -1,14 +1,15 @@ + xmlns:StyledText="org.daisy.pipeline.braille.css.xpath.StyledText" + stylesheet="../../main/resources/xml/library.xsl"> - + - + diff --git a/modules/braille/braille-css-utils/pom.xml b/modules/braille/braille-css-utils/pom.xml index 8fb86fe260..0ff3dd2f62 100644 --- a/modules/braille/braille-css-utils/pom.xml +++ b/modules/braille/braille-css-utils/pom.xml @@ -12,7 +12,7 @@ org.daisy.pipeline.modules.braille braille-css-utils - 3.1.2-SNAPSHOT + 4.0.0-SNAPSHOT bundle DP2 Braille Modules :: braille-css-utils @@ -42,6 +42,10 @@ commons-io commons-io + + org.daisy.pipeline + saxon-adapter + org.daisy.libs saxon-he @@ -50,13 +54,9 @@ org.slf4j slf4j-api - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules @@ -68,8 +68,8 @@ org.daisy.pipeline.modules.impl.Module_braille_css_utils, org.daisy.pipeline.braille.css.impl.BrailleCssCascader, - org.daisy.pipeline.braille.css.calabash.impl.CssShiftIdStep$Provider, - org.daisy.pipeline.braille.css.calabash.impl.CssShiftStringSetStep$Provider, + org.daisy.pipeline.braille.css.saxon.impl.StyleFunctionProvider, + org.daisy.pipeline.braille.css.saxon.impl.StyledTextFunctionProvider, org.daisy.pipeline.braille.css.saxon.impl.ParseStylesheetDefinition, org.daisy.pipeline.braille.css.saxon.impl.RenderTableByDefinition @@ -112,7 +112,8 @@ - org.daisy.pipeline.modules.impl.Module_test_module + org.daisy.pipeline.modules.impl.Module_test_module, + MockCSSExtension diff --git a/modules/braille/braille-css-utils/src/main/README.md b/modules/braille/braille-css-utils/src/main/README.md index 0de9e39bba..969ec12aef 100644 --- a/modules/braille/braille-css-utils/src/main/README.md +++ b/modules/braille/braille-css-utils/src/main/README.md @@ -23,21 +23,11 @@ of medium "print" is supported though, namely the properties color, font-style, font-weight, text-decoration. -## XMLCalabash XProc steps ([`org.daisy.common.xproc.calabash.XProcStepProvider`](http://daisy.github.io/pipeline/api/org/daisy/common/xproc/calabash/XProcStepProvider.html)) - -- [`{http://www.daisy.org/ns/pipeline/braille-css}shift-id`](java/org/daisy/pipeline/braille/css/calabash/impl/CssShiftIdStep.java) - - Move css:id attributes to inline boxes, see [XProc documentation](resources/xml/shift-id.xpl) - -- [`{http://www.daisy.org/ns/pipeline/braille-css}shift-string-set`](java/org/daisy/pipeline/braille/css/calabash/impl/CssShiftStringSetStep.java) - - Move 'string-set' declarations to inline boxes, see [XProc documentation](resources/xml/shift-string-set.xpl) - ## Saxon XPath functions ([`net.sf.saxon.lib.ExtensionFunctionDefinition`](https://www.saxonica.com/html/documentation9.8/javadoc/net/sf/saxon/lib/ExtensionFunctionDefinition.html)) - [`{http://www.daisy.org/ns/pipeline/braille-css}parse-stylesheet`](java/org/daisy/pipeline/braille/css/saxon/impl/ParseStylesheetDefinition.java) - Parse a style sheet, see [XSLT documentation](resources/xml/base.xsl) + Parse a style sheet, see [XSLT documentation](resources/xml/library.xsl) - [`{http://www.daisy.org/ns/pipeline/braille-css}render-table-by`](java/org/daisy/pipeline/braille/css/saxon/impl/RenderTableByDefinition.java) @@ -46,8 +36,6 @@ - - diff --git a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/CSSStyledText.java b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/CSSStyledText.java index b9e1c4ff99..e2ef0b1e6a 100644 --- a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/CSSStyledText.java +++ b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/CSSStyledText.java @@ -1,31 +1,24 @@ package org.daisy.pipeline.braille.css; -import java.util.function.Function; import java.util.HashMap; import java.util.Locale; import java.util.Map; -import java.lang.reflect.InvocationTargetException; -import org.daisy.braille.css.InlineStyle; -import org.daisy.braille.css.InlineStyle.RuleMainBlock; -import org.daisy.braille.css.RuleTextTransform; import org.daisy.braille.css.SimpleInlineStyle; - -import cz.vutbr.web.css.RuleBlock; +import org.daisy.pipeline.braille.css.impl.BrailleCssSerializer; /** * Note that CSSStyledText objects are not immutable because {@link SimpleInlineStyle} - * is mutable (due to {@link SimpleInlineStyle#removeProperty(String)} and {@link - * SimpleInlineStyle#iterator()} methods). + * is mutable (due to the {@link SimpleInlineStyle#removeProperty(String)} and {@link + * SimpleInlineStyle#iterator()} methods and because the {@link cz.vutbr.web.css.Term} and {@link + * cz.vutbr.web.css.Declaration} objects are not immutable). */ public class CSSStyledText implements Cloneable { private final String text; private final Locale language; private Map textAttributes; - private Style style; - - private static final Function parseCSS = memoize(Style::parse); + private SimpleInlineStyle style; public CSSStyledText(String text, SimpleInlineStyle style) { this(text, style, null, null); @@ -41,30 +34,7 @@ public CSSStyledText(String text, SimpleInlineStyle style, Map te public CSSStyledText(String text, SimpleInlineStyle style, Locale language, Map textAttributes) { this.text = text; - this.style = new Style(); - this.style.properties = style; - this.language = language; - this.textAttributes = textAttributes; - } - - public CSSStyledText(String text, String style) { - this(text, style, null, null); - } - - public CSSStyledText(String text, String style, Locale language) { - this(text, style, language, null); - } - - public CSSStyledText(String text, String style, Map textAttributes) { - this(text, style, null, textAttributes); - } - - public CSSStyledText(String text, String style, Locale language, Map textAttributes) { - this.text = text; - if (style == null) - this.style = null; - else - this.style = parseCSS.apply(style); + this.style = style; this.language = language; this.textAttributes = textAttributes; } @@ -81,26 +51,7 @@ public String getText() { } public SimpleInlineStyle getStyle() { - if (style == null) - return null; - else - return style.properties; - } - - // currently not used - public RuleTextTransform getDefaultTextTransformDefinition() { - if (style == null) - return null; - else - return style.defaultTextTransformDef; - } - - // currently not used - public RuleTextTransform getTextTransformDefinition(String name) { - if (style == null || style.textTransformDefs == null) - return null; - else - return style.textTransformDefs.get(name); + return style; } public Locale getLanguage() { @@ -121,7 +72,7 @@ public CSSStyledText clone() { } } if (style != null) - clone.style = (Style)style.clone(); + clone.style = (SimpleInlineStyle)style.clone(); if (textAttributes != null) clone.textAttributes = new HashMap(textAttributes); return clone; @@ -130,8 +81,8 @@ public CSSStyledText clone() { @Override public String toString() { String s = text; - if (style != null && style.properties != null && !style.properties.isEmpty()) - s += "{" + style.properties + "}"; + if (style != null && !style.isEmpty()) + s += "{" + BrailleCssSerializer.serializeDeclarationList(style) + "}"; if (language != null && !"und".equals(language.toLanguageTag())) s += "{" + language.toLanguageTag() + "}"; return s; @@ -158,12 +109,12 @@ public boolean equals(Object other) { return false; } else if (that.textAttributes != null && !that.textAttributes.isEmpty()) return false; - if (this.style != null && this.style.properties != null && !this.style.properties.isEmpty()) { - if (that.style == null || that.style.properties == null || that.style.properties.isEmpty()) + if (this.style != null && !this.style.isEmpty()) { + if (that.style == null || that.style.isEmpty()) return false; if (!this.style.equals(that.style)) return false; - } else if (that.style != null && that.style.properties != null && !that.style.properties.isEmpty()) + } else if (that.style != null && !that.style.isEmpty()) return false; return true; } @@ -177,99 +128,4 @@ public int hashCode() { hash = prime * hash + (style == null ? 0 : style.hashCode()); return hash; } - - private static class Style implements Cloneable { - - SimpleInlineStyle properties; - RuleTextTransform defaultTextTransformDef; - Map textTransformDefs; - - @Override - public Object clone() { - Style clone; { - try { - clone = (Style)super.clone(); - } catch (CloneNotSupportedException e) { - throw new InternalError("coding error"); - } - } - if (properties != null) - clone.properties = (SimpleInlineStyle)properties.clone(); - return clone; - } - - @Override - // FIXME: don't ignore text-transform defs - public boolean equals(Object other) { - if (this == other) - return true; - if (!(other instanceof Style)) - return false; - Style that = (Style)other; - if (this.properties != null) { - if (that.properties == null) - return false; - if (!this.properties.equals(that.properties)) - return false; - } else if (that.properties != null) - return false; - return true; - } - - @Override - public int hashCode() { - final int prime = 31; - int hash = 1; - hash = prime * hash + (properties == null ? 0 : properties.hashCode()); - return hash; - } - - public static Style parse(String style) { - InlineStyle inlineStyle = new InlineStyle(style); - Style s = new Style(); - s.properties = new SimpleInlineStyle(inlineStyle.getMainStyle()); - for (RuleBlock b : inlineStyle) - if (b instanceof RuleMainBlock) {} // already handled - else if (b instanceof RuleTextTransform) { - RuleTextTransform def = (RuleTextTransform)b; - String name = def.getName(); - if (name != null) { - if (s.textTransformDefs == null) - s.textTransformDefs = new HashMap(); - s.textTransformDefs.put(name, def); - } else - s.defaultTextTransformDef = def; - } else - throw new RuntimeException("Unexpected style: " + b); - return s; - } - } - - private static Function memoize(Function function) { - Map cache = new HashMap(); - return new Function() { - public V apply(K key) { - V value; - if (cache.containsKey(key)) - value = cache.get(key); - else { - value = function.apply(key); - if (value != null) - cache.put(key, value); } - if (value == null) - return null; - else { - try { - return (V)value.getClass().getMethod("clone").invoke(value); } - catch (IllegalAccessException - | IllegalArgumentException - | InvocationTargetException - | NoSuchMethodException - | SecurityException e) { - throw new RuntimeException("Could not invoke clone() method", e); - } - } - } - }; - } } diff --git a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/CounterFunction.java b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/CounterFunction.java new file mode 100644 index 0000000000..15c083578a --- /dev/null +++ b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/CounterFunction.java @@ -0,0 +1,20 @@ +package org.daisy.pipeline.braille.css; + +import java.util.Optional; + +import cz.vutbr.web.css.Term; + +import org.daisy.pipeline.css.CounterStyle; + +/** + * A {@code counter()} function. See
Printing + * Counters in the braille CSS specification. + */ +public interface CounterFunction extends Term { + + public String getCounter(); + + public Optional getStyle(); + +} diff --git a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/CounterSet.java b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/CounterSet.java new file mode 100644 index 0000000000..f566d96122 --- /dev/null +++ b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/CounterSet.java @@ -0,0 +1,11 @@ +package org.daisy.pipeline.braille.css; + +import cz.vutbr.web.css.TermPair; + +/** + * A counter name-value pair. See Manipulating + * Counters in the braille CSS specification. + */ +public interface CounterSet extends TermPair { +} diff --git a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/StyleTransformer.java b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/StyleTransformer.java new file mode 100644 index 0000000000..4db78a4df4 --- /dev/null +++ b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/StyleTransformer.java @@ -0,0 +1,26 @@ +package org.daisy.pipeline.braille.css; + +import org.daisy.braille.css.SimpleInlineStyle; +import org.daisy.braille.css.SupportedBrailleCSS; +import org.daisy.pipeline.braille.css.impl.StyleTransformerImpl; +import org.daisy.pipeline.braille.css.xpath.Style; + +/** + * Transform a style to a different model. + */ +public interface StyleTransformer { + + /** + * @param fromSupportedBrailleCSS input model + * @param toSupportedBrailleCSS output model + */ + public static StyleTransformer of(SupportedBrailleCSS fromSupportedBrailleCSS, + SupportedBrailleCSS toSupportedBrailleCSS) { + return new StyleTransformerImpl(fromSupportedBrailleCSS, toSupportedBrailleCSS); + } + + public SimpleInlineStyle transform(SimpleInlineStyle style); + + public Style transform(Style style); + +} diff --git a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/TextStyleParser.java b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/TextStyleParser.java new file mode 100644 index 0000000000..1590457242 --- /dev/null +++ b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/TextStyleParser.java @@ -0,0 +1,27 @@ +package org.daisy.pipeline.braille.css; + +import org.daisy.braille.css.SimpleInlineStyle; + +import org.daisy.pipeline.braille.css.impl.BrailleCssParser; + +/** + * Parse string to {@link SimpleInlineStyle} for use in {@link CSSStyledText}. + */ +public interface TextStyleParser { + + public SimpleInlineStyle parse(String style); + + /** + * Parse a style and inherit from a parent style. Using this method, rather than using {@link + * #parse(String)} and calling {@link SimpleInlineStyle#inheritFrom} on the result, allows for + * caching to happen. + * + * As opposed to {@link #parse(String)}, this method always concretizes "inherit" values (even + * if the provided parent style is {@code null} or empty). + */ + public SimpleInlineStyle parse(String style, SimpleInlineStyle parent); + + public static TextStyleParser getInstance() { + return BrailleCssParser.getInstance(); + } +} diff --git a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/BrailleCssCascader.java b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/BrailleCssCascader.java index 5522a6ebf2..4df821b179 100644 --- a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/BrailleCssCascader.java +++ b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/BrailleCssCascader.java @@ -13,6 +13,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.Set; @@ -25,7 +26,6 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; -import cz.vutbr.web.css.CSSProperty; import cz.vutbr.web.css.Declaration; import cz.vutbr.web.css.NodeData; import cz.vutbr.web.css.Rule; @@ -39,12 +39,14 @@ import cz.vutbr.web.css.TermIdent; import cz.vutbr.web.css.TermURI; import cz.vutbr.web.csskit.antlr.CSSParserFactory; +import cz.vutbr.web.csskit.DeclarationImpl; import cz.vutbr.web.csskit.RuleFactoryImpl; +import cz.vutbr.web.csskit.TermURIImpl; import cz.vutbr.web.domassign.DeclarationTransformer; -import org.daisy.braille.css.AnyAtRule; -import org.daisy.braille.css.BrailleCSSDeclarationTransformer; +import org.daisy.braille.css.BrailleCSSExtension; import org.daisy.braille.css.BrailleCSSParserFactory; +import org.daisy.braille.css.BrailleCSSParserFactory.Context; import org.daisy.braille.css.BrailleCSSProperty; import org.daisy.braille.css.BrailleCSSRuleFactory; import org.daisy.braille.css.RuleCounterStyle; @@ -53,11 +55,12 @@ import org.daisy.braille.css.RuleVolume; import org.daisy.braille.css.RuleVolumeArea; import org.daisy.braille.css.SelectorImpl.PseudoElementImpl; -import org.daisy.braille.css.SimpleInlineStyle; import org.daisy.braille.css.SupportedBrailleCSS; +import org.daisy.braille.css.VendorAtRule; import org.daisy.common.file.URLs; import org.daisy.common.transform.XMLTransformer; import org.daisy.pipeline.braille.css.SupportedPrintCSS; +import org.daisy.pipeline.braille.css.impl.BrailleCssParser; import org.daisy.pipeline.braille.css.impl.BrailleCssSerializer; import org.daisy.pipeline.css.CssCascader; import org.daisy.pipeline.css.CssPreProcessor; @@ -65,7 +68,14 @@ import org.daisy.pipeline.css.Medium; import org.daisy.pipeline.css.XsltProcessor; +import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -76,6 +86,8 @@ ) public class BrailleCssCascader implements CssCascader { + private static final Logger logger = LoggerFactory.getLogger(BrailleCssCascader.class); + /** * Note that this implementation only supports a very small subset of medium "print", namely the * properties color, font-style, font-weight, text-decoration. @@ -106,7 +118,7 @@ public XMLTransformer newInstance(Medium medium, case EMBOSSED: case BRAILLE: return new Transformer(uriResolver, preProcessor, xsltProcessor, userAndUserAgentStylesheets, medium, attributeName, - brailleParserFactory, brailleRuleFactory, brailleCSS, brailleDeclarationTransformer); + brailleParserFactory, brailleRuleFactory, brailleCSS, brailleCSS); case PRINT: return new Transformer(uriResolver, preProcessor, xsltProcessor, userAndUserAgentStylesheets, medium, attributeName, printParserFactory, printRuleFactory, printCSS, printDeclarationTransformer); @@ -122,13 +134,51 @@ public XMLTransformer newInstance(Medium medium, private static final CSSParserFactory printParserFactory = CSSParserFactory.getInstance(); // medium braille/embossed - private static final SupportedCSS brailleCSS = new SupportedBrailleCSS(false, true); - private static final DeclarationTransformer brailleDeclarationTransformer - = new BrailleCSSDeclarationTransformer(brailleCSS); - private static final RuleFactory brailleRuleFactory = new BrailleCSSRuleFactory(); - private static final CSSParserFactory brailleParserFactory = new BrailleCSSParserFactory(); + private final List brailleCSSExtensions = new ArrayList<>(); + private SupportedBrailleCSS brailleCSS = null; + private BrailleCSSRuleFactory brailleRuleFactory = null; + private BrailleCSSParserFactory brailleParserFactory = null; + private BrailleCssParser brailleCSSParser = null; + + + @Reference( + name = "BrailleCSSExtension", + unbind = "-", + service = BrailleCSSExtension.class, + cardinality = ReferenceCardinality.MULTIPLE, + policy = ReferencePolicy.STATIC + ) + protected void addBrailleCSSExtension(BrailleCSSExtension x) { + logger.debug("Binding BrailleCSSExtension: {}", x); + brailleCSSExtensions.add(x); + } - private static class Transformer extends JStyleParserCssCascader { + @Activate + protected void init() { + boolean allowUnknownVendorExtensions = false; + brailleCSS = new SupportedBrailleCSS(false, true, brailleCSSExtensions, allowUnknownVendorExtensions); + brailleRuleFactory = new BrailleCSSRuleFactory(brailleCSSExtensions, allowUnknownVendorExtensions); + brailleParserFactory = new BrailleCSSParserFactory(brailleRuleFactory); + brailleCSSParser = new BrailleCssParser() { + @Override + public BrailleCSSParserFactory getBrailleCSSParserFactory() { + return brailleParserFactory; + } + @Override + public Optional getSupportedBrailleCSS(Context context) { + switch (context) { + case ELEMENT: + case PAGE: + case VOLUME: + return Optional.of(brailleCSS); + default: + return Optional.empty(); + } + } + }; + } + + private class Transformer extends JStyleParserCssCascader { private final QName attributeName; private final boolean isBrailleCss; @@ -148,7 +198,7 @@ private Transformer(URIResolver resolver, CssPreProcessor preProcessor, XsltProc private Iterable textTransformRules = null; private Iterable hyphenationResourceRules = null; private Iterable counterStyleRules = null; - private Iterable otherAtRules = null; + private Iterable>> otherAtRules = null; protected Map serializeStyle(NodeData mainStyle, Map pseudoStyles, Element context) { if (isBrailleCss && pageRules == null) { @@ -188,7 +238,7 @@ protected Map serializeStyle(NodeData mainStyle, Map>>)(Iterable)Iterables.filter(styleSheet, VendorAtRule.class); } StringBuilder style = new StringBuilder(); if (mainStyle != null) @@ -202,11 +252,11 @@ protected Map serializeStyle(NodeData mainStyle, Map pageRule = getPageRule(mainStyle, pageRules); if (pageRule != null) { - insertPageStyle(style, pageRule, true); } + insertPageStyle(style, pageRule); } else if (isRoot) { pageRule = getPageRule("auto", pageRules); if (pageRule != null) - insertPageStyle(style, pageRule, true); } + insertPageStyle(style, pageRule); } if (isRoot) { Map volumeRule = getVolumeRule("auto", volumeRules); if (volumeRule != null) @@ -217,15 +267,16 @@ else if (isRoot) { insertHyphenationResourceDefinition(style, r); for (RuleCounterStyle r : counterStyleRules) insertCounterStyleDefinition(style, r); - for (AnyAtRule r : otherAtRules) { + for (VendorAtRule> r : otherAtRules) { if (style.length() > 0 && !style.toString().endsWith("} ")) { style.insert(0, "{ "); style.append("} "); } insertAtRule(style, r); }}} - if (!style.toString().replaceAll("\\s+", "").isEmpty()) - return ImmutableMap.of(attributeName, style.toString().trim()); - else + if (style.toString().trim().isEmpty()) return null; + if (style.length() > 1 && style.substring(style.length() - 2).equals("; ")) + style.delete(style.length() - 2, style.length()); + return ImmutableMap.of(attributeName, style.toString().trim()); } protected String serializeValue(Term value) { @@ -256,20 +307,17 @@ public int compare(PseudoElement e1, PseudoElement e2) { } }; - // FIXME: move parts of this to BrailleCssSerializer + // FIXME: make more use of BrailleCssSerializer private static void insertStyle(StringBuilder builder, NodeData nodeData) { - List keys = new ArrayList(nodeData.getPropertyNames()); - keys.remove("page"); - Collections.sort(keys); - for (String key : keys) { - Term value = nodeData.getValue(key, false); - if (value != null) - builder.append(key).append(": ").append(BrailleCssSerializer.toString(value)).append("; "); - else { - CSSProperty prop = nodeData.getProperty(key, false); - if (prop != null) // can be null for unspecified inherited properties - builder.append(key).append(": ").append(prop).append("; "); }} + List properties = new ArrayList(nodeData.getPropertyNames()); + properties.remove("page"); + Collections.sort(properties); + for (String prop : properties) { + String val = BrailleCssSerializer.serializePropertyValue(nodeData, prop, false); + if (val != null) // can be null for unspecified inherited properties + builder.append(prop).append(": ").append(val).append("; "); + } } private static void pseudoElementToString(StringBuilder builder, PseudoElement elem) { @@ -288,51 +336,29 @@ private static void pseudoElementToString(StringBuilder builder, PseudoElement e builder.append("(").append(s).append(")"); }} } - private static void insertPseudoStyle(StringBuilder builder, NodeData nodeData, PseudoElement elem, - Map> pageRules) { + private void insertPseudoStyle(StringBuilder builder, NodeData nodeData, PseudoElement elem, + Map> pageRules) { pseudoElementToString(builder, elem); builder.append(" { "); insertStyle(builder, nodeData); Map pageRule = getPageRule(nodeData, pageRules); if (pageRule != null) - insertPageStyle(builder, pageRule, false); + insertPageStyle(builder, pageRule); + if (builder.substring(builder.length() - 2).equals("; ")) + builder.replace(builder.length() - 2, builder.length(), " "); builder.append("} "); } - private static void insertPageStyle(StringBuilder builder, Map pageRule, boolean topLevel) { + private void insertPageStyle(StringBuilder builder, Map pageRule) { for (RulePage r : pageRule.values()) - insertPageStyle(builder, r, topLevel); + insertPageStyle(builder, r); } - private static void insertPageStyle(StringBuilder builder, RulePage pageRule, boolean topLevel) { - builder.append("@page"); - String pseudo = pageRule.getPseudo(); - if (pseudo != null && !"".equals(pseudo)) - builder.append(":").append(pseudo); - builder.append(" { "); - for (Declaration decl : Iterables.filter(pageRule, Declaration.class)) - insertDeclaration(builder, decl); - for (RuleMargin margin : Iterables.filter(pageRule, RuleMargin.class)) - insertMarginStyle(builder, margin); - builder.append("} "); - } - - private static void insertMarginStyle(StringBuilder builder, RuleMargin ruleMargin) { - builder.append("@").append(ruleMargin.getMarginArea()).append(" { "); - insertStyle(builder, new SimpleInlineStyle(ruleMargin, null, brailleDeclarationTransformer, brailleCSS)); - builder.append("} "); - } - - private static void insertDeclaration(StringBuilder builder, Declaration decl) { - StringBuilder s = new StringBuilder(); - Iterator> it = decl.iterator(); - while (it.hasNext()) { - s.append(BrailleCssSerializer.toString(it.next())); - if (it.hasNext()) s.append(" "); } - builder.append(decl.getProperty()).append(": ").append(s).append("; "); + private void insertPageStyle(StringBuilder builder, RulePage pageRule) { + builder.append(BrailleCssSerializer.toString(pageRule, brailleCSSParser)).append(" "); } - private static Map getPageRule(NodeData nodeData, Map> pageRules) { + private Map getPageRule(NodeData nodeData, Map> pageRules) { BrailleCSSProperty.Page pageProperty; { if (nodeData != null) pageProperty = nodeData.getProperty("page", false); @@ -354,7 +380,7 @@ private static Map getPageRule(NodeData nodeData, Map getPageRule(String name, Map> pageRules) { + private Map getPageRule(String name, Map> pageRules) { Map auto = pageRules == null ? null : pageRules.get("auto"); Map named = null; if (!name.equals("auto")) @@ -386,7 +412,7 @@ private static Map getPageRule(String name, Map from) { + private RulePage makePageRule(String name, String pseudo, List from) { RulePage pageRule = brailleRuleFactory.createPage().setName(name).setPseudo(pseudo); for (RulePage f : from) for (Rule r : f) @@ -424,28 +450,32 @@ private static RuleMargin getRuleMargin(Collection> rule, Stri return null; } - private static void insertVolumeStyle(StringBuilder builder, Map volumeRule, Map> pageRules) { + private void insertVolumeStyle(StringBuilder builder, Map volumeRule, Map> pageRules) { for (Map.Entry r : volumeRule.entrySet()) insertVolumeStyle(builder, r, pageRules); } - private static void insertVolumeStyle(StringBuilder builder, Map.Entry volumeRule, Map> pageRules) { + private void insertVolumeStyle(StringBuilder builder, Map.Entry volumeRule, Map> pageRules) { builder.append("@volume"); String pseudo = volumeRule.getKey(); if (pseudo != null && !"".equals(pseudo)) builder.append(":").append(pseudo); builder.append(" { "); - for (Declaration decl : Iterables.filter(volumeRule.getValue(), Declaration.class)) - insertDeclaration(builder, decl); + String declarations = BrailleCssSerializer.serializeDeclarationList(Iterables.filter(volumeRule.getValue(), Declaration.class)); + if (!declarations.isEmpty()) + builder.append(declarations).append("; "); for (RuleVolumeArea volumeArea : Iterables.filter(volumeRule.getValue(), RuleVolumeArea.class)) insertVolumeAreaStyle(builder, volumeArea, pageRules); + if (builder.substring(builder.length() - 2).equals("; ")) + builder.replace(builder.length() - 2, builder.length(), " "); builder.append("} "); } - private static void insertVolumeAreaStyle(StringBuilder builder, RuleVolumeArea ruleVolumeArea, Map> pageRules) { + private void insertVolumeAreaStyle(StringBuilder builder, RuleVolumeArea ruleVolumeArea, Map> pageRules) { builder.append("@").append(ruleVolumeArea.getVolumeArea().value).append(" { "); StringBuilder innerStyle = new StringBuilder(); Map pageRule = null; + List declarations = new ArrayList<>(); for (Declaration decl : Iterables.filter(ruleVolumeArea, Declaration.class)) if ("page".equals(decl.getProperty())) { StringBuilder s = new StringBuilder(); @@ -455,9 +485,13 @@ private static void insertVolumeAreaStyle(StringBuilder builder, RuleVolumeArea if (it.hasNext()) s.append(" "); } pageRule = getPageRule(s.toString(), pageRules); } else - insertDeclaration(innerStyle, decl); + declarations.add(decl); + if (!declarations.isEmpty()) + innerStyle.append(BrailleCssSerializer.serializeDeclarationList(declarations)).append("; "); if (pageRule != null) - insertPageStyle(innerStyle, pageRule, false); + insertPageStyle(innerStyle, pageRule); + if (innerStyle.length() > 1 && innerStyle.substring(innerStyle.length() - 2).equals("; ")) + innerStyle.replace(innerStyle.length() - 2, innerStyle.length(), " "); builder.append(innerStyle).append("} "); } @@ -466,9 +500,10 @@ private static void insertTextTransformDefinition(StringBuilder builder, RuleTex String name = rule.getName(); if (name != null) builder.append(' ').append(name); builder.append(" { "); - for (Declaration decl : rule) { - if (decl.size() == 1 && decl.get(0) instanceof TermURI) { - TermURI term = (TermURI)decl.get(0); + List declarationList = new ArrayList<>(); + for (Declaration d : rule) { + if (d.size() == 1 && d.get(0) instanceof TermURI) { + TermURI term = (TermURI)d.get(0); URI uri = URLs.asURI(term.getValue()); if (!uri.isAbsolute() && !uri.getSchemeSpecificPart().startsWith("/")) { // relative resource: make absolute and convert to "volatile-file" URI to bypass @@ -485,12 +520,14 @@ private static void insertTextTransformDefinition(StringBuilder builder, RuleTex } catch (IllegalArgumentException e) { // not a file URI } + d = createDeclaration(d.getProperty(), createTermURI(uri)); } - builder.append(decl.getProperty()).append(": ").append("url(\"" + uri + "\")").append("; "); - continue; } - insertDeclaration(builder, decl); + declarationList.add(d); } + String declarations = BrailleCssSerializer.serializeDeclarationList(declarationList); + if (!declarations.isEmpty()) + builder.append(declarations).append(" "); builder.append("} "); } @@ -498,9 +535,10 @@ private static void insertHyphenationResourceDefinition(StringBuilder builder, R builder.append("@hyphenation-resource"); builder.append(":lang(").append(BrailleCssSerializer.serializeLanguageRanges(rule.getLanguageRanges())).append(")"); builder.append(" { "); - for (Declaration decl : rule) { - if (decl.size() == 1 && decl.get(0) instanceof TermURI) { - TermURI term = (TermURI)decl.get(0); + List declarationList = new ArrayList<>(); + for (Declaration d : rule) { + if (d.size() == 1 && d.get(0) instanceof TermURI) { + TermURI term = (TermURI)d.get(0); URI uri = URLs.asURI(term.getValue()); if (!uri.isAbsolute() && !uri.getSchemeSpecificPart().startsWith("/")) { // relative resource: make absolute and convert to "volatile-file" URI to bypass @@ -517,29 +555,53 @@ private static void insertHyphenationResourceDefinition(StringBuilder builder, R } catch (IllegalArgumentException e) { // not a file URI } + try { + d = createDeclaration(d.getProperty(), createTermURI(uri)); + } catch (RuntimeException e) { + e.printStackTrace(); + throw e; + } } - builder.append(decl.getProperty()).append(": ").append("url(\"" + uri + "\")").append("; "); - continue; } - insertDeclaration(builder, decl); + declarationList.add(d); } + String declarations = BrailleCssSerializer.serializeDeclarationList(declarationList); + if (!declarations.isEmpty()) + builder.append(declarations).append(" "); builder.append("} "); } + private static Declaration createDeclaration(String prop, Term val) { + return new DeclarationImpl() {{ + this.property = prop; + this.list = ImmutableList.of(val); + }}; + } + + private static TermURI createTermURI(URI uri) { + return new TermURIImpl() {{ + this.value = uri.toString(); + }}; + } + private static void insertCounterStyleDefinition(StringBuilder builder, RuleCounterStyle rule) { String name = rule.getName(); builder.append("@counter-style ").append(name).append(" { "); - for (Declaration decl : rule) - insertDeclaration(builder, decl); + String declarations = BrailleCssSerializer.serializeDeclarationList(rule); + if (!declarations.isEmpty()) + builder.append(declarations).append(" "); builder.append("} "); } - private static void insertAtRule(StringBuilder builder, AnyAtRule rule) { + private static void insertAtRule(StringBuilder builder, VendorAtRule> rule) { builder.append("@").append(rule.getName()).append(" { "); - for (Declaration decl : Iterables.filter(rule, Declaration.class)) - insertDeclaration(builder, decl); - for (AnyAtRule r : Iterables.filter(rule, AnyAtRule.class)) + String declarations = BrailleCssSerializer.serializeDeclarationList(Iterables.filter(rule, Declaration.class)); + if (!declarations.isEmpty()) + builder.append(declarations).append("; "); + for (VendorAtRule> r : Iterables.filter(rule, VendorAtRule.class)) insertAtRule(builder, r); + if (builder.substring(builder.length() - 2).equals("; ")) + builder.replace(builder.length() - 2, builder.length(), " "); builder.append("} "); } diff --git a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/BrailleCssParser.java b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/BrailleCssParser.java new file mode 100644 index 0000000000..547720075b --- /dev/null +++ b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/BrailleCssParser.java @@ -0,0 +1,790 @@ +package org.daisy.pipeline.braille.css.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.TimeUnit; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; + +import com.google.common.cache.CacheBuilder; + +import cz.vutbr.web.css.CSSProperty; +import cz.vutbr.web.css.CSSProperty.CounterIncrement; +import cz.vutbr.web.css.CSSProperty.CounterSet; +import cz.vutbr.web.css.CSSProperty.CounterReset; +import cz.vutbr.web.css.Declaration; +import cz.vutbr.web.css.NodeData; +import cz.vutbr.web.css.Term; +import cz.vutbr.web.css.TermList; + +import org.daisy.braille.css.BrailleCSSExtension; +import org.daisy.braille.css.BrailleCSSParserFactory; +import org.daisy.braille.css.BrailleCSSParserFactory.Context; +import org.daisy.braille.css.BrailleCSSProperty.Content; +import org.daisy.braille.css.BrailleCSSProperty.StringSet; +import org.daisy.braille.css.BrailleCSSProperty.TextTransform; +import org.daisy.braille.css.BrailleCSSRuleFactory; +import org.daisy.braille.css.PropertyValue; +import org.daisy.braille.css.SimpleInlineStyle; +import org.daisy.braille.css.SupportedBrailleCSS; +import org.daisy.common.spi.ServiceLoader; +import org.daisy.pipeline.braille.css.impl.ContentList.ContentFunction; +import org.daisy.pipeline.braille.css.TextStyleParser; + +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.w3c.dom.Element; + +public abstract class BrailleCssParser implements TextStyleParser { + + private static final Logger logger = LoggerFactory.getLogger(BrailleCssParser.class); + + private static BrailleCssParser INSTANCE_WITH_EXTENSIONS = null; + + public static BrailleCssParser getInstance() { + if (INSTANCE_WITH_EXTENSIONS == null) { + List extensions = new ArrayList<>(); + try { + Iterator i = getCSSExtensions(); + while (i.hasNext()) { + try { + BrailleCSSExtension extension = i.next(); + extensions.add(extension); + logger.debug("Binding BrailleCSSExtension: {}", extension); + } catch (Throwable e) { + logger.error("Error while binding BrailleCSSExtension", e); + } + } + } catch (Throwable e) { + logger.error("Error while binding BrailleCSSExtension services", e); + } + boolean allowUnknownVendorExtensions = false; + BrailleCSSParserFactory parserFactory = new BrailleCSSParserFactory( + new BrailleCSSRuleFactory(extensions, allowUnknownVendorExtensions)); + Map supportedBrailleCSS = new HashMap<>(); + INSTANCE_WITH_EXTENSIONS = new BrailleCssParser() {{ + for (Context context : new Context[]{Context.ELEMENT, Context.PAGE, Context.VOLUME}) + supportedBrailleCSS.put( + context, + new DeepDeclarationTransformer( + context, true, false, extensions, allowUnknownVendorExtensions)); + } + public BrailleCSSParserFactory getBrailleCSSParserFactory() { + return parserFactory; + } + public Optional getSupportedBrailleCSS(Context context) { + return Optional.ofNullable(supportedBrailleCSS.get(context)); + } + }; + } + return INSTANCE_WITH_EXTENSIONS; + } + + abstract BrailleCSSParserFactory getBrailleCSSParserFactory(); + + abstract Optional getSupportedBrailleCSS(Context context); + + private SupportedBrailleCSS supportedBrailleCSS = null; + + public final boolean isSupportedCSSProperty(String propertyName) { + if (supportedBrailleCSS == null) { + Optional o = getSupportedBrailleCSS(Context.ELEMENT); + if (!o.isPresent()) + throw new IllegalStateException(); + supportedBrailleCSS = o.get(); + } + return supportedBrailleCSS.isSupportedCSSProperty(propertyName); + } + + /** + * The returned object is immutable. + */ + public BrailleCssStyle parseInlineStyle(String style, Context context) { + if (context == null) + throw new IllegalArgumentException(); + BrailleCssStyle s = cache.get(context, style); + if (s == null) { + // try if a declaration was cached + if (context == Context.ELEMENT) { + Declaration d = declCache.get(style); + if (d != null) + s = BrailleCssStyle.of(d); + } + if (s == null) + s = BrailleCssStyle.of(this, context, style); + cache.put(context, style, s); + } + return s; + } + + /** + * Concretizes "inherit" even if the parent style is null or empty. + * + * The returned object is immutable. + */ + public BrailleCssStyle parseInlineStyle(String style, Context context, BrailleCssStyle parent) { + if (context != Context.ELEMENT) + throw new IllegalArgumentException(); + ParsedDeclarations parentDecls; { + if (parent == null || parent.declarations == null) + parentDecls = ParsedDeclarations.EMPTY; + else if (!(parent.declarations instanceof ParsedDeclarations)) + throw new IllegalArgumentException(); + else + parentDecls = (ParsedDeclarations)parent.declarations; + } + BrailleCssStyle s = cache.get(context, style, parentDecls, true); + if (s == null) { + s = parseInlineStyle(style, context); + s = s.inheritFrom(parentDecls); + cache.put(context, style, parentDecls, true, s); + } + return s; + } + + /** + * Style assumed to be specified in the context of a (pseudo-)element. + * + * @param context element for evaluating attr() and content() values against. + * @param mutable Whether the caller wishes to mutate the returned declaration. + */ + public Optional parseDeclaration(String property, String value, Element context, boolean mutable) { + String style = String.format("%s: %s", property, value); + Declaration declaration = declCache.get(style); + if (declaration == null) { + declaration = parseInlineStyle(style, Context.ELEMENT).getDeclaration(property); + if (declaration == null) + return Optional.empty(); + declCache.put(style, declaration); + } + if (context != null) { + Declaration evaluated = BrailleCssStyle.evaluateDeclaration(declaration, context); + if (evaluated != declaration) + return Optional.of(evaluated); + } + if (mutable) { + Declaration unlocked = BrailleCssStyle.unlockDeclaration(declaration); + if (unlocked != declaration) + return Optional.of(unlocked); + return Optional.of((Declaration)declaration.clone()); + } + return Optional.of(declaration); + } + + /** + * @param style assumed to be specified in the context of a (pseudo-)element + * @param context element for evaluating attr() and content() values against. + * @param mutable Whether the caller wishes to mutate the returned style object. + */ + public SimpleInlineStyle parseSimpleInlineStyle(String style, Element context, boolean mutable) { + BrailleCssStyle s = parseInlineStyle(style, Context.ELEMENT); + // evaluate attr() and content() values in content and string-set properties + if (context != null) + s = s.evaluate(context); + try { + return s.asSimpleInlineStyle(mutable); + } catch (UnsupportedOperationException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Concretizes "inherit" even if the parent style is null or empty. + */ + public SimpleInlineStyle parseSimpleInlineStyle(String style, + Element context, + SimpleInlineStyle parent, + boolean mutable) { + BrailleCssStyle s = parseInlineStyle( + style, Context.ELEMENT, parent != null ? BrailleCssStyle.of(parent) : null); + // evaluate attr() and content() values in content and string-set properties + if (context != null) + s = s.evaluate(context); + try { + return s.asSimpleInlineStyle(mutable); + } catch (UnsupportedOperationException e) { + throw new IllegalArgumentException(e); + } + } + + @Override // TextStyleParser + public SimpleInlineStyle parse(String style) { + if (style == null) style = ""; + // clone because we make SimpleInlineStyle available and SimpleInlineStyle is mutable (and we want it to be) + return parseSimpleInlineStyle(style, null, true); + } + + @Override // TextStyleParser + public SimpleInlineStyle parse(String style, SimpleInlineStyle parent) { + if (style == null) style = ""; + // clone because we make SimpleInlineStyle available and SimpleInlineStyle is mutable (and we want it to be) + return parseSimpleInlineStyle(style, null, parent, true); + } + + private final Map declCache = CacheBuilder.newBuilder() + .expireAfterAccess(60, TimeUnit.SECONDS) + .build() + .asMap(); + + /* =================================================================== */ + /* The fields and classes below are used only from BrailleCssStyle but */ + /* we keep it in this class because it is all related with parsing. */ + /* =================================================================== */ + + /** + * {@link SimpleInlineStyle} that is part of a {@link BrailleCssStyle} and created + * by {@link BrailleCssParser}. + * + * The purpose of having this class instead of {@link SimpleInlineStyle} is: + * + * 1. to easier recognize styles that were created by {@link BrailleCssParser}. + * + * 2. to control by whom and when styles can be mutated. Objects are mutable, but should be regarded as + * being immutable outside this class, unless the {@code locked} field was set to {@code false}. + */ + static class ParsedDeclarations extends SimpleInlineStyle { + + public static final ParsedDeclarations EMPTY = new ParsedDeclarations(); + private BrailleCssParser parser; + private Context context; + + /** + * @param parser {@link BrailleCssParser} to be used to process declarations that are not {@link + * ParsedDeclaration} instances. If non-{@code null}, assert that declarations that are + * {@link ParsedDeclaration} were created using the given {@link BrailleCssParser}. If + * {@code null}, assert that all {@link ParsedDeclaration} declarations were created with + * the same {@link BrailleCssParser}. + */ + ParsedDeclarations(BrailleCssParser parser, Context context, Iterable declarations) { + super(declarations, null, parser != null ? parser.getSupportedBrailleCSS(context).get() : null); + this.parser = parser; + this.context = context; + checkParserAndContext(); + } + + // for ParsedDeclarations.EMPTY + private ParsedDeclarations() { + super(null, null, null); + parser = null; + context = null; + } + + /** + * Check that all declarations were parsed with the same parser and context, + * and attach parser and context to the Quadruple. + */ + private void checkParserAndContext() { + for (Map.Entry e : map.entrySet()) { + ParsedDeclaration d = new ParsedDeclaration(parser, context, super.get(e.getKey())); + if (parser == null) + parser = d.getParser(); + if (context == null) + context = d.getContext(); + e.setValue(d.quadruple); + } + } + + // This is a bit of a lazy implementation of the object's immutability, because it is never + // actually enforced that the object is not mutated when it is "locked". It is difficult to + // actually enforce it because a SimpleInlineStyle can be mutated in various ways: + // declarations can be removed or values can be mutated. This approach is however safe + // enough, because whenever the object is exposed to the outside (namely through the + // TextStyleParser/CSSStyledText API), a copy is made. When exposed through the XPath API, no + // copying is needed because the XPath API doesn't allow for mutation. + + // object should always be cloned before it is unlocked + boolean locked = true; + + BrailleCssParser getParser() { + return parser; + } + + Context getContext() { + return context; + } + + // override only method of SimpleInlineStyle that creates new instances of PropertyValue + @Override + public PropertyValue get(String property) { + PropertyValue d = super.get(property); + if (d != null) + d = new ParsedDeclaration(parser, context, d); + return d; + } + + public PropertyValue getOrDefault(String property) { + PropertyValue d = get(property); + if (d == null && parser != null) { + SupportedBrailleCSS css = parser.getSupportedBrailleCSS(context).orElse(null); + if (css != null) + return new ParsedDeclaration( + parser, + context, + property, + css.getDefaultProperty(property), + css.getDefaultValue(property), + null); + } + return d; + } + + @Override + public ParsedDeclarations inheritFrom(NodeData parent) { + if (!(parent instanceof ParsedDeclarations)) + throw new IllegalArgumentException(); + ParsedDeclarations r = (ParsedDeclarations)super.inheritFrom(parent); + BrailleCssParser p = ((ParsedDeclarations)parent).parser; + if (r.parser == null) + r.parser = p; + else if (p != null && r.parser != p) + throw new IllegalArgumentException(); + Context c = ((ParsedDeclarations)parent).context; + if (r.context == null) + r.context = c; + else if (c != null && r.context != c) + throw new IllegalArgumentException(); + r.checkParserAndContext(); + if (!r.isEmpty()) { + // Make sure that the resulting SimpleInlineStyle is not based on a parent and that it + // is not concretized because that would result in an exception when inheritFrom() is + // called on it. Note that concretize() does not have the same effect. + // Also perform special inheritance of text-transform. + List list = new ArrayList<>(); + r.forEach(list::add); + ListIterator i = list.listIterator(); + while (i.hasNext()) { + ParsedDeclaration v = (ParsedDeclaration)i.next(); + PropertyValue flat = null; + if (!((ParsedDeclarations)parent).isEmpty() && v.getCSSProperty() instanceof TextTransform) + for (PropertyValue vv : (ParsedDeclarations)parent) + if (vv.getCSSProperty() instanceof TextTransform) { + TextTransformList t = TextTransformList.of(v); + t.inheritFrom(TextTransformList.of(vv)); // this mutates the value + if (t != v.getValue()) + flat = new ParsedDeclaration( + v.getParser(), + v.getContext(), + v.getProperty(), + t.equalsAuto() ? TextTransform.AUTO + : t.equalsInitial() ? TextTransform.INITIAL + : t.equalsNone() ? TextTransform.NONE + : TextTransform.list_values, + t.equalsAuto() || t.equalsInitial() || t.equalsNone() ? null : t, + v.getSourceDeclaration()); + break; + } + if (flat == null) + flat = new ParsedDeclaration( + v.getParser(), + v.getContext(), + v.getProperty(), + v.getCSSProperty(), + v.getValue(), + v.getSourceDeclaration()); + i.set(flat); + } + r = new ParsedDeclarations(r.parser, r.context, list); + } + return r; + } + } + + /** + * Declaration with reference to used parser and parser context. May cache + * itself when it is serialized. + * + * Clones are never cached. + */ + static class ParsedDeclaration extends PropertyValue { + + private final BrailleCssParser parser; + private final Context context; + private final Quadruple quadruple; // to make propertyValue available outside this class + private boolean enableCaching = false; + + /** + * @throws IllegalArgumentException if the used parser and parser context can + * not be derived from the declaration. + */ + ParsedDeclaration(PropertyValue declaration) { + this(null, null, declaration); + } + + /** + * @param parser If {@code null}, parser is derived from declaration + * @param context If {@code null}, context is derived from declaration + */ + ParsedDeclaration(BrailleCssParser parser, Context context, PropertyValue declaration) { + super(declaration); + if (this.propertyValue instanceof Quadruple) { + this.parser = ((Quadruple)this.propertyValue).getParser(); + this.context = ((Quadruple)this.propertyValue).getContext(); + if (parser != null && this.parser != parser) + throw new IllegalArgumentException(); + if (context != null && this.context != context) + throw new IllegalArgumentException(); + } else { + if (parser == null || context == null) + throw new IllegalArgumentException(); + this.parser = parser; + this.context = context; + this.propertyValue = new Quadruple(this.getProperty(), + this.getCSSProperty(), + this.getValue(), + this.getSourceDeclaration(), + this.getSupportedBrailleCSS()); + } + this.quadruple = (Quadruple)this.propertyValue; + this.enableCaching = (this.context == Context.ELEMENT); + } + + ParsedDeclaration(BrailleCssParser parser, + Context context, + String propertyName, + CSSProperty property, + Term value, + Declaration sourceDeclaration) { + this(parser, context, new PropertyValue(propertyName, + property, + value, + sourceDeclaration, + parser.getSupportedBrailleCSS(context).get())); + } + + BrailleCssParser getParser() { + return parser; + } + + Context getContext() { + return context; + } + + @Override + public ParsedDeclaration getDefault() { + PropertyValue d = super.getDefault(); + if (d == null) + return null; + else if (d instanceof ParsedDeclaration) + return (ParsedDeclaration)d; + else + return new ParsedDeclaration(parser, context, d); + } + + private String valueSerialized = null; + private String serialized = null; + + @Override + public String toString() { + if (serialized == null) + valueToString(); // this also sets serialized and updates cache + return serialized; + } + + // for use in BrailleCssSerializer.serializePropertyValue() + String valueToString() { + if (valueSerialized == null) { + valueSerialized = BrailleCssSerializer.serializePropertyValue(new PropertyValue(this)); + serialized = getProperty() + ": " + valueSerialized; + if (enableCaching) + parser.declCache.put(serialized, this); + } else { + // access cache to keep entry longer in it + if (enableCaching) + parser.declCache.get(serialized); + } + return valueSerialized; + } + + @Override + public ParsedDeclaration clone() { + ParsedDeclaration clone = (ParsedDeclaration)super.clone(); + // Clones are never cached because we assume that clones are made to expose a + // declaration to the outside, where they may possibly be mutated. If a clone is made + // for another purpose, such as for running evaluate() on it, caching might be + // desirable. Luckily, evalutation is normally already done before a declaration is + // serialized. An alternative would be to cache copies of the object every time + // toString() is called, and to also make copies when objects are retreived from the + // cache. This would allow objects to be mutated. + clone.enableCaching = false; + // forget serialization because object may be mutated after it has been cloned + clone.valueSerialized = null; + clone.serialized = null; + return clone; + } + + private class Quadruple extends cz.vutbr.web.domassign.SingleMapNodeData.Quadruple { + + private Quadruple(String propertyName, + final CSSProperty property, + final Term value, + final Declaration sourceDeclaration, + SupportedBrailleCSS css) { + super(css, propertyName); + curProp = property; + curValue = value; + curSource = sourceDeclaration; + } + + private BrailleCssParser getParser() { + return ParsedDeclaration.this.getParser(); + } + + private Context getContext() { + return ParsedDeclaration.this.getContext(); + } + } + } + + // used in BrailleCssSerializer + final Cache cache = new Cache(); + + class Cache { + + private final Map cache = CacheBuilder.newBuilder() + .expireAfterAccess(60, TimeUnit.SECONDS) + .build() + .asMap(); + void put(Context context, String serializedStyle, BrailleCssStyle style) { + put(context, serializedStyle, null, false, style); + } + + void put(Context context, String serializedStyle, ParsedDeclarations parent, boolean concretizeInherit, BrailleCssStyle style) { + if (context == null) return; + CacheKey key = new CacheKey(context, serializedStyle, parent, concretizeInherit); + cache.put(key, style); + } + + BrailleCssStyle get(Context context, String serializedStyle) { + return get(context, serializedStyle, null, false); + } + + BrailleCssStyle get(Context context, String serializedStyle, ParsedDeclarations parent, boolean concretizeInherit) { + if (context == null) return null; + CacheKey key = new CacheKey(context, serializedStyle, parent, concretizeInherit); + return cache.get(key); + } + } + + private static class CacheKey implements Comparable { + private final Context context; + private final String style; + private final String parent; + private final boolean concretizeInherit; + public CacheKey(Context context, String style, ParsedDeclarations parent, boolean concretizeInherit) { + if (parent != null && context != Context.ELEMENT) + throw new IllegalArgumentException(); + this.context = context; + this.style = style; + this.parent = parent != null ? BrailleCssSerializer.toString(parent) : null; + this.concretizeInherit = concretizeInherit; + } + @Override + public int compareTo(CacheKey that) { + int i = this.context.compareTo(that.context); + if (i != 0) + return i; + i = this.style.compareTo(that.style); + if (i != 0) + return i; + if (this.parent == null) + return that.parent == null ? 0 : -1; + else if (that.parent == null) + return 1; + i = this.parent.compareTo(that.parent); + if (i != 0) + return i; + if (this.concretizeInherit == that.concretizeInherit) + return 0; + else + return this.concretizeInherit ? -1 : 1; + } + @Override + public int hashCode() { + final int prime = 31; + int hash = 1; + hash = prime * hash + context.hashCode(); + hash = prime * hash + style.hashCode(); + hash = prime * hash + (parent == null ? 0 : parent.hashCode()); + hash = prime * hash + Boolean.hashCode(concretizeInherit); + return hash; + } + @Override + public boolean equals(Object o) { + if (o == null) + return false; + if (!(o instanceof CacheKey)) + return false; + CacheKey that = (CacheKey)o; + if (this.context != that.context) + return false; + if (!this.style.equals(that.style)) + return false; + if (this.parent == null) + return that.parent == null; + else if (that.parent == null) + return false; + else if (!this.parent.equals(that.parent)) + return false; + else + return this.concretizeInherit == that.concretizeInherit; + } + } + + private class DeepDeclarationTransformer extends SupportedBrailleCSS { + + private final Context context; + + public DeepDeclarationTransformer(Context context, + boolean allowComponentProperties, + boolean allowShorthandProperties, + Collection extensions, + boolean allowUnknownVendorExtensions) { + super(allowComponentProperties, allowShorthandProperties, extensions, allowUnknownVendorExtensions); + this.context = context; + } + + @Override + public boolean parseDeclaration(Declaration d, Map properties, Map> values) { + if ("text-transform".equalsIgnoreCase(d.getProperty())) { + if (super.processTextTransform(d, properties, values)) { + if (properties.get("text-transform") == TextTransform.list_values) { + Term value = values.get("text-transform"); + if (value instanceof TermList) + values.put("text-transform", TextTransformList.of((TermList)value)); + else + throw new IllegalStateException(); // should not happen + } + return true; + } else + return false; + } else if ("content".equalsIgnoreCase(d.getProperty())) { + if (super.processContent(d, properties, values)) { + if (properties.get("content") == Content.content_list) { + Term value = values.get("content"); + if (value instanceof TermList) { + ContentList l = ContentList.of(BrailleCssParser.this, extensions, context, (TermList)value); + for (Term t : l) + if (t instanceof ContentFunction && !((ContentFunction)t).target.isPresent()) + throw new IllegalArgumentException("unexpected term in content list: " + t); + values.put("content", l); + } else + throw new IllegalStateException(); // should not happen + } + return true; + } else + return false; + } else if ("string-set".equalsIgnoreCase(d.getProperty())) { + if (super.processStringSet(d, properties, values)) { + if (properties.get("string-set") == StringSet.list_values) { + Term value = values.get("string-set"); + if (value instanceof TermList) + values.put("string-set", StringSetList.of(BrailleCssParser.this, context, (TermList)value)); + else + throw new IllegalStateException(); // should not happen + } + return true; + } else + return false; + } else if ("counter-set".equalsIgnoreCase(d.getProperty())) { + if (super.processCounterSet(d, properties, values)) { + if (properties.get("counter-set") == CounterSet.list_values) { + Term value = values.get("counter-set"); + if (value instanceof TermList) + values.put("counter-set", CounterSetList.of((TermList)value)); + else + throw new IllegalStateException(); // should not happen + } + return true; + } else + return false; + } else if ("counter-reset".equalsIgnoreCase(d.getProperty())) { + if (super.processCounterReset(d, properties, values)) { + if (properties.get("counter-reset") == CounterReset.list_values) { + Term value = values.get("counter-reset"); + if (value instanceof TermList) + values.put("counter-reset", CounterSetList.of((TermList)value)); + else + throw new IllegalStateException(); // should not happen + } + return true; + } else + return false; + } else if ("counter-increment".equalsIgnoreCase(d.getProperty())) { + if (super.processCounterIncrement(d, properties, values)) { + if (properties.get("counter-increment") == CounterIncrement.list_values) { + Term value = values.get("counter-increment"); + if (value instanceof TermList) + values.put("counter-increment", CounterSetList.of((TermList)value)); + else + throw new IllegalStateException(); // should not happen + } + return true; + } else + return false; + } else + return super.parseDeclaration(d, properties, values); + } + } + + private static Iterator getCSSExtensions() { + if (OSGiHelper.inOSGiContext()) + return OSGiHelper.getCSSExtensions(); + else + return SPIHelper.getCSSExtensions(); + } + + // 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 getCSSExtensions() { + return new Iterator() { + private BundleContext bc = FrameworkUtil.getBundle(BrailleCssParser.class).getBundleContext(); + private ServiceReference[] refs; + private int index = 0; { + try { + refs = bc.getServiceReferences(BrailleCSSExtension.class.getName(), null); + } catch (InvalidSyntaxException e) { + throw new IllegalStateException(e); // should not happen + } + } + public boolean hasNext() { + return refs != null && index < refs.length; + } + public BrailleCSSExtension next() throws NoSuchElementException { + if (!hasNext()) + throw new NoSuchElementException(); + return (BrailleCSSExtension)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 getCSSExtensions() { + return ServiceLoader.load(BrailleCSSExtension.class).iterator(); + } + } +} diff --git a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/BrailleCssSerializer.java b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/BrailleCssSerializer.java index dc771e7f30..c791f4b925 100644 --- a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/BrailleCssSerializer.java +++ b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/BrailleCssSerializer.java @@ -1,6 +1,8 @@ package org.daisy.pipeline.braille.css.impl; +import java.net.URI; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -11,25 +13,48 @@ import javax.xml.stream.XMLStreamWriter; import com.google.common.collect.Iterables; +import com.google.common.collect.Ordering; +import cz.vutbr.web.css.CSSProperty; +import cz.vutbr.web.css.CSSProperty.CounterIncrement; +import cz.vutbr.web.css.CSSProperty.CounterSet; +import cz.vutbr.web.css.CSSProperty.CounterReset; import cz.vutbr.web.css.Declaration; +import cz.vutbr.web.css.NodeData; import cz.vutbr.web.css.Rule; import cz.vutbr.web.css.RuleBlock; import cz.vutbr.web.css.RulePage; import cz.vutbr.web.css.Selector; import cz.vutbr.web.css.Selector.Combinator; import cz.vutbr.web.css.Term; +import cz.vutbr.web.css.TermFunction; +import cz.vutbr.web.css.TermString; +import cz.vutbr.web.css.TermURI; import cz.vutbr.web.csskit.OutputUtil; import static org.daisy.common.stax.XMLStreamWriterHelper.writeAttribute; import static org.daisy.common.stax.XMLStreamWriterHelper.writeStartElement; +import org.daisy.braille.css.BrailleCSSParserFactory.Context; +import org.daisy.braille.css.BrailleCSSProperty.Content; +import org.daisy.braille.css.BrailleCSSProperty.StringSet; +import org.daisy.common.file.URLs; +import org.daisy.pipeline.braille.css.impl.BrailleCssParser.ParsedDeclaration; +import org.daisy.pipeline.braille.css.impl.BrailleCssParser.ParsedDeclarations; +import org.daisy.pipeline.braille.css.impl.ContentList.AttrFunction; +import org.daisy.pipeline.braille.css.impl.ContentList.ContentFunction; +import org.daisy.pipeline.braille.css.impl.ContentList.CounterFunction; +import org.daisy.pipeline.braille.css.impl.ContentList.CounterStyle; +import org.daisy.pipeline.braille.css.impl.ContentList.FlowFunction; +import org.daisy.pipeline.braille.css.impl.ContentList.LeaderFunction; +import org.daisy.pipeline.braille.css.impl.ContentList.StringFunction; +import org.daisy.pipeline.braille.css.impl.ContentList.TextFunction; +import org.daisy.pipeline.braille.css.impl.ContentList.URL; import org.daisy.braille.css.InlineStyle; import org.daisy.braille.css.InlineStyle.RuleMainBlock; import org.daisy.braille.css.InlineStyle.RuleRelativeBlock; import org.daisy.braille.css.LanguageRange; import org.daisy.braille.css.PropertyValue; -import org.daisy.braille.css.SimpleInlineStyle; import org.daisy.pipeline.css.CssSerializer; public final class BrailleCssSerializer { @@ -41,51 +66,166 @@ private BrailleCssSerializer() {} /* =================================================== */ public static String toString(Term term) { - return CssSerializer.toString(term); + if (term instanceof TextTransformList || + term instanceof ContentList || + term instanceof StringSetList || + term instanceof CounterSetList) + return serializeTermList((List>)term); + else if (term instanceof AttrFunction) { + AttrFunction f = (AttrFunction)term; + String s = "attr(" + toString(f.name); + if (f.asURL) + s = s + " url"; + s += ")"; + return s; } + else if (term instanceof ContentFunction) { + ContentFunction f = (ContentFunction)term; + if (f.target.isPresent()) + return "target-content(" + toString(f.target.get()) + ")"; + else + return "content()"; } + else if (term instanceof StringFunction) { + StringFunction f = (StringFunction)term; + if (f.target.isPresent()) + return "target-string(" + toString(f.target.get()) + ", " + toString(f.name) + ")"; + else { + String s = "string(" + toString(f.name); + if (f.scope.isPresent()) + s = s + ", " + f.scope.get().toString(); + s += ")"; + return s; }} + else if (term instanceof CounterFunction) { + CounterFunction f = (CounterFunction)term; + String s = ""; + if (f.target.isPresent()) + s = "target-counter(" + toString(f.target.get()) + ", " + toString(f.name); + else + s = "counter(" + toString(f.name); + if (f.style.isPresent()) + s = s + ", " + toString(f.style.get()); + s += ")"; + return s; } + else if (term instanceof TextFunction) { + TextFunction f = (TextFunction)term; + return "target-text(" + toString(f.target) + ")"; } + else if (term instanceof LeaderFunction) { + LeaderFunction f = (LeaderFunction)term; + String s = "leader(" + toString(f.pattern); + if (f.position.isPresent()) + s = s + ", " + toString(f.position.get()); + if (f.alignment.isPresent()) + s = s + ", " + f.alignment.get().toString(); + s += ")"; + return s; } + else if (term instanceof FlowFunction) { + FlowFunction f = (FlowFunction)term; + String s = "flow(" + toString(f.from); + if (f.scope.isPresent()) + s = s + ", " + f.scope.get().toString(); + s += ")"; + return s; } + else + return CssSerializer.toString(term, t -> toString(t)); } public static String toString(Declaration declaration) { - return declaration.getProperty() + ": " + serializeTermList((List>)declaration) + ";"; + if (declaration instanceof PropertyValue) + return declaration.getProperty() + ": " + serializePropertyValue((PropertyValue)declaration); + else + return declaration.getProperty() + ": " + serializeTermList(declaration); + } + + public static String toString(BrailleCssStyle style) { + if (style.serialized == null) { + style.serialized = toString(style, null, null); + // cache + if (style.parser != null) + style.parser.cache.put(style.context, style.serialized, style); + } else { + // access cache to keep entry longer in it + if (style.parser != null) + style.parser.cache.get(style.context, style.serialized); + } + return style.serialized; } - public static String toString(BrailleCssTreeBuilder.Style style) { - return toString(style, null); + /** + * @param relativeTo If not {@code null}, include only those declarations that are needed + * to reconstruct {@code style} with {@code relativeTo} as the parent + * style. Relativizes even if the parent style is empty. + */ + public static String toString(BrailleCssStyle style, BrailleCssStyle relativeTo) { + if (relativeTo == null) + return toString(style); + String s = toString(style.relativize(relativeTo)); + // cache + if (style.parser != null) + style.parser.cache.put(style.context, + s, + relativeTo.declarations != null + ? (ParsedDeclarations)relativeTo.declarations + : ParsedDeclarations.EMPTY, + true, + style); + return s; } - private static String toString(BrailleCssTreeBuilder.Style style, String base) { + public static String toString(BrailleCssStyle style, String indentation) { + // this function does update the cache, nor does it store the string in the style object, as + // it's meant to pretty print a style (for the purpose of showing in temporary files or log + //messages) + return toString(style, null, indentation); + } + + private static String toString(BrailleCssStyle style, String base, String indent) { StringBuilder b = new StringBuilder(); StringBuilder rel = new StringBuilder(); - if (style.declarations != null) - b.append(serializeDeclarationList(style.declarations)); + if ("".equals(indent)) indent = null; + String newline = indent != null ? "\n" : " "; + if (style.declarations != null) { + b.append(serializeDeclarationList(style.declarations, ";" + newline)); + if (indent != null && b.length() > 0) b.append(";"); + } if (style.nestedStyles != null) - for (Map.Entry e : style.nestedStyles.entrySet()) { + for (Map.Entry e : style.nestedStyles.entrySet()) { if (base != null && e.getKey().startsWith("&")) { - if (rel.length() > 0) rel.append(" "); - rel.append(toString(e.getValue(), base + e.getKey().substring(1))); + if (rel.length() > 0) rel.append(newline); + rel.append(toString(e.getValue(), base + e.getKey().substring(1), indent)); } else { - if (b.length() > 0) b.append(" "); - b.append(toString(e.getValue(), e.getKey())); + if (b.length() > 0) { + if (indent == null && b.charAt(b.length() - 1) != '}') b.append(";"); + b.append(newline); + } + b.append(toString(e.getValue(), e.getKey(), indent)); } } if (base != null && b.length() > 0) { - b.insert(0, base + " { "); - b.append(" }"); + if (indent != null) { + String s = b.toString(); + s = indent + s.trim().replaceAll("\n", "\n" + indent); + b.setLength(0); + b.append(s); + } + b.insert(0, base + " {" + newline); + b.append(newline + "}"); } if (rel.length() > 0) { - if (b.length() > 0) b.append(" "); + if (b.length() > 0) { + if (indent == null && b.charAt(b.length() - 1) != '}') b.append(";"); + b.append(newline); + } b.append(rel); } return b.toString(); } - public static String toString(InlineStyle style) { - return toString(BrailleCssTreeBuilder.Style.of(style)); - } - - public static String toString(SimpleInlineStyle style) { + public static String toString(NodeData style) { List declarations = new ArrayList<>(); - for (String p : style.getPropertyNames()) - declarations.add(p + ": " + serializePropertyValue(style.get(p))); + for (String p : style.getPropertyNames()) { + String v = serializePropertyValue(style, p); + if (v != null) + declarations.add(p + ": " + v); + } Collections.sort(declarations); StringBuilder s = new StringBuilder(); Iterator it = declarations.iterator(); @@ -96,12 +236,30 @@ public static String toString(SimpleInlineStyle style) { return s.toString(); } - public static String serializePropertyValue(PropertyValue propValue) { - Term value = propValue.getValue(); + public static String serializePropertyValue(NodeData style, String property) { + return serializePropertyValue(style, property, true); + } + + public static String serializePropertyValue(NodeData style, String property, boolean includeInherited) { + Term value = style.getValue(property, includeInherited); if (value != null) return toString(value); - else - return propValue.getProperty().toString(); + else { + CSSProperty p = style.getProperty(property, includeInherited); + return p != null ? p.toString() : null; + } + } + + public static String serializePropertyValue(PropertyValue propValue) { + if (propValue instanceof ParsedDeclaration) + return ((ParsedDeclaration)propValue).valueToString(); // this may trigger caching + else { + Term value = propValue.getValue(); + if (value != null) + return toString(value); + else + return propValue.getCSSProperty().toString(); + } } public static String toString(RuleMainBlock rule) { @@ -141,11 +299,8 @@ else if (r instanceof RulePage) return b.toString(); } - public static String toString(RulePage page) { - return toString( - new BrailleCssTreeBuilder.Style().add( - "@page", - BrailleCssTreeBuilder.Style.of(page))); + public static String toString(RulePage page, BrailleCssParser parser) { + return toString(BrailleCssStyle.of(parser, page)); } public static String serializeRuleBlockList(Iterable>> ruleBlocks) { @@ -170,74 +325,315 @@ else if (r instanceof RuleRelativeBlock) return b; } - public static String serializeTermList(List> termList) { - return CssSerializer.serializeTermList(termList); + public static String serializeTermList(Collection> termList) { + return CssSerializer.serializeTermList(termList, t -> toString(t)); } public static String serializeLanguageRanges(List languageRanges) { return OutputUtil.appendList(new StringBuilder(), languageRanges, OutputUtil.SELECTOR_DELIM).toString(); } - /* = PRIVATE ========================================= */ + public static String serializeDeclarationList(Iterable declarations) { + return serializeDeclarationList(declarations, "; "); + } - private static String serializeDeclarationList(Iterable declarations) { - List sortedDeclarations = new ArrayList(); - for (Declaration d : declarations) sortedDeclarations.add(d); + private static String serializeDeclarationList(Iterable declarations, String separator) { + List sortedDeclarations = new ArrayList<>(); + for (Declaration d : declarations) + sortedDeclarations.add(BrailleCssSerializer.toString(d)); Collections.sort(sortedDeclarations); StringBuilder s = new StringBuilder(); - Iterator it = sortedDeclarations.iterator(); + Iterator it = sortedDeclarations.iterator(); while (it.hasNext()) { - s.append(BrailleCssSerializer.toString(it.next())); - if (it.hasNext()) s.append(" "); + s.append(it.next()); + if (it.hasNext()) s.append(separator); } return s.toString(); } + /* = PRIVATE ========================================= */ + + private static String toString(URL url) { + if (url.url != null) + return toString(url.url); + else + return toString(url.urlAttr); + } + + private static String toString(CounterStyle style) { + if (style.name != null) + return style.name; + else if (style.symbol != null) + return toString(style.symbol); + else if (style.symbols != null) + return toString(style.symbols); + else + return "none"; + } + + /* =================================================== */ + /* toAttributes */ + /* =================================================== */ + + public static void toAttributes(BrailleCssStyle style, XMLStreamWriter writer) throws XMLStreamException { + if (style.nestedStyles != null) + throw new UnsupportedOperationException(); + if (style.declarations != null) + for (Declaration d : style.declarations) + toAttribute(d, writer); + } + + /** + * @param relativeTo If not {@code null}, include only those declarations that are needed + * to reconstruct {@code style} with {@code relativeTo} as the parent + * style. Relativizes even if the parent style is empty. + */ + public static void toAttributes(BrailleCssStyle style, BrailleCssStyle relativeTo, XMLStreamWriter writer) + throws XMLStreamException { + if (relativeTo == null) + toAttributes(style, writer); + else + toAttributes(style.relativize(relativeTo), writer); + } + + private static void toAttribute(Declaration declaration, XMLStreamWriter writer) throws XMLStreamException { + writeAttribute(writer, + new QName(XMLNS_CSS, declaration.getProperty().replaceAll("^-", "_"), "css"), + declaration instanceof PropertyValue + ? serializePropertyValue((PropertyValue)declaration) + : serializeTermList(declaration)); + } + /* =================================================== */ /* toXml */ /* =================================================== */ - private static final String XMLNS_CSS = "http://www.daisy.org/ns/pipeline/braille-css"; - private static final QName CSS_RULE = new QName(XMLNS_CSS, "rule", "css"); - private static final QName CSS_PROPERTY = new QName(XMLNS_CSS, "property", "css"); - private static final QName SELECTOR = new QName("selector"); - private static final QName STYLE = new QName("style"); - private static final QName NAME = new QName("name"); - private static final QName VALUE = new QName("value"); + private static final String XMLNS_CSS = "http://www.daisy.org/ns/pipeline/braille-css"; + private static final QName CSS_RULE = new QName(XMLNS_CSS, "rule", "css"); + private static final QName CSS_PROPERTY = new QName(XMLNS_CSS, "property", "css"); + private static final QName CSS_STRING = new QName(XMLNS_CSS, "string", "css"); + private static final QName CSS_CONTENT = new QName(XMLNS_CSS, "content", "css"); + private static final QName CSS_ATTR = new QName(XMLNS_CSS, "attr", "css"); + private static final QName CSS_COUNTER = new QName(XMLNS_CSS, "counter", "css"); + private static final QName CSS_TEXT = new QName(XMLNS_CSS, "text", "css"); + private static final QName CSS_LEADER = new QName(XMLNS_CSS, "leader", "css"); + private static final QName CSS_FLOW = new QName(XMLNS_CSS, "flow", "css"); + private static final QName CSS_CUSTOM_FUNC = new QName(XMLNS_CSS, "custom-func", "css"); + private static final QName CSS_STRING_SET = new QName(XMLNS_CSS, "string-set", "css"); + private static final QName CSS_COUNTER_SET = new QName(XMLNS_CSS, "counter-set", "css"); + private static final QName SELECTOR = new QName("selector"); + private static final QName NAME = new QName("name"); + private static final QName VALUE = new QName("value"); + private static final QName STYLE = new QName("style"); + private static final QName TARGET = new QName("target"); + private static final QName TARGET_ATTRIBUTE = new QName("target-attribute"); + private static final QName SCOPE = new QName("scope"); + private static final QName PATTERN = new QName("pattern"); + private static final QName POSITION = new QName("position"); + private static final QName ALIGNMENT = new QName("alignment"); + private static final QName FROM = new QName("from"); - public static void toXml(BrailleCssTreeBuilder.Style style, XMLStreamWriter writer, boolean deep) throws XMLStreamException { - toXml(style, writer, deep, false); + public static void toXml(BrailleCssStyle style, XMLStreamWriter writer) throws XMLStreamException { + toXml(style, writer, false); } - private static void toXml(BrailleCssTreeBuilder.Style style, + private static void toXml(BrailleCssStyle style, XMLStreamWriter w, - boolean deep, boolean recursive) throws XMLStreamException { - if (style.declarations != null) { - if (!deep || !recursive || style.nestedStyles != null) + if (style.declarations != null && !Iterables.isEmpty(style.declarations)) { + if (!recursive || style.nestedStyles != null) writeStartElement(w, CSS_RULE); - if (!deep) - writeAttribute(w, STYLE, serializeDeclarationList(style.declarations)); - if (deep) { - for (Declaration d : style.declarations) { - writeStartElement(w, CSS_PROPERTY); - writeAttribute(w, NAME, d.getProperty()); - writeAttribute(w, VALUE, serializeTermList((List>)d)); - w.writeEndElement(); - } - } - if (!deep || !recursive || style.nestedStyles != null) + List declarations = new ArrayList<>(); + for (Declaration d : style.declarations) declarations.add(d); + Collections.sort(declarations, Ordering.natural().onResultOf(Declaration::getProperty)); + for (Declaration d : declarations) + toXml(d, w); + if (!recursive || style.nestedStyles != null) w.writeEndElement(); } if (style.nestedStyles != null) - for (Map.Entry e : style.nestedStyles.entrySet()) { + for (Map.Entry e : style.nestedStyles.entrySet()) { writeStartElement(w, CSS_RULE); writeAttribute(w, SELECTOR, e.getKey()); - if (!deep) - writeAttribute(w, STYLE, e.getValue().toString()); - if (deep) - toXml(e.getValue(), w, true, true); + toXml(e.getValue(), w, true); w.writeEndElement(); } } + + public static void toXml(Declaration declaration, XMLStreamWriter writer) throws XMLStreamException { + if (declaration instanceof PropertyValue) + toXml((PropertyValue)declaration, writer); + else { + writeStartElement(writer, CSS_PROPERTY); + writeAttribute(writer, NAME, declaration.getProperty()); + writeAttribute(writer, VALUE, serializeTermList(declaration)); + writer.writeEndElement(); + } + } + + public static void toXml(PropertyValue value, XMLStreamWriter writer) throws XMLStreamException { + writeStartElement(writer, CSS_PROPERTY); + writeAttribute(writer, NAME, value.getProperty()); + CSSProperty p = value.getCSSProperty(); + if (p == Content.NONE || p == Content.INITIAL) + ; + else if (p == Content.INHERIT) + writeAttribute(writer, VALUE, Content.INHERIT.toString()); + else if (p == Content.content_list) { + if (value.getValue() instanceof ContentList) + toXml((ContentList)value.getValue(), writer); + else + throw new IllegalArgumentException(); + } else if (p == StringSet.NONE || p == StringSet.INITIAL) + ; + else if (p == StringSet.INHERIT) + writeAttribute(writer, VALUE, StringSet.INHERIT.toString()); + else if (p == StringSet.list_values) + if (value.getValue() instanceof StringSetList) + toXml((StringSetList)value.getValue(), writer); + else + throw new IllegalArgumentException(); + else if (p == CounterSet.NONE || p == CounterSet.INITIAL || + p == CounterReset.NONE || p == CounterReset.INITIAL || + p == CounterIncrement.NONE || p == CounterIncrement.INITIAL) + ; + else if (p == CounterSet.INHERIT || + p == CounterReset.INHERIT || + p == CounterIncrement.INHERIT) + writeAttribute(writer, VALUE, "inherit"); + else if (p == CounterSet.list_values || + p == CounterReset.list_values || + p == CounterIncrement.list_values) + if (value.getValue() instanceof CounterSetList) + toXml((CounterSetList)value.getValue(), writer); + else + throw new IllegalArgumentException(); + else + writeAttribute(writer, VALUE, serializePropertyValue(value)); + writer.writeEndElement(); + } + + public static void toXml(ContentList list, XMLStreamWriter w) throws XMLStreamException { + for (Term i : list) + if (i instanceof TermString || i instanceof TermURI) { + Term s = (Term)i; + writeStartElement(w, CSS_STRING); + writeAttribute(w, VALUE, s.getValue()); + w.writeEndElement(); } + else if (i instanceof AttrFunction) { + AttrFunction f = (AttrFunction)i; + writeStartElement(w, CSS_ATTR); + writeAttribute(w, NAME, f.name.getValue()); + w.writeEndElement(); } + else if (i instanceof ContentFunction) { + ContentFunction f = (ContentFunction)i; + writeStartElement(w, CSS_CONTENT); + if (f.target.isPresent()) + if (f.target.get().url != null) { + TermURI t = f.target.get().url; + URI url = URLs.asURI(t.getValue()); + if (t.getBase() != null) + url = URLs.resolve(URLs.asURI(t.getBase()), url); + writeAttribute(w, TARGET, url.toString()); + } else + writeAttribute(w, TARGET_ATTRIBUTE, f.target.get().urlAttr.name.getValue()); + w.writeEndElement(); } + else if (i instanceof StringFunction) { + StringFunction f = (StringFunction)i; + writeStartElement(w, CSS_STRING); + writeAttribute(w, NAME, f.name.getValue()); + if (f.target.isPresent()) + if (f.target.get().url != null) { + TermURI t = f.target.get().url; + URI url = URLs.asURI(t.getValue()); + if (t.getBase() != null) + url = URLs.resolve(URLs.asURI(t.getBase()), url); + writeAttribute(w, TARGET, url.toString()); + } else + writeAttribute(w, TARGET_ATTRIBUTE, f.target.get().urlAttr.name.getValue()); + else if (f.scope.isPresent()) + writeAttribute(w, SCOPE, f.scope.get().toString()); + w.writeEndElement(); } + else if (i instanceof CounterFunction) { + CounterFunction f = (CounterFunction)i; + writeStartElement(w, CSS_COUNTER); + writeAttribute(w, NAME, f.name.getValue()); + if (f.target.isPresent()) + if (f.target.get().url != null) { + TermURI t = f.target.get().url; + URI url = URLs.asURI(t.getValue()); + if (t.getBase() != null) + url = URLs.resolve(URLs.asURI(t.getBase()), url); + writeAttribute(w, TARGET, url.toString()); + } else + writeAttribute(w, TARGET_ATTRIBUTE, f.target.get().urlAttr.name.getValue()); + if (f.style.isPresent()) + writeAttribute(w, STYLE, toString(f.style.get())); + w.writeEndElement(); } + else if (i instanceof TextFunction) { + TextFunction f = (TextFunction)i; + writeStartElement(w, CSS_TEXT); + if (f.target.url != null) { + TermURI t = f.target.url; + URI url = URLs.asURI(t.getValue()); + if (t.getBase() != null) + url = URLs.resolve(URLs.asURI(t.getBase()), url); + writeAttribute(w, TARGET, url.toString()); + } else + writeAttribute(w, TARGET_ATTRIBUTE, f.target.urlAttr.name.getValue()); + w.writeEndElement(); } + else if (i instanceof LeaderFunction) { + LeaderFunction f = (LeaderFunction)i; + writeStartElement(w, CSS_LEADER); + writeAttribute(w, PATTERN, f.pattern.getValue()); + if (f.position.isPresent()) + writeAttribute(w, POSITION, toString(f.position.get())); + if (f.alignment.isPresent()) + writeAttribute(w, ALIGNMENT, f.alignment.get().toString()); + w.writeEndElement(); } + else if (i instanceof FlowFunction) { + FlowFunction f = (FlowFunction)i; + writeStartElement(w, CSS_FLOW); + writeAttribute(w, FROM, f.from.getValue()); + if (f.scope.isPresent()) + writeAttribute(w, SCOPE, f.scope.get().toString()); + w.writeEndElement(); } + else if (i instanceof TermFunction) { + TermFunction f = (TermFunction)i; + writeStartElement(w, CSS_CUSTOM_FUNC); + writeAttribute(w, NAME, f.getFunctionName()); + int k = 0; + for (Term arg : f) { + k++; + writeAttribute(w, new QName("arg" + k), toString(arg)); } + w.writeEndElement(); } + else + throw new RuntimeException("coding error"); + } + + public static void toXml(StringSetList list, XMLStreamWriter w) throws XMLStreamException { + for (Term i : list) { + if (i instanceof StringSetList.StringSet) { + StringSetList.StringSet s = (StringSetList.StringSet)i; + writeStartElement(w, CSS_STRING_SET); + writeAttribute(w, NAME, s.getKey()); + toXml(s.getValue(), w); + w.writeEndElement(); } + else + throw new RuntimeException("coding error"); + } + } + + public static void toXml(CounterSetList list, XMLStreamWriter w) throws XMLStreamException { + for (Term i : list) { + if (i instanceof CounterSetList.CounterSet) { + CounterSetList.CounterSet s = (CounterSetList.CounterSet)i; + writeStartElement(w, CSS_COUNTER_SET); + writeAttribute(w, NAME, s.getKey()); + writeAttribute(w, VALUE, "" + s.getValue()); + w.writeEndElement(); } + else + throw new RuntimeException("coding error"); + } + } } diff --git a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/BrailleCssStyle.java b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/BrailleCssStyle.java new file mode 100644 index 0000000000..88f750b743 --- /dev/null +++ b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/BrailleCssStyle.java @@ -0,0 +1,1155 @@ +package org.daisy.pipeline.braille.css.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.SortedMap; +import java.util.TreeMap; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSortedMap; +import com.google.common.collect.Iterables; + +import cz.vutbr.web.css.CSSProperty; +import cz.vutbr.web.css.Declaration; +import cz.vutbr.web.css.Rule; +import cz.vutbr.web.css.RuleBlock; +import cz.vutbr.web.css.RuleMargin; +import cz.vutbr.web.css.RulePage; +import cz.vutbr.web.css.Selector; +import cz.vutbr.web.css.Selector.Combinator; +import cz.vutbr.web.css.Selector.PseudoClass; +import cz.vutbr.web.css.Selector.SelectorPart; +import cz.vutbr.web.css.SourceLocator; +import cz.vutbr.web.css.Term; + +import org.daisy.braille.css.BrailleCSSParserFactory.Context; +import org.daisy.braille.css.BrailleCSSProperty.Content; +import org.daisy.braille.css.BrailleCSSProperty.StringSet; +import org.daisy.braille.css.BrailleCSSProperty.TextTransform; +import org.daisy.braille.css.InlineStyle; +import org.daisy.braille.css.InlineStyle.RuleMainBlock; +import org.daisy.braille.css.InlineStyle.RuleRelativeBlock; +import org.daisy.braille.css.InlineStyle.RuleRelativeHyphenationResource; +import org.daisy.braille.css.InlineStyle.RuleRelativePage; +import org.daisy.braille.css.InlineStyle.RuleRelativeVolume; +import org.daisy.braille.css.PropertyValue; +import org.daisy.braille.css.SelectorImpl.PseudoElementImpl; +import org.daisy.braille.css.SimpleInlineStyle; +import org.daisy.braille.css.SupportedBrailleCSS; +import org.daisy.braille.css.RuleCounterStyle; +import org.daisy.braille.css.RuleHyphenationResource; +import org.daisy.braille.css.RuleTextTransform; +import org.daisy.braille.css.RuleVolume; +import org.daisy.braille.css.RuleVolumeArea; +import org.daisy.braille.css.VendorAtRule; +import org.daisy.pipeline.braille.css.impl.BrailleCssParser.ParsedDeclaration; +import org.daisy.pipeline.braille.css.impl.BrailleCssParser.ParsedDeclarations; +import org.daisy.pipeline.css.CounterStyle; + +import org.w3c.dom.Element; + +/** + * Tree reprentation of a CSS style. The tree structure is based on Sass' nesting of selectors. + * + * This class is immutable + */ +public final class BrailleCssStyle implements Cloneable { + + public final static BrailleCssStyle EMPTY = new Builder().build(); + + // these fields need to be package private because they are used in BrailleCssSerializer and BrailleCssParser + // note that even though the declarations are assumed to not change, we don't assume they are unmodifiable + Iterable declarations; + SortedMap nestedStyles; // sorted by key + // used in BrailleCssSerializer + BrailleCssParser parser; + Context context; + + public final Object underlyingObject; // - CounterStyle + // - null + + private BrailleCssStyle(Builder builder) { + this.underlyingObject = builder.underlyingObject; + if ((builder.parser != null && builder.parser.getSupportedBrailleCSS(builder.context).isPresent()) + || builder.declarations != null && Iterables.any(builder.declarations.values(), + d -> d instanceof PropertyValue)) { + this.declarations = new ParsedDeclarations(builder.parser, + builder.context, + builder.declarations != null + ? builder.declarations.values() + : null); + this.parser = ((ParsedDeclarations)this.declarations).getParser(); + this.context = ((ParsedDeclarations)this.declarations).getContext(); + } else { + if (builder.declarations == null || builder.declarations.isEmpty()) + this.declarations = null; + else + this.declarations = ImmutableList.copyOf(builder.declarations.values()); + this.parser = builder.parser; + this.context = builder.context; + } + this.nestedStyles = builder.nestedStyles != null && !builder.nestedStyles.isEmpty() + ? ImmutableSortedMap.copyOfSorted(builder.buildNestedStyles()) + : null; + } + + public boolean isEmpty() { + if (nestedStyles != null) + return false; + if (declarations instanceof ParsedDeclarations) + // a ParsedDeclarations is never really empty because it contains information about initial values + return false; + if (declarations == null) + return true; + if (Iterables.isEmpty(declarations)) + return true; + return false; + } + + private boolean evaluated = false; + + /** + * Evaluate attr() and content() values in content and + * string-set properties. + */ + public BrailleCssStyle evaluate(Element context) { + if (evaluated) return this; + BrailleCssStyle copy = null; + if (declarations != null) { + Iterable evaluatedDeclarations = evaluateDeclarations(context); + if (copy == null && evaluatedDeclarations != declarations) + copy = clone(); + if (copy != null) { + copy.declarations = evaluatedDeclarations; + copy.declarationMap = null; + } + } + if (nestedStyles != null) { + SortedMap nestedStylesCopy = new TreeMap<>(sortSelectors); + for (Map.Entry e : nestedStyles.entrySet()) { + BrailleCssStyle nestedStyle = e.getValue(); + BrailleCssStyle nestedStyleEvaluated = nestedStyle.evaluate(context); + if (copy == null && nestedStyleEvaluated != nestedStyle) + copy = clone(); + nestedStylesCopy.put(e.getKey(), nestedStyleEvaluated); + } + if (copy != null) + copy.nestedStyles = nestedStylesCopy; + } + if (copy != null) { + copy.serialized = null; + copy.evaluated = true; + return copy; + } else + return this; + } + + private Iterable evaluateDeclarations(Element context) { + Iterable copy = null; + if (declarations != null) + for (Declaration d : declarations) { + if (d instanceof PropertyValue) { + PropertyValue pv = (PropertyValue)d; + CSSProperty p = pv.getCSSProperty(); + if (p == Content.content_list) { + Term v = pv.getValue(); + if (v instanceof ContentList) { + // clone because evaluate() mutates the value + if (copy == null) + copy = copyDeclarations(); + // find value in cloned declarations + for (Declaration dd : copy) + if (((PropertyValue)dd).getCSSProperty() == Content.content_list) { + ((ContentList)((PropertyValue)dd).getValue()).evaluate(context); + break; } + } else + throw new IllegalStateException(); // coding error + } else if (p == StringSet.list_values) { + Term v = pv.getValue(); + if (v instanceof StringSetList) { + // clone because evaluate() mutates the value + if (copy == null) + copy = copyDeclarations(); + // find value in cloned declarations + for (Declaration dd : copy) + if (((PropertyValue)dd).getCSSProperty() == StringSet.list_values) { + ((StringSetList)((PropertyValue)dd).getValue()).evaluate(context); + break; } + } else + throw new IllegalStateException(); // coding error + } + } + } + if (copy != null) + return copy; + else + return declarations; + } + + // also used in BrailleCssParser + static Declaration evaluateDeclaration(Declaration declaration, Element context) { + if (!(declaration instanceof PropertyValue)) + return declaration; + PropertyValue pv = (PropertyValue)declaration; + PropertyValue copy = null; + CSSProperty p = pv.getCSSProperty(); + if (p == Content.content_list) { + if (pv.getValue() instanceof ContentList) { + // clone because evaluate() mutates the value + copy = (PropertyValue)declaration.clone(); + ((ContentList)copy.getValue()).evaluate(context); + } else + throw new IllegalStateException(); // coding error + } else if (p == StringSet.list_values) { + if (pv.getValue() instanceof StringSetList) { + // clone because evaluate() mutates the value + copy = (PropertyValue)declaration.clone(); + ((StringSetList)copy.getValue()).evaluate(context); + } else + throw new IllegalStateException(); // coding error + } + if (copy != null) + return copy; + else + return declaration; + } + + /** + * @return a deep copy of the {@code declarations} field + */ + private Iterable copyDeclarations() { + if (declarations instanceof ParsedDeclarations) + // make sure that declarations field stays a ParsedDeclarations object because + // it is assumed throughout the code + return (ParsedDeclarations)((ParsedDeclarations)declarations).clone(); + else { + List declarationsCopy = new ArrayList<>(); + for (Declaration dd : declarations) + declarationsCopy.add((Declaration)dd.clone()); + return ImmutableList.copyOf(declarationsCopy); + } + } + + private Map declarationMap = null; + + private Map getDeclarationMap() { + if (declarations != null) { + synchronized (this) { + if (declarationMap == null) { + declarationMap = new TreeMap<>(); + for (Declaration d : declarations) + declarationMap.put(d.getProperty(), d); + } + } + } + return declarationMap; + } + + public Iterable getPropertyNames() { + if (declarations != null) + return getDeclarationMap().keySet(); + else + return Collections.emptyList(); + } + + /** + * Caller must guarantee that the {@link Declaration} objects will not be modified. + */ + public Iterable getDeclarations() { + if (declarations != null) + return getDeclarationMap().values(); + else + return Collections.emptyList(); + } + + public Iterable getSelectors() { + if (nestedStyles != null) + return nestedStyles.keySet(); + else + return Collections.emptyList(); + } + + private List rules = null; + + public Iterable getRules() { + if (nestedStyles != null) { + synchronized (this) { + if (rules == null) { + rules = new ArrayList<>(); + for (Map.Entry e : nestedStyles.entrySet()) { + String selector = e.getKey(); + rules.add(new Builder().add(selector, e.getValue()).build()); + } + } + } + return rules; + } else + return Collections.emptyList(); + } + + /** + * Return the style as a {@link SimpleInlineStyle} object. + * + * @param mutable Whether the caller wishes to mutate the returned object. + * @throws UnsupportedOperationException if this is not a simple inline style + */ + public SimpleInlineStyle asSimpleInlineStyle(boolean mutable) { + if (nestedStyles != null) + throw new UnsupportedOperationException("not a simple inline style"); + else if (declarations == null) + return SimpleInlineStyle.EMPTY; + else if (context != Context.ELEMENT) + throw new UnsupportedOperationException("not a simple inline style"); + else if (!(declarations instanceof ParsedDeclarations)) + throw new IllegalStateException(); // coding error + ParsedDeclarations s = (ParsedDeclarations)declarations; + if (mutable && !s.isEmpty()) { + s = (ParsedDeclarations)s.clone(); // make a deep copy + for (Declaration d : s) { + if (d instanceof PropertyValue) { + PropertyValue pv = (PropertyValue)d; + CSSProperty p = pv.getCSSProperty(); + if (p == TextTransform.list_values) { + Term v = pv.getValue(); + if (v instanceof TextTransformList) + ((TextTransformList)v).locked = false; + break; + } + } + } + // mark as mutable + s.locked = false; + } + return s; + } + + // used in BrailleCssParser + static Declaration unlockDeclaration(Declaration declaration) { + if (!(declaration instanceof PropertyValue)) + return declaration; + PropertyValue pv = (PropertyValue)declaration; + if (pv.getCSSProperty() == TextTransform.list_values) { + if (pv.getValue() instanceof TextTransformList) { + // clone because we're going to make the value mutable + pv = (PropertyValue)pv.clone(); + ((TextTransformList)pv.getValue()).locked = false; + } else + throw new IllegalStateException(); // coding error + } + return pv; + } + + /** + * Get the declaration for the given property name. + * + * Caller must guarantee that the object will not be modified. + */ + public Declaration getDeclaration(String propertyName) { + return getDeclaration(propertyName, false); + } + + /** + * @param includeInitial Whether to include the initial value if the style does not contain the property. + */ + public Declaration getDeclaration(String propertyName, boolean includeInitial) { + if (declarations != null) { + Declaration d = getDeclarationMap().get(propertyName); + if (d != null) + return d; + } + if (includeInitial && declarations instanceof ParsedDeclarations) + return ((ParsedDeclarations)declarations).getOrDefault(propertyName); + return null; + } + + /** + * Get the nested style for the given selector. + */ + public BrailleCssStyle getNestedStyle(String selector) { + if (nestedStyles != null) { + BrailleCssStyle n = nestedStyles.get(selector); + if (n != null) + return n; + } + if (parser != null) + switch (context) { + case ELEMENT: + // return empty @page rule rather than null, in order to make it possible to + // get default page property values using s:getOrDefault() + if ("@page".equals(selector)) + // by passing parser this object will not be empty (because it contains + // information about initial values, see #isEmpty()) + return new Builder(parser, Context.PAGE).build(); + break; + default: + break; + } + return null; + } + + /** + * Remove declaration if key is a property name, or nested style if key is a selector. + */ + public BrailleCssStyle remove(String key) { + if (getDeclaration(key) != null || + nestedStyles != null && nestedStyles.containsKey(key)) + return new Builder(this).remove(key).build(); + else + return this; + } + + /** + * For each provided key, remove declaration if key is a property name, or rule if key is a selector. + */ + public BrailleCssStyle remove(Iterator keys) { + if (keys.hasNext()) { + Builder b = new Builder(this); + while (keys.hasNext()) { + String key = keys.next(); + if (getDeclaration(key) != null || + nestedStyles != null && nestedStyles.containsKey(key)) + b = b.remove(key); + } + return b.build(); + } + return this; + } + + /** + * Add other styles to this style. Properties are overwritten by properties declared in + * following style items. + * + * @param styles objects must be of type {@link BrailleCssStyle} or {@link Declaration}. + */ + public BrailleCssStyle add(Iterator styles) { + Builder b = null; + while (styles.hasNext()) { + Object s = styles.next(); + if (s instanceof BrailleCssStyle) { + if (((BrailleCssStyle)s).isEmpty()) + continue; + if (b == null) b = new Builder(this); + b.add((BrailleCssStyle)s); + } else if (s instanceof Declaration) { + if (b == null) b = new Builder(this); + b.add((Declaration)s); + } else + throw new IllegalArgumentException(); + } + if (b != null) + return b.build(); + else + return this; + } + + /** + * Add a declaration. If a declaration for the same property already exists, it is overwritten. + */ + public BrailleCssStyle add(Declaration declaration) { + Builder b = new Builder(this); + b.add(declaration); + return b.build(); + } + + /** + * @param contentList assumed to not change + */ + public BrailleCssStyle add(String propertyName, ContentList contentList) { + Builder b = new Builder(this); + b.add(new ParsedDeclaration(contentList.getParser(), + contentList.getContext(), + propertyName, + Content.content_list, + contentList, + null)); + return b.build(); + } + + /** + * Add a rule. If a rule with the same selector already exists, the rules are merged. Properties + * in the existing rule are overwritten by properties in the new rule. + */ + public BrailleCssStyle add(String key, BrailleCssStyle nestedStyle) { + return new Builder(this).add(key, nestedStyle).build(); + } + + @Override + public BrailleCssStyle clone() { + try { + return (BrailleCssStyle)super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError("coding error"); + } + } + + // for BrailleCssSerializer + String serialized = null; + + @Override + public String toString() { + return BrailleCssSerializer.toString(this); + } + + // used in BrailleCssSerializer + BrailleCssStyle relativize(BrailleCssStyle base) { + if (base.declarations != null && !(base.declarations instanceof ParsedDeclarations)) + throw new IllegalArgumentException(); + return relativize((ParsedDeclarations)base.declarations).build(); + } + + private Builder relativize(ParsedDeclarations base) { + if (declarations != null && !(declarations instanceof ParsedDeclarations)) + throw new IllegalArgumentException(); + Builder relative = new Builder(this); + if (base != null) + for (PropertyValue p : base) { + if (p.getCSSProperty().inherited()) { + PropertyValue pp = (PropertyValue)getDeclaration(p.getProperty()); + if (pp != null) { + if (equal(p, pp)) + relative.remove(p.getProperty()); + } else { + PropertyValue def = p.getDefault(); + if (def != null && !equalIgnoreSource(p, def)) + relative.add(def); + } + } + } + // omit properties that have the default value and are not inherited or don't exist in the parent style + if (declarations != null) + for (Declaration d : getDeclarationMap().values()) { + PropertyValue p = (PropertyValue)d; + PropertyValue def = p.getDefault(); + if ((!p.getCSSProperty().inherited() || base == null || base.getProperty(p.getProperty()) == null) && equal(p, def)) + relative.remove(p.getProperty()); + } + // relativize nested styles + BrailleCssStyle main = null; + for (String sel : getSelectors()) { + if (sel.startsWith("@")) + // skip at-rules + continue; + BrailleCssStyle nested = getNestedStyle(sel); + if (sel.equals("&::before") || + sel.equals("&::after") || + (sel.startsWith("&:") && !sel.startsWith("&::"))) { + // pseudo-classes and ::before and ::after pseudo-elements inherit from main style + if (relative.declarations != null && !relative.declarations.isEmpty()) { + relative.remove(sel); + if (main == null) main = relative.build(); + if (main.declarations != null && !(main.declarations instanceof ParsedDeclarations)) + throw new IllegalStateException(); + relative.add(sel, nested.relativize((ParsedDeclarations)main.declarations)); + } + } else { + relative.remove(sel); + relative.add(sel, nested.relativize(base)); + } + } + return relative; + } + + @Override + public boolean equals(Object other) { + if (this == other) + return true; + if (!(other instanceof BrailleCssStyle)) + return false; + BrailleCssStyle that = (BrailleCssStyle)other; + if (declarations == null) + return that.declarations == null; + else if (that.declarations == null) + return false; + if (declarations instanceof ParsedDeclarations) { + if (!(that.declarations instanceof ParsedDeclarations)) + return false; + if (((ParsedDeclarations)that.declarations).getParser() != ((ParsedDeclarations)declarations).getParser()) + return false; + if (((ParsedDeclarations)that.declarations).getContext() != ((ParsedDeclarations)declarations).getContext()) + return false; + } else if (that.declarations instanceof ParsedDeclarations) + return false; + // using toString() for efficiency, because toString() is memoized + return toString().equals(that.toString()); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + private static boolean equal(PropertyValue a, PropertyValue b) { + return equal(a, b, false); + } + + private static boolean equalIgnoreSource(PropertyValue a, PropertyValue b) { + return equal(a, b, true); + } + + private static boolean equal(PropertyValue a, PropertyValue b, boolean ignoreSource) { + if (a == null) + return b == null; + else if (b == null) + return false; + else { + if (a.getCSSProperty() != b.getCSSProperty()) + return false; + Term va = a.getValue(); + Term vb = b.getValue(); + if (!equal(va != null ? BrailleCssSerializer.toString(va) : null, + vb != null ? BrailleCssSerializer.toString(vb) : null)) + return false; + if (!ignoreSource && !equal(a.getSource(), b.getSource())) + return false; + } + return true; + } + + private static boolean equal(SourceLocator a, SourceLocator b) { + if (a != null && a.getURL() == null) + a = null; + if (b != null && b.getURL() == null) + b = null; + if (a == null) + return b == null; + else if (b == null) + return false; + else if (!equal(a.getURL(), b.getURL())) + return false; + else if (a.getLineNumber() != b.getLineNumber()) + return false; + else if (a.getColumnNumber() != b.getColumnNumber()) + return false; + else + return true; + } + + private static boolean equal(Object a, Object b) { + if (a == null) + return b == null; + else if (b == null) + return false; + else + return a.equals(b); + } + + private static class Builder { + + private final BrailleCssParser parser; + private final Context context; + private final Object underlyingObject; + private Map declarations; + private Map nestedStyles; // values are instances of Builder or BrailleCssStyle + + /** + * @param context null means neither of { ELEMENT, PAGE, VOLUME, TEXT_TRANSFORM, COUNTER_STYLE, VENDOR_RULE } + */ + private Builder(BrailleCssParser parser, Context context) { + this(parser, context, null); + } + + private Builder(BrailleCssParser parser, Context context, Object underlyingObject) { + this.parser = parser; + this.context = context; + this.underlyingObject = underlyingObject; + } + + /** + * Create a non-parsing Builder. + */ + private Builder() { + this(null, null, null); + } + + private Builder(Object underlyingObject) { + this(null, null, underlyingObject); + } + + private Builder(BrailleCssStyle style) { + this(style.parser, style.context, style.underlyingObject); + add(style); + } + + /** + * @param declaration assumed to not change + */ + private Builder add(Declaration declaration) { + if (declaration != null) { + if (declarations == null) declarations = new TreeMap<>(); + declarations.put(declaration.getProperty(), declaration); + } + return this; + } + + private Builder add(String selector, Object nestedStyle) { + if (!(nestedStyle instanceof BrailleCssStyle || nestedStyle instanceof Builder)) + throw new IllegalArgumentException(); + if (this.nestedStyles == null) this.nestedStyles = new TreeMap(); + if (selector == null) { + if (nestedStyle instanceof BrailleCssStyle) + add((BrailleCssStyle)nestedStyle); + else + add((Builder)nestedStyle); + } else if (this.nestedStyles.containsKey(selector)) { + Object s = this.nestedStyles.get(selector); + if (s instanceof Builder) { + if (nestedStyle instanceof BrailleCssStyle) + ((Builder)s).add((BrailleCssStyle)nestedStyle); + else + ((Builder)s).add((Builder)nestedStyle); + } else { + if (nestedStyle instanceof BrailleCssStyle) + this.nestedStyles.put(selector, new Builder((BrailleCssStyle)s).add((BrailleCssStyle)nestedStyle)); + else + this.nestedStyles.put(selector, new Builder((BrailleCssStyle)s).add((Builder)nestedStyle)); + } + } else + this.nestedStyles.put(selector, nestedStyle); + return this; + } + + private Builder add(BrailleCssStyle style) { + if (style.declarations != null) + for (Declaration d : style.declarations) + add(d); + if (style.nestedStyles != null) + for (Map.Entry e : style.nestedStyles.entrySet()) + add(e.getKey(), e.getValue()); + return this; + } + + private Builder add(Builder style) { + if (style.declarations != null) + for (Declaration d : style.declarations.values()) + add(d); + if (style.nestedStyles != null) + for (Map.Entry e : style.nestedStyles.entrySet()) { + Object s = e.getValue(); + if (s instanceof Builder) + add(e.getKey(), (Builder)s); + else + add(e.getKey(), (BrailleCssStyle)s); + } + return this; + } + + /** + * @param key property name or selector + */ + private Builder remove(String key) { + if (this.declarations != null && this.declarations.remove(key) != null) + ; + else if (this.nestedStyles != null) + this.nestedStyles.remove(key); + return this; + } + + private BrailleCssStyle build() { + return new BrailleCssStyle(this); + } + + private SortedMap buildNestedStyles() { + if (nestedStyles == null) + return null; + SortedMap map = null; + for (Map.Entry e : nestedStyles.entrySet()) { + Object v = e.getValue(); + BrailleCssStyle s = v instanceof Builder ? ((Builder)v).build() : (BrailleCssStyle)v; + if (!s.isEmpty()) { + if (map == null) + map = new TreeMap(sortSelectors); + map.put(e.getKey(), s); + } + } + return map; + } + } + + /** + * @param declarations assumed to not change + */ + public static BrailleCssStyle of(SimpleInlineStyle declarations) { + if (declarations == SimpleInlineStyle.EMPTY) + return BrailleCssStyle.EMPTY; + else if (declarations instanceof ParsedDeclarations) { + BrailleCssStyle style = new Builder().build(); + style.declarations = declarations; + style.parser = ((ParsedDeclarations)declarations).getParser(); + style.context = ((ParsedDeclarations)declarations).getContext(); + ((ParsedDeclarations)declarations).locked = true; + return style; + } else { + // assuming that declarations originate from a ParsedDeclarations + Builder b = new Builder(); + for (PropertyValue d : declarations) + b.add(d); + return b.build(); + } + } + + /** + * @param declaration assumed to not change + */ + public static BrailleCssStyle of(Declaration declaration) { + Builder style = new Builder(); + style.add(declaration); + return style.build(); + } + + /** + * @param page assumed to not change + * @param parser {@link BrailleCssParser} for declarations inside page and margin area rules. + */ + // also used in BrailleCssSerializer.toString(RulePage, BrailleCssParser) + static BrailleCssStyle of(BrailleCssParser parser, RulePage page) { + return of(parser, page, false); + } + + private static BrailleCssStyle of(BrailleCssParser parser, RuleRelativePage page) { + return of(parser, page.asRulePage(), true); + } + + private static BrailleCssStyle of(BrailleCssParser parser, RulePage page, boolean relative) { + // assumed to be anonymous page + Builder style = new Builder(parser, Context.PAGE); + for (Rule r : page) + if (r instanceof Declaration) + style.add((Declaration)r); + else if (r instanceof RuleMargin) { + Builder margin = new Builder(parser, Context.PAGE); + for (Declaration d : (RuleMargin)r) + margin.add(d); + style.add("@" + ((RuleMargin)r).getMarginArea(), margin); + } else + throw new RuntimeException("coding error"); + String pseudo = page.getPseudo(); + Builder relativeRule = (pseudo == null) + ? style + : new Builder(parser, Context.PAGE).add("&:" + pseudo, style); + if (relative) + return relativeRule.build(); + else + return new Builder().add("@page", relativeRule).build(); + } + + /** + * @param volumeArea assumed to not change + */ + private static BrailleCssStyle of(BrailleCssParser parser, RuleVolumeArea volumeArea) { + Builder style = new Builder(parser, Context.VOLUME); + for (Rule r : volumeArea) + if (r instanceof Declaration) + style.add((Declaration)r); + else if (r instanceof RulePage) + style.add(of(parser, (RulePage)r)); + else + throw new RuntimeException("coding error"); + return new Builder().add("@" + volumeArea.getVolumeArea().value, style).build(); + } + + /** + * @param volume assumed to not change + */ + private static BrailleCssStyle of(BrailleCssParser parser, RuleVolume volume) { + return of(parser, volume, false); + } + + private static BrailleCssStyle of(BrailleCssParser parser, RuleRelativeVolume volume) { + return of(parser, volume.asRuleVolume(), true); + } + + private static BrailleCssStyle of(BrailleCssParser parser, RuleVolume volume, boolean relative) { + Builder style = new Builder(parser, Context.VOLUME); + for (Rule r : volume) + if (r instanceof Declaration) + style.add((Declaration)r); + else if (r instanceof RuleVolumeArea) + style.add(of(parser, (RuleVolumeArea)r)); + else + throw new RuntimeException("coding error"); + String pseudo = volume.getPseudo(); + Builder relativeRule = (pseudo == null) + ? style + : new Builder(parser, Context.VOLUME).add("&:" + pseudo, style); + if (relative) + return relativeRule.build(); + else + return new Builder().add("@volume", relativeRule).build(); + } + + /** + * @param hyphenationResource assumed to not change + */ + private static BrailleCssStyle of(BrailleCssParser parser, RuleHyphenationResource hyphenationResource) { + return of(parser, hyphenationResource, false); + } + + private static BrailleCssStyle of(BrailleCssParser parser, RuleRelativeHyphenationResource hyphenationResource) { + return of(parser, hyphenationResource.asRuleHyphenationResource(), true); + } + + private static BrailleCssStyle of(BrailleCssParser parser, RuleHyphenationResource hyphenationResource, boolean relative) { + Builder style = new Builder(parser, Context.HYPHENATION_RESOURCE); + for (Declaration d : hyphenationResource) + style.add(d); + Builder relativeRule = new Builder(parser, Context.HYPHENATION_RESOURCE) + .add("&:lang(" + BrailleCssSerializer.serializeLanguageRanges(hyphenationResource.getLanguageRanges()) + ")", style); + if (relative) + return relativeRule.build(); + else + return new Builder().add("@hyphenation-resource", relativeRule).build(); + } + + /** + * @param rule assumed to not change + */ + private static BrailleCssStyle of(BrailleCssParser parser, VendorAtRule> rule) { + Builder style = new Builder(); + for (Rule r : rule) + if (r instanceof Declaration) + style.add((Declaration)r); + else if (r instanceof VendorAtRule) + style.add("@" + ((VendorAtRule)r).getName(), of(parser, (VendorAtRule>)r)); + else + throw new RuntimeException("coding error"); + return style.build(); + } + + /** + * @param inlineStyle assumed to not change + */ + private static BrailleCssStyle of(BrailleCssParser parser, Context context, InlineStyle inlineStyle) { + Builder style = new Builder(parser, context); + Map counterStyleRules = null; + for (RuleBlock rule : inlineStyle) { + if (rule instanceof RuleMainBlock) + // Note that the declarations have not been transformed by SupportedBrailleCSS yet. + // This will be done when build() is called. + for (Declaration d : (RuleMainBlock)rule) + style.add(d); + else if (rule instanceof RuleRelativeBlock) { + String[] selector = serializeSelector(((RuleRelativeBlock)rule).getSelector()); + if (context == Context.COUNTER_STYLE && selector.length == 1 && selector[0].startsWith("& ")) { + String name = selector[0].substring(2); + RuleCounterStyle counterStyle = new RuleCounterStyle(name); { + for (Rule r : (RuleRelativeBlock)rule) + if (!(r instanceof Declaration)) + throw new RuntimeException(); // @page can not occur inside @counter-style + counterStyle.replaceAll((RuleBlock)rule); } + if (counterStyleRules == null) + counterStyleRules = new HashMap<>(); + counterStyleRules.put(name, counterStyle); + } else { + Builder decls = new Builder(parser, context); { + for (Rule r : (RuleRelativeBlock)rule) + if (r instanceof Declaration) + decls.add((Declaration)r); + else if (r instanceof RulePage) + decls.add(of(parser, (RulePage)r)); + else + throw new RuntimeException("coding error"); + } + if (selector.length > 0) { + for (int i = selector.length - 1; i > 0; i--) + decls = new Builder(parser, context).add(selector[i], decls); + style.add(selector[0], decls); + } + } + } else if (rule instanceof RulePage) + style.add(of(parser, (RulePage)rule)); + else if (rule instanceof RuleVolume) + style.add(of(parser, (RuleVolume)rule)); + else if (rule instanceof RuleTextTransform) { + String name = ((RuleTextTransform)rule).getName(); + Builder textTransform = new Builder(parser, Context.TEXT_TRANSFORM); + for (Declaration d : (RuleTextTransform)rule) + textTransform.add(d); + if (name == null) + style.add("@text-transform", textTransform); + else + style.add("@text-transform", new Builder(parser, Context.TEXT_TRANSFORM).add("& " + name, textTransform)); } + else if (rule instanceof RuleHyphenationResource) + style.add(of(parser, (RuleHyphenationResource)rule)); + else if (rule instanceof RuleCounterStyle) { + if (counterStyleRules == null) + counterStyleRules = new HashMap<>(); + counterStyleRules.put(((RuleCounterStyle)rule).getName(), (RuleCounterStyle)rule); } + else if (rule instanceof RuleMargin) { + Builder margin = new Builder(parser, Context.PAGE); + for (Declaration d : (RuleMargin)rule) + margin.add(d); + style.add("@" + ((RuleMargin)rule).getMarginArea(), margin); + } else if (rule instanceof RuleVolumeArea) + style.add(of(parser, (RuleVolumeArea)rule)); + else if (rule instanceof RuleRelativePage) + style.add(of(parser, (RuleRelativePage)rule)); + else if (rule instanceof RuleRelativeVolume) + style.add(of(parser, (RuleRelativeVolume)rule)); + else if (rule instanceof RuleRelativeHyphenationResource) + style.add(of(parser, (RuleRelativeHyphenationResource)rule)); + else if (rule instanceof VendorAtRule) + style.add("@" + ((VendorAtRule)rule).getName(), of(parser, (VendorAtRule>)rule)); + else + throw new RuntimeException("coding error"); + } + if (counterStyleRules != null) { + for (Map.Entry e : CounterStyle.parseCounterStyleRules(counterStyleRules.values()).entrySet()) { + Builder counterStyle = new Builder(parser, Context.COUNTER_STYLE, e.getValue()); + for (Declaration d : counterStyleRules.get(e.getKey())) + counterStyle.add(d); + if (context == Context.COUNTER_STYLE) + style.add("& " + e.getKey(), counterStyle); + else + style.add("@counter-style", new Builder(parser, Context.COUNTER_STYLE).add("& " + e.getKey(), counterStyle)); + } + } + return style.build(); + } + + // used in BrailleCssParser + static BrailleCssStyle of(BrailleCssParser parser, Context context, String inlineStyle) { + if (context == null) + throw new IllegalArgumentException(); + return of(parser, context, new InlineStyle(inlineStyle, context, null, + parser.getBrailleCSSParserFactory())); + } + + // used in BrailleCssParser + BrailleCssStyle inheritFrom(ParsedDeclarations base) { + if (declarations != null && !(declarations instanceof ParsedDeclarations)) + throw new IllegalStateException(); + ParsedDeclarations decls = declarations != null + ? (ParsedDeclarations)declarations + : ParsedDeclarations.EMPTY; + decls = decls.inheritFrom(base); + SortedMap nested = null; + if (nestedStyles != null) + for (String sel : nestedStyles.keySet()) + if (!sel.startsWith("@")) { + BrailleCssStyle n = nestedStyles.get(sel); + BrailleCssStyle nn; { + if (sel.equals("&::before") || + sel.equals("&::after") || + (sel.startsWith("&:") && !sel.startsWith("&::"))) + // pseudo-classes and ::before and ::after pseudo-elements inherit from main style + nn = n.inheritFrom(decls); + else + nn = n.inheritFrom(base); + } + if (n != nn) { + if (nested == null) + nested = new TreeMap<>(nestedStyles); + nested.put(sel, nn); + } + } + if (declarations != null || !decls.isEmpty() || nested != null) { + BrailleCssStyle copy = clone(); + copy.declarations = decls.isEmpty() ? new ParsedDeclarations(parser, context, null) : decls; + copy.declarationMap = null; + if (nested != null) { + copy.nestedStyles = nested; + copy.rules = null; + } + copy.serialized = null; + return copy; + } + return this; + } + + // note that this BrailleCssStyle will never be cached because the context is unknown + public static BrailleCssStyle of(String selector, BrailleCssStyle style) { + return new Builder().add(selector, style).build(); + } + + private static final Comparator sortSelectors = new Comparator() { + public int compare(String selector1, String selector2) { + if (selector1.startsWith("&") && !selector2.startsWith("&")) + return 1; + else if (!selector1.startsWith("&") && selector2.startsWith("&")) + return -1; + else + return selector1.compareTo(selector2); + } + }; + + /* Split the selector parts and serialize */ + private static String[] serializeSelector(List combinedSelector) { + List selector = new ArrayList<>(); + for (CombinatorSelectorPart part : flattenSelector(combinedSelector)) + selector.add("&" + part); + return selector.toArray(new String[selector.size()]); + } + + /* Convert a combined selector, which may contain pseudo element parts with other pseudo + * elements stacked onto them, into a flat list of selector parts and combinators */ + private static List flattenSelector(List combinedSelector) { + List selector = new ArrayList<>(); + for (Selector s : combinedSelector) + flattenSelector(selector, s); + return selector; + } + + private static void flattenSelector(List collect, Selector selector) { + Combinator combinator = selector.getCombinator(); + for (SelectorPart part : selector) { + flattenSelector(collect, combinator, part); + combinator = null; + } + } + + private static void flattenSelector(List collect, Combinator combinator, SelectorPart part) { + collect.add(new CombinatorSelectorPart(combinator, part)); + if (part instanceof PseudoElementImpl) { + PseudoElementImpl pe = (PseudoElementImpl)part; + if (!pe.getCombinedSelectors().isEmpty()) + for (Selector s: pe.getCombinedSelectors()) + flattenSelector(collect, s); + else { + if (!pe.getPseudoClasses().isEmpty()) + for (PseudoClass pc : pe.getPseudoClasses()) + collect.add(new CombinatorSelectorPart(null, pc)); + if (pe.hasStackedPseudoElement()) + flattenSelector(collect, null, pe.getStackedPseudoElement()); + } + } + } + + private static class CombinatorSelectorPart { + final Combinator combinator; + final SelectorPart selector; + CombinatorSelectorPart(Combinator combinator, SelectorPart selector) { + this.combinator = combinator; + this.selector = selector; + } + @Override + public String toString() { + StringBuilder b = new StringBuilder(); + if (combinator != null) + b.append(combinator.value()); + if (selector instanceof PseudoElementImpl) { + PseudoElementImpl pe = (PseudoElementImpl)selector; + b.append(":"); + if (!pe.isSpecifiedAsClass()) + b.append(":"); + b.append(pe.getName()); + String[] args = pe.getArguments(); + if (args.length > 0) { + b.append("("); + for (int i = 0; i < args.length; i++) { + if (i > 0) b.append(", "); + b.append(args[i]); + } + b.append(")"); + } + } else + b.append(selector); + return b.toString(); + } + } +} diff --git a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/BrailleCssTreeBuilder.java b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/BrailleCssTreeBuilder.java deleted file mode 100644 index 86b0d41868..0000000000 --- a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/BrailleCssTreeBuilder.java +++ /dev/null @@ -1,293 +0,0 @@ -package org.daisy.pipeline.braille.css.impl; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; - -import cz.vutbr.web.css.Declaration; -import cz.vutbr.web.css.Rule; -import cz.vutbr.web.css.RuleBlock; -import cz.vutbr.web.css.RuleMargin; -import cz.vutbr.web.css.RulePage; -import cz.vutbr.web.css.Selector; -import cz.vutbr.web.css.Selector.Combinator; -import cz.vutbr.web.css.Selector.PseudoClass; -import cz.vutbr.web.css.Selector.SelectorPart; - -import org.daisy.braille.css.AnyAtRule; -import org.daisy.braille.css.InlineStyle; -import org.daisy.braille.css.InlineStyle.RuleMainBlock; -import org.daisy.braille.css.InlineStyle.RuleRelativeBlock; -import org.daisy.braille.css.InlineStyle.RuleRelativeHyphenationResource; -import org.daisy.braille.css.InlineStyle.RuleRelativePage; -import org.daisy.braille.css.InlineStyle.RuleRelativeVolume; -import org.daisy.braille.css.SelectorImpl.PseudoElementImpl; -import org.daisy.braille.css.RuleCounterStyle; -import org.daisy.braille.css.RuleHyphenationResource; -import org.daisy.braille.css.RuleTextTransform; -import org.daisy.braille.css.RuleVolume; -import org.daisy.braille.css.RuleVolumeArea; - -public final class BrailleCssTreeBuilder { - - private BrailleCssTreeBuilder() {} - - public static class Style implements Comparator { - - List declarations; - SortedMap nestedStyles; // sorted by key - - public int compare(String selector1, String selector2) { - if (selector1.startsWith("&") && !selector2.startsWith("&")) - return 1; - else if (!selector1.startsWith("&") && selector2.startsWith("&")) - return -1; - else - return selector1.compareTo(selector2); - } - - private Style add(Declaration declaration) { - if (declaration != null) { - if (this.declarations == null) this.declarations = new ArrayList(); - declarations.add(declaration); - } - return this; - } - - private Style add(Iterable declarations) { - if (declarations != null) - for (Declaration d : declarations) - add(d); - return this; - } - - Style add(String selector, Style nestedStyle) { - if (this.nestedStyles == null) this.nestedStyles = new TreeMap(this); - if (selector == null) - add(nestedStyle); - if (this.nestedStyles.containsKey(selector)) - this.nestedStyles.get(selector).add(nestedStyle); - else - this.nestedStyles.put(selector, nestedStyle); - return this; - } - - Style add(String[] selector, Style nestedStyle) { - if (selector.length == 0) - return this; - for (int i = selector.length - 1; i > 0; i--) - nestedStyle = new Style().add(selector[i], nestedStyle); - return add(selector[0], nestedStyle); - } - - private Style add(Style style) { - add(style.declarations); - if (style.nestedStyles != null) - for (Map.Entry e : style.nestedStyles.entrySet()) - add(e.getKey(), e.getValue()); - return this; - } - - static Style of(RulePage page) { - // assumed to be anonymous page - Style style = new Style(); - for (Rule r : page) - if (r instanceof Declaration) - style.add((Declaration)r); - else if (r instanceof RuleMargin) - style.add("@" + ((RuleMargin)r).getMarginArea(), - new Style().add((List)r)); - else - throw new RuntimeException("coding error"); - String pseudo = page.getPseudo(); - return pseudo == null - ? style - : new Style().add("&:" + pseudo, style); - } - - private static Style of(RuleVolumeArea volumeArea) { - Style style = new Style(); - for (Rule r : volumeArea) - if (r instanceof Declaration) - style.add((Declaration)r); - else if (r instanceof RulePage) - style.add("@page", Style.of((RulePage)r)); - else - throw new RuntimeException("coding error"); - return style; - } - - private static Style of(RuleVolume volume) { - Style style = new Style(); - for (Rule r : volume) - if (r instanceof Declaration) - style.add((Declaration)r); - else if (r instanceof RuleVolumeArea) - style.add("@" + ((RuleVolumeArea)r).getVolumeArea().value, Style.of((RuleVolumeArea)r)); - else - throw new RuntimeException("coding error"); - String pseudo = volume.getPseudo(); - return pseudo == null - ? style - : new Style().add("&:" + pseudo, style); - } - - private static Style of(RuleHyphenationResource rule) { - return new Style().add("&:lang(" - + BrailleCssSerializer.serializeLanguageRanges(rule.getLanguageRanges()) - + ")", - new Style().add((List)rule)); - } - - private static Style of(AnyAtRule rule) { - Style style = new Style(); - for (Rule r : rule) - if (r instanceof Declaration) - style.add((Declaration)r); - else if (r instanceof AnyAtRule) - style.add("@" + ((AnyAtRule)r).getName(), Style.of((AnyAtRule)r)); - else - throw new RuntimeException("coding error"); - return style; - } - - public static Style of(InlineStyle inlineStyle) { - Style style = new Style(); - for (RuleBlock rule : inlineStyle) { - if (rule instanceof RuleMainBlock) - // Note that the declarations have not been transformed by BrailleCSSDeclarationTransformer yet - style.add((List)rule); - else if (rule instanceof RuleRelativeBlock) { - String[] selector = serializeSelector(((RuleRelativeBlock)rule).getSelector()); - Style decls = new Style(); { - for (Rule r : (RuleRelativeBlock)rule) - if (r instanceof Declaration) - decls.add((Declaration)r); - else if (r instanceof RulePage) - decls.add("@page", Style.of((RulePage)r)); - else - throw new RuntimeException("coding error"); - } - style.add(selector, decls); } - else if (rule instanceof RulePage) - style.add("@page", Style.of((RulePage)rule)); - else if (rule instanceof RuleVolume) - style.add("@volume", Style.of((RuleVolume)rule)); - else if (rule instanceof RuleTextTransform) { - String name = ((RuleTextTransform)rule).getName(); - Style textTransform = new Style().add((List)rule); - if (name == null) - style.add("@text-transform", textTransform); - else - style.add("@text-transform", new Style().add("& " + name, textTransform)); } - else if (rule instanceof RuleHyphenationResource) - style.add("@hyphenation-resource", Style.of((RuleHyphenationResource)rule)); - else if (rule instanceof RuleCounterStyle) { - String name = ((RuleCounterStyle)rule).getName(); - Style counterStyle = new Style().add((List)rule); - style.add("@counter-style", new Style().add("& " + name, counterStyle)); } - else if (rule instanceof RuleMargin) - style.add("@" + ((RuleMargin)rule).getMarginArea(), - new Style().add((List)rule)); - else if (rule instanceof RuleVolumeArea) - style.add("@" + ((RuleVolumeArea)rule).getVolumeArea().value, - Style.of((RuleVolumeArea)rule)); - else if (rule instanceof RuleRelativePage) - style.add(Style.of(((RuleRelativePage)rule).asRulePage())); - else if (rule instanceof RuleRelativeVolume) - style.add(Style.of(((RuleRelativeVolume)rule).asRuleVolume())); - else if (rule instanceof RuleRelativeHyphenationResource) - style.add(Style.of(((RuleRelativeHyphenationResource)rule).asRuleHyphenationResource())); - else if (rule instanceof AnyAtRule) - style.add("@" + ((AnyAtRule)rule).getName(), - Style.of((AnyAtRule)rule)); - else - throw new RuntimeException("coding error"); - } - return style; - } - - @Override - public String toString() { - return BrailleCssSerializer.toString(this); - } - } - - /* Split the selector parts and serialize */ - private static String[] serializeSelector(List combinedSelector) { - List selector = new ArrayList<>(); - for (CombinatorSelectorPart part : flattenSelector(combinedSelector)) - selector.add("&" + part); - return selector.toArray(new String[selector.size()]); - } - - /* Convert a combined selector, which may contain pseudo element parts with other pseudo - * elements stacked onto them, into a flat list of selector parts and combinators */ - private static List flattenSelector(List combinedSelector) { - List selector = new ArrayList<>(); - for (Selector s : combinedSelector) - flattenSelector(selector, s); - return selector; - } - - private static void flattenSelector(List collect, Selector selector) { - Combinator combinator = selector.getCombinator(); - for (SelectorPart part : selector) { - flattenSelector(collect, combinator, part); - combinator = null; - } - } - - private static void flattenSelector(List collect, Combinator combinator, SelectorPart part) { - collect.add(new CombinatorSelectorPart(combinator, part)); - if (part instanceof PseudoElementImpl) { - PseudoElementImpl pe = (PseudoElementImpl)part; - if (!pe.getCombinedSelectors().isEmpty()) - for (Selector s: pe.getCombinedSelectors()) - flattenSelector(collect, s); - else { - if (!pe.getPseudoClasses().isEmpty()) - for (PseudoClass pc : pe.getPseudoClasses()) - collect.add(new CombinatorSelectorPart(null, pc)); - if (pe.hasStackedPseudoElement()) - flattenSelector(collect, null, pe.getStackedPseudoElement()); - } - } - } - - private static class CombinatorSelectorPart { - final Combinator combinator; - final SelectorPart selector; - CombinatorSelectorPart(Combinator combinator, SelectorPart selector) { - this.combinator = combinator; - this.selector = selector; - } - @Override - public String toString() { - StringBuilder b = new StringBuilder(); - if (combinator != null) - b.append(combinator.value()); - if (selector instanceof PseudoElementImpl) { - PseudoElementImpl pe = (PseudoElementImpl)selector; - b.append(":"); - if (!pe.isSpecifiedAsClass()) - b.append(":"); - b.append(pe.getName()); - String[] args = pe.getArguments(); - if (args.length > 0) { - b.append("("); - for (int i = 0; i < args.length; i++) { - if (i > 0) b.append(", "); - b.append(args[i]); - } - b.append(")"); - } - } else - b.append(selector); - return b.toString(); - } - } -} diff --git a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/ContentList.java b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/ContentList.java new file mode 100644 index 0000000000..1ace6dcf58 --- /dev/null +++ b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/ContentList.java @@ -0,0 +1,840 @@ +package org.daisy.pipeline.braille.css.impl; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +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.TermLengthOrPercent; +import cz.vutbr.web.css.TermPercent; +import cz.vutbr.web.css.TermString; +import cz.vutbr.web.css.TermURI; +import cz.vutbr.web.csskit.TermStringImpl; +import cz.vutbr.web.csskit.TermURIImpl; + +import org.daisy.braille.css.BrailleCSSExtension; +import org.daisy.braille.css.BrailleCSSParserFactory.Context; +import org.daisy.braille.css.SupportedBrailleCSS; +import org.daisy.common.file.URLs; + +import org.w3c.dom.Attr; +import org.w3c.dom.Element; + +/** + * This class is immutable + */ +public class ContentList extends AbstractList> implements Term { + + private List> list; + private final BrailleCssParser parser; + private final Context context; + + private ContentList(BrailleCssParser parser, Context context, List> list) { + super(); + this.list = list; + this.parser = parser; + this.context = context; + } + + /** + * @param list assumed to not change + */ + public static ContentList of(BrailleCssParser parser, Context context, List> list) { + return of(parser, null, context, list); + } + + public static ContentList of(BrailleCssParser parser, + Collection extensions, + Context context, + List> list) { + List> items = new ArrayList<>(); + for (Term t : list) { + if (t instanceof UnmodifiableTermString || + t instanceof AttrFunction || + t instanceof ContentFunction || + t instanceof StringFunction || + t instanceof CounterFunction || + t instanceof TextFunction || + t instanceof LeaderFunction || + t instanceof FlowFunction) + items.add(t); + else if (t instanceof TermString) + items.add(new UnmodifiableTermString((TermString)t)); + else if (t instanceof TermFunction) { + TermFunction func = (TermFunction)t; + String funcname = func.getFunctionName(); + if ("attr".equals(funcname)) { + AttrFunction f = AttrFunction.of(func); + if (f != null && !f.asURL) items.add(f); } + else if ("content".equals(funcname) || "target-content".equals(funcname)) { + ContentFunction f = ContentFunction.of(func); + if (f != null) items.add(f); } + else if ("string".equals(funcname) || "target-string".equals(funcname)) { + StringFunction f = StringFunction.of(func); + if (f != null) items.add(f); } + else if ("counter".equals(funcname) || "target-counter".equals(funcname)) { + CounterFunction f = CounterFunction.of(func, extensions); + if (f != null) items.add(f); } + else if ("target-text".equals(funcname)) { + TextFunction f = TextFunction.of(func); + if (f != null) items.add(f); } + else if ("leader".equals(funcname)) { + LeaderFunction f = LeaderFunction.of(func); + if (f != null) items.add(f); } + else if ("flow".equals(funcname)) { + FlowFunction f = FlowFunction.of(func); + if (f != null) items.add(f); } + else if (funcname.matches("-(\\p{L}|_)+-.*")) // vendor prefixed + items.add(func); + } else + throw new IllegalArgumentException("unexpected term in content list: " + t); + } + return new ContentList(parser, context, items); + } + + @Override + public Term get(int index) { + return list.get(index); + } + + @Override + public int size() { + return list.size(); + } + + @Override + public ContentList getValue() { + return this; + } + + @Override + public Operator getOperator() { + return null; + } + + @Override + public ContentList shallowClone() { + try { + return (ContentList)super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError("coding error"); + } + } + + @Override + public ContentList clone() { + ContentList clone = shallowClone(); + clone.list = new ArrayList<>(list); + return clone; + } + + @Override + public ContentList setValue(ContentList value) { + throw new UnsupportedOperationException("Unmodifiable"); + } + + @Override + public ContentList setOperator(Operator operator) { + throw new UnsupportedOperationException("Unmodifiable"); + } + + /** + * {@link BrailleCssParser} instance that was used to create this {@link ContentList}. + */ + public BrailleCssParser getParser() { + return parser; + } + + /** + * {@link Context} in which this {@link ContentList} was created. + */ + public Context getContext() { + return context; + } + + /** + * Evaluate attr() and content() values. + * + * This method is mutating, but we can still say that the object is immutable because the method + * is package private and only used by {@link BrailleCssStyle}, {@link StringSetList} and {@link + * BrailleCssParser} _before_ the object is made available. + */ + void evaluate(Element context) { + for (int i = 0; i < list.size(); i++) { + Term t = list.get(i); + if (t instanceof AttrFunction) + list.set(i, ((AttrFunction)t).evaluate(context)); + if (t instanceof ContentFunction) + list.set(i, ((ContentFunction)t).evaluate(context)); + else if (t instanceof StringFunction) + list.set(i, ((StringFunction)t).evaluate(context)); + else if (t instanceof CounterFunction) + list.set(i, ((CounterFunction)t).evaluate(context)); + else if (t instanceof TextFunction) + list.set(i, ((TextFunction)t).evaluate(context)); + if (list.get(i) == null) + list.remove(i--); + } + } + + /** + * This class is immutable + */ + public static class AttrFunction extends UnmodifiableTerm { + + // these fields need to be package private because they are used in BrailleCssSerializer + final TermIdent name; + final boolean asURL; + + private AttrFunction(TermIdent name, boolean asURL) { + this.name = name; + this.asURL = asURL; + } + + /** + * @param fn assumed to not change + */ + private static AttrFunction of(TermFunction fn) { + if (!"attr".equals(fn.getFunctionName())) + return null; + TermIdent name = null; + boolean asURL = false; + for (Term arg : fn) + if (name == null) + if (arg instanceof TermIdent) + name = (TermIdent)arg; + else + return null; + else if (asURL == false) + if (arg instanceof TermIdent) + if ("url".equals(((TermIdent)arg).getValue())) + asURL = true; + else + return null; + else + return null; + else + return null; + if (name != null) + return new AttrFunction(name, asURL); + return null; + } + + /** + * Evaluate attr() and content() values. + */ + private Term evaluate(Element context) { + Attr attr = context.getAttributeNode(name.getValue()); + if (attr == null) + return null; + if (asURL) { + TermURI evaluated = new TermURIImpl(){}.setValue(attr.getValue()); + String base = context.getBaseURI(); + if (base != null && !"".equals(base)) + evaluated.setBase(URLs.asURL(base)); + return new UnmodifiableTermURI(evaluated); + } else + return new UnmodifiableTermString(new TermStringImpl(){}.setValue(attr.getValue())); + } + } + + public static class URL { + + // these fields need to be package private because they are used in BrailleCssSerializer + final TermURI url; + final AttrFunction urlAttr; + + private URL(TermURI url) { + this.url = url; + this.urlAttr = null; + } + + private URL(AttrFunction urlAttr) { + this.url = null; + this.urlAttr = urlAttr; + } + + /** + * @param t assumed to not change + */ + private static URL of(Term t) { + if (t instanceof TermURI) + return new URL((TermURI)t); + else if (t instanceof TermFunction) { + AttrFunction a = AttrFunction.of((TermFunction)t); + if (a != null) { + if (!a.asURL) + a = new AttrFunction(a.name, true); + return new URL(a); + } else + return null; } + else + return null; + } + + /** + * Evaluate attr() and content() values. + */ + private URL evaluate(Element context) { + if (urlAttr != null) { + Attr attr = context.getAttributeNode(urlAttr.name.getValue()); + if (attr == null) + return null; + TermURI evaluated = new TermURIImpl(){}.setValue(attr.getValue()); + String base = context.getBaseURI(); + if (base != null && !"".equals(base)) + evaluated.setBase(URLs.asURL(base)); + return new URL(evaluated); + } else + return this; + } + } + + /** + * This class is immutable + */ + public static class ContentFunction extends UnmodifiableTerm { + + // this field needs to be package private because it is used in BrailleCssSerializer + final Optional target; + + private ContentFunction() { + this.target = Optional.empty(); + } + + private ContentFunction(URL target) { + this.target = Optional.of(target); + } + + /** + * @param fn assumed to not change + */ + private static ContentFunction of(TermFunction fn) { + boolean needTarget = false; + if ("target-content".equals(fn.getFunctionName())) + needTarget = true; + else if (!"content".equals(fn.getFunctionName())) + return null; + URL target = null; + for (Term arg : fn) + if (needTarget && target == null) { + target = URL.of(arg); + if (target == null) + return null; } + else + return null; + if (needTarget && target != null) + return new ContentFunction(target); + else if (!needTarget && target == null) + return new ContentFunction(); + return null; + } + + /** + * Evaluate attr() and content() values. + */ + private Term evaluate(Element context) { + if (target.isPresent()) { + URL url = target.get(); + URL evaluatedURL = url.evaluate(context); + if (evaluatedURL == null) + return null; + else if (evaluatedURL != url) + return new ContentFunction(evaluatedURL); + } else + return new UnmodifiableTermString(new TermStringImpl(){}.setValue(context.getTextContent())); + return this; + } + } + + /** + * This class is immutable + */ + public static class StringFunction extends UnmodifiableTerm { + + // these fields need to be package private because they are used in BrailleCssSerializer + final Optional target; + final TermIdent name; + final Optional scope; + + public enum Scope { + + FIRST("first"), + START("start"), + LAST("last"), + START_EXCEPT_FIRST("start-except-first"), + START_EXCEPT_LAST("start-except-last"), + LAST_EXCEPT_START("last-except-start"), + PAGE_FIRST("page-first"), + PAGE_START("page-start"), + PAGE_LAST("page-last"), + PAGE_START_EXCEPT_LAST("page-start-except-last"), + PAGE_LAST_EXCEPT_START("page-last-except-start"), + SPREAD_FIRST("spread-first"), + SPREAD_START("spread-start"), + SPREAD_LAST("spread-last"), + SPREAD_START_EXCEPT_LAST("spread-start-except-last"), + SPREAD_LAST_EXCEPT_START("spread-last-except-start"); + + private final String ident; + + private static final Map lookup = new HashMap<>(); static { + for (Scope scope : EnumSet.allOf(Scope.class)) + lookup.put(scope.ident, scope); } + + private Scope(String ident) { + this.ident = ident; + } + + public static Scope of(TermIdent term) throws IllegalArgumentException { + Scope scope = lookup.get(term.getValue()); + if (scope == null) + throw new IllegalArgumentException("unexpected scope argument in string() function: " + term.getValue()); + return scope; + } + + @Override + public String toString() { + return ident; + } + } + + private StringFunction(TermIdent name, Optional scope) throws IllegalArgumentException { + this.target = Optional.empty(); + this.name = name; + this.scope = scope.map(Scope::of); + } + + private StringFunction(URL target, TermIdent name) { + this.target = Optional.of(target); + this.name = name; + this.scope = Optional.empty(); + } + + /** + * @param fn assumed to not change + */ + private static StringFunction of(TermFunction fn) { + boolean needTarget = false; + if ("target-string".equals(fn.getFunctionName())) + needTarget = true; + else if (!"string".equals(fn.getFunctionName())) + return null; + URL target = null; + TermIdent name = null; + TermIdent scope = null; + for (Term arg : fn) + if (needTarget && target == null) { + target = URL.of(arg); + if (target == null) + return null; } + else if (name == null) + if (arg instanceof TermIdent) + name = (TermIdent)arg; + else + return null; + else if (scope == null) + if (arg instanceof TermIdent) + scope = (TermIdent)arg; + else + return null; + else + return null; + if (name != null) + if (needTarget && target != null) + if (scope != null) + return null; + else + return new StringFunction(target, name); + else if (!needTarget && target == null) + try { + return new StringFunction(name, Optional.ofNullable(scope)); + } catch (IllegalArgumentException e) { + // invalid scope argument + } + return null; + } + + /** + * Evaluate attr() and content() values. + */ + private StringFunction evaluate(Element context) { + if (target.isPresent()) { + URL url = target.get(); + URL evaluatedURL = url.evaluate(context); + if (evaluatedURL == null) + return null; + else if (evaluatedURL != url) + return new StringFunction(evaluatedURL, name); + } + return this; + } + } + + public static class CounterStyle { + + // these fields need to be package private because they are used in BrailleCssSerializer + final String name; + final TermString symbol; + final TermFunction symbols; + final org.daisy.pipeline.css.CounterStyle style; + + private CounterStyle(TermIdent name) { + if ("none".equals(name.getValue())) + this.name = null; + else + this.name = name.getValue(); + this.symbol = null; + this.symbols = null; + this.style = null; + } + + private CounterStyle(TermString symbol) { + this.name = null; + this.symbol = symbol; + this.symbols = null; + this.style = org.daisy.pipeline.css.CounterStyle.constant(symbol.getValue()); + } + + private CounterStyle(TermFunction style) throws IllegalArgumentException { + this.name = null; + this.symbol = null; + this.symbols = style; + this.style = org.daisy.pipeline.css.CounterStyle.fromSymbolsFunction(style); + } + + /** + * @param t assumed to not change + */ + private static CounterStyle of(Term t) { + if (t instanceof TermIdent) + return new CounterStyle((TermIdent)t); + else if (t instanceof TermString) + return new CounterStyle((TermString)t); + else if (t instanceof TermFunction) + try { + return new CounterStyle((TermFunction)t); + } catch (IllegalArgumentException e) { + // invalid symbols() function + } + return null; + } + } + + /** + * This class is immutable + */ + public static class CounterFunction extends UnmodifiableTerm + implements org.daisy.pipeline.braille.css.CounterFunction { + + // these fields need to be package private because they are used in BrailleCssSerializer + final Optional target; + final TermIdent name; + final Optional style; + + @Override + public String getCounter() { + return name.getValue(); + } + + @Override + public Optional getStyle() { + return style.map(x -> x.style); + } + + private CounterFunction(TermIdent name, Optional style) { + this.target = Optional.empty(); + this.name = name; + this.style = style; + } + + private CounterFunction(URL target, TermIdent name, Optional style) { + this.target = Optional.of(target); + this.name = name; + this.style = style; + } + + /** + * @param fn assumed to not change + */ + private static CounterFunction of(TermFunction fn, Collection extensions) { + boolean needTarget = false; + if ("target-counter".equals(fn.getFunctionName())) + needTarget = true; + else if (!"counter".equals(fn.getFunctionName())) + return null; + URL target = null; + TermIdent name = null; + CounterStyle style = null; + for (Term arg : fn) + if (needTarget && target == null) { + target = URL.of(arg); + if (target == null) + return null; } + else if (name == null) { + if (extensions != null) + if (arg instanceof TermIdent && ((TermIdent)arg).getValue().startsWith("-")) { + // if the counter name starts with an extension prefix, let the extension parse it + String n = ((TermIdent)arg).getValue(); + for (BrailleCSSExtension x : extensions) + if (n.startsWith(x.getPrefix())) + try { + name = x.parseCounterName(arg); + break; + } catch (IllegalArgumentException e) { + return null; + } + } else + // other, give extensions the chance to normalize names + for (BrailleCSSExtension x : extensions) + try { + name = x.parseCounterName(arg); + break; + } catch (IllegalArgumentException e) { + // continue + } + if (name == null && arg instanceof TermIdent) + name = (TermIdent)arg; + if (name == null) + return null; + } else if (style == null) { + style = CounterStyle.of(arg); + if (style == null) + return null; } + else + return null; + if (name != null) + if (needTarget && target != null) + return new CounterFunction(target, name, Optional.ofNullable(style)); + else if (!needTarget && target == null) + return new CounterFunction(name, Optional.ofNullable(style)); + return null; + } + + /** + * Evaluate attr() and content() values. + */ + private CounterFunction evaluate(Element context) { + if (target.isPresent()) { + URL url = target.get(); + URL evaluatedURL = url.evaluate(context); + if (evaluatedURL == null) + return null; + else if (evaluatedURL != url) + return new CounterFunction(evaluatedURL, name, style); + } + return this; + } + } + + /** + * This class is immutable + */ + public static class TextFunction extends UnmodifiableTerm { + + // this field needs to be package private because it is used in BrailleCssSerializer + final URL target; + + private TextFunction(URL target) { + this.target = target; + } + + /** + * @param fn assumed to not change + */ + private static TextFunction of(TermFunction fn) { + if (!"target-text".equals(fn.getFunctionName())) + return null; + URL target = null; + for (Term arg : fn) + if (target == null) { + target = URL.of(arg); + if (target == null) + return null; } + else + return null; + if (target != null) + return new TextFunction(target); + return null; + } + + /** + * Evaluate attr() and content() values. + */ + private TextFunction evaluate(Element context) { + URL evaluatedTarget = target.evaluate(context); + if (evaluatedTarget == null) + return null; + else if (evaluatedTarget != target) + return new TextFunction(evaluatedTarget); + return this; + } + } + + /** + * This class is immutable + */ + public static class LeaderFunction extends UnmodifiableTerm { + + // these fields need to be package private because they are used in BrailleCssSerializer + final TermString pattern; + final Optional position; + final Optional alignment; + + public enum Alignment { + + LEFT("left"), + CENTER("center"), + RIGHT("right"); + + private final String ident; + + private static final Map lookup = new HashMap<>(); static { + for (Alignment alignment : EnumSet.allOf(Alignment.class)) + lookup.put(alignment.ident, alignment); } + + private Alignment(String ident) { + this.ident = ident; + } + + public static Alignment of(TermIdent term) throws IllegalArgumentException { + Alignment alignment = lookup.get(term.getValue()); + if (alignment == null) + throw new IllegalArgumentException("unexpected alignment argument in leader() function: " + term.getValue()); + return alignment; + } + + @Override + public String toString() { + return ident; + } + } + + private LeaderFunction(TermString pattern, Optional position, Optional alignment) + throws IllegalArgumentException { + this.pattern = pattern; + this.position = position; + this.alignment = alignment.map(Alignment::of); + } + + /** + * @param fn assumed to not change + */ + private static LeaderFunction of(TermFunction fn) { + if (!"leader".equals(fn.getFunctionName())) + return null; + TermString pattern = null; + TermLengthOrPercent position = null; + TermIdent alignment = null; + for (Term arg : fn) + if (pattern == null) + if (arg instanceof TermString) { + pattern = (TermString)arg; + if (!pattern.getValue().matches("[\\u2800-\\u28ff]*")) // \\p{IsBraillePatterns}* + return null; } + else + return null; + else if (position == null) + if (arg instanceof TermInteger || arg instanceof TermPercent) + position = (TermLengthOrPercent)arg; + else + return null; + else if (alignment == null) + if (arg instanceof TermIdent) + alignment = (TermIdent)arg; + else + return null; + else + return null; + if (pattern != null) + try { + return new LeaderFunction(pattern, Optional.ofNullable(position), Optional.ofNullable(alignment)); + } catch (IllegalArgumentException e) { + // invalid alignment argument + } + return null; + } + } + + /** + * This class is immutable + */ + public static class FlowFunction extends UnmodifiableTerm { + + // these fields need to be package private because they are used in BrailleCssSerializer + final TermIdent from; + final Optional scope; + + public enum Scope { + + DOCUMENT("document"), + VOLUME("volume"), + PAGE("page"); + + private final String ident; + + private static final Map lookup = new HashMap<>(); static { + for (Scope scope : EnumSet.allOf(Scope.class)) + lookup.put(scope.ident, scope); } + + private Scope(String ident) { + this.ident = ident; + } + + public static Scope of(TermIdent term) throws IllegalArgumentException { + Scope scope = lookup.get(term.getValue()); + if (scope == null) + throw new IllegalArgumentException("unexpected scope argument in string() function: " + term.getValue()); + return scope; + } + + @Override + public String toString() { + return ident; + } + } + + private FlowFunction(TermIdent from, Optional scope) throws IllegalArgumentException { + this.from = from; + this.scope = scope.map(Scope::of); + } + + /** + * @param fn assumed to not change + */ + private static FlowFunction of(TermFunction fn) { + if (!"flow".equals(fn.getFunctionName())) + return null; + TermIdent from = null; + TermIdent scope = null; + for (Term arg : fn) + if (from == null) + if (arg instanceof TermIdent) + from = (TermIdent)arg; + else + return null; + else if (scope == null) + if (arg instanceof TermIdent) + scope = (TermIdent)arg; + else + return null; + else + return null; + if (from != null) + try { + return new FlowFunction(from, Optional.ofNullable(scope)); + } catch (IllegalArgumentException e) { + // invalid scope argument + } + return null; + } + } +} diff --git a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/CounterSetList.java b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/CounterSetList.java new file mode 100644 index 0000000000..dbd4cb2fa2 --- /dev/null +++ b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/CounterSetList.java @@ -0,0 +1,116 @@ +package org.daisy.pipeline.braille.css.impl; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import cz.vutbr.web.css.Term; +import cz.vutbr.web.css.TermPair; + +/** + * This class is immutable + */ +public class CounterSetList extends AbstractList> implements Term { + + /** + * This class is immutable + */ + public static class CounterSet extends UnmodifiableTerm implements org.daisy.pipeline.braille.css.CounterSet { + private final String name; + private final Integer value; + private CounterSet(String name, Integer value) { + super(); + this.name = name; + this.value = value; + } + @Override + public String getKey() { + return name; + } + @Override + public Integer getValue() { + return value; + } + @Override + public TermPair setKey(String key) { + throw new UnsupportedOperationException("Unmodifiable"); + } + } + + private List> list; + + private CounterSetList(List> list) { + super(); + this.list = list; + } + + /** + * @param list assumed to not change + */ + public static CounterSetList of(List> list) { + Map pairs = new LinkedHashMap<>(); // preserves order of insertion + for (Term t : list) { + if (t instanceof TermPair) { + TermPair pair = (TermPair)t; + Object k = pair.getKey(); + Object v = pair.getValue(); + if (k instanceof String && v instanceof Integer) { + pairs.remove((String)k); + pairs.put((String)k, (Integer)v); + } else + throw new IllegalArgumentException("unexpected term in counter-set list: " + t); + } else + throw new IllegalArgumentException("unexpected term in counter-set list: " + t); + } + List> pairsList = new ArrayList(); + for (Map.Entry e : pairs.entrySet()) + pairsList.add(new CounterSet(e.getKey(), e.getValue())); + return new CounterSetList(pairsList); + } + + @Override + public Term get(int index) { + return list.get(index); + } + + @Override + public int size() { + return list.size(); + } + + @Override + public CounterSetList getValue() { + return this; + } + + @Override + public Operator getOperator() { + return null; + } + + @Override + public CounterSetList shallowClone() { + try { + return (CounterSetList)super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError("coding error"); + } + } + + @Override + public CounterSetList clone() { + return shallowClone(); + } + + @Override + public CounterSetList setValue(CounterSetList value) { + throw new UnsupportedOperationException("Unmodifiable"); + } + + @Override + public CounterSetList setOperator(Operator operator) { + throw new UnsupportedOperationException("Unmodifiable"); + } +} diff --git a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/StringSetList.java b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/StringSetList.java new file mode 100644 index 0000000000..6d1358d4af --- /dev/null +++ b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/StringSetList.java @@ -0,0 +1,160 @@ +package org.daisy.pipeline.braille.css.impl; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.List; + +import cz.vutbr.web.css.Term; +import cz.vutbr.web.css.TermList; +import cz.vutbr.web.css.TermPair; +import cz.vutbr.web.css.TermString; + +import org.daisy.braille.css.BrailleCSSParserFactory.Context; +import org.daisy.pipeline.braille.css.impl.ContentList.AttrFunction; +import org.daisy.pipeline.braille.css.impl.ContentList.ContentFunction; + +import org.w3c.dom.Element; + +/** + * This class is immutable + */ +public class StringSetList extends AbstractList> implements Term { + + /** + * This class is immutable + */ + public static class StringSet extends UnmodifiableTerm implements TermPair { + private final String name; + private final ContentList value; + private final Operator operator; + private StringSet(String name, ContentList value, Operator operator) throws IllegalArgumentException { + this(name, value, operator, false); + } + private StringSet(String name, ContentList value, Operator operator, boolean noCheck) throws IllegalArgumentException { + super(); + if (!noCheck) { + for (Term v : value) + if (!(v instanceof TermString + || v instanceof AttrFunction + || (v instanceof ContentFunction && !((ContentFunction)v).target.isPresent()))) + throw new IllegalArgumentException("unexpected term in string-set list: " + v); + } + this.name = name; + this.value = value; + this.operator = operator; + } + @Override + public String getKey() { + return name; + } + @Override + public ContentList getValue() { + return value; + } + @Override + public Operator getOperator() { + return operator; + } + @Override + public TermPair setKey(String key) { + throw new UnsupportedOperationException("Unmodifiable"); + } + @Override + public String toString() { + return BrailleCssSerializer.toString(this); + } + /** + * Evaluate attr() and content() values. + */ + private StringSet evaluate(Element context) { + ContentList evaluatedValue = value.clone(); + evaluatedValue.evaluate(context); + return new StringSet(name, evaluatedValue, operator, true); + } + } + + private List> list; + + private StringSetList(List> list) { + super(); + this.list = list; + } + + /** + * @param list assumed to not change + */ + public static StringSetList of(BrailleCssParser parser, Context context, TermList list) throws IllegalArgumentException { + List> pairs = new ArrayList<>(); + for (Term t : list) { + if (t instanceof TermPair) { + TermPair pair = (TermPair)t; + Object k = pair.getKey(); + Object v = pair.getValue(); + if (k instanceof String && v instanceof TermList) + pairs.add(new StringSet((String)k, ContentList.of(parser, context, (TermList)v), t.getOperator())); + else + throw new IllegalArgumentException("unexpected term in string-set list: " + t); + } else + throw new IllegalArgumentException("unexpected term in string-set list: " + t); + } + return new StringSetList(pairs); + } + + @Override + public Term get(int index) { + return list.get(index); + } + + @Override + public int size() { + return list.size(); + } + + @Override + public StringSetList getValue() { + return this; + } + + @Override + public Operator getOperator() { + return null; + } + + @Override + public StringSetList shallowClone() { + try { + return (StringSetList)super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError("coding error"); + } + } + + @Override + public StringSetList clone() { + StringSetList clone = shallowClone(); + clone.list = new ArrayList<>(list); + return clone; + } + + @Override + public StringSetList setValue(StringSetList value) { + throw new UnsupportedOperationException("Unmodifiable"); + } + + @Override + public StringSetList setOperator(Operator operator) { + throw new UnsupportedOperationException("Unmodifiable"); + } + + /** + * Evaluate attr() and content() values. + * + * This method is mutating, but we can still say that the object is immutable because the method + * is package private and only used by {@link BrailleCssStyle} and {@link BrailleCssParser} + * _before_ the object is made available. + */ + void evaluate(Element context) { + for (int i = 0; i < list.size(); i++) + list.set(i, ((StringSet)list.get(i)).evaluate(context)); + } +} diff --git a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/StyleTransformerImpl.java b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/StyleTransformerImpl.java new file mode 100644 index 0000000000..1ca4d3e8ec --- /dev/null +++ b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/StyleTransformerImpl.java @@ -0,0 +1,129 @@ +package org.daisy.pipeline.braille.css.impl; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import cz.vutbr.web.css.CSSProperty; +import cz.vutbr.web.css.Declaration; +import cz.vutbr.web.css.Term; + +import org.daisy.braille.css.BrailleCSSParserFactory; +import org.daisy.braille.css.BrailleCSSParserFactory.Context; +import org.daisy.braille.css.PropertyValue; +import org.daisy.braille.css.SimpleInlineStyle; +import org.daisy.braille.css.SupportedBrailleCSS; +import org.daisy.pipeline.braille.css.impl.BrailleCssParser.ParsedDeclaration; +import org.daisy.pipeline.braille.css.impl.BrailleCssParser.ParsedDeclarations; +import org.daisy.pipeline.braille.css.StyleTransformer; +import org.daisy.pipeline.braille.css.xpath.Style; +import org.daisy.pipeline.braille.css.xpath.impl.Stylesheet; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class StyleTransformerImpl implements StyleTransformer { + + private final static Logger logger = LoggerFactory.getLogger(StyleTransformer.class); + + private final SupportedBrailleCSS fromSupportedBrailleCSS; + private final SupportedBrailleCSS toSupportedBrailleCSS; + private final BrailleCssParser parser; + private final Set propertiesWithDifferentDefault; + + public StyleTransformerImpl(SupportedBrailleCSS fromSupportedBrailleCSS, + SupportedBrailleCSS toSupportedBrailleCSS) { + this.fromSupportedBrailleCSS = fromSupportedBrailleCSS; + this.toSupportedBrailleCSS = toSupportedBrailleCSS; + this.parser = new BrailleCssParser() { + @Override + public BrailleCSSParserFactory getBrailleCSSParserFactory() { + return null; }; // should be fine to return null (assuming parseInlineStyle() will not + // be called on this parser object) + @Override + public Optional getSupportedBrailleCSS(Context context) { + return Optional.of(toSupportedBrailleCSS); }}; + this.propertiesWithDifferentDefault = new HashSet<>(); { + for (String p : toSupportedBrailleCSS.getDefinedPropertyNames()) + if (fromSupportedBrailleCSS.isSupportedCSSProperty(p)) + if ("".equals(fromSupportedBrailleCSS.getDefaultProperty(p).toString()) + ? (Objects.equals(fromSupportedBrailleCSS.getDefaultProperty(p), + toSupportedBrailleCSS.getDefaultProperty(p)) && + Objects.equals(fromSupportedBrailleCSS.getDefaultValue(p), + toSupportedBrailleCSS.getDefaultValue(p))) + : Objects.equals(fromSupportedBrailleCSS.getDefaultProperty(p).toString(), + toSupportedBrailleCSS.getDefaultProperty(p).toString())) + ; // same default + else + propertiesWithDifferentDefault.add(p); + } + } + + @Override + public final SimpleInlineStyle transform(SimpleInlineStyle style) { + Map properties = new HashMap<>(); + Map> values = new HashMap<>(); + Map sourceDeclarations = new HashMap<>(); + List declarations = new ArrayList<>(); + for (PropertyValue d : style) { + if (d.getSupportedBrailleCSS() != fromSupportedBrailleCSS) + throw new IllegalArgumentException(); + String p = d.getProperty(); + if (!toSupportedBrailleCSS.isSupportedCSSProperty(p)) { + // ignore "inherit" (if style is based on parent, inherit has already been concretized; + // if not based on parent, this is equivalent to "initial") + if (d.getCSSProperty().equalsInherit()) + ; + // ignore "initial" (should already have been concretized; properties with different + // default in the output model are handled below) + else if (d.getCSSProperty().equalsInitial()) + ; + // ignore values that are the default (equivalent to "initial") + else if (Objects.equals(d.getCSSProperty(), fromSupportedBrailleCSS.getDefaultProperty(p)) && + Objects.equals(d.getValue(), fromSupportedBrailleCSS.getDefaultValue(p))) + ; + else + logger.warn("Property '{}' not supported", p); + } else if (!toSupportedBrailleCSS.parseDeclaration(d, properties, values)) + logger.warn("Property '{}' not supported", d); + else + sourceDeclarations.put(p, d.getSourceDeclaration()); + } + for (String p : toSupportedBrailleCSS.getDefinedPropertyNames()) { + CSSProperty property = null; + Term value = null; + if (properties.containsKey(p)) + declarations.add(new ParsedDeclaration(parser, + Context.ELEMENT, + p, + properties.get(p), + values.get(p), + sourceDeclarations.get(p))); + else if (propertiesWithDifferentDefault.contains(p)) + declarations.add(new ParsedDeclaration(parser, + Context.ELEMENT, + p, + toSupportedBrailleCSS.getDefaultProperty(p), + toSupportedBrailleCSS.getDefaultValue(p), + null)); + } + return new ParsedDeclarations(parser, Context.ELEMENT, declarations); + } + + @Override + public final Style transform(Style style) { + if (!(style instanceof Stylesheet)) + throw new IllegalArgumentException(); + Stylesheet fs = (Stylesheet)style; + SimpleInlineStyle s = fs.style != null + ? fs.style.asSimpleInlineStyle(false) + : SimpleInlineStyle.EMPTY; + s = transform(s); + return Stylesheet.of(BrailleCssStyle.of(s)); + } +} diff --git a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/TextTransformList.java b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/TextTransformList.java new file mode 100644 index 0000000000..14576e4216 --- /dev/null +++ b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/TextTransformList.java @@ -0,0 +1,181 @@ +package org.daisy.pipeline.braille.css.impl; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.List; + +import org.daisy.braille.css.BrailleCSSProperty.TextTransform; +import org.daisy.braille.css.PropertyValue; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + +import cz.vutbr.web.css.CSSProperty; +import cz.vutbr.web.css.Term; +import cz.vutbr.web.css.TermIdent; +import cz.vutbr.web.css.TermList; +import cz.vutbr.web.csskit.TermIdentImpl; + +/** + * This class is immutable except when locked is set to false in which case terms may be removed. + */ +public class TextTransformList extends AbstractList> implements TermList { + + private List list; + + // object should always be cloned before it is unlocked + boolean locked = true; + + private final static ImmutableList INITIAL = ImmutableList.of(new UnmodifiableTermIdent(new TermIdentImpl(){}.setValue("initial"))); + private final static ImmutableList AUTO = ImmutableList.of(new UnmodifiableTermIdent(new TermIdentImpl(){}.setValue("auto"))); + private final static ImmutableList NONE = ImmutableList.of(new UnmodifiableTermIdent(new TermIdentImpl(){}.setValue("none"))); + + private TextTransformList(List list) { + super(); + this.list = list; + } + + /** + * @param value assumed to not change + */ + public static TextTransformList of(TermIdent value) { + String v = value.getValue(); + if (v.equalsIgnoreCase("initial")) + return new TextTransformList(INITIAL); + else if (v.equalsIgnoreCase("auto")) + return new TextTransformList(AUTO); + else if (v.equalsIgnoreCase("none")) + return new TextTransformList(NONE); + else if (v.equalsIgnoreCase("inherit")) + throw new IllegalArgumentException(); + else { + List list = new ArrayList<>(); + list.add(new UnmodifiableTermIdent(value)); + return new TextTransformList(list); + } + } + + /** + * @param value assumed to not change + */ + public static TextTransformList of(TermList value) { + List list = new ArrayList<>(); + for (Term t : value) { + if (t instanceof TermIdent) + list.add(new UnmodifiableTermIdent((TermIdent)t)); + else + throw new IllegalArgumentException("unexpected term in text-transform list: " + t); + } + return new TextTransformList(list); + } + + /** + * @param value assumed to not change + */ + public static TextTransformList of(PropertyValue value) { + CSSProperty p = value.getCSSProperty(); + if (p instanceof TextTransform) { + if (p == TextTransform.list_values) { + Term v = value.getValue(); + if (v instanceof TextTransformList) + return (TextTransformList)v; + else if (v instanceof TermList) + return of((TermList)v); + else + throw new IllegalStateException(); // should not happen + } else if (p == TextTransform.AUTO) + return new TextTransformList(AUTO); + else if (p == TextTransform.INITIAL) + return new TextTransformList(INITIAL); + else if (p == TextTransform.NONE) + return new TextTransformList(NONE); + } + throw new IllegalArgumentException(); + } + + @Override + public TermIdent get(int index) { + return list.get(index); + } + + @Override + public TermIdent remove(int index) { + if (locked) + throw new UnsupportedOperationException("Unmodifiable"); + return list.remove(index); + } + + @Override + public int size() { + return list.size(); + } + + @Override + public TextTransformList getValue() { + return this; + } + + @Override + public Operator getOperator() { + return null; + } + + @Override + public TextTransformList shallowClone() { + try { + return (TextTransformList)super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError("coding error"); + } + } + + @Override + public TextTransformList clone() { + TextTransformList clone = shallowClone(); + if (!(list instanceof ImmutableList)) + clone.list = new ArrayList<>(list); + return clone; + } + + @Override + public TextTransformList setValue(List> value) { + throw new UnsupportedOperationException("Unmodifiable"); + } + + @Override + public TextTransformList setOperator(Operator operator) { + throw new UnsupportedOperationException("Unmodifiable"); + } + + /** + * This method is mutating, but we can still say that the object is immutable because the method + * is package private and only used by {@link BrailleCssStyle} _before_ the object is made + * available. + */ + void inheritFrom(TextTransformList parent) { + if (equalsNone() || parent.equalsAuto() || parent.equalsInitial() || parent.equalsNone()) + ; + else if (equalsAuto() || equalsInitial()) + list = parent.list; + else { + List list = new ArrayList<>(); + list.addAll(this.list); + for (TermIdent t : parent.list) + if (!Iterables.any(list, x -> x.getValue().equalsIgnoreCase(t.getValue()))) + list.add(t); + this.list = list; + } + } + + public boolean equalsAuto() { + return list == AUTO; + } + + public boolean equalsInitial() { + return list == INITIAL; + } + + public boolean equalsNone() { + return list == NONE; + } +} diff --git a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/UnmodifiableTerm.java b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/UnmodifiableTerm.java new file mode 100644 index 0000000000..5bca393948 --- /dev/null +++ b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/UnmodifiableTerm.java @@ -0,0 +1,41 @@ +package org.daisy.pipeline.braille.css.impl; + +import cz.vutbr.web.css.Term; + +abstract class UnmodifiableTerm implements Term { + + @Override + public T getValue() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public Operator getOperator() { + return null; + } + + @SuppressWarnings("unchecked") + @Override + public UnmodifiableTerm shallowClone() { + try { + return (UnmodifiableTerm)super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError("coding error"); + } + } + + @Override + public UnmodifiableTerm clone() { + return shallowClone(); + } + + @Override + public UnmodifiableTerm setValue(T value) { + throw new UnsupportedOperationException("Unmodifiable"); + } + + @Override + public UnmodifiableTerm setOperator(Operator operator) { + throw new UnsupportedOperationException("Unmodifiable"); + } +} diff --git a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/UnmodifiableTermIdent.java b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/UnmodifiableTermIdent.java new file mode 100644 index 0000000000..ae002b7815 --- /dev/null +++ b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/UnmodifiableTermIdent.java @@ -0,0 +1,18 @@ +package org.daisy.pipeline.braille.css.impl; + +import cz.vutbr.web.css.Term; +import cz.vutbr.web.css.TermIdent; + +class UnmodifiableTermIdent extends UnmodifiableTerm implements TermIdent { + + private final Term ident; + + UnmodifiableTermIdent(Term ident) { + this.ident = ident; + } + + @Override + public String getValue() { + return ident.getValue(); + } +} diff --git a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/UnmodifiableTermString.java b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/UnmodifiableTermString.java new file mode 100644 index 0000000000..f1471eefba --- /dev/null +++ b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/UnmodifiableTermString.java @@ -0,0 +1,18 @@ +package org.daisy.pipeline.braille.css.impl; + +import cz.vutbr.web.css.Term; +import cz.vutbr.web.css.TermString; + +class UnmodifiableTermString extends UnmodifiableTerm implements TermString { + + private final Term string; + + UnmodifiableTermString(Term string) { + this.string = string; + } + + @Override + public String getValue() { + return string.getValue(); + } +} \ No newline at end of file diff --git a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/UnmodifiableTermURI.java b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/UnmodifiableTermURI.java new file mode 100644 index 0000000000..30cf8fdd94 --- /dev/null +++ b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/impl/UnmodifiableTermURI.java @@ -0,0 +1,25 @@ +package org.daisy.pipeline.braille.css.impl; + +import cz.vutbr.web.css.TermURI; + +class UnmodifiableTermURI extends UnmodifiableTerm implements TermURI { + + private final TermURI uri; + + UnmodifiableTermURI(TermURI uri) { + this.uri = uri; + } + + @Override + public String getValue() { + return uri.getValue(); + } + + public java.net.URL getBase() { + return uri.getBase(); + } + + public TermURI setBase(java.net.URL base) { + throw new UnsupportedOperationException("Unmodifiable"); + } +} \ No newline at end of file diff --git a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/saxon/impl/ParseStylesheetDefinition.java b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/saxon/impl/ParseStylesheetDefinition.java index 9a7a1f31e8..d7572e62a4 100644 --- a/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/saxon/impl/ParseStylesheetDefinition.java +++ b/modules/braille/braille-css-utils/src/main/java/org/daisy/pipeline/braille/css/saxon/impl/ParseStylesheetDefinition.java @@ -1,126 +1,103 @@ package org.daisy.pipeline.braille.css.saxon.impl; -import java.util.ArrayList; -import java.util.List; - -import javax.xml.namespace.QName; - -import net.sf.saxon.expr.XPathContext; -import net.sf.saxon.lib.ExtensionFunctionCall; -import net.sf.saxon.lib.ExtensionFunctionDefinition; -import net.sf.saxon.om.Item; -import net.sf.saxon.om.NodeInfo; -import net.sf.saxon.om.Sequence; -import net.sf.saxon.om.StructuredQName; -import net.sf.saxon.s9api.XdmNode; -import net.sf.saxon.trans.XPathException; -import net.sf.saxon.value.BooleanValue; -import net.sf.saxon.value.EmptySequence; -import net.sf.saxon.value.QNameValue; -import net.sf.saxon.value.SequenceExtent; -import net.sf.saxon.value.SequenceType; +import java.util.Optional; import org.daisy.braille.css.BrailleCSSParserFactory.Context; -import org.daisy.braille.css.InlineStyle; -import org.daisy.common.saxon.SaxonOutputValue; -import org.daisy.pipeline.braille.css.impl.BrailleCssSerializer; -import org.daisy.pipeline.braille.css.impl.BrailleCssTreeBuilder.Style; +import org.daisy.pipeline.braille.css.impl.BrailleCssParser; +import org.daisy.pipeline.braille.css.impl.BrailleCssStyle; +import org.daisy.pipeline.braille.css.xpath.impl.Declaration; +import org.daisy.pipeline.braille.css.xpath.impl.Stylesheet; +import org.daisy.pipeline.braille.css.xpath.Style; + +import org.daisy.common.xpath.saxon.ExtensionFunctionProvider; +import org.daisy.common.xpath.saxon.ReflexiveExtensionFunctionProvider; import org.osgi.service.component.annotations.Component; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.Node; @Component( - name = "css:parse-stylesheet", - service = { ExtensionFunctionDefinition.class } + name = "ParseStylesheet", + service = { ExtensionFunctionProvider.class } ) -public class ParseStylesheetDefinition extends ExtensionFunctionDefinition { - +public class ParseStylesheetDefinition extends ReflexiveExtensionFunctionProvider { + private static final String XMLNS_CSS = "http://www.daisy.org/ns/pipeline/braille-css"; - - private static final StructuredQName funcname = new StructuredQName("css", XMLNS_CSS, "parse-stylesheet"); - - private static final QName PAGE = new QName("page"); - private static final QName VOLUME = new QName("volume"); - private static final QName HYPHENATION_RESOURCE = new QName("hyphenation-resource"); - private static final QName VENDOR_RULE = new QName("vendor-rule"); - - public StructuredQName getFunctionQName() { - return funcname; - } - - @Override - public int getMinimumNumberOfArguments() { - return 1; - } - - @Override - public int getMaximumNumberOfArguments() { - return 3; - } - - public SequenceType[] getArgumentTypes() { - return new SequenceType[] { - SequenceType.OPTIONAL_STRING, - SequenceType.SINGLE_BOOLEAN, - SequenceType.OPTIONAL_QNAME, - }; - } - - public SequenceType getResultType(SequenceType[] suppliedArgumentTypes) { - return SequenceType.NODE_SEQUENCE; + + private static final BrailleCssParser parser = BrailleCssParser.getInstance(); + + public ParseStylesheetDefinition() { + super(ParseStylesheet.class); } - public ExtensionFunctionCall makeCallExpression() { - return new ExtensionFunctionCall() { - public Sequence call(XPathContext context, Sequence[] arguments) throws XPathException { - if (arguments.length == 0) - return EmptySequence.getInstance(); - Item arg = arguments[0].head(); - if (arg == null) - return EmptySequence.getInstance(); - boolean deep = arguments.length > 1 - ? ((BooleanValue)arguments[1]).getBooleanValue() - : false; - Context styleCtxt = Context.ELEMENT; { - if (arguments.length > 2) { - Item i = arguments[2].head(); - if (i != null) { - QName qn = ((QNameValue)i).toJaxpQName(); - if (qn.equals(PAGE)) - styleCtxt = Context.PAGE; - else if (qn.equals(VOLUME)) - styleCtxt = Context.VOLUME; - else if (qn.equals(HYPHENATION_RESOURCE)) - styleCtxt = Context.HYPHENATION_RESOURCE; - else if (qn.equals(VENDOR_RULE)) - styleCtxt = Context.VENDOR_RULE; - else - throw new RuntimeException(); }}} - List result = new ArrayList<>(); - try { - Style style = Style.of(new InlineStyle(arg.getStringValue(), styleCtxt)); - BrailleCssSerializer.toXml( - style, - new SaxonOutputValue( - item -> { - if (item instanceof XdmNode) - result.add(((XdmNode)item).getUnderlyingNode()); - else - throw new RuntimeException(); // should not happen - }, - context.getConfiguration() - ).asXMLStreamWriter(), - deep); - } catch (Exception e) { - logger.error("Error happened while parsing " + arg, e); + public static class ParseStylesheet { + + public static Optional - -

-

+ +

+

@@ -71,9 +71,9 @@ } - -

-

+ +

+

diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_extract.xprocspec b/modules/braille/braille-css-utils/src/test/xprocspec/test_extract.xprocspec index 1723fcd950..1161b426ac 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_extract.xprocspec +++ b/modules/braille/braille-css-utils/src/test/xprocspec/test_extract.xprocspec @@ -68,46 +68,46 @@ - @page page-1 { - @top-left { - content: "foo" - } -} -@page page-2 { - @top-left { - content: "foo" - } -} -@page page-2:left { - @top-left { - content: "foo" - } - @top-right { - content: "bar" - } -} -#a { - page: page-1 + #a { + page: page-2; } #b { display: block; - page: page-2 + page: page-1; } #c { - display: block; border-bottom-align: center; border-bottom-pattern: ⠒; border-bottom-style: none; - border-bottom-width: 1 + border-bottom-width: 1; + display: block; } #d, #e { - display: block + display: block; } #f { - display: block + display: block; } #f::before { - content: "foo" + content: 'foo'; +} +@page page-1 { + @top-left { + content: 'foo'; + } +} +@page page-1:left { + @top-left { + content: 'foo'; + } + @top-right { + content: 'bar'; + } +} +@page page-2 { + @top-left { + content: 'foo'; + } } diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_bom.xprocspec b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_bom.xprocspec index ff469e22a1..5541d9d163 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_bom.xprocspec +++ b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_bom.xprocspec @@ -34,7 +34,7 @@ -

+

diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_content.xprocspec b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_content.xprocspec index 619a38b1ae..17b580ed91 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_content.xprocspec +++ b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_content.xprocspec @@ -38,8 +38,8 @@ - - + +
foobarfoobar
@@ -73,7 +73,7 @@ - + @@ -112,7 +112,7 @@ - + foo @@ -146,7 +146,7 @@ -
+
@@ -183,7 +183,7 @@ - foo + foo diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_context.xprocspec b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_context.xprocspec index 1c3c323131..d4cd5ef849 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_context.xprocspec +++ b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_context.xprocspec @@ -70,7 +70,7 @@ - foobar + foobar diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_counter-style.xprocspec b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_counter-style.xprocspec index 1795394a12..410d5a82a9 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_counter-style.xprocspec +++ b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_counter-style.xprocspec @@ -38,7 +38,7 @@ + equals="'@counter-style decimal { symbols: ''0'' ''1'' ''2'' ''3'' ''4'' ''5'' ''6'' ''7'' ''8'' ''9''; system: numeric } @counter-style lower-roman { additive-symbols: 1000 m, 900 cm, 500 d, 400 cd, 100 c, 90 xc, 50 l, 40 xl, 10 x, 9 ix, 5 v, 4 iv, 1 i; range: 1 3999; system: additive }'"/> diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_encoding.xprocspec b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_encoding.xprocspec index bec93b4845..28932bb00e 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_encoding.xprocspec +++ b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_encoding.xprocspec @@ -32,7 +32,7 @@ -

+

@@ -70,7 +70,7 @@ -

+

@@ -107,7 +107,7 @@ -

+

@@ -145,7 +145,7 @@ -

+

@@ -183,7 +183,7 @@ -

+

diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_extension.xprocspec b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_extension.xprocspec index 6331d01ddb..008177f411 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_extension.xprocspec +++ b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_extension.xprocspec @@ -14,13 +14,15 @@

+

@@ -32,8 +34,9 @@ -

-

+

+

+

@@ -49,8 +52,8 @@ @@ -68,7 +71,7 @@

-

+

@@ -84,15 +87,13 @@

-

@@ -105,8 +106,7 @@

-

-

+

@@ -122,8 +122,8 @@ @@ -141,7 +141,7 @@

-

+

diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_hyphenation-resource.xprocspec b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_hyphenation-resource.xprocspec index 2c58451024..6293b34827 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_hyphenation-resource.xprocspec +++ b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_hyphenation-resource.xprocspec @@ -27,7 +27,7 @@ + '") }')"/> diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_import.xprocspec b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_import.xprocspec index 62eeef18d5..644f76f4c8 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_import.xprocspec +++ b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_import.xprocspec @@ -32,8 +32,8 @@ -

-

+

+

@@ -67,7 +67,7 @@

-

+

@@ -99,7 +99,7 @@

-

+

diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_inherit.xprocspec b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_inherit.xprocspec index 6b15d6929a..ecd9c50d5a 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_inherit.xprocspec +++ b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_inherit.xprocspec @@ -27,9 +27,9 @@ - + foo -

bar

+

bar

diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_invalid.xprocspec b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_invalid.xprocspec index be2493c9fa..3285fa33ad 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_invalid.xprocspec +++ b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_invalid.xprocspec @@ -64,7 +64,7 @@ -

+

@@ -98,7 +98,7 @@ - + diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_media.xprocspec b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_media.xprocspec index 0dae1398a2..ee4f81d233 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_media.xprocspec +++ b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_media.xprocspec @@ -53,7 +53,7 @@ -

+

foobar

@@ -71,7 +71,7 @@

- foobar + foobar

@@ -116,7 +116,7 @@ -

+

foobar

@@ -159,7 +159,7 @@ -

+

foobar

@@ -202,7 +202,7 @@ -

+

foobar

@@ -247,7 +247,7 @@ -

+

foobar

@@ -287,7 +287,7 @@ - + foobar diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_namespace.xprocspec b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_namespace.xprocspec index c91521ab97..a8b966a1c5 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_namespace.xprocspec +++ b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_namespace.xprocspec @@ -42,11 +42,11 @@ -

-

-

-

-

+

+

+

+

+

@@ -92,11 +92,11 @@ -

-

-

-

-

+

+

+

+

+

@@ -144,10 +144,10 @@ -

-

-

-

+

+

+

+

@@ -179,7 +179,7 @@ -

+

diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_page.xprocspec b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_page.xprocspec index eea3ab08c1..dc561ceb72 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_page.xprocspec +++ b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_page.xprocspec @@ -48,7 +48,7 @@ - +

foobar

@@ -119,7 +119,7 @@ - +

foobar

@@ -155,7 +155,7 @@ - + @@ -185,7 +185,7 @@ - + @@ -217,7 +217,7 @@ - + @@ -247,7 +247,7 @@ - + @@ -275,7 +275,7 @@ + equals="'@page:left { margin-top: 2 }'"/> @@ -300,7 +300,30 @@ + equals="'@page { @footnotes { border-top-align: center; border-top-pattern: ⠒; border-top-style: none; border-top-width: 1 } }'"/> + + + + + + + + + + + + + + + + + + diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_properties.xprocspec b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_properties.xprocspec index 7a6fde350e..430278d80b 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_properties.xprocspec +++ b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_properties.xprocspec @@ -32,8 +32,8 @@ - foo -

bar

+ foo +

bar

@@ -75,12 +75,12 @@ -

-

-

-

+

+

+

+

-

+

@@ -112,7 +112,7 @@ -

foo

+

foo

@@ -144,8 +144,8 @@ - - 1 + + 1 @@ -181,9 +181,9 @@ - -

-

+ +

+

diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_resolver.xprocspec b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_resolver.xprocspec index 059b414a95..197bf28fe7 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_resolver.xprocspec +++ b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_resolver.xprocspec @@ -26,8 +26,8 @@ -

-

+

+

@@ -57,7 +57,7 @@

-

+

diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_sass.xprocspec b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_sass.xprocspec index b2a0c2e691..987143595e 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_sass.xprocspec +++ b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_sass.xprocspec @@ -29,7 +29,7 @@

-

+

@@ -60,7 +60,7 @@ -

+

@@ -95,7 +95,7 @@

-

+

@@ -126,8 +126,8 @@ -

-

+

+

@@ -159,7 +159,7 @@

-

+

@@ -191,7 +191,7 @@

-

+

@@ -223,7 +223,7 @@

-

+

@@ -255,7 +255,7 @@

-

+

@@ -287,7 +287,7 @@

-

+

@@ -320,7 +320,7 @@

-

+

@@ -357,7 +357,7 @@

-

+

@@ -395,7 +395,7 @@ -

+

@@ -442,7 +442,7 @@ -
+
@@ -486,7 +486,7 @@ -
+
@@ -531,7 +531,7 @@ -
+
diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_selectors.xprocspec b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_selectors.xprocspec index 4e546aa3ed..0aeef05392 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_selectors.xprocspec +++ b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_selectors.xprocspec @@ -30,9 +30,9 @@ - - foo -

foo

+ + foo +

foo

@@ -64,7 +64,7 @@ -

foo

+

foo

@@ -101,7 +101,7 @@

foo

-

foo

+

foo

@@ -140,7 +140,7 @@
-

foo

+

foo

foo

@@ -179,7 +179,7 @@ foo -

foo

+

foo

foo

@@ -222,12 +222,12 @@ - -

-

-

-

-

+ +

+

+

+

+

foo

@@ -264,10 +264,10 @@ - -

+ +

-

+

@@ -303,8 +303,8 @@ -

-

+

+

@@ -352,16 +352,16 @@

    -
  • +
  • -
  • +
    1. -
    2. +
  • -
  • +
- +
@@ -397,7 +397,7 @@ -

foobar

+

foobar

@@ -414,19 +414,16 @@ @@ -443,7 +440,7 @@ -
+
@@ -474,7 +471,7 @@ - + @@ -518,7 +515,7 @@ -
+
@@ -560,9 +557,9 @@

-

foo

+

foo

-

foo

+

foo

diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_specificity.xprocspec b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_specificity.xprocspec index 035ee9f09b..e1fbc2baa5 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_specificity.xprocspec +++ b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_specificity.xprocspec @@ -48,7 +48,7 @@

-

+

diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_text-transform.xprocspec b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_text-transform.xprocspec index 660b09b65c..ebcca6a110 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_text-transform.xprocspec +++ b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_text-transform.xprocspec @@ -35,7 +35,7 @@ + equals="'@text-transform foo { system: -foo-transform; x: 1; y: 2; z: ''\27 3\27 '' }'"/> @@ -43,7 +43,7 @@

- foo bar + foo bar

diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_user-stylesheet.xprocspec b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_user-stylesheet.xprocspec index 99955fa1fe..ca5c712eae 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_user-stylesheet.xprocspec +++ b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_user-stylesheet.xprocspec @@ -24,8 +24,8 @@ -

-

+

+

@@ -51,8 +51,8 @@ -

-

+

+

@@ -83,8 +83,8 @@ -

-

+

+

diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_volume.xprocspec b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_volume.xprocspec index 447807c921..c0662115cb 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_volume.xprocspec +++ b/modules/braille/braille-css-utils/src/test/xprocspec/test_inline/test_volume.xprocspec @@ -34,7 +34,7 @@ + equals="'@volume { max-length: 100; min-length: 80 } @volume:last { max-length: 100; min-length: 0 } @volume:only { max-length: 120; min-length: 0 } @volume:nth(2) { max-length: auto; min-length: 80 } @volume:first { max-length: 120; min-length: 80 }'"/> @@ -65,7 +65,7 @@ + equals='"@volume { max-length: 100; @begin { content: flow(toc) } } @volume:first { max-length: 100; @begin { content: flow(cover) flow(toc) } }"'/> @@ -93,7 +93,7 @@ + equals="'@volume { max-length: 75; @begin { content: flow(volume-toc) } }'"/> @@ -130,7 +130,7 @@ + equals="'@page { size: 15 5 } @volume { max-length: 75; @begin { content: flow(frontmatter); @page { size: 15 5; @top-right { content: counter(page) } } } }'"/> @@ -156,58 +156,7 @@ - - - - - - - - - - - - - -

-
-
-
- - - - - - - - - - - - - - - -
-
-
-
- - - - - + equals="'@volume:first { max-length: 75 }'"/> @@ -217,7 +166,7 @@ + + + + + + + + + + + + + + application/x-pef+xml + + + + +
+ + ⠋⠀⠕⠀⠕⠀⠃⠀⠁⠀⠗ + +
+
+ +
+
+
+
+ diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_label-targets.xprocspec b/modules/braille/dotify-utils/src/test/xprocspec/test_label-targets.xprocspec similarity index 96% rename from modules/braille/braille-css-utils/src/test/xprocspec/test_label-targets.xprocspec rename to modules/braille/dotify-utils/src/test/xprocspec/test_label-targets.xprocspec index a8e288d15d..813e52b382 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_label-targets.xprocspec +++ b/modules/braille/dotify-utils/src/test/xprocspec/test_label-targets.xprocspec @@ -1,11 +1,12 @@ - + @@ -75,7 +76,7 @@ - + @@ -111,7 +112,7 @@ - + diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_make-anonymous-block-boxes.xprocspec b/modules/braille/dotify-utils/src/test/xprocspec/test_make-anonymous-block-boxes.xprocspec similarity index 93% rename from modules/braille/braille-css-utils/src/test/xprocspec/test_make-anonymous-block-boxes.xprocspec rename to modules/braille/dotify-utils/src/test/xprocspec/test_make-anonymous-block-boxes.xprocspec index 72052cb778..b293092c96 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_make-anonymous-block-boxes.xprocspec +++ b/modules/braille/dotify-utils/src/test/xprocspec/test_make-anonymous-block-boxes.xprocspec @@ -1,10 +1,11 @@ - + @@ -44,7 +45,7 @@ - + diff --git a/modules/braille/dotify-utils/src/test/xprocspec/test_new-definition.xprocspec b/modules/braille/dotify-utils/src/test/xprocspec/test_new-definition.xprocspec new file mode 100644 index 0000000000..995e6e4f4c --- /dev/null +++ b/modules/braille/dotify-utils/src/test/xprocspec/test_new-definition.xprocspec @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_shift-id.xprocspec b/modules/braille/dotify-utils/src/test/xprocspec/test_shift-id.xprocspec similarity index 95% rename from modules/braille/braille-css-utils/src/test/xprocspec/test_shift-id.xprocspec rename to modules/braille/dotify-utils/src/test/xprocspec/test_shift-id.xprocspec index aa34126391..cfa913e32f 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_shift-id.xprocspec +++ b/modules/braille/dotify-utils/src/test/xprocspec/test_shift-id.xprocspec @@ -1,11 +1,12 @@ - + <_> @@ -59,7 +60,7 @@ - + diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_shift-string-set.xprocspec b/modules/braille/dotify-utils/src/test/xprocspec/test_shift-string-set.xprocspec similarity index 94% rename from modules/braille/braille-css-utils/src/test/xprocspec/test_shift-string-set.xprocspec rename to modules/braille/dotify-utils/src/test/xprocspec/test_shift-string-set.xprocspec index 895c034dde..5f9b6c5891 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_shift-string-set.xprocspec +++ b/modules/braille/dotify-utils/src/test/xprocspec/test_shift-string-set.xprocspec @@ -1,11 +1,12 @@ - + @@ -47,7 +48,7 @@ - + diff --git a/modules/braille/braille-css-utils/src/test/xprocspec/test_split.xprocspec b/modules/braille/dotify-utils/src/test/xprocspec/test_split.xprocspec similarity index 97% rename from modules/braille/braille-css-utils/src/test/xprocspec/test_split.xprocspec rename to modules/braille/dotify-utils/src/test/xprocspec/test_split.xprocspec index 96ebc2ba81..78d82beae5 100644 --- a/modules/braille/braille-css-utils/src/test/xprocspec/test_split.xprocspec +++ b/modules/braille/dotify-utils/src/test/xprocspec/test_split.xprocspec @@ -2,11 +2,12 @@ - + @@ -59,7 +60,7 @@ - + @@ -90,7 +91,7 @@ - + @@ -152,7 +153,7 @@ - + @@ -237,7 +238,7 @@ - + diff --git a/modules/braille/braille-css-utils/src/test/xspec/test_adjust-boxes.xspec b/modules/braille/dotify-utils/src/test/xspec/test_adjust-boxes.xspec similarity index 100% rename from modules/braille/braille-css-utils/src/test/xspec/test_adjust-boxes.xspec rename to modules/braille/dotify-utils/src/test/xspec/test_adjust-boxes.xspec diff --git a/modules/braille/dotify-utils/src/test/xspec/test_generate-obfl-layout-master.xspec b/modules/braille/dotify-utils/src/test/xspec/test_generate-obfl-layout-master.xspec index 58f2fd2829..7cf549066e 100644 --- a/modules/braille/dotify-utils/src/test/xspec/test_generate-obfl-layout-master.xspec +++ b/modules/braille/dotify-utils/src/test/xspec/test_generate-obfl-layout-master.xspec @@ -1,6 +1,7 @@ @@ -11,9 +12,7 @@ - - - + @@ -29,23 +28,18 @@ - - - - - - - - - - - - - - - - - + @@ -78,33 +72,24 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -150,14 +135,13 @@ - - - - - - - - + @@ -180,14 +164,13 @@ - - - - - - - - + @@ -218,19 +201,16 @@ - - - - - - - - - - - - - + @@ -253,19 +233,16 @@ - - - - - - - - - - - - - + @@ -296,20 +273,17 @@ - - - - - - - - - - - - - - + @@ -340,20 +314,17 @@ - - - - - - - - - - - - - - + @@ -384,35 +355,26 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -458,20 +420,17 @@ - - - - - - - - - - - - - - + @@ -490,32 +449,25 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/modules/braille/braille-css-utils/src/test/xspec/test_make-anonymous-inline-boxes.xspec b/modules/braille/dotify-utils/src/test/xspec/test_make-anonymous-inline-boxes.xspec similarity index 100% rename from modules/braille/braille-css-utils/src/test/xspec/test_make-anonymous-inline-boxes.xspec rename to modules/braille/dotify-utils/src/test/xspec/test_make-anonymous-inline-boxes.xspec diff --git a/modules/braille/braille-css-utils/src/test/xspec/test_make-boxes.xspec b/modules/braille/dotify-utils/src/test/xspec/test_make-boxes.xspec similarity index 98% rename from modules/braille/braille-css-utils/src/test/xspec/test_make-boxes.xspec rename to modules/braille/dotify-utils/src/test/xspec/test_make-boxes.xspec index 1db040d20d..d28ad16764 100644 --- a/modules/braille/braille-css-utils/src/test/xspec/test_make-boxes.xspec +++ b/modules/braille/dotify-utils/src/test/xspec/test_make-boxes.xspec @@ -38,7 +38,7 @@ - + diff --git a/modules/braille/braille-css-utils/src/test/xspec/test_make-pseudo-elements.xspec b/modules/braille/dotify-utils/src/test/xspec/test_make-pseudo-elements.xspec similarity index 100% rename from modules/braille/braille-css-utils/src/test/xspec/test_make-pseudo-elements.xspec rename to modules/braille/dotify-utils/src/test/xspec/test_make-pseudo-elements.xspec diff --git a/modules/braille/braille-css-utils/src/test/xspec/test_make-table-grid.xspec b/modules/braille/dotify-utils/src/test/xspec/test_make-table-grid.xspec similarity index 100% rename from modules/braille/braille-css-utils/src/test/xspec/test_make-table-grid.xspec rename to modules/braille/dotify-utils/src/test/xspec/test_make-table-grid.xspec diff --git a/modules/braille/braille-css-utils/src/test/xspec/test_padding-to-margin.xspec b/modules/braille/dotify-utils/src/test/xspec/test_padding-to-margin.xspec similarity index 100% rename from modules/braille/braille-css-utils/src/test/xspec/test_padding-to-margin.xspec rename to modules/braille/dotify-utils/src/test/xspec/test_padding-to-margin.xspec diff --git a/modules/braille/braille-css-utils/src/test/xspec/test_preserve-white-space.xspec b/modules/braille/dotify-utils/src/test/xspec/test_preserve-white-space.xspec similarity index 100% rename from modules/braille/braille-css-utils/src/test/xspec/test_preserve-white-space.xspec rename to modules/braille/dotify-utils/src/test/xspec/test_preserve-white-space.xspec diff --git a/modules/braille/dotify-utils/src/test/xspec/test_resolve-css-string.xspec b/modules/braille/dotify-utils/src/test/xspec/test_resolve-css-string.xspec new file mode 100644 index 0000000000..ec0bf84eff --- /dev/null +++ b/modules/braille/dotify-utils/src/test/xspec/test_resolve-css-string.xspec @@ -0,0 +1,31 @@ + + + + + page + + + + + + + + + +

+ +

+ +

+ + + + + + + + + diff --git a/modules/braille/dotify-utils/src/test/xspec/test_round-line-height.xspec b/modules/braille/dotify-utils/src/test/xspec/test_round-line-height.xspec new file mode 100644 index 0000000000..04e9a32af2 --- /dev/null +++ b/modules/braille/dotify-utils/src/test/xspec/test_round-line-height.xspec @@ -0,0 +1,32 @@ + + + + + page + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/braille/dotify-utils/src/test/xspec/test_volume-stylesheets-use-when.xspec b/modules/braille/dotify-utils/src/test/xspec/test_volume-stylesheets-use-when.xspec index 126f6095ad..1ddbdff0c7 100644 --- a/modules/braille/dotify-utils/src/test/xspec/test_volume-stylesheets-use-when.xspec +++ b/modules/braille/dotify-utils/src/test/xspec/test_volume-stylesheets-use-when.xspec @@ -1,5 +1,6 @@ @@ -10,17 +11,12 @@ - - - - - - - - - - - + + styledText(String... textAndStyle) { List styledText = new ArrayList(); @@ -105,7 +108,7 @@ private Iterable styledText(String... textAndStyle) { boolean textSet = false; for (String s : textAndStyle) { if (textSet) - styledText.add(new CSSStyledText(text, s)); + styledText.add(new CSSStyledText(text, cssParser.parse(s))); else text = s; textSet = !textSet; } @@ -117,7 +120,7 @@ private Iterable styledText(String... textAndStyle) { private Iterable text(String... text) { List styledText = new ArrayList(); for (String t : text) - styledText.add(new CSSStyledText(t, "")); + styledText.add(new CSSStyledText(t, cssParser.parse(""))); return styledText; } diff --git a/modules/braille/liblouis-utils/pom.xml b/modules/braille/liblouis-utils/pom.xml index 858b04ba17..eb744a552f 100644 --- a/modules/braille/liblouis-utils/pom.xml +++ b/modules/braille/liblouis-utils/pom.xml @@ -85,23 +85,17 @@ org.slf4j slf4j-api - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime - - - -

- foobar -

-

- foobar foobar -

- - - - -

⠋⠕⠕­⠃⠜

-

⠋⠕⠕⠃⠜ ⠋⠕⠕­⠃⠜

-
-
-
- -
diff --git a/modules/braille/pef-utils/pom.xml b/modules/braille/pef-utils/pom.xml index c786db28e9..7c0ea5a2b0 100644 --- a/modules/braille/pef-utils/pom.xml +++ b/modules/braille/pef-utils/pom.xml @@ -62,12 +62,17 @@ com.openhtmltopdf openhtmltopdf-slf4j + + org.daisy.pipeline.modules + file-utils + org.liblouis liblouis-java + runtime org.daisy.pipeline.modules common-utils - runtime diff --git a/modules/common/file-utils/src/main/resources/META-INF/catalog.xml b/modules/common/file-utils/src/main/resources/META-INF/catalog.xml index f63d56f941..39d846c776 100644 --- a/modules/common/file-utils/src/main/resources/META-INF/catalog.xml +++ b/modules/common/file-utils/src/main/resources/META-INF/catalog.xml @@ -7,6 +7,4 @@ - - diff --git a/modules/common/fileset-utils/pom.xml b/modules/common/fileset-utils/pom.xml index 980faa62ba..c9f770f8f3 100644 --- a/modules/common/fileset-utils/pom.xml +++ b/modules/common/fileset-utils/pom.xml @@ -37,18 +37,13 @@ org.daisy.pipeline calabash-adapter - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules zip-utils - runtime diff --git a/modules/common/fileset-utils/src/main/resources/META-INF/catalog.xml b/modules/common/fileset-utils/src/main/resources/META-INF/catalog.xml index c6333c0697..dfdf9885b3 100755 --- a/modules/common/fileset-utils/src/main/resources/META-INF/catalog.xml +++ b/modules/common/fileset-utils/src/main/resources/META-INF/catalog.xml @@ -5,8 +5,4 @@ - - - - diff --git a/modules/common/mediatype-utils/pom.xml b/modules/common/mediatype-utils/pom.xml index 74471f2867..da2c0850e4 100644 --- a/modules/common/mediatype-utils/pom.xml +++ b/modules/common/mediatype-utils/pom.xml @@ -17,13 +17,9 @@ DAISY Pipeline 2 module :: mediatype-utils - org.daisy.pipeline.modules fileset-utils - runtime diff --git a/modules/common/mediatype-utils/src/main/resources/META-INF/catalog.xml b/modules/common/mediatype-utils/src/main/resources/META-INF/catalog.xml index 1ea7cf6c26..ab486eb81f 100755 --- a/modules/common/mediatype-utils/src/main/resources/META-INF/catalog.xml +++ b/modules/common/mediatype-utils/src/main/resources/META-INF/catalog.xml @@ -2,5 +2,4 @@ - diff --git a/modules/common/validation-utils/pom.xml b/modules/common/validation-utils/pom.xml index 065ffd13a1..5670df6eb4 100644 --- a/modules/common/validation-utils/pom.xml +++ b/modules/common/validation-utils/pom.xml @@ -17,26 +17,16 @@ DAISY Pipeline 2 module :: validation-utils - - - org.daisy.pipeline.modules - file-utils - runtime - org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules common-utils - runtime org.daisy.libs diff --git a/modules/common/validation-utils/src/main/resources/META-INF/catalog.xml b/modules/common/validation-utils/src/main/resources/META-INF/catalog.xml index 75f066d604..e06b66d437 100755 --- a/modules/common/validation-utils/src/main/resources/META-INF/catalog.xml +++ b/modules/common/validation-utils/src/main/resources/META-INF/catalog.xml @@ -2,7 +2,4 @@ - - - diff --git a/modules/nlp/dtbook-break-detection/pom.xml b/modules/nlp/dtbook-break-detection/pom.xml index bdc8da06bf..6bd478a628 100644 --- a/modules/nlp/dtbook-break-detection/pom.xml +++ b/modules/nlp/dtbook-break-detection/pom.xml @@ -19,13 +19,9 @@ Performs sentence and word detection on a DTBOOK document - org.daisy.pipeline.modules nlp-common - runtime diff --git a/modules/nlp/dtbook-break-detection/src/main/resources/META-INF/catalog.xml b/modules/nlp/dtbook-break-detection/src/main/resources/META-INF/catalog.xml index 98c0bdc4f0..71bbcbe1c4 100755 --- a/modules/nlp/dtbook-break-detection/src/main/resources/META-INF/catalog.xml +++ b/modules/nlp/dtbook-break-detection/src/main/resources/META-INF/catalog.xml @@ -2,7 +2,4 @@ - - - diff --git a/modules/nlp/html-break-detection/pom.xml b/modules/nlp/html-break-detection/pom.xml index 5cb9915d7c..1a62feecce 100644 --- a/modules/nlp/html-break-detection/pom.xml +++ b/modules/nlp/html-break-detection/pom.xml @@ -19,13 +19,9 @@ Performs sentence and word detection on a HTML document - org.daisy.pipeline.modules nlp-common - runtime org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules nlp-common - runtime diff --git a/modules/nlp/zedai-break-detection/src/main/resources/META-INF/catalog.xml b/modules/nlp/zedai-break-detection/src/main/resources/META-INF/catalog.xml index 61c460b419..718fe76278 100755 --- a/modules/nlp/zedai-break-detection/src/main/resources/META-INF/catalog.xml +++ b/modules/nlp/zedai-break-detection/src/main/resources/META-INF/catalog.xml @@ -2,7 +2,4 @@ - - - diff --git a/modules/parent/pom.xml b/modules/parent/pom.xml index c74c9de020..7bf216332e 100644 --- a/modules/parent/pom.xml +++ b/modules/parent/pom.xml @@ -149,7 +149,7 @@ org.daisy.pipeline.build modules-build-helper - 2.8.0 + 3.0.0-SNAPSHOT org.apache.maven.plugins @@ -159,7 +159,7 @@ org.daisy.pipeline.build ds-to-spi-maven-plugin - 1.1.4 + 1.1.5-SNAPSHOT org.apache.felix @@ -203,6 +203,11 @@ xproc-maven-plugin 1.0.3 + + org.daisy.maven + xprocspec-runner + 1.2.8-SNAPSHOT + org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules diff --git a/modules/scripts-utils/ace-adapter/src/main/resources/META-INF/catalog.xml b/modules/scripts-utils/ace-adapter/src/main/resources/META-INF/catalog.xml index 410ad861b7..093c6291d3 100644 --- a/modules/scripts-utils/ace-adapter/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts-utils/ace-adapter/src/main/resources/META-INF/catalog.xml @@ -1,6 +1,4 @@ - - diff --git a/modules/scripts-utils/css-utils/pom.xml b/modules/scripts-utils/css-utils/pom.xml index c226a76c7d..8320175c8e 100644 --- a/modules/scripts-utils/css-utils/pom.xml +++ b/modules/scripts-utils/css-utils/pom.xml @@ -11,7 +11,7 @@ css-utils - 5.4.1-SNAPSHOT + 6.0.0-SNAPSHOT bundle DAISY Pipeline 2 module :: CSS Utils @@ -38,6 +38,18 @@ org.daisy.pipeline common-utils + + org.daisy.pipeline.modules + file-utils + + + org.daisy.pipeline.modules + fileset-utils + + + org.daisy.pipeline.modules + mediatype-utils + org.daisy.libs io.bit3.jsass @@ -106,24 +118,6 @@ org.slf4j slf4j-api - - - org.daisy.pipeline.modules - file-utils - runtime - - - org.daisy.pipeline.modules - fileset-utils - runtime - - - org.daisy.pipeline.modules - mediatype-utils - runtime - diff --git a/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/CounterEvaluator.java b/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/CounterEvaluator.java new file mode 100644 index 0000000000..6de7ea30b5 --- /dev/null +++ b/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/CounterEvaluator.java @@ -0,0 +1,362 @@ +package org.daisy.pipeline.css; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; + +import cz.vutbr.web.css.Term; +import cz.vutbr.web.css.TermFunction; +import cz.vutbr.web.css.TermIdent; +import cz.vutbr.web.css.TermPair; +import cz.vutbr.web.css.TermString; + +import org.daisy.braille.css.BrailleCSSProperty.Display; + +/** + * Traverses a document while keeping track of counter values and providing the ability to + * generate counter representations. + */ +public abstract class CounterEvaluator { + + /** + * Get {@code counter-reset} property. + */ + protected abstract Collection> getCounterReset(E elementStyle); + + /** + * Get {@code counter-set} property. + */ + protected abstract Collection> getCounterSet(E elementStyle); + + /** + * Get {@code counter-increment} property. + */ + protected abstract Collection> getCounterIncrement(E elementStyle); + + /** + * Get {@code display} property. + */ + protected abstract Display getDisplay(E elementStyle); + + /** + * Get {@code content} property of {@code ::marker} pseudo-element. + * + * {@code null} means there is no {@code ::marker} pseudo-element. An empty list means the + * pseudo-element has no content. + */ + protected abstract Collection> getMarkerContent(E elementStyle); + + /** + * Get list counter style. + */ + protected abstract CounterStyle getListStyleType(E elementStyle, CounterStyle parentListStyleType); + + /** + * Get counter style for given name + */ + protected abstract CounterStyle getNamedCounterStyle(String name); + + private final LinkedList listCounterStyle = new LinkedList<>(); + protected final LinkedList> counterValues = new LinkedList<>(); + private boolean previousEventWasStartElement = true; // false = endElement + + /** + * This method must be called at the start of every element while traversing the document tree + * in depth-first order. + * + * New counter values are determined based on the given style. + * + * @param elementStyle The style of the current element, possibly containing {@code + * counter-reset}, {@code counter-set} and {@code counter-increment} + * properties. + */ + public void startElement(E elementStyle) { + if (listCounterStyle.isEmpty()) + listCounterStyle.push(CounterStyle.DISC); + if (previousEventWasStartElement) + counterValues.push(null); + previousEventWasStartElement = true; + updateCounterValues(elementStyle); + updateCurrentListCounterStyle(elementStyle); + } + + /** + * This method must be called at the end of every element while traversing the document tree in + * depth-first order. + */ + public void endElement() { + listCounterStyle.pop(); + if (!previousEventWasStartElement) + counterValues.pop(); + previousEventWasStartElement = false; + } + + /** + * Determine current counter values (see https://www.w3.org/TR/css-lists-3/#creating-counters) + */ + private void updateCounterValues(E elementStyle) { + // counter-reset + { + Collection> list = getCounterReset(elementStyle); + if (list != null) { + for (TermPair p : list) { + String name = p.getKey(); + Map createdBySiblingOrSelf = counterValues.peek(); + if (createdBySiblingOrSelf == null) { + createdBySiblingOrSelf = new HashMap<>(); + counterValues.set(0, createdBySiblingOrSelf); + } + createdBySiblingOrSelf.put(name, p.getValue()); + } + } + } + // counter-set or counter-increment work in addition to counter-reset + Set done = null; + { + Collection> list = getCounterSet(elementStyle); + if (list != null) { + for (TermPair p : list) { + String name = p.getKey(); + boolean exists = false; + for (Map c : counterValues) { + if (c != null) { + c.put(name, p.getValue()); + exists = true; + break; + } + } + if (!exists) { + Map createdBySiblingOrSelf = counterValues.peek(); + if (createdBySiblingOrSelf == null) { + createdBySiblingOrSelf = new HashMap<>(); + counterValues.set(0, createdBySiblingOrSelf); + } + createdBySiblingOrSelf.put(name, p.getValue()); + } + if (done == null) + done = new HashSet<>(); + done.add(name); + } + } + } + { + Collection> list = getCounterIncrement(elementStyle); + if (list != null) { + for (TermPair p : list) { + String name = p.getKey(); + if (done == null || !done.contains(name)) { + boolean exists = false; + for (Map c : counterValues) { + if (c != null) { + c.put(name, c.getOrDefault(name, 0) + p.getValue()); + exists = true; + break; + } + } + if (!exists) { + Map createdBySiblingOrSelf = counterValues.peek(); + if (createdBySiblingOrSelf == null) { + createdBySiblingOrSelf = new HashMap<>(); + counterValues.set(0, createdBySiblingOrSelf); + } + createdBySiblingOrSelf.put(name, p.getValue()); + } + if (done == null) + done = new HashSet<>(); + done.add(name); + } + } + } + } + // by default list items increment the 'list-item' counter + if ((done == null || !done.contains("list-item")) && getDisplay(elementStyle) == Display.LIST_ITEM) { + boolean exists = false; + for (Map c : counterValues) { + if (c != null) { + c.put("list-item", c.getOrDefault("list-item", 0) + 1); + exists = true; + break; + } + } + if (!exists) { + Map createdBySiblingOrSelf = counterValues.peek(); + if (createdBySiblingOrSelf == null) { + createdBySiblingOrSelf = new HashMap<>(); + counterValues.set(0, createdBySiblingOrSelf); + } + createdBySiblingOrSelf.put("list-item", 1); + } + } + } + + /** + * Determine current list counter style + */ + private void updateCurrentListCounterStyle(E elementStyle) { + listCounterStyle.push(getListStyleType(elementStyle, listCounterStyle.peek())); + } + + /** + * Evaluate marker contents (see https://www.w3.org/TR/css-lists-3/#content-property) + */ + public String generateMarkerContents(E elementStyle) throws IllegalArgumentException { + if (getDisplay(elementStyle) == Display.LIST_ITEM) { + Collection> content = getMarkerContent(elementStyle); + if (content != null) { + if (!content.isEmpty()) + return evaluateContent(content); + else + return null; + } + CounterStyle listCounterStyle = this.listCounterStyle.peek(); + if (listCounterStyle != null) + return evaluateCounter("list-item", listCounterStyle, true); + } + return null; + } + + private String evaluateContent(Collection> content) throws IllegalArgumentException { + StringBuilder s = new StringBuilder(); + for (Term t : content) { + if (t instanceof TermString) { + s.append(((TermString)t).getValue()); + } else if (t instanceof TermFunction) { + TermFunction f = (TermFunction)t; + if ("counter".equalsIgnoreCase(f.getFunctionName())) { + String name = null; + CounterStyle style = null; + for (Term arg : f) + if (name == null) + if (arg instanceof TermIdent) + name = ((TermIdent)arg).getValue(); + else + throw new IllegalArgumentException( + "invalid first argument of counter() function: should be the counter name"); + else if (style == null) { + if (arg instanceof TermIdent) + style = getNamedCounterStyle(((TermIdent)arg).getValue()); + else if (arg instanceof TermFunction) + try { + style = CounterStyle.fromSymbolsFunction((TermFunction)arg); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException( + "invalid second argument of counter() function: should be a counter style", e); + } + else + throw new IllegalArgumentException( + "invalid second argument of counter() function: should be a counter style"); } + else + throw new IllegalArgumentException( + "unexpected argument of counter() function: function takes at most two arguments"); + if (name == null) + throw new IllegalArgumentException("counter() function requires at least one argument"); + if (style == null) + style = CounterStyle.DECIMAL; + s.append(evaluateCounter(name, style, false)); + } else if ("counters".equalsIgnoreCase(f.getFunctionName())) { + String name = null; + String separator = null; + CounterStyle style = null; + for (Term arg : f) + if (name == null) + if (arg instanceof TermIdent) + name = ((TermIdent)arg).getValue(); + else + throw new IllegalArgumentException( + "invalid first argument of counters() function: should be the counter name"); + else if (separator == null) { + if (arg instanceof TermString) + separator = ((TermString)arg).getValue(); + else + throw new IllegalArgumentException( + "invalid second argument of counters() function: should be a string"); } + else if (style == null) { + if (arg instanceof TermIdent) + style = getNamedCounterStyle(((TermIdent)arg).getValue()); + else if (arg instanceof TermFunction) + try { + style = CounterStyle.fromSymbolsFunction((TermFunction)arg); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException( + "invalid third argument of counters() function: should be a counter style", e); + } + else + throw new IllegalArgumentException( + "invalid third argument of counters() function: should be a counter style"); } + else + throw new IllegalArgumentException( + "unexpected argument of counters() function: function takes at most three arguments"); + if (name == null || separator == null) + throw new IllegalArgumentException("counters() function requires at least two arguments"); + if (style == null) + style = CounterStyle.DECIMAL; + s.append(evaluateCounters(name, style, separator)); + } else + throw new RuntimeException(f.getFunctionName() + "() function not supported in content list"); // FIXME + } else + throw new IllegalStateException(); // cannot happen + } + return s.toString(); + } + + /** + * Generate a counter representation of the innermost counter in the counter set with the given + * name (see https://www.w3.org/TR/css-lists-3/#counter-functions) + */ + public String evaluateCounter(String name, CounterStyle style, boolean withPrefixAndSuffix) { + int value = evaluateCounter(name); + return style.format(value, withPrefixAndSuffix); + } + + /** + * Get the value of the innermost counter in the counter set with the given name, or instaniate + * a counter with value {@code 0} (see https://www.w3.org/TR/css-lists-3/#counter-functions) + */ + public int evaluateCounter(String name) { + Integer value = null; + boolean exists = false; + for (Map c : counterValues) + if (c != null && c.containsKey(name)) { + value = c.get(name); + break; + } + if (value == null) { + value = 0; + Map createdBySiblingOrSelf = counterValues.peek(); + if (createdBySiblingOrSelf == null) { + createdBySiblingOrSelf = new HashMap<>(); + counterValues.set(0, createdBySiblingOrSelf); + } + createdBySiblingOrSelf.put(name, value); + } + return value; + } + + /** + * Generate a counter representation of all the counters in the counter set with the given name + * (see https://www.w3.org/TR/css-lists-3/#counter-functions) + */ + public String evaluateCounters(String name, CounterStyle style, String separator) { + StringBuilder s = new StringBuilder(); + for (Map c : counterValues) + if (c != null && c.containsKey(name)) { + if (s.length() > 0) + s.insert(0, separator); + s.insert(0, style.format(c.get(name))); + } + if (s.length() == 0) { + Map createdBySiblingOrSelf = counterValues.peek(); + if (createdBySiblingOrSelf == null) { + createdBySiblingOrSelf = new HashMap<>(); + counterValues.set(0, createdBySiblingOrSelf); + } + createdBySiblingOrSelf.put(name, 0); + s.append(style.format(0)); + } + return s.toString(); + } +} diff --git a/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/CounterStyle.java b/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/CounterStyle.java index ec762acf36..bba97f4864 100644 --- a/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/CounterStyle.java +++ b/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/CounterStyle.java @@ -1,6 +1,7 @@ package org.daisy.pipeline.css; import java.util.ArrayList; +import java.util.Collections; import java.util.function.Supplier; import java.util.HashMap; import java.util.Iterator; @@ -8,6 +9,7 @@ import java.util.Map; import java.util.Optional; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import cz.vutbr.web.css.Declaration; @@ -27,7 +29,66 @@ import org.unbescape.css.CssEscape; -public class CounterStyle { +/** + * See http://www.w3.org/TR/css-counter-styles-3 + */ +public interface CounterStyle { + + public String format(int counterValue); + + public String format(int counterValue, boolean withPrefixAndSuffix); + + @Deprecated + public String getTextTransform(int counterValue); + + // numeric + public final static CounterStyle DECIMAL = CounterStyleImpl.predefinedCounterStyles.get("decimal"); + // alphabetic + public final static CounterStyle LOWER_ALPHA = CounterStyleImpl.predefinedCounterStyles.get("lower-alpha"); + public final static CounterStyle UPPER_ALPHA = CounterStyleImpl.predefinedCounterStyles.get("upper-alpha"); + public final static CounterStyle LOWER_ROMAN = CounterStyleImpl.predefinedCounterStyles.get("lower-roman"); + public final static CounterStyle UPPER_ROMAN = CounterStyleImpl.predefinedCounterStyles.get("upper-roman"); + // cyclic + public final static CounterStyle DISC = CounterStyleImpl.predefinedCounterStyles.get("disc"); + + /** + * Create a CounterStyle from a symbols() function + */ + public static CounterStyle fromSymbolsFunction(Term term) throws IllegalArgumentException { + return CounterStyleImpl.fromSymbolsFunction(term); + } + + /** + * Create a map of named CounterStyle from a set of @counter-style rules. + */ + public static Map parseCounterStyleRules(Iterable style) { + return (Map)(Map)CounterStyleImpl.parseCounterStyleRules(style); + } + + /** + * Return the predefined counter style with the given name, or {@code null} if no counter style + * with that name exists. + */ + public static CounterStyle predefined(String name) { + return CounterStyleImpl.predefinedCounterStyles.get(name); + } + + /** + * Create a constant CounterStyle (cyclic with a single symbol) + */ + public static CounterStyle constant(String symbol) { + return new CounterStyleImpl(symbol); + } + + /** + * Create a cyclic CounterStyle + */ + public static CounterStyle cycle(Iterable symbols) { + return new CounterStyleImpl(ImmutableList.copyOf(symbols)); + } +} + +class CounterStyleImpl implements CounterStyle { private enum System { ALPHABETIC, @@ -38,7 +99,8 @@ private enum System { ADDITIVE }; - private static class AdditiveTuple { + // package private for tests + static class AdditiveTuple { final int weight; final String symbol; AdditiveTuple(int weight, String symbol) { @@ -49,7 +111,7 @@ private static class AdditiveTuple { private final static Logger logger = LoggerFactory.getLogger(CounterStyle.class); - private final static Map predefinedCounterStyles = parseCounterStyleRules( + final static Map predefinedCounterStyles = parseCounterStyleRules( Iterables.filter( new InlineStyle( "@counter-style decimal {" + "\n" + @@ -90,21 +152,12 @@ private static class AdditiveTuple { "}" ), RuleCounterStyle.class)); - // numeric - public final static CounterStyle DECIMAL = predefinedCounterStyles.get("decimal"); - // alphabetic - public final static CounterStyle LOWER_ALPHA = predefinedCounterStyles.get("lower-alpha"); - public final static CounterStyle UPPER_ALPHA = predefinedCounterStyles.get("upper-alpha"); - public final static CounterStyle LOWER_ROMAN = predefinedCounterStyles.get("lower-roman"); - public final static CounterStyle UPPER_ROMAN = predefinedCounterStyles.get("upper-roman"); - // cyclic - public final static CounterStyle DISC = predefinedCounterStyles.get("disc"); - private final System system; private final List symbols; private final List additiveSymbols; private final String negative; - private final Supplier fallback; + private final Supplier fallback; + private final String textTransform; // for braille CSS private final String prefix; private final String suffix; @@ -113,7 +166,7 @@ private static class AdditiveTuple { * * @param term assumed to be a "symbols" function */ - private CounterStyle(TermFunction term) throws IllegalArgumentException { + CounterStyleImpl(TermFunction term) throws IllegalArgumentException { System system; List> symbols; if (term.size() > 0 && term.get(0) instanceof TermIdent) { @@ -129,7 +182,8 @@ private CounterStyle(TermFunction term) throws IllegalArgumentException { this.additiveSymbols = null; this.system = system; this.negative = "-"; - this.fallback = () -> DECIMAL; + this.fallback = () -> (CounterStyleImpl)DECIMAL; + this.textTransform = "auto"; this.prefix = ""; this.suffix = " "; } @@ -138,8 +192,7 @@ private CounterStyle(TermFunction term) throws IllegalArgumentException { * Create a CounterStyle from a @counter-style rule */ // FIXME: the "range" descriptor is not taken into account - // FIXME: the "text-transform" descriptor (from braille CSS) is not taken into account - private CounterStyle(RuleCounterStyle rule, Map fallbacks) throws IllegalArgumentException { + CounterStyleImpl(RuleCounterStyle rule, Map fallbacks) throws IllegalArgumentException { Declaration systemDecl = null; Declaration symbolsDecl = null; Declaration additiveSymbolsDecl = null; @@ -147,6 +200,7 @@ private CounterStyle(RuleCounterStyle rule, Map fallbacks) Declaration fallbackDecl = null; Declaration prefixDecl = null; Declaration suffixDecl = null; + Declaration textTransformDecl = null; for (Declaration d : rule) { String prop = d.getProperty(); if ("system".equals(prop)) { @@ -163,6 +217,8 @@ private CounterStyle(RuleCounterStyle rule, Map fallbacks) if (prefixDecl == null) prefixDecl = d; } else if ("suffix".equals(prop)) { if (suffixDecl == null) suffixDecl = d; + } else if ("text-transform".equals(prop)) { + if (textTransformDecl == null) textTransformDecl = d; } } System system; { @@ -216,6 +272,20 @@ private CounterStyle(RuleCounterStyle rule, Map fallbacks) ident = "decimal"; fallback = ident; } + String textTransform; { + textTransform = null; + if (textTransformDecl != null) { + logger.warn("'text-transform' descriptor in @counter-style rule is deprecated. " + + "Use a 'text-transform' property within the rule containing the 'content' property instead."); + try { + textTransform = readSingleIdent("text-transform", textTransformDecl).getValue(); + } catch (IllegalArgumentException e) { + logger.warn(e.getMessage()); + } + } + if (textTransform == null) + textTransform = "auto"; + } String prefix; { prefix = null; if (prefixDecl != null) @@ -236,7 +306,7 @@ private CounterStyle(RuleCounterStyle rule, Map fallbacks) logger.warn(e.getMessage()); } if (suffix == null) - suffix = ". "; // note that for braille CSS this should be the empty string + suffix = ". "; } this.system = system; this.symbols = symbols; @@ -249,26 +319,45 @@ private CounterStyle(RuleCounterStyle rule, Map fallbacks) else if (predefinedCounterStyles.containsKey(fallback)) return predefinedCounterStyles.get(fallback); else - return DECIMAL; + return (CounterStyleImpl)DECIMAL; } ); + this.textTransform = textTransform; this.prefix = prefix; this.suffix = suffix; } - private static final Map fromSymbolsCache = new HashMap<>(); + /** + * Create a constant CounterStyle (cyclic with a single symbol) + */ + CounterStyleImpl(String symbol) { + this(Collections.singletonList(symbol)); + } /** - * Create a CounterStyle from a symbols() function + * Create a cyclic CounterStyle */ - public static CounterStyle fromSymbolsFunction(Term term) throws IllegalArgumentException { + CounterStyleImpl(List symbols) { + system = System.CYCLIC; + this.symbols = symbols; + textTransform = "none"; + prefix = ""; + suffix = " "; + additiveSymbols = null; + negative = null; + fallback = null; + } + + static final Map fromSymbolsCache = new HashMap<>(); + + static CounterStyle fromSymbolsFunction(Term term) throws IllegalArgumentException { if (term instanceof TermFunction) { TermFunction function = (TermFunction)term; if (function.getFunctionName().equals("symbols")) { String k = function.toString(); CounterStyle s = fromSymbolsCache.get(k); if (s == null) { - s = new CounterStyle(function); + s = new CounterStyleImpl(function); fromSymbolsCache.put(k, s); } return s; @@ -277,21 +366,27 @@ public static CounterStyle fromSymbolsFunction(Term term) throws IllegalArgum throw new IllegalArgumentException("argument must be a symbols() function"); } - /** - * Create a map of named CounterStyle from a set of @counter-style rules. - */ - public static Map parseCounterStyleRules(Iterable style) { - Map namedStyles = new HashMap<>(); + static Map parseCounterStyleRules(Iterable style) { + Map namedStyles = new HashMap<>(); for (RuleCounterStyle rule : style) try { - namedStyles.put(rule.getName(), new CounterStyle(rule, namedStyles)); + namedStyles.put(rule.getName(), new CounterStyleImpl(rule, namedStyles)); } catch (IllegalArgumentException e) { logger.warn(e.getMessage()); } return namedStyles; } + @Override public String format(int counterValue) { + Object o = formatOrFallback(counterValue); + if (o instanceof String) + return (String)o; + CounterStyleImpl fallback = (CounterStyleImpl)o; + return fallback.format(counterValue); + } + + private Object formatOrFallback(int counterValue) { switch (system) { case ALPHABETIC: { Optional formatted = counterRepresentationAlphabetic(counterValue, symbols); @@ -322,9 +417,10 @@ public String format(int counterValue) { } if (fallback.get() == null || fallback.get() == this) throw new IllegalStateException(); // can not happen - return fallback.get().format(counterValue); + return fallback.get(); } + @Override public String format(int counterValue, boolean withPrefixAndSuffix) { // prefix and suffix always come from the specified counter-style, even if the actual // representation is constructed by a fallback style @@ -334,6 +430,15 @@ public String format(int counterValue, boolean withPrefixAndSuffix) { return format(counterValue); } + @Override + public String getTextTransform(int counterValue) { + Object o = formatOrFallback(counterValue); + if (o instanceof String) + return this.textTransform;; + CounterStyleImpl fallback = (CounterStyleImpl)o; + return fallback.textTransform; + } + /* ============ private ========================================================= */ private static System readSystem(List> terms) throws IllegalArgumentException { @@ -351,14 +456,8 @@ private static System readSystem(TermIdent ident) throws IllegalArgumentExceptio private static List readSymbols(List> terms) throws IllegalArgumentException { List symbols = new ArrayList<>(); - for (Term t : terms) { - if (t instanceof TermString) - symbols.add(((TermString)t).getValue()); - else if (t instanceof TermIdent) - symbols.add(CssEscape.unescapeCss(((TermIdent)t).getValue())); - else - throw new IllegalArgumentException("Invalid symbol: " + t); - } + for (Term t : terms) + symbols.add(readSymbol(t)); if (symbols.isEmpty()) throw new IllegalArgumentException("Empty symbols list"); return symbols; @@ -378,11 +477,11 @@ private static List readAdditiveSymbols(List> terms) thro prevWeight = weight; if (tt.hasNext()) { t = tt.next(); - if (t instanceof TermString) { - String symbol = ((TermString)t).getValue(); - symbols.add(new AdditiveTuple(weight, symbol)); - } else - throw new IllegalArgumentException("Invalid additive tuple: expected symbol but got " + t); + try { + symbols.add(new AdditiveTuple(weight, readSymbol(t))); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid additive tuple: expected symbol but got " + t, e); + } } else throw new IllegalArgumentException("Invalid additive tuple: expected symbol"); } else @@ -393,6 +492,17 @@ private static List readAdditiveSymbols(List> terms) thro return symbols; } + private static String readSymbol(Term term) { + if (term instanceof TermString) + return ((TermString)term).getValue(); + else if (term instanceof TermIdent) + return CssEscape.unescapeCss(((TermIdent)term).getValue()); + else if (term instanceof TermInteger) + return "" + ((TermInteger)term).getValue(); + else + throw new IllegalArgumentException("Invalid symbol: " + term); + } + private static TermIdent readSingleIdent(String property, List> terms) throws IllegalArgumentException { if (terms.size() != 1 || !(terms.get(0) instanceof TermIdent)) throw new IllegalArgumentException("Invalid " + property + ": " + CssSerializer.serializeTermList(terms)); diff --git a/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/CssSerializer.java b/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/CssSerializer.java index 670612d613..a36612556e 100644 --- a/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/CssSerializer.java +++ b/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/CssSerializer.java @@ -1,12 +1,14 @@ package org.daisy.pipeline.css; import java.net.URI; +import java.util.Collection; import java.util.function.Function; import java.util.List; import cz.vutbr.web.css.Declaration; 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; @@ -53,7 +55,7 @@ else if (term instanceof TermList else if (term instanceof TermPair) { TermPair pair = (TermPair)term; Object val = pair.getValue(); - return "" + pair.getKey() + " " + (val instanceof Term ? toString((Term)val, toStringFunction) : val.toString()); } + return "" + pair.getKey() + " " + (val instanceof Term ? toStringFunction.apply((Term)val) : val.toString()); } else if (term instanceof TermURI) { TermURI termURI = (TermURI)term; URI uri = URLs.asURI(termURI.getValue()); @@ -63,15 +65,18 @@ else if (term instanceof TermURI) { else if (term instanceof TermString) { TermString string = (TermString)term; return "'" + string.getValue().replaceAll("\n", "\\\\A ").replaceAll("'", "\\\\27 ") + "'"; } + else if (term instanceof TermIdent) { + TermIdent ident = (TermIdent)term; + return ident.getValue(); } else return term.toString().replaceAll("^[,/ ]+", ""); } - public static String serializeTermList(List> termList) { + public static String serializeTermList(Collection> termList) { return serializeTermList(termList, t -> toString(t)); } - public static String serializeTermList(List> termList, Function,String> toStringFunction) { + public static String serializeTermList(Collection> termList, Function,String> toStringFunction) { String s = ""; for (Term t : termList) { if (!s.isEmpty()) { diff --git a/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/JStyleParserCssCascader.java b/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/JStyleParserCssCascader.java index a0683c3d28..f91c139d9d 100644 --- a/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/JStyleParserCssCascader.java +++ b/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/JStyleParserCssCascader.java @@ -263,22 +263,27 @@ public URL getURL() { return URLs.asURL(URI.create(info.getBaseURI())); } public int getLineNumber() { - return info.getLineNumber(); + int line = info.getLineNumber(); + if (line > 0) + return line - 1; // line numbers are 1-based in Saxon + else + return -1; } public int getColumnNumber() { - return info.getColumnNumber(); + return info.getColumnNumber(); // -1 } }; } else { + // should not happen return new SourceLocator() { public URL getURL() { return URLs.asURL(URI.create(n.getBaseURI())); } public int getLineNumber() { - return 0; + return -1; } public int getColumnNumber() { - return 0; + return -1; } }; } diff --git a/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/StyleAccessor.java b/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/StyleAccessor.java index 7420b837c0..faf83add82 100644 --- a/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/StyleAccessor.java +++ b/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/StyleAccessor.java @@ -9,7 +9,7 @@ import org.w3c.dom.Element; -public abstract class StyleAccessor { +public interface StyleAccessor { @Component( name = "StyleAccesor", @@ -26,13 +26,13 @@ public Provider() { * href="https://www.w3.org/TR/2013/CR-css-cascade-3-20131003/#specified">specified * value of a CSS property and element. */ - public abstract Optional get(Element element, String property); + public Optional get(Element element, String property); /** * Test whether an element matches a CSS selector. * * @throws IllegalArgumentException if the selector can not be compiled */ - public abstract boolean matches(Element element, String selector); + public boolean matches(Element element, String selector); } diff --git a/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/impl/DefaultCssCascader.java b/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/impl/DefaultCssCascader.java index 91376755fa..4b0cda90d5 100644 --- a/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/impl/DefaultCssCascader.java +++ b/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/impl/DefaultCssCascader.java @@ -1,11 +1,9 @@ package org.daisy.pipeline.css.impl; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Set; import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamException; @@ -19,19 +17,15 @@ import cz.vutbr.web.css.NodeData; import cz.vutbr.web.css.RuleFactory; import cz.vutbr.web.css.Selector.PseudoElement; -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.TermList; import cz.vutbr.web.css.TermPair; -import cz.vutbr.web.css.TermString; import cz.vutbr.web.csskit.antlr.CSSParserFactory; import cz.vutbr.web.csskit.RuleFactoryImpl; -import cz.vutbr.web.domassign.DeclarationTransformer; import cz.vutbr.web.domassign.StyleMap; -import org.daisy.braille.css.BrailleCSSDeclarationTransformer; import org.daisy.braille.css.BrailleCSSParserFactory; import org.daisy.braille.css.BrailleCSSProperty.Content; import org.daisy.braille.css.BrailleCSSProperty.Display; @@ -41,6 +35,7 @@ import org.daisy.common.stax.BaseURIAwareXMLStreamWriter; import org.daisy.common.stax.XMLStreamWriterHelper; import org.daisy.common.transform.XMLTransformer; +import org.daisy.pipeline.css.CounterEvaluator; import org.daisy.pipeline.css.CounterStyle; import org.daisy.pipeline.css.CssCascader; import org.daisy.pipeline.css.CssPreProcessor; @@ -100,9 +95,7 @@ public XMLTransformer newInstance(Medium medium, // using braille-css because @counter-style is not supported by jStyleParser private static final CSSParserFactory parserFactory = new BrailleCSSParserFactory(); private static final RuleFactory ruleFactory = RuleFactoryImpl.getInstance(); - private static final SupportedCSS supportedCSS = new SupportedBrailleCSS(false, true); // FIXME: support "list-style" shorthand - private static final DeclarationTransformer declarationTransformer - = new BrailleCSSDeclarationTransformer(supportedCSS); + private static final SupportedBrailleCSS supportedCSS = new SupportedBrailleCSS(false, true); // FIXME: support "list-style" shorthand /* * We can make use of JStyleParserCssCascader for the evaluation of marker contents because to @@ -112,17 +105,14 @@ public XMLTransformer newInstance(Medium medium, private static class Transformer extends JStyleParserCssCascader { private Map namedCounterStyles = null; - private LinkedList listCounterStyle = new LinkedList<>(); - private LinkedList> counterValues = new LinkedList<>(); + private CounterEvaluator evaluator; private final QName markerAttributeName; private final QName markerContentAttributeName; private Transformer(URIResolver resolver, CssPreProcessor preProcessor, XsltProcessor xsltProcessor, String userStyleSheet, Medium medium, QName attributeName, boolean multipleAttrs) { super(resolver, preProcessor, xsltProcessor, userStyleSheet, medium, attributeName, - parserFactory, ruleFactory, supportedCSS, declarationTransformer); - listCounterStyle.push(CounterStyle.DISC); - counterValues.push(null); + parserFactory, ruleFactory, supportedCSS, supportedCSS); if (attributeName == null) { markerAttributeName= null; markerContentAttributeName = null; @@ -139,15 +129,137 @@ private Transformer(URIResolver resolver, CssPreProcessor preProcessor, XsltProc @Override protected void processElement(Element element, StyleMap styleMap, BaseURIAwareXMLStreamWriter writer) throws XMLStreamException, IllegalArgumentException { + if (evaluator == null) + // styleMap is constant so we can define evaluator based on styleMap argument of first call + evaluator = new CounterEvaluator() { + @Override + protected Collection> getCounterReset(Element element) { + NodeData style = styleMap.get(element); + if (style.getProperty("counter-reset", false) == CounterReset.list_values) { + List> list = style.getValue(TermList.class, "counter-reset", false); + if (list != null) { + for (Term t : list) { + if (!(t instanceof TermPair)) + throw new IllegalStateException("coding error"); + TermPair p = (TermPair)t; + if (!(p.getKey() instanceof String)) + throw new IllegalStateException("coding error"); + if (!(p.getValue() instanceof Integer)) + throw new IllegalStateException("coding error"); + } + return (List>)(List)list; + } + } + return null; + } + @Override + protected Collection> getCounterSet(Element element) { + NodeData style = styleMap.get(element); + if (style.getProperty("counter-set", false) == CounterSet.list_values) { + List> list = style.getValue(TermList.class, "counter-set", false); + if (list != null) { + for (Term t : list) { + if (!(t instanceof TermPair)) + throw new IllegalStateException("coding error"); + TermPair p = (TermPair)t; + if (!(p.getKey() instanceof String)) + throw new IllegalStateException("coding error"); + if (!(p.getValue() instanceof Integer)) + throw new IllegalStateException("coding error"); + } + return (List>)(List)list; + } + } + return null; + } + @Override + protected Collection> getCounterIncrement(Element element) { + NodeData style = styleMap.get(element); + if (style.getProperty("counter-increment", false) == CounterIncrement.list_values) { + List> list = style.getValue(TermList.class, "counter-increment", false); + if (list != null) { + for (Term t : list) { + if (!(t instanceof TermPair)) + throw new IllegalStateException("coding error"); + TermPair p = (TermPair)t; + if (!(p.getKey() instanceof String)) + throw new IllegalStateException("coding error"); + if (!(p.getValue() instanceof Integer)) + throw new IllegalStateException("coding error"); + } + return (List>)(List)list; + } + } + return null; + } + @Override + protected Display getDisplay(Element element) { + NodeData style = styleMap.get(element); + return style.getProperty("display", false); + } + @Override + protected Collection> getMarkerContent(Element element) { + NodeData style = styleMap.get(element); + for (PseudoElement pseudo : styleMap.pseudoSet(element)) { + if ("marker".equals(pseudo.getName())) { + NodeData pseudoStyle = styleMap.get(element, pseudo); + if (pseudoStyle.getProperty("content", false) == Content.content_list) { + List> content = pseudoStyle.getValue(TermList.class, "content", false); + if (content != null) + return content; + } + return Collections.emptyList(); + } + } + return null; + } + @Override + protected CounterStyle getListStyleType(Element element, CounterStyle parentListStyleType) { + NodeData style = styleMap.get(element); + ListStyleType type = style.getProperty("list-style-type", false); + if (type == null) + return parentListStyleType; // value is inherited from parent + else { + switch (type) { + case INHERIT: + // should not happen + return parentListStyleType; + case INITIAL: + return CounterStyle.DISC; // see https://www.w3.org/TR/css-lists-3/#propdef-list-style-type + case counter_style_name: + return getNamedCounterStyle(style.getValue(TermIdent.class, "list-style-type").getValue()); + case symbols_fn: + return CounterStyle.fromSymbolsFunction(style.getValue(TermFunction.class, "list-style-type")); + case braille_string: // note that braille CSS does not support any string, as standard CSS does + return null; + case DECIMAL: + return CounterStyle.DECIMAL; + case LOWER_ALPHA: + return CounterStyle.LOWER_ALPHA; + case LOWER_ROMAN: + return CounterStyle.LOWER_ROMAN; + case UPPER_ALPHA: + return CounterStyle.UPPER_ALPHA; + case UPPER_ROMAN: + return CounterStyle.UPPER_ROMAN; + case NONE: + default: + return null; + } + } + } + @Override + protected CounterStyle getNamedCounterStyle(String name) { + if (namedCounterStyles == null) + namedCounterStyles = CounterStyle.parseCounterStyleRules( + Iterables.filter(getParsedStyleSheet(), RuleCounterStyle.class)); + return namedCounterStyles.getOrDefault(name, CounterStyle.DECIMAL); + } + }; XMLStreamWriterHelper.writeStartElement(writer, element); copyAttributes(element, writer); - Map pseudoStyles = new HashMap<>(); { - for (PseudoElement pseudo : styleMap.pseudoSet(element)) - pseudoStyles.put(pseudo, styleMap.get(element, pseudo)); } - NodeData mainStyle = styleMap.get(element); - updateCounterValues(mainStyle); - CounterStyle listCounterStyle = getCurrentListCounterStyle(mainStyle); - String marker = generateMarkerContents(mainStyle, pseudoStyles, element, listCounterStyle); + evaluator.startElement(element); + String marker = evaluator.generateMarkerContents(element); if (marker != null) { if (markerContentAttributeName != null) // if attribute namespace specified, insert as "marker-content" attribute @@ -161,309 +273,12 @@ else if (markerAttributeName != null) // if no attribute specified, insert as text writer.writeCharacters(marker); } - this.listCounterStyle.push(listCounterStyle); - this.counterValues.push(null); for (Node child = element.getFirstChild(); child != null; child = child.getNextSibling()) traverse(child, styleMap, writer); - this.listCounterStyle.pop(); - this.counterValues.pop(); + evaluator.endElement(); writer.writeEndElement(); } - /** - * Determine counter values (see https://www.w3.org/TR/css-lists-3/#creating-counters) - */ - private void updateCounterValues(NodeData style) { - if (style.getProperty("counter-reset", false) == CounterReset.list_values) { - List> list = style.getValue(TermList.class, "counter-reset", false); - if (list != null) { - for (Term t : list) { - if (!(t instanceof TermPair)) - throw new IllegalStateException("coding error"); - TermPair p = (TermPair)t; - String name = p.getKey(); - Map createdBySiblingOrSelf = counterValues.peek(); - if (createdBySiblingOrSelf == null) { - createdBySiblingOrSelf = new HashMap<>(); - counterValues.set(0, createdBySiblingOrSelf); - } - createdBySiblingOrSelf.put(name, p.getValue()); - } - } - } - // counter-set or counter-increment work in addition to counter-reset - Set done = null; - if (style.getProperty("counter-set", false) == CounterSet.list_values) { - List> list = style.getValue(TermList.class, "counter-set", false); - if (list != null) { - for (Term t : list) { - if (!(t instanceof TermPair)) - throw new IllegalStateException("coding error"); - TermPair p = (TermPair)t; - String name = p.getKey(); - boolean exists = false; - for (Map c : counterValues) { - if (c != null) { - c.put(name, p.getValue()); - exists = true; - break; - } - } - if (!exists) { - Map createdBySiblingOrSelf = counterValues.peek(); - if (createdBySiblingOrSelf == null) { - createdBySiblingOrSelf = new HashMap<>(); - counterValues.set(0, createdBySiblingOrSelf); - } - createdBySiblingOrSelf.put(name, p.getValue()); - } - if (done == null) - done = new HashSet<>(); - done.add(name); - } - } - } - if (style.getProperty("counter-increment", false) == CounterIncrement.list_values) { - List> list = style.getValue(TermList.class, "counter-increment", false); - if (list != null) { - for (Term t : list) { - if (!(t instanceof TermPair)) - throw new IllegalStateException("coding error"); - TermPair p = (TermPair)t; - String name = p.getKey(); - if (done == null || !done.contains(name)) { - boolean exists = false; - for (Map c : counterValues) { - if (c != null) { - c.put(name, c.getOrDefault(name, 0) + p.getValue()); - exists = true; - break; - } - } - if (!exists) { - Map createdBySiblingOrSelf = counterValues.peek(); - if (createdBySiblingOrSelf == null) { - createdBySiblingOrSelf = new HashMap<>(); - counterValues.set(0, createdBySiblingOrSelf); - } - createdBySiblingOrSelf.put(name, p.getValue()); - } - if (done == null) - done = new HashSet<>(); - done.add(name); - } - } - } - } - // by default list items increment the 'list-item' counter - if ((done == null || !done.contains("list-item")) && style.getProperty("display", false) == Display.LIST_ITEM) { - boolean exists = false; - for (Map c : counterValues) { - if (c != null) { - c.put("list-item", c.getOrDefault("list-item", 0) + 1); - exists = true; - break; - } - } - if (!exists) { - Map createdBySiblingOrSelf = counterValues.peek(); - if (createdBySiblingOrSelf == null) { - createdBySiblingOrSelf = new HashMap<>(); - counterValues.set(0, createdBySiblingOrSelf); - } - createdBySiblingOrSelf.put("list-item", 1); - } - } - } - - private CounterStyle getCurrentListCounterStyle(NodeData style) { - ListStyleType type = style.getProperty("list-style-type", false); - if (type == null) - return this.listCounterStyle.peek(); // value is inherited from parent - else { - switch (type) { - case INHERIT: - // should not happen - return this.listCounterStyle.peek(); - case INITIAL: - return CounterStyle.DISC; // see https://www.w3.org/TR/css-lists-3/#propdef-list-style-type - case counter_style_name: - return getNamedCounterStyle(style.getValue(TermIdent.class, "list-style-type").getValue()); - case symbols_fn: - return CounterStyle.fromSymbolsFunction(style.getValue(TermFunction.class, "list-style-type")); - case braille_string: // note that braille CSS does not support any string, as standard CSS does - return null; - case DECIMAL: - return CounterStyle.DECIMAL; - case LOWER_ALPHA: - return CounterStyle.LOWER_ALPHA; - case LOWER_ROMAN: - return CounterStyle.LOWER_ROMAN; - case UPPER_ALPHA: - return CounterStyle.UPPER_ALPHA; - case UPPER_ROMAN: - return CounterStyle.UPPER_ROMAN; - case NONE: - default: - return null; - } - } - } - - private CounterStyle getNamedCounterStyle(String name) { - if (namedCounterStyles == null) - namedCounterStyles = CounterStyle.parseCounterStyleRules( - Iterables.filter(getParsedStyleSheet(), RuleCounterStyle.class)); - return namedCounterStyles.getOrDefault(name, CounterStyle.DECIMAL); - } - - private String evaluateCounter(String name, CounterStyle style, boolean withPrefixAndSuffix) { - Integer value = null; - boolean exists = false; - for (Map c : counterValues) - if (c != null && c.containsKey(name)) { - value = c.get(name); - break; - } - if (value == null) { - value = 0; - Map createdBySiblingOrSelf = counterValues.peek(); - if (createdBySiblingOrSelf == null) { - createdBySiblingOrSelf = new HashMap<>(); - counterValues.set(0, createdBySiblingOrSelf); - } - createdBySiblingOrSelf.put(name, value); - } - return style.format(value, withPrefixAndSuffix); - } - - private String evaluateCounters(String name, CounterStyle style, String separator) { - StringBuilder s = new StringBuilder(); - for (Map c : counterValues) - if (c != null && c.containsKey(name)) { - if (s.length() > 0) - s.insert(0, separator); - s.insert(0, style.format(c.get(name))); - } - if (s.length() == 0) { - Map createdBySiblingOrSelf = counterValues.peek(); - if (createdBySiblingOrSelf == null) { - createdBySiblingOrSelf = new HashMap<>(); - counterValues.set(0, createdBySiblingOrSelf); - } - createdBySiblingOrSelf.put(name, 0); - s.append(style.format(0)); - } - return s.toString(); - } - - private String evaluateContent(List> content, Element element) throws IllegalArgumentException { - StringBuilder s = new StringBuilder(); - for (Term t : content) { - if (t instanceof TermString) { - s.append(((TermString)t).getValue()); - } else if (t instanceof TermFunction) { - TermFunction f = (TermFunction)t; - if ("counter".equalsIgnoreCase(f.getFunctionName())) { - String name = null; - CounterStyle style = null; - for (Term arg : f) - if (name == null) - if (arg instanceof TermIdent) - name = ((TermIdent)arg).getValue(); - else - throw new IllegalArgumentException( - "invalid first argument of counter() function: should be the counter name"); - else if (style == null) { - if (arg instanceof TermIdent) - style = getNamedCounterStyle(((TermIdent)arg).getValue()); - else if (arg instanceof TermFunction) - try { - style = CounterStyle.fromSymbolsFunction((TermFunction)arg); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException( - "invalid second argument of counter() function: should be a counter style", e); - } - else - throw new IllegalArgumentException( - "invalid second argument of counter() function: should be a counter style"); } - else - throw new IllegalArgumentException( - "unexpected argument of counter() function: function takes at most two arguments"); - if (name == null) - throw new IllegalArgumentException("counter() function requires at least one argument"); - if (style == null) - style = CounterStyle.DECIMAL; - s.append(evaluateCounter(name, style, false)); - } else if ("counters".equalsIgnoreCase(f.getFunctionName())) { - String name = null; - String separator = null; - CounterStyle style = null; - for (Term arg : f) - if (name == null) - if (arg instanceof TermIdent) - name = ((TermIdent)arg).getValue(); - else - throw new IllegalArgumentException( - "invalid first argument of counters() function: should be the counter name"); - else if (separator == null) { - if (arg instanceof TermString) - separator = ((TermString)arg).getValue(); - else - throw new IllegalArgumentException( - "invalid second argument of counters() function: should be a string"); } - else if (style == null) { - if (arg instanceof TermIdent) - style = getNamedCounterStyle(((TermIdent)arg).getValue()); - else if (arg instanceof TermFunction) - try { - style = CounterStyle.fromSymbolsFunction((TermFunction)arg); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException( - "invalid third argument of counters() function: should be a counter style", e); - } - else - throw new IllegalArgumentException( - "invalid third argument of counters() function: should be a counter style"); } - else - throw new IllegalArgumentException( - "unexpected argument of counters() function: function takes at most three arguments"); - if (name == null || separator == null) - throw new IllegalArgumentException("counters() function requires at least two arguments"); - if (style == null) - style = CounterStyle.DECIMAL; - s.append(evaluateCounters(name, style, separator)); - } else - throw new RuntimeException(f.getFunctionName() + "() function not supported in content list"); // FIXME - } else - throw new IllegalStateException(); // cannot happen - } - return s.toString(); - } - - /** - * Evaluate marker contents (see https://www.w3.org/TR/css-lists-3/#content-property) - */ - private String generateMarkerContents(NodeData mainStyle, Map pseudoStyles, Element element, - CounterStyle listCounterStyle) throws IllegalArgumentException { - if (mainStyle.getProperty("display", false) == Display.LIST_ITEM) { - for (PseudoElement pseudo : pseudoStyles.keySet()) { - if ("marker".equals(pseudo.getName())) { - NodeData pseudoStyle = pseudoStyles.get(pseudo); - if (pseudoStyle.getProperty("content", false) == Content.content_list) { - List> content = pseudoStyle.getValue(TermList.class, "content", false); - if (content != null) - return evaluateContent(content, element); - } - return null; - } - } - if (listCounterStyle != null) - return evaluateCounter("list-item", listCounterStyle, true); - } - return null; - } - protected Map serializeStyle(NodeData mainStyle, Map pseudoStyles, Element context) { throw new UnsupportedOperationException(); // not needed } diff --git a/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/sass/impl/SassAnalyzer.java b/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/sass/impl/SassAnalyzer.java index 26b207a580..0cce94889e 100644 --- a/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/sass/impl/SassAnalyzer.java +++ b/modules/scripts-utils/css-utils/src/main/java/org/daisy/pipeline/css/sass/impl/SassAnalyzer.java @@ -186,7 +186,7 @@ public Collection getVariableDeclarations(Iterable userAnd Reader r = ss.getReader(); if (r == null) r = new InputStreamReader(ss.getInputStream(), StandardCharsets.UTF_8); - stylesheets.add(new CSSSource(CharStreams.toString(r), null, base, 0, 0)); + stylesheets.add(new CSSSource(CharStreams.toString(r), (String)null, base, 0, 0)); } else { if (base == null) { InputSource is = SAXSource.sourceToInputSource(s); @@ -200,7 +200,7 @@ public Collection getVariableDeclarations(Iterable userAnd else throw new IllegalArgumentException("unexpected source: no content and no base URI"); } - stylesheets.add(new CSSSource(CharStreams.toString(r), null, base, 0, 0)); + stylesheets.add(new CSSSource(CharStreams.toString(r), (String)null, base, 0, 0)); } else stylesheets.add(new CSSSource(base, StandardCharsets.UTF_8, null)); } diff --git a/modules/scripts-utils/css-utils/src/main/resources/META-INF/catalog.xml b/modules/scripts-utils/css-utils/src/main/resources/META-INF/catalog.xml index 6d7d0449fa..82bf6aecba 100755 --- a/modules/scripts-utils/css-utils/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts-utils/css-utils/src/main/resources/META-INF/catalog.xml @@ -4,8 +4,4 @@ - - - - diff --git a/modules/scripts-utils/css-utils/src/main/resources/xml/css-cascade.xpl b/modules/scripts-utils/css-utils/src/main/resources/xml/css-cascade.xpl index 33e6e2ff65..389f9d4402 100644 --- a/modules/scripts-utils/css-utils/src/main/resources/xml/css-cascade.xpl +++ b/modules/scripts-utils/css-utils/src/main/resources/xml/css-cascade.xpl @@ -17,7 +17,7 @@ CSS and SCSS style sheets in XML.

Inlining is done with style attributes with the syntax described in - braille CSS.

+ braille CSS. Note that concretizing "inherit" is not part of cascading.

diff --git a/modules/scripts-utils/css-utils/src/test/java/org/daisy/pipeline/css/CounterStyleTest.java b/modules/scripts-utils/css-utils/src/test/java/org/daisy/pipeline/css/CounterStyleTest.java index 06bc5bbdb8..931c75c65b 100644 --- a/modules/scripts-utils/css-utils/src/test/java/org/daisy/pipeline/css/CounterStyleTest.java +++ b/modules/scripts-utils/css-utils/src/test/java/org/daisy/pipeline/css/CounterStyleTest.java @@ -1,6 +1,9 @@ package org.daisy.pipeline.css; import java.util.ArrayList; +import java.util.List; + +import org.daisy.pipeline.css.CounterStyleImpl.AdditiveTuple; import org.junit.Assert; import org.junit.Test; @@ -9,7 +12,7 @@ public class CounterStyleTest { @Test public void testCounterRepresentation() { - ArrayList symbols = new ArrayList(); + List symbols = new ArrayList(); symbols.add("a"); symbols.add("b"); symbols.add("c"); @@ -18,43 +21,66 @@ public void testCounterRepresentation() { symbols.add("f"); symbols.add("g"); symbols.add("h"); - Assert.assertEquals("", CounterStyle.counterRepresentationAlphabetic(0, symbols).orElse("")); - Assert.assertEquals("a", CounterStyle.counterRepresentationAlphabetic(1, symbols).orElse("")); - Assert.assertEquals("b", CounterStyle.counterRepresentationAlphabetic(2, symbols).orElse("")); - Assert.assertEquals("add", CounterStyle.counterRepresentationAlphabetic(100, symbols).orElse("")); - Assert.assertEquals("", CounterStyle.counterRepresentationAlphabetic(-1, symbols).orElse("")); - Assert.assertEquals("h", CounterStyle.counterRepresentationCyclic(0 , symbols)); - Assert.assertEquals("a", CounterStyle.counterRepresentationCyclic(1 , symbols)); - Assert.assertEquals("b", CounterStyle.counterRepresentationCyclic(2 , symbols)); - Assert.assertEquals("d", CounterStyle.counterRepresentationCyclic(100 , symbols)); - Assert.assertEquals("g", CounterStyle.counterRepresentationCyclic(-1 , symbols)); - Assert.assertEquals("", CounterStyle.counterRepresentationFixed(0 , symbols).orElse("")); - Assert.assertEquals("a", CounterStyle.counterRepresentationFixed(1 , symbols).orElse("")); - Assert.assertEquals("b", CounterStyle.counterRepresentationFixed(2 , symbols).orElse("")); - Assert.assertEquals("", CounterStyle.counterRepresentationFixed(100 , symbols).orElse("")); - Assert.assertEquals("", CounterStyle.counterRepresentationFixed(-1 , symbols).orElse("")); - Assert.assertEquals("a", CounterStyle.counterRepresentationNumeric(0 , symbols, "-")); - Assert.assertEquals("b", CounterStyle.counterRepresentationNumeric(1 , symbols, "-")); - Assert.assertEquals("c", CounterStyle.counterRepresentationNumeric(2 , symbols, "-")); - Assert.assertEquals("bee", CounterStyle.counterRepresentationNumeric(100 , symbols, "-")); - Assert.assertEquals("-b", CounterStyle.counterRepresentationNumeric(-1 , symbols, "-")); - Assert.assertEquals("", CounterStyle.counterRepresentationSymbolic(0 , symbols).orElse("")); - Assert.assertEquals("a", CounterStyle.counterRepresentationSymbolic(1 , symbols).orElse("")); - Assert.assertEquals("b", CounterStyle.counterRepresentationSymbolic(2 , symbols).orElse("")); - Assert.assertEquals("ddddddddddddd", CounterStyle.counterRepresentationSymbolic(100 , symbols).orElse("")); - Assert.assertEquals("", CounterStyle.counterRepresentationSymbolic(-1 , symbols).orElse("")); + Assert.assertEquals("", CounterStyleImpl.counterRepresentationAlphabetic(0, symbols).orElse("")); + Assert.assertEquals("a", CounterStyleImpl.counterRepresentationAlphabetic(1, symbols).orElse("")); + Assert.assertEquals("b", CounterStyleImpl.counterRepresentationAlphabetic(2, symbols).orElse("")); + Assert.assertEquals("add", CounterStyleImpl.counterRepresentationAlphabetic(100, symbols).orElse("")); + Assert.assertEquals("", CounterStyleImpl.counterRepresentationAlphabetic(-1, symbols).orElse("")); + Assert.assertEquals("h", CounterStyleImpl.counterRepresentationCyclic(0, symbols)); + Assert.assertEquals("a", CounterStyleImpl.counterRepresentationCyclic(1, symbols)); + Assert.assertEquals("b", CounterStyleImpl.counterRepresentationCyclic(2, symbols)); + Assert.assertEquals("d", CounterStyleImpl.counterRepresentationCyclic(100, symbols)); + Assert.assertEquals("g", CounterStyleImpl.counterRepresentationCyclic(-1, symbols)); + Assert.assertEquals("", CounterStyleImpl.counterRepresentationFixed(0, symbols).orElse("")); + Assert.assertEquals("a", CounterStyleImpl.counterRepresentationFixed(1, symbols).orElse("")); + Assert.assertEquals("b", CounterStyleImpl.counterRepresentationFixed(2, symbols).orElse("")); + Assert.assertEquals("", CounterStyleImpl.counterRepresentationFixed(100, symbols).orElse("")); + Assert.assertEquals("", CounterStyleImpl.counterRepresentationFixed(-1, symbols).orElse("")); + Assert.assertEquals("a", CounterStyleImpl.counterRepresentationNumeric(0, symbols, "-")); + Assert.assertEquals("b", CounterStyleImpl.counterRepresentationNumeric(1, symbols, "-")); + Assert.assertEquals("c", CounterStyleImpl.counterRepresentationNumeric(2, symbols, "-")); + Assert.assertEquals("bee", CounterStyleImpl.counterRepresentationNumeric(100, symbols, "-")); + Assert.assertEquals("-b", CounterStyleImpl.counterRepresentationNumeric(-1, symbols, "-")); + Assert.assertEquals("", CounterStyleImpl.counterRepresentationSymbolic(0, symbols).orElse("")); + Assert.assertEquals("a", CounterStyleImpl.counterRepresentationSymbolic(1, symbols).orElse("")); + Assert.assertEquals("b", CounterStyleImpl.counterRepresentationSymbolic(2, symbols).orElse("")); + Assert.assertEquals("ddddddddddddd", CounterStyleImpl.counterRepresentationSymbolic(100, symbols).orElse("")); + Assert.assertEquals("", CounterStyleImpl.counterRepresentationSymbolic(-1, symbols).orElse("")); + } + + @Test + public void testCounterRepresentationAdditive() { + List symbols = new ArrayList<>(); + symbols.add(new AdditiveTuple(1000, "⠍")); + symbols.add(new AdditiveTuple(900, "⠉⠍")); + symbols.add(new AdditiveTuple(500, "⠙")); + symbols.add(new AdditiveTuple(400, "⠉⠙")); + symbols.add(new AdditiveTuple(100, "⠉")); + symbols.add(new AdditiveTuple(90, "⠭⠉")); + symbols.add(new AdditiveTuple(50, "⠇")); + symbols.add(new AdditiveTuple(40, "⠭⠇")); + symbols.add(new AdditiveTuple(10, "⠭")); + symbols.add(new AdditiveTuple(9, "⠊⠭")); + symbols.add(new AdditiveTuple(5, "⠧")); + symbols.add(new AdditiveTuple(4, "⠊⠧")); + symbols.add(new AdditiveTuple(1, "⠊")); + Assert.assertEquals("⠍⠉⠍⠭⠉⠧⠊⠊⠊", CounterStyleImpl.counterRepresentationAdditive(1998, symbols).orElse("")); } @Test public void testPredefinedCounterStyles() { + Assert.assertEquals("100", CounterStyle.DECIMAL.format(100)); + Assert.assertEquals("-25", CounterStyle.DECIMAL.format(-25)); Assert.assertEquals("a", CounterStyle.LOWER_ALPHA.format(1)); Assert.assertEquals("j", CounterStyle.LOWER_ALPHA.format(10)); Assert.assertEquals("aa", CounterStyle.LOWER_ALPHA.format(27)); + Assert.assertEquals("cv", CounterStyle.LOWER_ALPHA.format(100)); Assert.assertEquals("0", CounterStyle.LOWER_ALPHA.format(0)); Assert.assertEquals("-1", CounterStyle.LOWER_ALPHA.format(-1)); Assert.assertEquals("i", CounterStyle.LOWER_ROMAN.format(1)); Assert.assertEquals("xcix", CounterStyle.LOWER_ROMAN.format(99)); Assert.assertEquals("0", CounterStyle.LOWER_ROMAN.format(0)); Assert.assertEquals("-1", CounterStyle.LOWER_ROMAN.format(-1)); + Assert.assertEquals("MCMXCVIII", CounterStyle.UPPER_ROMAN.format(1998)); } } diff --git a/modules/scripts-utils/daisy202-utils/pom.xml b/modules/scripts-utils/daisy202-utils/pom.xml index d3e49e47d8..5c84084702 100644 --- a/modules/scripts-utils/daisy202-utils/pom.xml +++ b/modules/scripts-utils/daisy202-utils/pom.xml @@ -17,48 +17,37 @@ DAISY Pipeline 2 module :: DAISY 2.02 Utils - org.daisy.pipeline framework-core - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules html-utils - runtime org.daisy.pipeline.modules smil-utils - runtime org.daisy.pipeline.modules audio-common - runtime org.daisy.pipeline.modules mediatype-utils - runtime diff --git a/modules/scripts-utils/daisy202-utils/src/main/resources/META-INF/catalog.xml b/modules/scripts-utils/daisy202-utils/src/main/resources/META-INF/catalog.xml index 312a136893..ecbc20054b 100755 --- a/modules/scripts-utils/daisy202-utils/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts-utils/daisy202-utils/src/main/resources/META-INF/catalog.xml @@ -9,7 +9,7 @@ - + @@ -26,12 +26,4 @@ - - - - - - - - diff --git a/modules/scripts-utils/daisy3-utils/pom.xml b/modules/scripts-utils/daisy3-utils/pom.xml index 6d3ea49d04..f433fe3ca2 100644 --- a/modules/scripts-utils/daisy3-utils/pom.xml +++ b/modules/scripts-utils/daisy3-utils/pom.xml @@ -17,43 +17,33 @@ DAISY Pipeline 2 module :: DAISY 3 Utils - org.daisy.pipeline framework-core - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules smil-utils - runtime org.daisy.pipeline.modules dtbook-utils - runtime org.daisy.pipeline.modules audio-common - runtime diff --git a/modules/scripts-utils/daisy3-utils/src/main/resources/META-INF/catalog.xml b/modules/scripts-utils/daisy3-utils/src/main/resources/META-INF/catalog.xml index cf29889e5c..9bbe8bd018 100755 --- a/modules/scripts-utils/daisy3-utils/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts-utils/daisy3-utils/src/main/resources/META-INF/catalog.xml @@ -29,8 +29,4 @@ - - - - diff --git a/modules/scripts-utils/daisy3-utils/src/test/java/XProcSpecTest.java b/modules/scripts-utils/daisy3-utils/src/test/java/XProcSpecTest.java index 4a5e497f37..eae9bf5d3f 100644 --- a/modules/scripts-utils/daisy3-utils/src/test/java/XProcSpecTest.java +++ b/modules/scripts-utils/daisy3-utils/src/test/java/XProcSpecTest.java @@ -9,6 +9,8 @@ protected String[] testDependencies() { pipelineModule("file-utils"), pipelineModule("fileset-utils"), pipelineModule("smil-utils"), + pipelineModule("dtbook-utils"), + pipelineModule("audio-common"), }; } } diff --git a/modules/scripts-utils/dtbook-utils/pom.xml b/modules/scripts-utils/dtbook-utils/pom.xml index 1ea7376383..f8b57f1270 100644 --- a/modules/scripts-utils/dtbook-utils/pom.xml +++ b/modules/scripts-utils/dtbook-utils/pom.xml @@ -31,53 +31,41 @@ ds-to-spi-runtime provided
- org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules mediatype-utils - runtime org.daisy.pipeline.modules validation-utils - runtime org.daisy.pipeline.modules css-utils - runtime org.daisy.pipeline.modules metadata-utils - runtime org.daisy.pipeline.modules mathml-utils - runtime org.daisy.pipeline.modules dtbook-break-detection - runtime org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules html-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules smil-utils - runtime org.daisy.pipeline.modules mediatype-utils - runtime org.daisy.pipeline.modules zip-utils - runtime org.daisy.pipeline.modules odf-utils - runtime org.daisy.pipeline.modules daisy3-utils - runtime org.daisy.pipeline.modules validation-utils - runtime org.daisy.pipeline.modules epubcheck-adapter - runtime org.daisy.pipeline.modules ace-adapter - runtime
diff --git a/modules/scripts-utils/epub-utils/src/main/resources/META-INF/catalog.xml b/modules/scripts-utils/epub-utils/src/main/resources/META-INF/catalog.xml index ad1a6ac5ba..1f4b1f8295 100755 --- a/modules/scripts-utils/epub-utils/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts-utils/epub-utils/src/main/resources/META-INF/catalog.xml @@ -2,16 +2,4 @@ - - - - - - - - - - - - diff --git a/modules/scripts-utils/epub3-to-html/pom.xml b/modules/scripts-utils/epub3-to-html/pom.xml index c945592f7b..ab83118b34 100644 --- a/modules/scripts-utils/epub3-to-html/pom.xml +++ b/modules/scripts-utils/epub3-to-html/pom.xml @@ -23,13 +23,9 @@ - org.daisy.pipeline.modules epub-utils - runtime - + @@ -71,7 +71,7 @@ - + @@ -116,12 +116,4 @@ - - - - - - - - diff --git a/modules/scripts-utils/html-utils/src/main/resources/xml/dtd/rdfa/xhtml-metaAttributes-2.mod b/modules/scripts-utils/html-utils/src/main/resources/xml/dtd/rdfa11/xhtml-metaAttributes-2.mod similarity index 100% rename from modules/scripts-utils/html-utils/src/main/resources/xml/dtd/rdfa/xhtml-metaAttributes-2.mod rename to modules/scripts-utils/html-utils/src/main/resources/xml/dtd/rdfa11/xhtml-metaAttributes-2.mod diff --git a/modules/scripts-utils/html-utils/src/main/resources/xml/dtd/rdfa/xhtml-rdfa-2.dtd b/modules/scripts-utils/html-utils/src/main/resources/xml/dtd/rdfa11/xhtml-rdfa-2.dtd similarity index 100% rename from modules/scripts-utils/html-utils/src/main/resources/xml/dtd/rdfa/xhtml-rdfa-2.dtd rename to modules/scripts-utils/html-utils/src/main/resources/xml/dtd/rdfa11/xhtml-rdfa-2.dtd diff --git a/modules/scripts-utils/html-utils/src/main/resources/xml/dtd/rdfa/xhtml-rdfa-model-2.mod b/modules/scripts-utils/html-utils/src/main/resources/xml/dtd/rdfa11/xhtml-rdfa-model-2.mod similarity index 100% rename from modules/scripts-utils/html-utils/src/main/resources/xml/dtd/rdfa/xhtml-rdfa-model-2.mod rename to modules/scripts-utils/html-utils/src/main/resources/xml/dtd/rdfa11/xhtml-rdfa-model-2.mod diff --git a/modules/scripts-utils/html-utils/src/main/resources/xml/xslt/html5-normalize-sections-headings.xsl b/modules/scripts-utils/html-utils/src/main/resources/xml/xslt/html5-normalize-sections-headings.xsl index 484901306d..9b594c8358 100644 --- a/modules/scripts-utils/html-utils/src/main/resources/xml/xslt/html5-normalize-sections-headings.xsl +++ b/modules/scripts-utils/html-utils/src/main/resources/xml/xslt/html5-normalize-sections-headings.xsl @@ -14,7 +14,7 @@ - + - org.daisy.pipeline.modules - validation-utils - runtime - - - org.daisy.libs - jing - runtime + org.daisy.pipeline + calabash-adapter + test - + org.daisy.pipeline.modules.impl.Module_metadata_utils - - \ No newline at end of file + + diff --git a/modules/scripts-utils/metadata-utils/src/main/resources/META-INF/catalog.xml b/modules/scripts-utils/metadata-utils/src/main/resources/META-INF/catalog.xml index a45fcfc37e..d643a672df 100755 --- a/modules/scripts-utils/metadata-utils/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts-utils/metadata-utils/src/main/resources/META-INF/catalog.xml @@ -1,5 +1,4 @@ - diff --git a/modules/scripts-utils/metadata-utils/src/test/java/XProcSpecTest.java b/modules/scripts-utils/metadata-utils/src/test/java/XProcSpecTest.java index c2a5718199..8c7e052fed 100644 --- a/modules/scripts-utils/metadata-utils/src/test/java/XProcSpecTest.java +++ b/modules/scripts-utils/metadata-utils/src/test/java/XProcSpecTest.java @@ -1,13 +1,4 @@ import org.daisy.pipeline.junit.AbstractXSpecAndXProcSpecTest; public class XProcSpecTest extends AbstractXSpecAndXProcSpecTest { - - @Override - protected String[] testDependencies() { - return new String[] { - "org.daisy.libs:jing:?", - pipelineModule("common-utils"), - pipelineModule("validation-utils"), - }; - } } diff --git a/modules/scripts-utils/odf-utils/pom.xml b/modules/scripts-utils/odf-utils/pom.xml index 0d0615d998..b92d9ce831 100644 --- a/modules/scripts-utils/odf-utils/pom.xml +++ b/modules/scripts-utils/odf-utils/pom.xml @@ -21,23 +21,17 @@ org.daisy.pipeline calabash-adapter - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules file-utils - runtime diff --git a/modules/scripts-utils/odf-utils/src/main/resources/META-INF/catalog.xml b/modules/scripts-utils/odf-utils/src/main/resources/META-INF/catalog.xml index bf1fbdc7b8..d65850fb46 100755 --- a/modules/scripts-utils/odf-utils/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts-utils/odf-utils/src/main/resources/META-INF/catalog.xml @@ -1,7 +1,4 @@ - - - diff --git a/modules/scripts-utils/smil-utils/pom.xml b/modules/scripts-utils/smil-utils/pom.xml index f2cfe5247e..25485e64d5 100644 --- a/modules/scripts-utils/smil-utils/pom.xml +++ b/modules/scripts-utils/smil-utils/pom.xml @@ -17,28 +17,21 @@ DAISY Pipeline 2 module :: SMIL Utilities - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules mediatype-utils - runtime diff --git a/modules/scripts-utils/smil-utils/src/main/resources/META-INF/catalog.xml b/modules/scripts-utils/smil-utils/src/main/resources/META-INF/catalog.xml index c430bffbfb..5da87af82a 100755 --- a/modules/scripts-utils/smil-utils/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts-utils/smil-utils/src/main/resources/META-INF/catalog.xml @@ -12,8 +12,4 @@ - - - - diff --git a/modules/scripts-utils/zedai-utils/pom.xml b/modules/scripts-utils/zedai-utils/pom.xml index ba49455596..5b79c19549 100644 --- a/modules/scripts-utils/zedai-utils/pom.xml +++ b/modules/scripts-utils/zedai-utils/pom.xml @@ -17,33 +17,25 @@ DAISY Pipeline 2 module :: ZedAI Utilities - org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules mathml-utils - runtime org.daisy.pipeline.modules mediatype-utils - runtime org.daisy.pipeline.modules validation-utils - runtime org.daisy.pipeline.modules css-utils - runtime diff --git a/modules/scripts-utils/zedai-utils/src/main/resources/META-INF/catalog.xml b/modules/scripts-utils/zedai-utils/src/main/resources/META-INF/catalog.xml index d278e05181..ae74b20022 100755 --- a/modules/scripts-utils/zedai-utils/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts-utils/zedai-utils/src/main/resources/META-INF/catalog.xml @@ -2,9 +2,4 @@ - - - - - diff --git a/modules/scripts/daisy202-to-daisy3/pom.xml b/modules/scripts/daisy202-to-daisy3/pom.xml index 7a782ef7af..4ba1a9447a 100644 --- a/modules/scripts/daisy202-to-daisy3/pom.xml +++ b/modules/scripts/daisy202-to-daisy3/pom.xml @@ -21,53 +21,41 @@ org.daisy.pipeline framework-core - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules html-utils - runtime org.daisy.pipeline.modules smil-utils - runtime org.daisy.pipeline.modules daisy202-utils - runtime org.daisy.pipeline.modules daisy3-utils - runtime org.daisy.pipeline.modules dtbook-utils - runtime org.daisy.pipeline.modules html-to-dtbook - runtime diff --git a/modules/scripts/daisy202-to-daisy3/src/main/resources/META-INF/catalog.xml b/modules/scripts/daisy202-to-daisy3/src/main/resources/META-INF/catalog.xml index bf291c2e5c..6d3c6fbd79 100644 --- a/modules/scripts/daisy202-to-daisy3/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts/daisy202-to-daisy3/src/main/resources/META-INF/catalog.xml @@ -1,13 +1,4 @@ - - - - - - - - - diff --git a/modules/scripts/daisy202-to-epub3/pom.xml b/modules/scripts/daisy202-to-epub3/pom.xml index 3e0b6736e9..1c5f2e4f5a 100644 --- a/modules/scripts/daisy202-to-epub3/pom.xml +++ b/modules/scripts/daisy202-to-epub3/pom.xml @@ -17,7 +17,6 @@ DAISY Pipeline 2 module :: DAISY 2.02 to EPUB 3 - org.daisy.pipeline framework-core @@ -28,42 +27,34 @@ org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules daisy202-utils - runtime org.daisy.pipeline.modules epub-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules html-utils - runtime org.daisy.pipeline.modules smil-utils - runtime org.daisy.pipeline.modules mediatype-utils - runtime diff --git a/modules/scripts/daisy202-to-epub3/src/main/resources/META-INF/catalog.xml b/modules/scripts/daisy202-to-epub3/src/main/resources/META-INF/catalog.xml index dabde81c16..906e6151a5 100755 --- a/modules/scripts/daisy202-to-epub3/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts/daisy202-to-epub3/src/main/resources/META-INF/catalog.xml @@ -2,13 +2,4 @@ - - - - - - - - - diff --git a/modules/scripts/daisy202-to-mp3/pom.xml b/modules/scripts/daisy202-to-mp3/pom.xml index 5c1c8e5de8..e535df9109 100644 --- a/modules/scripts/daisy202-to-mp3/pom.xml +++ b/modules/scripts/daisy202-to-mp3/pom.xml @@ -21,38 +21,29 @@ org.daisy.pipeline framework-core - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules audio-common - runtime org.daisy.pipeline.modules daisy202-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules smil-utils - runtime org.daisy.pipeline framework-core - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules daisy202-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules smil-utils - runtime org.daisy.pipeline.modules mediatype-utils - runtime org.daisy.pipeline.modules validation-utils - runtime diff --git a/modules/scripts/daisy202-validator/src/main/resources/META-INF/catalog.xml b/modules/scripts/daisy202-validator/src/main/resources/META-INF/catalog.xml index 9654bbb32d..224b0c77e9 100644 --- a/modules/scripts/daisy202-validator/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts/daisy202-validator/src/main/resources/META-INF/catalog.xml @@ -4,11 +4,4 @@ - - - - - - - diff --git a/modules/scripts/daisy3-to-daisy202/pom.xml b/modules/scripts/daisy3-to-daisy202/pom.xml index 7d26996ea0..4b1d448e86 100644 --- a/modules/scripts/daisy3-to-daisy202/pom.xml +++ b/modules/scripts/daisy3-to-daisy202/pom.xml @@ -17,48 +17,37 @@ DAISY Pipeline 2 module :: DAISY 3 to DAISY 2.02 - org.daisy.pipeline framework-core - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules daisy3-utils - runtime org.daisy.pipeline.modules daisy202-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules smil-utils - runtime org.daisy.pipeline.modules mediatype-utils - runtime diff --git a/modules/scripts/daisy3-to-daisy202/src/main/resources/META-INF/catalog.xml b/modules/scripts/daisy3-to-daisy202/src/main/resources/META-INF/catalog.xml index a8b0870e4a..3ada378f91 100755 --- a/modules/scripts/daisy3-to-daisy202/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts/daisy3-to-daisy202/src/main/resources/META-INF/catalog.xml @@ -6,11 +6,4 @@ px:extends="../../../../../common-options.xpl"/> - - - - - - - diff --git a/modules/scripts/daisy3-to-epub3/pom.xml b/modules/scripts/daisy3-to-epub3/pom.xml index 64afc47503..ded00cad05 100644 --- a/modules/scripts/daisy3-to-epub3/pom.xml +++ b/modules/scripts/daisy3-to-epub3/pom.xml @@ -17,53 +17,41 @@ DAISY Pipeline 2 module :: DAISY 3 to EPUB 3 - org.daisy.pipeline framework-core - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules html-utils - runtime org.daisy.pipeline.modules daisy3-utils - runtime org.daisy.pipeline.modules dtbook-to-html - runtime org.daisy.pipeline.modules epub-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules smil-utils - runtime diff --git a/modules/scripts/daisy3-to-epub3/src/main/resources/META-INF/catalog.xml b/modules/scripts/daisy3-to-epub3/src/main/resources/META-INF/catalog.xml index 786e2daf4a..bf9327b631 100755 --- a/modules/scripts/daisy3-to-epub3/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts/daisy3-to-epub3/src/main/resources/META-INF/catalog.xml @@ -4,12 +4,4 @@ uri="../xml/xproc/daisy3-to-epub3.xpl" px:extends="../../../../../common-options.xpl" px:content-type="script" px:id="daisy3-to-epub3"/> - - - - - - - - diff --git a/modules/scripts/daisy3-to-mp3/pom.xml b/modules/scripts/daisy3-to-mp3/pom.xml index ff51f46e66..d7ceb4b9d7 100644 --- a/modules/scripts/daisy3-to-mp3/pom.xml +++ b/modules/scripts/daisy3-to-mp3/pom.xml @@ -21,38 +21,29 @@ org.daisy.pipeline framework-core - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules audio-common - runtime org.daisy.pipeline.modules daisy3-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules smil-utils - runtime org.daisy.pipeline framework-core - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules css-utils - runtime org.daisy.pipeline.modules daisy3-utils - runtime org.daisy.pipeline.modules dtbook-tts - runtime org.daisy.pipeline.modules dtbook-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules smil-utils - runtime - diff --git a/modules/scripts/dtbook-to-epub3/pom.xml b/modules/scripts/dtbook-to-epub3/pom.xml index 3e05d36bfc..70024b153e 100644 --- a/modules/scripts/dtbook-to-epub3/pom.xml +++ b/modules/scripts/dtbook-to-epub3/pom.xml @@ -17,48 +17,37 @@ DAISY Pipeline 2 module :: DTBook to EPUB 3 - org.daisy.pipeline framework-core - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules css-utils - runtime org.daisy.pipeline.modules dtbook-to-zedai - runtime org.daisy.pipeline.modules dtbook-utils - runtime org.daisy.pipeline.modules epub-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules zedai-to-epub3 - runtime org.daisy.pipeline.modules @@ -73,11 +62,6 @@ nlp-omnilang-lexer test - - org.daisy.pipeline.modules - audio-common - test - org.daisy.pipeline.modules tts-mocks @@ -93,4 +77,4 @@ - \ No newline at end of file + diff --git a/modules/scripts/dtbook-to-epub3/src/main/resources/META-INF/catalog.xml b/modules/scripts/dtbook-to-epub3/src/main/resources/META-INF/catalog.xml index fabe1d798f..f6894ebc41 100755 --- a/modules/scripts/dtbook-to-epub3/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts/dtbook-to-epub3/src/main/resources/META-INF/catalog.xml @@ -3,11 +3,4 @@ - - - - - - - diff --git a/modules/scripts/dtbook-to-html/pom.xml b/modules/scripts/dtbook-to-html/pom.xml index 8e932dd963..b93682201f 100644 --- a/modules/scripts/dtbook-to-html/pom.xml +++ b/modules/scripts/dtbook-to-html/pom.xml @@ -17,43 +17,33 @@ DAISY Pipeline 2 module :: DTBook to HTML - org.daisy.pipeline framework-core - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules dtbook-to-zedai - runtime org.daisy.pipeline.modules dtbook-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules zedai-to-html - runtime diff --git a/modules/scripts/dtbook-to-html/src/main/resources/META-INF/catalog.xml b/modules/scripts/dtbook-to-html/src/main/resources/META-INF/catalog.xml index dd5707ae19..ddc95e9386 100755 --- a/modules/scripts/dtbook-to-html/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts/dtbook-to-html/src/main/resources/META-INF/catalog.xml @@ -4,10 +4,4 @@ uri="../xml/dtbook-to-html.xpl" px:extends="../../../../../common-options.xpl" px:content-type="script" px:id="dtbook-to-html"/> - - - - - - diff --git a/modules/scripts/dtbook-to-odt/pom.xml b/modules/scripts/dtbook-to-odt/pom.xml index 3de6e0305b..51f31c30fa 100644 --- a/modules/scripts/dtbook-to-odt/pom.xml +++ b/modules/scripts/dtbook-to-odt/pom.xml @@ -18,38 +18,29 @@ DAISY Pipeline 2 module :: DTBook to ODT - org.daisy.pipeline framework-core - org.daisy.pipeline.modules asciimath-utils - runtime org.daisy.pipeline.modules dtbook-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules odf-utils - runtime org.daisy.pipeline.modules image-utils - runtime diff --git a/modules/scripts/dtbook-to-odt/src/main/resources/META-INF/catalog.xml b/modules/scripts/dtbook-to-odt/src/main/resources/META-INF/catalog.xml index 8e7329d967..7cf75538d3 100644 --- a/modules/scripts/dtbook-to-odt/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts/dtbook-to-odt/src/main/resources/META-INF/catalog.xml @@ -4,9 +4,4 @@ - - - - - diff --git a/modules/scripts/dtbook-to-pef/pom.xml b/modules/scripts/dtbook-to-pef/pom.xml index 3feb527c32..a20429418b 100644 --- a/modules/scripts/dtbook-to-pef/pom.xml +++ b/modules/scripts/dtbook-to-pef/pom.xml @@ -18,7 +18,6 @@ DAISY Pipeline 2 module :: DTBook to PEF - org.daisy.pipeline framework-core @@ -27,48 +26,37 @@ org.daisy.pipeline.modules css-utils - org.daisy.pipeline.modules.braille braille-common - runtime org.daisy.pipeline.modules.braille pef-utils - runtime org.daisy.pipeline.modules dtbook-utils - runtime org.daisy.pipeline.modules metadata-utils - runtime org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules dtbook-to-epub3 - runtime - - - - - - - - - - diff --git a/modules/scripts/dtbook-to-pef/src/main/resources/xml/xproc/dtbook-to-pef.xpl b/modules/scripts/dtbook-to-pef/src/main/resources/xml/xproc/dtbook-to-pef.xpl index 4ee6439e2c..27c1c25f70 100644 --- a/modules/scripts/dtbook-to-pef/src/main/resources/xml/xproc/dtbook-to-pef.xpl +++ b/modules/scripts/dtbook-to-pef/src/main/resources/xml/xproc/dtbook-to-pef.xpl @@ -110,10 +110,7 @@ sheet modules) are available for use in Sass style sheets: - - + px:dtbook-to-pef px:dtbook-to-pef.store diff --git a/modules/scripts/dtbook-to-rtf/pom.xml b/modules/scripts/dtbook-to-rtf/pom.xml index 040d483a0a..6b5b299d56 100644 --- a/modules/scripts/dtbook-to-rtf/pom.xml +++ b/modules/scripts/dtbook-to-rtf/pom.xml @@ -17,33 +17,25 @@ DAISY Pipeline 2 module :: DTBook to RTF - org.daisy.pipeline framework-core - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules dtbook-utils - runtime diff --git a/modules/scripts/dtbook-to-rtf/src/main/resources/META-INF/catalog.xml b/modules/scripts/dtbook-to-rtf/src/main/resources/META-INF/catalog.xml index 649ca679d3..5830420faf 100644 --- a/modules/scripts/dtbook-to-rtf/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts/dtbook-to-rtf/src/main/resources/META-INF/catalog.xml @@ -3,8 +3,4 @@ - - - - diff --git a/modules/scripts/dtbook-to-zedai/pom.xml b/modules/scripts/dtbook-to-zedai/pom.xml index 9656e2c4eb..44c66b0848 100644 --- a/modules/scripts/dtbook-to-zedai/pom.xml +++ b/modules/scripts/dtbook-to-zedai/pom.xml @@ -17,43 +17,33 @@ DAISY Pipeline 2 module :: DTBook to ZedAI - org.daisy.pipeline framework-core - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules dtbook-utils - runtime org.daisy.pipeline.modules zedai-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules mediatype-utils - runtime diff --git a/modules/scripts/dtbook-to-zedai/src/main/resources/META-INF/catalog.xml b/modules/scripts/dtbook-to-zedai/src/main/resources/META-INF/catalog.xml index 04f2287065..3d56313f09 100755 --- a/modules/scripts/dtbook-to-zedai/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts/dtbook-to-zedai/src/main/resources/META-INF/catalog.xml @@ -3,10 +3,4 @@ - - - - - - diff --git a/modules/scripts/dtbook-validator/pom.xml b/modules/scripts/dtbook-validator/pom.xml index 2415ee4058..6f4c80e6db 100644 --- a/modules/scripts/dtbook-validator/pom.xml +++ b/modules/scripts/dtbook-validator/pom.xml @@ -17,28 +17,21 @@ DAISY Pipeline 2 module :: DTBook Validator - org.daisy.pipeline framework-core - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules dtbook-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime diff --git a/modules/scripts/dtbook-validator/src/main/resources/META-INF/catalog.xml b/modules/scripts/dtbook-validator/src/main/resources/META-INF/catalog.xml index 3d319acac0..e27d4a8e79 100755 --- a/modules/scripts/dtbook-validator/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts/dtbook-validator/src/main/resources/META-INF/catalog.xml @@ -2,7 +2,4 @@ - - - diff --git a/modules/scripts/epub-to-daisy/pom.xml b/modules/scripts/epub-to-daisy/pom.xml index 6b9b6576d2..6a99b10bab 100644 --- a/modules/scripts/epub-to-daisy/pom.xml +++ b/modules/scripts/epub-to-daisy/pom.xml @@ -22,43 +22,33 @@ org.daisy.pipeline framework-core - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules epub-utils - runtime org.daisy.pipeline.modules epub2-to-epub3 - runtime org.daisy.pipeline.modules epub3-to-epub3 - runtime org.daisy.pipeline.modules epub3-to-daisy202 - runtime org.daisy.pipeline.modules epub3-to-daisy3 - runtime org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules html-utils - runtime org.daisy.pipeline.modules epub-utils - runtime diff --git a/modules/scripts/epub2-to-epub3/src/main/resources/META-INF/catalog.xml b/modules/scripts/epub2-to-epub3/src/main/resources/META-INF/catalog.xml index 07a2849126..3825114377 100644 --- a/modules/scripts/epub2-to-epub3/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts/epub2-to-epub3/src/main/resources/META-INF/catalog.xml @@ -3,8 +3,4 @@ - - - - diff --git a/modules/scripts/epub3-to-daisy202/pom.xml b/modules/scripts/epub3-to-daisy202/pom.xml index b3e5d20b79..40a07502c2 100644 --- a/modules/scripts/epub3-to-daisy202/pom.xml +++ b/modules/scripts/epub3-to-daisy202/pom.xml @@ -17,53 +17,41 @@ DAISY Pipeline 2 module :: EPUB 3 to DAISY 2.02 - org.daisy.pipeline framework-core - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules html-utils - runtime org.daisy.pipeline.modules mediatype-utils - runtime org.daisy.pipeline.modules epub-utils - runtime org.daisy.pipeline.modules daisy202-utils - runtime org.daisy.pipeline.modules smil-utils - runtime org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules html-utils - runtime org.daisy.pipeline.modules epub-utils - runtime org.daisy.pipeline.modules dtbook-utils - runtime org.daisy.pipeline.modules daisy3-utils - runtime org.daisy.pipeline.modules smil-utils - runtime org.daisy.pipeline.modules @@ -66,7 +56,6 @@ org.daisy.pipeline.modules epub3-to-html - runtime diff --git a/modules/scripts/epub3-to-daisy3/src/main/resources/META-INF/catalog.xml b/modules/scripts/epub3-to-daisy3/src/main/resources/META-INF/catalog.xml index c80acf1000..51e7d604e4 100755 --- a/modules/scripts/epub3-to-daisy3/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts/epub3-to-daisy3/src/main/resources/META-INF/catalog.xml @@ -5,14 +5,4 @@ px:extends="../../../../../common-options.xpl"/> - - - - - - - - - - diff --git a/modules/scripts/epub3-to-epub3/pom.xml b/modules/scripts/epub3-to-epub3/pom.xml index ff11b75d0b..97ae16d14c 100644 --- a/modules/scripts/epub3-to-epub3/pom.xml +++ b/modules/scripts/epub3-to-epub3/pom.xml @@ -18,68 +18,53 @@ DAISY Pipeline 2 module :: EPUB 3 Enhancer - org.daisy.pipeline framework-core - org.daisy.pipeline.modules css-utils - runtime org.daisy.pipeline.modules.braille braille-css-utils - runtime org.daisy.pipeline.modules.braille braille-common - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules html-utils - runtime org.daisy.pipeline.modules epub-utils - runtime org.daisy.pipeline.modules epub3-tts - runtime org.daisy.pipeline.modules html-break-detection - runtime org.daisy.pipeline.modules tts-common - runtime org.daisy.pipeline framework-core - org.daisy.pipeline.modules.braille html-to-pef - runtime org.daisy.pipeline.modules.braille braille-common - runtime org.daisy.pipeline.modules css-utils - runtime org.daisy.pipeline.modules.braille pef-utils - runtime org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime - - - org.daisy.pipeline.modules - mediatype-utils - runtime org.daisy.pipeline.modules epub-utils - runtime - - - - - - - - - - diff --git a/modules/scripts/epub3-to-pef/src/main/resources/xml/xproc/epub3-to-pef.xpl b/modules/scripts/epub3-to-pef/src/main/resources/xml/xproc/epub3-to-pef.xpl index 3438b643c3..d151835a2a 100644 --- a/modules/scripts/epub3-to-pef/src/main/resources/xml/xproc/epub3-to-pef.xpl +++ b/modules/scripts/epub3-to-pef/src/main/resources/xml/xproc/epub3-to-pef.xpl @@ -107,10 +107,7 @@ even though the provided CSS is more specific. - - + px:epub3-to-pef.load px:epub3-to-pef diff --git a/modules/scripts/epub3-to-pef/src/test/java/XProcSpecTest.java b/modules/scripts/epub3-to-pef/src/test/java/XProcSpecTest.java index e5ec6e17cf..17c54e3110 100644 --- a/modules/scripts/epub3-to-pef/src/test/java/XProcSpecTest.java +++ b/modules/scripts/epub3-to-pef/src/test/java/XProcSpecTest.java @@ -16,7 +16,6 @@ protected String[] testDependencies() { pipelineModule("file-utils"), pipelineModule("fileset-utils"), pipelineModule("common-utils"), - pipelineModule("mediatype-utils"), pipelineModule("epub-utils"), }; } diff --git a/modules/scripts/epub3-validator/pom.xml b/modules/scripts/epub3-validator/pom.xml index 115ad046d9..96129763e2 100644 --- a/modules/scripts/epub3-validator/pom.xml +++ b/modules/scripts/epub3-validator/pom.xml @@ -17,18 +17,13 @@ DAISY Pipeline 2 module :: EPUB 3 Validator - org.daisy.pipeline framework-core - org.daisy.pipeline.modules epub-utils - runtime diff --git a/modules/scripts/epub3-validator/src/main/resources/META-INF/catalog.xml b/modules/scripts/epub3-validator/src/main/resources/META-INF/catalog.xml index 7d790f69d5..b1965fbcd2 100644 --- a/modules/scripts/epub3-validator/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts/epub3-validator/src/main/resources/META-INF/catalog.xml @@ -1,8 +1,4 @@ - - - - diff --git a/modules/scripts/html-to-dtbook/pom.xml b/modules/scripts/html-to-dtbook/pom.xml index 77915f1974..d8973975f7 100644 --- a/modules/scripts/html-to-dtbook/pom.xml +++ b/modules/scripts/html-to-dtbook/pom.xml @@ -17,33 +17,25 @@ DAISY Pipeline 2 module :: HTML to DTBook - org.daisy.pipeline framework-core - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules epub-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline framework-core - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules epub-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules html-utils - runtime org.daisy.pipeline.modules zedai-to-html - runtime org.daisy.pipeline.modules epub3-tts - runtime diff --git a/modules/scripts/html-to-epub3/src/main/resources/META-INF/catalog.xml b/modules/scripts/html-to-epub3/src/main/resources/META-INF/catalog.xml index 153ebf82c4..66afe8214a 100755 --- a/modules/scripts/html-to-epub3/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts/html-to-epub3/src/main/resources/META-INF/catalog.xml @@ -2,11 +2,4 @@ - - - - - - - diff --git a/modules/scripts/html-to-pef/pom.xml b/modules/scripts/html-to-pef/pom.xml index 8b9913cff2..c0f77b8f84 100644 --- a/modules/scripts/html-to-pef/pom.xml +++ b/modules/scripts/html-to-pef/pom.xml @@ -18,7 +18,6 @@ DAISY Pipeline 2 module :: HTML to PEF - org.daisy.pipeline framework-core @@ -27,38 +26,29 @@ org.daisy.pipeline.modules css-utils - org.daisy.pipeline.modules.braille braille-common - runtime org.daisy.pipeline.modules.braille pef-utils - runtime org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules html-utils - runtime org.daisy.pipeline.modules html-to-epub3 - runtime - - - - - - - - diff --git a/modules/scripts/html-to-pef/src/main/resources/xml/xproc/html-to-pef.xpl b/modules/scripts/html-to-pef/src/main/resources/xml/xproc/html-to-pef.xpl index eaaef6ae25..39921e03f9 100644 --- a/modules/scripts/html-to-pef/src/main/resources/xml/xproc/html-to-pef.xpl +++ b/modules/scripts/html-to-pef/src/main/resources/xml/xproc/html-to-pef.xpl @@ -118,10 +118,7 @@ sheet modules) are available for use in Sass style sheets: px:delete-parameters - - + px:html-to-pef px:html-to-pef.store diff --git a/modules/scripts/nimas-fileset-validator/pom.xml b/modules/scripts/nimas-fileset-validator/pom.xml index 6dcfc03766..e0c4d58cea 100644 --- a/modules/scripts/nimas-fileset-validator/pom.xml +++ b/modules/scripts/nimas-fileset-validator/pom.xml @@ -17,33 +17,25 @@ DAISY Pipeline 2 module :: NIMAS Fileset Validator - org.daisy.pipeline framework-core - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules dtbook-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules validation-utils - runtime - - - - - + + - - - - - - - - - - - - - - - + + + + + + + + + diff --git a/modules/scripts/zedai-to-epub3/pom.xml b/modules/scripts/zedai-to-epub3/pom.xml index b73027f839..5ff7d83462 100644 --- a/modules/scripts/zedai-to-epub3/pom.xml +++ b/modules/scripts/zedai-to-epub3/pom.xml @@ -17,53 +17,41 @@ DAISY Pipeline 2 module :: ZedAI to EPUB 3 - org.daisy.pipeline framework-core - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules css-utils - runtime org.daisy.pipeline.modules epub-utils - runtime org.daisy.pipeline.modules html-to-epub3 - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules zedai-to-html - runtime org.daisy.pipeline.modules zedai-utils - runtime @@ -74,4 +62,4 @@ - \ No newline at end of file + diff --git a/modules/scripts/zedai-to-epub3/src/main/resources/META-INF/catalog.xml b/modules/scripts/zedai-to-epub3/src/main/resources/META-INF/catalog.xml index 100a9baf04..7ab2eaf6d3 100755 --- a/modules/scripts/zedai-to-epub3/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts/zedai-to-epub3/src/main/resources/META-INF/catalog.xml @@ -3,12 +3,4 @@ - - - - - - - - diff --git a/modules/scripts/zedai-to-html/pom.xml b/modules/scripts/zedai-to-html/pom.xml index 1f4b6b9e50..c2861774fc 100644 --- a/modules/scripts/zedai-to-html/pom.xml +++ b/modules/scripts/zedai-to-html/pom.xml @@ -17,38 +17,29 @@ DAISY Pipeline 2 module :: ZedAI to HTML - org.daisy.pipeline framework-core - org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules html-utils - runtime org.daisy.pipeline.modules zedai-utils - runtime diff --git a/modules/scripts/zedai-to-html/src/main/resources/META-INF/catalog.xml b/modules/scripts/zedai-to-html/src/main/resources/META-INF/catalog.xml index ddd7524c70..8bd8758500 100755 --- a/modules/scripts/zedai-to-html/src/main/resources/META-INF/catalog.xml +++ b/modules/scripts/zedai-to-html/src/main/resources/META-INF/catalog.xml @@ -4,10 +4,4 @@ uri="../xml/xproc/zedai-to-html.xpl" px:content-type="script" px:id="zedai-to-html"/> - - - - - - diff --git a/modules/scripts/zedai-to-pef/pom.xml b/modules/scripts/zedai-to-pef/pom.xml index 84910da3a7..320e1ab270 100644 --- a/modules/scripts/zedai-to-pef/pom.xml +++ b/modules/scripts/zedai-to-pef/pom.xml @@ -18,58 +18,45 @@ DAISY Pipeline 2 module :: ZedAI to PEF - org.daisy.pipeline framework-core - org.daisy.pipeline.modules.braille braille-common - runtime org.daisy.pipeline.modules css-utils - runtime org.daisy.pipeline.modules.braille braille-css-utils - runtime org.daisy.pipeline.modules.braille pef-utils - runtime org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules file-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules zedai-utils - runtime org.daisy.pipeline.modules zedai-to-epub3 - runtime org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules dtbook-break-detection - runtime org.daisy.pipeline.modules tts-common - runtime org.daisy.pipeline.modules mathml-to-ssml - runtime org.daisy.pipeline.modules common-utils - runtime org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules html-break-detection - runtime org.daisy.pipeline.modules tts-common - runtime org.daisy.pipeline.modules mathml-to-ssml - runtime @@ -59,4 +51,4 @@ - \ No newline at end of file + diff --git a/modules/tts/epub3-tts/src/main/resources/META-INF/catalog.xml b/modules/tts/epub3-tts/src/main/resources/META-INF/catalog.xml index bbcb32048b..9027fa8963 100644 --- a/modules/tts/epub3-tts/src/main/resources/META-INF/catalog.xml +++ b/modules/tts/epub3-tts/src/main/resources/META-INF/catalog.xml @@ -6,11 +6,4 @@ px:stylesheet-for-content-type="application/xhtml+xml" px:stylesheet-for-media="speech"/> - - - - - - - diff --git a/modules/tts/mathml-to-ssml/pom.xml b/modules/tts/mathml-to-ssml/pom.xml index 635b026a99..d0083c3ebf 100644 --- a/modules/tts/mathml-to-ssml/pom.xml +++ b/modules/tts/mathml-to-ssml/pom.xml @@ -27,7 +27,6 @@ org.daisy.pipeline.modules common-utils - runtime diff --git a/modules/tts/mathml-to-ssml/src/main/resources/META-INF/catalog.xml b/modules/tts/mathml-to-ssml/src/main/resources/META-INF/catalog.xml index f9c9c47bb1..fa5944040b 100644 --- a/modules/tts/mathml-to-ssml/src/main/resources/META-INF/catalog.xml +++ b/modules/tts/mathml-to-ssml/src/main/resources/META-INF/catalog.xml @@ -1,8 +1,4 @@ - - - - diff --git a/modules/tts/tts-adapters/tts-adapter-sapinative/src/test/java/org/daisy/pipeline/tts/sapi/impl/SapiSSMLTest.java b/modules/tts/tts-adapters/tts-adapter-sapinative/src/test/java/org/daisy/pipeline/tts/sapi/impl/SapiSSMLTest.java index 56aa4a1ad9..12e5399bd0 100644 --- a/modules/tts/tts-adapters/tts-adapter-sapinative/src/test/java/org/daisy/pipeline/tts/sapi/impl/SapiSSMLTest.java +++ b/modules/tts/tts-adapters/tts-adapter-sapinative/src/test/java/org/daisy/pipeline/tts/sapi/impl/SapiSSMLTest.java @@ -84,9 +84,9 @@ public void exampleSSML() throws SaxonApiException, IOException, SAXException { XdmNode toTest = Proc.newDocumentBuilder().build(new StreamSource(new StringReader( "" + "" + - "this" + - "is" + - "a" + + "this " + + "is " + + "a " + "sentence" + "" + "" diff --git a/modules/tts/tts-common/pom.xml b/modules/tts/tts-common/pom.xml index 67e782e176..a6e44971e4 100644 --- a/modules/tts/tts-common/pom.xml +++ b/modules/tts/tts-common/pom.xml @@ -54,9 +54,6 @@ org.daisy.pipeline.modules css-utils - org.daisy.pipeline.modules common-utils @@ -64,12 +61,10 @@ org.daisy.pipeline.modules fileset-utils - runtime org.daisy.pipeline.modules file-utils - runtime + 1.14.7 @@ -55,7 +55,7 @@ org.daisy.maven xproc-engine-calabash - 1.1.4 + 1.2.0-SNAPSHOT org.slf4j @@ -65,6 +65,10 @@ org.daisy.pipeline modules-registry + + org.daisy.pipeline + saxon-adapter + org.codehaus.woodstox woodstox-core-lgpl @@ -119,11 +123,6 @@ pom test - - org.daisy.pipeline - saxon-adapter - test - org.daisy.pipeline calabash-adapter @@ -133,7 +132,7 @@ org.daisy.pipeline framework-core test - + org.daisy.pipeline.build ds-to-spi-maven-plugin @@ -155,13 +154,13 @@ xprocspec ${xprocspec.version} test - + org.daisy.maven xproc-engine-daisy-pipeline @@ -172,6 +171,14 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + maven-plugin-plugin 3.5 @@ -188,7 +195,7 @@ @{project.artifactId}-@{project.version} - + + org.daisy.pipeline:framework-bom:${framework.version}:pom org.daisy.xprocspec:xprocspec:${xprocspec.version} org.daisy.pipeline:ds-to-spi-runtime:${ds-to-spi-runtime.version} @@ -233,7 +240,7 @@ - + --> diff --git a/utils/build-utils/modules-build-helper/src/it/it-pipeline-module/pom.xml b/utils/build-utils/modules-build-helper/src/it/it-pipeline-module/pom.xml index 70e4148bfb..cacccd316d 100644 --- a/utils/build-utils/modules-build-helper/src/it/it-pipeline-module/pom.xml +++ b/utils/build-utils/modules-build-helper/src/it/it-pipeline-module/pom.xml @@ -12,7 +12,7 @@ org.daisy.pipeline.modules modules-parent - 1.14.20 + 1.14.26 org.daisy.pipeline.build.modules-build-helper @@ -28,6 +28,11 @@ pom import + + org.daisy.pipeline + logging-activator + 2.1.0 + org.daisy.pipeline.modules modules-bom @@ -35,12 +40,6 @@ pom import - - - net.java.dev.jna - jna - 5.14.0 - @@ -59,15 +58,21 @@ org.daisy.pipeline framework-core + org.daisy.pipeline ds-to-spi-runtime @@ -80,11 +85,6 @@ 4.3.1 provided - - org.daisy.pipeline - logging-appender - test - junit junit @@ -276,7 +276,8 @@ maven-surefire-plugin - PaxExam,OSGiLessRunner + + OSGiLessRunner diff --git a/utils/build-utils/modules-build-helper/src/it/it-pipeline-module/src/it/xprocspec/test.xprocspec b/utils/build-utils/modules-build-helper/src/it/it-pipeline-module/src/it/xprocspec/test.xprocspec index 936da446d7..80f1ebb972 100644 --- a/utils/build-utils/modules-build-helper/src/it/it-pipeline-module/src/it/xprocspec/test.xprocspec +++ b/utils/build-utils/modules-build-helper/src/it/it-pipeline-module/src/it/xprocspec/test.xprocspec @@ -27,23 +27,21 @@ - - + - - + @@ -67,17 +65,16 @@ + - - - - + + @@ -260,7 +257,7 @@
- + @@ -294,10 +291,6 @@ - - - - @@ -312,10 +305,10 @@ - - - - + + + + @@ -455,10 +448,10 @@ - - - - + + + + diff --git a/utils/build-utils/modules-build-helper/src/it/it-pipeline-module/src/main/resources/META-INF/catalog.xml b/utils/build-utils/modules-build-helper/src/it/it-pipeline-module/src/main/resources/META-INF/catalog.xml index 97f3c69224..00bf238c83 100644 --- a/utils/build-utils/modules-build-helper/src/it/it-pipeline-module/src/main/resources/META-INF/catalog.xml +++ b/utils/build-utils/modules-build-helper/src/it/it-pipeline-module/src/main/resources/META-INF/catalog.xml @@ -12,7 +12,7 @@ ../css/foo-2.xpl"/> - + + diff --git a/utils/build-utils/modules-build-helper/src/it/it-pipeline-module/src/main/resources/liblouis/foo.utb b/utils/build-utils/modules-build-helper/src/it/it-pipeline-module/src/main/resources/liblouis/foo.utb new file mode 100644 index 0000000000..2888443896 --- /dev/null +++ b/utils/build-utils/modules-build-helper/src/it/it-pipeline-module/src/main/resources/liblouis/foo.utb @@ -0,0 +1,2 @@ +letter f 124 +letter o 135 diff --git a/utils/build-utils/modules-build-helper/src/it/it-pipeline-module/src/test/java/ServicesTest.java b/utils/build-utils/modules-build-helper/src/it/it-pipeline-module/src/test/java/ServicesTest.java index cdafe6b848..bec9b5271c 100644 --- a/utils/build-utils/modules-build-helper/src/it/it-pipeline-module/src/test/java/ServicesTest.java +++ b/utils/build-utils/modules-build-helper/src/it/it-pipeline-module/src/test/java/ServicesTest.java @@ -7,17 +7,11 @@ import org.daisy.pipeline.script.XProcScriptService; import org.daisy.pipeline.junit.AbstractXSpecAndXProcSpecTest; -import static org.daisy.pipeline.pax.exam.Options.mavenBundle; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import org.junit.Test; -import org.ops4j.pax.exam.Configuration; -import static org.ops4j.pax.exam.CoreOptions.composite; -import static org.ops4j.pax.exam.CoreOptions.options; -import org.ops4j.pax.exam.Option; - public class ServicesTest extends AbstractXSpecAndXProcSpecTest { @Inject @@ -39,8 +33,11 @@ public void testDatatype() { assertTrue(ids.remove("foo:choice")); assertTrue(ids.remove("px:bar-2.params-option-2")); assertTrue(ids.remove("px:script-option-1")); - assertTrue(ids.remove("transform-query")); // because o.d.p.modules.braille:common-utils on class path - assertTrue(ids.isEmpty()); + // assertTrue(ids.remove("transform-query")); // because braille-common on class path + // assertTrue(ids.remove("stylesheet-parameters")); // because css-utils on class path + // assertTrue(ids.remove("preview-table")); // because pef-utils on class path + //assertTrue(ids.remove("liblouis-table-query")); // because liblouis-utils on class path + assertTrue("ids not empty: " + ids, ids.isEmpty()); } @Inject @@ -60,15 +57,7 @@ protected String[] testDependencies() { return new String[]{ "org.daisy.pipeline:framework-core:?", "org.daisy.pipeline:calabash-adapter:?", - "org.daisy.pipeline.modules.braille:liblouis-utils:?" + // "org.daisy.pipeline.modules.braille:liblouis-utils:?" }; } - - @Override @Configuration - public Option[] config() { - return options( - // FIXME: BrailleUtils (dependency of liblouis-utils) needs older version of jing - mavenBundle("org.daisy.libs:jing:20120724.0.0"), - composite(super.config())); - } } diff --git a/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/CalabashWithPipelineModules.java b/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/CalabashWithPipelineModules.java index 2c028bea4f..76aa86b5b4 100644 --- a/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/CalabashWithPipelineModules.java +++ b/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/CalabashWithPipelineModules.java @@ -1,11 +1,6 @@ package org.daisy.pipeline.maven.plugin; -import java.io.File; -import java.net.MalformedURLException; import java.net.URI; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.Collection; import javax.xml.transform.Source; import javax.xml.transform.TransformerException; @@ -16,7 +11,7 @@ import org.daisy.maven.xproc.calabash.Calabash; -import org.daisy.pipeline.modules.impl.resolver.ModuleUriResolver; +import org.daisy.pipeline.modules.impl.ModuleUriResolver; import static org.daisy.pipeline.maven.plugin.utils.URLs.asURI; @@ -24,7 +19,7 @@ class CalabashWithPipelineModules extends Calabash { - CalabashWithPipelineModules(Collection classPath) { + CalabashWithPipelineModules(ClassLoader classPath) { super(); final URIResolver resolver = getModuleUriResolver(classPath); setURIResolver(new URIResolver() { @@ -43,24 +38,15 @@ public Source resolve(String href, String base) throws TransformerException { }); } - static URIResolver getModuleUriResolver(Collection classPath) { + // also used in HtmlizeSourcesMojo + static URIResolver getModuleUriResolver(ClassLoader classLoader) { ClassLoader restoreClassLoader = Thread.currentThread().getContextClassLoader(); try { - URLClassLoader classLoader; { - URL[] classPathURLs = new URL[classPath.size()]; { - int i = 0; - for (String path : classPath) - classPathURLs[i++] = new File(path).toURI().toURL(); - } - classLoader = new URLClassLoader(classPathURLs, Thread.currentThread().getContextClassLoader()); - } Thread.currentThread().setContextClassLoader(classLoader); for (URIResolver r : ServiceLoader.load(URIResolver.class)) if (r instanceof ModuleUriResolver) return r; throw new RuntimeException("No ModuleUriResolver found"); - } catch (MalformedURLException e) { - throw new RuntimeException("No ModuleUriResolver found", e); } finally { Thread.currentThread().setContextClassLoader(restoreClassLoader); } diff --git a/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/GenerateModuleClassFunctionProvider.java b/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/GenerateModuleClassFunctionProvider.java new file mode 100644 index 0000000000..c49c2a0110 --- /dev/null +++ b/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/GenerateModuleClassFunctionProvider.java @@ -0,0 +1,685 @@ +package org.daisy.pipeline.maven.plugin; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.nio.file.NoSuchFileException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLInputFactory; + +import org.apache.maven.plugin.logging.Log; + +import org.daisy.common.spi.ServiceLoader; +import org.daisy.common.xpath.saxon.ReflexiveExtensionFunctionProvider; +import org.daisy.pipeline.modules.Component; +import org.daisy.pipeline.modules.Dependency; +import org.daisy.pipeline.modules.JavaDependency; +import org.daisy.pipeline.modules.Module; +import org.daisy.pipeline.modules.ModuleRegistry; +import org.daisy.pipeline.modules.RelaxNGResource; +import org.daisy.pipeline.modules.ResourceLoader; +import org.daisy.pipeline.modules.UseXSLTPackage; +import org.daisy.pipeline.modules.XProcResource; +import org.daisy.pipeline.modules.XSLTPackage; +import org.daisy.pipeline.modules.XSLTResource; + +import org.osgi.framework.Version; + +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +public class GenerateModuleClassFunctionProvider extends ReflexiveExtensionFunctionProvider { + + private final XMLInputFactory xmlParser; + private final Set sourceRoots; + private final ClassLoader compileClassPath; + private final ModuleRegistry moduleRegistry; + private final Log logger; + + public GenerateModuleClassFunctionProvider(ClassLoader compileClassPath, Collection sourceRoots, Log logger) { + super(GenerateModuleClass.class); + xmlParser = XMLInputFactory.newInstance(); + this.sourceRoots = new HashSet<>(); { + for (String dir : sourceRoots) { + File f = new File(dir); + if (f.exists()) + this.sourceRoots.add(f); }} + this.compileClassPath = compileClassPath; + moduleRegistry = getModuleRegistry(compileClassPath); + this.logger = logger; + } + + public class GenerateModuleClass { + + public String apply(String className, + String moduleName, + String moduleVersion, + String moduleTitle, + Element catalog) { + Set catalogEntries = parseCatalog(catalog); + URI catalogBaseURI = URI.create(catalog.getBaseURI()); + // fake module to pass to {XProc,XSLT,RelaxNG}Resource + Module moduleUnderCompilation = new Module(moduleName, moduleVersion, moduleTitle, new ResourceLoader() { + @Override + public URL loadResource(String path) throws NoSuchFileException { + URI uri = catalogBaseURI.resolve(path); + try { + // catalogBaseURI expected to be a file URI + if (!new File(uri).exists()) + throw new NoSuchFileException("file does not exist: " + path); + } catch (IllegalArgumentException e) { + throw new NoSuchFileException("file does not exist: " + path); + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new IllegalStateException("coding error"); + } + } + @Override + public Iterable loadResources(String path) { + throw new UnsupportedOperationException(); + } + }) { + @Override + public void resolveDependencies() { + // no need to parse catalog.xml for this fake module + } + }; + // whether catalog.xml has entries with name + boolean hasCatalog = false; + // catalog entries with content-type="xslt-package" + Map xsltPackages = new HashMap<>(); + // dependency checks to be performed for a catalog entry when it has a name + Map componentDependencyChecks = new HashMap<>(); + // dependency checks to be performed for a catalog entry when it has content-type="xslt-package" + Map xsltPackageDependencyChecks = new HashMap<>(); + // dependency checks to be performed for a catalog entry when it is an internal resource + Map resourceDependencyChecks = new HashMap<>(); + // whether some components (of type XSLT, XProc or RelaxNG) depend on an external component (xsl:import, xsl:include, p:import, cx:import, grammar:include) + boolean componentChecks = false; + // whether some components (of type XSLT) depend on a (internal) Java class + boolean extensionFunctionChecks = false; + // whether some components (of type XSLT) depend on an XSLT package (xsl:use-package) + boolean xsltPackageChecks = false; + Set imports = new HashSet<>(); + for (CatalogEntry entry : catalogEntries) { + if (entry.name != null) + hasCatalog = true; + if (entry.contentType == ContentType.XSLT_PACKAGE) { + try { + XSLTPackage p = new XSLTPackage(moduleUnderCompilation, entry.uri.toString(), xmlParser); + xsltPackages.put(entry.uri, p); + } catch (NoSuchFileException e) { + throw new RuntimeException("Could not process catalog entry: resource not found: " + entry.uri.toString(), e); + } catch (IllegalArgumentException e) { + throw new RuntimeException("Could not process catalog entry: invalid XSLT package: " + entry.uri.toString(), e); + } + } + Set dependencies; { + try { + if (entry.uri.toString().endsWith(".xpl")) + dependencies = new XProcResource(moduleUnderCompilation, entry.uri.toString()) + .listDependencies(moduleRegistry, sourceRoots, compileClassPath, xmlParser); + else if (entry.uri.toString().endsWith(".xsl")) + dependencies = new XSLTResource(moduleUnderCompilation, entry.uri.toString()) + .listDependencies(moduleRegistry, sourceRoots, compileClassPath, xmlParser); + else if (entry.uri.toString().endsWith(".rng")) + dependencies = new RelaxNGResource(moduleUnderCompilation, entry.uri.toString()) + .listDependencies(moduleRegistry, xmlParser); + else + dependencies = null; + } catch (NoSuchFileException e) { + throw new RuntimeException("Could not process catalog entry: resource not found: " + entry.uri.toString(), e); + } + } + if (dependencies != null && !dependencies.isEmpty()) { + StringBuilder s = new StringBuilder(); + for (Dependency dependency : dependencies) { + if (s.length() > 0) + s.append("\n"); + if (dependency instanceof Component) { + componentChecks = true; + Component component = (Component)dependency; + Version version = Version.parseVersion(component.getVersion()); + String versionRange = "[" + version + "," + new Version(version.getMajor() + 1, 0, 0) + ")"; + s.append("tryResolveComponent(\"" + component.getURI() + "\",\n"); + s.append(" \"" + versionRange + "\");"); + } else if (dependency instanceof UseXSLTPackage) { + xsltPackageChecks = true; + UseXSLTPackage usePackage = (UseXSLTPackage)dependency; + s.append("tryResolveXSLTPackage(\"" + usePackage.getName() + "\",\n"); + s.append(" \"" + usePackage.getVersion() + "\");"); + } else if (dependency instanceof JavaDependency) { + extensionFunctionChecks = true; + s.append("tryResolveXPathExtensionFunction(\"" + ((JavaDependency)dependency).getClassName() + "\");"); + } else + throw new IllegalStateException("Unknown dependency: " + dependency); + } + if (entry.name != null) + componentDependencyChecks.put(entry.name, s.toString()); + else if (entry.contentType == ContentType.XSLT_PACKAGE) + xsltPackageDependencyChecks.put(entry.uri.toString(), s.toString()); + else + resourceDependencyChecks.put(entry.uri.toString(), s.toString()); + } + } + if (!hasCatalog) + hasCatalog = catalog.getElementsByTagNameNS(CAT_PUBLIC.getNamespaceURI(), CAT_PUBLIC.getLocalPart()).getLength() > 0 || + catalog.getElementsByTagNameNS(CAT_SYSTEM.getNamespaceURI(), CAT_SYSTEM.getLocalPart()).getLength() > 0 || + catalog.getElementsByTagNameNS(CAT_REWRITE_URI.getNamespaceURI(), CAT_REWRITE_URI.getLocalPart()).getLength() > 0; + StringBuilder result = new StringBuilder(); + imports.add("org.daisy.pipeline.modules.Module"); + imports.add("org.daisy.pipeline.xmlcatalog.XmlCatalogParser"); + imports.add("org.osgi.service.component.annotations.Reference"); + imports.add("org.osgi.service.component.annotations.ReferenceCardinality"); + imports.add("org.osgi.service.component.annotations.ReferencePolicy"); + imports.add("org.slf4j.Logger"); + imports.add("org.slf4j.LoggerFactory"); + result.append("@"); + if (!componentDependencyChecks.isEmpty()) + result.append("org.osgi.service.component.annotations."); + else + imports.add("org.osgi.service.component.annotations.Component"); + result.append("Component(\n"); + result.append(" name = \"org.daisy.pipeline.modules.impl." + className + "\",\n"); + result.append(" service = { Module.class }\n"); + result.append(")\n"); + result.append("public class " + className + " extends Module {\n"); + result.append("\n"); + result.append(" private static final Logger logger = LoggerFactory.getLogger(" + className + ".class);\n"); + result.append("\n"); + if (hasCatalog) + result.append(" private boolean initialized = false;\n"); + result.append(" private XmlCatalogParser catalogParser;\n"); + if (!xsltPackageDependencyChecks.isEmpty()) { + imports.add("javax.xml.stream.XMLInputFactory"); + result.append(" private XMLInputFactory xmlParser;\n"); + } + if (xsltPackageChecks) { + imports.add("java.util.List"); + imports.add("net.sf.saxon.trans.packages.PackageDetails"); + result.append(" private List packageDetails;\n"); + } + if (componentChecks) { + imports.add("org.daisy.pipeline.modules.ModuleRegistry"); + result.append(" private ModuleRegistry moduleRegistry;\n"); + } + if (!componentDependencyChecks.isEmpty()) { + imports.add("java.util.LinkedList"); + imports.add("org.daisy.pipeline.modules.Component"); + result.append(" private final LinkedList componentsToAdd;\n"); + result.append(" private final LinkedList componentsBeingAdded;\n"); + } + if (!xsltPackageDependencyChecks.isEmpty()) { + imports.add("java.util.LinkedList"); + result.append(" private final LinkedList xsltPackagesToAdd;\n"); + result.append(" private final LinkedList xsltPackagesBeingAdded;\n"); + } + result.append("\n"); + result.append(" public " + className + "() {\n"); + result.append(" super(\"" + moduleName + "\",\n"); + result.append(" \"" + moduleVersion + "\",\n"); + result.append(" \"" + moduleTitle.replace("\"", "\\\"") + "\");\n"); + if (!componentDependencyChecks.isEmpty()) { + imports.add("java.util.LinkedList"); + result.append(" componentsToAdd = new LinkedList<>();\n"); + result.append(" componentsBeingAdded = new LinkedList<>();\n"); + } + if (!xsltPackageDependencyChecks.isEmpty()) { + imports.add("java.util.LinkedList"); + result.append(" xsltPackagesToAdd = new LinkedList<>();\n"); + result.append(" xsltPackagesBeingAdded = new LinkedList<>();\n"); + } + result.append(" }\n"); + result.append("\n"); + result.append(" @Reference(\n"); + result.append(" name = \"XmlCatalogParser\",\n"); + result.append(" unbind = \"-\",\n"); + result.append(" service = XmlCatalogParser.class,\n"); + result.append(" cardinality = ReferenceCardinality.MANDATORY,\n"); + result.append(" policy = ReferencePolicy.STATIC\n"); + result.append(" )\n"); + result.append(" public void setParser(XmlCatalogParser parser) {\n"); + result.append(" catalogParser = parser;\n"); + result.append(" }\n"); + result.append("\n"); + if (!xsltPackageDependencyChecks.isEmpty()) { + imports.add("javax.xml.stream.XMLInputFactory"); + result.append(" @Reference(\n"); + result.append(" name = \"XMLInputFactory\",\n"); + result.append(" unbind = \"-\",\n"); + result.append(" service = XMLInputFactory.class,\n"); + result.append(" cardinality = ReferenceCardinality.MANDATORY,\n"); + result.append(" policy = ReferencePolicy.STATIC\n"); + result.append(" )\n"); + result.append(" protected void setXMLInputFactory(XMLInputFactory parser) {\n"); + result.append(" xmlParser = parser;\n"); + result.append(" }\n"); + result.append("\n"); + } + if (componentChecks) { + imports.add("java.net.URI"); + imports.add("org.daisy.pipeline.modules.ModuleRegistry"); + imports.add("org.daisy.pipeline.modules.ResolutionException"); + result.append(" @Reference(\n"); + result.append(" name = \"ModuleRegistry\",\n"); + result.append(" unbind = \"-\",\n"); + result.append(" service = ModuleRegistry.class,\n"); + result.append(" cardinality = ReferenceCardinality.MANDATORY,\n"); + result.append(" policy = ReferencePolicy.STATIC\n"); + result.append(" )\n"); + result.append(" public void setModuleRegistry(ModuleRegistry registry) {\n"); + result.append(" moduleRegistry = registry;\n"); + result.append(" }\n"); + result.append("\n"); + result.append(" private void tryResolveComponent(String uri, String versionRange) throws ResolutionException {\n"); + result.append(" if (moduleRegistry.getModuleByComponent(URI.create(uri), versionRange) == null)\n"); + result.append(" throw new ResolutionException(\"Unresolved dependency \" + uri + \" with version=\" + versionRange);\n"); + result.append(" }\n"); + result.append("\n"); + } + if (!componentDependencyChecks.isEmpty() || !xsltPackageDependencyChecks.isEmpty()) { + imports.add("org.osgi.service.component.annotations.Activate"); + result.append(" @Activate\n"); + result.append(" protected void init() {\n"); + result.append(" logger.debug(\"Initializing module \" + getName());\n"); + if (!componentDependencyChecks.isEmpty()) { + imports.add("java.net.URI"); + imports.add("org.slf4j.helpers.NOPLogger"); + result.append(" Module tmpModule = new Module(getName(), getVersion(), getTitle()) {\n"); + result.append(" @Override public void resolveDependencies() {}\n"); + result.append(" @Override public Logger getLogger() { return NOPLogger.NOP_LOGGER; }\n"); + result.append(" };\n"); + result.append(" Module.parseCatalog(tmpModule, catalogParser);\n"); + result.append(" for (String e : tmpModule.getEntities()) {\n"); + result.append(" addEntity(tmpModule.getEntity(e));\n"); + result.append(" logger.debug(\"Loaded entity: \" + e);\n"); + result.append(" }\n"); + result.append(" for (URI c : tmpModule.getComponents()) {\n"); + result.append(" components.put(c, () -> null); // will be replaced during resolveDependencies()\n"); + result.append(" componentsToAdd.add(tmpModule.getComponent(c));\n"); + result.append(" }\n"); + } else + result.append(" Module.parseCatalog(this, catalogParser);\n"); + for (URI path : xsltPackages.keySet()) { + XSLTPackage p = xsltPackages.get(path); + if (xsltPackageDependencyChecks.containsKey(path.toString())) { + result.append(" xsltPackagesToAdd.add(\"" + path + "\");\n"); + } else { + imports.add("java.nio.file.NoSuchFileException"); + result.append(" try {\n"); + result.append(" addXSLTPackage(\"" + p.getName() + "\",\n"); + result.append(" \"" + p.getVersion() + "\",\n"); + result.append(" \"" + path + "\");\n"); + result.append(" } catch (NoSuchFileException e) {\n"); + result.append(" logger.warn(\"XSLT package " + p.getName() + " can not be loaded: \" + e.getMessage());\n"); + result.append(" }\n"); + } + } + result.append(" logger.debug(\"Module \" + getName() + \" initialized, but dependencies have not been resolved yet\");\n"); + result.append(" }\n"); + result.append("\n"); + result.append(" // Not calling resolveDependencies() during object construction yet because it depends\n"); + result.append(" // on other modules, and other modules may depend on this module. resolveDependencies()\n"); + result.append(" // will be called when the object gets bound in ModuleRegistry. The method may recursively\n"); + result.append(" // call itself. It copes with that by activating components in a different order\n"); + result.append(" // when called recursively. ModuleRegistry has protection against endless recursion.\n"); + result.append(" @Override\n"); + result.append(" public void resolveDependencies() {\n"); + result.append(" if (!initialized) {\n"); + if (!componentDependencyChecks.isEmpty() && !xsltPackageDependencyChecks.isEmpty()) + result.append(" boolean recursive = !componentsBeingAdded.isEmpty() || !xsltPackagesBeingAdded.isEmpty();\n"); + else if (!componentDependencyChecks.isEmpty()) + result.append(" boolean recursive = !componentsBeingAdded.isEmpty();\n"); + else + result.append(" boolean recursive = !xsltPackagesBeingAdded.isEmpty();\n"); + result.append(" if (recursive)\n"); + result.append(" logger.debug(\"Resolving dependencies of module \" + getName() + \" (continuing in recursive call)\");\n"); + result.append(" else\n"); + result.append(" logger.debug(\"Resolving dependencies of module \" + getName());\n"); + if (!componentDependencyChecks.isEmpty()) { + imports.add("org.daisy.pipeline.modules.Component"); + result.append(" while (true) {\n"); + result.append(" if (componentsToAdd.isEmpty()) {\n"); + result.append(" if (componentsBeingAdded.isEmpty())\n"); + result.append(" break;\n"); + result.append(" componentsToAdd.addAll(componentsBeingAdded);\n"); + result.append(" componentsBeingAdded.clear();\n"); + result.append(" }\n"); + result.append(" Component c = componentsToAdd.poll();\n"); + result.append(" componentsBeingAdded.add(c);\n"); + result.append(" if (addComponent(c)) // may call resolveDependencies() recursively\n"); + result.append(" logger.debug(\"Loaded component: \" + c.getURI());\n"); + result.append(" componentsBeingAdded.remove(c);\n"); + result.append(" componentsToAdd.remove(c);\n"); + result.append(" }\n"); + } + if (!xsltPackageDependencyChecks.isEmpty()) { + imports.add("java.nio.file.NoSuchFileException"); + result.append(" while (true) {\n"); + result.append(" if (xsltPackagesToAdd.isEmpty()) {\n"); + result.append(" if (xsltPackagesBeingAdded.isEmpty())\n"); + result.append(" break;\n"); + result.append(" xsltPackagesToAdd.addAll(xsltPackagesBeingAdded);\n"); + result.append(" xsltPackagesBeingAdded.clear();\n"); + result.append(" }\n"); + result.append(" String p = xsltPackagesToAdd.poll();\n"); + result.append(" logger.debug(\"Loading XSLT package in module \" + getName() + \": \" + p);\n"); + result.append(" xsltPackagesBeingAdded.add(p);\n"); + result.append(" try {\n"); + result.append(" addXSLTPackage(p, xmlParser); // may call resolveDependencies() recursively\n"); + result.append(" } catch (NoSuchFileException e) {\n"); + result.append(" logger.warn(\"XSLT package can not be loaded: \" + e.getMessage());\n"); + result.append(" } catch (IllegalArgumentException e) {\n"); + result.append(" throw new IllegalStateException(); // should not happen: file validated during compilation\n"); + result.append(" }\n"); + result.append(" xsltPackagesBeingAdded.remove(p);\n"); + result.append(" xsltPackagesToAdd.remove(p);\n"); + result.append(" }\n"); + } + result.append(" if (!initialized)\n"); + result.append(" logger.debug(\"Done resolving dependencies of module \" + getName());\n"); + result.append(" initialized = true;\n"); + result.append(" }\n"); + result.append(" }\n"); + if (!componentDependencyChecks.isEmpty()) { + imports.add("org.daisy.pipeline.modules.Component"); + imports.add("org.daisy.pipeline.modules.ResolutionException"); + result.append("\n"); + result.append(" @Override\n"); + result.append(" protected boolean addComponent(Component component) {\n"); + result.append(" String name = component.getURI().toString();\n"); + result.append(" try {\n"); + boolean first = true; + for (URI componentName : componentDependencyChecks.keySet()) { + if (first) + result.append(" "); + else + result.append(" else "); + first = false; + result.append("if (\"" + componentName + "\".equals(name)) {\n"); + for (String s : componentDependencyChecks.get(componentName).split("\n")) + result.append(" ").append(s).append("\n"); + result.append(" }"); + } + result.append("\n"); + result.append(" } catch (ResolutionException re) {\n"); + result.append(" logger.warn(\"Component \" + component.getURI() + \" can not be loaded: \" + re.getMessage());\n"); + result.append(" return false;\n"); + result.append(" }\n"); + result.append(" return super.addComponent(component);\n"); + result.append(" }\n"); + } + } else { + if (hasCatalog || !xsltPackages.isEmpty()) { + imports.add("org.osgi.service.component.annotations.Activate"); + result.append(" @Activate\n"); + result.append(" protected void init() {\n"); + result.append(" logger.debug(\"Initializing module \" + getName());\n"); + if (hasCatalog) + result.append(" Module.parseCatalog(this, catalogParser);\n"); + for (URI path : xsltPackages.keySet()) { + imports.add("java.nio.file.NoSuchFileException"); + XSLTPackage p = xsltPackages.get(path); + result.append(" try {\n"); + result.append(" addXSLTPackage(\"" + p.getName() + "\",\n"); + result.append(" \"" + p.getVersion() + "\",\n"); + result.append(" \"" + path + "\");\n"); + result.append(" } catch (NoSuchFileException e) {\n"); + result.append(" logger.warn(\"XSLT package " + p.getName() + " can not be loaded: \" + e.getMessage());\n"); + result.append(" }\n"); + } + result.append(" logger.debug(\"Module \" + getName() + \" initialized\");\n"); + result.append(" }\n"); + result.append("\n"); + } + result.append(" @Override\n"); + result.append(" public void resolveDependencies() {\n"); + result.append(" logger.debug(\"No dependencies to resolve for module \" + getName());\n"); + result.append(" }\n"); + } + if (extensionFunctionChecks) { + imports.add("java.nio.file.NoSuchFileException"); + imports.add("org.daisy.pipeline.modules.ResolutionException"); + result.append("\n"); + result.append(" private void tryResolveXPathExtensionFunction(String className) throws ResolutionException {\n"); + result.append(" try {\n"); + result.append(" getResource(\"../\" + className.replace('.', '/') + \".class\");\n"); + result.append(" return;\n"); + result.append(" } catch (NoSuchFileException e) {\n"); + result.append(" }\n"); + result.append(" // if the function is not implemented in the same module, the class must be on the class path\n"); + result.append(" try {\n"); + result.append(" Class.forName(className);\n"); + result.append(" return;\n"); + result.append(" } catch (Throwable e) {\n"); + result.append(" }\n"); + result.append(" throw new ResolutionException(\n"); + result.append(" \"Unresolved Java dependency: no class \" + className + \" found on the class path\");\n"); + result.append(" }\n"); + } + if (xsltPackageChecks) { + imports.add("java.util.ArrayList"); + imports.add("net.sf.saxon.style.PackageVersion"); + imports.add("net.sf.saxon.style.PackageVersionRanges"); + imports.add("net.sf.saxon.trans.packages.PackageDetails"); + imports.add("net.sf.saxon.trans.XPathException"); + imports.add("org.daisy.pipeline.modules.ResolutionException"); + imports.add("org.daisy.pipeline.modules.XSLTPackage"); + result.append("\n"); + result.append(" @Reference(\n"); + result.append(" name = \"PackageDetails\",\n"); + result.append(" unbind = \"-\",\n"); + result.append(" service = PackageDetails.class,\n"); + result.append(" cardinality = ReferenceCardinality.MULTIPLE,\n"); + result.append(" policy = ReferencePolicy.STATIC\n"); + result.append(" )\n"); + result.append(" public void addPackageDetails(PackageDetails pack) {\n"); + result.append(" if (packageDetails == null)\n"); + result.append(" packageDetails = new ArrayList<>();\n"); + result.append(" packageDetails.add(pack);\n"); + result.append(" }\n"); + result.append("\n"); + result.append(" private void tryResolveXSLTPackage(String name, String versionRange) throws ResolutionException{\n"); + result.append(" if (packageDetails != null) {\n"); + result.append(" for (PackageDetails pack : packageDetails) {\n"); + result.append(" if (name.equals(pack.nameAndVersion.packageName)) {\n"); + result.append(" if (versionRange != null) {\n"); + result.append(" // see https://www.w3.org/TR/xslt-30/#package-versions\n"); + result.append(" try {\n"); + result.append(" if (new PackageVersionRanges(versionRange).contains(pack.nameAndVersion.packageVersion));\n"); + result.append(" return true;\n"); + result.append(" else\n"); + result.append(" logger.debug(\"Version of \" + name + \" (\" + pack.nameAndVersion.packageVersion + \") \n"); + result.append(" + \"not in requested range (\" + versionRange + \")\");\n"); + result.append(" } catch (XPathException e) {\n"); + result.append(" \n"); + result.append(" }\n"); + result.append(" } else {\n"); + result.append(" return;\n"); + result.append(" }\n"); + result.append(" }\n"); + result.append(" }\n"); + result.append(" }\n"); + result.append(" if (getModuleByXSLTPackage(name, versionRange) != null)\n"); + result.append(" return;\n"); + result.append(" throw new ResolutionException(\"Unresolved XSLT package \" + name + \" with version=\" + versionRange);\n"); + result.append(" }\n"); + result.append("\n"); + result.append(" public Module getModuleByXSLTPackage(String name, String versionRange) {\n"); + result.append(" Module module = moduleRegistry.getModuleByXSLTPackage(name);\n"); + result.append(" if (module != null && versionRange != null) {\n"); + result.append(" XSLTPackage pack = module.getXSLTPackage(name);\n"); + result.append(" if (pack == null)\n"); + result.append(" throw new IllegalStateException(\"coding error\"); // can not happen\n"); + result.append(" PackageVersion version; {\n"); + result.append(" try {\n"); + result.append(" version = new PackageVersion(pack.getVersion());\n"); + result.append(" } catch (XPathException e) {\n"); + result.append(" logger.debug(\"Version of \" + name + \" can not be parsed: \" + pack.getVersion());\n"); + result.append(" return null;\n"); + result.append(" }\n"); + result.append(" }\n"); + result.append(" try {\n"); + result.append(" if (!(new PackageVersionRanges(versionRange).contains(version))) {\n"); + result.append(" logger.debug(\"Version of \" + name + \" (\" + version + \") not in requested range (\" + versionRange + \")\");\n"); + result.append(" return null;\n"); + result.append(" }\n"); + result.append(" } catch (XPathException e) {\n"); + result.append(" logger.debug(\"Version range can not be parsed: \" + versionRange);\n"); + result.append(" return null;\n"); + result.append(" }\n"); + result.append(" }\n"); + result.append(" return module;\n"); + result.append(" }\n"); + } + if (!resourceDependencyChecks.isEmpty() || !xsltPackageDependencyChecks.isEmpty()) { + imports.add("java.net.URL"); + imports.add("java.nio.file.NoSuchFileException"); + imports.add("org.daisy.pipeline.modules.ResolutionException"); + resourceDependencyChecks.putAll(xsltPackageDependencyChecks); + result.append("\n"); + for (int i = 1; i <= resourceDependencyChecks.size(); i++) { + result.append(" private URL resource" + i + " = null;\n"); + } + result.append("\n"); + result.append(" @Override\n"); + result.append(" public URL getResource(String path) throws NoSuchFileException {\n"); + result.append(" URL url = super.getResource(path);\n"); + result.append(" try {\n"); + int i = 1; + for (String path : resourceDependencyChecks.keySet()) { + result.append(" if (resource" + i + " == null)\n"); + result.append(" resource" + i + " = super.getResource(\"" + path + "\");\n"); + result.append(" if (url.equals(resource" + i + ")) {\n"); + for (String s : resourceDependencyChecks.get(path).split("\n")) { + result.append(" ").append(s).append("\n"); + } + result.append(" return url;\n"); + result.append(" }\n"); + i++; + } + result.append(" } catch (ResolutionException re) {\n"); + result.append(" throw new NoSuchFileException(\n"); + result.append(" \"Resource \" + path + \" in module \" + getName() + \" can not be loaded: \""); + result.append(" + re.getMessage());\n"); + result.append(" }\n"); + result.append(" return url;\n"); + result.append(" }\n"); + } + result.append("}\n"); + // insert package and imports + if (imports.size() > 0) { + List sortedImports = new ArrayList<>(imports); + Collections.sort(sortedImports); + String lastPrefix = null; + StringBuilder s = new StringBuilder(); + for (String i : sortedImports) { + String prefix = i.replaceAll("^([^.]+)\\..*$", "$1"); + if (lastPrefix == null) + lastPrefix = prefix; + else if (!lastPrefix.equals(prefix)) { + lastPrefix = prefix; + s.append("\n"); + } + s.append("import ").append(i).append(";\n"); + } + result.insert(0, "\n").insert(0, s); + } + result.insert(0, "package org.daisy.pipeline.modules.impl;\n\n"); + logger.debug("Successfully generated class " + className); + return result.toString(); + } + } + + private static final String XML_CATALOG_NS = "urn:oasis:names:tc:entity:xmlns:xml:catalog"; + private static final QName CAT_URI = new QName(XML_CATALOG_NS, "uri"); + private static final QName CAT_PUBLIC = new QName(XML_CATALOG_NS, "public"); + private static final QName CAT_SYSTEM = new QName(XML_CATALOG_NS, "system"); + private static final QName CAT_REWRITE_URI = new QName(XML_CATALOG_NS, "rewriteURI"); + private static final QName _NAME = new QName("name"); + private static final QName _URI = new QName("uri"); + private static final String PIPELINE_EXT_NS = "http://www.daisy.org/ns/pipeline"; + private static final QName PX_CONTENT_TYPE = new QName(PIPELINE_EXT_NS, "content-type"); + + private static Set parseCatalog(Element catalog) { + Set entries = new HashSet<>(); + NodeList children = catalog.getElementsByTagNameNS(CAT_URI.getNamespaceURI(), CAT_URI.getLocalPart()); + for (int i = 0; i < children.getLength(); i++) { + Element e = (Element)children.item(i); + Attr name = e.getAttributeNodeNS(_NAME.getNamespaceURI(), _NAME.getLocalPart()); + Attr uri = e.getAttributeNodeNS(_URI.getNamespaceURI(), _URI.getLocalPart()); + Attr contentType = e.getAttributeNodeNS(PX_CONTENT_TYPE.getNamespaceURI(), PX_CONTENT_TYPE.getLocalPart()); + entries.add(new CatalogEntry(name != null ? URI.create(name.getValue()) : null, + uri != null ? URI.create(uri.getValue()) : null, + contentType != null ? ContentType.create(contentType.getValue()) : null)); + } + return entries; + } + + private static class CatalogEntry { + public final URI name; + public final URI uri; + public final ContentType contentType; + public CatalogEntry(URI name, URI uri, ContentType contentType) { + if (uri == null) + throw new IllegalArgumentException("uri must not be null"); + this.name = name; + this.uri = uri; + this.contentType = contentType; + } + @Override + public String toString() { + return "" + uri; + } + } + + private static enum ContentType { + + SCRIPT("script"), + DATA_TYPE("data-type"), + XSLT_PACKAGE("xslt-package"), + PARAMS("params"), + CALABASH_CONFIG("calabash-config"), + LIBLOUIS_TABLES("liblouis-tables"), + LIBHYPHEN_TABLES("libhyphen-tables"), + USER_AGENT_STYLESHEET("user-agent-stylesheet"); + + private final String type; + + private ContentType(String type) { + this.type = type; + } + + public static ContentType create(String type) { + for (ContentType t : ContentType.values()) + if (t.type.equals(type)) + return t; + throw new IllegalArgumentException("Not a valid content-type: " + type); + } + } + + private static ModuleRegistry getModuleRegistry(ClassLoader classLoader) { + ClassLoader restoreClassLoader = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(classLoader); + for (ModuleRegistry r : ServiceLoader.load(ModuleRegistry.class)) + return r; + throw new RuntimeException("No ModuleRegistry found"); + } finally { + Thread.currentThread().setContextClassLoader(restoreClassLoader); + } + } +} diff --git a/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/HtmlizeSourcesMojo.java b/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/HtmlizeSourcesMojo.java index ee26d0353b..b125dfaf73 100644 --- a/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/HtmlizeSourcesMojo.java +++ b/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/HtmlizeSourcesMojo.java @@ -32,11 +32,12 @@ import org.daisy.maven.xproc.api.XProcEngine; import org.daisy.maven.xproc.api.XProcExecutionException; +import static org.daisy.pipeline.maven.plugin.utils.getClassLoader; import static org.daisy.pipeline.maven.plugin.utils.URLs.asURI; import static org.daisy.pipeline.maven.plugin.utils.URLs.relativize; import static org.daisy.pipeline.maven.plugin.utils.XML.evaluateXPath; import static org.daisy.pipeline.maven.plugin.utils.XML.transform; -import org.daisy.pipeline.modules.impl.resolver.ModuleUriResolver; +import org.daisy.pipeline.modules.impl.ModuleUriResolver; /** * @goal htmlize-sources @@ -78,7 +79,7 @@ public class HtmlizeSourcesMojo extends AbstractMojo { public void execute() throws MojoFailureException { try { - final List compileClassPath = mavenProject.getCompileClasspathElements(); + final ClassLoader compileClassPath = getClassLoader(mavenProject.getCompileClasspathElements()); final XProcEngine engine = new CalabashWithPipelineModules(compileClassPath); List sources = new ArrayList(); if (includes == null) diff --git a/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/ProcessCatalogMojo.java b/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/ProcessCatalogMojo.java index d0f31fc96c..ca7cedfddd 100644 --- a/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/ProcessCatalogMojo.java +++ b/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/ProcessCatalogMojo.java @@ -9,6 +9,8 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import net.sf.saxon.lib.ExtensionFunctionDefinition; + import org.apache.maven.model.Resource; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoFailureException; @@ -18,8 +20,9 @@ import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; -import org.daisy.maven.xproc.api.XProcEngine; +import org.daisy.common.xpath.saxon.ExtensionFunctionProvider; +import static org.daisy.pipeline.maven.plugin.utils.getClassLoader; import static org.daisy.pipeline.maven.plugin.utils.URLs.asURI; @Mojo( @@ -85,7 +88,18 @@ public void execute() throws MojoFailureException { getLog().info("Skipping the execution."); return; } try { - XProcEngine engine = new CalabashWithPipelineModules(mavenProject.getCompileClasspathElements()); + ClassLoader compileClassPath = getClassLoader(mavenProject.getCompileClasspathElements()); + CalabashWithPipelineModules engine = new CalabashWithPipelineModules(compileClassPath); + ExtensionFunctionProvider generateModuleClassFunctionProvider = new GenerateModuleClassFunctionProvider( + compileClassPath, + mavenProject.getCompileSourceRoots(), + getLog()); + engine.setConfiguration( + config -> { + for (ExtensionFunctionDefinition function : generateModuleClassFunctionProvider.getDefinitions()) + config.getProcessor().getUnderlyingConfiguration().registerExtensionFunction(function); + } + ); Map options = new HashMap(); { options.put("generatedResourcesDirectory", asURI(generatedResourcesDirectory).toASCIIString()); options.put("generatedSourcesDirectory", asURI(generatedSourcesDirectory).toASCIIString()); diff --git a/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/ProcessTestCatalogMojo.java b/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/ProcessTestCatalogMojo.java index fc36c1d72b..3c4fc66f67 100644 --- a/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/ProcessTestCatalogMojo.java +++ b/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/ProcessTestCatalogMojo.java @@ -9,6 +9,8 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import net.sf.saxon.lib.ExtensionFunctionDefinition; + import org.apache.maven.model.Resource; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoFailureException; @@ -18,8 +20,9 @@ import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; -import org.daisy.maven.xproc.api.XProcEngine; +import org.daisy.common.xpath.saxon.ExtensionFunctionProvider; +import static org.daisy.pipeline.maven.plugin.utils.getClassLoader; import static org.daisy.pipeline.maven.plugin.utils.URLs.asURI; @Mojo( @@ -85,7 +88,18 @@ public void execute() throws MojoFailureException { getLog().info("Tests are skipped."); return; } try { - XProcEngine engine = new CalabashWithPipelineModules(mavenProject.getCompileClasspathElements()); + ClassLoader compileClassPath = getClassLoader(mavenProject.getCompileClasspathElements()); + CalabashWithPipelineModules engine = new CalabashWithPipelineModules(compileClassPath); + ExtensionFunctionProvider generateModuleClassFunctionProvider = new GenerateModuleClassFunctionProvider( + compileClassPath, + mavenProject.getCompileSourceRoots(), + getLog()); + engine.setConfiguration( + config -> { + for (ExtensionFunctionDefinition function : generateModuleClassFunctionProvider.getDefinitions()) + config.getProcessor().getUnderlyingConfiguration().registerExtensionFunction(function); + } + ); Map options = new HashMap(); { options.put("generatedResourcesDirectory", asURI(generatedResourcesDirectory).toASCIIString()); options.put("generatedSourcesDirectory", asURI(generatedSourcesDirectory).toASCIIString()); diff --git a/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/utils.java b/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/utils.java index aba441219e..db78101176 100644 --- a/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/utils.java +++ b/utils/build-utils/modules-build-helper/src/main/java/org/daisy/pipeline/maven/plugin/utils.java @@ -5,8 +5,10 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.net.URLClassLoader; import java.net.URLDecoder; import java.util.ArrayList; +import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -31,6 +33,19 @@ abstract class utils { + static ClassLoader getClassLoader(Collection classPath) { + try { + URL[] classPathURLs = new URL[classPath.size()]; { + int i = 0; + for (String path : classPath) + classPathURLs[i++] = new File(path).toURI().toURL(); + } + return new URLClassLoader(classPathURLs, Thread.currentThread().getContextClassLoader()); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + static abstract class URLs { static URI asURI(File file) { diff --git a/utils/build-utils/modules-build-helper/src/main/resources/process-catalog/process-catalog.xsl b/utils/build-utils/modules-build-helper/src/main/resources/process-catalog/process-catalog.xsl index 8262a5fbb2..bbc84251ea 100644 --- a/utils/build-utils/modules-build-helper/src/main/resources/process-catalog/process-catalog.xsl +++ b/utils/build-utils/modules-build-helper/src/main/resources/process-catalog/process-catalog.xsl @@ -10,6 +10,7 @@ xmlns:html="http://www.w3.org/1999/xhtml" xmlns:f="http://www.daisy.org/ns/pipeline/internal-functions" xmlns:pf="http://www.daisy.org/ns/pipeline/functions" + xmlns:GenerateModuleClass="org.daisy.pipeline.maven.plugin.GenerateModuleClassFunctionProvider$GenerateModuleClass" exclude-result-prefixes="#all" version="2.0"> @@ -58,16 +59,6 @@ - - - - Require-Bundle: - - - - @@ -89,56 +80,23 @@ - - + + + + + - package org.daisy.pipeline.modules.impl; - -import org.daisy.pipeline.modules.Module; -import org.daisy.pipeline.xmlcatalog.XmlCatalogParser; - -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferenceCardinality; -import org.osgi.service.component.annotations.ReferencePolicy; - -@Component( - name = "org.daisy.pipeline.modules.impl.", - service = { Module.class } -) -public class extends Module { - - private XmlCatalogParser catalogParser; - - public () { - super("", - "", - ""); - } - - @Activate - public void activate() { - super.init(catalogParser); - } - - @Reference( - name = "XmlCatalogParser", - unbind = "-", - service = XmlCatalogParser.class, - cardinality = ReferenceCardinality.MANDATORY, - policy = ReferencePolicy.STATIC - ) - public void setParser(XmlCatalogParser parser) { - catalogParser = parser; - } -} - + @@ -147,12 +105,11 @@ public class extends Module { - + - + + + @@ -203,7 +160,7 @@ public class extends Module { 'liblouis-tables', 'libhyphen-tables')]| cat:uri/@px:content-type[.=('script', - 'xsl-package', + 'xslt-package', 'data-type', 'params', 'user-agent-stylesheet', @@ -288,65 +245,6 @@ public class extends XProcScriptService { } - - - - not a xsl:package: - - - missing @name: - - - - - - - - - - - - - - - - package org.daisy.common.saxon.impl; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; -import javax.xml.transform.stream.StreamSource; - -import net.sf.saxon.trans.packages.PackageDetails; -import net.sf.saxon.trans.packages.VersionedPackageName; -import net.sf.saxon.trans.XPathException; - -import org.daisy.common.file.URLs; - -import org.osgi.service.component.annotations.Component; - -@Component( - name = "", - service = { PackageDetails.class } -) -public class extends PackageDetails { - public () { - try { - nameAndVersion = new VersionedPackageName("", ""); - URL url = URLs.getResourceFromJAR("", - .class); - sourceLocation = new StreamSource(url.openStream(), url.toURI().toASCIIString()); - } catch (XPathException|IOException|URISyntaxException e) { - throw new IllegalStateException( - "Failed to create PackageDetails object for package ''", e); - } - } -} - - surefire-report:report @@ -146,8 +147,4 @@ - - - v1.0.3 - diff --git a/utils/xproc-maven-plugin/xproc-maven-plugin/src/it/it-xprocspec-catalog-calabash/pom.xml b/utils/xproc-maven-plugin/xproc-maven-plugin/src/it/it-xprocspec-catalog-calabash/pom.xml index b44e94c196..7c2b3ace44 100644 --- a/utils/xproc-maven-plugin/xproc-maven-plugin/src/it/it-xprocspec-catalog-calabash/pom.xml +++ b/utils/xproc-maven-plugin/xproc-maven-plugin/src/it/it-xprocspec-catalog-calabash/pom.xml @@ -7,6 +7,15 @@ it-xprocspec-catalog-calabash 0.0-SNAPSHOT + + + + org.apache.maven.plugins + maven-surefire-report-plugin + 2.14.1 + + + org.daisy.maven diff --git a/utils/xproc-maven-plugin/xproc-maven-plugin/src/it/it-xprocspec-config-calabash/pom.xml b/utils/xproc-maven-plugin/xproc-maven-plugin/src/it/it-xprocspec-config-calabash/pom.xml index a392e7ac4e..621972f6b0 100644 --- a/utils/xproc-maven-plugin/xproc-maven-plugin/src/it/it-xprocspec-config-calabash/pom.xml +++ b/utils/xproc-maven-plugin/xproc-maven-plugin/src/it/it-xprocspec-config-calabash/pom.xml @@ -58,6 +58,11 @@ 1.6 + + org.apache.maven.plugins + maven-surefire-report-plugin + 2.14.1 + diff --git a/utils/xproc-maven-plugin/xproc-maven-plugin/src/it/it-xprocspec-error-calabash/pom.xml b/utils/xproc-maven-plugin/xproc-maven-plugin/src/it/it-xprocspec-error-calabash/pom.xml index 28a00af63a..d9ad32fe9a 100644 --- a/utils/xproc-maven-plugin/xproc-maven-plugin/src/it/it-xprocspec-error-calabash/pom.xml +++ b/utils/xproc-maven-plugin/xproc-maven-plugin/src/it/it-xprocspec-error-calabash/pom.xml @@ -7,6 +7,15 @@ it-xprocspec-error-calabash 0.0-SNAPSHOT + + + + org.apache.maven.plugins + maven-surefire-report-plugin + 2.14.1 + + + org.daisy.maven diff --git a/utils/xproc-maven-plugin/xproc-maven-plugin/src/it/it-xprocspec-hello-world-calabash/pom.xml b/utils/xproc-maven-plugin/xproc-maven-plugin/src/it/it-xprocspec-hello-world-calabash/pom.xml index aa72dc54fe..42548baec9 100644 --- a/utils/xproc-maven-plugin/xproc-maven-plugin/src/it/it-xprocspec-hello-world-calabash/pom.xml +++ b/utils/xproc-maven-plugin/xproc-maven-plugin/src/it/it-xprocspec-hello-world-calabash/pom.xml @@ -7,6 +7,15 @@ it-xprocspec-hello-world-calabash 0.0-SNAPSHOT + + + + org.apache.maven.plugins + maven-surefire-report-plugin + 2.14.1 + + + org.daisy.maven @@ -30,13 +39,4 @@ - - - - org.apache.maven.plugins - maven-surefire-report-plugin - 2.14.1 - - - diff --git a/utils/xproc-maven-plugin/xprocspec-runner/pom.xml b/utils/xproc-maven-plugin/xprocspec-runner/pom.xml index 5a30c35ba7..9f81d80517 100644 --- a/utils/xproc-maven-plugin/xprocspec-runner/pom.xml +++ b/utils/xproc-maven-plugin/xprocspec-runner/pom.xml @@ -12,7 +12,7 @@ org.daisy.maven xprocspec-runner - 1.2.7 + 1.2.8-SNAPSHOT bundle xprocspec-runner @@ -21,7 +21,7 @@ org.daisy.pipeline framework-bom - 1.14.16 + 1.14.21-SNAPSHOT pom import @@ -117,7 +117,7 @@ org.daisy.pipeline.build modules-build-helper - 2.5.0 + 3.0.0-SNAPSHOT process-catalog diff --git a/website/.gitrepo b/website/.gitrepo index 8dfe8f4e48..c52fb83e2b 100644 --- a/website/.gitrepo +++ b/website/.gitrepo @@ -6,6 +6,6 @@ [subrepo] remote = git@github.com:daisy/pipeline.git branch = website - commit = 40bdfe8c0b30a012a6d6c92bb185343fd4775d35 - parent = c844c9642350493e191304e1cdffdfe116ae2a5b + commit = a8d1a3e3dd391f31e77eb2b2af994f49a077b14b + parent = b40a9bbc10ff80e6ff32e94df49a603fb9cb8c67 cmdver = 0.3.1 diff --git a/website/Makefile b/website/Makefile index 04fc45f300..c4b4821600 100644 --- a/website/Makefile +++ b/website/Makefile @@ -1,8 +1,16 @@ MVN ?= mvn JEKYLL_SRC_DIR := src SHELL := bash +# to use ruby 2.7 on macOS: +# - brew tap homebrew/core --force +# - brew edit ruby@2 +# - comment out the line starting with "disable!" +# - HOMEBREW_NO_INSTALL_FROM_API=1 brew install ruby@2 +# - /opt/homebrew/opt/ruby@2.7/bin/bundle config build.nokogiri --use-system-libraries +# - run this Makefile with `BUNDLE=/opt/homebrew/opt/ruby@2.7/bin/bundle make' +BUNDLE ?= bundle # RUBYOPT='-W:no-deprecated' does not work for some reason (https://stackoverflow.com/questions/60350374/cannot-suppress-ruby-2-7-0-warnings) -RUBY := RUBYOPT=-W0 bundle exec +RUBY := RUBYOPT=-W0 $(BUNDLE) exec JEKYLL := $(RUBY) jekyll JEKYLL_SRC_FILES_CONTENT := $(shell find $(JEKYLL_SRC_DIR)/{_wiki,_wiki_ui,_wiki_webui} -type f -not -name '_*' -not -name '*.png' -not -name '*.jpg' ) JEKYLL_SRC_FILES_MUSTACHE := $(shell find $(JEKYLL_SRC_DIR)/ -type f -name '_Sidebar.md') @@ -30,10 +38,10 @@ baseurl := $(call yaml_get,$(CONFIG_FILE),baseurl) .PHONY : all all : $(JEKYLL_DIR)/_site -ifneq (,$(findstring bundle exec,$(RUBY))) +ifneq (,$(findstring $(BUNDLE) exec,$(RUBY))) .SECONDARY : gems gems : Gemfile.lock - bundle install --path gems + $(BUNDLE) install --path gems else .PHONY : gems gems : @@ -186,15 +194,8 @@ $(MAVEN_DIR)/pom.xml : $(JEKYLL_SRC_DIR)/_data/versions.yml $(JEKYLL_SRC_DIR)/_d fi .PHONY : serve -serve : ws all - ws -d $(CURDIR)/$(JEKYLL_DIR)/_site - -.PHONY : ws -ws : - @if ! which $@ >/dev/null 2>/dev/null; then \ - echo "ws is not installed, install with 'npm install -g local-web-server'" 2>&1; \ - exit 1; \ - fi +serve : all + ruby -r webrick -e "s = WEBrick::HTTPServer.new(:Port => 8080, :DocumentRoot => '$(CURDIR)/$(JEKYLL_DIR)/_site'); trap('INT') { s.shutdown }; s.start" .PHONY : publish publish : all diff --git a/website/src/Download.md b/website/src/Download.md index c5fb30f73b..ddbbce5bd4 100644 --- a/website/src/Download.md +++ b/website/src/Download.md @@ -3,6 +3,8 @@ layout: default --- # Download + + For installation instructions see [Installation]({{site.baseurl}}/Get-Help/User-Guide/Installation/). diff --git a/website/src/_includes/download-link b/website/src/_includes/download-link index 5ec0b01226..371655c4f1 100644 --- a/website/src/_includes/download-link +++ b/website/src/_includes/download-link @@ -1,4 +1,4 @@ - + For {% case include.file.platform %} {% when 'windows' %} Windows diff --git a/website/src/_wiki/Get-Help/User-Guide.md b/website/src/_wiki/Get-Help/User-Guide.md index 756e9bb5c3..30763e4059 100644 --- a/website/src/_wiki/Get-Help/User-Guide.md +++ b/website/src/_wiki/Get-Help/User-Guide.md @@ -3,22 +3,35 @@ DAISY Pipeline 2 is an umbrella term for various things. - DAISY Pipeline 2 is a [desktop - application](https://github.com/daisy/pipeline-ui/wiki/Quick-Start-Guide/). + application](https://github.com/daisy/pipeline-ui/wiki/Quick-Start-Guide/) + for Windows and macOS. -- DAISY Pipeline 2 can also be run as a [service](Pipeline-as-Service) - to which client programs can connect, through a web API. +- It is a [web server](Pipeline-as-Service) to which client programs + can connect through a RESTful API. - The DAISY Pipeline 2 [web application](https://github.com/daisy/pipeline-webui/wiki/User-Guide/) is an example of such a server-client configuration. Unlike with the desktop application, multiple users can work together in the same - application, each through their own web browser, while the real work - happens on a central server. The web application offers some - additional features that are not available in the desktop - application yet, such as + application, each through their own web browser, while the actual + processing happens on a central server. The web application offers + some additional features that are not available in the desktop + application, such as "[templates](https://github.com/daisy/pipeline-webui/wiki/Templating/)". -- There is also a [command line tool](Command-Line). +- There is also a [command line interface](Command-Line). -DAISY Pipeline 2, in all its forms, is supported on Windows, Mac OS, -and Linux. +- DAISY Pipeline 2 is a [Java library](API-Documentation#java-api). + +- For Windows users, there is also the [Save As DAISY addin for MS + Word](https://daisy.org/activities/software/save-as-daisy-ms-word-add-in/), + which is becoming a DAISY Pipeline user interface specifically for + processing Word documents. + +- "DAISY Pipeline" could also refer to the predecessor of DAISY + Pipeline 2 which we now call "Pipeline 1". Because a lot of + organisations still rely on some Pipeline 1 converters in their + production today, we are currently working on making these legacy + converters available through the DAISY Pipeline 2 user and + programming interfaces, in order to make the transition to Pipeline + 2 smoother and to retire the Pipeline 1 user interface. diff --git a/website/src/_wiki/Get-Help/User-Guide/Installation.md b/website/src/_wiki/Get-Help/User-Guide/Installation.md index d672c0a2d3..6e6b082a99 100644 --- a/website/src/_wiki/Get-Help/User-Guide/Installation.md +++ b/website/src/_wiki/Get-Help/User-Guide/Installation.md @@ -13,22 +13,12 @@ These are the installation instructions for each package. Before starting with the installation make sure the [system requirements](#system-requirements) are fulfilled. -### Windows +### Windows and macOS -For Windows users there is the Windows installer. It installs the -desktop application. Launch the installer and follow the instructions -on the screen. The desktop application can also be used to run a -server. - -### Mac OS - -For Mac OS users there is the disk image. It contains the desktop -application. To install, open the image and drag the application file -to the "Applications" folder, or any other destination you want. - -Contents of the disc image - -The desktop application can also be used to run a server. +For Windows and macOS users we provide easy to use installers for +installing the desktop application. Launch the installer and follow +the instructions on the screen. The desktop application can also be +used to run a server. ### Debian @@ -178,5 +168,5 @@ To learn more about Docker see [https://docs.docker.com](https://docs.docker.com The standalone server requires a Java runtime environment. The minimum required version of Java is 11. We recommend installing Java from https://adoptium.net/. Users of the desktop application (Windows and -MacOS) do not have to worry about Java because it is included in the +macOS) do not have to worry about Java because it is included in the application. diff --git a/website/src/_wiki/Get-Help/User-Guide/Installation/disk-image.png b/website/src/_wiki/Get-Help/User-Guide/Installation/disk-image.png deleted file mode 100644 index b2691cc341..0000000000 Binary files a/website/src/_wiki/Get-Help/User-Guide/Installation/disk-image.png and /dev/null differ diff --git a/website/src/_wiki/Support.md b/website/src/_wiki/Support.md new file mode 100644 index 0000000000..949b98b913 --- /dev/null +++ b/website/src/_wiki/Support.md @@ -0,0 +1,11 @@ +# Support our work + +

+ + +If you find DAISY Pipeline useful, please help us by donating to support its +ongoing maintenance. [Support out work](https://daisy.org/pipelineAppSponsor). diff --git a/website/src/_wiki/_Sidebar.md b/website/src/_wiki/_Sidebar.md index 0040e935dc..a2dc81ee98 100644 --- a/website/src/_wiki/_Sidebar.md +++ b/website/src/_wiki/_Sidebar.md @@ -52,9 +52,8 @@ * [[Troubleshooting Desktop Application|https://github.com/daisy/pipeline-ui/wiki/Troubleshooting]] * [[Forum|https://github.com/daisy/pipeline/discussions]] * [[Contact|mailto:daisy-pipeline@mail.daisy.org]] -* [[Contribute​|Contribute]] +* [[Contribute|Contribute]] * [[Source Code|https://github.com/daisy/pipeline]] - * [[Developers Mailing List|https://groups.google.com/forum/#!forum/daisy-pipeline-dev]] * [[Developer Guide|Developer-Guide]] * [[Source Code|Sources]] * [[Build System|Building]] @@ -65,4 +64,5 @@ * [[Debugging|Debugging]] * [[Release Management|Releasing]] * [[Easy Hacks|http://daisy.github.io/pipeline/Contribute/Easy-Hacks.html]] +* [[Support​|Support]] * [[DAISY Home|http://www.daisy.org]] diff --git a/website/src/index.md b/website/src/index.md index 8aa9686681..a30fbd7a20 100644 --- a/website/src/index.md +++ b/website/src/index.md @@ -4,55 +4,61 @@ title: Home --- # DAISY Pipeline 2 -The DAISY Pipeline 2 is an open-source, cross-platform framework for -the automated processing of digital content in and between various -file formats. It intends to facilitate the production and maintenance -of accessible content for people with print disabilities. +The DAISY Pipeline 2 is an open-source, cross-platform framework and +user interface for the automated processing of digital content in and +between various file formats. It intends to facilitate the production +and maintenance of accessible content for people with print +disabilities. ## Accessibility -The Pipeline was developed by and for the -[DAISY community](http://www.daisy.org/), a group of organizations -committed to making content accessible. It goes without saying that -accessiblity is the main interest of the tool. There are Pipeline -transformation for migrating from one accessible format to another, -enriching an input format with certain accessible features, and -producing formats targeting a specific disability. +The Pipeline was developed by and for the [DAISY +community](http://www.daisy.org/), a group of organizations committed +to making content accessible. It goes without saying that accessiblity +is the main interest of the tool. There are Pipeline transformation +for migrating from one accessible format to another, enriching an +input format with certain accessible features, and producing formats +targeting a specific disability. ## Standards -Accessibility goes hand in hand with standards. The file formats that -the Pipeline primarily focuses on are [EPUB 3](http://idpf.org/epub/301), -[DAISY](http://www.daisy.org/specifications) and -[PEF](http://pef-format.org/?page_id=15). +Accessibility goes hand in hand with standards. Notable file formats +that DAISY Pipeline evolves around are +[EPUB](https://www.w3.org/TR/epub-33/), +[DAISY](https://daisy.org/activities/standards/), +[PEF](https://braillespecs.github.io/pef/pef-specification.html) and +[eBraille](https://daisy.github.io/ebraille/). Standards are also important under the hood. The system is based on standard XML processing technologies, notably W3C recommendations like -XProc and XSLT 2.0, but also XPath 2.0, OASIS XML Catalogs, etc. These +XProc and XSLT 3.0, but also XPath 3.1, OASIS XML Catalogs, etc. These technologies are platform neutral, supported by active communities, and easy to maintain. ## Cross-platform -The application can be run on most common operating systems. It must -be run in a Java runtime environment, but its platform neutral RESTful -API (web service) allows it to be called from any programming language -and makes it interoperable with heterogenous production workflows. +The application can be run in some form on all common operating +systems. A desktop application is provided for Windows and macOS. In +addition, for Windows users, there is the [Save As DAISY addin for MS +Word](https://daisy.org/activities/software/save-as-daisy-ms-word-add-in/), +which is becoming a DAISY Pipeline user interface specifically for +processing Word documents. -In addition to the programming interface there is also a command-line -interface and there are graphical user interfaces. +DAISY Pipeline also has programming interfaces. When run as a daemon +(web server), there are no restrictions. The server can be run on any +OS, and its platform neutral RESTful API allows it to be called from +any programming language and makes it interoperable with heterogenous +production workflows. A command-line interface is available too. When +used as a Java library, there are no OS restictions either. ## Modular The system was designed with a modular architecture. Modularity is the -key to a better scalability and extensibility. Processing -functionality is provided by cohesive and reusable software -components, called "modules". The runtime framework is backed by a -service-oriented and dynamic module system (OSGi). +key to a better scalability and extensibility. ## Collaborative -The project is led and maintained by the DAISY Consortium but involves +The project is led and maintained by the DAISY Consortium and involves several member organizations. This reduces the duplication of effort and ensures maximum sharing of best practices among the user community. @@ -65,10 +71,6 @@ under a business-friendly licence stimulate collaboration between organizations and to maximize reuse and integration in other contexts, including commercial software. - - Find out how you can [join the community](Contribute).
@@ -86,11 +88,12 @@ Pipeline 2 and find out [how it works](Get-Help). ## Background -The DAISY Pipeline is a collaborative project maintained by the -[DAISY Consortium](http://www.daisy.org/), with numerous organizations +The DAISY Pipeline is a collaborative project maintained by the [DAISY +Consortium](http://www.daisy.org/), with numerous organizations participating and contributing to the development. The DAISY Pipeline -2 project is the follow-up of the -[DAISY Pipeline 1](http://www.daisy.org/pipeline) project. +2 project is the follow-up of the original [DAISY +Pipeline](https://daisy.org/info-help/document-archive/archived-projects/pipeline-1/) +(now referred to as "Pipeline 1") project. The initial DAISY Pipeline project was started in 2006. Since then, new standards and technologies have emerged and have been embraced in @@ -115,9 +118,11 @@ those standards), version 2 of the project aims to - increase interoperability with the heterogeneous production workflows - increase the likelihood of re-use in both open source and commercial applications. - - - +Because a lot of organisations still rely on some Pipeline 1 +converters in their production today, we are currently working on +making these legacy converters available through the DAISY Pipeline 2 +user and programming interfaces, in order to make the transition to +Pipeline 2 smoother and to retire the Pipeline 1 user interface.