1 package org.daisy.pipeline.braille.css.saxon.impl;
2
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.Comparator;
6 import java.util.concurrent.atomic.AtomicReference;
7 import java.util.Deque;
8 import java.util.HashMap;
9 import java.util.HashSet;
10 import java.util.Iterator;
11 import java.util.LinkedHashMap;
12 import java.util.LinkedList;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.NoSuchElementException;
16 import java.util.Set;
17
18 import javax.xml.namespace.QName;
19 import static javax.xml.stream.XMLStreamConstants.CHARACTERS;
20 import static javax.xml.stream.XMLStreamConstants.END_ELEMENT;
21 import static javax.xml.stream.XMLStreamConstants.START_ELEMENT;
22 import javax.xml.stream.XMLStreamException;
23 import javax.xml.stream.XMLStreamReader;
24 import javax.xml.stream.XMLStreamWriter;
25 import javax.xml.XMLConstants;
26
27 import com.google.common.base.Predicate;
28 import com.google.common.base.Splitter;
29 import com.google.common.collect.ImmutableList;
30
31 import cz.vutbr.web.css.Rule;
32 import cz.vutbr.web.css.RuleBlock;
33 import cz.vutbr.web.css.Selector;
34 import cz.vutbr.web.css.Selector.PseudoClass;
35 import cz.vutbr.web.css.Selector.SelectorPart;
36 import cz.vutbr.web.csskit.AbstractRuleBlock;
37
38 import org.daisy.braille.css.InlineStyle;
39 import org.daisy.braille.css.InlineStyle.RuleMainBlock;
40 import org.daisy.braille.css.InlineStyle.RuleRelativeBlock;
41 import org.daisy.braille.css.SelectorImpl;
42 import org.daisy.braille.css.SelectorImpl.PseudoClassImpl;
43 import org.daisy.braille.css.SelectorImpl.PseudoElementImpl;
44 import org.daisy.braille.css.SimpleInlineStyle;
45
46 import org.daisy.common.stax.XMLStreamWriterHelper.ToStringWriter;
47 import org.daisy.common.stax.XMLStreamWriterHelper.WriterEvent;
48 import org.daisy.common.transform.InputValue;
49 import org.daisy.common.transform.Mult;
50 import org.daisy.common.transform.SingleInSingleOutXMLTransformer;
51 import org.daisy.common.transform.TransformerException;
52 import org.daisy.common.transform.XMLInputValue;
53 import org.daisy.common.transform.XMLOutputValue;
54 import org.daisy.common.stax.BaseURIAwareXMLStreamReader;
55 import org.daisy.common.stax.BaseURIAwareXMLStreamWriter;
56 import static org.daisy.common.stax.XMLStreamWriterHelper.writeAttribute;
57 import static org.daisy.common.stax.XMLStreamWriterHelper.writeElement;
58 import static org.daisy.common.stax.XMLStreamWriterHelper.writeStartElement;
59 import org.daisy.pipeline.braille.css.CSSStyledText;
60 import org.daisy.pipeline.braille.css.impl.BrailleCssSerializer;
61 import org.daisy.pipeline.braille.css.TextStyleParser;
62
63 public class TableAsList extends SingleInSingleOutXMLTransformer {
64
65 private static final String XMLNS_CSS = "http://www.daisy.org/ns/pipeline/braille-css";
66
67 private static final QName _STYLE = new QName("style");
68 private static final QName _ID = new QName("id");
69 private static final QName XML_LANG = new QName(XMLConstants.XML_NS_URI, "lang", XMLConstants.XML_NS_PREFIX);
70
71 private static final String XMLNS_HTML = "http://www.w3.org/1999/xhtml";
72 private static final String XMLNS_DTB = "http://www.daisy.org/z3986/2005/dtbook/";
73
74 private static final String TABLE = "table";
75 private static final String THEAD = "thead";
76 private static final String TFOOT = "tfoot";
77 private static final String TBODY = "tbody";
78 private static final String TR = "tr";
79 private static final String TD = "td";
80 private static final String TH = "th";
81 private static final String COLGROUP = "colgroup";
82 private static final String COL = "col";
83
84 private static final QName _HEADERS = new QName("headers");
85 private static final QName _SCOPE = new QName("scope");
86 private static final QName _AXIS = new QName("axis");
87 private static final QName _ROWSPAN = new QName("rowspan");
88 private static final QName _COLSPAN = new QName("colspan");
89
90 private static final QName CSS_TABLE_HEADER_POLICY = new QName(XMLNS_CSS, "table-header-policy", "css");
91 private static final QName CSS_TABLE_BY = new QName(XMLNS_CSS, "table-by", "css");
92 private static final QName CSS_LIST_ITEM = new QName(XMLNS_CSS, "list-item", "css");
93 private static final QName CSS_LIST_HEADER = new QName(XMLNS_CSS, "list-header", "css");
94 private static final QName CSS_ID = new QName(XMLNS_CSS, "id", "css");
95 private static final QName CSS_FLOW = new QName(XMLNS_CSS, "flow", "css");
96
97 private static final Splitter HEADERS_SPLITTER = Splitter.on(' ').trimResults().omitEmptyStrings();
98 private static final Splitter AXIS_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();
99
100 private static final TextStyleParser cssParser = TextStyleParser.getInstance();
101
102 final List<String> axes;
103
104 TableAsList(String axes) {
105 this.axes = new ArrayList<String>(AXIS_SPLITTER.splitToList(axes));
106 if (this.axes.remove("auto"))
107 if (!this.axes.isEmpty())
108 throw new RuntimeException();
109 }
110
111 private List<WriterEvent> writeActionsBefore;
112 private List<WriterEvent> writeActionsAfter;
113 private List<TableCell> cells;
114 private Set<CellCoordinates> coveredCoordinates;
115
116 @Override
117 public Runnable transform(XMLInputValue<?> source, XMLOutputValue<?> result, InputValue<?> params)
118 throws IllegalArgumentException {
119
120 if (source == null || result == null)
121 throw new IllegalArgumentException();
122 return () -> transform(source.ensureSingleItem().mult(2), result.asXMLStreamWriter());
123 }
124
125 private void transform(Mult<? extends XMLInputValue<?>> source, BaseURIAwareXMLStreamWriter writer)
126 throws TransformerException {
127 XMLStreamReader reader = source.get().asXMLStreamReader();
128 try {
129 writeActionsBefore = new ArrayList<WriterEvent>();
130 writeActionsAfter = new ArrayList<WriterEvent>();
131 cells = new ArrayList<TableCell>();
132 coveredCoordinates = new HashSet<CellCoordinates>();
133 List<WriterEvent> writeActions = writeActionsBefore;
134 int depth = 0;
135 TableCell withinCell = null;
136 TableCell.RowType rowType = TableCell.RowType.TBODY;
137 int rowGroup = 1;
138 int row = 1;
139 int col = 1;
140 String namespace = null;
141 Deque<SimpleInlineStyle> inheritedStyle = new LinkedList<>();
142 Deque<String> inheritedLang = new LinkedList<>();
143 while (reader.hasNext()) {
144 switch (reader.next()) {
145 case START_ELEMENT: {
146 QName name = reader.getName();
147 depth++;
148 boolean isCell = false;
149 if (depth == 1) {
150 if (!isHTMLorDTBookElement(TABLE, name))
151 throw new RuntimeException("Expected table element (html|dtb).");
152 if (XMLNS_HTML.equals(name.getNamespaceURI()))
153 namespace = XMLNS_HTML;
154 else if (XMLNS_DTB.equals(name.getNamespaceURI()))
155 namespace = XMLNS_DTB; }
156 else if (isHTMLorDTBookElement(THEAD, name) ||
157 isHTMLorDTBookElement(TFOOT, name) ||
158 isHTMLorDTBookElement(TBODY, name) ||
159 isHTMLorDTBookElement(TR, name)) {
160 rowType = isHTMLorDTBookElement(THEAD, name)
161 ? TableCell.RowType.THEAD
162 : isHTMLorDTBookElement(TFOOT, name)
163 ? TableCell.RowType.TFOOT
164 : isHTMLorDTBookElement(TBODY, name)
165 ? TableCell.RowType.TBODY
166 : rowType;
167 ;
168 String style = null; {
169 for (int i = 0; i < reader.getAttributeCount(); i++) {
170 if (_STYLE.equals(reader.getAttributeName(i))) {
171 style = reader.getAttributeValue(i);
172 break; }}}
173
174
175
176 if (style == null) style = "";
177 SimpleInlineStyle s = cssParser.parse(style);
178 if (!inheritedStyle.isEmpty())
179 s = s.inheritFrom(inheritedStyle.peek()).concretize();
180 inheritedStyle.push(s);
181 String lang = null; {
182 for (int i = 0; i < reader.getAttributeCount(); i++) {
183 if (XML_LANG.equals(reader.getAttributeName(i))) {
184 lang = reader.getAttributeValue(i);
185 break; }}}
186 inheritedLang.push(lang != null ? lang : inheritedLang.isEmpty() ? null : inheritedLang.peek());
187 break; }
188 else if (isHTMLorDTBookElement(COLGROUP, name) || isHTMLorDTBookElement(COL, name))
189 throw new RuntimeException("Elements colgroup and col not supported yet.");
190 if (isHTMLorDTBookElement(TD, name) || isHTMLorDTBookElement(TH, name)) {
191 isCell = true;
192 withinCell = new TableCell();
193 withinCell.row = row;
194 withinCell.col = col;
195 withinCell.rowGroup = rowGroup;
196 withinCell.rowType = rowType;
197 withinCell.ns = namespace;
198 setCovered(row, col);
199 cells.add(withinCell);
200 if (isHTMLorDTBookElement(TH, name))
201 withinCell.type = TableCell.CellType.TH;
202 String style = null; {
203 for (int i = 0; i < reader.getAttributeCount(); i++) {
204 if (_STYLE.equals(reader.getAttributeName(i))) {
205 style = reader.getAttributeValue(i);
206 break; }}}
207 if (style == null) style = "";
208 withinCell.style = cssParser.parse(style);
209 if (!inheritedStyle.isEmpty())
210 withinCell.style = withinCell.style.inheritFrom(inheritedStyle.peek()).concretize();
211 withinCell.lang = inheritedLang.isEmpty() ? null : inheritedLang.peek();
212 writeActions = withinCell.content; }
213 else {
214 if (withinCell != null) {
215 String flow = "normal";
216 for (int i = 0; i < reader.getAttributeCount(); i++)
217 if (CSS_FLOW.equals(reader.getAttributeName(i))) {
218 flow = reader.getAttributeValue(i);
219 break; }
220 if (!"normal".equals(flow)) {
221 writeActions.add(writeElementOnce(reader));
222 break; }}
223 writeActions.add(w -> writeStartElement(w, name)); }
224 for (int i = 0; i < reader.getNamespaceCount(); i++) {
225 String prf = reader.getNamespacePrefix(i);
226 String ns = reader.getNamespaceURI(i);
227 writeActions.add(w -> w.writeNamespace(prf, ns)); }
228 for (int i = 0; i < reader.getAttributeCount(); i++) {
229 QName attrName = reader.getAttributeName(i);
230 String attrValue = reader.getAttributeValue(i);
231 if (CSS_ID.equals(attrName)) {
232 WriteOnlyOnce writeOnlyOnce = new WriteOnlyOnce();
233 writeOnlyOnce.add(w -> writeAttribute(w, attrName, attrValue));
234 writeActions.add(writeOnlyOnce); }
235 else if (CSS_TABLE_HEADER_POLICY.equals(attrName)) {
236 if (isCell)
237 if ("once".equals(attrValue))
238 withinCell.headerPolicy = TableCell.HeaderPolicy.ONCE;
239 else if ("always".equals(attrValue))
240 withinCell.headerPolicy = TableCell.HeaderPolicy.ALWAYS;
241 else if ("front".equals(attrValue))
242 withinCell.headerPolicy = TableCell.HeaderPolicy.FRONT;
243 else
244 throw new RuntimeException(
245 "Expected value once|always for table-header-policy property but got " + attrValue); }
246 else if (isCell && _HEADERS.equals(attrName))
247 withinCell.headers = HEADERS_SPLITTER.splitToList(attrValue);
248 else if (isCell && _SCOPE.equals(attrName)) {
249 if ("row".equals(attrValue))
250 withinCell.scope = TableCell.Scope.ROW;
251 else if ("col".equals(attrValue))
252 withinCell.scope = TableCell.Scope.COL;
253 else if ("colgroup".equals(attrValue) || "rowgroup".equals(attrValue))
254 throw new RuntimeException(
255 "Value " + attrValue + " for scope attribute not supported yet.");
256 else
257 throw new RuntimeException(
258 "Expected value col|row|colgroup|rowgroup for scope attribute but got " + attrValue); }
259 else if (isCell && _AXIS.equals(attrName))
260 withinCell.axis = AXIS_SPLITTER.splitToList(attrValue);
261 else if (isCell && _ROWSPAN.equals(attrName)) {
262 try {
263 withinCell.rowspan = Integer.parseInt(attrValue);
264 if (withinCell.rowspan < 0)
265 throw new InvalidTableException("Invalid rowspan: " + attrValue);
266 else if (withinCell.rowspan == 0)
267 throw new RuntimeException("rowspan 0 not supported yet."); }
268 catch (NumberFormatException e) {
269 throw new InvalidTableException("Invalid rowspan: " + attrValue); }
270 for (int m = 1; m < withinCell.rowspan; m++)
271 for (int n = 0; n < withinCell.colspan; n++)
272 setCovered(row + m, col + n); }
273 else if (isCell && _COLSPAN.equals(attrName)) {
274 try {
275 withinCell.colspan = Integer.parseInt(attrValue);
276 if (withinCell.colspan < 0)
277 throw new InvalidTableException("Invalid colspan: " + attrValue);
278 else if (withinCell.colspan == 0)
279 throw new RuntimeException("colspan 0 not supported yet."); }
280 catch (NumberFormatException e) {
281 throw new InvalidTableException("Invalid colspan: " + attrValue); }
282 for (int m = 0; m < withinCell.rowspan; m++)
283 for (int n = 1; n < withinCell.colspan; n++)
284 setCovered(row + m, col + n); }
285
286
287 else if (isCell && _ID.equals(attrName))
288 withinCell.id = attrValue;
289 else if (depth == 1 && _STYLE.equals(attrName)) {
290 String newStyle; {
291 InlineStyle style = new InlineStyle(attrValue);
292 List<RuleBlock<? extends Rule<?>>> builder = new ArrayList<>();
293 for (RuleBlock<?> block : style) {
294 if (block instanceof RuleMainBlock)
295 builder.add(style.getMainStyle());
296 else if (block instanceof RuleRelativeBlock) {
297 RuleRelativeBlock ruleblock = (RuleRelativeBlock)block;
298 List<Selector> selector = ruleblock.getSelector();
299 if (selector.size() > 0) {
300 if (selector.get(0).size() > 0) {
301
302
303
304
305 if (selector.get(0).get(0) instanceof PseudoElementImpl) {
306
307
308
309
310 PseudoElementImpl pseudo = (PseudoElementImpl)selector.get(0).get(0);
311 if ("list-item".equals(pseudo.getName()))
312
313
314 ;
315 else if ("list-header".equals(pseudo.getName())) {
316 if (pseudo.getPseudoClasses().isEmpty())
317 addListHeaderStyle(
318 new ListItemStyle(rest(ruleblock))); }
319 else if ("table-by".equals(pseudo.getName())) {
320 String axis = pseudo.getArguments()[0];
321 if (pseudo.getPseudoClasses().isEmpty()) {
322 if (pseudo.hasStackedPseudoElement()) {
323 pseudo = pseudo.getStackedPseudoElement();
324 ruleblock = (RuleRelativeBlock)rest(ruleblock);
325 if ("list-item".equals(pseudo.getName()))
326 getTableByStyle(axis).addListItemStyle(
327 pseudo.getPseudoClasses(),
328 new ListItemStyle(rest(ruleblock)));
329 else if ("list-header".equals(pseudo.getName())) {
330 if (pseudo.getPseudoClasses().isEmpty())
331 getTableByStyle(axis).addListHeaderStyle(
332 new ListItemStyle(rest(ruleblock))); }
333 else
334 getTableByStyle(axis).addRuleBlock(ruleblock); }
335 else
336 getTableByStyle(axis).addRuleBlock(rest(ruleblock)); }}
337 else
338
339
340
341
342
343
344 builder.add(ruleblock); }
345 else
346 builder.add(ruleblock); }
347 else
348 builder.add(ruleblock); }}
349 else
350 throw new RuntimeException("Unexpected style " + block); }
351 newStyle = BrailleCssSerializer.serializeRuleBlockList(builder); }
352 if (!newStyle.isEmpty())
353 writeActions.add(w -> writeAttribute(w, attrName, newStyle)); }
354 else if (isCell && _STYLE.equals(attrName))
355 ;
356 else
357 writeActions.add(w -> writeAttribute(w, attrName, attrValue)); }
358 break; }
359 case CHARACTERS:
360 String chars = reader.getText();
361 writeActions.add(w -> w.writeCharacters(chars));
362 break;
363 case END_ELEMENT: {
364 QName name = reader.getName();
365 depth--;
366 if (isHTMLorDTBookElement(THEAD, name)
367 || isHTMLorDTBookElement(TFOOT, name)
368 || isHTMLorDTBookElement(TBODY, name)) {
369 inheritedStyle.pop();
370 inheritedLang.pop();
371 rowGroup++;
372 break; }
373 else if (isHTMLorDTBookElement(TR, name)) {
374 inheritedStyle.pop();
375 inheritedLang.pop();
376 row++;
377 col = 1;
378 while (isCovered(row, col)) col++;
379 break; }
380 if (isHTMLorDTBookElement(TD, name) || isHTMLorDTBookElement(TH, name)) {
381 withinCell = null;
382 writeActions = writeActionsAfter;
383 while (isCovered(row, col)) col++; }
384 else
385 writeActions.add(w -> w.writeEndElement());
386 break; }}}
387
388
389 List<TableCell> moreCells = new ArrayList<TableCell>();
390 for (TableCell c : cells)
391 if (!isHeader(c)) {
392 if (c.rowspan > 1) {
393 int span = c.rowspan;
394 c.rowspan = 1;
395 for (int i = 1; i < span; i++) {
396 TableCell dup = c.clone();
397 dup.row = c.row + i;
398 moreCells.add(dup); }}
399 if (c.colspan > 1) {
400 int span = c.colspan;
401 c.colspan = 1;
402 for (int i = 1; i < span; i++) {
403 TableCell dup = c.clone();
404 dup.col = c.col + i;
405 moreCells.add(dup); }}}
406 cells.addAll(moreCells);
407
408
409 Collections.sort(cells, compose(sortByRowType, sortByRow, sortByColumn));
410 rowGroup = 0;
411 row = 0;
412 int newRowGroup = rowGroup = 0;
413 int newRow = row = 0;
414 for (TableCell c : cells) {
415 if (c.rowGroup != rowGroup) {
416 rowGroup = c.rowGroup;
417 newRowGroup++; }
418 if (c.row != row) {
419 row = c.row;
420 newRow++; }
421 c.rowGroup = newRowGroup;
422 c.row = newRow; }
423
424 write(writer);
425 } catch (XMLStreamException e) {
426 throw new RuntimeException(e);
427 } catch (InvalidTableException e) {
428 String message = "Table structure broken: " + e.getMessage();
429 throw new TransformerException(
430 new RuntimeException(
431 message,
432
433 new RuntimeException(message + ":\n" + elementToString(source.get()))));
434 } catch (RuntimeException e) {
435 String message = "Error happened while processing table";
436 throw new TransformerException(
437 new RuntimeException(
438 message,
439
440 new RuntimeException(message + ":\n" + elementToString(source.get()), e)));
441 }
442 }
443
444 private boolean isHTMLorDTBookElement(String element, QName name) {
445 return ((XMLNS_HTML.equals(name.getNamespaceURI())
446 || XMLNS_DTB.equals(name.getNamespaceURI()))
447 && name.getLocalPart().equalsIgnoreCase(element));
448 }
449
450 private void setCovered(int row, int col) {
451 CellCoordinates coords = new CellCoordinates(row, col);
452 if (coveredCoordinates.contains(coords))
453 throw new InvalidTableException("cells overlap");
454 coveredCoordinates.add(coords);
455 }
456
457 private boolean isCovered(int row, int col) {
458 return coveredCoordinates.contains(new CellCoordinates(row, col));
459 }
460
461 private void write(XMLStreamWriter writer) throws XMLStreamException {
462 for (WriterEvent action : writeActionsBefore)
463 action.writeTo(writer);
464 List<TableCell> dataCells = new ArrayList<TableCell>();
465 for (TableCell c : cells)
466 if (!isHeader(c))
467 dataCells.add(c);
468 new TableCellGroup(dataCells, axes.iterator()).write(writer);
469 for (WriterEvent action : writeActionsAfter)
470 action.writeTo(writer);
471 }
472
473 private static final List<TableCell> emptyList = new ArrayList<TableCell>();
474
475 private abstract class TableCellCollection {
476
477
478
479
480 public abstract List<TableCell> newlyRenderedHeaders();
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495 public abstract List<TableCell> newlyPromotedHeaders();
496
497
498
499 public abstract void write(XMLStreamWriter writer) throws XMLStreamException;
500 }
501
502 private class SingleTableCell extends TableCellCollection {
503
504 private final TableCell cell;
505 private final TableCellGroup parent;
506 private final SingleTableCell precedingSibling;
507 private final boolean columnAppliedBeforeRow;
508
509 private SingleTableCell(TableCell cell, TableCellGroup parent, SingleTableCell precedingSibling, boolean columnAppliedBeforeRow) {
510 this.cell = cell;
511 this.parent = parent;
512 this.precedingSibling = precedingSibling;
513 this.columnAppliedBeforeRow = columnAppliedBeforeRow;
514 }
515
516
517 private List<TableCell> newlyAppliedHeaders;
518 public List<TableCell> newlyAppliedHeaders() {
519 if (newlyAppliedHeaders == null) {
520 newlyAppliedHeaders = new ArrayList<TableCell>();
521 for (TableCell h : findHeaders(cell, !columnAppliedBeforeRow))
522 if (!parent.appliedHeaders().contains(h))
523 newlyAppliedHeaders.add(h); }
524 return newlyAppliedHeaders;
525 }
526
527
528
529
530 private List<TableCell> newlyRenderedOrPromotedHeaders;
531 private List<TableCell> newlyRenderedOrPromotedHeaders() {
532 if (newlyRenderedOrPromotedHeaders == null) {
533 newlyRenderedOrPromotedHeaders = new ArrayList<TableCell>();
534 Iterator<TableCell> lastAppliedHeaders = (
535 precedingSibling != null ? precedingSibling.newlyAppliedHeaders() : emptyList
536 ).iterator();
537 boolean canOmit = true;
538
539 for (TableCell h : parent.deferredHeaders()) {
540 newlyRenderedOrPromotedHeaders.add(h);
541
542 canOmit = false; }
543 for (TableCell h : newlyAppliedHeaders()) {
544
545 if (canOmit
546 && h.headerPolicy != TableCell.HeaderPolicy.ALWAYS
547 && lastAppliedHeaders.hasNext() && lastAppliedHeaders.next().equals(h))
548 continue;
549
550 newlyRenderedOrPromotedHeaders.add(h);
551 canOmit = false; }}
552 return newlyRenderedOrPromotedHeaders;
553 }
554
555 private List<TableCell> newlyPromotedHeaders;
556 public List<TableCell> newlyPromotedHeaders() {
557 if (newlyPromotedHeaders == null) {
558 newlyPromotedHeaders = new ArrayList<TableCell>();
559 int i = newlyRenderedOrPromotedHeaders().size() - 1;
560 while (i >= 0 && newlyRenderedOrPromotedHeaders().get(i).headerPolicy != TableCell.HeaderPolicy.FRONT)
561 i--;
562 while (i >= 0)
563 newlyPromotedHeaders.add(0, newlyRenderedOrPromotedHeaders().get(i--)); }
564 return newlyPromotedHeaders;
565 }
566
567 private List<TableCell> newlyRenderedHeaders;
568 public List<TableCell> newlyRenderedHeaders() {
569 if (newlyRenderedHeaders == null) {
570 newlyRenderedHeaders = new ArrayList<TableCell>();
571 int i = newlyRenderedOrPromotedHeaders().size() - 1;
572 while (i >= 0 && newlyRenderedOrPromotedHeaders().get(i).headerPolicy != TableCell.HeaderPolicy.FRONT)
573 newlyRenderedHeaders.add(0, newlyRenderedOrPromotedHeaders().get(i--)); }
574 return newlyRenderedHeaders;
575 }
576
577 public void write(XMLStreamWriter writer) throws XMLStreamException {
578 cell.write(writer);
579 }
580
581 @Override
582 public String toString() {
583 ToStringWriter xml = new ToStringWriter();
584 try {
585 write(xml); }
586 catch (Exception e) {
587 throw new RuntimeException("coding error", e); }
588 StringBuilder s = new StringBuilder();
589 s.append("SingleTableCell[header: ").append(newlyRenderedHeaders());
590 s.append(", cell: ").append(cell);
591 s.append(", xml: ").append(xml).append("]");
592 return s.toString();
593 }
594 }
595
596 private class TableCellGroup extends TableCellCollection {
597
598 private final TableCellGroup parent;
599 private final TableCell groupingHeader;
600 private final String groupingAxis;
601 private final TableCellGroup precedingSibling;
602 private final boolean hasSubGroups;
603 private final String subGroupingAxis;
604 private final List<TableCellCollection> children;
605 private final boolean rowApplied;
606 private final boolean columnAppliedBeforeRow;
607
608 private TableCellGroup(List<TableCell> cells, Iterator<String> nextAxes) {
609 this(cells, nextAxes, null, null, null, null, false, false);
610 }
611
612 private TableCellGroup(List<TableCell> cells, Iterator<String> nextAxes,
613 TableCellGroup parent, TableCell groupingHeader, String groupingAxis,
614 TableCellGroup precedingSibling,
615 boolean rowApplied, boolean columnAppliedBeforeRow) {
616 this.parent = parent;
617 this.groupingHeader = groupingHeader;
618 this.groupingAxis = groupingAxis;
619 this.precedingSibling = precedingSibling;
620 this.rowApplied = rowApplied;
621 this.columnAppliedBeforeRow = columnAppliedBeforeRow;
622 children = groupCellsBy(cells, nextAxes);
623 hasSubGroups = !children.isEmpty() && children.get(0) instanceof TableCellGroup;
624 subGroupingAxis = hasSubGroups ? ((TableCellGroup)children.get(0)).groupingAxis : null;
625 }
626
627 private List<TableCellCollection> groupCellsBy(List<TableCell> cells, Iterator<String> axes) {
628 String firstAxis = axes.hasNext() ? axes.next() : null;
629 List<String> nextAxes = ImmutableList.copyOf(axes);
630
631
632
633
634 TableCellCollection transferChild
635 = (precedingSibling != null
636 && precedingSibling.newlyAppliedHeaders().isEmpty()
637 && newlyAppliedHeaders().isEmpty())
638 ? precedingSibling.children.get(precedingSibling.children.size() - 1)
639 : null;
640 if (firstAxis != null) {
641
642
643
644
645 Map<TableCell,List<TableCell>> categories = new LinkedHashMap<TableCell,List<TableCell>>();
646 List<TableCell> uncategorized = null;
647 for (TableCell c : cells) {
648 boolean categorized = false;
649 for (TableCell h : findHeaders(c, !columnAppliedBeforeRow))
650 if (h.axis != null && h.axis.contains(firstAxis)) {
651 List<TableCell> category = categories.get(h);
652 if (category == null) {
653 category = new ArrayList<TableCell>();
654 categories.put(h, category); }
655 category.add(c);
656 categorized = true; }
657 if (!categorized) {
658 if (uncategorized == null)
659 uncategorized = new ArrayList<TableCell>();
660 uncategorized.add(c); }}
661 if (!categories.isEmpty()) {
662 List<TableCellCollection> children = new ArrayList<TableCellCollection>();
663 TableCellGroup child = null;
664 if (transferChild instanceof TableCellGroup)
665 child = (TableCellGroup)transferChild;
666 for (TableCell h : categories.keySet()) {
667 child = new TableCellGroup(categories.get(h), nextAxes.iterator(), this, h, firstAxis, child,
668 rowApplied, columnAppliedBeforeRow);
669 children.add(child); }
670 if (uncategorized != null) {
671 child = new TableCellGroup(uncategorized, nextAxes.iterator(), this, null, firstAxis, child,
672 rowApplied, columnAppliedBeforeRow);
673 children.add(child); }
674 return children; }
675
676
677
678 else if ("row".equals(firstAxis)) {
679 List<TableCellCollection> children = new ArrayList<TableCellCollection>();
680 Map<Integer,List<TableCell>> rows = new LinkedHashMap<Integer,List<TableCell>>();
681 for (TableCell c : cells) {
682 List<TableCell> row = rows.get(c.row);
683 if (row == null) {
684 row = new ArrayList<TableCell>();
685 rows.put(c.row, row); }
686 row.add(c); }
687 TableCellGroup child = null;
688 if (transferChild instanceof TableCellGroup)
689 child = (TableCellGroup)transferChild;
690 for (List<TableCell> row : rows.values()) {
691 child = new TableCellGroup(row, nextAxes.iterator(), this, null, firstAxis, child, true, columnAppliedBeforeRow);
692 children.add(child); }
693 return children; }
694
695
696
697 else if ("column".equals(firstAxis) || "col".equals(firstAxis)) {
698 List<TableCellCollection> children = new ArrayList<TableCellCollection>();
699 Map<Integer,List<TableCell>> columns = new LinkedHashMap<Integer,List<TableCell>>();
700 for (TableCell c : cells) {
701 List<TableCell> column = columns.get(c.col);
702 if (column == null) {
703 column = new ArrayList<TableCell>();
704 columns.put(c.col, column); }
705 column.add(c); }
706 TableCellGroup child = null;
707 if (transferChild instanceof TableCellGroup)
708 child = (TableCellGroup)transferChild;
709 for (List<TableCell> column : columns.values()) {
710 child = new TableCellGroup(column, nextAxes.iterator(), this, null, firstAxis, child, rowApplied, !rowApplied);
711 children.add(child); }
712 return children; }
713
714
715
716 else if ("row-group".equals(firstAxis)) {
717 List<TableCellCollection> children = new ArrayList<TableCellCollection>();
718 Map<Integer,List<TableCell>> rowGroups = new LinkedHashMap<Integer,List<TableCell>>();
719 for (TableCell c : cells) {
720 List<TableCell> rowGroup = rowGroups.get(c.rowGroup);
721 if (rowGroup == null) {
722 rowGroup = new ArrayList<TableCell>();
723 rowGroups.put(c.rowGroup, rowGroup); }
724 rowGroup.add(c); }
725 TableCellGroup child = null;
726 if (transferChild instanceof TableCellGroup)
727 child = (TableCellGroup)transferChild;
728 for (List<TableCell> rowGroup : rowGroups.values()) {
729 child = new TableCellGroup(rowGroup, nextAxes.iterator(), this, null, firstAxis, child,
730 rowApplied, columnAppliedBeforeRow);
731 children.add(child); }
732 return children; }
733 else
734 return groupCellsBy(cells, nextAxes.iterator()); }
735 else {
736 List<TableCellCollection> children = new ArrayList<TableCellCollection>();
737 SingleTableCell child = null;
738 if (transferChild instanceof SingleTableCell)
739 child = (SingleTableCell)transferChild;
740 for (TableCell c : cells) {
741 child = new SingleTableCell(c, this, child, columnAppliedBeforeRow);
742 children.add(child); }
743 return children; }
744 }
745
746
747 private List<TableCell> newlyAppliedHeaders;
748 public List<TableCell> newlyAppliedHeaders() {
749 if (newlyAppliedHeaders == null) {
750 newlyAppliedHeaders = new ArrayList<TableCell>();
751 if (groupingHeader != null)
752 for (TableCell h : findHeaders(groupingHeader, !columnAppliedBeforeRow))
753 if (!previouslyAppliedHeaders().contains(h))
754 newlyAppliedHeaders.add(h); }
755 return newlyAppliedHeaders;
756 }
757
758
759 private List<TableCell> previouslyAppliedHeaders() {
760 if (parent != null)
761 return parent.appliedHeaders();
762 else
763 return emptyList;
764 }
765
766
767 private List<TableCell> appliedHeaders;
768 public List<TableCell> appliedHeaders() {
769 if (appliedHeaders == null) {
770 appliedHeaders = new ArrayList<TableCell>();
771 appliedHeaders.addAll(previouslyAppliedHeaders());
772 appliedHeaders.addAll(newlyAppliedHeaders()); }
773 return appliedHeaders;
774 }
775
776
777 private List<TableCell> newlyDeferredHeaders;
778 private List<TableCell> newlyDeferredHeaders() {
779 if (newlyDeferredHeaders == null) {
780 newlyDeferredHeaders = new ArrayList<TableCell>();
781 int i = newlyAppliedHeaders().size() - 1;
782 while (i >= 0 && newlyAppliedHeaders().get(i).headerPolicy == TableCell.HeaderPolicy.ALWAYS)
783 newlyDeferredHeaders.add(0, newlyAppliedHeaders().get(i--)); }
784 return newlyDeferredHeaders;
785 }
786
787
788 private List<TableCell> deferredHeaders;
789 private List<TableCell> deferredHeaders() {
790 if (deferredHeaders == null) {
791 deferredHeaders = new ArrayList<TableCell>();
792 if (newlyRenderedOrPromotedHeaders().isEmpty())
793 deferredHeaders.addAll(previouslyDeferredHeaders());
794 deferredHeaders.addAll(newlyDeferredHeaders()); }
795 return deferredHeaders;
796 }
797
798
799 private List<TableCell> previouslyDeferredHeaders() {
800 if (parent != null)
801 return parent.deferredHeaders();
802 else
803 return emptyList;
804 }
805
806
807
808
809 private List<TableCell> newlyRenderedOrPromotedHeaders;
810 private List<TableCell> newlyRenderedOrPromotedHeaders() {
811 if (newlyRenderedOrPromotedHeaders == null) {
812 newlyRenderedOrPromotedHeaders = new ArrayList<TableCell>();
813 int i = newlyAppliedHeaders().size() - 1;
814
815 while (i >= 0 && newlyAppliedHeaders().get(i).headerPolicy == TableCell.HeaderPolicy.ALWAYS)
816 i--;
817
818 if (i >= 0) {
819 Iterator<TableCell> lastAppliedHeaders = (
820 precedingSibling != null ? precedingSibling.newlyAppliedHeaders() : emptyList
821 ).iterator();
822 boolean canOmit = true;
823
824 for (TableCell h : previouslyDeferredHeaders()) {
825 newlyRenderedOrPromotedHeaders.add(h);
826
827 canOmit = false; }
828 for (int j = 0; j <= i; j++) {
829 TableCell h = newlyAppliedHeaders().get(j);
830
831 if (canOmit
832 && h.headerPolicy != TableCell.HeaderPolicy.ALWAYS
833 && lastAppliedHeaders.hasNext() && lastAppliedHeaders.next().equals(h))
834 continue;
835
836 newlyRenderedOrPromotedHeaders.add(h);
837 canOmit = false; }}}
838 return newlyRenderedOrPromotedHeaders;
839 }
840
841 private List<TableCell> newlyPromotedHeaders;
842 public List<TableCell> newlyPromotedHeaders() {
843 if (newlyPromotedHeaders == null) {
844 newlyPromotedHeaders = new ArrayList<TableCell>();
845 int i = newlyRenderedOrPromotedHeaders().size() - 1;
846 while (i >= 0 && newlyRenderedOrPromotedHeaders().get(i).headerPolicy != TableCell.HeaderPolicy.FRONT)
847 i--;
848 while (i >= 0)
849 newlyPromotedHeaders.add(0, newlyRenderedOrPromotedHeaders().get(i--)); }
850 return newlyPromotedHeaders;
851 }
852
853 private List<TableCell> newlyRenderedHeaders;
854 public List<TableCell> newlyRenderedHeaders() {
855 if (newlyRenderedHeaders == null) {
856 newlyRenderedHeaders = new ArrayList<TableCell>();
857 int i = newlyRenderedOrPromotedHeaders().size() - 1;
858 while (i >= 0 && newlyRenderedOrPromotedHeaders().get(i).headerPolicy != TableCell.HeaderPolicy.FRONT)
859 newlyRenderedHeaders.add(0, newlyRenderedOrPromotedHeaders().get(i--)); }
860 return newlyRenderedHeaders;
861 }
862
863 public void write(XMLStreamWriter writer) {
864 try {
865 if (hasSubGroups) {
866 writeStartElement(writer, CSS_TABLE_BY);
867 writeAttribute(writer, _AXIS, subGroupingAxis);
868 writeStyleAttribute(writer, getTableByStyle(subGroupingAxis)); }
869 List<List<TableCell>> promotedHeaders = null;
870 int i = 0;
871 for (TableCellCollection c : children) {
872 if (c instanceof TableCellGroup) {
873 TableCellGroup g = (TableCellGroup)c;
874 int j = 0;
875 for (TableCellCollection cc : g.children) {
876 if (!cc.newlyPromotedHeaders().isEmpty()) {
877 if (promotedHeaders == null) {
878 if (i == 0 && j == 0) {
879 writeStartElement(writer, CSS_LIST_HEADER);
880 writeStyleAttribute(writer, getTableByStyle(g.groupingAxis).getListHeaderStyle());
881 if (g.hasSubGroups) {
882 writeStartElement(writer, CSS_TABLE_BY);
883 writeAttribute(writer, _AXIS, g.subGroupingAxis);
884 writeStyleAttribute(writer, getTableByStyle(g.subGroupingAxis)); }
885 promotedHeaders = new ArrayList<List<TableCell>>(); }
886 else
887 throw new RuntimeException("Some headers of children promoted but not all children have a promoted header."); }
888 if (i == 0) {
889 if (g.hasSubGroups) {
890 writeStartElement(writer, CSS_LIST_ITEM);
891 Predicate<PseudoClass> matcher = matchesPosition(j + 1, g.children.size());
892 writeStyleAttribute(writer, getTableByStyle(g.subGroupingAxis).getListItemStyle(matcher)); }
893 for (TableCell h : cc.newlyPromotedHeaders())
894 h.write(writer);
895 if (g.hasSubGroups)
896 writer.writeEndElement();
897 promotedHeaders.add(cc.newlyPromotedHeaders()); }
898 else if (!promotedHeaders.get(j).equals(cc.newlyPromotedHeaders()))
899 throw new RuntimeException("Headers of children promoted but not the same as promoted headers of sibling groups."); }
900 else if (promotedHeaders != null)
901 throw new RuntimeException("Some headers of children promoted but not all children have a promoted header.");
902 j++; }
903 if (promotedHeaders != null && promotedHeaders.size() != j) {
904 throw new RuntimeException("Headers of children promoted but not the same as promoted headers of sibling groups."); }}
905 else if (promotedHeaders != null)
906 throw new RuntimeException("Coding error");
907 i++; }
908 if (promotedHeaders != null) {
909 if (((TableCellGroup)children.get(0)).hasSubGroups)
910 writer.writeEndElement();
911 writer.writeEndElement(); }
912 i = 0;
913 for (TableCellCollection c : children) {
914 if (hasSubGroups) {
915 writeStartElement(writer, CSS_LIST_ITEM);
916 Predicate<PseudoClass> matcher = matchesPosition(i + 1, children.size());
917 writeStyleAttribute(writer, getTableByStyle(subGroupingAxis).getListItemStyle(matcher)); }
918 for (TableCell h : c.newlyRenderedHeaders())
919 h.write(writer);
920 c.write(writer);
921 if (hasSubGroups)
922 writer.writeEndElement();
923 i++; }
924 if (hasSubGroups)
925 writer.writeEndElement();
926 } catch (XMLStreamException e) {
927 throw new RuntimeException(e); }
928 }
929
930 @Override
931 public String toString() {
932 ToStringWriter xml = new ToStringWriter();
933 write(xml);
934 StringBuilder s = new StringBuilder();
935 s.append("TableCellGroup[header: ").append(newlyRenderedHeaders());
936 s.append(", children: ").append(children);
937 s.append(", xml: ").append(xml).append("]");
938 return s.toString();
939 }
940 }
941
942 private static void writeStyleAttribute(XMLStreamWriter writer, PseudoElementStyle style) throws XMLStreamException {
943 if (!style.isEmpty())
944 writeAttribute(writer, _STYLE, style.toString());
945 }
946
947 final private Map<String,TableByStyle> tableByStyles = new HashMap<String,TableByStyle>();
948 private ListItemStyle listHeaderStyle = new ListItemStyle();
949
950 public void addListHeaderStyle(ListItemStyle style) {
951 listHeaderStyle = listHeaderStyle.mergeWith(style);
952 }
953
954 public TableByStyle getTableByStyle(String axis) {
955 TableByStyle style = tableByStyles.get(axis);
956 if (style == null) {
957 style = new TableByStyle();
958 tableByStyles.put(axis, style); }
959 return style;
960 }
961
962 public ListItemStyle getListHeaderStyle() {
963 return listHeaderStyle;
964 }
965
966 private static class PseudoElementStyle {
967
968 final protected Map<List<Selector>,RuleBlock<Rule<?>>> ruleBlocks = new HashMap<>();
969
970 public void addRuleBlock(RuleBlock<Rule<?>> ruleblock) {
971 if (!ruleblock.isEmpty()) {
972 List<Selector> selector = null;
973 if (ruleblock instanceof RuleRelativeBlock)
974 selector = ((RuleRelativeBlock)ruleblock).getSelector();
975 RuleBlock<Rule<?>> r;
976 if (ruleBlocks.containsKey(selector))
977 r = ruleBlocks.get(selector);
978 else {
979
980
981
982 r = selector != null
983 ? new RuleRelativeBlock(selector)
984 : new AbstractRuleBlock<Rule<?>>();
985 r.unlock();
986 }
987
988 r.addAll(ruleblock);
989 ruleBlocks.put(selector, r);
990 }
991 }
992
993 public boolean isEmpty() {
994 return ruleBlocks.isEmpty();
995 }
996
997 @Override
998 public String toString() {
999 return BrailleCssSerializer.serializeRuleBlockList(ruleBlocks.values());
1000 }
1001 }
1002
1003 private static class TableByStyle extends PseudoElementStyle {
1004
1005 final private Map<List<PseudoClass>,ListItemStyle> listItemStyles = new LinkedHashMap<List<PseudoClass>,ListItemStyle>();
1006 private ListItemStyle listHeaderStyle = new ListItemStyle();
1007
1008 public TableByStyle() {}
1009
1010 public void addListItemStyle(List<PseudoClass> pseudo, ListItemStyle style) {
1011 if (!listItemStyles.containsKey(pseudo))
1012 listItemStyles.put(pseudo, style);
1013 else
1014 listItemStyles.put(pseudo, listItemStyles.get(pseudo).mergeWith(style));
1015 }
1016
1017 public void addListHeaderStyle(ListItemStyle style) {
1018 listHeaderStyle = listHeaderStyle.mergeWith(style);
1019 }
1020
1021 public ListItemStyle getListItemStyle(Predicate<PseudoClass> matcher) {
1022 ListItemStyle style = new ListItemStyle();
1023 outer: for (List<PseudoClass> pseudoClasses : listItemStyles.keySet()) {
1024 for (PseudoClass pseudoClass : pseudoClasses)
1025 if (!matcher.apply(pseudoClass))
1026 continue outer;
1027 style = style.mergeWith(listItemStyles.get(pseudoClasses)); }
1028 return style;
1029 }
1030
1031 public ListItemStyle getListHeaderStyle() {
1032 return listHeaderStyle;
1033 }
1034 }
1035
1036 private static class ListItemStyle extends PseudoElementStyle {
1037
1038 public ListItemStyle() {}
1039
1040 public ListItemStyle(RuleBlock<Rule<?>> ruleblock) {
1041 addRuleBlock(ruleblock);
1042 }
1043
1044 public ListItemStyle mergeWith(ListItemStyle style) {
1045 for (RuleBlock<Rule<?>> r: style.ruleBlocks.values())
1046 addRuleBlock(r);
1047 return this;
1048 }
1049 }
1050
1051 private final static Predicate<PseudoClass> matchesPosition(final int position, final int elementCount) {
1052 return new Predicate<PseudoClass>() {
1053 public boolean apply(PseudoClass pseudo) {
1054 if (pseudo instanceof PseudoClassImpl)
1055 return ((PseudoClassImpl)pseudo).matchesPosition(position, elementCount);
1056 return false;
1057 }
1058 };
1059 }
1060
1061
1062 private List<TableCell> findHeaders(TableCell cell, boolean firstLeftThenUpward) {
1063 List<TableCell> headers = new ArrayList<TableCell>();
1064 if (isHeader(cell))
1065 headers.add(cell);
1066 findHeaders(headers, 0, cell, firstLeftThenUpward);
1067 return headers;
1068 }
1069
1070 private int findHeaders(List<TableCell> headers, int index, TableCell cell, boolean firstLeftThenUpward) {
1071
1072
1073 if (cell.headers != null) {
1074 for (String id : cell.headers)
1075 index = recurAddHeader(headers, index, getById(id), firstLeftThenUpward);
1076 return index; }
1077
1078
1079 List<TableCell> rowHeaders = new ArrayList<TableCell>();
1080 List<TableCell> colHeaders = new ArrayList<TableCell>();
1081 for (TableCell h : cells)
1082 if (h != cell && h.scope != null) {
1083 switch (h.scope) {
1084 case ROW:
1085 if (h.row <= (cell.row + cell.rowspan - 1) && cell.row <= (h.row + h.rowspan - 1))
1086 rowHeaders.add(h);
1087 break;
1088 case COL:
1089 if (h.col <= (cell.col + cell.colspan - 1) && cell.col <= (h.col + h.colspan - 1))
1090 colHeaders.add(h);
1091 break; }}
1092 Collections.sort(rowHeaders, sortByColumnAndThenRow);
1093 for (TableCell h : rowHeaders)
1094 index = recurAddHeader(headers, index, h, firstLeftThenUpward);
1095 Collections.sort(colHeaders, sortByRowAndThenColumn);
1096 for (TableCell h : colHeaders)
1097 index = recurAddHeader(headers, index, h, firstLeftThenUpward);
1098
1099 if (!isHeader(cell)) {
1100 int direction = (firstLeftThenUpward ? 0 : 1);
1101 outer: while (true) {
1102 switch (direction) {
1103 case 0: {
1104 int k = 0;
1105 for (int i = 0; i < cell.rowspan; i++)
1106 for (int j = cell.col - 1; j > 0;) {
1107 boolean foundHeader = false;
1108 TableCell c = getByCoordinates(cell.row + i, j);
1109 if (c != null && isHeader(c)) {
1110 foundHeader = true;
1111 if (c.scope == null) {
1112 int l = recurAddHeader(headers, index, c, firstLeftThenUpward) - index;
1113 k += l;
1114 if (l > 1)
1115 break; }}
1116 else if (foundHeader)
1117 break;
1118 if (c == null)
1119 j--;
1120 else
1121 j = j - c.colspan; }
1122 index += k;
1123 if (!firstLeftThenUpward)
1124 break outer; }
1125 case 1: {
1126 int k = 0;
1127 for (int i = 0; i < cell.colspan; i++)
1128 for (int j = cell.row - 1; j > 0;) {
1129 boolean foundHeader = false;
1130 TableCell c = getByCoordinates(j, cell.col + i);
1131 if (c != null && isHeader(c)) {
1132 foundHeader = true;
1133 if (c.scope == null) {
1134 int l = recurAddHeader(headers, index, c, firstLeftThenUpward) - index;
1135 k += l;
1136 if (l > 1)
1137 break; }}
1138 else if (foundHeader)
1139 break;
1140 if (c == null)
1141 j--;
1142 else
1143 j = j - c.rowspan; }
1144 index += k;
1145 if (!firstLeftThenUpward) {
1146 direction--;
1147 continue outer; }
1148 break outer; }}}}
1149
1150 return index;
1151 }
1152
1153 private int recurAddHeader(List<TableCell> headers, int index, TableCell header, boolean firstLeftThenUpward) {
1154 if (headers.contains(header))
1155 ;
1156 else {
1157 headers.add(index, header);
1158 index = findHeaders(headers, index, header, firstLeftThenUpward);
1159 index ++; }
1160 return index;
1161 }
1162
1163 private TableCell getById(String id) {
1164 for (TableCell c : cells)
1165 if (id.equals(c.id))
1166 return c;
1167 throw new InvalidTableException("No element found with id " + id);
1168 }
1169
1170 private TableCell getByCoordinates(int row, int col) {
1171 for (TableCell c : cells)
1172 if (c.row <= row && (c.row + c.rowspan - 1) >= row &&
1173 c.col <= col && (c.col + c.colspan - 1) >= col)
1174 return c;
1175 return null;
1176 }
1177
1178 private static boolean isHeader(TableCell cell) {
1179 return (cell.type == TableCell.CellType.TH || (cell.axis != null) || (cell.scope != null));
1180 }
1181
1182 @SafeVarargs
1183 private static final <T> Comparator<T> compose(final Comparator<T>... comparators) {
1184 return new Comparator<T>() {
1185 public int compare(T x1, T x2) {
1186 for (Comparator<T> comparator : comparators) {
1187 int result = comparator.compare(x1, x2);
1188 if (result != 0)
1189 return result; }
1190 return 0;
1191 }
1192 };
1193 }
1194
1195 private static final Comparator<TableCell> sortByRow = new Comparator<TableCell>() {
1196 public int compare(TableCell c1, TableCell c2) {
1197 return new Integer(c1.row).compareTo(c2.row);
1198 }
1199 };
1200
1201 private static final Comparator<TableCell> sortByColumn = new Comparator<TableCell>() {
1202 public int compare(TableCell c1, TableCell c2) {
1203 return new Integer(c1.col).compareTo(c2.col);
1204 }
1205 };
1206
1207 private static final Comparator<TableCell> sortByRowType = new Comparator<TableCell>() {
1208 public int compare(TableCell c1, TableCell c2) {
1209 return c1.rowType.compareTo(c2.rowType);
1210 }
1211 };
1212
1213 private static final Comparator<TableCell> sortByRowAndThenColumn = compose(sortByRow, sortByColumn);
1214
1215 private static final Comparator<TableCell> sortByColumnAndThenRow = compose(sortByColumn, sortByRow);
1216
1217 private static class TableCell {
1218
1219 enum CellType {
1220 TD,
1221 TH
1222 }
1223
1224 enum RowType {
1225 THEAD,
1226 TBODY,
1227 TFOOT
1228 }
1229
1230 enum HeaderPolicy {
1231 ALWAYS,
1232 ONCE,
1233 FRONT
1234 }
1235
1236
1237 enum Scope {
1238 COL,
1239 ROW
1240 }
1241
1242 int rowGroup;
1243 int row;
1244 int col;
1245 CellType type = CellType.TD;
1246 RowType rowType = RowType.TBODY;
1247 HeaderPolicy headerPolicy = HeaderPolicy.ONCE;
1248 String id;
1249 List<String> headers;
1250 Scope scope = null;
1251 List<String> axis;
1252 int rowspan = 1;
1253 int colspan = 1;
1254 String ns;
1255 SimpleInlineStyle style = null;
1256 String lang = null;
1257 List<WriterEvent> content = new ArrayList<WriterEvent>();
1258
1259 private AtomicReference<Boolean> written = new AtomicReference<Boolean>(false);
1260
1261 public void write(XMLStreamWriter writer) throws XMLStreamException {
1262 writer.writeStartElement(ns, type == CellType.TD ? "td" : "th");
1263 if (axis != null) {
1264 StringBuilder s = new StringBuilder();
1265 Iterator<String> it = axis.iterator();
1266 while (it.hasNext()) {
1267 s.append(it.next());
1268 if (it.hasNext()) s.append(","); }
1269 writeAttribute(writer, _AXIS, s.toString()); }
1270 if (id != null && !written.get()) {
1271 writeAttribute(writer, _ID, id);
1272 written.set(true); }
1273 if (headers != null) {
1274 StringBuilder s = new StringBuilder();
1275 Iterator<String> it = headers.iterator();
1276 while (it.hasNext()) {
1277 s.append(it.next());
1278 if (it.hasNext()) s.append(" "); }
1279 writeAttribute(writer, _HEADERS, s.toString()); }
1280 if (lang != null)
1281 writeAttribute(writer, XML_LANG, lang);
1282 if (style != null) {
1283 String styleAttr = BrailleCssSerializer.toString(style);
1284 if (styleAttr != null && !"".equals(styleAttr))
1285 writeAttribute(writer, _STYLE, styleAttr); }
1286 for (WriterEvent action : content)
1287 action.writeTo(writer);
1288 writer.writeEndElement();
1289 }
1290
1291 public TableCell clone() {
1292 TableCell clone = new TableCell();
1293 clone.rowGroup = this.rowGroup;
1294 clone.row = this.row;
1295 clone.col = this.col;
1296 clone.type = this.type;
1297 clone.rowType = this.rowType;
1298 clone.headerPolicy = this.headerPolicy;
1299 clone.id = this.id;
1300 clone.headers = this.headers;
1301 clone.scope = this.scope;
1302 clone.axis = this.axis;
1303 clone.rowspan = this.rowspan;
1304 clone.colspan = this.colspan;
1305 clone.ns = this.ns;
1306 clone.style = (SimpleInlineStyle)this.style.clone();
1307 clone.content.addAll(this.content);
1308 clone.written = this.written;
1309 return clone;
1310 }
1311
1312 @Override
1313 public String toString() {
1314 ToStringWriter xml = new ToStringWriter();
1315 try {
1316 write(xml); }
1317 catch (Exception e) {
1318 throw new RuntimeException("coding error", e); }
1319 StringBuilder s = new StringBuilder();
1320 s.append("TableCell{" + row + "," + col + "}[").append(xml).append("]");
1321 return s.toString();
1322 }
1323 }
1324
1325 private static class CellCoordinates {
1326
1327 private final int row;
1328 private final int col;
1329
1330 private CellCoordinates(int row, int col) {
1331 this.row = row;
1332 this.col = col;
1333 }
1334
1335 public int hashCode() {
1336 final int prime = 31;
1337 int result = 1;
1338 result = prime * result + col;
1339 result = prime * result + row;
1340 return result;
1341 }
1342
1343 public boolean equals(Object obj) {
1344 if (this == obj)
1345 return true;
1346 if (obj == null)
1347 return false;
1348 if (getClass() != obj.getClass())
1349 return false;
1350 CellCoordinates other = (CellCoordinates) obj;
1351 if (col != other.col)
1352 return false;
1353 if (row != other.row)
1354 return false;
1355 return true;
1356 }
1357 }
1358
1359 private static class WriteOnlyOnce extends ArrayList<WriterEvent> implements WriterEvent {
1360 public void writeTo(XMLStreamWriter writer) throws XMLStreamException {
1361 Iterator<WriterEvent> i = iterator();
1362 while (i.hasNext()) {
1363 i.next().writeTo(writer);
1364 i.remove();
1365 }
1366 }
1367 }
1368
1369 public static WriterEvent writeElementOnce(XMLStreamReader reader) throws XMLStreamException {
1370 WriteOnlyOnce list = new WriteOnlyOnce();
1371 int depth = 0;
1372 element: while (true)
1373 try {
1374 switch (reader.getEventType()) {
1375 case START_ELEMENT: {
1376 QName name = reader.getName();
1377 depth++;
1378 list.add(w -> writeStartElement(w, name));
1379 for (int i = 0; i < reader.getNamespaceCount(); i++) {
1380 String prf = reader.getNamespacePrefix(i);
1381 String ns = reader.getNamespaceURI(i);
1382 list.add(w -> w.writeNamespace(prf, ns)); }
1383 for (int i = 0; i < reader.getAttributeCount(); i++) {
1384 QName attrName = reader.getAttributeName(i);
1385 String attrValue = reader.getAttributeValue(i);
1386 list.add(w -> writeAttribute(w, attrName, attrValue)); }
1387 break; }
1388 case CHARACTERS:
1389 String chars = reader.getText();
1390 list.add(w -> w.writeCharacters(chars));
1391 break;
1392 case END_ELEMENT: {
1393 QName name = reader.getName();
1394 list.add(w -> w.writeEndElement());
1395 depth--;
1396 if (depth == 0)
1397 break element; }}
1398 reader.next(); }
1399 catch (NoSuchElementException e) {
1400 throw new RuntimeException("coding error"); }
1401 return list;
1402 }
1403
1404
1405 private static RuleBlock<Rule<?>> rest(RuleRelativeBlock rule) {
1406 List<Selector> combinedSelector = rule.getSelector();
1407 if (combinedSelector.size() == 0 || combinedSelector.get(0).size() == 0)
1408 throw new RuntimeException("coding error");
1409 SelectorPart first = combinedSelector.get(0).get(0);
1410 if (first instanceof PseudoElementImpl) {
1411
1412 List<Selector> rest = combinedSelector.subList(1, combinedSelector.size());
1413 RuleBlock<Rule<?>> newRule;
1414 PseudoElementImpl pseudo = (PseudoElementImpl)first;
1415 if (pseudo.hasStackedPseudoElement()) {
1416 combinedSelector = new ArrayList<>();
1417 Selector selector = (Selector)new SelectorImpl().unlock();
1418 selector.add(pseudo.getStackedPseudoElement());
1419 combinedSelector.add(selector);
1420 combinedSelector.addAll(rest);
1421 newRule = new RuleRelativeBlock(combinedSelector);
1422 } else {
1423 combinedSelector = pseudo.getCombinedSelectors();
1424 newRule = combinedSelector.isEmpty()
1425 ? new AbstractRuleBlock<Rule<?>>()
1426 : new RuleRelativeBlock(combinedSelector);
1427 }
1428 newRule.replaceAll(rule);
1429 return newRule;
1430 } else {
1431 throw new RuntimeException("not implemented");
1432 }
1433 }
1434
1435 private static String elementToString(XMLInputValue<?> element) {
1436 ToStringWriter xml = new ToStringWriter() {
1437 int skipElement = 0;
1438 @Override
1439 public void writeStartElement(String prefix, String localName, String namespaceURI)
1440 throws XMLStreamException {
1441 if (skipElement > 0) {
1442 skipElement++;
1443 return;
1444 }
1445 if (XMLNS_CSS.equals(namespaceURI)) {
1446 skipElement++;
1447 return;
1448 }
1449 super.writeStartElement(prefix, localName, namespaceURI);
1450 }
1451 @Override
1452 public void writeEndElement() throws XMLStreamException {
1453 if (skipElement > 0) {
1454 skipElement--;
1455 return;
1456 }
1457 super.writeEndElement();
1458 }
1459 @Override
1460 public void writeAttribute(String prefix, String namespaceURI, String localName, String value)
1461 throws XMLStreamException {
1462 if (skipElement > 0)
1463 return;
1464 if (XMLNS_CSS.equals(namespaceURI))
1465 return;
1466 if ("style".equals(localName))
1467 return;
1468 super.writeAttribute(prefix, namespaceURI, localName, value);
1469 }
1470 @Override
1471 public void writeCharacters(String text) throws XMLStreamException {
1472 if (skipElement > 0)
1473 return;
1474 super.writeCharacters(text);
1475 }
1476 };
1477 XMLStreamReader r = element.asXMLStreamReader();
1478 try {
1479 if (r.next() != START_ELEMENT)
1480 throw new IllegalArgumentException();
1481 writeElement(xml, r);
1482 } catch(NoSuchElementException e) {
1483 throw new IllegalArgumentException();
1484 } catch (XMLStreamException e) {
1485 throw new RuntimeException(e);
1486 }
1487 return xml.toString();
1488 }
1489 }