1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.doxia.module.apt;
20
21 import javax.inject.Named;
22 import javax.inject.Singleton;
23
24 import java.io.IOException;
25 import java.io.Reader;
26 import java.io.StringReader;
27 import java.io.StringWriter;
28 import java.util.HashMap;
29 import java.util.Map;
30 import java.util.StringTokenizer;
31
32 import org.apache.commons.io.IOUtils;
33 import org.apache.commons.lang3.StringUtils;
34 import org.apache.maven.doxia.macro.MacroExecutionException;
35 import org.apache.maven.doxia.macro.MacroRequest;
36 import org.apache.maven.doxia.macro.manager.MacroNotFoundException;
37 import org.apache.maven.doxia.parser.AbstractTextParser;
38 import org.apache.maven.doxia.parser.ParseException;
39 import org.apache.maven.doxia.sink.Sink;
40 import org.apache.maven.doxia.sink.SinkEventAttributes;
41 import org.apache.maven.doxia.sink.impl.AbstractLocator;
42 import org.apache.maven.doxia.sink.impl.SinkAdapter;
43 import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
44 import org.apache.maven.doxia.util.DoxiaUtils;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48
49
50
51
52
53
54
55 @Singleton
56 @Named("apt")
57 public class AptParser extends AbstractTextParser implements AptMarkup {
58 private static final Logger LOGGER = LoggerFactory.getLogger(AptParser.class);
59
60
61 private static final int TITLE = 0;
62
63
64 private static final int SECTION1 = 1;
65
66
67 private static final int SECTION2 = 2;
68
69
70 private static final int SECTION3 = 3;
71
72
73 private static final int SECTION4 = 4;
74
75
76 private static final int SECTION5 = 5;
77
78
79 private static final int PARAGRAPH = 6;
80
81
82 private static final int VERBATIM = 7;
83
84
85 private static final int FIGURE = 8;
86
87
88 private static final int TABLE = 9;
89
90
91 private static final int LIST_ITEM = 10;
92
93
94 private static final int NUMBERED_LIST_ITEM = 11;
95
96
97 private static final int DEFINITION_LIST_ITEM = 12;
98
99
100 private static final int HORIZONTAL_RULE = 13;
101
102
103 private static final int PG_BREAK = 14;
104
105
106 private static final int LIST_BREAK = 15;
107
108
109 private static final int MACRO = 16;
110
111
112 private static final int COMMENT_BLOCK = 17;
113
114
115 private static final String[] TYPE_NAMES = {
116 "TITLE",
117 "SECTION1",
118 "SECTION2",
119 "SECTION3",
120 "SECTION4",
121 "SECTION5",
122 "PARAGRAPH",
123 "VERBATIM",
124 "FIGURE",
125 "TABLE",
126 "LIST_ITEM",
127 "NUMBERED_LIST_ITEM",
128 "DEFINITION_LIST_ITEM",
129 "HORIZONTAL_RULE",
130 "PG_BREAK",
131 "LIST_BREAK",
132 "MACRO",
133 "COMMENT_BLOCK"
134 };
135
136
137 protected static final char[] SPACES;
138
139
140 public static final int TAB_WIDTH = 8;
141
142
143
144
145
146
147 private AptSource source;
148
149
150 private Block block;
151
152
153 private String blockFileName;
154
155
156 private int blockLineNumber;
157
158
159 protected String sourceContent;
160
161
162 protected Sink sink;
163
164
165 protected String line;
166
167 private static final int NUMBER_OF_SPACES = 85;
168
169 static {
170 SPACES = new char[NUMBER_OF_SPACES];
171
172 for (int i = 0; i < NUMBER_OF_SPACES; i++) {
173 SPACES[i] = ' ';
174 }
175 }
176
177
178
179
180
181
182 @Override
183 public void parse(Reader source, Sink sink) throws ParseException {
184 parse(source, sink, null);
185 }
186
187 private static final class AptSourceLocator extends AbstractLocator {
188 private final AptSource aptSource;
189
190 AptSourceLocator(AptSource aptSource, String reference) {
191 super(reference);
192 this.aptSource = aptSource;
193 }
194
195 @Override
196 public int getLineNumber() {
197 return aptSource.getLineNumber() > -1 ? aptSource.getLineNumber() + 1 : -1;
198 }
199
200 @Override
201 public int getColumnNumber() {
202 return -1;
203 }
204 }
205
206
207 @Override
208 public void parse(Reader source, Sink sink, String reference) throws ParseException {
209 init();
210
211 try {
212 StringWriter contentWriter = new StringWriter();
213 IOUtils.copy(source, contentWriter);
214 sourceContent = contentWriter.toString();
215 } catch (IOException e) {
216 throw new AptParseException(e);
217 }
218
219 try {
220 this.source = new AptReaderSource(new StringReader(sourceContent), reference);
221
222 this.sink = getWrappedSink(sink);
223 sink.setDocumentLocator(new AptSourceLocator(this.source, reference));
224
225 blockFileName = null;
226
227 blockLineNumber = -1;
228
229
230 nextLine();
231
232
233 nextBlock( true);
234
235
236 while ((block != null) && (block.getType() == COMMENT_BLOCK)) {
237 block.traverse();
238 nextBlock( true);
239 }
240
241 traverseHead();
242
243 traverseBody();
244 } catch (AptParseException ape) {
245
246 throw new AptParseException(null, ape, getSourceName(), getSourceLineNumber(), -1);
247 } finally {
248 setSecondParsing(false);
249 init();
250 }
251 }
252
253
254
255
256
257
258 public String getSourceName() {
259
260 return blockFileName;
261 }
262
263
264
265
266
267
268 public int getSourceLineNumber() {
269
270 return blockLineNumber;
271 }
272
273
274
275
276
277
278
279
280
281
282 protected void nextLine() throws AptParseException {
283 line = source.getNextLine();
284 }
285
286
287
288
289
290
291
292
293
294
295 protected void doTraverseText(String text, int begin, int end, Sink sink) throws AptParseException {
296 boolean anchor = false;
297 boolean link = false;
298 boolean italic = false;
299 boolean bold = false;
300 boolean monospaced = false;
301 StringBuilder buffer = new StringBuilder(end - begin);
302
303 for (int i = begin; i < end; ++i) {
304 char c = text.charAt(i);
305 switch (c) {
306 case BACKSLASH:
307 if (i + 1 < end) {
308 char escaped = text.charAt(i + 1);
309 switch (escaped) {
310 case SPACE:
311 ++i;
312 flushTraversed(buffer, sink);
313 sink.nonBreakingSpace();
314 break;
315 case '\r':
316 case '\n':
317 ++i;
318
319 while (i + 1 < end && Character.isWhitespace(text.charAt(i + 1))) {
320 ++i;
321 }
322 flushTraversed(buffer, sink);
323 sink.lineBreak();
324 break;
325 case BACKSLASH:
326 case PIPE:
327 case COMMENT:
328 case EQUAL:
329 case MINUS:
330 case PLUS:
331 case STAR:
332 case LEFT_SQUARE_BRACKET:
333 case RIGHT_SQUARE_BRACKET:
334 case LESS_THAN:
335 case GREATER_THAN:
336 case LEFT_CURLY_BRACKET:
337 case RIGHT_CURLY_BRACKET:
338 ++i;
339 buffer.append(escaped);
340 break;
341 case 'x':
342 if (i + 3 < end && isHexChar(text.charAt(i + 2)) && isHexChar(text.charAt(i + 3))) {
343 int value = '?';
344 try {
345 value = Integer.parseInt(text.substring(i + 2, i + 4), 16);
346 } catch (NumberFormatException e) {
347 LOGGER.debug("Not a number: {}", text.substring(i + 2, i + 4));
348 }
349
350 i += 3;
351 buffer.append((char) value);
352 } else {
353 buffer.append(BACKSLASH);
354 }
355 break;
356 case 'u':
357 if (i + 5 < end
358 && isHexChar(text.charAt(i + 2))
359 && isHexChar(text.charAt(i + 3))
360 && isHexChar(text.charAt(i + 4))
361 && isHexChar(text.charAt(i + 5))) {
362 int value = '?';
363 try {
364 value = Integer.parseInt(text.substring(i + 2, i + 6), 16);
365 } catch (NumberFormatException e) {
366 LOGGER.debug("Not a number: {}", text.substring(i + 2, i + 6));
367 }
368
369 i += 5;
370 buffer.append((char) value);
371 } else {
372 buffer.append(BACKSLASH);
373 }
374 break;
375 default:
376 if (isOctalChar(escaped)) {
377 int octalChars = 1;
378 if (isOctalChar(charAt(text, end, i + 2))) {
379 ++octalChars;
380 if (isOctalChar(charAt(text, end, i + 3))) {
381 ++octalChars;
382 }
383 }
384 int value = '?';
385 try {
386 value = Integer.parseInt(text.substring(i + 1, i + 1 + octalChars), 8);
387 } catch (NumberFormatException e) {
388 LOGGER.debug("Not a number: {}", text.substring(i + 1, i + 1 + octalChars));
389 }
390
391 i += octalChars;
392 buffer.append((char) value);
393 } else {
394 buffer.append(BACKSLASH);
395 }
396 }
397 } else {
398 buffer.append(BACKSLASH);
399 }
400 break;
401
402 case LEFT_CURLY_BRACKET:
403 if (!anchor && !link) {
404 if (i + 1 < end && text.charAt(i + 1) == LEFT_CURLY_BRACKET ) {
405 ++i;
406 link = true;
407 flushTraversed(buffer, sink);
408
409 String linkAnchor = null;
410
411 if (i + 1 < end && text.charAt(i + 1) == LEFT_CURLY_BRACKET ) {
412 ++i;
413 StringBuilder buf = new StringBuilder();
414 i = skipTraversedLinkAnchor(text, i + 1, end, buf);
415 linkAnchor = buf.toString();
416 }
417
418 if (linkAnchor == null) {
419 linkAnchor = getTraversedLink(text, i + 1, end);
420 }
421
422 if (AptUtils.isInternalLink(linkAnchor)) {
423 linkAnchor = "#" + linkAnchor;
424 }
425
426 int hashIndex = linkAnchor.indexOf("#");
427
428 if (hashIndex != -1 && !AptUtils.isExternalLink(linkAnchor)) {
429 String hash = linkAnchor.substring(hashIndex + 1);
430
431 if (hash.endsWith(".html") && !hash.startsWith("./")) {
432 LOGGER.debug("Ambiguous link '{}'. If this is a local link, prepend \"./\"!", hash);
433 }
434
435
436 if (hash.startsWith("#")) {
437 linkAnchor = linkAnchor.substring(0, hashIndex) + hash;
438 } else if (!DoxiaUtils.isValidId(hash)) {
439 linkAnchor = linkAnchor.substring(0, hashIndex) + "#" + DoxiaUtils.encodeId(hash);
440
441 LOGGER.debug("Modified invalid link '{}' to '{}'", hash, linkAnchor);
442 }
443 }
444
445 sink.link(linkAnchor);
446 } else {
447 anchor = true;
448 flushTraversed(buffer, sink);
449
450 String linkAnchor = getTraversedAnchor(text, i + 1, end);
451
452 linkAnchor = DoxiaUtils.encodeId(linkAnchor);
453
454 sink.anchor(linkAnchor);
455 }
456 } else {
457 buffer.append(c);
458 }
459 break;
460
461 case RIGHT_CURLY_BRACKET:
462 if (link && i + 1 < end && text.charAt(i + 1) == RIGHT_CURLY_BRACKET) {
463 ++i;
464 link = false;
465 flushTraversed(buffer, sink);
466 sink.link_();
467 } else if (anchor) {
468 anchor = false;
469 flushTraversed(buffer, sink);
470 sink.anchor_();
471 } else {
472 buffer.append(c);
473 }
474 break;
475
476 case LESS_THAN:
477 if (!italic && !bold && !monospaced) {
478 if (i + 1 < end && text.charAt(i + 1) == LESS_THAN) {
479 if (i + 2 < end && text.charAt(i + 2) == LESS_THAN) {
480 i += 2;
481 monospaced = true;
482 flushTraversed(buffer, sink);
483 sink.monospaced();
484 } else {
485 ++i;
486 bold = true;
487 flushTraversed(buffer, sink);
488 sink.bold();
489 }
490 } else {
491 italic = true;
492 flushTraversed(buffer, sink);
493 sink.italic();
494 }
495 } else {
496 buffer.append(c);
497 }
498 break;
499
500 case GREATER_THAN:
501 if (monospaced
502 && i + 2 < end
503 && text.charAt(i + 1) == GREATER_THAN
504 && text.charAt(i + 2) == GREATER_THAN) {
505 i += 2;
506 monospaced = false;
507 flushTraversed(buffer, sink);
508 sink.monospaced_();
509 } else if (bold && i + 1 < end && text.charAt(i + 1) == GREATER_THAN) {
510 ++i;
511 bold = false;
512 flushTraversed(buffer, sink);
513 sink.bold_();
514 } else if (italic) {
515 italic = false;
516 flushTraversed(buffer, sink);
517 sink.italic_();
518 } else {
519 buffer.append(c);
520 }
521 break;
522
523 default:
524 if (Character.isWhitespace(c)) {
525 buffer.append(SPACE);
526
527
528 while (i + 1 < end && Character.isWhitespace(text.charAt(i + 1))) {
529 ++i;
530 }
531 } else {
532 buffer.append(c);
533 }
534 }
535 }
536
537 if (monospaced) {
538 throw new AptParseException("missing '" + MONOSPACED_END_MARKUP + "'");
539 }
540 if (bold) {
541 throw new AptParseException("missing '" + BOLD_END_MARKUP + "'");
542 }
543 if (italic) {
544 throw new AptParseException("missing '" + ITALIC_END_MARKUP + "'");
545 }
546 if (link) {
547 throw new AptParseException("missing '" + LINK_END_MARKUP + "'");
548 }
549 if (anchor) {
550 throw new AptParseException("missing '" + ANCHOR_END_MARKUP + "'");
551 }
552
553 flushTraversed(buffer, sink);
554 }
555
556
557
558
559
560
561
562
563
564
565
566 protected static char charAt(String string, int length, int i) {
567 return (i < length) ? string.charAt(i) : '\0';
568 }
569
570
571
572
573
574
575
576
577
578 protected static int skipSpace(String string, int length, int i) {
579 loop:
580 for (; i < length; ++i) {
581 switch (string.charAt(i)) {
582 case SPACE:
583 case TAB:
584 break;
585 default:
586 break loop;
587 }
588 }
589 return i;
590 }
591
592
593
594
595
596
597
598
599
600 protected static String replaceAll(String string, String oldSub, String newSub) {
601 StringBuilder replaced = new StringBuilder();
602 int oldSubLength = oldSub.length();
603 int begin, end;
604
605 begin = 0;
606 while ((end = string.indexOf(oldSub, begin)) >= 0) {
607 if (end > begin) {
608 replaced.append(string, begin, end);
609 }
610 replaced.append(newSub);
611 begin = end + oldSubLength;
612 }
613 if (begin < string.length()) {
614 replaced.append(string.substring(begin));
615 }
616
617 return replaced.toString();
618 }
619
620
621
622
623 protected void init() {
624 super.init();
625
626 this.sourceContent = null;
627 this.sink = null;
628 this.source = null;
629 this.block = null;
630 this.blockFileName = null;
631 this.blockLineNumber = 0;
632 this.line = null;
633 }
634
635
636
637
638
639
640
641
642
643
644 private void traverseHead() throws AptParseException {
645 sink.head();
646
647 if (block != null && block.getType() == TITLE) {
648 block.traverse();
649 nextBlock();
650 }
651
652 sink.head_();
653 }
654
655
656
657
658
659
660 private void traverseBody() throws AptParseException {
661 sink.body();
662
663 if (block != null) {
664 traverseSectionBlocks();
665 }
666
667 while (block != null) {
668 traverseSection(0);
669 }
670
671 sink.body_();
672 }
673
674
675
676
677
678
679
680 private void traverseSection(int level) throws AptParseException {
681 if (block == null) {
682 return;
683 }
684
685 int type = SECTION1 + level;
686
687 expectedBlock(type);
688
689 switch (level) {
690 case 0:
691 sink.section1();
692 break;
693 case 1:
694 sink.section2();
695 break;
696 case 2:
697 sink.section3();
698 break;
699 case 3:
700 sink.section4();
701 break;
702 case 4:
703 sink.section5();
704 break;
705 default:
706 break;
707 }
708
709 block.traverse();
710
711 nextBlock();
712
713 traverseSectionBlocks();
714
715 while (block != null) {
716 if (block.getType() <= type) {
717 break;
718 }
719
720 traverseSection(level + 1);
721 }
722
723 switch (level) {
724 case 0:
725 sink.section1_();
726 break;
727 case 1:
728 sink.section2_();
729 break;
730 case 2:
731 sink.section3_();
732 break;
733 case 3:
734 sink.section4_();
735 break;
736 case 4:
737 sink.section5_();
738 break;
739 default:
740 break;
741 }
742 }
743
744
745
746
747
748
749 private void traverseSectionBlocks() throws AptParseException {
750 loop:
751 while (block != null) {
752 switch (block.getType()) {
753 case PARAGRAPH:
754 case VERBATIM:
755 case FIGURE:
756 case TABLE:
757 case HORIZONTAL_RULE:
758 case PG_BREAK:
759 case MACRO:
760 case COMMENT_BLOCK:
761 block.traverse();
762 nextBlock();
763 break;
764
765 case LIST_ITEM:
766 traverseList();
767 break;
768
769 case NUMBERED_LIST_ITEM:
770 traverseNumberedList();
771 break;
772
773 case DEFINITION_LIST_ITEM:
774 traverseDefinitionList();
775 break;
776
777 case LIST_BREAK:
778
779
780 nextBlock();
781 break;
782
783 default:
784
785 break loop;
786 }
787 }
788 }
789
790
791
792
793
794
795 private void traverseList() throws AptParseException {
796 if (block == null) {
797 return;
798 }
799
800 expectedBlock(LIST_ITEM);
801
802 int listIndent = block.getIndent();
803
804 sink.list();
805
806 sink.listItem();
807
808 block.traverse();
809
810 nextBlock();
811
812 loop:
813 while (block != null) {
814 int blockIndent = block.getIndent();
815
816 switch (block.getType()) {
817 case PARAGRAPH:
818 if (blockIndent < listIndent) {
819 break loop;
820 }
821
822 case VERBATIM:
823 case MACRO:
824 case FIGURE:
825 case TABLE:
826 case HORIZONTAL_RULE:
827 case PG_BREAK:
828 block.traverse();
829 nextBlock();
830 break;
831
832 case LIST_ITEM:
833 if (blockIndent < listIndent) {
834 break loop;
835 }
836
837 if (blockIndent > listIndent) {
838 traverseList();
839 } else {
840 sink.listItem_();
841 sink.listItem();
842 block.traverse();
843 nextBlock();
844 }
845 break;
846
847 case NUMBERED_LIST_ITEM:
848 if (blockIndent < listIndent) {
849 break loop;
850 }
851
852 traverseNumberedList();
853 break;
854
855 case DEFINITION_LIST_ITEM:
856 if (blockIndent < listIndent) {
857 break loop;
858 }
859
860 traverseDefinitionList();
861 break;
862
863 case LIST_BREAK:
864 if (blockIndent >= listIndent) {
865 nextBlock();
866 }
867
868 default:
869
870 break loop;
871 }
872 }
873
874 sink.listItem_();
875 sink.list_();
876 }
877
878
879
880
881
882
883 private void traverseNumberedList() throws AptParseException {
884 if (block == null) {
885 return;
886 }
887 expectedBlock(NUMBERED_LIST_ITEM);
888 int listIndent = block.getIndent();
889
890 sink.numberedList(((NumberedListItem) block).getNumbering());
891 sink.numberedListItem();
892 block.traverse();
893 nextBlock();
894
895 loop:
896 while (block != null) {
897 int blockIndent = block.getIndent();
898
899 switch (block.getType()) {
900 case PARAGRAPH:
901 if (blockIndent < listIndent) {
902 break loop;
903 }
904
905 case VERBATIM:
906 case FIGURE:
907 case TABLE:
908 case HORIZONTAL_RULE:
909 case PG_BREAK:
910 block.traverse();
911 nextBlock();
912 break;
913
914 case LIST_ITEM:
915 if (blockIndent < listIndent) {
916 break loop;
917 }
918
919 traverseList();
920 break;
921
922 case NUMBERED_LIST_ITEM:
923 if (blockIndent < listIndent) {
924 break loop;
925 }
926
927 if (blockIndent > listIndent) {
928 traverseNumberedList();
929 } else {
930 sink.numberedListItem_();
931 sink.numberedListItem();
932 block.traverse();
933 nextBlock();
934 }
935 break;
936
937 case DEFINITION_LIST_ITEM:
938 if (blockIndent < listIndent) {
939 break loop;
940 }
941
942 traverseDefinitionList();
943 break;
944
945 case LIST_BREAK:
946 if (blockIndent >= listIndent) {
947 nextBlock();
948 }
949
950 default:
951
952 break loop;
953 }
954 }
955
956 sink.numberedListItem_();
957 sink.numberedList_();
958 }
959
960
961
962
963
964
965 private void traverseDefinitionList() throws AptParseException {
966 if (block == null) {
967 return;
968 }
969 expectedBlock(DEFINITION_LIST_ITEM);
970 int listIndent = block.getIndent();
971
972 sink.definitionList();
973 sink.definitionListItem();
974 block.traverse();
975 nextBlock();
976
977 loop:
978 while (block != null) {
979 int blockIndent = block.getIndent();
980
981 switch (block.getType()) {
982 case PARAGRAPH:
983 if (blockIndent < listIndent) {
984 break loop;
985 }
986
987 case VERBATIM:
988 case FIGURE:
989 case TABLE:
990 case HORIZONTAL_RULE:
991 case PG_BREAK:
992 block.traverse();
993 nextBlock();
994 break;
995
996 case LIST_ITEM:
997 if (blockIndent < listIndent) {
998 break loop;
999 }
1000
1001 traverseList();
1002 break;
1003
1004 case NUMBERED_LIST_ITEM:
1005 if (blockIndent < listIndent) {
1006 break loop;
1007 }
1008
1009 traverseNumberedList();
1010 break;
1011
1012 case DEFINITION_LIST_ITEM:
1013 if (blockIndent < listIndent) {
1014 break loop;
1015 }
1016
1017 if (blockIndent > listIndent) {
1018 traverseDefinitionList();
1019 } else {
1020 sink.definition_();
1021 sink.definitionListItem_();
1022 sink.definitionListItem();
1023 block.traverse();
1024 nextBlock();
1025 }
1026 break;
1027
1028 case LIST_BREAK:
1029 if (blockIndent >= listIndent) {
1030 nextBlock();
1031 }
1032
1033 default:
1034
1035 break loop;
1036 }
1037 }
1038
1039 sink.definition_();
1040 sink.definitionListItem_();
1041 sink.definitionList_();
1042 }
1043
1044
1045
1046
1047
1048
1049 private void nextBlock() throws AptParseException {
1050 nextBlock( false);
1051 }
1052
1053
1054
1055
1056
1057
1058
1059 private void nextBlock(boolean firstBlock) throws AptParseException {
1060
1061 int length, indent, i;
1062
1063 skipLoop:
1064 for (; ; ) {
1065 if (line == null) {
1066 block = null;
1067 return;
1068 }
1069
1070 length = line.length();
1071 indent = 0;
1072 for (i = 0; i < length; ++i) {
1073 switch (line.charAt(i)) {
1074 case SPACE:
1075 ++indent;
1076 break;
1077 case TAB:
1078 indent += 8;
1079 break;
1080 default:
1081 break skipLoop;
1082 }
1083 }
1084
1085 if (i == length) {
1086 nextLine();
1087 }
1088 }
1089
1090 blockFileName = source.getName();
1091 blockLineNumber = source.getLineNumber();
1092 block = null;
1093 switch (line.charAt(i)) {
1094 case STAR:
1095 if (indent == 0) {
1096 if (charAt(line, length, i + 1) == MINUS && charAt(line, length, i + 2) == MINUS) {
1097 block = new Table(indent, line);
1098 } else if (charAt(line, length, i + 1) == STAR) {
1099 if (charAt(line, length, i + 2) == STAR) {
1100 if (charAt(line, length, i + 3) == STAR) {
1101 block = new Section5(indent, line);
1102 } else {
1103 block = new Section4(indent, line);
1104 }
1105 } else {
1106 block = new Section3(indent, line);
1107 }
1108 } else {
1109 block = new Section2(indent, line);
1110 }
1111 } else {
1112 block = new ListItem(indent, line);
1113 }
1114 break;
1115 case LEFT_SQUARE_BRACKET:
1116 if (charAt(line, length, i + 1) == RIGHT_SQUARE_BRACKET) {
1117 block = new ListBreak(indent, line);
1118 } else {
1119 if (indent == 0) {
1120 block = new Figure(indent, line);
1121 } else {
1122 if (charAt(line, length, i + 1) == LEFT_SQUARE_BRACKET) {
1123 int numbering;
1124
1125 switch (charAt(line, length, i + 2)) {
1126 case NUMBERING_LOWER_ALPHA_CHAR:
1127 numbering = Sink.NUMBERING_LOWER_ALPHA;
1128 break;
1129 case NUMBERING_UPPER_ALPHA_CHAR:
1130 numbering = Sink.NUMBERING_UPPER_ALPHA;
1131 break;
1132 case NUMBERING_LOWER_ROMAN_CHAR:
1133 numbering = Sink.NUMBERING_LOWER_ROMAN;
1134 break;
1135 case NUMBERING_UPPER_ROMAN_CHAR:
1136 numbering = Sink.NUMBERING_UPPER_ROMAN;
1137 break;
1138 case NUMBERING:
1139 default:
1140
1141
1142 numbering = Sink.NUMBERING_DECIMAL;
1143 }
1144
1145 block = new NumberedListItem(indent, line, numbering);
1146 } else {
1147 block = new DefinitionListItem(indent, line);
1148 }
1149 }
1150 }
1151 break;
1152 case MINUS:
1153 if (charAt(line, length, i + 1) == MINUS && charAt(line, length, i + 2) == MINUS) {
1154 if (indent == 0) {
1155 block = new Verbatim(indent, line);
1156 } else {
1157 if (firstBlock) {
1158 block = new Title(indent, line);
1159 }
1160 }
1161 }
1162 break;
1163 case PLUS:
1164 if (indent == 0 && charAt(line, length, i + 1) == MINUS && charAt(line, length, i + 2) == MINUS) {
1165 block = new Verbatim(indent, line);
1166 }
1167 break;
1168 case EQUAL:
1169 if (indent == 0 && charAt(line, length, i + 1) == EQUAL && charAt(line, length, i + 2) == EQUAL) {
1170 block = new HorizontalRule(indent, line);
1171 }
1172 break;
1173 case PAGE_BREAK:
1174 if (indent == 0) {
1175 block = new PageBreak(indent, line);
1176 }
1177 break;
1178 case PERCENT:
1179 if (indent == 0 && charAt(line, length, i + 1) == LEFT_CURLY_BRACKET) {
1180 block = new MacroBlock(indent, line);
1181 }
1182 break;
1183 case COMMENT:
1184 if (charAt(line, length, i + 1) == COMMENT) {
1185 block = new Comment(line.substring(i + 2));
1186 }
1187 break;
1188 default:
1189 break;
1190 }
1191
1192 if (block == null) {
1193 if (indent == 0) {
1194 block = new Section1(indent, line);
1195 } else {
1196 block = new Paragraph(indent, line);
1197 }
1198 }
1199 }
1200
1201
1202
1203
1204
1205
1206
1207 private void expectedBlock(int type) throws AptParseException {
1208 int blockType = block.getType();
1209
1210 if (blockType != type) {
1211 throw new AptParseException("expected " + TYPE_NAMES[type] + ", found " + TYPE_NAMES[blockType]);
1212 }
1213 }
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223 private static boolean isOctalChar(char c) {
1224 return (c >= '0' && c <= '7');
1225 }
1226
1227
1228
1229
1230
1231
1232
1233 private static boolean isHexChar(char c) {
1234 return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
1235 }
1236
1237
1238
1239
1240
1241
1242
1243 private static void flushTraversed(StringBuilder buffer, Sink sink) {
1244 if (buffer.length() > 0) {
1245 sink.text(buffer.toString());
1246 buffer.setLength(0);
1247 }
1248 }
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260 private static int skipTraversedLinkAnchor(String text, int begin, int end, StringBuilder linkAnchor)
1261 throws AptParseException {
1262 int i;
1263 loop:
1264 for (i = begin; i < end; ++i) {
1265 char c = text.charAt(i);
1266 switch (c) {
1267 case RIGHT_CURLY_BRACKET:
1268 break loop;
1269 case BACKSLASH:
1270 if (i + 1 < end) {
1271 ++i;
1272 linkAnchor.append(text.charAt(i));
1273 } else {
1274 linkAnchor.append(BACKSLASH);
1275 }
1276 break;
1277 default:
1278 linkAnchor.append(c);
1279 }
1280 }
1281 if (i == end) {
1282 throw new AptParseException("missing '" + RIGHT_CURLY_BRACKET + "'");
1283 }
1284
1285 return i;
1286 }
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297 private String getTraversedLink(String text, int begin, int end) throws AptParseException {
1298 char previous2 = LEFT_CURLY_BRACKET;
1299 char previous = LEFT_CURLY_BRACKET;
1300 int i;
1301
1302 for (i = begin; i < end; ++i) {
1303 char c = text.charAt(i);
1304 if (c == RIGHT_CURLY_BRACKET && previous == RIGHT_CURLY_BRACKET && previous2 != BACKSLASH) {
1305 break;
1306 }
1307
1308 previous2 = previous;
1309 previous = c;
1310 }
1311 if (i == end) {
1312 throw new AptParseException("missing '" + LEFT_CURLY_BRACKET + LEFT_CURLY_BRACKET + "'");
1313 }
1314
1315 return doGetTraversedLink(text, begin, i - 1);
1316 }
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327 private String getTraversedAnchor(String text, int begin, int end) throws AptParseException {
1328 char previous = LEFT_CURLY_BRACKET;
1329 int i;
1330
1331 for (i = begin; i < end; ++i) {
1332 char c = text.charAt(i);
1333 if (c == RIGHT_CURLY_BRACKET && previous != BACKSLASH) {
1334 break;
1335 }
1336
1337 previous = c;
1338 }
1339 if (i == end) {
1340 throw new AptParseException("missing '" + RIGHT_CURLY_BRACKET + "'");
1341 }
1342
1343 return doGetTraversedLink(text, begin, i);
1344 }
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355 private String doGetTraversedLink(String text, int begin, int end) throws AptParseException {
1356 final StringBuilder buffer = new StringBuilder(end - begin);
1357
1358 Sink linkSink = new SinkAdapter() {
1359 @Override
1360 public void lineBreak(SinkEventAttributes attributes) {
1361 buffer.append(SPACE);
1362 }
1363
1364 @Override
1365 public void nonBreakingSpace() {
1366 buffer.append(SPACE);
1367 }
1368
1369 @Override
1370 public void text(String text, SinkEventAttributes attributes) {
1371 buffer.append(text);
1372 }
1373 };
1374 doTraverseText(text, begin, end, linkSink);
1375
1376 return buffer.toString().trim();
1377 }
1378
1379
1380
1381
1382 private abstract class Block {
1383
1384 protected int type;
1385
1386
1387 protected int indent;
1388
1389
1390 protected String text;
1391
1392
1393 protected int textLength;
1394
1395
1396
1397
1398
1399
1400
1401
1402 Block(int type, int indent) throws AptParseException {
1403 this(type, indent, null);
1404 }
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414 Block(int type, int indent, String firstLine) throws AptParseException {
1415 this.type = type;
1416 this.indent = indent;
1417
1418
1419 AptParser.this.nextLine();
1420
1421 if (firstLine == null) {
1422 text = null;
1423 textLength = 0;
1424 } else {
1425
1426 StringBuilder buffer = new StringBuilder(firstLine);
1427
1428 while (AptParser.this.line != null) {
1429 String l = AptParser.this.line;
1430 int length = l.length();
1431 int i = 0;
1432
1433 i = skipSpace(l, length, i);
1434 if (i == length) {
1435
1436 AptParser.this.nextLine();
1437 break;
1438 } else if ((AptParser.charAt(l, length, i) == COMMENT
1439 && AptParser.charAt(l, length, i + 1) == COMMENT)
1440 || type == COMMENT_BLOCK) {
1441
1442 break;
1443 }
1444
1445 buffer.append(EOL);
1446 buffer.append(l);
1447
1448 AptParser.this.nextLine();
1449 }
1450
1451 text = buffer.toString();
1452 textLength = text.length();
1453 }
1454 }
1455
1456
1457
1458
1459
1460
1461 public final int getType() {
1462 return type;
1463 }
1464
1465
1466
1467
1468
1469
1470 public final int getIndent() {
1471 return indent;
1472 }
1473
1474
1475
1476
1477
1478
1479 public abstract void traverse() throws AptParseException;
1480
1481
1482
1483
1484
1485
1486
1487 protected void traverseText(int begin) throws AptParseException {
1488 traverseText(begin, text.length());
1489 }
1490
1491
1492
1493
1494
1495
1496
1497
1498 protected void traverseText(int begin, int end) throws AptParseException {
1499 AptParser.this.doTraverseText(text, begin, end, AptParser.this.sink);
1500 }
1501
1502
1503
1504
1505
1506
1507 protected int skipLeadingBullets() {
1508 int i = skipSpaceFrom(0);
1509 for (; i < textLength; ++i) {
1510 if (text.charAt(i) != STAR) {
1511 break;
1512 }
1513 }
1514 return skipSpaceFrom(i);
1515 }
1516
1517
1518
1519
1520
1521
1522
1523
1524 protected int skipFromLeftToRightBracket(int i) throws AptParseException {
1525 char previous = LEFT_SQUARE_BRACKET;
1526 for (++i; i < textLength; ++i) {
1527 char c = text.charAt(i);
1528 if (c == RIGHT_SQUARE_BRACKET && previous != BACKSLASH) {
1529 break;
1530 }
1531 previous = c;
1532 }
1533 if (i == textLength) {
1534 throw new AptParseException("missing '" + RIGHT_SQUARE_BRACKET + "'");
1535 }
1536
1537 return i;
1538 }
1539
1540
1541
1542
1543
1544
1545
1546 protected final int skipSpaceFrom(int i) {
1547 return AptParser.skipSpace(text, textLength, i);
1548 }
1549 }
1550
1551
1552 private class ListBreak extends AptParser.Block {
1553
1554
1555
1556
1557
1558
1559
1560 ListBreak(int indent, String firstLine) throws AptParseException {
1561 super(AptParser.LIST_BREAK, indent, firstLine);
1562 }
1563
1564
1565 public void traverse() throws AptParseException {
1566 throw new AptParseException("internal error: traversing list break");
1567 }
1568 }
1569
1570
1571 private class Title extends Block {
1572
1573
1574
1575
1576
1577
1578
1579 Title(int indent, String firstLine) throws AptParseException {
1580 super(TITLE, indent, firstLine);
1581 }
1582
1583
1584 public void traverse() throws AptParseException {
1585 StringTokenizer lines = new StringTokenizer(text, EOL);
1586 int separator = -1;
1587 boolean firstLine = true;
1588 boolean title = false;
1589 boolean author = false;
1590 boolean date = false;
1591
1592 loop:
1593 while (lines.hasMoreTokens()) {
1594 String line = lines.nextToken().trim();
1595 int lineLength = line.length();
1596
1597 if (AptParser.charAt(line, lineLength, 0) == MINUS
1598 && AptParser.charAt(line, lineLength, 1) == MINUS
1599 && AptParser.charAt(line, lineLength, 2) == MINUS) {
1600 switch (separator) {
1601 case 0:
1602 if (title) {
1603 AptParser.this.sink.title_();
1604 }
1605 case 1:
1606 if (author) {
1607 AptParser.this.sink.author_();
1608 }
1609 break;
1610 case 2:
1611
1612
1613 break loop;
1614 default:
1615 break;
1616 }
1617
1618 ++separator;
1619 firstLine = true;
1620 } else {
1621 if (firstLine) {
1622 firstLine = false;
1623 switch (separator) {
1624 case 0:
1625 title = true;
1626 AptParser.this.sink.title();
1627 break;
1628 case 1:
1629 author = true;
1630 AptParser.this.sink.author();
1631 break;
1632 case 2:
1633 date = true;
1634 AptParser.this.sink.date();
1635 break;
1636 default:
1637 break;
1638 }
1639 } else {
1640 if (separator == 1) {
1641 AptParser.this.sink.author_();
1642 AptParser.this.sink.author();
1643 } else {
1644
1645 AptParser.this.sink.lineBreak();
1646 }
1647 }
1648
1649 AptParser.this.doTraverseText(line, 0, lineLength, AptParser.this.sink);
1650 }
1651 }
1652
1653 switch (separator) {
1654 case 0:
1655 if (title) {
1656 AptParser.this.sink.title_();
1657 }
1658 case 1:
1659 if (author) {
1660 AptParser.this.sink.author_();
1661 }
1662 break;
1663 case 2:
1664 if (date) {
1665 AptParser.this.sink.date_();
1666 }
1667 break;
1668 default:
1669 break;
1670 }
1671 }
1672 }
1673
1674
1675 private abstract class Section extends Block {
1676
1677
1678
1679
1680
1681
1682
1683
1684 Section(int type, int indent, String firstLine) throws AptParseException {
1685 super(type, indent, firstLine);
1686 }
1687
1688
1689 public void traverse() throws AptParseException {
1690 Title();
1691 traverseText(skipLeadingBullets());
1692 Title_();
1693 }
1694
1695
1696 public abstract void Title();
1697
1698
1699 public abstract void Title_();
1700 }
1701
1702
1703 private class Section1 extends Section {
1704
1705
1706
1707
1708
1709
1710
1711 Section1(int indent, String firstLine) throws AptParseException {
1712 super(SECTION1, indent, firstLine);
1713 }
1714
1715
1716 public void Title() {
1717 AptParser.this.sink.sectionTitle1();
1718 }
1719
1720
1721 public void Title_() {
1722 AptParser.this.sink.sectionTitle1_();
1723 }
1724 }
1725
1726
1727 private class Section2 extends Section {
1728
1729
1730
1731
1732
1733
1734
1735 Section2(int indent, String firstLine) throws AptParseException {
1736 super(SECTION2, indent, firstLine);
1737 }
1738
1739
1740 public void Title() {
1741 AptParser.this.sink.sectionTitle2();
1742 }
1743
1744
1745 public void Title_() {
1746 AptParser.this.sink.sectionTitle2_();
1747 }
1748 }
1749
1750
1751 public class Section3 extends Section {
1752
1753
1754
1755
1756
1757
1758
1759 Section3(int indent, String firstLine) throws AptParseException {
1760 super(SECTION3, indent, firstLine);
1761 }
1762
1763
1764 public void Title() {
1765 AptParser.this.sink.sectionTitle3();
1766 }
1767
1768
1769 public void Title_() {
1770 AptParser.this.sink.sectionTitle3_();
1771 }
1772 }
1773
1774
1775 private class Section4 extends Section {
1776
1777
1778
1779
1780
1781
1782
1783 Section4(int indent, String firstLine) throws AptParseException {
1784 super(SECTION4, indent, firstLine);
1785 }
1786
1787
1788 public void Title() {
1789 AptParser.this.sink.sectionTitle4();
1790 }
1791
1792
1793 public void Title_() {
1794 AptParser.this.sink.sectionTitle4_();
1795 }
1796 }
1797
1798
1799 private class Section5 extends Section {
1800
1801
1802
1803
1804
1805
1806
1807 Section5(int indent, String firstLine) throws AptParseException {
1808 super(SECTION5, indent, firstLine);
1809 }
1810
1811
1812 public void Title() {
1813 AptParser.this.sink.sectionTitle5();
1814 }
1815
1816
1817 public void Title_() {
1818 AptParser.this.sink.sectionTitle5_();
1819 }
1820 }
1821
1822
1823 private class Paragraph extends Block {
1824
1825
1826
1827
1828
1829
1830
1831 Paragraph(int indent, String firstLine) throws AptParseException {
1832 super(PARAGRAPH, indent, firstLine);
1833 }
1834
1835
1836 public void traverse() throws AptParseException {
1837 AptParser.this.sink.paragraph();
1838 traverseText(skipSpaceFrom(0));
1839 AptParser.this.sink.paragraph_();
1840 }
1841 }
1842
1843
1844 private class Comment extends Block {
1845
1846
1847
1848
1849
1850
1851 Comment(String line) throws AptParseException {
1852 super(COMMENT_BLOCK, 0, line);
1853 }
1854
1855
1856 public void traverse() throws AptParseException {
1857 if (isEmitComments()) {
1858 AptParser.this.sink.comment(text);
1859 }
1860 }
1861 }
1862
1863
1864 private class Verbatim extends Block {
1865
1866 private boolean source;
1867
1868
1869
1870
1871
1872
1873
1874
1875 Verbatim(int indent, String firstLine) throws AptParseException {
1876 super(VERBATIM, indent, null);
1877
1878
1879
1880 StringBuilder buffer = new StringBuilder();
1881 char firstChar = firstLine.charAt(0);
1882 source = (firstChar == PLUS);
1883
1884 while (AptParser.this.line != null) {
1885 String l = AptParser.this.line;
1886 int length = l.length();
1887
1888 if (AptParser.charAt(l, length, 0) == firstChar
1889 && AptParser.charAt(l, length, 1) == MINUS
1890 && AptParser.charAt(l, length, 2) == MINUS) {
1891 AptParser.this.nextLine();
1892
1893 break;
1894 }
1895
1896
1897
1898 int prevColumn, column;
1899
1900 column = 0;
1901
1902 for (int i = 0; i < length; ++i) {
1903 char c = l.charAt(i);
1904
1905 if (c == TAB) {
1906 prevColumn = column;
1907
1908 column = ((column + 1 + TAB_WIDTH - 1) / TAB_WIDTH) * TAB_WIDTH;
1909
1910 buffer.append(SPACES, 0, column - prevColumn);
1911 } else {
1912 ++column;
1913 buffer.append(c);
1914 }
1915 }
1916 buffer.append(EOL);
1917
1918 AptParser.this.nextLine();
1919 }
1920
1921
1922
1923 textLength = buffer.length();
1924
1925 if (textLength > 0) {
1926 --textLength;
1927
1928 buffer.setLength(textLength);
1929 }
1930
1931 text = buffer.toString();
1932 }
1933
1934
1935 public void traverse() throws AptParseException {
1936 AptParser.this.sink.verbatim(source ? SinkEventAttributeSet.SOURCE : null);
1937 AptParser.this.sink.text(text);
1938 AptParser.this.sink.verbatim_();
1939 }
1940 }
1941
1942
1943 private class Figure extends Block {
1944
1945
1946
1947
1948
1949
1950
1951 Figure(int indent, String firstLine) throws AptParseException {
1952 super(FIGURE, indent, firstLine);
1953 }
1954
1955
1956 public void traverse() throws AptParseException {
1957 AptParser.this.sink.figure();
1958
1959 int i = skipFromLeftToRightBracket(0);
1960 AptParser.this.sink.figureGraphics(text.substring(1, i));
1961
1962 i = skipSpaceFrom(i + 1);
1963 if (i < textLength) {
1964 AptParser.this.sink.figureCaption();
1965 traverseText(i);
1966 AptParser.this.sink.figureCaption_();
1967 }
1968
1969 AptParser.this.sink.figure_();
1970 }
1971 }
1972
1973
1974 private class Table extends Block {
1975
1976
1977
1978
1979
1980
1981
1982 Table(int indent, String firstLine) throws AptParseException {
1983 super(TABLE, indent, firstLine);
1984 }
1985
1986
1987 public void traverse() throws AptParseException {
1988 int captionIndex = -1;
1989 int nextLineIndex = 0;
1990 int init = 2;
1991 int[] justification = null;
1992 int rows = 0;
1993 int columns = 0;
1994 StringBuilder[] cells = null;
1995 boolean[] headers = null;
1996 boolean grid;
1997
1998 AptParser.this.sink.table();
1999
2000 while (nextLineIndex < textLength) {
2001 int i = text.indexOf("*--", nextLineIndex);
2002 if (i < 0) {
2003 captionIndex = nextLineIndex;
2004 break;
2005 }
2006
2007 String line;
2008 i = text.indexOf('\n', nextLineIndex);
2009 if (i < 0) {
2010 line = text.substring(nextLineIndex);
2011 nextLineIndex = textLength;
2012 } else {
2013 line = text.substring(nextLineIndex, i);
2014 nextLineIndex = i + 1;
2015 }
2016 int lineLength = line.length();
2017
2018 if (line.indexOf("*--") == 0) {
2019 if (init == 2) {
2020 init = 1;
2021 justification = parseJustification(line, lineLength);
2022 columns = justification.length;
2023 cells = new StringBuilder[columns];
2024 headers = new boolean[columns];
2025 for (i = 0; i < columns; ++i) {
2026 cells[i] = new StringBuilder();
2027 headers[i] = false;
2028 }
2029 } else {
2030 if (traverseRow(cells, headers, justification)) {
2031 ++rows;
2032 }
2033 justification = parseJustification(line, lineLength);
2034 }
2035 } else {
2036 if (init == 1) {
2037 init = 0;
2038 grid = (AptParser.charAt(line, lineLength, 0) == PIPE);
2039 AptParser.this.sink.tableRows(justification, grid);
2040 }
2041
2042 line = replaceAll(line, "\\|", "\\u007C");
2043
2044 StringTokenizer cellLines = new StringTokenizer(line, "|", true);
2045
2046 i = 0;
2047 boolean processedGrid = false;
2048 while (cellLines.hasMoreTokens()) {
2049 String cellLine = cellLines.nextToken();
2050 if ("|".equals(cellLine)) {
2051 if (processedGrid) {
2052 headers[i] = true;
2053 } else {
2054 processedGrid = true;
2055 headers[i] = false;
2056 }
2057 continue;
2058 }
2059 processedGrid = false;
2060 cellLine = replaceAll(cellLine, "\\", "\\u00A0");
2061
2062 cellLine = replaceAll(cellLine, "\\u00A0~", "\\~");
2063 cellLine = replaceAll(cellLine, "\\u00A0=", "\\=");
2064 cellLine = replaceAll(cellLine, "\\u00A0-", "\\-");
2065 cellLine = replaceAll(cellLine, "\\u00A0+", "\\+");
2066 cellLine = replaceAll(cellLine, "\\u00A0*", "\\*");
2067 cellLine = replaceAll(cellLine, "\\u00A0[", "\\[");
2068 cellLine = replaceAll(cellLine, "\\u00A0]", "\\]");
2069 cellLine = replaceAll(cellLine, "\\u00A0<", "\\<");
2070 cellLine = replaceAll(cellLine, "\\u00A0>", "\\>");
2071 cellLine = replaceAll(cellLine, "\\u00A0{", "\\{");
2072 cellLine = replaceAll(cellLine, "\\u00A0}", "\\}");
2073 cellLine = replaceAll(cellLine, "\\u00A0u", "\\u");
2074 cellLine = replaceAll(cellLine, "\\u00A0\\u00A0", "\\\\");
2075 cellLine = cellLine.trim();
2076
2077 StringBuilder cell = cells[i];
2078 if (cellLine.length() > 0) {
2079
2080 if (cell.toString().trim().endsWith("\\u00A0")) {
2081 cell.append("\\\n");
2082 } else {
2083 if (cell.length() != 0) {
2084
2085 cell.append(" ");
2086 }
2087 }
2088
2089 cell.append(cellLine);
2090 }
2091
2092 ++i;
2093 if (i == columns) {
2094 break;
2095 }
2096 }
2097 }
2098 }
2099 if (rows == 0) {
2100 throw new AptParseException("no table rows");
2101 }
2102 AptParser.this.sink.tableRows_();
2103
2104 if (captionIndex >= 0) {
2105 AptParser.this.sink.tableCaption();
2106 AptParser.this.doTraverseText(text, captionIndex, textLength, AptParser.this.sink);
2107 AptParser.this.sink.tableCaption_();
2108 }
2109
2110 AptParser.this.sink.table_();
2111 }
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121 private int[] parseJustification(String jline, int lineLength) throws AptParseException {
2122 int columns = 0;
2123
2124 for (int i = 2 ; i < lineLength; ++i) {
2125 switch (jline.charAt(i)) {
2126 case STAR:
2127 case PLUS:
2128 case COLON:
2129 ++columns;
2130 break;
2131 default:
2132 break;
2133 }
2134 }
2135
2136 if (columns == 0) {
2137 throw new AptParseException("no columns specified");
2138 }
2139
2140 int[] justification = new int[columns];
2141 columns = 0;
2142 for (int i = 2; i < lineLength; ++i) {
2143 switch (jline.charAt(i)) {
2144 case STAR:
2145 justification[columns++] = Sink.JUSTIFY_CENTER;
2146 break;
2147 case PLUS:
2148 justification[columns++] = Sink.JUSTIFY_LEFT;
2149 break;
2150 case COLON:
2151 justification[columns++] = Sink.JUSTIFY_RIGHT;
2152 break;
2153 default:
2154 break;
2155 }
2156 }
2157
2158 return justification;
2159 }
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170 private boolean traverseRow(StringBuilder[] cells, boolean[] headers, int[] justification)
2171 throws AptParseException {
2172
2173 boolean traversed = false;
2174 for (StringBuilder cell1 : cells) {
2175 if (cell1.length() > 0) {
2176 traversed = true;
2177 break;
2178 }
2179 }
2180
2181 if (traversed) {
2182 AptParser.this.sink.tableRow();
2183 for (int i = 0; i < cells.length; ++i) {
2184 StringBuilder cell = cells[i];
2185
2186 SinkEventAttributes justif;
2187 switch (justification[i]) {
2188 case Sink.JUSTIFY_CENTER:
2189 justif = SinkEventAttributeSet.CENTER;
2190 break;
2191 case Sink.JUSTIFY_LEFT:
2192 justif = SinkEventAttributeSet.LEFT;
2193 break;
2194 case Sink.JUSTIFY_RIGHT:
2195 justif = SinkEventAttributeSet.RIGHT;
2196 break;
2197 default:
2198 justif = SinkEventAttributeSet.LEFT;
2199 break;
2200 }
2201 SinkEventAttributeSet event = new SinkEventAttributeSet();
2202 event.addAttributes(justif);
2203
2204 if (headers[i]) {
2205 AptParser.this.sink.tableHeaderCell(event);
2206 } else {
2207 AptParser.this.sink.tableCell(event);
2208 }
2209 if (cell.length() > 0) {
2210 AptParser.this.doTraverseText(cell.toString(), 0, cell.length(), AptParser.this.sink);
2211 cell.setLength(0);
2212 }
2213 if (headers[i]) {
2214 AptParser.this.sink.tableHeaderCell_();
2215
2216 headers[i] = false;
2217 } else {
2218 AptParser.this.sink.tableCell_();
2219 }
2220 }
2221 AptParser.this.sink.tableRow_();
2222 }
2223
2224 return traversed;
2225 }
2226 }
2227
2228
2229 private class ListItem extends Block {
2230
2231
2232
2233
2234
2235
2236
2237 ListItem(int indent, String firstLine) throws AptParseException {
2238 super(LIST_ITEM, indent, firstLine);
2239 }
2240
2241
2242 public void traverse() throws AptParseException {
2243 traverseText(skipLeadingBullets());
2244 }
2245 }
2246
2247
2248 private class NumberedListItem extends Block {
2249
2250 private int numbering;
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260 NumberedListItem(int indent, String firstLine, int number) throws AptParseException {
2261 super(NUMBERED_LIST_ITEM, indent, firstLine);
2262 this.numbering = number;
2263 }
2264
2265
2266
2267
2268
2269
2270 public int getNumbering() {
2271 return numbering;
2272 }
2273
2274
2275 public void traverse() throws AptParseException {
2276 traverseText(skipItemNumber());
2277 }
2278
2279
2280
2281
2282
2283
2284
2285 private int skipItemNumber() throws AptParseException {
2286 int i = skipSpaceFrom(0);
2287
2288 char prevChar = SPACE;
2289 for (; i < textLength; ++i) {
2290 char c = text.charAt(i);
2291 if (c == RIGHT_SQUARE_BRACKET && prevChar == RIGHT_SQUARE_BRACKET) {
2292 break;
2293 }
2294 prevChar = c;
2295 }
2296
2297 if (i == textLength) {
2298 throw new AptParseException("missing '" + RIGHT_SQUARE_BRACKET + RIGHT_SQUARE_BRACKET + "'");
2299 }
2300
2301 return skipSpaceFrom(i + 1);
2302 }
2303 }
2304
2305
2306 private class DefinitionListItem extends Block {
2307
2308
2309
2310
2311
2312
2313
2314 DefinitionListItem(int indent, String firstLine) throws AptParseException {
2315 super(DEFINITION_LIST_ITEM, indent, firstLine);
2316 }
2317
2318
2319 public void traverse() throws AptParseException {
2320 int i = skipSpaceFrom(0);
2321 int j = skipFromLeftToRightBracket(i);
2322
2323 AptParser.this.sink.definedTerm();
2324 traverseText(i + 1, j);
2325 AptParser.this.sink.definedTerm_();
2326
2327 j = skipSpaceFrom(j + 1);
2328 if (j == textLength) {
2329
2330
2331 }
2332
2333 AptParser.this.sink.definition();
2334 traverseText(j);
2335 }
2336 }
2337
2338
2339 private class HorizontalRule extends Block {
2340
2341
2342
2343
2344
2345
2346
2347 HorizontalRule(int indent, String firstLine) throws AptParseException {
2348 super(HORIZONTAL_RULE, indent, firstLine);
2349 }
2350
2351
2352 public void traverse() throws AptParseException {
2353 AptParser.this.sink.horizontalRule();
2354 }
2355 }
2356
2357
2358 private class PageBreak extends Block {
2359
2360
2361
2362
2363
2364
2365
2366 PageBreak(int indent, String firstLine) throws AptParseException {
2367 super(PG_BREAK, indent, firstLine);
2368 }
2369
2370
2371 public void traverse() throws AptParseException {
2372 AptParser.this.sink.pageBreak();
2373 }
2374 }
2375
2376
2377 private class MacroBlock extends Block {
2378
2379
2380
2381
2382
2383
2384
2385 MacroBlock(int indent, String firstLine) throws AptParseException {
2386 super(MACRO, indent);
2387
2388 text = firstLine;
2389 }
2390
2391
2392 public void traverse() throws AptParseException {
2393 if (isSecondParsing()) {
2394 return;
2395 }
2396
2397 final int start = text.indexOf('{');
2398 final int end = text.indexOf('}');
2399
2400 String s = text.substring(start + 1, end);
2401
2402 s = escapeForMacro(s);
2403
2404 String[] params = StringUtils.split(s, "|");
2405
2406 String macroId = params[0];
2407
2408 Map<String, Object> parameters = new HashMap<>();
2409
2410 for (int i = 1; i < params.length; i++) {
2411 String[] param = StringUtils.split(params[i], "=");
2412
2413 if (param.length == 1) {
2414 throw new AptParseException("Missing 'key=value' pair for macro parameter: " + params[i]);
2415 }
2416
2417 String key = unescapeForMacro(param[0]);
2418 String value = unescapeForMacro(param[1]);
2419
2420 parameters.put(key, value);
2421 }
2422
2423
2424
2425 MacroRequest request = new MacroRequest(sourceContent, new AptParser(), parameters, getBasedir());
2426 try {
2427 AptParser.this.executeMacro(macroId, request, sink);
2428 } catch (MacroExecutionException e) {
2429 throw new AptParseException("Unable to execute macro in the APT document", e);
2430 } catch (MacroNotFoundException e) {
2431 throw new AptParseException("Unable to find macro used in the APT document", e);
2432 }
2433 }
2434
2435
2436
2437
2438
2439
2440
2441 private String escapeForMacro(String s) {
2442 if (s == null || s.length() < 1) {
2443 return s;
2444 }
2445
2446 String result = s;
2447
2448
2449
2450 result = StringUtils.replace(result, "\\=", "\u0011");
2451 result = StringUtils.replace(result, "\\|", "\u0012");
2452
2453 return result;
2454 }
2455
2456
2457
2458
2459
2460
2461
2462 private String unescapeForMacro(String s) {
2463 if (s == null || s.length() < 1) {
2464 return s;
2465 }
2466
2467 String result = s;
2468
2469 result = StringUtils.replace(result, "\u0011", "=");
2470 result = StringUtils.replace(result, "\u0012", "|");
2471
2472 return result;
2473 }
2474 }
2475 }