All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.maven.doxia.module.apt.AptParser Maven / Gradle / Ivy

Go to download

A Doxia module for Almost Plain Text source documents. APT format is supported both as source and target formats.

There is a newer version: 2.0.0-M12
Show newest version
package org.apache.maven.doxia.module.apt;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import org.apache.maven.doxia.macro.MacroExecutionException;
import org.apache.maven.doxia.macro.MacroRequest;
import org.apache.maven.doxia.macro.manager.MacroNotFoundException;
import org.apache.maven.doxia.parser.AbstractTextParser;
import org.apache.maven.doxia.parser.ParseException;
import org.apache.maven.doxia.parser.Parser;
import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.doxia.sink.SinkAdapter;
import org.apache.maven.doxia.sink.SinkEventAttributeSet;
import org.apache.maven.doxia.sink.SinkEventAttributes;
import org.apache.maven.doxia.util.DoxiaUtils;

import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringUtils;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;

/**
 * The APT parser.
 * 
* Based on the APTconvert project. * * @version $Id: AptParser.java 1465336 2013-04-07 07:39:00Z hboutemy $ * @since 1.0 */ @Component( role = Parser.class, hint = "apt" ) public class AptParser extends AbstractTextParser implements AptMarkup { /** Title event id */ private static final int TITLE = 0; /** Section 1 event id */ private static final int SECTION1 = 1; /** Section 2 event id */ private static final int SECTION2 = 2; /** Section 3 event id */ private static final int SECTION3 = 3; /** Section 4 event id */ private static final int SECTION4 = 4; /** Section 5 event id */ private static final int SECTION5 = 5; /** Paragraph event id */ private static final int PARAGRAPH = 6; /** Verbatim event id */ private static final int VERBATIM = 7; /** Figure event id */ private static final int FIGURE = 8; /** Table event id */ private static final int TABLE = 9; /** List event id */ private static final int LIST_ITEM = 10; /** Numbered list event id */ private static final int NUMBERED_LIST_ITEM = 11; /** Definition list event id */ private static final int DEFINITION_LIST_ITEM = 12; /** Horizontal rule event id */ private static final int HORIZONTAL_RULE = 13; /** Page break event id */ private static final int PG_BREAK = 14; /** List break event id */ private static final int LIST_BREAK = 15; /** Macro event id */ private static final int MACRO = 16; /** Comment event id. */ private static final int COMMENT_BLOCK = 17; /** String representations of event ids */ private static final String TYPE_NAMES[] = { "TITLE", "SECTION1", "SECTION2", "SECTION3", "SECTION4", "SECTION5", "PARAGRAPH", "VERBATIM", "FIGURE", "TABLE", "LIST_ITEM", "NUMBERED_LIST_ITEM", "DEFINITION_LIST_ITEM", "HORIZONTAL_RULE", "PG_BREAK", "LIST_BREAK", "MACRO", "COMMENT_BLOCK" }; /** An array of 85 spaces. */ protected static final char[] SPACES; /** Default tab width. */ public static final int TAB_WIDTH = 8; // ---------------------------------------------------------------------- // Instance fields // ---------------------------------------------------------------------- /** the AptSource. */ private AptSource source; /** a block of AptSource. */ private Block block; /** blockFileName. */ private String blockFileName; /** blockLineNumber. */ private int blockLineNumber; /** sourceContent. */ protected String sourceContent; /** the sink to receive the events. */ protected Sink sink; /** a line of AptSource. */ protected String line; /** Map of warn messages with a String as key to describe the error type and a Set as value. * Using to reduce warn messages. */ protected Map> warnMessages; private static final int NUMBER_OF_SPACES = 85; static { SPACES = new char[NUMBER_OF_SPACES]; for ( int i = 0; i < NUMBER_OF_SPACES; i++ ) { SPACES[i] = ' '; } } // ---------------------------------------------------------------------- // Public methods // ---------------------------------------------------------------------- /** {@inheritDoc} */ public void parse( Reader source, Sink sink ) throws ParseException { init(); try { StringWriter contentWriter = new StringWriter(); IOUtil.copy( source, contentWriter ); sourceContent = contentWriter.toString(); } catch ( IOException e ) { throw new AptParseException( "IOException: " + e.getMessage(), e ); } try { this.source = new AptReaderSource( new StringReader( sourceContent ) ); this.sink = sink; sink.enableLogging( getLog() ); blockFileName = null; blockLineNumber = -1; // Lookahead line. nextLine(); // Lookahead block. nextBlock( /*first*/true ); // traverse comments while ( ( block != null ) && ( block.getType() == COMMENT_BLOCK ) ) { block.traverse(); nextBlock( /*first*/true ); } traverseHead(); traverseBody(); } catch ( AptParseException ape ) { // TODO handle column number throw new AptParseException( ape.getMessage(), ape, getSourceName(), getSourceLineNumber(), -1 ); } finally { logWarnings(); setSecondParsing( false ); init(); } } /** * Returns the name of the Apt source document. * * @return the source name. */ public String getSourceName() { // Use this rather than source.getName() to report errors. return blockFileName; } /** * Returns the current line number of the Apt source document. * * @return the line number. */ public int getSourceLineNumber() { // Use this rather than source.getLineNumber() to report errors. return blockLineNumber; } // ---------------------------------------------------------------------- // Protected methods // ---------------------------------------------------------------------- /** * Parse the next line of the Apt source document. * * @throws org.apache.maven.doxia.module.apt.AptParseException if something goes wrong. */ protected void nextLine() throws AptParseException { line = source.getNextLine(); } /** * Parse the given text. * * @param text the text to parse. * @param begin offset. * @param end offset. * @param sink the sink to receive the events. * @throws org.apache.maven.doxia.module.apt.AptParseException if something goes wrong. */ protected void doTraverseText( String text, int begin, int end, Sink sink ) throws AptParseException { boolean anchor = false; boolean link = false; boolean italic = false; boolean bold = false; boolean monospaced = false; StringBuilder buffer = new StringBuilder( end - begin ); for ( int i = begin; i < end; ++i ) { char c = text.charAt( i ); switch ( c ) { case BACKSLASH: if ( i + 1 < end ) { char escaped = text.charAt( i + 1 ); switch ( escaped ) { case SPACE: ++i; flushTraversed( buffer, sink ); sink.nonBreakingSpace(); break; case '\r': case '\n': ++i; // Skip white space which may follow a line break. while ( i + 1 < end && Character.isWhitespace( text.charAt( i + 1 ) ) ) { ++i; } flushTraversed( buffer, sink ); sink.lineBreak(); break; case BACKSLASH: case PIPE: case COMMENT: case EQUAL: case MINUS: case PLUS: case STAR: case LEFT_SQUARE_BRACKET: case RIGHT_SQUARE_BRACKET: case LESS_THAN: case GREATER_THAN: case LEFT_CURLY_BRACKET: case RIGHT_CURLY_BRACKET: ++i; buffer.append( escaped ); break; case 'x': if ( i + 3 < end && isHexChar( text.charAt( i + 2 ) ) && isHexChar( text.charAt( i + 3 ) ) ) { int value = '?'; try { value = Integer.parseInt( text.substring( i + 2, i + 4 ), 16 ); } catch ( NumberFormatException e ) { if ( getLog().isDebugEnabled() ) { getLog().debug( "Not a number: " + text.substring( i + 2, i + 4 ) ); } } i += 3; buffer.append( (char) value ); } else { buffer.append( BACKSLASH ); } break; case 'u': if ( i + 5 < end && isHexChar( text.charAt( i + 2 ) ) && isHexChar( text.charAt( i + 3 ) ) && isHexChar( text.charAt( i + 4 ) ) && isHexChar( text.charAt( i + 5 ) ) ) { int value = '?'; try { value = Integer.parseInt( text.substring( i + 2, i + 6 ), 16 ); } catch ( NumberFormatException e ) { if ( getLog().isDebugEnabled() ) { getLog().debug( "Not a number: " + text.substring( i + 2, i + 6 ) ); } } i += 5; buffer.append( (char) value ); } else { buffer.append( BACKSLASH ); } break; default: if ( isOctalChar( escaped ) ) { int octalChars = 1; if ( isOctalChar( charAt( text, end, i + 2 ) ) ) { ++octalChars; if ( isOctalChar( charAt( text, end, i + 3 ) ) ) { ++octalChars; } } int value = '?'; try { value = Integer.parseInt( text.substring( i + 1, i + 1 + octalChars ), 8 ); } catch ( NumberFormatException e ) { if ( getLog().isDebugEnabled() ) { getLog().debug( "Not a number: " + text.substring( i + 1, i + 1 + octalChars ) ); } } i += octalChars; buffer.append( (char) value ); } else { buffer.append( BACKSLASH ); } } } else { buffer.append( BACKSLASH ); } break; case LEFT_CURLY_BRACKET: /*}*/ if ( !anchor && !link ) { if ( i + 1 < end && text.charAt( i + 1 ) == LEFT_CURLY_BRACKET /*}*/ ) { ++i; link = true; flushTraversed( buffer, sink ); String linkAnchor = null; if ( i + 1 < end && text.charAt( i + 1 ) == LEFT_CURLY_BRACKET /*}*/ ) { ++i; StringBuilder buf = new StringBuilder(); i = skipTraversedLinkAnchor( text, i + 1, end, buf ); linkAnchor = buf.toString(); } if ( linkAnchor == null ) { linkAnchor = getTraversedLink( text, i + 1, end ); } if ( AptUtils.isInternalLink( linkAnchor ) ) { linkAnchor = "#" + linkAnchor; } int hashIndex = linkAnchor.indexOf( "#" ); if ( hashIndex != -1 && !AptUtils.isExternalLink( linkAnchor ) ) { String hash = linkAnchor.substring( hashIndex + 1 ); if ( hash.endsWith( ".html" ) && !hash.startsWith( "./" ) ) { String msg = "Ambiguous link: '" + hash + "'. If this is a local link, prepend \"./\"!"; logMessage( "ambiguousLink", msg ); } // link##anchor means literal if ( hash.startsWith( "#" ) ) { linkAnchor = linkAnchor.substring( 0, hashIndex ) + hash; } else if ( !DoxiaUtils.isValidId( hash ) ) { linkAnchor = linkAnchor.substring( 0, hashIndex ) + "#" + DoxiaUtils.encodeId( hash, true ); String msg = "Modified invalid link: '" + hash + "' to '" + linkAnchor + "'"; logMessage( "modifiedLink", msg ); } } sink.link( linkAnchor ); } else { anchor = true; flushTraversed( buffer, sink ); String linkAnchor = getTraversedAnchor( text, i + 1, end ); linkAnchor = AptUtils.encodeAnchor( linkAnchor ); sink.anchor( linkAnchor ); } } else { buffer.append( c ); } break; case /*{*/RIGHT_CURLY_BRACKET: if ( link && i + 1 < end && text.charAt( i + 1 ) == /*{*/RIGHT_CURLY_BRACKET ) { ++i; link = false; flushTraversed( buffer, sink ); sink.link_(); } else if ( anchor ) { anchor = false; flushTraversed( buffer, sink ); sink.anchor_(); } else { buffer.append( c ); } break; case LESS_THAN: if ( !italic && !bold && !monospaced ) { if ( i + 1 < end && text.charAt( i + 1 ) == LESS_THAN ) { if ( i + 2 < end && text.charAt( i + 2 ) == LESS_THAN ) { i += 2; monospaced = true; flushTraversed( buffer, sink ); sink.monospaced(); } else { ++i; bold = true; flushTraversed( buffer, sink ); sink.bold(); } } else { italic = true; flushTraversed( buffer, sink ); sink.italic(); } } else { buffer.append( c ); } break; case GREATER_THAN: if ( monospaced && i + 2 < end && text.charAt( i + 1 ) == GREATER_THAN && text.charAt( i + 2 ) == GREATER_THAN ) { i += 2; monospaced = false; flushTraversed( buffer, sink ); sink.monospaced_(); } else if ( bold && i + 1 < end && text.charAt( i + 1 ) == GREATER_THAN ) { ++i; bold = false; flushTraversed( buffer, sink ); sink.bold_(); } else if ( italic ) { italic = false; flushTraversed( buffer, sink ); sink.italic_(); } else { buffer.append( c ); } break; default: if ( Character.isWhitespace( c ) ) { buffer.append( SPACE ); // Skip to the last char of a sequence of white spaces. while ( i + 1 < end && Character.isWhitespace( text.charAt( i + 1 ) ) ) { ++i; } } else { buffer.append( c ); } } } if ( monospaced ) { throw new AptParseException( "missing '" + MONOSPACED_END_MARKUP + "'" ); } if ( bold ) { throw new AptParseException( "missing '" + BOLD_END_MARKUP + "'" ); } if ( italic ) { throw new AptParseException( "missing '" + ITALIC_END_MARKUP + "'" ); } if ( link ) { throw new AptParseException( "missing '" + LINK_END_MARKUP + "'" ); } if ( anchor ) { throw new AptParseException( "missing '" + ANCHOR_END_MARKUP + "'" ); } flushTraversed( buffer, sink ); } // ----------------------------------------------------------------------- /** * Returns the character at position i of the given string. * * @param string the string. * @param length length. * @param i offset. * @return the character, or '\0' if i > length. */ protected static char charAt( String string, int length, int i ) { return ( i < length ) ? string.charAt( i ) : '\0'; } /** * Skip spaces. * * @param string string. * @param length length. * @param i offset. * @return int. */ protected static int skipSpace( String string, int length, int i ) { loop: for ( ; i < length; ++i ) { switch ( string.charAt( i ) ) { case SPACE: case TAB: break; default: break loop; } } return i; } /** * Replace part of a string. * * @param string the string * @param oldSub the substring to replace * @param newSub the replacement string * @return String */ protected static String replaceAll( String string, String oldSub, String newSub ) { StringBuilder replaced = new StringBuilder(); int oldSubLength = oldSub.length(); int begin, end; begin = 0; while ( ( end = string.indexOf( oldSub, begin ) ) >= 0 ) { if ( end > begin ) { replaced.append( string.substring( begin, end ) ); } replaced.append( newSub ); begin = end + oldSubLength; } if ( begin < string.length() ) { replaced.append( string.substring( begin ) ); } return replaced.toString(); } /** {@inheritDoc} */ protected void init() { super.init(); this.sourceContent = null; this.sink = null; this.source = null; this.block = null; this.blockFileName = null; this.blockLineNumber = 0; this.line = null; this.warnMessages = null; } // ---------------------------------------------------------------------- // Private methods // ---------------------------------------------------------------------- /** * Parse the head of the Apt source document. * * @throws AptParseException if something goes wrong. */ private void traverseHead() throws AptParseException { sink.head(); if ( block != null && block.getType() == TITLE ) { block.traverse(); nextBlock(); } sink.head_(); } /** * Parse the body of the Apt source document. * * @throws AptParseException if something goes wrong. */ private void traverseBody() throws AptParseException { sink.body(); if ( block != null ) { traverseSectionBlocks(); } while ( block != null ) { traverseSection( 0 ); } sink.body_(); } /** * Parse a section of the Apt source document. * * @param level The section level. * @throws AptParseException if something goes wrong. */ private void traverseSection( int level ) throws AptParseException { if ( block == null ) { return; } int type = SECTION1 + level; expectedBlock( type ); switch ( level ) { case 0: sink.section1(); break; case 1: sink.section2(); break; case 2: sink.section3(); break; case 3: sink.section4(); break; case 4: sink.section5(); break; default: break; } block.traverse(); nextBlock(); traverseSectionBlocks(); while ( block != null ) { if ( block.getType() <= type ) { break; } traverseSection( level + 1 ); } switch ( level ) { case 0: sink.section1_(); break; case 1: sink.section2_(); break; case 2: sink.section3_(); break; case 3: sink.section4_(); break; case 4: sink.section5_(); break; default: break; } } /** * Parse the section blocks of the Apt source document. * * @throws AptParseException if something goes wrong. */ private void traverseSectionBlocks() throws AptParseException { loop: while ( block != null ) { switch ( block.getType() ) { case PARAGRAPH: case VERBATIM: case FIGURE: case TABLE: case HORIZONTAL_RULE: case PG_BREAK: case MACRO: case COMMENT_BLOCK: block.traverse(); nextBlock(); break; case LIST_ITEM: traverseList(); break; case NUMBERED_LIST_ITEM: traverseNumberedList(); break; case DEFINITION_LIST_ITEM: traverseDefinitionList(); break; case LIST_BREAK: // May be this is a list break which has not been indented // very precisely. nextBlock(); break; default: // A section block which starts a new section. break loop; } } } /** * Parse a list of the Apt source document. * * @throws AptParseException if something goes wrong. */ private void traverseList() throws AptParseException { if ( block == null ) { return; } expectedBlock( LIST_ITEM ); int listIndent = block.getIndent(); sink.list(); sink.listItem(); block.traverse(); nextBlock(); loop: while ( block != null ) { int blockIndent = block.getIndent(); switch ( block.getType() ) { case PARAGRAPH: if ( blockIndent < listIndent ) { break loop; } /*FALLTHROUGH*/ case VERBATIM: case MACRO: case FIGURE: case TABLE: case HORIZONTAL_RULE: case PG_BREAK: block.traverse(); nextBlock(); break; case LIST_ITEM: if ( blockIndent < listIndent ) { break loop; } if ( blockIndent > listIndent ) { traverseList(); } else { sink.listItem_(); sink.listItem(); block.traverse(); nextBlock(); } break; case NUMBERED_LIST_ITEM: if ( blockIndent < listIndent ) { break loop; } traverseNumberedList(); break; case DEFINITION_LIST_ITEM: if ( blockIndent < listIndent ) { break loop; } traverseDefinitionList(); break; case LIST_BREAK: if ( blockIndent >= listIndent ) { nextBlock(); } /*FALLTHROUGH*/ default: // A block which ends the list. break loop; } } sink.listItem_(); sink.list_(); } /** * Parse a numbered list of the Apt source document. * * @throws AptParseException if something goes wrong. */ private void traverseNumberedList() throws AptParseException { if ( block == null ) { return; } expectedBlock( NUMBERED_LIST_ITEM ); int listIndent = block.getIndent(); sink.numberedList( ( (NumberedListItem) block ).getNumbering() ); sink.numberedListItem(); block.traverse(); nextBlock(); loop: while ( block != null ) { int blockIndent = block.getIndent(); switch ( block.getType() ) { case PARAGRAPH: if ( blockIndent < listIndent ) { break loop; } /*FALLTHROUGH*/ case VERBATIM: case FIGURE: case TABLE: case HORIZONTAL_RULE: case PG_BREAK: block.traverse(); nextBlock(); break; case LIST_ITEM: if ( blockIndent < listIndent ) { break loop; } traverseList(); break; case NUMBERED_LIST_ITEM: if ( blockIndent < listIndent ) { break loop; } if ( blockIndent > listIndent ) { traverseNumberedList(); } else { sink.numberedListItem_(); sink.numberedListItem(); block.traverse(); nextBlock(); } break; case DEFINITION_LIST_ITEM: if ( blockIndent < listIndent ) { break loop; } traverseDefinitionList(); break; case LIST_BREAK: if ( blockIndent >= listIndent ) { nextBlock(); } /*FALLTHROUGH*/ default: // A block which ends the list. break loop; } } sink.numberedListItem_(); sink.numberedList_(); } /** * Parse a definition list of the Apt source document. * * @throws AptParseException if something goes wrong. */ private void traverseDefinitionList() throws AptParseException { if ( block == null ) { return; } expectedBlock( DEFINITION_LIST_ITEM ); int listIndent = block.getIndent(); sink.definitionList(); sink.definitionListItem(); block.traverse(); nextBlock(); loop: while ( block != null ) { int blockIndent = block.getIndent(); switch ( block.getType() ) { case PARAGRAPH: if ( blockIndent < listIndent ) { break loop; } /*FALLTHROUGH*/ case VERBATIM: case FIGURE: case TABLE: case HORIZONTAL_RULE: case PG_BREAK: block.traverse(); nextBlock(); break; case LIST_ITEM: if ( blockIndent < listIndent ) { break loop; } traverseList(); break; case NUMBERED_LIST_ITEM: if ( blockIndent < listIndent ) { break loop; } traverseNumberedList(); break; case DEFINITION_LIST_ITEM: if ( blockIndent < listIndent ) { break loop; } if ( blockIndent > listIndent ) { traverseDefinitionList(); } else { sink.definition_(); sink.definitionListItem_(); sink.definitionListItem(); block.traverse(); nextBlock(); } break; case LIST_BREAK: if ( blockIndent >= listIndent ) { nextBlock(); } /*FALLTHROUGH*/ default: // A block which ends the list. break loop; } } sink.definition_(); sink.definitionListItem_(); sink.definitionList_(); } /** * Parse the next block of the Apt source document. * * @throws AptParseException if something goes wrong. */ private void nextBlock() throws AptParseException { nextBlock( /*first*/false ); } /** * Parse the next block of the Apt source document. * * @param firstBlock True if this is the first block of the Apt source document. * @throws AptParseException if something goes wrong. */ private void nextBlock( boolean firstBlock ) throws AptParseException { // Skip open lines. int length, indent, i; skipLoop: for ( ;; ) { if ( line == null ) { block = null; return; } length = line.length(); indent = 0; for ( i = 0; i < length; ++i ) { switch ( line.charAt( i ) ) { case SPACE: ++indent; break; case TAB: indent += 8; break; default: break skipLoop; } } if ( i == length ) { nextLine(); } } blockFileName = source.getName(); blockLineNumber = source.getLineNumber(); block = null; switch ( line.charAt( i ) ) { case STAR: if ( indent == 0 ) { if ( charAt( line, length, i + 1 ) == MINUS && charAt( line, length, i + 2 ) == MINUS ) { block = new Table( indent, line ); } else if ( charAt( line, length, i + 1 ) == STAR ) { if ( charAt( line, length, i + 2 ) == STAR ) { if ( charAt( line, length, i + 3 ) == STAR ) { block = new Section5( indent, line ); } else { block = new Section4( indent, line ); } } else { block = new Section3( indent, line ); } } else { block = new Section2( indent, line ); } } else { block = new ListItem( indent, line ); } break; case LEFT_SQUARE_BRACKET: if ( charAt( line, length, i + 1 ) == RIGHT_SQUARE_BRACKET ) { block = new ListBreak( indent, line ); } else { if ( indent == 0 ) { block = new Figure( indent, line ); } else { if ( charAt( line, length, i + 1 ) == LEFT_SQUARE_BRACKET ) { int numbering; switch ( charAt( line, length, i + 2 ) ) { case NUMBERING_LOWER_ALPHA_CHAR: numbering = Sink.NUMBERING_LOWER_ALPHA; break; case NUMBERING_UPPER_ALPHA_CHAR: numbering = Sink.NUMBERING_UPPER_ALPHA; break; case NUMBERING_LOWER_ROMAN_CHAR: numbering = Sink.NUMBERING_LOWER_ROMAN; break; case NUMBERING_UPPER_ROMAN_CHAR: numbering = Sink.NUMBERING_UPPER_ROMAN; break; case NUMBERING: default: // The first item establishes the numbering // scheme for the whole list. numbering = Sink.NUMBERING_DECIMAL; } block = new NumberedListItem( indent, line, numbering ); } else { block = new DefinitionListItem( indent, line ); } } } break; case MINUS: if ( charAt( line, length, i + 1 ) == MINUS && charAt( line, length, i + 2 ) == MINUS ) { if ( indent == 0 ) { block = new Verbatim( indent, line ); } else { if ( firstBlock ) { block = new Title( indent, line ); } } } break; case PLUS: if ( indent == 0 && charAt( line, length, i + 1 ) == MINUS && charAt( line, length, i + 2 ) == MINUS ) { block = new Verbatim( indent, line ); } break; case EQUAL: if ( indent == 0 && charAt( line, length, i + 1 ) == EQUAL && charAt( line, length, i + 2 ) == EQUAL ) { block = new HorizontalRule( indent, line ); } break; case PAGE_BREAK: if ( indent == 0 ) { block = new PageBreak( indent, line ); } break; case PERCENT: if ( indent == 0 && charAt( line, length, i + 1 ) == LEFT_CURLY_BRACKET ) { block = new MacroBlock( indent, line ); } break; case COMMENT: if ( charAt( line, length, i + 1 ) == COMMENT ) { block = new Comment( line.substring( i + 2 ).trim() ); } break; default: break; } if ( block == null ) { if ( indent == 0 ) { block = new Section1( indent, line ); } else { block = new Paragraph( indent, line ); } } } /** * Checks that the current block is of the expected type. * * @param type the expected type. * @throws AptParseException if something goes wrong. */ private void expectedBlock( int type ) throws AptParseException { int blockType = block.getType(); if ( blockType != type ) { throw new AptParseException( "expected " + TYPE_NAMES[type] + ", found " + TYPE_NAMES[blockType] ); } } // ----------------------------------------------------------------------- /** * Determine if c is an octal character. * * @param c the character. * @return boolean */ private static boolean isOctalChar( char c ) { return ( c >= '0' && c <= '7' ); } /** * Determine if c is an hex character. * * @param c the character. * @return boolean */ private static boolean isHexChar( char c ) { return ( ( c >= '0' && c <= '9' ) || ( c >= 'a' && c <= 'f' ) || ( c >= 'A' && c <= 'F' ) ); } /** * Emits the text so far parsed into the given sink. * * @param buffer A StringBuilder that contains the text to be flushed. * @param sink The sink to receive the text. */ private static void flushTraversed( StringBuilder buffer, Sink sink ) { if ( buffer.length() > 0 ) { sink.text( buffer.toString() ); buffer.setLength( 0 ); } } /** * Parse the given text. * * @param text the text to parse. * @param begin offset. * @param end offset. * @param linkAnchor a StringBuilder. * @return int * @throws AptParseException if something goes wrong. */ private static int skipTraversedLinkAnchor( String text, int begin, int end, StringBuilder linkAnchor ) throws AptParseException { int i; loop: for ( i = begin; i < end; ++i ) { char c = text.charAt( i ); switch ( c ) { case RIGHT_CURLY_BRACKET: break loop; case BACKSLASH: if ( i + 1 < end ) { ++i; linkAnchor.append( text.charAt( i ) ); } else { linkAnchor.append( BACKSLASH ); } break; default: linkAnchor.append( c ); } } if ( i == end ) { throw new AptParseException( "missing '" + RIGHT_CURLY_BRACKET + "'" ); } return i; } /** * Parse the given text. * * @param text the text to parse. * @param begin offset. * @param end offset. * @return String * @throws AptParseException if something goes wrong. */ private String getTraversedLink( String text, int begin, int end ) throws AptParseException { char previous2 = LEFT_CURLY_BRACKET; char previous = LEFT_CURLY_BRACKET; int i; for ( i = begin; i < end; ++i ) { char c = text.charAt( i ); if ( c == RIGHT_CURLY_BRACKET && previous == RIGHT_CURLY_BRACKET && previous2 != BACKSLASH ) { break; } previous2 = previous; previous = c; } if ( i == end ) { throw new AptParseException( "missing '" + LEFT_CURLY_BRACKET + LEFT_CURLY_BRACKET + "'" ); } return doGetTraversedLink( text, begin, i - 1 ); } /** * Parse the given text. * * @param text the text to parse. * @param begin offset. * @param end offset. * @return String * @throws AptParseException if something goes wrong. */ private String getTraversedAnchor( String text, int begin, int end ) throws AptParseException { char previous = LEFT_CURLY_BRACKET; int i; for ( i = begin; i < end; ++i ) { char c = text.charAt( i ); if ( c == RIGHT_CURLY_BRACKET && previous != BACKSLASH ) { break; } previous = c; } if ( i == end ) { throw new AptParseException( "missing '" + RIGHT_CURLY_BRACKET + "'" ); } return doGetTraversedLink( text, begin, i ); } /** * Parse the given text. * * @param text the text to parse. * @param begin offset. * @param end offset. * @return String * @throws AptParseException if something goes wrong. */ private String doGetTraversedLink( String text, int begin, int end ) throws AptParseException { final StringBuilder buffer = new StringBuilder( end - begin ); Sink linkSink = new SinkAdapter() { /** {@inheritDoc} */ public void lineBreak() { buffer.append( SPACE ); } /** {@inheritDoc} */ public void nonBreakingSpace() { buffer.append( SPACE ); } /** {@inheritDoc} */ public void text( String text ) { buffer.append( text ); } }; doTraverseText( text, begin, end, linkSink ); return buffer.toString().trim(); } /** * If debug mode is enabled, log the msg as is, otherwise add unique msg in warnMessages. * * @param key not null * @param msg not null * @see #parse(Reader, Sink) * @since 1.1.1 */ private void logMessage( String key, String msg ) { msg = "[APT Parser] " + msg; if ( getLog().isDebugEnabled() ) { getLog().debug( msg ); return; } if ( warnMessages == null ) { warnMessages = new HashMap>(); } Set set = warnMessages.get( key ); if ( set == null ) { set = new TreeSet(); } set.add( msg ); warnMessages.put( key, set ); } /** * @since 1.1.2 */ private void logWarnings() { if ( getLog().isWarnEnabled() && this.warnMessages != null && !isSecondParsing() ) { for ( Map.Entry> entry : this.warnMessages.entrySet() ) { for ( String msg : entry.getValue() ) { getLog().warn( msg ); } } this.warnMessages = null; } } // ----------------------------------------------------------------------- /** A block of an apt source document. */ private abstract class Block { /** type. */ protected int type; /** indent. */ protected int indent; /** text. */ protected String text; /** textLength. */ protected int textLength; /** * Constructor. * * @param type the block type. * @param indent indent. * @throws AptParseException AptParseException */ public Block( int type, int indent ) throws AptParseException { this( type, indent, null ); } /** * Constructor. * * @param type type. * @param indent indent. * @param firstLine the first line. * @throws AptParseException AptParseException */ public Block( int type, int indent, String firstLine ) throws AptParseException { this.type = type; this.indent = indent; // Skip first line --- AptParser.this.nextLine(); if ( firstLine == null ) { text = null; textLength = 0; } else { // Read block --- StringBuilder buffer = new StringBuilder( firstLine ); while ( AptParser.this.line != null ) { String l = AptParser.this.line; int length = l.length(); int i = 0; i = skipSpace( l, length, i ); if ( i == length ) { // Stop after open line and skip it. AptParser.this.nextLine(); break; } else if ( ( AptParser.charAt( l, length, i ) == COMMENT && AptParser.charAt( l, length, i + 1 ) == COMMENT ) || type == COMMENT_BLOCK ) { // parse comments as separate blocks line by line break; } buffer.append( EOL ); buffer.append( l ); AptParser.this.nextLine(); } text = buffer.toString(); textLength = text.length(); } } /** * Return the block type. * * @return int */ public final int getType() { return type; } /** * Return the block indent. * * @return int */ public final int getIndent() { return indent; } /** * Parse the block. * * @throws AptParseException if something goes wrong. */ public abstract void traverse() throws AptParseException; /** * Traverse the text. * * @param begin offset. * @throws AptParseException if something goes wrong. */ protected void traverseText( int begin ) throws AptParseException { traverseText( begin, text.length() ); } /** * Traverse the text. * * @param begin offset. * @param end offset. * @throws AptParseException if something goes wrong. */ protected void traverseText( int begin, int end ) throws AptParseException { AptParser.this.doTraverseText( text, begin, end, AptParser.this.sink ); } /** * Skip spaces. * * @return int. */ protected int skipLeadingBullets() { int i = skipSpaceFrom( 0 ); for ( ; i < textLength; ++i ) { if ( text.charAt( i ) != STAR ) { break; } } return skipSpaceFrom( i ); } /** * Skip brackets. * * @param i offset. * @return int. * @throws AptParseException if something goes wrong. */ protected int skipFromLeftToRightBracket( int i ) throws AptParseException { char previous = LEFT_SQUARE_BRACKET; for ( ++i; i < textLength; ++i ) { char c = text.charAt( i ); if ( c == RIGHT_SQUARE_BRACKET && previous != BACKSLASH ) { break; } previous = c; } if ( i == textLength ) { throw new AptParseException( "missing '" + RIGHT_SQUARE_BRACKET + "'" ); } return i; } /** * Skip spaces. * * @param i offset. * @return int. */ protected final int skipSpaceFrom( int i ) { return AptParser.skipSpace( text, textLength, i ); } } /** A ListBreak Block. */ private class ListBreak extends AptParser.Block { /** * Constructor. * * @param indent indent. * @param firstLine the first line. * @throws AptParseException AptParseException */ public ListBreak( int indent, String firstLine ) throws AptParseException { super( AptParser.LIST_BREAK, indent, firstLine ); } /** {@inheritDoc} */ public void traverse() throws AptParseException { throw new AptParseException( "internal error: traversing list break" ); } } /** A Title Block. */ private class Title extends Block { /** * Constructor. * * @param indent indent. * @param firstLine the first line. * @throws AptParseException AptParseException */ public Title( int indent, String firstLine ) throws AptParseException { super( TITLE, indent, firstLine ); } /** {@inheritDoc} */ public void traverse() throws AptParseException { StringTokenizer lines = new StringTokenizer( text, EOL ); int separator = -1; boolean firstLine = true; boolean title = false; boolean author = false; boolean date = false; loop: while ( lines.hasMoreTokens() ) { String line = lines.nextToken().trim(); int lineLength = line.length(); if ( AptParser.charAt( line, lineLength, 0 ) == MINUS && AptParser.charAt( line, lineLength, 1 ) == MINUS && AptParser.charAt( line, lineLength, 2 ) == MINUS ) { switch ( separator ) { case 0: if ( title ) { AptParser.this.sink.title_(); } else { throw new AptParseException( "missing title" ); } break; case 1: if ( author ) { AptParser.this.sink.author_(); } break; case 2: // Note that an extra decorative line is allowed // at the end of the author. break loop; default: break; } ++separator; firstLine = true; } else { if ( firstLine ) { firstLine = false; switch ( separator ) { case 0: title = true; AptParser.this.sink.title(); break; case 1: author = true; AptParser.this.sink.author(); break; case 2: date = true; AptParser.this.sink.date(); break; default: break; } } else { // An implicit lineBreak separates title lines. AptParser.this.sink.lineBreak(); } AptParser.this.doTraverseText( line, 0, lineLength, AptParser.this.sink ); } } switch ( separator ) { case 0: if ( title ) { AptParser.this.sink.title_(); } else { throw new AptParseException( "missing title" ); } break; case 1: if ( author ) { AptParser.this.sink.author_(); } break; case 2: if ( date ) { AptParser.this.sink.date_(); } break; default: break; } } } /** A Section Block. */ private abstract class Section extends Block { /** * Constructor. * * @param type type. * @param indent indent. * @param firstLine the first line. * @throws AptParseException AptParseException */ public Section( int type, int indent, String firstLine ) throws AptParseException { super( type, indent, firstLine ); } /** {@inheritDoc} */ public void traverse() throws AptParseException { Title(); traverseText( skipLeadingBullets() ); Title_(); } /** Start a title. */ public abstract void Title(); /** End a title. */ public abstract void Title_(); } /** A Section1 Block. */ private class Section1 extends Section { /** * Constructor. * * @param indent indent. * @param firstLine the first line. * @throws AptParseException AptParseException */ public Section1( int indent, String firstLine ) throws AptParseException { super( SECTION1, indent, firstLine ); } /** {@inheritDoc} */ public void Title() { AptParser.this.sink.sectionTitle1(); } /** {@inheritDoc} */ public void Title_() { AptParser.this.sink.sectionTitle1_(); } } /** A Section2 Block. */ private class Section2 extends Section { /** * Constructor. * * @param indent indent. * @param firstLine the first line. * @throws AptParseException AptParseException */ public Section2( int indent, String firstLine ) throws AptParseException { super( SECTION2, indent, firstLine ); } /** {@inheritDoc} */ public void Title() { AptParser.this.sink.sectionTitle2(); } /** {@inheritDoc} */ public void Title_() { AptParser.this.sink.sectionTitle2_(); } } /** A Section3 Block. */ private class Section3 extends Section { /** * Constructor. * * @param indent indent. * @param firstLine the first line. * @throws AptParseException AptParseException */ public Section3( int indent, String firstLine ) throws AptParseException { super( SECTION3, indent, firstLine ); } /** {@inheritDoc} */ public void Title() { AptParser.this.sink.sectionTitle3(); } /** {@inheritDoc} */ public void Title_() { AptParser.this.sink.sectionTitle3_(); } } /** A Section4 Block. */ private class Section4 extends Section { /** * Constructor. * * @param indent indent. * @param firstLine the first line. * @throws AptParseException AptParseException */ public Section4( int indent, String firstLine ) throws AptParseException { super( SECTION4, indent, firstLine ); } /** {@inheritDoc} */ public void Title() { AptParser.this.sink.sectionTitle4(); } /** {@inheritDoc} */ public void Title_() { AptParser.this.sink.sectionTitle4_(); } } /** A Section5 Block. */ private class Section5 extends Section { /** * Constructor. * * @param indent indent. * @param firstLine the first line. * @throws AptParseException AptParseException */ public Section5( int indent, String firstLine ) throws AptParseException { super( SECTION5, indent, firstLine ); } /** {@inheritDoc} */ public void Title() { AptParser.this.sink.sectionTitle5(); } /** {@inheritDoc} */ public void Title_() { AptParser.this.sink.sectionTitle5_(); } } /** A Paragraph Block. */ private class Paragraph extends Block { /** * Constructor. * * @param indent indent. * @param firstLine the first line. * @throws AptParseException AptParseException */ public Paragraph( int indent, String firstLine ) throws AptParseException { super( PARAGRAPH, indent, firstLine ); } /** {@inheritDoc} */ public void traverse() throws AptParseException { AptParser.this.sink.paragraph(); traverseText( skipSpaceFrom( 0 ) ); AptParser.this.sink.paragraph_(); } } /** A Comment Block. */ private class Comment extends Block { /** * Constructor. * * @param line the comment line. * @throws AptParseException AptParseException */ public Comment( String line ) throws AptParseException { super( COMMENT_BLOCK, 0, line ); } /** {@inheritDoc} */ public void traverse() throws AptParseException { AptParser.this.sink.comment( text ); } } /** A Verbatim Block. */ private class Verbatim extends Block { /** boxed. */ private boolean boxed; /** * Constructor. * * @param indent indent. * @param firstLine the first line. * @throws AptParseException AptParseException */ public Verbatim( int indent, String firstLine ) throws AptParseException { super( VERBATIM, indent, null ); // Read block (first line already skipped) --- StringBuilder buffer = new StringBuilder(); char firstChar = firstLine.charAt( 0 ); boxed = ( firstChar == PLUS ); while ( AptParser.this.line != null ) { String l = AptParser.this.line; int length = l.length(); if ( AptParser.charAt( l, length, 0 ) == firstChar && AptParser.charAt( l, length, 1 ) == MINUS && AptParser.charAt( l, length, 2 ) == MINUS ) { AptParser.this.nextLine(); break; } // Expand tabs --- int prevColumn, column; column = 0; for ( int i = 0; i < length; ++i ) { char c = l.charAt( i ); if ( c == TAB ) { prevColumn = column; column = ( ( column + 1 + TAB_WIDTH - 1 ) / TAB_WIDTH ) * TAB_WIDTH; buffer.append( SPACES, 0, column - prevColumn ); } else { ++column; buffer.append( c ); } } buffer.append( EOL ); AptParser.this.nextLine(); } // The last '\n' is mandatory before the "---" delimeter but is // not part of the verbatim text. textLength = buffer.length(); if ( textLength > 0 ) { --textLength; buffer.setLength( textLength ); } text = buffer.toString(); } /** {@inheritDoc} */ public void traverse() throws AptParseException { AptParser.this.sink.verbatim( boxed ? SinkEventAttributeSet.BOXED : null ); AptParser.this.sink.text( text ); AptParser.this.sink.verbatim_(); } } /** A Figure Block. */ private class Figure extends Block { /** * Constructor. * * @param indent indent. * @param firstLine the first line. * @throws AptParseException AptParseException */ public Figure( int indent, String firstLine ) throws AptParseException { super( FIGURE, indent, firstLine ); } /** {@inheritDoc} */ public void traverse() throws AptParseException { AptParser.this.sink.figure(); int i = skipFromLeftToRightBracket( 0 ); AptParser.this.sink.figureGraphics( text.substring( 1, i ) ); i = skipSpaceFrom( i + 1 ); if ( i < textLength ) { AptParser.this.sink.figureCaption(); traverseText( i ); AptParser.this.sink.figureCaption_(); } AptParser.this.sink.figure_(); } } /** A Table Block. */ private class Table extends Block { /** * Constructor. * * @param indent indent. * @param firstLine the first line. * @throws AptParseException AptParseException */ public Table( int indent, String firstLine ) throws AptParseException { super( TABLE, indent, firstLine ); } /** {@inheritDoc} */ public void traverse() throws AptParseException { int captionIndex = -1; int nextLineIndex = 0; int init = 2; int[] justification = null; int rows = 0; int columns = 0; StringBuilder[] cells = null; boolean[] headers = null; boolean grid; AptParser.this.sink.table(); while ( nextLineIndex < textLength ) { int i = text.indexOf( "*--", nextLineIndex ); if ( i < 0 ) { captionIndex = nextLineIndex; break; } String line; i = text.indexOf( '\n', nextLineIndex ); if ( i < 0 ) { line = text.substring( nextLineIndex ); nextLineIndex = textLength; } else { line = text.substring( nextLineIndex, i ); nextLineIndex = i + 1; } int lineLength = line.length(); if ( line.indexOf( "*--" ) == 0 ) { if ( init == 2 ) { init = 1; justification = parseJustification( line, lineLength ); columns = justification.length; cells = new StringBuilder[columns]; headers = new boolean[columns]; for ( i = 0; i < columns; ++i ) { cells[i] = new StringBuilder(); headers[i] = false; } } else { if ( traverseRow( cells, headers, justification ) ) { ++rows; } justification = parseJustification( line, lineLength ); } } else { if ( init == 1 ) { init = 0; grid = ( AptParser.charAt( line, lineLength, 0 ) == PIPE ); AptParser.this.sink.tableRows( justification, grid ); } line = replaceAll( line, "\\|", "\\u007C" ); StringTokenizer cellLines = new StringTokenizer( line, "|", true ); i = 0; boolean processedGrid = false; while ( cellLines.hasMoreTokens() ) { String cellLine = cellLines.nextToken(); if ( "|".equals( cellLine ) ) { if ( processedGrid ) { headers[i] = true; } else { processedGrid = true; headers[i] = false; } continue; } processedGrid = false; cellLine = replaceAll( cellLine, "\\", "\\u00A0" ); // linebreak // Escaped special characters: \~, \=, \-, \+, \*, \[, \], \<, \>, \{, \}, \\. cellLine = replaceAll( cellLine, "\\u00A0~", "\\~" ); cellLine = replaceAll( cellLine, "\\u00A0=", "\\=" ); cellLine = replaceAll( cellLine, "\\u00A0-", "\\-" ); cellLine = replaceAll( cellLine, "\\u00A0+", "\\+" ); cellLine = replaceAll( cellLine, "\\u00A0*", "\\*" ); cellLine = replaceAll( cellLine, "\\u00A0[", "\\[" ); cellLine = replaceAll( cellLine, "\\u00A0]", "\\]" ); cellLine = replaceAll( cellLine, "\\u00A0<", "\\<" ); cellLine = replaceAll( cellLine, "\\u00A0>", "\\>" ); cellLine = replaceAll( cellLine, "\\u00A0{", "\\{" ); cellLine = replaceAll( cellLine, "\\u00A0}", "\\}" ); cellLine = replaceAll( cellLine, "\\u00A0u", "\\u" ); cellLine = replaceAll( cellLine, "\\u00A0\\u00A0", "\\\\" ); cellLine = cellLine.trim(); StringBuilder cell = cells[i]; if ( cellLine.length() > 0 ) { // line break in table cells if ( cell.toString().trim().endsWith( "\\u00A0" ) ) { cell.append( "\\\n" ); } else { if ( cell.length() != 0 ) { // Always add a space for multi line tables cells cell.append( " " ); } } cell.append( cellLine ); } ++i; if ( i == columns ) { break; } } } } if ( rows == 0 ) { throw new AptParseException( "no table rows" ); } AptParser.this.sink.tableRows_(); if ( captionIndex >= 0 ) { AptParser.this.sink.tableCaption(); AptParser.this.doTraverseText( text, captionIndex, textLength, AptParser.this.sink ); AptParser.this.sink.tableCaption_(); } AptParser.this.sink.table_(); } /** * Parse a table justification line. * * @param jline the justification line. * @param lineLength the length of the line. Must be > 2. * @return int[] * @throws AptParseException if something goes wrong. */ private int[] parseJustification( String jline, int lineLength ) throws AptParseException { int columns = 0; for ( int i = 2 /*Skip '*--'*/; i < lineLength; ++i ) { switch ( jline.charAt( i ) ) { case STAR: case PLUS: case COLON: ++columns; break; default: break; } } if ( columns == 0 ) { throw new AptParseException( "no columns specified" ); } int[] justification = new int[columns]; columns = 0; for ( int i = 2; i < lineLength; ++i ) { switch ( jline.charAt( i ) ) { case STAR: justification[columns++] = Sink.JUSTIFY_CENTER; break; case PLUS: justification[columns++] = Sink.JUSTIFY_LEFT; break; case COLON: justification[columns++] = Sink.JUSTIFY_RIGHT; break; default: break; } } return justification; } /** * Traverse a table row. * * @param cells The table cells. * @param headers true for header cells. * @param justification the justification for each cell. * @return boolean * @throws AptParseException if something goes wrong. */ private boolean traverseRow( StringBuilder[] cells, boolean[] headers, int[] justification ) throws AptParseException { // Skip empty row (a decorative line). boolean traversed = false; for ( int i = 0; i < cells.length; ++i ) { if ( cells[i].length() > 0 ) { traversed = true; break; } } if ( traversed ) { AptParser.this.sink.tableRow(); for ( int i = 0; i < cells.length; ++i ) { StringBuilder cell = cells[i]; SinkEventAttributes justif; switch ( justification[i] ) { case Sink.JUSTIFY_CENTER: justif = SinkEventAttributeSet.CENTER; break; case Sink.JUSTIFY_LEFT: justif = SinkEventAttributeSet.LEFT; break; case Sink.JUSTIFY_RIGHT: justif = SinkEventAttributeSet.RIGHT; break; default: justif = SinkEventAttributeSet.LEFT; break; } SinkEventAttributeSet event = new SinkEventAttributeSet(); event.addAttributes( justif ); if ( headers[i] ) { AptParser.this.sink.tableHeaderCell( event ); } else { AptParser.this.sink.tableCell( event ); } if ( cell.length() > 0 ) { AptParser.this.doTraverseText( cell.toString(), 0, cell.length(), AptParser.this.sink ); cell.setLength( 0 ); } if ( headers[i] ) { AptParser.this.sink.tableHeaderCell_(); // DOXIA-404: reset header for next row headers[i] = false; } else { AptParser.this.sink.tableCell_(); } } AptParser.this.sink.tableRow_(); } return traversed; } } /** A ListItem Block. */ private class ListItem extends Block { /** * Constructor. * * @param indent indent. * @param firstLine the first line. * @throws AptParseException AptParseException */ public ListItem( int indent, String firstLine ) throws AptParseException { super( LIST_ITEM, indent, firstLine ); } /** {@inheritDoc} */ public void traverse() throws AptParseException { traverseText( skipLeadingBullets() ); } } /** A NumberedListItem Block. */ private class NumberedListItem extends Block { /** numbering. */ private int numbering; /** * Constructor. * * @param indent indent. * @param firstLine the first line. * @param number numbering. * @throws AptParseException AptParseException */ public NumberedListItem( int indent, String firstLine, int number ) throws AptParseException { super( NUMBERED_LIST_ITEM, indent, firstLine ); this.numbering = number; } /** * getNumbering. * * @return int */ public int getNumbering() { return numbering; } /** {@inheritDoc} */ public void traverse() throws AptParseException { traverseText( skipItemNumber() ); } /** * skipItemNumber. * * @return int * @throws AptParseException AptParseException */ private int skipItemNumber() throws AptParseException { int i = skipSpaceFrom( 0 ); char prevChar = SPACE; for ( ; i < textLength; ++i ) { char c = text.charAt( i ); if ( c == RIGHT_SQUARE_BRACKET && prevChar == RIGHT_SQUARE_BRACKET ) { break; } prevChar = c; } if ( i == textLength ) { throw new AptParseException( "missing '" + RIGHT_SQUARE_BRACKET + RIGHT_SQUARE_BRACKET + "'" ); } return skipSpaceFrom( i + 1 ); } } /** A DefinitionListItem Block. */ private class DefinitionListItem extends Block { /** * Constructor. * * @param indent indent. * @param firstLine the first line. * @throws AptParseException AptParseException */ public DefinitionListItem( int indent, String firstLine ) throws AptParseException { super( DEFINITION_LIST_ITEM, indent, firstLine ); } /** {@inheritDoc} */ public void traverse() throws AptParseException { int i = skipSpaceFrom( 0 ); int j = skipFromLeftToRightBracket( i ); AptParser.this.sink.definedTerm(); traverseText( i + 1, j ); AptParser.this.sink.definedTerm_(); j = skipSpaceFrom( j + 1 ); if ( j == textLength ) { // TODO: this doesn't handle the case of a dd in a paragraph //throw new AptParseException( "no definition" ); } AptParser.this.sink.definition(); traverseText( j ); } } /** A HorizontalRule Block. */ private class HorizontalRule extends Block { /** * Constructor. * * @param indent indent. * @param firstLine the first line. * @throws AptParseException AptParseException */ public HorizontalRule( int indent, String firstLine ) throws AptParseException { super( HORIZONTAL_RULE, indent, firstLine ); } /** {@inheritDoc} */ public void traverse() throws AptParseException { AptParser.this.sink.horizontalRule(); } } /** A PageBreak Block. */ private class PageBreak extends Block { /** * Constructor. * * @param indent indent. * @param firstLine the first line. * @throws AptParseException AptParseException */ public PageBreak( int indent, String firstLine ) throws AptParseException { super( PG_BREAK, indent, firstLine ); } /** {@inheritDoc} */ public void traverse() throws AptParseException { AptParser.this.sink.pageBreak(); } } /** A MacroBlock Block. */ private class MacroBlock extends Block { /** * Constructor. * * @param indent indent. * @param firstLine the first line. * @throws AptParseException AptParseException */ public MacroBlock( int indent, String firstLine ) throws AptParseException { super( MACRO, indent ); text = firstLine; } /** {@inheritDoc} */ public void traverse() throws AptParseException { if ( isSecondParsing() ) { return; } final int start = text.indexOf( '{' ); final int end = text.indexOf( '}' ); String s = text.substring( start + 1, end ); s = escapeForMacro( s ); String[] params = StringUtils.split( s, "|" ); String macroId = params[0]; Map parameters = new HashMap(); for ( int i = 1; i < params.length; i++ ) { String[] param = StringUtils.split( params[i], "=" ); if ( param.length == 1 ) { throw new AptParseException( "Missing 'key=value' pair for macro parameter: " + params[i] ); } String key = unescapeForMacro( param[0] ); String value = unescapeForMacro( param[1] ); parameters.put( key, value ); } parameters.put( "sourceContent", sourceContent ); AptParser aptParser = new AptParser(); aptParser.setSecondParsing( true ); aptParser.enableLogging( getLog() ); parameters.put( "parser", aptParser ); // getBasedir() does not work in multi-module builds, see DOXIA-373 // the basedir should be injected from here, see DOXIA-224 MacroRequest request = new MacroRequest( parameters, getBasedir() ); try { AptParser.this.executeMacro( macroId, request, sink ); } catch ( MacroExecutionException e ) { throw new AptParseException( "Unable to execute macro in the APT document", e ); } catch ( MacroNotFoundException e ) { throw new AptParseException( "Unable to find macro used in the APT document", e ); } } /** * escapeForMacro * * @param s String * @return String */ private String escapeForMacro( String s ) { if ( s == null || s.length() < 1 ) { return s; } String result = s; // use some outrageously out-of-place chars for text // (these are device control one/two in unicode) result = StringUtils.replace( result, "\\=", "\u0011" ); result = StringUtils.replace( result, "\\|", "\u0012" ); return result; } /** * unescapeForMacro * * @param s String * @return String */ private String unescapeForMacro( String s ) { if ( s == null || s.length() < 1 ) { return s; } String result = s; result = StringUtils.replace( result, "\u0011", "=" ); result = StringUtils.replace( result, "\u0012", "|" ); return result; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy