/*
* Copyright (C) 2000 - 2015 aw2.0 Ltd
*
* This file is part of Open BlueDragon (OpenBD) CFML Server Engine.
*
* OpenBD is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* Free Software Foundation,version 3.
*
* OpenBD is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenBD. If not, see http://www.gnu.org/licenses/
*
* Additional permission under GNU GPL version 3 section 7
*
* If you modify this Program, or any covered work, by linking or combining
* it with any of the JARS listed in the README.txt (or a modified version of
* (that library), containing parts covered by the terms of that JAR, the
* licensors of this Program grant you additional permission to convey the
* resulting work.
* README.txt @ http://www.openbluedragon.org/license/README.txt
*
* http://openbd.org/
* $Id: cfTag.java 2486 2015-01-22 03:22:37Z alan $
*/
package com.naryx.tagfusion.cfm.tag;
import java.io.CharArrayWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import com.nary.util.CaseSensitiveMap;
import com.nary.util.EmptyFastMap;
import com.nary.util.FastMap;
import com.nary.util.charVector;
import com.nary.util.string;
import com.naryx.tagfusion.cfm.engine.catchDataFactory;
import com.naryx.tagfusion.cfm.engine.cfArgStructData;
import com.naryx.tagfusion.cfm.engine.cfCatchData;
import com.naryx.tagfusion.cfm.engine.cfData;
import com.naryx.tagfusion.cfm.engine.cfSession;
import com.naryx.tagfusion.cfm.engine.cfStringData;
import com.naryx.tagfusion.cfm.engine.cfStructData;
import com.naryx.tagfusion.cfm.engine.cfmAbortException;
import com.naryx.tagfusion.cfm.engine.cfmBadFileException;
import com.naryx.tagfusion.cfm.engine.cfmExitException;
import com.naryx.tagfusion.cfm.engine.cfmRunTimeException;
import com.naryx.tagfusion.cfm.engine.dataNotSupportedException;
import com.naryx.tagfusion.cfm.file.cfFile;
import com.naryx.tagfusion.cfm.parser.CFContext;
import com.naryx.tagfusion.cfm.parser.CFExpression;
import com.naryx.tagfusion.cfm.parser.CFMLCache;
import com.naryx.tagfusion.cfm.parser.runTime;
import com.naryx.tagfusion.expression.function.functionBase;
/**
* This is the base class for all tags in the system.
*/
public class cfTag implements java.io.Serializable {
static final long serialVersionUID = 1;
static final CaseSensitiveMap<String, String> emptyProperties = new EmptyFastMap<String, String>();
protected CaseSensitiveMap<String, String> properties;
public cfTag parentTag; // The tag that this tag belongs in
private cfFile parentFile; // The cfFile instant that holds this tag
public List<cfTag> tagList; // List of tags this page has; only available during parse
public List<Integer> commentList; // List of the line numbers for comments this page has
protected char[] tagContents = null; // Buffer contents
private boolean flushable = true; // True=we can send this tag out when we have data
protected String tagName;
public int posLine=1, posColumn=1; // Position of this tag
public int posEndLine=0, posEndColumn=0; // Position of this end tag; 0 if no end tag
protected char[][] tagBody;
protected cfTag[] childTagList;
protected CFExpression[] expressionList;
protected byte[] controlList;
protected int[] expressionPos;
// don't use 0 or negative numbers for these constants
private static final char CHAR_POUND = '#';
public static final byte COMMENT_MARKER = 4;
public static final byte TAG_MARKER = 3;
public static final byte EXP_MARKER = 1;
public static final byte CHR_MARKER = 2;
public static final char[] emptyArray = new char[0];
public static final char[][] emptyTagBody = new char[0][];
public static final cfTag[] emptyTagList = new cfTag[0];
public static final CFExpression[] emptyExpression = new CFExpression[0];
public cfTag(cfFile _parentFile) {
this();
parentFile = _parentFile;
}
public cfTag() {
parentTag = null;
parentFile = null;
tagList = new ArrayList<cfTag>(1);
commentList = new ArrayList<Integer>(1);
properties = new FastMap<String, String>( FastMap.CASE_INSENSITIVE );
tagContents = emptyArray;
}
/**
* Sets value for parent tag.
*/
public void setParentTag( cfTag _parentTag ){
parentTag = _parentTag;
}
public void setParentFile( cfFile _parentFile ) {
parentFile = _parentFile;
}
public void setProperties(CaseSensitiveMap<String, String> _properties){
properties = _properties;
}
public cfFile getFile(){
// Returns the cfFile inwhich this tag belongs
if ( parentFile != null ) {
return parentFile;
}
if ( parentTag != null ) {
return parentTag.getFile();
}
return null;
}
/**
* Return the end tag marker.
*/
public String getEndMarker(){
return null;
}
/**
* This method allows you set default attribute values for the specified tag.
*
* @param _tag - the tag that store defualt value for.
*/
protected void defaultParameters( String _tag ) throws cfmBadFileException {}
protected void tagLoadingComplete() throws cfmBadFileException {}
protected cfStructData setAttributeCollection(cfSession _Session) throws cfmRunTimeException {
cfData attributeCollection = getDynamic(_Session, "ATTRIBUTECOLLECTION");
if (attributeCollection != null && attributeCollection.getDataType() == cfData.CFSTRUCTDATA) {
return (cfStructData)attributeCollection;
}else
return null;
}
// return the tag attributes in a cfStructData
protected cfStructData getMetaData( boolean _preEval ) throws cfmBadFileException {
cfStructData metaData = new cfStructData();
if ( !properties.isEmpty() ){
Iterator<String> iter = properties.keySet().iterator();
while ( iter.hasNext() ) {
String key = iter.next();
metaData.setData( key, _preEval ? getEvaluatedConstant( key ) : new cfStringData( getConstant(key) ) );
}
}
return metaData;
}
/**
* Determines if this tag can be flushable. This is used to determine whether or not we can
* send data out in chunks or the more inefficient route of one block.
*
* @param _flushable
* - the boolean value, true if a tag is flushable.
*/
protected void setFlushable(boolean _flushable) {
flushable = _flushable;
}
/**
* Returns true if a tag can be flushable, else return false.
*/
protected boolean flushable(){
return flushable;
}
/**
* Returns true if all tags in the tag list are flushable, else return false.
*/
public boolean isFlushable(){
return false;
}
/**
* Returns true if the tag contains embedded pound signs ( the default is false,
* subclasses should override to return true when needed)
*/
public boolean doesTagHaveEmbeddedPoundSigns() {
return false;
}
public int getExpressionPosition( int _i ){
// check expressionPos isn't null. It could be if an older version of cfTag was deserialized
if ( expressionPos == null || _i < 0 || _i >= expressionPos.length )
return 0;
return this.expressionPos[_i];
}
//-------------------------------------------------------------------------------
public cfTagReturnType render( cfSession _Session ) throws cfmRunTimeException {
return coreRender( _Session );
}
public cfTagReturnType coreRender( cfSession _Session ) throws cfmRunTimeException {
int t = 0, e = 0, s = 0;
for ( int x = 0; x < controlList.length; x++ ) {
if ( _Session.isStopped() ) {
_Session.abortPageProcessing();
}else if ( controlList[ x ] == CHR_MARKER ) {
_Session.write( tagBody[ s++ ] );
} else if ( controlList[ x ] == TAG_MARKER ) {
_Session.pushTag( childTagList[ t ] );
cfTagReturnType rt = childTagList[ t++ ].render( _Session );
_Session.popTag();
if ( !rt.isNormal() ) {
return rt;
}
} else if ( controlList[ x ] == EXP_MARKER ) {
_Session.setActiveExpression( e );
renderExpression( _Session, expressionList[ e++ ] );
}
}
return cfTagReturnType.NORMAL;
}
protected void renderExpression( cfSession _Session, CFExpression expr )throws cfmRunTimeException, dataNotSupportedException {
boolean oldEscapeSingleQuotes = _Session.isEscapeSingleQuotes();
// save the escapeSingleQuotes flag to restore after runExpression;
// the PreserveSingleQuotes method sets this flag to false if executed
_Session.debugger.runExpression( expr );
_Session.getCFContext().setLineCol( 0,0 );
cfData token = runTime.runExpression( _Session, expr );
if ( ( token != null ) && ( token.getDataType() != cfData.UNKNOWN ) ) {
if ( _Session.isEscapeSingleQuotes() && ( token.getDataType() == cfData.CFSTRINGDATA )
&& !( (cfStringData) token).isDateConvertible( false )
&& ( expr.getType() != CFExpression.FUNCTION || expr.isEscapeSingleQuotes() ) ) {
_Session.write( string.replaceString( token.getString(), "'", "''" ) );
} else {
_Session.write( token.getString() );
}
}
_Session.setEscapeSingleQuotes( oldEscapeSingleQuotes );
}
public static final int DEFAULT_OPTIONS = 0;
public static final int ESCAPE_SINGLE_QUOTES = 1;
public static final int CF_OUTPUT_ONLY = 2;
public static final int HONOR_CF_SETTING = 4;
public static final int SUPPRESS_WHITESPACE = 8;
public static final int SUPPRESS_OUTPUT_AFTER_ABORT = 16;
private static boolean isSet( int options, int mask ) {
return ( ( options & mask ) == mask );
}
public cfTagReturnType renderToString( cfSession _Session ) throws cfmRunTimeException {
return renderToString( _Session, cfTag.DEFAULT_OPTIONS );
}
public cfTagReturnType renderToString( cfSession _Session, int options ) throws cfmRunTimeException {
cfSession offlineSession = new cfSession( _Session, isSet( options, ESCAPE_SINGLE_QUOTES ) );
if ( isSet( options, CF_OUTPUT_ONLY ) ) {
offlineSession.setProcessingCfOutput( false );
} else if ( !isSet( options, HONOR_CF_SETTING ) ) {
offlineSession.clearCfSettings();
}
offlineSession.setSuppressWhiteSpace( isSet( options, SUPPRESS_WHITESPACE ) );
CFContext context = _Session.getCFContext();
try {
context.setSession( offlineSession ); // have to set the session context so that
cfTagReturnType rt = coreRender( offlineSession ); // writeOutput writes to the right session
if ( rt.isExitOrReturn() ) {
rt.setOutput( offlineSession.getOutputAsString() );
return rt;
}else if ( rt.isNormal() ){
return cfTagReturnType.newNormal( offlineSession.getOutputAsString() );
}
return rt;
} catch ( cfmRunTimeException rte ) {
//this is part of the fix for NA bug #3282
rte.setOutput( offlineSession.getOutputAsString() );
throw rte;
} catch ( cfmExitException ae ) {
ae.setOutput( offlineSession.getOutputAsString() );
throw ae;
} catch ( cfmAbortException ae ) {
String output = offlineSession.getOutputAsString();
ae.setOutput( output );
if ( !isSet( options, SUPPRESS_OUTPUT_AFTER_ABORT ) && ae.flushOutput() ) {
_Session.clearCfSettings();
_Session.write( output );
}
throw ae;
} finally {
_Session.setActiveExpression( offlineSession.getActiveExpression() );
_Session.setBufferReset( offlineSession.hasBufferReset() );
_Session.setLocale( offlineSession.getLocale() );
context.setSession( _Session ); // reset the session back to the original
}
}
//-------------------------------------------------------------------------------
public int getNoOfAttributes(){
return properties.size();
}
public CaseSensitiveMap<String, String> getProperties(){
return properties;
}
public String getConstant( String _attribute ){
return getConstant( _attribute, true );
}
public boolean getConstantAsBoolean( String _attribute ){
String t = getConstant( _attribute, true );
if ( t.equalsIgnoreCase( "YES" ) ||
t.equalsIgnoreCase( "TRUE" ) ||
t.equals( "1" ) ){
return true;
}else
return false;
}
public cfData getEvaluatedConstant( String _attribute ) throws cfmBadFileException{
String constant = getConstant( _attribute, false );
if ( constant != null ){
try{
return runTime.runExpression( constant );
}catch( cfmRunTimeException e ){
throw newBadFileException( "Error evaluating attribute [" + _attribute + "]", e.getMessage() );
}
}
return null; // keep compiler happy
}
public String getConstant( String _attribute, boolean _stripQuotes ){
if ( properties.containsKey( _attribute ) ){
String rhs = properties.get( _attribute );
if ( _stripQuotes && rhs.length() >= 2 && ((rhs.charAt(0) == '\"' && rhs.charAt(rhs.length()-1) == '\"') || (rhs.charAt(0) == '\'' && rhs.charAt(rhs.length()-1) == '\'')) )
return rhs.substring( 1, rhs.length()-1 );
else
return rhs;
}else
return null;
}
// checks that the attribute exists, throws an exception if not
public void requiredAttribute( String _attribute ) throws cfmBadFileException {
if ( !properties.containsKey( _attribute ) ) {
throw newBadFileException( "Missing Attribute", "The " + _attribute.toUpperCase() + " attribute must be specified" );
}
}
// checks that the attribute contains a constant value, throws an exception if not
public void constantAttribute( String _attribute ) throws cfmBadFileException {
if ( properties.containsKey( _attribute ) && getConstant( _attribute, true ).indexOf( '#' ) >= 0 ) {
throw newBadFileException( "Invalid Expression", "The " + _attribute.toUpperCase() + " attribute must contain a constant value" );
}
}
// checks that the attribute contains a constant boolean value, throws an exception is not
public void booleanAttribute( String _attribute ) throws cfmBadFileException {
if ( properties.containsKey( _attribute ) ) {
String attrValue = getConstant( _attribute, true );
if ( !attrValue.equalsIgnoreCase( "YES" ) &&
!attrValue.equalsIgnoreCase( "TRUE" ) &&
!attrValue.equalsIgnoreCase( "NO" ) &&
!attrValue.equalsIgnoreCase( "FALSE" ) &&
!attrValue.equals( "1" ) &&
!attrValue.equals( "0" ) )
{
throw newBadFileException( "Invalid Attribute", "Cannot convert the " + _attribute.toUpperCase() + " attribute to a boolean value" );
}
}
}
/**
* Designed specifically for tags that are merely wrappers to the function versions of themselves.
* This function wraps up the tag attributes and creates a functionArgs structure for it to use.
* The key is that we need to know which params we want
*
* @param session
* @param params
* @param attributes
* @return
* @throws cfmRunTimeException
*/
public cfArgStructData getFunctionArgsFromAttributes( cfSession session, List<String> params, cfStructData attributes ) throws cfmRunTimeException {
cfArgStructData argData = new cfArgStructData(true);
Iterator<String> it = params.iterator();
while ( it.hasNext() ){
String key = it.next();
cfData val = getDynamic( attributes, session, key );
if ( val != null )
argData.setData(key.toLowerCase(), val);
}
return argData;
}
public String getDynamicAsString( cfStructData attributes, cfSession _Session, String _attribute ) throws cfmRunTimeException {
if ( attributes != null && attributes.containsKey( _attribute )){
return attributes.getData(_attribute).getString();
}else
return getDynamic(_Session,_attribute).getString();
}
public cfData getDynamic( cfStructData attributes, cfSession _Session, String _attribute ) throws cfmRunTimeException {
if ( attributes != null && attributes.containsKey( _attribute )){
return attributes.getData(_attribute);
}else
return getDynamic(_Session,_attribute);
}
public cfData getDynamic( cfSession _Session, String _attribute ) throws cfmRunTimeException {
if ( !properties.containsKey( _attribute ) )
return null;
String attr = properties.get( _attribute );
return getDynamicAttribute( _Session, attr );
}
public static cfData getDynamicAttribute( cfSession _Session, String attr ) throws cfmRunTimeException {
if ( attr.length() == 0 )
return null;
char firstChar = attr.charAt(0);
char lastChar = attr.charAt(attr.length() - 1);
if ( ( firstChar != '"' && lastChar != '"' ) &&
( firstChar != '\'' && lastChar != '\'' ) &&
( firstChar != '#' && lastChar != '#' ) ){
attr = "\"" + attr + "\"";
}
cfData data = runTime.runExpression( _Session, attr );
// duplicate simple values passed as tag attributes (see bug #3073)
if ( cfData.isSimpleValue( data ) ) {
data = data.duplicate();
}
return data;
}
public void defaultAttribute( String _key, String _value ){
properties.put( _key, "\"" + _value + "\"" );
}
public void defaultAttribute( String _key, int _value ){
properties.put( _key, Integer.toString( _value ) );
}
public boolean containsAttribute( String _key ){
return properties.containsKey( _key );
}
public void removeAttribute( String _key ){
properties.remove( _key );
}
public void removeAttributes(){
properties.clear();
}
public boolean containsAttribute( cfStructData attributes, String _key ){
return (attributes != null && attributes.containsKey(_key)) || properties.containsKey( _key );
}
/**
* The vast majority of tags never modify the Attributes once initialised. However
* some do. Instead of keeping around multiple references to an empty attributes map
* that will never change, we point them all to the same one. However for those that do
* need to change it at run time, must ensure they have their own version of it.
*
* This method will check to see if they are using the default empty one and then give them
* their own.
*/
public void unclipAttributes(){
if ( emptyProperties == properties )
properties = new FastMap<String, String>();
}
/**
* Pop the tag stack until this tag is at the top
*/
protected void restoreTagStack( cfSession _Session ) {
while ( _Session.activeTag() != this )
_Session.popTag();
}
//-------------------------------------------------------------------------------
protected void parseTagHeader( String tag ) throws cfmBadFileException {
parseTagHeader( tag, false );
}
protected void parseTagHeader( String tag, boolean oneParameter ) throws cfmBadFileException {
try{
new tagParameters( tag, oneParameter, properties );
}catch(Exception E){
throw new cfmBadFileException( catchDataFactory.invalidTagAttributeException( this, E.getMessage() ) );
}
}
//--[ ----------------------------------------------------------
//--[ RunTime exception methods
public cfmRunTimeException newRunTimeException( String _ErrMessage ) {
return new cfmRunTimeException( catchDataFactory.runtimeException( this, _ErrMessage ) );
}
public static cfmRunTimeException newRunTimeException( cfCatchData catchData ) {
return new cfmRunTimeException( catchData );
}
//--[ ----------------------------------------------------------
//--[ BadFile exception methods
public cfmBadFileException newBadFileException( String _type, String _ErrMessage ) {
return new cfmBadFileException( catchDataFactory.invalidAttributeException( this, "runtime.general", new String[]{_type +": " + _ErrMessage} ) );
}
protected cfmBadFileException invalidAttributeException( String detail, String values[] ){
return new cfmBadFileException( catchDataFactory.invalidAttributeException( this, detail, values ) );
}
protected cfmBadFileException missingAttributeException( String detail, String values[] ){
return new cfmBadFileException( catchDataFactory.missingAttributeException( this, detail, values ) );
}
protected cfmBadFileException invalidExpressionException( String detail, int _subline ){
return new cfmBadFileException( catchDataFactory.invalidExpressionException( this, detail, _subline ) );
}
public String getTagName(){
if ( tagName == null ) {
tagName = tagUtils.getLastToken( this.getClass().getName() ).toUpperCase();
}
return tagName;
}
public cfTag[] getTagList(){
return childTagList;
}
public char[] getStaticBody(){
return tagBody[0];
}
public void normalise( boolean isFileEncodingSearch ) throws cfmBadFileException {
normalise( new normaliseTracker(), isFileEncodingSearch );
if ( !isFileEncodingSearch )
tagLoadingComplete();
}
private void normaliseStatic(normaliseTracker nTracker) throws cfmBadFileException {
//--[ Normalise the Vector of tags into an Object Array
if ( tagList.size() > 0 )
throw newBadFileException( "Bad Tag", "This tag is not permitted to have any child tags" );
tagBody = new char[1][];
controlList = new byte[1];
controlList[0] = CHR_MARKER;
for (int x=0; x < tagContents.length; x++)
tagBody[0] = tagContents;
}
private void normalise( normaliseTracker nTracker, boolean isFileEncodingSearch ) throws cfmBadFileException {
//--[ Normalise the Vector of tags into an Object Array
childTagList = new cfTag[ tagList.size() ];
for( int x=0; x < tagList.size(); x++ ) childTagList[x] = tagList.get(x);
//--[ Normalise the tag contents
List<Object> workingList = new ArrayList<Object>();
List<Integer> expressionPositions = new ArrayList<Integer>();
int currentChildTag = 0;
int currentCommentTag = 0;
CharArrayWriter currentBlock = null;
int bracketCount=0,sqBracketCount=0;
char lastImpChar = 0;
charVector endMarkers = new charVector(5);
StringBuilder expression = null;
boolean possibleEscape = false;
boolean inString = false;
int totalExpressions = 0, totalChars = 0;
int lineCount = 0;
for (int x=0; x < tagContents.length; x++){
if ( tagContents[x] == TAG_MARKER ){
if ( currentBlock != null ){
workingList.add( currentBlock );
totalChars++;
currentBlock = null;
}
//---[ Determine the type of the tag this is.
if ( childTagList[currentChildTag] instanceof ContentTypeStaticInterface ){
childTagList[currentChildTag].normaliseStatic(nTracker);
//- The tag has now been parsed
if ( !isFileEncodingSearch )
childTagList[currentChildTag].tagLoadingComplete();
}else{
nTracker.startTag( childTagList[currentChildTag] );
childTagList[currentChildTag].normalise(nTracker, isFileEncodingSearch);
//- The tag has now been parsed
if ( !isFileEncodingSearch )
childTagList[currentChildTag].tagLoadingComplete();
nTracker.endTag( childTagList[currentChildTag] );
}
// update the lineCount based on this child tag position
lineCount = childTagList[currentChildTag].posLine - this.posLine;
//---[ Add the tag to the list
workingList.add( childTagList[currentChildTag] );
currentChildTag++;
}else if ( tagContents[x] == COMMENT_MARKER ){
// update the lineCount based on comment lines
lineCount = this.commentList.get( currentCommentTag ).intValue() - this.posLine;
currentCommentTag++;
}else{
if ( nTracker.isFiltered() ){
//--[ We are now looking for #
char thisChar = tagContents[x];
//--[ increment lineCount if necessary
if ( thisChar == '\n' ){
lineCount++;
}else if ( thisChar == '\r' ){
if ( !( x+1 < tagContents.length && tagContents[x+1] == '\n') ){
lineCount++;
}// if this is a \r\n sequence only increment on the \n
}
if ( thisChar == CHAR_POUND && bracketCount == 0 && sqBracketCount == 0 && endMarkers.size() == 0 ){
if (expression == null){ //-- Start of the expression
if ( currentBlock != null ){
workingList.add( currentBlock );
totalChars++;
currentBlock = null;
}
expression = new StringBuilder();
}else if ( expression.length() == 0 ){
//--[ The expression is empty, therefore it was escaped
currentBlock = new CharArrayWriter();
currentBlock.write( '#' );
workingList.add( currentBlock );
totalChars++;
currentBlock = null;
expression = null;
}else{
//--[ Expression was found, parse to make sure all is well. Will catch initial bugs
try{
CFMLCache.getExpression( expression.toString() );
}catch(Exception E){
throw newBadFileException( "Bad Expression", "[" + expression.toString() + "]: " + E.getMessage() );
}
workingList.add( expression.toString() );
totalExpressions++;
expressionPositions.add( new Integer( lineCount ) );
expression = null;
}
}else if ( thisChar == CHAR_POUND && !inString && ( bracketCount != 0 || sqBracketCount != 0 ) && endMarkers.size() == 0 ){
throw this.invalidExpressionException( "Bad Expression [#" + expression.toString() + "#]", lineCount );
}else if ( expression != null ){
// update the variables so we know when the expression has finished
if ( possibleEscape && thisChar == lastImpChar ){ // was an escape char
inString = true;
if ( thisChar != CHAR_POUND ){
endMarkers.add( thisChar );
}
possibleEscape = false;
}else if ( inString ){
possibleEscape = false;
if ( thisChar == CHAR_POUND ){
lastImpChar = CHAR_POUND;
inString = false;
possibleEscape = true;
}else if ( thisChar == endMarkers.getLast() ){
// assume this is the end of the string but store
// the char in case next char is the escape char for
// this single/double quote
lastImpChar = thisChar;
inString = false;
possibleEscape = true;
endMarkers.removeLast();
}
// else, don't do anything
}else{
possibleEscape = false;
switch( thisChar ){
case CHAR_POUND:
inString = true;
break;
case '(':
bracketCount++;
break;
case ')':
bracketCount--;
break;
case '[':
sqBracketCount++;
break;
case ']':
sqBracketCount--;
break;
case '"':
case '\'':
inString = true;
endMarkers.add( thisChar );
break;
}
}
expression.append( thisChar );
}else{
if ( currentBlock == null ) currentBlock = new CharArrayWriter();
currentBlock.write( thisChar );
}
}else{
if ( currentBlock == null ) currentBlock = new CharArrayWriter();
currentBlock.write( tagContents[x] );
}
}
}
//--[ We are finished with the tagContents
tagContents = null;
//--[ Make sure that if this was filtered and the expression
if ( nTracker.isFiltered() && expression != null )
throw newBadFileException( "Bad Expression", "[" + expression.toString() + "] was not closed properly" );
//--[ At the end of the loop this one will be caught
if ( currentBlock != null ){ workingList.add( currentBlock ); totalChars++;}
//--[ We now have a Vector of all the elements in a row
expressionList = new CFExpression[ totalExpressions ];
expressionPos = new int[ totalExpressions ];
tagBody = (totalChars==0) ? emptyTagBody : new char[totalChars][];
controlList = new byte[ workingList.size() ];
int e=0, t=0;
for ( int x=0; x < controlList.length; x++ ){
if ( workingList.get(x) instanceof CharArrayWriter ){
tagBody[t++] = ((CharArrayWriter)workingList.get(x)).toCharArray();
controlList[x] = CHR_MARKER;
}else if ( workingList.get(x) instanceof cfTag ){
controlList[x] = TAG_MARKER;
}else{
expressionList[e] = CFExpression.getCFExpression( (String)workingList.get(x) );
expressionPos[e] = expressionPositions.get(e).intValue();
e++;
controlList[x] = EXP_MARKER;
}
}
// Clean up the unused vars
wrapUnsedVars();
}
/**
* Convience method to let us construct a tag from scratch that has an inner tag
* @param tag
*/
public void setChildTag( cfTag tag ){
childTagList = new cfTag[]{tag};
controlList = new byte[]{TAG_MARKER};
wrapUnsedVars();
}
protected void wrapUnsedVars(){
// Clean up the variables
commentList = null;
tagList = null;
// Use the static empty array if there are no child tags
if ( childTagList != null && childTagList.length == 0 )
childTagList = emptyTagList;
if ( expressionList != null && expressionList.length == 0 )
expressionList = emptyExpression;
// If this attribute scope is blank then remove the current one and point to the static empty one
if ( properties.size() == 0 )
properties = emptyProperties;
}
//-------------
class normaliseTracker {
private int cfFilter = 0;
public void startTag(cfTag inTag){
if ( inTag.doesTagHaveEmbeddedPoundSigns() ){
cfFilter += 1;
}
}
public void endTag(cfTag inTag){
if ( inTag.doesTagHaveEmbeddedPoundSigns() ){
cfFilter -= 1;
}
}
public boolean isFiltered(){
return (cfFilter > 0);
}
}
//
public boolean isSubordinate( String parentTagName, boolean immediateParent ) {
cfTag parent = this.parentTag;
if ( parent != null ) {
if ( immediateParent ) {
return parent.getTagName().equalsIgnoreCase( parentTagName );
}
while ( parent != null ) {
if ( parent.getTagName().equalsIgnoreCase( parentTagName ) ) {
return true;
}
parent = parent.parentTag;
}
}
return false;
}
public boolean isSubordinate(String parentTagName) {
return isSubordinate(parentTagName, true);
}
// Some tags have subordinates that must be at the beginning of the body block
// before any other tag. This method allows for the checking of that condition.
public boolean areSubordinatesFirst(String subordinateTagName, boolean mustBePresent) {
int idx, length;
cfTag[] subordinates = null;
boolean lastMatchingTagFound = false;
// get list of subordinates
subordinates = getTagList();
length = subordinates.length;
if(length > 0){
if(!subordinates[0].getTagName().equalsIgnoreCase(subordinateTagName)){
if(mustBePresent){
return false;
}else{
lastMatchingTagFound = true;
}
}
for(idx = 1; idx < length; idx++) {
if(!subordinates[idx].getTagName().equalsIgnoreCase(subordinateTagName)){
lastMatchingTagFound = true;
}else{
if(lastMatchingTagFound){
return false;
}
}
}
}
return true;
}
// Overload specifying that tags need not be present.
public boolean areSubordinatesFirst(String subordinateTagName){
return areSubordinatesFirst(subordinateTagName, false);
}
public boolean containsTag(String tagName){
return containsTag(this, tagName);
}
public boolean containsTag(cfTag startTag, String tagName){
boolean ret = false;
int idx, length;
cfTag[] grandChildren = null;
cfTag child = null;
length = startTag.childTagList.length;
for(idx = 0; idx < length; idx++){
child = startTag.childTagList[idx];
if(child.getTagName().equalsIgnoreCase(tagName)){
return true;
}
grandChildren = child.getTagList();
if(grandChildren != null && grandChildren.length > 0) {
ret = containsTag(child, tagName);
if(ret == true){
return ret;
}
}
}
return false;
}
/**
* This is for files that have been loaded from a BDA. This ensures
* all the tagLoadedComplete() methods are called to initialise any
* values.
*/
public void reInitialiseTags() throws cfmBadFileException {
if ( childTagList != null ) {
// Go through the child tags first of all
for ( int i = 0; i < childTagList.length; i++ ) {
childTagList[ i ].reInitialiseTags();
}
}
tagLoadingComplete();
}
/*
* MetaData Routines
*/
public java.util.Map<String,String> getInfo(){
return createInfo( "unknown", "no documentation available" );
}
public java.util.Map[] getAttInfo(){
return new java.util.Map[0];
}
protected java.util.Map<String,String> createInfo( String category, String summary ){
java.util.HashMap<String,String> map = new java.util.HashMap<String,String>();
map.put("category", category.toLowerCase() );
map.put("summary", summary );
return map;
}
protected java.util.Map<String,String> createAttInfo( String name, String summary ){
return createAttInfo( name, summary, "", false );
}
protected java.util.Map<String,String> createAttInfo( String name, String summary, String defaultValue, boolean required ){
java.util.HashMap<String,String> map = new java.util.HashMap<String,String>();
map.put("name", name.toUpperCase() );
map.put("summary", summary );
map.put("default", defaultValue);
map.put("required", String.valueOf(required) );
return map;
}
public java.util.Map getInfo(functionBase func){
java.util.Map map = func.getInfo();
return createInfo( (String)map.get("category"), (String)map.get("summary") );
}
protected java.util.Map[] makeAttInfoFromFunction(functionBase func, int extraAtts){
List<String> params = func.getFormals();
String[] paramInfo = func.getParamInfo();
java.util.Map[] tagParams = new java.util.Map[params.size()+extraAtts];
for ( int x=0; x < params.size(); x++ ){
tagParams[x] = new HashMap();
tagParams[x].put( "name", params.get(x).toUpperCase() );
tagParams[x].put( "summary", paramInfo[x] );
tagParams[x].put( "default", "" );
if ( x < func.getMin() )
tagParams[x].put("required", "true" );
else
tagParams[x].put("required", "false" );
}
return tagParams;
}
}