UNPKG

ractive

Version:

Next-generation DOM manipulation

173 lines (137 loc) 4.16 kB
import types from 'config/types'; import voidElementNames from 'config/voidElementNames'; import getMustache from 'parse/converters/mustache'; import getComment from 'parse/converters/comment'; import getText from 'parse/converters/text'; import getClosingTag from 'parse/converters/element/closingTag'; import getAttribute from 'parse/converters/element/attribute'; import processDirective from 'parse/converters/element/processDirective'; var tagNamePattern = /^[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/, validTagNameFollower = /^[\s\n\/>]/, onPattern = /^on/, proxyEventPattern = /^on-([a-zA-Z$_][a-zA-Z$_0-9\-]+)$/, reservedEventNames = /^(?:change|reset|teardown|update)$/, directives = { 'intro-outro': 't0', intro: 't1', outro: 't2', decorator: 'o' }, exclude = { exclude: true }, converters; // Different set of converters, because this time we're looking for closing tags converters = [ getMustache, getComment, getElement, getText, getClosingTag ]; export default getElement; function getElement ( parser ) { var start, startPos, element, lowerCaseName, directiveName, match, addProxyEvent, attribute, directive, selfClosing, children, child; start = parser.pos; startPos = parser.getLinePos(); if ( parser.inside ) { return null; } if ( !parser.matchString( '<' ) ) { return null; } // if this is a closing tag, abort straight away if ( parser.nextChar() === '/' ) { return null; } element = { t: types.ELEMENT }; if ( parser.includeLinePositions ) { element.p = startPos.toJSON(); } if ( parser.matchString( '!' ) ) { element.y = 1; } // element name element.e = parser.matchPattern( tagNamePattern ); if ( !element.e ) { return null; } // next character must be whitespace, closing solidus or '>' if ( !validTagNameFollower.test( parser.nextChar() ) ) { parser.error( 'Illegal tag name' ); } addProxyEvent = function ( name, directive ) { var directiveName = directive.n || directive; if ( reservedEventNames.test( directiveName ) ) { parser.pos -= directiveName.length; parser.error( 'Cannot use reserved event names (change, reset, teardown, update)' ); } element.v[ name ] = directive; }; // directives and attributes while ( attribute = getAttribute( parser ) ) { // intro, outro, decorator if ( directiveName = directives[ attribute.name ] ) { element[ directiveName ] = processDirective( attribute.value ); } // on-click etc else if ( match = proxyEventPattern.exec( attribute.name ) ) { if ( !element.v ) element.v = {}; directive = processDirective( attribute.value ); addProxyEvent( match[1], directive ); } else { if ( !parser.sanitizeEventAttributes || !onPattern.test( attribute.name ) ) { if ( !element.a ) element.a = {}; element.a[ attribute.name ] = attribute.value || 0; } } } // allow whitespace before closing solidus parser.allowWhitespace(); // self-closing solidus? if ( parser.matchString( '/' ) ) { selfClosing = true; } // closing angle bracket if ( !parser.matchString( '>' ) ) { return null; } lowerCaseName = element.e.toLowerCase(); if ( !selfClosing && !voidElementNames.test( element.e ) ) { // Special case - if we open a script element, further tags should // be ignored unless they're a closing script element if ( lowerCaseName === 'script' || lowerCaseName === 'style' ) { parser.inside = lowerCaseName; } children = []; while ( child = parser.read( converters ) ) { // Special case - closing section tag if ( child.t === types.CLOSING ) { break; } if ( child.t === types.CLOSING_TAG ) { break; // TODO verify that this tag can close this element (is either the same, or // a parent that can close child elements implicitly) //parser.error( 'Expected closing </' + element.e + '> tag' ); } children.push( child ); // TODO handle sibling elements that close blocks } if ( children.length ) { element.f = children; } } parser.inside = null; if ( parser.sanitizeElements && parser.sanitizeElements.indexOf( lowerCaseName ) !== -1 ) { return exclude; } return element; }