package railo.transformer.cfml.tag;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import railo.commons.io.res.util.ResourceUtil;
import railo.commons.lang.StringUtil;
import railo.commons.lang.types.RefBoolean;
import railo.commons.lang.types.RefBooleanImpl;
import railo.runtime.Info;
import railo.runtime.SourceFile;
import railo.runtime.config.ConfigImpl;
import railo.runtime.exp.ApplicationException;
import railo.runtime.exp.PageExceptionImpl;
import railo.runtime.exp.TemplateException;
import railo.runtime.op.Caster;
import railo.runtime.type.KeyImpl;
import railo.runtime.type.util.KeyConstants;
import railo.runtime.type.util.ListUtil;
import railo.transformer.bytecode.Body;
import railo.transformer.bytecode.BodyBase;
import railo.transformer.bytecode.Page;
import railo.transformer.bytecode.Position;
import railo.transformer.bytecode.cast.CastOther;
import railo.transformer.bytecode.expression.Expression;
import railo.transformer.bytecode.expression.var.NullExpression;
import railo.transformer.bytecode.literal.LitString;
import railo.transformer.bytecode.statement.PrintOut;
import railo.transformer.bytecode.statement.StatementBase;
import railo.transformer.bytecode.statement.tag.Attribute;
import railo.transformer.bytecode.statement.tag.Tag;
import railo.transformer.bytecode.util.ASMUtil;
import railo.transformer.cfml.ExprTransformer;
import railo.transformer.cfml.attributes.AttributeEvaluatorException;
import railo.transformer.cfml.evaluator.EvaluatorException;
import railo.transformer.cfml.evaluator.impl.ProcessingDirectiveException;
import railo.transformer.cfml.expression.SimpleExprTransformer;
import railo.transformer.cfml.script.AbstrCFMLScriptTransformer.ComponentTemplateException;
import railo.transformer.cfml.script.CFMLScriptTransformer;
import railo.transformer.library.function.FunctionLib;
import railo.transformer.library.tag.CustomTagLib;
import railo.transformer.library.tag.TagLib;
import railo.transformer.library.tag.TagLibException;
import railo.transformer.library.tag.TagLibFactory;
import railo.transformer.library.tag.TagLibTag;
import railo.transformer.library.tag.TagLibTagAttr;
import railo.transformer.util.CFMLString;
/**
* Die Klasse CFMLTransformer ist das Herzstck des ᅵbersetzungsprozess,
* es liest die bergebene CFML Datei ein und bersetzt diese in ein valid (CFXD) XML Dokument
* in der Form eines org.w3c.dom.Document Object,
* die dann als weitere Vorlage zum bersetzten in PHP dient.
* Der CFMLTransformer bersetzt nur die Tags die innerhalb einer CFML Seite vorkommen,
* nicht die Ausdrcke die innerhalb von Attributen und dem Body eines Tag vorkommen knnen,
* fr dies ist der ExprTransformer zust¦ndig,
* der in der jeweiligen Tag Library definiert ist.
* Der CFMLTransformer kann zwar durch seine Grammatik,
* Tags erkennen aber nicht validieren.
* Erst mithilfe der im zugeteilten Tag Libraries kann er vergleichen ob ein Tag nur
* ein normaler HTML Tag ist, das er einfach als literale Zeichenkette aufnimmt,
* oder ob es sich um einen Tag handelt der eine konkrete Anweisung implementiert.
* Die Tag Library definiert alle in CFML vorhanden Tags,
* deren individuelle Grammatik und deren Aufbau und Verhalten.
* <pre>
Parser Grammatik nach EBNF (Extended Backus-Naur Form)
transform = {body}
body = [comment] ("</" | "<" tag body | literal body);
comment = "<!---" {?-"--->"} "--->";
literal = ("<" | {?-"#"-"<"} "<" | {"#" expression "#"} "<" ) | ({?-"<"} "<")
(* Welcher Teil der "oder" Bedingung ausgefhrt wird, ist abh¦ngig was die Tag-Lib vorgibt,
dass Expression geparst werden sollen oder nicht. *)
tag = name-space identifier spaces attributes ("/>" | ">" [body "</" identifier spaces ">"]);
(* Ob dem Tag ein Body und ein End-Tag folgt ist abh¦ngig von Definition des body-content in Tag-Lib, gleices gilt fr appendix *)
name-space = < tagLib[].getNameSpaceAndSeperator() >;
(* Vergleicht Zeichen mit den Namespacedefinitionen der Tag Libraries. *)
attributes = ({spaces attribute} "/>" | {spaces attribute} ">") | attribute-value;
(* Welcher Teil der "oder" Bedingung ausgefhrt wird, ist abh¦ngig von der Tag Attribute Definition in der Tag Lib. *)
attribute = attribute-name spaces "=" spaces attribute-value;
attribute-name = ("expression"|'expression'|expression) | identifier;
(* Ruft identifier oder den Expression Transformer auf je nach Attribute Definition in der Tag Lib. *)
attribute-value = expression;
identifier = (letter | "_") {letter | "_"|digit};
letter = "a".."z"|"A".."Z";
digit = "0".."9";
expression = <ExprTransfomer.expression()>; (* Ruft den Expression Transformer auf. *)
spaces = {space};
space = "\s"|"\t"|"\f"|"\t"|"\n";
{"x"}= 0 bis n mal "x"
["x"]= 0 bis 1 mal "x"
("x" | "y")"z" = "xz" oder "yz"
</pre>
*
*
*/
public final class CFMLTransformer {
public static short TAG_LIB_GLOBAL=0;
public static short TAG_LIB_PAGE=1;
/**
* Startmethode zum transfomieren einer CFML Datei.
* <br />
* EBNF:<br />
* <code>{body}</code>
* @param config
* @param sf CFML File
* @param tlibs Tag Library Deskriptoren, nach denen innerhalb der CFML Datei geprft werden soll.
* @param flibs Function Library Deskriptoren, nach denen innerhalb der Expressions der CFML Datei geprft werden soll.
* @return ᅵbersetztes CFXD Dokument Element.
* @throws TemplateException
* @throws IOException
*/
public Page transform(ConfigImpl config,SourceFile sf, TagLib[] tlibs, FunctionLib[] flibs) throws TemplateException, IOException {
Page p;
CFMLString cfml;
boolean writeLog=config.getExecutionLogEnabled();
String charset=config.getTemplateCharset();
boolean dotUpper = config.getDotNotationUpperCase();
while(true){
try {
cfml=new CFMLString(sf,charset,writeLog);
p = transform(config,cfml,tlibs,flibs,sf.getResource().lastModified(),dotUpper);
break;
}
catch(ProcessingDirectiveException pde) {
if(pde.getWriteLog()!=null)writeLog=pde.getWriteLog().booleanValue();
if(pde.getDotNotationUpperCase()!=null)dotUpper=pde.getDotNotationUpperCase().booleanValue();
if(!StringUtil.isEmpty(pde.getCharset()))charset=pde.getCharset();
}
}
// if cfc has no component tag or is script without cfscript
if(p.isPage() && ResourceUtil.getExtension(sf.getResource(),"").equalsIgnoreCase(config.getCFCExtension())){
cfml.setPos(0);
TagLibTag tlt;
CFMLString original = cfml;
// try inside a cfscript
tlt = CFMLTransformer.getTLT(original,"script");
String text="<"+tlt.getFullName()+">"+original.getText()+"</"+tlt.getFullName()+">";
cfml=new CFMLString(text,charset,writeLog,sf);
try {
while(true){
if(cfml==null){
cfml=new CFMLString(sf,charset,writeLog);
text="<"+tlt.getFullName()+">"+cfml.getText()+"</"+tlt.getFullName()+">";
cfml=new CFMLString(text,charset,writeLog,sf);
}
try {
p= transform(config,cfml,tlibs,flibs,sf.getResource().lastModified(),dotUpper);
break;
}
catch(ProcessingDirectiveException pde) {
if(pde.getWriteLog()!=null)writeLog=pde.getWriteLog().booleanValue();
if(pde.getDotNotationUpperCase()!=null)dotUpper=pde.getDotNotationUpperCase().booleanValue();
if(!StringUtil.isEmpty(pde.getCharset()))charset=pde.getCharset();
cfml=null;
}
}
}
catch (ComponentTemplateException e) {
throw e.getTemplateException();
}
catch (TemplateException e) {
//print.printST(e);
}
// try inside a component
if(p.isPage()){
tlt = CFMLTransformer.getTLT(original,"component");
text="<"+tlt.getFullName()+">"+original.getText()+"</"+tlt.getFullName()+">";
cfml=new CFMLString(text,charset,writeLog,sf);
while(true){
if(cfml==null){
cfml=new CFMLString(sf,charset,writeLog);
text="<"+tlt.getFullName()+">"+cfml.getText()+"</"+tlt.getFullName()+">";
cfml=new CFMLString(text,charset,writeLog,sf);
}
try {
p= transform(config,cfml,tlibs,flibs,sf.getResource().lastModified(),dotUpper);
break;
}
catch(ProcessingDirectiveException pde) {
if(pde.getWriteLog()!=null)writeLog=pde.getWriteLog().booleanValue();
if(pde.getDotNotationUpperCase()!=null)dotUpper=pde.getDotNotationUpperCase().booleanValue();
if(!StringUtil.isEmpty(pde.getCharset()))charset=pde.getCharset();
cfml=null;
}
}
}
}
return p;
}
public static TagLibTag getTLT(CFMLString cfml,String name) throws TemplateException {
TagLib tl;
try {
// this is already loaded, oherwise we where not here
tl = TagLibFactory.loadFromSystem();
return tl.getTag(name);
}
catch (TagLibException e) {
throw new TemplateException(cfml,e);
}
}
/**
* Startmethode zum transfomieren einer CFMLString.
* <br />
* EBNF:<br />
* <code>{body}</code>
* @param config
* @param cfml CFMLString
* @param tlibs Tag Library Deskriptoren, nach denen innerhalb der CFML Datei geprft werden soll.
* @param flibs Function Library Deskriptoren, nach denen innerhalb der Expressions der CFML Datei geprft werden soll.
* @param sourceLastModified
* @return ᅵbersetztes CFXD Dokument Element.
* @throws TemplateException
*/
public Page transform(ConfigImpl config,CFMLString cfml,TagLib[] tlibs,FunctionLib[] flibs, long sourceLastModified, Boolean dotNotationUpperCase) throws TemplateException {
TagLib[][] _tlibs=new TagLib[][]{null,new TagLib[0]};
_tlibs[TAG_LIB_GLOBAL]=tlibs;
// reset page tlds
if(_tlibs[TAG_LIB_PAGE].length>0) {
_tlibs[TAG_LIB_PAGE]=new TagLib[0];
}
SourceFile source=cfml.getSourceFile();
Page page=new Page(config,source.getPhyscalFile(),source.getFullClassName(),Info.getFullVersionInfo(),sourceLastModified,cfml.getWriteLog(),config.getSupressWSBeforeArg());
TagData data = new TagData(_tlibs,flibs,config.getCoreTagLib().getScriptTags(),cfml,dotNotationUpperCase,page);
//Body body=page;
try {
do {
body(data,page,false,null);
if(data.cfml.isAfterLast()) break;
if(data.cfml.forwardIfCurrent("</")){
int pos = data.cfml.getPos();
TagLib tagLib=nameSpace(data);
if(tagLib==null){
page.addPrintOut("</", null,null);
}
else {
String name = identifier(data.cfml,true);
if(tagLib.getIgnoreUnknowTags()) {
TagLibTag tlt = tagLib.getTag(name);
if(tlt==null) {
data.cfml.setPos(pos);
page.addPrintOut("</", null,null);
}
}
else throw new TemplateException(cfml,"no matching start tag for end tag ["+tagLib.getNameSpaceAndSeparator()+name+"]");
}
}
else
throw new TemplateException(cfml,"Error while transforming CFML File");
}while(true);
// call-back of evaluators
data.ep.run();
return page;
}
catch(TemplateException e) {
data.ep.clear();
throw e;
}
}
/**
* Liest den Body eines Tag ein. Kommentare, Tags und Literale inkl. Expressions.
* <br />
* EBNF:<br />
* <code>[comment] ("</" | "<" tag body | literal body);</code>
* @param body CFXD Body Element dem der Inhalt zugeteilt werden soll.
* @param parseExpression Definiert ob Expressions innerhalb von Literalen bersetzt werden sollen oder nicht.
* @param transformer Expression Transfomer zum bersetzten von Expression.
* @throws TemplateException
*/
private void body(TagData data,Body body, boolean parseExpression, ExprTransformer transformer) throws TemplateException {
boolean parseLiteral=true;
// Comment
comment(data.cfml,false);
// Tag
// is Tag Beginning
if(data.cfml.isCurrent('<')) {
// return if end tag and inside tag
if(data.cfml.isNext('/')) {
//railo.print.ln("early return");
return;
}
parseLiteral=!tag(data,body,parseExpression);
}
// no Tag
if(parseLiteral) {
literal(data,body, parseExpression, transformer);
}
// not at the end
if(data.cfml.isValidIndex())
body(data,body,parseExpression, transformer);
}
/**
* Liest einen Kommentar ein, Kommentare werden nicht in die CFXD bertragen sondern verworfen.
* Komentare knnen auch Kommentare enthalten.
* <br />
* EBNF:<br />
* <code>"<!---" {?-"--->"} "--->";</code>
* @throws TemplateException
*/
public static void comment(CFMLString cfml,boolean removeSpace) throws TemplateException {
if(!removeSpace) {
comment(cfml);
}
else {
cfml.removeSpace();
if(comment(cfml))cfml.removeSpace();
}
}
public static boolean comment(CFMLString cfml) throws TemplateException {
if(!cfml.forwardIfCurrent("<!---"))
return false;
int start=cfml.getPos();
short counter=1;
while(true) {
if(cfml.isAfterLast()) {
cfml.setPos(start);
throw new TemplateException(cfml,"no end comment found");
}
else if(cfml.forwardIfCurrent("<!---")) {
counter++;
}
else if(cfml.forwardIfCurrent("--->")) {
if(--counter==0) {
comment(cfml);
return true;
}
}
else {
cfml.next();
}
}
}
/**
* Liest Literale Zeichenketten ein die sich innerhalb und auserhalb von tgas befinden,
* beim Einlesen wird unterschieden ob Expression geparsst werden mssen oder nicht,
* dies ist abh¦ngig, von der Definition des Tag in dem man sich allenfalls befindet, innerhalb der TLD.
* @param parent bergeordnetes Element.
* @param parseExpression Definiert on Expressions geparset werden sollen oder nicht.
* @param transformer Expression Transfomer zum bersetzen der Expressions innerhalb des Literals.
* @throws TemplateException
*
* <br />
* EBNF:<br />
* <code>("<" | {?-"#"-"<"} "<" | {"#" expression "#"} "<" ) | ({?-"<"} "<")
(* Welcher Teil der "oder" Bedingung ausgefhrt wird, ist abh¦ngig ob die Tag-Lib vorgibt,
dass Expression geparst werden sollen oder nicht. *)</code>
*/
private void literal(TagData data,Body parent,boolean parseExpression, ExprTransformer transformer) throws TemplateException {
// with expression
if(parseExpression) {
if(data.cfml.isAfterLast())return;
// data.cfml.getCurrent()
StringBuffer text=new StringBuffer();
int count=0;
while(data.cfml.isValidIndex()) {
count++;
// #
if(data.cfml.isCurrent('#')) {
data.cfml.next();
if(data.cfml.isCurrent('#')) {
text.append('#');
}
else {
if(text.length()>0) {
parent.addPrintOut(text.toString(),null,null);
text=new StringBuffer();
}
Position line = data.cfml.getPosition();
PrintOut po;
parent.addStatement(po=new PrintOut(transformer.transform(data.page,data.ep,data.flibs,data.scriptTags,data.cfml,data.settings),line,null));
po.setEnd(data.cfml.getPosition());
if(!data.cfml.isCurrent('#'))
throw new TemplateException(data.cfml,"missing terminating [#] for expression");
}
}
else if(data.cfml.isCurrent('<') && count>1) {
break;
}
else
text.append(data.cfml.getCurrent());
data.cfml.next();
}
if(text.length()>0)parent.addPrintOut(text.toString(), null,null);
}
// no expression
else {
int start=data.cfml.getPos();
data.cfml.next();
int end=data.cfml.indexOfNext('<');
String text;
if(end==-1) {
text=data.cfml.substring(start);
data.cfml.setPos(data.cfml.length());
}
else {
text=data.cfml.substring(start,end-start);
data.cfml.setPos(end);
}
parent.addPrintOut(text, null,null);
}
}
/**
* Liest einen Tag ein, prft hierbei ob das Tag innerhalb einer der geladenen Tag-Lib existiert,
* ansonsten wird ein Tag einfach als literal-string aufgenommen.
* <br />
* EBNF:<br />
* <code>name-space identifier spaces attributes ("/>" | ">" [body "</" identifier spaces ">"]);(* Ob dem Tag ein Body und ein End-Tag folgt ist abh¦ngig von Definition des body-content in Tag-Lib, gleices gilt fr appendix *)</code>
* @param parent bergeornetes Tag
* @param parseExpression sollen Expresson innerhalb des Body geparste werden oder nicht.
* @return Gibt zurck ob es sich um ein Tag as einer Tag-Lib handelte oder nicht.
* @throws TemplateException
*/
private boolean tag(TagData data,Body parent,boolean parseExpression) throws TemplateException {
//railo.print.ln("--->"+data.cfml.getCurrent());
boolean hasBody=false;
Position line = data.cfml.getPosition();
//int column=data.cfml.getColumn();
int start=data.cfml.getPos();
data.cfml.next();
// read in namesapce of tag
TagLib tagLib=nameSpace(data);
// return if no matching tag lib
if(tagLib==null) {
data.cfml.previous();
return false;
}
// Get matching tag from tag lib
String strNameNormal=identifier(data.cfml,false);
if(strNameNormal==null) {
data.cfml.setPos((data.cfml.getPos()-tagLib.getNameSpaceAndSeparator().length())-1);
return false;
}
String strName=strNameNormal.toLowerCase();
String appendix=null;
TagLibTag tagLibTag=tagLib.getTag(strName);
// get taglib
if(tagLibTag==null) {
tagLibTag=tagLib.getAppendixTag(strName);
if(tagLibTag==null) {
if(tagLib.getIgnoreUnknowTags()){
data.cfml.setPos(start);
return false;
}
throw new TemplateException(data.cfml,"undefined tag ["+tagLib.getNameSpaceAndSeparator()+strName+"]");
}
appendix=StringUtil.removeStartingIgnoreCase(strNameNormal,tagLibTag.getName());
}
// CFXD Element
Tag tag;
try {
tag = tagLibTag.getTag(line,data.cfml.getPosition());
}
catch (Exception e) {
throw new TemplateException(data.cfml,e);
}
parent.addStatement(tag);
// get tag from tag library
if(appendix!=null) {
tag.setAppendix(appendix);
tag.setFullname(tagLibTag.getFullName().concat(appendix));
}
else {
tag.setFullname(tagLibTag.getFullName());
}
if(tag.getFullname().equalsIgnoreCase("cfcomponent"))data.page.setIsComponent(true); // MUST to hardcoded, to better
else if(tag.getFullname().equalsIgnoreCase("cfinterface"))data.page.setIsInterface(true); // MUST to hardcoded, to better
tag.setTagLibTag(tagLibTag);
comment(data.cfml,true);
// Tag Translator Evaluator
if(tagLibTag.hasTteClass()) {
data.ep.add(tagLibTag,tag,data.flibs,data.cfml);
}
//get Attributes
attributes(data,tagLibTag,tag);
if(tagLibTag.hasAttributeEvaluator()) {
try {
tagLibTag=tagLibTag.getAttributeEvaluator().evaluate(tagLibTag,tag);
} catch (AttributeEvaluatorException e) {
throw new TemplateException(data.cfml, e);
}
}
// End of begin Tag
// TODO muss erlaubt sein
if(data.cfml.forwardIfCurrent('>')) {
hasBody=tagLibTag.getHasBody();
}
else if(data.cfml.forwardIfCurrent('/','>')) {
if(tagLibTag.getHasBody())tag.setBody(new BodyBase());
}
else {
throw createTemplateException(data.cfml, "tag ["+tagLibTag.getFullName()+"] is not closed",tagLibTag);
}
// Body
if(hasBody) {
// get Body
if(tagLibTag.isTagDependent()) {
// get TagDependentBodyTransformer
TagDependentBodyTransformer tdbt=null;
try {
tdbt=tagLibTag.getBodyTransformer();
} catch (TagLibException e) {
throw new TemplateException(data.cfml,e);
}
if(tdbt==null) throw createTemplateException(data.cfml,"Tag dependent body Transformer is invalid for Tag ["+tagLibTag.getFullName()+"]",tagLibTag);
tdbt.transform(data.page,this,data.ep,data.flibs,tag,tagLibTag,data.scriptTags,data.cfml,data.settings);
// get TagLib of end Tag
if(!data.cfml.forwardIfCurrent("</")) {
// MUST this is a patch, do a more proper implementation
TemplateException te = new TemplateException(data.cfml,"invalid construct");
if(tdbt instanceof CFMLScriptTransformer && ASMUtil.containsComponent(tag.getBody())) {
throw new CFMLScriptTransformer.ComponentTemplateException(te);
}
throw te;
}
TagLib tagLibEnd=nameSpace(data);
// same NameSpace
if(!(tagLibEnd!=null && tagLibEnd.getNameSpaceAndSeparator().equals(tagLib.getNameSpaceAndSeparator())))
throw new TemplateException(data.cfml,"invalid construct");
// get end Tag
String strNameEnd=identifier(data.cfml,true).toLowerCase();
// not the same name Tag
if(!strName.equals(strNameEnd)) {
data.cfml.setPos(start);
throw new TemplateException(data.cfml,"Start and End Tag has not the same Name ["+tagLib.getNameSpaceAndSeparator()+strName+"-"+tagLibEnd.getNameSpaceAndSeparator()+strNameEnd+"]");
}
data.cfml.removeSpace();
if(!data.cfml.forwardIfCurrent('>'))
throw new TemplateException(data.cfml,"End Tag ["+tagLibEnd.getNameSpaceAndSeparator()+strNameEnd+"] not closed");
}
else {
// get body of Tag
BodyBase body=new BodyBase();
body.setParent(tag);
//tag.setBody(body);
//parseExpression=(tagLibTag.getParseBody())?true:parseExpression;
if(tagLibTag.getParseBody())parseExpression=true;
while(true) {
// Load Expession Transformer from TagLib
ExprTransformer transfomer=null;
if(parseExpression) {
try {
transfomer = tagLibTag.getTagLib().getExprTransfomer();
} catch (TagLibException e) {
throw new TemplateException(data.cfml,e);
}
}
// call body
body(data,body,parseExpression,transfomer);
// no End Tag
if(data.cfml.isAfterLast()) {
if(tagLibTag.isBodyReq()) {
data.cfml.setPos(start);
throw createTemplateException(data.cfml,"No matching end tag found for tag ["+tagLibTag.getFullName()+"]",tagLibTag);
}
body.moveStatmentsTo(parent);
return executeEvaluator(data,tagLibTag, tag);
}
// Invalid Construct
int posBeforeEndTag=data.cfml.getPos();
if(!data.cfml.forwardIfCurrent('<','/'))
throw createTemplateException(data.cfml,"Missing end tag for ["+tagLibTag.getFullName()+"]",tagLibTag);
// get TagLib of end Tag
int _start = data.cfml.getPos();
TagLib tagLibEnd=nameSpace(data);
// same NameSpace
if(tagLibEnd!=null) {
String strNameEnd="";
//railo.print.ln(data.cfml.getLine()+" - "+data.cfml.getColumn()+" - "+tagLibEnd.getNameSpaceAndSeperator()+".equals("+tagLib.getNameSpaceAndSeperator()+")");
if(tagLibEnd.getNameSpaceAndSeparator().equals(tagLib.getNameSpaceAndSeparator())) {
// get end Tag
strNameEnd=identifier(data.cfml,true).toLowerCase();
// not the same name Tag
// new part
data.cfml.removeSpace();
if(strName.equals(strNameEnd)) {
if(!data.cfml.forwardIfCurrent('>'))
throw new TemplateException(data.cfml,"End Tag ["+tagLibEnd.getNameSpaceAndSeparator()+strNameEnd+"] not closed");
break;
}
}
// new part
if(tagLibTag.isBodyReq()) {
TagLibTag endTag = tagLibEnd.getTag(strNameEnd);
if(endTag!=null && !endTag.getHasBody())
throw new TemplateException(data.cfml,
"End Tag ["+
tagLibEnd.getNameSpaceAndSeparator()+strNameEnd+"] is not allowed, for this tag only a Start Tag is allowed");
data.cfml.setPos(start);
if(tagLibEnd.getIgnoreUnknowTags() && (tagLibEnd.getTag(strNameEnd))==null){
data.cfml.setPos(_start);
}
else throw new TemplateException(data.cfml,
"Start and End Tag has not the same Name ["+
tagLib.getNameSpaceAndSeparator()+strName+"-"+tagLibEnd.getNameSpaceAndSeparator()+strNameEnd+"]");
}
else {
body.moveStatmentsTo(parent);
data.cfml.setPos(posBeforeEndTag);
return executeEvaluator(data,tagLibTag, tag);
}
/// new part
}
body.addPrintOut("</",null,null);
}
tag.setBody(body);
}
}
if(tag instanceof StatementBase)
((StatementBase)tag).setEnd(data.cfml.getPosition());
// Tag Translator Evaluator
return executeEvaluator(data,tagLibTag, tag);
}
private boolean executeEvaluator(TagData data,TagLibTag tagLibTag, Tag tag) throws TemplateException {
if(tagLibTag.hasTteClass()) {
try {
TagLib lib=tagLibTag.getEvaluator().execute(data.config,tag,tagLibTag,data.flibs,data);
if(lib!=null) {
// set
for(int i=0;i<data.tlibs[TAG_LIB_PAGE].length;i++) {
if(data.tlibs[TAG_LIB_PAGE][i].getNameSpaceAndSeparator().equalsIgnoreCase(lib.getNameSpaceAndSeparator())){
boolean extIsCustom=data.tlibs[TAG_LIB_PAGE][i] instanceof CustomTagLib;
boolean newIsCustom=lib instanceof CustomTagLib;
// TagLib + CustomTagLib (visa/versa)
if(extIsCustom){
((CustomTagLib)data.tlibs[TAG_LIB_PAGE][i]).append(lib);
return true;
}
else if(newIsCustom){
((CustomTagLib)lib).append(data.tlibs[TAG_LIB_PAGE][i]);
data.tlibs[TAG_LIB_PAGE][i]=lib;
return true;
}
}
}
// TODO make sure longer namespace ar checked firts to support subsets, same for core libs
// insert
TagLib[] newTlibs=new TagLib[data.tlibs[TAG_LIB_PAGE].length+1];
for(int i=0;i<data.tlibs[TAG_LIB_PAGE].length;i++) {
newTlibs[i]=data.tlibs[TAG_LIB_PAGE][i];
}
newTlibs[data.tlibs[TAG_LIB_PAGE].length]=lib;
data.tlibs[TAG_LIB_PAGE]=newTlibs;
}
}
catch (EvaluatorException e) {
throw new TemplateException(data.cfml,e);
}
}
return true;
}
/**
* Vergleicht folgende Zeichen mit den Namespacedefinitionen der Tag Libraries,
* gibt eine Tag-Lib zurck falls eine passt, ansonsten null.
* <br />
* EBNF:<br />
* <code>< tagLib[].getNameSpaceAndSeperator() >(* Vergleicht Zeichen mit den Namespacedefinitionen der Tag Libraries. *) </code>
* @return TagLib Passende Tag Lirary oder null.
*/
private TagLib nameSpace(TagData data) {
boolean hasTag=false;
int start = data.cfml.getPos();
TagLib tagLib=null;
// loop over NameSpaces
for(int i=1;i>=0;i--) {
for(int ii=0;ii<data.tlibs[i].length;ii++) {
tagLib= data.tlibs[i][ii];
char[] c=tagLib.getNameSpaceAndSeperatorAsCharArray();
// Loop over char of NameSpace and Sepearator
hasTag=true;
for(int y=0;y<c.length;y++) {
if(!(data.cfml.isValidIndex() && c[y]==data.cfml.getCurrentLower())) {
//hasTag=true;
//} else {
hasTag=false;
data.cfml.setPos(start);
break;
}
data.cfml.next();
}
if(hasTag)return tagLib;//break;
}
//if(hasTag) return tagLib;
}
return null;
}
/**
* Liest die Attribute eines Tags ein, dies Abh¦ngig von der Definition innerhalb der Tag-Lib.
* Hierbei unterscheiden wir vier verschiedene Arten von Attributen:<br>
* <ul>
* <li>FIX: Definierte Attribute Fix, fr jedes Attribut ist definiert ob es required ist oder nicht (gleich wie JSP). </li>
* <li>DYNAMIC: Die Attribute des Tag sind frei, keine Namen sind vorgegeben.
* Es kann aber definiert sein wieviele Attribute maximal und minimal verwendetet werden drfen.</li>
* <li>FULLDYNAMIC: Gleich wie DYNAMIC, jedoch kann der Name des Attribut auch ein dynamischer Wert sein (wie bei cfset).</li>
* <li>NONAME: Ein Tag welches nur ein Attribut besitzt ohne Name, sondern einfach nur mit einem Attribut Wert</li>
* </ul>
* <br />
* EBNF:<br />
* <code>({spaces attribute} "/>" | {spaces attribute} ">") | attribute-value;(* Welcher Teil der "oder" Bedingung ausgefhrt wird, ist abh¦ngig von der Tag Attribute Definition in der Tag Lib. *)</code>
* @param tag
* @param parent
* @throws TemplateException
*/
public static void attributes(TagData data,TagLibTag tag, Tag parent) throws TemplateException {
int type=tag.getAttributeType();
int start = data.cfml.getPos();
// Tag with attribute names
if( type!=TagLibTag.ATTRIBUTE_TYPE_NONAME) {
try{
int min=tag.getMin();
int max=tag.getMax();
int count=0;
ArrayList<String> args=new ArrayList<String>();
RefBoolean allowDefaultValue=new RefBooleanImpl(tag.getDefaultAttribute()!=null);
while(data.cfml.isValidIndex()) {
data.cfml.removeSpace();
// if no more attributes break
if(data.cfml.isCurrent('/') || data.cfml.isCurrent('>')) break;
parent.addAttribute(attribute(data,tag,args,allowDefaultValue));
count++;
}
// set default values
if(tag.hasDefaultValue()) {
Map<String, TagLibTagAttr> hash = tag.getAttributes();
Iterator<String> it = hash.keySet().iterator();
while(it.hasNext()) {
TagLibTagAttr att=hash.get(it.next());
if(!parent.containsAttribute(att.getName()) && att.hasDefaultValue()) {
Attribute attr=new Attribute(tag.getAttributeType()==TagLibTag.ATTRIBUTE_TYPE_DYNAMIC,
att.getName(),
CastOther.toExpression(LitString.toExprString(Caster.toString(att.getDefaultValue(),null)),att.getType()),att.getType()
);
parent.addAttribute(attr);
}
}
}
boolean hasAttributeCollection=args.contains("attributecollection");
// to less attributes
if(!hasAttributeCollection && min>count)
throw createTemplateException(data.cfml,"the tag "+tag.getFullName()+" must have at least "+min+" attributes",tag);
// too much attributes
if(!hasAttributeCollection && max>0 && max<count)
throw createTemplateException(data.cfml,"the tag "+tag.getFullName()+" can have a maximum of "+max+" attributes",tag);
// not defined attributes
if(type==TagLibTag.ATTRIBUTE_TYPE_FIXED || type==TagLibTag.ATTRIBUTE_TYPE_MIXED) {
Map<String, TagLibTagAttr> hash = tag.getAttributes();
Iterator<String> it = hash.keySet().iterator();
while(it.hasNext()) {
TagLibTagAttr att=hash.get(it.next());
if(att.isRequired() && !args.contains(att.getName()) && att.getDefaultValue()==null) {
if(!hasAttributeCollection)throw createTemplateException(data.cfml,"attribute "+att.getName()+" is required for tag "+tag.getFullName(),tag);
parent.addMissingAttribute(att.getName(),att.getType());
}
}
}
}
catch(TemplateException te){
data.cfml.setPos(start);
// if the tag supports a non name attribute try this
TagLibTagAttr sa = tag.getSingleAttr();
if(sa!=null) attrNoName(parent,tag,data,sa);
else throw te;
}
}
// tag without attributes name
else {
attrNoName(parent,tag,data,null);
}
}
private static void attrNoName(Tag parent, TagLibTag tag, TagData data,TagLibTagAttr attr) throws TemplateException {
if(attr==null)attr=tag.getFirstAttribute();
String strName="noname";
String strType="any";
boolean pe=true;
if(attr!=null) {
strName=attr.getName();
strType=attr.getType();
pe=attr.getRtexpr();
}
//LitString.toExprString("",-1);
Attribute att=new Attribute(false,strName,attributeValue(data,tag,strType,pe,true,NullExpression.NULL_EXPRESSION),strType);
parent.addAttribute(att);
}
/**
* Liest ein einzelnes Atribut eines tag ein (nicht NONAME).
* <br />
* EBNF:<br />
* <code>attribute-name spaces "=" spaces attribute-value;</code>
* @param tag Definition des Tag das dieses Attribut enth¦lt.
* @param args Container zum Speichern einzelner Attribute Namen zum nachtr¦glichen Prufen gegen die Tag-Lib.
* @return Element Attribute Element.
* @throws TemplateException
*/
private static Attribute attribute(TagData data,TagLibTag tag, ArrayList<String> args,RefBoolean allowDefaultValue) throws TemplateException {
Expression value=null;
// Name
StringBuffer sbType=new StringBuffer();
RefBoolean dynamic=new RefBooleanImpl(false);
boolean isDefaultValue=false;
boolean[] parseExpression=new boolean[2];
parseExpression[0]=true;
parseExpression[1]=false;
String name=attributeName(data.cfml,dynamic,args,tag,sbType,parseExpression,allowDefaultValue.toBooleanValue());
// mixed in a noname attribute
if(StringUtil.isEmpty(name)){
allowDefaultValue.setValue(false);
TagLibTagAttr attr = tag.getDefaultAttribute();
if(attr==null)
throw new TemplateException(data.cfml,"Invalid Identifier.");
name=attr.getName();
sbType.append(attr.getType());
isDefaultValue=true;
}
comment(data.cfml,true);
if(isDefaultValue || data.cfml.forwardIfCurrent('=')) {
comment(data.cfml,true);
// Value
value=attributeValue(data,tag,sbType.toString(),parseExpression[0],false,LitString.toExprString(""));
}
// default value boolean true
else {
value=tag.getAttributeDefaultValue();
if(sbType.toString().length()>0) {
value=CastOther.toExpression(value, sbType.toString());
}
}
comment(data.cfml,true);
return new Attribute(dynamic.toBooleanValue(),name,value,sbType.toString());
}
/**
* Liest den Namen eines Attribut ein, je nach Attribut-Definition innerhalb der Tag-Lib,
* wird der Name ber den identifier oder den Expression Transformer eingelesen.
* <ul>
* <li>FIX und DYNAMIC --> identifier </li>
* <li>FULLDYNAMIC --> Expression Transformer </li>
* </ul>
* <br />
* EBNF:<br />
* <code>("expression"|'expression'|expression) | identifier;(* Ruft identifier oder den Expression Transformer auf je nach Attribute Definition in der Tag Lib. *)</code>
* @param dynamic
* @param args Container zum Speichern einzelner Attribute Namen zum nachtr¦glichen Prufen gegen die Tag-Lib.
* @param tag Aktuelles tag aus der Tag-Lib
* @param sbType Die Methode speichert innerhalb von sbType den Typ des Tags, zur Interpretation in der attribute Methode.
* @param parseExpression Soll der Wert des Attributes geparst werden
* @return Attribute Name
* @throws TemplateException
*/
private static String attributeName(CFMLString cfml,RefBoolean dynamic, ArrayList<String> args, TagLibTag tag,
StringBuffer sbType, boolean[] parseExpression,boolean allowDefaultValue) throws TemplateException {
String _id = identifier(cfml,!allowDefaultValue);
if(StringUtil.isEmpty(_id)){
return null;
}
int typeDef=tag.getAttributeType();
String id=StringUtil.toLowerCase(_id);
if(args.contains(id)) throw createTemplateException(cfml,"you can't use the same tag attribute ["+id+"] twice",tag);
args.add(id);
if("attributecollection".equals(id)){
dynamic.setValue(tag.getAttribute(id)==null);
sbType.append("struct");
parseExpression[0]=true;
parseExpression[1]=true;
}
else if(typeDef==TagLibTag.ATTRIBUTE_TYPE_FIXED || typeDef==TagLibTag.ATTRIBUTE_TYPE_MIXED) {
TagLibTagAttr attr=tag.getAttribute(id);
if(attr==null) {
if(typeDef==TagLibTag.ATTRIBUTE_TYPE_FIXED) {
String names=tag.getAttributeNames();
if(StringUtil.isEmpty(names))
throw createTemplateException(cfml,
"Attribute "+id+" is not allowed for tag "+tag.getFullName(),tag);
try{
names=ListUtil.sort(names, "textnocase",null, null);
}
catch(Throwable t){}
throw createTemplateException(cfml,
"Attribute "+id+" is not allowed for tag "+tag.getFullName(),
"valid attribute names are ["+names+"]",tag);
}
dynamic.setValue(true);
}
else {
sbType.append(attr.getType());
parseExpression[0]=attr.getRtexpr();
}
}
else if(typeDef==TagLibTag.ATTRIBUTE_TYPE_DYNAMIC){
dynamic.setValue(true);
}
return id;
}
/**
* Liest den Wert eines Attribut, mithilfe des innerhalb der Tag-Lib definierten Expression Transformer, ein.
* <br />
* EBNF:<br />
* <code>expression;</code>
* @param tag
* @param type
* @param parseExpression
* @param isNonName
* @return Element Eingelesener bersetzer Wert des Attributes.
* @throws TemplateException
*/
public static Expression attributeValue(TagData data,TagLibTag tag, String type,boolean parseExpression,boolean isNonName, Expression noExpression) throws TemplateException {
Expression expr;
try {
ExprTransformer transfomer=null;
if(parseExpression){
transfomer = tag.getTagLib().getExprTransfomer();
}
else {
if(data.getSimpleExprTransformer()==null) {
data.setSimpleExprTransformer(new SimpleExprTransformer('#'));
//set.setSpecialChar();
}
transfomer=data.getSimpleExprTransformer();
}
if(isNonName) {
int pos=data.cfml.getPos();
try {
expr=transfomer.transform(data.page,data.ep,data.flibs,data.scriptTags,data.cfml,data.settings);
}
catch(TemplateException ete) {
if(data.cfml.getPos()==pos)expr=noExpression;
else throw ete;
}
}
else expr=transfomer.transformAsString(data.page,data.ep,data.flibs,data.scriptTags,data.cfml,data.settings,true);
if(type.length()>0) {
expr=CastOther.toExpression(expr, type);
}
} catch (TagLibException e) {
throw new TemplateException(data.cfml,e);
}
return expr;
}
/**
* Liest einen Identifier ein und gibt diesen als String zurck.
* <br />
* EBNF:<br />
* <code>(letter | "_") {letter | "_"|digit};</code>
* @param throwError throw error or return null if name is invalid
* @return Identifier String.
* @throws TemplateException
*/
public static String identifier(CFMLString cfml,boolean throwError) throws TemplateException {
int start = cfml.getPos();
if(!cfml.isCurrentBetween('a','z') && !cfml.isCurrent('_')) {
if(throwError)throw new TemplateException(cfml,"Invalid Identifier.");
return null;
}
do {
cfml.next();
if(!(cfml.isCurrentBetween('a','z')
|| cfml.isCurrentBetween('0','9')
|| cfml.isCurrent('_')
|| cfml.isCurrent(':')
|| cfml.isCurrent('-'))) {
break;
}
}
while (cfml.isValidIndex());
return cfml.substring(start,cfml.getPos()-start);
}
public static TemplateException createTemplateException(CFMLString cfml,String msg, String detail,TagLibTag tag) {
TemplateException te = new TemplateException(cfml,msg,detail);
setAddional(te,tag);
return te;
}
public static TemplateException createTemplateException(CFMLString cfml,String msg, TagLibTag tag) {
TemplateException te = new TemplateException(cfml,msg);
setAddional(te,tag);
return te;
}
public static TemplateException setAddional(TemplateException te, TagLibTag tlt) {
setAddional((PageExceptionImpl)te, tlt);
return te;
}
public static ApplicationException setAddional(ApplicationException ae, TagLibTag tlt) {
setAddional((PageExceptionImpl)ae, tlt);
return ae;
}
private static void setAddional(PageExceptionImpl pe, TagLibTag tlt) {
Map<String, TagLibTagAttr> attrs = tlt.getAttributes();
Iterator<Entry<String, TagLibTagAttr>> it = attrs.entrySet().iterator();
Entry<String, TagLibTagAttr> entry;
TagLibTagAttr attr;
// Pattern
StringBuilder pattern=new StringBuilder("<");
pattern.append((tlt.getFullName()));
StringBuilder req=new StringBuilder();
StringBuilder opt=new StringBuilder();
StringBuilder tmp;
pattern.append(" ");
int c=0;
while(it.hasNext()){
entry = it.next();
attr=entry.getValue();
tmp=attr.isRequired()?req:opt;
tmp.append(" ");
if(!attr.isRequired()) tmp.append("[");
if(c++>0)pattern.append(" ");
tmp.append(attr.getName());
tmp.append("\"");
tmp.append(attr.getType());
tmp.append("\"");
if(!attr.isRequired()) tmp.append("]");
}
if(req.length()>0)pattern.append(req);
if(opt.length()>0)pattern.append(opt);
if(tlt.getAttributeType()==TagLibTag.ATTRIBUTE_TYPE_MIXED || tlt.getAttributeType()==TagLibTag.ATTRIBUTE_TYPE_DYNAMIC)
pattern.append(" ...");
pattern.append(">");
if(tlt.getHasBody()) {
if(tlt.isBodyReq()){
pattern.append("</");
pattern.append(tlt.getFullName());
pattern.append(">");
}
else if(tlt.isBodyFree()){
pattern.append("[</");
pattern.append(tlt.getFullName());
pattern.append(">]");
}
}
pe.setAdditional(KeyConstants._Pattern, pattern);
// Documentation
StringBuilder doc=new StringBuilder(tlt.getDescription());
req=new StringBuilder();
opt=new StringBuilder();
doc.append("\n");
it = attrs.entrySet().iterator();
while(it.hasNext()){
entry = it.next();
attr=entry.getValue();
tmp=attr.isRequired()?req:opt;
tmp.append("* ");
tmp.append(attr.getName());
tmp.append(" (");
tmp.append(attr.getType());
tmp.append("): ");
tmp.append(attr.getDescription());
tmp.append("\n");
}
if(req.length()>0)doc.append("\nRequired:\n").append(req);
if(opt.length()>0)doc.append("\nOptional:\n").append(opt);
pe.setAdditional(KeyImpl.init("Documentation"), doc);
}
}