You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jspwiki.apache.org by aj...@apache.org on 2008/09/28 14:59:41 UTC
svn commit: r699812 [1/2] - in
/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH: ./
tests/com/ecyrd/jspwiki/ui/stripes/
Author: ajaquith
Date: Sun Sep 28 05:59:41 2008
New Revision: 699812
URL: http://svn.apache.org/viewvc?rev=699812&view=rev
Log:
Additional refactoring of JSP transformer code. One more batch to go...
Added:
incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/Markup.java
- copied, changed from r696723, incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JspMarkup.java
Removed:
incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JspMarkup.java
Modified:
incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/build.xml
incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/AbstractNode.java
incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JSPWikiJspTransformer.java
incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JSPWikiJspTransformerTest.java
incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JspMigrator.java
incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JspParser.java
incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JspParserTest.java
incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/NodeType.java
incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/StripesJspTransformer.java
incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/StripesJspTransformerTest.java
Modified: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/build.xml
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/build.xml?rev=699812&r1=699811&r2=699812&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/build.xml (original)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/build.xml Sun Sep 28 05:59:41 2008
@@ -279,6 +279,21 @@
</javadoc>
</target>
+
+ <!-- ============================================================== -->
+
+ <!-- This target migrates Stripes JSPs -->
+
+ <target name="migrate" depends="jar,jartests">
+ <mkdir dir="build/migrated" />
+ <java classname="com.ecyrd.jspwiki.ui.stripes.JspMigrator" fork="yes" maxmemory="512m">
+ <classpath>
+ <path refid="path.tests" />
+ </classpath>
+ <arg value="src/webdocs" />
+ <arg value="build/migrated" />
+ </java>
+ </target>
<!-- ============================================================== -->
Modified: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/AbstractNode.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/AbstractNode.java?rev=699812&r1=699811&r2=699812&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/AbstractNode.java (original)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/AbstractNode.java Sun Sep 28 05:59:41 2008
@@ -240,7 +240,7 @@
public boolean isJspNode()
{
return m_type == NodeType.JSP_COMMENT || m_type == NodeType.JSP_DECLARATION || m_type == NodeType.JSP_EXPRESSION
- || m_type == NodeType.SCRIPTLET || m_type == NodeType.JSP_DIRECTIVE || m_type == NodeType.UNRESOLVED_JSP_TAG;
+ || m_type == NodeType.SCRIPTLET || m_type == NodeType.JSP_DIRECTIVE;
}
/**
Modified: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JSPWikiJspTransformer.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JSPWikiJspTransformer.java?rev=699812&r1=699811&r2=699812&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JSPWikiJspTransformer.java (original)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JSPWikiJspTransformer.java Sun Sep 28 05:59:41 2008
@@ -18,33 +18,18 @@
// For all HTML tags...
if( node.isHtmlNode() )
{
- Tag tag = (Tag)node;
-
+ Tag tag = (Tag) node;
+
// Check any form or stripes:form elements
if( "form".equals( tag.getName() ) || "stripes:form".equals( tag.getName() ) )
{
- // Change "accept-charset" or "acceptcharset" values to UTF-8
- Attribute attribute = tag.getAttribute( "accept-charset" );
- if ( attribute == null )
- {
- attribute = tag.getAttribute( "acceptcharset" );
- }
- if( attribute != null )
- {
- message( attribute, "Changed value to \"UTF-8\"." );
- attribute.setValue( "UTF-8" );
- }
-
- // Remove onsubmit() attribute and warn the user
- attribute = tag.getAttribute( "onsubmit" );
- if( attribute != null )
- {
- String value = attribute.getValue();
- message( attribute, "Removed JavaScript call \"" + value + "\". REASON: it probably does not work with Stripes." );
- tag.removeAttribute( attribute );
- }
+ processFormTag( tag );
+ }
+ else if( "fmt:setBundle".equals( tag.getName() ) )
+ {
+ removeSetBundle( tag );
}
-
+
// Advise user about <input type="hidden"> tags
boolean isTypeHidden = false;
isTypeHidden = "stripes:form".equals( tag.getName() );
@@ -56,16 +41,61 @@
if( isTypeHidden )
{
Attribute hidden = tag.getAttribute( "name" );
- message( hidden, "NOTE: hidden form input \"" + hidden.getValue() +"\" should probably correspond to a Stripes ActionBean getter/settter. Refactor?" );
+ message( hidden, "NOTE: hidden form input \"" + hidden.getValue()
+ + "\" should probably correspond to a Stripes ActionBean getter/settter. Refactor?" );
}
-
+
// Tell user about <wiki:Messages> tags.
- if ( "wiki:Messages".equals( tag.getName() ) )
+ if( "wiki:Messages".equals( tag.getName() ) )
{
- message( tag, "Consider using <stripes:errors> tags instead of <wiki:Messages> for displaying validation errors." );
+ message( tag,
+ "Consider using <stripes:errors> tags instead of <wiki:Messages> for displaying validation errors." );
}
}
}
}
+ /**
+ * Removes the <fmt:setBundle> tag and advises the user.
+ *
+ * @param tag the tag to remove
+ */
+ private void removeSetBundle( Tag tag )
+ {
+ Node parent = tag.getParent();
+ parent.removeChild( tag );
+ message( tag, "Removed <fmt:setBundle> tag because it is automatically set in web.xml." );
+ }
+
+ /**
+ * For <form> and <stripes:form> tags, changes
+ * <code>accept-charset</code> or <code>acceptcharset</code> attribute
+ * value to "UTF-8", and removes any <code>onsubmit</code> function calls.
+ *
+ * @param tag the form tag
+ */
+ private void processFormTag( Tag tag )
+ {
+ // Change "accept-charset" or "acceptcharset" values to UTF-8
+ Attribute attribute = tag.getAttribute( "accept-charset" );
+ if( attribute == null )
+ {
+ attribute = tag.getAttribute( "acceptcharset" );
+ }
+ if( attribute != null )
+ {
+ message( attribute, "Changed value to \"UTF-8\"." );
+ attribute.setValue( "UTF-8" );
+ }
+
+ // Remove onsubmit() attribute and warn the user
+ attribute = tag.getAttribute( "onsubmit" );
+ if( attribute != null )
+ {
+ String value = attribute.getValue();
+ message( attribute, "Removed JavaScript call \"" + value + "\". REASON: it probably does not work with Stripes." );
+ tag.removeAttribute( attribute );
+ }
+ }
+
}
Modified: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JSPWikiJspTransformerTest.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JSPWikiJspTransformerTest.java?rev=699812&r1=699811&r2=699812&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JSPWikiJspTransformerTest.java (original)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JSPWikiJspTransformerTest.java Sun Sep 28 05:59:41 2008
@@ -50,6 +50,28 @@
assertEquals( "method", attribute.getName() );
assertEquals( "POST", attribute.getValue() );
}
+
+ public void testSetBundle() throws Exception
+ {
+ String s = "<fmt:setBundle basename=\"templates.default\"/>\n<form method=\"POST\" onsubmit=\"return Wiki.submitOnce(this);\" />";
+ JspDocument doc = new JspParser().parse( s );
+
+ // Should be 3 nodes: 2 HTML + 1 text
+ assertEquals( 3, doc.getNodes().size() );
+ assertEquals( 3, doc.getRoot().getChildren().size() );
+
+ // Run the transformer
+ m_transformer.transform( m_sharedState, doc );
+
+ // Now, should be only 2 nodes now because the <fmt:setBundle> tag was removed
+ assertEquals( 2, doc.getNodes().size() );
+ assertEquals( 2, doc.getRoot().getChildren().size() );
+ Node node = doc.getNodes().get( 0 );
+ assertEquals( NodeType.TEXT, node.getType() );
+ node = doc.getNodes().get( 1 );
+ assertEquals( "form", node.getName() );
+ }
+
public static Test suite()
{
Modified: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JspMigrator.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JspMigrator.java?rev=699812&r1=699811&r2=699812&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JspMigrator.java (original)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JspMigrator.java Sun Sep 28 05:59:41 2008
@@ -44,6 +44,8 @@
throw new IllegalArgumentException( "Source and destination cannot be the same." );
}
JspMigrator migrator = new JspMigrator();
+ migrator.addTransformer( new StripesJspTransformer() );
+ migrator.addTransformer( new JSPWikiJspTransformer() );
try
{
migrator.migrate( src, dest );
Modified: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JspParser.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JspParser.java?rev=699812&r1=699811&r2=699812&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JspParser.java (original)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/tests/com/ecyrd/jspwiki/ui/stripes/JspParser.java Sun Sep 28 05:59:41 2008
@@ -6,721 +6,1023 @@
* Parser that reads JSP document and constructs a {@link JspDocument} with the
* results.
*/
-class JspParser
+public class JspParser
{
- private static final class Counter
- {
- private int m_pos;
+ private static final ParserDelegate TEXT_PARSER = new TextParser();
- Counter()
- {
- m_pos = 0;
- }
+ private static final JspTagParser JSP_TAG_PARSER = new JspTagParser();
- public void increment()
+ private static final ParserDelegate HTML_TAG_PARSER = new HtmlTagParser();
+
+ private static final AttributeParser ATTRIBUTE_PARSER = new AttributeParser();
+
+ public static class AttributeParser extends ParserDelegate
+ {
+ public void beginStage()
{
- m_pos++;
}
- public int position()
+ public void endStage()
{
- return m_pos;
}
- }
- private enum NodeLifecycle
- {
- TAG_RESOLUTION,
- /**
- * Characters after the directive tag start (<) and whitespace, but
- * before whitespace that delimit the attributes.
- */
- /**
- * Characters after the opening left bracket (<), but before
- * whitespace that delimit the attributes.
- */
- TAG_NAME,
/**
- * After <@ that determines that it's a JSP element, but before we
- * know for sure what it is.
+ * Handles {@link NodeLifecycle#TAG_WHITESPACE},
+ * {@link NodeLifecycle#ATTRIBUTE_EQUALS},
+ * {@link NodeLifecycle#ATTRIBUTE_VALUE}.
*/
- JSP_DIRECTIVE_NAME,
- /**
- * Whitespace between the node name and the first attribute, and between
- * attributes.
- */
- BETWEEN_ATTRIBUTES,
- /**
- * Characters that name an attribute, after whitespace but before the
- * equals (=) character.
- */
- ATTRIBUTE_NAME,
- /**
- * When the current character is the equals (=) character that separates
- * the attribute name and value.
- */
- ATTRIBUTE_EQUALS,
- /**
- * The opening quote, closing quote, and all characters in between, that
- * denote an attribute's value.
- */
- ATTRIBUTE_VALUE,
- /** Any outside of a tag or element (i.e., part of a text node). */
- TEXT_NODE,
- /** Any text inside of a scriptlet, JSP comment, or JSP declaration. */
- CODE_OR_COMMENT,
- }
+ public void handle( char ch )
+ {
+ ParseContext ctx = ParseContext.currentContext();
+ int pos = ctx.position();
- /**
- * Encapsulates the current state of document parsing.
- */
- private static class ParseContext
- {
- private Node m_node = null;
+ switch( ctx.getStage() )
+ {
+ // Whitespace between node name and first attribute, or
+ // between attributes.
+ case TAG_WHITESPACE: {
+ switch( ch )
+ {
+ case (' '): {
+ break;
+ }
+ case ('/'): {
+ // If we see /, switch back to HTML tag parser
+ ctx.setParser( HTML_TAG_PARSER, NodeLifecycle.TAG_WHITESPACE );
+ break;
+ }
+ case ('%'): {
+ // Ignore the % because we might be in a JSP
+ // directive/element
+ break;
+ }
+ case ('>'): {
+ // Switch back to HTML tag parser so its endStage()
+ // method is called
+ ctx.setParser( HTML_TAG_PARSER );
+ ctx.setParser( TEXT_PARSER, NodeLifecycle.PARSING_TEXT );
+ break;
+ }
+ default: {
+ // Create new attribute
+ Attribute attribute = new Attribute( ctx.getDocument() );
+ attribute.setParent( ctx.getNode() );
+ ctx.setAttribute( attribute );
+ ctx.setStage( NodeLifecycle.ATTRIBUTE_NAME );
+ }
+ }
+ break;
+ }
- private Attribute m_attribute = null;
+ // Characters that name an attribute, after whitespace but
+ // before equals (=).
+ case ATTRIBUTE_NAME: {
+ if( ch == '=' )
+ {
+ // Set the attribute name
+ Attribute attribute = ctx.getAttribute();
+ attribute.setName( ctx.getSource().substring( attribute.getStart(), pos ) );
+ ctx.setStage( NodeLifecycle.ATTRIBUTE_EQUALS );
+ }
+ break;
+ }
+
+ // The equals (=) that separates the attribute name and
+ // value.
+ case ATTRIBUTE_EQUALS: {
+ if( ch == '\'' || ch == '\"' )
+ {
+ // Save the quote delimiter for later
+ Attribute attribute = ctx.getAttribute();
+ attribute.setAttributeDelimiter( ch );
+
+ // Push current ParseContext and Node onto stack
+ ctx = ctx.push();
+ ctx.pushNode( attribute );
- private Counter m_counter = null;;
+ // Assign new text node to ParseContext, set as child of
+ // attribute
+ Text text = new Text( ctx.getDocument() );
+ text.setParent( ctx.getParentNode() );
+ ctx.setNode( text );
+ ctx.setStartPosition( text, ctx.position() );
+ ctx.setParser( TEXT_PARSER, NodeLifecycle.PARSING_TEXT );
+ }
+ break;
+ }
+ }
+ }
- private NodeLifecycle m_stage = NodeLifecycle.TEXT_NODE;
+ }
- public ParseContext( Counter counter )
+ public static class HtmlTagParser extends ParserDelegate
+ {
+ public void beginStage()
{
- m_counter = counter;
}
-
- private Map<NodeLifecycle,Integer> m_markers = new HashMap<NodeLifecycle,Integer>();
- /**
- * Sets a "marker" for the current stage (as reported by
- * {@link #getStage()} at the current position (as reported by
- * {@link #position()}. The marker can be retrieved later via
- * {@link #getMarker(com.ecyrd.jspwiki.ui.stripes.JspParser.NodeLifecycle)}.
- * Callers may place only one marker per lifecycle stage. Generally,
- * markers are used to set character positions that are important to
- * retrieve later, for example the position of the left angle-bracket
- * during the {@link NodeLifecycle#TAG_RESOLUTION} stage.
- */
- public void mark()
+ public void endStage()
{
- m_markers.put( getStage(), position() );
+ ParseContext ctx = ParseContext.currentContext();
+ Node node = ctx.getNode();
+
+ // Finalize the node type if it is still undefined
+ if( node.getType() == NodeType.UNRESOLVED_HTML_TAG )
+ {
+ char lastCh = ctx.getSource().charAt( ctx.position() - 1 );
+ if( lastCh == '/' )
+ {
+ node.setType( NodeType.HTML_COMBINED_TAG );
+ }
+ else
+ {
+ // If no /, it's an HTML start tag, and new nodes should be
+ // children of it
+ node.setType( NodeType.HTML_START_TAG );
+ ctx.pushNode( node );
+ }
+ }
+
+ // 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 );
+ }
}
/**
- * 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.
+ * Handles {@link NodeLifecycle#TAG_NAME}.
*/
- public int getMarker( NodeLifecycle stage )
+ public void handle( char ch )
{
- Integer mark = m_markers.get( stage );
- return mark == null ? Node.POSITION_NOT_SET : mark.intValue();
- }
+ ParseContext ctx = ParseContext.currentContext();
- public Attribute getAttribute()
- {
- return m_attribute;
- }
+ switch( ctx.getStage() )
+ {
+ // After < but before whitespace that delimit attributes.
+ case TAG_NAME: {
+ switch( ch )
+ {
+ // If current character is whitespace, set the name and
+ // move
+ // to attributes stage
+ case (' '): {
+ finalizeTagName();
+ ctx.setParser( ATTRIBUTE_PARSER );
+ ctx.setStage( NodeLifecycle.TAG_WHITESPACE );
+ break;
+ }
- public Counter getCounter()
- {
- return m_counter;
- }
+ // Right angle bracket == end of the node
+ case ('>'): {
+ finalizeTagName();
+ ctx.setParser( TEXT_PARSER, NodeLifecycle.PARSING_TEXT );
+ break;
+ }
+ }
+ break;
+ }
- public Node getNode()
- {
- return m_node;
- }
+ // Whitespace immediately before >
+ case TAG_WHITESPACE: {
+ switch( ch )
+ {
+ case ('>'): {
+ ctx.setParser( TEXT_PARSER, NodeLifecycle.PARSING_TEXT );
+ break;
+ }
+ }
+ break;
+ }
- public NodeLifecycle getStage()
- {
- return m_stage;
- }
+ case CODE_OR_COMMENT: {
+ switch( ch )
+ {
+ // Terminating %> means the end of the comment
+ case ('>'): {
+ Node node = ctx.getNode();
+ String lookbehind = ctx.lookbehind( 3 );
+ if( lookbehind.equals( NodeType.HTML_COMMENT.getTagEnd() ) )
+ {
+ // Set the end position
+ node.setEnd( ctx.position() + 1 );
- public void incrementPosition()
- {
- m_counter.increment();
+ // 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, NodeLifecycle.PARSING_TEXT );
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
}
- public int position()
+ private void finalizeTagName()
{
- return m_counter.position();
+ ParseContext ctx = ParseContext.currentContext();
+ Node node = ctx.getNode();
+ int nameStart = node.getStart() + node.getType().getTagStart().length();
+ int nameEnd = ctx.position();
+ if( nameEnd - nameStart > 0 )
+ {
+ if( ctx.getSource().charAt( nameEnd - 1 ) == '/' )
+ {
+ nameEnd--;
+ }
+ node.setName( ctx.getSource().substring( nameStart, nameEnd ) );
+ }
}
+ }
- public void setAttribute( Attribute attribute )
+ public static class JspTagParser extends ParserDelegate
+ {
+ public void beginStage()
{
- m_attribute = attribute;
}
- public void setNode( Node node )
+ public void endStage()
{
- m_node = node;
}
- public void setStage( NodeLifecycle stage )
+ /**
+ * Handles {@link NodeLifecycle#TAG_WHITESPACE},
+ * {@link NodeLifecycle#TAG_NAME},
+ * {@link NodeLifecycle#CODE_OR_COMMENT}.
+ */
+ public void handle( char ch )
{
- m_stage = stage;
- }
- }
+ ParseContext ctx = ParseContext.currentContext();
+ Node node = ctx.getNode();
- private final List<Integer> lineBreaks = new ArrayList<Integer>();
+ switch( ctx.getStage() )
+ {
+ // Whitespace between node name and first attribute, or
+ // between attributes.
+ case TAG_WHITESPACE: {
+ switch( ch )
+ {
+ case (' '): {
+ break;
+ }
+ default: {
+ // If directive name not set, start new ParseContext (and set marker)
+ if ( node.getName() == null )
+ {
+ ctx = ctx.push();
+ ctx.setParser( JSP_TAG_PARSER );
+ ctx.setStage( NodeLifecycle.TAG_NAME ); // Sets the marker too
+ }
+
+ // Otherwise, it's the start of a new attribute
+ else
+ {
+ }
+ }
+ }
+ break;
+ }
+ // Characters that supply the JSP directive name.
+ case TAG_NAME: {
+ if( ch == ' ' )
+ {
+ int nameStart = ctx.getMarker( NodeLifecycle.TAG_NAME ); // Retrieve the marker
+ ctx = ctx.pop();
+ Node directive = ctx.getNode();
+ directive.setName( ctx.getSource().substring( nameStart, ctx.position() ) );
+ ctx.setParser( ATTRIBUTE_PARSER );
+ ctx.setStage( NodeLifecycle.TAG_WHITESPACE );
+ }
+ break;
+ }
- private JspDocument doc = null;
+ case CODE_OR_COMMENT: {
+ switch( ch )
+ {
+ // Terminating %> means the end of the scriptlet
+ case ('>'): {
+ String lookbehind = ctx.lookbehind( 2 );
+ if( lookbehind.equals( NodeType.SCRIPTLET.getTagEnd() ) )
+ {
+ // Set the end position
+ node.setEnd( ctx.position() + 1 );
- protected Stack<ParseContext> contextStack = new Stack<ParseContext>();
+ // Set the value
+ NodeType type = node.getType();
+ node.setValue( ctx.getSource().substring( node.getStart() + type.getTagStart().length(),
+ node.getEnd() - type.getTagEnd().length() ) );
- private Stack<Node> nodeStack = new Stack<Node>();
+ // If node length is > 0, add it to the parent
+ if( node.getEnd() > node.getStart() )
+ {
+ node.getParent().addChild( node );
+ }
+ ctx.setParser( TEXT_PARSER, NodeLifecycle.PARSING_TEXT );
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
- private String m_source;
+ }
- /** The current parsing context. */
- private ParseContext context;
+ }
- /**
- * Constructs a new JspDocument.
- */
- public JspParser()
+ public static abstract class ParserDelegate
{
- super();
+ public abstract void beginStage();
+
+ public abstract void endStage();
+
+ public abstract void handle( char ch );
}
- /**
- * Parses a JSP file, supplied as a String, into Nodes.
- *
- * @param m_source the JSP file contents
- */
- public JspDocument parse( String source )
+ public static class TextParser extends ParserDelegate
{
- // Initialize the cached document, m_source, and stack variables
- this.doc = new JspDocument();
- m_source = source;
- contextStack.clear();
- nodeStack.clear();
- nodeStack.push( doc.getRoot() );
- // Create new parse context and put it on the stack
- context = new ParseContext( new Counter() );
- initText( context.position() );
+ public void beginStage()
+ {
+ ParseContext ctx = ParseContext.currentContext();
+ int pos = ctx.position() + 1;
- // Initialize parser delegates
- ParserDelegate textParser = new TextParser();
+ // Create new Text
+ Text text = new Text( ctx.getDocument() );
- // Parse the file, character by character
- for( char currentChar : source.toCharArray() )
+ // Set parent relationship
+ text.setParent( ctx.getParentNode() );
+ ctx.setNode( text );
+ ctx.setAttribute( null );
+
+ // Set the start, end, linebreak
+ ctx.setStartPosition( text, pos );
+ }
+
+ public void endStage()
{
- // Is the current character whitespace?
- boolean isWhitespace = Character.isWhitespace( currentChar );
- char ch = isWhitespace ? ' ' : currentChar; // For case statements
- int pos = context.position();
+ // Finalize current node
+ ParseContext ctx = ParseContext.currentContext();
+ if( ctx.position() > 0 )
+ {
+ Node node = ctx.getNode();
- switch( context.getStage() )
+ // 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 );
+ }
+ }
+ }
+
+ /**
+ * Handles {@link NodeLifecycle#PARSING_TEXT}.
+ */
+ public void handle( char ch )
+ {
+ ParseContext ctx = ParseContext.currentContext();
+ switch( ctx.getStage() )
{
- // Part of a text node.
- case TEXT_NODE: {
- textParser.handle( ch, context );
+ case PARSING_TEXT: {
switch( ch )
{
// If we see a quote, check to see if it's a part of a
// parent attribute
case ('\''):
case ('"'): {
- if( contextStack.size() > 0 )
+ if( ctx.hasParentContext() )
{
- Attribute parentAttribute = contextStack.peek().getAttribute();
- if( parentAttribute != null && ch == parentAttribute.getAttributeDelimiter() )
+ Attribute attribute = ctx.getParentContext().getAttribute();
+ if( attribute != null && ch == attribute.getAttributeDelimiter() )
{
- // Finish the current text node and attach
- // it to the parent attribute
- finalizeText();
-
- // Restore the parent ParseContext and Node
- context = contextStack.pop();
- nodeStack.pop();
+ // Pop the ParseContext (and run its
+ // endStage method) and Node
+ ctx = ctx.pop();
+ ctx.popNode();
// Finish the parent attribute
- finalizeAttribute();
+ Node node = attribute.getParent();
+ attribute.setEnd( ctx.position() + 1 );
+ if( node.isHtmlNode() )
+ {
+ ((Tag) node).addAttribute( attribute );
+ }
+ else if( node.getType() == NodeType.JSP_DIRECTIVE )
+ {
+ ((JspDirective) node).addAttribute( attribute );
+ }
+ ctx.setAttribute( null );
+ ctx.setParser( ATTRIBUTE_PARSER, NodeLifecycle.TAG_WHITESPACE );
}
}
break;
}
case ('<'): {
- // Finalize current node and start a new
- // (unresolved) one
- finalizeText();
- initTag( NodeType.UNRESOLVED, context.position() );
- context.setStage( NodeLifecycle.TAG_RESOLUTION );
- break;
- }
- }
- break;
- }
- case TAG_RESOLUTION: {
- Node node = context.getNode();
- switch( node.getType() )
- {
+ // Figure out what this tag is
+ String lookahead = ctx.lookahead( 4 );
+ JspDocument doc = ctx.getDocument();
- case UNRESOLVED: {
- switch( ch )
+ // <%- means hidden JSP comment
+ if( lookahead.startsWith( NodeType.JSP_COMMENT.getTagStart() ) )
{
- // If <%, it's a JSP element
- case ('%'): {
- // Re-initialize the node as a JSPMarkup
- // node
- initJspMarkup();
- break;
- }
-
- // If </, it's an HTML end tag
- case ('/'): {
- // Re-initialize the node as an end tag
- initTag( NodeType.HTML_END_TAG, node.getStart() );
- context.setStage( NodeLifecycle.TAG_NAME );
- break;
- }
-
- // If < plus space, it's just ordinary
- // (albeit sloppy) markup
- case (' '): {
- // Re-initialize the node as a text node
- initText( node.getStart() );
- context.setStage( NodeLifecycle.TEXT_NODE );
- break;
- }
-
- // Any other char means its HTML start tag
- // or combined tag
- default: {
- node.setType( NodeType.UNRESOLVED_HTML_TAG );
- context.setStage( NodeLifecycle.TAG_NAME );
- }
+ ctx.setParser( JSP_TAG_PARSER, NodeLifecycle.CODE_OR_COMMENT );
+ initNode( new Markup( doc, NodeType.JSP_COMMENT ) );
}
- break;
- }
- // If JSP element, next character narrows it down
- case UNRESOLVED_JSP_TAG: {
- switch( ch )
- {
- // Dash after <% means hidden JSP
- // comment
- case ('-'): {
- node.setType( NodeType.JSP_COMMENT );
- context.setStage( NodeLifecycle.CODE_OR_COMMENT );
- break;
- }
+ // <%! means JSP declaration
+ else if( lookahead.startsWith( NodeType.JSP_DECLARATION.getTagStart() ) )
+ {
+ ctx.setParser( JSP_TAG_PARSER, NodeLifecycle.CODE_OR_COMMENT );
+ initNode( new Markup( doc, NodeType.JSP_DECLARATION ) );
+ }
- // Bang after <% means JSP
- // declaration
- case ('!'): {
- node.setType( NodeType.JSP_DECLARATION );
- context.setStage( NodeLifecycle.CODE_OR_COMMENT );
- break;
- }
+ // <%= means JSP expression
+ else if( lookahead.startsWith( NodeType.JSP_EXPRESSION.getTagStart() ) )
+ {
+ ctx.setParser( JSP_TAG_PARSER, NodeLifecycle.CODE_OR_COMMENT );
+ initNode( new Markup( doc, NodeType.JSP_EXPRESSION ) );
+ }
- // Equals after <% means JSP
- // expression
- case ('='): {
- node.setType( NodeType.JSP_EXPRESSION );
- context.setStage( NodeLifecycle.CODE_OR_COMMENT );
- break;
- }
+ // <%@ means JSP directive
+ else if( lookahead.startsWith( NodeType.JSP_DIRECTIVE.getTagStart() ) )
+ {
+ ctx.setParser( JSP_TAG_PARSER, NodeLifecycle.TAG_WHITESPACE );
+ initNode( new JspDirective( doc ) );
+ }
- // At-sign after <% means JSP
- // directive
- case ('@'): {
- // Re-initialize the node as a JspDirective
- initJspDirective();
- context.setStage( NodeLifecycle.BETWEEN_ATTRIBUTES );
- break;
- }
+ // <!-- means HTML comment
+ else if( lookahead.startsWith( NodeType.HTML_COMMENT.getTagStart() ) )
+ {
+ ctx.setParser( HTML_TAG_PARSER, NodeLifecycle.CODE_OR_COMMENT );
+ initNode( new Markup( doc, NodeType.HTML_COMMENT ) );
+ }
- // Whitespace after <% means
- // scriptlet
- case (' '): {
- node.setType( NodeType.SCRIPTLET );
- context.setStage( NodeLifecycle.CODE_OR_COMMENT );
- break;
+ // Whitespace after <% means
+ // scriptlet
+ else if( lookahead.startsWith( NodeType.SCRIPTLET.getTagStart() ) )
+ {
+ if( lookahead.length() >= 3 && Character.isWhitespace( lookahead.charAt( 2 ) ) )
+ {
+ ctx.setParser( JSP_TAG_PARSER, NodeLifecycle.CODE_OR_COMMENT );
+ initNode( new Markup( doc, NodeType.SCRIPTLET ) );
}
}
- break;
- }
- }
- break;
- }
- case CODE_OR_COMMENT: {
- switch( ch )
- {
- // Terminating %> means the end of the scriptlet
- case ('>'): {
- if( source.charAt( pos - 1 ) == '%' )
+ // If </, it's an HTML end tag
+ else if( lookahead.startsWith( NodeType.HTML_END_TAG.getTagStart() ) )
{
- finalizeJspMarkup();
- initText( pos + 1 );
+ ctx.setParser( HTML_TAG_PARSER, NodeLifecycle.TAG_NAME );
+ initNode( new Tag( doc, NodeType.HTML_END_TAG ) );
}
- break;
- }
- }
- break;
- }
-
- // Characters that supply the JSP directive name.
- case JSP_DIRECTIVE_NAME: {
- if( isWhitespace )
- {
- Node node = context.getNode();
- Attribute directive = context.getAttribute();
- node.setName( m_source.substring( directive.getStart(), context.position() ) );
- context.setAttribute( null );
- context.setStage( NodeLifecycle.BETWEEN_ATTRIBUTES );
- }
- break;
- }
- // After < but before whitespace that delimit attributes.
- case TAG_NAME: {
- switch( ch )
- {
- // If current character is whitespace, set the name and
- // move
- // to attributes stage
- case (' '): {
- finalizeTagName();
- context.setStage( NodeLifecycle.BETWEEN_ATTRIBUTES );
- break;
- }
+ // If < plus space, it's just ordinary
+ // (albeit sloppy) markup
+ else if( "< ".equals( lookahead.subSequence( 0, 2 ) ) )
+ {
+ ctx.setStage( NodeLifecycle.PARSING_TEXT );
+ initNode( new Text( doc ) );
+ }
- // Right angle bracket == end of the node
- case ('>'): {
- finalizeTagName();
- finalizeNode( pos + 1 );
- initText( pos + 1 );
- break;
- }
- }
- break;
- }
+ // Any other char means its HTML start tag
+ // or combined tag
+ else
+ {
+ ctx.setParser( HTML_TAG_PARSER, NodeLifecycle.TAG_NAME );
+ initNode( new Tag( doc, NodeType.UNRESOLVED_HTML_TAG ) );
+ }
- // Whitespace between node name and first attribute, or
- // between attributes.
- case BETWEEN_ATTRIBUTES: {
- switch( ch )
- {
- case (' '): {
break;
}
- case ('/'): {
- // Ignore the / because we might be in HTML
- // combined tag
- break;
- }
- case ('%'): {
- // Ignore the % because we might be in a JSP
- // directive/element
- break;
- }
- case ('>'): {
- finalizeNode( pos + 1 );
- initText( pos + 1 );
- break;
- }
- default:
- initAttribute();
- }
- break;
- }
-
- // Characters that name an attribute, after whitespace but
- // before equals (=).
- case ATTRIBUTE_NAME: {
- if( ch == '=' )
- {
- Attribute attribute = context.getAttribute();
- attribute.setName( m_source.substring( attribute.getStart(), pos ) );
- context.setStage( NodeLifecycle.ATTRIBUTE_EQUALS );
- }
- break;
- }
-
- // The equals (=) that separates the attribute name and
- // value.
- case ATTRIBUTE_EQUALS: {
- if( ch == '\'' || ch == '\"' )
- {
- // Save the quote delimiter for later
- Attribute attribute = context.getAttribute();
- attribute.setAttributeDelimiter( ch );
-
- // Push current ParseContext and Node onto stack
- contextStack.push( context );
- nodeStack.push( attribute );
-
- // Create new context, with text node as child of
- // attribute
- context = new ParseContext( context.getCounter() );
- initText( context.position() + 1 );
- context.getNode().setParent( attribute );
}
break;
}
-
}
- // Reset the line/column counters if we encounter linebreaks
- if( currentChar == '\r' || currentChar == '\n' )
+ }
+
+ /**
+ * 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
+ */
+ private void initNode( Node node )
+ {
+ ParseContext ctx = ParseContext.currentContext();
+
+ // If HTML end tag, pop the node stack first
+ if( node.getType() == NodeType.HTML_END_TAG )
{
- lineBreaks.add( pos );
+ ctx.popNode();
}
- // Increment the character position
- context.incrementPosition();
+ // Set parent relationship
+ node.setParent( ctx.getParentNode() );
+ ctx.setNode( node );
+ ctx.setAttribute( null );
+
+ // Set the start, end, linebreak
+ ctx.setStartPosition( node, ctx.position() );
+
+ // Skip ahead if tag start > 1 char long
+ int increment = node.getType().getTagStart().length() - 1;
+ for( int i = 0; i < increment; i++ )
+ {
+ ctx.incrementPosition();
+ }
}
- // Finalize the last node and return the parsed JSP
- finalizeNode( context.position() );
- return doc;
}
- private void finalizeJspMarkup()
+ private static final class Counter
{
- Node node = context.getNode();
- NodeType type = node.getType();
+ private int m_pos;
- // Set the end position
- node.setEnd( context.position() + 1 );
+ Counter()
+ {
+ m_pos = 0;
+ }
- node.setValue( m_source
- .substring( node.getStart() + type.getTagStart().length(), node.getEnd() - type.getTagEnd().length() ) );
+ public void increment()
+ {
+ m_pos++;
+ }
- // If node length is > 0, add it to the parent
- if( node.getEnd() > node.getStart() )
+ public int position()
{
- node.getParent().addChild( node );
+ return m_pos;
}
}
- private void finalizeAttribute()
+ private enum NodeLifecycle
+ {
+ /** Parsing any text outside of a tag or element. */
+ PARSING_TEXT,
+ /**
+ * Characters after the opening left bracket (<), but before
+ * whitespace that delimit the attributes.
+ */
+ TAG_NAME,
+ /**
+ * Whitespace between the node or directive name and the first
+ * attribute, and between attributes.
+ */
+ TAG_WHITESPACE,
+ /**
+ * Characters that name an attribute, after whitespace but before the
+ * equals (=) character.
+ */
+ ATTRIBUTE_NAME,
+ /**
+ * When the current character is the equals (=) character that separates
+ * the attribute name and value.
+ */
+ ATTRIBUTE_EQUALS,
+ /**
+ * The opening quote, closing quote, and all characters in between, that
+ * denote an attribute's value.
+ */
+ ATTRIBUTE_VALUE,
+ /** Any text inside of a scriptlet, JSP comment, or JSP declaration. */
+ CODE_OR_COMMENT,
+ }
+
+ /**
+ * Encapsulates the current state of document parsing.
+ */
+ private static class ParseContext
{
- Attribute attribute = context.getAttribute();
- Node node = attribute.getParent();
- attribute.setEnd( context.position() + 1 );
- if( node.isHtmlNode() )
+ private static final Stack<Node> NODE_STACK = new Stack<Node>();
+
+ 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 )
{
- ((Tag) node).addAttribute( attribute );
+ NODE_STACK.clear();
+ CONTEXT_STACK.clear();
+ ParseContext context = new ParseContext( doc, source, new Counter() );
+ context.pushNode( doc.getRoot() );
+ CURRENT_CONTEXT = context;
+
+ // Set the default stage to TEXT
+ Text text = new Text( doc );
+ text.setParent( doc.getRoot() );
+ context.setNode( text );
+ context.setAttribute( null );
+ context.setStartPosition( text, 0 );
+ context.m_stage = NodeLifecycle.PARSING_TEXT;
+ context.m_parser = TEXT_PARSER;
+
+ // Return the init'ed context
+ return context;
}
- else if( node.getType() == NodeType.JSP_DIRECTIVE )
+
+ private ParserDelegate m_parser = null;
+
+ private final List<Integer> lineBreaks = new ArrayList<Integer>();
+
+ private Node m_node = null;
+
+ private Attribute m_attribute = null;
+
+ private final Counter m_counter;
+
+ private final String m_source;
+
+ private NodeLifecycle m_stage = NodeLifecycle.PARSING_TEXT;
+
+ private JspDocument m_doc;
+
+ private Map<NodeLifecycle, Integer> m_markers = new HashMap<NodeLifecycle, Integer>();
+
+ private ParseContext( JspDocument doc, String source, Counter counter )
{
- ((JspDirective) node).addAttribute( attribute );
+ super();
+ m_doc = doc;
+ m_source = source;
+ m_counter = counter;
}
- context.setAttribute( null );
- context.setStage( NodeLifecycle.BETWEEN_ATTRIBUTES );
- }
- private void finalizeText()
- {
- Node node = context.getNode();
+ /**
+ * 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 );
+ }
- // Set the end position
- node.setEnd( context.position() );
+ /**
+ * 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 );
+ }
- node.setValue( m_source.substring( node.getStart(), node.getEnd() ) );
+ public Node getParentNode()
+ {
+ return NODE_STACK.peek();
+ }
- // If node length is > 0, add it to the parent
- if( node.getEnd() > node.getStart() )
+ public Attribute getAttribute()
{
- node.getParent().addChild( node );
+ return m_attribute;
}
- }
- /**
- * Finalizes the current Tag (returned by {@link ParseContext#getNode()}),
- * adds it as a child to the parent node, and initializes a new {@link Text}
- * node that will begin at a specified character position. The parent node
- * is determined by taking the Node currently at the top of the internal
- * node stack.
- *
- * @param pos the desired start position for the new text node
- */
- private void finalizeNode( int pos )
- {
- Node node = context.getNode();
- NodeType type = node.getType();
+ public Counter getCounter()
+ {
+ return m_counter;
+ }
- // Set the end position
- node.setEnd( pos );
+ public JspDocument getDocument()
+ {
+ return m_doc;
+ }
- // Finalize the node type if it is still undefined
- if( type == NodeType.UNRESOLVED_HTML_TAG )
+ /**
+ * 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 getMarker( NodeLifecycle stage )
{
- char lastCh = m_source.charAt( pos - 2 );
- if( lastCh == '/' )
- {
- node.setType( NodeType.HTML_COMBINED_TAG );
- }
- else
+ 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 ParserDelegate#beginStage()} or
+ * {@link ParserDelegate#endStage()} methods.
+ *
+ * @param parser the parser to set
+ */
+ public void setParser( ParserDelegate parser )
+ {
+ m_parser = parser;
+ }
+
+ public ParseContext getParentContext()
+ {
+ return CONTEXT_STACK.peek();
+ }
+
+ public ParserDelegate getParser()
+ {
+ return m_parser;
+ }
+
+ public String getSource()
+ {
+ return m_source;
+ }
+
+ public NodeLifecycle 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
+ char currentChar = m_source.charAt( position() );
+ if( currentChar == '\r' || currentChar == '\n' )
{
- // If no /, it's an HTML start tag, and new nodes should be
- // children of it
- node.setType( NodeType.HTML_START_TAG );
- nodeStack.push( node );
+ lineBreaks.add( position() );
}
+
+ m_counter.increment();
}
- else if( type == NodeType.TEXT )
+
+ /**
+ * Sets a "marker" for the current stage (as reported by
+ * {@link #getStage()} at the current position (as reported by
+ * {@link #position()}. The marker can be retrieved later via
+ * {@link #getMarker(com.ecyrd.jspwiki.ui.stripes.JspParser.NodeLifecycle)}.
+ * Callers may place only one marker per lifecycle stage. Generally,
+ * markers are used to set character positions that are important to
+ * retrieve later.
+ */
+ public void mark()
{
- node.setValue( m_source.substring( node.getStart(), node.getEnd() ) );
+ m_markers.put( getStage(), position() );
}
- else if( node.isJspNode() )
+
+ /**
+ * 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 ParserDelegate#endStage()}
+ * method is executed.
+ */
+ public ParseContext pop()
{
- node.setValue( m_source.substring( node.getStart() + type.getTagStart().length(), node.getEnd()
- - type.getTagEnd().length() ) );
+ // Run the endStage method for the current stage
+ ParseContext ctx = ParseContext.currentContext();
+ ctx.getParser().endStage();
+
+ ctx = CONTEXT_STACK.pop();
+ CURRENT_CONTEXT = ctx;
+ return ctx;
}
- // If node length is > 0, add it to the parent
- if( node.getEnd() > node.getStart() )
+ public void popNode()
{
- node.getParent().addChild( node );
+ NODE_STACK.pop();
}
- }
- private void finalizeTagName()
- {
- Node node = context.getNode();
- int nameStart = node.getStart() + node.getType().getTagStart().length();
- int pos = context.position();
- if( pos - nameStart > 0 )
+ public int position()
{
- node.setName( m_source.substring( nameStart, pos ) );
+ return m_counter.position();
}
- }
- private void initAttribute()
- {
- Attribute attribute = new Attribute( doc );
- attribute.setParent( context.getNode() );
- context.setAttribute( attribute );
+ /**
+ * 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 ParserDelegate's {@link ParserDelegate#endStage()}
+ * method.
+ *
+ * @return the new ParseContext
+ */
+ public ParseContext push()
+ {
+ CONTEXT_STACK.push( this );
+ ParseContext context = new ParseContext( m_doc, m_source, m_counter );
+ CURRENT_CONTEXT = context;
- // Set the start, end, linebreak
- updatePosition( attribute, context.position() );
+ // Set the default stage to TEXT
+ context.setParser( TEXT_PARSER, NodeLifecycle.PARSING_TEXT );
+
+ return context;
+ }
- // Set the correct lifecycle stage
- Node node = context.getNode();
- if( node.getType() == NodeType.JSP_DIRECTIVE && node.getName() == null )
+ public void pushNode( Node node )
{
- context.setStage( NodeLifecycle.JSP_DIRECTIVE_NAME );
+ NODE_STACK.push( node );
}
- else
+
+ /**
+ * Sets the ParseContext's current attribute, and sets it start position
+ * if not null.
+ *
+ * @param attribute
+ */
+ public void setAttribute( Attribute attribute )
{
- context.setStage( NodeLifecycle.ATTRIBUTE_NAME );
+ m_attribute = attribute;
+ if( attribute != null )
+ {
+ setStartPosition( attribute, position() );
+ }
}
- }
- /**
- * Sets the start, end and line/column positions for a supplied node, based
- * on the position in the ParseContext.
- *
- * @param node the node to set
- */
- private void updatePosition( Node node, int pos )
- {
- // Set the start, end, linebreak
- node.setStart( pos );
- int lastLineBreakPos = lineBreaks.size() == 0 ? Node.POSITION_NOT_SET : lineBreaks.get( lineBreaks.size() - 1 );
- node.setLine( lineBreaks.size() + 1 );
- node.setColumn( pos - lastLineBreakPos );
- }
+ public void setNode( Node node )
+ {
+ m_node = node;
+ }
- /**
- * Factory method that returns a new JspDirective node.
- */
- private void initJspDirective()
- {
- // Create new JspDirective
- JspDirective node = new JspDirective( doc );
+ /**
+ * Sets the current lifecycle stage, without resetting the current
+ * parser.
+ *
+ * @param stage
+ */
+ public void setStage( NodeLifecycle stage )
+ {
+ // Set the new stage and set a marker at the current position
+ m_stage = stage;
+ mark();
+ }
+
+ /**
+ * <p>
+ * Ends the current {@link NodeLifecycle} and starts another, and sets a
+ * marker for the current position. When this method is called, the
+ * active {@link ParserDelegate} is finalized for the previous stage by
+ * calling its {@link ParserDelegate#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. Finally, the new
+ * stage is initialized by calling the new parser's
+ * {@link ParserDelegate #beginStage()} method.
+ * </p>
+ *
+ * @param stage
+ */
+ public void setParser( ParserDelegate parser, NodeLifecycle stage )
+ {
+ // Finish the parser's current stage
+ if( parser != null && m_parser != null )
+ {
+ m_parser.endStage();
+ }
- // Set parent relationship
- node.setParent( nodeStack.peek() );
- context.setNode( node );
- context.setAttribute( null );
+ // Set the new stage and set a marker at the current position
+ m_stage = stage;
+ mark();
+
+ // 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 = lineBreaks.size() == 0 ? Node.POSITION_NOT_SET : lineBreaks.get( lineBreaks.size() - 1 );
+ node.setLine( lineBreaks.size() + 1 );
+ node.setColumn( pos - lastLineBreakPos );
+ }
- // Set the start, end, linebreak
- updatePosition( node, context.position() - 2 );
+ /**
+ * 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 );
+ }
}
/**
- * Factory method that returns a new JspMarkup node.
+ * Constructs a new JspDocument.
*/
- private void initJspMarkup()
+ public JspParser()
{
- // Create new JspMarkup
- JspMarkup node = new JspMarkup( doc, NodeType.UNRESOLVED_JSP_TAG );
-
- // Set parent relationship
- node.setParent( nodeStack.peek() );
- context.setNode( node );
- context.setAttribute( null );
-
- // Set the start, end, linebreak
- updatePosition( node, context.position() - 1 );
+ super();
}
/**
- * Factory method that constructs and returns a new Tag. When constructed,
- * 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.
+ * Parses a JSP file, supplied as a String, into Nodes.
*
- * @param type the node type
- * @param pos the start position for the tag
+ * @param m_source the JSP file contents
*/
- private void initTag( NodeType type, int pos )
+ public JspDocument parse( String source )
{
- // Create new Tag
- Tag tag = new Tag( doc, type );
-
- // If HTML end tag, pop the node stack first
- if( tag.getType() == NodeType.HTML_END_TAG )
- {
- nodeStack.pop();
- }
+ // Initialize the cached document, m_source, and stack variables
+ JspDocument doc = new JspDocument();
- // Set parent relationship
- tag.setParent( nodeStack.peek() );
- context.setNode( tag );
- context.setAttribute( null );
+ // Create new parse context and put it on the stack
+ ParseContext ctx = ParseContext.initialContext( doc, source );
- // Set the start, end, linebreak
- updatePosition( tag, pos );
- }
+ // Parse the file, character by character
+ int pos = ctx.position();
+ while ( pos < source.length() )
+ {
+ ctx = ParseContext.currentContext();
+ char currentChar = source.charAt( pos );
- private void initText( int pos )
- {
- // Create new Text
- Text text = new Text( doc );
+ // Is the current character whitespace?
+ boolean isWhitespace = Character.isWhitespace( currentChar );
+ char ch = isWhitespace ? ' ' : currentChar; // For case statements
- // Set parent relationship
- text.setParent( nodeStack.peek() );
- context.setNode( text );
- context.setAttribute( null );
-
- // Set the start, end, linebreak
- updatePosition( text, pos );
- context.setStage( NodeLifecycle.TEXT_NODE );
- }
+ // Handle the current character
+ ParserDelegate parser = ctx.getParser();
+ parser.handle( ch );
- public static abstract class ParserDelegate
- {
- public abstract void handle( char ch, ParseContext context );
- }
+ // Increment the character position
+ ctx.incrementPosition();
+ pos = ctx.position();
+ }
- public static class TextParser extends ParserDelegate
- {
- public void handle( char ch, ParseContext context )
+ // Finalize the last node and return the parsed JSP
+ Node node = ctx.getNode();
+ node.setEnd( ctx.position() );
+ if( node.getType() == NodeType.TEXT )
{
+ node.setValue( ctx.getSource().substring( node.getStart(), node.getEnd() ) );
+ if( node.getEnd() > node.getStart() )
+ {
+ node.getParent().addChild( node );
+ }
}
+ return doc;
}
}