mediawiki-extensions-Visual.../modules/syntaxhighlight/helpers/ve.ce.MWSyntaxHighlightValidator.js

308 lines
7.8 KiB
JavaScript
Raw Normal View History

/*!
* VisualEditor MediaWiki UserInterface raster icon styles.
*
* @copyright 2011-2014 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* MWSyntaxHighlight validator
*
* @constructor
*/
ve.ce.MWSyntaxHighlightValidator = function VeCeMWSyntaxHighlightValidator() {
// Properties
// 'methodname' : method reference
this.roster = {
'error' : this.err,
'balanceAA' : this.balanceAA,
'balanceAB' : this.balanceAB,
'matchAB' : this.matchAB
};
// Data to perform validation on
this.data = '';
// Indices of included areas
this.matchableArea = [];
};
/* Methods */
/**
* Get the roster of helper methods
*
* @method
* @returns {Object} Method name, method reference
*/
ve.ce.MWSyntaxHighlightValidator.prototype.getRoster = function(){
return this.roster;
};
/**
* Set the data for validation
*
* @method
* @param {string} dataString
*/
ve.ce.MWSyntaxHighlightValidator.prototype.setData = function( dataString ){
this.data = dataString;
};
/**
* Get the overlap of two ranges
*
* @method
* @param {Object} x Model index range
* @param {Object} y Model index range
* @returns {Object} Model index range
*/
ve.ce.MWSyntaxHighlightValidator.prototype.getOverlap = function( x, y ){
if ( x.b <= y.a || y.b <= x.a ){
return null;
} else {
var sorted = [x.a,x.b,y.a,y.b].sort(function(a,b){return a-b;});
return {'a':sorted[1], 'b':sorted[2]};
}
};
/**
* Get the subtraction of two overlapped ranges
*
* @method
* @param {Object} x Model index range
* @param {Object} y Model index range
* @returns {Array} Model index ranges
*/
ve.ce.MWSyntaxHighlightValidator.prototype.subtractRange = function( x, y ){
var range1 = {'a':x.a, 'b':y.a},
range2 = {'a':y.b, 'b':x.b},
r = [];
if (range1.a !== range1.b){ r.push(range1); }
if (range2.a !== range2.b){ r.push(range2); }
return r;
};
/**
* Check whether a model index falls within matchable area(s)
*
* @method
* @param {int} index Model index
* @returns {boolean}
*/
ve.ce.MWSyntaxHighlightValidator.prototype.canMatch = function( index ){
for ( var i = 0; i < this.matchableArea.length; i++ ){
if (this.matchableArea[i].a <= index && this.matchableArea[i].b >= index){
return true;
}
}
return false;
};
/**
* Initialize matchable areas
*
* @method
* @param {Array} parsedExcept Regex to determine exceptions
* @param {Array} parsedWithin Regex to determine included areas
* @param {Array} parsedOutside Regex to determine excluded areas
*/
ve.ce.MWSyntaxHighlightValidator.prototype.initRange = function( parsedExcept, parsedWithin, parsedOutside ){
var exceptions = [],
inclusions = [],
exclusions = [],
match,
i, j, o, n;
for ( i = 0; i < parsedExcept.length; i++ ){
while ( (match = parsedExcept[i].exec( this.data ) ) !== null ){
match.index += match[0].indexOf(match[1]);
exceptions.push({'a': match.index, 'b': match.index + match[1].length});
}
}
for ( i = 0; i < parsedOutside.length; i++ ){
while ( (match = parsedOutside[i].exec( this.data ) ) !== null ){
match.index += match[0].indexOf(match[1]);
exclusions.push({'a': match.index, 'b': match.index + match[1].length});
}
}
if (parsedWithin.length === 0){
inclusions.push({'a': 0, 'b': this.data.length});
} else {
for ( i = 0; i < parsedWithin.length; i++ ){
while ( (match = parsedWithin[i].exec( this.data ) ) !== null ){
match.index += match[0].indexOf(match[1]);
inclusions.push({'a': match.index, 'b': match.index + match[1].length});
}
}
}
exceptions.sort(function(a, b){ return a.a - b.a; });
inclusions.sort(function(a, b){ return a.a - b.a; });
exclusions.sort(function(a, b){ return a.a - b.a; });
for ( i = 0; i < inclusions.length; i++ ){
for ( j = 0; j < exclusions.length; j++ ){
o = this.getOverlap( inclusions[i] , exclusions[j] );
if ( o !== null ){
n = this.subtractRange( inclusions[i], o );
if (n.length === 1){ inclusions.splice(i, 1, n[0]); }
else if (n.length === 2){ inclusions.splice(i, 1, n[0], n[1]); }
else if (n.length === 0){ inclusions.splice(i, 1); }
}
}
for ( j = 0; j < exceptions.length; j++ ){
o = this.getOverlap( inclusions[i] , exceptions[j] );
if ( o !== null ){
n = this.subtractRange( inclusions[i], o );
if (n.length === 1){ inclusions.splice(i, 1, n[0]); }
else if (n.length === 2){ inclusions.splice(i, 1, n[0], n[1]); }
else if (n.length === 0){ inclusions.splice(i, 1); }
}
}
}
this.matchableArea = inclusions;
};
/* Helper methods on roster */
/**
* Return error for matches
*
* @method
* @param {Array} parsedRegexGroup RegExp objects
* @param {Object} context Context
* @returns {Object} If need error highlights; regex matches to be highlighted
*/
ve.ce.MWSyntaxHighlightValidator.prototype.err = function( parsedRegexGroup, context ){
var matching = [],
match,
i;
for ( i = 0; i < parsedRegexGroup.length; i++ ){
while ( (match = parsedRegexGroup[i].exec( context.data ) ) !== null ){
match.index = match.index + match[0].indexOf(match[1]);
if (context.canMatch(match.index)){
matching.push(match);
}
}
}
return {
'needMarks' : true,
'matches' : matching
};
};
/**
* Balance the existence of the same token; 1-1 relationship
*
* @method
* @param {Array} parsedRegexGroup RegExp objects
* @param {Object} context Context
* @returns {Object} If need error highlights; regex matches to be highlighted
*/
ve.ce.MWSyntaxHighlightValidator.prototype.balanceAA = function( parsedRegexGroup, context ){
var a = [],
match,
pushing = true,
regexA = parsedRegexGroup[0];
while ( (match = regexA.exec( context.data ) ) !== null ){
match.index = match.index + match[0].indexOf(match[1]);
if (context.canMatch(match.index)){
if (pushing){
a.push(match);
pushing = false;
} else {
a.pop();
pushing = true;
}
}
}
if (a.length === 0){
return {
'needMarks' : false,
'matches' : []
};
} else {
return {
'needMarks' : true,
'matches' : a
};
}
};
/**
* Balance the existence of two different tokens; 1-1 relationship
*
* @method
* @param {Array} parsedRegexGroup RegExp objects
* @param {Object} context Context
* @returns {Object} If need error highlights; regex matches to be highlighted
*/
ve.ce.MWSyntaxHighlightValidator.prototype.balanceAB = function( parsedRegexGroup, context ){
var a = [],
b = [],
match,
regexA = parsedRegexGroup[0],
regexB = parsedRegexGroup[1],
regex = new RegExp('('+regexA.source + '|' + regexB.source+')', 'g');
while ( (match = regex.exec( context.data ) ) !== null ){
match.index = match.index + match[0].indexOf(match[1]);
if (context.canMatch(match.index)){
if (regexA.test(match[1])){
a.push(match);
} else {
if (a.length !== 0){
a.pop();
} else {
b.push(match);
}
}
}
}
if (a.length === 0 && b.length === 0){
return {
'needMarks' : false,
'matches' : []
};
} else {
return {
'needMarks' : true,
'matches' : a.concat(b)
};
}
};
/**
* Check co-existence of two different tokens; n-1 relationship
*
* @method
* @param {Array} parsedRegexGroup RegExp objects
* @param {Object} context Context
* @returns {Object} If need error highlights; regex matches to be highlighted
*/
ve.ce.MWSyntaxHighlightValidator.prototype.matchAB = function( parsedRegexGroup, context ){
var match,
regexA = parsedRegexGroup[0],
regexB = parsedRegexGroup[1],
a = [],
matchOpen = true,
regex = new RegExp('('+regexA.source + '|' + regexB.source+')', 'g');
while ( (match = regex.exec( context.data ) ) !== null ){
match.index = match.index + match[0].indexOf(match[1]);
if (context.canMatch(match.index)){
if (regexA.test(match[1])){
matchOpen = true;
a.push(match);
} else {
matchOpen = false;
a = [];
}
}
}
if (!matchOpen){
return {
'needMarks' : false,
'matches' : []
};
} else {
return {
'needMarks' : true,
'matches' : a
};
}
};