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