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