1   package org.daisy.pipeline.braille.css.calabash.impl;
2   
3   import java.util.ArrayList;
4   import java.util.Iterator;
5   import java.util.List;
6   import java.util.Map;
7   import java.util.NoSuchElementException;
8   import java.util.Stack;
9   
10  import javax.xml.namespace.QName;
11  import static javax.xml.stream.XMLStreamConstants.END_ELEMENT;
12  import static javax.xml.stream.XMLStreamConstants.START_ELEMENT;
13  import javax.xml.stream.XMLStreamException;
14  import javax.xml.stream.XMLStreamWriter;
15  
16  import com.xmlcalabash.core.XProcRuntime;
17  import com.xmlcalabash.io.ReadablePipe;
18  import com.xmlcalabash.io.WritablePipe;
19  import com.xmlcalabash.library.DefaultStep;
20  import com.xmlcalabash.runtime.XAtomicStep;
21  
22  import cz.vutbr.web.css.Term;
23  import cz.vutbr.web.css.TermList;
24  import cz.vutbr.web.css.TermPair;
25  
26  import net.sf.saxon.s9api.SaxonApiException;
27  
28  import org.daisy.braille.css.BrailleCSSProperty.StringSet;
29  import org.daisy.braille.css.PropertyValue;
30  import org.daisy.common.stax.BaseURIAwareXMLStreamReader;
31  import org.daisy.common.stax.BaseURIAwareXMLStreamWriter;
32  import org.daisy.common.stax.XMLStreamWriterHelper.BufferedXMLStreamWriter;
33  import org.daisy.common.stax.XMLStreamWriterHelper.FutureWriterEvent;
34  import static org.daisy.common.stax.XMLStreamWriterHelper.writeAttribute;
35  import static org.daisy.common.stax.XMLStreamWriterHelper.writeAttributes;
36  import static org.daisy.common.stax.XMLStreamWriterHelper.writeEvent;
37  import static org.daisy.common.stax.XMLStreamWriterHelper.writeStartElement;
38  import org.daisy.common.transform.InputValue;
39  import org.daisy.common.transform.SingleInSingleOutXMLTransformer;
40  import org.daisy.common.transform.TransformerException;
41  import org.daisy.common.transform.XMLInputValue;
42  import org.daisy.common.transform.XMLOutputValue;
43  import org.daisy.common.xproc.calabash.XMLCalabashInputValue;
44  import org.daisy.common.xproc.calabash.XMLCalabashOutputValue;
45  import org.daisy.common.xproc.calabash.XProcStep;
46  import org.daisy.common.xproc.calabash.XProcStepProvider;
47  import org.daisy.common.xproc.XProcMonitor;
48  import org.daisy.pipeline.braille.css.impl.BrailleCssSerializer;
49  
50  import org.osgi.service.component.annotations.Component;
51  
52  import org.slf4j.Logger;
53  import org.slf4j.LoggerFactory;
54  
55  public class CssShiftStringSetStep extends DefaultStep implements XProcStep {
56  	
57  	@Component(
58  		name = "css:shift-string-set",
59  		service = { XProcStepProvider.class },
60  		property = { "type:String={http://www.daisy.org/ns/pipeline/braille-css}shift-string-set" }
61  	)
62  	public static class Provider implements XProcStepProvider {
63  		
64  		@Override
65  		public XProcStep newStep(XProcRuntime runtime, XAtomicStep step, XProcMonitor monitor, Map<String,String> properties) {
66  			return new CssShiftStringSetStep(runtime, step);
67  		}
68  	}
69  	
70  	private ReadablePipe sourcePipe = null;
71  	private WritablePipe resultPipe = null;
72  	
73  	private static final String XMLNS_CSS = "http://www.daisy.org/ns/pipeline/braille-css";
74  	private static final QName CSS_STRING_SET = new QName(XMLNS_CSS, "string-set");
75  	private static final QName CSS_BOX = new QName(XMLNS_CSS, "box");
76  	private static final QName CSS__ = new QName(XMLNS_CSS, "_");
77  	private static final QName _TYPE = new QName("type");
78  	
79  	private CssShiftStringSetStep(XProcRuntime runtime, XAtomicStep step) {
80  		super(runtime, step);
81  	}
82  	
83  	@Override
84  	public void setInput(String port, ReadablePipe pipe) {
85  		sourcePipe = pipe;
86  	}
87  	
88  	@Override
89  	public void setOutput(String port, WritablePipe pipe) {
90  		resultPipe = pipe;
91  	}
92  	
93  	@Override
94  	public void reset() {
95  		sourcePipe.resetReader();
96  		resultPipe.resetWriter();
97  	}
98  	
99  	@Override
100 	public void run() throws SaxonApiException {
101 		super.run();
102 		try {
103 			new CssShiftStringSetTransformer()
104 			.transform(
105 				new XMLCalabashInputValue(sourcePipe),
106 				new XMLCalabashOutputValue(resultPipe, runtime))
107 			.run(); }
108 		catch (Throwable e) {
109 			throw XProcStep.raiseError(e, step); }
110 	}
111 	
112 	private static class CssShiftStringSetTransformer extends SingleInSingleOutXMLTransformer {
113 		
114 		public Runnable transform(XMLInputValue<?> source, XMLOutputValue<?> result, InputValue<?> params) throws IllegalArgumentException {
115 			if (source == null || result == null)
116 				throw new IllegalArgumentException();
117 			return () -> transform(source.ensureSingleItem().asXMLStreamReader(), result.asXMLStreamWriter());
118 		}
119 		
120 		void transform(BaseURIAwareXMLStreamReader reader, BaseURIAwareXMLStreamWriter output) throws TransformerException {
121 			BufferedXMLStreamWriter writer = new BufferedXMLStreamWriter(output);
122 			boolean insideInlineBox = false;
123 			Stack<Boolean> blockBoxes = new Stack<Boolean>();
124 			Stack<Boolean> inlineBoxes = new Stack<Boolean>();
125 			List<TermPair<String,TermList>> pendingStringSet = new ArrayList<TermPair<String,TermList>>();
126 			ShiftedStringSet shiftedStringSet = null;
127 			try {
128 				int event = reader.getEventType();
129 				while (true)
130 					try {
131 						switch (event) {
132 						case START_ELEMENT: {
133 							writeEvent(writer, reader);
134 							boolean isInlineBox = false;
135 							boolean isBlockBox = false;
136 							if (insideInlineBox)
137 								writeAttributes(writer, reader);
138 							else {
139 								boolean isBox = CSS_BOX.equals(reader.getName());
140 								String stringSet = null;
141 								for (int i = 0; i < reader.getAttributeCount(); i++) {
142 									QName name = reader.getAttributeName(i);
143 									String value = reader.getAttributeValue(i);
144 									if (CSS_STRING_SET.equals(name))
145 										stringSet = value;
146 									else {
147 										if (isBox && _TYPE.equals(name))
148 											if ("inline".equalsIgnoreCase(value))
149 												isInlineBox = true;
150 											else if ("block".equalsIgnoreCase(value))
151 												isBlockBox = true;
152 										writeAttribute(writer, name, value); }}
153 								if (isBlockBox || isInlineBox)
154 									if (shiftedStringSet != null) {
155 										shiftedStringSet.render();
156 										shiftedStringSet = null; }
157 								if (isInlineBox) {
158 									if (stringSet != null)
159 										if (!pendingStringSet.isEmpty())
160 											parseStringSet(stringSet, pendingStringSet);
161 										else
162 											writeAttribute(writer, CSS_STRING_SET, stringSet);
163 									if (!pendingStringSet.isEmpty()) {
164 										stringSet = serializeStringSet(pendingStringSet);
165 										pendingStringSet.clear();
166 										if (stringSet != null)
167 											writeAttribute(writer, CSS_STRING_SET, stringSet); }}
168 								else if (stringSet != null)
169 									parseStringSet(stringSet, pendingStringSet);
170 								if (isInlineBox)
171 									insideInlineBox = true; }
172 							blockBoxes.push(isBlockBox);
173 							inlineBoxes.push(isInlineBox);
174 							break; }
175 						case END_ELEMENT: {
176 							boolean isBlockBox = blockBoxes.pop();
177 							boolean isInlineBox = inlineBoxes.pop();
178 							if (isBlockBox) {
179 								if (!pendingStringSet.isEmpty()) {
180 									if (shiftedStringSet == null)
181 										throw new RuntimeException();
182 									else
183 										shiftedStringSet.putAll(pendingStringSet);
184 									pendingStringSet.clear(); }}
185 							if (isInlineBox) {
186 								if (shiftedStringSet != null)
187 									throw new RuntimeException("coding error");
188 								shiftedStringSet = new ShiftedStringSet();
189 								writer.writeEvent(shiftedStringSet); }
190 							if (isInlineBox)
191 								insideInlineBox = false;
192 							writeEvent(writer, reader);
193 							break; }
194 						default:
195 							writeEvent(writer, reader); }
196 						event = reader.next(); }
197 					catch (NoSuchElementException e) {
198 						break; }
199 				if (!pendingStringSet.isEmpty())
200 					if (shiftedStringSet == null)
201 						throw new RuntimeException("invalid input");
202 					else
203 						shiftedStringSet.putAll(pendingStringSet);
204 				if (shiftedStringSet != null)
205 					shiftedStringSet.render();
206 				writer.flush(); }
207 			catch (XMLStreamException e) {
208 				throw new TransformerException(e); }
209 		}
210 		
211 		private static class ShiftedStringSet implements FutureWriterEvent {
212 			
213 			private List<TermPair<String,TermList>> stringSet;
214 			private boolean ready = false;
215 			
216 			private ShiftedStringSet() {
217 			}
218 			
219 			private void put(TermPair<String,TermList> stringSet) {
220 				if (this.stringSet == null)
221 					this.stringSet = new ArrayList<TermPair<String,TermList>>();
222 				this.stringSet.add(stringSet);
223 			}
224 			
225 			private void putAll(List<TermPair<String,TermList>> stringSet) {
226 				for (TermPair<String,TermList> s : stringSet)
227 					put(s);
228 			}
229 			
230 			private void render() {
231 				ready = true;
232 			}
233 			
234 			public void writeTo(XMLStreamWriter writer) throws XMLStreamException {
235 				if (!ready)
236 					throw new XMLStreamException("not ready");
237 				if (stringSet != null) {
238 					String value = serializeStringSet(stringSet);
239 					if (value != null)
240 						writeStartElement(writer, CSS__);
241 						writeAttribute(writer, CSS_STRING_SET, value);
242 						writer.writeEndElement(); }
243 			}
244 			
245 			public boolean isReady() {
246 				return ready;
247 			}
248 		}
249 	}
250 	
251 	private static void parseStringSet(String value, List<TermPair<String,TermList>> appendTo) {
252 		PropertyValue decl = PropertyValue.parse("string-set", value);
253 		if (decl != null) {
254 			StringSet stringSet = (StringSet)decl.getProperty();
255 			switch (stringSet) {
256 			case INHERIT:
257 				throw new RuntimeException("'string-set: inherit' not supported");
258 			case list_values:
259 				for (Term<?> t : (TermList)decl.getValue())
260 					appendTo.add((TermPair<String,TermList>)t);
261 				break;
262 			case NONE:
263 				break; }}
264 	}
265 	
266 	private static String serializeStringSet(List<TermPair<String,TermList>> stringSet) {
267 		if (stringSet.isEmpty())
268 			return null;
269 		StringBuilder s = new StringBuilder();
270 		Iterator<TermPair<String,TermList>> it = stringSet.iterator();
271 		while (it.hasNext()) {
272 			s.append(BrailleCssSerializer.toString(it.next()));
273 			if (it.hasNext()) s.append(", "); }
274 		return s.toString();
275 	}
276 	
277 	private static final Logger logger = LoggerFactory.getLogger(CssShiftStringSetStep.class);
278 	
279 }