All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.daisy.pipeline.css.speech.impl.SpeechCssCascader Maven / Gradle / Ivy

The newest version!
package org.daisy.pipeline.css.speech.impl;

import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.namespace.QName;
import javax.xml.transform.URIResolver;

import cz.vutbr.web.css.CSSProperty;
import cz.vutbr.web.css.CSSProperty.Content;
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.TermString;
import cz.vutbr.web.css.TermURI;
import cz.vutbr.web.csskit.antlr.CSSParserFactory;
import cz.vutbr.web.csskit.RuleFactoryImpl;
import cz.vutbr.web.domassign.DeclarationTransformer;
import cz.vutbr.web.domassign.SupportedCSS21;

import org.daisy.braille.css.BrailleCSSParserFactory;
import org.daisy.common.file.URLs;
import org.daisy.common.transform.XMLTransformer;
import org.daisy.pipeline.css.CssCascader;
import org.daisy.pipeline.css.CssPreProcessor;
import org.daisy.pipeline.css.CssSerializer;
import org.daisy.pipeline.css.JStyleParserCssCascader;
import org.daisy.pipeline.css.Medium;
import org.daisy.pipeline.css.speech.SpeechDeclarationTransformer;
import org.daisy.pipeline.css.XsltProcessor;

import org.osgi.service.component.annotations.Component;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.w3c.dom.Element;

@Component(
	name = "SpeechCssCascader",
	service = { CssCascader.class }
)
public class SpeechCssCascader implements CssCascader {

	public boolean supportsMedium(Medium medium) {
		switch (medium.getType()) {
		case SPEECH:
			return true;
		default:
			return false;
		}
	}

	public XMLTransformer newInstance(Medium medium,
	                                  String userAndUserAgentStylesheets,
	                                  URIResolver uriResolver,
	                                  CssPreProcessor preProcessor,
	                                  XsltProcessor xsltProcessor,
	                                  QName attributeName,
	                                  boolean multipleAttrs) {
		if (!multipleAttrs)
			throw new UnsupportedOperationException("Cascading to single attribute per element not supported");
		switch (medium.getType()) {
		case SPEECH:
			return new Transformer(uriResolver, preProcessor, xsltProcessor, userAndUserAgentStylesheets, medium, attributeName);
		default:
			throw new IllegalArgumentException("medium not supported: " + medium);
		}
	}

	// using braille-css because :has() and :note() are not supported by jStyleParser
	private static final CSSParserFactory parserFactory = new BrailleCSSParserFactory();
	private static final RuleFactory ruleFactory = RuleFactoryImpl.getInstance();
	private static final SupportedCSS speechCSS = SupportedCSS21.getInstance();
	private static final DeclarationTransformer declarationTransformer = new SpeechDeclarationTransformer();
	private static final Set speechCSSProperties = new HashSet<>(
		Arrays.asList(
			"voice-family", "stress", "richness", "cue", "cue-before", "cue-after", "pause",
			"pause-after", "pause-before", "azimuth", "volume", "speak", "play-during",
			"elevation", "speech-rate", "pitch", "pitch-range", "stress", "speak-punctuation",
			"speak-numeral", "speak-header"));

	private static class Transformer extends JStyleParserCssCascader {

		private final String attributePrefix;
		private final String attributeNamespaceURI;

		private Transformer(URIResolver resolver, CssPreProcessor preProcessor, XsltProcessor xsltProcessor,
		                    String userAndUserAgentStylesheets, Medium medium, QName attributeNamespace) {
			super(resolver, preProcessor, xsltProcessor, userAndUserAgentStylesheets, medium, null,
			      parserFactory, ruleFactory, speechCSS, declarationTransformer);
			this.attributePrefix = attributeNamespace.getPrefix();
			this.attributeNamespaceURI = attributeNamespace.getNamespaceURI();
		}

		protected Map serializeStyle(NodeData mainStyle, Map pseudoStyles, Element context) {
			Map style = null;
			if (mainStyle != null) {
				for (String property : mainStyle.getPropertyNames()) {
					if (speechCSSProperties.contains(property)) {
						String s; {
							Term v = mainStyle.getValue(property, false);
							if (v == null || v.getValue() == null) {
								CSSProperty prop = mainStyle.getProperty(property, false);
								if (prop == null) // can be null for unspecified inherited properties
									continue;
								s = prop.toString().replace('_', '-');
							} else {
								s = serializeTerm(v);
							}
						}
						if (style == null)
							style = new HashMap<>();
						style.put(new QName(attributeNamespaceURI, property, attributePrefix), s);
					}
				}
			}
			// process ::after and ::before pseudo elements
			for (PseudoElement pseudo : pseudoStyles.keySet()) {
				if ("::after".equals(pseudo.toString()) || "::before".equals(pseudo.toString())) {
					NodeData nd = pseudoStyles.get(pseudo);
					if (nd != null) {
						for (String property : nd.getPropertyNames()) {
							if ("content".equals(property)) {
								switch ((Content)nd.getProperty(property)) {
								case list_values:
									StringBuilder value = new StringBuilder();
									for (Term t : (TermList)nd.getValue(property, false)) {
										if (t instanceof TermString) {
											value.append(t.getValue());
											continue;
										} else if (t instanceof TermFunction) {
											TermFunction f = (TermFunction)t;
											if ("attr".equals(f.getFunctionName().toLowerCase())
											    && f.size() == 1
											    && f.get(0) instanceof TermIdent) {
												value.append(context.getAttribute(((TermIdent)f.get(0)).getValue()));
												continue;
											}
										}
										logger.warn("Don't know how to speak content value " + t
										            + " within ::" + pseudo.getName() + " pseudo-element");
										value = null;
										break;
									}
									if (value != null && value.length() > 0) {
										if (style == null)
											style = new HashMap<>();
										style.put(new QName(attributeNamespaceURI, pseudo.getName(), attributePrefix), value.toString());
									}
									break;
								case NORMAL:
								case NONE:
								case INHERIT:
								case INITIAL:
								default:
									break;
								}
								break;
							} else if (speechCSSProperties.contains(property)) {
								logger.warn("Ignoring property '"+ property
								            + "' within ::" + pseudo.getName() + " pseudo-element");
							}
						}
					}
				}
			}
			return style;
		}

		private static String serializeTerm(Term term) {
			if (term instanceof TermString)
				return ((TermString)term).getValue();
			else if (term instanceof TermURI) {
				TermURI termURI = (TermURI)term;
				URI uri = URLs.asURI(termURI.getValue());
				if (termURI.getBase() != null)
					uri = URLs.resolve(URLs.asURI(termURI.getBase()), uri);
				return uri.toASCIIString();
			} else if (term instanceof TermIdent)
				return CssSerializer.toString(term).replace('_', '-');
			else
				return CssSerializer.toString(term, Transformer::serializeTerm);
		}

		protected String serializeValue(Term value) {
			throw new UnsupportedOperationException();
		}
	}

	private final static Logger logger = LoggerFactory.getLogger(SpeechCssCascader.class);

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy