UNPKG

ractive

Version:

Next-generation DOM manipulation

236 lines (194 loc) 7.62 kB
import types from 'config/types'; import Parser from 'parse/Parser/_Parser'; import mustache from 'parse/converters/mustache'; import comment from 'parse/converters/comment'; import element from 'parse/converters/element'; import text from 'parse/converters/text'; import trimWhitespace from 'parse/utils/trimWhitespace'; import stripStandalones from 'parse/utils/stripStandalones'; // Ractive.parse // =============== // // Takes in a string, and returns an object representing the parsed template. // A parsed template is an array of 1 or more 'templates', which in some // cases have children. // // The format is optimised for size, not readability, however for reference the // keys for each template are as follows: // // * r - Reference, e.g. 'mustache' in {{mustache}} // * t - Type code (e.g. 1 is text, 2 is interpolator...) // * f - Fragment. Contains a template's children // * l - eLse fragment. Contains a template's children in the else case // * e - Element name // * a - map of element Attributes, or proxy event/transition Arguments // * d - Dynamic proxy event/transition arguments // * n - indicates an iNverted section // * i - Index reference, e.g. 'num' in {{#section:num}}content{{/section}} // * v - eVent proxies (i.e. when user e.g. clicks on a node, fire proxy event) // * x - eXpressions // * s - String representation of an expression function // * t0 - intro/outro Transition // * t1 - intro Transition // * t2 - outro Transition // * o - decOrator // * y - is doctYpe // * c - is Content (e.g. of a comment node) // * p - line Position information - array with line number and character position of each node var StandardParser, parse, contiguousWhitespace = /[ \t\f\r\n]+/g, inlinePartialStart = /<!--\s*\{\{\s*>\s*([a-zA-Z_$][a-zA-Z_$0-9]*)\s*}\}\s*-->/, inlinePartialEnd = /<!--\s*\{\{\s*\/\s*([a-zA-Z_$][a-zA-Z_$0-9]*)\s*}\}\s*-->/, preserveWhitespaceElements = /^(?:pre|script|style|textarea)$/i, leadingWhitespace = /^\s+/, trailingWhitespace = /\s+$/; StandardParser = Parser.extend({ init: function ( str, options ) { // config this.delimiters = options.delimiters || [ '{{', '}}' ]; this.tripleDelimiters = options.tripleDelimiters || [ '{{{', '}}}' ]; this.staticDelimiters = options.staticDelimiters || [ '[[', ']]' ]; this.staticTripleDelimiters = options.staticTripleDelimiters || [ '[[[', ']]]' ]; this.sectionDepth = 0; this.interpolate = { script: !options.interpolate || options.interpolate.script !== false, style: !options.interpolate || options.interpolate.style !== false }; if ( options.sanitize === true ) { options.sanitize = { // blacklist from https://code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/lang/html/html4-elements-whitelist.json elements: 'applet base basefont body frame frameset head html isindex link meta noframes noscript object param script style title'.split( ' ' ), eventAttributes: true }; } this.sanitizeElements = options.sanitize && options.sanitize.elements; this.sanitizeEventAttributes = options.sanitize && options.sanitize.eventAttributes; this.includeLinePositions = options.includeLinePositions; }, postProcess: function ( items, options ) { cleanup( items, options.stripComments !== false, options.preserveWhitespace, !options.preserveWhitespace, !options.preserveWhitespace, options.rewriteElse !== false ); return items; }, converters: [ mustache, comment, element, text ] }); parse = function ( template, options = {} ) { var result, remaining, partials, name, startMatch, endMatch; result = { v: 1 // template spec version, defined in https://github.com/ractivejs/template-spec }; if ( inlinePartialStart.test( template ) ) { remaining = template; template = ''; while ( startMatch = inlinePartialStart.exec( remaining ) ) { name = startMatch[1]; template += remaining.substr( 0, startMatch.index ); remaining = remaining.substring( startMatch.index + startMatch[0].length ); endMatch = inlinePartialEnd.exec( remaining ); if ( !endMatch || endMatch[1] !== name ) { throw new Error( 'Inline partials must have a closing delimiter, and cannot be nested' ); } ( partials || ( partials = {} ) )[ name ] = new StandardParser( remaining.substr( 0, endMatch.index ), options ).result; remaining = remaining.substring( endMatch.index + endMatch[0].length ); } result.p = partials; } result.t = new StandardParser( template, options ).result; return result; }; export default parse; function cleanup ( items, stripComments, preserveWhitespace, removeLeadingWhitespace, removeTrailingWhitespace, rewriteElse ) { var i, item, previousItem, nextItem, preserveWhitespaceInsideFragment, removeLeadingWhitespaceInsideFragment, removeTrailingWhitespaceInsideFragment, unlessBlock, key; // First pass - remove standalones and comments etc stripStandalones( items ); i = items.length; while ( i-- ) { item = items[i]; // Remove delimiter changes, unsafe elements etc if ( item.exclude ) { items.splice( i, 1 ); } // Remove comments, unless we want to keep them else if ( stripComments && item.t === types.COMMENT ) { items.splice( i, 1 ); } } // If necessary, remove leading and trailing whitespace trimWhitespace( items, removeLeadingWhitespace, removeTrailingWhitespace ); i = items.length; while ( i-- ) { item = items[i]; // Recurse if ( item.f ) { preserveWhitespaceInsideFragment = preserveWhitespace || ( item.t === types.ELEMENT && preserveWhitespaceElements.test( item.e ) ); if ( !preserveWhitespaceInsideFragment ) { previousItem = items[ i - 1 ]; nextItem = items[ i + 1 ]; // if the previous item was a text item with trailing whitespace, // remove leading whitespace inside the fragment if ( !previousItem || ( typeof previousItem === 'string' && trailingWhitespace.test( previousItem ) ) ) { removeLeadingWhitespaceInsideFragment = true; } // and vice versa if ( !nextItem || ( typeof nextItem === 'string' && leadingWhitespace.test( nextItem ) ) ) { removeTrailingWhitespaceInsideFragment = true; } } cleanup( item.f, stripComments, preserveWhitespaceInsideFragment, removeLeadingWhitespaceInsideFragment, removeTrailingWhitespaceInsideFragment, rewriteElse ); // Split if-else blocks into two (an if, and an unless) if ( item.l ) { cleanup( item.l, stripComments, preserveWhitespace, removeLeadingWhitespaceInsideFragment, removeTrailingWhitespaceInsideFragment, rewriteElse ); if ( rewriteElse ) { unlessBlock = { t: 4, n: types.SECTION_UNLESS, f: item.l }; // copy the conditional based on its type if( item.r ) { unlessBlock.r = item.r; } if( item.x ) { unlessBlock.x = item.x; } if( item.rx ) { unlessBlock.rx = item.rx; } items.splice( i + 1, 0, unlessBlock ); delete item.l; } } } // Clean up element attributes if ( item.a ) { for ( key in item.a ) { if ( item.a.hasOwnProperty( key ) && typeof item.a[ key ] !== 'string' ) { cleanup( item.a[ key ], stripComments, preserveWhitespace, rewriteElse ); } } } } // final pass - fuse text nodes together i = items.length; while ( i-- ) { if ( typeof items[i] === 'string' ) { if ( typeof items[i+1] === 'string' ) { items[i] = items[i] + items[i+1]; items.splice( i + 1, 1 ); } if ( !preserveWhitespace ) { items[i] = items[i].replace( contiguousWhitespace, ' ' ); } if ( items[i] === '' ) { items.splice( i, 1 ); } } } }