Name
sn_cd.cd_ContentParser
Description
API to parse rich text and perform variable substitutions.
Script
var cd_ContentParser = Class.create();
cd_ContentParser.prototype = {
initialize: function() {
/*
REGEX:
/ - begin regex
\${ - string starting with
([a-zA-Z0-9_.]+) - field name can contain 1 (+ at the end) or more
( | | |\s|\t)* - zero or more combination of non-breaking space, en space, em space, space, or tab
(\-|\+){1} - exactly one occurence of either (-) or (+)
( | | |\s|\t)* - zero or more combination of non-breaking space, en space, em space, space, or tab
(\d{1,4}) - 1 to 4 number long co-efficient for the offset
([wdm]{1}) - exactly one occurence of w(week), d(day) or m(month)
( | | |\s|\t)* - zero or more combination of non-breaking space, en space, em space, space, or tab
} - ending with
/ - end regex
*/
this.ALL_FIELD_REGEX = /\${([a-zA-Z0-9_.]+)( | | |\s|\t)*([\-|\+]{1})( | | |\s|\t)*(\d{1,4})([wdm]{1})( | | |\s|\t)*}/;
/*
REGEX:
/ - begin regex
\${ - string starting with
(Date) - current date 'Date' field
( | | |\s|\t)* - zero or more combination of non-breaking space, en space, em space, space, or tab
(\-|\+){1} - exactly one occurence of either (-) or (+)
( | | |\s|\t)* - zero or more combination of non-breaking space, en space, em space, space, or tab
(\d{1,4}) - 1 to 4 number long co-efficient for the offset
([wdm]{1}) - exactly one occurence of w(week), d(day) or m(month)
( | | |\s|\t)* - zero or more combination of non-breaking space, en space, em space, space, or tab
} - ending with
/ - end regex
*/
this.CURRENT_DATE_REGEX = /\${(Date)( | | |\s|\t)*([\-|\+]{1})( | | |\s|\t)*(\d{1,4})([wdm]{1})( | | |\s|\t)*}/;
// Date regex with global flag
this.ALL_CURRENT_DATE_REGEX = new RegExp(this.CURRENT_DATE_REGEX.source, "g");
this.unEvaluatedVariable = [];
this.inaccessibleVariable = [];
this.customVariables = '${Date}';
},
/*
Parser should support all below:
${Date}
${Date + 1d}
${Date + 1w}
${Date + 1m}
${Date - 1d}
${Date - 1w}
${Date - 1m}
${employment_start_date}
${employment_start_date - 52w}
${user.manager}
Requirements for Content Publishing:
Support above examples.
If field doesn't exist, no substitution should happen. For example, ${bogus_field} where bogus_field is a non-existent field name should return "${bogus_field}".
Additionally, UI should attempt to prevent. Current HR logic aborts update with message:
Following variables are not valid or defined: ${bogus_field}.
UX also highlights the bad field in red (is happening on save, not real time).
if no permissions, return blank
if there is no 'user' record to use:
For example, the user table is HR Profile, user field is 'user', but the logged in user has no HR Profile, then you'd expect to see like below:
Employment start date:
52 weeks prior to your start date is
User Manager:
*/
/* Validate rich text
* @param parsedBody String Text to validate
* @param tableName String Name of table to validate parsedBody with
* @return String Parsed body
*/
validateTemplate: function(parsedBody, tableName) {
if (tableName) {
parsedBody = this.resetErroredSpanInDocument(parsedBody);
parsedBody = this._convertEscapedPlusSymbol(parsedBody);
var grs = new GlideRecordSecure(tableName);
grs.initialize();
if (grs.isValid())
return this._doVariableSubstitutions(parsedBody, grs, true);
}
return parsedBody;
},
/* Parse a body of text by doing variable substitutions
* @param docBody String String text to parse
* @param grs GlideRecordSecure Record for a user or user reference table
* @return String Parsed body
*/
parseRichTextHtmlBody : function(docBody, grs) {
docBody = this._convertEscapedPlusSymbol(docBody);
docBody = this._doVariableSubstitutions(docBody, grs, false);
docBody = this._setDateValues(docBody);
return docBody;
},
/* Set date value substitutions in a string
* @param docBody String Text to parse
* return String Parsed body with date occurences replaced
*/
_setDateValues: function(docBody) {
// Get offsetObject for each date occurence and replace with offset value
var date = new GlideDateTime().getLocalDate();
var offsetCurrentDateOccurence = docBody.match(this.ALL_CURRENT_DATE_REGEX);
for (var index in offsetCurrentDateOccurence) {
var offsetObject = this._offsetValues(offsetCurrentDateOccurence[index]);
var dateWithOffset = this._applyOffset(date, offsetObject);
docBody = docBody.replace(offsetCurrentDateOccurence[index], dateWithOffset);
}
// Replace current date tag ${Date} with current date value
return docBody.replace(/\${Date\}/gi, date.getDisplayValue());
},
/* Runs through text body and checks every variable, replacing with the value they represent if initial check passes
* @param parsedBody String String text to parse
* @param gr GlideRecordSecure Record for a user or user reference table
* @param validate boolean Whether to just validate (true), or actually do substitutions (false)
* @return String Parsed body with variable occurences replaced where applicable
*/
_doVariableSubstitutions: function (parsedBody, gr, validate) {
var regex = /\${([^}]*)}/g;
var matched = parsedBody.match(regex);
for (var i in matched) {
if (this.unEvaluatedVariable.indexOf(matched[i]) > -1 || this.inaccessibleVariable.indexOf(matched[i]) > -1 || this.customVariables.indexOf(matched[i]) > -1 || this._isCurrentDateWithOffset(matched[i]))
continue;
//Date processing
var isOffsetDate = this._isOffsetDate(matched[i], gr);
var offsetData = this._offsetValues(matched[i]);
var str = isOffsetDate ? offsetData.reference.trim() : matched[i].match(/\${(.*)}/).pop().trim();
if (!str || !gr) {
parsedBody = parsedBody.replace(matched[i], '');
continue;
}
var ge = gr.getElement(str);
if (ge === null || ge.toString() === null) {
if (validate)
parsedBody = this._pushToInaccessible(parsedBody, matched[i], false);
else
parsedBody = parsedBody.replace(matched[i], '');
} else if (!ge.canRead()) {
if (validate)
parsedBody = this._pushToInaccessible(parsedBody, matched[i], true);
else
parsedBody = parsedBody.replace(matched[i], '');
} else {
if (validate)
continue;
else if (isOffsetDate)
parsedBody = parsedBody.replace(matched[i], this._applyOffset(ge.getDisplayValue(), offsetData));
else
parsedBody = parsedBody.replace(matched[i], GlideStringUtil.escapeHTML(ge.getDisplayValue()));
}
}
return parsedBody;
},
_isOffsetDate: function(field, glideRecSecure) {
var re = this.ALL_FIELD_REGEX;
var dateWithOffset = field.match(re);
var isDateType = false;
if (dateWithOffset != null && glideRecSecure)
isDateType = this._isDateType(dateWithOffset[1], glideRecSecure);
return (dateWithOffset != null && isDateType);
},
// Checks if an element is of known date types i.e. glide_date_time, date, glide_date
_isDateType: function (fieldName, glideRecSecure) {
if (glideRecSecure.isValidField(fieldName)) {
var dateTypes = ['glide_date_time', 'date', 'glide_date', 'due_date'];
var element = glideRecSecure.getElement(fieldName);
return ((element.toString() != null) && dateTypes.indexOf(element.getED().getInternalType()) > -1);
}
return false;
},
_isCurrentDateWithOffset: function(field) {
var re = this.CURRENT_DATE_REGEX;
return (field.match(re) != null);
},
_offsetValues: function(field) {
var re = this.ALL_FIELD_REGEX;
var dateWithOffset = field.match(re);
if (dateWithOffset != null) {
return {
"reference" : dateWithOffset[1], //field name
"sign" : dateWithOffset[3], // add (+) or subtract (-) date
"quantity" : dateWithOffset[5],
"type" : dateWithOffset[6]
};
}
return {
"reference" : "", //field name
"sign" : "", // add (+) or subtract (-) date
"quantity" : "",
"type" : ""
};
},
_applyOffset : function(elementValue, offsetValue) {
if (elementValue && !this._isOffsetObjectEmpty(offsetValue)) {
var date = new GlideDateTime();
date.setDisplayValue(elementValue);
var sign = parseInt(offsetValue.sign + '1');
var offset = sign * offsetValue.quantity;
switch (offsetValue.type) {
case "w" : date.addWeeksLocalTime(offset);
break;
case "m" : date.addMonthsLocalTime(offset);
break;
case "d" : date.addDaysLocalTime(offset);
break;
default : break;
}
return date.getLocalDate().getDisplayValue();
}
return "";
},
_isOffsetObjectEmpty: function(offsetObject) {
return offsetObject == null || offsetObject.reference == "" || offsetObject.sign == "" || offsetObject.quantity == "" || offsetObject.type == "";
},
resetErroredSpanInDocument : function(documentBody) {
//regular expression that matches all the span pairs in the documentBody with class as errored-field
var spanRegex = /<\s*span\s*class="errored-field".*?>/g;
var matchedSpanTags = documentBody.match(spanRegex);
for (var i in matchedSpanTags)
documentBody = documentBody.replace(matchedSpanTags[i],"<span>");
return documentBody;
},
// Convert an escaped plus symbol (+ -> +) to a plus symbol
_convertEscapedPlusSymbol: function(parsedBody) {
parsedBody = parsedBody.replace(new RegExp('+', 'g'), '+');
return parsedBody;
},
//Moves improper matched fields to inaccessible list and changes their text components to red
_pushToInaccessible: function(docBody, matched, evaluated) {
docBody = docBody.replace(matched, '<span class="errored-field" style="color:#ff0000;">' + matched + '</span>');
if (evaluated)
this.inaccessibleVariable.push(matched);
else
this.unEvaluatedVariable.push(matched);
return docBody;
},
getUnevaluatedVariables: function() {
return this.unEvaluatedVariable;
},
getInaccessibleVariables: function() {
return this.inaccessibleVariable;
},
type: 'cd_ContentParser'
};
Sys ID
1c791e0753430300d901a7e6a11c08f9