mirror of
https://github.com/lucaspalomodevelop/txtmark.git
synced 2026-03-12 23:37:22 +00:00
Maven folder structure, dropped ant build.
This commit is contained in:
parent
66ad9595d0
commit
d42dae270a
2
.gitignore
vendored
2
.gitignore
vendored
@ -5,3 +5,5 @@ release/
|
||||
.classpath
|
||||
.project
|
||||
target/
|
||||
*~
|
||||
|
||||
|
||||
13
README.md
13
README.md
@ -24,19 +24,6 @@ For an in-depth explanation of markdown have a look at the original [Markdown Sy
|
||||
|
||||
Txtmark is now available as a maven artifact. Have a look [here] (http://renejeschke.de/maven/).
|
||||
|
||||
***
|
||||
|
||||
### Build instructions
|
||||
|
||||
1. Clone the [repo] or download the sources as [tar] or [zip]
|
||||
2. Install [Apache Ant(TM)]
|
||||
3. Do
|
||||
|
||||
ant release
|
||||
|
||||
and you will find everything you need inside the `release` folder.
|
||||
|
||||
|
||||
***
|
||||
|
||||
### Txtmark extensions
|
||||
|
||||
3
pom.xml
3
pom.xml
@ -80,9 +80,6 @@
|
||||
<version>2.2</version>
|
||||
</extension>
|
||||
</extensions>
|
||||
|
||||
<sourceDirectory>${basedir}/src/java</sourceDirectory>
|
||||
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
|
||||
296
src/main/java/com/github/rjeschke/txtmark/Block.java
Normal file
296
src/main/java/com/github/rjeschke/txtmark/Block.java
Normal file
@ -0,0 +1,296 @@
|
||||
/*
|
||||
* Copyright (C) 2011 René Jeschke <rene_jeschke@yahoo.de>
|
||||
*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
/** Head and tail of linked lines. */
|
||||
public Line lines = null, lineTail = null;
|
||||
/** Head and tail of child blocks. */
|
||||
public Block blocks = null, blockTail = null;
|
||||
/** Next block. */
|
||||
public Block next = null;
|
||||
/** Depth of headline BlockType. */
|
||||
public int hlDepth = 0;
|
||||
/** ID for headlines and list items */
|
||||
public String id = null;
|
||||
|
||||
/** Constructor. */
|
||||
public Block()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if this block contains lines.
|
||||
*/
|
||||
public boolean hasLines()
|
||||
{
|
||||
return this.lines != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes leading and trailing empty lines.
|
||||
*/
|
||||
public void removeSurroundingEmptyLines()
|
||||
{
|
||||
if(this.lines != null)
|
||||
{
|
||||
this.removeTrailingEmptyLines();
|
||||
this.removeLeadingEmptyLines();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets <code>hlDepth</code> and takes care of '#' chars.
|
||||
*/
|
||||
public void transfromHeadline()
|
||||
{
|
||||
if(this.hlDepth > 0)
|
||||
return;
|
||||
int level = 0;
|
||||
final Line line = this.lines;
|
||||
if(line.isEmpty)
|
||||
return;
|
||||
int start = line.leading;
|
||||
while(start < line.value.length() && line.value.charAt(start) == '#')
|
||||
{
|
||||
level++;
|
||||
start++;
|
||||
}
|
||||
while(start < line.value.length() && line.value.charAt(start) == ' ')
|
||||
start++;
|
||||
if(start >= line.value.length())
|
||||
{
|
||||
line.setEmpty();
|
||||
}
|
||||
else
|
||||
{
|
||||
int end = line.value.length() - line.trailing - 1;
|
||||
while(line.value.charAt(end) == '#')
|
||||
end--;
|
||||
while(line.value.charAt(end) == ' ')
|
||||
end--;
|
||||
line.value = line.value.substring(start, end + 1);
|
||||
line.leading = line.trailing = 0;
|
||||
}
|
||||
this.hlDepth = Math.min(level, 6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for nested lists. Removes list markers and up to 4 leading spaces.
|
||||
*/
|
||||
public void removeListIndent()
|
||||
{
|
||||
Line line = this.lines;
|
||||
while(line != null)
|
||||
{
|
||||
if(!line.isEmpty)
|
||||
{
|
||||
switch(line.getLineType())
|
||||
{
|
||||
case ULIST:
|
||||
line.value = line.value.substring(line.leading + 2);
|
||||
break;
|
||||
case OLIST:
|
||||
line.value = line.value.substring(line.value.indexOf('.') + 2);
|
||||
break;
|
||||
default:
|
||||
line.value = line.value.substring(Math.min(line.leading, 4));
|
||||
break;
|
||||
}
|
||||
line.initLeading();
|
||||
}
|
||||
line = line.next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for nested block quotes. Removes '>' char.
|
||||
*/
|
||||
public void removeBlockQuotePrefix()
|
||||
{
|
||||
Line line = this.lines;
|
||||
while(line != null)
|
||||
{
|
||||
if(!line.isEmpty)
|
||||
{
|
||||
if(line.value.charAt(line.leading) == '>')
|
||||
{
|
||||
int rem = 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();
|
||||
}
|
||||
}
|
||||
line = line.next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
this.removeLine(line);
|
||||
line = this.lines;
|
||||
wasEmpty = true;
|
||||
}
|
||||
return wasEmpty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes trailing empty lines.
|
||||
*/
|
||||
public void removeTrailingEmptyLines()
|
||||
{
|
||||
Line line = this.lineTail;
|
||||
while(line != null && line.isEmpty)
|
||||
{
|
||||
this.removeLine(line);
|
||||
line = this.lineTail;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public Block split(final Line line)
|
||||
{
|
||||
final Block block = new Block();
|
||||
|
||||
block.lines = this.lines;
|
||||
block.lineTail = line;
|
||||
this.lines = line.next;
|
||||
line.next = null;
|
||||
if(this.lines == null)
|
||||
this.lineTail = null;
|
||||
else
|
||||
this.lines.previous = null;
|
||||
|
||||
if(this.blocks == null)
|
||||
this.blocks = this.blockTail = block;
|
||||
else
|
||||
{
|
||||
this.blockTail.next = block;
|
||||
this.blockTail = block;
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given line from this block.
|
||||
*
|
||||
* @param line Line to remove.
|
||||
*/
|
||||
public void removeLine(final Line line)
|
||||
{
|
||||
if(line.previous == null)
|
||||
this.lines = line.next;
|
||||
else
|
||||
line.previous.next = line.next;
|
||||
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)
|
||||
this.lines = this.lineTail = line;
|
||||
else
|
||||
{
|
||||
this.lineTail.nextEmpty = line.isEmpty;
|
||||
line.prevEmpty = this.lineTail.isEmpty;
|
||||
line.previous = this.lineTail;
|
||||
this.lineTail.next = line;
|
||||
this.lineTail = line;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes all Blocks of type <code>NONE</code> to <code>PARAGRAPH</code> if this Block
|
||||
* is a List and any of the ListItems contains a paragraph.
|
||||
*/
|
||||
public void expandListParagraphs()
|
||||
{
|
||||
if(this.type != BlockType.ORDERED_LIST && this.type != BlockType.UNORDERED_LIST)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Block outer = this.blocks, inner;
|
||||
boolean hasParagraph = false;
|
||||
while(outer != null && !hasParagraph)
|
||||
{
|
||||
if(outer.type == BlockType.LIST_ITEM)
|
||||
{
|
||||
inner = outer.blocks;
|
||||
while(inner != null && !hasParagraph)
|
||||
{
|
||||
if(inner.type == BlockType.PARAGRAPH)
|
||||
{
|
||||
hasParagraph = true;
|
||||
}
|
||||
inner = inner.next;
|
||||
}
|
||||
}
|
||||
outer = outer.next;
|
||||
}
|
||||
if(hasParagraph)
|
||||
{
|
||||
outer = this.blocks;
|
||||
while(outer != null)
|
||||
{
|
||||
if(outer.type == BlockType.LIST_ITEM)
|
||||
{
|
||||
inner = outer.blocks;
|
||||
while(inner != null)
|
||||
{
|
||||
if(inner.type == BlockType.NONE)
|
||||
{
|
||||
inner.type = BlockType.PARAGRAPH;
|
||||
}
|
||||
inner = inner.next;
|
||||
}
|
||||
}
|
||||
outer = outer.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
70
src/main/java/com/github/rjeschke/txtmark/BlockEmitter.java
Normal file
70
src/main/java/com/github/rjeschke/txtmark/BlockEmitter.java
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2011 René Jeschke <rene_jeschke@yahoo.de>
|
||||
*
|
||||
* 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.util.List;
|
||||
|
||||
/**
|
||||
* Block emitter interface. An example for a code block emitter would look like
|
||||
* this:
|
||||
*
|
||||
* <pre>
|
||||
* <code>public void emitBlock(StringBuilder out, List<String> lines)
|
||||
* {
|
||||
* for(final String s : lines)
|
||||
* {
|
||||
* for(int i = 0; i < s.length(); i++)
|
||||
* {
|
||||
* final char c = s.charAt(i);
|
||||
* switch(c)
|
||||
* {
|
||||
* case '&':
|
||||
* out.append("&amp;");
|
||||
* break;
|
||||
* case '<':
|
||||
* out.append("&lt;");
|
||||
* break;
|
||||
* case '>':
|
||||
* out.append("&gt;");
|
||||
* break;
|
||||
* default:
|
||||
* out.append(c);
|
||||
* break;
|
||||
* }
|
||||
* }
|
||||
* out.append('\n');
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* @author René Jeschke <rene_jeschke@yahoo.de>
|
||||
* @since 0.7
|
||||
*/
|
||||
public interface BlockEmitter
|
||||
{
|
||||
/**
|
||||
* This method is responsible for outputting a markdown block. All
|
||||
* processing must be done inside this method.
|
||||
*
|
||||
* @param out
|
||||
* The StringBuilder to append to
|
||||
* @param lines
|
||||
* List of lines
|
||||
*/
|
||||
public void emitBlock(StringBuilder out, List<String> lines);
|
||||
}
|
||||
45
src/main/java/com/github/rjeschke/txtmark/BlockType.java
Normal file
45
src/main/java/com/github/rjeschke/txtmark/BlockType.java
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2011 René Jeschke <rene_jeschke@yahoo.de>
|
||||
*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Block type enum.
|
||||
*
|
||||
* @author René Jeschke <rene_jeschke@yahoo.de>
|
||||
*/
|
||||
enum BlockType
|
||||
{
|
||||
/** Unspecified. Used for root block and list items without paragraphs. */
|
||||
NONE,
|
||||
/** A block quote. */
|
||||
BLOCKQUOTE,
|
||||
/** A code block. */
|
||||
CODE,
|
||||
/** A headline. */
|
||||
HEADLINE,
|
||||
/** A list item. */
|
||||
LIST_ITEM,
|
||||
/** An ordered list. */
|
||||
ORDERED_LIST,
|
||||
/** A paragraph. */
|
||||
PARAGRAPH,
|
||||
/** A horizontal ruler. */
|
||||
RULER,
|
||||
/** An unordered list. */
|
||||
UNORDERED_LIST,
|
||||
/** A XML block. */
|
||||
XML
|
||||
}
|
||||
198
src/main/java/com/github/rjeschke/txtmark/Configuration.java
Normal file
198
src/main/java/com/github/rjeschke/txtmark/Configuration.java
Normal file
@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright (C) 2011 René Jeschke <rene_jeschke@yahoo.de>
|
||||
*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Txtmark configuration.
|
||||
*
|
||||
* @author René Jeschke <rene_jeschke@yahoo.de>
|
||||
* @since 0.7
|
||||
*/
|
||||
public class Configuration
|
||||
{
|
||||
final boolean safeMode;
|
||||
final String encoding;
|
||||
final Decorator decorator;
|
||||
final BlockEmitter codeBlockEmitter;
|
||||
|
||||
/**
|
||||
* <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>
|
||||
* <li><code>decorator = DefaultDecorator</code></li>
|
||||
* <li><code>codeBlockEmitter = null</code></li>
|
||||
* </ul>
|
||||
*/
|
||||
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>
|
||||
* <li><code>decorator = DefaultDecorator</code></li>
|
||||
* <li><code>codeBlockEmitter = null</code></li>
|
||||
* </ul>
|
||||
*/
|
||||
public final static Configuration DEFAULT_SAFE = Configuration.builder().enableSafeMode().build();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param safeMode
|
||||
* @param encoding
|
||||
* @param decorator
|
||||
*/
|
||||
Configuration(boolean safeMode, String encoding, Decorator decorator, BlockEmitter codeBlockEmitter)
|
||||
{
|
||||
this.safeMode = safeMode;
|
||||
this.encoding = encoding;
|
||||
this.decorator = decorator;
|
||||
this.codeBlockEmitter = codeBlockEmitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Builder instance.
|
||||
*
|
||||
* @return A new Builder instance.
|
||||
*/
|
||||
public static Builder builder()
|
||||
{
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration builder.
|
||||
*
|
||||
* @author René Jeschke <rene_jeschke@yahoo.de>
|
||||
* @since 0.7
|
||||
*/
|
||||
public static class Builder
|
||||
{
|
||||
private boolean safeMode = false;
|
||||
private String encoding = "UTF-8";
|
||||
private Decorator decorator = new DefaultDecorator();
|
||||
private BlockEmitter codeBlockEmitter = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
*/
|
||||
Builder()
|
||||
{
|
||||
// empty
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables HTML safe mode.
|
||||
*
|
||||
* Default: <code>false</code>
|
||||
*
|
||||
* @return This builder
|
||||
* @since 0.7
|
||||
*/
|
||||
public Builder enableSafeMode()
|
||||
{
|
||||
this.safeMode = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
this.safeMode = flag;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the character encoding for txtmark.
|
||||
*
|
||||
* Default: <code>"UTF-8"</code>
|
||||
*
|
||||
* @param encoding
|
||||
* The encoding
|
||||
* @return This builder
|
||||
* @since 0.7
|
||||
*/
|
||||
public Builder setEncoding(String encoding)
|
||||
{
|
||||
this.encoding = encoding;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
this.decorator = decorator;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
this.codeBlockEmitter = emitter;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
281
src/main/java/com/github/rjeschke/txtmark/Decorator.java
Normal file
281
src/main/java/com/github/rjeschke/txtmark/Decorator.java
Normal file
@ -0,0 +1,281 @@
|
||||
/*
|
||||
* Copyright (C) 2011 René Jeschke <rene_jeschke@yahoo.de>
|
||||
*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Decorator interface.
|
||||
*
|
||||
* @author René Jeschke <rene_jeschke@yahoo.de>
|
||||
*/
|
||||
public interface Decorator
|
||||
{
|
||||
/**
|
||||
* Called when a paragraph is opened.
|
||||
*
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code>out.append("<p>");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void openParagraph(final StringBuilder out);
|
||||
|
||||
/**
|
||||
* Called when a paragraph is closed.
|
||||
*
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code>out.append("</p>\n");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void closeParagraph(final StringBuilder out);
|
||||
|
||||
/**
|
||||
* Called when a blockquote is opened.
|
||||
*
|
||||
* Default implementation is:
|
||||
* <pre><code>out.append("<blockquote>");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void openBlockquote(final StringBuilder out);
|
||||
|
||||
/**
|
||||
* Called when a blockquote is closed.
|
||||
*
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code>out.append("</blockquote>\n");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void closeBlockquote(final StringBuilder out);
|
||||
|
||||
/**
|
||||
* Called when a code block is opened.
|
||||
*
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code>out.append("<pre><code>");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void openCodeBlock(final StringBuilder out);
|
||||
|
||||
/**
|
||||
* Called when a code block is closed.
|
||||
*
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code>out.append("</code></pre>\n");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void closeCodeBlock(final StringBuilder out);
|
||||
|
||||
/**
|
||||
* Called when a code span is opened.
|
||||
*
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code>out.append("<code>");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void openCodeSpan(final StringBuilder out);
|
||||
|
||||
/**
|
||||
* Called when a code span is closed.
|
||||
*
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code>out.append("</code>");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void closeCodeSpan(final StringBuilder out);
|
||||
|
||||
/**
|
||||
* Called when a headline is opened.
|
||||
*
|
||||
* <p><strong>Note:</strong> Don't close the HTML tag!</p>
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code> out.append("<h");
|
||||
* out.append(level);</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void openHeadline(final StringBuilder out, int level);
|
||||
|
||||
/**
|
||||
* Called when a headline is closed.
|
||||
*
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code> out.append("</h");
|
||||
* out.append(level);
|
||||
* out.append(">\n");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void closeHeadline(final StringBuilder out, int level);
|
||||
|
||||
/**
|
||||
* Called when a strong span is opened.
|
||||
*
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code>out.append("<strong>");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void openStrong(final StringBuilder out);
|
||||
|
||||
/**
|
||||
* Called when a strong span is closed.
|
||||
*
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code>out.append("</strong>");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void closeStrong(final StringBuilder out);
|
||||
|
||||
/**
|
||||
* Called when an emphasis span is opened.
|
||||
*
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code>out.append("<em>");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void openEmphasis(final StringBuilder out);
|
||||
|
||||
/**
|
||||
* Called when an emphasis span is closed.
|
||||
*
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code>out.append("</em>");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void closeEmphasis(final StringBuilder out);
|
||||
|
||||
/**
|
||||
* Called when a superscript span is opened.
|
||||
*
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code>out.append("<sup>");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void openSuper(final StringBuilder out);
|
||||
|
||||
/**
|
||||
* Called when a superscript span is closed.
|
||||
*
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code>out.append("</sup>");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void closeSuper(final StringBuilder out);
|
||||
|
||||
/**
|
||||
* Called when an ordered list is opened.
|
||||
*
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code>out.append("<ol>\n");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void openOrderedList(final StringBuilder out);
|
||||
|
||||
/**
|
||||
* Called when an ordered list is closed.
|
||||
*
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code>out.append("</ol>\n");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void closeOrderedList(final StringBuilder out);
|
||||
|
||||
/**
|
||||
* Called when an unordered list is opened.
|
||||
*
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code>out.append("<ul>\n");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void openUnorderedList(final StringBuilder out);
|
||||
|
||||
/**
|
||||
* Called when an unordered list is closed.
|
||||
*
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code>out.append("</ul>\n");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void closeUnorderedList(final StringBuilder out);
|
||||
|
||||
/**
|
||||
* Called when a list item is opened.
|
||||
*
|
||||
* <p><strong>Note:</strong> Don't close the HTML tag!</p>
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code>out.append("<li");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void openListItem(final StringBuilder out);
|
||||
|
||||
/**
|
||||
* Called when a list item is closed.
|
||||
*
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code>out.append("</li>\n");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void closeListItem(final StringBuilder out);
|
||||
|
||||
/**
|
||||
* Called when a horizontal ruler is encountered.
|
||||
*
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code>out.append("<hr />\n");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void horizontalRuler(final StringBuilder out);
|
||||
|
||||
/**
|
||||
* Called when a link is opened.
|
||||
*
|
||||
* <p><strong>Note:</strong> Don't close the HTML tag!</p>
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code>out.append("<a");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void openLink(final StringBuilder out);
|
||||
|
||||
/**
|
||||
* Called when an image is opened.
|
||||
*
|
||||
* <p><strong>Note:</strong> Don't close the HTML tag!</p>
|
||||
* <p>Default implementation is:</p>
|
||||
* <pre><code>out.append("<img");</code></pre>
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
*/
|
||||
public void openImage(final StringBuilder out);
|
||||
}
|
||||
230
src/main/java/com/github/rjeschke/txtmark/DefaultDecorator.java
Normal file
230
src/main/java/com/github/rjeschke/txtmark/DefaultDecorator.java
Normal file
@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright (C) 2011 René Jeschke <rene_jeschke@yahoo.de>
|
||||
*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Default Decorator implementation.
|
||||
*
|
||||
* <p>
|
||||
* Example for a user Decorator having a class attribute on <p> tags.
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* <code>public class MyDecorator extends DefaultDecorator
|
||||
* {
|
||||
* @Override
|
||||
* public void openParagraph(StringBuilder out)
|
||||
* {
|
||||
* out.append("<p class=\"myclass\">");
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @author René Jeschke <rene_jeschke@yahoo.de>
|
||||
*/
|
||||
public class DefaultDecorator implements Decorator
|
||||
{
|
||||
/** Constructor. */
|
||||
public DefaultDecorator()
|
||||
{
|
||||
// empty
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#openParagraph(StringBuilder) */
|
||||
@Override
|
||||
public void openParagraph(StringBuilder out)
|
||||
{
|
||||
out.append("<p>");
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#closeParagraph(StringBuilder) */
|
||||
@Override
|
||||
public void closeParagraph(StringBuilder out)
|
||||
{
|
||||
out.append("</p>\n");
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#openBlockquote(StringBuilder) */
|
||||
@Override
|
||||
public void openBlockquote(StringBuilder out)
|
||||
{
|
||||
out.append("<blockquote>");
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#closeBlockquote(StringBuilder) */
|
||||
@Override
|
||||
public void closeBlockquote(StringBuilder out)
|
||||
{
|
||||
out.append("</blockquote>\n");
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#openCodeBlock(StringBuilder) */
|
||||
@Override
|
||||
public void openCodeBlock(StringBuilder out)
|
||||
{
|
||||
out.append("<pre><code>");
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#closeCodeBlock(StringBuilder) */
|
||||
@Override
|
||||
public void closeCodeBlock(StringBuilder out)
|
||||
{
|
||||
out.append("</code></pre>\n");
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#openCodeSpan(StringBuilder) */
|
||||
@Override
|
||||
public void openCodeSpan(StringBuilder out)
|
||||
{
|
||||
out.append("<code>");
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#closeCodeSpan(StringBuilder) */
|
||||
@Override
|
||||
public void closeCodeSpan(StringBuilder out)
|
||||
{
|
||||
out.append("</code>");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see com.github.rjeschke.txtmark.Decorator#openHeadline(StringBuilder,
|
||||
* int)
|
||||
*/
|
||||
@Override
|
||||
public void openHeadline(StringBuilder out, int level)
|
||||
{
|
||||
out.append("<h");
|
||||
out.append(level);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see com.github.rjeschke.txtmark.Decorator#closeHeadline(StringBuilder,
|
||||
* int)
|
||||
*/
|
||||
@Override
|
||||
public void closeHeadline(StringBuilder out, int level)
|
||||
{
|
||||
out.append("</h");
|
||||
out.append(level);
|
||||
out.append(">\n");
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#openStrong(StringBuilder) */
|
||||
@Override
|
||||
public void openStrong(StringBuilder out)
|
||||
{
|
||||
out.append("<strong>");
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#closeStrong(StringBuilder) */
|
||||
@Override
|
||||
public void closeStrong(StringBuilder out)
|
||||
{
|
||||
out.append("</strong>");
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#openEmphasis(StringBuilder) */
|
||||
@Override
|
||||
public void openEmphasis(StringBuilder out)
|
||||
{
|
||||
out.append("<em>");
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#closeEmphasis(StringBuilder) */
|
||||
@Override
|
||||
public void closeEmphasis(StringBuilder out)
|
||||
{
|
||||
out.append("</em>");
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#openSuper(StringBuilder) */
|
||||
@Override
|
||||
public void openSuper(StringBuilder out)
|
||||
{
|
||||
out.append("<sup>");
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#closeSuper(StringBuilder) */
|
||||
@Override
|
||||
public void closeSuper(StringBuilder out)
|
||||
{
|
||||
out.append("</sup>");
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#openOrderedList(StringBuilder) */
|
||||
@Override
|
||||
public void openOrderedList(StringBuilder out)
|
||||
{
|
||||
out.append("<ol>\n");
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#closeOrderedList(StringBuilder) */
|
||||
@Override
|
||||
public void closeOrderedList(StringBuilder out)
|
||||
{
|
||||
out.append("</ol>\n");
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#openUnorderedList(StringBuilder) */
|
||||
@Override
|
||||
public void openUnorderedList(StringBuilder out)
|
||||
{
|
||||
out.append("<ul>\n");
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#closeUnorderedList(StringBuilder) */
|
||||
@Override
|
||||
public void closeUnorderedList(StringBuilder out)
|
||||
{
|
||||
out.append("</ul>\n");
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#openListItem(StringBuilder) */
|
||||
@Override
|
||||
public void openListItem(StringBuilder out)
|
||||
{
|
||||
out.append("<li");
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#closeListItem(StringBuilder) */
|
||||
@Override
|
||||
public void closeListItem(StringBuilder out)
|
||||
{
|
||||
out.append("</li>\n");
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#horizontalRuler(StringBuilder) */
|
||||
@Override
|
||||
public void horizontalRuler(StringBuilder out)
|
||||
{
|
||||
out.append("<hr />\n");
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#openLink(StringBuilder) */
|
||||
@Override
|
||||
public void openLink(StringBuilder out)
|
||||
{
|
||||
out.append("<a");
|
||||
}
|
||||
|
||||
/** @see com.github.rjeschke.txtmark.Decorator#openImage(StringBuilder) */
|
||||
@Override
|
||||
public void openImage(StringBuilder out)
|
||||
{
|
||||
out.append("<img");
|
||||
}
|
||||
}
|
||||
895
src/main/java/com/github/rjeschke/txtmark/Emitter.java
Normal file
895
src/main/java/com/github/rjeschke/txtmark/Emitter.java
Normal file
@ -0,0 +1,895 @@
|
||||
/*
|
||||
* Copyright (C) 2011 René Jeschke <rene_jeschke@yahoo.de>
|
||||
*
|
||||
* 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.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Emitter class responsible for generating HTML output.
|
||||
*
|
||||
* @author René Jeschke <rene_jeschke@yahoo.de>
|
||||
*/
|
||||
class Emitter
|
||||
{
|
||||
/** Link references. */
|
||||
private final HashMap<String, LinkRef> linkRefs = new HashMap<String, LinkRef>();
|
||||
/** The configuration. */
|
||||
private final Configuration config;
|
||||
/** Extension flag. */
|
||||
public boolean useExtensions = false;
|
||||
|
||||
/** Constructor. */
|
||||
public Emitter(final Configuration config)
|
||||
{
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a LinkRef to this set of LinkRefs.
|
||||
*
|
||||
* @param key The key/id.
|
||||
* @param linkRef The LinkRef.
|
||||
*/
|
||||
public void addLinkRef(final String key, final LinkRef linkRef)
|
||||
{
|
||||
this.linkRefs.put(key.toLowerCase(), linkRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the given block recursively into HTML.
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param root The Block to process.
|
||||
*/
|
||||
public void emit(final StringBuilder out, final Block root)
|
||||
{
|
||||
root.removeSurroundingEmptyLines();
|
||||
|
||||
switch(root.type)
|
||||
{
|
||||
case RULER:
|
||||
this.config.decorator.horizontalRuler(out);
|
||||
return;
|
||||
case NONE:
|
||||
case XML:
|
||||
break;
|
||||
case HEADLINE:
|
||||
this.config.decorator.openHeadline(out, root.hlDepth);
|
||||
if(this.useExtensions && root.id != null)
|
||||
{
|
||||
out.append(" id=\"");
|
||||
Utils.appendCode(out, root.id, 0, root.id.length());
|
||||
out.append('"');
|
||||
}
|
||||
out.append('>');
|
||||
break;
|
||||
case PARAGRAPH:
|
||||
this.config.decorator.openParagraph(out);
|
||||
break;
|
||||
case CODE:
|
||||
this.config.decorator.openCodeBlock(out);
|
||||
break;
|
||||
case BLOCKQUOTE:
|
||||
this.config.decorator.openBlockquote(out);
|
||||
break;
|
||||
case UNORDERED_LIST:
|
||||
this.config.decorator.openUnorderedList(out);
|
||||
break;
|
||||
case ORDERED_LIST:
|
||||
this.config.decorator.openOrderedList(out);
|
||||
break;
|
||||
case LIST_ITEM:
|
||||
this.config.decorator.openListItem(out);
|
||||
if(this.useExtensions && root.id != null)
|
||||
{
|
||||
out.append(" id=\"");
|
||||
Utils.appendCode(out, root.id, 0, root.id.length());
|
||||
out.append('"');
|
||||
}
|
||||
out.append('>');
|
||||
break;
|
||||
}
|
||||
|
||||
if(root.hasLines())
|
||||
{
|
||||
this.emitLines(out, root);
|
||||
}
|
||||
else
|
||||
{
|
||||
Block block = root.blocks;
|
||||
while(block != null)
|
||||
{
|
||||
this.emit(out, block);
|
||||
block = block.next;
|
||||
}
|
||||
}
|
||||
|
||||
switch(root.type)
|
||||
{
|
||||
case RULER:
|
||||
case NONE:
|
||||
case XML:
|
||||
break;
|
||||
case HEADLINE:
|
||||
this.config.decorator.closeHeadline(out, root.hlDepth);
|
||||
break;
|
||||
case PARAGRAPH:
|
||||
this.config.decorator.closeParagraph(out);
|
||||
break;
|
||||
case CODE:
|
||||
this.config.decorator.closeCodeBlock(out);
|
||||
break;
|
||||
case BLOCKQUOTE:
|
||||
this.config.decorator.closeBlockquote(out);
|
||||
break;
|
||||
case UNORDERED_LIST:
|
||||
this.config.decorator.closeUnorderedList(out);
|
||||
break;
|
||||
case ORDERED_LIST:
|
||||
this.config.decorator.closeOrderedList(out);
|
||||
break;
|
||||
case LIST_ITEM:
|
||||
this.config.decorator.closeListItem(out);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms lines into HTML.
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param block The Block to process.
|
||||
*/
|
||||
private void emitLines(final StringBuilder out, final Block block)
|
||||
{
|
||||
switch(block.type)
|
||||
{
|
||||
case CODE:
|
||||
this.emitCodeLines(out, block.lines);
|
||||
break;
|
||||
case XML:
|
||||
this.emitRawLines(out, block.lines);
|
||||
break;
|
||||
default:
|
||||
this.emitMarkedLines(out, block.lines);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the position of the given Token in the given String.
|
||||
*
|
||||
* @param in The String to search on.
|
||||
* @param start The starting character position.
|
||||
* @param token The token to find.
|
||||
* @return The position of the token or -1 if none could be found.
|
||||
*/
|
||||
private int findToken(final String in, int start, MarkToken token)
|
||||
{
|
||||
int pos = start;
|
||||
while(pos < in.length())
|
||||
{
|
||||
if(this.getToken(in, pos) == token)
|
||||
return pos;
|
||||
pos++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there is a valid markdown link definition.
|
||||
*
|
||||
* @param out The StringBuilder containing the generated output.
|
||||
* @param in Input String.
|
||||
* @param start Starting position.
|
||||
* @param token Either LINK or IMAGE.
|
||||
* @return The new position or -1 if there is no valid markdown link.
|
||||
*/
|
||||
private int checkLink(final StringBuilder out, final String in, int start, MarkToken token)
|
||||
{
|
||||
boolean isAbbrev = false;
|
||||
int pos = start + (token == MarkToken.LINK ? 1 : 2);
|
||||
final StringBuilder temp = new StringBuilder();
|
||||
|
||||
temp.setLength(0);
|
||||
pos = Utils.readMdLinkId(temp, in, pos);
|
||||
if(pos < start)
|
||||
return -1;
|
||||
|
||||
String name = temp.toString(), link = null, comment = null;
|
||||
final int oldPos = pos++;
|
||||
pos = Utils.skipSpaces(in, pos);
|
||||
if(pos < start)
|
||||
{
|
||||
final LinkRef lr = this.linkRefs.get(name.toLowerCase());
|
||||
if(lr != null)
|
||||
{
|
||||
isAbbrev = lr.isAbbrev;
|
||||
link = lr.link;
|
||||
comment = lr.title;
|
||||
pos = oldPos;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else if(in.charAt(pos) == '(')
|
||||
{
|
||||
pos++;
|
||||
pos = Utils.skipSpaces(in, pos);
|
||||
if(pos < start)
|
||||
return -1;
|
||||
temp.setLength(0);
|
||||
boolean useLt = in.charAt(pos) == '<';
|
||||
pos = useLt ? Utils.readUntil(temp, in, pos + 1, '>') : Utils.readMdLink(temp, in, pos);
|
||||
if(pos < start)
|
||||
return -1;
|
||||
if(useLt)
|
||||
pos++;
|
||||
link = temp.toString();
|
||||
|
||||
if(in.charAt(pos) == ' ')
|
||||
{
|
||||
pos = Utils.skipSpaces(in, pos);
|
||||
if(pos > start && in.charAt(pos) == '"')
|
||||
{
|
||||
pos++;
|
||||
temp.setLength(0);
|
||||
pos = Utils.readUntil(temp, in, pos, '"');
|
||||
if(pos < start)
|
||||
return -1;
|
||||
comment = temp.toString();
|
||||
pos++;
|
||||
pos = Utils.skipSpaces(in, pos);
|
||||
if(pos == -1)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if(in.charAt(pos) != ')')
|
||||
return -1;
|
||||
}
|
||||
else if(in.charAt(pos) == '[')
|
||||
{
|
||||
pos++;
|
||||
temp.setLength(0);
|
||||
pos = Utils.readRawUntil(temp, in, pos, ']');
|
||||
if(pos < start)
|
||||
return -1;
|
||||
final String id = temp.length() > 0 ? temp.toString() : name;
|
||||
final LinkRef lr = this.linkRefs.get(id.toLowerCase());
|
||||
if(lr != null)
|
||||
{
|
||||
link = lr.link;
|
||||
comment = lr.title;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
final LinkRef lr = this.linkRefs.get(name.toLowerCase());
|
||||
if(lr != null)
|
||||
{
|
||||
isAbbrev = lr.isAbbrev;
|
||||
link = lr.link;
|
||||
comment = lr.title;
|
||||
pos = oldPos;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if(link == null)
|
||||
return -1;
|
||||
|
||||
if(token == MarkToken.LINK)
|
||||
{
|
||||
if(isAbbrev && comment != null)
|
||||
{
|
||||
if(!this.useExtensions)
|
||||
return -1;
|
||||
out.append("<abbr title=\"");
|
||||
Utils.appendValue(out, comment, 0, comment.length());
|
||||
out.append("\">");
|
||||
this.recursiveEmitLine(out, name, 0, MarkToken.NONE);
|
||||
out.append("</abbr>");
|
||||
}
|
||||
else
|
||||
{
|
||||
this.config.decorator.openLink(out);
|
||||
out.append(" href=\"");
|
||||
Utils.appendValue(out, link, 0, link.length());
|
||||
out.append('"');
|
||||
if(comment != null)
|
||||
{
|
||||
out.append(" title=\"");
|
||||
Utils.appendValue(out, comment, 0, comment.length());
|
||||
out.append('"');
|
||||
}
|
||||
out.append('>');
|
||||
this.recursiveEmitLine(out, name, 0, MarkToken.NONE);
|
||||
out.append("</a>");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.config.decorator.openImage(out);
|
||||
out.append(" src=\"");
|
||||
Utils.appendValue(out, link, 0, link.length());
|
||||
out.append("\" alt=\"");
|
||||
Utils.appendValue(out, name, 0, name.length());
|
||||
out.append('"');
|
||||
if(comment != null)
|
||||
{
|
||||
out.append(" title=\"");
|
||||
Utils.appendValue(out, comment, 0, comment.length());
|
||||
out.append('"');
|
||||
}
|
||||
out.append(" />");
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is a valid HTML tag here.
|
||||
* This method also transforms auto links and mailto auto links.
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param in Input String.
|
||||
* @param start Starting position.
|
||||
* @return The new position or -1 if nothing valid has been found.
|
||||
*/
|
||||
private int checkHtml(final StringBuilder out, final String in, int start)
|
||||
{
|
||||
final StringBuilder temp = new StringBuilder();
|
||||
int pos;
|
||||
|
||||
// Check for auto links
|
||||
temp.setLength(0);
|
||||
pos = Utils.readUntil(temp, in, start + 1, ':', ' ', '>', '\n');
|
||||
if(pos != -1 && in.charAt(pos) == ':' && HTML.isLinkPrefix(temp.toString()))
|
||||
{
|
||||
pos = Utils.readUntil(temp, in, pos, '>');
|
||||
if(pos != -1)
|
||||
{
|
||||
final String link = temp.toString();
|
||||
this.config.decorator.openLink(out);
|
||||
out.append(" href=\"");
|
||||
Utils.appendValue(out, link, 0, link.length());
|
||||
out.append("\">");
|
||||
Utils.appendValue(out, link, 0, link.length());
|
||||
out.append("</a>");
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for mailto auto link
|
||||
temp.setLength(0);
|
||||
pos = Utils.readUntil(temp, in, start + 1, '@', ' ', '>', '\n');
|
||||
if(pos != -1 && in.charAt(pos) == '@')
|
||||
{
|
||||
pos = Utils.readUntil(temp, in, pos, '>');
|
||||
if(pos != -1)
|
||||
{
|
||||
final String link = temp.toString();
|
||||
this.config.decorator.openLink(out);
|
||||
out.append(" href=\"");
|
||||
Utils.appendMailto(out, "mailto:", 0, 7);
|
||||
Utils.appendMailto(out, link, 0, link.length());
|
||||
out.append("\">");
|
||||
Utils.appendMailto(out, link, 0, link.length());
|
||||
out.append("</a>");
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for inline html
|
||||
if(start + 2 < in.length())
|
||||
{
|
||||
temp.setLength(0);
|
||||
return Utils.readXML(out, in, start, this.config.safeMode);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a valid XML/HTML entity.
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param in Input String.
|
||||
* @param start Starting position
|
||||
* @return The new position or -1 if this entity in invalid.
|
||||
*/
|
||||
private int checkEntity(final StringBuilder out, final String in, int start)
|
||||
{
|
||||
int pos = Utils.readUntil(out, in, start, ';');
|
||||
if(pos < 0 || out.length() < 3)
|
||||
return -1;
|
||||
if(out.charAt(1) == '#')
|
||||
{
|
||||
if(out.charAt(2) == 'x' || out.charAt(2) == 'X')
|
||||
{
|
||||
if(out.length() < 4)
|
||||
return -1;
|
||||
for(int i = 3; i < out.length(); i++)
|
||||
{
|
||||
final char c = out.charAt(i);
|
||||
if((c < '0' || c > '9') && ((c < 'a' || c > 'f') && (c < 'A' || c > 'F')))
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int i = 2; i < out.length(); i++)
|
||||
{
|
||||
final char c = out.charAt(i);
|
||||
if(c < '0' || c > '9')
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
out.append(';');
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int i = 1; i < out.length(); i++)
|
||||
{
|
||||
final char c = out.charAt(i);
|
||||
if((c < 'a' || c > 'z') && (c < 'A' || c > 'Z'))
|
||||
return -1;
|
||||
}
|
||||
out.append(';');
|
||||
return HTML.isEntity(out.toString()) ? pos : -1;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively scans through the given line, taking care of any markdown stuff.
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param in Input String.
|
||||
* @param start Start position.
|
||||
* @param token The matching Token (for e.g. '*')
|
||||
* @return The position of the matching Token or -1 if token was NONE or no Token could be found.
|
||||
*/
|
||||
private int recursiveEmitLine(final StringBuilder out, final String in, int start, MarkToken token)
|
||||
{
|
||||
int pos = start, a, b;
|
||||
final StringBuilder temp = new StringBuilder();
|
||||
while(pos < in.length())
|
||||
{
|
||||
final MarkToken mt = this.getToken(in, pos);
|
||||
if(token != MarkToken.NONE && (mt == token || token == MarkToken.EM_STAR && mt == MarkToken.STRONG_STAR || token == MarkToken.EM_UNDERSCORE && mt == MarkToken.STRONG_UNDERSCORE))
|
||||
return pos;
|
||||
|
||||
switch(mt)
|
||||
{
|
||||
case IMAGE:
|
||||
case LINK:
|
||||
temp.setLength(0);
|
||||
b = this.checkLink(temp, in, pos, mt);
|
||||
if(b > 0)
|
||||
{
|
||||
out.append(temp);
|
||||
pos = b;
|
||||
}
|
||||
else
|
||||
{
|
||||
out.append(in.charAt(pos));
|
||||
}
|
||||
break;
|
||||
case EM_STAR:
|
||||
case EM_UNDERSCORE:
|
||||
temp.setLength(0);
|
||||
b = this.recursiveEmitLine(temp, in, pos + 1, mt);
|
||||
if(b > 0)
|
||||
{
|
||||
this.config.decorator.openEmphasis(out);
|
||||
out.append(temp);
|
||||
this.config.decorator.closeEmphasis(out);
|
||||
pos = b;
|
||||
}
|
||||
else
|
||||
{
|
||||
out.append(in.charAt(pos));
|
||||
}
|
||||
break;
|
||||
case STRONG_STAR:
|
||||
case STRONG_UNDERSCORE:
|
||||
temp.setLength(0);
|
||||
b = this.recursiveEmitLine(temp, in, pos + 2, mt);
|
||||
if(b > 0)
|
||||
{
|
||||
this.config.decorator.openStrong(out);
|
||||
out.append(temp);
|
||||
this.config.decorator.closeStrong(out);
|
||||
pos = b + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
out.append(in.charAt(pos));
|
||||
}
|
||||
break;
|
||||
case SUPER:
|
||||
temp.setLength(0);
|
||||
b = this.recursiveEmitLine(temp, in, pos + 1, mt);
|
||||
if(b > 0)
|
||||
{
|
||||
this.config.decorator.openSuper(out);
|
||||
out.append(temp);
|
||||
this.config.decorator.closeSuper(out);
|
||||
pos = b;
|
||||
}
|
||||
else
|
||||
{
|
||||
out.append(in.charAt(pos));
|
||||
}
|
||||
break;
|
||||
case CODE_SINGLE:
|
||||
case CODE_DOUBLE:
|
||||
a = pos + (mt == MarkToken.CODE_DOUBLE ? 2 : 1);
|
||||
b = this.findToken(in, a, mt);
|
||||
if(b > 0)
|
||||
{
|
||||
pos = b + (mt == MarkToken.CODE_DOUBLE ? 1 : 0);
|
||||
while(a < b && in.charAt(a) == ' ')
|
||||
a++;
|
||||
if(a < b)
|
||||
{
|
||||
while(in.charAt(b - 1) == ' ')
|
||||
b--;
|
||||
this.config.decorator.openCodeSpan(out);
|
||||
Utils.appendCode(out, in, a, b);
|
||||
this.config.decorator.closeCodeSpan(out);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
out.append(in.charAt(pos));
|
||||
}
|
||||
break;
|
||||
case HTML:
|
||||
temp.setLength(0);
|
||||
b = this.checkHtml(temp, in, pos);
|
||||
if(b > 0)
|
||||
{
|
||||
out.append(temp);
|
||||
pos = b;
|
||||
}
|
||||
else
|
||||
{
|
||||
out.append("<");
|
||||
}
|
||||
break;
|
||||
case ENTITY:
|
||||
temp.setLength(0);
|
||||
b = this.checkEntity(temp, in, pos);
|
||||
if(b > 0)
|
||||
{
|
||||
out.append(temp);
|
||||
pos = b;
|
||||
}
|
||||
else
|
||||
{
|
||||
out.append("&");
|
||||
}
|
||||
break;
|
||||
case X_COPY:
|
||||
out.append("©");
|
||||
pos += 2;
|
||||
break;
|
||||
case X_REG:
|
||||
out.append("®");
|
||||
pos += 2;
|
||||
break;
|
||||
case X_TRADE:
|
||||
out.append("™");
|
||||
pos += 3;
|
||||
break;
|
||||
case X_NDASH:
|
||||
out.append("–");
|
||||
pos++;
|
||||
break;
|
||||
case X_MDASH:
|
||||
out.append("—");
|
||||
pos += 2;
|
||||
break;
|
||||
case X_HELLIP:
|
||||
out.append("…");
|
||||
pos += 2;
|
||||
break;
|
||||
case X_LAQUO:
|
||||
out.append("«");
|
||||
pos++;
|
||||
break;
|
||||
case X_RAQUO:
|
||||
out.append("»");
|
||||
pos++;
|
||||
break;
|
||||
case X_RDQUO:
|
||||
out.append("”");
|
||||
break;
|
||||
case X_LDQUO:
|
||||
out.append("“");
|
||||
break;
|
||||
case ESCAPE:
|
||||
pos++;
|
||||
//$FALL-THROUGH$
|
||||
default:
|
||||
out.append(in.charAt(pos));
|
||||
break;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is any markdown Token.
|
||||
*
|
||||
* @param in Input String.
|
||||
* @param pos Starting position.
|
||||
* @return The Token.
|
||||
*/
|
||||
private MarkToken getToken(final String in, final int pos)
|
||||
{
|
||||
final char c0 = pos > 0 ? in.charAt(pos - 1) : ' ';
|
||||
final char c = in.charAt(pos);
|
||||
final char c1 = pos + 1 < in.length() ? in.charAt(pos + 1) : ' ';
|
||||
final char c2 = pos + 2 < in.length() ? in.charAt(pos + 2) : ' ';
|
||||
final char c3 = pos + 3 < in.length() ? in.charAt(pos + 3) : ' ';
|
||||
|
||||
switch(c)
|
||||
{
|
||||
case '*':
|
||||
if(c1 == '*')
|
||||
{
|
||||
return c0 != ' ' || c2 != ' ' ? MarkToken.STRONG_STAR : MarkToken.EM_STAR;
|
||||
}
|
||||
return c0 != ' ' || c1 != ' ' ? MarkToken.EM_STAR : MarkToken.NONE;
|
||||
case '_':
|
||||
if(c1 == '_')
|
||||
{
|
||||
return c0 != ' ' || c2 != ' ' ? MarkToken.STRONG_UNDERSCORE : MarkToken.EM_UNDERSCORE;
|
||||
}
|
||||
if(this.useExtensions)
|
||||
{
|
||||
return c0 != ' ' && c0 != '_' && c1 != ' ' ? MarkToken.NONE : MarkToken.EM_UNDERSCORE;
|
||||
}
|
||||
return c0 != ' ' || c1 != ' ' ? MarkToken.EM_UNDERSCORE : MarkToken.NONE;
|
||||
case '!':
|
||||
if(c1 == '[')
|
||||
return MarkToken.IMAGE;
|
||||
return MarkToken.NONE;
|
||||
case '[':
|
||||
return MarkToken.LINK;
|
||||
case '`':
|
||||
return c1 == '`' ? MarkToken.CODE_DOUBLE : MarkToken.CODE_SINGLE;
|
||||
case '\\':
|
||||
switch(c1)
|
||||
{
|
||||
case '\\':
|
||||
case '[':
|
||||
case ']':
|
||||
case '(':
|
||||
case ')':
|
||||
case '{':
|
||||
case '}':
|
||||
case '#':
|
||||
case '"':
|
||||
case '\'':
|
||||
case '.':
|
||||
case '>':
|
||||
case '<':
|
||||
case '*':
|
||||
case '+':
|
||||
case '-':
|
||||
case '_':
|
||||
case '!':
|
||||
case '`':
|
||||
case '^':
|
||||
return MarkToken.ESCAPE;
|
||||
default:
|
||||
return MarkToken.NONE;
|
||||
}
|
||||
case '<':
|
||||
if(this.useExtensions && c1 == '<')
|
||||
return MarkToken.X_LAQUO;
|
||||
return MarkToken.HTML;
|
||||
case '&':
|
||||
return MarkToken.ENTITY;
|
||||
default:
|
||||
if(this.useExtensions)
|
||||
{
|
||||
switch(c)
|
||||
{
|
||||
case '-':
|
||||
if(c1 == '-')
|
||||
return c2 == '-' ? MarkToken.X_MDASH : MarkToken.X_NDASH;
|
||||
break;
|
||||
case '^':
|
||||
return c0 == '^' || c1 == '^' ? MarkToken.NONE : MarkToken.SUPER;
|
||||
case '>':
|
||||
if(c1 == '>')
|
||||
return MarkToken.X_RAQUO;
|
||||
break;
|
||||
case '.':
|
||||
if(c1 == '.' && c2 == '.')
|
||||
return MarkToken.X_HELLIP;
|
||||
break;
|
||||
case '(':
|
||||
if(c1 == 'C' && c2 == ')')
|
||||
return MarkToken.X_COPY;
|
||||
if(c1 == 'R' && c2 == ')')
|
||||
return MarkToken.X_REG;
|
||||
if(c1 == 'T' & c2 == 'M' & c3 == ')')
|
||||
return MarkToken.X_TRADE;
|
||||
break;
|
||||
case '"':
|
||||
if(!Character.isLetterOrDigit(c0) && c1 != ' ')
|
||||
return MarkToken.X_LDQUO;
|
||||
if(c0 != ' ' && !Character.isLetterOrDigit(c1))
|
||||
return MarkToken.X_RDQUO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return MarkToken.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a set of markdown lines into the StringBuilder.
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param lines The lines to write.
|
||||
*/
|
||||
private void emitMarkedLines(final StringBuilder out, final Line lines)
|
||||
{
|
||||
final StringBuilder in = new StringBuilder();
|
||||
Line line = lines;
|
||||
while(line != null)
|
||||
{
|
||||
if(!line.isEmpty)
|
||||
{
|
||||
in.append(line.value.substring(line.leading, line.value.length() - line.trailing));
|
||||
if(line.trailing >= 2)
|
||||
in.append("<br />");
|
||||
}
|
||||
if(line.next != null)
|
||||
in.append('\n');
|
||||
line = line.next;
|
||||
}
|
||||
|
||||
this.recursiveEmitLine(out, in.toString(), 0, MarkToken.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a set of raw lines into the StringBuilder.
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param lines The lines to write.
|
||||
*/
|
||||
private void emitRawLines(final StringBuilder out, final Line lines)
|
||||
{
|
||||
Line line = lines;
|
||||
if(this.config.safeMode)
|
||||
{
|
||||
final StringBuilder temp = new StringBuilder();
|
||||
while(line != null)
|
||||
{
|
||||
if(!line.isEmpty)
|
||||
{
|
||||
temp.append(line.value);
|
||||
}
|
||||
temp.append('\n');
|
||||
line = line.next;
|
||||
}
|
||||
final String in = temp.toString();
|
||||
for(int pos = 0; pos < in.length(); pos++)
|
||||
{
|
||||
if(in.charAt(pos) == '<')
|
||||
{
|
||||
temp.setLength(0);
|
||||
final int t = Utils.readXML(temp, in, pos, this.config.safeMode);
|
||||
if(t != -1)
|
||||
{
|
||||
out.append(temp);
|
||||
pos = t;
|
||||
}
|
||||
else
|
||||
{
|
||||
out.append(in.charAt(pos));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
out.append(in.charAt(pos));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while(line != null)
|
||||
{
|
||||
if(!line.isEmpty)
|
||||
{
|
||||
out.append(line.value);
|
||||
}
|
||||
out.append('\n');
|
||||
line = line.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a code block into the StringBuilder.
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param lines The lines to write.
|
||||
*/
|
||||
private void emitCodeLines(final StringBuilder out, final Line lines)
|
||||
{
|
||||
Line line = lines;
|
||||
if(this.config.codeBlockEmitter != null)
|
||||
{
|
||||
final ArrayList<String> list = new ArrayList<String>();
|
||||
while(line != null)
|
||||
{
|
||||
if(line.isEmpty)
|
||||
list.add("");
|
||||
else
|
||||
list.add(line.value.substring(4));
|
||||
line = line.next;
|
||||
}
|
||||
this.config.codeBlockEmitter.emitBlock(out, list);
|
||||
}
|
||||
else
|
||||
{
|
||||
while(line != null)
|
||||
{
|
||||
if(!line.isEmpty)
|
||||
{
|
||||
for(int i = 4; i < line.value.length(); i++)
|
||||
{
|
||||
final char c;
|
||||
switch(c = line.value.charAt(i))
|
||||
{
|
||||
case '&':
|
||||
out.append("&");
|
||||
break;
|
||||
case '<':
|
||||
out.append("<");
|
||||
break;
|
||||
case '>':
|
||||
out.append(">");
|
||||
break;
|
||||
default:
|
||||
out.append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
out.append('\n');
|
||||
line = line.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
220
src/main/java/com/github/rjeschke/txtmark/HTML.java
Normal file
220
src/main/java/com/github/rjeschke/txtmark/HTML.java
Normal file
@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright (C) 2011 René Jeschke <rene_jeschke@yahoo.de>
|
||||
*
|
||||
* 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.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* HTML utility class.
|
||||
*
|
||||
* @author René Jeschke <rene_jeschke@yahoo.de>
|
||||
*/
|
||||
class HTML
|
||||
{
|
||||
/** List of valid HTML/XML entity names. */
|
||||
private final static String[] ENTITY_NAMES = {
|
||||
"Â", "â", "´", "Æ", "æ", "À", "à", "ℵ",
|
||||
"Α", "α", "&", "∧", "∠", "'", "Å", "å",
|
||||
"≈", "Ã", "ã", "Ä", "ä", "„", "Β", "β",
|
||||
"¦", "•", "∩", "Ç", "ç", "¸", "¢", "Χ",
|
||||
"χ", "ˆ", "♣", "≅", "©", "↵", "∪", "¤",
|
||||
"‡", "†", "⇓", "↓", "°", "Δ", "δ", "♦",
|
||||
"÷", "É", "é", "Ê", "ê", "È", "è", "∅",
|
||||
" ", " ", "Ε", "ε", "≡", "Η", "η", "Ð",
|
||||
"ð", "Ë", "ë", "€", "∃", "ƒ", "∀", "½",
|
||||
"¼", "¾", "⁄", "Γ", "γ", "≥", ">", "⇔",
|
||||
"↔", "♥", "…", "Í", "í", "Î", "î", "¡",
|
||||
"Ì", "ì", "ℑ", "∞", "∫", "Ι", "ι", "¿",
|
||||
"∈", "Ï", "ï", "Κ", "κ", "Λ", "λ", "⟨",
|
||||
"«", "⇐", "←", "⌈", "“", "≤", "⌊", "∗",
|
||||
"◊", "‎", "‹", "‘", "<", "¯", "—", "µ",
|
||||
"·", "−", "Μ", "μ", "∇", " ", "–", "≠",
|
||||
"∋", "¬", "∉", "⊄", "Ñ", "ñ", "Ν", "ν",
|
||||
"Ó", "ó", "Ô", "ô", "Œ", "œ", "Ò", "ò",
|
||||
"‾", "Ω", "ω", "Ο", "ο", "⊕", "∨", "ª",
|
||||
"º", "Ø", "ø", "Õ", "õ", "⊗", "Ö", "ö",
|
||||
"¶", "∂", "‰", "⊥", "Φ", "φ", "Π", "π",
|
||||
"ϖ", "±", "£", "″", "′", "∏", "∝", "Ψ",
|
||||
"ψ", """, "√", "⟩", "»", "⇒", "→", "⌉",
|
||||
"”", "ℜ", "®", "⌋", "Ρ", "ρ", "‏", "›",
|
||||
"’", "‚", "Š", "š", "⋅", "§", "­", "Σ",
|
||||
"σ", "ς", "∼", "♠", "⊂", "⊆", "∑", "⊃",
|
||||
"¹", "²", "³", "⊇", "ß", "Τ", "τ", "∴",
|
||||
"Θ", "θ", "ϑ", " ", "þ", "˜", "×", "™",
|
||||
"Ú", "ú", "⇑", "↑", "Û", "û", "Ù", "ù",
|
||||
"¨", "ϒ", "Υ", "υ", "Ü", "ü", "℘", "Ξ",
|
||||
"ξ", "Ý", "ý", "¥", "Ÿ", "ÿ", "Ζ", "ζ",
|
||||
"‍", "‌"
|
||||
};
|
||||
/** Characters corresponding to ENTITY_NAMES. */
|
||||
private final static char[] ENTITY_CHARS = {
|
||||
'\u00C2', '\u00E2', '\u00B4', '\u00C6', '\u00E6', '\u00C0', '\u00E0', '\u2135',
|
||||
'\u0391', '\u03B1', '\u0026', '\u2227', '\u2220', '\'', '\u00C5', '\u00E5',
|
||||
'\u2248', '\u00C3', '\u00E3', '\u00C4', '\u00E4', '\u201E', '\u0392', '\u03B2',
|
||||
'\u00A6', '\u2022', '\u2229', '\u00C7', '\u00E7', '\u00B8', '\u00A2', '\u03A7',
|
||||
'\u03C7', '\u02C6', '\u2663', '\u2245', '\u00A9', '\u21B5', '\u222A', '\u00A4',
|
||||
'\u2021', '\u2020', '\u21D3', '\u2193', '\u00B0', '\u0394', '\u03B4', '\u2666',
|
||||
'\u00F7', '\u00C9', '\u00E9', '\u00CA', '\u00EA', '\u00C8', '\u00E8', '\u2205',
|
||||
'\u2003', '\u2002', '\u0395', '\u03B5', '\u2261', '\u0397', '\u03B7', '\u00D0',
|
||||
'\u00F0', '\u00CB', '\u00EB', '\u20AC', '\u2203', '\u0192', '\u2200', '\u00BD',
|
||||
'\u00BC', '\u00BE', '\u2044', '\u0393', '\u03B3', '\u2265', '\u003E', '\u21D4',
|
||||
'\u2194', '\u2665', '\u2026', '\u00CD', '\u00ED', '\u00CE', '\u00EE', '\u00A1',
|
||||
'\u00CC', '\u00EC', '\u2111', '\u221E', '\u222B', '\u0399', '\u03B9', '\u00BF',
|
||||
'\u2208', '\u00CF', '\u00EF', '\u039A', '\u03BA', '\u039B', '\u03BB', '\u2329',
|
||||
'\u00AB', '\u21D0', '\u2190', '\u2308', '\u201C', '\u2264', '\u230A', '\u2217',
|
||||
'\u25CA', '\u200E', '\u2039', '\u2018', '\u003C', '\u00AF', '\u2014', '\u00B5',
|
||||
'\u00B7', '\u2212', '\u039C', '\u03BC', '\u2207', '\u00A0', '\u2013', '\u2260',
|
||||
'\u220B', '\u00AC', '\u2209', '\u2284', '\u00D1', '\u00F1', '\u039D', '\u03BD',
|
||||
'\u00D3', '\u00F3', '\u00D4', '\u00F4', '\u0152', '\u0153', '\u00D2', '\u00F2',
|
||||
'\u203E', '\u03A9', '\u03C9', '\u039F', '\u03BF', '\u2295', '\u2228', '\u00AA',
|
||||
'\u00BA', '\u00D8', '\u00F8', '\u00D5', '\u00F5', '\u2297', '\u00D6', '\u00F6',
|
||||
'\u00B6', '\u2202', '\u2030', '\u22A5', '\u03A6', '\u03C6', '\u03A0', '\u03C0',
|
||||
'\u03D6', '\u00B1', '\u00A3', '\u2033', '\u2032', '\u220F', '\u221D', '\u03A8',
|
||||
'\u03C8', '\u0022', '\u221A', '\u232A', '\u00BB', '\u21D2', '\u2192', '\u2309',
|
||||
'\u201D', '\u211C', '\u00AE', '\u230B', '\u03A1', '\u03C1', '\u200F', '\u203A',
|
||||
'\u2019', '\u201A', '\u0160', '\u0161', '\u22C5', '\u00A7', '\u00AD', '\u03A3',
|
||||
'\u03C3', '\u03C2', '\u223C', '\u2660', '\u2282', '\u2286', '\u2211', '\u2283',
|
||||
'\u00B9', '\u00B2', '\u00B3', '\u2287', '\u00DF', '\u03A4', '\u03C4', '\u2234',
|
||||
'\u0398', '\u03B8', '\u03D1', '\u00DE', '\u00FE', '\u02DC', '\u00D7', '\u2122',
|
||||
'\u00DA', '\u00FA', '\u21D1', '\u2191', '\u00DB', '\u00FB', '\u00D9', '\u00F9',
|
||||
'\u00A8', '\u03D2', '\u03A5', '\u03C5', '\u00DC', '\u00FC', '\u2118', '\u039E',
|
||||
'\u03BE', '\u00DD', '\u00FD', '\u00A5', '\u0178', '\u00FF', '\u0396', '\u03B6',
|
||||
'\u200D', '\u200C'
|
||||
};
|
||||
/** Valid markdown link prefixes for auto links. */
|
||||
private final static String[] LINK_PREFIXES = {
|
||||
"http", "https",
|
||||
"ftp", "ftps"
|
||||
};
|
||||
|
||||
/** HTML block level elements. */
|
||||
private final static HTMLElement[] BLOCK_ELEMENTS = {
|
||||
HTMLElement.address,
|
||||
HTMLElement.blockquote,
|
||||
HTMLElement.del, HTMLElement.div, HTMLElement.dl,
|
||||
HTMLElement.fieldset, HTMLElement.form,
|
||||
HTMLElement.h1, HTMLElement.h2, HTMLElement.h3, HTMLElement.h4, HTMLElement.h5, HTMLElement.h6, HTMLElement.hr,
|
||||
HTMLElement.ins,
|
||||
HTMLElement.noscript,
|
||||
HTMLElement.ol,
|
||||
HTMLElement.p, HTMLElement.pre,
|
||||
HTMLElement.table,
|
||||
HTMLElement.ul
|
||||
};
|
||||
|
||||
/** HTML unsafe elements. */
|
||||
private final static HTMLElement[] UNSAFE_ELEMENTS = {
|
||||
HTMLElement.applet,
|
||||
HTMLElement.head,
|
||||
HTMLElement.html,
|
||||
HTMLElement.body,
|
||||
HTMLElement.frame,
|
||||
HTMLElement.frameset,
|
||||
HTMLElement.iframe,
|
||||
HTMLElement.script,
|
||||
HTMLElement.object,
|
||||
};
|
||||
|
||||
/** Character to entity encoding map. */
|
||||
private final static HashMap<Character, String> encodeMap = new HashMap<Character, String>();
|
||||
/** Entity to character decoding map. */
|
||||
private final static HashMap<String, Character> decodeMap = new HashMap<String, Character>();
|
||||
/** Set of valid HTML tags. */
|
||||
private final static HashSet<String> HTML_ELEMENTS = new HashSet<String>();
|
||||
/** Set of unsafe HTML tags. */
|
||||
private final static HashSet<String> HTML_UNSAFE = new HashSet<String>();
|
||||
/** Set of HTML block level tags. */
|
||||
private final static HashSet<String> HTML_BLOCK_ELEMENTS = new HashSet<String>();
|
||||
/** Set of valid markdown link prefixes. */
|
||||
private final static HashSet<String> LINK_PREFIX = new HashSet<String>();
|
||||
|
||||
static
|
||||
{
|
||||
for(final HTMLElement h : HTMLElement.values())
|
||||
{
|
||||
HTML_ELEMENTS.add(h.toString());
|
||||
}
|
||||
for(final HTMLElement h : UNSAFE_ELEMENTS)
|
||||
{
|
||||
HTML_UNSAFE.add(h.toString());
|
||||
}
|
||||
for(final HTMLElement h : BLOCK_ELEMENTS)
|
||||
{
|
||||
HTML_BLOCK_ELEMENTS.add(h.toString());
|
||||
}
|
||||
for(int i = 0; i < ENTITY_NAMES.length; i++)
|
||||
{
|
||||
encodeMap.put(ENTITY_CHARS[i], ENTITY_NAMES[i]);
|
||||
decodeMap.put(ENTITY_NAMES[i], ENTITY_CHARS[i]);
|
||||
}
|
||||
for(int i = 0; i < LINK_PREFIXES.length; i++)
|
||||
{
|
||||
LINK_PREFIX.add(LINK_PREFIXES[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/** Constructor. (Singleton) */
|
||||
private HTML()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value String to check.
|
||||
* @return Returns <code>true</code> if the given String is a link prefix.
|
||||
*/
|
||||
public static boolean isLinkPrefix(final String value)
|
||||
{
|
||||
return LINK_PREFIX.contains(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value String to check.
|
||||
* @return Returns <code>true</code> if the given String is an entity.
|
||||
*/
|
||||
public static boolean isEntity(final String value)
|
||||
{
|
||||
return decodeMap.containsKey(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value String to check.
|
||||
* @return Returns <code>true</code> if the given String is a HTML tag.
|
||||
*/
|
||||
public static boolean isHtmlElement(final String value)
|
||||
{
|
||||
return HTML_ELEMENTS.contains(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value String to check.
|
||||
* @return Returns <code>true</code> if the given String is a HTML block level tag.
|
||||
*/
|
||||
public static boolean isHtmlBlockElement(final String value)
|
||||
{
|
||||
return HTML_BLOCK_ELEMENTS.contains(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value String to check.
|
||||
* @return Returns <code>true</code> if the given String is an unsafe HTML tag.
|
||||
*/
|
||||
public static boolean isUnsafeHtmlElement(final String value)
|
||||
{
|
||||
return HTML_UNSAFE.contains(value);
|
||||
}
|
||||
}
|
||||
45
src/main/java/com/github/rjeschke/txtmark/HTMLElement.java
Normal file
45
src/main/java/com/github/rjeschke/txtmark/HTMLElement.java
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2011 René Jeschke <rene_jeschke@yahoo.de>
|
||||
*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Enum of HTML tags.
|
||||
*
|
||||
* @author René Jeschke <rene_jeschke@yahoo.de>
|
||||
*/
|
||||
enum HTMLElement
|
||||
{
|
||||
NONE,
|
||||
a, abbr, acronym, address, applet, area,
|
||||
b, base, basefont, bdo, big, blockquote, body, br, button,
|
||||
caption, cite, code, col, colgroup,
|
||||
dd, del, dfn, div, dl, dt,
|
||||
em,
|
||||
fieldset, font, form, frame, frameset,
|
||||
h1, h2, h3, h4, h5, h6, head, hr, html,
|
||||
i, iframe, img, input, ins,
|
||||
kbd,
|
||||
label, legend, li, link,
|
||||
map, meta,
|
||||
noscript,
|
||||
object, ol, optgroup, option,
|
||||
p, param, pre,
|
||||
q,
|
||||
s, samp, script, select, small, span, strike, strong, style, sub, sup,
|
||||
table, tbody, td, textarea, tfoot, th, thead, title, tr, tt,
|
||||
u, ul,
|
||||
var
|
||||
}
|
||||
492
src/main/java/com/github/rjeschke/txtmark/Line.java
Normal file
492
src/main/java/com/github/rjeschke/txtmark/Line.java
Normal file
@ -0,0 +1,492 @@
|
||||
/*
|
||||
* Copyright (C) 2011 René Jeschke <rene_jeschke@yahoo.de>
|
||||
*
|
||||
* 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.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;
|
||||
/** Leading and trailing spaces. */
|
||||
public int leading = 0, trailing = 0;
|
||||
/** Is this line empty? */
|
||||
public boolean isEmpty = true;
|
||||
/** This line's value. */
|
||||
public String value = null;
|
||||
/** Previous and next line. */
|
||||
public Line previous = null, next = null;
|
||||
/** Is previous/next line empty? */
|
||||
public boolean prevEmpty, nextEmpty;
|
||||
/** Final line of a XML block. */
|
||||
public Line xmlEndLine;
|
||||
/** Constructor. */
|
||||
public Line()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates leading and trailing spaces. Also sets empty if needed.
|
||||
*/
|
||||
public void init()
|
||||
{
|
||||
this.leading = 0;
|
||||
while(this.leading < this.value.length() && this.value.charAt(this.leading) == ' ')
|
||||
this.leading++;
|
||||
|
||||
if(this.leading == this.value.length())
|
||||
{
|
||||
this.setEmpty();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.isEmpty = false;
|
||||
this.trailing = 0;
|
||||
while(this.value.charAt(this.value.length() - this.trailing - 1) == ' ')
|
||||
this.trailing++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate leading spaces.
|
||||
*/
|
||||
public void initLeading()
|
||||
{
|
||||
this.leading = 0;
|
||||
while(this.leading < this.value.length() && this.value.charAt(this.leading) == ' ')
|
||||
this.leading++;
|
||||
|
||||
if(this.leading == this.value.length())
|
||||
{
|
||||
this.setEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) == ' ')
|
||||
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)
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
int pos = this.pos;
|
||||
while(pos < this.value.length())
|
||||
{
|
||||
final char ch = this.value.charAt(pos);
|
||||
if(ch == '\\' && pos + 1 < this.value.length())
|
||||
{
|
||||
final char c;
|
||||
switch(c = this.value.charAt(pos + 1))
|
||||
{
|
||||
case '\\':
|
||||
case '[':
|
||||
case ']':
|
||||
case '(':
|
||||
case ')':
|
||||
case '{':
|
||||
case '}':
|
||||
case '#':
|
||||
case '"':
|
||||
case '\'':
|
||||
case '.':
|
||||
case '>':
|
||||
case '*':
|
||||
case '+':
|
||||
case '-':
|
||||
case '_':
|
||||
case '!':
|
||||
case '`':
|
||||
sb.append(c);
|
||||
pos++;
|
||||
break;
|
||||
default:
|
||||
sb.append(ch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
boolean endReached = false;
|
||||
for(int n = 0; n < end.length; n++)
|
||||
{
|
||||
if(ch == end[n])
|
||||
{
|
||||
endReached = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
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++)
|
||||
{
|
||||
if(ch == end[n])
|
||||
{
|
||||
this.pos = pos;
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks this line empty. Also sets previous/next line's empty attributes.
|
||||
*/
|
||||
public void setEmpty()
|
||||
{
|
||||
this.value = "";
|
||||
this.leading = this.trailing = 0;
|
||||
this.isEmpty = true;
|
||||
if(this.previous != null)
|
||||
this.previous.nextEmpty = true;
|
||||
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)
|
||||
{
|
||||
int count = 0;
|
||||
for(int i = 0; i < this.value.length(); i++)
|
||||
{
|
||||
final char c = this.value.charAt(i);
|
||||
if(c == ' ')
|
||||
continue;
|
||||
if(c == ch)
|
||||
{
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
count = 0;
|
||||
break;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this line's type.
|
||||
*
|
||||
* @return The LineType.
|
||||
*/
|
||||
public LineType getLineType()
|
||||
{
|
||||
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(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)
|
||||
return LineType.HR;
|
||||
}
|
||||
|
||||
if(this.value.length() - this.leading >= 2 && this.value.charAt(this.leading + 1) == ' ')
|
||||
{
|
||||
switch(this.value.charAt(this.leading))
|
||||
{
|
||||
case '*':
|
||||
case '-':
|
||||
case '+':
|
||||
return LineType.ULIST;
|
||||
}
|
||||
}
|
||||
|
||||
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)))
|
||||
i++;
|
||||
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.checkHTML())
|
||||
return LineType.XML;
|
||||
}
|
||||
|
||||
if(this.next != null && !this.next.isEmpty)
|
||||
{
|
||||
if((this.next.value.charAt(0) == '-') && (this.next.countChars('-') > 0))
|
||||
return LineType.HEADLINE2;
|
||||
if((this.next.value.charAt(0) == '=') && (this.next.countChars('=') > 0))
|
||||
return LineType.HEADLINE1;
|
||||
}
|
||||
|
||||
return LineType.OTHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an XML comment. Sets <code>xmlEndLine</code>.
|
||||
*
|
||||
* @param firstLine The Line to start reading from.
|
||||
* @param start The starting position.
|
||||
* @return The new position or -1 if it is no valid comment.
|
||||
*/
|
||||
private int readXMLComment(final Line firstLine, final int start)
|
||||
{
|
||||
Line line = firstLine;
|
||||
if(start + 3 < line.value.length())
|
||||
{
|
||||
if(line.value.charAt(2) == '-' && line.value.charAt(3) == '-')
|
||||
{
|
||||
int pos = start + 4;
|
||||
while(line != null)
|
||||
{
|
||||
while(pos < line.value.length() && line.value.charAt(pos) != '-')
|
||||
{
|
||||
pos++;
|
||||
}
|
||||
if(pos == line.value.length())
|
||||
{
|
||||
line = line.next;
|
||||
pos = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(pos + 2 < line.value.length())
|
||||
{
|
||||
if(line.value.charAt(pos + 1) == '-' && line.value.charAt(pos + 2) == '>')
|
||||
{
|
||||
this.xmlEndLine = line;
|
||||
return pos + 3;
|
||||
}
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) != '}')
|
||||
return null;
|
||||
int p = this.leading;
|
||||
boolean found = false;
|
||||
while(p < this.value.length() && !found)
|
||||
{
|
||||
switch(this.value.charAt(p))
|
||||
{
|
||||
case '\\':
|
||||
if(p + 1 < this.value.length())
|
||||
{
|
||||
switch(this.value.charAt(p + 1))
|
||||
{
|
||||
case '{':
|
||||
p++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
p++;
|
||||
break;
|
||||
case '{':
|
||||
found = true;
|
||||
break;
|
||||
default:
|
||||
p++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(found)
|
||||
{
|
||||
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)
|
||||
{
|
||||
switch(this.value.charAt(p))
|
||||
{
|
||||
case '\\':
|
||||
if(p + 1 < this.value.length())
|
||||
{
|
||||
switch(this.value.charAt(p + 1))
|
||||
{
|
||||
case '}':
|
||||
p++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
p++;
|
||||
break;
|
||||
case '}':
|
||||
found = true;
|
||||
break;
|
||||
default:
|
||||
p++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(found)
|
||||
{
|
||||
final String id = this.value.substring(start, p).trim();
|
||||
if(this.leading != 0)
|
||||
{
|
||||
this.value = this.value.substring(0, this.leading) + this.value.substring(this.leading, start - 2).trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.value = this.value.substring(this.leading, start - 2).trim();
|
||||
}
|
||||
this.trailing = 0;
|
||||
return id.length() > 0 ? id : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for a valid HTML block. Sets <code>xmlEndLine</code>.
|
||||
*
|
||||
* @return <code>true</code> if it is a valid block.
|
||||
*/
|
||||
private boolean checkHTML()
|
||||
{
|
||||
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.readXMLComment(this, this.leading) > 0)
|
||||
return true;
|
||||
}
|
||||
pos = Utils.readXML(temp, this.value, this.leading, false);
|
||||
String element, tag;
|
||||
if(pos > -1)
|
||||
{
|
||||
element = temp.toString();
|
||||
temp.setLength(0);
|
||||
Utils.getXMLTag(temp, element);
|
||||
tag = temp.toString().toLowerCase();
|
||||
if(!HTML.isHtmlBlockElement(tag))
|
||||
return false;
|
||||
if(tag.equals("hr"))
|
||||
{
|
||||
this.xmlEndLine = this;
|
||||
return true;
|
||||
}
|
||||
tags.add(tag);
|
||||
|
||||
Line line = this;
|
||||
while(line != null)
|
||||
{
|
||||
while(pos < line.value.length() && line.value.charAt(pos) != '<')
|
||||
{
|
||||
pos++;
|
||||
}
|
||||
if(pos >= line.value.length())
|
||||
{
|
||||
line = line.next;
|
||||
pos = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
temp.setLength(0);
|
||||
final int newPos = Utils.readXML(temp, line.value, pos, false);
|
||||
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(element.charAt(1) == '/')
|
||||
{
|
||||
if(!tags.getLast().equals(tag))
|
||||
return false;
|
||||
tags.removeLast();
|
||||
}
|
||||
else
|
||||
{
|
||||
tags.addLast(tag);
|
||||
}
|
||||
}
|
||||
if(tags.size() == 0)
|
||||
{
|
||||
this.xmlEndLine = line;
|
||||
break;
|
||||
}
|
||||
pos = newPos;
|
||||
}
|
||||
else
|
||||
{
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return tags.size() == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
41
src/main/java/com/github/rjeschke/txtmark/LineType.java
Normal file
41
src/main/java/com/github/rjeschke/txtmark/LineType.java
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2011 René Jeschke <rene_jeschke@yahoo.de>
|
||||
*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Line type enumeration.
|
||||
*
|
||||
* @author René Jeschke <rene_jeschke@yahoo.de>
|
||||
*/
|
||||
enum LineType
|
||||
{
|
||||
/** Empty line. */
|
||||
EMPTY,
|
||||
/** Undefined content. */
|
||||
OTHER,
|
||||
/** A markdown headline. */
|
||||
HEADLINE, HEADLINE1, HEADLINE2,
|
||||
/** A code block line. */
|
||||
CODE,
|
||||
/** A list. */
|
||||
ULIST, OLIST,
|
||||
/** A block quote. */
|
||||
BQUOTE,
|
||||
/** A horizontal ruler. */
|
||||
HR,
|
||||
/** Start of a XML block. */
|
||||
XML
|
||||
}
|
||||
51
src/main/java/com/github/rjeschke/txtmark/LinkRef.java
Normal file
51
src/main/java/com/github/rjeschke/txtmark/LinkRef.java
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2011 René Jeschke <rene_jeschke@yahoo.de>
|
||||
*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* A markdown link reference.
|
||||
*
|
||||
* @author René Jeschke <rene_jeschke@yahoo.de>
|
||||
*/
|
||||
class LinkRef
|
||||
{
|
||||
/** The link. */
|
||||
public final String link;
|
||||
/** The optional comment/title. */
|
||||
public String title;
|
||||
/** Flag indicating that this is an abbreviation. */
|
||||
public final boolean isAbbrev;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param link The link.
|
||||
* @param title The title (may be <code>null</code>).
|
||||
*/
|
||||
public LinkRef(final String link, final String title, final boolean isAbbrev)
|
||||
{
|
||||
this.link = link;
|
||||
this.title = title;
|
||||
this.isAbbrev = isAbbrev;
|
||||
}
|
||||
|
||||
/** @see java.lang.Object#toString() */
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return this.link + " \"" + this.title + "\"";
|
||||
}
|
||||
}
|
||||
71
src/main/java/com/github/rjeschke/txtmark/MarkToken.java
Normal file
71
src/main/java/com/github/rjeschke/txtmark/MarkToken.java
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2011 René Jeschke <rene_jeschke@yahoo.de>
|
||||
*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Markdown token enumeration.
|
||||
*
|
||||
* @author René Jeschke <rene_jeschke@yahoo.de>
|
||||
*/
|
||||
enum MarkToken
|
||||
{
|
||||
/** No token. */
|
||||
NONE,
|
||||
/** * */
|
||||
EM_STAR, // x*x
|
||||
/** _ */
|
||||
EM_UNDERSCORE, // x_x
|
||||
/** ** */
|
||||
STRONG_STAR, // x**x
|
||||
/** __ */
|
||||
STRONG_UNDERSCORE, // x__x
|
||||
/** ` */
|
||||
CODE_SINGLE, // `
|
||||
/** `` */
|
||||
CODE_DOUBLE, // ``
|
||||
/** [ */
|
||||
LINK, // [
|
||||
/** < */
|
||||
HTML, // <
|
||||
/** ![ */
|
||||
IMAGE, // ![
|
||||
/** & */
|
||||
ENTITY, // &
|
||||
/** \ */
|
||||
ESCAPE, // \x
|
||||
/** Extended: ^ */
|
||||
SUPER, // ^
|
||||
/** Extended: (C) */
|
||||
X_COPY, // (C)
|
||||
/** Extended: (R) */
|
||||
X_REG, // (R)
|
||||
/** Extended: (TM) */
|
||||
X_TRADE, // (TM)
|
||||
/** Extended: << */
|
||||
X_LAQUO, // <<
|
||||
/** Extended: >> */
|
||||
X_RAQUO, // >>
|
||||
/** Extended: -- */
|
||||
X_NDASH, // --
|
||||
/** Extended: --- */
|
||||
X_MDASH, // ---
|
||||
/** Extended: ... */
|
||||
X_HELLIP, // ...
|
||||
/** Extended: "x */
|
||||
X_RDQUO, // "
|
||||
/** Extended: x" */
|
||||
X_LDQUO // "
|
||||
}
|
||||
940
src/main/java/com/github/rjeschke/txtmark/Processor.java
Normal file
940
src/main/java/com/github/rjeschke/txtmark/Processor.java
Normal file
@ -0,0 +1,940 @@
|
||||
/*
|
||||
* Copyright (C) 2011 René Jeschke <rene_jeschke@yahoo.de>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* Example usage:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* <code>String result = Processor.process("This is ***TXTMARK***");
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @author René Jeschke <rene_jeschke@yahoo.de>
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
private Processor(final Reader reader, final Configuration config)
|
||||
{
|
||||
this.reader = reader;
|
||||
this.config = config;
|
||||
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 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 static String process(final String input, final Configuration configuration)
|
||||
{
|
||||
try
|
||||
{
|
||||
return process(new StringReader(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.
|
||||
* @return The processed String.
|
||||
* @throws IOException
|
||||
* if an IO error occurs
|
||||
* @since 0.7
|
||||
* @see Configuration
|
||||
*/
|
||||
public 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 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 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 <code>true</code> to escape unsafe HTML tags.
|
||||
* @return The processed String.
|
||||
* @see Configuration#DEFAULT
|
||||
*/
|
||||
public 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 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 <code>true</code> to escape unsafe HTML tags.
|
||||
* @return The processed String.
|
||||
* @see Configuration#DEFAULT
|
||||
*/
|
||||
public 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 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 <code>true</code> to escape unsafe HTML tags.
|
||||
* @return The processed String.
|
||||
* @throws IOException
|
||||
* if an IO error occurs
|
||||
* @see Configuration#DEFAULT
|
||||
*/
|
||||
public 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 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 <code>true</code> to escape unsafe HTML tags.
|
||||
* @return The processed String.
|
||||
* @throws IOException
|
||||
* if an IO error occurs
|
||||
* @see Configuration#DEFAULT
|
||||
*/
|
||||
public 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 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 <code>true</code> to escape unsafe HTML tags.
|
||||
* @return The processed String.
|
||||
* @throws IOException
|
||||
* if an IO error occurs
|
||||
* @see Configuration#DEFAULT
|
||||
*/
|
||||
public 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 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 <code>true</code> to escape unsafe HTML tags.
|
||||
* @return The processed String.
|
||||
* @throws IOException
|
||||
* if an IO error occurs
|
||||
* @see Configuration#DEFAULT
|
||||
*/
|
||||
public 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 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 <code>true</code> to escape unsafe HTML tags.
|
||||
* @return The processed String.
|
||||
* @throws IOException
|
||||
* if an IO error occurs
|
||||
* @see Configuration#DEFAULT
|
||||
*/
|
||||
public 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 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 <code>true</code> to escape unsafe HTML tags.
|
||||
* @return The processed String.
|
||||
* @throws IOException
|
||||
* if an IO error occurs
|
||||
* @see Configuration#DEFAULT
|
||||
*/
|
||||
public 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 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 <code>true</code> to escape unsafe HTML tags.
|
||||
* @return The processed String.
|
||||
* @throws IOException
|
||||
* if an IO error occurs
|
||||
* @see Configuration#DEFAULT
|
||||
*/
|
||||
public 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 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 <code>true</code> to escape unsafe HTML tags.
|
||||
* @return The processed String.
|
||||
* @throws IOException
|
||||
* if an IO error occurs
|
||||
* @see Configuration#DEFAULT
|
||||
*/
|
||||
public 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 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 <code>true</code> to escape unsafe HTML tags.
|
||||
* @return The processed String.
|
||||
* @throws IOException
|
||||
* if an IO error occurs
|
||||
* @see Configuration#DEFAULT
|
||||
*/
|
||||
public 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 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 <code>true</code> to escape unsafe HTML tags.
|
||||
* @return The processed String.
|
||||
* @throws IOException
|
||||
* if an IO error occurs
|
||||
* @see Configuration#DEFAULT
|
||||
*/
|
||||
public 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.
|
||||
* <p>
|
||||
* Takes care of markdown link references.
|
||||
* </p>
|
||||
*
|
||||
* @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();
|
||||
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();
|
||||
if(this.useExtensions && root.lines != null && root.lines.getLineType() != 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();
|
||||
switch(type)
|
||||
{
|
||||
case OTHER:
|
||||
{
|
||||
final boolean wasEmpty = line.prevEmpty;
|
||||
while(line != null && !line.isEmpty)
|
||||
{
|
||||
final LineType t = line.getLineType();
|
||||
if((listMode || this.useExtensions) && (t == LineType.OLIST || t == LineType.ULIST))
|
||||
break;
|
||||
if(this.useExtensions && (t == LineType.CODE))
|
||||
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() != 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 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();
|
||||
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;
|
||||
this.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();
|
||||
}
|
||||
}
|
||||
93
src/main/java/com/github/rjeschke/txtmark/Run.java
Normal file
93
src/main/java/com/github/rjeschke/txtmark/Run.java
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2011 René Jeschke <rene_jeschke@yahoo.de>
|
||||
*
|
||||
* 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.InputStreamReader;
|
||||
|
||||
/**
|
||||
* Simple class for processing markdown files on the command line.
|
||||
*
|
||||
* <p>Usage:</p>
|
||||
* <pre><code>java -cp txtmark.jar txtmark.Run filename [header_footer_file]
|
||||
* </code></pre>
|
||||
*
|
||||
* <p>The <code>header_footer_file</code> is an optional UTF-8 encoded file containing
|
||||
* a header and a footer to output around the generated HTML code.</p>
|
||||
*
|
||||
* <p>Example:</p>
|
||||
*
|
||||
* <pre><code><?xml version="1.0" encoding="UTF-8"?>
|
||||
*<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
* "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
*<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
*<head>
|
||||
*<title>markdown</title>
|
||||
*<link type="text/css" href="style.css" rel="stylesheet"/>
|
||||
*<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
|
||||
*</head>
|
||||
*<body>
|
||||
*<!-- the following line separates header from footer -->
|
||||
*<!-- ### -->
|
||||
*</body>
|
||||
*</html>
|
||||
*</code></pre>
|
||||
*
|
||||
* @author René Jeschke <rene_jeschke@yahoo.de>
|
||||
*/
|
||||
public class Run
|
||||
{
|
||||
/**
|
||||
* Static main.
|
||||
*
|
||||
* @param args Program arguments.
|
||||
* @throws IOException If an IO error occurred.
|
||||
*/
|
||||
public static void main(String[] args) throws IOException
|
||||
{
|
||||
// This is just a _hack_ ...
|
||||
BufferedReader reader = null;
|
||||
if(args.length == 0)
|
||||
{
|
||||
System.err.println("No input file specified.");
|
||||
System.exit(-1);
|
||||
}
|
||||
if(args.length > 1)
|
||||
{
|
||||
reader = new BufferedReader(new InputStreamReader(new FileInputStream(args[1]), "UTF-8"));
|
||||
String line = reader.readLine();
|
||||
while(line != null && !line.startsWith("<!-- ###"))
|
||||
{
|
||||
System.out.println(line);
|
||||
line = reader.readLine();
|
||||
}
|
||||
}
|
||||
System.out.println(Processor.process(new File(args[0])));
|
||||
if(args.length > 1 && reader != null)
|
||||
{
|
||||
String line = reader.readLine();
|
||||
while(line != null)
|
||||
{
|
||||
System.out.println(line);
|
||||
line = reader.readLine();
|
||||
}
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
548
src/main/java/com/github/rjeschke/txtmark/Utils.java
Normal file
548
src/main/java/com/github/rjeschke/txtmark/Utils.java
Normal file
@ -0,0 +1,548 @@
|
||||
/*
|
||||
* Copyright (C) 2011 René Jeschke <rene_jeschke@yahoo.de>
|
||||
*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Utilities.
|
||||
*
|
||||
* @author René Jeschke <rene_jeschke@yahoo.de>
|
||||
*/
|
||||
class Utils
|
||||
{
|
||||
/** Random number generator value. */
|
||||
private static int RND = (int)System.nanoTime();
|
||||
|
||||
/**
|
||||
* LCG random number generator.
|
||||
*
|
||||
* @return A pseudo random number between 0 and 1023
|
||||
*/
|
||||
public static int rnd()
|
||||
{
|
||||
return (RND = RND * 1664525 + 1013904223) >>> 22;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips spaces in the given String.
|
||||
*
|
||||
* @param in Input String.
|
||||
* @param start Starting position.
|
||||
* @return The new position or -1 if EOL has been reached.
|
||||
*/
|
||||
public static int skipSpaces(final String in, final int start)
|
||||
{
|
||||
int pos = start;
|
||||
while(pos < in.length() && (in.charAt(pos) == ' ' || in.charAt(pos) == '\n'))
|
||||
pos++;
|
||||
return pos < in.length() ? pos : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processed the given escape sequence.
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param ch The character.
|
||||
* @param pos Current parsing position.
|
||||
* @return The new position.
|
||||
*/
|
||||
public static int escape(final StringBuilder out, final char ch, final int pos)
|
||||
{
|
||||
switch(ch)
|
||||
{
|
||||
case '\\':
|
||||
case '[':
|
||||
case ']':
|
||||
case '(':
|
||||
case ')':
|
||||
case '{':
|
||||
case '}':
|
||||
case '#':
|
||||
case '"':
|
||||
case '\'':
|
||||
case '.':
|
||||
case '>':
|
||||
case '<':
|
||||
case '*':
|
||||
case '+':
|
||||
case '-':
|
||||
case '_':
|
||||
case '!':
|
||||
case '`':
|
||||
case '^':
|
||||
out.append(ch);
|
||||
return pos + 1;
|
||||
default:
|
||||
out.append('\\');
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads characters until any 'end' character is encountered.
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param in The Input String.
|
||||
* @param start Starting position.
|
||||
* @param end End characters.
|
||||
* @return The new position or -1 if no 'end' char was found.
|
||||
*/
|
||||
public static int readUntil(final StringBuilder out, final String in, final int start, final char... end)
|
||||
{
|
||||
int pos = start;
|
||||
while(pos < in.length())
|
||||
{
|
||||
final char ch = in.charAt(pos);
|
||||
if(ch == '\\' && pos + 1 < in.length())
|
||||
{
|
||||
pos = escape(out, in.charAt(pos + 1), pos);
|
||||
}
|
||||
else
|
||||
{
|
||||
boolean endReached = false;
|
||||
for(int n = 0; n < end.length; n++)
|
||||
{
|
||||
if(ch == end[n])
|
||||
{
|
||||
endReached = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(endReached)
|
||||
break;
|
||||
out.append(ch);
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
|
||||
return (pos == in.length()) ? -1 : pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads characters until the 'end' character is encountered.
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param in The Input String.
|
||||
* @param start Starting position.
|
||||
* @param end End characters.
|
||||
* @return The new position or -1 if no 'end' char was found.
|
||||
*/
|
||||
public static int readUntil(final StringBuilder out, final String in, final int start, final char end)
|
||||
{
|
||||
int pos = start;
|
||||
while(pos < in.length())
|
||||
{
|
||||
final char ch = in.charAt(pos);
|
||||
if(ch == '\\' && pos + 1 < in.length())
|
||||
{
|
||||
pos = escape(out, in.charAt(pos + 1), pos);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(ch == end)
|
||||
break;
|
||||
out.append(ch);
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
|
||||
return (pos == in.length()) ? -1 : pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a markdown link.
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param in Input String.
|
||||
* @param start Starting position.
|
||||
* @return The new position or -1 if this is no valid markdown link.
|
||||
*/
|
||||
public static int readMdLink(final StringBuilder out, final String in, final int start)
|
||||
{
|
||||
int pos = start;
|
||||
int counter = 1;
|
||||
while(pos < in.length())
|
||||
{
|
||||
final char ch = in.charAt(pos);
|
||||
if(ch == '\\' && pos + 1 < in.length())
|
||||
{
|
||||
pos = escape(out, in.charAt(pos + 1), pos);
|
||||
}
|
||||
else
|
||||
{
|
||||
boolean endReached = false;
|
||||
switch(ch)
|
||||
{
|
||||
case '(':
|
||||
counter++;
|
||||
break;
|
||||
case ' ':
|
||||
if(counter == 1)
|
||||
endReached = true;
|
||||
break;
|
||||
case ')':
|
||||
counter--;
|
||||
if(counter == 0)
|
||||
endReached = true;
|
||||
break;
|
||||
}
|
||||
if(endReached)
|
||||
break;
|
||||
out.append(ch);
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
|
||||
return (pos == in.length()) ? -1 : pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a markdown link ID.
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param in Input String.
|
||||
* @param start Starting position.
|
||||
* @return The new position or -1 if this is no valid markdown link ID.
|
||||
*/
|
||||
public static int readMdLinkId(final StringBuilder out, final String in, final int start)
|
||||
{
|
||||
int pos = start;
|
||||
int counter = 1;
|
||||
while(pos < in.length())
|
||||
{
|
||||
final char ch = in.charAt(pos);
|
||||
boolean endReached = false;
|
||||
switch(ch)
|
||||
{
|
||||
case '\n':
|
||||
out.append(' ');
|
||||
break;
|
||||
case '[':
|
||||
counter++;
|
||||
out.append(ch);
|
||||
break;
|
||||
case ']':
|
||||
counter--;
|
||||
if(counter == 0)
|
||||
endReached = true;
|
||||
else
|
||||
out.append(ch);
|
||||
break;
|
||||
default:
|
||||
out.append(ch);
|
||||
break;
|
||||
}
|
||||
if(endReached)
|
||||
break;
|
||||
pos++;
|
||||
}
|
||||
|
||||
return (pos == in.length()) ? -1 : pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads characters until any 'end' character is encountered, ignoring escape sequences.
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param in The Input String.
|
||||
* @param start Starting position.
|
||||
* @param end End characters.
|
||||
* @return The new position or -1 if no 'end' char was found.
|
||||
*/
|
||||
public static int readRawUntil(final StringBuilder out, final String in, final int start, final char... end)
|
||||
{
|
||||
int pos = start;
|
||||
while(pos < in.length())
|
||||
{
|
||||
final char ch = in.charAt(pos);
|
||||
boolean endReached = false;
|
||||
for(int n = 0; n < end.length; n++)
|
||||
{
|
||||
if(ch == end[n])
|
||||
{
|
||||
endReached = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(endReached)
|
||||
break;
|
||||
out.append(ch);
|
||||
pos++;
|
||||
}
|
||||
|
||||
return (pos == in.length()) ? -1 : pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads characters until the end character is encountered, ignoring escape sequences.
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param in The Input String.
|
||||
* @param start Starting position.
|
||||
* @param end End characters.
|
||||
* @return The new position or -1 if no 'end' char was found.
|
||||
*/
|
||||
public static int readRawUntil(final StringBuilder out, final String in, final int start, final char end)
|
||||
{
|
||||
int pos = start;
|
||||
while(pos < in.length())
|
||||
{
|
||||
final char ch = in.charAt(pos);
|
||||
if(ch == end)
|
||||
break;
|
||||
out.append(ch);
|
||||
pos++;
|
||||
}
|
||||
|
||||
return (pos == in.length()) ? -1 : pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given string encoding special HTML characters.
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param in Input String.
|
||||
* @param start Input String starting position.
|
||||
* @param end Input String end position.
|
||||
*/
|
||||
public static void appendCode(final StringBuilder out, final String in, final int start, final int end)
|
||||
{
|
||||
for(int i = start; i < end; i++)
|
||||
{
|
||||
final char c;
|
||||
switch(c = in.charAt(i))
|
||||
{
|
||||
case '&':
|
||||
out.append("&");
|
||||
break;
|
||||
case '<':
|
||||
out.append("<");
|
||||
break;
|
||||
case '>':
|
||||
out.append(">");
|
||||
break;
|
||||
default:
|
||||
out.append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given string encoding special HTML characters (used in HTML attribute values).
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param in Input String.
|
||||
* @param start Input String starting position.
|
||||
* @param end Input String end position.
|
||||
*/
|
||||
public static void appendValue(final StringBuilder out, final String in, final int start, final int end)
|
||||
{
|
||||
for(int i = start; i < end; i++)
|
||||
{
|
||||
final char c;
|
||||
switch(c = in.charAt(i))
|
||||
{
|
||||
case '&':
|
||||
out.append("&");
|
||||
break;
|
||||
case '<':
|
||||
out.append("<");
|
||||
break;
|
||||
case '>':
|
||||
out.append(">");
|
||||
break;
|
||||
case '"':
|
||||
out.append(""");
|
||||
break;
|
||||
case '\'':
|
||||
out.append("'");
|
||||
break;
|
||||
default:
|
||||
out.append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the given char as a decimal HTML entity.
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param value The character.
|
||||
*/
|
||||
public static void appendDecEntity(final StringBuilder out, final char value)
|
||||
{
|
||||
out.append("&#");
|
||||
out.append((int)value);
|
||||
out.append(';');
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the given char as a hexadecimal HTML entity.
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param value The character.
|
||||
*/
|
||||
public static void appendHexEntity(final StringBuilder out, final char value)
|
||||
{
|
||||
out.append("&#x");
|
||||
out.append(Integer.toHexString(value));
|
||||
out.append(';');
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given mailto link using obfuscation.
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param in Input String.
|
||||
* @param start Input String starting position.
|
||||
* @param end Input String end position.
|
||||
*/
|
||||
public static void appendMailto(final StringBuilder out, final String in, final int start, final int end)
|
||||
{
|
||||
for(int i = start; i < end; i++)
|
||||
{
|
||||
final char c;
|
||||
final int r = rnd();
|
||||
switch(c = in.charAt(i))
|
||||
{
|
||||
case '&':
|
||||
case '<':
|
||||
case '>':
|
||||
case '"':
|
||||
case '\'':
|
||||
case '@':
|
||||
if(r < 512)
|
||||
appendDecEntity(out, c);
|
||||
else
|
||||
appendHexEntity(out, c);
|
||||
break;
|
||||
default:
|
||||
if(r < 32)
|
||||
out.append(c);
|
||||
else if(r < 520)
|
||||
appendDecEntity(out, c);
|
||||
else
|
||||
appendHexEntity(out, c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the tag from an XML element.
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param in Input StringBuilder.
|
||||
*/
|
||||
public static void getXMLTag(final StringBuilder out, final StringBuilder in)
|
||||
{
|
||||
int pos = 1;
|
||||
if(in.charAt(1) == '/')
|
||||
pos++;
|
||||
while(Character.isLetterOrDigit(in.charAt(pos)))
|
||||
{
|
||||
out.append(in.charAt(pos++));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the tag from an XML element.
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param in Input String.
|
||||
*/
|
||||
public static void getXMLTag(final StringBuilder out, final String in)
|
||||
{
|
||||
int pos = 1;
|
||||
if(in.charAt(1) == '/')
|
||||
pos++;
|
||||
while(Character.isLetterOrDigit(in.charAt(pos)))
|
||||
{
|
||||
out.append(in.charAt(pos++));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an XML element.
|
||||
*
|
||||
* @param out The StringBuilder to write to.
|
||||
* @param in Input String.
|
||||
* @param start Starting position.
|
||||
* @param safeMode Whether to escape unsafe HTML tags or not
|
||||
* @return The new position or -1 if this is no valid XML element.
|
||||
*/
|
||||
public static int readXML(final StringBuilder out, final String in, final int start, final boolean safeMode)
|
||||
{
|
||||
int pos;
|
||||
final boolean isCloseTag;
|
||||
try {
|
||||
if(in.charAt(start + 1) == '/')
|
||||
{
|
||||
isCloseTag = true;
|
||||
pos = start + 2;
|
||||
}
|
||||
else if(in.charAt(start + 1) == '!')
|
||||
{
|
||||
out.append("<!");
|
||||
return start + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
isCloseTag = false;
|
||||
pos = start + 1;
|
||||
}
|
||||
if(safeMode)
|
||||
{
|
||||
final StringBuilder temp = new StringBuilder();
|
||||
pos = readRawUntil(temp, in, pos, ' ', '/', '>');
|
||||
if(pos == -1) return -1;
|
||||
final String tag = temp.toString().trim().toLowerCase();
|
||||
if(HTML.isUnsafeHtmlElement(tag))
|
||||
{
|
||||
out.append("<");
|
||||
if(isCloseTag)
|
||||
out.append('/');
|
||||
out.append(temp);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
out.append('<');
|
||||
if(isCloseTag)
|
||||
out.append('/');
|
||||
pos = readRawUntil(out, in, pos, ' ', '/', '>');
|
||||
}
|
||||
if(pos == -1) return -1;
|
||||
pos = readRawUntil(out, in, pos, '/', '>');
|
||||
if(in.charAt(pos) == '/')
|
||||
{
|
||||
out.append(" /");
|
||||
pos = readRawUntil(out, in, pos + 1, '>');
|
||||
if(pos == -1)
|
||||
return -1;
|
||||
}
|
||||
if(in.charAt(pos) == '>')
|
||||
{
|
||||
out.append('>');
|
||||
return pos;
|
||||
}
|
||||
} catch (StringIndexOutOfBoundsException e) {
|
||||
return -1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user