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 }