Package org.cfeclipse.cfml.parser

Source Code of org.cfeclipse.cfml.parser.CFParser

/*
* Created on Mar 21, 2004
*
* The MIT License
* Copyright (c) 2004 Oliver Tupman
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software
* is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package org.cfeclipse.cfml.parser;

import java.io.CharArrayReader;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.antlr.runtime.BitSet;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.IntStream;
import org.antlr.runtime.ParserRuleReturnScope;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.Token;
import org.antlr.runtime.tree.CommonTree;
import org.cfeclipse.cfml.CFMLPlugin;
import org.cfeclipse.cfml.dictionary.DictionaryManager;
import org.cfeclipse.cfml.parser.cfmltagitems.CfmlComment;
import org.cfeclipse.cfml.parser.cfmltagitems.CfmlTagCase;
import org.cfeclipse.cfml.parser.cfmltagitems.CfmlTagCatch;
import org.cfeclipse.cfml.parser.cfmltagitems.CfmlTagDefaultCase;
import org.cfeclipse.cfml.parser.cfmltagitems.CfmlTagElse;
import org.cfeclipse.cfml.parser.cfmltagitems.CfmlTagElseIf;
import org.cfeclipse.cfml.parser.cfmltagitems.CfmlTagFunction;
import org.cfeclipse.cfml.parser.cfmltagitems.CfmlTagIf;
import org.cfeclipse.cfml.parser.cfmltagitems.CfmlTagInvokeArgument;
import org.cfeclipse.cfml.parser.cfmltagitems.CfmlTagProperty;
import org.cfeclipse.cfml.parser.cfmltagitems.CfmlTagQueryParam;
import org.cfeclipse.cfml.parser.cfmltagitems.CfmlTagSet;
import org.cfeclipse.cfml.parser.docitems.AttributeItem;
import org.cfeclipse.cfml.parser.docitems.CfmlCustomTag;
import org.cfeclipse.cfml.parser.docitems.CfmlTagItem;
import org.cfeclipse.cfml.parser.docitems.DocItem;
import org.cfeclipse.cfml.parser.docitems.ScriptItem;
import org.cfeclipse.cfml.parser.docitems.TagItem;
import org.cfeclipse.cfml.preferences.ParserPreferenceConstants;
import org.cfeclipse.cfml.properties.CFMLPropertyManager;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.texteditor.MarkerUtilities;

import cfml.parsing.CFMLParser;
import cfml.parsing.cfscript.ANTLRNoCaseReaderStream;
import cfml.parsing.cfscript.CFScriptLexer;
import cfml.parsing.cfscript.CFScriptParser;
import cfml.parsing.cfscript.IErrorReporter;
import cfml.parsing.cfscript.poundSignFilterStream;
import cfml.parsing.cfscript.script.CFScriptStatement;


/*
Nasty, bastard test data for the parser:
<html>
  <cffunction name="test">
    <cfargument name="fred" test="test"/>
    <cfscript>
      WriteOutput("FREDFREDFRED");
    </cfscript>
    <cfif thisisatest is 1>
      <cfoutput>#fred#</cfoutput>
    </cfif>
  </cffunction>
  <cfscript>
    todaysDate = now();
   
    function doSomething(String doWhat) {
      var done = arguments.doWhat & " later";
      return done;
    }
    function returnSomething(theThing) {
      return theThing;
    }
  </cfscript>

  <cfset fred = 2/>
  <cfset bob = doSomething("build a parser") />
  <cfset test(fred)/>
  <cffunction name="test" >
    <cfset var woo="hoo" />
    <cfargument name="test" default="#WriteOutput("">"")#"/> <!--- I think this is valid! --->
  </cffunction>
  <cfoutput>
    This is a <b>test</b>
  </cfoutput>
  <table>
    <tr>
      <td style="<cfoutput>#somethinghere#</cfoutput>">asdfasdf</td>
      <td style="fred"></td>
    </td>
  </table>
</html>

*/

/**
* @author Oliver Tupman
*
* CFParser basically parses a CF file and builds the document tree from the result.
*
* Currently the document tree only contains CF elements as the HTML handling isn't implemented.
* HTML tags are matched, just not inserted into the document tree.
* I believe we need some kind of server-side/client-side view. The reasoning for this is that
* at the client-side one only sees HTML and no CF. But the documents we are viewing can contain
* embedded CF therefore breaking some of the attributes - we may not know the correct values
* because the embedded CF decides it.
*/
public class CFParser {
 
 
  /**
   * <code>REG_TAG</code> - the regular expression for matching tags. NB: Doesn't work on multi-line tags :(
   * TODO: Either modify the REG_TAG regex to match multiline tags or completely rewrite the tag matcher.
   */
  static protected final String REG_TAG = "<(\\w*)(.*)/{0,1}>";
  // the below matches multiple lines, but doesn't seem to do anything different
  //static protected final String REG_TAG = "(?s)<(\\w*)(.*)/{0,1}>";

  /**
   * <code>REG_ATTRIBUTES</code> - regular expression for getting the attributes out of a tag match.
   * (\w+\s?=\s?)?((((\w+ & )?\x22|\x27)((?!\4).|\4{2})*\4?(.*&.*)?))
   */

  /*
   * Various "almost" regexes, might have to use something dynamical-er :-/
   * this gets any double-quote/apos escaped strings ("woo ""hoo"" man!"  or ' ''blah''! ')
   *  (\x22|\x27)((?!\1).).*((\1{2})+.*)\1
   *
   * this one is super close -- gets any string var, just can't get the attrib name first :(
   *  (\x22|\x27)((?!\1).|\1{2})*\1
   * 
   *  (\w+)[\s=]+(((\x22|\x27|#)((?!\4).|\4{2})*\4))
   * 
   *  Holy crap, right?  Maybe better to split it?  gets: [blah] = "wee ""hoo"" you!", etc. but no &s 
   *  (\w+\s?=)?((\w+\s?\&\s?)?([\&?\w?\s?&\s?\&?]?\x22|\x27)((?!\4).|\4{2})*\4(\s?&?\s?\w+\s?&?\s)?)
   *  same, but better
   *  (\w+\s?=\s?)?((\x22|\x27)((?!\3).|\3{2})*\3?(.*&.*)?)
   *  bester (gets: [blah=] [fun & "wee ""hoo"" you!" & whatever])  we'll roll with this!
   *  (\w+\s?=\s?)?((((\w+ & )?\x22|\x27)((?!\4).|\4{2})*\4?(.*&.*)?))
   *  bestest (captures woo= #hoo# stuff too!)
   *  (\w+)[\s?=\s?]+?((((\w+ & )?\x22|\x27|#)((?!\4).|\4{2})*\4?(.*&.*)?))
   *  bestester?
   *  (\w+)[\s=]+((((\w+ & )?\x22|\x27|#)((?!\4).|\4{2})*\4?(\s*&\s*\w*)*))
   * 
   *  (\w+)[\s=]+(((\x22|\x27|#)((?!\4).|\4{2})*\4)(\s*&\s*\w+)*(\s&\s)*)
   * 
   *  Wow...
   *  (\w+)[\s=]+(((\x22|\x27|#)((?!\4).|\4{2})*\4)(\s*&\s*\w+)*(\s*\&\s*((\x22|\x27|#)((?!\4).|\4{2})*\4))*)
   *  At some point, parsing the string by hand will be faster :-)p
   *  (\w+)[\s=]+(((\x22|\x27|#)((?!\4).|\4{2})*\4)(\s*&\s*\w+)*(\s*\&\s*((\x22|\x27|#)((?!\4).|\4{2})*\4))*(\s*&\s*\w+)*)
   *  This handles everything, apparently, but ouch!
   *  (\w+)\s*=\s*(((\x22|\x27|#)((?!\4).|\4{2})*\4)((\s*&\s*\w+)*(\s*\&\s*((\x22|\x27|#)((?!\10).|\10{2})*\10))*(\s*&\s*\w+)*)*)
   */
  //static protected final String REG_ATTRIBUTES = "(\\w+)\\s*=\\s*(((\\x22|\\x27|#)((?!\\4).|\\4{2})*\\4)((\\s*&\\s*\\w+)*(\\s*\\&\\s*((\\x22|\\x27|#)((?!\\10).|\\10{2})*\\10))*(\\s*&\\s*\\w+)*)*)";

  // this was closest out of all so far
  //static protected final String REG_ATTRIBUTES = "(\\w+)?[\\s=]?((((\\w+ & )?\\x22|\\x27)((?!\\4).|\\4{2})*\\4?(.*&.*)?))";

//  static protected final String REG_ATTRIBUTES_BRACKETS= "(?<!\\\\)\\[(\\\\\\[|\\\\\\]|[^\\[\\]]|(?<!\\\\)\\[.*(?<!\\\\)\\])*(?<!\\\\)\\]";
  static protected final String REG_ATTRIBUTES_BRACKETS= "[^\\[]*(\\[.*\\])[^\\]]*"
//  static protected final String REG_ATTRIBUTES_BRACES = "(?<!\\\\)\\{(\\\\\\{|\\\\\\}|[^\\{\\}]|(?<!\\\\)\\{.*(?<!\\\\)\\})*(?<!\\\\)\\}";
  static protected final String REG_ATTRIBUTES_BRACES = "[^\\{]*(\\{.*\\})[^\\}]*";
  static protected final String REG_ATTRIBUTES_BB = "(?s)(\\w+)+\\s*+=\\s*+(" + REG_ATTRIBUTES_BRACKETS + "|" + REG_ATTRIBUTES_BRACES + ")";
  // unescaped: Curly ({}): (?<!\\)\{(\\\{|\\\}|[^\{\}]|(?<!\\)\{.*(?<!\\)\})*(?<!\\)\}
//  static protected final String REG_ATTRIBUTES = "(?s)(\\w++)\\s?=\\s?+((((\\w++ & )?\\x22|\\x27|#)((?!\\4).|\\4{2})*\\4?(.*&.*)?))";
  static protected final String REG_ATTRIBUTES = "(?si)(\\w+)[\\s=]+(((\\x22|\\x27|#)((?!\\4).|\\4{2})*\\4))";
  // unescaped: (\w++)[\s=]+((((\w++ & )?\x22|\x27|#)((?!\4).|\4{2})*\4?(.*&.*)?))
  static protected final int USRMSG_INFO     = 0x00;
  static protected final int USRMSG_WARNING   = 0x01;
  static protected final int USRMSG_ERROR    = 0x02;
 
  protected State parserState = null;
 
  protected IResource res = null;

  /** Tells the parser whether it should parse CFScript blocks or not. Pretty buggy at the moment */
  protected boolean parseCFScript = false;
  protected boolean reportErrors  = true;
 
  /**
   * Tells the parser whether it should be reporting the errors it finds.
   *
   * @param enable set to true to report errors to the Problems view or false to not.
   */
  public void setReportErrors(boolean enable) {
    this.reportErrors = enable;

  }
 
  /**
   * Tells the parser whether it should parse CFScript blocks or not
   * @param enable set to true to parse CFScript, false to not
   */
  public void setCFScriptParsing(boolean enable) {
    this.parseCFScript = enable;
  }
 
  /**
   * <code>parseDoc</code> - the document to parse. Could just use a string, but IDocument provides line number capabilities.
   */
  protected IDocument parseDoc = null;
  /**
   * <code>docFilename</code> - the pathname of the document, so we can stick messages into the Problems tasklist
   */
  protected IPath docFilename = null// Document file info... not working just yet.

  /**
   * <code>parseResult</code> - the resultant document tree.
   * <b>NB:</b> Currently the root node is called 'root' and has no real data, it's just a root node.
   */
  protected CFDocument parseResult = null// The end result of the parse.
 
  protected String data2Parse = "";
 
  /**
   * <code>getParseResult</code> - Get's the document tree from a parse
   * @return The CF document tree that results from calling <code>parseDoc()</code>
   */
  public CFDocument getParseResult()  {
    if(parseResult == null)
      //System.err.println("CFParser::getParseResult() - WARNING: parseResult is null!");
      ;
    if(parserState == null)
      //System.err.println("CFParser::getParseResult() - WARNING: parserState is null. This probably means that the parser has not been run.");
      ;
    return parseResult;
  }
 
  /**
   * <code>CFParser</code> Constructor without params.
   *
   * Just sets the parseDoc to null.
   *
   */
  public CFParser() {
    super();
    parseDoc = null;
    data2Parse = null;
  }
 
  private void setData2Parse(String data) {
      //SPIKE: Added the toLowerCase() as a quick hack to sort out the case sensitivity issues.
    //OLLIE: Stuck a generic call to set the data to parse to put the toLowerCase() in the same
    //       place. This enables better debugging of the actual cause of the problem.
  //  this.data2Parse = data.toLowerCase();
    this.data2Parse = data;
  }

  private void initOrgCFParser() {
    if (parser == null) {
      CFMLPropertyManager propertyManager = new CFMLPropertyManager();
      if (res == null) {
        IEditorPart editor = CFMLPlugin.getDefault().getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor();
        String dict = propertyManager.getCurrentDictionary(((IFileEditorInput) editor.getEditorInput()).getFile().getProject());
        parser = CFMLPlugin.newCFMLParser(dict);
      } else {
        String dict = propertyManager.getCurrentDictionary(res.getProject());
        parser = CFMLPlugin.newCFMLParser(dict);
      }
    }
  }
 
  /**
   * <code>CFParser</code> - Constructor.
   * 
   * @param doc2Parse - the IDocument doc to parse
   * @param filename - the pathname of the file so we can attempt to report stuff to the user
   */
  public CFParser(IDocument doc2Parse, IResource newRes)
  {
    parseDoc = doc2Parse;
    this.setData2Parse(this.parseDoc.get());
    res = newRes;
    initOrgCFParser();
  }

  public CFParser(String inData, IResource dataRes)
  {
    this.setData2Parse(inData);
    res = dataRes;
    initOrgCFParser();
  }
 
  /**
   * The line offsets within the parsed file.
   * Element 0 is line 1 and the value is the end of the line doc offset
   * Element 1 is line 2 and the value is the end of line 2 in a doc offset, and so on.
   */
  protected int [] lineOffsets = null;

  /**
   * Returns the line number that the document offset lies on.
   * @param docOffset
   * @return the line number, 0 indexed (i.e. 0 is line 1)
   */
  protected int getLineNumber(int docOffset)/* throws BadLocationException */
  {
    /*
     * Line number calculation simply goes through the lineOffset var looking for when the
     * line offset is greater than the docOffset passed.
     *
     * Should this method throw a BadLocationException???
     */
   
    //
    // Quick check to make sure it's not negative.
    if(docOffset < 0)
      return 0;//throw new BadLocationException("Doc offset is less than 0. Value was " + docOffset);
   
    for(int i = 0; i < lineOffsets.length; i++)
    {
      if(lineOffsets[i] > docOffset)
        return i;
    }
    //
    // We should never reach here. Throw a BadLocationException;
    //throw new BadLocationException("Doc offset " + docOffset + " is out of range. Max value is " + lineOffsets[lineOffsets.length]);
    return 0;
 
 
  /**
   * <code>userMessage</code> - Outputs a message at a certain tree depth to the console
   * @param indent - the indent to use
   * @param method - the method that is doing the calling, so we can keep track nicely
   * @param message - the message to give to the user.
   */
  protected void userMessage(int indent, String method, String message)
  {
    ////System.out..println("CFParser::userMessage() - " + Util.GetIndent(indent) + "CFParser::" + method + "() - " + message);
  }

  /**
   * <code>userMessage</code> - Outputs a message at a certain tree depth to the console.
   * Also allows the passing of message types so the method can decide whether to report
   * the message to the user or not.
   *
   * <b>NB:</b> USRMSG_ERRORs are inserted into the Problems tasklist <b>and will not disappear</b>
   * until you restart Eclipse.
   * TODO: Need to work out how to keep track of markers & invalidate them as the parser is run again & again.
   *
   * @param indent - the indent to output the message at
   * @param method - the method doing the calling
   * @param message - the message
   * @param msgType - the type of message. CFParser.USERMSG_* (i.e. CFParser.USERMSG_ERROR is an error to the user)
   */
  protected void userMessage(int indent, String method, String message, int msgType, ParseItemMatch match)
  {
    switch(msgType)
    {
      case USRMSG_WARNING:
        userMessage(indent, method, "WARNING: " + message);
        break;
      case USRMSG_ERROR:
        if(this.reportErrors && this.res != null) {

        /*
                  IWorkspaceRoot myWorkspaceRoot = CFMLPlugin.getWorkspace().getRoot();
                      parserState.addMessage(new ParseError(
                        getLineNumber(match.getStartPos()), match.getStartPos(), match.getStartPos() + match.getMatch().length(), match.getMatch(),
                        message
                      ));
        */
          try {         
            //
            // Not sure what the start & end positions are good for!
           
            //MarkerUtilities.createMarker(this.res, attrs, IMarker.PROBLEM);
            IMarker marker = this.res.createMarker("org.cfeclipse.cfml.parserProblemMarker");
            Map attrs = new HashMap();
            MarkerUtilities.setLineNumber(attrs, match.lineNumber+1);
            MarkerUtilities.setMessage(attrs, message);
            MarkerUtilities.setCharStart(attrs, match.startPos);
            MarkerUtilities.setCharEnd(attrs, match.endPos);
            marker.setAttributes(attrs);
            marker.setAttribute(IMarker.MESSAGE,message);
            MarkerUtilities.createMarker(this.res, attrs, IMarker.PROBLEM);
          }catch(CoreException excep) {
            userMessage(0, "userMessage", "ERROR: Caught CoreException when creating a problem marker. Message: \'" + excep.getMessage() + "\'");
            }
        }

        break;
      default:
      case USRMSG_INFO:
        userMessage(indent, method, message);
        break;
    }
  } 
 
 
  /**
   * <code>stripAttributes</code> - Strips the attributes from some data and puts them into a hash map
   * @param inData - the string data to get the attributes out of
   * @param match
   * @return array list of the attributes found. May contain duplicates
   */
  protected ArrayList stripAttributes(String inData, int lineNum, int offset, ParseItemMatch match)
  {
    ArrayList attributes = new ArrayList();
    Matcher matcher;
    Pattern pattern;
    String attributeName,attributeValue;
    pattern = Pattern.compile(REG_ATTRIBUTES,Pattern.CASE_INSENSITIVE);
    matcher = pattern.matcher(inData);
    if(inData.trim().endsWith("&")){
      userMessage(0,
          "stripAttributes", "Last attribute cannot be an ampersand",
          USRMSG_ERROR, match);     
    }

    while(matcher.find())
    {
     
        if (matcher.group(1) != null && matcher.group(2) != null) {
          AttributeItem newAttr;
         
          attributeName = matcher.group(1).trim();
          attributeValue = matcher.group(2).trim();
          attributeValue = attributeValue.substring(1,attributeValue.length()-1);
          newAttr = new AttributeItem(lineNum, offset + matcher.start(1), offset + matcher.end(1),
                          attributeName, attributeValue);
          attributes.add(newAttr);
          //System.out.println(attributeName + " = " +attributeValue);
        }
        else {
            System.out.println("CFParser::stripAttributes() - failed on |" + inData + "| with " + matcher.groupCount() + " matches");
//            for (int i = 0; i<=matcher.groupCount(); i++) {
//              System.out.println("Match " + i + " : " + matcher.group(i));
//            }
        }
    }
   
    return attributes;
  }

 
 
  /**
   * <code>handleClosingTag</code> - Handles a closing tag in the document
   * @return true - everything okay, false - error during parsing.
   * @param match the match that's a closer
   * @param matchStack - the stack of matched items
   */
  protected boolean handleClosingTag(ParseItemMatch match, Stack matchStack)
  {
  /*
    * Quite simply it works out what the item is. Then it grabs the top-most item
   * out of the matchStack, as that's the most recent opening tag. If the topItem
   * matches the closing tag we're okay. We get the next item off the stack which
   * is the tag's parent and add the opener & closer to the parent's children.
   * Then we push the parent back onto the stack to wait for another day.
   *
   * If the item does not match the most recent item we've a problem. At the moment
   * it reports an error and throws away the closer.
  */
    //System.out..println("CFParser::handleClosingTag() - " + Util.GetTabs(matchStack) + "Parser: Found closing tag of " + match.match);
    // Closing tag, so we attempt to match it to the current top of the stack.
    String closerName = match.match;
    //SPIKE: Added the toLowerCase()
    if(closerName.toLowerCase().indexOf("</cf") != -1)
    {
      // CF tag
        closerName = closerName.substring(2, closerName.length()-1);
     
       
        DocItem topItem = (DocItem)matchStack.peek();
       
      //System.out.println("Top item on stack is " + topItem.getName());
     
      if(topItem instanceof TagItem)
      { 
          // Look for hybrids at the top of the stack
          // and remove them if there is an opener below them.
          try {
        boolean foundCloser = false;
          ArrayList removals = new ArrayList();
          Object[] items = matchStack.toArray();
          //System.out.println("Looking on stack for opening " + closerName + ". Closer found on line: " + this.getLineNumber(match.getStartPos()));
          for (int i=items.length-1;i>0;i--) {
              if (items[i] instanceof TagItem) {
                  TagItem item = (TagItem)items[i];
                  //System.out.println("Checking " + item.getName());
               
                if (item.getName().equalsIgnoreCase(closerName)) {
                    //System.out.println("Found opener. Exiting loop.");
                    foundCloser = true;
                    break;
                } else if (item.isHybrid()) {
                    removals.add(item);
                }
              }
          }
          // If we found a closer, we want to remove any unclosed hybrids.
          if (foundCloser) {
     
              items = removals.toArray();
                DocItem parent = (DocItem)matchStack.get(items.length);
              for (int i=0;i<items.length;i++) {
                  TagItem item = (TagItem)items[i];
                  //System.out.println(item.getChildNodes().size() + " children need to be moved to " + parent.getName());
                  Object[] orphans = item.getChildNodes().toArray();
                  if (item instanceof CfmlCustomTag) {
                    ((CfmlCustomTag)item).hasCloser = false;
                    parent.addChild(item);
                    item.setParent(parent);
                  }
                  for (int j=0;j<orphans.length;j++) {
                      DocItem orphan = (DocItem)orphans[j];
                      //System.out.println("Moving " + orphan.getName() + " under " + parent.getName());
                      parent.addChild(orphan);
                      item.removeChild(orphan);
                  }
                  //System.out.println("Removing " + ((TagItem)items[i]).getName() + " from the stack." + " Current parent is " + parent.getName());
                  matchStack.remove(items[i]);
              }
             
              Iterator iter = parent.getChildNodes().iterator();
              while (iter.hasNext()) {
                DocItem di = (DocItem)iter.next();
                //System.out.println("Child: " + di.getName());
              }
          }
          else {
              //System.out.println("Opener not found on stack for " + closerName);
              //System.out.println(" ");
          }
         
          }
          catch (Exception e) {
              e.printStackTrace();
          }
         
        try {
          TagItem tempItem = new TagItem(match.lineNumber, match.startPos, match.endPos+1, match.match);
          ((TagItem)topItem).setMatchingItem(tempItem);
          } catch(Exception e){
          System.err.println("Caught exception: " + e.getMessage());
          e.printStackTrace();
        }
      }
     
      // Take the top item off the stack.
      topItem = (DocItem)matchStack.pop()// Should be the opening item for this closer
      //System.out..println("CFParser::handleClosingTag() - " + Util.GetTabs(matchStack) + "Parser: Does \'" + closerName + "\' match \'" + topItem.itemName + "\'");             
     
      //SPIKE: Made this case insensitive
      if(topItem.getName().compareToIgnoreCase(closerName) == 0)
      {
          //System.out.println("Found matcher at top of stack!!!");
        DocItem parentItem = (DocItem)matchStack.pop();
        try {
          parentItem.addChild(topItem);
        }
        catch(Exception excep)
        {
          //
          // Tell the user there was a problem and then rethrow the exception.
          //System.out..println("CFParser::handleClosingTag() - Caught exception \'" + excep.getMessage() + "\'");
          excep.printStackTrace();
          //System.out..println(excep.hashCode());
          throw (RuntimeException)excep.fillInStackTrace();
        }
        matchStack.push(parentItem);
      }
     
      else
      {
        //
        // If we're here that means that the top item of the match stack isn't the
        // opener of the current closer. Therefore we report this as an error
        // and finish parsing as we can't easily make sense of the rest of the document.
       
        ParseItemMatch tempMatch = new ParseItemMatch(match.match, match.startPos, match.endPos,
                          getLineNumber(match.startPos), MATCHER_NOTHING);
                         
        userMessage(matchStack.size(),
              "handleClosingTag", "Closing tag \'" + match.match +
              "\' does not match the current parent item: \'" + topItem.getName() + "\'",
              USRMSG_ERROR, tempMatch);
        return false;
        //
        // So we just push the top item back onto the stack, ready to be matched again.
        // NB: Note that this only copes with extra closing tags, not extra opening tags.
        //matchStack.push(topItem);
      }
    }
    return true;
  }
 
  private CFMLParser parser;
  private Stack<DocItem> matchStack;
  private CommonTokenStream tokenStream;

  private void addParseError(int lineNumber, int startPos, int endPos, String message) {
    IMarker marker;
    try {
      if (this.res != null) {
        marker = this.res.createMarker("org.cfeclipse.cfml.parserProblemMarker");
        Map attrs = new HashMap();
        MarkerUtilities.setLineNumber(attrs, lineNumber);
        MarkerUtilities.setMessage(attrs, message);
        MarkerUtilities.setCharStart(attrs, startPos);
        MarkerUtilities.setCharEnd(attrs, endPos);
        marker.setAttributes(attrs);
        marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
        marker.setAttribute(IMarker.MESSAGE, message);
      }
      // MarkerUtilities.createMarker(this.res, attrs, IMarker.PROBLEM);
    } catch (CoreException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

  }

  public class StdErrReporter implements IErrorReporter {

    private String getTokenName(int i) {
      Class<CFScriptParser> c = CFScriptParser.class;
        for (Field f : c.getDeclaredFields()) {
          int mod = f.getModifiers();
          if (Modifier.isStatic(mod) && Modifier.isPublic(mod) && Modifier.isFinal(mod)) {
            try {
              System.out.printf("%s = %d%n", f.getName(), f.get(null));
            if ((Integer) f.get(null) == i)
              return f.getName();
            } catch (IllegalAccessException e) {
              e.printStackTrace();
            }
          }
        }
        return "";
    }

    public void reportError(String error) {
      addParseError(0, 0, 0, "(no stracktrace): " + error);
    }

    public void reportError(RecognitionException re) {
      int offset = getLineOffset(re.line) + re.charPositionInLine;
      int endoffset = offset + 1;
      String tokenText = "";
      if (re.token != null) {
        endoffset += re.token.getText().length();
        if (re.token.getText() != null) {
          tokenText = re.token.getText();
        }
      }
      addParseError(re.line, offset, endoffset, re.getMessage() + " " + tokenText + " " + re.getUnexpectedType()
          + re.getClass().getName());
      //re.printStackTrace();
      // System.out.println(re.getMessage() + " line:" + re.line);
    }

    public void reportError(String[] tokenNames, RecognitionException re) {
      System.err.println(tokenNames);
      int offset = getLineOffset(re.line) + re.charPositionInLine;
      int endoffset = offset + 1;
      if (re.token != null) {
        endoffset += +re.token.getText().length();
      }
      addParseError(re.line, offset, endoffset, re.getMessage());
      // System.err.println(re.getMessage());
    }

    public void reportError(IntStream input, RecognitionException re, BitSet follow) {
      int offset = getLineOffset(re.line) + re.charPositionInLine;
      int endoffset = offset + 1;
      if (re.token != null) {
        endoffset += +re.token.getText().length();
      }
      addParseError(re.line, offset, endoffset, re.getMessage());
      System.err.println(re.getMessage());
    }
  }

  /**
   * <code>handleCFScriptBlock</code> - handles a CFScript'd block (at the moment it does nothing)
   * @param match - the match
   * @param matchStack - the stack of tag items. This will have all of the new Script items added to it.
   */
 
  protected void handleCFScriptBlock(ParseItemMatch match, Stack matchStack)
  {
    String mainData = match.match;
    mainData = mainData.toLowerCase().substring("<cfscript>".length());
    StringReader tempRdr =new StringReader(mainData);
    CFScriptStatement rootElement = null;

    if(!this.parseCFScript) {
      return;
    }
    parseCFScript(mainData, match.getLineNumber(), match.startPos);
    return;

  }

  protected void parseCFScript(String mainData) {
    if(mainData.length() > 9) {
      parseCFScript(mainData, 0, 0);
    }
  }

  protected void parseCFScript(String mainData, int addLines, int addOffset) {
    mainData = mainData.replaceFirst("<cfscript>", "");
    CFScriptStatement rootElement = null;

    if (!this.parseCFScript) {
      return;
    }
    if (parser == null) {
      initOrgCFParser();
    } else {
      parser.getMessages().removeAll(parser.getMessages());
      parserState.getMessages().removeAll(parserState.getMessages());
    }
   
    try {
      char[] scriptWithEndTag = mainData.toCharArray();
      poundSignFilterStream psfstream = new poundSignFilterStream(new CharArrayReader(scriptWithEndTag));
      ANTLRNoCaseReaderStream input = new ANTLRNoCaseReaderStream(psfstream); // +
      //ANTLRNoCaseReaderStream input = new ANTLRNoCaseReaderStream(new CharArrayReader(scriptWithEndTag)); // +
      CFScriptLexer lexer = new CFScriptLexer(input);
      tokenStream = new CommonTokenStream(lexer);
      CFScriptParser parser = new CFScriptParser(tokenStream);
      StdErrReporter errorReporter = new StdErrReporter();
      lexer.setErrorReporter(errorReporter);
      parser.setErrorReporter(errorReporter);
      try {
        // "</CFSCRIPT>")
        // )
        // );
        ParserRuleReturnScope r = parser.scriptBlock();
        CommonTree tree = (CommonTree) r.getTree();
        // first item is EOF, so get kids to get kids
        if (tree == null) {
          ScriptItem errorNode = new ScriptItem(0, 0, 0, "error");
          errorNode.setItemData("Error creating outline: " + parserState.getMessages().toString());
          addDocItemToTree(errorNode);
        } else {
          scriptItemTree(tree, matchStack.peek(), addLines, addOffset, true);
        }
      } catch (RecognitionException e) {
        e.printStackTrace();
      } catch (org.antlr.runtime.tree.RewriteEmptyStreamException e) {
        ScriptItem errorNode = new ScriptItem(0, 0, 0, "error");
        errorNode.setItemData("Error parsing: " + parserState.getMessages().toString());
        addDocItemToTree(errorNode);
      }
    } catch (cfml.parsing.cfscript.ParseException e) {
      parserState.addMessage(new ParseError(e.getLine() + addLines, e.getCol() + addOffset, e.getCol() + addOffset, e.getMessage()
          + e.getLine(), e
          .getMessage() + e.getLine() + ":" + e.getCol()));
      e.printStackTrace();
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

    return;
   
  }
 
  private Map<String, Integer> getTreeLineAndOffset(CommonTree tree, Boolean recurse) {
    HashMap<String, Integer> lineAndOffset = new HashMap<String, Integer>();
    while (tree.getTokenStartIndex() < 0) {
      tree = (CommonTree) tree.getChild(0);
    }
    Token startToken = tokenStream.get(tree.getTokenStartIndex());
    Token stopToken = tokenStream.get(tree.getTokenStopIndex());
    int length = 0;
    int endOffset = getLineOffset(stopToken.getLine()) + stopToken.getCharPositionInLine();
    int offset = getLineOffset(startToken.getLine()) + startToken.getCharPositionInLine();
    if (offset == -1) {
      offset = 0;
      endOffset = 0;
    } else if (endOffset == offset) {
      endOffset = endOffset + startToken.getText().length() - 1;
    } else {
      length = startToken.getText().length();
    }
    lineAndOffset.put("line", startToken.getLine());
    lineAndOffset.put("offset", offset);
    lineAndOffset.put("length", length);
    lineAndOffset.put("endoffset", endOffset);
    return lineAndOffset;
  }

  private void scriptItemTree(CommonTree tree, DocItem rootNode, int addLines, int addOffset, boolean root) {
    Map<String, Integer> lineAndOffset = getTreeLineAndOffset(tree, true);
    int startLine = lineAndOffset.get("line");
    int offset = lineAndOffset.get("offset");
    int startPos = offset;
    int endPos = lineAndOffset.get("endoffset");
    ScriptItem node = getScriptNode(tree, startLine, startPos, endPos);
    if (node == null) {
      // hidden nodes like function bodies ("{", etc.)
      node = (ScriptItem) rootNode;
    } else {
//      node.setItemData(node.getItemData() + " line:" + startLine + " length:" + lineAndOffset.get("length") + " offset:" + startPos
//          + " endoffset:" + endPos);
//      node.setItemData(node.getItemData() + " line:" + node.getLineNumber() + " length:" + lineAndOffset.get("length") + " offset:"
//          + node.getStartPosition() + " endoffset:" + node.getEndPosition());
      rootNode.addChild(node);
      node.setParent(rootNode);
    }
    if (tree.getChildCount() != 0) {
      for (Object kid : tree.getChildren()) {
        CommonTree leaf = (CommonTree) kid;
        scriptItemTree(leaf, node, startLine, endPos, root);
      }
    }
  }

  private int getLineOffset(int line) {
    if(line == 0) return 0;
    if (line == 1)
      return 0;
    return lineOffsets[line - 2];
  }

  private ScriptItem getScriptNode(CommonTree tree, int startLine, int startPos, int endPos) {
    ScriptItem childNode = null;
    switch (tree.getType()) {
    case CFScriptParser.FUNCTION_NAME:
    case CFScriptParser.FUNCTION_RETURNTYPE:
    case CFScriptParser.SLASH:
    case CFScriptParser.EMPTYARGS:
    case CFScriptParser.RIGHTCURLYBRACKET:
    case CFScriptParser.DOT:
    case CFScriptParser.EOF:
      break;
    case CFScriptParser.FUNCDECL:
    case CFScriptParser.FUNCTION:
      FunctionInfo funkInfo = new FunctionInfo(tree);
      funkInfo.setLineNumber(startLine);
      funkInfo.setStartPosition(startPos);
      // funkInfo.setStartPosition(startPos + funkInfo.getStartPosition());
      // funkInfo.setEndPosition(funkInfo.getEndPosition() + endPos);
      funkInfo.setEndPosition(endPos);
      childNode = funkInfo;
      break;
    case CFScriptParser.FUNCTION_PARAMETER:
      childNode = new ScriptItem(startLine, startPos, endPos, "ASTFunctionParameter");
      childNode.setItemData(getChildrenText(tree, ' '));
      break;
    case CFScriptParser.NEW:
      childNode = new ScriptItem(startLine, startPos, endPos, "ASTNewOperator");
      childNode.setItemData(getChildrenText(tree, '.'));
      break;
    case CFScriptParser.IDENTIFIER:
      if (tree.getParent().getType() == CFScriptParser.NEW) {
        childNode = new ScriptItem(startLine, startPos, endPos, "ASTComponent");
        childNode.setItemData("new " + tree.getText() + "()");
      } else if (tree.getParent().getType() == CFScriptParser.FUNCTIONCALL) {
        if (tree.getParent().getChild(0).getText().toLowerCase().equals("createobject")) {
          childNode = new ScriptItem(startLine, startPos, endPos, "ASTComponent");
          childNode.setItemData("createobject " + tree.getText() + "()");
        } else if (tree.getParent().getChild(0).getText().toLowerCase().equals("entitynew")) {
          childNode = new ScriptItem(startLine, startPos, endPos, "ASTComponent");
          childNode.setItemData("entitynew " + tree.getText() + "()");
        } else {
          childNode = new ScriptItem(startLine, startPos, endPos, "FunctionCall");
          childNode.setItemData(tree.getText());
        }
      } else if (tree.getParent().getType() == CFScriptParser.JAVAMETHODCALL) {
        childNode = new ScriptItem(startLine, startPos, endPos, "FunctionCall");
        childNode.setItemData(tree.getText());
      } else {
        childNode = new ScriptItem(startLine, startPos, endPos, "ASTIdentifier");
        childNode.setItemData(tree.getText());
      }
      break;
    case CFScriptParser.IMPLICITARRAY:
    case CFScriptParser.IMPLICITSTRUCT:
      childNode = new ScriptItem(startLine, startPos, endPos, "ASTUnassigned");
      childNode.setItemData(tree.getText());
      break;
    case CFScriptParser.INTEGER_LITERAL:
    case CFScriptParser.STRING_LITERAL:
      if (tree.getParent().getType() == CFScriptParser.FUNCTIONCALL) {
        if (tree.getParent().getChild(0).getText().toLowerCase().equals("createobject")) {
          childNode = new ScriptItem(startLine, startPos, endPos, "ASTComponent");
          childNode.setItemData(tree.getText());
        } else {
          childNode = new ScriptItem(startLine, startPos, endPos, "Literal");
          childNode.setItemData(tree.getText());
        }
      } else {
        childNode = new ScriptItem(startLine, startPos, endPos, "Literal");
        childNode.setItemData(tree.getText());
      }
      break;
    case CFScriptParser.FUNCTIONCALL:
    case CFScriptParser.JAVAMETHODCALL:
      childNode = new ScriptItem(startLine, startPos, endPos, "Call");
      childNode.setItemData("call");
      break;
    case CFScriptParser.RETURN:
      childNode = new ScriptItem(startLine, startPos, endPos, "return");
      childNode.setItemData(tree.getText());
      break;
    case CFScriptParser.VARLOCAL:
      // endPos += ((CommonToken) ((CommonTree) tree.getChild(2)).getToken()).getStopIndex() - 2;
      childNode = new ScriptItem(startLine, startPos, endPos, "ASTVarDeclaration");
      childNode.setItemData("var " + tree.getChild(0).getText() + tree.getChild(1).getText() + tree.getChild(2).getText());
      break;
    case CFScriptParser.COMPDECL:
    case CFScriptParser.COMPONENT:
      // endPos += ((CommonToken) ((CommonTree) tree.getChild(0)).getToken()).getStopIndex() - 2;
      childNode = new ScriptItem(startLine, startPos, endPos, "cfcomponent");
      childNode.setItemData("component");
      break;
    case CFScriptParser.LEFTCURLYBRACKET:
      break;
    case CFScriptParser.FUNCTION_ATTRIBUTE:
      childNode = new ScriptItem(startLine, startPos, endPos, "ASTFunctionAttribute");
      childNode.setItemData(tree.getChild(0).getText() + "=" + tree.getChild(1).getText());
      break;
    case CFScriptParser.COMPONENT_ATTRIBUTE:
      childNode = new ScriptItem(startLine, startPos, endPos, "ASTComponentAttribute");
      childNode.setItemData(tree.getChild(0).getText() + "=" + tree.getChild(1).getText());
      break;
    case CFScriptParser.PROPERTYSTATEMENT:
      childNode = new ScriptItem(startLine, startPos, endPos, "ASTPropertyStatement");
      childNode.setItemData(getChildrenText(tree, ' '));
      break;
    case CFScriptParser.ANDOPERATOR:
      childNode = new ScriptItem(startLine, startPos, endPos, "ASTAndOperator");
      childNode.setItemData(getChildrenText(tree, ' '));
      break;
    case CFScriptParser.CFMLFUNCTIONSTATEMENT:
      childNode = new ScriptItem(startLine, startPos, endPos, "ASTCFMLFunctionStatement");
      childNode.setItemData(getChildrenText(tree, ' '));
      break;
    case CFScriptParser.COLON:
      if (tree.getParent().getType() == CFScriptParser.COMPONENT_ATTRIBUTE) {
        childNode = new ScriptItem(startLine, startPos, endPos, "ASTComponentAttribute");
        childNode.setItemData(getChildrenText((CommonTree) tree.getParent(), ' '));
      } else if (tree.getParent().getType() == CFScriptParser.CASE) {
        childNode = new ScriptItem(startLine, startPos, endPos, "ASTSwitchStatement");
        childNode.setItemData(getChildrenText((CommonTree) tree.getParent(), ' '));
      } else if (tree.getParent().getType() == CFScriptParser.DEFAULT) {
        childNode = new ScriptItem(startLine, startPos, endPos, "ASTSwitchStatement");
        childNode.setItemData(getChildrenText((CommonTree) tree.getParent(), ' '));
      } else {
        childNode = new ScriptItem(startLine, startPos, endPos, "ASTAssignment");
        childNode.setItemData(tree.getChild(0).getText() + ":" + tree.getChild(1).getText());
      }
      break;
    case CFScriptParser.SWITCH:
      childNode = new ScriptItem(startLine, startPos, endPos, "ASTSwitchStatement");
      childNode.setItemData(tree.getText());
      break;
    case CFScriptParser.EQUALSOP:
      // unscoped assignment registers as EQUALSOP currently
      if (tree.getChildren() != null) {
        // startPos -= (tree.getChild(0).getText().length() + 1);
        // endPos += ((CommonToken) ((CommonTree) tree.getChild(1)).getToken()).getStopIndex();
        childNode = new ScriptItem(startLine, startPos, endPos, "ASTAssignment");
        if (tree.getChild(1).getText().toLowerCase().equals("new")) {
          childNode.setItemData(tree.getChild(0).getText() + '=' + getChildrenText((CommonTree) tree.getChild(1), '.'));
        } else {
          childNode.setItemData(tree.getChild(0).getText() + '=' + tree.getChild(1).getText());
        }
      }
      break;
    default:
      childNode = new ScriptItem(startLine, startPos, endPos, "ASTUnassigned");
      childNode.setItemData(tree.getText() + ":" + tree.getType());
      break;
    }
    return childNode;
  }

  private String getChildrenText(CommonTree tree, char delimiter) {
    StringBuffer sb = new StringBuffer();
    if (tree.getChildCount() != 0) {
      for (Object childOb : tree.getChildren()) {
        CommonTree child = (CommonTree) childOb;
        String text = ((CommonTree) child).getText();
        if (text.equals("EMPTYARGS"))
          text = ")";
        if (text.equals("=") && child.getChildCount() > 0)
          text = child.getChild(0).getText() + '=' + child.getChild(1).getText() + " ";
        if (text.length() != 0) {
          sb.append(text);
        }
      }
    } else {
      System.out.println("No children for " + tree.getText() + "!");
    }
    return sb.toString().trim();
  }

  /**
   * <code>handleHTMLTag</code> - Handles an HTML tag (does nothing at the moment.)<br/>
   * <b>NB:</b> HTML files will bugger up the parser as handleClosing doesn't handle closing HTML tags!
   * @param tagName - tag name with chevrons removed
   * @param match - the tag match made
   * @param matchStack
   * @param attrMap - map of attributes for this item
   * @param isACloser - whether the tag is a closer or not (or has been closed by the user)
   */
  protected void handleHTMLTag(String tagName, ParseItemMatch match, Stack matchStack, ArrayList attrList, boolean isACloser)
  {
    //System.err.println("CFParser::handleHTMLTag() - " +  Util.GetTabs(matchStack) + "Parser: Got an HTML tag called \'" + tagName + "\'. Ignoring for the moment");
  }
 
  /**
   * Returns an instance of a CfmlTagItem based upon the tag name provided.
   * Essentially this is a class factory... it would go in a separate class but I'm not
   * sure how best to do that. Gah, my poor Java knowledge :)
   *
   * @param tagName - tag name to match. Note this tag name should just be the cf-less name (e.g. else for &lt;cfelse&gt;)
   * @param match - the TagMatch data.
   * @param lineNum - line that the match occured on.
   * @return An instance of the matched tag.
   */
  // TODO: Make a class factory for CfmlTagItems...
  protected CfmlTagItem getNameBasedCfmlTag(String tagName, ParseItemMatch match, int lineNum)
  {
    //
    // There _so_ must be a better way of doing this, but what it is I'm not sure!
    if(tagName.compareToIgnoreCase("else") == 0)
      return new CfmlTagElse(lineNum, match.startPos, match.endPos, tagName);
    else if(tagName.compareToIgnoreCase("if") == 0)
      return new CfmlTagIf(lineNum, match.startPos, match.endPos, tagName);
    else if(tagName.compareToIgnoreCase("case") == 0)
      return new CfmlTagCase(lineNum, match.startPos, match.endPos, tagName);
    else if(tagName.compareToIgnoreCase("defaultcase") == 0)
      return new CfmlTagDefaultCase(lineNum, match.startPos, match.endPos, tagName);
    else if(tagName.compareToIgnoreCase("catch") == 0)
      return new CfmlTagCatch(lineNum, match.startPos, match.endPos, tagName);
    else if(tagName.compareToIgnoreCase("property") == 0)
      return new CfmlTagProperty(lineNum, match.startPos, match.endPos, tagName);
    else if(tagName.compareToIgnoreCase("elseif") == 0)
      return new CfmlTagElseIf(lineNum, match.startPos, match.endPos, tagName);
    else if(tagName.compareToIgnoreCase("queryparam") == 0)
      return new CfmlTagQueryParam(lineNum, match.startPos, match.endPos, tagName);
    else if(tagName.compareToIgnoreCase("invokeargument") == 0)
      return new CfmlTagInvokeArgument(lineNum, match.startPos, match.endPos, tagName);
    else if(tagName.compareToIgnoreCase("function") == 0)
      return new CfmlTagFunction(lineNum, match.startPos, match.endPos, tagName);
    else if(tagName.compareToIgnoreCase("set") == 0)
      return new CfmlTagSet(lineNum, match.startPos, match.endPos, tagName);
    else
      return new CfmlTagItem(lineNum, match.startPos, match.endPos, tagName);
  }
 
  /**
   * <code>handleCFTag</code> - Handles a opening CF tag.
   *
   * @param tagName - name of the tag
   * @param match - the TagMatch made
   * @param matchStack - match stack
   * @param attrList - map of attributes that are for this tag
   * @param isACloser - whether it's a self-closer
   */
  protected void handleCFTag(String tagName, ParseItemMatch match, Stack matchStack, ArrayList attrList, boolean isACloser)
  throws Exception
  {
    //
    // If a CF tag then we get it's CF tag name (i.e. <cffunction, CF tag name is 'function')
    // and init the new CfmlTagItem with that. We then check to see whether this type of tag
    // has a closing tag.
    // If it has it means that it is a branch element on the tree, so we pop it onto the stack.
    // If not then it's a child element and so we add it to the child list of the top element
    // of the stack.
     
      tagName = tagName.substring(1, tagName.length());
    TagItem newItem;
    //System.out.println("CFParser::handleCFTag found " + tagName);
   
   
    boolean singleQuoted = false;
    boolean doubleQuoted = false;
    char c;
    for (int i=0;i<tagName.length();i++) {
      c = tagName.charAt(i);
      if (c == '\'' && !doubleQuoted) {
        singleQuoted = !singleQuoted;
      }
      if (c == '\"' && !singleQuoted) {
        doubleQuoted = !doubleQuoted;
      }
      if (c == '<' && !singleQuoted && !doubleQuoted) {
          parserState.addMessage(new ParseError(
              getLineNumber(match.getStartPos()), match.getStartPos(), match.getStartPos() + match.getMatch().length(), match.getMatch(),
              "Invalid token \"" + c + "\" found in opening <b>"  + tagName + "</b> tag. The tag is probably missing a closing \">\""
            ));
         
          throw new FatalException("Fatal parser error. Unable to continue parsing past line " + getLineNumber(match.getStartPos()));
      }
    }
    //    }
       
    //  }
    //}
    //
    // First test to see whether we've found a custom tag. If so we do nothing fancy (yet).
    // Also tests to make sure it catches CFX tags.
    if(tagName.length() >= 3 && (tagName.charAt(2) == '_'
      || (
          (tagName.charAt(2) == 'x' || (tagName.charAt(2) == 'X'))
          && tagName.charAt(3) == '_')
        ))
    {

      newItem = new CfmlCustomTag(getLineNumber(match.startPos), match.startPos, match.endPos, tagName);
      newItem.setItemData(match.match);

    }
    else
    {
      newItem = getNameBasedCfmlTag(tagName, match, getLineNumber(match.startPos));
      newItem.initDictionary(DictionaryManager.getDictionary(DictionaryManager.CFDIC));
      newItem.setItemData(match.match);
    }

    newItem.addAttributes(attrList);
    addTagItemToTree(match, matchStack, isACloser, newItem);
  }

  private void addDocItemToTree(DocItem item) {
    DocItem topItem = (DocItem) matchStack.pop();
    topItem.addChild(item);
    matchStack.push(topItem);
  }

  private void addDocItemToTree(ParseItemMatch match, DocItem newItem)
  {
    if(newItem instanceof TagItem) {
      addTagItemToTree(match, matchStack, false, (TagItem)newItem);
      System.err.println("CFParser::addDocItemToTree() - A tag item has been passed. This is wrong but I\'ve passed it to addTagItemToTree as a non-closer");
      return;
    }
    addDocItemToTree(newItem);
  }
 
  /**
   * @param match
   * @param matchStack
   * @param isACloser
   * @param newItem
   */
  private void addTagItemToTree(ParseItemMatch match, Stack matchStack, boolean isACloser, TagItem newItem) {
    //
    //  Either the syntax dictionary says it closes itself or the user has specified it will
   
    try {
      if(newItem.hasClosingTag() && !isACloser)
      {  // Not a closing item, it's an opener so on the stack it goes.
        matchStack.push(newItem);
      }
      else
      {   // It's a closing item, so we get the parent item and add this item to it's children.
        DocItem top = (DocItem)matchStack.pop();

        top.addChild(newItem);
        matchStack.push(top);
     
    } catch(Exception anExcep) {
      parserState.addMessage(new ParseError(getLineNumber(match.startPos), match.startPos, match.endPos, match.match, "An unknown error occurred during parsing."));
      System.err.println("CFParser::handleCFTag() - Caught an exception during item popping. Exception was " + anExcep.getLocalizedMessage());
      anExcep.printStackTrace();
      ////System.out..println(anExcep.hashCode());
      throw (RuntimeException)anExcep.fillInStackTrace();
    }
  }

  /**
   *
   * @param tagName
   * @param matches
   * @param matchPos
   * @param isACloser
   * @return
   */
  boolean isTagACloser(String tagName, ArrayList matches, int matchPos, boolean isACloser)
  {
    if(tagName.compareToIgnoreCase("<cfinvoke") == 0)
    {
      if(((ParseItemMatch)matches.get(matchPos+1)).match.indexOf("invokeargument") == -1)
      {
        isACloser = true;
      }
      else
      {
        isACloser = false;
      }
     
    }
    return isACloser;
  }

  /**
   * <code>createDocTree</code> - Creates the document tree from the TagMatches made
   *
   * @param inData
   *
   * @param matches
   *            - the matches found previously
   * @return a document tree.
   *
   */
  /*
   * The document tree is created using two things:
   * 1) A match stack.
   * 2) A document tree
   *
   * The method loops through all of the matches. For every opening tag there must be a closing tag.
   * Therefore for every opening tag we take it and push it onto the stack. For every closing tag
   * we pop the most recent tag off, compare it with the closer. If there's a match it's the closing
   * tag for the opener. We add them to their parent's entry and carry on.
   * If they don't match then at present the closer is thrown away.
   *
   * Eventually we reach the end of the matches and that should mean - in theory - that we've got a
   * valid tree.
   *
   * Unfortunately this can easily not be the case due to any user errors. At the moment it just
   * carries on regardless of any major errors. Perhaps some higher-level routine should take care
   * of whether the document tree is valid or not. Perhaps the tree display?
   *
   * TODO: break open CFSET's and grab variable assignments.
   * TODO: somehow implement tag variable grabbing (i.e. from <cfquery>'s 'name' attribute) Should the tag object do it, or the parser?
   *
   */
  public CFDocument createDocTree(String inData)
  {
    ArrayList matches = parserState.getMatches();
    // System.out.println("=============> Beginning match dump" );
    // Util.dumpMatches(matches);
    // System.out.println("=============> Finishing match dump");
    CFDocument newDoc = new CFDocument();
    matchStack = new Stack();
    ArrayList rootElements = new ArrayList();
    TagItem rootItem = new TagItem(0, 0, 0, "Doc Root");
    int matchPos = 0;
    StringBuffer nonParsedTextBuffer = new StringBuffer();
    matchStack.push(rootItem);
    ParseItemMatch lastMatch = null;
    // little hackish way to detect cfscript based cfcs TODO: something better
    if (inData.split("(?i).*component[^>]+\\{").length > 1) {
      parseCFScript(inData);
      newDoc.setDocumentRoot(rootItem);
      return newDoc;
    }
    try {
     
      for(; matchPos < matches.size(); matchPos++)
      {
        ParseItemMatch match = (ParseItemMatch)matches.get(matchPos);
       
        String matchStr = match.match;
        /*
        if(lastMatch != null)
        {
            int difference = match.getStartPos() - lastMatch.getEndPos();
            System.out.println(lastMatch.getMatch() + " -> " + match.getMatch() + ": diff of : "+ difference);
            String nonParsedText = this.data2Parse.substring(lastMatch.getEndPos()+1, difference + lastMatch.getEndPos());
            System.out.println("-> Thinks this is non matched: \'" + nonParsedText + "\'");
            TextNode textNode = new TextNode(match.getLineNumber(), match.getStartPos(), match.getEndPos(), "#TEXT");
            textNode.setNodeText(nonParsedText);
            addDocItemToTree(match, matchStack, textNode);
        }
        */
        lastMatch = match;
       
        if(matchStr.charAt(0) == '<'// Funnily enough this should always be the case!
        {
          if(matchStr.charAt(1) == '/') {
            if(!handleClosingTag(match, matchStack)) {               
              parserState.addMessage(new ParseError(
                  getLineNumber(matchPos), matchPos, matchPos, "",
                  "Something in here is (probably) missing a closing tag or a closing \">\" and thus totally borking the parse!"
                ));
              break;
            }
          } else {
            // get just tag name, e.g. : <cffunction name="blah" becomes cffunction
            String tagName = matchStr.split("[\\s/>]")[0];
            tagName = "<"+tagName.substring(1, tagName.length()).toLowerCase();
            int tagEnd = matchStr.indexOf(tagName)+tagName.length();
            boolean isACloser = false;
            //
            // Find the end of the tag
            int currPos = 0;
            for(int quoteCount = 0; currPos < match.match.length(); currPos++) {
              char currChar = match.match.charAt(currPos);
              boolean inQuotes = (1 == quoteCount % 2);
              if(!inQuotes && currChar == '>') {
                break;
              }
              else if(currChar == '\"')
                quoteCount++;
            }

            //
            // Handle a self-closer (i.e. <cfproperty ... />
            String attributes = "";           
            if(match.match.charAt(currPos-1) == '/') {
              isACloser = true;
              if(match.match.length() - tagEnd >= 2)
                attributes = match.match.substring(tagEnd, match.match.length()-2); // minus one to strip the closing '/>'
            }
            else
              attributes = match.match.substring(tagEnd, match.match.length()-1); // minus one to strip the closing '>'

            switch(match.getMatchType())
            {
              case MATCHER_CFMLTAG:
                if(tagName.startsWith("<cfif") || tagName.startsWith("<cfelseif") || tagName.startsWith("<cfmodule")
                    || tagName.startsWith("<cfset"))               
                {
                  handleCFTag(tagName, match, matchStack, new ArrayList(), isACloser);
                } else {
                  handleCFTag(tagName, match, matchStack, stripAttributes(attributes, match.lineNumber, tagEnd, match), isACloser);                 
                }
                if((tagName.startsWith("<cfif") || tagName.startsWith("<cfelseif") || tagName.startsWith("<cfmodule")
                    || tagName.startsWith("<cfset"))
                    && attributes.trim().length() == 0)
                {
                    userMessage(0,
                        "stripAttributes", tagName + "> requires at least one attribute",
                        USRMSG_ERROR, match);     
                }

                break;
              case MATCHER_CFMLCOMMENT:
                //System.out.println("CFParser::createDocTree() - Got a CFML comment!");
                DocItem newComment = new CfmlComment(
                    match.getLineNumber(),
                    match.getStartPos(),
                    match.getEndPos(),
                    match.getMatch()
                );
               
                newComment.setItemData(match.getMatch());
              addDocItemToTree(match, newComment);
               
                break;
              case MATCHER_CFSCRIPT:
                tagName = tagName.substring(1, tagName.length());
                TagItem newItem;               
                newItem = getNameBasedCfmlTag(tagName.substring(0, "cfscript".length()), match, getLineNumber(match.startPos));
                newItem.initDictionary(DictionaryManager.getDictionary(DictionaryManager.CFDIC));
                newItem.setItemData("");
                addTagItemToTree(match, matchStack, isACloser, newItem);
                handleCFScriptBlock(match, matchStack);
                break;
              default:
                break;
            }
          }
        }
      }
      //newDoc.docTree = matchStack;
     
    }
    catch (FatalException e) {
      parserState.addMessage(new ParseError(
          getLineNumber(matchPos), matchPos, matchPos, "",
          e.getMessage()
        ));
    }
    catch(Exception anyException) {
      parserState.addMessage(new ParseError(
        getLineNumber(matchPos), matchPos, matchPos, "",
        "Doc tree creation: caught an unhandled exception: "
        + anyException.getMessage()
      ));
      //System.err.println(
      //  Util.GetTabs(matchStack) + "Parser: Caught an exception!"
      //  + anyException.getMessage()
      //);
      anyException.printStackTrace();
      ////System.out..println(anyException.hashCode());
    }
    handleUnclosedTags(matchStack,rootItem);
   
    newDoc.setDocumentRoot(rootItem);
    return newDoc;
  }
 
 
  private void handleUnclosedTags(Stack matchStack, DocItem defaultParent) {
    try {
      // If we've got more than one item left on the stack check if any of the remaining items are unclosed custom tags
      if (matchStack.size() > 1) {
        for (int i=0;i<matchStack.size();i++) {
          TagItem t = (TagItem)matchStack.peek();
          String tagName = t.getName();
          //System.out.println("Looking at " + tagName);
          // Look for tags that got left over.
          if (tagName.toLowerCase().startsWith("cf") ) {
            // Mark them as being self closers.
            if (tagName.toLowerCase().startsWith("cf_") || tagName.toLowerCase().startsWith("cfx_")) {
              ((CfmlCustomTag)t).hasCloser= false;
            }
            // Get the current parent of the tag.
            DocItem parent = t.getParent();
            if (parent == null) {
              parent = defaultParent;
            }
            // Don't make the thing a child of itself.
            if (!parent.equals(t)) {
              parent.addChild(t);
 
              CFNodeList childNodes = t.getChildNodes();
              Iterator iter = childNodes.iterator();
              ArrayList deletedChildren = new ArrayList();
              while (iter.hasNext()) {
                Object o = iter.next();
                if (o instanceof DocItem) {
                  DocItem d = (DocItem)o;
                  d.setParent(parent);
                  parent.addChild(d);
                  deletedChildren.add(d);
                  System.out.println("Added " + d.getClass().getName() + " as child of " + parent.getName() + ". Was child of " + tagName);
                }
              }
              iter = deletedChildren.iterator();
              while(iter.hasNext()) {
                t.removeChild((DocItem)iter.next());
              }
            }
          }
        }
       
      }
      // any tags left will be unclosed tags that should be closed
      // or maybe a hybrid (cfmodule poped up without the check)
      while(matchStack.size() > 1) //leave the doc root
      {       
        TagItem orphanTag = (TagItem)matchStack.pop();
        if (!orphanTag.isHybrid()) {         
          parserState.addMessage(new ParseError(
              orphanTag.getLineNumber(), orphanTag.getStartPosition(), orphanTag.getEndPosition(), orphanTag.getName(),
              orphanTag.getName() + " is missing a closing tag"
          ));
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
 
  public final int MATCHER_NOTHING =     0x00;
  public final int MATCHER_COMMENT =     0x01;
  public final int MATCHER_HTMLTAG =    0x02;
  public final int MATCHER_ATTRIBUTE =   0x04;
  public final int MATCHER_CFSCRIPT =   0x08;
  public final int MATCHER_CFMLCOMMENT =   0x16;
  public final int MATCHER_CFSCRCOMMENT = 0x32;
  public final int MATCHER_STRING =     0x64;
  public final int MATCHER_CFMLTAG =     0x128;
 
  protected final int INDEX_NOTFOUND =  -1// For String::indexOf(), make it nicer to read!
 
  protected int matchingHTML(State parseState, String inData, int currDocOffset)
  {
    int finalOffset = currDocOffset;
    int currPos = currDocOffset + 1;
    ParseItemMatch embeddedMatch = null;
    int cfTagCount = 0;
    int quoteCount = 0;
   
    for(; currPos < inData.length(); currPos++)
    {
      char currChar = inData.charAt(currPos);
      boolean inQuotes = (1 == quoteCount % 2);
      String next2Chars = "";
      String next3Chars = "";
     
      if(inData.length() - currPos > 2// For CF stuff we get the next two chars as well.
        next2Chars = inData.substring(currPos + 1, currPos + 3);
      if(inData.length() - currPos > 3// For CF closer tags </cf...
        next3Chars = inData.substring(currPos + 1, currPos + 4);
     
      if(currChar == '<' && (next2Chars.compareToIgnoreCase("cf") == 0 || next3Chars.compareToIgnoreCase("/cf") == 0))
      {  // CFML tag embedded in HTML
        ////System.out..println("CFParser::matchingHTML() - FOUND!: an embedded CFML tag within HTML! : " + inData.substring(0, currPos));
        currPos = matchingCFML(parseState, inData, currPos);
        cfTagCount++;
      }
      else if(!inQuotes && currChar == '>')
      {
        ////System.out..println("CFParser::matchingHTML() - FOUND!: an HTML tag!: " + inData.substring(currDocOffset, currPos+1));       
        parseState.addMatch(new ParseItemMatch(inData.substring(currDocOffset, currPos+1), currDocOffset, currPos, 0, MATCHER_HTMLTAG),
                  State.ADD_BEFORE, cfTagCount);
        finalOffset = currPos;
        break;
      }
      else if(currChar == '\"')
        quoteCount++;
    }
    if(finalOffset != currPos)
    {
      //System.err.println("CFParser::matchingHTML() - FATAL ERROR: Failed to find the end of an HTML tag!: " + inData.substring(currDocOffset, currPos));
     
      parseState.addMessage(new ParseError(getLineNumber(currDocOffset), currDocOffset, currPos,
                        inData.substring(currDocOffset, currPos),
                        "Reached end of document before finding end of HTML tag.",
                        true )); // Fatal error
    }
    return currPos;
  }
 
  protected int matchingCFScript(State parseState, String inData, int currDocOffset)
  {
     int finalOffset = currDocOffset;
    int currPos = currDocOffset;
    String nextChars = ""; // </cfscript>
    String closingText = "</cfscript>";
    //
    //System.out.println("CFParser::matchingCFScript() - Matching CFScript");
   
    for(; currPos < inData.length(); currPos++)
    {
      if(inData.length() - currPos + 1 > closingText.length())
        nextChars = inData.substring(currPos, currPos + closingText.length());
      else
        break// Not enough space left for it to be a closing cfscript tag.
     
      if(nextChars.compareToIgnoreCase(closingText) == 0)
      {
        finalOffset = currPos;
        break;
      }
     
    }
    //System.out.println("matchingCFScript() -");
    //System.out.println(inData.substring(currDocOffset, finalOffset));
 
    if(finalOffset != currPos)
    {
      System.err.println("FATAL ERROR: Searching for a closing <cfscript> tag but could not find one: " + inData.substring(currDocOffset, currPos));
     
      parseState.addMessage(new ParseError(getLineNumber(currDocOffset), currDocOffset, currPos,
                        inData.substring(currDocOffset, currPos),
                        "Reached end of document before finding a closing cfscript tag.",
                        true )); // Fatal error
   
    //} else if(this.parseCFScript) {
    } else if(true) {
      int scriptStart = currDocOffset + "<cfscript>".length();
      String cfScriptData = inData.substring(currDocOffset, finalOffset);
      cfScriptData = cfScriptData.trim();
      //System.out.println("CFScript data:");
      //System.out.println(cfScriptData);
      //
      // We cheat now. We're actually creating a tag match for a <cfscript> block and pass all
      // of the data in so we have a tag called "<cfscript>...". But this breaks if it's empty,
      // so we trimmed the data and now we compare with <cfscript>. If it doesn't equal it (
      // and therefore it's got CFScript data in) we add the match.
      //if(cfScriptData.toLowerCase().startsWith("<cfscript>")) {
        ParseItemMatch scriptMatch = new ParseItemMatch(cfScriptData, scriptStart, finalOffset, getLineNumber(scriptStart), MATCHER_CFSCRIPT);
        parseState.addMatch(scriptMatch);
        /*
        TagMatch endScriptTag = new TagMatch("</cfscript>", finalOffset,
                          finalOffset + "</cfscript>".length(), getLineNumber(finalOffset + 3));
        parseState.addMatch(endScriptTag);
        */
      //}

    }
   
   
    //
    // finalOffset is assigned the end of the cfscript block _including_ the closing '</cfscript>'
    // so we remove it from the offset so that the parser can handle the closing tag correctly.
    return finalOffset-1;// - "</cfscript>".length();
  }
 
  /*
  protected int matchingCFScript(State parseState, String inData, int currDocOffset)
  {
    int finalOffset = currDocOffset;
    int currPos = currDocOffset;
    String nextChars = ""; // </cfscript>
    String closingText = "</cfscript>";
    ////System.out..println("CFParser::matchingCFScript() - Matching CFScript");
    for(; currPos < inData.length(); currPos++)
    {
      if(inData.length() - currPos + 1 > closingText.length())
        nextChars = inData.substring(currPos, currPos + closingText.length());
      else
        break;  // Not enough space left for it to be a closing cfscript tag.
     
      if(nextChars.compareToIgnoreCase(closingText) == 0)
      {
        finalOffset = currPos;
        break;
      }
     
    }
   
    int scriptStart = currDocOffset + "<cfscript>".length();
    String cfScriptData = inData.substring(scriptStart, finalOffset);
   
    TagMatch scriptMatch = new TagMatch(cfScriptData, scriptStart, finalOffset, getLineNumber(scriptStart));
    parseState.addMatch(scriptMatch);
 
    if(finalOffset != currPos)
    {
      //System.err.println("FATAL ERROR: Searching for a closing <cfscript> tag but could not find one: " + inData.substring(currDocOffset, currPos));
     
      parseState.addMessage(new ParseError(getLineNumber(currDocOffset), currDocOffset, currPos,
                        inData.substring(currDocOffset, currPos),
                        "Reached end of document before finding a closing cfscript tag.",
                        true )); // Fatal error
   
    }
   
    return finalOffset;
  }
  */
  protected int matchingCFML(State parseState, String inData, int currDocOffset)
  {
    int finalOffset = currDocOffset;
    int currPos = currDocOffset;
   
    // for recognizing quote escape sequences.
    // If it's even we're out of quotes, odd we're in 'em. Try it out manually and see!
    char currQuote = 0;
    int quoteCount = 0;
    boolean inComment = false;
   
    for(; currPos < inData.length(); currPos++) {
      char currChar = inData.charAt(currPos);
      if(currChar == '<' && inData.length() >= 5 && inData.substring(currPos,currPos+5).equals("<!---")) {
        inComment = true;
      }
      if(currChar == '>' && inData.length()-3 >= 0 && inData.substring(currPos-3, currPos).equals("---")) {
        inComment = false;
        continue;
      }
      if(inComment) {
        // if we're inside a comment, we don't give a hoot, keep going until out of it
        continue;
      }
      boolean inQuotes = (1 == quoteCount % 2);
      if(!inQuotes && currChar == '>') {
        finalOffset = currPos;
        //System.out.println("Parser found a cfml tag: " + inData.substring(currDocOffset, currPos+1));
        parseState.addMatch(new ParseItemMatch(inData.substring(currDocOffset, currPos+1), currDocOffset, currPos,
                        getLineNumber(currDocOffset), MATCHER_CFMLTAG));
        break;
      } else if( inQuotes && currQuote == currChar ) {
        // We are in quotes and we found the character that started the quotes
        quoteCount++;
      } else if( !inQuotes && (currChar == '\"' || currChar == '\'') ) {
        // Store what started the quote so we know what to look for to end the current quote
        currQuote = currChar;
        quoteCount++;
      }
    }
   
    if(finalOffset != currPos && reportErrors) {
      //System.err.println("FATAL ERROR: Failed to find the end of a CFML tag!: " + inData.substring(currDocOffset, currPos));
     
      parseState.addMessage(new ParseError(getLineNumber(currDocOffset), currDocOffset, currPos,
                        inData.substring(currDocOffset, currPos),
                        "Reached end of document before finding end of CFML tag.",
                        true )); // Fatal error
    }
   
    return finalOffset;
  }
 
  /**
   * This function goes through the incoming stream of characters that represents the document to parse.
   * It processes the document scanning for patterns within the data. Once a pattern has been found it
   * is added to a list of matches. This match list will be used later on by the document tree creator.
   *
   * @param inData
   * @return
   */
  protected ArrayList tagMatchingAttempts(String inData)
  {
    //String data = inData;
    String data = this.data2Parse;
    int lastMatch = 0;
    int currPos = 0;
    int currState = 0;
    Stack stateStack = new Stack();
    Stack statePositionStack = new Stack();
   
    ArrayList matches = new ArrayList();
    try {
      for(currPos = 0; currPos < data.length(); currPos++)
      {
        char currChar = data.charAt(currPos);
        String next2Chars = "";
        String next3Chars = "";
        String next4Chars = "";
        String around = "";
       
        // Make sure we haven't had any fatal errors during parsing.
        if(parserState.hadFatal())
          break;
        //
        // Get some next data that will make our life easier in the code ahead
        next2Chars = (data.length() - currPos > 2) ? data.substring(currPos + 1, currPos + 3) : "";
        next3Chars = (data.length() - currPos > 3) ? next2Chars + data.charAt(currPos + 3) : "";
        next4Chars = (data.length() - currPos > 4) ? next3Chars + data.charAt(currPos + 4) : "";
       
        around = getSurroundingData(data, currPos);
       
        if((currState == MATCHER_NOTHING || currState == MATCHER_CFMLCOMMENT && next4Chars.compareTo("!---") == 0) && currChar == '<')
        { 
          if(next2Chars.compareTo("!-") == 0)
          {  // Testing for comment: <!--
            // TODO: Find out whether comments can occur in tags
            if(next4Chars.compareTo("!---") == 0)
            {
              stateStack.push(new Integer(currState));
              statePositionStack.push(new Integer(currPos));
              currState = MATCHER_CFMLCOMMENT;
              lastMatch = currPos;
            }
            else
            {
              currState = MATCHER_COMMENT;
              lastMatch = currPos;
            }
          }
          else if(next2Chars.compareToIgnoreCase("cf") == 0)
          {
           
            //TODO: saving a <cfscript> tag with no contents causes a heap error
            //
            // The following handles a CFScript tag. A CFScript tag is NOT part of the document tree as it is a
            // container *only* for things to go in the document tree.
            if(data.length() - currPos > "<cfscript>".length() &&
               data.substring(currPos, currPos + "<cfscript>".length()).compareToIgnoreCase("<cfscript>") == 0)
            {
              currPos = matchingCFScript(parserState, inData, currPos);
            }
            else
              currPos = matchingCFML(parserState, inData, currPos);
          }
          else // Notice that the above if doesn't match </cf, that's because it's like a standard HTML tag.
          {
            // if this test indicates an HTML tag then we need to parse it.
            // this specifically works around the issue where an < operator
            // in SQL statements was causing false error reporting. Paul V.
            // Ticket #146
           
            // BEWARE! If this breaks, code folding breaks!!!
            if(next2Chars.matches("^(/?)[a-zA-Z]{0,2}(>?)$"))
              currPos = matchingHTML(parserState, inData, currPos);
          }
         
        }
        else if(currState == MATCHER_CFMLCOMMENT
            && currChar == '-'
            && next2Chars.compareTo("--") == 0
            && inData.charAt(currPos+3) == '>')
        {
          currState = ((Integer)stateStack.pop()).intValue();
          int lastStatePos = ((Integer)statePositionStack.pop()).intValue();
          if(currState == MATCHER_NOTHING)
          {
           
            ParseItemMatch commentMatch = new ParseItemMatch(
              inData.substring(lastStatePos, currPos + 4), lastStatePos, currPos + 4,
              getLineNumber(lastStatePos), MATCHER_CFMLCOMMENT
            );
            parserState.addMatch(commentMatch);
           
          }
        }
        else if(currState == MATCHER_COMMENT
            && currChar == '-'
            && next2Chars.compareTo("->") == 0)
        {
          currState = MATCHER_NOTHING;
        }
      }
    }catch(Exception excep) {
      parserState.addMessage(new ParseError(0, currPos, currPos, "", "Caught an exception during parsing.", true));
    }
    return matches;
  }
 
  /**
   * Gets some surrounding data that is around the current cursor position.
   *
   * @param data The data currently being scanned
   * @param currPos The current position in the document
   * @return The +/- 10 characters around the current position in the document
   */
  private String getSurroundingData(String data, int currPos) {
    String around = "";
    if(data.length() - currPos > 10 && currPos > 10)
      around = data.substring(currPos - 10, currPos) + data.substring(currPos, currPos + 10);
    else if(data.length() - currPos > 10)
      around = data.substring(currPos, currPos + 10);
   
    return around;
  }

  protected void processParseResultMessages()
  {
    ArrayList messages = parserState.getMessages();
    IWorkspaceRoot myWorkspaceRoot = CFMLPlugin.getWorkspace().getRoot();
   
    for(int i = 0; i < messages.size(); i++)
    {
      ParseMessage currMsg = (ParseMessage)messages.get(i);
      Map attrs = new HashMap();
      MarkerUtilities.setLineNumber(attrs, currMsg.getLineNumber() + 1);
      MarkerUtilities.setMessage(attrs, currMsg.getMessage());
      // attrs.put(IMarker.CHAR_START, new Integer(currMsg.docStartOffset));
      int endOffset = 0;
      if (currMsg.docEndOffset > currMsg.docStartOffset) {
        endOffset = currMsg.docEndOffset;
        // System.out.println("End offset is: " + endOffset + " start is " + currMsg.docStartOffset);
      } else {
        endOffset = currMsg.docStartOffset + currMsg.docData.length();
      }

      //
      // Not sure what the start & end positions are good for!
      // MarkerUtilities.setCharStart(attrs, currMsg.getDocStartOffset());
      // MarkerUtilities.setCharEnd(attrs, currMsg.getDocEndOffset());
      //
      // Not sure right now how to set the problem to be a warning or an error.
      // There is IMarker.SEVERITY_ERROR & IMarker.SEVERITY_WARNING but I'm
      // not sure how I set them.
      String type = "org.cfeclipse.cfml.parserProblemMarker";
      if (currMsg instanceof ParseError) {
        type = "org.cfeclipse.cfml.parserProblemMarker";
      } else if (currMsg instanceof ParseWarning) {
        type = "org.cfeclipse.cfml.parserWarningMarker";
      }

      try {
        MarkerUtilities.createMarker(this.res, attrs, type);
        // MarkerUtilities.createMarker(this.res, attrs, IMarker.PROBLEM);
      } catch (CoreException excep) {
        userMessage(0, "userMessage",
            "ERROR: Caught CoreException when creating a problem marker. Message: \'" + excep.getMessage() + "\'");
      } catch (Exception anyExcep) {
        userMessage(0, "processParseResultMessage", "ERROR: Caught exception " + anyExcep.getMessage());
      }

    }
  }
 
  /**
   * Traverses the document tree for the final time, calling each document item's
   * sanity checker and then retrieving any parse messages that each document item
   * may hold.
   * @param startNode The node to start at.
   * @return an <code>ArrayList</code> of the messages retrieved.
   */
  public ArrayList finalDocTreeTraversal(DocItem startNode)
  {
   
    ArrayList messages = new ArrayList();
   
    //
    // Perform sanity check. Method adds to the object's message list which we shall gather next.
   
    startNode.IsSane();
   
    if(startNode.getParseState() != null) {     
      messages.addAll(startNode.getParseState().getMessages());
    }
   
    if(startNode.hasChildren())
    {
      CFNodeList children = startNode.getChildNodes();
      Iterator nodeIter = children.iterator();
      //System.out.println("Node ("+startNode.getClass().getSimpleName()+ ") " + startNode.toString() + " has " + children.size() + " children");
      while(nodeIter.hasNext())
      {
        DocItem item = (DocItem)nodeIter.next();
        //item.setParent(startNode);
        messages.addAll(finalDocTreeTraversal(item));
      }
    }
    return messages;
  }
 
  public CFDocument parseDoc(String inData)
  {
    CFDocument docTree = null;
   
    try {
      parserState = new State("doesn\'t matter!");
      lineOffsets = Util.calcLineNumbers(inData);
     
      this.setData2Parse(inData);
      // Code folding is performed by tagMatchingAttempts
      // this code will cause a warning as codeFoldingMatches is never referenced again...
      ArrayList codeFoldingMatches = tagMatchingAttempts(inData);
     
      //parserState.getMatches() was called twice in succession, this should speed it up a bit.
      docTree = createDocTree(inData);
      DocItem documentRoot = docTree.getDocumentRoot();
      ArrayList list = finalDocTreeTraversal(documentRoot);
      parserState.addMessages(list);
      processParseResultMessages();
     
      //This should parse a document and setup all the variables
      //TODO: Make sure the variable parser only parses up to the cursor
      //need to get preferences
      IPreferenceStore prefStore = CFMLPlugin.getDefault().getPreferenceStore();
      if(prefStore.getBoolean(ParserPreferenceConstants.P_PARSE_VARIABLES)){
       
        //System.out.println("calling the variables parser!");
        VariablesParser vParser = new VariablesParser(docTree,inData);
        docTree.setVariableMap(vParser.getVariableMap());
      }
    } catch(Exception excep)
    {
      System.err.println("CFParser::parseDoc() - Exception: " + excep.getMessage());
      excep.printStackTrace();
    }
   
    return docTree;   
  }
 
  public CFDocument parseDoc()
  {
    if(parseDoc == null)
    {
      return parseDoc(data2Parse);
    }
    else
      return parseDoc(parseDoc.get());
  }
 
  public CFDocument parseDoc(IDocument doc2Parse)
  {
    return parseDoc(doc2Parse.get());
  }
 
  /**
   * Parses the document and saves the result into the parseResult variable
   * so it maintains it's tree.
   * @author rob
   * @deprecated
   */
  /* public void parseSaveDoc()
  {
    //////System.out..println(parseDoc.get());
    parseResult = parseDoc();
  } */
 
TOP

Related Classes of org.cfeclipse.cfml.parser.CFParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.