ractive
Version:
Next-generation DOM manipulation
212 lines (166 loc) • 5.35 kB
JavaScript
import types from 'config/types';
import delimiterChange from 'parse/converters/mustache/delimiterChange';
import delimiterTypes from 'parse/converters/mustache/delimiterTypes';
import mustacheContent from 'parse/converters/mustache/content';
import handlebarsBlockCodes from 'parse/converters/mustache/handlebarsBlockCodes';
var delimiterChangeToken = { t: types.DELIMCHANGE, exclude: true },
handlebarsIndexRefPattern = /^@(?:index|key)$/;
export default getMustache;
function getMustache ( parser ) {
var types;
types = delimiterTypes.slice().sort( function compare (a, b) {
// Sort in order of descending opening delimiter length (longer first),
// to protect against opening delimiters being substrings of each other
return parser[ b.delimiters ][ 0 ].length - parser[ a.delimiters ][ 0 ].length;
} );
return ( function r ( type ) {
if ( !type ) {
return null;
} else {
return getMustacheOfType( parser, type ) || r( types.shift() );
}
} ( types.shift() ) );
}
function getMustacheOfType ( parser, delimiterType ) {
var start, startPos, mustache, delimiters, children, expectedClose, elseChildren, currentChildren, child, indexRef;
start = parser.pos;
startPos = parser.getLinePos();
delimiters = parser[ delimiterType.delimiters ];
if ( !parser.matchString( delimiters[0] ) ) {
return null;
}
// delimiter change?
if ( mustache = delimiterChange( parser ) ) {
// find closing delimiter or abort...
if ( !parser.matchString( delimiters[1] ) ) {
return null;
}
// ...then make the switch
parser[ delimiterType.delimiters ] = mustache;
return delimiterChangeToken;
}
parser.allowWhitespace();
mustache = mustacheContent( parser, delimiterType );
if ( mustache === null ) {
parser.pos = start;
return null;
}
// allow whitespace before closing delimiter
parser.allowWhitespace();
if ( !parser.matchString( delimiters[1] ) ) {
parser.error( 'Expected closing delimiter \'' + delimiters[1] + '\' after reference' );
}
if ( mustache.t === types.COMMENT ) {
mustache.exclude = true;
}
if ( mustache.t === types.CLOSING ) {
parser.sectionDepth -= 1;
if ( parser.sectionDepth < 0 ) {
parser.pos = start;
parser.error( 'Attempted to close a section that wasn\'t open' );
}
}
// section children
if ( isSection( mustache ) ) {
parser.sectionDepth += 1;
children = [];
currentChildren = children;
expectedClose = mustache.n;
while ( child = parser.read() ) {
if ( child.t === types.CLOSING ) {
if ( expectedClose && child.r !== expectedClose ) {
parser.error( 'Expected {{/' + expectedClose + '}}' );
}
break;
}
// {{else}} tags require special treatment
if ( child.t === types.INTERPOLATOR && child.r === 'else' ) {
switch ( mustache.n ) {
case 'unless':
parser.error( '{{else}} not allowed in {{#unless}}' );
break;
case 'with':
parser.error( '{{else}} not allowed in {{#with}}' );
break;
default:
currentChildren = elseChildren = [];
continue;
}
}
currentChildren.push( child );
}
if ( children.length ) {
mustache.f = children;
// If this is an 'each' section, and it contains an {{@index}} or {{@key}},
// we need to set the index reference accordingly
if ( !mustache.i && mustache.n === 'each' && ( indexRef = handlebarsIndexRef( mustache.f ) ) ) {
mustache.i = indexRef;
}
}
if ( elseChildren && elseChildren.length ) {
mustache.l = elseChildren;
}
}
if ( parser.includeLinePositions ) {
mustache.p = startPos.toJSON();
}
// Replace block name with code
if ( mustache.n ) {
mustache.n = handlebarsBlockCodes[ mustache.n ];
} else if ( mustache.t === types.INVERTED ) {
mustache.t = types.SECTION;
mustache.n = types.SECTION_UNLESS;
}
return mustache;
}
function handlebarsIndexRef ( fragment ) {
var i, child, indexRef;
i = fragment.length;
while ( i-- ) {
child = fragment[i];
// Recurse into elements (but not sections)
if ( child.t === types.ELEMENT && child.f && ( indexRef = handlebarsIndexRef( child.f ) ) ) {
return indexRef;
}
// Mustache?
if ( child.t === types.INTERPOLATOR || child.t === types.TRIPLE || child.t === types.SECTION ) {
// Normal reference?
if ( child.r && handlebarsIndexRefPattern.test( child.r ) ) {
return child.r;
}
// Expression?
if ( child.x && ( indexRef = indexRefContainedInExpression( child.x ) ) ) {
return indexRef;
}
// Reference expression?
if ( child.rx && ( indexRef = indexRefContainedInReferenceExpression( child.rx ) ) ) {
return indexRef;
}
}
}
}
function indexRefContainedInExpression ( expression ) {
var i;
i = expression.r.length;
while ( i-- ) {
if ( handlebarsIndexRefPattern.test( expression.r[i] ) ) {
return expression.r[i];
}
}
}
function indexRefContainedInReferenceExpression ( referenceExpression ) {
var i, indexRef, member;
i = referenceExpression.m.length;
while ( i-- ) {
member = referenceExpression.m[i];
if ( member.r && ( indexRef = indexRefContainedInExpression( member ) ) ) {
return indexRef;
}
if ( member.t === types.REFERENCE && handlebarsIndexRefPattern.test( member.n ) ) {
return member.n;
}
}
}
function isSection ( mustache ) {
return mustache.t === types.SECTION || mustache.t === types.INVERTED;
}