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