1 package org.daisy.pipeline.css.calabash.impl;
2
3 import java.net.URI;
4 import java.net.URISyntaxException;
5 import java.net.URL;
6 import java.util.ArrayList;
7 import java.util.Arrays;
8 import java.util.Collections;
9 import java.util.Iterator;
10 import java.util.LinkedHashMap;
11 import java.util.List;
12 import java.util.Map;
13 import java.util.StringTokenizer;
14
15 import javax.xml.namespace.QName;
16 import javax.xml.stream.XMLStreamWriter;
17 import javax.xml.transform.dom.DOMSource;
18 import javax.xml.transform.sax.SAXSource;
19 import javax.xml.transform.Source;
20 import javax.xml.transform.stream.StreamSource;
21 import javax.xml.transform.TransformerException;
22 import javax.xml.transform.URIResolver;
23
24 import static com.google.common.collect.Iterators.forArray;
25
26 import com.xmlcalabash.core.XProcConstants;
27 import com.xmlcalabash.core.XProcRuntime;
28 import com.xmlcalabash.io.ReadablePipe;
29 import com.xmlcalabash.io.WritablePipe;
30 import com.xmlcalabash.library.DefaultStep;
31 import com.xmlcalabash.model.RuntimeValue;
32 import com.xmlcalabash.runtime.XAtomicStep;
33
34 import net.sf.saxon.ma.map.MapItem;
35 import net.sf.saxon.s9api.SaxonApiException;
36
37 import org.daisy.common.file.URLs;
38 import org.daisy.common.saxon.SaxonHelper;
39 import static org.daisy.common.stax.XMLStreamWriterHelper.writeAttribute;
40 import static org.daisy.common.stax.XMLStreamWriterHelper.writeStartElement;
41 import org.daisy.common.xproc.calabash.XMLCalabashInputValue;
42 import org.daisy.common.xproc.calabash.XMLCalabashOutputValue;
43 import org.daisy.common.xproc.calabash.XProcStep;
44 import org.daisy.common.xproc.calabash.XProcStepProvider;
45 import org.daisy.common.xproc.XProcMonitor;
46 import org.daisy.pipeline.css.Medium;
47 import org.daisy.pipeline.css.CssAnalyzer;
48 import org.daisy.pipeline.css.CssAnalyzer.SassVariable;
49 import org.daisy.pipeline.css.UserAgentStylesheetRegistry;
50
51 import org.osgi.service.component.annotations.Component;
52 import org.osgi.service.component.annotations.Reference;
53 import org.osgi.service.component.annotations.ReferenceCardinality;
54 import org.osgi.service.component.annotations.ReferencePolicy;
55
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59 import org.w3c.dom.Document;
60 import org.w3c.dom.Node;
61
62 import org.xml.sax.InputSource;
63
64 public class CssAnalyzeStep extends DefaultStep implements XProcStep {
65
66 private ReadablePipe sourcePipe = null;
67 private ReadablePipe contextPipe = null;
68 private WritablePipe resultPipe = null;
69 private final InMemoryURIResolver inMemoryResolver;
70 private final URIResolver cssURIResolver;
71 private final UserAgentStylesheetRegistry userAgentStylesheets;
72
73 private final static QName c_param_set = new QName(XProcConstants.NS_XPROC_STEP, "param-set");
74 private final static QName c_param = new QName(XProcConstants.NS_XPROC_STEP, "param");
75 private final static QName _name = new QName("name");
76 private final static QName _value = new QName("value");
77 private static final net.sf.saxon.s9api.QName _user_stylesheet = new net.sf.saxon.s9api.QName("user-stylesheet");
78 private static final net.sf.saxon.s9api.QName _include_user_agent_stylesheet
79 = new net.sf.saxon.s9api.QName("include-user-agent-stylesheet");
80 private static final net.sf.saxon.s9api.QName _parameters = new net.sf.saxon.s9api.QName("parameters");
81 private static final net.sf.saxon.s9api.QName _content_type = new net.sf.saxon.s9api.QName("content-type");
82 private static final net.sf.saxon.s9api.QName _media = new net.sf.saxon.s9api.QName("media");
83
84 private static final String DEFAULT_MEDIUM = "embossed";
85
86 private CssAnalyzeStep(XProcRuntime runtime, XAtomicStep step,
87 URIResolver resolver, UserAgentStylesheetRegistry userAgentStylesheets) {
88 super(runtime, step);
89 inMemoryResolver = new InMemoryURIResolver();
90 cssURIResolver = fallback(inMemoryResolver, resolver, simpleURIResolver);
91 this.userAgentStylesheets = userAgentStylesheets;
92 }
93
94 @Override
95 public void setInput(String port, ReadablePipe pipe) {
96 if ("source".equals(port))
97 sourcePipe = pipe;
98 else
99 contextPipe = pipe;
100 }
101
102 @Override
103 public void setOutput(String port, WritablePipe pipe) {
104 resultPipe = pipe;
105 }
106
107 @Override
108 public void reset() {
109 sourcePipe.resetReader();
110 contextPipe.resetReader();
111 resultPipe.resetWriter();
112 }
113
114 @Override
115 public void run() throws SaxonApiException {
116 super.run();
117 try {
118 Map<String,String> params = new LinkedHashMap<>();
119 RuntimeValue paramOption = getOption(_parameters);
120 if (paramOption != null)
121 for (Map.Entry<String,Object> e
122 : SaxonHelper.mapFromMapItem(
123 (MapItem)SaxonHelper.getSingleItem(paramOption.getValue().getUnderlyingValue()),
124 Object.class
125 ).entrySet())
126 params.put(e.getKey(), "" + e.getValue());
127 List<Medium> media = Medium.parseMultiple(getOption(_media, DEFAULT_MEDIUM));
128 inMemoryResolver.setContext(contextPipe);
129 Node doc = new XMLCalabashInputValue(sourcePipe).ensureSingleItem().asNodeIterator().next();
130 if (!(doc instanceof Document))
131 throw new IllegalArgumentException();
132 URI baseURI = new URI(doc.getBaseURI());
133 Source sourceDocument = new DOMSource(doc, baseURI.toASCIIString());
134 List<Source> stylesheets = new ArrayList<>(); {
135 if (getOption(_include_user_agent_stylesheet, false))
136 for (URL u : userAgentStylesheets.get(Collections.singleton("text/x-scss"),
137 Arrays.asList(getOption(_content_type, "").trim().split("\\s+")),
138 media))
139 stylesheets.add(
140 new SAXSource(new InputSource(u.toString())));
141 String s = getOption(_user_stylesheet, "");
142 if (s != null) {
143 StringTokenizer t = new StringTokenizer(s);
144 while (t.hasMoreTokens())
145 stylesheets.add(
146 new SAXSource(
147 new InputSource(URLs.resolve(baseURI, URLs.asURI(t.nextToken())).toASCIIString())));
148 }
149 }
150 for (SassVariable v : new CssAnalyzer(media, cssURIResolver, null)
151 .analyze(stylesheets, sourceDocument)
152 .getVariables()) {
153 if (params.containsKey(v.getName()) && v.isDefault())
154 continue;
155 params.put(v.getName(), v.getValue());
156 }
157 XMLStreamWriter writer = new XMLCalabashOutputValue(resultPipe, runtime).asXMLStreamWriter();
158 writer.writeStartDocument();
159 writeStartElement(writer, c_param_set);
160 for (String p : params.keySet()) {
161 writeStartElement(writer, c_param);
162 writeAttribute(writer, _name, p);
163 writeAttribute(writer, _value, params.get(p));
164 writer.writeEndElement();
165 }
166 writer.writeEndElement();
167 writer.writeEndDocument();
168 } catch (Throwable e) {
169 throw XProcStep.raiseError(e, step);
170 }
171 }
172
173 @Component(
174 name = "pxi:css-analyze",
175 service = { XProcStepProvider.class },
176 property = { "type:String={http://www.daisy.org/ns/pipeline/xproc/internal}css-analyze" }
177 )
178 public static class Provider implements XProcStepProvider {
179
180 private URIResolver resolver;
181 private UserAgentStylesheetRegistry userAgentStylesheets;
182
183 @Override
184 public XProcStep newStep(XProcRuntime runtime, XAtomicStep step, XProcMonitor monitor, Map<String,String> properties) {
185 return new CssAnalyzeStep(runtime, step, resolver, userAgentStylesheets);
186 }
187
188 @Reference(
189 name = "URIResolver",
190 unbind = "-",
191 service = URIResolver.class,
192 cardinality = ReferenceCardinality.MANDATORY,
193 policy = ReferencePolicy.STATIC
194 )
195 public void setUriResolver(URIResolver resolver) {
196 this.resolver = resolver;
197 }
198
199 @Reference(
200 name = "UserAgentStylesheetRegistry",
201 unbind = "-",
202 service = UserAgentStylesheetRegistry.class,
203 cardinality = ReferenceCardinality.MANDATORY,
204 policy = ReferencePolicy.STATIC
205 )
206 public void setUserAgentStylesheetRegistry(UserAgentStylesheetRegistry registry) {
207 this.userAgentStylesheets = registry;
208 }
209 }
210
211 private static URIResolver simpleURIResolver = new URIResolver() {
212 @Override
213 public Source resolve(String href, String base) throws TransformerException {
214 try {
215 URI uri; {
216 if (base != null)
217 uri = new URI(base).resolve(new URI(href));
218 else
219 uri = new URI(href);
220 }
221 return new SAXSource(new InputSource(uri.toASCIIString()));
222 } catch (URISyntaxException e) {
223 throw new TransformerException(e);
224 }
225 }
226 };
227
228 private static URIResolver fallback(URIResolver... resolvers) {
229 return new URIResolver() {
230 public Source resolve(String href, String base) throws javax.xml.transform.TransformerException {
231 Iterator<URIResolver> iterator = forArray(resolvers);
232 while (iterator.hasNext()) {
233 Source source = iterator.next().resolve(href, base);
234 if (source != null)
235 return source; }
236 return null;
237 }
238 };
239 }
240
241 private static final Logger logger = LoggerFactory.getLogger(CssAnalyzeStep.class);
242
243 }