panda.doc.markdown.Processor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of panda-core Show documentation
Show all versions of panda-core Show documentation
Panda Core is the core module of Panda Framework, it contains commonly used utility classes similar to apache-commons.
package panda.doc.markdown;
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 panda.io.stream.CharSequenceReader;
/**
* Markdown processor class.
*
* Example usage:
*
*
*
* String result = Processor.process("This is ***TXTMARK***");
*
*
*/
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.
* @param config the configuration
*/
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
* @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.
* @see Configuration
*/
public final static String process(final CharSequence input, final Configuration configuration) {
try {
return process(new CharSequenceReader(input), configuration);
}
catch (IOException e) {
// This _can never_ happen
return null;
}
}
/**
* Transforms an input file into HTML using the given Configuration.
*
* @param file The File to process.
* @param configuration The Contifuration.
* @return The processed String.
* @throws IOException if an IO error occurs
* @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
* @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.pos < line.value.length() && 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, 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.TABLE || t == LineType.TABLEB
|| 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 TABLE:
while (line != null && !line.isEmpty) {
line = line.next;
}
block = root.split(line != null ? line.previous : root.lineTail);
block.type = BlockType.TABLE;
block.removeSurroundingEmptyLines();
line = root.lines;
break;
case FENCED_CODE:
case TABLEB:
case PLUGIN:
line = line.next;
while (line != null) {
if (line.getLineType(this.useExtensions) == type)
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);
switch (type) {
case TABLEB:
block.type = BlockType.TABLEB;
break;
case PLUGIN:
block.type = BlockType.PLUGIN;
break;
default:
block.type = BlockType.FENCED_CODE;
break;
}
block.meta = Utils.getMetaFromFence(block.lines.value);
block.lines.setEmpty();
if (block.lineTail.getLineType(this.useExtensions) == type)
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();
}
}