Adressed issue #23, unrelated formatting and style changes.

This commit is contained in:
René Jeschke 2015-03-18 11:06:05 +01:00
parent 5e3322545c
commit a933bf5f89
4 changed files with 448 additions and 299 deletions

View File

@ -17,25 +17,25 @@ package com.github.rjeschke.txtmark;
/**
* This class represents a block of lines.
*
*
* @author René Jeschke <rene_jeschke@yahoo.de>
*/
class Block
{
/** This block's type. */
public BlockType type = BlockType.NONE;
public BlockType type = BlockType.NONE;
/** Head and tail of linked lines. */
public Line lines = null, lineTail = null;
public Line lines = null, lineTail = null;
/** Head and tail of child blocks. */
public Block blocks = null, blockTail = null;
public Block blocks = null, blockTail = null;
/** Next block. */
public Block next = null;
public Block next = null;
/** Depth of headline BlockType. */
public int hlDepth = 0;
public int hlDepth = 0;
/** ID for headlines and list items */
public String id = null;
public String id = null;
/** Block meta information */
public String meta = "";
public String meta = "";
/** Constructor. */
public Block()
@ -56,7 +56,7 @@ class Block
*/
public void removeSurroundingEmptyLines()
{
if(this.lines != null)
if (this.lines != null)
{
this.removeTrailingEmptyLines();
this.removeLeadingEmptyLines();
@ -68,31 +68,41 @@ class Block
*/
public void transfromHeadline()
{
if(this.hlDepth > 0)
if (this.hlDepth > 0)
{
return;
}
int level = 0;
final Line line = this.lines;
if(line.isEmpty)
if (line.isEmpty)
{
return;
}
int start = line.leading;
while(start < line.value.length() && line.value.charAt(start) == '#')
while (start < line.value.length() && line.value.charAt(start) == '#')
{
level++;
start++;
}
while(start < line.value.length() && line.value.charAt(start) == ' ')
while (start < line.value.length() && line.value.charAt(start) == ' ')
{
start++;
if(start >= line.value.length())
}
if (start >= line.value.length())
{
line.setEmpty();
}
else
{
int end = line.value.length() - line.trailing - 1;
while(line.value.charAt(end) == '#')
while (line.value.charAt(end) == '#')
{
end--;
while(line.value.charAt(end) == ' ')
}
while (line.value.charAt(end) == ' ')
{
end--;
}
line.value = line.value.substring(start, end + 1);
line.leading = line.trailing = 0;
}
@ -101,18 +111,19 @@ class Block
/**
* Used for nested lists. Removes list markers and up to 4 leading spaces.
*
* @param extendedMode
* Whether extended profile ist activated or not
*
* @param configuration
* txtmark configuration
*
*/
public void removeListIndent(boolean extendedMode)
public void removeListIndent(final Configuration configuration)
{
Line line = this.lines;
while(line != null)
while (line != null)
{
if(!line.isEmpty)
if (!line.isEmpty)
{
switch(line.getLineType(extendedMode))
switch (line.getLineType(configuration))
{
case ULIST:
line.value = line.value.substring(line.leading + 2);
@ -136,15 +147,17 @@ class Block
public void removeBlockQuotePrefix()
{
Line line = this.lines;
while(line != null)
while (line != null)
{
if(!line.isEmpty)
if (!line.isEmpty)
{
if(line.value.charAt(line.leading) == '>')
if (line.value.charAt(line.leading) == '>')
{
int rem = line.leading + 1;
if(line.leading + 1 < line.value.length() && line.value.charAt(line.leading + 1) == ' ')
if (line.leading + 1 < line.value.length() && line.value.charAt(line.leading + 1) == ' ')
{
rem++;
}
line.value = line.value.substring(rem);
line.initLeading();
}
@ -155,14 +168,14 @@ class Block
/**
* Removes leading empty lines.
*
*
* @return <code>true</code> if an empty line was removed.
*/
public boolean removeLeadingEmptyLines()
{
boolean wasEmpty = false;
Line line = this.lines;
while(line != null && line.isEmpty)
while (line != null && line.isEmpty)
{
this.removeLine(line);
line = this.lines;
@ -177,7 +190,7 @@ class Block
public void removeTrailingEmptyLines()
{
Line line = this.lineTail;
while(line != null && line.isEmpty)
while (line != null && line.isEmpty)
{
this.removeLine(line);
line = this.lineTail;
@ -187,7 +200,7 @@ class Block
/**
* Splits this block's lines, creating a new child block having 'line' as
* it's lineTail.
*
*
* @param line
* The line to split from.
* @return The newly created Block.
@ -200,13 +213,19 @@ class Block
block.lineTail = line;
this.lines = line.next;
line.next = null;
if(this.lines == null)
if (this.lines == null)
{
this.lineTail = null;
}
else
{
this.lines.previous = null;
}
if(this.blocks == null)
if (this.blocks == null)
{
this.blocks = this.blockTail = block;
}
else
{
this.blockTail.next = block;
@ -218,33 +237,43 @@ class Block
/**
* Removes the given line from this block.
*
*
* @param line
* Line to remove.
*/
public void removeLine(final Line line)
{
if(line.previous == null)
if (line.previous == null)
{
this.lines = line.next;
}
else
{
line.previous.next = line.next;
if(line.next == null)
}
if (line.next == null)
{
this.lineTail = line.previous;
}
else
{
line.next.previous = line.previous;
}
line.previous = line.next = null;
}
/**
* Appends the given line to this block.
*
*
* @param line
* Line to append.
*/
public void appendLine(final Line line)
{
if(this.lineTail == null)
if (this.lineTail == null)
{
this.lines = this.lineTail = line;
}
else
{
this.lineTail.nextEmpty = line.isEmpty;
@ -261,20 +290,20 @@ class Block
*/
public void expandListParagraphs()
{
if(this.type != BlockType.ORDERED_LIST && this.type != BlockType.UNORDERED_LIST)
if (this.type != BlockType.ORDERED_LIST && this.type != BlockType.UNORDERED_LIST)
{
return;
}
Block outer = this.blocks, inner;
boolean hasParagraph = false;
while(outer != null && !hasParagraph)
while (outer != null && !hasParagraph)
{
if(outer.type == BlockType.LIST_ITEM)
if (outer.type == BlockType.LIST_ITEM)
{
inner = outer.blocks;
while(inner != null && !hasParagraph)
while (inner != null && !hasParagraph)
{
if(inner.type == BlockType.PARAGRAPH)
if (inner.type == BlockType.PARAGRAPH)
{
hasParagraph = true;
}
@ -283,17 +312,17 @@ class Block
}
outer = outer.next;
}
if(hasParagraph)
if (hasParagraph)
{
outer = this.blocks;
while(outer != null)
while (outer != null)
{
if(outer.type == BlockType.LIST_ITEM)
if (outer.type == BlockType.LIST_ITEM)
{
inner = outer.blocks;
while(inner != null)
while (inner != null)
{
if(inner.type == BlockType.NONE)
if (inner.type == BlockType.NONE)
{
inner.type = BlockType.PARAGRAPH;
}

View File

@ -17,25 +17,26 @@ package com.github.rjeschke.txtmark;
/**
* Txtmark configuration.
*
*
* @author René Jeschke &lt;rene_jeschke@yahoo.de&gt;
* @since 0.7
*/
public class Configuration
{
final boolean safeMode;
final String encoding;
final Decorator decorator;
final BlockEmitter codeBlockEmitter;
final boolean forceExtendedProfile;
final SpanEmitter specialLinkEmitter;
final boolean safeMode;
final String encoding;
final Decorator decorator;
final BlockEmitter codeBlockEmitter;
final boolean forceExtendedProfile;
final boolean allowSpacesInFencedDelimiters;
final SpanEmitter specialLinkEmitter;
/**
* <p>
* This is the default configuration for txtmark's <code>process</code>
* methods
* </p>
*
*
* <ul>
* <li><code>safeMode = false</code></li>
* <li><code>encoding = UTF-8</code></li>
@ -43,13 +44,13 @@ public class Configuration
* <li><code>codeBlockEmitter = null</code></li>
* </ul>
*/
public final static Configuration DEFAULT = Configuration.builder().build();
public final static Configuration DEFAULT = Configuration.builder().build();
/**
* <p>
* Default safe configuration
* </p>
*
*
* <ul>
* <li><code>safeMode = true</code></li>
* <li><code>encoding = UTF-8</code></li>
@ -61,13 +62,15 @@ public class Configuration
/**
* Constructor.
*
*
* @param safeMode
* @param encoding
* @param decorator
*/
Configuration(boolean safeMode, String encoding, Decorator decorator, BlockEmitter codeBlockEmitter,
boolean forceExtendedProfile, SpanEmitter specialLinkEmitter)
Configuration(final boolean safeMode, final String encoding, final Decorator decorator,
final BlockEmitter codeBlockEmitter,
final boolean forceExtendedProfile, final SpanEmitter specialLinkEmitter,
final boolean allowSpacesInFencedDelimiters)
{
this.safeMode = safeMode;
this.encoding = encoding;
@ -75,11 +78,12 @@ public class Configuration
this.codeBlockEmitter = codeBlockEmitter;
this.forceExtendedProfile = forceExtendedProfile;
this.specialLinkEmitter = specialLinkEmitter;
this.allowSpacesInFencedDelimiters = allowSpacesInFencedDelimiters;
}
/**
* Creates a new Builder instance.
*
*
* @return A new Builder instance.
*/
public static Builder builder()
@ -89,22 +93,23 @@ public class Configuration
/**
* Configuration builder.
*
*
* @author René Jeschke &lt;rene_jeschke@yahoo.de&gt;
* @since 0.7
*/
public static class Builder
{
private boolean safeMode = false;
private boolean forceExtendedProfile = false;
private String encoding = "UTF-8";
private Decorator decorator = new DefaultDecorator();
private BlockEmitter codeBlockEmitter = null;
private SpanEmitter specialLinkEmitter = null;
private boolean safeMode = false;
private boolean forceExtendedProfile = false;
private boolean allowSpacesInFencedDelimiters = true;
private String encoding = "UTF-8";
private Decorator decorator = new DefaultDecorator();
private BlockEmitter codeBlockEmitter = null;
private SpanEmitter specialLinkEmitter = null;
/**
* Constructor.
*
*
*/
Builder()
{
@ -113,9 +118,9 @@ public class Configuration
/**
* Enables HTML safe mode.
*
*
* Default: <code>false</code>
*
*
* @return This builder
* @since 0.7
*/
@ -127,7 +132,7 @@ public class Configuration
/**
* Forces extened profile to be enabled by default.
*
*
* @return This builder.
* @since 0.7
*/
@ -139,15 +144,15 @@ public class Configuration
/**
* Sets the HTML safe mode flag.
*
*
* Default: <code>false</code>
*
*
* @param flag
* <code>true</code> to enable safe mode
* @return This builder
* @since 0.7
*/
public Builder setSafeMode(boolean flag)
public Builder setSafeMode(final boolean flag)
{
this.safeMode = flag;
return this;
@ -155,15 +160,15 @@ public class Configuration
/**
* Sets the character encoding for txtmark.
*
*
* Default: <code>&quot;UTF-8&quot;</code>
*
*
* @param encoding
* The encoding
* @return This builder
* @since 0.7
*/
public Builder setEncoding(String encoding)
public Builder setEncoding(final String encoding)
{
this.encoding = encoding;
return this;
@ -171,16 +176,16 @@ public class Configuration
/**
* Sets the decorator for txtmark.
*
*
* Default: <code>DefaultDecorator()</code>
*
*
* @param decorator
* The decorator
* @return This builder
* @see DefaultDecorator
* @since 0.7
*/
public Builder setDecorator(Decorator decorator)
public Builder setDecorator(final Decorator decorator)
{
this.decorator = decorator;
return this;
@ -188,16 +193,16 @@ public class Configuration
/**
* Sets the code block emitter.
*
*
* Default: <code>null</code>
*
*
* @param emitter
* The BlockEmitter
* @return This builder
* @see BlockEmitter
* @since 0.7
*/
public Builder setCodeBlockEmitter(BlockEmitter emitter)
public Builder setCodeBlockEmitter(final BlockEmitter emitter)
{
this.codeBlockEmitter = emitter;
return this;
@ -205,28 +210,42 @@ public class Configuration
/**
* Sets the emitter for special link spans ([[ ... ]]).
*
*
* @param emitter
* The emitter.
* @return This builder.
* @since 0.7
*/
public Builder setSpecialLinkEmitter(SpanEmitter emitter)
public Builder setSpecialLinkEmitter(final SpanEmitter emitter)
{
this.specialLinkEmitter = emitter;
return this;
}
/**
* (Dis-)Allows spaces in fenced code block delimiter lines.
*
* @param allow
* whether to allow or not
* @return This builder.
* @since 0.12
*/
public Builder setAllowSpacesInFencedCodeBlockDelimiters(final boolean allow)
{
this.allowSpacesInFencedDelimiters = allow;
return this;
}
/**
* Builds a configuration instance.
*
*
* @return a Configuration instance
* @since 0.7
*/
public Configuration build()
{
return new Configuration(this.safeMode, this.encoding, this.decorator, this.codeBlockEmitter,
this.forceExtendedProfile, this.specialLinkEmitter);
this.forceExtendedProfile, this.specialLinkEmitter, this.allowSpacesInFencedDelimiters);
}
}
}

View File

@ -19,29 +19,29 @@ import java.util.LinkedList;
/**
* This class represents a text line.
*
*
* <p>
* It also provides methods for processing and analyzing a line.
* </p>
*
*
* @author René Jeschke <rene_jeschke@yahoo.de>
*/
class Line
{
/** Current cursor position. */
public int pos;
public int pos;
/** Leading and trailing spaces. */
public int leading = 0, trailing = 0;
public int leading = 0, trailing = 0;
/** Is this line empty? */
public boolean isEmpty = true;
/** This line's value. */
public String value = null;
public String value = null;
/** Previous and next line. */
public Line previous = null, next = null;
public Line previous = null, next = null;
/** Is previous/next line empty? */
public boolean prevEmpty, nextEmpty;
/** Final line of a XML block. */
public Line xmlEndLine;
public Line xmlEndLine;
/** Constructor. */
public Line()
@ -55,10 +55,12 @@ class Line
public void init()
{
this.leading = 0;
while(this.leading < this.value.length() && this.value.charAt(this.leading) == ' ')
while (this.leading < this.value.length() && this.value.charAt(this.leading) == ' ')
{
this.leading++;
}
if(this.leading == this.value.length())
if (this.leading == this.value.length())
{
this.setEmpty();
}
@ -66,8 +68,10 @@ class Line
{
this.isEmpty = false;
this.trailing = 0;
while(this.value.charAt(this.value.length() - this.trailing - 1) == ' ')
while (this.value.charAt(this.value.length() - this.trailing - 1) == ' ')
{
this.trailing++;
}
}
}
@ -77,10 +81,12 @@ class Line
public void initLeading()
{
this.leading = 0;
while(this.leading < this.value.length() && this.value.charAt(this.leading) == ' ')
while (this.leading < this.value.length() && this.value.charAt(this.leading) == ' ')
{
this.leading++;
}
if(this.leading == this.value.length())
if (this.leading == this.value.length())
{
this.setEmpty();
}
@ -88,37 +94,39 @@ class Line
/**
* Skips spaces.
*
*
* @return <code>false</code> if end of line is reached
*/
// TODO use Util#skipSpaces
public boolean skipSpaces()
{
while(this.pos < this.value.length() && this.value.charAt(this.pos) == ' ')
while (this.pos < this.value.length() && this.value.charAt(this.pos) == ' ')
{
this.pos++;
}
return this.pos < this.value.length();
}
/**
* Reads chars from this line until any 'end' char is reached.
*
*
* @param end
* Delimiting character(s)
* @return The read String or <code>null</code> if no 'end' char was
* reached.
*/
// TODO use Util#readUntil
public String readUntil(char... end)
public String readUntil(final char... end)
{
final StringBuilder sb = new StringBuilder();
int pos = this.pos;
while(pos < this.value.length())
while (pos < this.value.length())
{
final char ch = this.value.charAt(pos);
if(ch == '\\' && pos + 1 < this.value.length())
if (ch == '\\' && pos + 1 < this.value.length())
{
final char c;
switch(c = this.value.charAt(pos + 1))
switch (c = this.value.charAt(pos + 1))
{
case '\\':
case '[':
@ -150,25 +158,27 @@ class Line
else
{
boolean endReached = false;
for(int n = 0; n < end.length; n++)
for (int n = 0; n < end.length; n++)
{
if(ch == end[n])
if (ch == end[n])
{
endReached = true;
break;
}
}
if(endReached)
if (endReached)
{
break;
}
sb.append(ch);
}
pos++;
}
final char ch = pos < this.value.length() ? this.value.charAt(pos) : '\n';
for(int n = 0; n < end.length; n++)
for (int n = 0; n < end.length; n++)
{
if(ch == end[n])
if (ch == end[n])
{
this.pos = pos;
return sb.toString();
@ -185,28 +195,34 @@ class Line
this.value = "";
this.leading = this.trailing = 0;
this.isEmpty = true;
if(this.previous != null)
if (this.previous != null)
{
this.previous.nextEmpty = true;
if(this.next != null)
}
if (this.next != null)
{
this.next.prevEmpty = true;
}
}
/**
* Counts the amount of 'ch' in this line.
*
*
* @param ch
* The char to count.
* @return A value > 0 if this line only consists of 'ch' end spaces.
*/
private int countChars(char ch)
private int countChars(final char ch)
{
int count = 0;
for(int i = 0; i < this.value.length(); i++)
for (int i = 0; i < this.value.length(); i++)
{
final char c = this.value.charAt(i);
if(c == ' ')
if (c == ' ')
{
continue;
if(c == ch)
}
if (c == ch)
{
count++;
continue;
@ -218,72 +234,98 @@ class Line
}
/**
* Counts the amount of 'ch' at the start of this line ignoring spaces.
*
* Counts the amount of 'ch' at the start of this line optionally ignoring
* spaces.
*
* @param ch
* The char to count.
* @param allowSpaces
* Whether to allow spaces or not
* @return Number of characters found.
* @since 0.7
* @since 0.12
*/
private int countCharsStart(char ch)
private int countCharsStart(final char ch, final boolean allowSpaces)
{
int count = 0;
for(int i = 0; i < this.value.length(); i++)
for (int i = 0; i < this.value.length(); i++)
{
final char c = this.value.charAt(i);
if(c == ' ')
if (c == ' ' && allowSpaces)
{
continue;
if(c == ch)
}
if (c == ch)
{
count++;
}
else
{
break;
}
}
return count;
}
/**
* Gets this line's type.
*
* @param extendedMode
* Whether extended profile is enabled or not
*
* @param configuration
* txtmark configuration
*
* @return The LineType.
*/
public LineType getLineType(boolean extendedMode)
public LineType getLineType(final Configuration configuration)
{
if(this.isEmpty)
return LineType.EMPTY;
if(this.leading > 3)
return LineType.CODE;
if(this.value.charAt(this.leading) == '#')
return LineType.HEADLINE;
if(this.value.charAt(this.leading) == '>')
return LineType.BQUOTE;
if(extendedMode)
if (this.isEmpty)
{
if(this.value.length() - this.leading - this.trailing > 2)
return LineType.EMPTY;
}
if (this.leading > 3)
{
return LineType.CODE;
}
if (this.value.charAt(this.leading) == '#')
{
return LineType.HEADLINE;
}
if (this.value.charAt(this.leading) == '>')
{
return LineType.BQUOTE;
}
if (configuration.forceExtendedProfile)
{
if (this.value.length() - this.leading - this.trailing > 2)
{
if(this.value.charAt(this.leading) == '`' && this.countCharsStart('`') >= 3)
if (this.value.charAt(this.leading) == '`'
&& this.countCharsStart('`', configuration.allowSpacesInFencedDelimiters) >= 3)
{
return LineType.FENCED_CODE;
if(this.value.charAt(this.leading) == '~' && this.countCharsStart('~') >= 3)
}
if (this.value.charAt(this.leading) == '~'
&& this.countCharsStart('~', configuration.allowSpacesInFencedDelimiters) >= 3)
{
return LineType.FENCED_CODE;
}
}
}
if(this.value.length() - this.leading - this.trailing > 2
if (this.value.length() - this.leading - this.trailing > 2
&& (this.value.charAt(this.leading) == '*' || this.value.charAt(this.leading) == '-' || this.value
.charAt(this.leading) == '_'))
{
if(this.countChars(this.value.charAt(this.leading)) >= 3)
if (this.countChars(this.value.charAt(this.leading)) >= 3)
{
return LineType.HR;
}
}
if(this.value.length() - this.leading >= 2 && this.value.charAt(this.leading + 1) == ' ')
if (this.value.length() - this.leading >= 2 && this.value.charAt(this.leading + 1) == ' ')
{
switch(this.value.charAt(this.leading))
switch (this.value.charAt(this.leading))
{
case '*':
case '-':
@ -292,27 +334,37 @@ class Line
}
}
if(this.value.length() - this.leading >= 3 && Character.isDigit(this.value.charAt(this.leading)))
if (this.value.length() - this.leading >= 3 && Character.isDigit(this.value.charAt(this.leading)))
{
int i = this.leading + 1;
while(i < this.value.length() && Character.isDigit(this.value.charAt(i)))
while (i < this.value.length() && Character.isDigit(this.value.charAt(i)))
{
i++;
if(i + 1 < this.value.length() && this.value.charAt(i) == '.' && this.value.charAt(i + 1) == ' ')
}
if (i + 1 < this.value.length() && this.value.charAt(i) == '.' && this.value.charAt(i + 1) == ' ')
{
return LineType.OLIST;
}
}
if(this.value.charAt(this.leading) == '<')
if (this.value.charAt(this.leading) == '<')
{
if(this.checkHTML())
if (this.checkHTML())
{
return LineType.XML;
}
}
if(this.next != null && !this.next.isEmpty)
if (this.next != null && !this.next.isEmpty)
{
if((this.next.value.charAt(0) == '-') && (this.next.countChars('-') > 0))
if ((this.next.value.charAt(0) == '-') && (this.next.countChars('-') > 0))
{
return LineType.HEADLINE2;
if((this.next.value.charAt(0) == '=') && (this.next.countChars('=') > 0))
}
if ((this.next.value.charAt(0) == '=') && (this.next.countChars('=') > 0))
{
return LineType.HEADLINE1;
}
}
return LineType.OTHER;
@ -320,7 +372,7 @@ class Line
/**
* Reads an XML comment. Sets <code>xmlEndLine</code>.
*
*
* @param firstLine
* The Line to start reading from.
* @param start
@ -330,27 +382,27 @@ class Line
private int readXMLComment(final Line firstLine, final int start)
{
Line line = firstLine;
if(start + 3 < line.value.length())
if (start + 3 < line.value.length())
{
if(line.value.charAt(2) == '-' && line.value.charAt(3) == '-')
if (line.value.charAt(2) == '-' && line.value.charAt(3) == '-')
{
int pos = start + 4;
while(line != null)
while (line != null)
{
while(pos < line.value.length() && line.value.charAt(pos) != '-')
while (pos < line.value.length() && line.value.charAt(pos) != '-')
{
pos++;
}
if(pos == line.value.length())
if (pos == line.value.length())
{
line = line.next;
pos = 0;
}
else
{
if(pos + 2 < line.value.length())
if (pos + 2 < line.value.length())
{
if(line.value.charAt(pos + 1) == '-' && line.value.charAt(pos + 2) == '>')
if (line.value.charAt(pos + 1) == '-' && line.value.charAt(pos + 2) == '>')
{
this.xmlEndLine = line;
return pos + 3;
@ -367,24 +419,26 @@ class Line
/**
* Checks if this line contains an ID at it's end and removes it from the
* line.
*
*
* @return The ID or <code>null</code> if no valid ID exists.
*/
// FIXME ... hack
public String stripID()
{
if(this.isEmpty || this.value.charAt(this.value.length() - this.trailing - 1) != '}')
if (this.isEmpty || this.value.charAt(this.value.length() - this.trailing - 1) != '}')
{
return null;
}
int p = this.leading;
boolean found = false;
while(p < this.value.length() && !found)
while (p < this.value.length() && !found)
{
switch(this.value.charAt(p))
switch (this.value.charAt(p))
{
case '\\':
if(p + 1 < this.value.length())
if (p + 1 < this.value.length())
{
switch(this.value.charAt(p + 1))
switch (this.value.charAt(p + 1))
{
case '{':
p++;
@ -402,21 +456,21 @@ class Line
}
}
if(found)
if (found)
{
if(p + 1 < this.value.length() && this.value.charAt(p + 1) == '#')
if (p + 1 < this.value.length() && this.value.charAt(p + 1) == '#')
{
final int start = p + 2;
p = start;
found = false;
while(p < this.value.length() && !found)
while (p < this.value.length() && !found)
{
switch(this.value.charAt(p))
switch (this.value.charAt(p))
{
case '\\':
if(p + 1 < this.value.length())
if (p + 1 < this.value.length())
{
switch(this.value.charAt(p + 1))
switch (this.value.charAt(p + 1))
{
case '}':
p++;
@ -433,10 +487,10 @@ class Line
break;
}
}
if(found)
if (found)
{
final String id = this.value.substring(start, p).trim();
if(this.leading != 0)
if (this.leading != 0)
{
this.value = this.value.substring(0, this.leading)
+ this.value.substring(this.leading, start - 2).trim();
@ -455,7 +509,7 @@ class Line
/**
* Checks for a valid HTML block. Sets <code>xmlEndLine</code>.
*
*
* @return <code>true</code> if it is a valid block.
*/
private boolean checkHTML()
@ -463,22 +517,26 @@ class Line
final LinkedList<String> tags = new LinkedList<String>();
final StringBuilder temp = new StringBuilder();
int pos = this.leading;
if(this.value.charAt(this.leading + 1) == '!')
if (this.value.charAt(this.leading + 1) == '!')
{
if(this.readXMLComment(this, this.leading) > 0)
if (this.readXMLComment(this, this.leading) > 0)
{
return true;
}
}
pos = Utils.readXML(temp, this.value, this.leading, false);
String element, tag;
if(pos > -1)
if (pos > -1)
{
element = temp.toString();
temp.setLength(0);
Utils.getXMLTag(temp, element);
tag = temp.toString().toLowerCase();
if(!HTML.isHtmlBlockElement(tag))
if (!HTML.isHtmlBlockElement(tag))
{
return false;
if(tag.equals("hr"))
}
if (tag.equals("hr"))
{
this.xmlEndLine = this;
return true;
@ -486,13 +544,13 @@ class Line
tags.add(tag);
Line line = this;
while(line != null)
while (line != null)
{
while(pos < line.value.length() && line.value.charAt(pos) != '<')
while (pos < line.value.length() && line.value.charAt(pos) != '<')
{
pos++;
}
if(pos >= line.value.length())
if (pos >= line.value.length())
{
line = line.next;
pos = 0;
@ -501,18 +559,20 @@ class Line
{
temp.setLength(0);
final int newPos = Utils.readXML(temp, line.value, pos, false);
if(newPos > 0)
if (newPos > 0)
{
element = temp.toString();
temp.setLength(0);
Utils.getXMLTag(temp, element);
tag = temp.toString().toLowerCase();
if(HTML.isHtmlBlockElement(tag) && !tag.equals("hr"))
if (HTML.isHtmlBlockElement(tag) && !tag.equals("hr"))
{
if(element.charAt(1) == '/')
if (element.charAt(1) == '/')
{
if(!tags.getLast().equals(tag))
if (!tags.getLast().equals(tag))
{
return false;
}
tags.removeLast();
}
else
@ -520,7 +580,7 @@ class Line
tags.addLast(tag);
}
}
if(tags.size() == 0)
if (tags.size() == 0)
{
this.xmlEndLine = line;
break;

View File

@ -26,32 +26,32 @@ import java.io.StringReader;
/**
* Markdown processor class.
*
*
* <p>
* Example usage:
* </p>
*
*
* <pre>
* <code>String result = Processor.process("This is ***TXTMARK***");
* </code>
* </pre>
*
*
* @author René Jeschke &lt;rene_jeschke@yahoo.de&gt;
*/
public class Processor
{
/** The reader. */
private final Reader reader;
private final Reader reader;
/** The emitter. */
private final Emitter emitter;
/** The Configuration. */
final Configuration config;
final Configuration config;
/** Extension flag. */
private boolean useExtensions = false;
private boolean useExtensions = false;
/**
* Constructor.
*
*
* @param reader
* The input reader.
*/
@ -65,7 +65,7 @@ public class Processor
/**
* Transforms an input stream into HTML using the given Configuration.
*
*
* @param reader
* The Reader to process.
* @param configuration
@ -85,7 +85,7 @@ public class Processor
/**
* Transforms an input String into HTML using the given Configuration.
*
*
* @param input
* The String to process.
* @param configuration
@ -100,7 +100,7 @@ public class Processor
{
return process(new StringReader(input), configuration);
}
catch (IOException e)
catch (final IOException e)
{
// This _can never_ happen
return null;
@ -109,9 +109,11 @@ public class Processor
/**
* Transforms an input file into HTML using the given Configuration.
*
* @param file The File to process.
* @param configuration the Configuration
*
* @param file
* The File to process.
* @param configuration
* the Configuration
* @return The processed String.
* @throws IOException
* if an IO error occurs
@ -128,7 +130,7 @@ public class Processor
/**
* Transforms an input stream into HTML using the given Configuration.
*
*
* @param input
* The InputStream to process.
* @param configuration
@ -148,7 +150,7 @@ public class Processor
/**
* Transforms an input String into HTML using the default Configuration.
*
*
* @param input
* The String to process.
* @return The processed String.
@ -161,7 +163,7 @@ public class Processor
/**
* Transforms an input String into HTML.
*
*
* @param input
* The String to process.
* @param safeMode
@ -176,7 +178,7 @@ public class Processor
/**
* Transforms an input String into HTML.
*
*
* @param input
* The String to process.
* @param decorator
@ -191,7 +193,7 @@ public class Processor
/**
* Transforms an input String into HTML.
*
*
* @param input
* The String to process.
* @param decorator
@ -208,7 +210,7 @@ public class Processor
/**
* Transforms an input file into HTML using the default Configuration.
*
*
* @param file
* The File to process.
* @return The processed String.
@ -223,7 +225,7 @@ public class Processor
/**
* Transforms an input file into HTML.
*
*
* @param file
* The File to process.
* @param safeMode
@ -240,7 +242,7 @@ public class Processor
/**
* Transforms an input file into HTML.
*
*
* @param file
* The File to process.
* @param decorator
@ -257,7 +259,7 @@ public class Processor
/**
* Transforms an input file into HTML.
*
*
* @param file
* The File to process.
* @param decorator
@ -277,7 +279,7 @@ public class Processor
/**
* Transforms an input file into HTML.
*
*
* @param file
* The File to process.
* @param encoding
@ -294,7 +296,7 @@ public class Processor
/**
* Transforms an input file into HTML.
*
*
* @param file
* The File to process.
* @param encoding
@ -314,7 +316,7 @@ public class Processor
/**
* Transforms an input file into HTML.
*
*
* @param file
* The File to process.
* @param encoding
@ -334,7 +336,7 @@ public class Processor
/**
* Transforms an input file into HTML.
*
*
* @param file
* The File to process.
* @param encoding
@ -357,7 +359,7 @@ public class Processor
/**
* Transforms an input stream into HTML.
*
*
* @param input
* The InputStream to process.
* @return The processed String.
@ -372,7 +374,7 @@ public class Processor
/**
* Transforms an input stream into HTML.
*
*
* @param input
* The InputStream to process.
* @param safeMode
@ -389,7 +391,7 @@ public class Processor
/**
* Transforms an input stream into HTML.
*
*
* @param input
* The InputStream to process.
* @param decorator
@ -406,7 +408,7 @@ public class Processor
/**
* Transforms an input stream into HTML.
*
*
* @param input
* The InputStream to process.
* @param decorator
@ -426,7 +428,7 @@ public class Processor
/**
* Transforms an input stream into HTML.
*
*
* @param input
* The InputStream to process.
* @param encoding
@ -443,7 +445,7 @@ public class Processor
/**
* Transforms an input stream into HTML.
*
*
* @param input
* The InputStream to process.
* @param encoding
@ -463,7 +465,7 @@ public class Processor
/**
* Transforms an input stream into HTML.
*
*
* @param input
* The InputStream to process.
* @param encoding
@ -483,7 +485,7 @@ public class Processor
/**
* Transforms an input stream into HTML.
*
*
* @param input
* The InputStream to process.
* @param encoding
@ -506,7 +508,7 @@ public class Processor
/**
* Transforms an input stream into HTML using the default Configuration.
*
*
* @param reader
* The Reader to process.
* @return The processed String.
@ -521,7 +523,7 @@ public class Processor
/**
* Transforms an input stream into HTML.
*
*
* @param reader
* The Reader to process.
* @param safeMode
@ -538,7 +540,7 @@ public class Processor
/**
* Transforms an input stream into HTML.
*
*
* @param reader
* The Reader to process.
* @param decorator
@ -555,7 +557,7 @@ public class Processor
/**
* Transforms an input stream into HTML.
*
*
* @param reader
* The Reader to process.
* @param decorator
@ -578,7 +580,7 @@ public class Processor
* <p>
* Takes care of markdown link references.
* </p>
*
*
* @return A Block containing all lines.
* @throws IOException
* If an IO error occurred.
@ -589,34 +591,38 @@ public class Processor
final StringBuilder sb = new StringBuilder(80);
int c = this.reader.read();
LinkRef lastLinkRef = null;
while(c != -1)
while (c != -1)
{
sb.setLength(0);
int pos = 0;
boolean eol = false;
while(!eol)
while (!eol)
{
switch(c)
switch (c)
{
case -1:
eol = true;
break;
case '\n':
c = this.reader.read();
if(c == '\r')
if (c == '\r')
{
c = this.reader.read();
}
eol = true;
break;
case '\r':
c = this.reader.read();
if(c == '\n')
if (c == '\n')
{
c = this.reader.read();
}
eol = true;
break;
case '\t':
{
final int np = pos + (4 - (pos & 3));
while(pos < np)
while (pos < np)
{
sb.append(' ');
pos++;
@ -639,57 +645,63 @@ public class Processor
// 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) == '[')
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())
if (id != null && line.pos + 2 < line.value.length())
{
// Check for ':' ([...]:...)
if(line.value.charAt(line.pos + 1) == ':')
if (line.value.charAt(line.pos + 1) == ':')
{
line.pos += 2;
line.skipSpaces();
// Check for link syntax
if(line.value.charAt(line.pos) == '<')
if (line.value.charAt(line.pos) == '<')
{
line.pos++;
link = line.readUntil('>');
line.pos++;
}
else
{
link = line.readUntil(' ', '\n');
}
// Is link valid?
if(link != null)
if (link != null)
{
// Any non-whitespace characters following?
if(line.skipSpaces())
if (line.skipSpaces())
{
final char ch = line.value.charAt(line.pos);
// Read comment
if(ch == '\"' || ch == '\'' || ch == '(')
if (ch == '\"' || ch == '\'' || ch == '(')
{
line.pos++;
comment = line.readUntil(ch == '(' ? ')' : ch);
// Valid linkRef only if comment is valid
if(comment != null)
if (comment != null)
{
isLinkRef = true;
}
}
}
else
{
isLinkRef = true;
}
}
}
}
}
// To make compiler happy: add != null checks
if(isLinkRef && id != null && link != null)
if (isLinkRef && id != null && link != null)
{
if(id.toLowerCase().equals("$profile$"))
if (id.toLowerCase().equals("$profile$"))
{
this.emitter.useExtensions = this.useExtensions = link.toLowerCase().equals("extended");
lastLinkRef = null;
@ -700,31 +712,35 @@ public class Processor
final LinkRef lr = new LinkRef(link, comment, comment != null
&& (link.length() == 1 && link.charAt(0) == '*'));
this.emitter.addLinkRef(id, lr);
if(comment == null)
if (comment == null)
{
lastLinkRef = lr;
}
}
}
else
{
comment = null;
// Check for multi-line linkRef
if(!line.isEmpty && lastLinkRef != null)
if (!line.isEmpty && lastLinkRef != null)
{
line.pos = line.leading;
final char ch = line.value.charAt(line.pos);
if(ch == '\"' || ch == '\'' || ch == '(')
if (ch == '\"' || ch == '\'' || ch == '(')
{
line.pos++;
comment = line.readUntil(ch == '(' ? ')' : ch);
}
if(comment != null)
if (comment != null)
{
lastLinkRef.title = comment;
}
lastLinkRef = null;
}
// No multi-line linkRef, store line
if(comment == null)
if (comment == null)
{
line.pos = 0;
block.appendLine(line);
@ -737,7 +753,7 @@ public class Processor
/**
* Initializes a list block by separating it into list item blocks.
*
*
* @param root
* The Block to process.
*/
@ -745,10 +761,10 @@ public class Processor
{
Line line = root.lines;
line = line.next;
while(line != null)
while (line != null)
{
final LineType t = line.getLineType(this.useExtensions);
if((t == LineType.OLIST || t == LineType.ULIST)
final LineType t = line.getLineType(this.config);
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;
@ -760,53 +776,64 @@ public class Processor
/**
* 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)
private void recurse(final Block root, final boolean listMode)
{
Block block, list;
Line line = root.lines;
if(listMode)
if (listMode)
{
root.removeListIndent(this.useExtensions);
if(this.useExtensions && root.lines != null && root.lines.getLineType(this.useExtensions) != LineType.CODE)
root.removeListIndent(this.config);
if (this.useExtensions && root.lines != null && root.lines.getLineType(this.config) != LineType.CODE)
{
root.id = root.lines.stripID();
}
}
while(line != null && line.isEmpty)
line = line.next;
if(line == null)
return;
while(line != null)
while (line != null && line.isEmpty)
{
final LineType type = line.getLineType(this.useExtensions);
switch(type)
line = line.next;
}
if (line == null)
{
return;
}
while (line != null)
{
final LineType type = line.getLineType(this.config);
switch (type)
{
case OTHER:
{
final boolean wasEmpty = line.prevEmpty;
while(line != null && !line.isEmpty)
while (line != null && !line.isEmpty)
{
final LineType t = line.getLineType(this.useExtensions);
if((listMode || this.useExtensions) && (t == LineType.OLIST || t == LineType.ULIST))
final LineType t = line.getLineType(this.config);
if ((listMode || this.useExtensions) && (t == LineType.OLIST || t == LineType.ULIST))
{
break;
if(this.useExtensions && (t == LineType.CODE || t == LineType.FENCED_CODE))
}
if (this.useExtensions && (t == LineType.CODE || t == LineType.FENCED_CODE))
{
break;
if(t == LineType.HEADLINE || t == LineType.HEADLINE1 || t == LineType.HEADLINE2 || t == LineType.HR
}
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)
if (line != null && !line.isEmpty)
{
bt = (listMode && !wasEmpty) ? BlockType.NONE : BlockType.PARAGRAPH;
root.split(line.previous).type = bt;
@ -823,7 +850,7 @@ public class Processor
break;
}
case CODE:
while(line != null && (line.isEmpty || line.leading > 3))
while (line != null && (line.isEmpty || line.leading > 3))
{
line = line.next;
}
@ -832,7 +859,7 @@ public class Processor
block.removeSurroundingEmptyLines();
break;
case XML:
if(line.previous != null)
if (line.previous != null)
{
// FIXME ... this looks wrong
root.split(line.previous);
@ -842,11 +869,13 @@ public class Processor
line = root.lines;
break;
case BQUOTE:
while(line != null)
while (line != null)
{
if(!line.isEmpty
&& (line.prevEmpty && line.leading == 0 && line.getLineType(this.useExtensions) != LineType.BQUOTE))
if (!line.isEmpty
&& (line.prevEmpty && line.leading == 0 && line.getLineType(this.config) != LineType.BQUOTE))
{
break;
}
line = line.next;
}
block = root.split(line != null ? line.previous : root.lineTail);
@ -857,7 +886,7 @@ public class Processor
line = root.lines;
break;
case HR:
if(line.previous != null)
if (line.previous != null)
{
// FIXME ... this looks wrong
root.split(line.previous);
@ -868,53 +897,65 @@ public class Processor
break;
case FENCED_CODE:
line = line.next;
while(line != null)
while (line != null)
{
if(line.getLineType(this.useExtensions) == LineType.FENCED_CODE)
if (line.getLineType(this.config) == LineType.FENCED_CODE)
{
break;
}
// TODO ... is this really necessary? Maybe add a special
// flag?
line = line.next;
}
if(line != null)
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)
if (block.lineTail.getLineType(this.config) == LineType.FENCED_CODE)
{
block.lineTail.setEmpty();
}
block.removeSurroundingEmptyLines();
break;
case HEADLINE:
case HEADLINE1:
case HEADLINE2:
if(line.previous != null)
if (line.previous != null)
{
root.split(line.previous);
}
if(type != LineType.HEADLINE)
if (type != LineType.HEADLINE)
{
line.next.setEmpty();
}
block = root.split(line);
block.type = BlockType.HEADLINE;
if(type != LineType.HEADLINE)
if (type != LineType.HEADLINE)
{
block.hlDepth = type == LineType.HEADLINE1 ? 1 : 2;
if(this.useExtensions)
}
if (this.useExtensions)
{
block.id = block.lines.stripID();
}
block.transfromHeadline();
root.removeLeadingEmptyLines();
line = root.lines;
break;
case OLIST:
case ULIST:
while(line != null)
while (line != null)
{
final LineType t = line.getLineType(this.useExtensions);
if(!line.isEmpty
final LineType t = line.getLineType(this.config);
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);
@ -923,9 +964,9 @@ public class Processor
list.lineTail.nextEmpty = false;
list.removeSurroundingEmptyLines();
list.lines.prevEmpty = list.lineTail.nextEmpty = false;
initListBlock(list);
this.initListBlock(list);
block = list.blocks;
while(block != null)
while (block != null)
{
this.recurse(block, true);
block = block.next;
@ -941,7 +982,7 @@ public class Processor
/**
* Does all the processing.
*
*
* @return The processed String.
* @throws IOException
* If an IO error occurred.
@ -954,7 +995,7 @@ public class Processor
this.recurse(parent, false);
Block block = parent.blocks;
while(block != null)
while (block != null)
{
this.emitter.emit(out, block);
block = block.next;