| package org.apache.maven.doxia.module.confluence.parser; |
| |
| /* |
| * 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 java.util.ArrayList; |
| import java.util.List; |
| |
| import org.codehaus.plexus.util.StringUtils; |
| |
| /** |
| * Re-usable builder that can be used to generate paragraph and list item text from a string containing all the content |
| * and wiki formatting. This class is intentionally stateful, but cheap to create, so create one as needed and keep it |
| * on the stack to preserve stateless behaviour in the caller. |
| * |
| * @author Dave Syer |
| * @version $Id$ |
| * @since 1.1 |
| */ |
| public class ChildBlocksBuilder |
| { |
| private boolean insideBold = false; |
| |
| private boolean insideItalic = false; |
| |
| private boolean insideLink = false; |
| |
| private boolean insideLinethrough = false; |
| |
| private boolean insideUnderline = false; |
| |
| private boolean insideSub = false; |
| |
| private boolean insideSup = false; |
| |
| private List<Block> blocks = new ArrayList<Block>(); |
| |
| private StringBuilder text = new StringBuilder(); |
| |
| private String input; |
| |
| private boolean insideMonospaced; |
| |
| /** |
| * <p>Constructor for ChildBlocksBuilder.</p> |
| * |
| * @param input the input. |
| */ |
| public ChildBlocksBuilder( String input ) |
| { |
| this.input = input; |
| } |
| |
| /** |
| * Utility method to convert marked up content into blocks for rendering. |
| * |
| * @return a list of Blocks that can be used to render it |
| */ |
| public List<Block> getBlocks() |
| { |
| List<Block> specialBlocks = new ArrayList<Block>(); |
| |
| for ( int i = 0; i < input.length(); i++ ) |
| { |
| char c = input.charAt( i ); |
| |
| switch ( c ) |
| { |
| case '*': |
| if ( insideBold ) |
| { |
| insideBold = false; |
| specialBlocks = getList( new BoldBlock( getChildren( text, specialBlocks ) ), specialBlocks ); |
| text = new StringBuilder(); |
| } |
| else if ( insideMonospaced ) |
| { |
| text.append( c ); |
| } |
| else |
| { |
| text = addTextBlockIfNecessary( blocks, specialBlocks, text ); |
| insideBold = true; |
| } |
| |
| break; |
| case '_': |
| if ( insideItalic ) |
| { |
| insideItalic = false; |
| specialBlocks = getList( new ItalicBlock( getChildren( text, specialBlocks ) ), specialBlocks ); |
| text = new StringBuilder(); |
| } |
| else if ( insideLink || insideMonospaced ) |
| { |
| text.append( c ); |
| } |
| else |
| { |
| text = addTextBlockIfNecessary( blocks, specialBlocks, text ); |
| insideItalic = true; |
| } |
| |
| break; |
| case '-': |
| if ( insideLinethrough ) |
| { |
| insideLinethrough = false; |
| blocks.add( new LinethroughBlock( text.toString() ) ); |
| text = new StringBuilder(); |
| } |
| else if ( insideLink || insideMonospaced ) |
| { |
| text.append( c ); |
| } |
| else |
| { |
| text = addTextBlockIfNecessary( blocks, specialBlocks, text ); |
| insideLinethrough = true; |
| } |
| break; |
| case '+': |
| if ( insideUnderline ) |
| { |
| insideUnderline = false; |
| blocks.add( new UnderlineBlock( text.toString() ) ); |
| text = new StringBuilder(); |
| } |
| else if ( insideLink || insideMonospaced ) |
| { |
| text.append( c ); |
| } |
| else |
| { |
| text = addTextBlockIfNecessary( blocks, specialBlocks, text ); |
| insideUnderline = true; |
| } |
| break; |
| case '~': |
| if ( insideSub ) |
| { |
| insideSub = false; |
| blocks.add( new SubBlock( text.toString() ) ); |
| text = new StringBuilder(); |
| } |
| else if ( insideLink || insideMonospaced ) |
| { |
| text.append( c ); |
| } |
| else |
| { |
| text = addTextBlockIfNecessary( blocks, specialBlocks, text ); |
| insideSub = true; |
| } |
| break; |
| case '^': |
| if ( insideSup ) |
| { |
| insideSup = false; |
| blocks.add( new SupBlock( text.toString() ) ); |
| text = new StringBuilder(); |
| } |
| else if ( insideLink || insideMonospaced ) |
| { |
| text.append( c ); |
| } |
| else |
| { |
| text = addTextBlockIfNecessary( blocks, specialBlocks, text ); |
| insideSup = true; |
| } |
| break; |
| case '[': |
| if ( insideMonospaced ) |
| { |
| text.append( c ); |
| } |
| else |
| { |
| insideLink = true; |
| text = addTextBlockIfNecessary( blocks, specialBlocks, text ); |
| } |
| break; |
| case ']': |
| if ( insideLink ) |
| { |
| boolean addHTMLSuffix = false; |
| String link = text.toString(); |
| |
| if ( !link.endsWith( ".html" ) ) |
| { |
| if ( !link.contains( "http" ) ) |
| { |
| // relative path: see DOXIA-298 |
| addHTMLSuffix = true; |
| } |
| } |
| if ( link.contains( "|" ) ) |
| { |
| String[] pieces = StringUtils.split( text.toString(), "|" ); |
| |
| if ( pieces[1].startsWith( "^" ) ) |
| { |
| // use the "file attachment" ^ syntax to force verbatim link: needed to allow actually |
| // linking to some non-html resources |
| pieces[1] = pieces[1].substring( 1 ); // now just get rid of the lead ^ |
| addHTMLSuffix = false; // force verbatim link to support attaching files/resources (not |
| // just .html files) |
| } |
| |
| if ( addHTMLSuffix ) |
| { |
| if ( !pieces[1].contains( "#" ) ) |
| { |
| pieces[1] = pieces[1].concat( ".html" ); |
| } |
| else |
| { |
| if ( !pieces[1].startsWith( "#" ) ) |
| { |
| String[] temp = pieces[1].split( "#" ); |
| pieces[1] = temp[0] + ".html#" + temp[1]; |
| } |
| } |
| } |
| |
| blocks.add( new LinkBlock( pieces[1], pieces[0] ) ); |
| } |
| else |
| { |
| String value = link; |
| |
| if ( link.startsWith( "#" ) ) |
| { |
| value = link.substring( 1 ); |
| } |
| else if ( link.startsWith( "^" ) ) |
| { |
| link = link.substring( 1 ); // chop off the lead ^ from link and from value |
| value = link; |
| addHTMLSuffix = false; // force verbatim link to support attaching files/resources (not |
| // just .html files) |
| } |
| |
| if ( addHTMLSuffix ) |
| { |
| if ( !link.contains( "#" ) ) |
| { |
| link = link.concat( ".html" ); |
| } |
| else |
| { |
| if ( !link.startsWith( "#" ) ) |
| { |
| String[] temp = link.split( "#" ); |
| link = temp[0] + ".html#" + temp[1]; |
| } |
| } |
| } |
| |
| blocks.add( new LinkBlock( link, value ) ); |
| } |
| |
| text = new StringBuilder(); |
| insideLink = false; |
| } |
| else if ( insideMonospaced ) |
| { |
| text.append( c ); |
| } |
| |
| break; |
| case '{': |
| if ( insideMonospaced ) |
| { |
| text.append( c ); |
| } |
| else |
| { |
| text = addTextBlockIfNecessary( blocks, specialBlocks, text ); |
| |
| if ( nextChar( input, i ) == '{' ) // it's monospaced |
| { |
| i++; |
| insideMonospaced = true; |
| } |
| } |
| // else it's a confluence macro... |
| |
| break; |
| case '}': |
| if ( nextChar( input, i ) == '}' ) |
| { |
| i++; |
| insideMonospaced = false; |
| specialBlocks = getList( new MonospaceBlock( getChildren( text, specialBlocks ) ), |
| specialBlocks ); |
| text = new StringBuilder(); |
| } |
| else if ( insideMonospaced ) |
| { |
| text.append( c ); |
| } |
| else |
| { |
| String name = text.toString(); |
| if ( name.startsWith( "anchor:" ) ) |
| { |
| blocks.add( new AnchorBlock( name.substring( "anchor:".length() ) ) ); |
| } |
| else |
| { |
| blocks.add( new TextBlock( "{" + name + "}" ) ); |
| } |
| text = new StringBuilder(); |
| } |
| |
| break; |
| case '\\': |
| if ( insideMonospaced ) |
| { |
| text.append( c ); |
| } |
| else if ( nextChar( input, i ) == '\\' ) |
| { |
| i++; |
| text = addTextBlockIfNecessary( blocks, specialBlocks, text ); |
| blocks.add( new LinebreakBlock() ); |
| } |
| else |
| { |
| // DOXIA-467 single trailing backward slash, double is considered linebreak |
| if ( i == input.length() - 1 ) |
| { |
| text.append( '\\' ); |
| } |
| else |
| { |
| text.append( input.charAt( ++i ) ); |
| } |
| } |
| |
| break; |
| default: |
| text.append( c ); |
| } |
| |
| if ( !specialBlocks.isEmpty() ) |
| { |
| if ( !insideItalic && !insideBold && !insideMonospaced ) |
| { |
| blocks.addAll( specialBlocks ); |
| specialBlocks.clear(); |
| } |
| } |
| |
| } |
| |
| if ( text.length() > 0 ) |
| { |
| blocks.add( new TextBlock( text.toString() ) ); |
| } |
| |
| return blocks; |
| } |
| |
| private List<Block> getList( Block block, List<Block> currentBlocks ) |
| { |
| List<Block> list = new ArrayList<Block>(); |
| |
| if ( insideBold || insideItalic || insideMonospaced ) |
| { |
| list.addAll( currentBlocks ); |
| } |
| |
| list.add( block ); |
| |
| return list; |
| } |
| |
| private List<Block> getChildren( StringBuilder buffer, List<Block> currentBlocks ) |
| { |
| String txt = buffer.toString().trim(); |
| |
| if ( currentBlocks.isEmpty() && StringUtils.isEmpty( txt ) ) |
| { |
| return new ArrayList<Block>(); |
| } |
| |
| ArrayList<Block> list = new ArrayList<Block>(); |
| |
| if ( !insideBold && !insideItalic && !insideMonospaced ) |
| { |
| list.addAll( currentBlocks ); |
| } |
| |
| if ( StringUtils.isEmpty( txt ) ) |
| { |
| return list; |
| } |
| |
| list.add( new TextBlock( txt ) ); |
| |
| return list; |
| } |
| |
| private static char nextChar( String input, int i ) |
| { |
| return input.length() > i + 1 ? input.charAt( i + 1 ) : '\0'; |
| } |
| |
| private StringBuilder addTextBlockIfNecessary( List<Block> blcks, List<Block> specialBlocks, StringBuilder txt ) |
| { |
| if ( txt.length() == 0 ) |
| { |
| return txt; |
| } |
| |
| TextBlock textBlock = new TextBlock( txt.toString() ); |
| |
| if ( !insideBold && !insideItalic && !insideMonospaced ) |
| { |
| blcks.add( textBlock ); |
| } |
| else |
| { |
| specialBlocks.add( textBlock ); |
| } |
| |
| return new StringBuilder(); |
| } |
| |
| } |