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

com.github.rjeschke.txtmark.Processor Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2011 René Jeschke 
 *
 * Licensed 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.
 */
package com.github.rjeschke.txtmark;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;

/**
 * Markdown processor class.
 * 
 * 

* Example usage: *

* *
 * String result = Processor.process("This is ***TXTMARK***");
 * 
 * 
* * @author René Jeschke */ public class Processor { /** The reader. */ private final Reader reader; /** The emitter. */ private final Emitter emitter; /** The Configuration. */ final Configuration config; /** Extension flag. */ private boolean useExtensions = false; /** * Constructor. * * @param reader * The input reader. */ protected Processor( final Reader reader, final Configuration config ) { this.reader = reader; this.config = config; this.useExtensions = config.forceExtendedProfile; this.emitter = new Emitter( this.config ); } /** * Transforms an input stream into HTML using the given Configuration. * * @param reader * The Reader to process. * @param configuration * The Configuration. * @return The processed String. * @throws IOException * if an IO error occurs * @since 0.7 * @see Configuration */ public final static String process( final Reader reader, final Configuration configuration ) throws IOException { final Processor p = new Processor( !( reader instanceof BufferedReader ) ? new BufferedReader( reader ) : reader, configuration ); return p.process(); } /** * Transforms an input String into HTML using the given Configuration. * * @param input * The String to process. * @param configuration * The Configuration. * @return The processed String. * @since 0.7 * @see Configuration */ public final static String process( final String input, final Configuration configuration ) { if ( input == null ) { return null; } try { return process( new StringReader( input ), configuration ); } catch ( final IOException e ) { // This _can never_ happen return null; } } /** * Transforms an input file into HTML using the given Configuration. * * @param file * The File to process. * @return The processed String. * @throws IOException * if an IO error occurs * @since 0.7 * @see Configuration */ public final static String process( final File file, final Configuration configuration ) throws IOException { final FileInputStream input = new FileInputStream( file ); final String ret = process( input, configuration ); input.close(); return ret; } /** * Transforms an input stream into HTML using the given Configuration. * * @param input * The InputStream to process. * @param configuration * The Configuration. * @return The processed String. * @throws IOException * if an IO error occurs * @since 0.7 * @see Configuration */ public final static String process( final InputStream input, final Configuration configuration ) throws IOException { final Processor p = new Processor( new BufferedReader( new InputStreamReader( input, configuration.encoding ) ), configuration ); return p.process(); } /** * Transforms an input String into HTML using the default Configuration. * * @param input * The String to process. * @return The processed String. * @see Configuration#DEFAULT */ public final static String process( final String input ) { return process( input, Configuration.DEFAULT ); } /** * Transforms an input String into HTML. * * @param input * The String to process. * @param safeMode * Set to true to escape unsafe HTML tags. * @return The processed String. * @see Configuration#DEFAULT */ public final static String process( final String input, final boolean safeMode ) { return process( input, Configuration.builder() .setSafeMode( safeMode ) .build() ); } /** * Transforms an input String into HTML. * * @param input * The String to process. * @param decorator * The decorator to use. * @return The processed String. * @see Configuration#DEFAULT */ public final static String process( final String input, final Decorator decorator ) { return process( input, Configuration.builder() .setDecorator( decorator ) .build() ); } /** * Transforms an input String into HTML. * * @param input * The String to process. * @param decorator * The decorator to use. * @param safeMode * Set to true to escape unsafe HTML tags. * @return The processed String. * @see Configuration#DEFAULT */ public final static String process( final String input, final Decorator decorator, final boolean safeMode ) { return process( input, Configuration.builder() .setDecorator( decorator ) .setSafeMode( safeMode ) .build() ); } /** * Transforms an input file into HTML using the default Configuration. * * @param file * The File to process. * @return The processed String. * @throws IOException * if an IO error occurs * @see Configuration#DEFAULT */ public final static String process( final File file ) throws IOException { return process( file, Configuration.DEFAULT ); } /** * Transforms an input file into HTML. * * @param file * The File to process. * @param safeMode * Set to true to escape unsafe HTML tags. * @return The processed String. * @throws IOException * if an IO error occurs * @see Configuration#DEFAULT */ public final static String process( final File file, final boolean safeMode ) throws IOException { return process( file, Configuration.builder() .setSafeMode( safeMode ) .build() ); } /** * Transforms an input file into HTML. * * @param file * The File to process. * @param decorator * The decorator to use. * @return The processed String. * @throws IOException * if an IO error occurs * @see Configuration#DEFAULT */ public final static String process( final File file, final Decorator decorator ) throws IOException { return process( file, Configuration.builder() .setDecorator( decorator ) .build() ); } /** * Transforms an input file into HTML. * * @param file * The File to process. * @param decorator * The decorator to use. * @param safeMode * Set to true to escape unsafe HTML tags. * @return The processed String. * @throws IOException * if an IO error occurs * @see Configuration#DEFAULT */ public final static String process( final File file, final Decorator decorator, final boolean safeMode ) throws IOException { return process( file, Configuration.builder() .setDecorator( decorator ) .setSafeMode( safeMode ) .build() ); } /** * Transforms an input file into HTML. * * @param file * The File to process. * @param encoding * The encoding to use. * @return The processed String. * @throws IOException * if an IO error occurs * @see Configuration#DEFAULT */ public final static String process( final File file, final String encoding ) throws IOException { return process( file, Configuration.builder() .setEncoding( encoding ) .build() ); } /** * Transforms an input file into HTML. * * @param file * The File to process. * @param encoding * The encoding to use. * @param safeMode * Set to true to escape unsafe HTML tags. * @return The processed String. * @throws IOException * if an IO error occurs * @see Configuration#DEFAULT */ public final static String process( final File file, final String encoding, final boolean safeMode ) throws IOException { return process( file, Configuration.builder() .setEncoding( encoding ) .setSafeMode( safeMode ) .build() ); } /** * Transforms an input file into HTML. * * @param file * The File to process. * @param encoding * The encoding to use. * @param decorator * The decorator to use. * @return The processed String. * @throws IOException * if an IO error occurs * @see Configuration#DEFAULT */ public final static String process( final File file, final String encoding, final Decorator decorator ) throws IOException { return process( file, Configuration.builder() .setEncoding( encoding ) .setDecorator( decorator ) .build() ); } /** * Transforms an input file into HTML. * * @param file * The File to process. * @param encoding * The encoding to use. * @param decorator * The decorator to use. * @param safeMode * Set to true to escape unsafe HTML tags. * @return The processed String. * @throws IOException * if an IO error occurs * @see Configuration#DEFAULT */ public final static String process( final File file, final String encoding, final Decorator decorator, final boolean safeMode ) throws IOException { return process( file, Configuration.builder() .setEncoding( encoding ) .setSafeMode( safeMode ) .setDecorator( decorator ) .build() ); } /** * Transforms an input stream into HTML. * * @param input * The InputStream to process. * @return The processed String. * @throws IOException * if an IO error occurs * @see Configuration#DEFAULT */ public final static String process( final InputStream input ) throws IOException { return process( input, Configuration.DEFAULT ); } /** * Transforms an input stream into HTML. * * @param input * The InputStream to process. * @param safeMode * Set to true to escape unsafe HTML tags. * @return The processed String. * @throws IOException * if an IO error occurs * @see Configuration#DEFAULT */ public final static String process( final InputStream input, final boolean safeMode ) throws IOException { return process( input, Configuration.builder() .setSafeMode( safeMode ) .build() ); } /** * Transforms an input stream into HTML. * * @param input * The InputStream to process. * @param decorator * The decorator to use. * @return The processed String. * @throws IOException * if an IO error occurs * @see Configuration#DEFAULT */ public final static String process( final InputStream input, final Decorator decorator ) throws IOException { return process( input, Configuration.builder() .setDecorator( decorator ) .build() ); } /** * Transforms an input stream into HTML. * * @param input * The InputStream to process. * @param decorator * The decorator to use. * @param safeMode * Set to true to escape unsafe HTML tags. * @return The processed String. * @throws IOException * if an IO error occurs * @see Configuration#DEFAULT */ public final static String process( final InputStream input, final Decorator decorator, final boolean safeMode ) throws IOException { return process( input, Configuration.builder() .setDecorator( decorator ) .setSafeMode( safeMode ) .build() ); } /** * Transforms an input stream into HTML. * * @param input * The InputStream to process. * @param encoding * The encoding to use. * @return The processed String. * @throws IOException * if an IO error occurs * @see Configuration#DEFAULT */ public final static String process( final InputStream input, final String encoding ) throws IOException { return process( input, Configuration.builder() .setEncoding( encoding ) .build() ); } /** * Transforms an input stream into HTML. * * @param input * The InputStream to process. * @param encoding * The encoding to use. * @param safeMode * Set to true to escape unsafe HTML tags. * @return The processed String. * @throws IOException * if an IO error occurs * @see Configuration#DEFAULT */ public final static String process( final InputStream input, final String encoding, final boolean safeMode ) throws IOException { return process( input, Configuration.builder() .setEncoding( encoding ) .setSafeMode( safeMode ) .build() ); } /** * Transforms an input stream into HTML. * * @param input * The InputStream to process. * @param encoding * The encoding to use. * @param decorator * The decorator to use. * @return The processed String. * @throws IOException * if an IO error occurs * @see Configuration#DEFAULT */ public final static String process( final InputStream input, final String encoding, final Decorator decorator ) throws IOException { return process( input, Configuration.builder() .setEncoding( encoding ) .setDecorator( decorator ) .build() ); } /** * Transforms an input stream into HTML. * * @param input * The InputStream to process. * @param encoding * The encoding to use. * @param decorator * The decorator to use. * @param safeMode * Set to true to escape unsafe HTML tags. * @return The processed String. * @throws IOException * if an IO error occurs * @see Configuration#DEFAULT */ public final static String process( final InputStream input, final String encoding, final Decorator decorator, final boolean safeMode ) throws IOException { return process( input, Configuration.builder() .setEncoding( encoding ) .setDecorator( decorator ) .setSafeMode( safeMode ) .build() ); } /** * Transforms an input stream into HTML using the default Configuration. * * @param reader * The Reader to process. * @return The processed String. * @throws IOException * if an IO error occurs * @see Configuration#DEFAULT */ public final static String process( final Reader reader ) throws IOException { return process( reader, Configuration.DEFAULT ); } /** * Transforms an input stream into HTML. * * @param reader * The Reader to process. * @param safeMode * Set to true to escape unsafe HTML tags. * @return The processed String. * @throws IOException * if an IO error occurs * @see Configuration#DEFAULT */ public final static String process( final Reader reader, final boolean safeMode ) throws IOException { return process( reader, Configuration.builder() .setSafeMode( safeMode ) .build() ); } /** * Transforms an input stream into HTML. * * @param reader * The Reader to process. * @param decorator * The decorator to use. * @return The processed String. * @throws IOException * if an IO error occurs * @see Configuration#DEFAULT */ public final static String process( final Reader reader, final Decorator decorator ) throws IOException { return process( reader, Configuration.builder() .setDecorator( decorator ) .build() ); } /** * Transforms an input stream into HTML. * * @param reader * The Reader to process. * @param decorator * The decorator to use. * @param safeMode * Set to true to escape unsafe HTML tags. * @return The processed String. * @throws IOException * if an IO error occurs * @see Configuration#DEFAULT */ public final static String process( final Reader reader, final Decorator decorator, final boolean safeMode ) throws IOException { return process( reader, Configuration.builder() .setDecorator( decorator ) .setSafeMode( safeMode ) .build() ); } /** * Reads all lines from our reader. *

* Takes care of markdown link references. *

* * @return A Block containing all lines. * @throws IOException * If an IO error occurred. */ private Block readLines() throws IOException { final Block block = new Block(); final StringBuilder sb = new StringBuilder( 80 ); int c = this.reader.read(); LinkRef lastLinkRef = null; while ( c != -1 ) { sb.setLength( 0 ); int pos = 0; boolean eol = false; while ( !eol ) { switch ( c ) { case -1: eol = true; break; case '\n': c = this.reader.read(); if ( c == '\r' ) { c = this.reader.read(); } eol = true; break; case '\r': c = this.reader.read(); if ( c == '\n' ) { c = this.reader.read(); } eol = true; break; case '\t': { final int np = pos + ( 4 - ( pos & 3 ) ); while ( pos < np ) { sb.append( ' ' ); pos++; } c = this.reader.read(); break; } default: pos++; sb.append( (char) c ); c = this.reader.read(); break; } } final Line line = new Line(); line.value = sb.toString(); line.init(); // Check for link definitions boolean isLinkRef = false; String id = null, link = null, comment = null; if ( !line.isEmpty && line.leading < 4 && line.value.charAt( line.leading ) == '[' ) { line.pos = line.leading + 1; // Read ID up to ']' id = line.readUntil( ']' ); // Is ID valid and are there any more characters? if ( id != null && line.pos + 2 < line.value.length() ) { // Check for ':' ([...]:...) if ( line.value.charAt( line.pos + 1 ) == ':' ) { line.pos += 2; line.skipSpaces(); // Check for link syntax if ( line.value.charAt( line.pos ) == '<' ) { line.pos++; link = line.readUntil( '>' ); line.pos++; } else { link = line.readUntil( ' ', '\n' ); } // Is link valid? if ( link != null ) { // Any non-whitespace characters following? if ( line.skipSpaces() ) { final char ch = line.value.charAt( line.pos ); // Read comment if ( ch == '\"' || ch == '\'' || ch == '(' ) { line.pos++; comment = line.readUntil( ch == '(' ? ')' : ch ); // Valid linkRef only if comment is valid if ( comment != null ) { isLinkRef = true; } } } else { isLinkRef = true; } } } } } // To make compiler happy: add != null checks if ( isLinkRef && id != null && link != null ) { if ( id.toLowerCase() .equals( "$profile$" ) ) { this.emitter.useExtensions = this.useExtensions = link.toLowerCase() .equals( "extended" ); lastLinkRef = null; } else { // Store linkRef and skip line final LinkRef lr = new LinkRef( link, comment, comment != null && ( link.length() == 1 && link.charAt( 0 ) == '*' ) ); this.emitter.addLinkRef( id, lr ); if ( comment == null ) { lastLinkRef = lr; } } } else { comment = null; // Check for multi-line linkRef if ( !line.isEmpty && lastLinkRef != null ) { line.pos = line.leading; final char ch = line.value.charAt( line.pos ); if ( ch == '\"' || ch == '\'' || ch == '(' ) { line.pos++; comment = line.readUntil( ch == '(' ? ')' : ch ); } if ( comment != null ) { lastLinkRef.title = comment; } lastLinkRef = null; } // No multi-line linkRef, store line if ( comment == null ) { line.pos = 0; block.appendLine( line ); } } } return block; } /** * Initializes a list block by separating it into list item blocks. * * @param root * The Block to process. */ private void initListBlock( final Block root ) { Line line = root.lines; line = line.next; while ( line != null ) { final LineType t = line.getLineType( this.useExtensions ); if ( ( t == LineType.OLIST || t == LineType.ULIST ) || ( !line.isEmpty && ( line.prevEmpty && line.leading == 0 && !( t == LineType.OLIST || t == LineType.ULIST ) ) ) ) { root.split( line.previous ).type = BlockType.LIST_ITEM; } line = line.next; } root.split( root.lineTail ).type = BlockType.LIST_ITEM; } /** * Recursively process the given Block. * * @param root * The Block to process. * @param listMode * Flag indicating that we're in a list item block. */ private void recurse( final Block root, final boolean listMode ) { Block block, list; Line line = root.lines; if ( listMode ) { root.removeListIndent( this.useExtensions ); if ( this.useExtensions && root.lines != null && root.lines.getLineType( this.useExtensions ) != LineType.CODE ) { root.id = root.lines.stripID(); } } while ( line != null && line.isEmpty ) { line = line.next; } if ( line == null ) { return; } while ( line != null ) { final LineType type = line.getLineType( this.useExtensions ); switch ( type ) { case OTHER: { final boolean wasEmpty = line.prevEmpty; while ( line != null && !line.isEmpty ) { final LineType t = line.getLineType( this.useExtensions ); if ( ( listMode || this.useExtensions ) && ( t == LineType.OLIST || t == LineType.ULIST ) ) { break; } if ( this.useExtensions && ( t == LineType.CODE || t == LineType.FENCED_CODE || t == LineType.PLUGIN ) ) { break; } if ( t == LineType.HEADLINE || t == LineType.HEADLINE1 || t == LineType.HEADLINE2 || t == LineType.HR || t == LineType.BQUOTE || t == LineType.XML ) { break; } line = line.next; } final BlockType bt; if ( line != null && !line.isEmpty ) { bt = ( listMode && !wasEmpty ) ? BlockType.NONE : BlockType.PARAGRAPH; root.split( line.previous ).type = bt; root.removeLeadingEmptyLines(); } else { bt = ( listMode && ( line == null || !line.isEmpty ) && !wasEmpty ) ? BlockType.NONE : BlockType.PARAGRAPH; root.split( line == null ? root.lineTail : line ).type = bt; root.removeLeadingEmptyLines(); } line = root.lines; break; } case CODE: while ( line != null && ( line.isEmpty || line.leading > 3 ) ) { line = line.next; } block = root.split( line != null ? line.previous : root.lineTail ); block.type = BlockType.CODE; block.removeSurroundingEmptyLines(); break; case XML: if ( line.previous != null ) { // FIXME ... this looks wrong root.split( line.previous ); } root.split( line.xmlEndLine ).type = BlockType.XML; root.removeLeadingEmptyLines(); line = root.lines; break; case BQUOTE: while ( line != null ) { if ( !line.isEmpty && ( line.prevEmpty && line.leading == 0 && line.getLineType( this.useExtensions ) != LineType.BQUOTE ) ) { break; } line = line.next; } block = root.split( line != null ? line.previous : root.lineTail ); block.type = BlockType.BLOCKQUOTE; block.removeSurroundingEmptyLines(); block.removeBlockQuotePrefix(); this.recurse( block, false ); line = root.lines; break; case HR: if ( line.previous != null ) { // FIXME ... this looks wrong root.split( line.previous ); } root.split( line ).type = BlockType.RULER; root.removeLeadingEmptyLines(); line = root.lines; break; case FENCED_CODE: line = line.next; while ( line != null ) { if ( line.getLineType( this.useExtensions ) == LineType.FENCED_CODE ) { break; } // TODO ... is this really necessary? Maybe add a special // flag? line = line.next; } if ( line != null ) { line = line.next; } block = root.split( line != null ? line.previous : root.lineTail ); block.type = BlockType.FENCED_CODE; block.meta = Utils.getMetaFromFence( block.lines.value ); block.lines.setEmpty(); if ( block.lineTail.getLineType( this.useExtensions ) == LineType.FENCED_CODE ) { block.lineTail.setEmpty(); } block.removeSurroundingEmptyLines(); break; case PLUGIN: line = line.next; while ( line != null ) { if ( line.getLineType( this.useExtensions ) == LineType.PLUGIN ) { break; } // TODO ... is this really necessary? Maybe add a special // flag? line = line.next; } if ( line != null ) { line = line.next; } block = root.split( line != null ? line.previous : root.lineTail ); block.type = BlockType.PLUGIN; block.meta = Utils.getMetaFromFence( block.lines.value ); block.lines.setEmpty(); if ( block.lineTail.getLineType( this.useExtensions ) == LineType.PLUGIN ) { block.lineTail.setEmpty(); } block.removeSurroundingEmptyLines(); break; case HEADLINE: case HEADLINE1: case HEADLINE2: if ( line.previous != null ) { root.split( line.previous ); } if ( type != LineType.HEADLINE ) { line.next.setEmpty(); } block = root.split( line ); block.type = BlockType.HEADLINE; if ( type != LineType.HEADLINE ) { block.hlDepth = type == LineType.HEADLINE1 ? 1 : 2; } if ( this.useExtensions ) { block.id = block.lines.stripID(); } block.transfromHeadline(); root.removeLeadingEmptyLines(); line = root.lines; break; case OLIST: case ULIST: while ( line != null ) { final LineType t = line.getLineType( this.useExtensions ); if ( !line.isEmpty && ( line.prevEmpty && line.leading == 0 && !( t == LineType.OLIST || t == LineType.ULIST ) ) ) { break; } line = line.next; } list = root.split( line != null ? line.previous : root.lineTail ); list.type = type == LineType.OLIST ? BlockType.ORDERED_LIST : BlockType.UNORDERED_LIST; list.lines.prevEmpty = false; list.lineTail.nextEmpty = false; list.removeSurroundingEmptyLines(); list.lines.prevEmpty = list.lineTail.nextEmpty = false; initListBlock( list ); block = list.blocks; while ( block != null ) { this.recurse( block, true ); block = block.next; } list.expandListParagraphs(); break; default: line = line.next; break; } } } /** * Does all the processing. * * @return The processed String. * @throws IOException * If an IO error occurred. */ private String process() throws IOException { final StringBuilder out = new StringBuilder(); final Block parent = this.readLines(); parent.removeSurroundingEmptyLines(); this.recurse( parent, false ); Block block = parent.blocks; while ( block != null ) { this.emitter.emit( out, block ); block = block.next; } return out.toString(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy