1   package org.daisy.pipeline.braille.common.impl;
2   
3   import java.net.URI;
4   import java.util.Map;
5   
6   import javax.xml.namespace.QName;
7   
8   import com.google.common.base.MoreObjects;
9   import com.google.common.base.MoreObjects.ToStringHelper;
10  import com.google.common.collect.ImmutableMap;
11  
12  import com.xmlcalabash.core.XProcRuntime;
13  import com.xmlcalabash.runtime.XAtomicStep;
14  
15  import org.daisy.common.file.URLs;
16  import org.daisy.common.saxon.SaxonInputValue;
17  import org.daisy.common.transform.InputValue;
18  import org.daisy.common.transform.Mult;
19  import org.daisy.common.transform.SingleInSingleOutXMLTransformer;
20  import org.daisy.common.transform.TransformerException;
21  import org.daisy.common.transform.XMLInputValue;
22  import org.daisy.common.transform.XMLOutputValue;
23  import org.daisy.common.xproc.calabash.XProcStep;
24  import org.daisy.common.xproc.calabash.XProcStepProvider;
25  import org.daisy.common.xproc.XProcMonitor;
26  import org.daisy.pipeline.braille.common.AbstractBrailleTranslator;
27  import org.daisy.pipeline.braille.common.AbstractTransformProvider;
28  import org.daisy.pipeline.braille.common.AbstractTransformProvider.util.Function;
29  import org.daisy.pipeline.braille.common.AbstractTransformProvider.util.Iterables;
30  import static org.daisy.pipeline.braille.common.AbstractTransformProvider.util.Iterables.transform;
31  import static org.daisy.pipeline.braille.common.AbstractTransformProvider.util.logCreate;
32  import static org.daisy.pipeline.braille.common.AbstractTransformProvider.util.logSelect;
33  import org.daisy.pipeline.braille.common.BrailleTranslator;
34  import org.daisy.pipeline.braille.common.BrailleTranslatorProvider;
35  import org.daisy.pipeline.braille.common.BrailleTranslatorRegistry;
36  import org.daisy.pipeline.braille.common.calabash.CxEvalBasedTransformer;
37  import org.daisy.pipeline.braille.common.Hyphenator;
38  import org.daisy.pipeline.braille.common.HyphenatorRegistry;
39  import org.daisy.pipeline.braille.common.Query;
40  import org.daisy.pipeline.braille.common.Query.Feature;
41  import org.daisy.pipeline.braille.common.Query.MutableQuery;
42  import static org.daisy.pipeline.braille.common.Query.util.mutableQuery;
43  import org.daisy.pipeline.braille.common.TransformProvider;
44  import org.daisy.pipeline.braille.common.util.Function0;
45  import org.daisy.pipeline.braille.common.util.Functions;
46  
47  import org.osgi.service.component.annotations.Activate;
48  import org.osgi.service.component.annotations.Component;
49  import org.osgi.service.component.annotations.Reference;
50  import org.osgi.service.component.annotations.ReferenceCardinality;
51  import org.osgi.service.component.annotations.ReferencePolicy;
52  
53  import org.slf4j.Logger;
54  import org.slf4j.LoggerFactory;
55  
56  import org.w3c.dom.Document;
57  import org.w3c.dom.Node;
58  
59  /**
60   * @see <a href="../../../../../../../README.md">Documentation</a>
61   * @see <a href="../../../../../../../resources/xml/block-translator.xpl">XProc code</a>
62   */
63  public interface CSSBlockTransform {
64  	
65  	@Component(
66  		name = "org.daisy.pipeline.braille.common.impl.CSSBlockTransform.Provider",
67  		service = {
68  			BrailleTranslatorProvider.class,
69  			TransformProvider.class
70  		}
71  	)
72  	public class Provider extends AbstractTransformProvider<BrailleTranslator> implements BrailleTranslatorProvider<BrailleTranslator> {
73  		
74  		private URI href;
75  		
76  		@Activate
77  		protected void activate(final Map<?,?> properties) {
78  			href = URLs.asURI(URLs.getResourceFromJAR("xml/block-translator.xpl", CSSBlockTransform.class));
79  			translatorRegistry = translatorRegistry.withContext(logger);
80  		}
81  		
82  		private final static Iterable<BrailleTranslator> empty = Iterables.<BrailleTranslator>empty();
83  		
84  		protected Iterable<BrailleTranslator> _get(Query query) {
85  			final MutableQuery q = mutableQuery(query);
86  			for (Feature f : q.removeAll("input"))
87  				if ("html".equals(f.getValue().get())) {}
88  				else if (!"css".equals(f.getValue().get()))
89  					return empty;
90  			boolean braille = false; {
91  				for (Feature f : q.removeAll("output"))
92  					if ("css".equals(f.getValue().get())) {}
93  					else if ("html".equals(f.getValue().get())) {}
94  					else if ("braille".equals(f.getValue().get()))
95  						braille = true;
96  					else
97  						return empty;
98  			}
99  			final String brailleCharset = q.containsKey("braille-charset") ? q.getOnly("braille-charset").getValue().get() : null;
100 			q.add("input", "text-css");
101 			if (braille)
102 				q.add("output", "braille");
103 			return transform(
104 				logSelect(q, translatorRegistry.getWithHyphenator(q)),
105 				new Function<BrailleTranslator,BrailleTranslator>() {
106 					public BrailleTranslator _apply(BrailleTranslator translator) {
107 						return __apply(
108 							logCreate(new TransformImpl(translator, false, brailleCharset, q))
109 						);
110 					}
111 				}
112 			);
113 		}
114 			
115 		@Override
116 		public ToStringHelper toStringHelper() {
117 			return MoreObjects.toStringHelper("CSSBlockTransform$Provider");
118 		}
119 		
120 		private class TransformImpl extends AbstractBrailleTranslator implements XProcStepProvider {
121 			
122 			private final Query mainQuery;
123 			private final BrailleTranslator mainTranslator;
124 			private final boolean forceMainTranslator;
125 			private final Map<String,String> options;
126 			private final String brailleCharset;
127 			
128 			/**
129 			 * @param mainTranslator translator to be used for the parts of the document that do
130 			 *                       not have their own sub-translator
131 			 * @param forceMainTranslator if true, the main translator is used even if a default
132 			 *                            translator has been defined in CSS
133 			 */
134 			// FIXME: mainTranslator is optional if default translator has been defined in CSS (which we can not know in advance)
135 			private TransformImpl(BrailleTranslator mainTranslator, boolean forceMainTranslator,
136 			                      String brailleCharset, Query query) {
137 				options = ImmutableMap.of("braille-charset", brailleCharset != null ? brailleCharset : "");
138 				this.mainTranslator = mainTranslator;
139 				this.forceMainTranslator = forceMainTranslator;
140 				mainQuery = query;
141 				this.brailleCharset = brailleCharset;
142 			}
143 			
144 			private Hyphenator hyphenator = null;
145 			
146 			private TransformImpl(TransformImpl from, BrailleTranslator mainTranslator) {
147 				super(from);
148 				this.mainQuery = from.mainQuery;
149 				this.mainTranslator = mainTranslator;
150 				this.forceMainTranslator = from.forceMainTranslator;
151 				this.options = from.options;
152 				this.brailleCharset = from.brailleCharset;
153 			}
154 			
155 			/**
156 			 * @throws UnsupportedOperationException if {@code mainTranslator.withHyphenator()} throws
157 			 *                                       UnsupportedOperationException
158 			 */
159 			public TransformImpl _withHyphenator(Hyphenator hyphenator) throws UnsupportedOperationException {
160 				TransformImpl t = new TransformImpl(this, mainTranslator.withHyphenator(hyphenator));
161 				Provider.this.rememberId(t);
162 				return t;
163 			}
164 			
165 			@Override
166 			public XProcStep newStep(XProcRuntime runtime, XAtomicStep step, XProcMonitor monitor, Map<String,String> properties) {
167 				return XProcStep.of(
168 					new SingleInSingleOutXMLTransformer() {
169 						public Runnable transform(XMLInputValue<?> source, XMLOutputValue<?> result, InputValue<?> params) {
170 							return () -> {
171 								if (!(source instanceof SaxonInputValue))
172 									throw new IllegalArgumentException();
173 								Mult<SaxonInputValue> mult = ((SaxonInputValue)source).mult(2);
174 
175 								// analyze the input
176 								Node doc = mult.get().ensureSingleItem().asNodeIterator().next();
177 								if (!(doc instanceof Document))
178 									throw new TransformerException(new IllegalArgumentException());
179 								String style = (((Document)doc).getDocumentElement()).getAttribute("style");
180 								URI styleBaseURI = URLs.asURI(((Document)doc).getBaseURI());
181 								BrailleTranslator compoundTranslator
182 									= translatorRegistry.getWithHyphenator(mainQuery, style, styleBaseURI, forceMainTranslator)
183 									                    .iterator().next();
184 								if (hyphenator != null)
185 									compoundTranslator = compoundTranslator.withHyphenator(hyphenator);
186 								Function0<Void> evictTempTranslator; {
187 									if (compoundTranslator != mainTranslator)
188 										// translatorRegistry.get() call above probably returned an object that was not cached
189 										evictTempTranslator = Provider.this.provideTemporarily(compoundTranslator);
190 									else
191 										evictTempTranslator = Functions.noOp;
192 								}
193 
194 								// run the transformation
195 								new CxEvalBasedTransformer(
196 									href,
197 									null,
198 									ImmutableMap.<String,String>builder()
199 									            .putAll(options)
200 									            .put("text-transform",
201 									                 mutableQuery().add("id", compoundTranslator.getIdentifier()).toString())
202 									            .build()
203 								).newStep(runtime, step, monitor, properties).transform(
204 									ImmutableMap.of(
205 										new QName("source"), mult.get(),
206 										new QName("parameters"), params),
207 									ImmutableMap.of(
208 										new QName("result"), result)
209 								).run();
210 								evictTempTranslator.apply();
211 							};
212 						}
213 					},
214 					runtime,
215 					step
216 				);
217 			}
218 			
219 			@Override
220 			public ToStringHelper toStringHelper() {
221 				return MoreObjects.toStringHelper("CSSBlockTransform$Provider$TransformImpl")
222 					.add("translator", mainTranslator);
223 			}
224 		}
225 		
226 		// FIXME: should ideally bind BrailleTranslatorRegistry, but does not work because
227 		// BrailleTranslatorRegistry binds BrailleTranslatorProvider, which CSSBlockTransform
228 		// implements (so there's a cyclic dependency)
229 		@Reference(
230 			name = "BrailleTranslatorProvider",
231 			unbind = "-",
232 			service = BrailleTranslatorProvider.class,
233 			cardinality = ReferenceCardinality.MULTIPLE,
234 			policy = ReferencePolicy.STATIC
235 		)
236 		protected void bindBrailleTranslatorProvider(BrailleTranslatorProvider<?> provider) {
237 			translatorRegistry.addProvider(provider);
238 		}
239 		
240 		@Reference(
241 			name = "HyphenatorRegistry",
242 			unbind = "-",
243 			service = HyphenatorRegistry.class,
244 			cardinality = ReferenceCardinality.MANDATORY,
245 			policy = ReferencePolicy.STATIC
246 		)
247 		protected void bindHyphenatorRegistry(HyphenatorRegistry registry) {
248 			translatorRegistry.bindHyphenatorRegistry(registry);
249 		}
250 		
251 		private BrailleTranslatorRegistry translatorRegistry = new BrailleTranslatorRegistry();
252 		
253 		private static final Logger logger = LoggerFactory.getLogger(Provider.class);
254 		
255 	}
256 }