/********** XHTML LEXER/PARSER **********/
/*
* @name xml
* @description Use these methods to generate XML and XHTML compliant tags and
* escape tag attributes correctly
* @author Bermi Ferrer - http://bermi.org
* @author David Heinemeier Hansson http://loudthinking.com
*/
WYMeditor.XmlHelper = function()
{
this._entitiesDiv = document.createElement('div');
return this;
};
/*
* @name tag
* @description
* Returns an empty HTML tag of type *name* which by default is XHTML
* compliant. Setting *open* to true will create an open tag compatible
* with HTML 4.0 and below. Add HTML attributes by passing an attributes
* array to *options*. For attributes with no value like (disabled and
* readonly), give it a value of true in the *options* array.
*
* Examples:
*
* this.tag('br')
* # =>
* this.tag ('br', false, true)
* # =>
* this.tag ('input', $({type:'text',disabled:true }) )
* # =>
*/
WYMeditor.XmlHelper.prototype.tag = function(name, options, open)
{
options = options || false;
open = open || false;
return '<'+name+(options ? this.tagOptions(options) : '')+(open ? '>' : ' />');
};
/*
* @name contentTag
* @description
* Returns a XML block tag of type *name* surrounding the *content*. Add
* XML attributes by passing an attributes array to *options*. For attributes
* with no value like (disabled and readonly), give it a value of true in
* the *options* array. You can use symbols or strings for the attribute names.
*
* this.contentTag ('p', 'Hello world!' )
* # =>
Hello world!
* this.contentTag('div', this.contentTag('p', "Hello world!"), $({class : "strong"}))
* # =>
* this.contentTag("select", options, $({multiple : true}))
* # => ...options...
*/
WYMeditor.XmlHelper.prototype.contentTag = function(name, content, options)
{
options = options || false;
return '<'+name+(options ? this.tagOptions(options) : '')+'>'+content+''+name+'>';
};
/*
* @name cdataSection
* @description
* Returns a CDATA section for the given +content+. CDATA sections
* are used to escape blocks of text containing characters which would
* otherwise be recognized as markup. CDATA sections begin with the string
* <![CDATA[ and } with (and may not contain) the string
* ]]> .
*/
WYMeditor.XmlHelper.prototype.cdataSection = function(content)
{
return '';
};
/*
* @name escapeOnce
* @description
* Returns the escaped +xml+ without affecting existing escaped entities.
*
* this.escapeOnce( "1 > 2 & 3")
* # => "1 > 2 & 3"
*/
WYMeditor.XmlHelper.prototype.escapeOnce = function(xml)
{
return this._fixDoubleEscape(this.escapeEntities(xml));
};
/*
* @name _fixDoubleEscape
* @description
* Fix double-escaped entities, such as &, {, etc.
*/
WYMeditor.XmlHelper.prototype._fixDoubleEscape = function(escaped)
{
return escaped.replace(/&([a-z]+|(#\d+));/ig, "&$1;");
};
/*
* @name tagOptions
* @description
* Takes an array like the one generated by Tag.parseAttributes
* [["src", "http://www.editam.com/?a=b&c=d&f=g"], ["title", "Editam, CMS"]]
* or an object like {src:"http://www.editam.com/?a=b&c=d&f=g", title:"Editam, CMS"}
* and returns a string properly escaped like
* ' src = "http://www.editam.com/?a=b&c=d&f=g" title = "Editam, <Simplified> CMS"'
* which is valid for strict XHTML
*/
WYMeditor.XmlHelper.prototype.tagOptions = function(options)
{
var xml = this;
xml._formated_options = '';
for (var key in options) {
var formated_options = '';
var value = options[key];
if(typeof value != 'function' && value.length > 0) {
if(parseInt(key) == key && typeof value == 'object'){
key = value.shift();
value = value.pop();
}
if(key != '' && value != ''){
xml._formated_options += ' '+key+'="'+xml.escapeOnce(value)+'"';
}
}
}
return xml._formated_options;
};
/*
* @name escapeEntities
* @description
* Escapes XML/HTML entities <, >, & and ". If seccond parameter is set to false it
* will not escape ". If set to true it will also escape '
*/
WYMeditor.XmlHelper.prototype.escapeEntities = function(string, escape_quotes)
{
this._entitiesDiv.innerHTML = string;
this._entitiesDiv.textContent = string;
var result = this._entitiesDiv.innerHTML;
if(typeof escape_quotes == 'undefined'){
if(escape_quotes != false) result = result.replace('"', '"');
if(escape_quotes == true) result = result.replace('"', ''');
}
return result;
};
/*
* Parses a string conatining tag attributes and values an returns an array formated like
* [["src", "http://www.editam.com"], ["title", "Editam, Simplified CMS"]]
*/
WYMeditor.XmlHelper.prototype.parseAttributes = function(tag_attributes)
{
// Use a compounded regex to match single quoted, double quoted and unquoted attribute pairs
var result = [];
var matches = tag_attributes.split(/((=\s*")(")("))|((=\s*\')(\')(\'))|((=\s*[^>\s]*))/g);
if(matches.toString() != tag_attributes){
for (var k in matches) {
var v = matches[k];
if(typeof v != 'function' && v.length != 0){
var re = new RegExp('(\\w+)\\s*'+v);
if(match = tag_attributes.match(re) ){
var value = v.replace(/^[\s=]+/, "");
var delimiter = value.charAt(0);
delimiter = delimiter == '"' ? '"' : (delimiter=="'"?"'":'');
if(delimiter != ''){
value = delimiter == '"' ? value.replace(/^"|"+$/g, '') : value.replace(/^'|'+$/g, '');
}
tag_attributes = tag_attributes.replace(match[0],'');
result.push([match[1] , value]);
}
}
}
}
return result;
};
/**
* Compounded regular expression. Any of
* the contained patterns could match and
* when one does, it's label is returned.
*
* Constructor. Starts with no patterns.
* @param boolean case True for case sensitive, false
* for insensitive.
* @access public
* @author Marcus Baker (http://lastcraft.com)
* @author Bermi Ferrer (http://bermi.org)
*/
WYMeditor.ParallelRegex = function(case_sensitive)
{
this._case = case_sensitive;
this._patterns = [];
this._labels = [];
this._regex = null;
return this;
};
/**
* Adds a pattern with an optional label.
* @param string pattern Perl style regex, but ( and )
* lose the usual meaning.
* @param string label Label of regex to be returned
* on a match.
* @access public
*/
WYMeditor.ParallelRegex.prototype.addPattern = function(pattern, label)
{
label = label || true;
var count = this._patterns.length;
this._patterns[count] = pattern;
this._labels[count] = label;
this._regex = null;
};
/**
* Attempts to match all patterns at once against
* a string.
* @param string subject String to match against.
*
* @return boolean True on success.
* @return string match First matched portion of
* subject.
* @access public
*/
WYMeditor.ParallelRegex.prototype.match = function(subject)
{
if (this._patterns.length == 0) {
return [false, ''];
}
var matches = subject.match(this._getCompoundedRegex());
if(!matches){
return [false, ''];
}
var match = matches[0];
for (var i = 1; i < matches.length; i++) {
if (matches[i]) {
return [this._labels[i-1], match];
}
}
return [true, matches[0]];
};
/**
* Compounds the patterns into a single
* regular expression separated with the
* "or" operator. Caches the regex.
* Will automatically escape (, ) and / tokens.
* @param array patterns List of patterns in order.
* @access private
*/
WYMeditor.ParallelRegex.prototype._getCompoundedRegex = function()
{
if (this._regex == null) {
for (var i = 0, count = this._patterns.length; i < count; i++) {
this._patterns[i] = '(' + this._untokenizeRegex(this._tokenizeRegex(this._patterns[i]).replace(/([\/\(\)])/g,'\\$1')) + ')';
}
this._regex = new RegExp(this._patterns.join("|") ,this._getPerlMatchingFlags());
}
return this._regex;
};
/**
* Escape lookahead/lookbehind blocks
*/
WYMeditor.ParallelRegex.prototype._tokenizeRegex = function(regex)
{
return regex.
replace(/\(\?(i|m|s|x|U)\)/, '~~~~~~Tk1\$1~~~~~~').
replace(/\(\?(\-[i|m|s|x|U])\)/, '~~~~~~Tk2\$1~~~~~~').
replace(/\(\?\=(.*)\)/, '~~~~~~Tk3\$1~~~~~~').
replace(/\(\?\!(.*)\)/, '~~~~~~Tk4\$1~~~~~~').
replace(/\(\?\<\=(.*)\)/, '~~~~~~Tk5\$1~~~~~~').
replace(/\(\?\<\!(.*)\)/, '~~~~~~Tk6\$1~~~~~~').
replace(/\(\?\:(.*)\)/, '~~~~~~Tk7\$1~~~~~~');
};
/**
* Unscape lookahead/lookbehind blocks
*/
WYMeditor.ParallelRegex.prototype._untokenizeRegex = function(regex)
{
return regex.
replace(/~~~~~~Tk1(.{1})~~~~~~/, "(?\$1)").
replace(/~~~~~~Tk2(.{2})~~~~~~/, "(?\$1)").
replace(/~~~~~~Tk3(.*)~~~~~~/, "(?=\$1)").
replace(/~~~~~~Tk4(.*)~~~~~~/, "(?!\$1)").
replace(/~~~~~~Tk5(.*)~~~~~~/, "(?<=\$1)").
replace(/~~~~~~Tk6(.*)~~~~~~/, "(?", 'Comment');
};
WYMeditor.XhtmlLexer.prototype.addScriptTokens = function(scope)
{
this.addEntryPattern("