blob: 617519f070bf3e273ab6e355c2a4a15c10b2e3f4 [file] [log] [blame]
/*
JSPWiki - a JSP-based WikiWiki clone.
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.wiki.ui.migrator;
import java.util.*;
/**
* Parser that reads JSP document and constructs a {@link JspDocument} with the
* results.
*/
public class JspParser
{
private static final Parser TEXT_PARSER = new TextParser();
private static final Parser TAG_PARSER = new TagParser();
private static final AttributeParser ATTRIBUTE_PARSER = new AttributeParser();
private static final DynamicAttributeParser DYNAMIC_ATTRIBUTE_PARSER = new DynamicAttributeParser();
/**
* Parses dynamic attributes in a Tag declaration.
*/
public static class DynamicAttributeParser implements Parser
{
/**
* {@inheritDoc}
*/
public void beginStage()
{
}
/**
* {@inheritDoc}
*/
public void endStage()
{
}
/**
* {@inheritDoc}
*/
public void handle( char ch )
{
ParseContext ctx = ParseContext.currentContext();
int leftAngleBrackets = 1;
boolean increment = false;
do
{
// Get character at current position
// Increment position
if ( increment )
{
ctx.incrementPosition();
}
ch = ctx.getSource().charAt( ctx.position() );
switch (ch)
{
case ('<'):
{
leftAngleBrackets++;
break;
}
case ('>'):
{
leftAngleBrackets--;
break;
}
default:
{
// Ignore any other character
}
increment = true;
}
} while ( leftAngleBrackets != 0 );
// Set the end position, name and value.
Attribute attribute = (Attribute)ctx.getNode();
ctx.setEndPosition( attribute );
attribute.setType( NodeType.DYNAMIC_ATTRIBUTE );
attribute.setValue( ctx.getSource().substring( ctx.getMarkerForStage( Stage.CODE_OR_COMMENT), attribute.getEnd() ) );
// Add to parent
Tag parent = (Tag)ctx.getParentContext().getNode();
parent.addAttribute( attribute );
// Pop the ParseContext and exit
ParseContext.pop();
}
}
public static class AttributeParser implements Parser
{
/**
* {@inheritDoc}
*/
public void beginStage()
{
ParseContext ctx = ParseContext.currentContext();
Node attribute = new Attribute( ctx.getDocument() );
ctx.setNode( attribute );
}
/**
* {@inheritDoc}
*/
public void endStage()
{
ParseContext ctx = ParseContext.currentContext();
Node attribute = ctx.getNode();
attribute.setEnd( ctx.position() );
}
/**
* {@inheritDoc}
*/
public void handle( char ch )
{
ParseContext ctx = ParseContext.currentContext();
int pos = ctx.position();
switch( ctx.getStage() )
{
// Characters that name an attribute, after whitespace but
// before equals (=).
case NAME: {
if( ch == '=' )
{
// Set the attribute name
Attribute attribute = (Attribute) ctx.getNode();
attribute.setName( ctx.getSource().substring( attribute.getStart(), pos ) );
// The next character will be a single/double quote
ctx.incrementPosition();
char delimiter = ctx.getSource().charAt( ctx.position() );
attribute.setAttributeDelimiter( delimiter );
// Start a new ParseContext, and Text node in position
// after quote
ctx = ParseContext.push();
ctx.setStartPosition( ctx.getNode(), ctx.position() + 1 );
}
break;
}
case ATTRIBUTE_END: {
// Add the finished attribute to the parent
Attribute attribute = (Attribute) ctx.getNode();
Tag parent = (Tag) attribute.getParent();
parent.addAttribute( attribute );
// Retrieve the Tag ParseContext & fire the handler method
ctx = ParseContext.pop();
Parser parser = ctx.getParser();
parser.handle( ch );
break;
}
}
}
}
public static class TagParser implements Parser
{
/**
* Factory method that initializes a supplied Node. When initialized,
* the node's start position, line number, column number and level are
* set automatically based on JspDocument's internal cache of
* line-breaks and nodes. Note that the new node is not actually added
* to the internal node tree until the method
* {@link #finalizeNode(Node, int)} is called.
*
* @param type the node type
* @param the stage to set at the end of the initialization
*/
private void initNode( Node node, Stage stage )
{
ParseContext ctx = ParseContext.currentContext();
ctx.setNode( node );
// Skip ahead until we get to the first name character
int pos = ctx.position();
int increment = node.getType().getTagStart().length();
String source = ctx.getSource();
while ( pos + increment < source.length() && Character.isWhitespace( source.charAt( pos + increment ) ) )
{
increment++;
}
// Walk backwards one position
increment--;
for( int i = 0; i < increment; i++ )
{
ctx.incrementPosition();
}
// Set the new stage and mark it at the next character
ctx.setStage( stage, 1 );
}
/**
* {@inheritDoc}
*/
public void beginStage()
{
ParseContext ctx = ParseContext.currentContext();
// Figure out what this tag is
String lookahead = ctx.lookahead( 10 );
JspDocument doc = ctx.getDocument();
// <%- means hidden JSP comment
if( lookahead.startsWith( NodeType.JSP_COMMENT.getTagStart() ) )
{
initNode( new Text( doc, NodeType.JSP_COMMENT ), Stage.CODE_OR_COMMENT );
}
// <%! means JSP declaration
else if( lookahead.startsWith( NodeType.JSP_DECLARATION.getTagStart() ) )
{
initNode( new Text( doc, NodeType.JSP_DECLARATION ), Stage.CODE_OR_COMMENT );
}
// <%= means JSP expression
else if( lookahead.startsWith( NodeType.JSP_EXPRESSION.getTagStart() ) )
{
initNode( new Text( doc, NodeType.JSP_EXPRESSION ), Stage.CODE_OR_COMMENT );
}
// <%@ + optional space means JSP directive
else if( lookahead.startsWith( NodeType.JSP_DIRECTIVE.getTagStart() ) )
{
initNode( new Tag( doc, NodeType.JSP_DIRECTIVE ), Stage.NAME );
}
// <!-- means HTML comment
else if( lookahead.startsWith( NodeType.COMMENT.getTagStart() ) )
{
initNode( new Text( doc, NodeType.COMMENT ), Stage.CODE_OR_COMMENT );
}
// Whitespace after <% means
// scriptlet
else if( lookahead.startsWith( NodeType.SCRIPTLET.getTagStart() ) )
{
if( lookahead.length() >= 3 && Character.isWhitespace( lookahead.charAt( 2 ) ) )
{
initNode( new Text( doc, NodeType.SCRIPTLET ), Stage.CODE_OR_COMMENT );
}
}
// <? means declaration, such as XML declaration
else if ( lookahead.startsWith( NodeType.DECLARATION.getTagStart() ) )
{
initNode( new Tag( doc, NodeType.DECLARATION ), Stage.NAME );
}
// <!DOCTYPE means DOCTYPE
else if ( lookahead.startsWith( NodeType.DOCTYPE.getTagStart() ) )
{
initNode( new Text( doc, NodeType.DOCTYPE ), Stage.CODE_OR_COMMENT );
}
// <![CDATA[ means CDATA
else if( lookahead.startsWith( NodeType.CDATA.getTagStart() ) )
{
initNode( new Text( doc, NodeType.CDATA ), Stage.CODE_OR_COMMENT );
}
// If </, it's an HTML end tag
else if( lookahead.startsWith( NodeType.END_TAG.getTagStart() ) )
{
initNode( new Tag( doc, NodeType.END_TAG ), Stage.NAME );
}
// Any other char means its HTML start tag
// or combined tag
else
{
initNode( new Tag( doc, NodeType.UNRESOLVED_TAG ), Stage.NAME );
}
}
/**
* {@inheritDoc}
*/
public void endStage()
{
ParseContext ctx = ParseContext.currentContext();
Node node = ctx.getNode();
if( node != null )
{
// Set the end position
ctx.setEndPosition( node );
// If node length is > 0, add it to the parent
if( node.getEnd() > node.getStart() )
{
node.getParent().addChild( node );
}
}
}
/**
* {@inheritDoc}
*/
public void handle( char ch )
{
ParseContext ctx = ParseContext.currentContext();
Node node = ctx.getNode();
switch( ctx.getStage() )
{
// Characters that supply the tag name; immediately after <
case NAME: {
switch( ch )
{
// Whitespace == end of the name
case (' '): {
int nameStart = ctx.getMarkerForStage( Stage.NAME );
int nameEnd = ctx.position();
node.setName( ctx.getSource().substring( nameStart, nameEnd ) );
ctx.setStage( Stage.WHITESPACE, 0 );
break;
}
// Right angle bracket == end of the node
case ('>'): {
handleTagEnd();
break;
}
}
break;
}
// Whitespace between <%! and name, or between attributes.
case WHITESPACE: {
switch( ch )
{
case ('/'):
case (' '):
case ('?'):
case ('%'): {
break;
}
// It's the start of a new dynamic attribute
case ('<'): {
ctx = ParseContext.push();
ctx.setParser( DYNAMIC_ATTRIBUTE_PARSER, Stage.CODE_OR_COMMENT, 0 );
ctx.setNode( new Attribute( ctx.getDocument() ) );
break;
}
case ('>'): {
handleTagEnd();
break;
}
default: {
// It's the start of a new attribute
ctx = ParseContext.push();
ctx.setParser( ATTRIBUTE_PARSER, Stage.NAME, 0 );
}
}
break;
}
case CODE_OR_COMMENT: {
switch( ch )
{
// Terminating %>, > or --> means the end of the comment (or DOCTYPE)
case ('>'): {
String tagEnd = node.getType().getTagEnd();
String lookbehind = ctx.lookbehind( tagEnd.length() );
if( lookbehind.equals( tagEnd ) )
{
// Set the end position
node.setEnd( ctx.position() + 1 );
// Set the value
NodeType type = node.getType();
node.setValue( ctx.getSource().substring( node.getStart() + type.getTagStart().length(),
node.getEnd() - type.getTagEnd().length() ) );
ctx.setParser( TEXT_PARSER, Stage.TEXT, 1 );
}
break;
}
}
break;
}
}
}
/**
* <p>
* Finishes up an HTML or JSP tag when &gt; is detected by the handler
* method {@link #handle(char)}, but before {@link #endStage()}
* executes. Because HTML parsing can be messy, this method resolves
* several edge cases:
* </p>
* <ul>
* <li>If the tag's type has not been determined (that is, its current
* type is {@link NodeType#UNRESOLVED_TAG}, its type is resolved
* to either {@link NodeType#EMPTY_ELEMENT_TAG} or
* {@link NodeType#START_TAG}, depending on whether the last
* character was /.
* <li>
* <li>If the tag's name has not been set, because the parser has not
* encountered whitespace that delimits attributes, its name is set.</li>
* <li>If the tag is of type {@link NodeType#START_TAG} or
* {@link NodeType#START_TAG}, the current ParseContext is pushed
* on, or popped off, of the stack. In addition, if the tag is an end
* tag, its parent is re-wired to the same parent as the start tag. This
* makes the start and end tag logical peers, as they should be.</li>
* </ul>
* <p>
* After these cases are resolved, the current node is re-set to a Text
* node, in the character position starting after the right angle
* bracket (&gt;).
* </p>
*/
private void handleTagEnd()
{
ParseContext ctx = ParseContext.currentContext();
Node node = ctx.getNode();
// Special case if we have a META or LINK tag
if ( "LINK".equals( node.getName() ) )
{
node.setType( NodeType.LINK );
}
else if ( "META".equals( node.getName() ) )
{
node.setType( NodeType.META );
}
// Resolve tag type if not set
if( node.getType() == NodeType.UNRESOLVED_TAG )
{
String lookbehind = ctx.lookbehind( 2 );
if( NodeType.EMPTY_ELEMENT_TAG.getTagEnd().equals( lookbehind ) )
{
node.setType( NodeType.EMPTY_ELEMENT_TAG );
}
else
{
node.setType( NodeType.START_TAG );
}
}
// Set the name if not set
if( node.getName() == null )
{
int nameStart = ctx.getMarkerForStage( Stage.NAME );
int nameEnd = ctx.position() + 1 - node.getType().getTagEnd().length();
node.setName( ctx.getSource().substring( nameStart, nameEnd ) );
}
// If start or end tag, push/pop as needed;
// otherwise start new Text
switch( node.getType() )
{
case START_TAG: {
// Add the start tag to parent, and push it onto stack
endStage();
ctx = ParseContext.push();
break;
}
case END_TAG: {
// Make end tag the peer of the start tag
Node startTag = node.getParent();
node.setParent( startTag.getParent() );
// Does the end tag match the start tag?
if ( !node.getName().equals( startTag.getName() ) )
{
if ( startTag.equals( ctx.getDocument().getRoot() ) )
{
throw new IllegalStateException( "Encountered end tag </"
+ node.getName() + "> (" + node.getLine() + "," + node.getColumn() + " char " + node.getStart()
+ ") that did have a matching start tag." );
}
throw new IllegalStateException( "Encountered end tag </"
+ node.getName() + "> (" + node.getLine() + "," + node.getColumn() + " char " + node.getStart()
+ ") that did not match start tag <"
+ startTag.getName() + "> (" + startTag.getLine() + "," + startTag.getColumn() + " char " + startTag.getEnd() + ")"
);
}
// Get rid of the current node in the parent context
ctx = ParseContext.pop();
ctx.setNode( null );
break;
}
}
// Start new Text node after the >
ctx.setParser( TEXT_PARSER, Stage.TEXT, 1 );
}
}
public interface Parser
{
public abstract void beginStage();
public abstract void endStage();
public abstract void handle( char ch );
}
public static class TextParser implements Parser
{
/**
* {@inheritDoc}
*/
public void beginStage()
{
// Create Text node and set start at next character
ParseContext ctx = ParseContext.currentContext();
Text text = new Text( ctx.getDocument() );
ctx.setNode( text );
ctx.setStartPosition( ctx.getNode(), ctx.position() + 1 );
}
/**
* {@inheritDoc}
*/
public void endStage()
{
// Finalize current node
ParseContext ctx = ParseContext.currentContext();
if( ctx.position() > 0 )
{
Node node = ctx.getNode();
// Set the end position
node.setEnd( ctx.position() );
// Set the node value
node.setValue( ctx.getSource().substring( node.getStart(), node.getEnd() ) );
// If node length is > 0, add it to the parent
if( node.getEnd() > node.getStart() )
{
node.getParent().addChild( node );
}
}
}
/**
* {@inheritDoc}
*/
public void handle( char ch )
{
ParseContext ctx = ParseContext.currentContext();
switch( ctx.getStage() )
{
case TEXT: {
switch( ch )
{
// If we see a quote, check to see if it's a part of a
// parent attribute
case ('\''):
case ('"'): {
Node node = ctx.getNode();
Node parent = node.getParent();
if( parent instanceof Attribute )
{
Attribute attribute = (Attribute) parent;
if( ch == attribute.getAttributeDelimiter() )
{
ctx = ParseContext.pop();
ctx.setStage( Stage.ATTRIBUTE_END, 0 );
}
}
break;
}
case ('<'): {
// Valid XML or JSP tag start?
String lookahead = ctx.lookahead( 2 );
if( lookahead.length() == 2 )
{
char nextChar = lookahead.charAt( 1 );
if( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!/%_:?".indexOf( nextChar ) != -1 )
{
ctx.setParser( TAG_PARSER, Stage.TAG_START, 0 );
}
}
break;
}
}
break;
}
}
}
}
private static final class Counter
{
private int m_pos;
private final List<Integer> lineBreaks = new ArrayList<Integer>();
Counter()
{
m_pos = 0;
}
public void increment()
{
m_pos++;
}
public int position()
{
return m_pos;
}
}
private enum Stage
{
/**
* Parsing the left-angle bracket that starts the node (&lt;).
*/
TAG_START,
/** Parsing any text outside of a tag or element. */
TEXT,
/**
* Characters that name a node. For tags, the node name begins after the
* opening left bracket (&lt;) and ends before the first whitespace
* character. For attributes, the name begins after any whitespace and
* ends before the equals (=) character.
*/
NAME,
/**
* Whitespace between the node or directive name and the first
* attribute, and between attributes.
*/
WHITESPACE,
/** Any text inside of a scriptlet, JSP comment, or JSP declaration. */
CODE_OR_COMMENT,
/** Finishing an attribute. */
ATTRIBUTE_END
}
/**
* Encapsulates the current state of document parsing.
*/
private static class ParseContext
{
private static final Stack<ParseContext> CONTEXT_STACK = new Stack<ParseContext>();
private static ParseContext CURRENT_CONTEXT;
public static ParseContext currentContext()
{
return CURRENT_CONTEXT;
}
public static ParseContext initialContext( JspDocument doc, String source )
{
CONTEXT_STACK.clear();
ParseContext context = new ParseContext( doc, source, new Counter() );
CURRENT_CONTEXT = context;
// Set the default stage to TEXT
Text text = new Text( doc );
context.setNode( text );
context.setStartPosition( text, 0 );
context.m_stage = Stage.TEXT;
context.m_parser = TEXT_PARSER;
// Return the init'ed context
return context;
}
private Parser m_parser = null;
private Node m_node = null;
private final Counter m_counter;
private final String m_source;
private Stage m_stage = Stage.TEXT;
private JspDocument m_doc;
private Map<Stage, Integer> m_markers = new HashMap<Stage, Integer>();
private ParseContext( JspDocument doc, String source, Counter counter )
{
super();
m_doc = doc;
m_source = source;
m_counter = counter;
}
/**
* Returns an arbitrary number of characters ahead of the current
* position, starting with the current character. For example, if the
* current position returned by {@link #position()} is 70,
* <code>lookahead(3)</code> returns the characters at positions 70,
* 71 and 72. If the supplied length causes the lookahead position to
* exceed the length of the source string, the substring is truncated
* appropriately.
*
* @param length the number of characters to return
* @return the substring
*/
public String lookahead( int length )
{
int startPos = position();
int endPos = startPos + length > m_source.length() ? endPos = m_source.length() : startPos + length;
return m_source.substring( startPos, endPos );
}
/**
* Returns an arbitrary number of characters behind the current
* position, starting with the current character. For example, if the
* current position returned by {@link #position()} is 70,
* <code>lookbehind(3)</code> returns the characters at positions 68,
* 69 and 70. If the supplied length causes the lookbehind position to
* exceed the length of the source string, the substring is truncated
* appropriately.
*
* @param length the number of characters to return
* @return the substring
*/
public String lookbehind( int length )
{
int endPos = position() + 1;
int startPos = endPos - length < 0 ? 0 : endPos - length;
return m_source.substring( startPos, endPos );
}
public Counter getCounter()
{
return m_counter;
}
public JspDocument getDocument()
{
return m_doc;
}
/**
* Retrieves the position of the marker set for the current stage (as
* set by {@link #mark()}). If no marker was set, this method returns
* {@link Node#POSITION_NOT_SET}.
*
* @param stage the stage for which the marker position is desired
* @return the position of the marker.
*/
public int getMarkerForStage( Stage stage )
{
Integer mark = m_markers.get( stage );
return mark == null ? Node.POSITION_NOT_SET : mark.intValue();
}
public Node getNode()
{
return m_node;
}
/**
* Sets the active Parser without changing the stage or incrementing the
* position. Calling this method does <em>not</em> execute either the
* {@link Parser#beginStage()} or {@link Parser#endStage()} methods.
*
* @param parser the parser to set
*/
public void setParser( Parser parser )
{
m_parser = parser;
}
public ParseContext getParentContext()
{
return CONTEXT_STACK.size() == 0 ? null : CONTEXT_STACK.peek();
}
public Parser getParser()
{
return m_parser;
}
public String getSource()
{
return m_source;
}
public Stage getStage()
{
return m_stage;
}
/**
* Returns <code>true</code> if one or more ParseContexts have been
* pushed onto the stack (via {@link #push()}); <code>false</code>
* otherwise.
*
* @return the result
*/
public boolean hasParentContext()
{
return CONTEXT_STACK.size() > 0;
}
public void incrementPosition()
{
// Reset the line/column counters if we encounter linebreaks
int pos = position();
char currentChar = m_source.charAt( pos );
char nextChar = ( pos +1 ) < m_source.length() ? m_source.charAt( pos + 1 ) : currentChar;
if( currentChar == '\r' && nextChar == '\n' )
{
m_counter.lineBreaks.add( pos );
m_counter.increment();
}
else if ( currentChar == '\r' || currentChar == '\n' )
{
m_counter.lineBreaks.add( pos );
}
m_counter.increment();
}
/**
* Pops the topmost ParseContext from the stack and replaces the
* JspParser's current ParseContext with it. Before popping the
* ParseContext, the current stage's {@link Parser#endStage()} method is
* executed.
*/
public static ParseContext pop()
{
// Run the endStage method for the current stage
ParseContext ctx = ParseContext.currentContext();
ctx.getParser().endStage();
ctx = CONTEXT_STACK.pop();
CURRENT_CONTEXT = ctx;
return ctx;
}
public int position()
{
return m_counter.position();
}
/**
* Pushes this ParseContext onto the stack and replaces the JspParser's
* current ParseContext with a new one. This method does <em>not</em>
* call the current Parser's {@link Parser#endStage()} method.
*
* @return the new ParseContext
*/
public static ParseContext push()
{
ParseContext oldCtx = currentContext();
CONTEXT_STACK.push( oldCtx );
ParseContext ctx = new ParseContext( oldCtx.m_doc, oldCtx.m_source, oldCtx.m_counter );
CURRENT_CONTEXT = ctx;
// Set the default stage to TEXT
Node text = new Text( ctx.m_doc );
ctx.setNode( text );
ctx.m_stage = Stage.TEXT;
ctx.m_parser = TEXT_PARSER;
return ctx;
}
/**
* Sets the current Node to a supplied node. If the start position is
* not set, it set to the current position. If not already set, the
* parent is set to the parent ParseContext's node, or else the
* JspDocument's root node.
*
* @param node the node to set. If <code>null</code>, the current
* node is removed
*/
public void setNode( Node node )
{
m_node = node;
if( node == null )
{
return;
}
// If start not set, set it now
if( node.getStart() == Node.POSITION_NOT_SET )
{
setStartPosition( node, position() );
}
// Set the parent relationship
if( node.getParent() == null )
{
if( getParentContext() == null )
{
node.setParent( m_doc.getRoot() );
}
else
{
node.setParent( getParentContext().getNode() );
}
}
}
/**
* Sets the current lifecycle stage and mark its position, without
* resetting the current parser. The "marker" is set relative to the
* current position (as reported by {@link #position()}. The marker can
* be retrieved later via
* {@link #getMarkerForStage(org.apache.wiki.ui.stripes.JspParser.Stage)}.
*
* @param stage
* @param increment the number of characters ahead of the current
* position to set the marker
*/
public void setStage( Stage stage, int increment )
{
// Set the new stage.
m_stage = stage;
m_markers.put( getStage(), position() + increment );
}
/**
* <p>
* Ends the current {@link Stage} and starts another, and sets a marker
* for the next character position. When this method is called, the
* active {@link Parser} is finalized for the previous stage by calling
* its {@link Parser#endStage()} method. Then, the current text parser
* is replaced with the one that corresponds to the correct one for the
* new stage, and a marker is set at the next position. Finally, the new
* stage is initialized by calling the new parser's
* {@link Parser #beginStage()} method.
* </p>
*
* @param parser the parser to set
* @param stage the stage to set
* @increment the number of characters ahead to set the marker for the
* new stage. Must be zero or higher.
*/
public void setParser( Parser parser, Stage stage, int increment )
{
// Finish the parser's current stage
if( parser != null && m_parser != null )
{
m_parser.endStage();
}
// Set the new stage and set a marker at the next position
m_stage = stage;
m_markers.put( stage, position() + increment );
// Replace the parser and start it up
if( parser != null )
{
m_parser = parser;
m_parser.beginStage();
}
}
/**
* Sets the start line/column positions for a supplied node, based on
* the position in the ParseContext.
*
* @param node the node to set
* @param the position to set
*/
private void setStartPosition( Node node, int pos )
{
// Set the start, end, linebreak
node.setStart( pos );
int lastLineBreakPos = m_counter.lineBreaks.size() == 0 ? Node.POSITION_NOT_SET : m_counter.lineBreaks.get( m_counter.lineBreaks.size() - 1 );
node.setLine( m_counter.lineBreaks.size() + 1 );
node.setColumn( pos - lastLineBreakPos );
}
/**
* Sets the end position for a supplied node, based on the current
* position in the ParseContext plus one.
*
* @param node to set
*/
private void setEndPosition( Node node )
{
ParseContext ctx = ParseContext.currentContext();
int pos = ctx.position() + 1;
if( pos > ctx.getSource().length() )
{
pos = ctx.getSource().length();
}
node.setEnd( pos );
}
}
/**
* Constructs a new JspDocument.
*/
public JspParser()
{
super();
}
/**
* Parses a JSP file, supplied as a String, into Nodes.
*
* @param source the JSP file contents
*/
public JspDocument parse( String source )
{
// Initialize the cached document, m_source, and stack variables
JspDocument doc = new JspDocument();
// Create new parse context and put it on the stack
ParseContext ctx = ParseContext.initialContext( doc, source );
// Parse the file, character by character
int pos = ctx.position();
while ( pos < source.length() )
{
ctx = ParseContext.currentContext();
char currentChar = source.charAt( pos );
// Is the current character whitespace?
boolean isWhitespace = Character.isWhitespace( currentChar );
char ch = isWhitespace ? ' ' : currentChar; // For case statements
// Handle the current character
Parser parser = ctx.getParser();
parser.handle( ch );
// Increment the character position
ctx.incrementPosition();
pos = ctx.position();
}
// Finalize the last node and return the parsed JSP
Node node = ctx.getNode();
node.setEnd( ctx.position() );
if( node.getType() == NodeType.TEXT )
{
ctx.getParser().endStage();
}
return doc;
}
}