1   package org.daisy.pipeline.braille.dotify.impl;
2   
3   import java.net.URI;
4   import java.util.HashMap;
5   import java.util.Iterator;
6   import java.util.Map;
7   import java.util.NoSuchElementException;
8   
9   import javax.xml.namespace.QName;
10  
11  import com.google.common.base.MoreObjects;
12  import com.google.common.base.MoreObjects.ToStringHelper;
13  import com.google.common.collect.ImmutableMap;
14  
15  import com.xmlcalabash.core.XProcRuntime;
16  import com.xmlcalabash.runtime.XAtomicStep;
17  
18  import org.daisy.common.file.URLs;
19  import org.daisy.common.transform.InputValue;
20  import org.daisy.common.transform.Mult;
21  import org.daisy.common.transform.SingleInSingleOutXMLTransformer;
22  import org.daisy.common.transform.XMLInputValue;
23  import org.daisy.common.transform.XMLOutputValue;
24  import org.daisy.common.xproc.calabash.XProcStep;
25  import org.daisy.common.xproc.calabash.XProcStepProvider;
26  import org.daisy.common.xproc.XProcMonitor;
27  import org.daisy.dotify.api.table.Table;
28  import org.daisy.pipeline.braille.common.AbstractTransform;
29  import org.daisy.pipeline.braille.common.AbstractTransformProvider;
30  import org.daisy.pipeline.braille.common.AbstractTransformProvider.util.Iterables;
31  import org.daisy.pipeline.braille.common.calabash.CxEvalBasedTransformer;
32  import static org.daisy.pipeline.braille.common.AbstractTransformProvider.util.logCreate;
33  import static org.daisy.pipeline.braille.common.AbstractTransformProvider.util.logSelect;
34  import org.daisy.pipeline.braille.common.BrailleTranslator;
35  import org.daisy.pipeline.braille.common.BrailleTranslatorRegistry;
36  import org.daisy.pipeline.braille.common.Query;
37  import org.daisy.pipeline.braille.common.Query.Feature;
38  import org.daisy.pipeline.braille.common.Query.MutableQuery;
39  import static org.daisy.pipeline.braille.common.Query.util.mutableQuery;
40  import static org.daisy.pipeline.braille.common.Query.util.query;
41  import org.daisy.pipeline.braille.common.Transform;
42  import org.daisy.pipeline.braille.common.TransformProvider;
43  import org.daisy.pipeline.braille.pef.TableRegistry;
44  
45  import org.osgi.service.component.annotations.Activate;
46  import org.osgi.service.component.annotations.Component;
47  import org.osgi.service.component.annotations.Reference;
48  import org.osgi.service.component.annotations.ReferenceCardinality;
49  import org.osgi.service.component.annotations.ReferencePolicy;
50  
51  import static org.slf4j.helpers.NOPLogger.NOP_LOGGER;
52  
53  /**
54   * @see <a href="../../../../../../../../../doc/">User documentation</a>.
55   */
56  public interface DotifyCSSStyledDocumentTransform {
57  	
58  	@Component(
59  		name = "org.daisy.pipeline.braille.dotify.impl.DotifyCSSStyledDocumentTransform.Provider",
60  		service = {
61  			TransformProvider.class
62  		}
63  	)
64  	public class Provider extends AbstractTransformProvider<Transform> {
65  		
66  		private URI href;
67  		
68  		@Activate
69  		protected void activate(final Map<?,?> properties) {
70  			href = URLs.asURI(URLs.getResourceFromJAR("xml/transform/dotify-transform.xpl", DotifyCSSStyledDocumentTransform.class));
71  		}
72  		
73  		private final static Iterable<Transform> empty = Iterables.<Transform>empty();
74  		
75  		protected Iterable<Transform> _get(Query query) {
76  			final MutableQuery q = mutableQuery(query);
77  			try {
78  				if ("css".equals(q.removeOnly("input").getValue().get())) {
79  					if (q.containsKey("formatter"))
80  						if (!"dotify".equals(q.removeOnly("formatter").getValue().get()))
81  							return empty;
82  					boolean braille = false;
83  					boolean pef = false;
84  					boolean obfl = false;
85  					for (Feature f : q.removeAll("output"))
86  						if ("pef".equals(f.getValue().get()))
87  							pef = true;
88  						else if ("obfl".equals(f.getValue().get()))
89  							obfl = true;
90  						else if ("braille".equals(f.getValue().get()))
91  							braille = true;
92  						else
93  							return empty;
94  					if ((pef && obfl) || !(pef || obfl))
95  						return empty;
96  					boolean forcePretranslation = false;
97  					if (q.containsKey("force-pre-translation")) {
98  						forcePretranslation = true;
99  						q.removeOnly("force-pre-translation");
100 					}
101 					Query textTransformQuery = mutableQuery(q).add("input", "text-css").add("output", "braille");
102 					if (logSelect(textTransformQuery, translatorRegistry).apply(NOP_LOGGER).iterator().hasNext()) {
103 						MutableQuery blockTransformQuery = null; {
104 							// only pre-translate if an intermediary OBFL with braille content is requested
105 							if (obfl && braille || forcePretranslation) {
106 								blockTransformQuery = mutableQuery(q).add("input", "css")
107 								                                     .add("output", "css")
108 								                                     .add("output", "braille");
109 								if (!logSelect(blockTransformQuery, translatorRegistry).iterator().hasNext())
110 									return empty;
111 							}
112 						}
113 						return AbstractTransformProvider.util.Iterables.of(
114 							logCreate(new TransformImpl(obfl, blockTransformQuery, textTransformQuery))); }}}
115 			catch (IllegalStateException e) {}
116 			return empty;
117 		}
118 		
119 		private class TransformImpl extends AbstractTransform implements XProcStepProvider {
120 			
121 			private final String output;
122 			private final Query blockTransformQuery;
123 			private final Query textTransformQuery;
124 			private final Map<String,String> options;
125 			
126 			private TransformImpl(boolean obfl,
127 			                      Query blockTransformQuery,
128 			                      Query textTransformQuery) {
129 				MutableQuery mode = mutableQuery(textTransformQuery);
130 				String locale = "und";
131 				if (mode.containsKey("document-locale"))
132 					locale = mode.removeOnly("document-locale").getValue().get();
133 				// (input:text-css) is assumed
134 				Iterator<Feature> input = mode.get("input").iterator();
135 				while (input.hasNext())
136 					if ("text-css".equals(input.next().getValue().get()))
137 						input.remove();
138 				// (output:braille) is assumed
139 				Iterator<Feature> output = mode.get("output").iterator();
140 				while (output.hasNext())
141 					if ("braille".equals(output.next().getValue().get()))
142 						output.remove();
143 				this.output = obfl ? "obfl" : "pef";
144 				options = ImmutableMap.of(
145 					"output", this.output,
146 					"css-block-transform", blockTransformQuery != null ? blockTransformQuery.toString() : "",
147 					"document-locale", locale,
148 					"text-transform", mode.toString());
149 				this.blockTransformQuery = blockTransformQuery;
150 				this.textTransformQuery = textTransformQuery;
151 			}
152 			
153 			@Override
154 			public XProcStep newStep(XProcRuntime runtime, XAtomicStep step, XProcMonitor monitor, Map<String,String> properties) {
155 				return XProcStep.of(
156 					new SingleInSingleOutXMLTransformer() {
157 						public Runnable transform(XMLInputValue<?> source, XMLOutputValue<?> result, InputValue<?> params) {
158 							return () -> {
159 								Map<String,String> options = TransformImpl.this.options;
160 								InputValue<?> paramsCopy = params;
161 								// get value of preview-table parameter
162 								try {
163 									Mult<? extends InputValue<?>> m = params.mult(2); // multiply params input
164 									paramsCopy = m.get();
165 									Map map = m.get().asObject(Map.class);
166 									Object v = map.get(new QName("preview-table"));
167 									if (v != null)
168 										if (v instanceof InputValue) {
169 											m = ((InputValue<?>)v).mult(2); // multiply preview-table value
170 											map.put(new QName("preview-table"), m.get());
171 											try {
172 												v = m.get().asObject();
173 												if (v instanceof String) {
174 													try {
175 														Table previewTable = tableRegistry.get(query((String)v)).iterator().next();
176 														// Check if the output charset of the braille translator can be
177 														// set to this table. If not, ignore preview-table.
178 														MutableQuery q = mutableQuery(textTransformQuery).add("braille-charset",
179 														                                                      previewTable.getIdentifier());
180 														if (logSelect(q, translatorRegistry).iterator().hasNext()) {
181 															q.removeAll("document-locale");
182 															options = new HashMap<>(); {
183 																options.putAll(TransformImpl.this.options);
184 																options.put("css-block-transform",
185 																            blockTransformQuery != null
186 																                ? mutableQuery(blockTransformQuery)
187 																                      .add("braille-charset", previewTable.getIdentifier())
188 																                      .toString()
189 																                : "");
190 																options.put("braille-charset", previewTable.getIdentifier());
191 															}
192 														}
193 													} catch (NoSuchElementException e) {
194 													}
195 												}
196 											} catch (UnsupportedOperationException e) {
197 											}
198 										}
199 								} catch (UnsupportedOperationException e) {
200 								}
201 								new CxEvalBasedTransformer(
202 									href,
203 									null,
204 									options
205 								).newStep(runtime, step, monitor, properties).transform(
206 									ImmutableMap.of(
207 										new QName("source"), source,
208 										new QName("parameters"), paramsCopy),
209 									ImmutableMap.of(
210 										new QName("result"), result)
211 								).run();
212 							};
213 						}
214 					},
215 					runtime,
216 					step
217 				);
218 			}
219 			
220 			@Override
221 			public ToStringHelper toStringHelper() {
222 				return MoreObjects.toStringHelper("DotifyCSSStyledDocumentTransform$Provider$TransformImpl")
223 					.add("output", output)
224 					.add("textTransform", textTransformQuery);
225 			}
226 		}
227 		
228 		@Reference(
229 			name = "BrailleTranslatorRegistry",
230 			unbind = "-",
231 			service = BrailleTranslatorRegistry.class,
232 			cardinality = ReferenceCardinality.MANDATORY,
233 			policy = ReferencePolicy.STATIC
234 		)
235 		protected void bindBrailleTranslatorRegistry(BrailleTranslatorRegistry registry) {
236 			translatorRegistry = registry;
237 		}
238 		
239 		private BrailleTranslatorRegistry translatorRegistry;
240 		
241 		@Reference(
242 			name = "TableRegistry",
243 			unbind = "-",
244 			service = TableRegistry.class,
245 			cardinality = ReferenceCardinality.MANDATORY,
246 			policy = ReferencePolicy.STATIC
247 		)
248 		protected void bindTableRegistry(TableRegistry registry) {
249 			tableRegistry = registry;
250 		}
251 		
252 		private TableRegistry tableRegistry;
253 		
254 		@Override
255 		public ToStringHelper toStringHelper() {
256 			return MoreObjects.toStringHelper("DotifyCSSStyledDocumentTransform$Provider");
257 		}
258 	}
259 }