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