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