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