@doc>
Implements a predictive parser for dynamic expressions
(c) SAP AG 2003-2006. All rights reserved.
#INCLUDE[defs.inc]
///////////////////////////////////////////////////////////////////////
// CLASS DEFINITION
class DynExpParserPtn inherit dev:DynExpParser;
///////////////////////////////////////////////////////////////////////
// PROPERTIES
//FORMAT in A1S is translatable, so there are 2 obligatory args
override readonly property FORMAT_OBLIGATORY_ARGS = 2;
readonly property translationCounter = 0;
<@doc>Lists the supported function groups
override readonly property supportedFuncGroups = {s:true, n:true, d:true, t:true, q:true, b:true, m:true, o:true};
///////////////////////////////////////////////////////////////////////
// PUBLIC METHODS
<@doc scope="protected">
in VC A1S empty boolean field is not allowed
override method emptyExpression()
//check if expression is empty
var empty = this.supercall();
if (empty && this.expectedType == #[BOOLEAN])
throw new Error(-1, '#TEXT[XMSG_EMPTY_BOOLEAN]');
return empty;
end
<@doc scope="protected">
reset translation properties
override method resetTranslation()
this.translationCounter = 0;
end
<@doc scope="protected">
Validates the syntax of TRANSLATE/FORMAT
TRANLSATE and FORMAT behaves differently in CE and in A1S
FORMAT in A1S
=============
1)2 obligatory args which are the text and the tranlsation type. the text might contains place holders
for the rest of FORMAT's arguments (FORMAT in CE has 1 obligatory arg).
2)The first FORMAT arg can be only text.
3)FORMAT is translatable in A1S thus the second param is the translation type and
it is validated.
TRANSLATE in A1S
==============
1)validate TRANSLATE arguments only on parseByName mode, since the macro's syntax is different
in parseById mode (the persistence of TRANSLATE is different then the display to the user)
- the same goes for the FORMAT
2)if the trans type is missing it is added automatically. so, for the user the second arg
for TRANSLATE is optional.
the func handled
override method validateTransFunc(token)
if(!this.parseByID && token.token == this.TOKEN_FUNC)
{
var tokenValue = token.value;
var child0 = token.children && token.children[0];
var child1 = token.children && token.children[1] && token.children[1].value;
if(tokenValue == this.TOKEN_FUNC_TRANSLATE || tokenValue == this.TOKEN_FUNC_FORMAT)
{
//first argument for TRANSLATE/FORMAT should be literal.
if(child0 && child0.token != 'VAL')
throw new Error(-1, '#TEXT[XMSG_ERROR_INVALID_ARG_FORMAT] ' + tokenValue);
//validate translation type for TRANSLATE/FORMAT, is missing add it automaticaly
if(child1==null || child1==undefined)
{
token.children[1] = {token:'VAL', value:this.transType, type:'s'};
token.size=2;
this.warning= '#TEXT[XMSG_INVALID_TRANSLATION_TYPE] '.replace('{0}',this.transType);
}
//if trans type is missing - add the deafult
else if(!this.checkTranslationType(child1))
{
token.children[1].value = this.transType;
this.warning= '#TEXT[XMSG_INVALID_TRANSLATION_TYPE] '.replace('{0}',this.transType);
}
}
//validate no. of argument vs. place holders for FORMAT
if(tokenValue == this.TOKEN_FUNC_FORMAT)
{
var total = this.handleDynamicArgs(token, child0.value);
if (token.size > total) throw new Error(-1, '#TEXT[XMSG_ERROR_TOO_MANY_ARGS] '+tokenValue);
if (token.size < total ) throw new Error(-1, '#TEXT[XMSG_ERROR_MISSING_ARG_FUNC] '+tokenValue);
}
}
end
<@doc scope="private">
set the rule for validating field expression
field full name
token represents the field
override method isBOField(expr)
if(expr.indexOf("@")>=0 && expr.match(/^(\[([^\]]+)\]|([\w\.^\]]+))?\@(\[([^\]]+)\]|(\@)|([\w\.]+))+/))
{
this.lexpos += RegExp.lastIndex;
return this.validateField(RegExp.lastMatch);
}
return null;
end
<@doc scope="private">
checks if field name is valid
the field name structure: infoshape@field.subtype1.subtype2 (no cluster infosjapes for pattern)
when parsing by id - the structure is: AH@AB.subtype1.subtype2 where AH = infoshape id, AB = field id
field full name (by name of id)
token represents the field
method validateField(expression)
//TODO: change getAlias to getName in patterns code !!!
var theInfosets = [], theInfoShapes = [], theField = null;
var identifier = null;
var F, fieldName="", fieldId="";
var pair = [], names = [], givenInfoshapeName = "", givenFieldName = "";
var i = 0;
//split into 2 sections: infoshapes names and fields names
this.split(expression, '@', pair, true);
givenInfoshapeName = pair[0];
givenFieldName = pair[1];
//root infoset
//=======================
if(givenInfoshapeName.length > 0)
{
try
{
identifier = this.handleReservedChar(givenInfoshapeName, true);
//get the infoshape obj
var allInfosets = this.infosetEnum.getInfosetList(false);
if(this.parseByID) {
theInfosets.push(allInfosets[identifier]);
theInfoShapes.push(allInfosets[identifier].getInfoshape());
}
else
{
//get a list of all infoshape by that name
for(var set in allInfosets)
if(allInfosets[set].getInfosetAlias() == identifier) {
theInfosets.push(allInfosets[set]);
theInfoShapes.push(allInfosets[set].getInfoshape());
}
}
}
catch(e) //catch the error of wrong infoshape name
{
}
//not found the infoset sent
if(!theInfosets || theInfosets.length==0) {
fieldName = givenInfoshapeName;
fieldId = givenInfoshapeName;
throw new Error(-1, '#TEXT[XMSG_ERROR_MISSING_ITERACTOR] '+ fieldName);
}
}
else
{
//check if sub infoshape of current infoset
theInfosets.push(this.currInfoset);
theInfoShapes.push(this.currInfoset && this.currInfoset.getInfoshape());
}
//child infoshapes & fields
//=============
identifier = givenFieldName;
if (identifier == '@')
{
if(null == this.field)
{
throw new Error(-1, '#TEXT[XMSG_ERROR_CUR_FIELD_SUPPORT]');
}
if(this.parseByID)
{
identifier = this.field.getId();
}
else
{
identifier = this.field.getAlias(); //TODO: change to getName()
}
}
//==================================
//split infoshapes and fields names
this.split(identifier, '.', names, false);
for(var k in names)
names[k] = this.handleReservedChar(names[k], true);
var infoset = null;
var infoShape = null;
//when for UI - we get the last child id after the @ - infoset_id@flat_field_id
for(var j=0, len=theInfosets.length; j
for TRANLSATE/FORMAT persistence is different from UI
the text and the translation type are replaced with a key
token currently handles
displying the expr in UI or for persistence
holds the name of macro and arguments
override method setDisplayTransFunc(token, b4UI, buf)
var t=token.token, v=token.value, c=token.children;
var tmpIndex = 0;
if( (v==this.TOKEN_FUNC_TRANSLATE || v==this.TOKEN_FUNC_FORMAT) &&
#SYS[SUPPORT_TRANSLATION] && c[0].token == 'VAL' && this.element)
{
////// Support Inner DE Translations ////////
var outStr = '';
tmpIndex = 1;
var tType = null;
// Get needed trasnlations params:
var tText = TRIM(c[0].value);
if(!tText)
throw new Error(-1, '#TEXT[XMSG_ERR_TRANSLATION_PARAMETERS]');
// Is there already a translation Text item for the property given:
var key = this.element.getTransKey(this.propName);
if(!b4UI)
{
tType = c[1] && c[1].value;
// Validate translation type:
if(!this.checkTranslationType(tType))
tType = this.transType;
// Persisting the DE with the translation support.
// The Type should not be perstisted in the property value but in the translation section:
tmpIndex = 2;
if(this.translationCounter == 0)
{
// Start handling the translations by ignoring history:
if(key)
{
this.element.removeTranslation(key);
this.element.removeTransKey(this.propName);
}
key = [];
}
key[this.translationCounter] = this.element.insertTranslation(tType, tText);
if(key[this.translationCounter])
{// Now let the element know the property has a translation:
this.element.setTransKey( this.propName, key[this.translationCounter], true);
}
if(!key[this.translationCounter])throw new Error(-1, '#TEXT[XMSG_ERROR_TRANS]');
// The value of the text should be replaced by the returned translation key for persistency:
outStr = key[this.translationCounter];
}
else
{ // Presentation purposes (b4UI)
// Replace keys with values:
// Get the transalation: (here the tText is actually a translation key)
outStr = this.element.getTranslation(tText, true);
if(!ISARRAY(outStr))
throw new Error(-1, '#TEXT[XMSG_ERROR_TRANS]');
// Get the right type from the translation object itself:
tType = outStr[1];
outStr = outStr[0];
if(!key || this.translationCounter > 0)
// Now let the element know the property has a translation:
this.element.setTransKey( this.propName, tText, true)
}
// In case more than one translation in a DE:
this.translationCounter++;
// FORMAT anv TRANSLATION are built differently:
buf.push('"' + outStr + '"');
if(b4UI)
{
buf.push('"' + tType + '"');
}
}
return tmpIndex;
end
<@doc scope="public">
check if the translation type is correct
If not valid, an exception is thrown
translation type
override method checkTranslationType(ttype)
if(!this.Class.metadata.translationTypes[ttype])
//throw new Error(-1, '#TEXT[XMSG_ERROR_MISSING_TRANS_TYPE]'.replace('{0}','/'+this.TOKEN_FUNC_FORMAT));
return false;
return true;
end
///////////////////////////////////////////////////////////////////////////
// END OF CLASS