From 3c3461b75a00c3758c295beefa9b35bf839847bf Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Tue, 16 Apr 2013 14:13:12 -0700 Subject: [PATCH 001/938] tokenizer pulled out of npm html5, initial commit --- packages/html5-tokenizer/README.md | 3 + packages/html5-tokenizer/constants.js | 762 +++++++ packages/html5-tokenizer/entities.js | 2233 +++++++++++++++++++ packages/html5-tokenizer/package.js | 18 + packages/html5-tokenizer/tokenizer.js | 959 ++++++++ packages/html5-tokenizer/tokenizer_tests.js | 5 + 6 files changed, 3980 insertions(+) create mode 100644 packages/html5-tokenizer/README.md create mode 100644 packages/html5-tokenizer/constants.js create mode 100644 packages/html5-tokenizer/entities.js create mode 100644 packages/html5-tokenizer/package.js create mode 100644 packages/html5-tokenizer/tokenizer.js create mode 100644 packages/html5-tokenizer/tokenizer_tests.js diff --git a/packages/html5-tokenizer/README.md b/packages/html5-tokenizer/README.md new file mode 100644 index 0000000000..1cba5fcfec --- /dev/null +++ b/packages/html5-tokenizer/README.md @@ -0,0 +1,3 @@ + +Code comes from https://github.com/aredridel/html5 / `npm html5`, but +just the tokenizer, no parsing and no `jsdom` dependency. \ No newline at end of file diff --git a/packages/html5-tokenizer/constants.js b/packages/html5-tokenizer/constants.js new file mode 100644 index 0000000000..0165e05804 --- /dev/null +++ b/packages/html5-tokenizer/constants.js @@ -0,0 +1,762 @@ +var HTML5 = require('../html5'); + +HTML5.CONTENT_MODEL_FLAGS = [ + 'PCDATA', + 'RCDATA', + 'CDATA', + 'SCRIPT_CDATA', + 'PLAINTEXT' +]; + +HTML5.Marker = {type: 'Marker', data: 'this is a marker token'}; + + +(function() { + function EOF() { + } + + EOF.prototype = { + toString: function() { throw new Error("EOF added as string"); } + }; + HTML5.EOF = new EOF(); +})(); + + +HTML5.EOF_TOK = {type: 'EOF', data: 'End of File' }; +HTML5.DRAIN = -2; + +HTML5.SCOPING_ELEMENTS = [ + 'applet', 'caption', 'html', 'table', 'td', 'th', + 'marquee', 'object', 'math:mi', 'math:mo', 'math:mn', 'math:ms', 'math:mtext', + 'math:annotation-xml', 'svg:foreignObject', 'svg:desc', 'svg:title' +]; + +HTML5.LIST_SCOPING_ELEMENTS = [ + 'ol', 'ul', + 'applet', 'caption', 'html', 'table', 'td', 'th', + 'marquee', 'object', 'math:mi', 'math:mo', 'math:mn', 'math:ms', 'math:mtext', + 'math:annotation-xml', 'svg:foreignObject', 'svg:desc', 'svg:title' +]; +HTML5.BUTTON_SCOPING_ELEMENTS = [ + 'button', + 'applet', 'caption', 'html', 'table', 'td', 'th', + 'marquee', 'object', 'math:mi', 'math:mo', 'math:mn', 'math:ms', 'math:mtext', + 'math:annotation-xml', 'svg:foreignObject', 'svg:desc', 'svg:title' +]; +HTML5.TABLE_SCOPING_ELEMENTS = [ + 'table', 'html' +]; +HTML5.SELECT_SCOPING_ELEMENTS = [ + 'option', 'optgroup' +]; +HTML5.FORMATTING_ELEMENTS = [ + 'a', + 'b', + 'big', + 'code', + 'em', + 'font', + 'i', + 'nobr', + 's', + 'small', + 'strike', + 'strong', + 'tt', + 'u' +]; +HTML5.SPECIAL_ELEMENTS = [ + 'address', + 'area', + 'base', + 'basefont', + 'bgsound', + 'blockquote', + 'body', + 'br', + 'center', + 'col', + 'colgroup', + 'dd', + 'dir', + 'div', + 'dl', + 'dt', + 'embed', + 'fieldset', + 'form', + 'frame', + 'frameset', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'head', + 'hr', + 'iframe', + 'image', + 'img', + 'input', + 'isindex', + 'li', + 'link', + 'listing', + 'menu', + 'meta', + 'noembed', + 'noframes', + 'noscript', + 'ol', + 'optgroup', + 'option', + 'p', + 'param', + 'plaintext', + 'pre', + 'script', + 'select', + 'spacer', + 'style', + 'tbody', + 'textarea', + 'tfoot', + 'thead', + 'title', + 'tr', + 'ul', + 'wbr' +]; +HTML5.SPACE_CHARACTERS_IN = "\t\n\x0B\x0C\x20\u0012\r"; +HTML5.SPACE_CHARACTERS = "[\t\n\x0B\x0C\x20\r]"; +HTML5.SPACE_CHARACTERS_R = /^[\t\n\x0B\x0C \r]/; + +HTML5.TABLE_INSERT_MODE_ELEMENTS = [ + 'table', + 'tbody', + 'tfoot', + 'thead', + 'tr' +]; + +HTML5.ASCII_LOWERCASE = 'abcdefghijklmnopqrstuvwxyz'; +HTML5.ASCII_UPPERCASE = HTML5.ASCII_LOWERCASE.toUpperCase(); +HTML5.ASCII_LETTERS = "[a-zA-Z]"; +HTML5.ASCII_LETTERS_R = /^[a-zA-Z]/; +HTML5.DIGITS = '0123456789'; +HTML5.DIGITS_R = new RegExp('^[0123456789]'); +HTML5.HEX_DIGITS = HTML5.DIGITS + 'abcdefABCDEF'; +HTML5.HEX_DIGITS_R = new RegExp('^[' + HTML5.DIGITS + 'abcdefABCDEF' +']' ); + +// Heading elements need to be ordered +HTML5.HEADING_ELEMENTS = [ + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6' +]; + +HTML5.VOID_ELEMENTS = [ + 'base', + 'link', + 'meta', + 'hr', + 'br', + 'img', + 'embed', + 'param', + 'area', + 'col', + 'input' +]; + +HTML5.CDATA_ELEMENTS = [ + 'title', + 'textarea' +]; + +HTML5.RCDATA_ELEMENTS = [ + 'style', + 'script', + 'xmp', + 'iframe', + 'noembed', + 'noframes', + 'noscript' +]; + +HTML5.BOOLEAN_ATTRIBUTES = { + '_global': ['irrelevant'], + // Fixme? + 'style': ['scoped'], + 'img': ['ismap'], + 'audio': ['autoplay', 'controls'], + 'video': ['autoplay', 'controls'], + 'script': ['defer', 'async'], + 'details': ['open'], + 'datagrid': ['multiple', 'disabled'], + 'command': ['hidden', 'disabled', 'checked', 'default'], + 'menu': ['autosubmit'], + 'fieldset': ['disabled', 'readonly'], + 'option': ['disabled', 'readonly', 'selected'], + 'optgroup': ['disabled', 'readonly'], + 'button': ['disabled', 'autofocus'], + 'input': ['disabled', 'readonly', 'required', 'autofocus', 'checked', 'ismap'], + 'select': ['disabled', 'readonly', 'autofocus', 'multiple'], + 'output': ['disabled', 'readonly'] +}; + +HTML5.ENTITIES = require('html5-entities'); + +HTML5.ENCODINGS = [ + 'ansi_x3.4-1968', + 'iso-ir-6', + 'ansi_x3.4-1986', + 'iso_646.irv:1991', + 'ascii', + 'iso646-us', + 'us-ascii', + 'us', + 'ibm367', + 'cp367', + 'csascii', + 'ks_c_5601-1987', + 'korean', + 'iso-2022-kr', + 'csiso2022kr', + 'euc-kr', + 'iso-2022-jp', + 'csiso2022jp', + 'iso-2022-jp-2', + '', + 'iso-ir-58', + 'chinese', + 'csiso58gb231280', + 'iso_8859-1:1987', + 'iso-ir-100', + 'iso_8859-1', + 'iso-8859-1', + 'latin1', + 'l1', + 'ibm819', + 'cp819', + 'csisolatin1', + 'iso_8859-2:1987', + 'iso-ir-101', + 'iso_8859-2', + 'iso-8859-2', + 'latin2', + 'l2', + 'csisolatin2', + 'iso_8859-3:1988', + 'iso-ir-109', + 'iso_8859-3', + 'iso-8859-3', + 'latin3', + 'l3', + 'csisolatin3', + 'iso_8859-4:1988', + 'iso-ir-110', + 'iso_8859-4', + 'iso-8859-4', + 'latin4', + 'l4', + 'csisolatin4', + 'iso_8859-6:1987', + 'iso-ir-127', + 'iso_8859-6', + 'iso-8859-6', + 'ecma-114', + 'asmo-708', + 'arabic', + 'csisolatinarabic', + 'iso_8859-7:1987', + 'iso-ir-126', + 'iso_8859-7', + 'iso-8859-7', + 'elot_928', + 'ecma-118', + 'greek', + 'greek8', + 'csisolatingreek', + 'iso_8859-8:1988', + 'iso-ir-138', + 'iso_8859-8', + 'iso-8859-8', + 'hebrew', + 'csisolatinhebrew', + 'iso_8859-5:1988', + 'iso-ir-144', + 'iso_8859-5', + 'iso-8859-5', + 'cyrillic', + 'csisolatincyrillic', + 'iso_8859-9:1989', + 'iso-ir-148', + 'iso_8859-9', + 'iso-8859-9', + 'latin5', + 'l5', + 'csisolatin5', + 'iso-8859-10', + 'iso-ir-157', + 'l6', + 'iso_8859-10:1992', + 'csisolatin6', + 'latin6', + 'hp-roman8', + 'roman8', + 'r8', + 'ibm037', + 'cp037', + 'csibm037', + 'ibm424', + 'cp424', + 'csibm424', + 'ibm437', + 'cp437', + '437', + 'cspc8codepage437', + 'ibm500', + 'cp500', + 'csibm500', + 'ibm775', + 'cp775', + 'cspc775baltic', + 'ibm850', + 'cp850', + '850', + 'cspc850multilingual', + 'ibm852', + 'cp852', + '852', + 'cspcp852', + 'ibm855', + 'cp855', + '855', + 'csibm855', + 'ibm857', + 'cp857', + '857', + 'csibm857', + 'ibm860', + 'cp860', + '860', + 'csibm860', + 'ibm861', + 'cp861', + '861', + 'cp-is', + 'csibm861', + 'ibm862', + 'cp862', + '862', + 'cspc862latinhebrew', + 'ibm863', + 'cp863', + '863', + 'csibm863', + 'ibm864', + 'cp864', + 'csibm864', + 'ibm865', + 'cp865', + '865', + 'csibm865', + 'ibm866', + 'cp866', + '866', + 'csibm866', + 'ibm869', + 'cp869', + '869', + 'cp-gr', + 'csibm869', + 'ibm1026', + 'cp1026', + 'csibm1026', + 'koi8-r', + 'cskoi8r', + 'koi8-u', + 'big5-hkscs', + 'ptcp154', + 'csptcp154', + 'pt154', + 'cp154', + 'utf-7', + 'utf-16be', + 'utf-16le', + 'utf-16', + 'utf-8', + 'iso-8859-13', + 'iso-8859-14', + 'iso-ir-199', + 'iso_8859-14:1998', + 'iso_8859-14', + 'latin8', + 'iso-celtic', + 'l8', + 'iso-8859-15', + 'iso_8859-15', + 'iso-8859-16', + 'iso-ir-226', + 'iso_8859-16:2001', + 'iso_8859-16', + 'latin10', + 'l10', + 'gbk', + 'cp936', + 'ms936', + 'gb18030', + 'shift_jis', + 'ms_kanji', + 'csshiftjis', + 'euc-jp', + 'gb2312', + 'big5', + 'csbig5', + 'windows-1250', + 'windows-1251', + 'windows-1252', + 'windows-1253', + 'windows-1254', + 'windows-1255', + 'windows-1256', + 'windows-1257', + 'windows-1258', + 'tis-620', + 'hz-gb-2312' +]; + +HTML5.E = { + "null-character": + "Null character in input stream, replaced with U+FFFD.", + "incorrectly-placed-solidus": + "Solidus (/) incorrectly placed in tag.", + "incorrect-cr-newline-entity": + "Incorrect CR newline entity, replaced with LF.", + "illegal-windows-1252-entity": + "Entity used with illegal number (windows-1252 reference).", + "cant-convert-numeric-entity": + "Numeric entity couldn't be converted to character " + + "(codepoint U+%(charAsInt)08x).", + "illegal-codepoint-for-numeric-entity": + "Numeric entity represents an illegal codepoint=> " + + "U+%(charAsInt)08x.", + "numeric-entity-without-semicolon": + "Numeric entity didn't end with ';'.", + "expected-numeric-entity-but-got-eof": + "Numeric entity expected. Got end of file instead.", + "expected-numeric-entity": + "Numeric entity expected but none found.", + "named-entity-without-semicolon": + "Named entity didn't end with ';'.", + "expected-named-entity": + "Named entity expected. Got none.", + "attributes-in-end-tag": + "End tag contains unexpected attributes.", + "expected-tag-name-but-got-right-bracket": + "Expected tag name. Got '>' instead.", + "expected-tag-name-but-got-question-mark": + "Expected tag name. Got '?' instead. (HTML doesn't " + + "support processing instructions.)", + "expected-tag-name": + "Expected tag name. Got something else instead", + "expected-closing-tag-but-got-right-bracket": + "Expected closing tag. Got '>' instead. Ignoring ''.", + "expected-closing-tag-but-got-eof": + "Expected closing tag. Unexpected end of file.", + "expected-closing-tag-but-got-char": + "Expected closing tag. Unexpected character '%(data)' found.", + "eof-in-tag-name": + "Unexpected end of file in the tag name.", + "expected-attribute-name-but-got-eof": + "Unexpected end of file. Expected attribute name instead.", + "eof-in-attribute-name": + "Unexpected end of file in attribute name.", + "duplicate-attribute": + "Dropped duplicate attribute on tag.", + "expected-end-of-tag-name-but-got-eof": + "Unexpected end of file. Expected = or end of tag.", + "expected-attribute-value-but-got-eof": + "Unexpected end of file. Expected attribute value.", + "eof-in-attribute-value-double-quote": + "Unexpected end of file in attribute value (\").", + "eof-in-attribute-value-single-quote": + "Unexpected end of file in attribute value (').", + "eof-in-attribute-value-no-quotes": + "Unexpected end of file in attribute value.", + "expected-dashes-or-doctype": + "Expected '--' or 'DOCTYPE'. Not found.", + "incorrect-comment": + "Incorrect comment.", + "eof-in-comment": + "Unexpected end of file in comment.", + "eof-in-comment-end-dash": + "Unexpected end of file in comment (-)", + "unexpected-dash-after-double-dash-in-comment": + "Unexpected '-' after '--' found in comment.", + "eof-in-comment-double-dash": + "Unexpected end of file in comment (--).", + "unexpected-char-in-comment": + "Unexpected character in comment found.", + "need-space-after-doctype": + "No space after literal string 'DOCTYPE'.", + "expected-doctype-name-but-got-right-bracket": + "Unexpected > character. Expected DOCTYPE name.", + "expected-doctype-name-but-got-eof": + "Unexpected end of file. Expected DOCTYPE name.", + "eof-in-doctype-name": + "Unexpected end of file in DOCTYPE name.", + "eof-in-doctype": + "Unexpected end of file in DOCTYPE.", + "expected-space-or-right-bracket-in-doctype": + "Expected space or '>'. Got '%(data)'", + "unexpected-end-of-doctype": + "Unexpected end of DOCTYPE.", + "unexpected-char-in-doctype": + "Unexpected character in DOCTYPE.", + "eof-in-bogus-doctype": + "Unexpected end of file in bogus doctype.", + "eof-in-innerhtml": + "Unexpected EOF in inner html mode.", + "unexpected-doctype": + "Unexpected DOCTYPE. Ignored.", + "non-html-root": + "html needs to be the first start tag.", + "expected-doctype-but-got-eof": + "Unexpected End of file. Expected DOCTYPE.", + "unknown-doctype": + "Erroneous DOCTYPE.", + "expected-doctype-but-got-chars": + "Unexpected non-space characters. Expected DOCTYPE.", + "expected-doctype-but-got-start-tag": + "Unexpected start tag (%(name)). Expected DOCTYPE.", + "expected-doctype-but-got-end-tag": + "Unexpected end tag (%(name)). Expected DOCTYPE.", + "end-tag-after-implied-root": + "Unexpected end tag (%(name)) after the (implied) root element.", + "expected-named-closing-tag-but-got-eof": + "Unexpected end of file. Expected end tag (%(name)).", + "two-heads-are-not-better-than-one": + "Unexpected start tag head in existing head. Ignored.", + "unexpected-end-tag": + "Unexpected end tag (%(name)). Ignored.", + "unexpected-start-tag-out-of-my-head": + "Unexpected start tag (%(name)) that can be in head. Moved.", + "unexpected-start-tag": + "Unexpected start tag (%(name)).", + "missing-end-tag": + "Missing end tag (%(name)).", + "missing-end-tags": + "Missing end tags (%(name)).", + "unexpected-start-tag-implies-end-tag": + "Unexpected start tag (%(startName)) " + + "implies end tag (%(endName)).", + "unexpected-start-tag-treated-as": + "Unexpected start tag (%(originalName)). Treated as %(newName).", + "deprecated-tag": + "Unexpected start tag %(name). Don't use it!", + "unexpected-start-tag-ignored": + "Unexpected start tag %(name). Ignored.", + "expected-one-end-tag-but-got-another": + "Unexpected end tag (%(gotName). " + + "Missing end tag (%(expectedName)).", + "end-tag-too-early": + "End tag (%(name)) seen too early. Expected other end tag.", + "end-tag-too-early-named": + "Unexpected end tag (%(gotName)). Expected end tag (%(expectedName).", + "end-tag-too-early-ignored": + "End tag (%(name)) seen too early. Ignored.", + "adoption-agency-1.1": + "End tag (%(name) violates step 1, " + + "paragraph 1 of the adoption agency algorithm.", + "adoption-agency-1.2": + "End tag (%(name) violates step 1, " + + "paragraph 2 of the adoption agency algorithm.", + "adoption-agency-1.3": + "End tag (%(name) violates step 1, " + + "paragraph 3 of the adoption agency algorithm.", + "unexpected-end-tag-treated-as": + "Unexpected end tag (%(originalName)). Treated as %(newName).", + "no-end-tag": + "This element (%(name)) has no end tag.", + "unexpected-implied-end-tag-in-table": + "Unexpected implied end tag (%(name)) in the table phase.", + "unexpected-implied-end-tag-in-table-body": + "Unexpected implied end tag (%(name)) in the table body phase.", + "unexpected-char-implies-table-voodoo": + "Unexpected non-space characters in " + + "table context caused voodoo mode.", + "unpexted-hidden-input-in-table": + "Unexpected input with type hidden in table context.", + "unexpected-start-tag-implies-table-voodoo": + "Unexpected start tag (%(name)) in " + + "table context caused voodoo mode.", + "unexpected-end-tag-implies-table-voodoo": + "Unexpected end tag (%(name)) in " + + "table context caused voodoo mode.", + "unexpected-cell-in-table-body": + "Unexpected table cell start tag (%(name)) " + + "in the table body phase.", + "unexpected-cell-end-tag": + "Got table cell end tag (%(name)) " + + "while required end tags are missing.", + "unexpected-end-tag-in-table-body": + "Unexpected end tag (%(name)) in the table body phase. Ignored.", + "unexpected-implied-end-tag-in-table-row": + "Unexpected implied end tag (%(name)) in the table row phase.", + "unexpected-end-tag-in-table-row": + "Unexpected end tag (%(name)) in the table row phase. Ignored.", + "unexpected-select-in-select": + "Unexpected select start tag in the select phase " + + "treated as select end tag.", + "unexpected-input-in-select": + "Unexpected input start tag in the select phase.", + "unexpected-start-tag-in-select": + "Unexpected start tag token (%(name)) in the select phase. " + + "Ignored.", + "unexpected-end-tag-in-select": + "Unexpected end tag (%(name)) in the select phase. Ignored.", + "unexpected-table-element-start-tag-in-select-in-table": + "Unexpected table element start tag (%(name))s in the select in table phase.", + "unexpected-table-element-end-tag-in-select-in-table": + "Unexpected table element end tag (%(name))s in the select in table phase.", + "unexpected-char-after-body": + "Unexpected non-space characters in the after body phase.", + "unexpected-start-tag-after-body": + "Unexpected start tag token (%(name))" + + "in the after body phase.", + "unexpected-end-tag-after-body": + "Unexpected end tag token (%(name))" + + " in the after body phase.", + "unexpected-char-in-frameset": + "Unepxected characters in the frameset phase. Characters ignored.", + "unexpected-start-tag-in-frameset": + "Unexpected start tag token (%(name))" + + " in the frameset phase. Ignored.", + "unexpected-frameset-in-frameset-innerhtml": + "Unexpected end tag token (frameset " + + "in the frameset phase (innerHTML).", + "unexpected-end-tag-in-frameset": + "Unexpected end tag token (%(name))" + + " in the frameset phase. Ignored.", + "unexpected-char-after-frameset": + "Unexpected non-space characters in the " + + "after frameset phase. Ignored.", + "unexpected-start-tag-after-frameset": + "Unexpected start tag (%(name))" + + " in the after frameset phase. Ignored.", + "unexpected-end-tag-after-frameset": + "Unexpected end tag (%(name))" + + " in the after frameset phase. Ignored.", + "expected-eof-but-got-char": + "Unexpected non-space characters. Expected end of file.", + "expected-eof-but-got-start-tag": + "Unexpected start tag (%(name))" + + ". Expected end of file.", + "expected-eof-but-got-end-tag": + "Unexpected end tag (%(name))" + + ". Expected end of file.", + "unexpected-end-table-in-caption": + "Unexpected end table tag in caption. Generates implied end caption.", + "end-html-in-innerhtml": + "Unexpected html end tag in inner html mode.", + "expected-self-closing-tag": + "Expected a > after the /.", + "self-closing-end-tag": + "Self closing end tag.", + "eof-in-table": + "Unexpected end of file. Expected table content.", + "html-in-foreign-content": + "HTML start tag \"%(name)\" in a foreign namespace context.", + "unexpected-start-tag-in-table": + "Unexpected %(name). Expected table content." +}; + +HTML5.Models = {PCDATA: 'PCDATA', RCDATA: 'RCDATA', CDATA: 'CDATA', SCRIPT_CDATA: 'SCRIPT_CDATA'}; + +HTML5.TAGMODES = { + select: 'inSelect', + td: 'inCell', + th: 'inCell', + tr: 'inRow', + tbody: 'inTableBody', + thead: 'inTableBody', + tfoot: 'inTableBody', + caption: 'inCaption', + colgroup: 'inColumnGroup', + table: 'inTable', + head: 'inBody', + body: 'inBody', + frameset: 'inFrameset' +}; + +HTML5.SVGAttributeMap = { + attributename: 'attributeName', + attributetype: 'attributeType', + basefrequency: 'baseFrequency', + baseprofile: 'baseProfile', + calcmode: 'calcMode', + clippathunits: 'clipPathUnits', + contentscripttype: 'contentScriptType', + contentstyletype: 'contentStyleType', + diffuseconstant: 'diffuseConstant', + edgemode: 'edgeMode', + externalresourcesrequired: 'externalResourcesRequired', + filterres: 'filterRes', + filterunits: 'filterUnits', + glyphref: 'glyphRef', + gradienttransform: 'gradientTransform', + gradientunits: 'gradientUnits', + kernelmatrix: 'kernelMatrix', + kernelunitlength: 'kernelUnitLength', + keypoints: 'keyPoints', + keysplines: 'keySplines', + keytimes: 'keyTimes', + lengthadjust: 'lengthAdjust', + limitingconeangle: 'limitingConeAngle', + markerheight: 'markerHeight', + markerunits: 'markerUnits', + markerwidth: 'markerWidth', + maskcontentunits: 'maskContentUnits', + maskunits: 'maskUnits', + numoctaves: 'numOctaves', + pathlength: 'pathLength', + patterncontentunits: 'patternContentUnits', + patterntransform: 'patternTransform', + patternunits: 'patternUnits', + pointsatx: 'pointsAtX', + pointsaty: 'pointsAtY', + pointsatz: 'pointsAtZ', + preservealpha: 'preserveAlpha', + preserveaspectratio: 'preserveAspectRatio', + primitiveunits: 'primitiveUnits', + refx: 'refX', + refy: 'refY', + repeatcount: 'repeatCount', + repeatdur: 'repeatDur', + requiredextensions: 'requiredExtensions', + requiredfeatures: 'requiredFeatures', + specularconstant: 'specularConstant', + specularexponent: 'specularExponent', + spreadmethod: 'spreadMethod', + startoffset: 'startOffset', + stddeviation: 'stdDeviation', + stitchtiles: 'stitchTiles', + surfacescale: 'surfaceScale', + systemlanguage: 'systemLanguage', + tablevalues: 'tableValues', + targetx: 'targetX', + targety: 'targetY', + textlength: 'textLength', + viewbox: 'viewBox', + viewtarget: 'viewTarget', + xchannelselector: 'xChannelSelector', + ychannelselector: 'yChannelSelector', + zoomandpan: 'zoomAndPan' +}; + diff --git a/packages/html5-tokenizer/entities.js b/packages/html5-tokenizer/entities.js new file mode 100644 index 0000000000..75cde451f4 --- /dev/null +++ b/packages/html5-tokenizer/entities.js @@ -0,0 +1,2233 @@ +module.exports = { + "AElig": "\u00C6", + "AElig;": "\u00C6", + "AMP": "&", + "AMP;": "&", + "Aacute": "\u00C1", + "Aacute;": "\u00C1", + "Abreve;": "\u0102", + "Acirc": "\u00C2", + "Acirc;": "\u00C2", + "Acy;": "\u0410", + "Afr;": "\u1D504", + "Agrave": "\u00C0", + "Agrave;": "\u00C0", + "Alpha;": "\u0391", + "Amacr;": "\u0100", + "And;": "\u2A53", + "Aogon;": "\u0104", + "Aopf;": "\u1D538", + "ApplyFunction;": "\u2061", + "Aring": "\u00C5", + "Aring;": "\u00C5", + "Ascr;": "\u1D49C", + "Assign;": "\u2254", + "Atilde": "\u00C3", + "Atilde;": "\u00C3", + "Auml": "\u00C4", + "Auml;": "\u00C4", + "Backslash;": "\u2216", + "Barv;": "\u2AE7", + "Barwed;": "\u2306", + "Bcy;": "\u0411", + "Because;": "\u2235", + "Bernoullis;": "\u212C", + "Beta;": "\u0392", + "Bfr;": "\u1D505", + "Bopf;": "\u1D539", + "Breve;": "\u02D8", + "Bscr;": "\u212C", + "Bumpeq;": "\u224E", + "CHcy;": "\u0427", + "COPY": "\u00A9", + "COPY;": "\u00A9", + "Cacute;": "\u0106", + "Cap;": "\u22D2", + "CapitalDifferentialD;": "\u2145", + "Cayleys;": "\u212D", + "Ccaron;": "\u010C", + "Ccedil": "\u00C7", + "Ccedil;": "\u00C7", + "Ccirc;": "\u0108", + "Cconint;": "\u2230", + "Cdot;": "\u010A", + "Cedilla;": "\u00B8", + "CenterDot;": "\u00B7", + "Cfr;": "\u212D", + "Chi;": "\u03A7", + "CircleDot;": "\u2299", + "CircleMinus;": "\u2296", + "CirclePlus;": "\u2295", + "CircleTimes;": "\u2297", + "ClockwiseContourIntegral;": "\u2232", + "CloseCurlyDoubleQuote;": "\u201D", + "CloseCurlyQuote;": "\u2019", + "Colon;": "\u2237", + "Colone;": "\u2A74", + "Congruent;": "\u2261", + "Conint;": "\u222F", + "ContourIntegral;": "\u222E", + "Copf;": "\u2102", + "Coproduct;": "\u2210", + "CounterClockwiseContourIntegral;": "\u2233", + "Cross;": "\u2A2F", + "Cscr;": "\u1D49E", + "Cup;": "\u22D3", + "CupCap;": "\u224D", + "DD;": "\u2145", + "DDotrahd;": "\u2911", + "DJcy;": "\u0402", + "DScy;": "\u0405", + "DZcy;": "\u040F", + "Dagger;": "\u2021", + "Darr;": "\u21A1", + "Dashv;": "\u2AE4", + "Dcaron;": "\u010E", + "Dcy;": "\u0414", + "Del;": "\u2207", + "Delta;": "\u0394", + "Dfr;": "\u1D507", + "DiacriticalAcute;": "\u00B4", + "DiacriticalDot;": "\u02D9", + "DiacriticalDoubleAcute;": "\u02DD", + "DiacriticalGrave;": "`", + "DiacriticalTilde;": "\u02DC", + "Diamond;": "\u22C4", + "DifferentialD;": "\u2146", + "Dopf;": "\u1D53B", + "Dot;": "\u00A8", + "DotDot;": "\u20DC", + "DotEqual;": "\u2250", + "DoubleContourIntegral;": "\u222F", + "DoubleDot;": "\u00A8", + "DoubleDownArrow;": "\u21D3", + "DoubleLeftArrow;": "\u21D0", + "DoubleLeftRightArrow;": "\u21D4", + "DoubleLeftTee;": "\u2AE4", + "DoubleLongLeftArrow;": "\u27F8", + "DoubleLongLeftRightArrow;": "\u27FA", + "DoubleLongRightArrow;": "\u27F9", + "DoubleRightArrow;": "\u21D2", + "DoubleRightTee;": "\u22A8", + "DoubleUpArrow;": "\u21D1", + "DoubleUpDownArrow;": "\u21D5", + "DoubleVerticalBar;": "\u2225", + "DownArrow;": "\u2193", + "DownArrowBar;": "\u2913", + "DownArrowUpArrow;": "\u21F5", + "DownBreve;": "\u0311", + "DownLeftRightVector;": "\u2950", + "DownLeftTeeVector;": "\u295E", + "DownLeftVector;": "\u21BD", + "DownLeftVectorBar;": "\u2956", + "DownRightTeeVector;": "\u295F", + "DownRightVector;": "\u21C1", + "DownRightVectorBar;": "\u2957", + "DownTee;": "\u22A4", + "DownTeeArrow;": "\u21A7", + "Downarrow;": "\u21D3", + "Dscr;": "\u1D49F", + "Dstrok;": "\u0110", + "ENG;": "\u014A", + "ETH": "\u00D0", + "ETH;": "\u00D0", + "Eacute": "\u00C9", + "Eacute;": "\u00C9", + "Ecaron;": "\u011A", + "Ecirc": "\u00CA", + "Ecirc;": "\u00CA", + "Ecy;": "\u042D", + "Edot;": "\u0116", + "Efr;": "\u1D508", + "Egrave": "\u00C8", + "Egrave;": "\u00C8", + "Element;": "\u2208", + "Emacr;": "\u0112", + "EmptySmallSquare;": "\u25FB", + "EmptyVerySmallSquare;": "\u25AB", + "Eogon;": "\u0118", + "Eopf;": "\u1D53C", + "Epsilon;": "\u0395", + "Equal;": "\u2A75", + "EqualTilde;": "\u2242", + "Equilibrium;": "\u21CC", + "Escr;": "\u2130", + "Esim;": "\u2A73", + "Eta;": "\u0397", + "Euml": "\u00CB", + "Euml;": "\u00CB", + "Exists;": "\u2203", + "ExponentialE;": "\u2147", + "Fcy;": "\u0424", + "Ffr;": "\u1D509", + "FilledSmallSquare;": "\u25FC", + "FilledVerySmallSquare;": "\u25AA", + "Fopf;": "\u1D53D", + "ForAll;": "\u2200", + "Fouriertrf;": "\u2131", + "Fscr;": "\u2131", + "GJcy;": "\u0403", + "GT": ">", + "GT;": ">", + "Gamma;": "\u0393", + "Gammad;": "\u03DC", + "Gbreve;": "\u011E", + "Gcedil;": "\u0122", + "Gcirc;": "\u011C", + "Gcy;": "\u0413", + "Gdot;": "\u0120", + "Gfr;": "\u1D50A", + "Gg;": "\u22D9", + "Gopf;": "\u1D53E", + "GreaterEqual;": "\u2265", + "GreaterEqualLess;": "\u22DB", + "GreaterFullEqual;": "\u2267", + "GreaterGreater;": "\u2AA2", + "GreaterLess;": "\u2277", + "GreaterSlantEqual;": "\u2A7E", + "GreaterTilde;": "\u2273", + "Gscr;": "\u1D4A2", + "Gt;": "\u226B", + "HARDcy;": "\u042A", + "Hacek;": "\u02C7", + "Hat;": "^", + "Hcirc;": "\u0124", + "Hfr;": "\u210C", + "HilbertSpace;": "\u210B", + "Hopf;": "\u210D", + "HorizontalLine;": "\u2500", + "Hscr;": "\u210B", + "Hstrok;": "\u0126", + "HumpDownHump;": "\u224E", + "HumpEqual;": "\u224F", + "IEcy;": "\u0415", + "IJlig;": "\u0132", + "IOcy;": "\u0401", + "Iacute": "\u00CD", + "Iacute;": "\u00CD", + "Icirc": "\u00CE", + "Icirc;": "\u00CE", + "Icy;": "\u0418", + "Idot;": "\u0130", + "Ifr;": "\u2111", + "Igrave": "\u00CC", + "Igrave;": "\u00CC", + "Im;": "\u2111", + "Imacr;": "\u012A", + "ImaginaryI;": "\u2148", + "Implies;": "\u21D2", + "Int;": "\u222C", + "Integral;": "\u222B", + "Intersection;": "\u22C2", + "InvisibleComma;": "\u2063", + "InvisibleTimes;": "\u2062", + "Iogon;": "\u012E", + "Iopf;": "\u1D540", + "Iota;": "\u0399", + "Iscr;": "\u2110", + "Itilde;": "\u0128", + "Iukcy;": "\u0406", + "Iuml": "\u00CF", + "Iuml;": "\u00CF", + "Jcirc;": "\u0134", + "Jcy;": "\u0419", + "Jfr;": "\u1D50D", + "Jopf;": "\u1D541", + "Jscr;": "\u1D4A5", + "Jsercy;": "\u0408", + "Jukcy;": "\u0404", + "KHcy;": "\u0425", + "KJcy;": "\u040C", + "Kappa;": "\u039A", + "Kcedil;": "\u0136", + "Kcy;": "\u041A", + "Kfr;": "\u1D50E", + "Kopf;": "\u1D542", + "Kscr;": "\u1D4A6", + "LJcy;": "\u0409", + "LT": "<", + "LT;": "<", + "Lacute;": "\u0139", + "Lambda;": "\u039B", + "Lang;": "\u27EA", + "Laplacetrf;": "\u2112", + "Larr;": "\u219E", + "Lcaron;": "\u013D", + "Lcedil;": "\u013B", + "Lcy;": "\u041B", + "LeftAngleBracket;": "\u27E8", + "LeftArrow;": "\u2190", + "LeftArrowBar;": "\u21E4", + "LeftArrowRightArrow;": "\u21C6", + "LeftCeiling;": "\u2308", + "LeftDoubleBracket;": "\u27E6", + "LeftDownTeeVector;": "\u2961", + "LeftDownVector;": "\u21C3", + "LeftDownVectorBar;": "\u2959", + "LeftFloor;": "\u230A", + "LeftRightArrow;": "\u2194", + "LeftRightVector;": "\u294E", + "LeftTee;": "\u22A3", + "LeftTeeArrow;": "\u21A4", + "LeftTeeVector;": "\u295A", + "LeftTriangle;": "\u22B2", + "LeftTriangleBar;": "\u29CF", + "LeftTriangleEqual;": "\u22B4", + "LeftUpDownVector;": "\u2951", + "LeftUpTeeVector;": "\u2960", + "LeftUpVector;": "\u21BF", + "LeftUpVectorBar;": "\u2958", + "LeftVector;": "\u21BC", + "LeftVectorBar;": "\u2952", + "Leftarrow;": "\u21D0", + "Leftrightarrow;": "\u21D4", + "LessEqualGreater;": "\u22DA", + "LessFullEqual;": "\u2266", + "LessGreater;": "\u2276", + "LessLess;": "\u2AA1", + "LessSlantEqual;": "\u2A7D", + "LessTilde;": "\u2272", + "Lfr;": "\u1D50F", + "Ll;": "\u22D8", + "Lleftarrow;": "\u21DA", + "Lmidot;": "\u013F", + "LongLeftArrow;": "\u27F5", + "LongLeftRightArrow;": "\u27F7", + "LongRightArrow;": "\u27F6", + "Longleftarrow;": "\u27F8", + "Longleftrightarrow;": "\u27FA", + "Longrightarrow;": "\u27F9", + "Lopf;": "\u1D543", + "LowerLeftArrow;": "\u2199", + "LowerRightArrow;": "\u2198", + "Lscr;": "\u2112", + "Lsh;": "\u21B0", + "Lstrok;": "\u0141", + "Lt;": "\u226A", + "Map;": "\u2905", + "Mcy;": "\u041C", + "MediumSpace;": "\u205F", + "Mellintrf;": "\u2133", + "Mfr;": "\u1D510", + "MinusPlus;": "\u2213", + "Mopf;": "\u1D544", + "Mscr;": "\u2133", + "Mu;": "\u039C", + "NJcy;": "\u040A", + "Nacute;": "\u0143", + "Ncaron;": "\u0147", + "Ncedil;": "\u0145", + "Ncy;": "\u041D", + "NegativeMediumSpace;": "\u200B", + "NegativeThickSpace;": "\u200B", + "NegativeThinSpace;": "\u200B", + "NegativeVeryThinSpace;": "\u200B", + "NestedGreaterGreater;": "\u226B", + "NestedLessLess;": "\u226A", + "NewLine;": "\u000A", + "Nfr;": "\u1D511", + "NoBreak;": "\u2060", + "NonBreakingSpace;": "\u00A0", + "Nopf;": "\u2115", + "Not;": "\u2AEC", + "NotCongruent;": "\u2262", + "NotCupCap;": "\u226D", + "NotDoubleVerticalBar;": "\u2226", + "NotElement;": "\u2209", + "NotEqual;": "\u2260", + "NotEqualTilde;": "\u2242\u0338", + "NotExists;": "\u2204", + "NotGreater;": "\u226F", + "NotGreaterEqual;": "\u2271", + "NotGreaterFullEqual;": "\u2267\u0338", + "NotGreaterGreater;": "\u226B\u0338", + "NotGreaterLess;": "\u2279", + "NotGreaterSlantEqual;": "\u2A7E\u0338", + "NotGreaterTilde;": "\u2275", + "NotHumpDownHump;": "\u224E\u0338", + "NotHumpEqual;": "\u224F\u0338", + "NotLeftTriangle;": "\u22EA", + "NotLeftTriangleBar;": "\u29CF\u0338", + "NotLeftTriangleEqual;": "\u22EC", + "NotLess;": "\u226E", + "NotLessEqual;": "\u2270", + "NotLessGreater;": "\u2278", + "NotLessLess;": "\u226A\u0338", + "NotLessSlantEqual;": "\u2A7D\u0338", + "NotLessTilde;": "\u2274", + "NotNestedGreaterGreater;": "\u2AA2\u0338", + "NotNestedLessLess;": "\u2AA1\u0338", + "NotPrecedes;": "\u2280", + "NotPrecedesEqual;": "\u2AAF\u0338", + "NotPrecedesSlantEqual;": "\u22E0", + "NotReverseElement;": "\u220C", + "NotRightTriangle;": "\u22EB", + "NotRightTriangleBar;": "\u29D0\u0338", + "NotRightTriangleEqual;": "\u22ED", + "NotSquareSubset;": "\u228F\u0338", + "NotSquareSubsetEqual;": "\u22E2", + "NotSquareSuperset;": "\u2290\u0338", + "NotSquareSupersetEqual;": "\u22E3", + "NotSubset;": "\u2282\u20D2", + "NotSubsetEqual;": "\u2288", + "NotSucceeds;": "\u2281", + "NotSucceedsEqual;": "\u2AB0\u0338", + "NotSucceedsSlantEqual;": "\u22E1", + "NotSucceedsTilde;": "\u227F\u0338", + "NotSuperset;": "\u2283\u20D2", + "NotSupersetEqual;": "\u2289", + "NotTilde;": "\u2241", + "NotTildeEqual;": "\u2244", + "NotTildeFullEqual;": "\u2247", + "NotTildeTilde;": "\u2249", + "NotVerticalBar;": "\u2224", + "Nscr;": "\u1D4A9", + "Ntilde": "\u00D1", + "Ntilde;": "\u00D1", + "Nu;": "\u039D", + "OElig;": "\u0152", + "Oacute": "\u00D3", + "Oacute;": "\u00D3", + "Ocirc": "\u00D4", + "Ocirc;": "\u00D4", + "Ocy;": "\u041E", + "Odblac;": "\u0150", + "Ofr;": "\u1D512", + "Ograve": "\u00D2", + "Ograve;": "\u00D2", + "Omacr;": "\u014C", + "Omega;": "\u03A9", + "Omicron;": "\u039F", + "Oopf;": "\u1D546", + "OpenCurlyDoubleQuote;": "\u201C", + "OpenCurlyQuote;": "\u2018", + "Or;": "\u2A54", + "Oscr;": "\u1D4AA", + "Oslash": "\u00D8", + "Oslash;": "\u00D8", + "Otilde": "\u00D5", + "Otilde;": "\u00D5", + "Otimes;": "\u2A37", + "Ouml": "\u00D6", + "Ouml;": "\u00D6", + "OverBar;": "\u203E", + "OverBrace;": "\u23DE", + "OverBracket;": "\u23B4", + "OverParenthesis;": "\u23DC", + "PartialD;": "\u2202", + "Pcy;": "\u041F", + "Pfr;": "\u1D513", + "Phi;": "\u03A6", + "Pi;": "\u03A0", + "PlusMinus;": "\u00B1", + "Poincareplane;": "\u210C", + "Popf;": "\u2119", + "Pr;": "\u2ABB", + "Precedes;": "\u227A", + "PrecedesEqual;": "\u2AAF", + "PrecedesSlantEqual;": "\u227C", + "PrecedesTilde;": "\u227E", + "Prime;": "\u2033", + "Product;": "\u220F", + "Proportion;": "\u2237", + "Proportional;": "\u221D", + "Pscr;": "\u1D4AB", + "Psi;": "\u03A8", + "QUOT": "\u0022", + "QUOT;": "\u0022", + "Qfr;": "\u1D514", + "Qopf;": "\u211A", + "Qscr;": "\u1D4AC", + "RBarr;": "\u2910", + "REG": "\u00AE", + "REG;": "\u00AE", + "Racute;": "\u0154", + "Rang;": "\u27EB", + "Rarr;": "\u21A0", + "Rarrtl;": "\u2916", + "Rcaron;": "\u0158", + "Rcedil;": "\u0156", + "Rcy;": "\u0420", + "Re;": "\u211C", + "ReverseElement;": "\u220B", + "ReverseEquilibrium;": "\u21CB", + "ReverseUpEquilibrium;": "\u296F", + "Rfr;": "\u211C", + "Rho;": "\u03A1", + "RightAngleBracket;": "\u27E9", + "RightArrow;": "\u2192", + "RightArrowBar;": "\u21E5", + "RightArrowLeftArrow;": "\u21C4", + "RightCeiling;": "\u2309", + "RightDoubleBracket;": "\u27E7", + "RightDownTeeVector;": "\u295D", + "RightDownVector;": "\u21C2", + "RightDownVectorBar;": "\u2955", + "RightFloor;": "\u230B", + "RightTee;": "\u22A2", + "RightTeeArrow;": "\u21A6", + "RightTeeVector;": "\u295B", + "RightTriangle;": "\u22B3", + "RightTriangleBar;": "\u29D0", + "RightTriangleEqual;": "\u22B5", + "RightUpDownVector;": "\u294F", + "RightUpTeeVector;": "\u295C", + "RightUpVector;": "\u21BE", + "RightUpVectorBar;": "\u2954", + "RightVector;": "\u21C0", + "RightVectorBar;": "\u2953", + "Rightarrow;": "\u21D2", + "Ropf;": "\u211D", + "RoundImplies;": "\u2970", + "Rrightarrow;": "\u21DB", + "Rscr;": "\u211B", + "Rsh;": "\u21B1", + "RuleDelayed;": "\u29F4", + "SHCHcy;": "\u0429", + "SHcy;": "\u0428", + "SOFTcy;": "\u042C", + "Sacute;": "\u015A", + "Sc;": "\u2ABC", + "Scaron;": "\u0160", + "Scedil;": "\u015E", + "Scirc;": "\u015C", + "Scy;": "\u0421", + "Sfr;": "\u1D516", + "ShortDownArrow;": "\u2193", + "ShortLeftArrow;": "\u2190", + "ShortRightArrow;": "\u2192", + "ShortUpArrow;": "\u2191", + "Sigma;": "\u03A3", + "SmallCircle;": "\u2218", + "Sopf;": "\u1D54A", + "Sqrt;": "\u221A", + "Square;": "\u25A1", + "SquareIntersection;": "\u2293", + "SquareSubset;": "\u228F", + "SquareSubsetEqual;": "\u2291", + "SquareSuperset;": "\u2290", + "SquareSupersetEqual;": "\u2292", + "SquareUnion;": "\u2294", + "Sscr;": "\u1D4AE", + "Star;": "\u22C6", + "Sub;": "\u22D0", + "Subset;": "\u22D0", + "SubsetEqual;": "\u2286", + "Succeeds;": "\u227B", + "SucceedsEqual;": "\u2AB0", + "SucceedsSlantEqual;": "\u227D", + "SucceedsTilde;": "\u227F", + "SuchThat;": "\u220B", + "Sum;": "\u2211", + "Sup;": "\u22D1", + "Superset;": "\u2283", + "SupersetEqual;": "\u2287", + "Supset;": "\u22D1", + "THORN": "\u00DE", + "THORN;": "\u00DE", + "TRADE;": "\u2122", + "TSHcy;": "\u040B", + "TScy;": "\u0426", + "Tab;": "\u0009", + "Tau;": "\u03A4", + "Tcaron;": "\u0164", + "Tcedil;": "\u0162", + "Tcy;": "\u0422", + "Tfr;": "\u1D517", + "Therefore;": "\u2234", + "Theta;": "\u0398", + "ThickSpace;": "\u205F\u200A", + "ThinSpace;": "\u2009", + "Tilde;": "\u223C", + "TildeEqual;": "\u2243", + "TildeFullEqual;": "\u2245", + "TildeTilde;": "\u2248", + "Topf;": "\u1D54B", + "TripleDot;": "\u20DB", + "Tscr;": "\u1D4AF", + "Tstrok;": "\u0166", + "Uacute": "\u00DA", + "Uacute;": "\u00DA", + "Uarr;": "\u219F", + "Uarrocir;": "\u2949", + "Ubrcy;": "\u040E", + "Ubreve;": "\u016C", + "Ucirc": "\u00DB", + "Ucirc;": "\u00DB", + "Ucy;": "\u0423", + "Udblac;": "\u0170", + "Ufr;": "\u1D518", + "Ugrave": "\u00D9", + "Ugrave;": "\u00D9", + "Umacr;": "\u016A", + "UnderBar;": "_", + "UnderBrace;": "\u23DF", + "UnderBracket;": "\u23B5", + "UnderParenthesis;": "\u23DD", + "Union;": "\u22C3", + "UnionPlus;": "\u228E", + "Uogon;": "\u0172", + "Uopf;": "\u1D54C", + "UpArrow;": "\u2191", + "UpArrowBar;": "\u2912", + "UpArrowDownArrow;": "\u21C5", + "UpDownArrow;": "\u2195", + "UpEquilibrium;": "\u296E", + "UpTee;": "\u22A5", + "UpTeeArrow;": "\u21A5", + "Uparrow;": "\u21D1", + "Updownarrow;": "\u21D5", + "UpperLeftArrow;": "\u2196", + "UpperRightArrow;": "\u2197", + "Upsi;": "\u03D2", + "Upsilon;": "\u03A5", + "Uring;": "\u016E", + "Uscr;": "\u1D4B0", + "Utilde;": "\u0168", + "Uuml": "\u00DC", + "Uuml;": "\u00DC", + "VDash;": "\u22AB", + "Vbar;": "\u2AEB", + "Vcy;": "\u0412", + "Vdash;": "\u22A9", + "Vdashl;": "\u2AE6", + "Vee;": "\u22C1", + "Verbar;": "\u2016", + "Vert;": "\u2016", + "VerticalBar;": "\u2223", + "VerticalLine;": "|", + "VerticalSeparator;": "\u2758", + "VerticalTilde;": "\u2240", + "VeryThinSpace;": "\u200A", + "Vfr;": "\u1D519", + "Vopf;": "\u1D54D", + "Vscr;": "\u1D4B1", + "Vvdash;": "\u22AA", + "Wcirc;": "\u0174", + "Wedge;": "\u22C0", + "Wfr;": "\u1D51A", + "Wopf;": "\u1D54E", + "Wscr;": "\u1D4B2", + "Xfr;": "\u1D51B", + "Xi;": "\u039E", + "Xopf;": "\u1D54F", + "Xscr;": "\u1D4B3", + "YAcy;": "\u042F", + "YIcy;": "\u0407", + "YUcy;": "\u042E", + "Yacute": "\u00DD", + "Yacute;": "\u00DD", + "Ycirc;": "\u0176", + "Ycy;": "\u042B", + "Yfr;": "\u1D51C", + "Yopf;": "\u1D550", + "Yscr;": "\u1D4B4", + "Yuml;": "\u0178", + "ZHcy;": "\u0416", + "Zacute;": "\u0179", + "Zcaron;": "\u017D", + "Zcy;": "\u0417", + "Zdot;": "\u017B", + "ZeroWidthSpace;": "\u200B", + "Zeta;": "\u0396", + "Zfr;": "\u2128", + "Zopf;": "\u2124", + "Zscr;": "\u1D4B5", + "aacute": "\u00E1", + "aacute;": "\u00E1", + "abreve;": "\u0103", + "ac;": "\u223E", + "acE;": "\u223E\u0333", + "acd;": "\u223F", + "acirc": "\u00E2", + "acirc;": "\u00E2", + "acute": "\u00B4", + "acute;": "\u00B4", + "acy;": "\u0430", + "aelig": "\u00E6", + "aelig;": "\u00E6", + "af;": "\u2061", + "afr;": "\u1D51E", + "agrave": "\u00E0", + "agrave;": "\u00E0", + "alefsym;": "\u2135", + "aleph;": "\u2135", + "alpha;": "\u03B1", + "amacr;": "\u0101", + "amalg;": "\u2A3F", + "amp": "&", + "amp;": "&", + "and;": "\u2227", + "andand;": "\u2A55", + "andd;": "\u2A5C", + "andslope;": "\u2A58", + "andv;": "\u2A5A", + "ang;": "\u2220", + "ange;": "\u29A4", + "angle;": "\u2220", + "angmsd;": "\u2221", + "angmsdaa;": "\u29A8", + "angmsdab;": "\u29A9", + "angmsdac;": "\u29AA", + "angmsdad;": "\u29AB", + "angmsdae;": "\u29AC", + "angmsdaf;": "\u29AD", + "angmsdag;": "\u29AE", + "angmsdah;": "\u29AF", + "angrt;": "\u221F", + "angrtvb;": "\u22BE", + "angrtvbd;": "\u299D", + "angsph;": "\u2222", + "angst;": "\u00C5", + "angzarr;": "\u237C", + "aogon;": "\u0105", + "aopf;": "\u1D552", + "ap;": "\u2248", + "apE;": "\u2A70", + "apacir;": "\u2A6F", + "ape;": "\u224A", + "apid;": "\u224B", + "apos;": "\u0027", + "approx;": "\u2248", + "approxeq;": "\u224A", + "aring": "\u00E5", + "aring;": "\u00E5", + "ascr;": "\u1D4B6", + "ast;": "*", + "asymp;": "\u2248", + "asympeq;": "\u224D", + "atilde": "\u00E3", + "atilde;": "\u00E3", + "auml": "\u00E4", + "auml;": "\u00E4", + "awconint;": "\u2233", + "awint;": "\u2A11", + "bNot;": "\u2AED", + "backcong;": "\u224C", + "backepsilon;": "\u03F6", + "backprime;": "\u2035", + "backsim;": "\u223D", + "backsimeq;": "\u22CD", + "barvee;": "\u22BD", + "barwed;": "\u2305", + "barwedge;": "\u2305", + "bbrk;": "\u23B5", + "bbrktbrk;": "\u23B6", + "bcong;": "\u224C", + "bcy;": "\u0431", + "bdquo;": "\u201E", + "becaus;": "\u2235", + "because;": "\u2235", + "bemptyv;": "\u29B0", + "bepsi;": "\u03F6", + "bernou;": "\u212C", + "beta;": "\u03B2", + "beth;": "\u2136", + "between;": "\u226C", + "bfr;": "\u1D51F", + "bigcap;": "\u22C2", + "bigcirc;": "\u25EF", + "bigcup;": "\u22C3", + "bigodot;": "\u2A00", + "bigoplus;": "\u2A01", + "bigotimes;": "\u2A02", + "bigsqcup;": "\u2A06", + "bigstar;": "\u2605", + "bigtriangledown;": "\u25BD", + "bigtriangleup;": "\u25B3", + "biguplus;": "\u2A04", + "bigvee;": "\u22C1", + "bigwedge;": "\u22C0", + "bkarow;": "\u290D", + "blacklozenge;": "\u29EB", + "blacksquare;": "\u25AA", + "blacktriangle;": "\u25B4", + "blacktriangledown;": "\u25BE", + "blacktriangleleft;": "\u25C2", + "blacktriangleright;": "\u25B8", + "blank;": "\u2423", + "blk12;": "\u2592", + "blk14;": "\u2591", + "blk34;": "\u2593", + "block;": "\u2588", + "bne;": "\u003D\u20E5", + "bnequiv;": "\u2261\u20E5", + "bnot;": "\u2310", + "bopf;": "\u1D553", + "bot;": "\u22A5", + "bottom;": "\u22A5", + "bowtie;": "\u22C8", + "boxDL;": "\u2557", + "boxDR;": "\u2554", + "boxDl;": "\u2556", + "boxDr;": "\u2553", + "boxH;": "\u2550", + "boxHD;": "\u2566", + "boxHU;": "\u2569", + "boxHd;": "\u2564", + "boxHu;": "\u2567", + "boxUL;": "\u255D", + "boxUR;": "\u255A", + "boxUl;": "\u255C", + "boxUr;": "\u2559", + "boxV;": "\u2551", + "boxVH;": "\u256C", + "boxVL;": "\u2563", + "boxVR;": "\u2560", + "boxVh;": "\u256B", + "boxVl;": "\u2562", + "boxVr;": "\u255F", + "boxbox;": "\u29C9", + "boxdL;": "\u2555", + "boxdR;": "\u2552", + "boxdl;": "\u2510", + "boxdr;": "\u250C", + "boxh;": "\u2500", + "boxhD;": "\u2565", + "boxhU;": "\u2568", + "boxhd;": "\u252C", + "boxhu;": "\u2534", + "boxminus;": "\u229F", + "boxplus;": "\u229E", + "boxtimes;": "\u22A0", + "boxuL;": "\u255B", + "boxuR;": "\u2558", + "boxul;": "\u2518", + "boxur;": "\u2514", + "boxv;": "\u2502", + "boxvH;": "\u256A", + "boxvL;": "\u2561", + "boxvR;": "\u255E", + "boxvh;": "\u253C", + "boxvl;": "\u2524", + "boxvr;": "\u251C", + "bprime;": "\u2035", + "breve;": "\u02D8", + "brvbar": "\u00A6", + "brvbar;": "\u00A6", + "bscr;": "\u1D4B7", + "bsemi;": "\u204F", + "bsim;": "\u223D", + "bsime;": "\u22CD", + "bsol;": "\u005C", + "bsolb;": "\u29C5", + "bsolhsub;": "\u27C8", + "bull;": "\u2022", + "bullet;": "\u2022", + "bump;": "\u224E", + "bumpE;": "\u2AAE", + "bumpe;": "\u224F", + "bumpeq;": "\u224F", + "cacute;": "\u0107", + "cap;": "\u2229", + "capand;": "\u2A44", + "capbrcup;": "\u2A49", + "capcap;": "\u2A4B", + "capcup;": "\u2A47", + "capdot;": "\u2A40", + "caps;": "\u2229\uFE00", + "caret;": "\u2041", + "caron;": "\u02C7", + "ccaps;": "\u2A4D", + "ccaron;": "\u010D", + "ccedil": "\u00E7", + "ccedil;": "\u00E7", + "ccirc;": "\u0109", + "ccups;": "\u2A4C", + "ccupssm;": "\u2A50", + "cdot;": "\u010B", + "cedil": "\u00B8", + "cedil;": "\u00B8", + "cemptyv;": "\u29B2", + "cent": "\u00A2", + "cent;": "\u00A2", + "centerdot;": "\u00B7", + "cfr;": "\u1D520", + "chcy;": "\u0447", + "check;": "\u2713", + "checkmark;": "\u2713", + "chi;": "\u03C7", + "cir;": "\u25CB", + "cirE;": "\u29C3", + "circ;": "\u02C6", + "circeq;": "\u2257", + "circlearrowleft;": "\u21BA", + "circlearrowright;": "\u21BB", + "circledR;": "\u00AE", + "circledS;": "\u24C8", + "circledast;": "\u229B", + "circledcirc;": "\u229A", + "circleddash;": "\u229D", + "cire;": "\u2257", + "cirfnint;": "\u2A10", + "cirmid;": "\u2AEF", + "cirscir;": "\u29C2", + "clubs;": "\u2663", + "clubsuit;": "\u2663", + "colon;": ":", + "colone;": "\u2254", + "coloneq;": "\u2254", + "comma;": ",", + "commat;": "@", + "comp;": "\u2201", + "compfn;": "\u2218", + "complement;": "\u2201", + "complexes;": "\u2102", + "cong;": "\u2245", + "congdot;": "\u2A6D", + "conint;": "\u222E", + "copf;": "\u1D554", + "coprod;": "\u2210", + "copy": "\u00A9", + "copy;": "\u00A9", + "copysr;": "\u2117", + "crarr;": "\u21B5", + "cross;": "\u2717", + "cscr;": "\u1D4B8", + "csub;": "\u2ACF", + "csube;": "\u2AD1", + "csup;": "\u2AD0", + "csupe;": "\u2AD2", + "ctdot;": "\u22EF", + "cudarrl;": "\u2938", + "cudarrr;": "\u2935", + "cuepr;": "\u22DE", + "cuesc;": "\u22DF", + "cularr;": "\u21B6", + "cularrp;": "\u293D", + "cup;": "\u222A", + "cupbrcap;": "\u2A48", + "cupcap;": "\u2A46", + "cupcup;": "\u2A4A", + "cupdot;": "\u228D", + "cupor;": "\u2A45", + "cups;": "\u222A\uFE00", + "curarr;": "\u21B7", + "curarrm;": "\u293C", + "curlyeqprec;": "\u22DE", + "curlyeqsucc;": "\u22DF", + "curlyvee;": "\u22CE", + "curlywedge;": "\u22CF", + "curren": "\u00A4", + "curren;": "\u00A4", + "curvearrowleft;": "\u21B6", + "curvearrowright;": "\u21B7", + "cuvee;": "\u22CE", + "cuwed;": "\u22CF", + "cwconint;": "\u2232", + "cwint;": "\u2231", + "cylcty;": "\u232D", + "dArr;": "\u21D3", + "dHar;": "\u2965", + "dagger;": "\u2020", + "daleth;": "\u2138", + "darr;": "\u2193", + "dash;": "\u2010", + "dashv;": "\u22A3", + "dbkarow;": "\u290F", + "dblac;": "\u02DD", + "dcaron;": "\u010F", + "dcy;": "\u0434", + "dd;": "\u2146", + "ddagger;": "\u2021", + "ddarr;": "\u21CA", + "ddotseq;": "\u2A77", + "deg": "\u00B0", + "deg;": "\u00B0", + "delta;": "\u03B4", + "demptyv;": "\u29B1", + "dfisht;": "\u297F", + "dfr;": "\u1D521", + "dharl;": "\u21C3", + "dharr;": "\u21C2", + "diam;": "\u22C4", + "diamond;": "\u22C4", + "diamondsuit;": "\u2666", + "diams;": "\u2666", + "die;": "\u00A8", + "digamma;": "\u03DD", + "disin;": "\u22F2", + "div;": "\u00F7", + "divide": "\u00F7", + "divide;": "\u00F7", + "divideontimes;": "\u22C7", + "divonx;": "\u22C7", + "djcy;": "\u0452", + "dlcorn;": "\u231E", + "dlcrop;": "\u230D", + "dollar;": "$", + "dopf;": "\u1D555", + "dot;": "\u02D9", + "doteq;": "\u2250", + "doteqdot;": "\u2251", + "dotminus;": "\u2238", + "dotplus;": "\u2214", + "dotsquare;": "\u22A1", + "doublebarwedge;": "\u2306", + "downarrow;": "\u2193", + "downdownarrows;": "\u21CA", + "downharpoonleft;": "\u21C3", + "downharpoonright;": "\u21C2", + "drbkarow;": "\u2910", + "drcorn;": "\u231F", + "drcrop;": "\u230C", + "dscr;": "\u1D4B9", + "dscy;": "\u0455", + "dsol;": "\u29F6", + "dstrok;": "\u0111", + "dtdot;": "\u22F1", + "dtri;": "\u25BF", + "dtrif;": "\u25BE", + "duarr;": "\u21F5", + "duhar;": "\u296F", + "dwangle;": "\u29A6", + "dzcy;": "\u045F", + "dzigrarr;": "\u27FF", + "eDDot;": "\u2A77", + "eDot;": "\u2251", + "eacute": "\u00E9", + "eacute;": "\u00E9", + "easter;": "\u2A6E", + "ecaron;": "\u011B", + "ecir;": "\u2256", + "ecirc": "\u00EA", + "ecirc;": "\u00EA", + "ecolon;": "\u2255", + "ecy;": "\u044D", + "edot;": "\u0117", + "ee;": "\u2147", + "efDot;": "\u2252", + "efr;": "\u1D522", + "eg;": "\u2A9A", + "egrave": "\u00E8", + "egrave;": "\u00E8", + "egs;": "\u2A96", + "egsdot;": "\u2A98", + "el;": "\u2A99", + "elinters;": "\u23E7", + "ell;": "\u2113", + "els;": "\u2A95", + "elsdot;": "\u2A97", + "emacr;": "\u0113", + "empty;": "\u2205", + "emptyset;": "\u2205", + "emptyv;": "\u2205", + "emsp13;": "\u2004", + "emsp14;": "\u2005", + "emsp;": "\u2003", + "eng;": "\u014B", + "ensp;": "\u2002", + "eogon;": "\u0119", + "eopf;": "\u1D556", + "epar;": "\u22D5", + "eparsl;": "\u29E3", + "eplus;": "\u2A71", + "epsi;": "\u03B5", + "epsilon;": "\u03B5", + "epsiv;": "\u03F5", + "eqcirc;": "\u2256", + "eqcolon;": "\u2255", + "eqsim;": "\u2242", + "eqslantgtr;": "\u2A96", + "eqslantless;": "\u2A95", + "equals;": "=", + "equest;": "\u225F", + "equiv;": "\u2261", + "equivDD;": "\u2A78", + "eqvparsl;": "\u29E5", + "erDot;": "\u2253", + "erarr;": "\u2971", + "escr;": "\u212F", + "esdot;": "\u2250", + "esim;": "\u2242", + "eta;": "\u03B7", + "eth": "\u00F0", + "eth;": "\u00F0", + "euml": "\u00EB", + "euml;": "\u00EB", + "euro;": "\u20AC", + "excl;": "!", + "exist;": "\u2203", + "expectation;": "\u2130", + "exponentiale;": "\u2147", + "fallingdotseq;": "\u2252", + "fcy;": "\u0444", + "female;": "\u2640", + "ffilig;": "\uFB03", + "fflig;": "\uFB00", + "ffllig;": "\uFB04", + "ffr;": "\u1D523", + "filig;": "\uFB01", + "fjlig;": "\u0066", + "flat;": "\u266D", + "fllig;": "\uFB02", + "fltns;": "\u25B1", + "fnof;": "\u0192", + "fopf;": "\u1D557", + "forall;": "\u2200", + "fork;": "\u22D4", + "forkv;": "\u2AD9", + "fpartint;": "\u2A0D", + "frac12": "\u00BD", + "frac12;": "\u00BD", + "frac13;": "\u2153", + "frac14": "\u00BC", + "frac14;": "\u00BC", + "frac15;": "\u2155", + "frac16;": "\u2159", + "frac18;": "\u215B", + "frac23;": "\u2154", + "frac25;": "\u2156", + "frac34": "\u00BE", + "frac34;": "\u00BE", + "frac35;": "\u2157", + "frac38;": "\u215C", + "frac45;": "\u2158", + "frac56;": "\u215A", + "frac58;": "\u215D", + "frac78;": "\u215E", + "frasl;": "\u2044", + "frown;": "\u2322", + "fscr;": "\u1D4BB", + "gE;": "\u2267", + "gEl;": "\u2A8C", + "gacute;": "\u01F5", + "gamma;": "\u03B3", + "gammad;": "\u03DD", + "gap;": "\u2A86", + "gbreve;": "\u011F", + "gcirc;": "\u011D", + "gcy;": "\u0433", + "gdot;": "\u0121", + "ge;": "\u2265", + "gel;": "\u22DB", + "geq;": "\u2265", + "geqq;": "\u2267", + "geqslant;": "\u2A7E", + "ges;": "\u2A7E", + "gescc;": "\u2AA9", + "gesdot;": "\u2A80", + "gesdoto;": "\u2A82", + "gesdotol;": "\u2A84", + "gesl;": "\u22DB\uFE00", + "gesles;": "\u2A94", + "gfr;": "\u1D524", + "gg;": "\u226B", + "ggg;": "\u22D9", + "gimel;": "\u2137", + "gjcy;": "\u0453", + "gl;": "\u2277", + "glE;": "\u2A92", + "gla;": "\u2AA5", + "glj;": "\u2AA4", + "gnE;": "\u2269", + "gnap;": "\u2A8A", + "gnapprox;": "\u2A8A", + "gne;": "\u2A88", + "gneq;": "\u2A88", + "gneqq;": "\u2269", + "gnsim;": "\u22E7", + "gopf;": "\u1D558", + "grave;": "`", + "gscr;": "\u210A", + "gsim;": "\u2273", + "gsime;": "\u2A8E", + "gsiml;": "\u2A90", + "gt": ">", + "gt;": ">", + "gtcc;": "\u2AA7", + "gtcir;": "\u2A7A", + "gtdot;": "\u22D7", + "gtlPar;": "\u2995", + "gtquest;": "\u2A7C", + "gtrapprox;": "\u2A86", + "gtrarr;": "\u2978", + "gtrdot;": "\u22D7", + "gtreqless;": "\u22DB", + "gtreqqless;": "\u2A8C", + "gtrless;": "\u2277", + "gtrsim;": "\u2273", + "gvertneqq;": "\u2269\uFE00", + "gvnE;": "\u2269\uFE00", + "hArr;": "\u21D4", + "hairsp;": "\u200A", + "half;": "\u00BD", + "hamilt;": "\u210B", + "hardcy;": "\u044A", + "harr;": "\u2194", + "harrcir;": "\u2948", + "harrw;": "\u21AD", + "hbar;": "\u210F", + "hcirc;": "\u0125", + "hearts;": "\u2665", + "heartsuit;": "\u2665", + "hellip;": "\u2026", + "hercon;": "\u22B9", + "hfr;": "\u1D525", + "hksearow;": "\u2925", + "hkswarow;": "\u2926", + "hoarr;": "\u21FF", + "homtht;": "\u223B", + "hookleftarrow;": "\u21A9", + "hookrightarrow;": "\u21AA", + "hopf;": "\u1D559", + "horbar;": "\u2015", + "hscr;": "\u1D4BD", + "hslash;": "\u210F", + "hstrok;": "\u0127", + "hybull;": "\u2043", + "hyphen;": "\u2010", + "iacute": "\u00ED", + "iacute;": "\u00ED", + "ic;": "\u2063", + "icirc": "\u00EE", + "icirc;": "\u00EE", + "icy;": "\u0438", + "iecy;": "\u0435", + "iexcl": "\u00A1", + "iexcl;": "\u00A1", + "iff;": "\u21D4", + "ifr;": "\u1D526", + "igrave": "\u00EC", + "igrave;": "\u00EC", + "ii;": "\u2148", + "iiiint;": "\u2A0C", + "iiint;": "\u222D", + "iinfin;": "\u29DC", + "iiota;": "\u2129", + "ijlig;": "\u0133", + "imacr;": "\u012B", + "image;": "\u2111", + "imagline;": "\u2110", + "imagpart;": "\u2111", + "imath;": "\u0131", + "imof;": "\u22B7", + "imped;": "\u01B5", + "in;": "\u2208", + "incare;": "\u2105", + "infin;": "\u221E", + "infintie;": "\u29DD", + "inodot;": "\u0131", + "int;": "\u222B", + "intcal;": "\u22BA", + "integers;": "\u2124", + "intercal;": "\u22BA", + "intlarhk;": "\u2A17", + "intprod;": "\u2A3C", + "iocy;": "\u0451", + "iogon;": "\u012F", + "iopf;": "\u1D55A", + "iota;": "\u03B9", + "iprod;": "\u2A3C", + "iquest": "\u00BF", + "iquest;": "\u00BF", + "iscr;": "\u1D4BE", + "isin;": "\u2208", + "isinE;": "\u22F9", + "isindot;": "\u22F5", + "isins;": "\u22F4", + "isinsv;": "\u22F3", + "isinv;": "\u2208", + "it;": "\u2062", + "itilde;": "\u0129", + "iukcy;": "\u0456", + "iuml": "\u00EF", + "iuml;": "\u00EF", + "jcirc;": "\u0135", + "jcy;": "\u0439", + "jfr;": "\u1D527", + "jmath;": "\u0237", + "jopf;": "\u1D55B", + "jscr;": "\u1D4BF", + "jsercy;": "\u0458", + "jukcy;": "\u0454", + "kappa;": "\u03BA", + "kappav;": "\u03F0", + "kcedil;": "\u0137", + "kcy;": "\u043A", + "kfr;": "\u1D528", + "kgreen;": "\u0138", + "khcy;": "\u0445", + "kjcy;": "\u045C", + "kopf;": "\u1D55C", + "kscr;": "\u1D4C0", + "lAarr;": "\u21DA", + "lArr;": "\u21D0", + "lAtail;": "\u291B", + "lBarr;": "\u290E", + "lE;": "\u2266", + "lEg;": "\u2A8B", + "lHar;": "\u2962", + "lacute;": "\u013A", + "laemptyv;": "\u29B4", + "lagran;": "\u2112", + "lambda;": "\u03BB", + "lang;": "\u27E8", + "langd;": "\u2991", + "langle;": "\u27E8", + "lap;": "\u2A85", + "laquo": "\u00AB", + "laquo;": "\u00AB", + "larr;": "\u2190", + "larrb;": "\u21E4", + "larrbfs;": "\u291F", + "larrfs;": "\u291D", + "larrhk;": "\u21A9", + "larrlp;": "\u21AB", + "larrpl;": "\u2939", + "larrsim;": "\u2973", + "larrtl;": "\u21A2", + "lat;": "\u2AAB", + "latail;": "\u2919", + "late;": "\u2AAD", + "lates;": "\u2AAD\uFE00", + "lbarr;": "\u290C", + "lbbrk;": "\u2772", + "lbrace;": "{", + "lbrack;": "[", + "lbrke;": "\u298B", + "lbrksld;": "\u298F", + "lbrkslu;": "\u298D", + "lcaron;": "\u013E", + "lcedil;": "\u013C", + "lceil;": "\u2308", + "lcub;": "{", + "lcy;": "\u043B", + "ldca;": "\u2936", + "ldquo;": "\u201C", + "ldquor;": "\u201E", + "ldrdhar;": "\u2967", + "ldrushar;": "\u294B", + "ldsh;": "\u21B2", + "le;": "\u2264", + "leftarrow;": "\u2190", + "leftarrowtail;": "\u21A2", + "leftharpoondown;": "\u21BD", + "leftharpoonup;": "\u21BC", + "leftleftarrows;": "\u21C7", + "leftrightarrow;": "\u2194", + "leftrightarrows;": "\u21C6", + "leftrightharpoons;": "\u21CB", + "leftrightsquigarrow;": "\u21AD", + "leftthreetimes;": "\u22CB", + "leg;": "\u22DA", + "leq;": "\u2264", + "leqq;": "\u2266", + "leqslant;": "\u2A7D", + "les;": "\u2A7D", + "lescc;": "\u2AA8", + "lesdot;": "\u2A7F", + "lesdoto;": "\u2A81", + "lesdotor;": "\u2A83", + "lesg;": "\u22DA\uFE00", + "lesges;": "\u2A93", + "lessapprox;": "\u2A85", + "lessdot;": "\u22D6", + "lesseqgtr;": "\u22DA", + "lesseqqgtr;": "\u2A8B", + "lessgtr;": "\u2276", + "lesssim;": "\u2272", + "lfisht;": "\u297C", + "lfloor;": "\u230A", + "lfr;": "\u1D529", + "lg;": "\u2276", + "lgE;": "\u2A91", + "lhard;": "\u21BD", + "lharu;": "\u21BC", + "lharul;": "\u296A", + "lhblk;": "\u2584", + "ljcy;": "\u0459", + "ll;": "\u226A", + "llarr;": "\u21C7", + "llcorner;": "\u231E", + "llhard;": "\u296B", + "lltri;": "\u25FA", + "lmidot;": "\u0140", + "lmoust;": "\u23B0", + "lmoustache;": "\u23B0", + "lnE;": "\u2268", + "lnap;": "\u2A89", + "lnapprox;": "\u2A89", + "lne;": "\u2A87", + "lneq;": "\u2A87", + "lneqq;": "\u2268", + "lnsim;": "\u22E6", + "loang;": "\u27EC", + "loarr;": "\u21FD", + "lobrk;": "\u27E6", + "longleftarrow;": "\u27F5", + "longleftrightarrow;": "\u27F7", + "longmapsto;": "\u27FC", + "longrightarrow;": "\u27F6", + "looparrowleft;": "\u21AB", + "looparrowright;": "\u21AC", + "lopar;": "\u2985", + "lopf;": "\u1D55D", + "loplus;": "\u2A2D", + "lotimes;": "\u2A34", + "lowast;": "\u2217", + "lowbar;": "_", + "loz;": "\u25CA", + "lozenge;": "\u25CA", + "lozf;": "\u29EB", + "lpar;": "(", + "lparlt;": "\u2993", + "lrarr;": "\u21C6", + "lrcorner;": "\u231F", + "lrhar;": "\u21CB", + "lrhard;": "\u296D", + "lrm;": "\u200E", + "lrtri;": "\u22BF", + "lsaquo;": "\u2039", + "lscr;": "\u1D4C1", + "lsh;": "\u21B0", + "lsim;": "\u2272", + "lsime;": "\u2A8D", + "lsimg;": "\u2A8F", + "lsqb;": "[", + "lsquo;": "\u2018", + "lsquor;": "\u201A", + "lstrok;": "\u0142", + "lt": "<", + "lt;": "<", + "ltcc;": "\u2AA6", + "ltcir;": "\u2A79", + "ltdot;": "\u22D6", + "lthree;": "\u22CB", + "ltimes;": "\u22C9", + "ltlarr;": "\u2976", + "ltquest;": "\u2A7B", + "ltrPar;": "\u2996", + "ltri;": "\u25C3", + "ltrie;": "\u22B4", + "ltrif;": "\u25C2", + "lurdshar;": "\u294A", + "luruhar;": "\u2966", + "lvertneqq;": "\u2268\uFE00", + "lvnE;": "\u2268\uFE00", + "mDDot;": "\u223A", + "macr": "\u00AF", + "macr;": "\u00AF", + "male;": "\u2642", + "malt;": "\u2720", + "maltese;": "\u2720", + "map;": "\u21A6", + "mapsto;": "\u21A6", + "mapstodown;": "\u21A7", + "mapstoleft;": "\u21A4", + "mapstoup;": "\u21A5", + "marker;": "\u25AE", + "mcomma;": "\u2A29", + "mcy;": "\u043C", + "mdash;": "\u2014", + "measuredangle;": "\u2221", + "mfr;": "\u1D52A", + "mho;": "\u2127", + "micro": "\u00B5", + "micro;": "\u00B5", + "mid;": "\u2223", + "midast;": "*", + "midcir;": "\u2AF0", + "middot": "\u00B7", + "middot;": "\u00B7", + "minus;": "\u2212", + "minusb;": "\u229F", + "minusd;": "\u2238", + "minusdu;": "\u2A2A", + "mlcp;": "\u2ADB", + "mldr;": "\u2026", + "mnplus;": "\u2213", + "models;": "\u22A7", + "mopf;": "\u1D55E", + "mp;": "\u2213", + "mscr;": "\u1D4C2", + "mstpos;": "\u223E", + "mu;": "\u03BC", + "multimap;": "\u22B8", + "mumap;": "\u22B8", + "nGg;": "\u22D9\u0338", + "nGt;": "\u226B\u20D2", + "nGtv;": "\u226B\u0338", + "nLeftarrow;": "\u21CD", + "nLeftrightarrow;": "\u21CE", + "nLl;": "\u22D8\u0338", + "nLt;": "\u226A\u20D2", + "nLtv;": "\u226A\u0338", + "nRightarrow;": "\u21CF", + "nVDash;": "\u22AF", + "nVdash;": "\u22AE", + "nabla;": "\u2207", + "nacute;": "\u0144", + "nang;": "\u2220\u20D2", + "nap;": "\u2249", + "napE;": "\u2A70\u0338", + "napid;": "\u224B\u0338", + "napos;": "\u0149", + "napprox;": "\u2249", + "natur;": "\u266E", + "natural;": "\u266E", + "naturals;": "\u2115", + "nbsp": "\u00A0", + "nbsp;": "\u00A0", + "nbump;": "\u224E\u0338", + "nbumpe;": "\u224F\u0338", + "ncap;": "\u2A43", + "ncaron;": "\u0148", + "ncedil;": "\u0146", + "ncong;": "\u2247\u0338", + "ncongdot;": "\u2A6D", + "ncup;": "\u2A42", + "ncy;": "\u043D", + "ndash;": "\u2013", + "ne;": "\u2260", + "neArr;": "\u21D7", + "nearhk;": "\u2924", + "nearr;": "\u2197", + "nearrow;": "\u2197", + "nedot;": "\u2250\u0338", + "nequiv;": "\u2262", + "nesear;": "\u2928", + "nesim;": "\u2242\u0338", + "nexist;": "\u2204", + "nexists;": "\u2204", + "nfr;": "\u1D52B", + "ngE;": "\u2267\u0338", + "nge;": "\u2271", + "ngeq;": "\u2271", + "ngeqq;": "\u2267\u0338", + "ngeqslant;": "\u2A7E\u0338", + "nges;": "\u2A7E\u0338", + "ngsim;": "\u2275", + "ngt;": "\u226F", + "ngtr;": "\u226F", + "nhArr;": "\u21CE", + "nharr;": "\u21AE", + "nhpar;": "\u2AF2", + "ni;": "\u220B", + "nis;": "\u22FC", + "nisd;": "\u22FA", + "niv;": "\u220B", + "njcy;": "\u045A", + "nlArr;": "\u21CD", + "nlE;": "\u2266\u0338", + "nlarr;": "\u219A", + "nldr;": "\u2025", + "nle;": "\u2270", + "nleftarrow;": "\u219A", + "nleftrightarrow;": "\u21AE", + "nleq;": "\u2270", + "nleqq;": "\u2266\u0338", + "nleqslant;": "\u2A7D\u0338", + "nles;": "\u2A7D\u0338", + "nless;": "\u226E", + "nlsim;": "\u2274", + "nlt;": "\u226E", + "nltri;": "\u22EA", + "nltrie;": "\u22EC", + "nmid;": "\u2224", + "nopf;": "\u1D55F", + "not": "\u00AC", + "not;": "\u00AC", + "notin;": "\u2209", + "notinE;": "\u22F9\u0338", + "notindot;": "\u22F5\u0338", + "notinva;": "\u2209", + "notinvb;": "\u22F7", + "notinvc;": "\u22F6", + "notni;": "\u220C", + "notniva;": "\u220C", + "notnivb;": "\u22FE", + "notnivc;": "\u22FD", + "npar;": "\u2226", + "nparallel;": "\u2226", + "nparsl;": "\u2AFD\u20E5", + "npart;": "\u2202\u0338", + "npolint;": "\u2A14", + "npr;": "\u2280", + "nprcue;": "\u22E0", + "npre;": "\u2AAF", + "nprec;": "\u2280", + "npreceq;": "\u2AAF", + "nrArr;": "\u21CF", + "nrarr;": "\u219B", + "nrarrc;": "\u2933\u0338", + "nrarrw;": "\u219D\u0338", + "nrightarrow;": "\u219B", + "nrtri;": "\u22EB", + "nrtrie;": "\u22ED", + "nsc;": "\u2281", + "nsccue;": "\u22E1", + "nsce;": "\u2AB0\u0338", + "nscr;": "\u1D4C3", + "nshortmid;": "\u2224", + "nshortparallel;": "\u2226", + "nsim;": "\u2241", + "nsime;": "\u2244", + "nsimeq;": "\u2244", + "nsmid;": "\u2224", + "nspar;": "\u2226", + "nsqsube;": "\u22E2", + "nsqsupe;": "\u22E3", + "nsub;": "\u2284", + "nsubE;": "\u2AC5\u0338", + "nsube;": "\u2288", + "nsubset;": "\u2282\u0338", + "nsubseteq;": "\u2288", + "nsubseteqq;": "\u2AC5\u0338", + "nsucc;": "\u2281", + "nsucceq;": "\u2AB0\u0338", + "nsup;": "\u2285", + "nsupE;": "\u2AC6", + "nsupe;": "\u2289", + "nsupset;": "\u2283\u0338", + "nsupseteq;": "\u2289", + "nsupseteqq;": "\u2AC6\u0338", + "ntgl;": "\u2279", + "ntilde": "\u00F1", + "ntilde;": "\u00F1", + "ntlg;": "\u2278", + "ntriangleleft;": "\u22EA", + "ntrianglelefteq;": "\u22EC", + "ntriangleright;": "\u22EB", + "ntrianglerighteq;": "\u22ED", + "nu;": "\u03BD", + "num;": "#", + "numero;": "\u2116", + "numsp;": "\u2007", + "nvDash;": "\u22AD", + "nvHarr;": "\u2904", + "nvap;": "\u224D\u20D2", + "nvdash;": "\u22AC", + "nvge;": "\u2265\u20D2", + "nvgt;": "\u003E\u20D2", + "nvinfin;": "\u29DE", + "nvlArr;": "\u2902", + "nvle;": "\u2264\u20D2", + "nvlt;": "\u003C\u20D2", + "nvltrie;": "\u22B4\u20D2", + "nvrArr;": "\u2903", + "nvrtrie;": "\u22B5\u20D2", + "nvsim;": "\u223C\u20D2", + "nwArr;": "\u21D6", + "nwarhk;": "\u2923", + "nwarr;": "\u2196", + "nwarrow;": "\u2196", + "nwnear;": "\u2927", + "oS;": "\u24C8", + "oacute": "\u00F3", + "oacute;": "\u00F3", + "oast;": "\u229B", + "ocir;": "\u229A", + "ocirc": "\u00F4", + "ocirc;": "\u00F4", + "ocy;": "\u043E", + "odash;": "\u229D", + "odblac;": "\u0151", + "odiv;": "\u2A38", + "odot;": "\u2299", + "odsold;": "\u29BC", + "oelig;": "\u0153", + "ofcir;": "\u29BF", + "ofr;": "\u1D52C", + "ogon;": "\u02DB", + "ograve": "\u00F2", + "ograve;": "\u00F2", + "ogt;": "\u29C1", + "ohbar;": "\u29B5", + "ohm;": "\u03A9", + "oint;": "\u222E", + "olarr;": "\u21BA", + "olcir;": "\u29BE", + "olcross;": "\u29BB", + "oline;": "\u203E", + "olt;": "\u29C0", + "omacr;": "\u014D", + "omega;": "\u03C9", + "omicron;": "\u03BF", + "omid;": "\u29B6", + "ominus;": "\u2296", + "oopf;": "\u1D560", + "opar;": "\u29B7", + "operp;": "\u29B9", + "oplus;": "\u2295", + "or;": "\u2228", + "orarr;": "\u21BB", + "ord;": "\u2A5D", + "order;": "\u2134", + "orderof;": "\u2134", + "ordf": "\u00AA", + "ordf;": "\u00AA", + "ordm": "\u00BA", + "ordm;": "\u00BA", + "origof;": "\u22B6", + "oror;": "\u2A56", + "orslope;": "\u2A57", + "orv;": "\u2A5B", + "oscr;": "\u2134", + "oslash": "\u00F8", + "oslash;": "\u00F8", + "osol;": "\u2298", + "otilde": "\u00F5", + "otilde;": "\u00F5", + "otimes;": "\u2297", + "otimesas;": "\u2A36", + "ouml": "\u00F6", + "ouml;": "\u00F6", + "ovbar;": "\u233D", + "par;": "\u2225", + "para": "\u00B6", + "para;": "\u00B6", + "parallel;": "\u2225", + "parsim;": "\u2AF3", + "parsl;": "\u2AFD", + "part;": "\u2202", + "pcy;": "\u043F", + "percnt;": "%", + "period;": ".", + "permil;": "\u2030", + "perp;": "\u22A5", + "pertenk;": "\u2031", + "pfr;": "\u1D52D", + "phi;": "\u03C6", + "phiv;": "\u03D5", + "phmmat;": "\u2133", + "phone;": "\u260E", + "pi;": "\u03C0", + "pitchfork;": "\u22D4", + "piv;": "\u03D6", + "planck;": "\u210F", + "planckh;": "\u210E", + "plankv;": "\u210F", + "plus;": "+", + "plusacir;": "\u2A23", + "plusb;": "\u229E", + "pluscir;": "\u2A22", + "plusdo;": "\u2214", + "plusdu;": "\u2A25", + "pluse;": "\u2A72", + "plusmn": "\u00B1", + "plusmn;": "\u00B1", + "plussim;": "\u2A26", + "plustwo;": "\u2A27", + "pm;": "\u00B1", + "pointint;": "\u2A15", + "popf;": "\u1D561", + "pound": "\u00A3", + "pound;": "\u00A3", + "pr;": "\u227A", + "prE;": "\u2AB3", + "prap;": "\u2AB7", + "prcue;": "\u227C", + "pre;": "\u2AAF", + "prec;": "\u227A", + "precapprox;": "\u2AB7", + "preccurlyeq;": "\u227C", + "preceq;": "\u2AAF", + "precnapprox;": "\u2AB9", + "precneqq;": "\u2AB5", + "precnsim;": "\u22E8", + "precsim;": "\u227E", + "prime;": "\u2032", + "primes;": "\u2119", + "prnE;": "\u2AB5", + "prnap;": "\u2AB9", + "prnsim;": "\u22E8", + "prod;": "\u220F", + "profalar;": "\u232E", + "profline;": "\u2312", + "profsurf;": "\u2313", + "prop;": "\u221D", + "propto;": "\u221D", + "prsim;": "\u227E", + "prurel;": "\u22B0", + "pscr;": "\u1D4C5", + "psi;": "\u03C8", + "puncsp;": "\u2008", + "qfr;": "\u1D52E", + "qint;": "\u2A0C", + "qopf;": "\u1D562", + "qprime;": "\u2057", + "qscr;": "\u1D4C6", + "quaternions;": "\u210D", + "quatint;": "\u2A16", + "quest;": "?", + "questeq;": "\u225F", + "quot": "\u0022", + "quot;": "\u0022", + "rAarr;": "\u21DB", + "rArr;": "\u21D2", + "rAtail;": "\u291C", + "rBarr;": "\u290F", + "rHar;": "\u2964", + "race;": "\u223D\u0331", + "racute;": "\u0155", + "radic;": "\u221A", + "raemptyv;": "\u29B3", + "rang;": "\u27E9", + "rangd;": "\u2992", + "range;": "\u29A5", + "rangle;": "\u27E9", + "raquo": "\u00BB", + "raquo;": "\u00BB", + "rarr;": "\u2192", + "rarrap;": "\u2975", + "rarrb;": "\u21E5", + "rarrbfs;": "\u2920", + "rarrc;": "\u2933", + "rarrfs;": "\u291E", + "rarrhk;": "\u21AA", + "rarrlp;": "\u21AC", + "rarrpl;": "\u2945", + "rarrsim;": "\u2974", + "rarrtl;": "\u21A3", + "rarrw;": "\u219D", + "ratail;": "\u291A", + "ratio;": "\u2236", + "rationals;": "\u211A", + "rbarr;": "\u290D", + "rbbrk;": "\u2773", + "rbrace;": "}", + "rbrack;": "]", + "rbrke;": "\u298C", + "rbrksld;": "\u298E", + "rbrkslu;": "\u2990", + "rcaron;": "\u0159", + "rcedil;": "\u0157", + "rceil;": "\u2309", + "rcub;": "}", + "rcy;": "\u0440", + "rdca;": "\u2937", + "rdldhar;": "\u2969", + "rdquo;": "\u201D", + "rdquor;": "\u201D", + "rdsh;": "\u21B3", + "real;": "\u211C", + "realine;": "\u211B", + "realpart;": "\u211C", + "reals;": "\u211D", + "rect;": "\u25AD", + "reg": "\u00AE", + "reg;": "\u00AE", + "rfisht;": "\u297D", + "rfloor;": "\u230B", + "rfr;": "\u1D52F", + "rhard;": "\u21C1", + "rharu;": "\u21C0", + "rharul;": "\u296C", + "rho;": "\u03C1", + "rhov;": "\u03F1", + "rightarrow;": "\u2192", + "rightarrowtail;": "\u21A3", + "rightharpoondown;": "\u21C1", + "rightharpoonup;": "\u21C0", + "rightleftarrows;": "\u21C4", + "rightleftharpoons;": "\u21CC", + "rightrightarrows;": "\u21C9", + "rightsquigarrow;": "\u219D", + "rightthreetimes;": "\u22CC", + "ring;": "\u02DA", + "risingdotseq;": "\u2253", + "rlarr;": "\u21C4", + "rlhar;": "\u21CC", + "rlm;": "\u200F", + "rmoust;": "\u23B1", + "rmoustache;": "\u23B1", + "rnmid;": "\u2AEE", + "roang;": "\u27ED", + "roarr;": "\u21FE", + "robrk;": "\u27E7", + "ropar;": "\u2986", + "ropf;": "\u1D563", + "roplus;": "\u2A2E", + "rotimes;": "\u2A35", + "rpar;": ")", + "rpargt;": "\u2994", + "rppolint;": "\u2A12", + "rrarr;": "\u21C9", + "rsaquo;": "\u203A", + "rscr;": "\u1D4C7", + "rsh;": "\u21B1", + "rsqb;": "]", + "rsquo;": "\u2019", + "rsquor;": "\u2019", + "rthree;": "\u22CC", + "rtimes;": "\u22CA", + "rtri;": "\u25B9", + "rtrie;": "\u22B5", + "rtrif;": "\u25B8", + "rtriltri;": "\u29CE", + "ruluhar;": "\u2968", + "rx;": "\u211E", + "sacute;": "\u015B", + "sbquo;": "\u201A", + "sc;": "\u227B", + "scE;": "\u2AB4", + "scap;": "\u2AB8", + "scaron;": "\u0161", + "sccue;": "\u227D", + "sce;": "\u2AB0", + "scedil;": "\u015F", + "scirc;": "\u015D", + "scnE;": "\u2AB6", + "scnap;": "\u2ABA", + "scnsim;": "\u22E9", + "scpolint;": "\u2A13", + "scsim;": "\u227F", + "scy;": "\u0441", + "sdot;": "\u22C5", + "sdotb;": "\u22A1", + "sdote;": "\u2A66", + "seArr;": "\u21D8", + "searhk;": "\u2925", + "searr;": "\u2198", + "searrow;": "\u2198", + "sect": "\u00A7", + "sect;": "\u00A7", + "semi;": ";", + "seswar;": "\u2929", + "setminus;": "\u2216", + "setmn;": "\u2216", + "sext;": "\u2736", + "sfr;": "\u1D530", + "sfrown;": "\u2322", + "sharp;": "\u266F", + "shchcy;": "\u0449", + "shcy;": "\u0448", + "shortmid;": "\u2223", + "shortparallel;": "\u2225", + "shy": "\u00AD", + "shy;": "\u00AD", + "sigma;": "\u03C3", + "sigmaf;": "\u03C2", + "sigmav;": "\u03C2", + "sim;": "\u223C", + "simdot;": "\u2A6A", + "sime;": "\u2243", + "simeq;": "\u2243", + "simg;": "\u2A9E", + "simgE;": "\u2AA0", + "siml;": "\u2A9D", + "simlE;": "\u2A9F", + "simne;": "\u2246", + "simplus;": "\u2A24", + "simrarr;": "\u2972", + "slarr;": "\u2190", + "smallsetminus;": "\u2216", + "smashp;": "\u2A33", + "smeparsl;": "\u29E4", + "smid;": "\u2223", + "smile;": "\u2323", + "smt;": "\u2AAA", + "smte;": "\u2AAC", + "smtes;": "\u2AAC\uFE00", + "softcy;": "\u044C", + "sol;": "/", + "solb;": "\u29C4", + "solbar;": "\u233F", + "sopf;": "\u1D564", + "spades;": "\u2660", + "spadesuit;": "\u2660", + "spar;": "\u2225", + "sqcap;": "\u2293", + "sqcaps;": "\u2293\uFE00", + "sqcup;": "\u2294", + "sqcups;": "\u2294\uFE00", + "sqsub;": "\u228F", + "sqsube;": "\u2291", + "sqsubset;": "\u228F", + "sqsubseteq;": "\u2291", + "sqsup;": "\u2290", + "sqsupe;": "\u2292", + "sqsupset;": "\u2290", + "sqsupseteq;": "\u2292", + "squ;": "\u25A1", + "square;": "\u25A1", + "squarf;": "\u25AA", + "squf;": "\u25AA", + "srarr;": "\u2192", + "sscr;": "\u1D4C8", + "ssetmn;": "\u2216", + "ssmile;": "\u2323", + "sstarf;": "\u22C6", + "star;": "\u2606", + "starf;": "\u2605", + "straightepsilon;": "\u03F5", + "straightphi;": "\u03D5", + "strns;": "\u00AF", + "sub;": "\u2282", + "subE;": "\u2AC5", + "subdot;": "\u2ABD", + "sube;": "\u2286", + "subedot;": "\u2AC3", + "submult;": "\u2AC1", + "subnE;": "\u2ACB", + "subne;": "\u228A", + "subplus;": "\u2ABF", + "subrarr;": "\u2979", + "subset;": "\u2282", + "subseteq;": "\u2286", + "subseteqq;": "\u2AC5", + "subsetneq;": "\u228A", + "subsetneqq;": "\u2ACB", + "subsim;": "\u2AC7", + "subsub;": "\u2AD5", + "subsup;": "\u2AD3", + "succ;": "\u227B", + "succapprox;": "\u2AB8", + "succcurlyeq;": "\u227D", + "succeq;": "\u2AB0", + "succnapprox;": "\u2ABA", + "succneqq;": "\u2AB6", + "succnsim;": "\u22E9", + "succsim;": "\u227F", + "sum;": "\u2211", + "sung;": "\u266A", + "sup1": "\u00B9", + "sup1;": "\u00B9", + "sup2": "\u00B2", + "sup2;": "\u00B2", + "sup3": "\u00B3", + "sup3;": "\u00B3", + "sup;": "\u2283", + "supE;": "\u2AC6", + "supdot;": "\u2ABE", + "supdsub;": "\u2AD8", + "supe;": "\u2287", + "supedot;": "\u2AC4", + "suphsol;": "\u27C9", + "suphsub;": "\u2AD7", + "suplarr;": "\u297B", + "supmult;": "\u2AC2", + "supnE;": "\u2ACC", + "supne;": "\u228B", + "supplus;": "\u2AC0", + "supset;": "\u2283", + "supseteq;": "\u2287", + "supseteqq;": "\u2AC6", + "supsetneq;": "\u228B", + "supsetneqq;": "\u2ACC", + "supsim;": "\u2AC8", + "supsub;": "\u2AD4", + "supsup;": "\u2AD6", + "swArr;": "\u21D9", + "swarhk;": "\u2926", + "swarr;": "\u2199", + "swarrow;": "\u2199", + "swnwar;": "\u292A", + "szlig": "\u00DF", + "szlig;": "\u00DF", + "target;": "\u2316", + "tau;": "\u03C4", + "tbrk;": "\u23B4", + "tcaron;": "\u0165", + "tcedil;": "\u0163", + "tcy;": "\u0442", + "tdot;": "\u20DB", + "telrec;": "\u2315", + "tfr;": "\u1D531", + "there4;": "\u2234", + "therefore;": "\u2234", + "theta;": "\u03B8", + "thetasym;": "\u03D1", + "thetav;": "\u03D1", + "thickapprox;": "\u2248", + "thicksim;": "\u223C", + "thinsp;": "\u2009", + "thkap;": "\u2248", + "thksim;": "\u223C", + "thorn": "\u00FE", + "thorn;": "\u00FE", + "tilde;": "\u02DC", + "times": "\u00D7", + "times;": "\u00D7", + "timesb;": "\u22A0", + "timesbar;": "\u2A31", + "timesd;": "\u2A30", + "tint;": "\u222D", + "toea;": "\u2928", + "top;": "\u22A4", + "topbot;": "\u2336", + "topcir;": "\u2AF1", + "topf;": "\u1D565", + "topfork;": "\u2ADA", + "tosa;": "\u2929", + "tprime;": "\u2034", + "trade;": "\u2122", + "triangle;": "\u25B5", + "triangledown;": "\u25BF", + "triangleleft;": "\u25C3", + "trianglelefteq;": "\u22B4", + "triangleq;": "\u225C", + "triangleright;": "\u25B9", + "trianglerighteq;": "\u22B5", + "tridot;": "\u25EC", + "trie;": "\u225C", + "triminus;": "\u2A3A", + "triplus;": "\u2A39", + "trisb;": "\u29CD", + "tritime;": "\u2A3B", + "trpezium;": "\u23E2", + "tscr;": "\u1D4C9", + "tscy;": "\u0446", + "tshcy;": "\u045B", + "tstrok;": "\u0167", + "twixt;": "\u226C", + "twoheadleftarrow;": "\u219E", + "twoheadrightarrow;": "\u21A0", + "uArr;": "\u21D1", + "uHar;": "\u2963", + "uacute": "\u00FA", + "uacute;": "\u00FA", + "uarr;": "\u2191", + "ubrcy;": "\u045E", + "ubreve;": "\u016D", + "ucirc": "\u00FB", + "ucirc;": "\u00FB", + "ucy;": "\u0443", + "udarr;": "\u21C5", + "udblac;": "\u0171", + "udhar;": "\u296E", + "ufisht;": "\u297E", + "ufr;": "\u1D532", + "ugrave": "\u00F9", + "ugrave;": "\u00F9", + "uharl;": "\u21BF", + "uharr;": "\u21BE", + "uhblk;": "\u2580", + "ulcorn;": "\u231C", + "ulcorner;": "\u231C", + "ulcrop;": "\u230F", + "ultri;": "\u25F8", + "umacr;": "\u016B", + "uml": "\u00A8", + "uml;": "\u00A8", + "uogon;": "\u0173", + "uopf;": "\u1D566", + "uparrow;": "\u2191", + "updownarrow;": "\u2195", + "upharpoonleft;": "\u21BF", + "upharpoonright;": "\u21BE", + "uplus;": "\u228E", + "upsi;": "\u03C5", + "upsih;": "\u03D2", + "upsilon;": "\u03C5", + "upuparrows;": "\u21C8", + "urcorn;": "\u231D", + "urcorner;": "\u231D", + "urcrop;": "\u230E", + "uring;": "\u016F", + "urtri;": "\u25F9", + "uscr;": "\u1D4CA", + "utdot;": "\u22F0", + "utilde;": "\u0169", + "utri;": "\u25B5", + "utrif;": "\u25B4", + "uuarr;": "\u21C8", + "uuml": "\u00FC", + "uuml;": "\u00FC", + "uwangle;": "\u29A7", + "vArr;": "\u21D5", + "vBar;": "\u2AE8", + "vBarv;": "\u2AE9", + "vDash;": "\u22A8", + "vangrt;": "\u299C", + "varepsilon;": "\u03F5", + "varkappa;": "\u03F0", + "varnothing;": "\u2205", + "varphi;": "\u03D5", + "varpi;": "\u03D6", + "varpropto;": "\u221D", + "varr;": "\u2195", + "varrho;": "\u03F1", + "varsigma;": "\u03C2", + "varsubsetneq;": "\u228A\uFE00", + "varsubsetneqq;": "\u2ACB\uFE00", + "varsupsetneq;": "\u228B\uFE00", + "varsupsetneqq;": "\u2ACC\uFE00", + "vartheta;": "\u03D1", + "vartriangleleft;": "\u22B2", + "vartriangleright;": "\u22B3", + "vcy;": "\u0432", + "vdash;": "\u22A2", + "vee;": "\u2228", + "veebar;": "\u22BB", + "veeeq;": "\u225A", + "vellip;": "\u22EE", + "verbar;": "|", + "vert;": "|", + "vfr;": "\u1D533", + "vltri;": "\u22B2", + "vnsub;": "\u2282\u20D2", + "vnsup;": "\u2283\u20D2", + "vopf;": "\u1D567", + "vprop;": "\u221D", + "vrtri;": "\u22B3", + "vscr;": "\u1D4CB", + "vsubnE;": "\u2ACB\uFE00", + "vsubne;": "\u228A\uFE00", + "vsupnE;": "\u2ACC\uFE00", + "vsupne;": "\u228B\uFE00", + "vzigzag;": "\u299A", + "wcirc;": "\u0175", + "wedbar;": "\u2A5F", + "wedge;": "\u2227", + "wedgeq;": "\u2259", + "weierp;": "\u2118", + "wfr;": "\u1D534", + "wopf;": "\u1D568", + "wp;": "\u2118", + "wr;": "\u2240", + "wreath;": "\u2240", + "wscr;": "\u1D4CC", + "xcap;": "\u22C2", + "xcirc;": "\u25EF", + "xcup;": "\u22C3", + "xdtri;": "\u25BD", + "xfr;": "\u1D535", + "xhArr;": "\u27FA", + "xharr;": "\u27F7", + "xi;": "\u03BE", + "xlArr;": "\u27F8", + "xlarr;": "\u27F5", + "xmap;": "\u27FC", + "xnis;": "\u22FB", + "xodot;": "\u2A00", + "xopf;": "\u1D569", + "xoplus;": "\u2A01", + "xotime;": "\u2A02", + "xrArr;": "\u27F9", + "xrarr;": "\u27F6", + "xscr;": "\u1D4CD", + "xsqcup;": "\u2A06", + "xuplus;": "\u2A04", + "xutri;": "\u25B3", + "xvee;": "\u22C1", + "xwedge;": "\u22C0", + "yacute": "\u00FD", + "yacute;": "\u00FD", + "yacy;": "\u044F", + "ycirc;": "\u0177", + "ycy;": "\u044B", + "yen": "\u00A5", + "yen;": "\u00A5", + "yfr;": "\u1D536", + "yicy;": "\u0457", + "yopf;": "\u1D56A", + "yscr;": "\u1D4CE", + "yucy;": "\u044E", + "yuml": "\u00FF", + "yuml;": "\u00FF", + "zacute;": "\u017A", + "zcaron;": "\u017E", + "zcy;": "\u0437", + "zdot;": "\u017C", + "zeetrf;": "\u2128", + "zeta;": "\u03B6", + "zfr;": "\u1D537", + "zhcy;": "\u0436", + "zigrarr;": "\u21DD", + "zopf;": "\u1D56B", + "zscr;": "\u1D4CF", + "zwj;": "\u200D", + "zwnj;": "\u200C" +}; diff --git a/packages/html5-tokenizer/package.js b/packages/html5-tokenizer/package.js new file mode 100644 index 0000000000..a0850a6419 --- /dev/null +++ b/packages/html5-tokenizer/package.js @@ -0,0 +1,18 @@ + +Package.describe({ + summary: "HTML5 tokenizer" +}); + +Npm.depends({'html5': "0.3.10"}); + +Package.on_use(function (api, where) { + where = where || ['client', 'server']; + + api.add_files(['entities.js', 'constants.js', 'tokenizer.js'], where); +}); + +Package.on_test(function (api) { + api.use('html5-tokenixer'); + api.use('tinytest'); + api.add_files('tokenizer_tests.js', ['client', 'server']); +}); diff --git a/packages/html5-tokenizer/tokenizer.js b/packages/html5-tokenizer/tokenizer.js new file mode 100644 index 0000000000..461eac634b --- /dev/null +++ b/packages/html5-tokenizer/tokenizer.js @@ -0,0 +1,959 @@ +require('../core-upgrade'); +var HTML5 = require('../html5'); +var events = require('events'); +var Buffer = require('./buffer').Buffer; +var Models = HTML5.Models; + +function keys(h) { + var r = []; + for(var k in h) { + r.push(k); + } + return r; +} + +var ENTITY_KEYS = keys(HTML5.ENTITIES); + +var t = HTML5.Tokenizer = function HTML5Tokenizer(input, document, tree) { + var state; + var buffer = new Buffer(); + var escapeFlag = false; + var lastFourChars = ''; + var current_token = null; + var script_buffer = null; + var content_model = Models.PCDATA; + var source; + + function data_state(buffer) { + var c = buffer.char(); + if (c !== HTML5.EOF && (content_model == Models.CDATA || content_model == Models.RCDATA || content_model == Models.SCRIPT_CDATA)) { + lastFourChars += c; + if (lastFourChars.length >= 4) { + lastFourChars = lastFourChars.substr(-4); + } + } + + if (content_model == Models.SCRIPT_CDATA) { + if (script_buffer === null) { + script_buffer = ''; + } + } + + if (c === HTML5.EOF) { + emitToken(HTML5.EOF_TOK); + buffer.commit(); + return false; + } else if (c === '\0' && (content_model == Models.SCRIPT_CDATA || content_model == Models.PLAINTEXT || content_model == Models.RAWTEXT || content_model == Models.RCDATA)) { + emitToken({type: 'Characters', data: "\ufffd"}); + buffer.commit(); + } else if (c == '&' && (content_model == Models.PCDATA || content_model == Models.RCDATA) && !escapeFlag) { + newState(entity_data_state); + } else if (c == '-' && (content_model == Models.CDATA || content_model == Models.RCDATA || content_model == Models.SCRIPT_CDATA) && !escapeFlag && lastFourChars == '$/)) { + escapeFlag = false; + emitToken({type: 'Characters', data: c}); + buffer.commit(); + } else if (HTML5.SPACE_CHARACTERS_R.test(c)) { + emitToken({type: 'SpaceCharacters', data: c + buffer.matchWhile(HTML5.SPACE_CHARACTERS)}); + buffer.commit(); + } else { + var o = buffer.matchUntil("[&<>-]"); + if (o !== HTML5.EOF) { + c = c + o; + } + emitToken({type: 'Characters', data: c}); + lastFourChars += c; + lastFourChars = lastFourChars.slice(-4); + buffer.commit(); + } + return true; + } + + var entity_data_state = function entity_data_state(buffer) { + var entity = consume_entity(buffer); + if (entity) { + emitToken({type: 'Characters', data: entity}); + } else { + emitToken({type: 'Characters', data: '&'}); + } + newState(data_state); + return true; + }; + + this.tokenize = function() { + if (this.pump) this.pump(); + }; + + var emitToken = function emitToken(tok) { + tok = normalize_token(tok); + if (content_model == Models.SCRIPT_CDATA && (tok.type == 'Characters' || tok.type == 'SpaceCharacters') && !buffer.eof) { + HTML5.debug('tokenizer.addScriptData', tok); + script_buffer += tok.data; + } else { + HTML5.debug('tokenizer.token', tok); + this.emit('token', tok); + } + }.bind(this); + + function consume_entity(buffer, from_attr) { + var char = null; + var chars = buffer.char(); + var c; + if (chars === HTML5.EOF) return false; + if (chars.match(HTML5.SPACE_CHARACTERS) || chars == '<' || chars == '&') { + buffer.unget(chars); + } else if (chars[0] == '#') { // Maybe a numeric entity + c = buffer.shift(2); + if (c === HTML5.EOF) { + buffer.unget(chars); + return false; + } + chars += c; + if (chars[1] && chars[1].toLowerCase() == 'x' && HTML5.HEX_DIGITS_R.test(chars[2])) { + // Hex entity + buffer.unget(chars[2]); + char = consume_numeric_entity(buffer, true); + } else if (chars[1] && HTML5.DIGITS_R.test(chars[1])) { + // Decimal entity + buffer.unget(chars.slice(1)); + char = consume_numeric_entity(buffer, false); + } else { + // Not numeric + buffer.unget(chars); + parse_error("expected-numeric-entity"); + } + } else { + var filteredEntityList = ENTITY_KEYS.filter(function(e) { + return e[0] == chars[0]; + }); + var entityName = null; + var matches = function(e) { + return e.indexOf(chars) === 0; + }; + while(true) { + if (filteredEntityList.some(matches)) { + filteredEntityList = filteredEntityList.filter(matches); + c = buffer.char(); + if (c !== HTML5.EOF) { + chars += c; + } else { + break; + } + } else { + break; + } + + if (HTML5.ENTITIES[chars]) { + entityName = chars; + if (entityName[entityName.length - 1] == ';') break; + } + } + + if (entityName) { + char = HTML5.ENTITIES[entityName]; + + if (entityName[entityName.length - 1] != ';' && this.from_attribute && (HTML5.ASCII_LETTERS_R.test(chars.substr(entityName.length, 1) || HTML5.DIGITS.test(chars.substr(entityName.length, 1))))) { + buffer.unget(chars); + char = '&'; + } else { + buffer.unget(chars.slice(entityName.length)); + } + } else { + parse_error("expected-named-entity"); + buffer.unget(chars); + } + } + + return char; + } + + function replaceEntityNumbers(c) { + switch(c) { + case 0x00: return 0xFFFD; // REPLACEMENT CHARACTER + case 0x13: return 0x0010; // Carriage return + case 0x80: return 0x20AC; // EURO SIGN + case 0x81: return 0x0081; // + case 0x82: return 0x201A; // SINGLE LOW-9 QUOTATION MARK + case 0x83: return 0x0192; // LATIN SMALL LETTER F WITH HOOK + case 0x84: return 0x201E; // DOUBLE LOW-9 QUOTATION MARK + case 0x85: return 0x2026; // HORIZONTAL ELLIPSIS + case 0x86: return 0x2020; // DAGGER + case 0x87: return 0x2021; // DOUBLE DAGGER + case 0x88: return 0x02C6; // MODIFIER LETTER CIRCUMFLEX ACCENT + case 0x89: return 0x2030; // PER MILLE SIGN + case 0x8A: return 0x0160; // LATIN CAPITAL LETTER S WITH CARON + case 0x8B: return 0x2039; // SINGLE LEFT-POINTING ANGLE QUOTATION MARK + case 0x8C: return 0x0152; // LATIN CAPITAL LIGATURE OE + case 0x8D: return 0x008D; // + case 0x8E: return 0x017D; // LATIN CAPITAL LETTER Z WITH CARON + case 0x8F: return 0x008F; // + case 0x90: return 0x0090; // + case 0x91: return 0x2018; // LEFT SINGLE QUOTATION MARK + case 0x92: return 0x2019; // RIGHT SINGLE QUOTATION MARK + case 0x93: return 0x201C; // LEFT DOUBLE QUOTATION MARK + case 0x94: return 0x201D; // RIGHT DOUBLE QUOTATION MARK + case 0x95: return 0x2022; // BULLET + case 0x96: return 0x2013; // EN DASH + case 0x97: return 0x2014; // EM DASH + case 0x98: return 0x02DC; // SMALL TILDE + case 0x99: return 0x2122; // TRADE MARK SIGN + case 0x9A: return 0x0161; // LATIN SMALL LETTER S WITH CARON + case 0x9B: return 0x203A; // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK + case 0x9C: return 0x0153; // LATIN SMALL LIGATURE OE + case 0x9D: return 0x009D; // + case 0x9E: return 0x017E; // LATIN SMALL LETTER Z WITH CARON + case 0x9F: return 0x0178; // LATIN CAPITAL LETTER Y WITH DIAERESIS + default: + if ((c >= 0xD800 && c <= 0xDFFF) || c >= 0x10FFFF) { /// @todo. The spec says > 0x10FFFF, not >=. Section 8.2.4.69. + return 0xFFFD; + } else if ((c >= 0x0001 && c <= 0x0008) || (c >= 0x000E && c <= 0x001F) || + (c >= 0x007F && c <= 0x009F) || (c >= 0xFDD0 && c <= 0xFDEF) || + c == 0x000B || c == 0xFFFE || c == 0x1FFFE || c == 0x2FFFFE || + c == 0x2FFFF || c == 0x3FFFE || c == 0x3FFFF || c == 0x4FFFE || + c == 0x4FFFF || c == 0x5FFFE || c == 0x5FFFF || c == 0x6FFFE || + c == 0x6FFFF || c == 0x7FFFE || c == 0x7FFFF || c == 0x8FFFE || + c == 0x8FFFF || c == 0x9FFFE || c == 0x9FFFF || c == 0xAFFFE || + c == 0xAFFFF || c == 0xBFFFE || c == 0xBFFFF || c == 0xCFFFE || + c == 0xCFFFF || c == 0xDFFFE || c == 0xDFFFF || c == 0xEFFFE || + c == 0xEFFFF || c == 0xFFFFE || c == 0xFFFFF || c == 0x10FFFE || + c == 0x10FFFF) { + return c; + } + } + } + + function consume_numeric_entity(buffer, hex) { + var allowed, radix; + if (hex) { + allowed = HTML5.HEX_DIGITS_R; + radix = 16; + } else { + allowed = HTML5.DIGITS_R; + radix = 10; + } + + var chars = ''; + + var c = buffer.char(); + while(c !== HTML5.EOF && allowed.test(c)) { + chars = chars + c; + c = buffer.char(); + } + + var charAsInt = parseInt(chars, radix); + + var replacement = replaceEntityNumbers(charAsInt); + if (replacement) { + parse_error("invalid-numeric-entity-replaced", {old: charAsInt, 'new': replacement}); + charAsInt = replacement; + } + + var char = String.fromCharCode(charAsInt); + /*if (charAsInt <= 0x10FFFF && !(charAsInt >= 0xD800 && charAsInt <= 0xDFFF)) { + } else { + char = String.fromCharCode(0xFFFD); + parse_error("cant-convert-numeric-entity"); + } */ + + if (c !== ';') { + parse_error("numeric-entity-without-semicolon"); + buffer.unget(c); + } + + return char; + } + + function process_entity_in_attribute(buffer) { + var entity = consume_entity(buffer); + if (entity) { + current_token.data.last().nodeValue += entity; + } else { + current_token.data.last().nodeValue += '&'; + } + } + + function process_solidus_in_tag(buffer) { + var data = buffer.peek(1); + if (current_token.type == 'StartTag' && data == '>') { + current_token.type = 'EmptyTag'; + return true; + } else { + parse_error("incorrectly-placed-solidus"); + return false; + } + } + + function tag_open_state(buffer) { + var data = buffer.char(); + if (content_model == Models.PCDATA) { + if (data === HTML5.EOF) { + parse_error("bare-less-than-sign-at-eof"); + emitToken({type: 'Characters', data: '<'}); + newState(data_state); + } else if (data !== HTML5.EOF && HTML5.ASCII_LETTERS_R.test(data)) { + current_token = {type: 'StartTag', name: data, data: []}; + newState(tag_name_state); + } else if (data == '!') { + newState(markup_declaration_open_state); + } else if (data == '/') { + newState(close_tag_open_state); + } else if (data == '>') { + // XXX In theory it could be something besides a tag name. But + // do we really care? + parse_error("expected-tag-name-but-got-right-bracket"); + emitToken({type: 'Characters', data: "<>"}); + newState(data_state); + } else if (data == '?') { + // XXX In theory it could be something besides a tag name. But + // do we really care? + parse_error("expected-tag-name-but-got-question-mark"); + buffer.unget(data); + newState(bogus_comment_state); + } else { + // XXX + parse_error("expected-tag-name"); + emitToken({type: 'Characters', data: "<"}); + buffer.unget(data); + newState(data_state); + } + } else { + // We know the content model flag is set to either RCDATA or CDATA or SCRIPT_CDATA + // now because this state can never be entered with the PLAINTEXT + // flag. + if (data === '/') { + newState(close_tag_open_state); + } else { + emitToken({type: 'Characters', data: "<"}); + buffer.unget(data); + newState(data_state); + } + } + return true; + } + + function close_tag_open_state(buffer) { + if (content_model == Models.RCDATA || content_model == Models.CDATA || content_model == Models.SCRIPT_CDATA) { + var chars = ''; + if (current_token) { + for(var i = 0; i <= current_token.name.length; i++) { + var c = buffer.char(); + if (c === HTML5.EOF) break; + chars += c; + } + buffer.unget(chars); + } + + if (current_token && + current_token.name.toLowerCase() == chars.slice(0, current_token.name.length).toLowerCase() && + (chars.length > current_token.name.length ? new RegExp('[' + HTML5.SPACE_CHARACTERS_IN + '>') { + parse_error("expected-closing-tag-but-got-right-bracket"); + newState(data_state); + } else { + parse_error("expected-closing-tag-but-got-char", {data: data}); // param 1 is datavars: + buffer.unget(data); + newState(bogus_comment_state); + } + return true; + } + + function tag_name_state(buffer) { + var data = buffer.char(); + if (data === HTML5.EOF) { + parse_error('eof-in-tag-name'); + emit_current_token(); + } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { + newState(before_attribute_name_state); + } else if (HTML5.ASCII_LETTERS_R.test(data)) { + var c = buffer.matchWhile(HTML5.ASCII_LETTERS); + if (c !== HTML5.EOF) { + current_token.name += data + c; + } else { + current_token.name += data; + buffer.unget(c); + newState(data_state); + } + } else if (data == '>') { + emit_current_token(); + } else if (data == '/') { + process_solidus_in_tag(buffer); + newState(self_closing_tag_state); + } else { + current_token.name += data; + } + buffer.commit(); + + return true; + } + + function before_attribute_name_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error("expected-attribute-name-but-got-eof"); + emit_current_token(); + } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { + buffer.matchWhile(HTML5.SPACE_CHARACTERS); + } else if (HTML5.ASCII_LETTERS_R.test(data)) { + current_token.data.push({nodeName: data, nodeValue: ""}); + newState(attribute_name_state); + } else if (data == '>') { + emit_current_token(); + } else if (data == '/') { + newState(self_closing_tag_state); + } else if (data == "'" || data == '"' || data == '=') { + parse_error("invalid-character-in-attribute-name"); + current_token.data.push({nodeName: data, nodeValue: ""}); + newState(attribute_name_state); + } else { + current_token.data.push({nodeName: data, nodeValue: ""}); + newState(attribute_name_state); + } + return true; + } + + function attribute_name_state(buffer) { + var data = buffer.shift(1); + var leavingThisState = true; + var emitToken = false; + if (data === HTML5.EOF) { + parse_error("eof-in-attribute-name"); + newState(data_state); + emitToken = true; + } else if (data == '=') { + newState(before_attribute_value_state); + } else if (HTML5.ASCII_LETTERS_R.test(data)) { + current_token.data.last().nodeName += data + buffer.matchWhile(HTML5.ASCII_LETTERS); + leavingThisState = false; + } else if (data == '>') { + // XXX If we emit here the attributes are converted to a dict + // without being checked and when the code below runs we error + // because data is a dict not a list + emitToken = true; + } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { + newState(after_attribute_name_state); + } else if (data == '/') { + if (!process_solidus_in_tag(buffer)) { + newState(before_attribute_name_state); + } + } else if (data == "'" || data == '"') { + parse_error("invalid-character-in-attribute-name"); + current_token.data.last().nodeName += data; + leavingThisState = false; + } else { + current_token.data.last().nodeName += data; + leavingThisState = false; + } + + if (leavingThisState) { + // Attributes are not dropped at this stage. That happens when the + // start tag token is emitted so values can still be safely appended + // to attributes, but we do want to report the parse error in time. + if (this.lowercase_attr_name) { + current_token.data.last().nodeName = current_token.data.last().nodeName.toLowerCase(); + } + for (var k in current_token.data.slice(0, -1)) { + // FIXME this is a fucking mess. + if (current_token.data.slice(-1)[0] == current_token.data.slice(0, -1)[k].name) { + parse_error("duplicate-attribute"); + break; // Don't emit more than one of these errors + } + } + if (emitToken) emit_current_token(); + } else { + buffer.commit(); + } + return true; + } + + function after_attribute_name_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error("expected-end-of-tag-but-got-eof"); + emit_current_token(); + } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { + buffer.matchWhile(HTML5.SPACE_CHARACTERS); + } else if (data == '=') { + newState(before_attribute_value_state); + } else if (data == '>') { + emit_current_token(); + } else if (HTML5.ASCII_LETTERS_R.test(data)) { + current_token.data.push({nodeName: data, nodeValue: ""}); + newState(attribute_name_state); + } else if (data == '/') { + newState(self_closing_tag_state); + } else { + current_token.data.push({nodeName: data, nodeValue: ""}); + newState(attribute_name_state); + } + return true; + } + + function before_attribute_value_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error("expected-attribute-value-but-got-eof"); + emit_current_token(); + newState(attribute_value_unquoted_state); + } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { + buffer.matchWhile(HTML5.SPACE_CHARACTERS); + } else if (data == '"') { + newState(attribute_value_double_quoted_state); + } else if (data == '&') { + newState(attribute_value_unquoted_state); + buffer.unget(data); + } else if (data == "'") { + newState(attribute_value_single_quoted_state); + } else if (data == '>') { + emit_current_token(); + } else if (data == '=') { + parse_error("equals-in-unquoted-attribute-value"); + current_token.data.last().nodeValue += data; + newState(attribute_value_unquoted_state); + } else { + current_token.data.last().nodeValue += data; + newState(attribute_value_unquoted_state); + } + + return true; + } + + function attribute_value_double_quoted_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error("eof-in-attribute-value-double-quote"); + newState(data_state); + } else if (data == '"') { + newState(after_attribute_value_state); + } else if (data == '&') { + process_entity_in_attribute(buffer); + } else { + var s = buffer.matchUntil('["&]'); + if (s !== HTML5.EOF) data = data + s; + current_token.data.last().nodeValue += data; + } + return true; + } + + function attribute_value_single_quoted_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error("eof-in-attribute-value-single-quote"); + emit_current_token(); + } else if (data == "'") { + newState(after_attribute_value_state); + } else if (data == '&') { + process_entity_in_attribute(buffer); + } else { + current_token.data.last().nodeValue += data + buffer.matchUntil("['&]"); + } + return true; + } + + function attribute_value_unquoted_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error("eof-in-attribute-value-no-quotes"); + buffer.commit(); + emit_current_token(); + } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { + newState(before_attribute_name_state); + } else if (data == '&') { + process_entity_in_attribute(buffer); + } else if (data == '>') { + emit_current_token(); + } else if (data == '"' || data == "'" || data == '=') { + parse_error("unexpected-character-in-unquoted-attribute-value"); + current_token.data.last().nodeValue += data; + } else { + var o = buffer.matchUntil("["+ HTML5.SPACE_CHARACTERS_IN + '&<>' +"]"); + if (o === HTML5.EOF) { + parse_error("eof-in-attribute-value-no-quotes"); + emit_current_token(); + } + // Commit here since this state is re-enterable and its outcome won't change with more data. + buffer.commit(); + current_token.data.last().nodeValue += data + o; + } + return true; + } + + function after_attribute_value_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error( "unexpected-EOF-after-attribute-value"); + emit_current_token(); + buffer.unget(data); + newState(data_state); + } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { + newState(before_attribute_name_state); + } else if (data == '>') { + emit_current_token(); + newState(data_state); + } else if (data == '/') { + newState(self_closing_tag_state); + } else { + emitToken({type: 'ParseError', data: "unexpected-character-after-attribute-value"}); + buffer.unget(data); + newState(before_attribute_name_state); + } + return true; + } + + function self_closing_tag_state(buffer) { + var c = buffer.shift(1); + if (c === HTML5.EOF) { + parse_error("eof-in-tag-name"); + buffer.unget(c); + newState(data_state); + } else if (c == '>') { + current_token.self_closing = true; + emit_current_token(); + newState(data_state); + } else { + parse_error("expected-self-closing-tag"); + buffer.unget(c); + newState(before_attribute_name_state); + } + return true; + } + + function bogus_comment_state(buffer) { + var s = buffer.matchUntil('>'); + if (s === HTML5.EOF) { + s = ''; + } + var tok = {type: 'Comment', data: s}; + buffer.char(); + emitToken(tok); + newState(data_state); + return true; + } + + function markup_declaration_open_state(buffer) { + var chars = buffer.shift(2); + if (chars === '--') { + current_token = {type: 'Comment', data: ''}; + newState(comment_start_state); + } else { + var newchars = buffer.shift(5); + if (newchars === HTML5.EOF || chars === HTML5.EOF) { + parse_error("expected-dashes-or-doctype"); + newState(bogus_comment_state); + buffer.unget(chars); + return true; + } + + chars += newchars; + if (chars.toUpperCase() == 'DOCTYPE') { + current_token = {type: 'Doctype', name: '', publicId: null, systemId: null, correct: true}; + newState(doctype_state); + } else if (tree.open_elements.last() && tree.open_elements.last().namespace && chars == '[CDATA[') { + newState(cdata_section_state); + } else { + parse_error("expected-dashes-or-doctype"); + buffer.unget(chars); + newState(bogus_comment_state); + } + } + return true; + } + + function cdata_section_state(buffer) { + var data = buffer.matchUntil(/\]\]>/); + var slice; + if (/\]\]>$/.match(data)) { + slice = 4; + } else { + slice = 0; + } + + emitToken({type: 'Characters', data: data.slice(0, data.length - slice)}); + newState(data_state); + } + + function comment_start_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error("eof-in-comment"); + emitToken(current_token); + newState(data_state); + } else if (data == '-') { + newState(comment_start_dash_state); + } else if (data == '>') { + parse_error("incorrect comment"); + emitToken(current_token); + newState(data_state); + } else { + current_token.data += data + buffer.matchUntil('-'); + newState(comment_state); + } + return true; + } + + function comment_start_dash_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error("eof-in-comment"); + emitToken(current_token); + newState(data_state); + } else if (data == '-') { + newState(comment_end_state); + } else if (data == '>') { + parse_error("incorrect-comment"); + emitToken(current_token); + newState(data_state); + } else { + var s = buffer.matchUntil('-'); + if (s !== HTML5.EOF) data = data + s; + current_token.data += '-' + data; + newState(comment_state); + } + return true; + } + + function comment_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error("eof-in-comment"); + emitToken(current_token); + newState(data_state); + } else if (data == '-') { + newState(comment_end_dash_state); + } else { + current_token.data += data + buffer.matchUntil('-'); + } + return true; + } + + function comment_end_dash_state(buffer) { + var data = buffer.char(); + if (data === HTML5.EOF) { + parse_error("eof-in-comment-end-dash"); + emitToken(current_token); + newState(data_state); + } else if (data == '-') { + newState(comment_end_state); + } else { + current_token.data += '-' + data + buffer.matchUntil('-'); + // Consume the next character which is either a "-" or an :EOF as + // well so if there's a "-" directly after the "-" we go nicely to + // the "comment end state" without emitting a ParseError there. + buffer.char(); + } + return true; + } + + function comment_end_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error("eof-in-comment-double-dash"); + emitToken(current_token); + newState(data_state); + } else if (data == '>') { + emitToken(current_token); + newState(data_state); + } else if (data == '-') { + parse_error("unexpected-dash-after-double-dash-in-comment"); + current_token.data += data; + } else { + // XXX + parse_error("unexpected-char-in-comment"); + current_token.data += '--' + data; + newState(comment_state); + } + return true; + } + + function doctype_state(buffer) { + var data = buffer.shift(1); + if (HTML5.SPACE_CHARACTERS_R.test(data)) { + newState(before_doctype_name_state); + } else { + parse_error("need-space-after-doctype"); + buffer.unget(data); + newState(before_doctype_name_state); + } + return true; + } + + function before_doctype_name_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error("expected-doctype-name-but-got-eof"); + current_token.correct = false; + emit_current_token(); + newState(data_state); + } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { + } else if (data == '>') { + parse_error("expected-doctype-name-but-got-right-bracket"); + current_token.correct = false; + emit_current_token(); + newState(data_state); + } else { + current_token.name = data.toLowerCase(); + newState(doctype_name_state); + } + return true; + } + + function doctype_name_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + current_token.correct = false; + buffer.unget(data); + parse_error("eof-in-doctype"); + emit_current_token(); + newState(data_state); + } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { + newState(bogus_doctype_state); + } else if (data == '>') { + emit_current_token(); + newState(data_state); + } else { + current_token.name += data.toLowerCase(); + } + return true; + } + + function bogus_doctype_state(buffer) { + var data = buffer.shift(1); + current_token.correct = false; + if (data === HTML5.EOF) { + throw(new Error("Unimplemented!")); + } else if (data == '>') { + emit_current_token(); + newState(data_state); + } + return true; + } + + function parse_error(message, context) { + emitToken({type: 'ParseError', data: message}); + HTML5.debug('tokenizer.parseError', message, context); + } + + function emit_current_token() { + var tok = current_token; + switch(tok.type) { + case 'StartTag': + case 'EndTag': + case 'EmptyTag': + if (tok.type == 'EndTag' && tok.self_closing) { + parse_error('self-closing-end-tag'); + } + break; + } + if (current_token.name.toLowerCase() == "script" && tok.type == 'EndTag' && script_buffer) { + emitToken({ type: 'Characters', data: script_buffer }); + script_buffer = null; + } + emitToken(tok); + newState(data_state); + } + + function normalize_token(token) { + if (token.type == 'EmptyTag') { + if (HTML5.VOID_ELEMENTS.indexOf(token.name) == -1) { + parse_error('incorrectly-placed-solidus'); + } + token.type = 'StartTag'; + } + + if (token.type == 'StartTag') { + token.name = token.name.toLowerCase(); + if (token.data.length !== 0) { + var data = {}; + // the first value for each key wins + token.data.reverse(); + token.data.forEach(function(e) { + data[e.nodeName.toLowerCase()] = e.nodeValue; + }); + token.data = []; + for(var k in data) { + token.data.push({nodeName: k, nodeValue: data[k]}); + } + // restore original attribute order + token.data.reverse(); + } + } else if (token.type == 'EndTag') { + if (token.data.length !== 0) parse_error('attributes-in-end-tag'); + token.name = token.name.toLowerCase(); + } + + return token; + } + + if (typeof input === 'undefined') throw(new Error("No input given")); + this.document = document; + this.__defineSetter__('content_model', function(model) { + HTML5.debug('tokenizer.content_model=', model); + content_model = model; + }); + this.__defineGetter__('content_model', function() { + return content_model; + }); + function newState(newstate) { + HTML5.debug('tokenizer.state=', newstate.name); + state = newstate; + buffer.commit(); + } + + newState(data_state); + + if (input instanceof events.EventEmitter) { + source = input; + this.pump = null; + } else { + source = new events.EventEmitter(); + this.pump = function() { + source.emit('data', input); + source.emit('end'); + }; + } + + source.addListener('data', function(data) { + if (typeof data !== 'string') data = data.toString(); + buffer.append(data); + try { + while(state(buffer)); + } catch(e) { + if (e != HTML5.DRAIN) { + throw(e); + } else { + HTML5.debug('tokenizer.drain', 'Drain'); + buffer.undo(); + } + } + }); + source.addListener('end', function() { + buffer.eof = true; + while(state(buffer)); + this.emit('end'); + }.bind(this)); + +}; + +t.prototype = new events.EventEmitter(); diff --git a/packages/html5-tokenizer/tokenizer_tests.js b/packages/html5-tokenizer/tokenizer_tests.js new file mode 100644 index 0000000000..8641671374 --- /dev/null +++ b/packages/html5-tokenizer/tokenizer_tests.js @@ -0,0 +1,5 @@ +Tinytest.add("html5-tokenizer - basic", function (test) { + + + +}); \ No newline at end of file From 05cab0d66283437e4aae299ccb418733747a2981 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Tue, 16 Apr 2013 14:14:03 -0700 Subject: [PATCH 002/938] re-indent to two-space style --- packages/html5-tokenizer/constants.js | 1369 ++++---- packages/html5-tokenizer/entities.js | 4462 ++++++++++++------------- packages/html5-tokenizer/tokenizer.js | 1866 +++++------ 3 files changed, 3848 insertions(+), 3849 deletions(-) diff --git a/packages/html5-tokenizer/constants.js b/packages/html5-tokenizer/constants.js index 0165e05804..83fe69d654 100644 --- a/packages/html5-tokenizer/constants.js +++ b/packages/html5-tokenizer/constants.js @@ -1,24 +1,24 @@ var HTML5 = require('../html5'); HTML5.CONTENT_MODEL_FLAGS = [ - 'PCDATA', - 'RCDATA', - 'CDATA', - 'SCRIPT_CDATA', - 'PLAINTEXT' + 'PCDATA', + 'RCDATA', + 'CDATA', + 'SCRIPT_CDATA', + 'PLAINTEXT' ]; HTML5.Marker = {type: 'Marker', data: 'this is a marker token'}; (function() { - function EOF() { - } + function EOF() { + } - EOF.prototype = { - toString: function() { throw new Error("EOF added as string"); } - }; - HTML5.EOF = new EOF(); + EOF.prototype = { + toString: function() { throw new Error("EOF added as string"); } + }; + HTML5.EOF = new EOF(); })(); @@ -26,118 +26,118 @@ HTML5.EOF_TOK = {type: 'EOF', data: 'End of File' }; HTML5.DRAIN = -2; HTML5.SCOPING_ELEMENTS = [ - 'applet', 'caption', 'html', 'table', 'td', 'th', - 'marquee', 'object', 'math:mi', 'math:mo', 'math:mn', 'math:ms', 'math:mtext', - 'math:annotation-xml', 'svg:foreignObject', 'svg:desc', 'svg:title' + 'applet', 'caption', 'html', 'table', 'td', 'th', + 'marquee', 'object', 'math:mi', 'math:mo', 'math:mn', 'math:ms', 'math:mtext', + 'math:annotation-xml', 'svg:foreignObject', 'svg:desc', 'svg:title' ]; HTML5.LIST_SCOPING_ELEMENTS = [ - 'ol', 'ul', - 'applet', 'caption', 'html', 'table', 'td', 'th', - 'marquee', 'object', 'math:mi', 'math:mo', 'math:mn', 'math:ms', 'math:mtext', - 'math:annotation-xml', 'svg:foreignObject', 'svg:desc', 'svg:title' + 'ol', 'ul', + 'applet', 'caption', 'html', 'table', 'td', 'th', + 'marquee', 'object', 'math:mi', 'math:mo', 'math:mn', 'math:ms', 'math:mtext', + 'math:annotation-xml', 'svg:foreignObject', 'svg:desc', 'svg:title' ]; HTML5.BUTTON_SCOPING_ELEMENTS = [ - 'button', - 'applet', 'caption', 'html', 'table', 'td', 'th', - 'marquee', 'object', 'math:mi', 'math:mo', 'math:mn', 'math:ms', 'math:mtext', - 'math:annotation-xml', 'svg:foreignObject', 'svg:desc', 'svg:title' + 'button', + 'applet', 'caption', 'html', 'table', 'td', 'th', + 'marquee', 'object', 'math:mi', 'math:mo', 'math:mn', 'math:ms', 'math:mtext', + 'math:annotation-xml', 'svg:foreignObject', 'svg:desc', 'svg:title' ]; HTML5.TABLE_SCOPING_ELEMENTS = [ - 'table', 'html' + 'table', 'html' ]; HTML5.SELECT_SCOPING_ELEMENTS = [ - 'option', 'optgroup' + 'option', 'optgroup' ]; HTML5.FORMATTING_ELEMENTS = [ - 'a', - 'b', - 'big', - 'code', - 'em', - 'font', - 'i', - 'nobr', - 's', - 'small', - 'strike', - 'strong', - 'tt', - 'u' + 'a', + 'b', + 'big', + 'code', + 'em', + 'font', + 'i', + 'nobr', + 's', + 'small', + 'strike', + 'strong', + 'tt', + 'u' ]; HTML5.SPECIAL_ELEMENTS = [ - 'address', - 'area', - 'base', - 'basefont', - 'bgsound', - 'blockquote', - 'body', - 'br', - 'center', - 'col', - 'colgroup', - 'dd', - 'dir', - 'div', - 'dl', - 'dt', - 'embed', - 'fieldset', - 'form', - 'frame', - 'frameset', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'head', - 'hr', - 'iframe', - 'image', - 'img', - 'input', - 'isindex', - 'li', - 'link', - 'listing', - 'menu', - 'meta', - 'noembed', - 'noframes', - 'noscript', - 'ol', - 'optgroup', - 'option', - 'p', - 'param', - 'plaintext', - 'pre', - 'script', - 'select', - 'spacer', - 'style', - 'tbody', - 'textarea', - 'tfoot', - 'thead', - 'title', - 'tr', - 'ul', - 'wbr' + 'address', + 'area', + 'base', + 'basefont', + 'bgsound', + 'blockquote', + 'body', + 'br', + 'center', + 'col', + 'colgroup', + 'dd', + 'dir', + 'div', + 'dl', + 'dt', + 'embed', + 'fieldset', + 'form', + 'frame', + 'frameset', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'head', + 'hr', + 'iframe', + 'image', + 'img', + 'input', + 'isindex', + 'li', + 'link', + 'listing', + 'menu', + 'meta', + 'noembed', + 'noframes', + 'noscript', + 'ol', + 'optgroup', + 'option', + 'p', + 'param', + 'plaintext', + 'pre', + 'script', + 'select', + 'spacer', + 'style', + 'tbody', + 'textarea', + 'tfoot', + 'thead', + 'title', + 'tr', + 'ul', + 'wbr' ]; HTML5.SPACE_CHARACTERS_IN = "\t\n\x0B\x0C\x20\u0012\r"; HTML5.SPACE_CHARACTERS = "[\t\n\x0B\x0C\x20\r]"; HTML5.SPACE_CHARACTERS_R = /^[\t\n\x0B\x0C \r]/; HTML5.TABLE_INSERT_MODE_ELEMENTS = [ - 'table', - 'tbody', - 'tfoot', - 'thead', - 'tr' + 'table', + 'tbody', + 'tfoot', + 'thead', + 'tr' ]; HTML5.ASCII_LOWERCASE = 'abcdefghijklmnopqrstuvwxyz'; @@ -149,614 +149,613 @@ HTML5.DIGITS_R = new RegExp('^[0123456789]'); HTML5.HEX_DIGITS = HTML5.DIGITS + 'abcdefABCDEF'; HTML5.HEX_DIGITS_R = new RegExp('^[' + HTML5.DIGITS + 'abcdefABCDEF' +']' ); -// Heading elements need to be ordered +// Heading elements need to be ordered HTML5.HEADING_ELEMENTS = [ - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6' + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6' ]; HTML5.VOID_ELEMENTS = [ - 'base', - 'link', - 'meta', - 'hr', - 'br', - 'img', - 'embed', - 'param', - 'area', - 'col', - 'input' + 'base', + 'link', + 'meta', + 'hr', + 'br', + 'img', + 'embed', + 'param', + 'area', + 'col', + 'input' ]; HTML5.CDATA_ELEMENTS = [ - 'title', - 'textarea' + 'title', + 'textarea' ]; HTML5.RCDATA_ELEMENTS = [ - 'style', - 'script', - 'xmp', - 'iframe', - 'noembed', - 'noframes', - 'noscript' + 'style', + 'script', + 'xmp', + 'iframe', + 'noembed', + 'noframes', + 'noscript' ]; HTML5.BOOLEAN_ATTRIBUTES = { - '_global': ['irrelevant'], - // Fixme? - 'style': ['scoped'], - 'img': ['ismap'], - 'audio': ['autoplay', 'controls'], - 'video': ['autoplay', 'controls'], - 'script': ['defer', 'async'], - 'details': ['open'], - 'datagrid': ['multiple', 'disabled'], - 'command': ['hidden', 'disabled', 'checked', 'default'], - 'menu': ['autosubmit'], - 'fieldset': ['disabled', 'readonly'], - 'option': ['disabled', 'readonly', 'selected'], - 'optgroup': ['disabled', 'readonly'], - 'button': ['disabled', 'autofocus'], - 'input': ['disabled', 'readonly', 'required', 'autofocus', 'checked', 'ismap'], - 'select': ['disabled', 'readonly', 'autofocus', 'multiple'], - 'output': ['disabled', 'readonly'] + '_global': ['irrelevant'], + // Fixme? + 'style': ['scoped'], + 'img': ['ismap'], + 'audio': ['autoplay', 'controls'], + 'video': ['autoplay', 'controls'], + 'script': ['defer', 'async'], + 'details': ['open'], + 'datagrid': ['multiple', 'disabled'], + 'command': ['hidden', 'disabled', 'checked', 'default'], + 'menu': ['autosubmit'], + 'fieldset': ['disabled', 'readonly'], + 'option': ['disabled', 'readonly', 'selected'], + 'optgroup': ['disabled', 'readonly'], + 'button': ['disabled', 'autofocus'], + 'input': ['disabled', 'readonly', 'required', 'autofocus', 'checked', 'ismap'], + 'select': ['disabled', 'readonly', 'autofocus', 'multiple'], + 'output': ['disabled', 'readonly'] }; HTML5.ENTITIES = require('html5-entities'); HTML5.ENCODINGS = [ - 'ansi_x3.4-1968', - 'iso-ir-6', - 'ansi_x3.4-1986', - 'iso_646.irv:1991', - 'ascii', - 'iso646-us', - 'us-ascii', - 'us', - 'ibm367', - 'cp367', - 'csascii', - 'ks_c_5601-1987', - 'korean', - 'iso-2022-kr', - 'csiso2022kr', - 'euc-kr', - 'iso-2022-jp', - 'csiso2022jp', - 'iso-2022-jp-2', - '', - 'iso-ir-58', - 'chinese', - 'csiso58gb231280', - 'iso_8859-1:1987', - 'iso-ir-100', - 'iso_8859-1', - 'iso-8859-1', - 'latin1', - 'l1', - 'ibm819', - 'cp819', - 'csisolatin1', - 'iso_8859-2:1987', - 'iso-ir-101', - 'iso_8859-2', - 'iso-8859-2', - 'latin2', - 'l2', - 'csisolatin2', - 'iso_8859-3:1988', - 'iso-ir-109', - 'iso_8859-3', - 'iso-8859-3', - 'latin3', - 'l3', - 'csisolatin3', - 'iso_8859-4:1988', - 'iso-ir-110', - 'iso_8859-4', - 'iso-8859-4', - 'latin4', - 'l4', - 'csisolatin4', - 'iso_8859-6:1987', - 'iso-ir-127', - 'iso_8859-6', - 'iso-8859-6', - 'ecma-114', - 'asmo-708', - 'arabic', - 'csisolatinarabic', - 'iso_8859-7:1987', - 'iso-ir-126', - 'iso_8859-7', - 'iso-8859-7', - 'elot_928', - 'ecma-118', - 'greek', - 'greek8', - 'csisolatingreek', - 'iso_8859-8:1988', - 'iso-ir-138', - 'iso_8859-8', - 'iso-8859-8', - 'hebrew', - 'csisolatinhebrew', - 'iso_8859-5:1988', - 'iso-ir-144', - 'iso_8859-5', - 'iso-8859-5', - 'cyrillic', - 'csisolatincyrillic', - 'iso_8859-9:1989', - 'iso-ir-148', - 'iso_8859-9', - 'iso-8859-9', - 'latin5', - 'l5', - 'csisolatin5', - 'iso-8859-10', - 'iso-ir-157', - 'l6', - 'iso_8859-10:1992', - 'csisolatin6', - 'latin6', - 'hp-roman8', - 'roman8', - 'r8', - 'ibm037', - 'cp037', - 'csibm037', - 'ibm424', - 'cp424', - 'csibm424', - 'ibm437', - 'cp437', - '437', - 'cspc8codepage437', - 'ibm500', - 'cp500', - 'csibm500', - 'ibm775', - 'cp775', - 'cspc775baltic', - 'ibm850', - 'cp850', - '850', - 'cspc850multilingual', - 'ibm852', - 'cp852', - '852', - 'cspcp852', - 'ibm855', - 'cp855', - '855', - 'csibm855', - 'ibm857', - 'cp857', - '857', - 'csibm857', - 'ibm860', - 'cp860', - '860', - 'csibm860', - 'ibm861', - 'cp861', - '861', - 'cp-is', - 'csibm861', - 'ibm862', - 'cp862', - '862', - 'cspc862latinhebrew', - 'ibm863', - 'cp863', - '863', - 'csibm863', - 'ibm864', - 'cp864', - 'csibm864', - 'ibm865', - 'cp865', - '865', - 'csibm865', - 'ibm866', - 'cp866', - '866', - 'csibm866', - 'ibm869', - 'cp869', - '869', - 'cp-gr', - 'csibm869', - 'ibm1026', - 'cp1026', - 'csibm1026', - 'koi8-r', - 'cskoi8r', - 'koi8-u', - 'big5-hkscs', - 'ptcp154', - 'csptcp154', - 'pt154', - 'cp154', - 'utf-7', - 'utf-16be', - 'utf-16le', - 'utf-16', - 'utf-8', - 'iso-8859-13', - 'iso-8859-14', - 'iso-ir-199', - 'iso_8859-14:1998', - 'iso_8859-14', - 'latin8', - 'iso-celtic', - 'l8', - 'iso-8859-15', - 'iso_8859-15', - 'iso-8859-16', - 'iso-ir-226', - 'iso_8859-16:2001', - 'iso_8859-16', - 'latin10', - 'l10', - 'gbk', - 'cp936', - 'ms936', - 'gb18030', - 'shift_jis', - 'ms_kanji', - 'csshiftjis', - 'euc-jp', - 'gb2312', - 'big5', - 'csbig5', - 'windows-1250', - 'windows-1251', - 'windows-1252', - 'windows-1253', - 'windows-1254', - 'windows-1255', - 'windows-1256', - 'windows-1257', - 'windows-1258', - 'tis-620', - 'hz-gb-2312' + 'ansi_x3.4-1968', + 'iso-ir-6', + 'ansi_x3.4-1986', + 'iso_646.irv:1991', + 'ascii', + 'iso646-us', + 'us-ascii', + 'us', + 'ibm367', + 'cp367', + 'csascii', + 'ks_c_5601-1987', + 'korean', + 'iso-2022-kr', + 'csiso2022kr', + 'euc-kr', + 'iso-2022-jp', + 'csiso2022jp', + 'iso-2022-jp-2', + '', + 'iso-ir-58', + 'chinese', + 'csiso58gb231280', + 'iso_8859-1:1987', + 'iso-ir-100', + 'iso_8859-1', + 'iso-8859-1', + 'latin1', + 'l1', + 'ibm819', + 'cp819', + 'csisolatin1', + 'iso_8859-2:1987', + 'iso-ir-101', + 'iso_8859-2', + 'iso-8859-2', + 'latin2', + 'l2', + 'csisolatin2', + 'iso_8859-3:1988', + 'iso-ir-109', + 'iso_8859-3', + 'iso-8859-3', + 'latin3', + 'l3', + 'csisolatin3', + 'iso_8859-4:1988', + 'iso-ir-110', + 'iso_8859-4', + 'iso-8859-4', + 'latin4', + 'l4', + 'csisolatin4', + 'iso_8859-6:1987', + 'iso-ir-127', + 'iso_8859-6', + 'iso-8859-6', + 'ecma-114', + 'asmo-708', + 'arabic', + 'csisolatinarabic', + 'iso_8859-7:1987', + 'iso-ir-126', + 'iso_8859-7', + 'iso-8859-7', + 'elot_928', + 'ecma-118', + 'greek', + 'greek8', + 'csisolatingreek', + 'iso_8859-8:1988', + 'iso-ir-138', + 'iso_8859-8', + 'iso-8859-8', + 'hebrew', + 'csisolatinhebrew', + 'iso_8859-5:1988', + 'iso-ir-144', + 'iso_8859-5', + 'iso-8859-5', + 'cyrillic', + 'csisolatincyrillic', + 'iso_8859-9:1989', + 'iso-ir-148', + 'iso_8859-9', + 'iso-8859-9', + 'latin5', + 'l5', + 'csisolatin5', + 'iso-8859-10', + 'iso-ir-157', + 'l6', + 'iso_8859-10:1992', + 'csisolatin6', + 'latin6', + 'hp-roman8', + 'roman8', + 'r8', + 'ibm037', + 'cp037', + 'csibm037', + 'ibm424', + 'cp424', + 'csibm424', + 'ibm437', + 'cp437', + '437', + 'cspc8codepage437', + 'ibm500', + 'cp500', + 'csibm500', + 'ibm775', + 'cp775', + 'cspc775baltic', + 'ibm850', + 'cp850', + '850', + 'cspc850multilingual', + 'ibm852', + 'cp852', + '852', + 'cspcp852', + 'ibm855', + 'cp855', + '855', + 'csibm855', + 'ibm857', + 'cp857', + '857', + 'csibm857', + 'ibm860', + 'cp860', + '860', + 'csibm860', + 'ibm861', + 'cp861', + '861', + 'cp-is', + 'csibm861', + 'ibm862', + 'cp862', + '862', + 'cspc862latinhebrew', + 'ibm863', + 'cp863', + '863', + 'csibm863', + 'ibm864', + 'cp864', + 'csibm864', + 'ibm865', + 'cp865', + '865', + 'csibm865', + 'ibm866', + 'cp866', + '866', + 'csibm866', + 'ibm869', + 'cp869', + '869', + 'cp-gr', + 'csibm869', + 'ibm1026', + 'cp1026', + 'csibm1026', + 'koi8-r', + 'cskoi8r', + 'koi8-u', + 'big5-hkscs', + 'ptcp154', + 'csptcp154', + 'pt154', + 'cp154', + 'utf-7', + 'utf-16be', + 'utf-16le', + 'utf-16', + 'utf-8', + 'iso-8859-13', + 'iso-8859-14', + 'iso-ir-199', + 'iso_8859-14:1998', + 'iso_8859-14', + 'latin8', + 'iso-celtic', + 'l8', + 'iso-8859-15', + 'iso_8859-15', + 'iso-8859-16', + 'iso-ir-226', + 'iso_8859-16:2001', + 'iso_8859-16', + 'latin10', + 'l10', + 'gbk', + 'cp936', + 'ms936', + 'gb18030', + 'shift_jis', + 'ms_kanji', + 'csshiftjis', + 'euc-jp', + 'gb2312', + 'big5', + 'csbig5', + 'windows-1250', + 'windows-1251', + 'windows-1252', + 'windows-1253', + 'windows-1254', + 'windows-1255', + 'windows-1256', + 'windows-1257', + 'windows-1258', + 'tis-620', + 'hz-gb-2312' ]; HTML5.E = { - "null-character": - "Null character in input stream, replaced with U+FFFD.", - "incorrectly-placed-solidus": - "Solidus (/) incorrectly placed in tag.", - "incorrect-cr-newline-entity": - "Incorrect CR newline entity, replaced with LF.", - "illegal-windows-1252-entity": - "Entity used with illegal number (windows-1252 reference).", - "cant-convert-numeric-entity": - "Numeric entity couldn't be converted to character " + - "(codepoint U+%(charAsInt)08x).", - "illegal-codepoint-for-numeric-entity": - "Numeric entity represents an illegal codepoint=> " + - "U+%(charAsInt)08x.", - "numeric-entity-without-semicolon": - "Numeric entity didn't end with ';'.", - "expected-numeric-entity-but-got-eof": - "Numeric entity expected. Got end of file instead.", - "expected-numeric-entity": - "Numeric entity expected but none found.", - "named-entity-without-semicolon": - "Named entity didn't end with ';'.", - "expected-named-entity": - "Named entity expected. Got none.", - "attributes-in-end-tag": - "End tag contains unexpected attributes.", - "expected-tag-name-but-got-right-bracket": - "Expected tag name. Got '>' instead.", - "expected-tag-name-but-got-question-mark": - "Expected tag name. Got '?' instead. (HTML doesn't " + - "support processing instructions.)", - "expected-tag-name": - "Expected tag name. Got something else instead", - "expected-closing-tag-but-got-right-bracket": - "Expected closing tag. Got '>' instead. Ignoring ''.", - "expected-closing-tag-but-got-eof": - "Expected closing tag. Unexpected end of file.", - "expected-closing-tag-but-got-char": - "Expected closing tag. Unexpected character '%(data)' found.", - "eof-in-tag-name": - "Unexpected end of file in the tag name.", - "expected-attribute-name-but-got-eof": - "Unexpected end of file. Expected attribute name instead.", - "eof-in-attribute-name": - "Unexpected end of file in attribute name.", - "duplicate-attribute": - "Dropped duplicate attribute on tag.", - "expected-end-of-tag-name-but-got-eof": - "Unexpected end of file. Expected = or end of tag.", - "expected-attribute-value-but-got-eof": - "Unexpected end of file. Expected attribute value.", - "eof-in-attribute-value-double-quote": - "Unexpected end of file in attribute value (\").", - "eof-in-attribute-value-single-quote": - "Unexpected end of file in attribute value (').", - "eof-in-attribute-value-no-quotes": - "Unexpected end of file in attribute value.", - "expected-dashes-or-doctype": - "Expected '--' or 'DOCTYPE'. Not found.", - "incorrect-comment": - "Incorrect comment.", - "eof-in-comment": - "Unexpected end of file in comment.", - "eof-in-comment-end-dash": - "Unexpected end of file in comment (-)", - "unexpected-dash-after-double-dash-in-comment": - "Unexpected '-' after '--' found in comment.", - "eof-in-comment-double-dash": - "Unexpected end of file in comment (--).", - "unexpected-char-in-comment": - "Unexpected character in comment found.", - "need-space-after-doctype": - "No space after literal string 'DOCTYPE'.", - "expected-doctype-name-but-got-right-bracket": - "Unexpected > character. Expected DOCTYPE name.", - "expected-doctype-name-but-got-eof": - "Unexpected end of file. Expected DOCTYPE name.", - "eof-in-doctype-name": - "Unexpected end of file in DOCTYPE name.", - "eof-in-doctype": - "Unexpected end of file in DOCTYPE.", - "expected-space-or-right-bracket-in-doctype": - "Expected space or '>'. Got '%(data)'", - "unexpected-end-of-doctype": - "Unexpected end of DOCTYPE.", - "unexpected-char-in-doctype": - "Unexpected character in DOCTYPE.", - "eof-in-bogus-doctype": - "Unexpected end of file in bogus doctype.", - "eof-in-innerhtml": - "Unexpected EOF in inner html mode.", - "unexpected-doctype": - "Unexpected DOCTYPE. Ignored.", - "non-html-root": - "html needs to be the first start tag.", - "expected-doctype-but-got-eof": - "Unexpected End of file. Expected DOCTYPE.", - "unknown-doctype": - "Erroneous DOCTYPE.", - "expected-doctype-but-got-chars": - "Unexpected non-space characters. Expected DOCTYPE.", - "expected-doctype-but-got-start-tag": - "Unexpected start tag (%(name)). Expected DOCTYPE.", - "expected-doctype-but-got-end-tag": - "Unexpected end tag (%(name)). Expected DOCTYPE.", - "end-tag-after-implied-root": - "Unexpected end tag (%(name)) after the (implied) root element.", - "expected-named-closing-tag-but-got-eof": - "Unexpected end of file. Expected end tag (%(name)).", - "two-heads-are-not-better-than-one": - "Unexpected start tag head in existing head. Ignored.", - "unexpected-end-tag": - "Unexpected end tag (%(name)). Ignored.", - "unexpected-start-tag-out-of-my-head": - "Unexpected start tag (%(name)) that can be in head. Moved.", - "unexpected-start-tag": - "Unexpected start tag (%(name)).", - "missing-end-tag": - "Missing end tag (%(name)).", - "missing-end-tags": - "Missing end tags (%(name)).", - "unexpected-start-tag-implies-end-tag": - "Unexpected start tag (%(startName)) " + - "implies end tag (%(endName)).", - "unexpected-start-tag-treated-as": - "Unexpected start tag (%(originalName)). Treated as %(newName).", - "deprecated-tag": - "Unexpected start tag %(name). Don't use it!", - "unexpected-start-tag-ignored": - "Unexpected start tag %(name). Ignored.", - "expected-one-end-tag-but-got-another": - "Unexpected end tag (%(gotName). " + - "Missing end tag (%(expectedName)).", - "end-tag-too-early": - "End tag (%(name)) seen too early. Expected other end tag.", - "end-tag-too-early-named": - "Unexpected end tag (%(gotName)). Expected end tag (%(expectedName).", - "end-tag-too-early-ignored": - "End tag (%(name)) seen too early. Ignored.", - "adoption-agency-1.1": - "End tag (%(name) violates step 1, " + - "paragraph 1 of the adoption agency algorithm.", - "adoption-agency-1.2": - "End tag (%(name) violates step 1, " + - "paragraph 2 of the adoption agency algorithm.", - "adoption-agency-1.3": - "End tag (%(name) violates step 1, " + - "paragraph 3 of the adoption agency algorithm.", - "unexpected-end-tag-treated-as": - "Unexpected end tag (%(originalName)). Treated as %(newName).", - "no-end-tag": - "This element (%(name)) has no end tag.", - "unexpected-implied-end-tag-in-table": - "Unexpected implied end tag (%(name)) in the table phase.", - "unexpected-implied-end-tag-in-table-body": - "Unexpected implied end tag (%(name)) in the table body phase.", - "unexpected-char-implies-table-voodoo": - "Unexpected non-space characters in " + - "table context caused voodoo mode.", - "unpexted-hidden-input-in-table": - "Unexpected input with type hidden in table context.", - "unexpected-start-tag-implies-table-voodoo": - "Unexpected start tag (%(name)) in " + - "table context caused voodoo mode.", - "unexpected-end-tag-implies-table-voodoo": - "Unexpected end tag (%(name)) in " + - "table context caused voodoo mode.", - "unexpected-cell-in-table-body": - "Unexpected table cell start tag (%(name)) " + - "in the table body phase.", - "unexpected-cell-end-tag": - "Got table cell end tag (%(name)) " + - "while required end tags are missing.", - "unexpected-end-tag-in-table-body": - "Unexpected end tag (%(name)) in the table body phase. Ignored.", - "unexpected-implied-end-tag-in-table-row": - "Unexpected implied end tag (%(name)) in the table row phase.", - "unexpected-end-tag-in-table-row": - "Unexpected end tag (%(name)) in the table row phase. Ignored.", - "unexpected-select-in-select": - "Unexpected select start tag in the select phase " + - "treated as select end tag.", - "unexpected-input-in-select": - "Unexpected input start tag in the select phase.", - "unexpected-start-tag-in-select": - "Unexpected start tag token (%(name)) in the select phase. " + - "Ignored.", - "unexpected-end-tag-in-select": - "Unexpected end tag (%(name)) in the select phase. Ignored.", - "unexpected-table-element-start-tag-in-select-in-table": - "Unexpected table element start tag (%(name))s in the select in table phase.", - "unexpected-table-element-end-tag-in-select-in-table": - "Unexpected table element end tag (%(name))s in the select in table phase.", - "unexpected-char-after-body": - "Unexpected non-space characters in the after body phase.", - "unexpected-start-tag-after-body": - "Unexpected start tag token (%(name))" + - "in the after body phase.", - "unexpected-end-tag-after-body": - "Unexpected end tag token (%(name))" + - " in the after body phase.", - "unexpected-char-in-frameset": - "Unepxected characters in the frameset phase. Characters ignored.", - "unexpected-start-tag-in-frameset": - "Unexpected start tag token (%(name))" + - " in the frameset phase. Ignored.", - "unexpected-frameset-in-frameset-innerhtml": - "Unexpected end tag token (frameset " + - "in the frameset phase (innerHTML).", - "unexpected-end-tag-in-frameset": - "Unexpected end tag token (%(name))" + - " in the frameset phase. Ignored.", - "unexpected-char-after-frameset": - "Unexpected non-space characters in the " + - "after frameset phase. Ignored.", - "unexpected-start-tag-after-frameset": - "Unexpected start tag (%(name))" + - " in the after frameset phase. Ignored.", - "unexpected-end-tag-after-frameset": - "Unexpected end tag (%(name))" + - " in the after frameset phase. Ignored.", - "expected-eof-but-got-char": - "Unexpected non-space characters. Expected end of file.", - "expected-eof-but-got-start-tag": - "Unexpected start tag (%(name))" + - ". Expected end of file.", - "expected-eof-but-got-end-tag": - "Unexpected end tag (%(name))" + - ". Expected end of file.", - "unexpected-end-table-in-caption": - "Unexpected end table tag in caption. Generates implied end caption.", - "end-html-in-innerhtml": - "Unexpected html end tag in inner html mode.", - "expected-self-closing-tag": - "Expected a > after the /.", - "self-closing-end-tag": - "Self closing end tag.", - "eof-in-table": - "Unexpected end of file. Expected table content.", - "html-in-foreign-content": - "HTML start tag \"%(name)\" in a foreign namespace context.", - "unexpected-start-tag-in-table": - "Unexpected %(name). Expected table content." + "null-character": + "Null character in input stream, replaced with U+FFFD.", + "incorrectly-placed-solidus": + "Solidus (/) incorrectly placed in tag.", + "incorrect-cr-newline-entity": + "Incorrect CR newline entity, replaced with LF.", + "illegal-windows-1252-entity": + "Entity used with illegal number (windows-1252 reference).", + "cant-convert-numeric-entity": + "Numeric entity couldn't be converted to character " + + "(codepoint U+%(charAsInt)08x).", + "illegal-codepoint-for-numeric-entity": + "Numeric entity represents an illegal codepoint=> " + + "U+%(charAsInt)08x.", + "numeric-entity-without-semicolon": + "Numeric entity didn't end with ';'.", + "expected-numeric-entity-but-got-eof": + "Numeric entity expected. Got end of file instead.", + "expected-numeric-entity": + "Numeric entity expected but none found.", + "named-entity-without-semicolon": + "Named entity didn't end with ';'.", + "expected-named-entity": + "Named entity expected. Got none.", + "attributes-in-end-tag": + "End tag contains unexpected attributes.", + "expected-tag-name-but-got-right-bracket": + "Expected tag name. Got '>' instead.", + "expected-tag-name-but-got-question-mark": + "Expected tag name. Got '?' instead. (HTML doesn't " + + "support processing instructions.)", + "expected-tag-name": + "Expected tag name. Got something else instead", + "expected-closing-tag-but-got-right-bracket": + "Expected closing tag. Got '>' instead. Ignoring ''.", + "expected-closing-tag-but-got-eof": + "Expected closing tag. Unexpected end of file.", + "expected-closing-tag-but-got-char": + "Expected closing tag. Unexpected character '%(data)' found.", + "eof-in-tag-name": + "Unexpected end of file in the tag name.", + "expected-attribute-name-but-got-eof": + "Unexpected end of file. Expected attribute name instead.", + "eof-in-attribute-name": + "Unexpected end of file in attribute name.", + "duplicate-attribute": + "Dropped duplicate attribute on tag.", + "expected-end-of-tag-name-but-got-eof": + "Unexpected end of file. Expected = or end of tag.", + "expected-attribute-value-but-got-eof": + "Unexpected end of file. Expected attribute value.", + "eof-in-attribute-value-double-quote": + "Unexpected end of file in attribute value (\").", + "eof-in-attribute-value-single-quote": + "Unexpected end of file in attribute value (').", + "eof-in-attribute-value-no-quotes": + "Unexpected end of file in attribute value.", + "expected-dashes-or-doctype": + "Expected '--' or 'DOCTYPE'. Not found.", + "incorrect-comment": + "Incorrect comment.", + "eof-in-comment": + "Unexpected end of file in comment.", + "eof-in-comment-end-dash": + "Unexpected end of file in comment (-)", + "unexpected-dash-after-double-dash-in-comment": + "Unexpected '-' after '--' found in comment.", + "eof-in-comment-double-dash": + "Unexpected end of file in comment (--).", + "unexpected-char-in-comment": + "Unexpected character in comment found.", + "need-space-after-doctype": + "No space after literal string 'DOCTYPE'.", + "expected-doctype-name-but-got-right-bracket": + "Unexpected > character. Expected DOCTYPE name.", + "expected-doctype-name-but-got-eof": + "Unexpected end of file. Expected DOCTYPE name.", + "eof-in-doctype-name": + "Unexpected end of file in DOCTYPE name.", + "eof-in-doctype": + "Unexpected end of file in DOCTYPE.", + "expected-space-or-right-bracket-in-doctype": + "Expected space or '>'. Got '%(data)'", + "unexpected-end-of-doctype": + "Unexpected end of DOCTYPE.", + "unexpected-char-in-doctype": + "Unexpected character in DOCTYPE.", + "eof-in-bogus-doctype": + "Unexpected end of file in bogus doctype.", + "eof-in-innerhtml": + "Unexpected EOF in inner html mode.", + "unexpected-doctype": + "Unexpected DOCTYPE. Ignored.", + "non-html-root": + "html needs to be the first start tag.", + "expected-doctype-but-got-eof": + "Unexpected End of file. Expected DOCTYPE.", + "unknown-doctype": + "Erroneous DOCTYPE.", + "expected-doctype-but-got-chars": + "Unexpected non-space characters. Expected DOCTYPE.", + "expected-doctype-but-got-start-tag": + "Unexpected start tag (%(name)). Expected DOCTYPE.", + "expected-doctype-but-got-end-tag": + "Unexpected end tag (%(name)). Expected DOCTYPE.", + "end-tag-after-implied-root": + "Unexpected end tag (%(name)) after the (implied) root element.", + "expected-named-closing-tag-but-got-eof": + "Unexpected end of file. Expected end tag (%(name)).", + "two-heads-are-not-better-than-one": + "Unexpected start tag head in existing head. Ignored.", + "unexpected-end-tag": + "Unexpected end tag (%(name)). Ignored.", + "unexpected-start-tag-out-of-my-head": + "Unexpected start tag (%(name)) that can be in head. Moved.", + "unexpected-start-tag": + "Unexpected start tag (%(name)).", + "missing-end-tag": + "Missing end tag (%(name)).", + "missing-end-tags": + "Missing end tags (%(name)).", + "unexpected-start-tag-implies-end-tag": + "Unexpected start tag (%(startName)) " + + "implies end tag (%(endName)).", + "unexpected-start-tag-treated-as": + "Unexpected start tag (%(originalName)). Treated as %(newName).", + "deprecated-tag": + "Unexpected start tag %(name). Don't use it!", + "unexpected-start-tag-ignored": + "Unexpected start tag %(name). Ignored.", + "expected-one-end-tag-but-got-another": + "Unexpected end tag (%(gotName). " + + "Missing end tag (%(expectedName)).", + "end-tag-too-early": + "End tag (%(name)) seen too early. Expected other end tag.", + "end-tag-too-early-named": + "Unexpected end tag (%(gotName)). Expected end tag (%(expectedName).", + "end-tag-too-early-ignored": + "End tag (%(name)) seen too early. Ignored.", + "adoption-agency-1.1": + "End tag (%(name) violates step 1, " + + "paragraph 1 of the adoption agency algorithm.", + "adoption-agency-1.2": + "End tag (%(name) violates step 1, " + + "paragraph 2 of the adoption agency algorithm.", + "adoption-agency-1.3": + "End tag (%(name) violates step 1, " + + "paragraph 3 of the adoption agency algorithm.", + "unexpected-end-tag-treated-as": + "Unexpected end tag (%(originalName)). Treated as %(newName).", + "no-end-tag": + "This element (%(name)) has no end tag.", + "unexpected-implied-end-tag-in-table": + "Unexpected implied end tag (%(name)) in the table phase.", + "unexpected-implied-end-tag-in-table-body": + "Unexpected implied end tag (%(name)) in the table body phase.", + "unexpected-char-implies-table-voodoo": + "Unexpected non-space characters in " + + "table context caused voodoo mode.", + "unpexted-hidden-input-in-table": + "Unexpected input with type hidden in table context.", + "unexpected-start-tag-implies-table-voodoo": + "Unexpected start tag (%(name)) in " + + "table context caused voodoo mode.", + "unexpected-end-tag-implies-table-voodoo": + "Unexpected end tag (%(name)) in " + + "table context caused voodoo mode.", + "unexpected-cell-in-table-body": + "Unexpected table cell start tag (%(name)) " + + "in the table body phase.", + "unexpected-cell-end-tag": + "Got table cell end tag (%(name)) " + + "while required end tags are missing.", + "unexpected-end-tag-in-table-body": + "Unexpected end tag (%(name)) in the table body phase. Ignored.", + "unexpected-implied-end-tag-in-table-row": + "Unexpected implied end tag (%(name)) in the table row phase.", + "unexpected-end-tag-in-table-row": + "Unexpected end tag (%(name)) in the table row phase. Ignored.", + "unexpected-select-in-select": + "Unexpected select start tag in the select phase " + + "treated as select end tag.", + "unexpected-input-in-select": + "Unexpected input start tag in the select phase.", + "unexpected-start-tag-in-select": + "Unexpected start tag token (%(name)) in the select phase. " + + "Ignored.", + "unexpected-end-tag-in-select": + "Unexpected end tag (%(name)) in the select phase. Ignored.", + "unexpected-table-element-start-tag-in-select-in-table": + "Unexpected table element start tag (%(name))s in the select in table phase.", + "unexpected-table-element-end-tag-in-select-in-table": + "Unexpected table element end tag (%(name))s in the select in table phase.", + "unexpected-char-after-body": + "Unexpected non-space characters in the after body phase.", + "unexpected-start-tag-after-body": + "Unexpected start tag token (%(name))" + + "in the after body phase.", + "unexpected-end-tag-after-body": + "Unexpected end tag token (%(name))" + + " in the after body phase.", + "unexpected-char-in-frameset": + "Unepxected characters in the frameset phase. Characters ignored.", + "unexpected-start-tag-in-frameset": + "Unexpected start tag token (%(name))" + + " in the frameset phase. Ignored.", + "unexpected-frameset-in-frameset-innerhtml": + "Unexpected end tag token (frameset " + + "in the frameset phase (innerHTML).", + "unexpected-end-tag-in-frameset": + "Unexpected end tag token (%(name))" + + " in the frameset phase. Ignored.", + "unexpected-char-after-frameset": + "Unexpected non-space characters in the " + + "after frameset phase. Ignored.", + "unexpected-start-tag-after-frameset": + "Unexpected start tag (%(name))" + + " in the after frameset phase. Ignored.", + "unexpected-end-tag-after-frameset": + "Unexpected end tag (%(name))" + + " in the after frameset phase. Ignored.", + "expected-eof-but-got-char": + "Unexpected non-space characters. Expected end of file.", + "expected-eof-but-got-start-tag": + "Unexpected start tag (%(name))" + + ". Expected end of file.", + "expected-eof-but-got-end-tag": + "Unexpected end tag (%(name))" + + ". Expected end of file.", + "unexpected-end-table-in-caption": + "Unexpected end table tag in caption. Generates implied end caption.", + "end-html-in-innerhtml": + "Unexpected html end tag in inner html mode.", + "expected-self-closing-tag": + "Expected a > after the /.", + "self-closing-end-tag": + "Self closing end tag.", + "eof-in-table": + "Unexpected end of file. Expected table content.", + "html-in-foreign-content": + "HTML start tag \"%(name)\" in a foreign namespace context.", + "unexpected-start-tag-in-table": + "Unexpected %(name). Expected table content." }; HTML5.Models = {PCDATA: 'PCDATA', RCDATA: 'RCDATA', CDATA: 'CDATA', SCRIPT_CDATA: 'SCRIPT_CDATA'}; HTML5.TAGMODES = { - select: 'inSelect', - td: 'inCell', - th: 'inCell', - tr: 'inRow', - tbody: 'inTableBody', - thead: 'inTableBody', - tfoot: 'inTableBody', - caption: 'inCaption', - colgroup: 'inColumnGroup', - table: 'inTable', - head: 'inBody', - body: 'inBody', - frameset: 'inFrameset' + select: 'inSelect', + td: 'inCell', + th: 'inCell', + tr: 'inRow', + tbody: 'inTableBody', + thead: 'inTableBody', + tfoot: 'inTableBody', + caption: 'inCaption', + colgroup: 'inColumnGroup', + table: 'inTable', + head: 'inBody', + body: 'inBody', + frameset: 'inFrameset' }; HTML5.SVGAttributeMap = { - attributename: 'attributeName', - attributetype: 'attributeType', - basefrequency: 'baseFrequency', - baseprofile: 'baseProfile', - calcmode: 'calcMode', - clippathunits: 'clipPathUnits', - contentscripttype: 'contentScriptType', - contentstyletype: 'contentStyleType', - diffuseconstant: 'diffuseConstant', - edgemode: 'edgeMode', - externalresourcesrequired: 'externalResourcesRequired', - filterres: 'filterRes', - filterunits: 'filterUnits', - glyphref: 'glyphRef', - gradienttransform: 'gradientTransform', - gradientunits: 'gradientUnits', - kernelmatrix: 'kernelMatrix', - kernelunitlength: 'kernelUnitLength', - keypoints: 'keyPoints', - keysplines: 'keySplines', - keytimes: 'keyTimes', - lengthadjust: 'lengthAdjust', - limitingconeangle: 'limitingConeAngle', - markerheight: 'markerHeight', - markerunits: 'markerUnits', - markerwidth: 'markerWidth', - maskcontentunits: 'maskContentUnits', - maskunits: 'maskUnits', - numoctaves: 'numOctaves', - pathlength: 'pathLength', - patterncontentunits: 'patternContentUnits', - patterntransform: 'patternTransform', - patternunits: 'patternUnits', - pointsatx: 'pointsAtX', - pointsaty: 'pointsAtY', - pointsatz: 'pointsAtZ', - preservealpha: 'preserveAlpha', - preserveaspectratio: 'preserveAspectRatio', - primitiveunits: 'primitiveUnits', - refx: 'refX', - refy: 'refY', - repeatcount: 'repeatCount', - repeatdur: 'repeatDur', - requiredextensions: 'requiredExtensions', - requiredfeatures: 'requiredFeatures', - specularconstant: 'specularConstant', - specularexponent: 'specularExponent', - spreadmethod: 'spreadMethod', - startoffset: 'startOffset', - stddeviation: 'stdDeviation', - stitchtiles: 'stitchTiles', - surfacescale: 'surfaceScale', - systemlanguage: 'systemLanguage', - tablevalues: 'tableValues', - targetx: 'targetX', - targety: 'targetY', - textlength: 'textLength', - viewbox: 'viewBox', - viewtarget: 'viewTarget', - xchannelselector: 'xChannelSelector', - ychannelselector: 'yChannelSelector', - zoomandpan: 'zoomAndPan' + attributename: 'attributeName', + attributetype: 'attributeType', + basefrequency: 'baseFrequency', + baseprofile: 'baseProfile', + calcmode: 'calcMode', + clippathunits: 'clipPathUnits', + contentscripttype: 'contentScriptType', + contentstyletype: 'contentStyleType', + diffuseconstant: 'diffuseConstant', + edgemode: 'edgeMode', + externalresourcesrequired: 'externalResourcesRequired', + filterres: 'filterRes', + filterunits: 'filterUnits', + glyphref: 'glyphRef', + gradienttransform: 'gradientTransform', + gradientunits: 'gradientUnits', + kernelmatrix: 'kernelMatrix', + kernelunitlength: 'kernelUnitLength', + keypoints: 'keyPoints', + keysplines: 'keySplines', + keytimes: 'keyTimes', + lengthadjust: 'lengthAdjust', + limitingconeangle: 'limitingConeAngle', + markerheight: 'markerHeight', + markerunits: 'markerUnits', + markerwidth: 'markerWidth', + maskcontentunits: 'maskContentUnits', + maskunits: 'maskUnits', + numoctaves: 'numOctaves', + pathlength: 'pathLength', + patterncontentunits: 'patternContentUnits', + patterntransform: 'patternTransform', + patternunits: 'patternUnits', + pointsatx: 'pointsAtX', + pointsaty: 'pointsAtY', + pointsatz: 'pointsAtZ', + preservealpha: 'preserveAlpha', + preserveaspectratio: 'preserveAspectRatio', + primitiveunits: 'primitiveUnits', + refx: 'refX', + refy: 'refY', + repeatcount: 'repeatCount', + repeatdur: 'repeatDur', + requiredextensions: 'requiredExtensions', + requiredfeatures: 'requiredFeatures', + specularconstant: 'specularConstant', + specularexponent: 'specularExponent', + spreadmethod: 'spreadMethod', + startoffset: 'startOffset', + stddeviation: 'stdDeviation', + stitchtiles: 'stitchTiles', + surfacescale: 'surfaceScale', + systemlanguage: 'systemLanguage', + tablevalues: 'tableValues', + targetx: 'targetX', + targety: 'targetY', + textlength: 'textLength', + viewbox: 'viewBox', + viewtarget: 'viewTarget', + xchannelselector: 'xChannelSelector', + ychannelselector: 'yChannelSelector', + zoomandpan: 'zoomAndPan' }; - diff --git a/packages/html5-tokenizer/entities.js b/packages/html5-tokenizer/entities.js index 75cde451f4..5cff75bc35 100644 --- a/packages/html5-tokenizer/entities.js +++ b/packages/html5-tokenizer/entities.js @@ -1,2233 +1,2233 @@ module.exports = { - "AElig": "\u00C6", - "AElig;": "\u00C6", - "AMP": "&", - "AMP;": "&", - "Aacute": "\u00C1", - "Aacute;": "\u00C1", - "Abreve;": "\u0102", - "Acirc": "\u00C2", - "Acirc;": "\u00C2", - "Acy;": "\u0410", - "Afr;": "\u1D504", - "Agrave": "\u00C0", - "Agrave;": "\u00C0", - "Alpha;": "\u0391", - "Amacr;": "\u0100", - "And;": "\u2A53", - "Aogon;": "\u0104", - "Aopf;": "\u1D538", - "ApplyFunction;": "\u2061", - "Aring": "\u00C5", - "Aring;": "\u00C5", - "Ascr;": "\u1D49C", - "Assign;": "\u2254", - "Atilde": "\u00C3", - "Atilde;": "\u00C3", - "Auml": "\u00C4", - "Auml;": "\u00C4", - "Backslash;": "\u2216", - "Barv;": "\u2AE7", - "Barwed;": "\u2306", - "Bcy;": "\u0411", - "Because;": "\u2235", - "Bernoullis;": "\u212C", - "Beta;": "\u0392", - "Bfr;": "\u1D505", - "Bopf;": "\u1D539", - "Breve;": "\u02D8", - "Bscr;": "\u212C", - "Bumpeq;": "\u224E", - "CHcy;": "\u0427", - "COPY": "\u00A9", - "COPY;": "\u00A9", - "Cacute;": "\u0106", - "Cap;": "\u22D2", - "CapitalDifferentialD;": "\u2145", - "Cayleys;": "\u212D", - "Ccaron;": "\u010C", - "Ccedil": "\u00C7", - "Ccedil;": "\u00C7", - "Ccirc;": "\u0108", - "Cconint;": "\u2230", - "Cdot;": "\u010A", - "Cedilla;": "\u00B8", - "CenterDot;": "\u00B7", - "Cfr;": "\u212D", - "Chi;": "\u03A7", - "CircleDot;": "\u2299", - "CircleMinus;": "\u2296", - "CirclePlus;": "\u2295", - "CircleTimes;": "\u2297", - "ClockwiseContourIntegral;": "\u2232", - "CloseCurlyDoubleQuote;": "\u201D", - "CloseCurlyQuote;": "\u2019", - "Colon;": "\u2237", - "Colone;": "\u2A74", - "Congruent;": "\u2261", - "Conint;": "\u222F", - "ContourIntegral;": "\u222E", - "Copf;": "\u2102", - "Coproduct;": "\u2210", - "CounterClockwiseContourIntegral;": "\u2233", - "Cross;": "\u2A2F", - "Cscr;": "\u1D49E", - "Cup;": "\u22D3", - "CupCap;": "\u224D", - "DD;": "\u2145", - "DDotrahd;": "\u2911", - "DJcy;": "\u0402", - "DScy;": "\u0405", - "DZcy;": "\u040F", - "Dagger;": "\u2021", - "Darr;": "\u21A1", - "Dashv;": "\u2AE4", - "Dcaron;": "\u010E", - "Dcy;": "\u0414", - "Del;": "\u2207", - "Delta;": "\u0394", - "Dfr;": "\u1D507", - "DiacriticalAcute;": "\u00B4", - "DiacriticalDot;": "\u02D9", - "DiacriticalDoubleAcute;": "\u02DD", - "DiacriticalGrave;": "`", - "DiacriticalTilde;": "\u02DC", - "Diamond;": "\u22C4", - "DifferentialD;": "\u2146", - "Dopf;": "\u1D53B", - "Dot;": "\u00A8", - "DotDot;": "\u20DC", - "DotEqual;": "\u2250", - "DoubleContourIntegral;": "\u222F", - "DoubleDot;": "\u00A8", - "DoubleDownArrow;": "\u21D3", - "DoubleLeftArrow;": "\u21D0", - "DoubleLeftRightArrow;": "\u21D4", - "DoubleLeftTee;": "\u2AE4", - "DoubleLongLeftArrow;": "\u27F8", - "DoubleLongLeftRightArrow;": "\u27FA", - "DoubleLongRightArrow;": "\u27F9", - "DoubleRightArrow;": "\u21D2", - "DoubleRightTee;": "\u22A8", - "DoubleUpArrow;": "\u21D1", - "DoubleUpDownArrow;": "\u21D5", - "DoubleVerticalBar;": "\u2225", - "DownArrow;": "\u2193", - "DownArrowBar;": "\u2913", - "DownArrowUpArrow;": "\u21F5", - "DownBreve;": "\u0311", - "DownLeftRightVector;": "\u2950", - "DownLeftTeeVector;": "\u295E", - "DownLeftVector;": "\u21BD", - "DownLeftVectorBar;": "\u2956", - "DownRightTeeVector;": "\u295F", - "DownRightVector;": "\u21C1", - "DownRightVectorBar;": "\u2957", - "DownTee;": "\u22A4", - "DownTeeArrow;": "\u21A7", - "Downarrow;": "\u21D3", - "Dscr;": "\u1D49F", - "Dstrok;": "\u0110", - "ENG;": "\u014A", - "ETH": "\u00D0", - "ETH;": "\u00D0", - "Eacute": "\u00C9", - "Eacute;": "\u00C9", - "Ecaron;": "\u011A", - "Ecirc": "\u00CA", - "Ecirc;": "\u00CA", - "Ecy;": "\u042D", - "Edot;": "\u0116", - "Efr;": "\u1D508", - "Egrave": "\u00C8", - "Egrave;": "\u00C8", - "Element;": "\u2208", - "Emacr;": "\u0112", - "EmptySmallSquare;": "\u25FB", - "EmptyVerySmallSquare;": "\u25AB", - "Eogon;": "\u0118", - "Eopf;": "\u1D53C", - "Epsilon;": "\u0395", - "Equal;": "\u2A75", - "EqualTilde;": "\u2242", - "Equilibrium;": "\u21CC", - "Escr;": "\u2130", - "Esim;": "\u2A73", - "Eta;": "\u0397", - "Euml": "\u00CB", - "Euml;": "\u00CB", - "Exists;": "\u2203", - "ExponentialE;": "\u2147", - "Fcy;": "\u0424", - "Ffr;": "\u1D509", - "FilledSmallSquare;": "\u25FC", - "FilledVerySmallSquare;": "\u25AA", - "Fopf;": "\u1D53D", - "ForAll;": "\u2200", - "Fouriertrf;": "\u2131", - "Fscr;": "\u2131", - "GJcy;": "\u0403", - "GT": ">", - "GT;": ">", - "Gamma;": "\u0393", - "Gammad;": "\u03DC", - "Gbreve;": "\u011E", - "Gcedil;": "\u0122", - "Gcirc;": "\u011C", - "Gcy;": "\u0413", - "Gdot;": "\u0120", - "Gfr;": "\u1D50A", - "Gg;": "\u22D9", - "Gopf;": "\u1D53E", - "GreaterEqual;": "\u2265", - "GreaterEqualLess;": "\u22DB", - "GreaterFullEqual;": "\u2267", - "GreaterGreater;": "\u2AA2", - "GreaterLess;": "\u2277", - "GreaterSlantEqual;": "\u2A7E", - "GreaterTilde;": "\u2273", - "Gscr;": "\u1D4A2", - "Gt;": "\u226B", - "HARDcy;": "\u042A", - "Hacek;": "\u02C7", - "Hat;": "^", - "Hcirc;": "\u0124", - "Hfr;": "\u210C", - "HilbertSpace;": "\u210B", - "Hopf;": "\u210D", - "HorizontalLine;": "\u2500", - "Hscr;": "\u210B", - "Hstrok;": "\u0126", - "HumpDownHump;": "\u224E", - "HumpEqual;": "\u224F", - "IEcy;": "\u0415", - "IJlig;": "\u0132", - "IOcy;": "\u0401", - "Iacute": "\u00CD", - "Iacute;": "\u00CD", - "Icirc": "\u00CE", - "Icirc;": "\u00CE", - "Icy;": "\u0418", - "Idot;": "\u0130", - "Ifr;": "\u2111", - "Igrave": "\u00CC", - "Igrave;": "\u00CC", - "Im;": "\u2111", - "Imacr;": "\u012A", - "ImaginaryI;": "\u2148", - "Implies;": "\u21D2", - "Int;": "\u222C", - "Integral;": "\u222B", - "Intersection;": "\u22C2", - "InvisibleComma;": "\u2063", - "InvisibleTimes;": "\u2062", - "Iogon;": "\u012E", - "Iopf;": "\u1D540", - "Iota;": "\u0399", - "Iscr;": "\u2110", - "Itilde;": "\u0128", - "Iukcy;": "\u0406", - "Iuml": "\u00CF", - "Iuml;": "\u00CF", - "Jcirc;": "\u0134", - "Jcy;": "\u0419", - "Jfr;": "\u1D50D", - "Jopf;": "\u1D541", - "Jscr;": "\u1D4A5", - "Jsercy;": "\u0408", - "Jukcy;": "\u0404", - "KHcy;": "\u0425", - "KJcy;": "\u040C", - "Kappa;": "\u039A", - "Kcedil;": "\u0136", - "Kcy;": "\u041A", - "Kfr;": "\u1D50E", - "Kopf;": "\u1D542", - "Kscr;": "\u1D4A6", - "LJcy;": "\u0409", - "LT": "<", - "LT;": "<", - "Lacute;": "\u0139", - "Lambda;": "\u039B", - "Lang;": "\u27EA", - "Laplacetrf;": "\u2112", - "Larr;": "\u219E", - "Lcaron;": "\u013D", - "Lcedil;": "\u013B", - "Lcy;": "\u041B", - "LeftAngleBracket;": "\u27E8", - "LeftArrow;": "\u2190", - "LeftArrowBar;": "\u21E4", - "LeftArrowRightArrow;": "\u21C6", - "LeftCeiling;": "\u2308", - "LeftDoubleBracket;": "\u27E6", - "LeftDownTeeVector;": "\u2961", - "LeftDownVector;": "\u21C3", - "LeftDownVectorBar;": "\u2959", - "LeftFloor;": "\u230A", - "LeftRightArrow;": "\u2194", - "LeftRightVector;": "\u294E", - "LeftTee;": "\u22A3", - "LeftTeeArrow;": "\u21A4", - "LeftTeeVector;": "\u295A", - "LeftTriangle;": "\u22B2", - "LeftTriangleBar;": "\u29CF", - "LeftTriangleEqual;": "\u22B4", - "LeftUpDownVector;": "\u2951", - "LeftUpTeeVector;": "\u2960", - "LeftUpVector;": "\u21BF", - "LeftUpVectorBar;": "\u2958", - "LeftVector;": "\u21BC", - "LeftVectorBar;": "\u2952", - "Leftarrow;": "\u21D0", - "Leftrightarrow;": "\u21D4", - "LessEqualGreater;": "\u22DA", - "LessFullEqual;": "\u2266", - "LessGreater;": "\u2276", - "LessLess;": "\u2AA1", - "LessSlantEqual;": "\u2A7D", - "LessTilde;": "\u2272", - "Lfr;": "\u1D50F", - "Ll;": "\u22D8", - "Lleftarrow;": "\u21DA", - "Lmidot;": "\u013F", - "LongLeftArrow;": "\u27F5", - "LongLeftRightArrow;": "\u27F7", - "LongRightArrow;": "\u27F6", - "Longleftarrow;": "\u27F8", - "Longleftrightarrow;": "\u27FA", - "Longrightarrow;": "\u27F9", - "Lopf;": "\u1D543", - "LowerLeftArrow;": "\u2199", - "LowerRightArrow;": "\u2198", - "Lscr;": "\u2112", - "Lsh;": "\u21B0", - "Lstrok;": "\u0141", - "Lt;": "\u226A", - "Map;": "\u2905", - "Mcy;": "\u041C", - "MediumSpace;": "\u205F", - "Mellintrf;": "\u2133", - "Mfr;": "\u1D510", - "MinusPlus;": "\u2213", - "Mopf;": "\u1D544", - "Mscr;": "\u2133", - "Mu;": "\u039C", - "NJcy;": "\u040A", - "Nacute;": "\u0143", - "Ncaron;": "\u0147", - "Ncedil;": "\u0145", - "Ncy;": "\u041D", - "NegativeMediumSpace;": "\u200B", - "NegativeThickSpace;": "\u200B", - "NegativeThinSpace;": "\u200B", - "NegativeVeryThinSpace;": "\u200B", - "NestedGreaterGreater;": "\u226B", - "NestedLessLess;": "\u226A", - "NewLine;": "\u000A", - "Nfr;": "\u1D511", - "NoBreak;": "\u2060", - "NonBreakingSpace;": "\u00A0", - "Nopf;": "\u2115", - "Not;": "\u2AEC", - "NotCongruent;": "\u2262", - "NotCupCap;": "\u226D", - "NotDoubleVerticalBar;": "\u2226", - "NotElement;": "\u2209", - "NotEqual;": "\u2260", - "NotEqualTilde;": "\u2242\u0338", - "NotExists;": "\u2204", - "NotGreater;": "\u226F", - "NotGreaterEqual;": "\u2271", - "NotGreaterFullEqual;": "\u2267\u0338", - "NotGreaterGreater;": "\u226B\u0338", - "NotGreaterLess;": "\u2279", - "NotGreaterSlantEqual;": "\u2A7E\u0338", - "NotGreaterTilde;": "\u2275", - "NotHumpDownHump;": "\u224E\u0338", - "NotHumpEqual;": "\u224F\u0338", - "NotLeftTriangle;": "\u22EA", - "NotLeftTriangleBar;": "\u29CF\u0338", - "NotLeftTriangleEqual;": "\u22EC", - "NotLess;": "\u226E", - "NotLessEqual;": "\u2270", - "NotLessGreater;": "\u2278", - "NotLessLess;": "\u226A\u0338", - "NotLessSlantEqual;": "\u2A7D\u0338", - "NotLessTilde;": "\u2274", - "NotNestedGreaterGreater;": "\u2AA2\u0338", - "NotNestedLessLess;": "\u2AA1\u0338", - "NotPrecedes;": "\u2280", - "NotPrecedesEqual;": "\u2AAF\u0338", - "NotPrecedesSlantEqual;": "\u22E0", - "NotReverseElement;": "\u220C", - "NotRightTriangle;": "\u22EB", - "NotRightTriangleBar;": "\u29D0\u0338", - "NotRightTriangleEqual;": "\u22ED", - "NotSquareSubset;": "\u228F\u0338", - "NotSquareSubsetEqual;": "\u22E2", - "NotSquareSuperset;": "\u2290\u0338", - "NotSquareSupersetEqual;": "\u22E3", - "NotSubset;": "\u2282\u20D2", - "NotSubsetEqual;": "\u2288", - "NotSucceeds;": "\u2281", - "NotSucceedsEqual;": "\u2AB0\u0338", - "NotSucceedsSlantEqual;": "\u22E1", - "NotSucceedsTilde;": "\u227F\u0338", - "NotSuperset;": "\u2283\u20D2", - "NotSupersetEqual;": "\u2289", - "NotTilde;": "\u2241", - "NotTildeEqual;": "\u2244", - "NotTildeFullEqual;": "\u2247", - "NotTildeTilde;": "\u2249", - "NotVerticalBar;": "\u2224", - "Nscr;": "\u1D4A9", - "Ntilde": "\u00D1", - "Ntilde;": "\u00D1", - "Nu;": "\u039D", - "OElig;": "\u0152", - "Oacute": "\u00D3", - "Oacute;": "\u00D3", - "Ocirc": "\u00D4", - "Ocirc;": "\u00D4", - "Ocy;": "\u041E", - "Odblac;": "\u0150", - "Ofr;": "\u1D512", - "Ograve": "\u00D2", - "Ograve;": "\u00D2", - "Omacr;": "\u014C", - "Omega;": "\u03A9", - "Omicron;": "\u039F", - "Oopf;": "\u1D546", - "OpenCurlyDoubleQuote;": "\u201C", - "OpenCurlyQuote;": "\u2018", - "Or;": "\u2A54", - "Oscr;": "\u1D4AA", - "Oslash": "\u00D8", - "Oslash;": "\u00D8", - "Otilde": "\u00D5", - "Otilde;": "\u00D5", - "Otimes;": "\u2A37", - "Ouml": "\u00D6", - "Ouml;": "\u00D6", - "OverBar;": "\u203E", - "OverBrace;": "\u23DE", - "OverBracket;": "\u23B4", - "OverParenthesis;": "\u23DC", - "PartialD;": "\u2202", - "Pcy;": "\u041F", - "Pfr;": "\u1D513", - "Phi;": "\u03A6", - "Pi;": "\u03A0", - "PlusMinus;": "\u00B1", - "Poincareplane;": "\u210C", - "Popf;": "\u2119", - "Pr;": "\u2ABB", - "Precedes;": "\u227A", - "PrecedesEqual;": "\u2AAF", - "PrecedesSlantEqual;": "\u227C", - "PrecedesTilde;": "\u227E", - "Prime;": "\u2033", - "Product;": "\u220F", - "Proportion;": "\u2237", - "Proportional;": "\u221D", - "Pscr;": "\u1D4AB", - "Psi;": "\u03A8", - "QUOT": "\u0022", - "QUOT;": "\u0022", - "Qfr;": "\u1D514", - "Qopf;": "\u211A", - "Qscr;": "\u1D4AC", - "RBarr;": "\u2910", - "REG": "\u00AE", - "REG;": "\u00AE", - "Racute;": "\u0154", - "Rang;": "\u27EB", - "Rarr;": "\u21A0", - "Rarrtl;": "\u2916", - "Rcaron;": "\u0158", - "Rcedil;": "\u0156", - "Rcy;": "\u0420", - "Re;": "\u211C", - "ReverseElement;": "\u220B", - "ReverseEquilibrium;": "\u21CB", - "ReverseUpEquilibrium;": "\u296F", - "Rfr;": "\u211C", - "Rho;": "\u03A1", - "RightAngleBracket;": "\u27E9", - "RightArrow;": "\u2192", - "RightArrowBar;": "\u21E5", - "RightArrowLeftArrow;": "\u21C4", - "RightCeiling;": "\u2309", - "RightDoubleBracket;": "\u27E7", - "RightDownTeeVector;": "\u295D", - "RightDownVector;": "\u21C2", - "RightDownVectorBar;": "\u2955", - "RightFloor;": "\u230B", - "RightTee;": "\u22A2", - "RightTeeArrow;": "\u21A6", - "RightTeeVector;": "\u295B", - "RightTriangle;": "\u22B3", - "RightTriangleBar;": "\u29D0", - "RightTriangleEqual;": "\u22B5", - "RightUpDownVector;": "\u294F", - "RightUpTeeVector;": "\u295C", - "RightUpVector;": "\u21BE", - "RightUpVectorBar;": "\u2954", - "RightVector;": "\u21C0", - "RightVectorBar;": "\u2953", - "Rightarrow;": "\u21D2", - "Ropf;": "\u211D", - "RoundImplies;": "\u2970", - "Rrightarrow;": "\u21DB", - "Rscr;": "\u211B", - "Rsh;": "\u21B1", - "RuleDelayed;": "\u29F4", - "SHCHcy;": "\u0429", - "SHcy;": "\u0428", - "SOFTcy;": "\u042C", - "Sacute;": "\u015A", - "Sc;": "\u2ABC", - "Scaron;": "\u0160", - "Scedil;": "\u015E", - "Scirc;": "\u015C", - "Scy;": "\u0421", - "Sfr;": "\u1D516", - "ShortDownArrow;": "\u2193", - "ShortLeftArrow;": "\u2190", - "ShortRightArrow;": "\u2192", - "ShortUpArrow;": "\u2191", - "Sigma;": "\u03A3", - "SmallCircle;": "\u2218", - "Sopf;": "\u1D54A", - "Sqrt;": "\u221A", - "Square;": "\u25A1", - "SquareIntersection;": "\u2293", - "SquareSubset;": "\u228F", - "SquareSubsetEqual;": "\u2291", - "SquareSuperset;": "\u2290", - "SquareSupersetEqual;": "\u2292", - "SquareUnion;": "\u2294", - "Sscr;": "\u1D4AE", - "Star;": "\u22C6", - "Sub;": "\u22D0", - "Subset;": "\u22D0", - "SubsetEqual;": "\u2286", - "Succeeds;": "\u227B", - "SucceedsEqual;": "\u2AB0", - "SucceedsSlantEqual;": "\u227D", - "SucceedsTilde;": "\u227F", - "SuchThat;": "\u220B", - "Sum;": "\u2211", - "Sup;": "\u22D1", - "Superset;": "\u2283", - "SupersetEqual;": "\u2287", - "Supset;": "\u22D1", - "THORN": "\u00DE", - "THORN;": "\u00DE", - "TRADE;": "\u2122", - "TSHcy;": "\u040B", - "TScy;": "\u0426", - "Tab;": "\u0009", - "Tau;": "\u03A4", - "Tcaron;": "\u0164", - "Tcedil;": "\u0162", - "Tcy;": "\u0422", - "Tfr;": "\u1D517", - "Therefore;": "\u2234", - "Theta;": "\u0398", - "ThickSpace;": "\u205F\u200A", - "ThinSpace;": "\u2009", - "Tilde;": "\u223C", - "TildeEqual;": "\u2243", - "TildeFullEqual;": "\u2245", - "TildeTilde;": "\u2248", - "Topf;": "\u1D54B", - "TripleDot;": "\u20DB", - "Tscr;": "\u1D4AF", - "Tstrok;": "\u0166", - "Uacute": "\u00DA", - "Uacute;": "\u00DA", - "Uarr;": "\u219F", - "Uarrocir;": "\u2949", - "Ubrcy;": "\u040E", - "Ubreve;": "\u016C", - "Ucirc": "\u00DB", - "Ucirc;": "\u00DB", - "Ucy;": "\u0423", - "Udblac;": "\u0170", - "Ufr;": "\u1D518", - "Ugrave": "\u00D9", - "Ugrave;": "\u00D9", - "Umacr;": "\u016A", - "UnderBar;": "_", - "UnderBrace;": "\u23DF", - "UnderBracket;": "\u23B5", - "UnderParenthesis;": "\u23DD", - "Union;": "\u22C3", - "UnionPlus;": "\u228E", - "Uogon;": "\u0172", - "Uopf;": "\u1D54C", - "UpArrow;": "\u2191", - "UpArrowBar;": "\u2912", - "UpArrowDownArrow;": "\u21C5", - "UpDownArrow;": "\u2195", - "UpEquilibrium;": "\u296E", - "UpTee;": "\u22A5", - "UpTeeArrow;": "\u21A5", - "Uparrow;": "\u21D1", - "Updownarrow;": "\u21D5", - "UpperLeftArrow;": "\u2196", - "UpperRightArrow;": "\u2197", - "Upsi;": "\u03D2", - "Upsilon;": "\u03A5", - "Uring;": "\u016E", - "Uscr;": "\u1D4B0", - "Utilde;": "\u0168", - "Uuml": "\u00DC", - "Uuml;": "\u00DC", - "VDash;": "\u22AB", - "Vbar;": "\u2AEB", - "Vcy;": "\u0412", - "Vdash;": "\u22A9", - "Vdashl;": "\u2AE6", - "Vee;": "\u22C1", - "Verbar;": "\u2016", - "Vert;": "\u2016", - "VerticalBar;": "\u2223", - "VerticalLine;": "|", - "VerticalSeparator;": "\u2758", - "VerticalTilde;": "\u2240", - "VeryThinSpace;": "\u200A", - "Vfr;": "\u1D519", - "Vopf;": "\u1D54D", - "Vscr;": "\u1D4B1", - "Vvdash;": "\u22AA", - "Wcirc;": "\u0174", - "Wedge;": "\u22C0", - "Wfr;": "\u1D51A", - "Wopf;": "\u1D54E", - "Wscr;": "\u1D4B2", - "Xfr;": "\u1D51B", - "Xi;": "\u039E", - "Xopf;": "\u1D54F", - "Xscr;": "\u1D4B3", - "YAcy;": "\u042F", - "YIcy;": "\u0407", - "YUcy;": "\u042E", - "Yacute": "\u00DD", - "Yacute;": "\u00DD", - "Ycirc;": "\u0176", - "Ycy;": "\u042B", - "Yfr;": "\u1D51C", - "Yopf;": "\u1D550", - "Yscr;": "\u1D4B4", - "Yuml;": "\u0178", - "ZHcy;": "\u0416", - "Zacute;": "\u0179", - "Zcaron;": "\u017D", - "Zcy;": "\u0417", - "Zdot;": "\u017B", - "ZeroWidthSpace;": "\u200B", - "Zeta;": "\u0396", - "Zfr;": "\u2128", - "Zopf;": "\u2124", - "Zscr;": "\u1D4B5", - "aacute": "\u00E1", - "aacute;": "\u00E1", - "abreve;": "\u0103", - "ac;": "\u223E", - "acE;": "\u223E\u0333", - "acd;": "\u223F", - "acirc": "\u00E2", - "acirc;": "\u00E2", - "acute": "\u00B4", - "acute;": "\u00B4", - "acy;": "\u0430", - "aelig": "\u00E6", - "aelig;": "\u00E6", - "af;": "\u2061", - "afr;": "\u1D51E", - "agrave": "\u00E0", - "agrave;": "\u00E0", - "alefsym;": "\u2135", - "aleph;": "\u2135", - "alpha;": "\u03B1", - "amacr;": "\u0101", - "amalg;": "\u2A3F", - "amp": "&", - "amp;": "&", - "and;": "\u2227", - "andand;": "\u2A55", - "andd;": "\u2A5C", - "andslope;": "\u2A58", - "andv;": "\u2A5A", - "ang;": "\u2220", - "ange;": "\u29A4", - "angle;": "\u2220", - "angmsd;": "\u2221", - "angmsdaa;": "\u29A8", - "angmsdab;": "\u29A9", - "angmsdac;": "\u29AA", - "angmsdad;": "\u29AB", - "angmsdae;": "\u29AC", - "angmsdaf;": "\u29AD", - "angmsdag;": "\u29AE", - "angmsdah;": "\u29AF", - "angrt;": "\u221F", - "angrtvb;": "\u22BE", - "angrtvbd;": "\u299D", - "angsph;": "\u2222", - "angst;": "\u00C5", - "angzarr;": "\u237C", - "aogon;": "\u0105", - "aopf;": "\u1D552", - "ap;": "\u2248", - "apE;": "\u2A70", - "apacir;": "\u2A6F", - "ape;": "\u224A", - "apid;": "\u224B", - "apos;": "\u0027", - "approx;": "\u2248", - "approxeq;": "\u224A", - "aring": "\u00E5", - "aring;": "\u00E5", - "ascr;": "\u1D4B6", - "ast;": "*", - "asymp;": "\u2248", - "asympeq;": "\u224D", - "atilde": "\u00E3", - "atilde;": "\u00E3", - "auml": "\u00E4", - "auml;": "\u00E4", - "awconint;": "\u2233", - "awint;": "\u2A11", - "bNot;": "\u2AED", - "backcong;": "\u224C", - "backepsilon;": "\u03F6", - "backprime;": "\u2035", - "backsim;": "\u223D", - "backsimeq;": "\u22CD", - "barvee;": "\u22BD", - "barwed;": "\u2305", - "barwedge;": "\u2305", - "bbrk;": "\u23B5", - "bbrktbrk;": "\u23B6", - "bcong;": "\u224C", - "bcy;": "\u0431", - "bdquo;": "\u201E", - "becaus;": "\u2235", - "because;": "\u2235", - "bemptyv;": "\u29B0", - "bepsi;": "\u03F6", - "bernou;": "\u212C", - "beta;": "\u03B2", - "beth;": "\u2136", - "between;": "\u226C", - "bfr;": "\u1D51F", - "bigcap;": "\u22C2", - "bigcirc;": "\u25EF", - "bigcup;": "\u22C3", - "bigodot;": "\u2A00", - "bigoplus;": "\u2A01", - "bigotimes;": "\u2A02", - "bigsqcup;": "\u2A06", - "bigstar;": "\u2605", - "bigtriangledown;": "\u25BD", - "bigtriangleup;": "\u25B3", - "biguplus;": "\u2A04", - "bigvee;": "\u22C1", - "bigwedge;": "\u22C0", - "bkarow;": "\u290D", - "blacklozenge;": "\u29EB", - "blacksquare;": "\u25AA", - "blacktriangle;": "\u25B4", - "blacktriangledown;": "\u25BE", - "blacktriangleleft;": "\u25C2", - "blacktriangleright;": "\u25B8", - "blank;": "\u2423", - "blk12;": "\u2592", - "blk14;": "\u2591", - "blk34;": "\u2593", - "block;": "\u2588", - "bne;": "\u003D\u20E5", - "bnequiv;": "\u2261\u20E5", - "bnot;": "\u2310", - "bopf;": "\u1D553", - "bot;": "\u22A5", - "bottom;": "\u22A5", - "bowtie;": "\u22C8", - "boxDL;": "\u2557", - "boxDR;": "\u2554", - "boxDl;": "\u2556", - "boxDr;": "\u2553", - "boxH;": "\u2550", - "boxHD;": "\u2566", - "boxHU;": "\u2569", - "boxHd;": "\u2564", - "boxHu;": "\u2567", - "boxUL;": "\u255D", - "boxUR;": "\u255A", - "boxUl;": "\u255C", - "boxUr;": "\u2559", - "boxV;": "\u2551", - "boxVH;": "\u256C", - "boxVL;": "\u2563", - "boxVR;": "\u2560", - "boxVh;": "\u256B", - "boxVl;": "\u2562", - "boxVr;": "\u255F", - "boxbox;": "\u29C9", - "boxdL;": "\u2555", - "boxdR;": "\u2552", - "boxdl;": "\u2510", - "boxdr;": "\u250C", - "boxh;": "\u2500", - "boxhD;": "\u2565", - "boxhU;": "\u2568", - "boxhd;": "\u252C", - "boxhu;": "\u2534", - "boxminus;": "\u229F", - "boxplus;": "\u229E", - "boxtimes;": "\u22A0", - "boxuL;": "\u255B", - "boxuR;": "\u2558", - "boxul;": "\u2518", - "boxur;": "\u2514", - "boxv;": "\u2502", - "boxvH;": "\u256A", - "boxvL;": "\u2561", - "boxvR;": "\u255E", - "boxvh;": "\u253C", - "boxvl;": "\u2524", - "boxvr;": "\u251C", - "bprime;": "\u2035", - "breve;": "\u02D8", - "brvbar": "\u00A6", - "brvbar;": "\u00A6", - "bscr;": "\u1D4B7", - "bsemi;": "\u204F", - "bsim;": "\u223D", - "bsime;": "\u22CD", - "bsol;": "\u005C", - "bsolb;": "\u29C5", - "bsolhsub;": "\u27C8", - "bull;": "\u2022", - "bullet;": "\u2022", - "bump;": "\u224E", - "bumpE;": "\u2AAE", - "bumpe;": "\u224F", - "bumpeq;": "\u224F", - "cacute;": "\u0107", - "cap;": "\u2229", - "capand;": "\u2A44", - "capbrcup;": "\u2A49", - "capcap;": "\u2A4B", - "capcup;": "\u2A47", - "capdot;": "\u2A40", - "caps;": "\u2229\uFE00", - "caret;": "\u2041", - "caron;": "\u02C7", - "ccaps;": "\u2A4D", - "ccaron;": "\u010D", - "ccedil": "\u00E7", - "ccedil;": "\u00E7", - "ccirc;": "\u0109", - "ccups;": "\u2A4C", - "ccupssm;": "\u2A50", - "cdot;": "\u010B", - "cedil": "\u00B8", - "cedil;": "\u00B8", - "cemptyv;": "\u29B2", - "cent": "\u00A2", - "cent;": "\u00A2", - "centerdot;": "\u00B7", - "cfr;": "\u1D520", - "chcy;": "\u0447", - "check;": "\u2713", - "checkmark;": "\u2713", - "chi;": "\u03C7", - "cir;": "\u25CB", - "cirE;": "\u29C3", - "circ;": "\u02C6", - "circeq;": "\u2257", - "circlearrowleft;": "\u21BA", - "circlearrowright;": "\u21BB", - "circledR;": "\u00AE", - "circledS;": "\u24C8", - "circledast;": "\u229B", - "circledcirc;": "\u229A", - "circleddash;": "\u229D", - "cire;": "\u2257", - "cirfnint;": "\u2A10", - "cirmid;": "\u2AEF", - "cirscir;": "\u29C2", - "clubs;": "\u2663", - "clubsuit;": "\u2663", - "colon;": ":", - "colone;": "\u2254", - "coloneq;": "\u2254", - "comma;": ",", - "commat;": "@", - "comp;": "\u2201", - "compfn;": "\u2218", - "complement;": "\u2201", - "complexes;": "\u2102", - "cong;": "\u2245", - "congdot;": "\u2A6D", - "conint;": "\u222E", - "copf;": "\u1D554", - "coprod;": "\u2210", - "copy": "\u00A9", - "copy;": "\u00A9", - "copysr;": "\u2117", - "crarr;": "\u21B5", - "cross;": "\u2717", - "cscr;": "\u1D4B8", - "csub;": "\u2ACF", - "csube;": "\u2AD1", - "csup;": "\u2AD0", - "csupe;": "\u2AD2", - "ctdot;": "\u22EF", - "cudarrl;": "\u2938", - "cudarrr;": "\u2935", - "cuepr;": "\u22DE", - "cuesc;": "\u22DF", - "cularr;": "\u21B6", - "cularrp;": "\u293D", - "cup;": "\u222A", - "cupbrcap;": "\u2A48", - "cupcap;": "\u2A46", - "cupcup;": "\u2A4A", - "cupdot;": "\u228D", - "cupor;": "\u2A45", - "cups;": "\u222A\uFE00", - "curarr;": "\u21B7", - "curarrm;": "\u293C", - "curlyeqprec;": "\u22DE", - "curlyeqsucc;": "\u22DF", - "curlyvee;": "\u22CE", - "curlywedge;": "\u22CF", - "curren": "\u00A4", - "curren;": "\u00A4", - "curvearrowleft;": "\u21B6", - "curvearrowright;": "\u21B7", - "cuvee;": "\u22CE", - "cuwed;": "\u22CF", - "cwconint;": "\u2232", - "cwint;": "\u2231", - "cylcty;": "\u232D", - "dArr;": "\u21D3", - "dHar;": "\u2965", - "dagger;": "\u2020", - "daleth;": "\u2138", - "darr;": "\u2193", - "dash;": "\u2010", - "dashv;": "\u22A3", - "dbkarow;": "\u290F", - "dblac;": "\u02DD", - "dcaron;": "\u010F", - "dcy;": "\u0434", - "dd;": "\u2146", - "ddagger;": "\u2021", - "ddarr;": "\u21CA", - "ddotseq;": "\u2A77", - "deg": "\u00B0", - "deg;": "\u00B0", - "delta;": "\u03B4", - "demptyv;": "\u29B1", - "dfisht;": "\u297F", - "dfr;": "\u1D521", - "dharl;": "\u21C3", - "dharr;": "\u21C2", - "diam;": "\u22C4", - "diamond;": "\u22C4", - "diamondsuit;": "\u2666", - "diams;": "\u2666", - "die;": "\u00A8", - "digamma;": "\u03DD", - "disin;": "\u22F2", - "div;": "\u00F7", - "divide": "\u00F7", - "divide;": "\u00F7", - "divideontimes;": "\u22C7", - "divonx;": "\u22C7", - "djcy;": "\u0452", - "dlcorn;": "\u231E", - "dlcrop;": "\u230D", - "dollar;": "$", - "dopf;": "\u1D555", - "dot;": "\u02D9", - "doteq;": "\u2250", - "doteqdot;": "\u2251", - "dotminus;": "\u2238", - "dotplus;": "\u2214", - "dotsquare;": "\u22A1", - "doublebarwedge;": "\u2306", - "downarrow;": "\u2193", - "downdownarrows;": "\u21CA", - "downharpoonleft;": "\u21C3", - "downharpoonright;": "\u21C2", - "drbkarow;": "\u2910", - "drcorn;": "\u231F", - "drcrop;": "\u230C", - "dscr;": "\u1D4B9", - "dscy;": "\u0455", - "dsol;": "\u29F6", - "dstrok;": "\u0111", - "dtdot;": "\u22F1", - "dtri;": "\u25BF", - "dtrif;": "\u25BE", - "duarr;": "\u21F5", - "duhar;": "\u296F", - "dwangle;": "\u29A6", - "dzcy;": "\u045F", - "dzigrarr;": "\u27FF", - "eDDot;": "\u2A77", - "eDot;": "\u2251", - "eacute": "\u00E9", - "eacute;": "\u00E9", - "easter;": "\u2A6E", - "ecaron;": "\u011B", - "ecir;": "\u2256", - "ecirc": "\u00EA", - "ecirc;": "\u00EA", - "ecolon;": "\u2255", - "ecy;": "\u044D", - "edot;": "\u0117", - "ee;": "\u2147", - "efDot;": "\u2252", - "efr;": "\u1D522", - "eg;": "\u2A9A", - "egrave": "\u00E8", - "egrave;": "\u00E8", - "egs;": "\u2A96", - "egsdot;": "\u2A98", - "el;": "\u2A99", - "elinters;": "\u23E7", - "ell;": "\u2113", - "els;": "\u2A95", - "elsdot;": "\u2A97", - "emacr;": "\u0113", - "empty;": "\u2205", - "emptyset;": "\u2205", - "emptyv;": "\u2205", - "emsp13;": "\u2004", - "emsp14;": "\u2005", - "emsp;": "\u2003", - "eng;": "\u014B", - "ensp;": "\u2002", - "eogon;": "\u0119", - "eopf;": "\u1D556", - "epar;": "\u22D5", - "eparsl;": "\u29E3", - "eplus;": "\u2A71", - "epsi;": "\u03B5", - "epsilon;": "\u03B5", - "epsiv;": "\u03F5", - "eqcirc;": "\u2256", - "eqcolon;": "\u2255", - "eqsim;": "\u2242", - "eqslantgtr;": "\u2A96", - "eqslantless;": "\u2A95", - "equals;": "=", - "equest;": "\u225F", - "equiv;": "\u2261", - "equivDD;": "\u2A78", - "eqvparsl;": "\u29E5", - "erDot;": "\u2253", - "erarr;": "\u2971", - "escr;": "\u212F", - "esdot;": "\u2250", - "esim;": "\u2242", - "eta;": "\u03B7", - "eth": "\u00F0", - "eth;": "\u00F0", - "euml": "\u00EB", - "euml;": "\u00EB", - "euro;": "\u20AC", - "excl;": "!", - "exist;": "\u2203", - "expectation;": "\u2130", - "exponentiale;": "\u2147", - "fallingdotseq;": "\u2252", - "fcy;": "\u0444", - "female;": "\u2640", - "ffilig;": "\uFB03", - "fflig;": "\uFB00", - "ffllig;": "\uFB04", - "ffr;": "\u1D523", - "filig;": "\uFB01", - "fjlig;": "\u0066", - "flat;": "\u266D", - "fllig;": "\uFB02", - "fltns;": "\u25B1", - "fnof;": "\u0192", - "fopf;": "\u1D557", - "forall;": "\u2200", - "fork;": "\u22D4", - "forkv;": "\u2AD9", - "fpartint;": "\u2A0D", - "frac12": "\u00BD", - "frac12;": "\u00BD", - "frac13;": "\u2153", - "frac14": "\u00BC", - "frac14;": "\u00BC", - "frac15;": "\u2155", - "frac16;": "\u2159", - "frac18;": "\u215B", - "frac23;": "\u2154", - "frac25;": "\u2156", - "frac34": "\u00BE", - "frac34;": "\u00BE", - "frac35;": "\u2157", - "frac38;": "\u215C", - "frac45;": "\u2158", - "frac56;": "\u215A", - "frac58;": "\u215D", - "frac78;": "\u215E", - "frasl;": "\u2044", - "frown;": "\u2322", - "fscr;": "\u1D4BB", - "gE;": "\u2267", - "gEl;": "\u2A8C", - "gacute;": "\u01F5", - "gamma;": "\u03B3", - "gammad;": "\u03DD", - "gap;": "\u2A86", - "gbreve;": "\u011F", - "gcirc;": "\u011D", - "gcy;": "\u0433", - "gdot;": "\u0121", - "ge;": "\u2265", - "gel;": "\u22DB", - "geq;": "\u2265", - "geqq;": "\u2267", - "geqslant;": "\u2A7E", - "ges;": "\u2A7E", - "gescc;": "\u2AA9", - "gesdot;": "\u2A80", - "gesdoto;": "\u2A82", - "gesdotol;": "\u2A84", - "gesl;": "\u22DB\uFE00", - "gesles;": "\u2A94", - "gfr;": "\u1D524", - "gg;": "\u226B", - "ggg;": "\u22D9", - "gimel;": "\u2137", - "gjcy;": "\u0453", - "gl;": "\u2277", - "glE;": "\u2A92", - "gla;": "\u2AA5", - "glj;": "\u2AA4", - "gnE;": "\u2269", - "gnap;": "\u2A8A", - "gnapprox;": "\u2A8A", - "gne;": "\u2A88", - "gneq;": "\u2A88", - "gneqq;": "\u2269", - "gnsim;": "\u22E7", - "gopf;": "\u1D558", - "grave;": "`", - "gscr;": "\u210A", - "gsim;": "\u2273", - "gsime;": "\u2A8E", - "gsiml;": "\u2A90", - "gt": ">", - "gt;": ">", - "gtcc;": "\u2AA7", - "gtcir;": "\u2A7A", - "gtdot;": "\u22D7", - "gtlPar;": "\u2995", - "gtquest;": "\u2A7C", - "gtrapprox;": "\u2A86", - "gtrarr;": "\u2978", - "gtrdot;": "\u22D7", - "gtreqless;": "\u22DB", - "gtreqqless;": "\u2A8C", - "gtrless;": "\u2277", - "gtrsim;": "\u2273", - "gvertneqq;": "\u2269\uFE00", - "gvnE;": "\u2269\uFE00", - "hArr;": "\u21D4", - "hairsp;": "\u200A", - "half;": "\u00BD", - "hamilt;": "\u210B", - "hardcy;": "\u044A", - "harr;": "\u2194", - "harrcir;": "\u2948", - "harrw;": "\u21AD", - "hbar;": "\u210F", - "hcirc;": "\u0125", - "hearts;": "\u2665", - "heartsuit;": "\u2665", - "hellip;": "\u2026", - "hercon;": "\u22B9", - "hfr;": "\u1D525", - "hksearow;": "\u2925", - "hkswarow;": "\u2926", - "hoarr;": "\u21FF", - "homtht;": "\u223B", - "hookleftarrow;": "\u21A9", - "hookrightarrow;": "\u21AA", - "hopf;": "\u1D559", - "horbar;": "\u2015", - "hscr;": "\u1D4BD", - "hslash;": "\u210F", - "hstrok;": "\u0127", - "hybull;": "\u2043", - "hyphen;": "\u2010", - "iacute": "\u00ED", - "iacute;": "\u00ED", - "ic;": "\u2063", - "icirc": "\u00EE", - "icirc;": "\u00EE", - "icy;": "\u0438", - "iecy;": "\u0435", - "iexcl": "\u00A1", - "iexcl;": "\u00A1", - "iff;": "\u21D4", - "ifr;": "\u1D526", - "igrave": "\u00EC", - "igrave;": "\u00EC", - "ii;": "\u2148", - "iiiint;": "\u2A0C", - "iiint;": "\u222D", - "iinfin;": "\u29DC", - "iiota;": "\u2129", - "ijlig;": "\u0133", - "imacr;": "\u012B", - "image;": "\u2111", - "imagline;": "\u2110", - "imagpart;": "\u2111", - "imath;": "\u0131", - "imof;": "\u22B7", - "imped;": "\u01B5", - "in;": "\u2208", - "incare;": "\u2105", - "infin;": "\u221E", - "infintie;": "\u29DD", - "inodot;": "\u0131", - "int;": "\u222B", - "intcal;": "\u22BA", - "integers;": "\u2124", - "intercal;": "\u22BA", - "intlarhk;": "\u2A17", - "intprod;": "\u2A3C", - "iocy;": "\u0451", - "iogon;": "\u012F", - "iopf;": "\u1D55A", - "iota;": "\u03B9", - "iprod;": "\u2A3C", - "iquest": "\u00BF", - "iquest;": "\u00BF", - "iscr;": "\u1D4BE", - "isin;": "\u2208", - "isinE;": "\u22F9", - "isindot;": "\u22F5", - "isins;": "\u22F4", - "isinsv;": "\u22F3", - "isinv;": "\u2208", - "it;": "\u2062", - "itilde;": "\u0129", - "iukcy;": "\u0456", - "iuml": "\u00EF", - "iuml;": "\u00EF", - "jcirc;": "\u0135", - "jcy;": "\u0439", - "jfr;": "\u1D527", - "jmath;": "\u0237", - "jopf;": "\u1D55B", - "jscr;": "\u1D4BF", - "jsercy;": "\u0458", - "jukcy;": "\u0454", - "kappa;": "\u03BA", - "kappav;": "\u03F0", - "kcedil;": "\u0137", - "kcy;": "\u043A", - "kfr;": "\u1D528", - "kgreen;": "\u0138", - "khcy;": "\u0445", - "kjcy;": "\u045C", - "kopf;": "\u1D55C", - "kscr;": "\u1D4C0", - "lAarr;": "\u21DA", - "lArr;": "\u21D0", - "lAtail;": "\u291B", - "lBarr;": "\u290E", - "lE;": "\u2266", - "lEg;": "\u2A8B", - "lHar;": "\u2962", - "lacute;": "\u013A", - "laemptyv;": "\u29B4", - "lagran;": "\u2112", - "lambda;": "\u03BB", - "lang;": "\u27E8", - "langd;": "\u2991", - "langle;": "\u27E8", - "lap;": "\u2A85", - "laquo": "\u00AB", - "laquo;": "\u00AB", - "larr;": "\u2190", - "larrb;": "\u21E4", - "larrbfs;": "\u291F", - "larrfs;": "\u291D", - "larrhk;": "\u21A9", - "larrlp;": "\u21AB", - "larrpl;": "\u2939", - "larrsim;": "\u2973", - "larrtl;": "\u21A2", - "lat;": "\u2AAB", - "latail;": "\u2919", - "late;": "\u2AAD", - "lates;": "\u2AAD\uFE00", - "lbarr;": "\u290C", - "lbbrk;": "\u2772", - "lbrace;": "{", - "lbrack;": "[", - "lbrke;": "\u298B", - "lbrksld;": "\u298F", - "lbrkslu;": "\u298D", - "lcaron;": "\u013E", - "lcedil;": "\u013C", - "lceil;": "\u2308", - "lcub;": "{", - "lcy;": "\u043B", - "ldca;": "\u2936", - "ldquo;": "\u201C", - "ldquor;": "\u201E", - "ldrdhar;": "\u2967", - "ldrushar;": "\u294B", - "ldsh;": "\u21B2", - "le;": "\u2264", - "leftarrow;": "\u2190", - "leftarrowtail;": "\u21A2", - "leftharpoondown;": "\u21BD", - "leftharpoonup;": "\u21BC", - "leftleftarrows;": "\u21C7", - "leftrightarrow;": "\u2194", - "leftrightarrows;": "\u21C6", - "leftrightharpoons;": "\u21CB", - "leftrightsquigarrow;": "\u21AD", - "leftthreetimes;": "\u22CB", - "leg;": "\u22DA", - "leq;": "\u2264", - "leqq;": "\u2266", - "leqslant;": "\u2A7D", - "les;": "\u2A7D", - "lescc;": "\u2AA8", - "lesdot;": "\u2A7F", - "lesdoto;": "\u2A81", - "lesdotor;": "\u2A83", - "lesg;": "\u22DA\uFE00", - "lesges;": "\u2A93", - "lessapprox;": "\u2A85", - "lessdot;": "\u22D6", - "lesseqgtr;": "\u22DA", - "lesseqqgtr;": "\u2A8B", - "lessgtr;": "\u2276", - "lesssim;": "\u2272", - "lfisht;": "\u297C", - "lfloor;": "\u230A", - "lfr;": "\u1D529", - "lg;": "\u2276", - "lgE;": "\u2A91", - "lhard;": "\u21BD", - "lharu;": "\u21BC", - "lharul;": "\u296A", - "lhblk;": "\u2584", - "ljcy;": "\u0459", - "ll;": "\u226A", - "llarr;": "\u21C7", - "llcorner;": "\u231E", - "llhard;": "\u296B", - "lltri;": "\u25FA", - "lmidot;": "\u0140", - "lmoust;": "\u23B0", - "lmoustache;": "\u23B0", - "lnE;": "\u2268", - "lnap;": "\u2A89", - "lnapprox;": "\u2A89", - "lne;": "\u2A87", - "lneq;": "\u2A87", - "lneqq;": "\u2268", - "lnsim;": "\u22E6", - "loang;": "\u27EC", - "loarr;": "\u21FD", - "lobrk;": "\u27E6", - "longleftarrow;": "\u27F5", - "longleftrightarrow;": "\u27F7", - "longmapsto;": "\u27FC", - "longrightarrow;": "\u27F6", - "looparrowleft;": "\u21AB", - "looparrowright;": "\u21AC", - "lopar;": "\u2985", - "lopf;": "\u1D55D", - "loplus;": "\u2A2D", - "lotimes;": "\u2A34", - "lowast;": "\u2217", - "lowbar;": "_", - "loz;": "\u25CA", - "lozenge;": "\u25CA", - "lozf;": "\u29EB", - "lpar;": "(", - "lparlt;": "\u2993", - "lrarr;": "\u21C6", - "lrcorner;": "\u231F", - "lrhar;": "\u21CB", - "lrhard;": "\u296D", - "lrm;": "\u200E", - "lrtri;": "\u22BF", - "lsaquo;": "\u2039", - "lscr;": "\u1D4C1", - "lsh;": "\u21B0", - "lsim;": "\u2272", - "lsime;": "\u2A8D", - "lsimg;": "\u2A8F", - "lsqb;": "[", - "lsquo;": "\u2018", - "lsquor;": "\u201A", - "lstrok;": "\u0142", - "lt": "<", - "lt;": "<", - "ltcc;": "\u2AA6", - "ltcir;": "\u2A79", - "ltdot;": "\u22D6", - "lthree;": "\u22CB", - "ltimes;": "\u22C9", - "ltlarr;": "\u2976", - "ltquest;": "\u2A7B", - "ltrPar;": "\u2996", - "ltri;": "\u25C3", - "ltrie;": "\u22B4", - "ltrif;": "\u25C2", - "lurdshar;": "\u294A", - "luruhar;": "\u2966", - "lvertneqq;": "\u2268\uFE00", - "lvnE;": "\u2268\uFE00", - "mDDot;": "\u223A", - "macr": "\u00AF", - "macr;": "\u00AF", - "male;": "\u2642", - "malt;": "\u2720", - "maltese;": "\u2720", - "map;": "\u21A6", - "mapsto;": "\u21A6", - "mapstodown;": "\u21A7", - "mapstoleft;": "\u21A4", - "mapstoup;": "\u21A5", - "marker;": "\u25AE", - "mcomma;": "\u2A29", - "mcy;": "\u043C", - "mdash;": "\u2014", - "measuredangle;": "\u2221", - "mfr;": "\u1D52A", - "mho;": "\u2127", - "micro": "\u00B5", - "micro;": "\u00B5", - "mid;": "\u2223", - "midast;": "*", - "midcir;": "\u2AF0", - "middot": "\u00B7", - "middot;": "\u00B7", - "minus;": "\u2212", - "minusb;": "\u229F", - "minusd;": "\u2238", - "minusdu;": "\u2A2A", - "mlcp;": "\u2ADB", - "mldr;": "\u2026", - "mnplus;": "\u2213", - "models;": "\u22A7", - "mopf;": "\u1D55E", - "mp;": "\u2213", - "mscr;": "\u1D4C2", - "mstpos;": "\u223E", - "mu;": "\u03BC", - "multimap;": "\u22B8", - "mumap;": "\u22B8", - "nGg;": "\u22D9\u0338", - "nGt;": "\u226B\u20D2", - "nGtv;": "\u226B\u0338", - "nLeftarrow;": "\u21CD", - "nLeftrightarrow;": "\u21CE", - "nLl;": "\u22D8\u0338", - "nLt;": "\u226A\u20D2", - "nLtv;": "\u226A\u0338", - "nRightarrow;": "\u21CF", - "nVDash;": "\u22AF", - "nVdash;": "\u22AE", - "nabla;": "\u2207", - "nacute;": "\u0144", - "nang;": "\u2220\u20D2", - "nap;": "\u2249", - "napE;": "\u2A70\u0338", - "napid;": "\u224B\u0338", - "napos;": "\u0149", - "napprox;": "\u2249", - "natur;": "\u266E", - "natural;": "\u266E", - "naturals;": "\u2115", - "nbsp": "\u00A0", - "nbsp;": "\u00A0", - "nbump;": "\u224E\u0338", - "nbumpe;": "\u224F\u0338", - "ncap;": "\u2A43", - "ncaron;": "\u0148", - "ncedil;": "\u0146", - "ncong;": "\u2247\u0338", - "ncongdot;": "\u2A6D", - "ncup;": "\u2A42", - "ncy;": "\u043D", - "ndash;": "\u2013", - "ne;": "\u2260", - "neArr;": "\u21D7", - "nearhk;": "\u2924", - "nearr;": "\u2197", - "nearrow;": "\u2197", - "nedot;": "\u2250\u0338", - "nequiv;": "\u2262", - "nesear;": "\u2928", - "nesim;": "\u2242\u0338", - "nexist;": "\u2204", - "nexists;": "\u2204", - "nfr;": "\u1D52B", - "ngE;": "\u2267\u0338", - "nge;": "\u2271", - "ngeq;": "\u2271", - "ngeqq;": "\u2267\u0338", - "ngeqslant;": "\u2A7E\u0338", - "nges;": "\u2A7E\u0338", - "ngsim;": "\u2275", - "ngt;": "\u226F", - "ngtr;": "\u226F", - "nhArr;": "\u21CE", - "nharr;": "\u21AE", - "nhpar;": "\u2AF2", - "ni;": "\u220B", - "nis;": "\u22FC", - "nisd;": "\u22FA", - "niv;": "\u220B", - "njcy;": "\u045A", - "nlArr;": "\u21CD", - "nlE;": "\u2266\u0338", - "nlarr;": "\u219A", - "nldr;": "\u2025", - "nle;": "\u2270", - "nleftarrow;": "\u219A", - "nleftrightarrow;": "\u21AE", - "nleq;": "\u2270", - "nleqq;": "\u2266\u0338", - "nleqslant;": "\u2A7D\u0338", - "nles;": "\u2A7D\u0338", - "nless;": "\u226E", - "nlsim;": "\u2274", - "nlt;": "\u226E", - "nltri;": "\u22EA", - "nltrie;": "\u22EC", - "nmid;": "\u2224", - "nopf;": "\u1D55F", - "not": "\u00AC", - "not;": "\u00AC", - "notin;": "\u2209", - "notinE;": "\u22F9\u0338", - "notindot;": "\u22F5\u0338", - "notinva;": "\u2209", - "notinvb;": "\u22F7", - "notinvc;": "\u22F6", - "notni;": "\u220C", - "notniva;": "\u220C", - "notnivb;": "\u22FE", - "notnivc;": "\u22FD", - "npar;": "\u2226", - "nparallel;": "\u2226", - "nparsl;": "\u2AFD\u20E5", - "npart;": "\u2202\u0338", - "npolint;": "\u2A14", - "npr;": "\u2280", - "nprcue;": "\u22E0", - "npre;": "\u2AAF", - "nprec;": "\u2280", - "npreceq;": "\u2AAF", - "nrArr;": "\u21CF", - "nrarr;": "\u219B", - "nrarrc;": "\u2933\u0338", - "nrarrw;": "\u219D\u0338", - "nrightarrow;": "\u219B", - "nrtri;": "\u22EB", - "nrtrie;": "\u22ED", - "nsc;": "\u2281", - "nsccue;": "\u22E1", - "nsce;": "\u2AB0\u0338", - "nscr;": "\u1D4C3", - "nshortmid;": "\u2224", - "nshortparallel;": "\u2226", - "nsim;": "\u2241", - "nsime;": "\u2244", - "nsimeq;": "\u2244", - "nsmid;": "\u2224", - "nspar;": "\u2226", - "nsqsube;": "\u22E2", - "nsqsupe;": "\u22E3", - "nsub;": "\u2284", - "nsubE;": "\u2AC5\u0338", - "nsube;": "\u2288", - "nsubset;": "\u2282\u0338", - "nsubseteq;": "\u2288", - "nsubseteqq;": "\u2AC5\u0338", - "nsucc;": "\u2281", - "nsucceq;": "\u2AB0\u0338", - "nsup;": "\u2285", - "nsupE;": "\u2AC6", - "nsupe;": "\u2289", - "nsupset;": "\u2283\u0338", - "nsupseteq;": "\u2289", - "nsupseteqq;": "\u2AC6\u0338", - "ntgl;": "\u2279", - "ntilde": "\u00F1", - "ntilde;": "\u00F1", - "ntlg;": "\u2278", - "ntriangleleft;": "\u22EA", - "ntrianglelefteq;": "\u22EC", - "ntriangleright;": "\u22EB", - "ntrianglerighteq;": "\u22ED", - "nu;": "\u03BD", - "num;": "#", - "numero;": "\u2116", - "numsp;": "\u2007", - "nvDash;": "\u22AD", - "nvHarr;": "\u2904", - "nvap;": "\u224D\u20D2", - "nvdash;": "\u22AC", - "nvge;": "\u2265\u20D2", - "nvgt;": "\u003E\u20D2", - "nvinfin;": "\u29DE", - "nvlArr;": "\u2902", - "nvle;": "\u2264\u20D2", - "nvlt;": "\u003C\u20D2", - "nvltrie;": "\u22B4\u20D2", - "nvrArr;": "\u2903", - "nvrtrie;": "\u22B5\u20D2", - "nvsim;": "\u223C\u20D2", - "nwArr;": "\u21D6", - "nwarhk;": "\u2923", - "nwarr;": "\u2196", - "nwarrow;": "\u2196", - "nwnear;": "\u2927", - "oS;": "\u24C8", - "oacute": "\u00F3", - "oacute;": "\u00F3", - "oast;": "\u229B", - "ocir;": "\u229A", - "ocirc": "\u00F4", - "ocirc;": "\u00F4", - "ocy;": "\u043E", - "odash;": "\u229D", - "odblac;": "\u0151", - "odiv;": "\u2A38", - "odot;": "\u2299", - "odsold;": "\u29BC", - "oelig;": "\u0153", - "ofcir;": "\u29BF", - "ofr;": "\u1D52C", - "ogon;": "\u02DB", - "ograve": "\u00F2", - "ograve;": "\u00F2", - "ogt;": "\u29C1", - "ohbar;": "\u29B5", - "ohm;": "\u03A9", - "oint;": "\u222E", - "olarr;": "\u21BA", - "olcir;": "\u29BE", - "olcross;": "\u29BB", - "oline;": "\u203E", - "olt;": "\u29C0", - "omacr;": "\u014D", - "omega;": "\u03C9", - "omicron;": "\u03BF", - "omid;": "\u29B6", - "ominus;": "\u2296", - "oopf;": "\u1D560", - "opar;": "\u29B7", - "operp;": "\u29B9", - "oplus;": "\u2295", - "or;": "\u2228", - "orarr;": "\u21BB", - "ord;": "\u2A5D", - "order;": "\u2134", - "orderof;": "\u2134", - "ordf": "\u00AA", - "ordf;": "\u00AA", - "ordm": "\u00BA", - "ordm;": "\u00BA", - "origof;": "\u22B6", - "oror;": "\u2A56", - "orslope;": "\u2A57", - "orv;": "\u2A5B", - "oscr;": "\u2134", - "oslash": "\u00F8", - "oslash;": "\u00F8", - "osol;": "\u2298", - "otilde": "\u00F5", - "otilde;": "\u00F5", - "otimes;": "\u2297", - "otimesas;": "\u2A36", - "ouml": "\u00F6", - "ouml;": "\u00F6", - "ovbar;": "\u233D", - "par;": "\u2225", - "para": "\u00B6", - "para;": "\u00B6", - "parallel;": "\u2225", - "parsim;": "\u2AF3", - "parsl;": "\u2AFD", - "part;": "\u2202", - "pcy;": "\u043F", - "percnt;": "%", - "period;": ".", - "permil;": "\u2030", - "perp;": "\u22A5", - "pertenk;": "\u2031", - "pfr;": "\u1D52D", - "phi;": "\u03C6", - "phiv;": "\u03D5", - "phmmat;": "\u2133", - "phone;": "\u260E", - "pi;": "\u03C0", - "pitchfork;": "\u22D4", - "piv;": "\u03D6", - "planck;": "\u210F", - "planckh;": "\u210E", - "plankv;": "\u210F", - "plus;": "+", - "plusacir;": "\u2A23", - "plusb;": "\u229E", - "pluscir;": "\u2A22", - "plusdo;": "\u2214", - "plusdu;": "\u2A25", - "pluse;": "\u2A72", - "plusmn": "\u00B1", - "plusmn;": "\u00B1", - "plussim;": "\u2A26", - "plustwo;": "\u2A27", - "pm;": "\u00B1", - "pointint;": "\u2A15", - "popf;": "\u1D561", - "pound": "\u00A3", - "pound;": "\u00A3", - "pr;": "\u227A", - "prE;": "\u2AB3", - "prap;": "\u2AB7", - "prcue;": "\u227C", - "pre;": "\u2AAF", - "prec;": "\u227A", - "precapprox;": "\u2AB7", - "preccurlyeq;": "\u227C", - "preceq;": "\u2AAF", - "precnapprox;": "\u2AB9", - "precneqq;": "\u2AB5", - "precnsim;": "\u22E8", - "precsim;": "\u227E", - "prime;": "\u2032", - "primes;": "\u2119", - "prnE;": "\u2AB5", - "prnap;": "\u2AB9", - "prnsim;": "\u22E8", - "prod;": "\u220F", - "profalar;": "\u232E", - "profline;": "\u2312", - "profsurf;": "\u2313", - "prop;": "\u221D", - "propto;": "\u221D", - "prsim;": "\u227E", - "prurel;": "\u22B0", - "pscr;": "\u1D4C5", - "psi;": "\u03C8", - "puncsp;": "\u2008", - "qfr;": "\u1D52E", - "qint;": "\u2A0C", - "qopf;": "\u1D562", - "qprime;": "\u2057", - "qscr;": "\u1D4C6", - "quaternions;": "\u210D", - "quatint;": "\u2A16", - "quest;": "?", - "questeq;": "\u225F", - "quot": "\u0022", - "quot;": "\u0022", - "rAarr;": "\u21DB", - "rArr;": "\u21D2", - "rAtail;": "\u291C", - "rBarr;": "\u290F", - "rHar;": "\u2964", - "race;": "\u223D\u0331", - "racute;": "\u0155", - "radic;": "\u221A", - "raemptyv;": "\u29B3", - "rang;": "\u27E9", - "rangd;": "\u2992", - "range;": "\u29A5", - "rangle;": "\u27E9", - "raquo": "\u00BB", - "raquo;": "\u00BB", - "rarr;": "\u2192", - "rarrap;": "\u2975", - "rarrb;": "\u21E5", - "rarrbfs;": "\u2920", - "rarrc;": "\u2933", - "rarrfs;": "\u291E", - "rarrhk;": "\u21AA", - "rarrlp;": "\u21AC", - "rarrpl;": "\u2945", - "rarrsim;": "\u2974", - "rarrtl;": "\u21A3", - "rarrw;": "\u219D", - "ratail;": "\u291A", - "ratio;": "\u2236", - "rationals;": "\u211A", - "rbarr;": "\u290D", - "rbbrk;": "\u2773", - "rbrace;": "}", - "rbrack;": "]", - "rbrke;": "\u298C", - "rbrksld;": "\u298E", - "rbrkslu;": "\u2990", - "rcaron;": "\u0159", - "rcedil;": "\u0157", - "rceil;": "\u2309", - "rcub;": "}", - "rcy;": "\u0440", - "rdca;": "\u2937", - "rdldhar;": "\u2969", - "rdquo;": "\u201D", - "rdquor;": "\u201D", - "rdsh;": "\u21B3", - "real;": "\u211C", - "realine;": "\u211B", - "realpart;": "\u211C", - "reals;": "\u211D", - "rect;": "\u25AD", - "reg": "\u00AE", - "reg;": "\u00AE", - "rfisht;": "\u297D", - "rfloor;": "\u230B", - "rfr;": "\u1D52F", - "rhard;": "\u21C1", - "rharu;": "\u21C0", - "rharul;": "\u296C", - "rho;": "\u03C1", - "rhov;": "\u03F1", - "rightarrow;": "\u2192", - "rightarrowtail;": "\u21A3", - "rightharpoondown;": "\u21C1", - "rightharpoonup;": "\u21C0", - "rightleftarrows;": "\u21C4", - "rightleftharpoons;": "\u21CC", - "rightrightarrows;": "\u21C9", - "rightsquigarrow;": "\u219D", - "rightthreetimes;": "\u22CC", - "ring;": "\u02DA", - "risingdotseq;": "\u2253", - "rlarr;": "\u21C4", - "rlhar;": "\u21CC", - "rlm;": "\u200F", - "rmoust;": "\u23B1", - "rmoustache;": "\u23B1", - "rnmid;": "\u2AEE", - "roang;": "\u27ED", - "roarr;": "\u21FE", - "robrk;": "\u27E7", - "ropar;": "\u2986", - "ropf;": "\u1D563", - "roplus;": "\u2A2E", - "rotimes;": "\u2A35", - "rpar;": ")", - "rpargt;": "\u2994", - "rppolint;": "\u2A12", - "rrarr;": "\u21C9", - "rsaquo;": "\u203A", - "rscr;": "\u1D4C7", - "rsh;": "\u21B1", - "rsqb;": "]", - "rsquo;": "\u2019", - "rsquor;": "\u2019", - "rthree;": "\u22CC", - "rtimes;": "\u22CA", - "rtri;": "\u25B9", - "rtrie;": "\u22B5", - "rtrif;": "\u25B8", - "rtriltri;": "\u29CE", - "ruluhar;": "\u2968", - "rx;": "\u211E", - "sacute;": "\u015B", - "sbquo;": "\u201A", - "sc;": "\u227B", - "scE;": "\u2AB4", - "scap;": "\u2AB8", - "scaron;": "\u0161", - "sccue;": "\u227D", - "sce;": "\u2AB0", - "scedil;": "\u015F", - "scirc;": "\u015D", - "scnE;": "\u2AB6", - "scnap;": "\u2ABA", - "scnsim;": "\u22E9", - "scpolint;": "\u2A13", - "scsim;": "\u227F", - "scy;": "\u0441", - "sdot;": "\u22C5", - "sdotb;": "\u22A1", - "sdote;": "\u2A66", - "seArr;": "\u21D8", - "searhk;": "\u2925", - "searr;": "\u2198", - "searrow;": "\u2198", - "sect": "\u00A7", - "sect;": "\u00A7", - "semi;": ";", - "seswar;": "\u2929", - "setminus;": "\u2216", - "setmn;": "\u2216", - "sext;": "\u2736", - "sfr;": "\u1D530", - "sfrown;": "\u2322", - "sharp;": "\u266F", - "shchcy;": "\u0449", - "shcy;": "\u0448", - "shortmid;": "\u2223", - "shortparallel;": "\u2225", - "shy": "\u00AD", - "shy;": "\u00AD", - "sigma;": "\u03C3", - "sigmaf;": "\u03C2", - "sigmav;": "\u03C2", - "sim;": "\u223C", - "simdot;": "\u2A6A", - "sime;": "\u2243", - "simeq;": "\u2243", - "simg;": "\u2A9E", - "simgE;": "\u2AA0", - "siml;": "\u2A9D", - "simlE;": "\u2A9F", - "simne;": "\u2246", - "simplus;": "\u2A24", - "simrarr;": "\u2972", - "slarr;": "\u2190", - "smallsetminus;": "\u2216", - "smashp;": "\u2A33", - "smeparsl;": "\u29E4", - "smid;": "\u2223", - "smile;": "\u2323", - "smt;": "\u2AAA", - "smte;": "\u2AAC", - "smtes;": "\u2AAC\uFE00", - "softcy;": "\u044C", - "sol;": "/", - "solb;": "\u29C4", - "solbar;": "\u233F", - "sopf;": "\u1D564", - "spades;": "\u2660", - "spadesuit;": "\u2660", - "spar;": "\u2225", - "sqcap;": "\u2293", - "sqcaps;": "\u2293\uFE00", - "sqcup;": "\u2294", - "sqcups;": "\u2294\uFE00", - "sqsub;": "\u228F", - "sqsube;": "\u2291", - "sqsubset;": "\u228F", - "sqsubseteq;": "\u2291", - "sqsup;": "\u2290", - "sqsupe;": "\u2292", - "sqsupset;": "\u2290", - "sqsupseteq;": "\u2292", - "squ;": "\u25A1", - "square;": "\u25A1", - "squarf;": "\u25AA", - "squf;": "\u25AA", - "srarr;": "\u2192", - "sscr;": "\u1D4C8", - "ssetmn;": "\u2216", - "ssmile;": "\u2323", - "sstarf;": "\u22C6", - "star;": "\u2606", - "starf;": "\u2605", - "straightepsilon;": "\u03F5", - "straightphi;": "\u03D5", - "strns;": "\u00AF", - "sub;": "\u2282", - "subE;": "\u2AC5", - "subdot;": "\u2ABD", - "sube;": "\u2286", - "subedot;": "\u2AC3", - "submult;": "\u2AC1", - "subnE;": "\u2ACB", - "subne;": "\u228A", - "subplus;": "\u2ABF", - "subrarr;": "\u2979", - "subset;": "\u2282", - "subseteq;": "\u2286", - "subseteqq;": "\u2AC5", - "subsetneq;": "\u228A", - "subsetneqq;": "\u2ACB", - "subsim;": "\u2AC7", - "subsub;": "\u2AD5", - "subsup;": "\u2AD3", - "succ;": "\u227B", - "succapprox;": "\u2AB8", - "succcurlyeq;": "\u227D", - "succeq;": "\u2AB0", - "succnapprox;": "\u2ABA", - "succneqq;": "\u2AB6", - "succnsim;": "\u22E9", - "succsim;": "\u227F", - "sum;": "\u2211", - "sung;": "\u266A", - "sup1": "\u00B9", - "sup1;": "\u00B9", - "sup2": "\u00B2", - "sup2;": "\u00B2", - "sup3": "\u00B3", - "sup3;": "\u00B3", - "sup;": "\u2283", - "supE;": "\u2AC6", - "supdot;": "\u2ABE", - "supdsub;": "\u2AD8", - "supe;": "\u2287", - "supedot;": "\u2AC4", - "suphsol;": "\u27C9", - "suphsub;": "\u2AD7", - "suplarr;": "\u297B", - "supmult;": "\u2AC2", - "supnE;": "\u2ACC", - "supne;": "\u228B", - "supplus;": "\u2AC0", - "supset;": "\u2283", - "supseteq;": "\u2287", - "supseteqq;": "\u2AC6", - "supsetneq;": "\u228B", - "supsetneqq;": "\u2ACC", - "supsim;": "\u2AC8", - "supsub;": "\u2AD4", - "supsup;": "\u2AD6", - "swArr;": "\u21D9", - "swarhk;": "\u2926", - "swarr;": "\u2199", - "swarrow;": "\u2199", - "swnwar;": "\u292A", - "szlig": "\u00DF", - "szlig;": "\u00DF", - "target;": "\u2316", - "tau;": "\u03C4", - "tbrk;": "\u23B4", - "tcaron;": "\u0165", - "tcedil;": "\u0163", - "tcy;": "\u0442", - "tdot;": "\u20DB", - "telrec;": "\u2315", - "tfr;": "\u1D531", - "there4;": "\u2234", - "therefore;": "\u2234", - "theta;": "\u03B8", - "thetasym;": "\u03D1", - "thetav;": "\u03D1", - "thickapprox;": "\u2248", - "thicksim;": "\u223C", - "thinsp;": "\u2009", - "thkap;": "\u2248", - "thksim;": "\u223C", - "thorn": "\u00FE", - "thorn;": "\u00FE", - "tilde;": "\u02DC", - "times": "\u00D7", - "times;": "\u00D7", - "timesb;": "\u22A0", - "timesbar;": "\u2A31", - "timesd;": "\u2A30", - "tint;": "\u222D", - "toea;": "\u2928", - "top;": "\u22A4", - "topbot;": "\u2336", - "topcir;": "\u2AF1", - "topf;": "\u1D565", - "topfork;": "\u2ADA", - "tosa;": "\u2929", - "tprime;": "\u2034", - "trade;": "\u2122", - "triangle;": "\u25B5", - "triangledown;": "\u25BF", - "triangleleft;": "\u25C3", - "trianglelefteq;": "\u22B4", - "triangleq;": "\u225C", - "triangleright;": "\u25B9", - "trianglerighteq;": "\u22B5", - "tridot;": "\u25EC", - "trie;": "\u225C", - "triminus;": "\u2A3A", - "triplus;": "\u2A39", - "trisb;": "\u29CD", - "tritime;": "\u2A3B", - "trpezium;": "\u23E2", - "tscr;": "\u1D4C9", - "tscy;": "\u0446", - "tshcy;": "\u045B", - "tstrok;": "\u0167", - "twixt;": "\u226C", - "twoheadleftarrow;": "\u219E", - "twoheadrightarrow;": "\u21A0", - "uArr;": "\u21D1", - "uHar;": "\u2963", - "uacute": "\u00FA", - "uacute;": "\u00FA", - "uarr;": "\u2191", - "ubrcy;": "\u045E", - "ubreve;": "\u016D", - "ucirc": "\u00FB", - "ucirc;": "\u00FB", - "ucy;": "\u0443", - "udarr;": "\u21C5", - "udblac;": "\u0171", - "udhar;": "\u296E", - "ufisht;": "\u297E", - "ufr;": "\u1D532", - "ugrave": "\u00F9", - "ugrave;": "\u00F9", - "uharl;": "\u21BF", - "uharr;": "\u21BE", - "uhblk;": "\u2580", - "ulcorn;": "\u231C", - "ulcorner;": "\u231C", - "ulcrop;": "\u230F", - "ultri;": "\u25F8", - "umacr;": "\u016B", - "uml": "\u00A8", - "uml;": "\u00A8", - "uogon;": "\u0173", - "uopf;": "\u1D566", - "uparrow;": "\u2191", - "updownarrow;": "\u2195", - "upharpoonleft;": "\u21BF", - "upharpoonright;": "\u21BE", - "uplus;": "\u228E", - "upsi;": "\u03C5", - "upsih;": "\u03D2", - "upsilon;": "\u03C5", - "upuparrows;": "\u21C8", - "urcorn;": "\u231D", - "urcorner;": "\u231D", - "urcrop;": "\u230E", - "uring;": "\u016F", - "urtri;": "\u25F9", - "uscr;": "\u1D4CA", - "utdot;": "\u22F0", - "utilde;": "\u0169", - "utri;": "\u25B5", - "utrif;": "\u25B4", - "uuarr;": "\u21C8", - "uuml": "\u00FC", - "uuml;": "\u00FC", - "uwangle;": "\u29A7", - "vArr;": "\u21D5", - "vBar;": "\u2AE8", - "vBarv;": "\u2AE9", - "vDash;": "\u22A8", - "vangrt;": "\u299C", - "varepsilon;": "\u03F5", - "varkappa;": "\u03F0", - "varnothing;": "\u2205", - "varphi;": "\u03D5", - "varpi;": "\u03D6", - "varpropto;": "\u221D", - "varr;": "\u2195", - "varrho;": "\u03F1", - "varsigma;": "\u03C2", - "varsubsetneq;": "\u228A\uFE00", - "varsubsetneqq;": "\u2ACB\uFE00", - "varsupsetneq;": "\u228B\uFE00", - "varsupsetneqq;": "\u2ACC\uFE00", - "vartheta;": "\u03D1", - "vartriangleleft;": "\u22B2", - "vartriangleright;": "\u22B3", - "vcy;": "\u0432", - "vdash;": "\u22A2", - "vee;": "\u2228", - "veebar;": "\u22BB", - "veeeq;": "\u225A", - "vellip;": "\u22EE", - "verbar;": "|", - "vert;": "|", - "vfr;": "\u1D533", - "vltri;": "\u22B2", - "vnsub;": "\u2282\u20D2", - "vnsup;": "\u2283\u20D2", - "vopf;": "\u1D567", - "vprop;": "\u221D", - "vrtri;": "\u22B3", - "vscr;": "\u1D4CB", - "vsubnE;": "\u2ACB\uFE00", - "vsubne;": "\u228A\uFE00", - "vsupnE;": "\u2ACC\uFE00", - "vsupne;": "\u228B\uFE00", - "vzigzag;": "\u299A", - "wcirc;": "\u0175", - "wedbar;": "\u2A5F", - "wedge;": "\u2227", - "wedgeq;": "\u2259", - "weierp;": "\u2118", - "wfr;": "\u1D534", - "wopf;": "\u1D568", - "wp;": "\u2118", - "wr;": "\u2240", - "wreath;": "\u2240", - "wscr;": "\u1D4CC", - "xcap;": "\u22C2", - "xcirc;": "\u25EF", - "xcup;": "\u22C3", - "xdtri;": "\u25BD", - "xfr;": "\u1D535", - "xhArr;": "\u27FA", - "xharr;": "\u27F7", - "xi;": "\u03BE", - "xlArr;": "\u27F8", - "xlarr;": "\u27F5", - "xmap;": "\u27FC", - "xnis;": "\u22FB", - "xodot;": "\u2A00", - "xopf;": "\u1D569", - "xoplus;": "\u2A01", - "xotime;": "\u2A02", - "xrArr;": "\u27F9", - "xrarr;": "\u27F6", - "xscr;": "\u1D4CD", - "xsqcup;": "\u2A06", - "xuplus;": "\u2A04", - "xutri;": "\u25B3", - "xvee;": "\u22C1", - "xwedge;": "\u22C0", - "yacute": "\u00FD", - "yacute;": "\u00FD", - "yacy;": "\u044F", - "ycirc;": "\u0177", - "ycy;": "\u044B", - "yen": "\u00A5", - "yen;": "\u00A5", - "yfr;": "\u1D536", - "yicy;": "\u0457", - "yopf;": "\u1D56A", - "yscr;": "\u1D4CE", - "yucy;": "\u044E", - "yuml": "\u00FF", - "yuml;": "\u00FF", - "zacute;": "\u017A", - "zcaron;": "\u017E", - "zcy;": "\u0437", - "zdot;": "\u017C", - "zeetrf;": "\u2128", - "zeta;": "\u03B6", - "zfr;": "\u1D537", - "zhcy;": "\u0436", - "zigrarr;": "\u21DD", - "zopf;": "\u1D56B", - "zscr;": "\u1D4CF", - "zwj;": "\u200D", - "zwnj;": "\u200C" + "AElig": "\u00C6", + "AElig;": "\u00C6", + "AMP": "&", + "AMP;": "&", + "Aacute": "\u00C1", + "Aacute;": "\u00C1", + "Abreve;": "\u0102", + "Acirc": "\u00C2", + "Acirc;": "\u00C2", + "Acy;": "\u0410", + "Afr;": "\u1D504", + "Agrave": "\u00C0", + "Agrave;": "\u00C0", + "Alpha;": "\u0391", + "Amacr;": "\u0100", + "And;": "\u2A53", + "Aogon;": "\u0104", + "Aopf;": "\u1D538", + "ApplyFunction;": "\u2061", + "Aring": "\u00C5", + "Aring;": "\u00C5", + "Ascr;": "\u1D49C", + "Assign;": "\u2254", + "Atilde": "\u00C3", + "Atilde;": "\u00C3", + "Auml": "\u00C4", + "Auml;": "\u00C4", + "Backslash;": "\u2216", + "Barv;": "\u2AE7", + "Barwed;": "\u2306", + "Bcy;": "\u0411", + "Because;": "\u2235", + "Bernoullis;": "\u212C", + "Beta;": "\u0392", + "Bfr;": "\u1D505", + "Bopf;": "\u1D539", + "Breve;": "\u02D8", + "Bscr;": "\u212C", + "Bumpeq;": "\u224E", + "CHcy;": "\u0427", + "COPY": "\u00A9", + "COPY;": "\u00A9", + "Cacute;": "\u0106", + "Cap;": "\u22D2", + "CapitalDifferentialD;": "\u2145", + "Cayleys;": "\u212D", + "Ccaron;": "\u010C", + "Ccedil": "\u00C7", + "Ccedil;": "\u00C7", + "Ccirc;": "\u0108", + "Cconint;": "\u2230", + "Cdot;": "\u010A", + "Cedilla;": "\u00B8", + "CenterDot;": "\u00B7", + "Cfr;": "\u212D", + "Chi;": "\u03A7", + "CircleDot;": "\u2299", + "CircleMinus;": "\u2296", + "CirclePlus;": "\u2295", + "CircleTimes;": "\u2297", + "ClockwiseContourIntegral;": "\u2232", + "CloseCurlyDoubleQuote;": "\u201D", + "CloseCurlyQuote;": "\u2019", + "Colon;": "\u2237", + "Colone;": "\u2A74", + "Congruent;": "\u2261", + "Conint;": "\u222F", + "ContourIntegral;": "\u222E", + "Copf;": "\u2102", + "Coproduct;": "\u2210", + "CounterClockwiseContourIntegral;": "\u2233", + "Cross;": "\u2A2F", + "Cscr;": "\u1D49E", + "Cup;": "\u22D3", + "CupCap;": "\u224D", + "DD;": "\u2145", + "DDotrahd;": "\u2911", + "DJcy;": "\u0402", + "DScy;": "\u0405", + "DZcy;": "\u040F", + "Dagger;": "\u2021", + "Darr;": "\u21A1", + "Dashv;": "\u2AE4", + "Dcaron;": "\u010E", + "Dcy;": "\u0414", + "Del;": "\u2207", + "Delta;": "\u0394", + "Dfr;": "\u1D507", + "DiacriticalAcute;": "\u00B4", + "DiacriticalDot;": "\u02D9", + "DiacriticalDoubleAcute;": "\u02DD", + "DiacriticalGrave;": "`", + "DiacriticalTilde;": "\u02DC", + "Diamond;": "\u22C4", + "DifferentialD;": "\u2146", + "Dopf;": "\u1D53B", + "Dot;": "\u00A8", + "DotDot;": "\u20DC", + "DotEqual;": "\u2250", + "DoubleContourIntegral;": "\u222F", + "DoubleDot;": "\u00A8", + "DoubleDownArrow;": "\u21D3", + "DoubleLeftArrow;": "\u21D0", + "DoubleLeftRightArrow;": "\u21D4", + "DoubleLeftTee;": "\u2AE4", + "DoubleLongLeftArrow;": "\u27F8", + "DoubleLongLeftRightArrow;": "\u27FA", + "DoubleLongRightArrow;": "\u27F9", + "DoubleRightArrow;": "\u21D2", + "DoubleRightTee;": "\u22A8", + "DoubleUpArrow;": "\u21D1", + "DoubleUpDownArrow;": "\u21D5", + "DoubleVerticalBar;": "\u2225", + "DownArrow;": "\u2193", + "DownArrowBar;": "\u2913", + "DownArrowUpArrow;": "\u21F5", + "DownBreve;": "\u0311", + "DownLeftRightVector;": "\u2950", + "DownLeftTeeVector;": "\u295E", + "DownLeftVector;": "\u21BD", + "DownLeftVectorBar;": "\u2956", + "DownRightTeeVector;": "\u295F", + "DownRightVector;": "\u21C1", + "DownRightVectorBar;": "\u2957", + "DownTee;": "\u22A4", + "DownTeeArrow;": "\u21A7", + "Downarrow;": "\u21D3", + "Dscr;": "\u1D49F", + "Dstrok;": "\u0110", + "ENG;": "\u014A", + "ETH": "\u00D0", + "ETH;": "\u00D0", + "Eacute": "\u00C9", + "Eacute;": "\u00C9", + "Ecaron;": "\u011A", + "Ecirc": "\u00CA", + "Ecirc;": "\u00CA", + "Ecy;": "\u042D", + "Edot;": "\u0116", + "Efr;": "\u1D508", + "Egrave": "\u00C8", + "Egrave;": "\u00C8", + "Element;": "\u2208", + "Emacr;": "\u0112", + "EmptySmallSquare;": "\u25FB", + "EmptyVerySmallSquare;": "\u25AB", + "Eogon;": "\u0118", + "Eopf;": "\u1D53C", + "Epsilon;": "\u0395", + "Equal;": "\u2A75", + "EqualTilde;": "\u2242", + "Equilibrium;": "\u21CC", + "Escr;": "\u2130", + "Esim;": "\u2A73", + "Eta;": "\u0397", + "Euml": "\u00CB", + "Euml;": "\u00CB", + "Exists;": "\u2203", + "ExponentialE;": "\u2147", + "Fcy;": "\u0424", + "Ffr;": "\u1D509", + "FilledSmallSquare;": "\u25FC", + "FilledVerySmallSquare;": "\u25AA", + "Fopf;": "\u1D53D", + "ForAll;": "\u2200", + "Fouriertrf;": "\u2131", + "Fscr;": "\u2131", + "GJcy;": "\u0403", + "GT": ">", + "GT;": ">", + "Gamma;": "\u0393", + "Gammad;": "\u03DC", + "Gbreve;": "\u011E", + "Gcedil;": "\u0122", + "Gcirc;": "\u011C", + "Gcy;": "\u0413", + "Gdot;": "\u0120", + "Gfr;": "\u1D50A", + "Gg;": "\u22D9", + "Gopf;": "\u1D53E", + "GreaterEqual;": "\u2265", + "GreaterEqualLess;": "\u22DB", + "GreaterFullEqual;": "\u2267", + "GreaterGreater;": "\u2AA2", + "GreaterLess;": "\u2277", + "GreaterSlantEqual;": "\u2A7E", + "GreaterTilde;": "\u2273", + "Gscr;": "\u1D4A2", + "Gt;": "\u226B", + "HARDcy;": "\u042A", + "Hacek;": "\u02C7", + "Hat;": "^", + "Hcirc;": "\u0124", + "Hfr;": "\u210C", + "HilbertSpace;": "\u210B", + "Hopf;": "\u210D", + "HorizontalLine;": "\u2500", + "Hscr;": "\u210B", + "Hstrok;": "\u0126", + "HumpDownHump;": "\u224E", + "HumpEqual;": "\u224F", + "IEcy;": "\u0415", + "IJlig;": "\u0132", + "IOcy;": "\u0401", + "Iacute": "\u00CD", + "Iacute;": "\u00CD", + "Icirc": "\u00CE", + "Icirc;": "\u00CE", + "Icy;": "\u0418", + "Idot;": "\u0130", + "Ifr;": "\u2111", + "Igrave": "\u00CC", + "Igrave;": "\u00CC", + "Im;": "\u2111", + "Imacr;": "\u012A", + "ImaginaryI;": "\u2148", + "Implies;": "\u21D2", + "Int;": "\u222C", + "Integral;": "\u222B", + "Intersection;": "\u22C2", + "InvisibleComma;": "\u2063", + "InvisibleTimes;": "\u2062", + "Iogon;": "\u012E", + "Iopf;": "\u1D540", + "Iota;": "\u0399", + "Iscr;": "\u2110", + "Itilde;": "\u0128", + "Iukcy;": "\u0406", + "Iuml": "\u00CF", + "Iuml;": "\u00CF", + "Jcirc;": "\u0134", + "Jcy;": "\u0419", + "Jfr;": "\u1D50D", + "Jopf;": "\u1D541", + "Jscr;": "\u1D4A5", + "Jsercy;": "\u0408", + "Jukcy;": "\u0404", + "KHcy;": "\u0425", + "KJcy;": "\u040C", + "Kappa;": "\u039A", + "Kcedil;": "\u0136", + "Kcy;": "\u041A", + "Kfr;": "\u1D50E", + "Kopf;": "\u1D542", + "Kscr;": "\u1D4A6", + "LJcy;": "\u0409", + "LT": "<", + "LT;": "<", + "Lacute;": "\u0139", + "Lambda;": "\u039B", + "Lang;": "\u27EA", + "Laplacetrf;": "\u2112", + "Larr;": "\u219E", + "Lcaron;": "\u013D", + "Lcedil;": "\u013B", + "Lcy;": "\u041B", + "LeftAngleBracket;": "\u27E8", + "LeftArrow;": "\u2190", + "LeftArrowBar;": "\u21E4", + "LeftArrowRightArrow;": "\u21C6", + "LeftCeiling;": "\u2308", + "LeftDoubleBracket;": "\u27E6", + "LeftDownTeeVector;": "\u2961", + "LeftDownVector;": "\u21C3", + "LeftDownVectorBar;": "\u2959", + "LeftFloor;": "\u230A", + "LeftRightArrow;": "\u2194", + "LeftRightVector;": "\u294E", + "LeftTee;": "\u22A3", + "LeftTeeArrow;": "\u21A4", + "LeftTeeVector;": "\u295A", + "LeftTriangle;": "\u22B2", + "LeftTriangleBar;": "\u29CF", + "LeftTriangleEqual;": "\u22B4", + "LeftUpDownVector;": "\u2951", + "LeftUpTeeVector;": "\u2960", + "LeftUpVector;": "\u21BF", + "LeftUpVectorBar;": "\u2958", + "LeftVector;": "\u21BC", + "LeftVectorBar;": "\u2952", + "Leftarrow;": "\u21D0", + "Leftrightarrow;": "\u21D4", + "LessEqualGreater;": "\u22DA", + "LessFullEqual;": "\u2266", + "LessGreater;": "\u2276", + "LessLess;": "\u2AA1", + "LessSlantEqual;": "\u2A7D", + "LessTilde;": "\u2272", + "Lfr;": "\u1D50F", + "Ll;": "\u22D8", + "Lleftarrow;": "\u21DA", + "Lmidot;": "\u013F", + "LongLeftArrow;": "\u27F5", + "LongLeftRightArrow;": "\u27F7", + "LongRightArrow;": "\u27F6", + "Longleftarrow;": "\u27F8", + "Longleftrightarrow;": "\u27FA", + "Longrightarrow;": "\u27F9", + "Lopf;": "\u1D543", + "LowerLeftArrow;": "\u2199", + "LowerRightArrow;": "\u2198", + "Lscr;": "\u2112", + "Lsh;": "\u21B0", + "Lstrok;": "\u0141", + "Lt;": "\u226A", + "Map;": "\u2905", + "Mcy;": "\u041C", + "MediumSpace;": "\u205F", + "Mellintrf;": "\u2133", + "Mfr;": "\u1D510", + "MinusPlus;": "\u2213", + "Mopf;": "\u1D544", + "Mscr;": "\u2133", + "Mu;": "\u039C", + "NJcy;": "\u040A", + "Nacute;": "\u0143", + "Ncaron;": "\u0147", + "Ncedil;": "\u0145", + "Ncy;": "\u041D", + "NegativeMediumSpace;": "\u200B", + "NegativeThickSpace;": "\u200B", + "NegativeThinSpace;": "\u200B", + "NegativeVeryThinSpace;": "\u200B", + "NestedGreaterGreater;": "\u226B", + "NestedLessLess;": "\u226A", + "NewLine;": "\u000A", + "Nfr;": "\u1D511", + "NoBreak;": "\u2060", + "NonBreakingSpace;": "\u00A0", + "Nopf;": "\u2115", + "Not;": "\u2AEC", + "NotCongruent;": "\u2262", + "NotCupCap;": "\u226D", + "NotDoubleVerticalBar;": "\u2226", + "NotElement;": "\u2209", + "NotEqual;": "\u2260", + "NotEqualTilde;": "\u2242\u0338", + "NotExists;": "\u2204", + "NotGreater;": "\u226F", + "NotGreaterEqual;": "\u2271", + "NotGreaterFullEqual;": "\u2267\u0338", + "NotGreaterGreater;": "\u226B\u0338", + "NotGreaterLess;": "\u2279", + "NotGreaterSlantEqual;": "\u2A7E\u0338", + "NotGreaterTilde;": "\u2275", + "NotHumpDownHump;": "\u224E\u0338", + "NotHumpEqual;": "\u224F\u0338", + "NotLeftTriangle;": "\u22EA", + "NotLeftTriangleBar;": "\u29CF\u0338", + "NotLeftTriangleEqual;": "\u22EC", + "NotLess;": "\u226E", + "NotLessEqual;": "\u2270", + "NotLessGreater;": "\u2278", + "NotLessLess;": "\u226A\u0338", + "NotLessSlantEqual;": "\u2A7D\u0338", + "NotLessTilde;": "\u2274", + "NotNestedGreaterGreater;": "\u2AA2\u0338", + "NotNestedLessLess;": "\u2AA1\u0338", + "NotPrecedes;": "\u2280", + "NotPrecedesEqual;": "\u2AAF\u0338", + "NotPrecedesSlantEqual;": "\u22E0", + "NotReverseElement;": "\u220C", + "NotRightTriangle;": "\u22EB", + "NotRightTriangleBar;": "\u29D0\u0338", + "NotRightTriangleEqual;": "\u22ED", + "NotSquareSubset;": "\u228F\u0338", + "NotSquareSubsetEqual;": "\u22E2", + "NotSquareSuperset;": "\u2290\u0338", + "NotSquareSupersetEqual;": "\u22E3", + "NotSubset;": "\u2282\u20D2", + "NotSubsetEqual;": "\u2288", + "NotSucceeds;": "\u2281", + "NotSucceedsEqual;": "\u2AB0\u0338", + "NotSucceedsSlantEqual;": "\u22E1", + "NotSucceedsTilde;": "\u227F\u0338", + "NotSuperset;": "\u2283\u20D2", + "NotSupersetEqual;": "\u2289", + "NotTilde;": "\u2241", + "NotTildeEqual;": "\u2244", + "NotTildeFullEqual;": "\u2247", + "NotTildeTilde;": "\u2249", + "NotVerticalBar;": "\u2224", + "Nscr;": "\u1D4A9", + "Ntilde": "\u00D1", + "Ntilde;": "\u00D1", + "Nu;": "\u039D", + "OElig;": "\u0152", + "Oacute": "\u00D3", + "Oacute;": "\u00D3", + "Ocirc": "\u00D4", + "Ocirc;": "\u00D4", + "Ocy;": "\u041E", + "Odblac;": "\u0150", + "Ofr;": "\u1D512", + "Ograve": "\u00D2", + "Ograve;": "\u00D2", + "Omacr;": "\u014C", + "Omega;": "\u03A9", + "Omicron;": "\u039F", + "Oopf;": "\u1D546", + "OpenCurlyDoubleQuote;": "\u201C", + "OpenCurlyQuote;": "\u2018", + "Or;": "\u2A54", + "Oscr;": "\u1D4AA", + "Oslash": "\u00D8", + "Oslash;": "\u00D8", + "Otilde": "\u00D5", + "Otilde;": "\u00D5", + "Otimes;": "\u2A37", + "Ouml": "\u00D6", + "Ouml;": "\u00D6", + "OverBar;": "\u203E", + "OverBrace;": "\u23DE", + "OverBracket;": "\u23B4", + "OverParenthesis;": "\u23DC", + "PartialD;": "\u2202", + "Pcy;": "\u041F", + "Pfr;": "\u1D513", + "Phi;": "\u03A6", + "Pi;": "\u03A0", + "PlusMinus;": "\u00B1", + "Poincareplane;": "\u210C", + "Popf;": "\u2119", + "Pr;": "\u2ABB", + "Precedes;": "\u227A", + "PrecedesEqual;": "\u2AAF", + "PrecedesSlantEqual;": "\u227C", + "PrecedesTilde;": "\u227E", + "Prime;": "\u2033", + "Product;": "\u220F", + "Proportion;": "\u2237", + "Proportional;": "\u221D", + "Pscr;": "\u1D4AB", + "Psi;": "\u03A8", + "QUOT": "\u0022", + "QUOT;": "\u0022", + "Qfr;": "\u1D514", + "Qopf;": "\u211A", + "Qscr;": "\u1D4AC", + "RBarr;": "\u2910", + "REG": "\u00AE", + "REG;": "\u00AE", + "Racute;": "\u0154", + "Rang;": "\u27EB", + "Rarr;": "\u21A0", + "Rarrtl;": "\u2916", + "Rcaron;": "\u0158", + "Rcedil;": "\u0156", + "Rcy;": "\u0420", + "Re;": "\u211C", + "ReverseElement;": "\u220B", + "ReverseEquilibrium;": "\u21CB", + "ReverseUpEquilibrium;": "\u296F", + "Rfr;": "\u211C", + "Rho;": "\u03A1", + "RightAngleBracket;": "\u27E9", + "RightArrow;": "\u2192", + "RightArrowBar;": "\u21E5", + "RightArrowLeftArrow;": "\u21C4", + "RightCeiling;": "\u2309", + "RightDoubleBracket;": "\u27E7", + "RightDownTeeVector;": "\u295D", + "RightDownVector;": "\u21C2", + "RightDownVectorBar;": "\u2955", + "RightFloor;": "\u230B", + "RightTee;": "\u22A2", + "RightTeeArrow;": "\u21A6", + "RightTeeVector;": "\u295B", + "RightTriangle;": "\u22B3", + "RightTriangleBar;": "\u29D0", + "RightTriangleEqual;": "\u22B5", + "RightUpDownVector;": "\u294F", + "RightUpTeeVector;": "\u295C", + "RightUpVector;": "\u21BE", + "RightUpVectorBar;": "\u2954", + "RightVector;": "\u21C0", + "RightVectorBar;": "\u2953", + "Rightarrow;": "\u21D2", + "Ropf;": "\u211D", + "RoundImplies;": "\u2970", + "Rrightarrow;": "\u21DB", + "Rscr;": "\u211B", + "Rsh;": "\u21B1", + "RuleDelayed;": "\u29F4", + "SHCHcy;": "\u0429", + "SHcy;": "\u0428", + "SOFTcy;": "\u042C", + "Sacute;": "\u015A", + "Sc;": "\u2ABC", + "Scaron;": "\u0160", + "Scedil;": "\u015E", + "Scirc;": "\u015C", + "Scy;": "\u0421", + "Sfr;": "\u1D516", + "ShortDownArrow;": "\u2193", + "ShortLeftArrow;": "\u2190", + "ShortRightArrow;": "\u2192", + "ShortUpArrow;": "\u2191", + "Sigma;": "\u03A3", + "SmallCircle;": "\u2218", + "Sopf;": "\u1D54A", + "Sqrt;": "\u221A", + "Square;": "\u25A1", + "SquareIntersection;": "\u2293", + "SquareSubset;": "\u228F", + "SquareSubsetEqual;": "\u2291", + "SquareSuperset;": "\u2290", + "SquareSupersetEqual;": "\u2292", + "SquareUnion;": "\u2294", + "Sscr;": "\u1D4AE", + "Star;": "\u22C6", + "Sub;": "\u22D0", + "Subset;": "\u22D0", + "SubsetEqual;": "\u2286", + "Succeeds;": "\u227B", + "SucceedsEqual;": "\u2AB0", + "SucceedsSlantEqual;": "\u227D", + "SucceedsTilde;": "\u227F", + "SuchThat;": "\u220B", + "Sum;": "\u2211", + "Sup;": "\u22D1", + "Superset;": "\u2283", + "SupersetEqual;": "\u2287", + "Supset;": "\u22D1", + "THORN": "\u00DE", + "THORN;": "\u00DE", + "TRADE;": "\u2122", + "TSHcy;": "\u040B", + "TScy;": "\u0426", + "Tab;": "\u0009", + "Tau;": "\u03A4", + "Tcaron;": "\u0164", + "Tcedil;": "\u0162", + "Tcy;": "\u0422", + "Tfr;": "\u1D517", + "Therefore;": "\u2234", + "Theta;": "\u0398", + "ThickSpace;": "\u205F\u200A", + "ThinSpace;": "\u2009", + "Tilde;": "\u223C", + "TildeEqual;": "\u2243", + "TildeFullEqual;": "\u2245", + "TildeTilde;": "\u2248", + "Topf;": "\u1D54B", + "TripleDot;": "\u20DB", + "Tscr;": "\u1D4AF", + "Tstrok;": "\u0166", + "Uacute": "\u00DA", + "Uacute;": "\u00DA", + "Uarr;": "\u219F", + "Uarrocir;": "\u2949", + "Ubrcy;": "\u040E", + "Ubreve;": "\u016C", + "Ucirc": "\u00DB", + "Ucirc;": "\u00DB", + "Ucy;": "\u0423", + "Udblac;": "\u0170", + "Ufr;": "\u1D518", + "Ugrave": "\u00D9", + "Ugrave;": "\u00D9", + "Umacr;": "\u016A", + "UnderBar;": "_", + "UnderBrace;": "\u23DF", + "UnderBracket;": "\u23B5", + "UnderParenthesis;": "\u23DD", + "Union;": "\u22C3", + "UnionPlus;": "\u228E", + "Uogon;": "\u0172", + "Uopf;": "\u1D54C", + "UpArrow;": "\u2191", + "UpArrowBar;": "\u2912", + "UpArrowDownArrow;": "\u21C5", + "UpDownArrow;": "\u2195", + "UpEquilibrium;": "\u296E", + "UpTee;": "\u22A5", + "UpTeeArrow;": "\u21A5", + "Uparrow;": "\u21D1", + "Updownarrow;": "\u21D5", + "UpperLeftArrow;": "\u2196", + "UpperRightArrow;": "\u2197", + "Upsi;": "\u03D2", + "Upsilon;": "\u03A5", + "Uring;": "\u016E", + "Uscr;": "\u1D4B0", + "Utilde;": "\u0168", + "Uuml": "\u00DC", + "Uuml;": "\u00DC", + "VDash;": "\u22AB", + "Vbar;": "\u2AEB", + "Vcy;": "\u0412", + "Vdash;": "\u22A9", + "Vdashl;": "\u2AE6", + "Vee;": "\u22C1", + "Verbar;": "\u2016", + "Vert;": "\u2016", + "VerticalBar;": "\u2223", + "VerticalLine;": "|", + "VerticalSeparator;": "\u2758", + "VerticalTilde;": "\u2240", + "VeryThinSpace;": "\u200A", + "Vfr;": "\u1D519", + "Vopf;": "\u1D54D", + "Vscr;": "\u1D4B1", + "Vvdash;": "\u22AA", + "Wcirc;": "\u0174", + "Wedge;": "\u22C0", + "Wfr;": "\u1D51A", + "Wopf;": "\u1D54E", + "Wscr;": "\u1D4B2", + "Xfr;": "\u1D51B", + "Xi;": "\u039E", + "Xopf;": "\u1D54F", + "Xscr;": "\u1D4B3", + "YAcy;": "\u042F", + "YIcy;": "\u0407", + "YUcy;": "\u042E", + "Yacute": "\u00DD", + "Yacute;": "\u00DD", + "Ycirc;": "\u0176", + "Ycy;": "\u042B", + "Yfr;": "\u1D51C", + "Yopf;": "\u1D550", + "Yscr;": "\u1D4B4", + "Yuml;": "\u0178", + "ZHcy;": "\u0416", + "Zacute;": "\u0179", + "Zcaron;": "\u017D", + "Zcy;": "\u0417", + "Zdot;": "\u017B", + "ZeroWidthSpace;": "\u200B", + "Zeta;": "\u0396", + "Zfr;": "\u2128", + "Zopf;": "\u2124", + "Zscr;": "\u1D4B5", + "aacute": "\u00E1", + "aacute;": "\u00E1", + "abreve;": "\u0103", + "ac;": "\u223E", + "acE;": "\u223E\u0333", + "acd;": "\u223F", + "acirc": "\u00E2", + "acirc;": "\u00E2", + "acute": "\u00B4", + "acute;": "\u00B4", + "acy;": "\u0430", + "aelig": "\u00E6", + "aelig;": "\u00E6", + "af;": "\u2061", + "afr;": "\u1D51E", + "agrave": "\u00E0", + "agrave;": "\u00E0", + "alefsym;": "\u2135", + "aleph;": "\u2135", + "alpha;": "\u03B1", + "amacr;": "\u0101", + "amalg;": "\u2A3F", + "amp": "&", + "amp;": "&", + "and;": "\u2227", + "andand;": "\u2A55", + "andd;": "\u2A5C", + "andslope;": "\u2A58", + "andv;": "\u2A5A", + "ang;": "\u2220", + "ange;": "\u29A4", + "angle;": "\u2220", + "angmsd;": "\u2221", + "angmsdaa;": "\u29A8", + "angmsdab;": "\u29A9", + "angmsdac;": "\u29AA", + "angmsdad;": "\u29AB", + "angmsdae;": "\u29AC", + "angmsdaf;": "\u29AD", + "angmsdag;": "\u29AE", + "angmsdah;": "\u29AF", + "angrt;": "\u221F", + "angrtvb;": "\u22BE", + "angrtvbd;": "\u299D", + "angsph;": "\u2222", + "angst;": "\u00C5", + "angzarr;": "\u237C", + "aogon;": "\u0105", + "aopf;": "\u1D552", + "ap;": "\u2248", + "apE;": "\u2A70", + "apacir;": "\u2A6F", + "ape;": "\u224A", + "apid;": "\u224B", + "apos;": "\u0027", + "approx;": "\u2248", + "approxeq;": "\u224A", + "aring": "\u00E5", + "aring;": "\u00E5", + "ascr;": "\u1D4B6", + "ast;": "*", + "asymp;": "\u2248", + "asympeq;": "\u224D", + "atilde": "\u00E3", + "atilde;": "\u00E3", + "auml": "\u00E4", + "auml;": "\u00E4", + "awconint;": "\u2233", + "awint;": "\u2A11", + "bNot;": "\u2AED", + "backcong;": "\u224C", + "backepsilon;": "\u03F6", + "backprime;": "\u2035", + "backsim;": "\u223D", + "backsimeq;": "\u22CD", + "barvee;": "\u22BD", + "barwed;": "\u2305", + "barwedge;": "\u2305", + "bbrk;": "\u23B5", + "bbrktbrk;": "\u23B6", + "bcong;": "\u224C", + "bcy;": "\u0431", + "bdquo;": "\u201E", + "becaus;": "\u2235", + "because;": "\u2235", + "bemptyv;": "\u29B0", + "bepsi;": "\u03F6", + "bernou;": "\u212C", + "beta;": "\u03B2", + "beth;": "\u2136", + "between;": "\u226C", + "bfr;": "\u1D51F", + "bigcap;": "\u22C2", + "bigcirc;": "\u25EF", + "bigcup;": "\u22C3", + "bigodot;": "\u2A00", + "bigoplus;": "\u2A01", + "bigotimes;": "\u2A02", + "bigsqcup;": "\u2A06", + "bigstar;": "\u2605", + "bigtriangledown;": "\u25BD", + "bigtriangleup;": "\u25B3", + "biguplus;": "\u2A04", + "bigvee;": "\u22C1", + "bigwedge;": "\u22C0", + "bkarow;": "\u290D", + "blacklozenge;": "\u29EB", + "blacksquare;": "\u25AA", + "blacktriangle;": "\u25B4", + "blacktriangledown;": "\u25BE", + "blacktriangleleft;": "\u25C2", + "blacktriangleright;": "\u25B8", + "blank;": "\u2423", + "blk12;": "\u2592", + "blk14;": "\u2591", + "blk34;": "\u2593", + "block;": "\u2588", + "bne;": "\u003D\u20E5", + "bnequiv;": "\u2261\u20E5", + "bnot;": "\u2310", + "bopf;": "\u1D553", + "bot;": "\u22A5", + "bottom;": "\u22A5", + "bowtie;": "\u22C8", + "boxDL;": "\u2557", + "boxDR;": "\u2554", + "boxDl;": "\u2556", + "boxDr;": "\u2553", + "boxH;": "\u2550", + "boxHD;": "\u2566", + "boxHU;": "\u2569", + "boxHd;": "\u2564", + "boxHu;": "\u2567", + "boxUL;": "\u255D", + "boxUR;": "\u255A", + "boxUl;": "\u255C", + "boxUr;": "\u2559", + "boxV;": "\u2551", + "boxVH;": "\u256C", + "boxVL;": "\u2563", + "boxVR;": "\u2560", + "boxVh;": "\u256B", + "boxVl;": "\u2562", + "boxVr;": "\u255F", + "boxbox;": "\u29C9", + "boxdL;": "\u2555", + "boxdR;": "\u2552", + "boxdl;": "\u2510", + "boxdr;": "\u250C", + "boxh;": "\u2500", + "boxhD;": "\u2565", + "boxhU;": "\u2568", + "boxhd;": "\u252C", + "boxhu;": "\u2534", + "boxminus;": "\u229F", + "boxplus;": "\u229E", + "boxtimes;": "\u22A0", + "boxuL;": "\u255B", + "boxuR;": "\u2558", + "boxul;": "\u2518", + "boxur;": "\u2514", + "boxv;": "\u2502", + "boxvH;": "\u256A", + "boxvL;": "\u2561", + "boxvR;": "\u255E", + "boxvh;": "\u253C", + "boxvl;": "\u2524", + "boxvr;": "\u251C", + "bprime;": "\u2035", + "breve;": "\u02D8", + "brvbar": "\u00A6", + "brvbar;": "\u00A6", + "bscr;": "\u1D4B7", + "bsemi;": "\u204F", + "bsim;": "\u223D", + "bsime;": "\u22CD", + "bsol;": "\u005C", + "bsolb;": "\u29C5", + "bsolhsub;": "\u27C8", + "bull;": "\u2022", + "bullet;": "\u2022", + "bump;": "\u224E", + "bumpE;": "\u2AAE", + "bumpe;": "\u224F", + "bumpeq;": "\u224F", + "cacute;": "\u0107", + "cap;": "\u2229", + "capand;": "\u2A44", + "capbrcup;": "\u2A49", + "capcap;": "\u2A4B", + "capcup;": "\u2A47", + "capdot;": "\u2A40", + "caps;": "\u2229\uFE00", + "caret;": "\u2041", + "caron;": "\u02C7", + "ccaps;": "\u2A4D", + "ccaron;": "\u010D", + "ccedil": "\u00E7", + "ccedil;": "\u00E7", + "ccirc;": "\u0109", + "ccups;": "\u2A4C", + "ccupssm;": "\u2A50", + "cdot;": "\u010B", + "cedil": "\u00B8", + "cedil;": "\u00B8", + "cemptyv;": "\u29B2", + "cent": "\u00A2", + "cent;": "\u00A2", + "centerdot;": "\u00B7", + "cfr;": "\u1D520", + "chcy;": "\u0447", + "check;": "\u2713", + "checkmark;": "\u2713", + "chi;": "\u03C7", + "cir;": "\u25CB", + "cirE;": "\u29C3", + "circ;": "\u02C6", + "circeq;": "\u2257", + "circlearrowleft;": "\u21BA", + "circlearrowright;": "\u21BB", + "circledR;": "\u00AE", + "circledS;": "\u24C8", + "circledast;": "\u229B", + "circledcirc;": "\u229A", + "circleddash;": "\u229D", + "cire;": "\u2257", + "cirfnint;": "\u2A10", + "cirmid;": "\u2AEF", + "cirscir;": "\u29C2", + "clubs;": "\u2663", + "clubsuit;": "\u2663", + "colon;": ":", + "colone;": "\u2254", + "coloneq;": "\u2254", + "comma;": ",", + "commat;": "@", + "comp;": "\u2201", + "compfn;": "\u2218", + "complement;": "\u2201", + "complexes;": "\u2102", + "cong;": "\u2245", + "congdot;": "\u2A6D", + "conint;": "\u222E", + "copf;": "\u1D554", + "coprod;": "\u2210", + "copy": "\u00A9", + "copy;": "\u00A9", + "copysr;": "\u2117", + "crarr;": "\u21B5", + "cross;": "\u2717", + "cscr;": "\u1D4B8", + "csub;": "\u2ACF", + "csube;": "\u2AD1", + "csup;": "\u2AD0", + "csupe;": "\u2AD2", + "ctdot;": "\u22EF", + "cudarrl;": "\u2938", + "cudarrr;": "\u2935", + "cuepr;": "\u22DE", + "cuesc;": "\u22DF", + "cularr;": "\u21B6", + "cularrp;": "\u293D", + "cup;": "\u222A", + "cupbrcap;": "\u2A48", + "cupcap;": "\u2A46", + "cupcup;": "\u2A4A", + "cupdot;": "\u228D", + "cupor;": "\u2A45", + "cups;": "\u222A\uFE00", + "curarr;": "\u21B7", + "curarrm;": "\u293C", + "curlyeqprec;": "\u22DE", + "curlyeqsucc;": "\u22DF", + "curlyvee;": "\u22CE", + "curlywedge;": "\u22CF", + "curren": "\u00A4", + "curren;": "\u00A4", + "curvearrowleft;": "\u21B6", + "curvearrowright;": "\u21B7", + "cuvee;": "\u22CE", + "cuwed;": "\u22CF", + "cwconint;": "\u2232", + "cwint;": "\u2231", + "cylcty;": "\u232D", + "dArr;": "\u21D3", + "dHar;": "\u2965", + "dagger;": "\u2020", + "daleth;": "\u2138", + "darr;": "\u2193", + "dash;": "\u2010", + "dashv;": "\u22A3", + "dbkarow;": "\u290F", + "dblac;": "\u02DD", + "dcaron;": "\u010F", + "dcy;": "\u0434", + "dd;": "\u2146", + "ddagger;": "\u2021", + "ddarr;": "\u21CA", + "ddotseq;": "\u2A77", + "deg": "\u00B0", + "deg;": "\u00B0", + "delta;": "\u03B4", + "demptyv;": "\u29B1", + "dfisht;": "\u297F", + "dfr;": "\u1D521", + "dharl;": "\u21C3", + "dharr;": "\u21C2", + "diam;": "\u22C4", + "diamond;": "\u22C4", + "diamondsuit;": "\u2666", + "diams;": "\u2666", + "die;": "\u00A8", + "digamma;": "\u03DD", + "disin;": "\u22F2", + "div;": "\u00F7", + "divide": "\u00F7", + "divide;": "\u00F7", + "divideontimes;": "\u22C7", + "divonx;": "\u22C7", + "djcy;": "\u0452", + "dlcorn;": "\u231E", + "dlcrop;": "\u230D", + "dollar;": "$", + "dopf;": "\u1D555", + "dot;": "\u02D9", + "doteq;": "\u2250", + "doteqdot;": "\u2251", + "dotminus;": "\u2238", + "dotplus;": "\u2214", + "dotsquare;": "\u22A1", + "doublebarwedge;": "\u2306", + "downarrow;": "\u2193", + "downdownarrows;": "\u21CA", + "downharpoonleft;": "\u21C3", + "downharpoonright;": "\u21C2", + "drbkarow;": "\u2910", + "drcorn;": "\u231F", + "drcrop;": "\u230C", + "dscr;": "\u1D4B9", + "dscy;": "\u0455", + "dsol;": "\u29F6", + "dstrok;": "\u0111", + "dtdot;": "\u22F1", + "dtri;": "\u25BF", + "dtrif;": "\u25BE", + "duarr;": "\u21F5", + "duhar;": "\u296F", + "dwangle;": "\u29A6", + "dzcy;": "\u045F", + "dzigrarr;": "\u27FF", + "eDDot;": "\u2A77", + "eDot;": "\u2251", + "eacute": "\u00E9", + "eacute;": "\u00E9", + "easter;": "\u2A6E", + "ecaron;": "\u011B", + "ecir;": "\u2256", + "ecirc": "\u00EA", + "ecirc;": "\u00EA", + "ecolon;": "\u2255", + "ecy;": "\u044D", + "edot;": "\u0117", + "ee;": "\u2147", + "efDot;": "\u2252", + "efr;": "\u1D522", + "eg;": "\u2A9A", + "egrave": "\u00E8", + "egrave;": "\u00E8", + "egs;": "\u2A96", + "egsdot;": "\u2A98", + "el;": "\u2A99", + "elinters;": "\u23E7", + "ell;": "\u2113", + "els;": "\u2A95", + "elsdot;": "\u2A97", + "emacr;": "\u0113", + "empty;": "\u2205", + "emptyset;": "\u2205", + "emptyv;": "\u2205", + "emsp13;": "\u2004", + "emsp14;": "\u2005", + "emsp;": "\u2003", + "eng;": "\u014B", + "ensp;": "\u2002", + "eogon;": "\u0119", + "eopf;": "\u1D556", + "epar;": "\u22D5", + "eparsl;": "\u29E3", + "eplus;": "\u2A71", + "epsi;": "\u03B5", + "epsilon;": "\u03B5", + "epsiv;": "\u03F5", + "eqcirc;": "\u2256", + "eqcolon;": "\u2255", + "eqsim;": "\u2242", + "eqslantgtr;": "\u2A96", + "eqslantless;": "\u2A95", + "equals;": "=", + "equest;": "\u225F", + "equiv;": "\u2261", + "equivDD;": "\u2A78", + "eqvparsl;": "\u29E5", + "erDot;": "\u2253", + "erarr;": "\u2971", + "escr;": "\u212F", + "esdot;": "\u2250", + "esim;": "\u2242", + "eta;": "\u03B7", + "eth": "\u00F0", + "eth;": "\u00F0", + "euml": "\u00EB", + "euml;": "\u00EB", + "euro;": "\u20AC", + "excl;": "!", + "exist;": "\u2203", + "expectation;": "\u2130", + "exponentiale;": "\u2147", + "fallingdotseq;": "\u2252", + "fcy;": "\u0444", + "female;": "\u2640", + "ffilig;": "\uFB03", + "fflig;": "\uFB00", + "ffllig;": "\uFB04", + "ffr;": "\u1D523", + "filig;": "\uFB01", + "fjlig;": "\u0066", + "flat;": "\u266D", + "fllig;": "\uFB02", + "fltns;": "\u25B1", + "fnof;": "\u0192", + "fopf;": "\u1D557", + "forall;": "\u2200", + "fork;": "\u22D4", + "forkv;": "\u2AD9", + "fpartint;": "\u2A0D", + "frac12": "\u00BD", + "frac12;": "\u00BD", + "frac13;": "\u2153", + "frac14": "\u00BC", + "frac14;": "\u00BC", + "frac15;": "\u2155", + "frac16;": "\u2159", + "frac18;": "\u215B", + "frac23;": "\u2154", + "frac25;": "\u2156", + "frac34": "\u00BE", + "frac34;": "\u00BE", + "frac35;": "\u2157", + "frac38;": "\u215C", + "frac45;": "\u2158", + "frac56;": "\u215A", + "frac58;": "\u215D", + "frac78;": "\u215E", + "frasl;": "\u2044", + "frown;": "\u2322", + "fscr;": "\u1D4BB", + "gE;": "\u2267", + "gEl;": "\u2A8C", + "gacute;": "\u01F5", + "gamma;": "\u03B3", + "gammad;": "\u03DD", + "gap;": "\u2A86", + "gbreve;": "\u011F", + "gcirc;": "\u011D", + "gcy;": "\u0433", + "gdot;": "\u0121", + "ge;": "\u2265", + "gel;": "\u22DB", + "geq;": "\u2265", + "geqq;": "\u2267", + "geqslant;": "\u2A7E", + "ges;": "\u2A7E", + "gescc;": "\u2AA9", + "gesdot;": "\u2A80", + "gesdoto;": "\u2A82", + "gesdotol;": "\u2A84", + "gesl;": "\u22DB\uFE00", + "gesles;": "\u2A94", + "gfr;": "\u1D524", + "gg;": "\u226B", + "ggg;": "\u22D9", + "gimel;": "\u2137", + "gjcy;": "\u0453", + "gl;": "\u2277", + "glE;": "\u2A92", + "gla;": "\u2AA5", + "glj;": "\u2AA4", + "gnE;": "\u2269", + "gnap;": "\u2A8A", + "gnapprox;": "\u2A8A", + "gne;": "\u2A88", + "gneq;": "\u2A88", + "gneqq;": "\u2269", + "gnsim;": "\u22E7", + "gopf;": "\u1D558", + "grave;": "`", + "gscr;": "\u210A", + "gsim;": "\u2273", + "gsime;": "\u2A8E", + "gsiml;": "\u2A90", + "gt": ">", + "gt;": ">", + "gtcc;": "\u2AA7", + "gtcir;": "\u2A7A", + "gtdot;": "\u22D7", + "gtlPar;": "\u2995", + "gtquest;": "\u2A7C", + "gtrapprox;": "\u2A86", + "gtrarr;": "\u2978", + "gtrdot;": "\u22D7", + "gtreqless;": "\u22DB", + "gtreqqless;": "\u2A8C", + "gtrless;": "\u2277", + "gtrsim;": "\u2273", + "gvertneqq;": "\u2269\uFE00", + "gvnE;": "\u2269\uFE00", + "hArr;": "\u21D4", + "hairsp;": "\u200A", + "half;": "\u00BD", + "hamilt;": "\u210B", + "hardcy;": "\u044A", + "harr;": "\u2194", + "harrcir;": "\u2948", + "harrw;": "\u21AD", + "hbar;": "\u210F", + "hcirc;": "\u0125", + "hearts;": "\u2665", + "heartsuit;": "\u2665", + "hellip;": "\u2026", + "hercon;": "\u22B9", + "hfr;": "\u1D525", + "hksearow;": "\u2925", + "hkswarow;": "\u2926", + "hoarr;": "\u21FF", + "homtht;": "\u223B", + "hookleftarrow;": "\u21A9", + "hookrightarrow;": "\u21AA", + "hopf;": "\u1D559", + "horbar;": "\u2015", + "hscr;": "\u1D4BD", + "hslash;": "\u210F", + "hstrok;": "\u0127", + "hybull;": "\u2043", + "hyphen;": "\u2010", + "iacute": "\u00ED", + "iacute;": "\u00ED", + "ic;": "\u2063", + "icirc": "\u00EE", + "icirc;": "\u00EE", + "icy;": "\u0438", + "iecy;": "\u0435", + "iexcl": "\u00A1", + "iexcl;": "\u00A1", + "iff;": "\u21D4", + "ifr;": "\u1D526", + "igrave": "\u00EC", + "igrave;": "\u00EC", + "ii;": "\u2148", + "iiiint;": "\u2A0C", + "iiint;": "\u222D", + "iinfin;": "\u29DC", + "iiota;": "\u2129", + "ijlig;": "\u0133", + "imacr;": "\u012B", + "image;": "\u2111", + "imagline;": "\u2110", + "imagpart;": "\u2111", + "imath;": "\u0131", + "imof;": "\u22B7", + "imped;": "\u01B5", + "in;": "\u2208", + "incare;": "\u2105", + "infin;": "\u221E", + "infintie;": "\u29DD", + "inodot;": "\u0131", + "int;": "\u222B", + "intcal;": "\u22BA", + "integers;": "\u2124", + "intercal;": "\u22BA", + "intlarhk;": "\u2A17", + "intprod;": "\u2A3C", + "iocy;": "\u0451", + "iogon;": "\u012F", + "iopf;": "\u1D55A", + "iota;": "\u03B9", + "iprod;": "\u2A3C", + "iquest": "\u00BF", + "iquest;": "\u00BF", + "iscr;": "\u1D4BE", + "isin;": "\u2208", + "isinE;": "\u22F9", + "isindot;": "\u22F5", + "isins;": "\u22F4", + "isinsv;": "\u22F3", + "isinv;": "\u2208", + "it;": "\u2062", + "itilde;": "\u0129", + "iukcy;": "\u0456", + "iuml": "\u00EF", + "iuml;": "\u00EF", + "jcirc;": "\u0135", + "jcy;": "\u0439", + "jfr;": "\u1D527", + "jmath;": "\u0237", + "jopf;": "\u1D55B", + "jscr;": "\u1D4BF", + "jsercy;": "\u0458", + "jukcy;": "\u0454", + "kappa;": "\u03BA", + "kappav;": "\u03F0", + "kcedil;": "\u0137", + "kcy;": "\u043A", + "kfr;": "\u1D528", + "kgreen;": "\u0138", + "khcy;": "\u0445", + "kjcy;": "\u045C", + "kopf;": "\u1D55C", + "kscr;": "\u1D4C0", + "lAarr;": "\u21DA", + "lArr;": "\u21D0", + "lAtail;": "\u291B", + "lBarr;": "\u290E", + "lE;": "\u2266", + "lEg;": "\u2A8B", + "lHar;": "\u2962", + "lacute;": "\u013A", + "laemptyv;": "\u29B4", + "lagran;": "\u2112", + "lambda;": "\u03BB", + "lang;": "\u27E8", + "langd;": "\u2991", + "langle;": "\u27E8", + "lap;": "\u2A85", + "laquo": "\u00AB", + "laquo;": "\u00AB", + "larr;": "\u2190", + "larrb;": "\u21E4", + "larrbfs;": "\u291F", + "larrfs;": "\u291D", + "larrhk;": "\u21A9", + "larrlp;": "\u21AB", + "larrpl;": "\u2939", + "larrsim;": "\u2973", + "larrtl;": "\u21A2", + "lat;": "\u2AAB", + "latail;": "\u2919", + "late;": "\u2AAD", + "lates;": "\u2AAD\uFE00", + "lbarr;": "\u290C", + "lbbrk;": "\u2772", + "lbrace;": "{", + "lbrack;": "[", + "lbrke;": "\u298B", + "lbrksld;": "\u298F", + "lbrkslu;": "\u298D", + "lcaron;": "\u013E", + "lcedil;": "\u013C", + "lceil;": "\u2308", + "lcub;": "{", + "lcy;": "\u043B", + "ldca;": "\u2936", + "ldquo;": "\u201C", + "ldquor;": "\u201E", + "ldrdhar;": "\u2967", + "ldrushar;": "\u294B", + "ldsh;": "\u21B2", + "le;": "\u2264", + "leftarrow;": "\u2190", + "leftarrowtail;": "\u21A2", + "leftharpoondown;": "\u21BD", + "leftharpoonup;": "\u21BC", + "leftleftarrows;": "\u21C7", + "leftrightarrow;": "\u2194", + "leftrightarrows;": "\u21C6", + "leftrightharpoons;": "\u21CB", + "leftrightsquigarrow;": "\u21AD", + "leftthreetimes;": "\u22CB", + "leg;": "\u22DA", + "leq;": "\u2264", + "leqq;": "\u2266", + "leqslant;": "\u2A7D", + "les;": "\u2A7D", + "lescc;": "\u2AA8", + "lesdot;": "\u2A7F", + "lesdoto;": "\u2A81", + "lesdotor;": "\u2A83", + "lesg;": "\u22DA\uFE00", + "lesges;": "\u2A93", + "lessapprox;": "\u2A85", + "lessdot;": "\u22D6", + "lesseqgtr;": "\u22DA", + "lesseqqgtr;": "\u2A8B", + "lessgtr;": "\u2276", + "lesssim;": "\u2272", + "lfisht;": "\u297C", + "lfloor;": "\u230A", + "lfr;": "\u1D529", + "lg;": "\u2276", + "lgE;": "\u2A91", + "lhard;": "\u21BD", + "lharu;": "\u21BC", + "lharul;": "\u296A", + "lhblk;": "\u2584", + "ljcy;": "\u0459", + "ll;": "\u226A", + "llarr;": "\u21C7", + "llcorner;": "\u231E", + "llhard;": "\u296B", + "lltri;": "\u25FA", + "lmidot;": "\u0140", + "lmoust;": "\u23B0", + "lmoustache;": "\u23B0", + "lnE;": "\u2268", + "lnap;": "\u2A89", + "lnapprox;": "\u2A89", + "lne;": "\u2A87", + "lneq;": "\u2A87", + "lneqq;": "\u2268", + "lnsim;": "\u22E6", + "loang;": "\u27EC", + "loarr;": "\u21FD", + "lobrk;": "\u27E6", + "longleftarrow;": "\u27F5", + "longleftrightarrow;": "\u27F7", + "longmapsto;": "\u27FC", + "longrightarrow;": "\u27F6", + "looparrowleft;": "\u21AB", + "looparrowright;": "\u21AC", + "lopar;": "\u2985", + "lopf;": "\u1D55D", + "loplus;": "\u2A2D", + "lotimes;": "\u2A34", + "lowast;": "\u2217", + "lowbar;": "_", + "loz;": "\u25CA", + "lozenge;": "\u25CA", + "lozf;": "\u29EB", + "lpar;": "(", + "lparlt;": "\u2993", + "lrarr;": "\u21C6", + "lrcorner;": "\u231F", + "lrhar;": "\u21CB", + "lrhard;": "\u296D", + "lrm;": "\u200E", + "lrtri;": "\u22BF", + "lsaquo;": "\u2039", + "lscr;": "\u1D4C1", + "lsh;": "\u21B0", + "lsim;": "\u2272", + "lsime;": "\u2A8D", + "lsimg;": "\u2A8F", + "lsqb;": "[", + "lsquo;": "\u2018", + "lsquor;": "\u201A", + "lstrok;": "\u0142", + "lt": "<", + "lt;": "<", + "ltcc;": "\u2AA6", + "ltcir;": "\u2A79", + "ltdot;": "\u22D6", + "lthree;": "\u22CB", + "ltimes;": "\u22C9", + "ltlarr;": "\u2976", + "ltquest;": "\u2A7B", + "ltrPar;": "\u2996", + "ltri;": "\u25C3", + "ltrie;": "\u22B4", + "ltrif;": "\u25C2", + "lurdshar;": "\u294A", + "luruhar;": "\u2966", + "lvertneqq;": "\u2268\uFE00", + "lvnE;": "\u2268\uFE00", + "mDDot;": "\u223A", + "macr": "\u00AF", + "macr;": "\u00AF", + "male;": "\u2642", + "malt;": "\u2720", + "maltese;": "\u2720", + "map;": "\u21A6", + "mapsto;": "\u21A6", + "mapstodown;": "\u21A7", + "mapstoleft;": "\u21A4", + "mapstoup;": "\u21A5", + "marker;": "\u25AE", + "mcomma;": "\u2A29", + "mcy;": "\u043C", + "mdash;": "\u2014", + "measuredangle;": "\u2221", + "mfr;": "\u1D52A", + "mho;": "\u2127", + "micro": "\u00B5", + "micro;": "\u00B5", + "mid;": "\u2223", + "midast;": "*", + "midcir;": "\u2AF0", + "middot": "\u00B7", + "middot;": "\u00B7", + "minus;": "\u2212", + "minusb;": "\u229F", + "minusd;": "\u2238", + "minusdu;": "\u2A2A", + "mlcp;": "\u2ADB", + "mldr;": "\u2026", + "mnplus;": "\u2213", + "models;": "\u22A7", + "mopf;": "\u1D55E", + "mp;": "\u2213", + "mscr;": "\u1D4C2", + "mstpos;": "\u223E", + "mu;": "\u03BC", + "multimap;": "\u22B8", + "mumap;": "\u22B8", + "nGg;": "\u22D9\u0338", + "nGt;": "\u226B\u20D2", + "nGtv;": "\u226B\u0338", + "nLeftarrow;": "\u21CD", + "nLeftrightarrow;": "\u21CE", + "nLl;": "\u22D8\u0338", + "nLt;": "\u226A\u20D2", + "nLtv;": "\u226A\u0338", + "nRightarrow;": "\u21CF", + "nVDash;": "\u22AF", + "nVdash;": "\u22AE", + "nabla;": "\u2207", + "nacute;": "\u0144", + "nang;": "\u2220\u20D2", + "nap;": "\u2249", + "napE;": "\u2A70\u0338", + "napid;": "\u224B\u0338", + "napos;": "\u0149", + "napprox;": "\u2249", + "natur;": "\u266E", + "natural;": "\u266E", + "naturals;": "\u2115", + "nbsp": "\u00A0", + "nbsp;": "\u00A0", + "nbump;": "\u224E\u0338", + "nbumpe;": "\u224F\u0338", + "ncap;": "\u2A43", + "ncaron;": "\u0148", + "ncedil;": "\u0146", + "ncong;": "\u2247\u0338", + "ncongdot;": "\u2A6D", + "ncup;": "\u2A42", + "ncy;": "\u043D", + "ndash;": "\u2013", + "ne;": "\u2260", + "neArr;": "\u21D7", + "nearhk;": "\u2924", + "nearr;": "\u2197", + "nearrow;": "\u2197", + "nedot;": "\u2250\u0338", + "nequiv;": "\u2262", + "nesear;": "\u2928", + "nesim;": "\u2242\u0338", + "nexist;": "\u2204", + "nexists;": "\u2204", + "nfr;": "\u1D52B", + "ngE;": "\u2267\u0338", + "nge;": "\u2271", + "ngeq;": "\u2271", + "ngeqq;": "\u2267\u0338", + "ngeqslant;": "\u2A7E\u0338", + "nges;": "\u2A7E\u0338", + "ngsim;": "\u2275", + "ngt;": "\u226F", + "ngtr;": "\u226F", + "nhArr;": "\u21CE", + "nharr;": "\u21AE", + "nhpar;": "\u2AF2", + "ni;": "\u220B", + "nis;": "\u22FC", + "nisd;": "\u22FA", + "niv;": "\u220B", + "njcy;": "\u045A", + "nlArr;": "\u21CD", + "nlE;": "\u2266\u0338", + "nlarr;": "\u219A", + "nldr;": "\u2025", + "nle;": "\u2270", + "nleftarrow;": "\u219A", + "nleftrightarrow;": "\u21AE", + "nleq;": "\u2270", + "nleqq;": "\u2266\u0338", + "nleqslant;": "\u2A7D\u0338", + "nles;": "\u2A7D\u0338", + "nless;": "\u226E", + "nlsim;": "\u2274", + "nlt;": "\u226E", + "nltri;": "\u22EA", + "nltrie;": "\u22EC", + "nmid;": "\u2224", + "nopf;": "\u1D55F", + "not": "\u00AC", + "not;": "\u00AC", + "notin;": "\u2209", + "notinE;": "\u22F9\u0338", + "notindot;": "\u22F5\u0338", + "notinva;": "\u2209", + "notinvb;": "\u22F7", + "notinvc;": "\u22F6", + "notni;": "\u220C", + "notniva;": "\u220C", + "notnivb;": "\u22FE", + "notnivc;": "\u22FD", + "npar;": "\u2226", + "nparallel;": "\u2226", + "nparsl;": "\u2AFD\u20E5", + "npart;": "\u2202\u0338", + "npolint;": "\u2A14", + "npr;": "\u2280", + "nprcue;": "\u22E0", + "npre;": "\u2AAF", + "nprec;": "\u2280", + "npreceq;": "\u2AAF", + "nrArr;": "\u21CF", + "nrarr;": "\u219B", + "nrarrc;": "\u2933\u0338", + "nrarrw;": "\u219D\u0338", + "nrightarrow;": "\u219B", + "nrtri;": "\u22EB", + "nrtrie;": "\u22ED", + "nsc;": "\u2281", + "nsccue;": "\u22E1", + "nsce;": "\u2AB0\u0338", + "nscr;": "\u1D4C3", + "nshortmid;": "\u2224", + "nshortparallel;": "\u2226", + "nsim;": "\u2241", + "nsime;": "\u2244", + "nsimeq;": "\u2244", + "nsmid;": "\u2224", + "nspar;": "\u2226", + "nsqsube;": "\u22E2", + "nsqsupe;": "\u22E3", + "nsub;": "\u2284", + "nsubE;": "\u2AC5\u0338", + "nsube;": "\u2288", + "nsubset;": "\u2282\u0338", + "nsubseteq;": "\u2288", + "nsubseteqq;": "\u2AC5\u0338", + "nsucc;": "\u2281", + "nsucceq;": "\u2AB0\u0338", + "nsup;": "\u2285", + "nsupE;": "\u2AC6", + "nsupe;": "\u2289", + "nsupset;": "\u2283\u0338", + "nsupseteq;": "\u2289", + "nsupseteqq;": "\u2AC6\u0338", + "ntgl;": "\u2279", + "ntilde": "\u00F1", + "ntilde;": "\u00F1", + "ntlg;": "\u2278", + "ntriangleleft;": "\u22EA", + "ntrianglelefteq;": "\u22EC", + "ntriangleright;": "\u22EB", + "ntrianglerighteq;": "\u22ED", + "nu;": "\u03BD", + "num;": "#", + "numero;": "\u2116", + "numsp;": "\u2007", + "nvDash;": "\u22AD", + "nvHarr;": "\u2904", + "nvap;": "\u224D\u20D2", + "nvdash;": "\u22AC", + "nvge;": "\u2265\u20D2", + "nvgt;": "\u003E\u20D2", + "nvinfin;": "\u29DE", + "nvlArr;": "\u2902", + "nvle;": "\u2264\u20D2", + "nvlt;": "\u003C\u20D2", + "nvltrie;": "\u22B4\u20D2", + "nvrArr;": "\u2903", + "nvrtrie;": "\u22B5\u20D2", + "nvsim;": "\u223C\u20D2", + "nwArr;": "\u21D6", + "nwarhk;": "\u2923", + "nwarr;": "\u2196", + "nwarrow;": "\u2196", + "nwnear;": "\u2927", + "oS;": "\u24C8", + "oacute": "\u00F3", + "oacute;": "\u00F3", + "oast;": "\u229B", + "ocir;": "\u229A", + "ocirc": "\u00F4", + "ocirc;": "\u00F4", + "ocy;": "\u043E", + "odash;": "\u229D", + "odblac;": "\u0151", + "odiv;": "\u2A38", + "odot;": "\u2299", + "odsold;": "\u29BC", + "oelig;": "\u0153", + "ofcir;": "\u29BF", + "ofr;": "\u1D52C", + "ogon;": "\u02DB", + "ograve": "\u00F2", + "ograve;": "\u00F2", + "ogt;": "\u29C1", + "ohbar;": "\u29B5", + "ohm;": "\u03A9", + "oint;": "\u222E", + "olarr;": "\u21BA", + "olcir;": "\u29BE", + "olcross;": "\u29BB", + "oline;": "\u203E", + "olt;": "\u29C0", + "omacr;": "\u014D", + "omega;": "\u03C9", + "omicron;": "\u03BF", + "omid;": "\u29B6", + "ominus;": "\u2296", + "oopf;": "\u1D560", + "opar;": "\u29B7", + "operp;": "\u29B9", + "oplus;": "\u2295", + "or;": "\u2228", + "orarr;": "\u21BB", + "ord;": "\u2A5D", + "order;": "\u2134", + "orderof;": "\u2134", + "ordf": "\u00AA", + "ordf;": "\u00AA", + "ordm": "\u00BA", + "ordm;": "\u00BA", + "origof;": "\u22B6", + "oror;": "\u2A56", + "orslope;": "\u2A57", + "orv;": "\u2A5B", + "oscr;": "\u2134", + "oslash": "\u00F8", + "oslash;": "\u00F8", + "osol;": "\u2298", + "otilde": "\u00F5", + "otilde;": "\u00F5", + "otimes;": "\u2297", + "otimesas;": "\u2A36", + "ouml": "\u00F6", + "ouml;": "\u00F6", + "ovbar;": "\u233D", + "par;": "\u2225", + "para": "\u00B6", + "para;": "\u00B6", + "parallel;": "\u2225", + "parsim;": "\u2AF3", + "parsl;": "\u2AFD", + "part;": "\u2202", + "pcy;": "\u043F", + "percnt;": "%", + "period;": ".", + "permil;": "\u2030", + "perp;": "\u22A5", + "pertenk;": "\u2031", + "pfr;": "\u1D52D", + "phi;": "\u03C6", + "phiv;": "\u03D5", + "phmmat;": "\u2133", + "phone;": "\u260E", + "pi;": "\u03C0", + "pitchfork;": "\u22D4", + "piv;": "\u03D6", + "planck;": "\u210F", + "planckh;": "\u210E", + "plankv;": "\u210F", + "plus;": "+", + "plusacir;": "\u2A23", + "plusb;": "\u229E", + "pluscir;": "\u2A22", + "plusdo;": "\u2214", + "plusdu;": "\u2A25", + "pluse;": "\u2A72", + "plusmn": "\u00B1", + "plusmn;": "\u00B1", + "plussim;": "\u2A26", + "plustwo;": "\u2A27", + "pm;": "\u00B1", + "pointint;": "\u2A15", + "popf;": "\u1D561", + "pound": "\u00A3", + "pound;": "\u00A3", + "pr;": "\u227A", + "prE;": "\u2AB3", + "prap;": "\u2AB7", + "prcue;": "\u227C", + "pre;": "\u2AAF", + "prec;": "\u227A", + "precapprox;": "\u2AB7", + "preccurlyeq;": "\u227C", + "preceq;": "\u2AAF", + "precnapprox;": "\u2AB9", + "precneqq;": "\u2AB5", + "precnsim;": "\u22E8", + "precsim;": "\u227E", + "prime;": "\u2032", + "primes;": "\u2119", + "prnE;": "\u2AB5", + "prnap;": "\u2AB9", + "prnsim;": "\u22E8", + "prod;": "\u220F", + "profalar;": "\u232E", + "profline;": "\u2312", + "profsurf;": "\u2313", + "prop;": "\u221D", + "propto;": "\u221D", + "prsim;": "\u227E", + "prurel;": "\u22B0", + "pscr;": "\u1D4C5", + "psi;": "\u03C8", + "puncsp;": "\u2008", + "qfr;": "\u1D52E", + "qint;": "\u2A0C", + "qopf;": "\u1D562", + "qprime;": "\u2057", + "qscr;": "\u1D4C6", + "quaternions;": "\u210D", + "quatint;": "\u2A16", + "quest;": "?", + "questeq;": "\u225F", + "quot": "\u0022", + "quot;": "\u0022", + "rAarr;": "\u21DB", + "rArr;": "\u21D2", + "rAtail;": "\u291C", + "rBarr;": "\u290F", + "rHar;": "\u2964", + "race;": "\u223D\u0331", + "racute;": "\u0155", + "radic;": "\u221A", + "raemptyv;": "\u29B3", + "rang;": "\u27E9", + "rangd;": "\u2992", + "range;": "\u29A5", + "rangle;": "\u27E9", + "raquo": "\u00BB", + "raquo;": "\u00BB", + "rarr;": "\u2192", + "rarrap;": "\u2975", + "rarrb;": "\u21E5", + "rarrbfs;": "\u2920", + "rarrc;": "\u2933", + "rarrfs;": "\u291E", + "rarrhk;": "\u21AA", + "rarrlp;": "\u21AC", + "rarrpl;": "\u2945", + "rarrsim;": "\u2974", + "rarrtl;": "\u21A3", + "rarrw;": "\u219D", + "ratail;": "\u291A", + "ratio;": "\u2236", + "rationals;": "\u211A", + "rbarr;": "\u290D", + "rbbrk;": "\u2773", + "rbrace;": "}", + "rbrack;": "]", + "rbrke;": "\u298C", + "rbrksld;": "\u298E", + "rbrkslu;": "\u2990", + "rcaron;": "\u0159", + "rcedil;": "\u0157", + "rceil;": "\u2309", + "rcub;": "}", + "rcy;": "\u0440", + "rdca;": "\u2937", + "rdldhar;": "\u2969", + "rdquo;": "\u201D", + "rdquor;": "\u201D", + "rdsh;": "\u21B3", + "real;": "\u211C", + "realine;": "\u211B", + "realpart;": "\u211C", + "reals;": "\u211D", + "rect;": "\u25AD", + "reg": "\u00AE", + "reg;": "\u00AE", + "rfisht;": "\u297D", + "rfloor;": "\u230B", + "rfr;": "\u1D52F", + "rhard;": "\u21C1", + "rharu;": "\u21C0", + "rharul;": "\u296C", + "rho;": "\u03C1", + "rhov;": "\u03F1", + "rightarrow;": "\u2192", + "rightarrowtail;": "\u21A3", + "rightharpoondown;": "\u21C1", + "rightharpoonup;": "\u21C0", + "rightleftarrows;": "\u21C4", + "rightleftharpoons;": "\u21CC", + "rightrightarrows;": "\u21C9", + "rightsquigarrow;": "\u219D", + "rightthreetimes;": "\u22CC", + "ring;": "\u02DA", + "risingdotseq;": "\u2253", + "rlarr;": "\u21C4", + "rlhar;": "\u21CC", + "rlm;": "\u200F", + "rmoust;": "\u23B1", + "rmoustache;": "\u23B1", + "rnmid;": "\u2AEE", + "roang;": "\u27ED", + "roarr;": "\u21FE", + "robrk;": "\u27E7", + "ropar;": "\u2986", + "ropf;": "\u1D563", + "roplus;": "\u2A2E", + "rotimes;": "\u2A35", + "rpar;": ")", + "rpargt;": "\u2994", + "rppolint;": "\u2A12", + "rrarr;": "\u21C9", + "rsaquo;": "\u203A", + "rscr;": "\u1D4C7", + "rsh;": "\u21B1", + "rsqb;": "]", + "rsquo;": "\u2019", + "rsquor;": "\u2019", + "rthree;": "\u22CC", + "rtimes;": "\u22CA", + "rtri;": "\u25B9", + "rtrie;": "\u22B5", + "rtrif;": "\u25B8", + "rtriltri;": "\u29CE", + "ruluhar;": "\u2968", + "rx;": "\u211E", + "sacute;": "\u015B", + "sbquo;": "\u201A", + "sc;": "\u227B", + "scE;": "\u2AB4", + "scap;": "\u2AB8", + "scaron;": "\u0161", + "sccue;": "\u227D", + "sce;": "\u2AB0", + "scedil;": "\u015F", + "scirc;": "\u015D", + "scnE;": "\u2AB6", + "scnap;": "\u2ABA", + "scnsim;": "\u22E9", + "scpolint;": "\u2A13", + "scsim;": "\u227F", + "scy;": "\u0441", + "sdot;": "\u22C5", + "sdotb;": "\u22A1", + "sdote;": "\u2A66", + "seArr;": "\u21D8", + "searhk;": "\u2925", + "searr;": "\u2198", + "searrow;": "\u2198", + "sect": "\u00A7", + "sect;": "\u00A7", + "semi;": ";", + "seswar;": "\u2929", + "setminus;": "\u2216", + "setmn;": "\u2216", + "sext;": "\u2736", + "sfr;": "\u1D530", + "sfrown;": "\u2322", + "sharp;": "\u266F", + "shchcy;": "\u0449", + "shcy;": "\u0448", + "shortmid;": "\u2223", + "shortparallel;": "\u2225", + "shy": "\u00AD", + "shy;": "\u00AD", + "sigma;": "\u03C3", + "sigmaf;": "\u03C2", + "sigmav;": "\u03C2", + "sim;": "\u223C", + "simdot;": "\u2A6A", + "sime;": "\u2243", + "simeq;": "\u2243", + "simg;": "\u2A9E", + "simgE;": "\u2AA0", + "siml;": "\u2A9D", + "simlE;": "\u2A9F", + "simne;": "\u2246", + "simplus;": "\u2A24", + "simrarr;": "\u2972", + "slarr;": "\u2190", + "smallsetminus;": "\u2216", + "smashp;": "\u2A33", + "smeparsl;": "\u29E4", + "smid;": "\u2223", + "smile;": "\u2323", + "smt;": "\u2AAA", + "smte;": "\u2AAC", + "smtes;": "\u2AAC\uFE00", + "softcy;": "\u044C", + "sol;": "/", + "solb;": "\u29C4", + "solbar;": "\u233F", + "sopf;": "\u1D564", + "spades;": "\u2660", + "spadesuit;": "\u2660", + "spar;": "\u2225", + "sqcap;": "\u2293", + "sqcaps;": "\u2293\uFE00", + "sqcup;": "\u2294", + "sqcups;": "\u2294\uFE00", + "sqsub;": "\u228F", + "sqsube;": "\u2291", + "sqsubset;": "\u228F", + "sqsubseteq;": "\u2291", + "sqsup;": "\u2290", + "sqsupe;": "\u2292", + "sqsupset;": "\u2290", + "sqsupseteq;": "\u2292", + "squ;": "\u25A1", + "square;": "\u25A1", + "squarf;": "\u25AA", + "squf;": "\u25AA", + "srarr;": "\u2192", + "sscr;": "\u1D4C8", + "ssetmn;": "\u2216", + "ssmile;": "\u2323", + "sstarf;": "\u22C6", + "star;": "\u2606", + "starf;": "\u2605", + "straightepsilon;": "\u03F5", + "straightphi;": "\u03D5", + "strns;": "\u00AF", + "sub;": "\u2282", + "subE;": "\u2AC5", + "subdot;": "\u2ABD", + "sube;": "\u2286", + "subedot;": "\u2AC3", + "submult;": "\u2AC1", + "subnE;": "\u2ACB", + "subne;": "\u228A", + "subplus;": "\u2ABF", + "subrarr;": "\u2979", + "subset;": "\u2282", + "subseteq;": "\u2286", + "subseteqq;": "\u2AC5", + "subsetneq;": "\u228A", + "subsetneqq;": "\u2ACB", + "subsim;": "\u2AC7", + "subsub;": "\u2AD5", + "subsup;": "\u2AD3", + "succ;": "\u227B", + "succapprox;": "\u2AB8", + "succcurlyeq;": "\u227D", + "succeq;": "\u2AB0", + "succnapprox;": "\u2ABA", + "succneqq;": "\u2AB6", + "succnsim;": "\u22E9", + "succsim;": "\u227F", + "sum;": "\u2211", + "sung;": "\u266A", + "sup1": "\u00B9", + "sup1;": "\u00B9", + "sup2": "\u00B2", + "sup2;": "\u00B2", + "sup3": "\u00B3", + "sup3;": "\u00B3", + "sup;": "\u2283", + "supE;": "\u2AC6", + "supdot;": "\u2ABE", + "supdsub;": "\u2AD8", + "supe;": "\u2287", + "supedot;": "\u2AC4", + "suphsol;": "\u27C9", + "suphsub;": "\u2AD7", + "suplarr;": "\u297B", + "supmult;": "\u2AC2", + "supnE;": "\u2ACC", + "supne;": "\u228B", + "supplus;": "\u2AC0", + "supset;": "\u2283", + "supseteq;": "\u2287", + "supseteqq;": "\u2AC6", + "supsetneq;": "\u228B", + "supsetneqq;": "\u2ACC", + "supsim;": "\u2AC8", + "supsub;": "\u2AD4", + "supsup;": "\u2AD6", + "swArr;": "\u21D9", + "swarhk;": "\u2926", + "swarr;": "\u2199", + "swarrow;": "\u2199", + "swnwar;": "\u292A", + "szlig": "\u00DF", + "szlig;": "\u00DF", + "target;": "\u2316", + "tau;": "\u03C4", + "tbrk;": "\u23B4", + "tcaron;": "\u0165", + "tcedil;": "\u0163", + "tcy;": "\u0442", + "tdot;": "\u20DB", + "telrec;": "\u2315", + "tfr;": "\u1D531", + "there4;": "\u2234", + "therefore;": "\u2234", + "theta;": "\u03B8", + "thetasym;": "\u03D1", + "thetav;": "\u03D1", + "thickapprox;": "\u2248", + "thicksim;": "\u223C", + "thinsp;": "\u2009", + "thkap;": "\u2248", + "thksim;": "\u223C", + "thorn": "\u00FE", + "thorn;": "\u00FE", + "tilde;": "\u02DC", + "times": "\u00D7", + "times;": "\u00D7", + "timesb;": "\u22A0", + "timesbar;": "\u2A31", + "timesd;": "\u2A30", + "tint;": "\u222D", + "toea;": "\u2928", + "top;": "\u22A4", + "topbot;": "\u2336", + "topcir;": "\u2AF1", + "topf;": "\u1D565", + "topfork;": "\u2ADA", + "tosa;": "\u2929", + "tprime;": "\u2034", + "trade;": "\u2122", + "triangle;": "\u25B5", + "triangledown;": "\u25BF", + "triangleleft;": "\u25C3", + "trianglelefteq;": "\u22B4", + "triangleq;": "\u225C", + "triangleright;": "\u25B9", + "trianglerighteq;": "\u22B5", + "tridot;": "\u25EC", + "trie;": "\u225C", + "triminus;": "\u2A3A", + "triplus;": "\u2A39", + "trisb;": "\u29CD", + "tritime;": "\u2A3B", + "trpezium;": "\u23E2", + "tscr;": "\u1D4C9", + "tscy;": "\u0446", + "tshcy;": "\u045B", + "tstrok;": "\u0167", + "twixt;": "\u226C", + "twoheadleftarrow;": "\u219E", + "twoheadrightarrow;": "\u21A0", + "uArr;": "\u21D1", + "uHar;": "\u2963", + "uacute": "\u00FA", + "uacute;": "\u00FA", + "uarr;": "\u2191", + "ubrcy;": "\u045E", + "ubreve;": "\u016D", + "ucirc": "\u00FB", + "ucirc;": "\u00FB", + "ucy;": "\u0443", + "udarr;": "\u21C5", + "udblac;": "\u0171", + "udhar;": "\u296E", + "ufisht;": "\u297E", + "ufr;": "\u1D532", + "ugrave": "\u00F9", + "ugrave;": "\u00F9", + "uharl;": "\u21BF", + "uharr;": "\u21BE", + "uhblk;": "\u2580", + "ulcorn;": "\u231C", + "ulcorner;": "\u231C", + "ulcrop;": "\u230F", + "ultri;": "\u25F8", + "umacr;": "\u016B", + "uml": "\u00A8", + "uml;": "\u00A8", + "uogon;": "\u0173", + "uopf;": "\u1D566", + "uparrow;": "\u2191", + "updownarrow;": "\u2195", + "upharpoonleft;": "\u21BF", + "upharpoonright;": "\u21BE", + "uplus;": "\u228E", + "upsi;": "\u03C5", + "upsih;": "\u03D2", + "upsilon;": "\u03C5", + "upuparrows;": "\u21C8", + "urcorn;": "\u231D", + "urcorner;": "\u231D", + "urcrop;": "\u230E", + "uring;": "\u016F", + "urtri;": "\u25F9", + "uscr;": "\u1D4CA", + "utdot;": "\u22F0", + "utilde;": "\u0169", + "utri;": "\u25B5", + "utrif;": "\u25B4", + "uuarr;": "\u21C8", + "uuml": "\u00FC", + "uuml;": "\u00FC", + "uwangle;": "\u29A7", + "vArr;": "\u21D5", + "vBar;": "\u2AE8", + "vBarv;": "\u2AE9", + "vDash;": "\u22A8", + "vangrt;": "\u299C", + "varepsilon;": "\u03F5", + "varkappa;": "\u03F0", + "varnothing;": "\u2205", + "varphi;": "\u03D5", + "varpi;": "\u03D6", + "varpropto;": "\u221D", + "varr;": "\u2195", + "varrho;": "\u03F1", + "varsigma;": "\u03C2", + "varsubsetneq;": "\u228A\uFE00", + "varsubsetneqq;": "\u2ACB\uFE00", + "varsupsetneq;": "\u228B\uFE00", + "varsupsetneqq;": "\u2ACC\uFE00", + "vartheta;": "\u03D1", + "vartriangleleft;": "\u22B2", + "vartriangleright;": "\u22B3", + "vcy;": "\u0432", + "vdash;": "\u22A2", + "vee;": "\u2228", + "veebar;": "\u22BB", + "veeeq;": "\u225A", + "vellip;": "\u22EE", + "verbar;": "|", + "vert;": "|", + "vfr;": "\u1D533", + "vltri;": "\u22B2", + "vnsub;": "\u2282\u20D2", + "vnsup;": "\u2283\u20D2", + "vopf;": "\u1D567", + "vprop;": "\u221D", + "vrtri;": "\u22B3", + "vscr;": "\u1D4CB", + "vsubnE;": "\u2ACB\uFE00", + "vsubne;": "\u228A\uFE00", + "vsupnE;": "\u2ACC\uFE00", + "vsupne;": "\u228B\uFE00", + "vzigzag;": "\u299A", + "wcirc;": "\u0175", + "wedbar;": "\u2A5F", + "wedge;": "\u2227", + "wedgeq;": "\u2259", + "weierp;": "\u2118", + "wfr;": "\u1D534", + "wopf;": "\u1D568", + "wp;": "\u2118", + "wr;": "\u2240", + "wreath;": "\u2240", + "wscr;": "\u1D4CC", + "xcap;": "\u22C2", + "xcirc;": "\u25EF", + "xcup;": "\u22C3", + "xdtri;": "\u25BD", + "xfr;": "\u1D535", + "xhArr;": "\u27FA", + "xharr;": "\u27F7", + "xi;": "\u03BE", + "xlArr;": "\u27F8", + "xlarr;": "\u27F5", + "xmap;": "\u27FC", + "xnis;": "\u22FB", + "xodot;": "\u2A00", + "xopf;": "\u1D569", + "xoplus;": "\u2A01", + "xotime;": "\u2A02", + "xrArr;": "\u27F9", + "xrarr;": "\u27F6", + "xscr;": "\u1D4CD", + "xsqcup;": "\u2A06", + "xuplus;": "\u2A04", + "xutri;": "\u25B3", + "xvee;": "\u22C1", + "xwedge;": "\u22C0", + "yacute": "\u00FD", + "yacute;": "\u00FD", + "yacy;": "\u044F", + "ycirc;": "\u0177", + "ycy;": "\u044B", + "yen": "\u00A5", + "yen;": "\u00A5", + "yfr;": "\u1D536", + "yicy;": "\u0457", + "yopf;": "\u1D56A", + "yscr;": "\u1D4CE", + "yucy;": "\u044E", + "yuml": "\u00FF", + "yuml;": "\u00FF", + "zacute;": "\u017A", + "zcaron;": "\u017E", + "zcy;": "\u0437", + "zdot;": "\u017C", + "zeetrf;": "\u2128", + "zeta;": "\u03B6", + "zfr;": "\u1D537", + "zhcy;": "\u0436", + "zigrarr;": "\u21DD", + "zopf;": "\u1D56B", + "zscr;": "\u1D4CF", + "zwj;": "\u200D", + "zwnj;": "\u200C" }; diff --git a/packages/html5-tokenizer/tokenizer.js b/packages/html5-tokenizer/tokenizer.js index 461eac634b..e028f44902 100644 --- a/packages/html5-tokenizer/tokenizer.js +++ b/packages/html5-tokenizer/tokenizer.js @@ -5,954 +5,954 @@ var Buffer = require('./buffer').Buffer; var Models = HTML5.Models; function keys(h) { - var r = []; - for(var k in h) { - r.push(k); - } - return r; + var r = []; + for(var k in h) { + r.push(k); + } + return r; } var ENTITY_KEYS = keys(HTML5.ENTITIES); var t = HTML5.Tokenizer = function HTML5Tokenizer(input, document, tree) { - var state; - var buffer = new Buffer(); - var escapeFlag = false; - var lastFourChars = ''; - var current_token = null; - var script_buffer = null; - var content_model = Models.PCDATA; - var source; + var state; + var buffer = new Buffer(); + var escapeFlag = false; + var lastFourChars = ''; + var current_token = null; + var script_buffer = null; + var content_model = Models.PCDATA; + var source; - function data_state(buffer) { - var c = buffer.char(); - if (c !== HTML5.EOF && (content_model == Models.CDATA || content_model == Models.RCDATA || content_model == Models.SCRIPT_CDATA)) { - lastFourChars += c; - if (lastFourChars.length >= 4) { - lastFourChars = lastFourChars.substr(-4); - } - } + function data_state(buffer) { + var c = buffer.char(); + if (c !== HTML5.EOF && (content_model == Models.CDATA || content_model == Models.RCDATA || content_model == Models.SCRIPT_CDATA)) { + lastFourChars += c; + if (lastFourChars.length >= 4) { + lastFourChars = lastFourChars.substr(-4); + } + } - if (content_model == Models.SCRIPT_CDATA) { - if (script_buffer === null) { - script_buffer = ''; - } - } + if (content_model == Models.SCRIPT_CDATA) { + if (script_buffer === null) { + script_buffer = ''; + } + } - if (c === HTML5.EOF) { - emitToken(HTML5.EOF_TOK); - buffer.commit(); - return false; - } else if (c === '\0' && (content_model == Models.SCRIPT_CDATA || content_model == Models.PLAINTEXT || content_model == Models.RAWTEXT || content_model == Models.RCDATA)) { - emitToken({type: 'Characters', data: "\ufffd"}); - buffer.commit(); - } else if (c == '&' && (content_model == Models.PCDATA || content_model == Models.RCDATA) && !escapeFlag) { - newState(entity_data_state); - } else if (c == '-' && (content_model == Models.CDATA || content_model == Models.RCDATA || content_model == Models.SCRIPT_CDATA) && !escapeFlag && lastFourChars == '$/)) { - escapeFlag = false; - emitToken({type: 'Characters', data: c}); - buffer.commit(); - } else if (HTML5.SPACE_CHARACTERS_R.test(c)) { - emitToken({type: 'SpaceCharacters', data: c + buffer.matchWhile(HTML5.SPACE_CHARACTERS)}); - buffer.commit(); - } else { - var o = buffer.matchUntil("[&<>-]"); - if (o !== HTML5.EOF) { - c = c + o; - } - emitToken({type: 'Characters', data: c}); - lastFourChars += c; - lastFourChars = lastFourChars.slice(-4); - buffer.commit(); - } - return true; - } + if (c === HTML5.EOF) { + emitToken(HTML5.EOF_TOK); + buffer.commit(); + return false; + } else if (c === '\0' && (content_model == Models.SCRIPT_CDATA || content_model == Models.PLAINTEXT || content_model == Models.RAWTEXT || content_model == Models.RCDATA)) { + emitToken({type: 'Characters', data: "\ufffd"}); + buffer.commit(); + } else if (c == '&' && (content_model == Models.PCDATA || content_model == Models.RCDATA) && !escapeFlag) { + newState(entity_data_state); + } else if (c == '-' && (content_model == Models.CDATA || content_model == Models.RCDATA || content_model == Models.SCRIPT_CDATA) && !escapeFlag && lastFourChars == '$/)) { + escapeFlag = false; + emitToken({type: 'Characters', data: c}); + buffer.commit(); + } else if (HTML5.SPACE_CHARACTERS_R.test(c)) { + emitToken({type: 'SpaceCharacters', data: c + buffer.matchWhile(HTML5.SPACE_CHARACTERS)}); + buffer.commit(); + } else { + var o = buffer.matchUntil("[&<>-]"); + if (o !== HTML5.EOF) { + c = c + o; + } + emitToken({type: 'Characters', data: c}); + lastFourChars += c; + lastFourChars = lastFourChars.slice(-4); + buffer.commit(); + } + return true; + } - var entity_data_state = function entity_data_state(buffer) { - var entity = consume_entity(buffer); - if (entity) { - emitToken({type: 'Characters', data: entity}); - } else { - emitToken({type: 'Characters', data: '&'}); - } - newState(data_state); - return true; - }; + var entity_data_state = function entity_data_state(buffer) { + var entity = consume_entity(buffer); + if (entity) { + emitToken({type: 'Characters', data: entity}); + } else { + emitToken({type: 'Characters', data: '&'}); + } + newState(data_state); + return true; + }; - this.tokenize = function() { - if (this.pump) this.pump(); - }; + this.tokenize = function() { + if (this.pump) this.pump(); + }; - var emitToken = function emitToken(tok) { - tok = normalize_token(tok); - if (content_model == Models.SCRIPT_CDATA && (tok.type == 'Characters' || tok.type == 'SpaceCharacters') && !buffer.eof) { - HTML5.debug('tokenizer.addScriptData', tok); - script_buffer += tok.data; - } else { - HTML5.debug('tokenizer.token', tok); - this.emit('token', tok); - } - }.bind(this); + var emitToken = function emitToken(tok) { + tok = normalize_token(tok); + if (content_model == Models.SCRIPT_CDATA && (tok.type == 'Characters' || tok.type == 'SpaceCharacters') && !buffer.eof) { + HTML5.debug('tokenizer.addScriptData', tok); + script_buffer += tok.data; + } else { + HTML5.debug('tokenizer.token', tok); + this.emit('token', tok); + } + }.bind(this); - function consume_entity(buffer, from_attr) { - var char = null; - var chars = buffer.char(); - var c; - if (chars === HTML5.EOF) return false; - if (chars.match(HTML5.SPACE_CHARACTERS) || chars == '<' || chars == '&') { - buffer.unget(chars); - } else if (chars[0] == '#') { // Maybe a numeric entity - c = buffer.shift(2); - if (c === HTML5.EOF) { - buffer.unget(chars); - return false; - } - chars += c; - if (chars[1] && chars[1].toLowerCase() == 'x' && HTML5.HEX_DIGITS_R.test(chars[2])) { - // Hex entity - buffer.unget(chars[2]); - char = consume_numeric_entity(buffer, true); - } else if (chars[1] && HTML5.DIGITS_R.test(chars[1])) { - // Decimal entity - buffer.unget(chars.slice(1)); - char = consume_numeric_entity(buffer, false); - } else { - // Not numeric - buffer.unget(chars); - parse_error("expected-numeric-entity"); - } - } else { - var filteredEntityList = ENTITY_KEYS.filter(function(e) { - return e[0] == chars[0]; - }); - var entityName = null; - var matches = function(e) { - return e.indexOf(chars) === 0; - }; - while(true) { - if (filteredEntityList.some(matches)) { - filteredEntityList = filteredEntityList.filter(matches); - c = buffer.char(); - if (c !== HTML5.EOF) { - chars += c; - } else { - break; - } - } else { - break; - } - - if (HTML5.ENTITIES[chars]) { - entityName = chars; - if (entityName[entityName.length - 1] == ';') break; - } - } - - if (entityName) { - char = HTML5.ENTITIES[entityName]; - - if (entityName[entityName.length - 1] != ';' && this.from_attribute && (HTML5.ASCII_LETTERS_R.test(chars.substr(entityName.length, 1) || HTML5.DIGITS.test(chars.substr(entityName.length, 1))))) { - buffer.unget(chars); - char = '&'; - } else { - buffer.unget(chars.slice(entityName.length)); - } - } else { - parse_error("expected-named-entity"); - buffer.unget(chars); - } - } - - return char; - } - - function replaceEntityNumbers(c) { - switch(c) { - case 0x00: return 0xFFFD; // REPLACEMENT CHARACTER - case 0x13: return 0x0010; // Carriage return - case 0x80: return 0x20AC; // EURO SIGN - case 0x81: return 0x0081; // - case 0x82: return 0x201A; // SINGLE LOW-9 QUOTATION MARK - case 0x83: return 0x0192; // LATIN SMALL LETTER F WITH HOOK - case 0x84: return 0x201E; // DOUBLE LOW-9 QUOTATION MARK - case 0x85: return 0x2026; // HORIZONTAL ELLIPSIS - case 0x86: return 0x2020; // DAGGER - case 0x87: return 0x2021; // DOUBLE DAGGER - case 0x88: return 0x02C6; // MODIFIER LETTER CIRCUMFLEX ACCENT - case 0x89: return 0x2030; // PER MILLE SIGN - case 0x8A: return 0x0160; // LATIN CAPITAL LETTER S WITH CARON - case 0x8B: return 0x2039; // SINGLE LEFT-POINTING ANGLE QUOTATION MARK - case 0x8C: return 0x0152; // LATIN CAPITAL LIGATURE OE - case 0x8D: return 0x008D; // - case 0x8E: return 0x017D; // LATIN CAPITAL LETTER Z WITH CARON - case 0x8F: return 0x008F; // - case 0x90: return 0x0090; // - case 0x91: return 0x2018; // LEFT SINGLE QUOTATION MARK - case 0x92: return 0x2019; // RIGHT SINGLE QUOTATION MARK - case 0x93: return 0x201C; // LEFT DOUBLE QUOTATION MARK - case 0x94: return 0x201D; // RIGHT DOUBLE QUOTATION MARK - case 0x95: return 0x2022; // BULLET - case 0x96: return 0x2013; // EN DASH - case 0x97: return 0x2014; // EM DASH - case 0x98: return 0x02DC; // SMALL TILDE - case 0x99: return 0x2122; // TRADE MARK SIGN - case 0x9A: return 0x0161; // LATIN SMALL LETTER S WITH CARON - case 0x9B: return 0x203A; // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK - case 0x9C: return 0x0153; // LATIN SMALL LIGATURE OE - case 0x9D: return 0x009D; // - case 0x9E: return 0x017E; // LATIN SMALL LETTER Z WITH CARON - case 0x9F: return 0x0178; // LATIN CAPITAL LETTER Y WITH DIAERESIS - default: - if ((c >= 0xD800 && c <= 0xDFFF) || c >= 0x10FFFF) { /// @todo. The spec says > 0x10FFFF, not >=. Section 8.2.4.69. - return 0xFFFD; - } else if ((c >= 0x0001 && c <= 0x0008) || (c >= 0x000E && c <= 0x001F) || - (c >= 0x007F && c <= 0x009F) || (c >= 0xFDD0 && c <= 0xFDEF) || - c == 0x000B || c == 0xFFFE || c == 0x1FFFE || c == 0x2FFFFE || - c == 0x2FFFF || c == 0x3FFFE || c == 0x3FFFF || c == 0x4FFFE || - c == 0x4FFFF || c == 0x5FFFE || c == 0x5FFFF || c == 0x6FFFE || - c == 0x6FFFF || c == 0x7FFFE || c == 0x7FFFF || c == 0x8FFFE || - c == 0x8FFFF || c == 0x9FFFE || c == 0x9FFFF || c == 0xAFFFE || - c == 0xAFFFF || c == 0xBFFFE || c == 0xBFFFF || c == 0xCFFFE || - c == 0xCFFFF || c == 0xDFFFE || c == 0xDFFFF || c == 0xEFFFE || - c == 0xEFFFF || c == 0xFFFFE || c == 0xFFFFF || c == 0x10FFFE || - c == 0x10FFFF) { - return c; - } - } - } - - function consume_numeric_entity(buffer, hex) { - var allowed, radix; - if (hex) { - allowed = HTML5.HEX_DIGITS_R; - radix = 16; - } else { - allowed = HTML5.DIGITS_R; - radix = 10; - } - - var chars = ''; - - var c = buffer.char(); - while(c !== HTML5.EOF && allowed.test(c)) { - chars = chars + c; - c = buffer.char(); - } - - var charAsInt = parseInt(chars, radix); - - var replacement = replaceEntityNumbers(charAsInt); - if (replacement) { - parse_error("invalid-numeric-entity-replaced", {old: charAsInt, 'new': replacement}); - charAsInt = replacement; - } - - var char = String.fromCharCode(charAsInt); - /*if (charAsInt <= 0x10FFFF && !(charAsInt >= 0xD800 && charAsInt <= 0xDFFF)) { - } else { - char = String.fromCharCode(0xFFFD); - parse_error("cant-convert-numeric-entity"); - } */ - - if (c !== ';') { - parse_error("numeric-entity-without-semicolon"); - buffer.unget(c); - } - - return char; - } - - function process_entity_in_attribute(buffer) { - var entity = consume_entity(buffer); - if (entity) { - current_token.data.last().nodeValue += entity; - } else { - current_token.data.last().nodeValue += '&'; - } - } - - function process_solidus_in_tag(buffer) { - var data = buffer.peek(1); - if (current_token.type == 'StartTag' && data == '>') { - current_token.type = 'EmptyTag'; - return true; - } else { - parse_error("incorrectly-placed-solidus"); - return false; - } - } - - function tag_open_state(buffer) { - var data = buffer.char(); - if (content_model == Models.PCDATA) { - if (data === HTML5.EOF) { - parse_error("bare-less-than-sign-at-eof"); - emitToken({type: 'Characters', data: '<'}); - newState(data_state); - } else if (data !== HTML5.EOF && HTML5.ASCII_LETTERS_R.test(data)) { - current_token = {type: 'StartTag', name: data, data: []}; - newState(tag_name_state); - } else if (data == '!') { - newState(markup_declaration_open_state); - } else if (data == '/') { - newState(close_tag_open_state); - } else if (data == '>') { - // XXX In theory it could be something besides a tag name. But - // do we really care? - parse_error("expected-tag-name-but-got-right-bracket"); - emitToken({type: 'Characters', data: "<>"}); - newState(data_state); - } else if (data == '?') { - // XXX In theory it could be something besides a tag name. But - // do we really care? - parse_error("expected-tag-name-but-got-question-mark"); - buffer.unget(data); - newState(bogus_comment_state); - } else { - // XXX - parse_error("expected-tag-name"); - emitToken({type: 'Characters', data: "<"}); - buffer.unget(data); - newState(data_state); - } - } else { - // We know the content model flag is set to either RCDATA or CDATA or SCRIPT_CDATA - // now because this state can never be entered with the PLAINTEXT - // flag. - if (data === '/') { - newState(close_tag_open_state); - } else { - emitToken({type: 'Characters', data: "<"}); - buffer.unget(data); - newState(data_state); - } - } - return true; - } - - function close_tag_open_state(buffer) { - if (content_model == Models.RCDATA || content_model == Models.CDATA || content_model == Models.SCRIPT_CDATA) { - var chars = ''; - if (current_token) { - for(var i = 0; i <= current_token.name.length; i++) { - var c = buffer.char(); - if (c === HTML5.EOF) break; - chars += c; - } - buffer.unget(chars); - } - - if (current_token && - current_token.name.toLowerCase() == chars.slice(0, current_token.name.length).toLowerCase() && - (chars.length > current_token.name.length ? new RegExp('[' + HTML5.SPACE_CHARACTERS_IN + '>') { - parse_error("expected-closing-tag-but-got-right-bracket"); - newState(data_state); - } else { - parse_error("expected-closing-tag-but-got-char", {data: data}); // param 1 is datavars: - buffer.unget(data); - newState(bogus_comment_state); - } - return true; - } - - function tag_name_state(buffer) { - var data = buffer.char(); - if (data === HTML5.EOF) { - parse_error('eof-in-tag-name'); - emit_current_token(); - } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { - newState(before_attribute_name_state); - } else if (HTML5.ASCII_LETTERS_R.test(data)) { - var c = buffer.matchWhile(HTML5.ASCII_LETTERS); - if (c !== HTML5.EOF) { - current_token.name += data + c; - } else { - current_token.name += data; - buffer.unget(c); - newState(data_state); - } - } else if (data == '>') { - emit_current_token(); - } else if (data == '/') { - process_solidus_in_tag(buffer); - newState(self_closing_tag_state); - } else { - current_token.name += data; - } - buffer.commit(); - - return true; - } - - function before_attribute_name_state(buffer) { - var data = buffer.shift(1); - if (data === HTML5.EOF) { - parse_error("expected-attribute-name-but-got-eof"); - emit_current_token(); - } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { - buffer.matchWhile(HTML5.SPACE_CHARACTERS); - } else if (HTML5.ASCII_LETTERS_R.test(data)) { - current_token.data.push({nodeName: data, nodeValue: ""}); - newState(attribute_name_state); - } else if (data == '>') { - emit_current_token(); - } else if (data == '/') { - newState(self_closing_tag_state); - } else if (data == "'" || data == '"' || data == '=') { - parse_error("invalid-character-in-attribute-name"); - current_token.data.push({nodeName: data, nodeValue: ""}); - newState(attribute_name_state); - } else { - current_token.data.push({nodeName: data, nodeValue: ""}); - newState(attribute_name_state); - } - return true; - } - - function attribute_name_state(buffer) { - var data = buffer.shift(1); - var leavingThisState = true; - var emitToken = false; - if (data === HTML5.EOF) { - parse_error("eof-in-attribute-name"); - newState(data_state); - emitToken = true; - } else if (data == '=') { - newState(before_attribute_value_state); - } else if (HTML5.ASCII_LETTERS_R.test(data)) { - current_token.data.last().nodeName += data + buffer.matchWhile(HTML5.ASCII_LETTERS); - leavingThisState = false; - } else if (data == '>') { - // XXX If we emit here the attributes are converted to a dict - // without being checked and when the code below runs we error - // because data is a dict not a list - emitToken = true; - } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { - newState(after_attribute_name_state); - } else if (data == '/') { - if (!process_solidus_in_tag(buffer)) { - newState(before_attribute_name_state); - } - } else if (data == "'" || data == '"') { - parse_error("invalid-character-in-attribute-name"); - current_token.data.last().nodeName += data; - leavingThisState = false; - } else { - current_token.data.last().nodeName += data; - leavingThisState = false; - } - - if (leavingThisState) { - // Attributes are not dropped at this stage. That happens when the - // start tag token is emitted so values can still be safely appended - // to attributes, but we do want to report the parse error in time. - if (this.lowercase_attr_name) { - current_token.data.last().nodeName = current_token.data.last().nodeName.toLowerCase(); - } - for (var k in current_token.data.slice(0, -1)) { - // FIXME this is a fucking mess. - if (current_token.data.slice(-1)[0] == current_token.data.slice(0, -1)[k].name) { - parse_error("duplicate-attribute"); - break; // Don't emit more than one of these errors - } - } - if (emitToken) emit_current_token(); - } else { - buffer.commit(); - } - return true; - } - - function after_attribute_name_state(buffer) { - var data = buffer.shift(1); - if (data === HTML5.EOF) { - parse_error("expected-end-of-tag-but-got-eof"); - emit_current_token(); - } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { - buffer.matchWhile(HTML5.SPACE_CHARACTERS); - } else if (data == '=') { - newState(before_attribute_value_state); - } else if (data == '>') { - emit_current_token(); - } else if (HTML5.ASCII_LETTERS_R.test(data)) { - current_token.data.push({nodeName: data, nodeValue: ""}); - newState(attribute_name_state); - } else if (data == '/') { - newState(self_closing_tag_state); - } else { - current_token.data.push({nodeName: data, nodeValue: ""}); - newState(attribute_name_state); - } - return true; - } - - function before_attribute_value_state(buffer) { - var data = buffer.shift(1); - if (data === HTML5.EOF) { - parse_error("expected-attribute-value-but-got-eof"); - emit_current_token(); - newState(attribute_value_unquoted_state); - } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { - buffer.matchWhile(HTML5.SPACE_CHARACTERS); - } else if (data == '"') { - newState(attribute_value_double_quoted_state); - } else if (data == '&') { - newState(attribute_value_unquoted_state); - buffer.unget(data); - } else if (data == "'") { - newState(attribute_value_single_quoted_state); - } else if (data == '>') { - emit_current_token(); - } else if (data == '=') { - parse_error("equals-in-unquoted-attribute-value"); - current_token.data.last().nodeValue += data; - newState(attribute_value_unquoted_state); - } else { - current_token.data.last().nodeValue += data; - newState(attribute_value_unquoted_state); - } - - return true; - } - - function attribute_value_double_quoted_state(buffer) { - var data = buffer.shift(1); - if (data === HTML5.EOF) { - parse_error("eof-in-attribute-value-double-quote"); - newState(data_state); - } else if (data == '"') { - newState(after_attribute_value_state); - } else if (data == '&') { - process_entity_in_attribute(buffer); - } else { - var s = buffer.matchUntil('["&]'); - if (s !== HTML5.EOF) data = data + s; - current_token.data.last().nodeValue += data; - } - return true; - } - - function attribute_value_single_quoted_state(buffer) { - var data = buffer.shift(1); - if (data === HTML5.EOF) { - parse_error("eof-in-attribute-value-single-quote"); - emit_current_token(); - } else if (data == "'") { - newState(after_attribute_value_state); - } else if (data == '&') { - process_entity_in_attribute(buffer); - } else { - current_token.data.last().nodeValue += data + buffer.matchUntil("['&]"); - } - return true; - } - - function attribute_value_unquoted_state(buffer) { - var data = buffer.shift(1); - if (data === HTML5.EOF) { - parse_error("eof-in-attribute-value-no-quotes"); - buffer.commit(); - emit_current_token(); - } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { - newState(before_attribute_name_state); - } else if (data == '&') { - process_entity_in_attribute(buffer); - } else if (data == '>') { - emit_current_token(); - } else if (data == '"' || data == "'" || data == '=') { - parse_error("unexpected-character-in-unquoted-attribute-value"); - current_token.data.last().nodeValue += data; - } else { - var o = buffer.matchUntil("["+ HTML5.SPACE_CHARACTERS_IN + '&<>' +"]"); - if (o === HTML5.EOF) { - parse_error("eof-in-attribute-value-no-quotes"); - emit_current_token(); - } - // Commit here since this state is re-enterable and its outcome won't change with more data. - buffer.commit(); - current_token.data.last().nodeValue += data + o; - } - return true; - } - - function after_attribute_value_state(buffer) { - var data = buffer.shift(1); - if (data === HTML5.EOF) { - parse_error( "unexpected-EOF-after-attribute-value"); - emit_current_token(); - buffer.unget(data); - newState(data_state); - } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { - newState(before_attribute_name_state); - } else if (data == '>') { - emit_current_token(); - newState(data_state); - } else if (data == '/') { - newState(self_closing_tag_state); - } else { - emitToken({type: 'ParseError', data: "unexpected-character-after-attribute-value"}); - buffer.unget(data); - newState(before_attribute_name_state); - } - return true; - } - - function self_closing_tag_state(buffer) { - var c = buffer.shift(1); - if (c === HTML5.EOF) { - parse_error("eof-in-tag-name"); - buffer.unget(c); - newState(data_state); - } else if (c == '>') { - current_token.self_closing = true; - emit_current_token(); - newState(data_state); - } else { - parse_error("expected-self-closing-tag"); - buffer.unget(c); - newState(before_attribute_name_state); - } - return true; - } - - function bogus_comment_state(buffer) { - var s = buffer.matchUntil('>'); - if (s === HTML5.EOF) { - s = ''; - } - var tok = {type: 'Comment', data: s}; - buffer.char(); - emitToken(tok); - newState(data_state); - return true; - } - - function markup_declaration_open_state(buffer) { - var chars = buffer.shift(2); - if (chars === '--') { - current_token = {type: 'Comment', data: ''}; - newState(comment_start_state); - } else { - var newchars = buffer.shift(5); - if (newchars === HTML5.EOF || chars === HTML5.EOF) { - parse_error("expected-dashes-or-doctype"); - newState(bogus_comment_state); - buffer.unget(chars); - return true; - } - - chars += newchars; - if (chars.toUpperCase() == 'DOCTYPE') { - current_token = {type: 'Doctype', name: '', publicId: null, systemId: null, correct: true}; - newState(doctype_state); - } else if (tree.open_elements.last() && tree.open_elements.last().namespace && chars == '[CDATA[') { - newState(cdata_section_state); - } else { - parse_error("expected-dashes-or-doctype"); - buffer.unget(chars); - newState(bogus_comment_state); - } - } - return true; - } - - function cdata_section_state(buffer) { - var data = buffer.matchUntil(/\]\]>/); - var slice; - if (/\]\]>$/.match(data)) { - slice = 4; - } else { - slice = 0; - } - - emitToken({type: 'Characters', data: data.slice(0, data.length - slice)}); - newState(data_state); - } - - function comment_start_state(buffer) { - var data = buffer.shift(1); - if (data === HTML5.EOF) { - parse_error("eof-in-comment"); - emitToken(current_token); - newState(data_state); - } else if (data == '-') { - newState(comment_start_dash_state); - } else if (data == '>') { - parse_error("incorrect comment"); - emitToken(current_token); - newState(data_state); - } else { - current_token.data += data + buffer.matchUntil('-'); - newState(comment_state); - } - return true; - } - - function comment_start_dash_state(buffer) { - var data = buffer.shift(1); - if (data === HTML5.EOF) { - parse_error("eof-in-comment"); - emitToken(current_token); - newState(data_state); - } else if (data == '-') { - newState(comment_end_state); - } else if (data == '>') { - parse_error("incorrect-comment"); - emitToken(current_token); - newState(data_state); - } else { - var s = buffer.matchUntil('-'); - if (s !== HTML5.EOF) data = data + s; - current_token.data += '-' + data; - newState(comment_state); - } - return true; - } - - function comment_state(buffer) { - var data = buffer.shift(1); - if (data === HTML5.EOF) { - parse_error("eof-in-comment"); - emitToken(current_token); - newState(data_state); - } else if (data == '-') { - newState(comment_end_dash_state); - } else { - current_token.data += data + buffer.matchUntil('-'); - } - return true; - } - - function comment_end_dash_state(buffer) { - var data = buffer.char(); - if (data === HTML5.EOF) { - parse_error("eof-in-comment-end-dash"); - emitToken(current_token); - newState(data_state); - } else if (data == '-') { - newState(comment_end_state); - } else { - current_token.data += '-' + data + buffer.matchUntil('-'); - // Consume the next character which is either a "-" or an :EOF as - // well so if there's a "-" directly after the "-" we go nicely to - // the "comment end state" without emitting a ParseError there. - buffer.char(); - } - return true; - } - - function comment_end_state(buffer) { - var data = buffer.shift(1); - if (data === HTML5.EOF) { - parse_error("eof-in-comment-double-dash"); - emitToken(current_token); - newState(data_state); - } else if (data == '>') { - emitToken(current_token); - newState(data_state); - } else if (data == '-') { - parse_error("unexpected-dash-after-double-dash-in-comment"); - current_token.data += data; - } else { - // XXX - parse_error("unexpected-char-in-comment"); - current_token.data += '--' + data; - newState(comment_state); - } - return true; - } - - function doctype_state(buffer) { - var data = buffer.shift(1); - if (HTML5.SPACE_CHARACTERS_R.test(data)) { - newState(before_doctype_name_state); - } else { - parse_error("need-space-after-doctype"); - buffer.unget(data); - newState(before_doctype_name_state); - } - return true; - } - - function before_doctype_name_state(buffer) { - var data = buffer.shift(1); - if (data === HTML5.EOF) { - parse_error("expected-doctype-name-but-got-eof"); - current_token.correct = false; - emit_current_token(); - newState(data_state); - } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { - } else if (data == '>') { - parse_error("expected-doctype-name-but-got-right-bracket"); - current_token.correct = false; - emit_current_token(); - newState(data_state); - } else { - current_token.name = data.toLowerCase(); - newState(doctype_name_state); - } - return true; - } - - function doctype_name_state(buffer) { - var data = buffer.shift(1); - if (data === HTML5.EOF) { - current_token.correct = false; - buffer.unget(data); - parse_error("eof-in-doctype"); - emit_current_token(); - newState(data_state); - } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { - newState(bogus_doctype_state); - } else if (data == '>') { - emit_current_token(); - newState(data_state); - } else { - current_token.name += data.toLowerCase(); - } - return true; - } - - function bogus_doctype_state(buffer) { - var data = buffer.shift(1); - current_token.correct = false; - if (data === HTML5.EOF) { - throw(new Error("Unimplemented!")); - } else if (data == '>') { - emit_current_token(); - newState(data_state); - } - return true; - } - - function parse_error(message, context) { - emitToken({type: 'ParseError', data: message}); - HTML5.debug('tokenizer.parseError', message, context); - } - - function emit_current_token() { - var tok = current_token; - switch(tok.type) { - case 'StartTag': - case 'EndTag': - case 'EmptyTag': - if (tok.type == 'EndTag' && tok.self_closing) { - parse_error('self-closing-end-tag'); - } - break; - } - if (current_token.name.toLowerCase() == "script" && tok.type == 'EndTag' && script_buffer) { - emitToken({ type: 'Characters', data: script_buffer }); - script_buffer = null; - } - emitToken(tok); - newState(data_state); - } - - function normalize_token(token) { - if (token.type == 'EmptyTag') { - if (HTML5.VOID_ELEMENTS.indexOf(token.name) == -1) { - parse_error('incorrectly-placed-solidus'); - } - token.type = 'StartTag'; - } - - if (token.type == 'StartTag') { - token.name = token.name.toLowerCase(); - if (token.data.length !== 0) { - var data = {}; - // the first value for each key wins - token.data.reverse(); - token.data.forEach(function(e) { - data[e.nodeName.toLowerCase()] = e.nodeValue; - }); - token.data = []; - for(var k in data) { - token.data.push({nodeName: k, nodeValue: data[k]}); - } - // restore original attribute order - token.data.reverse(); - } - } else if (token.type == 'EndTag') { - if (token.data.length !== 0) parse_error('attributes-in-end-tag'); - token.name = token.name.toLowerCase(); - } - - return token; - } - - if (typeof input === 'undefined') throw(new Error("No input given")); - this.document = document; - this.__defineSetter__('content_model', function(model) { - HTML5.debug('tokenizer.content_model=', model); - content_model = model; - }); - this.__defineGetter__('content_model', function() { - return content_model; - }); - function newState(newstate) { - HTML5.debug('tokenizer.state=', newstate.name); - state = newstate; - buffer.commit(); - } - - newState(data_state); - - if (input instanceof events.EventEmitter) { - source = input; - this.pump = null; + function consume_entity(buffer, from_attr) { + var char = null; + var chars = buffer.char(); + var c; + if (chars === HTML5.EOF) return false; + if (chars.match(HTML5.SPACE_CHARACTERS) || chars == '<' || chars == '&') { + buffer.unget(chars); + } else if (chars[0] == '#') { // Maybe a numeric entity + c = buffer.shift(2); + if (c === HTML5.EOF) { + buffer.unget(chars); + return false; + } + chars += c; + if (chars[1] && chars[1].toLowerCase() == 'x' && HTML5.HEX_DIGITS_R.test(chars[2])) { + // Hex entity + buffer.unget(chars[2]); + char = consume_numeric_entity(buffer, true); + } else if (chars[1] && HTML5.DIGITS_R.test(chars[1])) { + // Decimal entity + buffer.unget(chars.slice(1)); + char = consume_numeric_entity(buffer, false); + } else { + // Not numeric + buffer.unget(chars); + parse_error("expected-numeric-entity"); + } + } else { + var filteredEntityList = ENTITY_KEYS.filter(function(e) { + return e[0] == chars[0]; + }); + var entityName = null; + var matches = function(e) { + return e.indexOf(chars) === 0; + }; + while(true) { + if (filteredEntityList.some(matches)) { + filteredEntityList = filteredEntityList.filter(matches); + c = buffer.char(); + if (c !== HTML5.EOF) { + chars += c; + } else { + break; + } } else { - source = new events.EventEmitter(); - this.pump = function() { - source.emit('data', input); - source.emit('end'); - }; + break; } - - source.addListener('data', function(data) { - if (typeof data !== 'string') data = data.toString(); - buffer.append(data); - try { - while(state(buffer)); - } catch(e) { - if (e != HTML5.DRAIN) { - throw(e); - } else { - HTML5.debug('tokenizer.drain', 'Drain'); - buffer.undo(); - } - } + + if (HTML5.ENTITIES[chars]) { + entityName = chars; + if (entityName[entityName.length - 1] == ';') break; + } + } + + if (entityName) { + char = HTML5.ENTITIES[entityName]; + + if (entityName[entityName.length - 1] != ';' && this.from_attribute && (HTML5.ASCII_LETTERS_R.test(chars.substr(entityName.length, 1) || HTML5.DIGITS.test(chars.substr(entityName.length, 1))))) { + buffer.unget(chars); + char = '&'; + } else { + buffer.unget(chars.slice(entityName.length)); + } + } else { + parse_error("expected-named-entity"); + buffer.unget(chars); + } + } + + return char; + } + + function replaceEntityNumbers(c) { + switch(c) { + case 0x00: return 0xFFFD; // REPLACEMENT CHARACTER + case 0x13: return 0x0010; // Carriage return + case 0x80: return 0x20AC; // EURO SIGN + case 0x81: return 0x0081; // + case 0x82: return 0x201A; // SINGLE LOW-9 QUOTATION MARK + case 0x83: return 0x0192; // LATIN SMALL LETTER F WITH HOOK + case 0x84: return 0x201E; // DOUBLE LOW-9 QUOTATION MARK + case 0x85: return 0x2026; // HORIZONTAL ELLIPSIS + case 0x86: return 0x2020; // DAGGER + case 0x87: return 0x2021; // DOUBLE DAGGER + case 0x88: return 0x02C6; // MODIFIER LETTER CIRCUMFLEX ACCENT + case 0x89: return 0x2030; // PER MILLE SIGN + case 0x8A: return 0x0160; // LATIN CAPITAL LETTER S WITH CARON + case 0x8B: return 0x2039; // SINGLE LEFT-POINTING ANGLE QUOTATION MARK + case 0x8C: return 0x0152; // LATIN CAPITAL LIGATURE OE + case 0x8D: return 0x008D; // + case 0x8E: return 0x017D; // LATIN CAPITAL LETTER Z WITH CARON + case 0x8F: return 0x008F; // + case 0x90: return 0x0090; // + case 0x91: return 0x2018; // LEFT SINGLE QUOTATION MARK + case 0x92: return 0x2019; // RIGHT SINGLE QUOTATION MARK + case 0x93: return 0x201C; // LEFT DOUBLE QUOTATION MARK + case 0x94: return 0x201D; // RIGHT DOUBLE QUOTATION MARK + case 0x95: return 0x2022; // BULLET + case 0x96: return 0x2013; // EN DASH + case 0x97: return 0x2014; // EM DASH + case 0x98: return 0x02DC; // SMALL TILDE + case 0x99: return 0x2122; // TRADE MARK SIGN + case 0x9A: return 0x0161; // LATIN SMALL LETTER S WITH CARON + case 0x9B: return 0x203A; // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK + case 0x9C: return 0x0153; // LATIN SMALL LIGATURE OE + case 0x9D: return 0x009D; // + case 0x9E: return 0x017E; // LATIN SMALL LETTER Z WITH CARON + case 0x9F: return 0x0178; // LATIN CAPITAL LETTER Y WITH DIAERESIS + default: + if ((c >= 0xD800 && c <= 0xDFFF) || c >= 0x10FFFF) { /// @todo. The spec says > 0x10FFFF, not >=. Section 8.2.4.69. + return 0xFFFD; + } else if ((c >= 0x0001 && c <= 0x0008) || (c >= 0x000E && c <= 0x001F) || + (c >= 0x007F && c <= 0x009F) || (c >= 0xFDD0 && c <= 0xFDEF) || + c == 0x000B || c == 0xFFFE || c == 0x1FFFE || c == 0x2FFFFE || + c == 0x2FFFF || c == 0x3FFFE || c == 0x3FFFF || c == 0x4FFFE || + c == 0x4FFFF || c == 0x5FFFE || c == 0x5FFFF || c == 0x6FFFE || + c == 0x6FFFF || c == 0x7FFFE || c == 0x7FFFF || c == 0x8FFFE || + c == 0x8FFFF || c == 0x9FFFE || c == 0x9FFFF || c == 0xAFFFE || + c == 0xAFFFF || c == 0xBFFFE || c == 0xBFFFF || c == 0xCFFFE || + c == 0xCFFFF || c == 0xDFFFE || c == 0xDFFFF || c == 0xEFFFE || + c == 0xEFFFF || c == 0xFFFFE || c == 0xFFFFF || c == 0x10FFFE || + c == 0x10FFFF) { + return c; + } + } + } + + function consume_numeric_entity(buffer, hex) { + var allowed, radix; + if (hex) { + allowed = HTML5.HEX_DIGITS_R; + radix = 16; + } else { + allowed = HTML5.DIGITS_R; + radix = 10; + } + + var chars = ''; + + var c = buffer.char(); + while(c !== HTML5.EOF && allowed.test(c)) { + chars = chars + c; + c = buffer.char(); + } + + var charAsInt = parseInt(chars, radix); + + var replacement = replaceEntityNumbers(charAsInt); + if (replacement) { + parse_error("invalid-numeric-entity-replaced", {old: charAsInt, 'new': replacement}); + charAsInt = replacement; + } + + var char = String.fromCharCode(charAsInt); + /*if (charAsInt <= 0x10FFFF && !(charAsInt >= 0xD800 && charAsInt <= 0xDFFF)) { + } else { + char = String.fromCharCode(0xFFFD); + parse_error("cant-convert-numeric-entity"); + } */ + + if (c !== ';') { + parse_error("numeric-entity-without-semicolon"); + buffer.unget(c); + } + + return char; + } + + function process_entity_in_attribute(buffer) { + var entity = consume_entity(buffer); + if (entity) { + current_token.data.last().nodeValue += entity; + } else { + current_token.data.last().nodeValue += '&'; + } + } + + function process_solidus_in_tag(buffer) { + var data = buffer.peek(1); + if (current_token.type == 'StartTag' && data == '>') { + current_token.type = 'EmptyTag'; + return true; + } else { + parse_error("incorrectly-placed-solidus"); + return false; + } + } + + function tag_open_state(buffer) { + var data = buffer.char(); + if (content_model == Models.PCDATA) { + if (data === HTML5.EOF) { + parse_error("bare-less-than-sign-at-eof"); + emitToken({type: 'Characters', data: '<'}); + newState(data_state); + } else if (data !== HTML5.EOF && HTML5.ASCII_LETTERS_R.test(data)) { + current_token = {type: 'StartTag', name: data, data: []}; + newState(tag_name_state); + } else if (data == '!') { + newState(markup_declaration_open_state); + } else if (data == '/') { + newState(close_tag_open_state); + } else if (data == '>') { + // XXX In theory it could be something besides a tag name. But + // do we really care? + parse_error("expected-tag-name-but-got-right-bracket"); + emitToken({type: 'Characters', data: "<>"}); + newState(data_state); + } else if (data == '?') { + // XXX In theory it could be something besides a tag name. But + // do we really care? + parse_error("expected-tag-name-but-got-question-mark"); + buffer.unget(data); + newState(bogus_comment_state); + } else { + // XXX + parse_error("expected-tag-name"); + emitToken({type: 'Characters', data: "<"}); + buffer.unget(data); + newState(data_state); + } + } else { + // We know the content model flag is set to either RCDATA or CDATA or SCRIPT_CDATA + // now because this state can never be entered with the PLAINTEXT + // flag. + if (data === '/') { + newState(close_tag_open_state); + } else { + emitToken({type: 'Characters', data: "<"}); + buffer.unget(data); + newState(data_state); + } + } + return true; + } + + function close_tag_open_state(buffer) { + if (content_model == Models.RCDATA || content_model == Models.CDATA || content_model == Models.SCRIPT_CDATA) { + var chars = ''; + if (current_token) { + for(var i = 0; i <= current_token.name.length; i++) { + var c = buffer.char(); + if (c === HTML5.EOF) break; + chars += c; + } + buffer.unget(chars); + } + + if (current_token && + current_token.name.toLowerCase() == chars.slice(0, current_token.name.length).toLowerCase() && + (chars.length > current_token.name.length ? new RegExp('[' + HTML5.SPACE_CHARACTERS_IN + '>') { + parse_error("expected-closing-tag-but-got-right-bracket"); + newState(data_state); + } else { + parse_error("expected-closing-tag-but-got-char", {data: data}); // param 1 is datavars: + buffer.unget(data); + newState(bogus_comment_state); + } + return true; + } + + function tag_name_state(buffer) { + var data = buffer.char(); + if (data === HTML5.EOF) { + parse_error('eof-in-tag-name'); + emit_current_token(); + } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { + newState(before_attribute_name_state); + } else if (HTML5.ASCII_LETTERS_R.test(data)) { + var c = buffer.matchWhile(HTML5.ASCII_LETTERS); + if (c !== HTML5.EOF) { + current_token.name += data + c; + } else { + current_token.name += data; + buffer.unget(c); + newState(data_state); + } + } else if (data == '>') { + emit_current_token(); + } else if (data == '/') { + process_solidus_in_tag(buffer); + newState(self_closing_tag_state); + } else { + current_token.name += data; + } + buffer.commit(); + + return true; + } + + function before_attribute_name_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error("expected-attribute-name-but-got-eof"); + emit_current_token(); + } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { + buffer.matchWhile(HTML5.SPACE_CHARACTERS); + } else if (HTML5.ASCII_LETTERS_R.test(data)) { + current_token.data.push({nodeName: data, nodeValue: ""}); + newState(attribute_name_state); + } else if (data == '>') { + emit_current_token(); + } else if (data == '/') { + newState(self_closing_tag_state); + } else if (data == "'" || data == '"' || data == '=') { + parse_error("invalid-character-in-attribute-name"); + current_token.data.push({nodeName: data, nodeValue: ""}); + newState(attribute_name_state); + } else { + current_token.data.push({nodeName: data, nodeValue: ""}); + newState(attribute_name_state); + } + return true; + } + + function attribute_name_state(buffer) { + var data = buffer.shift(1); + var leavingThisState = true; + var emitToken = false; + if (data === HTML5.EOF) { + parse_error("eof-in-attribute-name"); + newState(data_state); + emitToken = true; + } else if (data == '=') { + newState(before_attribute_value_state); + } else if (HTML5.ASCII_LETTERS_R.test(data)) { + current_token.data.last().nodeName += data + buffer.matchWhile(HTML5.ASCII_LETTERS); + leavingThisState = false; + } else if (data == '>') { + // XXX If we emit here the attributes are converted to a dict + // without being checked and when the code below runs we error + // because data is a dict not a list + emitToken = true; + } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { + newState(after_attribute_name_state); + } else if (data == '/') { + if (!process_solidus_in_tag(buffer)) { + newState(before_attribute_name_state); + } + } else if (data == "'" || data == '"') { + parse_error("invalid-character-in-attribute-name"); + current_token.data.last().nodeName += data; + leavingThisState = false; + } else { + current_token.data.last().nodeName += data; + leavingThisState = false; + } + + if (leavingThisState) { + // Attributes are not dropped at this stage. That happens when the + // start tag token is emitted so values can still be safely appended + // to attributes, but we do want to report the parse error in time. + if (this.lowercase_attr_name) { + current_token.data.last().nodeName = current_token.data.last().nodeName.toLowerCase(); + } + for (var k in current_token.data.slice(0, -1)) { + // FIXME this is a fucking mess. + if (current_token.data.slice(-1)[0] == current_token.data.slice(0, -1)[k].name) { + parse_error("duplicate-attribute"); + break; // Don't emit more than one of these errors + } + } + if (emitToken) emit_current_token(); + } else { + buffer.commit(); + } + return true; + } + + function after_attribute_name_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error("expected-end-of-tag-but-got-eof"); + emit_current_token(); + } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { + buffer.matchWhile(HTML5.SPACE_CHARACTERS); + } else if (data == '=') { + newState(before_attribute_value_state); + } else if (data == '>') { + emit_current_token(); + } else if (HTML5.ASCII_LETTERS_R.test(data)) { + current_token.data.push({nodeName: data, nodeValue: ""}); + newState(attribute_name_state); + } else if (data == '/') { + newState(self_closing_tag_state); + } else { + current_token.data.push({nodeName: data, nodeValue: ""}); + newState(attribute_name_state); + } + return true; + } + + function before_attribute_value_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error("expected-attribute-value-but-got-eof"); + emit_current_token(); + newState(attribute_value_unquoted_state); + } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { + buffer.matchWhile(HTML5.SPACE_CHARACTERS); + } else if (data == '"') { + newState(attribute_value_double_quoted_state); + } else if (data == '&') { + newState(attribute_value_unquoted_state); + buffer.unget(data); + } else if (data == "'") { + newState(attribute_value_single_quoted_state); + } else if (data == '>') { + emit_current_token(); + } else if (data == '=') { + parse_error("equals-in-unquoted-attribute-value"); + current_token.data.last().nodeValue += data; + newState(attribute_value_unquoted_state); + } else { + current_token.data.last().nodeValue += data; + newState(attribute_value_unquoted_state); + } + + return true; + } + + function attribute_value_double_quoted_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error("eof-in-attribute-value-double-quote"); + newState(data_state); + } else if (data == '"') { + newState(after_attribute_value_state); + } else if (data == '&') { + process_entity_in_attribute(buffer); + } else { + var s = buffer.matchUntil('["&]'); + if (s !== HTML5.EOF) data = data + s; + current_token.data.last().nodeValue += data; + } + return true; + } + + function attribute_value_single_quoted_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error("eof-in-attribute-value-single-quote"); + emit_current_token(); + } else if (data == "'") { + newState(after_attribute_value_state); + } else if (data == '&') { + process_entity_in_attribute(buffer); + } else { + current_token.data.last().nodeValue += data + buffer.matchUntil("['&]"); + } + return true; + } + + function attribute_value_unquoted_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error("eof-in-attribute-value-no-quotes"); + buffer.commit(); + emit_current_token(); + } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { + newState(before_attribute_name_state); + } else if (data == '&') { + process_entity_in_attribute(buffer); + } else if (data == '>') { + emit_current_token(); + } else if (data == '"' || data == "'" || data == '=') { + parse_error("unexpected-character-in-unquoted-attribute-value"); + current_token.data.last().nodeValue += data; + } else { + var o = buffer.matchUntil("["+ HTML5.SPACE_CHARACTERS_IN + '&<>' +"]"); + if (o === HTML5.EOF) { + parse_error("eof-in-attribute-value-no-quotes"); + emit_current_token(); + } + // Commit here since this state is re-enterable and its outcome won't change with more data. + buffer.commit(); + current_token.data.last().nodeValue += data + o; + } + return true; + } + + function after_attribute_value_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error( "unexpected-EOF-after-attribute-value"); + emit_current_token(); + buffer.unget(data); + newState(data_state); + } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { + newState(before_attribute_name_state); + } else if (data == '>') { + emit_current_token(); + newState(data_state); + } else if (data == '/') { + newState(self_closing_tag_state); + } else { + emitToken({type: 'ParseError', data: "unexpected-character-after-attribute-value"}); + buffer.unget(data); + newState(before_attribute_name_state); + } + return true; + } + + function self_closing_tag_state(buffer) { + var c = buffer.shift(1); + if (c === HTML5.EOF) { + parse_error("eof-in-tag-name"); + buffer.unget(c); + newState(data_state); + } else if (c == '>') { + current_token.self_closing = true; + emit_current_token(); + newState(data_state); + } else { + parse_error("expected-self-closing-tag"); + buffer.unget(c); + newState(before_attribute_name_state); + } + return true; + } + + function bogus_comment_state(buffer) { + var s = buffer.matchUntil('>'); + if (s === HTML5.EOF) { + s = ''; + } + var tok = {type: 'Comment', data: s}; + buffer.char(); + emitToken(tok); + newState(data_state); + return true; + } + + function markup_declaration_open_state(buffer) { + var chars = buffer.shift(2); + if (chars === '--') { + current_token = {type: 'Comment', data: ''}; + newState(comment_start_state); + } else { + var newchars = buffer.shift(5); + if (newchars === HTML5.EOF || chars === HTML5.EOF) { + parse_error("expected-dashes-or-doctype"); + newState(bogus_comment_state); + buffer.unget(chars); + return true; + } + + chars += newchars; + if (chars.toUpperCase() == 'DOCTYPE') { + current_token = {type: 'Doctype', name: '', publicId: null, systemId: null, correct: true}; + newState(doctype_state); + } else if (tree.open_elements.last() && tree.open_elements.last().namespace && chars == '[CDATA[') { + newState(cdata_section_state); + } else { + parse_error("expected-dashes-or-doctype"); + buffer.unget(chars); + newState(bogus_comment_state); + } + } + return true; + } + + function cdata_section_state(buffer) { + var data = buffer.matchUntil(/\]\]>/); + var slice; + if (/\]\]>$/.match(data)) { + slice = 4; + } else { + slice = 0; + } + + emitToken({type: 'Characters', data: data.slice(0, data.length - slice)}); + newState(data_state); + } + + function comment_start_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error("eof-in-comment"); + emitToken(current_token); + newState(data_state); + } else if (data == '-') { + newState(comment_start_dash_state); + } else if (data == '>') { + parse_error("incorrect comment"); + emitToken(current_token); + newState(data_state); + } else { + current_token.data += data + buffer.matchUntil('-'); + newState(comment_state); + } + return true; + } + + function comment_start_dash_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error("eof-in-comment"); + emitToken(current_token); + newState(data_state); + } else if (data == '-') { + newState(comment_end_state); + } else if (data == '>') { + parse_error("incorrect-comment"); + emitToken(current_token); + newState(data_state); + } else { + var s = buffer.matchUntil('-'); + if (s !== HTML5.EOF) data = data + s; + current_token.data += '-' + data; + newState(comment_state); + } + return true; + } + + function comment_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error("eof-in-comment"); + emitToken(current_token); + newState(data_state); + } else if (data == '-') { + newState(comment_end_dash_state); + } else { + current_token.data += data + buffer.matchUntil('-'); + } + return true; + } + + function comment_end_dash_state(buffer) { + var data = buffer.char(); + if (data === HTML5.EOF) { + parse_error("eof-in-comment-end-dash"); + emitToken(current_token); + newState(data_state); + } else if (data == '-') { + newState(comment_end_state); + } else { + current_token.data += '-' + data + buffer.matchUntil('-'); + // Consume the next character which is either a "-" or an :EOF as + // well so if there's a "-" directly after the "-" we go nicely to + // the "comment end state" without emitting a ParseError there. + buffer.char(); + } + return true; + } + + function comment_end_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error("eof-in-comment-double-dash"); + emitToken(current_token); + newState(data_state); + } else if (data == '>') { + emitToken(current_token); + newState(data_state); + } else if (data == '-') { + parse_error("unexpected-dash-after-double-dash-in-comment"); + current_token.data += data; + } else { + // XXX + parse_error("unexpected-char-in-comment"); + current_token.data += '--' + data; + newState(comment_state); + } + return true; + } + + function doctype_state(buffer) { + var data = buffer.shift(1); + if (HTML5.SPACE_CHARACTERS_R.test(data)) { + newState(before_doctype_name_state); + } else { + parse_error("need-space-after-doctype"); + buffer.unget(data); + newState(before_doctype_name_state); + } + return true; + } + + function before_doctype_name_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + parse_error("expected-doctype-name-but-got-eof"); + current_token.correct = false; + emit_current_token(); + newState(data_state); + } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { + } else if (data == '>') { + parse_error("expected-doctype-name-but-got-right-bracket"); + current_token.correct = false; + emit_current_token(); + newState(data_state); + } else { + current_token.name = data.toLowerCase(); + newState(doctype_name_state); + } + return true; + } + + function doctype_name_state(buffer) { + var data = buffer.shift(1); + if (data === HTML5.EOF) { + current_token.correct = false; + buffer.unget(data); + parse_error("eof-in-doctype"); + emit_current_token(); + newState(data_state); + } else if (HTML5.SPACE_CHARACTERS_R.test(data)) { + newState(bogus_doctype_state); + } else if (data == '>') { + emit_current_token(); + newState(data_state); + } else { + current_token.name += data.toLowerCase(); + } + return true; + } + + function bogus_doctype_state(buffer) { + var data = buffer.shift(1); + current_token.correct = false; + if (data === HTML5.EOF) { + throw(new Error("Unimplemented!")); + } else if (data == '>') { + emit_current_token(); + newState(data_state); + } + return true; + } + + function parse_error(message, context) { + emitToken({type: 'ParseError', data: message}); + HTML5.debug('tokenizer.parseError', message, context); + } + + function emit_current_token() { + var tok = current_token; + switch(tok.type) { + case 'StartTag': + case 'EndTag': + case 'EmptyTag': + if (tok.type == 'EndTag' && tok.self_closing) { + parse_error('self-closing-end-tag'); + } + break; + } + if (current_token.name.toLowerCase() == "script" && tok.type == 'EndTag' && script_buffer) { + emitToken({ type: 'Characters', data: script_buffer }); + script_buffer = null; + } + emitToken(tok); + newState(data_state); + } + + function normalize_token(token) { + if (token.type == 'EmptyTag') { + if (HTML5.VOID_ELEMENTS.indexOf(token.name) == -1) { + parse_error('incorrectly-placed-solidus'); + } + token.type = 'StartTag'; + } + + if (token.type == 'StartTag') { + token.name = token.name.toLowerCase(); + if (token.data.length !== 0) { + var data = {}; + // the first value for each key wins + token.data.reverse(); + token.data.forEach(function(e) { + data[e.nodeName.toLowerCase()] = e.nodeValue; }); - source.addListener('end', function() { - buffer.eof = true; - while(state(buffer)); - this.emit('end'); - }.bind(this)); + token.data = []; + for(var k in data) { + token.data.push({nodeName: k, nodeValue: data[k]}); + } + // restore original attribute order + token.data.reverse(); + } + } else if (token.type == 'EndTag') { + if (token.data.length !== 0) parse_error('attributes-in-end-tag'); + token.name = token.name.toLowerCase(); + } + + return token; + } + + if (typeof input === 'undefined') throw(new Error("No input given")); + this.document = document; + this.__defineSetter__('content_model', function(model) { + HTML5.debug('tokenizer.content_model=', model); + content_model = model; + }); + this.__defineGetter__('content_model', function() { + return content_model; + }); + function newState(newstate) { + HTML5.debug('tokenizer.state=', newstate.name); + state = newstate; + buffer.commit(); + } + + newState(data_state); + + if (input instanceof events.EventEmitter) { + source = input; + this.pump = null; + } else { + source = new events.EventEmitter(); + this.pump = function() { + source.emit('data', input); + source.emit('end'); + }; + } + + source.addListener('data', function(data) { + if (typeof data !== 'string') data = data.toString(); + buffer.append(data); + try { + while(state(buffer)); + } catch(e) { + if (e != HTML5.DRAIN) { + throw(e); + } else { + HTML5.debug('tokenizer.drain', 'Drain'); + buffer.undo(); + } + } + }); + source.addListener('end', function() { + buffer.eof = true; + while(state(buffer)); + this.emit('end'); + }.bind(this)); }; From 325a3ac24ac08daacdc9cfdfc338e65372fa8034 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Tue, 16 Apr 2013 14:14:34 -0700 Subject: [PATCH 003/938] fix-ups --- packages/html5-tokenizer/constants.js | 2 +- packages/html5-tokenizer/entities.js | 4 +++- packages/html5-tokenizer/tokenizer.js | 2 -- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/html5-tokenizer/constants.js b/packages/html5-tokenizer/constants.js index 83fe69d654..d1514994c6 100644 --- a/packages/html5-tokenizer/constants.js +++ b/packages/html5-tokenizer/constants.js @@ -1,4 +1,4 @@ -var HTML5 = require('../html5'); +HTML5 = (typeof HTML5 === 'undefined' ? {} : HTML5); HTML5.CONTENT_MODEL_FLAGS = [ 'PCDATA', diff --git a/packages/html5-tokenizer/entities.js b/packages/html5-tokenizer/entities.js index 5cff75bc35..87bc80fa2a 100644 --- a/packages/html5-tokenizer/entities.js +++ b/packages/html5-tokenizer/entities.js @@ -1,4 +1,6 @@ -module.exports = { +HTML5 = (typeof HTML5 === 'undefined' ? {} : HTML5); + +HTML5.ENTITIES = { "AElig": "\u00C6", "AElig;": "\u00C6", "AMP": "&", diff --git a/packages/html5-tokenizer/tokenizer.js b/packages/html5-tokenizer/tokenizer.js index e028f44902..c7f52c6c12 100644 --- a/packages/html5-tokenizer/tokenizer.js +++ b/packages/html5-tokenizer/tokenizer.js @@ -1,5 +1,3 @@ -require('../core-upgrade'); -var HTML5 = require('../html5'); var events = require('events'); var Buffer = require('./buffer').Buffer; var Models = HTML5.Models; From a1b25894b8200f23dcfce068a9141620edbc4caa Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Tue, 16 Apr 2013 14:14:58 -0700 Subject: [PATCH 004/938] add buffer.js --- packages/html5-tokenizer/buffer.js | 95 +++++++++++++++++++++++++++++ packages/html5-tokenizer/package.js | 3 +- 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 packages/html5-tokenizer/buffer.js diff --git a/packages/html5-tokenizer/buffer.js b/packages/html5-tokenizer/buffer.js new file mode 100644 index 0000000000..f976919aec --- /dev/null +++ b/packages/html5-tokenizer/buffer.js @@ -0,0 +1,95 @@ +var buffer = require('buffer'); +var HTML5 = require('../html5'); + +function Buffer() { + this.data = ''; + this.start = 0; + this.committed = 0; + this.eof = false; +} + +exports.Buffer = Buffer; + +Buffer.prototype = { + slice: function() { + if(this.start >= this.data.length) { + if(!this.eof) throw HTML5.DRAIN; + return HTML5.EOF; + } + return this.data.slice(this.start, this.data.length); + }, + char: function() { + if(!this.eof && this.start >= this.data.length - 1) throw HTML5.DRAIN; + if(this.start >= this.data.length) { + return HTML5.EOF; + } + return this.data[this.start++]; + }, + advance: function(amount) { + this.start += amount; + if(this.start >= this.data.length) { + if(!this.eof) throw HTML5.DRAIN; + return HTML5.EOF; + } else { + if(this.committed > this.data.length / 2) { + // Sliiiide + this.data = this.data.slice(this.committed); + this.start = this.start - this.committed; + this.committed = 0; + } + } + }, + matchWhile: function(re) { + if(this.eof && this.start >= this.data.length ) return ''; + var r = new RegExp("^"+re+"+"); + var m = r.exec(this.slice()); + if(m) { + if(!this.eof && m[0].length == this.data.length - this.start) throw HTML5.DRAIN; + this.advance(m[0].length); + return m[0]; + } else { + return ''; + } + }, + matchUntil: function(re) { + var m, s; + s = this.slice(); + if(s === HTML5.EOF) { + return ''; + } else if(m = new RegExp(re + (this.eof ? "|\0|$" : "|\0")).exec(this.slice())) { + var t = this.data.slice(this.start, this.start + m.index); + this.advance(m.index); + return t.toString(); + } else { + throw HTML5.DRAIN; + } + }, + append: function(data) { + this.data += data; + }, + shift: function(n) { + if(!this.eof && this.start + n >= this.data.length) throw HTML5.DRAIN; + if(this.eof && this.start >= this.data.length) return HTML5.EOF; + var d = this.data.slice(this.start, this.start + n).toString(); + this.advance(Math.min(n, this.data.length - this.start)); + return d; + }, + peek: function(n) { + if(!this.eof && this.start + n >= this.data.length) throw HTML5.DRAIN; + if(this.eof && this.start >= this.data.length) return HTML5.EOF; + return this.data.slice(this.start, Math.min(this.start + n, this.data.length)).toString(); + }, + length: function() { + return this.data.length - this.start - 1; + }, + unget: function(d) { + if(d === HTML5.EOF) return; + this.start -= (d.length); + }, + undo: function() { + this.start = this.committed; + }, + commit: function() { + this.committed = this.start; + } +}; diff --git a/packages/html5-tokenizer/package.js b/packages/html5-tokenizer/package.js index a0850a6419..5da2355c17 100644 --- a/packages/html5-tokenizer/package.js +++ b/packages/html5-tokenizer/package.js @@ -8,7 +8,8 @@ Npm.depends({'html5': "0.3.10"}); Package.on_use(function (api, where) { where = where || ['client', 'server']; - api.add_files(['entities.js', 'constants.js', 'tokenizer.js'], where); + api.add_files(['entities.js', 'constants.js', 'buffer.js', + 'tokenizer.js'], where); }); Package.on_test(function (api) { From bff87544d92acf11b256a3b19350183d37c61a3e Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Tue, 16 Apr 2013 14:15:23 -0700 Subject: [PATCH 005/938] toy EventEmitter --- packages/html5-tokenizer/buffer.js | 179 +++++++++++++------------- packages/html5-tokenizer/events.js | 23 ++++ packages/html5-tokenizer/package.js | 2 +- packages/html5-tokenizer/tokenizer.js | 5 +- 4 files changed, 113 insertions(+), 96 deletions(-) create mode 100644 packages/html5-tokenizer/events.js diff --git a/packages/html5-tokenizer/buffer.js b/packages/html5-tokenizer/buffer.js index f976919aec..238f1048c2 100644 --- a/packages/html5-tokenizer/buffer.js +++ b/packages/html5-tokenizer/buffer.js @@ -1,95 +1,90 @@ -var buffer = require('buffer'); -var HTML5 = require('../html5'); - -function Buffer() { - this.data = ''; - this.start = 0; - this.committed = 0; - this.eof = false; -} - -exports.Buffer = Buffer; +var Buffer = HTML5.Buffer = function Buffer() { + this.data = ''; + this.start = 0; + this.committed = 0; + this.eof = false; +}; Buffer.prototype = { - slice: function() { - if(this.start >= this.data.length) { - if(!this.eof) throw HTML5.DRAIN; - return HTML5.EOF; - } - return this.data.slice(this.start, this.data.length); - }, - char: function() { - if(!this.eof && this.start >= this.data.length - 1) throw HTML5.DRAIN; - if(this.start >= this.data.length) { - return HTML5.EOF; - } - return this.data[this.start++]; - }, - advance: function(amount) { - this.start += amount; - if(this.start >= this.data.length) { - if(!this.eof) throw HTML5.DRAIN; - return HTML5.EOF; - } else { - if(this.committed > this.data.length / 2) { - // Sliiiide - this.data = this.data.slice(this.committed); - this.start = this.start - this.committed; - this.committed = 0; - } - } - }, - matchWhile: function(re) { - if(this.eof && this.start >= this.data.length ) return ''; - var r = new RegExp("^"+re+"+"); - var m = r.exec(this.slice()); - if(m) { - if(!this.eof && m[0].length == this.data.length - this.start) throw HTML5.DRAIN; - this.advance(m[0].length); - return m[0]; - } else { - return ''; - } - }, - matchUntil: function(re) { - var m, s; - s = this.slice(); - if(s === HTML5.EOF) { - return ''; - } else if(m = new RegExp(re + (this.eof ? "|\0|$" : "|\0")).exec(this.slice())) { - var t = this.data.slice(this.start, this.start + m.index); - this.advance(m.index); - return t.toString(); - } else { - throw HTML5.DRAIN; - } - }, - append: function(data) { - this.data += data; - }, - shift: function(n) { - if(!this.eof && this.start + n >= this.data.length) throw HTML5.DRAIN; - if(this.eof && this.start >= this.data.length) return HTML5.EOF; - var d = this.data.slice(this.start, this.start + n).toString(); - this.advance(Math.min(n, this.data.length - this.start)); - return d; - }, - peek: function(n) { - if(!this.eof && this.start + n >= this.data.length) throw HTML5.DRAIN; - if(this.eof && this.start >= this.data.length) return HTML5.EOF; - return this.data.slice(this.start, Math.min(this.start + n, this.data.length)).toString(); - }, - length: function() { - return this.data.length - this.start - 1; - }, - unget: function(d) { - if(d === HTML5.EOF) return; - this.start -= (d.length); - }, - undo: function() { - this.start = this.committed; - }, - commit: function() { - this.committed = this.start; - } + slice: function() { + if(this.start >= this.data.length) { + if(!this.eof) throw HTML5.DRAIN; + return HTML5.EOF; + } + return this.data.slice(this.start, this.data.length); + }, + char: function() { + if(!this.eof && this.start >= this.data.length - 1) throw HTML5.DRAIN; + if(this.start >= this.data.length) { + return HTML5.EOF; + } + return this.data[this.start++]; + }, + advance: function(amount) { + this.start += amount; + if(this.start >= this.data.length) { + if(!this.eof) throw HTML5.DRAIN; + return HTML5.EOF; + } else { + if(this.committed > this.data.length / 2) { + // Sliiiide + this.data = this.data.slice(this.committed); + this.start = this.start - this.committed; + this.committed = 0; + } + } + }, + matchWhile: function(re) { + if(this.eof && this.start >= this.data.length ) return ''; + var r = new RegExp("^"+re+"+"); + var m = r.exec(this.slice()); + if(m) { + if(!this.eof && m[0].length == this.data.length - this.start) throw HTML5.DRAIN; + this.advance(m[0].length); + return m[0]; + } else { + return ''; + } + }, + matchUntil: function(re) { + var m, s; + s = this.slice(); + if(s === HTML5.EOF) { + return ''; + } else if(m = new RegExp(re + (this.eof ? "|\0|$" : "|\0")).exec(this.slice())) { + var t = this.data.slice(this.start, this.start + m.index); + this.advance(m.index); + return t.toString(); + } else { + throw HTML5.DRAIN; + } + }, + append: function(data) { + this.data += data; + }, + shift: function(n) { + if(!this.eof && this.start + n >= this.data.length) throw HTML5.DRAIN; + if(this.eof && this.start >= this.data.length) return HTML5.EOF; + var d = this.data.slice(this.start, this.start + n).toString(); + this.advance(Math.min(n, this.data.length - this.start)); + return d; + }, + peek: function(n) { + if(!this.eof && this.start + n >= this.data.length) throw HTML5.DRAIN; + if(this.eof && this.start >= this.data.length) return HTML5.EOF; + return this.data.slice(this.start, Math.min(this.start + n, this.data.length)).toString(); + }, + length: function() { + return this.data.length - this.start - 1; + }, + unget: function(d) { + if(d === HTML5.EOF) return; + this.start -= (d.length); + }, + undo: function() { + this.start = this.committed; + }, + commit: function() { + this.committed = this.start; + } }; diff --git a/packages/html5-tokenizer/events.js b/packages/html5-tokenizer/events.js new file mode 100644 index 0000000000..6e5856638f --- /dev/null +++ b/packages/html5-tokenizer/events.js @@ -0,0 +1,23 @@ +// dgreenspan's minimal implementation of events.EventEmitter + +toyevents = { + EventEmitter: function EventEmitter() { + this._listeners = {}; + } +}; + +EventEmitter.prototype.addListener = function (type, f) { + if (! f) + return; + this._listeners[type] = this._listeners[type] || []; + this._listeners[type].push(f); +}; + +EventEmitter.prototype.emit = function (type, data) { + var funcs = this._listeners[type]; + if (! funcs) + return; + + for (var i = 0, f; f = funcs[i]; i++) + f(type, data); +}; diff --git a/packages/html5-tokenizer/package.js b/packages/html5-tokenizer/package.js index 5da2355c17..a5d70be2d3 100644 --- a/packages/html5-tokenizer/package.js +++ b/packages/html5-tokenizer/package.js @@ -9,7 +9,7 @@ Package.on_use(function (api, where) { where = where || ['client', 'server']; api.add_files(['entities.js', 'constants.js', 'buffer.js', - 'tokenizer.js'], where); + 'events.js', 'tokenizer.js'], where); }); Package.on_test(function (api) { diff --git a/packages/html5-tokenizer/tokenizer.js b/packages/html5-tokenizer/tokenizer.js index c7f52c6c12..3ebe9c01ea 100644 --- a/packages/html5-tokenizer/tokenizer.js +++ b/packages/html5-tokenizer/tokenizer.js @@ -1,6 +1,5 @@ -var events = require('events'); -var Buffer = require('./buffer').Buffer; var Models = HTML5.Models; +var events = toyevents; function keys(h) { var r = []; @@ -14,7 +13,7 @@ var ENTITY_KEYS = keys(HTML5.ENTITIES); var t = HTML5.Tokenizer = function HTML5Tokenizer(input, document, tree) { var state; - var buffer = new Buffer(); + var buffer = new HTML5.Buffer(); var escapeFlag = false; var lastFourChars = ''; var current_token = null; From 27a76d66b2c664bce5ca4c9f175535cf68379def Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Tue, 16 Apr 2013 14:15:49 -0700 Subject: [PATCH 006/938] remove last() prototype pollution; don't require DOM tree --- packages/html5-tokenizer/tokenizer.js | 32 ++++++++++++++++----------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/packages/html5-tokenizer/tokenizer.js b/packages/html5-tokenizer/tokenizer.js index 3ebe9c01ea..93ed152d7a 100644 --- a/packages/html5-tokenizer/tokenizer.js +++ b/packages/html5-tokenizer/tokenizer.js @@ -9,6 +9,10 @@ function keys(h) { return r; } +var last = function (array) { + return array[array.length - 1]; +}; + var ENTITY_KEYS = keys(HTML5.ENTITIES); var t = HTML5.Tokenizer = function HTML5Tokenizer(input, document, tree) { @@ -21,6 +25,8 @@ var t = HTML5.Tokenizer = function HTML5Tokenizer(input, document, tree) { var content_model = Models.PCDATA; var source; + tree = tree || {open_elements: []}; + function data_state(buffer) { var c = buffer.char(); if (c !== HTML5.EOF && (content_model == Models.CDATA || content_model == Models.RCDATA || content_model == Models.SCRIPT_CDATA)) { @@ -268,9 +274,9 @@ var t = HTML5.Tokenizer = function HTML5Tokenizer(input, document, tree) { function process_entity_in_attribute(buffer) { var entity = consume_entity(buffer); if (entity) { - current_token.data.last().nodeValue += entity; + last(current_token.data).nodeValue += entity; } else { - current_token.data.last().nodeValue += '&'; + last(current_token.data).nodeValue += '&'; } } @@ -442,7 +448,7 @@ var t = HTML5.Tokenizer = function HTML5Tokenizer(input, document, tree) { } else if (data == '=') { newState(before_attribute_value_state); } else if (HTML5.ASCII_LETTERS_R.test(data)) { - current_token.data.last().nodeName += data + buffer.matchWhile(HTML5.ASCII_LETTERS); + last(current_token.data).nodeName += data + buffer.matchWhile(HTML5.ASCII_LETTERS); leavingThisState = false; } else if (data == '>') { // XXX If we emit here the attributes are converted to a dict @@ -457,10 +463,10 @@ var t = HTML5.Tokenizer = function HTML5Tokenizer(input, document, tree) { } } else if (data == "'" || data == '"') { parse_error("invalid-character-in-attribute-name"); - current_token.data.last().nodeName += data; + last(current_token.data).nodeName += data; leavingThisState = false; } else { - current_token.data.last().nodeName += data; + last(current_token.data).nodeName += data; leavingThisState = false; } @@ -469,7 +475,7 @@ var t = HTML5.Tokenizer = function HTML5Tokenizer(input, document, tree) { // start tag token is emitted so values can still be safely appended // to attributes, but we do want to report the parse error in time. if (this.lowercase_attr_name) { - current_token.data.last().nodeName = current_token.data.last().nodeName.toLowerCase(); + last(current_token.data).nodeName = last(current_token.data).nodeName.toLowerCase(); } for (var k in current_token.data.slice(0, -1)) { // FIXME this is a fucking mess. @@ -527,10 +533,10 @@ var t = HTML5.Tokenizer = function HTML5Tokenizer(input, document, tree) { emit_current_token(); } else if (data == '=') { parse_error("equals-in-unquoted-attribute-value"); - current_token.data.last().nodeValue += data; + last(current_token.data).nodeValue += data; newState(attribute_value_unquoted_state); } else { - current_token.data.last().nodeValue += data; + last(current_token.data).nodeValue += data; newState(attribute_value_unquoted_state); } @@ -549,7 +555,7 @@ var t = HTML5.Tokenizer = function HTML5Tokenizer(input, document, tree) { } else { var s = buffer.matchUntil('["&]'); if (s !== HTML5.EOF) data = data + s; - current_token.data.last().nodeValue += data; + last(current_token.data).nodeValue += data; } return true; } @@ -564,7 +570,7 @@ var t = HTML5.Tokenizer = function HTML5Tokenizer(input, document, tree) { } else if (data == '&') { process_entity_in_attribute(buffer); } else { - current_token.data.last().nodeValue += data + buffer.matchUntil("['&]"); + last(current_token.data).nodeValue += data + buffer.matchUntil("['&]"); } return true; } @@ -583,7 +589,7 @@ var t = HTML5.Tokenizer = function HTML5Tokenizer(input, document, tree) { emit_current_token(); } else if (data == '"' || data == "'" || data == '=') { parse_error("unexpected-character-in-unquoted-attribute-value"); - current_token.data.last().nodeValue += data; + last(current_token.data).nodeValue += data; } else { var o = buffer.matchUntil("["+ HTML5.SPACE_CHARACTERS_IN + '&<>' +"]"); if (o === HTML5.EOF) { @@ -592,7 +598,7 @@ var t = HTML5.Tokenizer = function HTML5Tokenizer(input, document, tree) { } // Commit here since this state is re-enterable and its outcome won't change with more data. buffer.commit(); - current_token.data.last().nodeValue += data + o; + last(current_token.data).nodeValue += data + o; } return true; } @@ -667,7 +673,7 @@ var t = HTML5.Tokenizer = function HTML5Tokenizer(input, document, tree) { if (chars.toUpperCase() == 'DOCTYPE') { current_token = {type: 'Doctype', name: '', publicId: null, systemId: null, correct: true}; newState(doctype_state); - } else if (tree.open_elements.last() && tree.open_elements.last().namespace && chars == '[CDATA[') { + } else if (last(tree.open_elements) && last(tree.open_elements).namespace && chars == '[CDATA[') { newState(cdata_section_state); } else { parse_error("expected-dashes-or-doctype"); From f345e2da921ab92debee8ae9da84a908e2d4b0a5 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Tue, 16 Apr 2013 14:16:28 -0700 Subject: [PATCH 007/938] tokenizer runs --- packages/html5-tokenizer/constants.js | 2 -- packages/html5-tokenizer/events.js | 6 ++--- packages/html5-tokenizer/html5-tokenizer.js | 25 +++++++++++++++++++++ packages/html5-tokenizer/package.js | 8 +++---- packages/html5-tokenizer/tokenizer.js | 2 ++ packages/html5-tokenizer/tokenizer_tests.js | 5 ++++- 6 files changed, 37 insertions(+), 11 deletions(-) create mode 100644 packages/html5-tokenizer/html5-tokenizer.js diff --git a/packages/html5-tokenizer/constants.js b/packages/html5-tokenizer/constants.js index d1514994c6..7991fc4fbd 100644 --- a/packages/html5-tokenizer/constants.js +++ b/packages/html5-tokenizer/constants.js @@ -209,8 +209,6 @@ HTML5.BOOLEAN_ATTRIBUTES = { 'output': ['disabled', 'readonly'] }; -HTML5.ENTITIES = require('html5-entities'); - HTML5.ENCODINGS = [ 'ansi_x3.4-1968', 'iso-ir-6', diff --git a/packages/html5-tokenizer/events.js b/packages/html5-tokenizer/events.js index 6e5856638f..c36adf7842 100644 --- a/packages/html5-tokenizer/events.js +++ b/packages/html5-tokenizer/events.js @@ -6,18 +6,18 @@ toyevents = { } }; -EventEmitter.prototype.addListener = function (type, f) { +toyevents.EventEmitter.prototype.addListener = function (type, f) { if (! f) return; this._listeners[type] = this._listeners[type] || []; this._listeners[type].push(f); }; -EventEmitter.prototype.emit = function (type, data) { +toyevents.EventEmitter.prototype.emit = function (type, data) { var funcs = this._listeners[type]; if (! funcs) return; for (var i = 0, f; f = funcs[i]; i++) - f(type, data); + f(data); }; diff --git a/packages/html5-tokenizer/html5-tokenizer.js b/packages/html5-tokenizer/html5-tokenizer.js new file mode 100644 index 0000000000..1217fd0f00 --- /dev/null +++ b/packages/html5-tokenizer/html5-tokenizer.js @@ -0,0 +1,25 @@ + +HTML5Tokenizer = { + tokenize: function (inputString) { + var tokens = []; + var tokenizer = new HTML5.Tokenizer(inputString); + tokenizer.addListener('token', function (tok) { + tokens.push(tok); + }); + tokenizer.tokenize(); + return tokens; + }, + tokenizeIncremental: function (tokenFunc) { + var emitter = new toyevents.EventEmitter(); + var tokenizer = new HTML5.Tokenizer(emitter); + tokenizer.addListener('token', tokenFunc); + return { + add: function (str) { + emitter.emit('data', str); + }, + finish: function () { + emitter.emit('end'); + } + }; + } +}; \ No newline at end of file diff --git a/packages/html5-tokenizer/package.js b/packages/html5-tokenizer/package.js index a5d70be2d3..394717dfb6 100644 --- a/packages/html5-tokenizer/package.js +++ b/packages/html5-tokenizer/package.js @@ -1,19 +1,17 @@ - Package.describe({ summary: "HTML5 tokenizer" }); -Npm.depends({'html5': "0.3.10"}); - Package.on_use(function (api, where) { where = where || ['client', 'server']; api.add_files(['entities.js', 'constants.js', 'buffer.js', - 'events.js', 'tokenizer.js'], where); + 'events.js', 'tokenizer.js', + 'html5-tokenizer.js'], where); }); Package.on_test(function (api) { - api.use('html5-tokenixer'); + api.use('html5-tokenizer'); api.use('tinytest'); api.add_files('tokenizer_tests.js', ['client', 'server']); }); diff --git a/packages/html5-tokenizer/tokenizer.js b/packages/html5-tokenizer/tokenizer.js index 93ed152d7a..2cd4f2f7ca 100644 --- a/packages/html5-tokenizer/tokenizer.js +++ b/packages/html5-tokenizer/tokenizer.js @@ -13,6 +13,8 @@ var last = function (array) { return array[array.length - 1]; }; +HTML5.debug = function () {}; + var ENTITY_KEYS = keys(HTML5.ENTITIES); var t = HTML5.Tokenizer = function HTML5Tokenizer(input, document, tree) { diff --git a/packages/html5-tokenizer/tokenizer_tests.js b/packages/html5-tokenizer/tokenizer_tests.js index 8641671374..af5e728c98 100644 --- a/packages/html5-tokenizer/tokenizer_tests.js +++ b/packages/html5-tokenizer/tokenizer_tests.js @@ -1,5 +1,8 @@ Tinytest.add("html5-tokenizer - basic", function (test) { - + test.equal(HTML5Tokenizer.tokenize('

foo'), + [ { type: 'StartTag', name: 'p', data: [] }, + { type: 'Characters', data: 'foo' }, + { type: 'EOF', data: 'End of File' } ]); }); \ No newline at end of file From 8698ea5d360e027a736962c14dad78daff4db336 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Tue, 16 Apr 2013 14:16:49 -0700 Subject: [PATCH 008/938] comment out incremental tokenizing --- packages/html5-tokenizer/html5-tokenizer.js | 32 ++++++++++++--------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/html5-tokenizer/html5-tokenizer.js b/packages/html5-tokenizer/html5-tokenizer.js index 1217fd0f00..2a78ee8dc7 100644 --- a/packages/html5-tokenizer/html5-tokenizer.js +++ b/packages/html5-tokenizer/html5-tokenizer.js @@ -8,18 +8,22 @@ HTML5Tokenizer = { }); tokenizer.tokenize(); return tokens; - }, - tokenizeIncremental: function (tokenFunc) { - var emitter = new toyevents.EventEmitter(); - var tokenizer = new HTML5.Tokenizer(emitter); - tokenizer.addListener('token', tokenFunc); - return { - add: function (str) { - emitter.emit('data', str); - }, - finish: function () { - emitter.emit('end'); - } - }; } -}; \ No newline at end of file + // Incremental tokenization turns out not to be useful + // for inspecting intermediate tokenizer state, just + // for async streaming. + // + // tokenizeIncremental: function (tokenFunc) { + // var emitter = new toyevents.EventEmitter(); + // var tokenizer = new HTML5.Tokenizer(emitter); + // tokenizer.addListener('token', tokenFunc); + // return { + // add: function (str) { + // emitter.emit('data', str); + // }, + // finish: function () { + // emitter.emit('end'); + // } + // }; + // } +}; From 25bddd9a23dda6b309804e052232b09637250f42 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Tue, 16 Apr 2013 14:18:07 -0700 Subject: [PATCH 009/938] minor, rename file --- packages/html5-tokenizer/README.md | 4 ++-- .../{html5-tokenizer.js => html5_tokenizer.js} | 0 packages/html5-tokenizer/package.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename packages/html5-tokenizer/{html5-tokenizer.js => html5_tokenizer.js} (100%) diff --git a/packages/html5-tokenizer/README.md b/packages/html5-tokenizer/README.md index 1cba5fcfec..b53a532b6c 100644 --- a/packages/html5-tokenizer/README.md +++ b/packages/html5-tokenizer/README.md @@ -1,3 +1,3 @@ -Code comes from https://github.com/aredridel/html5 / `npm html5`, but -just the tokenizer, no parsing and no `jsdom` dependency. \ No newline at end of file +Code comes from https://github.com/aredridel/html5 a.k.a. `npm html5`, but +just the tokenizer, no parsing and no `jsdom` dependency. diff --git a/packages/html5-tokenizer/html5-tokenizer.js b/packages/html5-tokenizer/html5_tokenizer.js similarity index 100% rename from packages/html5-tokenizer/html5-tokenizer.js rename to packages/html5-tokenizer/html5_tokenizer.js diff --git a/packages/html5-tokenizer/package.js b/packages/html5-tokenizer/package.js index 394717dfb6..e2584f6f65 100644 --- a/packages/html5-tokenizer/package.js +++ b/packages/html5-tokenizer/package.js @@ -7,7 +7,7 @@ Package.on_use(function (api, where) { api.add_files(['entities.js', 'constants.js', 'buffer.js', 'events.js', 'tokenizer.js', - 'html5-tokenizer.js'], where); + 'html5_tokenizer.js'], where); }); Package.on_test(function (api) { From 52fda4960292423e4891cedd15e98298113fb47f Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Tue, 16 Apr 2013 19:10:12 -0700 Subject: [PATCH 010/938] guess at a fix for tokenizer bug opened https://github.com/aredridel/html5/issues/75 --- packages/html5-tokenizer/tokenizer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/html5-tokenizer/tokenizer.js b/packages/html5-tokenizer/tokenizer.js index 2cd4f2f7ca..64d2668b35 100644 --- a/packages/html5-tokenizer/tokenizer.js +++ b/packages/html5-tokenizer/tokenizer.js @@ -840,6 +840,7 @@ var t = HTML5.Tokenizer = function HTML5Tokenizer(input, document, tree) { } else { current_token.name += data.toLowerCase(); } + buffer.commit(); return true; } From f32e3cd87d35923d8961bf70d1dbf392ebf23b18 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Tue, 16 Apr 2013 19:21:00 -0700 Subject: [PATCH 011/938] tests covering token types --- packages/html5-tokenizer/tokenizer_tests.js | 36 ++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/packages/html5-tokenizer/tokenizer_tests.js b/packages/html5-tokenizer/tokenizer_tests.js index af5e728c98..d71d5b3edd 100644 --- a/packages/html5-tokenizer/tokenizer_tests.js +++ b/packages/html5-tokenizer/tokenizer_tests.js @@ -1,8 +1,36 @@ Tinytest.add("html5-tokenizer - basic", function (test) { - test.equal(HTML5Tokenizer.tokenize('

foo'), - [ { type: 'StartTag', name: 'p', data: [] }, - { type: 'Characters', data: 'foo' }, - { type: 'EOF', data: 'End of File' } ]); + var run = function (input, expectedTokens) { + test.equal(HTML5Tokenizer.tokenize(input), + expectedTokens); + }; + + run('

foo', + [ { type: 'StartTag', name: 'p', data: [] }, + { type: 'Characters', data: 'foo' }, + { type: 'EOF', data: 'End of File' } ]); + + run('', + [ { type: 'Doctype', name: 'html', correct: true, + publicId: null, systemId: null }, + { type: 'EOF', data: 'End of File' } ]); + + run(' ', + [ { type: 'StartTag', name: 'a', + data: [{nodeName: 'b', nodeValue: ''}, + {nodeName: 'c', nodeValue: 'd'}] }, + { type: 'SpaceCharacters', data: ' ' }, + { type: 'EndTag', name: 'a', data: [] }, + { type: 'EOF', data: 'End of File' } ]); + + run('<3', + [{ type: 'ParseError', data: 'expected-tag-name' }, + { type: 'Characters', data: '<' }, + { type: 'Characters', data: '3' }, + { type: 'EOF', data: 'End of File' } ]); + + run('', + [{ type: 'Comment', data: 'foo' }, + { type: 'EOF', data: 'End of File' } ]); }); \ No newline at end of file From 2894a30eda1933a63dfe5d7c4595dd981e905da6 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Wed, 17 Apr 2013 14:56:58 -0700 Subject: [PATCH 012/938] new stache-tag parser --- packages/spacebars/package.js | 17 ++ packages/spacebars/spacebars.js | 236 ++++++++++++++++++++++++++ packages/spacebars/spacebars_tests.js | 104 ++++++++++++ 3 files changed, 357 insertions(+) create mode 100644 packages/spacebars/package.js create mode 100644 packages/spacebars/spacebars.js create mode 100644 packages/spacebars/spacebars_tests.js diff --git a/packages/spacebars/package.js b/packages/spacebars/package.js new file mode 100644 index 0000000000..fefc98e1c0 --- /dev/null +++ b/packages/spacebars/package.js @@ -0,0 +1,17 @@ +Package.describe({ + summary: "Handlebars-like template language for Meteor" +}); + +Package.on_use(function (api, where) { + where = where || ['client', 'server']; + + api.use('underscore', where); + api.use('jsparse', where); + api.add_files(['spacebars.js'], where); +}); + +Package.on_test(function (api) { + api.use('spacebars'); + api.use('tinytest'); + api.add_files('spacebars_tests.js', ['client', 'server']); +}); diff --git a/packages/spacebars/spacebars.js b/packages/spacebars/spacebars.js new file mode 100644 index 0000000000..c7e069e8d5 --- /dev/null +++ b/packages/spacebars/spacebars.js @@ -0,0 +1,236 @@ + +Spacebars = {}; + +var makeStacheTagStartRegex = function (r) { + return new RegExp(r.source + /(?![{>!#/])/.source, + r.ignoreCase ? 'i' : ''); +}; + +var prettyOffset = function (code, pos) { + var codeUpToPos = code.substring(0, pos); + var startOfLine = codeUpToPos.lastIndexOf('\n') + 1; + var indexInLine = pos - startOfLine; // 0-based + var lineNum = codeUpToPos.replace(/[^\n]+/g, '').length + 1; // 1-based + return "line " + lineNum + ", offset " + indexInLine; +}; + +var starts = { + ELSE: makeStacheTagStartRegex(/^\{\{\s*else(?=[\s}])/i), + DOUBLE: makeStacheTagStartRegex(/^\{\{\s*(?!\s)/), + TRIPLE: makeStacheTagStartRegex(/^\{\{\{\s*(?!\s)/), + COMMENT: makeStacheTagStartRegex(/^\{\{\s*!/), + INCLUSION: makeStacheTagStartRegex(/^\{\{\s*>\s*(?!\s)/), + BLOCKOPEN: makeStacheTagStartRegex(/^\{\{\s*#\s*(?!\s)/), + BLOCKCLOSE: makeStacheTagStartRegex(/^\{\{\s*\/\s*(?!\s)/) +}; + +var ends = { + DOUBLE: /^\s*\}\}/, + TRIPLE: /^\s*\}\}\}/ +}; + +Spacebars.starts = starts; + +// Parse a tag at `pos` in `inputString`. Succeeds or errors. +Spacebars.parseStacheTag = function (inputString, pos) { + pos = pos || 0; + var startPos = pos; + var str = inputString.slice(pos); + + var lexer = new JSLexer(inputString); + + var advance = function (amount) { + str = str.slice(amount); + pos += amount; + }; + + var run = function (regex) { + // regex is assumed to start with `^` + var result = regex.exec(str); + if (! result) + return null; + var ret = result[0]; + advance(ret.length); + return ret; + }; + + var scanToken = function () { + lexer.divisionPermitted = false; + lexer.pos = pos; + return lexer.next(); + }; + + var scanIdentifier = function () { + var tok = scanToken(); + // We don't care about overlap with JS keywords. This code + // won't accept "true", "false", or "null" as identifiers however. + if (tok.type() !== 'IDENTIFIER' && tok.type() !== 'KEYWORD') + expected('IDENTIFIER'); + var text = tok.text(); + advance(text.length); + return text; + }; + + var scanDottedIdentifier = function () { + var name = scanIdentifier(); + while (run(/^\./)) + name += '.' + scanIdentifier(); + return name; + }; + + var scanPathArg = function () { + var segments = []; + var arg = { type: 'PATH', segments: segments }; + // PATH arg can also have: { ofThis: true } + var dots; + + // handle `.` and `./`, disallow `..` + if ((dots = run(/^\.+/))) { + if (dots.length > 1) + error("`..` is not supported"); + arg.ofThis = true; + // only thing that can follow a `.` is a `/` + if (! run(/^\//)) + return arg; + } + + while (true) { + // scan a path segment + if (run(/^\[/)) { + var seg = run(/^[\s\S]*?\]/); + if (! seg) + error("Unterminated path segment"); + segments.push(seg.slice(0, -1)); + } else { + var id = scanIdentifier(); + if (id === 'this' && ! segments.length && ! arg.ofThis) { + // initial `this` + arg.ofThis = true; + } else { + segments.push(id); + } + } + + var sep = run(/^[\.\/]/); + if (! sep) + break; + if (/^\.\./.test(str)) + error("`..` is not supported"); + if (/^\./.test(str)) + error("`.` is only allowed at start of path"); + } + + return arg; + }; + + // scan an argument; must succeed + var scanArg = function (notKeyword) { + // all args have `type` and possibly `key` + var tok = scanToken(); + var tokType = tok.type(); + var text = tok.text(); + + if (/^[\.\[]/.test(str) && tokType !== 'NUMBER') + return scanPathArg(); + + if (tokType === 'BOOLEAN') { + advance(text.length); + return { type: 'BOOLEAN', value: Boolean(tok.text()) }; + } else if (tokType === 'NULL') { + advance(text.length); + return { type: 'NULL' }; + } else if (tokType === 'NUMBER') { + advance(text.length); + return { type: 'NUMBER', value: Number(tok.text()) }; + } else if (tokType === 'STRING') { + advance(text.length); + // single quote to double quote + if (text.slice(0, 1) === "'") + text = '"' + text.slice(1, -1) + '"'; + // replace line continuations with `\n` + text = text.replace(/[\r\n\u000A\u000D\u2028\u2029]/g, 'n'); + return { type: 'STRING', value: JSON.parse(text) }; + } else if (tokType === 'IDENTIFIER' || tokType === 'KEYWORD') { + if ((! notKeyword) && + /^\s*=/.test(str.slice(text.length))) { + // it's a keyword argument! + advance(text.length); + run(/^\s*=\s*/); + // recurse to scan value, disallowing a second `=`. + var arg = scanArg(true); + arg.key = text; + return arg; + } + return scanPathArg(); + } else { + expected('identifier, number, string, boolean, or null'); + } + }; + + var type; + + var error = function (msg) { + throw new Error(msg + " at " + prettyOffset(inputString, pos)); + }; + var expected = function (what) { + error('Expected ' + what + ', found "' + str.slice(0,5) + '"'); + }; + + // must do ELSE first; order of others doesn't matter + if (run(starts.ELSE)) type = 'ELSE'; + else if (run(starts.DOUBLE)) type = 'DOUBLE'; + else if (run(starts.TRIPLE)) type = 'TRIPLE'; + else if (run(starts.COMMENT)) type = 'COMMENT'; + else if (run(starts.INCLUSION)) type = 'INCLUSION'; + else if (run(starts.BLOCKOPEN)) type = 'BLOCKOPEN'; + else if (run(starts.BLOCKCLOSE)) type = 'BLOCKCLOSE'; + else + error('Unknown stache tag starting with "' + str.slice(0,5) + '"'); + + var tag = { type: type }; + + if (type === 'COMMENT') { + var result = run(/^[\s\S]*?\}\}/); + if (! result) + error("Unclosed comment"); + tag.value = result.slice(0, -2); + } else if (type === 'BLOCKCLOSE') { + tag.name = scanDottedIdentifier(); + if (! run(ends.DOUBLE)) + expected('`}}`'); + } else if (type === 'ELSE') { + if (! run(ends.DOUBLE)) + expected('`}}`'); + } else { + if (type === 'INCLUSION' || type === 'BLOCKOPEN') + tag.name = scanDottedIdentifier(); + else + tag.path = scanPathArg(); + tag.args = []; + while (true) { + run(/^\s*/); + if (type === 'TRIPLE') { + if (run(ends.TRIPLE)) + break; + else if (str.charAt(0) === '}') + expected('`}}}`'); + } else { + if (run(ends.DOUBLE)) + break; + else if (str.charAt(0) === '}') + expected('`}}`'); + } + tag.args.push(scanArg()); + if (run(/^(?=[\s}])/) !== '') + expected('space'); + } + } + + tag.charLength = pos - startPos; + return tag; +}; + + +Spacebars.parse = function (inputString) { + +}; \ No newline at end of file diff --git a/packages/spacebars/spacebars_tests.js b/packages/spacebars/spacebars_tests.js new file mode 100644 index 0000000000..4b3ef2ca23 --- /dev/null +++ b/packages/spacebars/spacebars_tests.js @@ -0,0 +1,104 @@ +Tinytest.add("spacebars - stache tags", function (test) { + + var run = function (input, expected) { + if (typeof expected === "string") { + // test for error starting with string `expected` + var msg = ''; + test.throws(function () { + try { + Spacebars.parseStacheTag(input); + } catch (e) { + msg = e.message; + throw e; + } + }); + test.equal(msg.slice(0, expected.length), expected); + } else { + var result = Spacebars.parseStacheTag(input); + test.equal(result.charLength, input.length); + delete result.charLength; + test.equal(result, expected); + } + } + + run('{{foo}}', {type: 'DOUBLE', path: {type: 'PATH', segments: ['foo']}, + args: []}); + run('{{foo3}}', {type: 'DOUBLE', path: {type: 'PATH', segments: ['foo3']}, + args: []}); + run('{{{foo}}}', {type: 'TRIPLE', path: {type: 'PATH', segments: ['foo']}, + args: []}); + run('{{{foo}}', "Expected `}}}`"); + run('{{{foo', "Expected"); + run('{{foo', "Expected"); + run('{{ {foo}}}', "Unknown stache tag"); + run('{{{{foo}}}}', "Unknown stache tag"); + run('{{{>foo}}}', "Unknown stache tag"); + run('{{>>foo}}', "Unknown stache tag"); + run('{{! asdf }}', {type: 'COMMENT', value: ' asdf '}); + run('{{ ! asdf }}', {type: 'COMMENT', value: ' asdf '}); + run('{{ ! asdf }asdf', "Unclosed"); + run('{{else}}', {type: 'ELSE'}); + run('{{ else }}', {type: 'ELSE'}); + run('{{else x}}', "Expected"); + run('{{else_x}}', {type: 'DOUBLE', path: {type: 'PATH', + segments: ['else_x']}, + args: []}); + run('{{/if}}', {type: 'BLOCKCLOSE', name: 'if'}); + run('{{ / if }}', {type: 'BLOCKCLOSE', name: 'if'}); + run('{{/if x}}', "Expected"); + run('{{#if}}', {type: 'BLOCKOPEN', name: 'if', args: []}); + run('{{ # if }}', {type: 'BLOCKOPEN', name: 'if', args: []}); + run('{{#if_3}}', {type: 'BLOCKOPEN', name: 'if_3', args: []}); + run('{{>x}}', {type: 'INCLUSION', name: 'x', args: []}); + run('{{ > x }}', {type: 'INCLUSION', name: 'x', args: []}); + run('{{>x_3}}', {type: 'INCLUSION', name: 'x_3', args: []}); + + + + run('{{foo 3}}', {type: 'DOUBLE', path: {type: 'PATH', segments: ['foo']}, + args: [{type: 'NUMBER', value: 3}]}); + run('{{ foo 3 }}', {type: 'DOUBLE', path: {type: 'PATH', + segments: ['foo']}, + args: [{type: 'NUMBER', value: 3}]}); + run('{{#foo 3}}', {type: 'BLOCKOPEN', name: 'foo', + args: [{type: 'NUMBER', value: 3}]}); + run('{{ # foo 3 }}', {type: 'BLOCKOPEN', name: 'foo', + args: [{type: 'NUMBER', value: 3}]}); + run('{{>foo 3}}', {type: 'INCLUSION', name: 'foo', + args: [{type: 'NUMBER', value: 3}]}); + run('{{ > foo 3 }}', {type: 'INCLUSION', name: 'foo', + args: [{type: 'NUMBER', value: 3}]}); + run('{{{foo 3}}}', {type: 'TRIPLE', path: {type: 'PATH', + segments: ['foo']}, + args: [{type: 'NUMBER', value: 3}]}); + + run('{{foo bar baz=qux x3=. ./foo foo/bar a.b.c}}', + {type: 'DOUBLE', path: {type: 'PATH', segments: ['foo']}, + args: [{type: 'PATH', segments: ['bar']}, + {type: 'PATH', segments: ['qux'], key: 'baz'}, + {type: 'PATH', segments: [], ofThis: true, key: 'x3'}, + {type: 'PATH', segments: ['foo'], ofThis: true}, + {type: 'PATH', segments: ['foo', 'bar']}, + {type: 'PATH', segments: ['a', 'b', 'c']}]}); + + run('{{{x 0.3 [0].[3] .4 ./[4]}}}', + {type: 'TRIPLE', path: {type: 'PATH', segments: ['x']}, + args: [{type: 'NUMBER', value: 0.3}, + {type: 'PATH', segments: ['0', '3']}, + {type: 'NUMBER', value: .4}, + {type: 'PATH', segments: ['4'], ofThis: true}]}); + + run('{{# foo this this.x null}}', + {type: 'BLOCKOPEN', name: 'foo', + args: [{type: 'PATH', segments: [], ofThis: true}, + {type: 'PATH', segments: ['x'], ofThis: true}, + {type: 'NULL'}]}); + + run('{{foo ..}}', "`..` is not supported"); + run('{{foo x/..}}', "`..` is not supported"); + run('{{foo x/.}}', "`.`"); + + run('{{#a.b.c}}', {type: 'BLOCKOPEN', name: 'a.b.c', args: []}); + run('{{> a.b.c}}', {type: 'INCLUSION', name: 'a.b.c', args: []}); + +}); From 40ede2204ea5a1ccfc9b1b86a249c4df98054a42 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Wed, 17 Apr 2013 18:18:23 -0700 Subject: [PATCH 013/938] parser and most of a compiler --- packages/spacebars/spacebars.js | 316 +++++++++++++++++++++++++- packages/spacebars/spacebars_tests.js | 2 + 2 files changed, 317 insertions(+), 1 deletion(-) diff --git a/packages/spacebars/spacebars.js b/packages/spacebars/spacebars.js index c7e069e8d5..e05ac7c545 100644 --- a/packages/spacebars/spacebars.js +++ b/packages/spacebars/spacebars.js @@ -123,7 +123,7 @@ Spacebars.parseStacheTag = function (inputString, pos) { return arg; }; - // scan an argument; must succeed + // scan an argument; succeeds or errors var scanArg = function (notKeyword) { // all args have `type` and possibly `key` var tok = scanToken(); @@ -226,11 +226,325 @@ Spacebars.parseStacheTag = function (inputString, pos) { } } + tag.charPos = startPos; tag.charLength = pos - startPos; return tag; }; +var randomLetters = function () { + var letters = "abcdefghijklmnopqrstuvwxyz"; + var str = ''; + for (var i = 0; i < 10; ++i) + str += Random.choice(letters); + return str; +}; + +var tokenizeHtml = function (html, preString, postString, tagInfoGetter) { + var tokens = HTML5Tokenizer.tokenize(html); + + var out = []; + + var noStache = function (chrs, customMessage) { + if (! chrs) + return chrs; + + var extracted = extractTags(chrs); + if (typeof extracted === "string") + return chrs; + + for (var i = 0; i < extracted.length; i++) + if (extracted[i].id) + throw new Error((customMessage || + "Can't use a stache tag at this position " + + "in an HTML tag") + ", at " + + tagInfoGetter(extracted[i].id).prettyOffset()); + return chrs; + }; + + var extractTags = function (str) { + if (! str) + return ''; + + var buf = []; + var lastPos = 0; + var pos; + while ((pos = str.indexOf(preString, lastPos)) >= 0) { + if (pos > lastPos) + buf.push(str.slice(lastPos, pos)); + var idStart = pos + preString.length; + var idEnd = str.indexOf(postString, idStart); + if (idEnd < 0) + throw new Error("error extracting tags"); // shouldn't happen + var x; + buf.push(x = {id: str.slice(idStart, idEnd)}); + x.ref = tagInfoGetter(x.id).ref(); + lastPos = idEnd + postString.length; + } + if (lastPos < str.length) + buf.push(str.slice(lastPos)); + + if (buf.length === 1 && typeof buf[0] === "string") + return buf[0]; + + return buf; + }; + + for (var i = 0; i < tokens.length; i++) { + var tok = tokens[i]; + if (tok.type === 'Characters') { + var s = tok.data; + // combine multiple adjacent "Characters" + while (tokens[i+1] && tokens[i+1].type === 'Characters') { + tok = tokens[++i]; + s += tok.data; + } + out.push({type: 'Characters', + data: extractTags(s)}); + } else if (tok.type === 'SpaceCharacters') { + out.push({type: 'SpaceCharacters', + data: tok.data}); + } else if (tok.type === 'EndTag') { + out.push({type: 'EndTag', + name: extractTags(tok.name)}); + } else if (tok.type === 'Doctype') { + out.push({type: 'DocType', + name: noStache(tok.name), + correct: tok.correct, + publicId: noStache(tok.publicId), + systemId: noStache(tok.systemId)}); + } else if (tok.type === 'Comment') { + out.push({type: 'Comment', + data: extractTags(tok.data)}); + } else if (tok.type === 'StartTag') { + out.push({ type: 'StartTag', + name: extractTags(tok.name), + data: _.map(tok.data, function (kv) { + return { nodeName: extractTags(kv.nodeName), + nodeValue: extractTags(kv.nodeValue) }; + }) }); + } else { + // ignore (ParseError, EOF) + } + } + + return out; +}; Spacebars.parse = function (inputString) { + // first, scan for all the stache tags + var stacheTags = []; + + var pos = 0; + while (pos < inputString.length) { + pos = inputString.indexOf('{{', pos); + if (pos < 0) { + pos = inputString.length; + } else { + var tag = Spacebars.parseStacheTag(inputString, pos); + stacheTags.push(tag); + pos += tag.charLength; + } + } + + // now build a tree where block contents put into an object + // with `type:'block'`. Also check that tags match. + + var parseBlock = function (openTagIndex) { + var isTopLevel = (openTagIndex < 0); + var block = { + type: 'block', + isBlock: true, + openTag: null, + elseTag: null, + closeTag: null, + bodyChildren: [], // tags and blocks + elseChildren: [] + }; + var children = block.bodyChildren; // repointed to elseChildren later + if (! isTopLevel) + block.openTag = stacheTags[openTagIndex]; + + + for (var i = (isTopLevel ? 0 : openTagIndex + 1); + i < stacheTags.length && ! block.closeTag; + i++) { + + var t = stacheTags[i]; + if (t.type === 'BLOCKOPEN') { + // recurse + var b = parseBlock(i); + children.push(b); + while (stacheTags[i] !== b.closeTag) + i++; + } else if (t.type === 'BLOCKCLOSE') { + if (isTopLevel) + throw new Error("Unexpected close tag `" +t.name + "` at " + + prettyOffset(inputString, t.charPos)); + if (t.name !== block.openTag.name) + throw new Error("Close tag at " + + prettyOffset(inputString, t.charPos) + + " doesn't match `" + block.openTag.name + + "`, found `" + t.name + "`"); + block.closeTag = t; + } else if (t.type === 'ELSE') { + if (isTopLevel) + throw new Error("Unexpected `{{else}}` at " + + prettyOffset(inputString, t.charPos)); + if (block.elseTag) + throw new Error("Duplicate `{{else}}` at " + + prettyOffset(inputString, t.charPos)); + block.elseTag = t; + children = block.elseChildren; + } else { + children.push(t); + } + } + + if (! isTopLevel && ! block.closeTag) + throw new Error("Unclosed `" + block.openTag.name + + "` tag at top level"); + + return block; + }; + + // get a tree of all the stache tags as a top-level "block" + // whose bodyChildren are the sub-blocks and other non-block + // stache tags. + var tree = parseBlock(-1); + + var preString = randomLetters(); + var postString = randomLetters(); + var nextId = 1; + + var tagEnd = function (t) { return t.charPos + t.charLength; }; + + var idLookup = {}; + + var tagInfoGetter = function (id) { + var t = idLookup[id]; + return { + prettyOffset: function () { + return t ? prettyOffset( + inputString, (t.isBlock ? t.openTag : t).charPos) : + "(unknown)"; + }, + ref: function () { + return t; + } + }; + }; + + var tokenizeBlock = function (block) { + // replace all child tags and blocks in the HTML with random + // identifiers! + + var isTopLevel = ! block.openTag; + var hasElse = !! block.elseTag; + + var getTokens = function (children, startPos, endPos) { + var html = ''; + var pos = startPos; + _.each(children, function (t) { + html += inputString.slice( + pos, (t.isBlock ? t.openTag : t).charPos); + idLookup[nextId] = t; + html += preString + (nextId++) + postString; + pos = tagEnd(t.isBlock ? t.closeTag : t); + + if (t.isBlock) + tokenizeBlock(t); // recurse + }); + html += inputString.slice(pos, endPos); + + return tokenizeHtml(html, preString, postString, + tagInfoGetter); + }; + + var bodyStart = (isTopLevel ? 0 : tagEnd(block.openTag)); + var bodyEnd = (isTopLevel ? inputString.length : + (hasElse ? block.elseTag.charPos : + block.closeTag.charPos)); + + block.bodyTokens = getTokens(block.bodyChildren, bodyStart, bodyEnd); + + if (hasElse) { + var elseStart = tagEnd(block.elseTag); + var elseEnd = block.closeTag.charPos; + + block.elseTokens = getTokens(block.elseChildren, elseStart, elseEnd); + } + }; + + tokenizeBlock(tree); + + return tree; +}; + +var toJSLiteral = function (obj) { + // http://timelessrepo.com/json-isnt-a-javascript-subset + return (JSON.stringify(obj) + .replace(/\u2028/g, '\\u2028') + .replace(/\u2029/g, '\\u2029')); +}; + +var tagLiteral = function (tag) { + var lit = _.extend({}, tag); + delete lit.charPos; + delete lit.charLength; + return toJSLiteral(lit); +}; + +Spacebars.compile = function (inputString) { + var tree; + if (typeof inputString === 'object') { + tree = inputString; // allow passing parse tree + } else { + tree = Spacebars.parse(inputString); + } + + var interpolate = function (strOrArray, indent) { + if (typeof strOrArray === "string") + return toJSLiteral(strOrArray); + + var parts = []; + _.each(strOrArray, function (strOrTagRef) { + if (typeof strOrTagRef === "string") { + parts.push(toJSLiteral(strOrTagRef)); + } else { + var tagOrBlock = strOrTagRef.ref; + if (tagOrBlock.isBlock) { + var block = tagOrBlock; + parts.push('env.tag(' + tagLiteral(block.openTag) + + ', ' + tokensToFunc(block.bodyTokens, indent) + + (block.elseTag ? ', ' + + tokensToFunc(block.elseTokens, indent) + : '') + ')'); + } else { + var tag = tagOrBlock; + parts.push('env.tag(' + tagLiteral(tag) + ')'); + } + } + }); + + return parts.join('+'); + }; + + var tokensToFunc = function (tokens, indent) { + var oldIndent = indent || ''; + indent = oldIndent + ' '; + var js = 'function (html, env) {\n'; + _.each(tokens, function (t) { + switch (t.type) { + case 'Characters': + js += indent + 'html.text(' + interpolate(t.data, indent) +');\n'; + break; + // XXX more types go here + } + }); + js += oldIndent + '}'; + return js; + }; + + return tokensToFunc(tree.bodyTokens); }; \ No newline at end of file diff --git a/packages/spacebars/spacebars_tests.js b/packages/spacebars/spacebars_tests.js index 4b3ef2ca23..de3304eb9f 100644 --- a/packages/spacebars/spacebars_tests.js +++ b/packages/spacebars/spacebars_tests.js @@ -15,7 +15,9 @@ Tinytest.add("spacebars - stache tags", function (test) { test.equal(msg.slice(0, expected.length), expected); } else { var result = Spacebars.parseStacheTag(input); + test.equal(result.charPos, 0); test.equal(result.charLength, input.length); + delete result.charPos; delete result.charLength; test.equal(result, expected); } From a7878a9ba60c9c66d8c0f71e4fe497c0b393d855 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Wed, 17 Apr 2013 19:21:18 -0700 Subject: [PATCH 014/938] complete compiler --- packages/spacebars/spacebars.js | 94 ++++++++++++++++++++------- packages/spacebars/spacebars_tests.js | 71 +++++++++----------- 2 files changed, 100 insertions(+), 65 deletions(-) diff --git a/packages/spacebars/spacebars.js b/packages/spacebars/spacebars.js index e05ac7c545..2599ddc692 100644 --- a/packages/spacebars/spacebars.js +++ b/packages/spacebars/spacebars.js @@ -78,20 +78,19 @@ Spacebars.parseStacheTag = function (inputString, pos) { return name; }; - var scanPathArg = function () { + var scanPath = function () { var segments = []; - var arg = { type: 'PATH', segments: segments }; - // PATH arg can also have: { ofThis: true } + // Initial empty string in segments means `this` or `.`. var dots; // handle `.` and `./`, disallow `..` if ((dots = run(/^\.+/))) { if (dots.length > 1) error("`..` is not supported"); - arg.ofThis = true; + segments.push(''); // only thing that can follow a `.` is a `/` if (! run(/^\//)) - return arg; + return segments; } while (true) { @@ -100,12 +99,15 @@ Spacebars.parseStacheTag = function (inputString, pos) { var seg = run(/^[\s\S]*?\]/); if (! seg) error("Unterminated path segment"); - segments.push(seg.slice(0, -1)); + seg = seg.slice(0, -1); + if (! seg && ! segments.length) + error("Path can't start with empty string"); + segments.push(seg); } else { var id = scanIdentifier(); - if (id === 'this' && ! segments.length && ! arg.ofThis) { + if (id === 'this' && ! segments.length) { // initial `this` - arg.ofThis = true; + segments.push(''); } else { segments.push(id); } @@ -120,7 +122,7 @@ Spacebars.parseStacheTag = function (inputString, pos) { error("`.` is only allowed at start of path"); } - return arg; + return segments; }; // scan an argument; succeeds or errors @@ -131,17 +133,17 @@ Spacebars.parseStacheTag = function (inputString, pos) { var text = tok.text(); if (/^[\.\[]/.test(str) && tokType !== 'NUMBER') - return scanPathArg(); + return ['PATH', scanPath()]; if (tokType === 'BOOLEAN') { advance(text.length); - return { type: 'BOOLEAN', value: Boolean(tok.text()) }; + return ['BOOLEAN', Boolean(tok.text())]; } else if (tokType === 'NULL') { advance(text.length); - return { type: 'NULL' }; + return ['NULL', null]; } else if (tokType === 'NUMBER') { advance(text.length); - return { type: 'NUMBER', value: Number(tok.text()) }; + return ['NUMBER', Number(tok.text())]; } else if (tokType === 'STRING') { advance(text.length); // single quote to double quote @@ -149,7 +151,7 @@ Spacebars.parseStacheTag = function (inputString, pos) { text = '"' + text.slice(1, -1) + '"'; // replace line continuations with `\n` text = text.replace(/[\r\n\u000A\u000D\u2028\u2029]/g, 'n'); - return { type: 'STRING', value: JSON.parse(text) }; + return ['STRING', JSON.parse(text)]; } else if (tokType === 'IDENTIFIER' || tokType === 'KEYWORD') { if ((! notKeyword) && /^\s*=/.test(str.slice(text.length))) { @@ -158,10 +160,10 @@ Spacebars.parseStacheTag = function (inputString, pos) { run(/^\s*=\s*/); // recurse to scan value, disallowing a second `=`. var arg = scanArg(true); - arg.key = text; + arg.push(text); // add third element for key return arg; } - return scanPathArg(); + return ['PATH', scanPath()]; } else { expected('identifier, number, string, boolean, or null'); } @@ -205,7 +207,7 @@ Spacebars.parseStacheTag = function (inputString, pos) { if (type === 'INCLUSION' || type === 'BLOCKOPEN') tag.name = scanDottedIdentifier(); else - tag.path = scanPathArg(); + tag.path = scanPath(); tag.args = []; while (true) { run(/^\s*/); @@ -291,18 +293,18 @@ var tokenizeHtml = function (html, preString, postString, tagInfoGetter) { for (var i = 0; i < tokens.length; i++) { var tok = tokens[i]; - if (tok.type === 'Characters') { + if (tok.type === 'Characters' || + tok.type === 'SpaceCharacters') { var s = tok.data; // combine multiple adjacent "Characters" - while (tokens[i+1] && tokens[i+1].type === 'Characters') { + while (tokens[i+1] && + (tokens[i+1].type === 'Characters' || + tokens[i+1].type === 'SpaceCharacters')) { tok = tokens[++i]; s += tok.data; } out.push({type: 'Characters', data: extractTags(s)}); - } else if (tok.type === 'SpaceCharacters') { - out.push({type: 'SpaceCharacters', - data: tok.data}); } else if (tok.type === 'EndTag') { out.push({type: 'EndTag', name: extractTags(tok.name)}); @@ -515,14 +517,36 @@ Spacebars.compile = function (inputString) { var tagOrBlock = strOrTagRef.ref; if (tagOrBlock.isBlock) { var block = tagOrBlock; - parts.push('env.tag(' + tagLiteral(block.openTag) + + var openTag = block.openTag; + parts.push('env.blockHelper(' + toJSLiteral(openTag.name) + + ', ' + toJSLiteral(openTag.args) + ', ' + tokensToFunc(block.bodyTokens, indent) + (block.elseTag ? ', ' + tokensToFunc(block.elseTokens, indent) : '') + ')'); } else { var tag = tagOrBlock; - parts.push('env.tag(' + tagLiteral(tag) + ')'); + switch (tag.type) { + case 'COMMENT': + // nothing to do + break; + case 'INCLUSION': + parts.push('env.include(' + toJSLiteral(tag.name) + + (tag.args.length ? ', ' +toJSLiteral(tag.args) : '') + + ')'); + break; + case 'DOUBLE': // fall through + case 'TRIPLE': + parts.push('env.' + + (tag.type === 'DOUBLE' ? 'dstache' : 'tstache') + + '(' + toJSLiteral(tag.path) + + (tag.args.length ? ', ' + toJSLiteral(tag.args) : '') + + ')'); + break; + default: + throw new Error("Unknown stache tag type: " + tag.type); + //parts.push('env.tag(' + tagLiteral(tag) + ')'); + } } } }); @@ -539,7 +563,27 @@ Spacebars.compile = function (inputString) { case 'Characters': js += indent + 'html.text(' + interpolate(t.data, indent) +');\n'; break; - // XXX more types go here + case 'StartTag': + js += indent + 'html.open(' + interpolate(t.name, indent) + + (t.data.length ? ', [' + _.map(t.data, function (kv) { + return '[' + interpolate(kv.nodeName, indent) + ', ' + + interpolate(kv.nodeValue, indent) + ']'; + }).join(', ') + ']' : '') + ');\n'; + break; + case 'EndTag': + js += indent + 'html.close(' + interpolate(t.name, indent) + ');\n'; + break; + case 'Comment': + js += indent + 'html.comment(' + interpolate(t.name, indent) + ');\n'; + break; + case 'DocType': + js += indent + 'html.doctype(' + toJSLiteral(t.name) + ', ' + + toJSLiteral({correct: t.correct, publicId: t.publicId, + systemId: t.systemId}) + ');\n'; + break; + default: + throw new Error("Unexpected token type: " + t.type); + break; } }); js += oldIndent + '}'; diff --git a/packages/spacebars/spacebars_tests.js b/packages/spacebars/spacebars_tests.js index de3304eb9f..8fe633a580 100644 --- a/packages/spacebars/spacebars_tests.js +++ b/packages/spacebars/spacebars_tests.js @@ -21,14 +21,11 @@ Tinytest.add("spacebars - stache tags", function (test) { delete result.charLength; test.equal(result, expected); } - } + }; - run('{{foo}}', {type: 'DOUBLE', path: {type: 'PATH', segments: ['foo']}, - args: []}); - run('{{foo3}}', {type: 'DOUBLE', path: {type: 'PATH', segments: ['foo3']}, - args: []}); - run('{{{foo}}}', {type: 'TRIPLE', path: {type: 'PATH', segments: ['foo']}, - args: []}); + run('{{foo}}', {type: 'DOUBLE', path: ['foo'], args: []}); + run('{{foo3}}', {type: 'DOUBLE', path: ['foo3'], args: []}); + run('{{{foo}}}', {type: 'TRIPLE', path: ['foo'], args: []}); run('{{{foo}}', "Expected `}}}`"); run('{{{foo', "Expected"); run('{{foo', "Expected"); @@ -42,9 +39,7 @@ Tinytest.add("spacebars - stache tags", function (test) { run('{{else}}', {type: 'ELSE'}); run('{{ else }}', {type: 'ELSE'}); run('{{else x}}', "Expected"); - run('{{else_x}}', {type: 'DOUBLE', path: {type: 'PATH', - segments: ['else_x']}, - args: []}); + run('{{else_x}}', {type: 'DOUBLE', path: ['else_x'], args: []}); run('{{/if}}', {type: 'BLOCKCLOSE', name: 'if'}); run('{{ / if }}', {type: 'BLOCKCLOSE', name: 'if'}); run('{{/if x}}', "Expected"); @@ -57,44 +52,38 @@ Tinytest.add("spacebars - stache tags", function (test) { - run('{{foo 3}}', {type: 'DOUBLE', path: {type: 'PATH', segments: ['foo']}, - args: [{type: 'NUMBER', value: 3}]}); - run('{{ foo 3 }}', {type: 'DOUBLE', path: {type: 'PATH', - segments: ['foo']}, - args: [{type: 'NUMBER', value: 3}]}); - run('{{#foo 3}}', {type: 'BLOCKOPEN', name: 'foo', - args: [{type: 'NUMBER', value: 3}]}); + run('{{foo 3}}', {type: 'DOUBLE', path: ['foo'], args: [['NUMBER', 3]]}); + run('{{ foo 3 }}', {type: 'DOUBLE', path: ['foo'], args: [['NUMBER', 3]]}); + run('{{#foo 3}}', {type: 'BLOCKOPEN', name: 'foo', args: [['NUMBER', 3]]}); run('{{ # foo 3 }}', {type: 'BLOCKOPEN', name: 'foo', - args: [{type: 'NUMBER', value: 3}]}); - run('{{>foo 3}}', {type: 'INCLUSION', name: 'foo', - args: [{type: 'NUMBER', value: 3}]}); + args: [['NUMBER', 3]]}); + run('{{>foo 3}}', {type: 'INCLUSION', name: 'foo', args: [['NUMBER', 3]]}); run('{{ > foo 3 }}', {type: 'INCLUSION', name: 'foo', - args: [{type: 'NUMBER', value: 3}]}); - run('{{{foo 3}}}', {type: 'TRIPLE', path: {type: 'PATH', - segments: ['foo']}, - args: [{type: 'NUMBER', value: 3}]}); + args: [['NUMBER', 3]]}); + run('{{{foo 3}}}', {type: 'TRIPLE', path: ['foo'], args: [['NUMBER', 3]]}); run('{{foo bar baz=qux x3=. ./foo foo/bar a.b.c}}', - {type: 'DOUBLE', path: {type: 'PATH', segments: ['foo']}, - args: [{type: 'PATH', segments: ['bar']}, - {type: 'PATH', segments: ['qux'], key: 'baz'}, - {type: 'PATH', segments: [], ofThis: true, key: 'x3'}, - {type: 'PATH', segments: ['foo'], ofThis: true}, - {type: 'PATH', segments: ['foo', 'bar']}, - {type: 'PATH', segments: ['a', 'b', 'c']}]}); + {type: 'DOUBLE', path: ['foo'], + args: [['PATH', ['bar']], + ['PATH', ['qux'], 'baz'], + ['PATH', [''], 'x3'], + ['PATH', ['', 'foo']], + ['PATH', ['foo', 'bar']], + ['PATH', ['a', 'b', 'c']]]}); run('{{{x 0.3 [0].[3] .4 ./[4]}}}', - {type: 'TRIPLE', path: {type: 'PATH', segments: ['x']}, - args: [{type: 'NUMBER', value: 0.3}, - {type: 'PATH', segments: ['0', '3']}, - {type: 'NUMBER', value: .4}, - {type: 'PATH', segments: ['4'], ofThis: true}]}); + {type: 'TRIPLE', path: ['x'], + args: [['NUMBER', 0.3], + ['PATH', ['0', '3']], + ['NUMBER', .4], + ['PATH', ['', '4']]]}); - run('{{# foo this this.x null}}', + run('{{# foo this this.x null z=null}}', {type: 'BLOCKOPEN', name: 'foo', - args: [{type: 'PATH', segments: [], ofThis: true}, - {type: 'PATH', segments: ['x'], ofThis: true}, - {type: 'NULL'}]}); + args: [['PATH', ['']], + ['PATH', ['', 'x']], + ['NULL', null], + ['NULL', null, 'z']]}); run('{{foo ..}}', "`..` is not supported"); run('{{foo x/..}}', "`..` is not supported"); @@ -103,4 +92,6 @@ Tinytest.add("spacebars - stache tags", function (test) { run('{{#a.b.c}}', {type: 'BLOCKOPEN', name: 'a.b.c', args: []}); run('{{> a.b.c}}', {type: 'INCLUSION', name: 'a.b.c', args: []}); + run('{{foo.[]/[]}}', {type: 'DOUBLE', path: ['foo', '', ''], args: []}); + run('{{[].foo}}', "Path can't start with empty string"); }); From 3a504091d56ac2fa447495725f875d1a431268a3 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Wed, 17 Apr 2013 19:44:55 -0700 Subject: [PATCH 015/938] fix package dependencies --- packages/spacebars/package.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/spacebars/package.js b/packages/spacebars/package.js index fefc98e1c0..84185bf3fc 100644 --- a/packages/spacebars/package.js +++ b/packages/spacebars/package.js @@ -7,6 +7,7 @@ Package.on_use(function (api, where) { api.use('underscore', where); api.use('jsparse', where); + api.use('html5-tokenizer', where); api.add_files(['spacebars.js'], where); }); From dca027c7facbc40054c3bcb5e2e67c77c3fe1b65 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Thu, 18 Apr 2013 11:24:46 -0700 Subject: [PATCH 016/938] no stache tags in HTML tag names --- packages/spacebars/spacebars.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/spacebars/spacebars.js b/packages/spacebars/spacebars.js index 2599ddc692..e073556dcd 100644 --- a/packages/spacebars/spacebars.js +++ b/packages/spacebars/spacebars.js @@ -307,7 +307,7 @@ var tokenizeHtml = function (html, preString, postString, tagInfoGetter) { data: extractTags(s)}); } else if (tok.type === 'EndTag') { out.push({type: 'EndTag', - name: extractTags(tok.name)}); + name: noStache(tok.name)}); } else if (tok.type === 'Doctype') { out.push({type: 'DocType', name: noStache(tok.name), @@ -319,7 +319,7 @@ var tokenizeHtml = function (html, preString, postString, tagInfoGetter) { data: extractTags(tok.data)}); } else if (tok.type === 'StartTag') { out.push({ type: 'StartTag', - name: extractTags(tok.name), + name: noStache(tok.name), data: _.map(tok.data, function (kv) { return { nodeName: extractTags(kv.nodeName), nodeValue: extractTags(kv.nodeValue) }; From 350d0f5cfeb1a2c633682bcd6df20737ef4c77b4 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Thu, 18 Apr 2013 11:48:34 -0700 Subject: [PATCH 017/938] import of shark work (UI Components) --- examples/unfinished/shark/.meteor/.gitignore | 1 + examples/unfinished/shark/.meteor/packages | 9 + examples/unfinished/shark/.meteor/release | 1 + examples/unfinished/shark/client/shark.html | 6 + examples/unfinished/shark/client/shark.js | 331 +++++++++++++++++++ packages/ui/chunk.js | 91 +++++ packages/ui/component.js | 301 +++++++++++++++++ packages/ui/package.js | 16 + 8 files changed, 756 insertions(+) create mode 100644 examples/unfinished/shark/.meteor/.gitignore create mode 100644 examples/unfinished/shark/.meteor/packages create mode 100644 examples/unfinished/shark/.meteor/release create mode 100644 examples/unfinished/shark/client/shark.html create mode 100644 examples/unfinished/shark/client/shark.js create mode 100644 packages/ui/chunk.js create mode 100644 packages/ui/component.js create mode 100644 packages/ui/package.js diff --git a/examples/unfinished/shark/.meteor/.gitignore b/examples/unfinished/shark/.meteor/.gitignore new file mode 100644 index 0000000000..4083037423 --- /dev/null +++ b/examples/unfinished/shark/.meteor/.gitignore @@ -0,0 +1 @@ +local diff --git a/examples/unfinished/shark/.meteor/packages b/examples/unfinished/shark/.meteor/packages new file mode 100644 index 0000000000..6fb55a3d6d --- /dev/null +++ b/examples/unfinished/shark/.meteor/packages @@ -0,0 +1,9 @@ +# Meteor packages used by this project, one per line. +# +# 'meteor add' and 'meteor remove' will edit this file for you, +# but you can also edit it by hand. + +autopublish +insecure +preserve-inputs +ui diff --git a/examples/unfinished/shark/.meteor/release b/examples/unfinished/shark/.meteor/release new file mode 100644 index 0000000000..621e94f0ec --- /dev/null +++ b/examples/unfinished/shark/.meteor/release @@ -0,0 +1 @@ +none diff --git a/examples/unfinished/shark/client/shark.html b/examples/unfinished/shark/client/shark.html new file mode 100644 index 0000000000..0f4947b0c9 --- /dev/null +++ b/examples/unfinished/shark/client/shark.html @@ -0,0 +1,6 @@ + + shark + + + + diff --git a/examples/unfinished/shark/client/shark.js b/examples/unfinished/shark/client/shark.js new file mode 100644 index 0000000000..ec2b9e9d21 --- /dev/null +++ b/examples/unfinished/shark/client/shark.js @@ -0,0 +1,331 @@ +var debug = function (method, component) { + console.log(method, component.nameInParent); +}; + +// Utility to HTML-escape a string. +var escapeForHtml = (function() { + var escape_map = { + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`", /* IE allows backtick-delimited attributes?? */ + "&": "&" + }; + var escape_one = function(c) { + return escape_map[c]; + }; + + return function (x) { + return x.replace(/[&<>"'`]/g, escape_one); + }; +})(); + +DebugComponent = Component.extend({ + init: function () { debug('init', this); }, + build: function (frag) { debug('build', this); }, + built: function () { debug('built', this); }, + attached: function () { debug('attached', this); }, + detached: function () { debug('detached', this); }, + destroyed: function () { debug('destroyed', this); }, + updated: function (args, oldArgs) { debug('updated', this); } +}); + +LI = DebugComponent.extend({ + build: function (frag) { + var li = document.createElement('LI'); + li.appendChild(document.createTextNode(this.args.text)); + frag.appendChild(li); + this.setBounds(li); + this.textNode = li.firstChild; + }, + updated: function (args, oldArgs) { + if (this.isBuilt) + this.textNode.nodeValue = args.text; + }, + toHtml: function () { + return "

  • " + escapeForHtml(this.args.text) + "
  • "; + } +}); + +UL = DebugComponent.extend({ + init: function () { + this.addChild(1, new LI({text: 'One'})); + this.addChild(2, new LI({text: 'Two'})); + this.addChild(3, new LI({text: 'Three'})); + this.numItems = 3; + }, + build: function (frag) { + var ul = document.createElement('UL'); + this.children[1].attach(ul); + this.children[2].attach(ul); + this.children[3].attach(ul); + frag.appendChild(ul); + this.setBounds(ul); + + var self = this; + self.timer = setInterval(function () { + if (self.isDestroyed || self.numItems >= 10) { + debug('stopping timer', self); + clearInterval(self.timer); + return; + } + var newItem = new LI({text: 'Another'}); + self.addChild(++self.numItems, newItem); + newItem.attach(ul); + + var hr = document.createElement('HR'); + self.parentNode().insertBefore( + hr, self.lastNode().nextSibling); + self.setBounds(ul, hr); + }, 2000); + }, + toHtml: function () { + return "
      " + + this.children[1].toHtml() + + this.children[2].toHtml() + + this.children[3].toHtml() + + "
    "; + } +}); + + +// Function equal to LocalCollection._idStringify, or the identity +// function if we don't have LiveData. Converts item keys (i.e. DDP +// keys) to strings for storage in an OrderedDict. +var idStringify; + +if (typeof LocalCollection !== 'undefined') { + idStringify = function (id) { + if (id === null) + return id; + else + return LocalCollection._idStringify(id); + }; +} else { + idStringify = function (id) { return id; }; +} + +// XXX duplicated code from minimongo.js. It's small though. +var applyChanges = function (doc, changeFields) { + _.each(changeFields, function (value, key) { + if (value === undefined) + delete doc[key]; + else + doc[key] = value; + }); +}; + +EmptyComponent = Component.extend({ + build: function (frag) { + var comment = document.createComment('empty'); + frag.appendChild(comment); + this.setBounds(comment, comment); + }, + toHtml: function () { + return ''; + } +}); + +Each = DebugComponent.extend({ + + items: new OrderedDict(idStringify), + init: function () { + var self = this; + var cursor = self.args.list; // XXX support arrays too + var items = self.items; + + // Templates should have access to data and methods added by the + // transformer, but observeChanges doesn't transform, so we have to do + // it here. + // + // NOTE: this is a little bit of an abstraction violation. Ideally, + // the only thing Spark should know about Minimongo is the contract of + // observeChanges. In theory, anything that implements observeChanges + // could be passed to Spark.list. But meh. + var transformedDoc = function (doc) { + if (cursor.getTransform && cursor.getTransform()) + return cursor.getTransform()(EJSON.clone(doc)); + return doc; + }; + + self.cursorHandle = cursor.observeChanges({ + addedBefore: function (id, item, beforeId) { + var doc = EJSON.clone(item); + doc._id = id; + items.putBefore(id, doc, beforeId); + var tdoc = transformedDoc(doc); + + self.itemAddedBefore(id, tdoc, beforeId); + }, + removed: function (id) { + items.remove(id); + + self.itemRemoved(id); + }, + movedBefore: function (id, beforeId) { + items.moveBefore(id, beforeId); + + self.itemMovedBefore(id, beforeId); + }, + changed: function (id, fields) { + var doc = items.get(id); + if (! doc) + throw new Error("Unknown id for changed: " + idStringify(id)); + applyChanges(doc, fields); + var tdoc = transformedDoc(doc); + + self.itemChanged(id, tdoc); + } + }); + + if (self.items.empty()) + self.initiallyEmpty(); + }, + + destroyed: function () { + var self = this; + if (self.cursorHandle) { + self.cursorHandle.stop(); + self.cursorHandle = null; + } + }, + + updated: function (args, oldArgs) { + // XXXX whhaaaaaaa + }, + + _itemChildId: function (id) { + return 'item:' + idStringify(id); + }, + addItemChild: function (id, comp) { + this.addChild(this._itemChildId(id), comp); + }, + removeItemChild: function (id) { + this.removeChild(this._itemChildId(id)); + }, + getItemChild: function (id) { + return this.children[this._itemChildId(id)]; + }, + // Utility to attach a child component for an item in its + // appropriate position in the DOM, after it is already + // in the correct position in the items dict. + // Also adjusts the component's bounds. + attachItemChild: function (id, comp, beforeId) { + if (! this.isBuilt) + throw new Error("Component must be built"); + + var isFirst = !this.items.prev(id); + var isLast = !beforeId; + var beforeNode = + (beforeId ? this.getItemChild(beforeId).firstNode() : + this.lastNode().nextSibling); + + comp.attach(this.parentNode(), beforeNode); + + if (isFirst) + this.setStart(comp); + if (isLast) + this.setEnd(comp); + }, + + itemAddedBefore: function (id, doc, beforeId) { + var bodyClass = this.args.bodyClass; + var comp = new bodyClass({data: doc}); + this.addItemChild(id, comp); + + if (this.isBuilt) { + this.attachItemChild(id, comp, beforeId); + + if (this.items.size() === 1) + // was empty + this.removeChild('else'); + } + }, + itemRemoved: function (id) { + if (this.items.size() === 1) { + // making empty + var elseClass = this.args.elseClass || EmptyComponent; + var comp = new elseClass({data: this.args.data}); + this.addChild('else', comp); + + if (this.isBuilt) { + comp.attach(this.parentNode(), this.firstNode()); + this.setBounds(comp); + } + } + this.removeItemChild(id); + }, + itemMovedBefore: function (id, beforeId) { + if (this.items.size() === 1) + return; // move is meaningless anyway + + if (this.isBuilt) { + var comp = this.getItemChild(id); + comp.detach(); + this.attachItemChild(id, comp, beforeId); + } + }, + itemChanged: function (id, doc) { + this.getItemChild(id).update({data: doc}); + }, + initiallyEmpty: function () { + var elseClass = this.args.elseClass || EmptyComponent; + this.addChild('else', new elseClass({data: this.args.data})); + }, + + build: function (frag) { + var self = this; + if (self.items.empty()) { + var elseChild = self.children['else']; + elseChild.attach(frag); + self.setBounds(elseChild); + } else { + self.items.forEach(function (doc, id) { + self.getItemChild(id).attach(frag); + }); + self.setBounds(self.getItemChild(self.items.first()), + self.getItemChild(self.items.last())); + } + } + // XXX toHtml + +}); + +MyLI = DebugComponent.extend({ + init: function () { + this.setChild('1', LI, {text: this.args.data.text || ''}); + }, + build: function (frag) { + var c = this.children['1']; + c.attach(frag); + this.setBounds(c); + }, + updated: function (args, oldArgs) { + this.init(); // XXX not necessarily the right pattern + }, + toHtml: function () { + return this.children['1'].toHtml(); + } +}); + +Meteor.startup(function () { +// a = new Chunk($("li").get(0)); +// b = new Chunk($("li").get(1)); +// c = new Chunk($("li").get(2)); +// d = new Chunk(a, c); + +// L = new UL().attach(document.body); + + C = new LocalCollection(); + var ul = document.createElement("UL"); + document.body.appendChild(ul); + + C.insert({text: 'Foo'}); + C.insert({text: 'Bar'}); + C.insert({text: 'Baz'}); + LIST = new Each({list: C.find({}, {sort: {text: 1}}), + bodyClass: MyLI}); + + LIST.attach(ul); +}); diff --git a/packages/ui/chunk.js b/packages/ui/chunk.js new file mode 100644 index 0000000000..ed6c018a0e --- /dev/null +++ b/packages/ui/chunk.js @@ -0,0 +1,91 @@ +// Like LiveRange, but a lot simpler, with unidirectional pointers. +// +// A Chunk represents a range of one or more consecutive sibling +// nodes in the DOM by keeping pointers to the first and last node. +// Trees of nested Chunks can be formed where the subchunks represent +// non-overlapping ranges within the superchunk. To facilitate this +// use, the endpoints of a Chunk may be defined by reference to +// subchunks. +// +// `new Chunk(start, [end])` +// +// `start` and `end` are each either a Chunk or a DOM Node. +// If omitted, `end` defaults to `start`. +// +// The *first node* of a chunk is defined recursively as the +// first node of `start`, if `start` is a Chunk, or else `start` +// itself. Likewise for the *last node* and `end`. +// +// The first node and last node of a Chunk must be siblings (or the +// same node), meaning they share the same non-null parent node. The +// siblings must be in order, that is, the last node must be the same +// as, or after, the first node. +// +// Conceptually, a Chunk points to the range of the DOM containing +// the first node, the last node, the siblings in between them, +// and all the descendents of those nodes. +// +// Chunks are mutable, and firstNode() and lastNode() are calculated +// when accessed based on the state of the Chunk and the Chunks it +// refers to. The main reason to mutate a Chunk is after changing +// the DOM within it, upon which the Chunk may start or end with a +// different Chunk or Node than before and need to be adjusted +// accordingly. +// +// Chunk pointers are unidirectional; there are no pointers back +// from the DOM. Chunks do not exist "in" the DOM, and if they +// form a hierarchy, it is not possible to walk up or across +// the hierarchy, only down, and only then if the outer chunk +// has an endpoint defined in terms of the inner Chunk. +// Chunk objects wrapped around each other are distinct only in +// their potential to mutate. For example, given a Chunk `c`, +// if you create a new Chunk(c, c) and never mutate it, you +// could equivalently use `c` instead. + +Chunk = function (start, end) { + this.set(start, end); +}; + +_.extend(Chunk.prototype, { + firstNode: function () { + return this.start instanceof Chunk ? + this.start.firstNode() : this.start; + }, + lastNode: function () { + return this.end instanceof Chunk ? + this.end.lastNode() : this.end; + }, + set: function (start, end) { + end = end || start; + if (! (start instanceof Chunk || (start && start.nodeType))) + throw new Error("start must be a Chunk or a Node"); + if (! (end instanceof Chunk || (end && end.nodeType))) + throw new Error("end must be a Chunk or a Node"); + + this.start = start; + this.end = end; + + // this check involves a little calculation but it catches + // too many errors to leave out. + var firstNodeParent = this.firstNode().parentNode; + var lastNodeParent = this.lastNode().parentNode; + if (! firstNodeParent || ! lastNodeParent) + throw new Error("start and end must have parents"); + if (firstNodeParent !== lastNodeParent) + throw new Error("start and end must have same parent"); + }, + parentNode: function () { + return this.firstNode().parentNode; + } +}); + +_.extend(Chunk.prototype, { + findOne: function (selector) { + return DomUtils.findClipped( + this.parentNode(), selector, this.firstNode(), this.lastNode()); + }, + findAll: function (selector) { + return DomUtils.findAllClipped( + this.parentNode(), selector, this.firstNode(), this.lastNode()); + } +}); diff --git a/packages/ui/component.js b/packages/ui/component.js new file mode 100644 index 0000000000..0b9501914e --- /dev/null +++ b/packages/ui/component.js @@ -0,0 +1,301 @@ + +Component = function (args) { + this.parent = null; + this.nameInParent = ''; + this.children = {}; + this.isInited = false; + this.isBuilt = false; + this.isAttached = false; + this.isDestroyed = false; + + this.dom = null; // Chunk, if built + this._fragment = null; // DocumentFragment, if built; empty when attached + this._uniqueIdCounter = 1; + + this.args = args; +}; + +_.extend(Component.prototype, { + _requireAlive: function () { + if (this.isDestroyed) + throw new Error("Component was destroyed"); + }, + _forceInit: function () { + this._requireAlive(); + if (! this.isInited) { + this.init(); + this.isInited = true; + } + }, + _build: function () { + this._forceInit(); + if (this.isBuilt) + throw new Error("Component already built"); + + this._fragment = document.createDocumentFragment(); + + this.build(this._fragment); + + if (! this.dom) + throw new Error("build() must call setBounds()"); + this.isBuilt = true; + this.built(); + }, + attach: function (parent, before) { + this._forceInit(); + if (this.isAttached) + throw new Error("Component already attached"); + + if (! this.isBuilt) + this._build(); + + parent.insertBefore(this._fragment, before); + + this.isAttached = true; + + this.attached(); + + return this; + }, + detach: function () { + this._requireAlive(); + if (! this.isAttached) + throw new Error("Component not attached"); + + var start = this.dom.firstNode(); + var end = this.dom.lastNode(); + var frag = this._fragment; + // extract start..end into frag + var parent = start.parentNode; + var before = start.previousSibling; + var after = end.nextSibling; + var n; + while ((n = (before ? before.nextSibling : parent.firstChild)) && + (n !== after)) + frag.appendChild(n); + + this.isAttached = false; + + this.detached(); + + return this; + }, + destroy: function () { + if (! this.isDestroyed) { + this.isDestroyed = true; + + // maybe GC the DOM sooner + this.dom = null; + this._fragment = null; + + this.destroyed(); + + var children = this.children; + for (var k in children) + if (children.hasOwnProperty(k)) + children[k].destroy(); + + if (this.parent && ! this.parent.isDestroyed) + delete this.parent.children[this.nameInParent]; + + this.children = {}; + } + + return this; + }, + hasChild: function (name) { + return this.children.hasOwnProperty(name); + }, + addChild: function (name, childComponent) { + if (name instanceof Component) { + // omitted name, generate unique child ID + childComponent = name; + name = "__child#" + (this._uniqueIdCounter++) + "__"; + } + name = String(name); + + this._requireAlive(); + if (this.hasChild(name)) + throw new Error("Already have a child named: " + name); + + if (childComponent.isDestroyed) + throw new Error("Can't add a destroyed component"); + if (childComponent.isInited) + throw new Error("Can't add a previously added or built component"); + + this.children[name] = childComponent; + + childComponent._added(name, this); + }, + setChild: function (name, childClass, childArgs) { + name = String(name); + + this._requireAlive(); + if (this.hasChild(name)) { + var oldChild = this.children[name]; + if (oldChild.constructor === childClass) { + // old child present with same class + oldChild.update(childArgs); + } else { + var newChild = new childClass(childArgs); + if (oldChild.isAttached) { + var beforeNode = oldChild.lastNode().nextSibling; + var parentNode = oldChild.parentNode(); + this.removeChild(name); + this.addChild(name, newChild); + newChild.attach(parentNode, beforeNode); + } else { + this.addChild(newChild); + } + } + } else { + this.addChild(name, new childClass(childArgs)); + } + }, + _added: function (name, parent) { + name = String(name); + this.nameInParent = name; + this.parent = parent; + + this._forceInit(); + }, + removeChild: function (name) { + name = String(name); + this._requireAlive(); + if (! this.hasChild(name)) + throw new Error("No such child component: " + name); + + var childComponent = this.children[name]; + + if (childComponent.isDestroyed) { + // shouldn't be possible, because destroying a component + // deletes it from the parent's children dictionary, + // but just in case... + delete this.children[name]; + } else { + + if (childComponent.isAttached) + childComponent.detach(); + + childComponent.destroy(); + + } + }, + setBounds: function (start, end) { + end = end || start; + if (start instanceof Component) + start = start.dom; + if (end instanceof Component) + end = end.dom; + + if (! (start instanceof Chunk || (start && start.nodeType))) + throw new Error("setBounds: start must be a built Component or a Node"); + if (! (end instanceof Chunk || (end && end.nodeType))) + throw new Error("setBounds: end must be a built Component or a Node"); + + if (! this.dom) { + this.dom = new Chunk(start, end); + } else { + this.dom.set(start, end); + } + }, + setStart: function (start) { + if (start instanceof Component) + start = start.dom; + + if (! (start instanceof Chunk || (start && start.nodeType))) + throw new Error("setStart: start must be a built Component or a Node"); + if (! this.dom) + throw new Error("Can only call setStart after setBounds has been called"); + + this.dom.start = start; + }, + setEnd: function (end) { + if (end instanceof Component) + end = end.dom; + + if (! (end instanceof Chunk || (end && end.nodeType))) + throw new Error("setEnd: end must be a built Component or a Node"); + if (! this.dom) + throw new Error("Can only call setEnd after setBounds has been called"); + + this.dom.end = end; + }, + update: function (args) { + var oldArgs = this.args; + this.args = args; + this.updated(args, oldArgs); + }, + findOne: function (selector) { return this.dom.findOne(selector); }, + findAll: function (selector) { return this.dom.findAll(selector); }, + firstNode: function () { return this.dom.firstNode(); }, + lastNode: function () { return this.dom.lastNode(); }, + parentNode: function () { return this.dom.parentNode(); }, + // Above methods are NOT overridable. + // + // These are all overridable, with the behavior that all implementations + // are executed from super to sub. + init: function () {}, + build: function (frag) {}, + built: function () {}, + attached: function () {}, + detached: function () {}, + destroyed: function () {}, + updated: function (args, oldArgs) {}, + // This is overridable but should probably get normal override behavior; + // it has a return value and we only run one implementation. + toHtml: function () { + return ''; + } +}); + +Component.extend = function (options) { + var superClass = this; + var baseClass = Component; + // all constructors just call the base constructor + var newClass = function CustomComponent(/*args*/) { + baseClass.apply(this, arguments); + }; + + // Establish a prototype link from newClass.prototype to + // superClass.prototype. This is similar to making + // newClass.prototype a `new superClass` but bypasses + // the constructor. + var fakeSuperClass = function () {}; + fakeSuperClass.prototype = superClass.prototype; + newClass.prototype = new fakeSuperClass; + + // Record the superClass for our future use. + newClass.superClass = superClass; + + // Inherit class (static) properties from parent. + _.extend(newClass, superClass); + + // For callbacks, call one in turn from super to sub. + // Or rather, redefine each callback we are given to call + // super method first. + // XXX TODO: clean this up. + // - General combining mechanism? Filtering mechanism? + // - Get the lookup hash out of here! + _.each(options, function (v, k) { + // important that we have a closure here to capture + // each old function! + var oldFunction = v; + if ({init:1, build:1, built:1, attached:1, detached:1, + destroyed:1, updated:1}.hasOwnProperty(k)) { + options[k] = function () { + superClass.prototype[k].apply(this, arguments); + oldFunction.apply(this, arguments); + }; + } + }); + + // Add instance properties and methods. + if (options) + _.extend(newClass.prototype, options); + + // For browsers that don't support it, fill in `obj.constructor`. + newClass.prototype.constructor = newClass; + + return newClass; +}; diff --git a/packages/ui/package.js b/packages/ui/package.js new file mode 100644 index 0000000000..cb2fa5761c --- /dev/null +++ b/packages/ui/package.js @@ -0,0 +1,16 @@ +Package.describe({ + summary: "Meteor UI Components framework" +}); + +Package.on_use(function (api) { + api.add_files(['chunk.js', 'component.js'], 'client'); +}); + +Package.on_test(function (api) { + api.use('tinytest'); + api.use(['test-helpers', 'dom-utils'], 'client'); + +// api.add_files([ +// 'component_tests.js' +// ], 'client'); +}); From f1a0fcd12c65795151d558eb43b46687fea29674 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Thu, 18 Apr 2013 15:31:25 -0700 Subject: [PATCH 018/938] component.getArg --- examples/unfinished/shark/.meteor/packages | 2 ++ examples/unfinished/shark/client/shark.js | 18 ++++++------ packages/ui/component.js | 32 ++++++++++++++++++++-- packages/ui/package.js | 2 ++ 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/examples/unfinished/shark/.meteor/packages b/examples/unfinished/shark/.meteor/packages index 6fb55a3d6d..50f082c829 100644 --- a/examples/unfinished/shark/.meteor/packages +++ b/examples/unfinished/shark/.meteor/packages @@ -7,3 +7,5 @@ autopublish insecure preserve-inputs ui +html5-tokenizer +spacebars diff --git a/examples/unfinished/shark/client/shark.js b/examples/unfinished/shark/client/shark.js index ec2b9e9d21..3416e873b1 100644 --- a/examples/unfinished/shark/client/shark.js +++ b/examples/unfinished/shark/client/shark.js @@ -34,7 +34,7 @@ DebugComponent = Component.extend({ LI = DebugComponent.extend({ build: function (frag) { var li = document.createElement('LI'); - li.appendChild(document.createTextNode(this.args.text)); + li.appendChild(document.createTextNode(this.getArg('text'))); frag.appendChild(li); this.setBounds(li); this.textNode = li.firstChild; @@ -44,7 +44,7 @@ LI = DebugComponent.extend({ this.textNode.nodeValue = args.text; }, toHtml: function () { - return "
  • " + escapeForHtml(this.args.text) + "
  • "; + return "
  • " + escapeForHtml(this.getArg('text')) + "
  • "; } }); @@ -132,7 +132,7 @@ Each = DebugComponent.extend({ items: new OrderedDict(idStringify), init: function () { var self = this; - var cursor = self.args.list; // XXX support arrays too + var cursor = self.getArg('list'); // XXX support arrays too var items = self.items; // Templates should have access to data and methods added by the @@ -230,7 +230,7 @@ Each = DebugComponent.extend({ }, itemAddedBefore: function (id, doc, beforeId) { - var bodyClass = this.args.bodyClass; + var bodyClass = this.getArg('bodyClass'); var comp = new bodyClass({data: doc}); this.addItemChild(id, comp); @@ -245,8 +245,8 @@ Each = DebugComponent.extend({ itemRemoved: function (id) { if (this.items.size() === 1) { // making empty - var elseClass = this.args.elseClass || EmptyComponent; - var comp = new elseClass({data: this.args.data}); + var elseClass = this.getArg('elseClass') || EmptyComponent; + var comp = new elseClass({data: this.getArg('data')}); this.addChild('else', comp); if (this.isBuilt) { @@ -270,8 +270,8 @@ Each = DebugComponent.extend({ this.getItemChild(id).update({data: doc}); }, initiallyEmpty: function () { - var elseClass = this.args.elseClass || EmptyComponent; - this.addChild('else', new elseClass({data: this.args.data})); + var elseClass = this.getArg('elseClass') || EmptyComponent; + this.addChild('else', new elseClass({data: this.getArg('data')})); }, build: function (frag) { @@ -294,7 +294,7 @@ Each = DebugComponent.extend({ MyLI = DebugComponent.extend({ init: function () { - this.setChild('1', LI, {text: this.args.data.text || ''}); + this.setChild('1', LI, {text: this.getArg('data').text || ''}); }, build: function (frag) { var c = this.children['1']; diff --git a/packages/ui/component.js b/packages/ui/component.js index 0b9501914e..162d65ddbb 100644 --- a/packages/ui/component.js +++ b/packages/ui/component.js @@ -12,7 +12,8 @@ Component = function (args) { this._fragment = null; // DocumentFragment, if built; empty when attached this._uniqueIdCounter = 1; - this.args = args; + this._args = args; + this._argDeps = {}; }; _.extend(Component.prototype, { @@ -221,9 +222,34 @@ _.extend(Component.prototype, { this.dom.end = end; }, + getArg: function (argName) { + var dep = (this._argDeps.hasOwnProperty(argName) ? + this._argDeps[argName] : + (this._argDeps[argName] = new Deps.Dependency)); + dep.depend(); + return this._args[argName]; + }, update: function (args) { - var oldArgs = this.args; - this.args = args; + var oldArgs = this._args; + this._args = args; + + var argDeps = this._argDeps; + + for (var k in args) { + if (args.hasOwnProperty(k) && + argDeps.hasOwnProperty(k) && + ! EJSON.equal(args[k], oldArgs[k])) { + argDeps[k].invalidate(); + delete oldArgs[k]; + } + } + for (var k in oldArgs) { + if (oldArgs.hasOwnProperty(k) && + argDeps.hasOwnProperty(k)) { + argDeps[k].invalidate(); + } + } + this.updated(args, oldArgs); }, findOne: function (selector) { return this.dom.findOne(selector); }, diff --git a/packages/ui/package.js b/packages/ui/package.js index cb2fa5761c..1a380e6bf4 100644 --- a/packages/ui/package.js +++ b/packages/ui/package.js @@ -3,6 +3,8 @@ Package.describe({ }); Package.on_use(function (api) { + api.use('underscore', 'client'); + api.add_files(['chunk.js', 'component.js'], 'client'); }); From 9eced0a9507166b3c711bd58b6a64c7d9f27d315 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 22 Apr 2013 20:39:33 -0700 Subject: [PATCH 019/938] work on HtmlBuilder --- packages/ui/html_builder.js | 138 ++++++++++++++++++++++++++++++++++++ packages/ui/package.js | 3 +- 2 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 packages/ui/html_builder.js diff --git a/packages/ui/html_builder.js b/packages/ui/html_builder.js new file mode 100644 index 0000000000..fa5e33f7f9 --- /dev/null +++ b/packages/ui/html_builder.js @@ -0,0 +1,138 @@ + +HtmlBuilder = function () { + this.builderId = Random.id(); + this.nextElementNum = 1; + this.htmlBuf = []; + this.elemKeyToNum = {}; + this.currentComponent = null; +}; + +var TAG_NAME_REGEX = /^[a-zA-Z0-9]+$/; +var ATTRIBUTE_NAME_REGEX = /^[^\s"'>/=/]+$/; +var ESCAPED_CHARS_UNQUOTED_REGEX = /[&<>]/g; +var ESCAPED_CHARS_QUOTED_REGEX = /[&<>"]/g; + +var escapeMap = { + "<": "<", + ">": ">", + "&": "&", + '"': """ +}; +var escapeOne = function(c) { + return escapeMap[c]; +}; + +var evaluateStringOrHelper = function (stringOrHelper, component) { + if ((typeof stringOrHelper) === 'string') + return stringOrHelper; + + if (! (component instanceof Component)) + throw new Error("Can only use a helper from a Component"); + if (! component.evaluateHelper) + throw new Error("Enclosing Component does not support helpers"); + + return component.evaluateHelper(stringOrHelper); +}; + +_.extend(HtmlBuilder.prototype, { + encodeEntities: function (text, isQuoted) { + // XXX + // All HTML entities in templates are decoded by the template + // parser and given to HtmlBuilder as Unicode. We then re-encode + // some characters into entities here, but not most characters. + // If you're trying to use entities to send ASCII representations + // of non-ASCII characters to the client, you'll need a different + // policy here. + return text.replace(isQuoted ? ESCAPED_CHARS_QUOTED_REGEX : + ESCAPED_CHARS_UNQUOTED_REGEX, escapeOne); + }, + computeAttributeValue: function (expression) { + var self = this; + + if ((typeof expression) === 'string') + return expression; + + var initialValue; + Deps.autorun(function (c) { + if (c.firstRun) { + c.expression = expression; + c.component = self.currentComponent; + } else { + return; // XXX + } + + initialValue = + _.map(c.expression, evaluateStringOrHelper).join(''); + }); + + return initialValue; + }, + openTag: function (tagName, attrs, options) { + var self = this; + + if ((typeof tagName) !== 'string' || + ! TAG_NAME_REGEX.test(tagName)) + throw new Error("Illegal HTML tag name: " + tagName); + + attrs = attrs || {}; + options = options || {}; + + var buf = this.htmlBuf; + buf.push('<', tagName); + _.each(attrs, function (attrValue, attrName) { + if ((typeof attrName) !== 'string' || + ! ATTRIBUTE_NAME_REGEX.test(attrName)) + throw new Error("Illegal HTML attribute name: " + attrName); + + buf.push(' ', attrName, '="'); + buf.push(self.encodeEntities(self.computeAttributeValue(attrValue), + true)); + buf.push('"'); + }); + if (options.selfClose) + buf.push('/'); + buf.push('>'); + }, + closeTag: function (tagName) { + if ((typeof tagName) !== 'string' || + ! TAG_NAME_REGEX.test(tagName)) + throw new Error("Illegal HTML tag name: " + tagName); + this.htmlBuf.push(''); + }, + text: function (stringOrHelper) { + var text = evaluateStringOrHelper(stringOrHelper); + this.htmlBuf.push(this.encodeEntities(text)); + }, + rawHtml: function (stringOrHelper) { + var html = evaluateStringOrHelper(stringOrHelper); + this.htmlBuf.push(html); + }, + finish: function () { + return this.htmlBuf.join(''); + } +}); + +// openChunk, closeChunk +// +// Drop comemnts at start and finish. Comments may have +// to be fished out due to missing close tags (some fun +// logic there). Eventually, can produce cleaner HTML +// using attributes in some cases instead of comments. +// Chunks bound components, and also text/raw inclusions. +// Consecutive openChunks or closeChunks create Chunks +// defined in terms of each other. +// +// When building is finished, it produces HTML and some +// other data. We don't do materialization, because in +// the server-side rendering case, the browser does that! +// +// After materialization, we want to somehow: +// - recalculate all the helpers with deps tracking +// - assign elements to components +// - set bounds of components +// +// ... based on walking the DOM. +// +// The HtmlBuilder probably has a tree of components +// with children and elements, if only to track comment +// references. diff --git a/packages/ui/package.js b/packages/ui/package.js index 1a380e6bf4..a117b58d10 100644 --- a/packages/ui/package.js +++ b/packages/ui/package.js @@ -5,7 +5,8 @@ Package.describe({ Package.on_use(function (api) { api.use('underscore', 'client'); - api.add_files(['chunk.js', 'component.js'], 'client'); + api.add_files(['chunk.js', 'component.js', 'html_builder.js'], + 'client'); }); Package.on_test(function (api) { From 949e277d8f3d11010adc94c9b5d07a4275b3fc28 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Fri, 26 Apr 2013 17:37:06 -0700 Subject: [PATCH 020/938] comments --- packages/ui/html_builder.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/ui/html_builder.js b/packages/ui/html_builder.js index fa5e33f7f9..bf02c8341c 100644 --- a/packages/ui/html_builder.js +++ b/packages/ui/html_builder.js @@ -36,7 +36,6 @@ var evaluateStringOrHelper = function (stringOrHelper, component) { _.extend(HtmlBuilder.prototype, { encodeEntities: function (text, isQuoted) { - // XXX // All HTML entities in templates are decoded by the template // parser and given to HtmlBuilder as Unicode. We then re-encode // some characters into entities here, but not most characters. @@ -136,3 +135,11 @@ _.extend(HtmlBuilder.prototype, { // The HtmlBuilder probably has a tree of components // with children and elements, if only to track comment // references. + +// Component inclusions are also calculated, so their expressions +// must be sent down. Components must also be serialized on the +// wire. Argument change leads to update, of course. + +// Are Component class names in templates resolved? Maybe. +// Assuming so, the test for whether a class has changed is +// comparing the resolved constructor names. \ No newline at end of file From cc3e06292e86df9f422b03b2cf2e23c29adf68ab Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Sat, 27 Apr 2013 08:22:25 -0700 Subject: [PATCH 021/938] notes --- packages/ui/html_builder.js | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/ui/html_builder.js b/packages/ui/html_builder.js index bf02c8341c..fe37362717 100644 --- a/packages/ui/html_builder.js +++ b/packages/ui/html_builder.js @@ -1,10 +1,31 @@ + +ComponentInfo = function (constructorName) { + this.constructorName = constructorName; +// this.children = {}; +// this.elements = {}; +}; + + HtmlBuilder = function () { - this.builderId = Random.id(); - this.nextElementNum = 1; this.htmlBuf = []; - this.elemKeyToNum = {}; - this.currentComponent = null; + + this.rootComponentInfo = null; + this.currentComponentInfo = null; + // parent chain of currentComponent + this.componentInfoStack = []; + +// this.builderId = Random.id(); +// this.nextElementNum = 1; + + //this.chunkPool = []; + // openChunk and closeChunk are primitives that build + // the chunkPool. They are possibly private. + // Can tell if openChunks or closeChunks are consecutive + // by looking at length of htmlBuf. Interesting algo + // problem to build the chunk info correctly. + // ChunkInfo class? Oh, it won't deserialize with + // class intact... without EJSON... }; var TAG_NAME_REGEX = /^[a-zA-Z0-9]+$/; From dde555a7b8b44ad98866397bc3767d53d36cb902 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 13 May 2013 10:22:40 -0700 Subject: [PATCH 022/938] component names are paths --- packages/spacebars/spacebars.js | 19 +++++------- packages/spacebars/spacebars_tests.js | 43 +++++++++++++++++---------- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/packages/spacebars/spacebars.js b/packages/spacebars/spacebars.js index e073556dcd..6d5024470d 100644 --- a/packages/spacebars/spacebars.js +++ b/packages/spacebars/spacebars.js @@ -71,12 +71,12 @@ Spacebars.parseStacheTag = function (inputString, pos) { return text; }; - var scanDottedIdentifier = function () { - var name = scanIdentifier(); - while (run(/^\./)) - name += '.' + scanIdentifier(); - return name; - }; + //var scanDottedIdentifier = function () { + // var name = scanIdentifier(); + // while (run(/^\./)) + // name += '.' + scanIdentifier(); + // return name; + //}; var scanPath = function () { var segments = []; @@ -197,17 +197,14 @@ Spacebars.parseStacheTag = function (inputString, pos) { error("Unclosed comment"); tag.value = result.slice(0, -2); } else if (type === 'BLOCKCLOSE') { - tag.name = scanDottedIdentifier(); + tag.path = scanPath(); if (! run(ends.DOUBLE)) expected('`}}`'); } else if (type === 'ELSE') { if (! run(ends.DOUBLE)) expected('`}}`'); } else { - if (type === 'INCLUSION' || type === 'BLOCKOPEN') - tag.name = scanDottedIdentifier(); - else - tag.path = scanPath(); + tag.path = scanPath(); tag.args = []; while (true) { run(/^\s*/); diff --git a/packages/spacebars/spacebars_tests.js b/packages/spacebars/spacebars_tests.js index 8fe633a580..da1d1fc401 100644 --- a/packages/spacebars/spacebars_tests.js +++ b/packages/spacebars/spacebars_tests.js @@ -40,25 +40,25 @@ Tinytest.add("spacebars - stache tags", function (test) { run('{{ else }}', {type: 'ELSE'}); run('{{else x}}', "Expected"); run('{{else_x}}', {type: 'DOUBLE', path: ['else_x'], args: []}); - run('{{/if}}', {type: 'BLOCKCLOSE', name: 'if'}); - run('{{ / if }}', {type: 'BLOCKCLOSE', name: 'if'}); + run('{{/if}}', {type: 'BLOCKCLOSE', path: ['if']}); + run('{{ / if }}', {type: 'BLOCKCLOSE', path: ['if']}); run('{{/if x}}', "Expected"); - run('{{#if}}', {type: 'BLOCKOPEN', name: 'if', args: []}); - run('{{ # if }}', {type: 'BLOCKOPEN', name: 'if', args: []}); - run('{{#if_3}}', {type: 'BLOCKOPEN', name: 'if_3', args: []}); - run('{{>x}}', {type: 'INCLUSION', name: 'x', args: []}); - run('{{ > x }}', {type: 'INCLUSION', name: 'x', args: []}); - run('{{>x_3}}', {type: 'INCLUSION', name: 'x_3', args: []}); + run('{{#if}}', {type: 'BLOCKOPEN', path: ['if'], args: []}); + run('{{ # if }}', {type: 'BLOCKOPEN', path: ['if'], args: []}); + run('{{#if_3}}', {type: 'BLOCKOPEN', path: ['if_3'], args: []}); + run('{{>x}}', {type: 'INCLUSION', path: ['x'], args: []}); + run('{{ > x }}', {type: 'INCLUSION', path: ['x'], args: []}); + run('{{>x_3}}', {type: 'INCLUSION', path: ['x_3'], args: []}); run('{{foo 3}}', {type: 'DOUBLE', path: ['foo'], args: [['NUMBER', 3]]}); run('{{ foo 3 }}', {type: 'DOUBLE', path: ['foo'], args: [['NUMBER', 3]]}); - run('{{#foo 3}}', {type: 'BLOCKOPEN', name: 'foo', args: [['NUMBER', 3]]}); - run('{{ # foo 3 }}', {type: 'BLOCKOPEN', name: 'foo', + run('{{#foo 3}}', {type: 'BLOCKOPEN', path: ['foo'], args: [['NUMBER', 3]]}); + run('{{ # foo 3 }}', {type: 'BLOCKOPEN', path: ['foo'], args: [['NUMBER', 3]]}); - run('{{>foo 3}}', {type: 'INCLUSION', name: 'foo', args: [['NUMBER', 3]]}); - run('{{ > foo 3 }}', {type: 'INCLUSION', name: 'foo', + run('{{>foo 3}}', {type: 'INCLUSION', path: ['foo'], args: [['NUMBER', 3]]}); + run('{{ > foo 3 }}', {type: 'INCLUSION', path: ['foo'], args: [['NUMBER', 3]]}); run('{{{foo 3}}}', {type: 'TRIPLE', path: ['foo'], args: [['NUMBER', 3]]}); @@ -79,7 +79,7 @@ Tinytest.add("spacebars - stache tags", function (test) { ['PATH', ['', '4']]]}); run('{{# foo this this.x null z=null}}', - {type: 'BLOCKOPEN', name: 'foo', + {type: 'BLOCKOPEN', path: ['foo'], args: [['PATH', ['']], ['PATH', ['', 'x']], ['NULL', null], @@ -89,9 +89,20 @@ Tinytest.add("spacebars - stache tags", function (test) { run('{{foo x/..}}', "`..` is not supported"); run('{{foo x/.}}', "`.`"); - run('{{#a.b.c}}', {type: 'BLOCKOPEN', name: 'a.b.c', args: []}); - run('{{> a.b.c}}', {type: 'INCLUSION', name: 'a.b.c', args: []}); + run('{{#a.b.c}}', {type: 'BLOCKOPEN', path: ['a', 'b', 'c'], + args: []}); + run('{{> a.b.c}}', {type: 'INCLUSION', path: ['a', 'b', 'c'], + args: []}); - run('{{foo.[]/[]}}', {type: 'DOUBLE', path: ['foo', '', ''], args: []}); + run('{{foo.[]/[]}}', {type: 'DOUBLE', path: ['foo', '', ''], + args: []}); run('{{[].foo}}', "Path can't start with empty string"); + + run('{{foo true}}', {type: 'DOUBLE', path: ['foo'], + args: [['BOOLEAN', true]]}); + run('{{foo "bar"}}', {type: 'DOUBLE', path: ['foo'], + args: [['STRING', 'bar']]}); + run("{{foo 'bar'}}", {type: 'DOUBLE', path: ['foo'], + args: [['STRING', 'bar']]}); + }); From d58e70610e768c21d36ef4f01eb3454a544d8ecf Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 13 May 2013 11:53:28 -0700 Subject: [PATCH 023/938] work on ui API --- packages/ui/html_builder.js | 66 ++++++++++++++++--------------------- packages/ui/package.js | 3 +- 2 files changed, 30 insertions(+), 39 deletions(-) diff --git a/packages/ui/html_builder.js b/packages/ui/html_builder.js index fe37362717..d4b898473b 100644 --- a/packages/ui/html_builder.js +++ b/packages/ui/html_builder.js @@ -1,19 +1,13 @@ -ComponentInfo = function (constructorName) { - this.constructorName = constructorName; -// this.children = {}; -// this.elements = {}; -}; - HtmlBuilder = function () { this.htmlBuf = []; - this.rootComponentInfo = null; - this.currentComponentInfo = null; - // parent chain of currentComponent - this.componentInfoStack = []; + //this.rootComponent = null; + //this.currentComponent = null; + // parent chain of currentComponent, exclusive + //this.componentStack = []; // this.builderId = Random.id(); // this.nextElementNum = 1; @@ -43,30 +37,20 @@ var escapeOne = function(c) { return escapeMap[c]; }; -var evaluateStringOrHelper = function (stringOrHelper, component) { - if ((typeof stringOrHelper) === 'string') - return stringOrHelper; - - if (! (component instanceof Component)) - throw new Error("Can only use a helper from a Component"); - if (! component.evaluateHelper) - throw new Error("Enclosing Component does not support helpers"); - - return component.evaluateHelper(stringOrHelper); +var encodeEntities = function (text, isQuoted) { + // All HTML entities in templates are decoded by the template parser + // and given to HtmlBuilder as Unicode. We then re-encode some + // characters into entities here, but not most characters. If + // you're trying to use entities to send ASCII representations of + // non-ASCII characters to the client, you'll need a different + // policy here. + return text.replace(isQuoted ? ESCAPED_CHARS_QUOTED_REGEX : + ESCAPED_CHARS_UNQUOTED_REGEX, escapeOne); }; _.extend(HtmlBuilder.prototype, { - encodeEntities: function (text, isQuoted) { - // All HTML entities in templates are decoded by the template - // parser and given to HtmlBuilder as Unicode. We then re-encode - // some characters into entities here, but not most characters. - // If you're trying to use entities to send ASCII representations - // of non-ASCII characters to the client, you'll need a different - // policy here. - return text.replace(isQuoted ? ESCAPED_CHARS_QUOTED_REGEX : - ESCAPED_CHARS_UNQUOTED_REGEX, escapeOne); - }, - computeAttributeValue: function (expression) { + _encodeEntities: encodeEntities, + /*computeAttributeValue: function (expression) { var self = this; if ((typeof expression) === 'string') @@ -86,7 +70,7 @@ _.extend(HtmlBuilder.prototype, { }); return initialValue; - }, + },*/ openTag: function (tagName, attrs, options) { var self = this; @@ -105,8 +89,9 @@ _.extend(HtmlBuilder.prototype, { throw new Error("Illegal HTML attribute name: " + attrName); buf.push(' ', attrName, '="'); - buf.push(self.encodeEntities(self.computeAttributeValue(attrValue), - true)); + var initialValue = (typeof attrValue === 'string' ? + attrValue : attrValue()); + buf.push(self._encodeEntities(initialValue, true)); buf.push('"'); }); if (options.selfClose) @@ -119,14 +104,19 @@ _.extend(HtmlBuilder.prototype, { throw new Error("Illegal HTML tag name: " + tagName); this.htmlBuf.push(''); }, - text: function (stringOrHelper) { - var text = evaluateStringOrHelper(stringOrHelper); + text: function (stringOrFunction) { + var text = (typeof stringOrFunction === 'string' ? + stringOrFunction : stringOrFunction()); this.htmlBuf.push(this.encodeEntities(text)); }, - rawHtml: function (stringOrHelper) { - var html = evaluateStringOrHelper(stringOrHelper); + rawHtml: function (stringOrFunction) { + var html = (typeof stringOrFunction === 'string' ? + stringOrFunction : stringOrFunction()); this.htmlBuf.push(html); }, + component: function (componentOrFunction) { + // XXX + }, finish: function () { return this.htmlBuf.join(''); } diff --git a/packages/ui/package.js b/packages/ui/package.js index a117b58d10..785291e434 100644 --- a/packages/ui/package.js +++ b/packages/ui/package.js @@ -11,7 +11,8 @@ Package.on_use(function (api) { Package.on_test(function (api) { api.use('tinytest'); - api.use(['test-helpers', 'dom-utils'], 'client'); + api.use('ui'); + api.use(['test-helpers', 'domutils'], 'client'); // api.add_files([ // 'component_tests.js' From e4c6eb7e79206411dbf0425cce4ddfc1d121b01b Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 13 May 2013 14:46:02 -0700 Subject: [PATCH 024/938] wip --- packages/ui/component.js | 10 ++++++-- packages/ui/html_builder.js | 47 ++++++++++++++++++++++++++++--------- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/packages/ui/component.js b/packages/ui/component.js index 162d65ddbb..4fd6b01b13 100644 --- a/packages/ui/component.js +++ b/packages/ui/component.js @@ -109,12 +109,18 @@ _.extend(Component.prototype, { }, addChild: function (name, childComponent) { if (name instanceof Component) { - // omitted name, generate unique child ID + // omitted name arg childComponent = name; - name = "__child#" + (this._uniqueIdCounter++) + "__"; + name = null; } + // omitted name, generate unique child ID + if (name === null || typeof name === 'undefined') + name = "__child#" + (this._uniqueIdCounter++) + "__"; name = String(name); + if (! (childComponent instanceof Component)) + throw new Error("not a Component: " + childComponent); + this._requireAlive(); if (this.hasChild(name)) throw new Error("Already have a child named: " + name); diff --git a/packages/ui/html_builder.js b/packages/ui/html_builder.js index d4b898473b..c13085cca1 100644 --- a/packages/ui/html_builder.js +++ b/packages/ui/html_builder.js @@ -4,10 +4,10 @@ HtmlBuilder = function () { this.htmlBuf = []; - //this.rootComponent = null; - //this.currentComponent = null; + this.rootComponent = null; + this.currentComponent = null; // parent chain of currentComponent, exclusive - //this.componentStack = []; + this.componentStack = []; // this.builderId = Random.id(); // this.nextElementNum = 1; @@ -89,8 +89,8 @@ _.extend(HtmlBuilder.prototype, { throw new Error("Illegal HTML attribute name: " + attrName); buf.push(' ', attrName, '="'); - var initialValue = (typeof attrValue === 'string' ? - attrValue : attrValue()); + var initialValue = (typeof attrValue === 'function' ? + attrValue() : attrValue); buf.push(self._encodeEntities(initialValue, true)); buf.push('"'); }); @@ -105,23 +105,48 @@ _.extend(HtmlBuilder.prototype, { this.htmlBuf.push(''); }, text: function (stringOrFunction) { - var text = (typeof stringOrFunction === 'string' ? - stringOrFunction : stringOrFunction()); + var text = (typeof stringOrFunction === 'function' ? + stringOrFunction() : stringOrFunction); this.htmlBuf.push(this.encodeEntities(text)); }, rawHtml: function (stringOrFunction) { - var html = (typeof stringOrFunction === 'string' ? - stringOrFunction : stringOrFunction()); + var html = (typeof stringOrFunction === 'function' ? + stringOrFunction() : stringOrFunction); this.htmlBuf.push(html); }, - component: function (componentOrFunction) { - // XXX + component: function (componentOrFunction, options) { + var self = this; + var comp = (typeof componentOrFunction === 'function' ? + componentOrFunction() : componentOrFunction); + + var parentComponent = self.currentComponent; // may be null + + if (self.currentComponent) + self.componentStack.push(self.currentComponent); + self.currentComponent = comp; + + var childKey = (options && options.childKey || null); + + try { + if (parentComponent) + parentComponent.addChild(childKey, comp); + comp.build(self); + // XXX + } finally { + self.currentComponent = self.componentStack.pop() || null; + } }, finish: function () { return this.htmlBuf.join(''); } }); +// CHANGES TO COMPONENT NEEDED: +// +// - childKey, elementKey -- call things "key" +// - no attach/detach -- need to wire things up from HTML... + + // openChunk, closeChunk // // Drop comemnts at start and finish. Comments may have From e05ae7b7f8c3d538946f372edf1d26c4ee1f187a Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Fri, 17 May 2013 10:06:42 -0700 Subject: [PATCH 025/938] component model next rev WIP next step: component.addChild([key], comp, [parentNode], [beforeNode]). Builds on the client but not on the server. HtmlBuilder -> RenderBuffer {buffer.text,buffer.rawHtml} create Components --- packages/ui/component.js | 196 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) diff --git a/packages/ui/component.js b/packages/ui/component.js index 4fd6b01b13..db592fa584 100644 --- a/packages/ui/component.js +++ b/packages/ui/component.js @@ -1,3 +1,197 @@ +Component = function (args) { + this.stage = Component.UNADDED; + + this._uniqueIdCounter = 1; + + // UNINITED Components get these: + this._args = args; + this._argDeps = {}; + + // INITED Components get these: + this.key = ''; + this.parent = null; + this.children = {}; + + // BUILT Components get these: + this._start = null; // first Component or Node + this._end = null; // last Component or Node +}; + +// life stages of a Component +_.extend(Component, { + UNADDED: ['UNADDED'], + ADDED: ['ADDED'], + BUILT: ['BUILT'], + DESTROYED: ['DESTROYED'] +}); + +_.extend(Component.prototype, { + _requireStage: function (stage) { + if (this.stage !== stage) + throw new Error("Need a " + stage + " Component, found a " + + this.stage + " Component."); + }, + _added: function (key, parent) { + this._requireStage(Component.UNADDED); + this.key = key; + this.parent = parent; + this.stage = Component.ADDED; + }, + destroy: function () { + // Leaves the DOM in place + + if (this.stage === Component.DESTROYED) + return; + + var oldStage = this.stage; + this.stage = Component.DESTROYED; + + if (oldStage === Component.UNADDED) + return; + + // maybe GC sooner + this._start = null; + this._end = null; + + this.destroyed(); + + var children = this.children; + for (var k in children) + if (children.hasOwnProperty(k)) + children[k].destroy(); + + if (this.parent) + delete this.parent.children[this.key]; + + this.children = {}; + } +}); + +// Once the Component is built, if the Component implementation +// modifies the DOM composition of the Component, it must specify +// the new bounds using some combination of these. +_.extend(Component.prototype, { + setStart: function (start) { + this._requireStage(Component.BUILT); + + if (! ((start instanceof Component && + start.stage === Component.BUILT) || + (start && start.nodeType))) + throw new Error("start must be a built Component or a Node"); + + this._start = start; + }, + setEnd: function (end) { + this._requireStage(Component.BUILT); + + if (! ((end instanceof Component && + end.stage === Component.BUILT) || + (end && end.nodeType))) + throw new Error("end must be a built Component or a Node"); + + this._end = end; + }, + setBounds: function (start, end) { + end = end || start; + this.setStart(start); + this.setEnd(end); + }, + firstNode: function () { + this._requireStage(Component.BUILT); + return this._start instanceof Component ? + this._start.firstNode() : this._start; + }, + lastNode: function () { + this._requireStage(Component.BUILT); + return this._end instanceof Component ? + this._end.lastNode() : this._end; + }, + parentNode: function () { + return this.firstNode().parentNode; + }, + findOne: function (selector) { + return DomUtils.findClipped( + this.parentNode(), selector, + this.firstNode(), this.lastNode()); + }, + findAll: function (selector) { + return DomUtils.findAllClipped( + this.parentNode(), selector, + this.firstNode(), this.lastNode()); + } +}); + +_.extend(Component.prototype, { + getArg: function (argName) { + var dep = (this._argDeps.hasOwnProperty(argName) ? + this._argDeps[argName] : + (this._argDeps[argName] = new Deps.Dependency)); + dep.depend(); + return this._args[argName]; + }, + update: function (args) { + var oldArgs = this._args; + this._args = args; + + var argDeps = this._argDeps; + + for (var k in args) { + if (args.hasOwnProperty(k) && + argDeps.hasOwnProperty(k) && + ! EJSON.equal(args[k], oldArgs[k])) { + argDeps[k].invalidate(); + delete oldArgs[k]; + } + } + for (var k in oldArgs) { + if (oldArgs.hasOwnProperty(k) && + argDeps.hasOwnProperty(k)) { + argDeps[k].invalidate(); + } + } + + this.updated(args, oldArgs); + } +}); + +_.extend(Component.prototype, { + hasChild: function (key) { + return this.children.hasOwnProperty(key); + }, + addChild: function (key, childComponent) { + if (key instanceof Component) { + // omitted key arg + childComponent = key; + key = null; + } + // omitted key, generate unique child key + if (key === null || typeof key === 'undefined') + key = "__child#" + (this._uniqueIdCounter++) + "__"; + key = String(key); + + if (! (childComponent instanceof Component)) + throw new Error("not a Component: " + childComponent); + + this._requireStage(Component.ADDED); + childComponent._requireStage(Component.UNADDED); + + if (this.hasChild(key)) + throw new Error("Already have a child with key: " + key); + + this.children[key] = childComponent; + + childComponent._added(key, this); + } +}); + +_.extend(Component.prototype, { + render: function (builder) {}, + updated: function (args, oldArgs) {}, + destroyed: function () {} +}); + +////////////////////////////////////////////////// + Component = function (args) { this.parent = null; @@ -281,6 +475,8 @@ _.extend(Component.prototype, { } }); +//////////////////// + Component.extend = function (options) { var superClass = this; var baseClass = Component; From 4c1c9979c17ab5c29e362e695929aa557512733d Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Sun, 19 May 2013 23:38:56 -0700 Subject: [PATCH 026/938] more Meteor UI --- examples/unfinished/shark/client/shark.js | 45 +++- packages/domutils/domutils.js | 9 +- packages/ui/component.js | 212 +++++++++++++++++- packages/ui/package.js | 2 +- .../ui/{html_builder.js => renderbuffer.js} | 89 ++++---- 5 files changed, 297 insertions(+), 60 deletions(-) rename packages/ui/{html_builder.js => renderbuffer.js} (71%) diff --git a/examples/unfinished/shark/client/shark.js b/examples/unfinished/shark/client/shark.js index 3416e873b1..e31aec2de3 100644 --- a/examples/unfinished/shark/client/shark.js +++ b/examples/unfinished/shark/client/shark.js @@ -1,4 +1,44 @@ -var debug = function (method, component) { + + + +IfComponent = Component.extend({ + arguments: { + positional: ['condition'], + required: ['bodyClass'] + }, + render: function (buf) { + if (this.getArg('condition')) + buf.component(this.getArg('bodyClass').create()); + else if (this.getArg('elseClass')) + buf.component(this.getArg('elseClass')); + } +}); + +Meteor.startup(function () { + /* + RC = RootComponent.create({ + bodyClass: Component.extend({ + render: function (buf) { + buf.text(Session.get('foo') || ''); + } + }) + }); + */ + RC = RootComponent.create({ + bodyClass: Component.extend({ + render: function (buf) { + buf.text(function () { + return Session.get('foo') || ''; + }); + } + }) + }); + RC.attach(document.body); +}); + + + +/*var debug = function (method, component) { console.log(method, component.nameInParent); }; @@ -9,7 +49,7 @@ var escapeForHtml = (function() { ">": ">", '"': """, "'": "'", - "`": "`", /* IE allows backtick-delimited attributes?? */ + "`": "`", // IE allows backtick-delimited attributes?? "&": "&" }; var escape_one = function(c) { @@ -329,3 +369,4 @@ Meteor.startup(function () { LIST.attach(ul); }); + */ \ No newline at end of file diff --git a/packages/domutils/domutils.js b/packages/domutils/domutils.js index 33d1b13415..303296afd9 100644 --- a/packages/domutils/domutils.js +++ b/packages/domutils/domutils.js @@ -458,18 +458,23 @@ DomUtils.compareElementIndex = function (a, b) { // // `frag` is a DocumentFragment and will be modified in // place. `container` is a DOM element. +// +// Returns the number of levels of wrapping applied, which is +// 0 if no wrapping was performed. DomUtils.wrapFragmentForContainer = function (frag, container) { if (container && container.nodeName === "TABLE" && _.any(frag.childNodes, function (n) { return n.nodeName === "TR"; })) { // Avoid putting a TR directly in a TABLE without an - // intervening TBODY, because it doesn't work in IE. We do - // the same thing on all browsers for ease of testing + // intervening TBODY, because it doesn't work in (old?) IE. + // We do the same thing on all browsers for ease of testing // and debugging. var tbody = document.createElement("TBODY"); tbody.appendChild(frag); frag.appendChild(tbody); + return 1; } + return 0; }; // Return true if `node` is part of the global DOM document. Like diff --git a/packages/ui/component.js b/packages/ui/component.js index db592fa584..753f4f1cf8 100644 --- a/packages/ui/component.js +++ b/packages/ui/component.js @@ -1,10 +1,17 @@ +var constructorsLocked = true; + Component = function (args) { + if (constructorsLocked) + throw new Error("To create a Component, " + + "use ComponentClass.create(...)"); + constructorsLocked = true; + this.stage = Component.UNADDED; this._uniqueIdCounter = 1; // UNINITED Components get these: - this._args = args; + this._args = args || {}; this._argDeps = {}; // INITED Components get these: @@ -15,6 +22,11 @@ Component = function (args) { // BUILT Components get these: this._start = null; // first Component or Node this._end = null; // last Component or Node + this.isAttached = false; + this._detachedContent = null; // DocumentFragment + this._buildComputation = null; + + this.constructed(); }; // life stages of a Component @@ -28,7 +40,7 @@ _.extend(Component, { _.extend(Component.prototype, { _requireStage: function (stage) { if (this.stage !== stage) - throw new Error("Need a " + stage + " Component, found a " + + throw new Error("Need " + stage + " Component, found " + this.stage + " Component."); }, _added: function (key, parent) { @@ -36,6 +48,40 @@ _.extend(Component.prototype, { this.key = key; this.parent = parent; this.stage = Component.ADDED; + this.init(); + }, + build: function () { + var self = this; + self._requireStage(Component.ADDED); + self._buildComputation = + Deps.autorun(function (c) { + var buf = new RenderBuffer(self); + self.render(buf); + var buildResult = buf.build(); + var wasAttachedParent = null; + var wasAttachedBefore = null; + if (! c.firstRun) { + // already built; rebuilding + if (self.isAttached) { + wasAttachedParent = self.parentNode(); + wasAttachedBefore = self.lastNode().nextSibling; + self.detach(true); + } + } + self._detachedContent = buildResult.fragment; + self._start = buildResult.start; + self._end = buildResult.end; + if (wasAttachedParent) { + self.attach(wasAttachedParent, wasAttachedBefore, + true); + } + if (c.firstRun) { + self.built(); + } else { + self.rebuilt(); + } + }); + self.stage = Component.BUILT; }, destroy: function () { // Leaves the DOM in place @@ -49,6 +95,9 @@ _.extend(Component.prototype, { if (oldStage === Component.UNADDED) return; + if (this._buildComputation) + this._buildComputation.stop(); + // maybe GC sooner this._start = null; this._end = null; @@ -64,6 +113,58 @@ _.extend(Component.prototype, { delete this.parent.children[this.key]; this.children = {}; + }, + attach: function (parent, before, _silent) { + if (this.stage === Component.ADDED) + this.build(); + + this._requireStage(Component.BUILT); + if (this.isAttached) + throw new Error("Component already attached"); + + if ((! parent) || ! parent.nodeType) + throw new Error("first argument of attach must be a Node"); + if (before && ! before.nodeType) + throw new Error("second argument of attach must be a Node" + + " if given"); + + var frag = this._detachedContent; + + if (DomUtils.wrapFragmentForContainer(frag, parent)) { + this._start = frag.firstChild; + this._end = frag.lastChild; + } + parent.insertBefore(frag, before); + this._detachedContent = null; + + this.isAttached = true; + + if (! _silent) + this.attached(); + }, + detach: function (_silent) { + this._requireStage(Component.BUILT); + if (! this.isAttached) + throw new Error("Component not attached"); + + this._detachedContent = document.createDocumentFragment(); + + var start = this.firstNode(); + var end = this.lastNode(); + var frag = this._detachedContent; + // extract start..end into frag + var parent = start.parentNode; + var before = start.previousSibling; + var after = end.nextSibling; + var n; + while ((n = (before ? before.nextSibling : parent.firstChild)) && + (n !== after)) + frag.appendChild(n); + + this.isAttached = false; + + if (! _silent) + this.detached(); } }); @@ -172,6 +273,9 @@ _.extend(Component.prototype, { if (! (childComponent instanceof Component)) throw new Error("not a Component: " + childComponent); + // XXX later: also work if we are BUILT, and build the + // child... maybe attach it too based on extra arguments + // to addChild like parentNode and beforeNode this._requireStage(Component.ADDED); childComponent._requireStage(Component.UNADDED); @@ -181,17 +285,49 @@ _.extend(Component.prototype, { this.children[key] = childComponent; childComponent._added(key, this); + }, + removeChild: function (key) { + key = String(key); + + // XXX later: also work if we are BUILT, and detach + // the child first if so. + this._requireStage(Component.ADDED); + + if (! this.hasChild(key)) + throw new Error("No such child component: " + key); + + var childComponent = this.children[key]; + + if (childComponent.isDestroyed) { + // shouldn't be possible, because destroying a component + // deletes it from the parent's children dictionary, + // but just in case... + delete this.children[key]; + } else { + + // XXX + //if (childComponent.isAttached) + //childComponent.detach(); + + childComponent.destroy(); + } } }); _.extend(Component.prototype, { - render: function (builder) {}, + constructed: function () {}, + init: function () {}, + render: function (buf) {}, updated: function (args, oldArgs) {}, - destroyed: function () {} + destroyed: function () {}, + attached: function () {}, + detached: function () {}, + built: function () {}, + rebuilt: function () {} }); ////////////////////////////////////////////////// - +/* Component = function (args) { this.parent = null; @@ -474,15 +610,30 @@ _.extend(Component.prototype, { return ''; } }); - +*/ //////////////////// +// Require ComponentClass.create(...) instead of +// new CompomentClass(...) because a factory method gives +// us more flexibility, and there should be one way to +// make a component. The `new` syntax is awkward if +// the component class is calculated by a complex expression +// (like a reactive getter). +Component.create = function (/*args*/) { + constructorsLocked = false; + var comp = new this; + Component.apply(comp, arguments); + return comp; +}; + Component.extend = function (options) { var superClass = this; - var baseClass = Component; // all constructors just call the base constructor - var newClass = function CustomComponent(/*args*/) { - baseClass.apply(this, arguments); + var newClass = function CustomComponent() { + if (constructorsLocked) + throw new Error("To create a Component, " + + "use ComponentClass.create(...)"); + // (Component.create kicks off construction) }; // Establish a prototype link from newClass.prototype to @@ -509,8 +660,16 @@ Component.extend = function (options) { // important that we have a closure here to capture // each old function! var oldFunction = v; - if ({init:1, build:1, built:1, attached:1, detached:1, - destroyed:1, updated:1}.hasOwnProperty(k)) { + if ({init:1, + render:1, + destroyed:1, + updated:1, + attached:1, + detached:1, + built:1, + rebuilt:1, + constructed:1 + }.hasOwnProperty(k)) { options[k] = function () { superClass.prototype[k].apply(this, arguments); oldFunction.apply(this, arguments); @@ -525,5 +684,36 @@ Component.extend = function (options) { // For browsers that don't support it, fill in `obj.constructor`. newClass.prototype.constructor = newClass; + newClass.create = Component.create; + return newClass; }; + +TextComponent = Component.extend({ + render: function (buf) { + buf.text(this.getArg('text')); + } +}); + +RawHtmlComponent = Component.extend({ + render: function (buf) { + buf.rawHtml(this.getArg('html')); + } +}); + +RootComponent = Component.extend({ + constructed: function () { + this.stage = Component.ADDED; + }, + render: function (buf) { + var bodyClass = this.getArg('bodyClass'); + if (bodyClass) + buf.component(bodyClass.create()); + } +}); + + +// need **rebuild**; "render" runs in a reactive context + +// What does RenderBuffer do to build a subcomponent? +// Assigning Chunks; build and rebuild \ No newline at end of file diff --git a/packages/ui/package.js b/packages/ui/package.js index 785291e434..a9580e513f 100644 --- a/packages/ui/package.js +++ b/packages/ui/package.js @@ -5,7 +5,7 @@ Package.describe({ Package.on_use(function (api) { api.use('underscore', 'client'); - api.add_files(['chunk.js', 'component.js', 'html_builder.js'], + api.add_files(['chunk.js', 'component.js', 'renderbuffer.js'], 'client'); }); diff --git a/packages/ui/html_builder.js b/packages/ui/renderbuffer.js similarity index 71% rename from packages/ui/html_builder.js rename to packages/ui/renderbuffer.js index c13085cca1..5ac84d5b64 100644 --- a/packages/ui/html_builder.js +++ b/packages/ui/renderbuffer.js @@ -1,25 +1,14 @@ -HtmlBuilder = function () { - this.htmlBuf = []; +RenderBuffer = function (component) { + this._component = component; + this._htmlBuf = []; - this.rootComponent = null; - this.currentComponent = null; - // parent chain of currentComponent, exclusive - this.componentStack = []; + this._builderId = Random.id(); + this._nextNum = 1; -// this.builderId = Random.id(); -// this.nextElementNum = 1; - - //this.chunkPool = []; - // openChunk and closeChunk are primitives that build - // the chunkPool. They are possibly private. - // Can tell if openChunks or closeChunks are consecutive - // by looking at length of htmlBuf. Interesting algo - // problem to build the chunk info correctly. - // ChunkInfo class? Oh, it won't deserialize with - // class intact... without EJSON... + this._childrenToAttach = []; // comment string -> component }; var TAG_NAME_REGEX = /^[a-zA-Z0-9]+$/; @@ -39,7 +28,7 @@ var escapeOne = function(c) { var encodeEntities = function (text, isQuoted) { // All HTML entities in templates are decoded by the template parser - // and given to HtmlBuilder as Unicode. We then re-encode some + // and given to RenderBuffer as Unicode. We then re-encode some // characters into entities here, but not most characters. If // you're trying to use entities to send ASCII representations of // non-ASCII characters to the client, you'll need a different @@ -48,7 +37,7 @@ var encodeEntities = function (text, isQuoted) { ESCAPED_CHARS_UNQUOTED_REGEX, escapeOne); }; -_.extend(HtmlBuilder.prototype, { +_.extend(RenderBuffer.prototype, { _encodeEntities: encodeEntities, /*computeAttributeValue: function (expression) { var self = this; @@ -81,7 +70,7 @@ _.extend(HtmlBuilder.prototype, { attrs = attrs || {}; options = options || {}; - var buf = this.htmlBuf; + var buf = this._htmlBuf; buf.push('<', tagName); _.each(attrs, function (attrValue, attrName) { if ((typeof attrName) !== 'string' || @@ -102,42 +91,54 @@ _.extend(HtmlBuilder.prototype, { if ((typeof tagName) !== 'string' || ! TAG_NAME_REGEX.test(tagName)) throw new Error("Illegal HTML tag name: " + tagName); - this.htmlBuf.push(''); + this._htmlBuf.push(''); }, text: function (stringOrFunction) { - var text = (typeof stringOrFunction === 'function' ? - stringOrFunction() : stringOrFunction); - this.htmlBuf.push(this.encodeEntities(text)); + if (typeof stringOrFunction === 'function') { + var func = stringOrFunction; + this.component(function () { + return new TextComponent({text: func()}); + }); + } else { + var text = stringOrFunction; + this._htmlBuf.push(this.encodeEntities(text)); + } }, rawHtml: function (stringOrFunction) { - var html = (typeof stringOrFunction === 'function' ? - stringOrFunction() : stringOrFunction); - this.htmlBuf.push(html); + if (typeof stringOrFunction === 'function') { + var func = stringOrFunction; + this.component(function () { + return new RawHtmlComponent({html: func()}); + }); + } else { + var html = stringOrFunction; + this._htmlBuf.push(html); + } }, component: function (componentOrFunction, options) { var self = this; var comp = (typeof componentOrFunction === 'function' ? componentOrFunction() : componentOrFunction); - var parentComponent = self.currentComponent; // may be null - - if (self.currentComponent) - self.componentStack.push(self.currentComponent); - self.currentComponent = comp; - var childKey = (options && options.childKey || null); - try { - if (parentComponent) - parentComponent.addChild(childKey, comp); - comp.build(self); - // XXX - } finally { - self.currentComponent = self.componentStack.pop() || null; - } + this._component.addChild(childKey, comp); + // build it detached + comp._build(); + + var commentString = this.builderId + '_' + + (this._nextNum++); + this._htmlBuf.push(''); + + this._childrenToAttach[commentString] = comp; }, - finish: function () { - return this.htmlBuf.join(''); + toFragment: function () { + var html = this._htmlBuf.join(''); + var frag = DomUtils.htmlToFragment(html); + + + + return frag; } }); @@ -168,7 +169,7 @@ _.extend(HtmlBuilder.prototype, { // // ... based on walking the DOM. // -// The HtmlBuilder probably has a tree of components +// The RenderBuffer probably has a tree of components // with children and elements, if only to track comment // references. From 35e75f38f443a992332320b1136bb4468090557b Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 20 May 2013 10:10:14 -0700 Subject: [PATCH 027/938] nail down add/remove/attach/detach logic --- packages/domutils/domutils.js | 14 +++ packages/ui/component.js | 212 ++++++++++++++++++++++------------ packages/ui/renderbuffer.js | 60 ++++++++-- 3 files changed, 200 insertions(+), 86 deletions(-) diff --git a/packages/domutils/domutils.js b/packages/domutils/domutils.js index 303296afd9..a5d417d984 100644 --- a/packages/domutils/domutils.js +++ b/packages/domutils/domutils.js @@ -558,3 +558,17 @@ DomUtils.getElementValue = function (node) { return node.value; } }; + +DomUtils.extractRange = function (start, end, optContainer) { + var parent = start.parentNode; + var before = start.previousSibling; + var after = end.nextSibling; + var n; + while ((n = (before ? before.nextSibling : parent.firstChild)) && + (n !== after)) { + if (optContainer) + optContainer.appendChild(n); + else + parent.removeChild(n); + } +}; \ No newline at end of file diff --git a/packages/ui/component.js b/packages/ui/component.js index 753f4f1cf8..d7d99a76f8 100644 --- a/packages/ui/component.js +++ b/packages/ui/component.js @@ -37,6 +37,9 @@ _.extend(Component, { DESTROYED: ['DESTROYED'] }); +// Fills in for _start and _end on a temporary basis. +var EMPTY = ['EMPTY']; + _.extend(Component.prototype, { _requireStage: function (stage) { if (this.stage !== stage) @@ -55,36 +58,45 @@ _.extend(Component.prototype, { self._requireStage(Component.ADDED); self._buildComputation = Deps.autorun(function (c) { + var isRebuild = (self.stage === Component.BUILT); + var oldFirstNode, oldLastNode; + if (isRebuild) { + oldFirstNode = self.firstNode(); + oldLastNode = self.lastNode(); + for (var k in self.children) { + if (self.children.hasOwnProperty(k)) { + var child = self.children[k]; + child.destroy(); + self.removeChild(child.key); + } + } + self.stage = Component.ADDED; + } var buf = new RenderBuffer(self); self.render(buf); var buildResult = buf.build(); - var wasAttachedParent = null; - var wasAttachedBefore = null; - if (! c.firstRun) { - // already built; rebuilding - if (self.isAttached) { - wasAttachedParent = self.parentNode(); - wasAttachedBefore = self.lastNode().nextSibling; - self.detach(true); - } + if (isRebuild) { + var parentNode = oldFirstNode.parentNode; + var beforeNode = oldLastNode.nextSibling; + DomUtils.extractRange(oldFirstNode, oldLastNode); + parentNode.insertBefore(buildResult.fragment, + beforeNode); + } else { + self._detachedContent = buildResult.fragment; } - self._detachedContent = buildResult.fragment; self._start = buildResult.start; self._end = buildResult.end; - if (wasAttachedParent) { - self.attach(wasAttachedParent, wasAttachedBefore, - true); - } + + self.stage = Component.BUILT; if (c.firstRun) { self.built(); } else { self.rebuilt(); } }); - self.stage = Component.BUILT; }, destroy: function () { - // Leaves the DOM in place + // Leaves the DOM and component hierarchy in place if (this.stage === Component.DESTROYED) return; @@ -108,63 +120,105 @@ _.extend(Component.prototype, { for (var k in children) if (children.hasOwnProperty(k)) children[k].destroy(); - - if (this.parent) - delete this.parent.children[this.key]; - - this.children = {}; }, - attach: function (parent, before, _silent) { - if (this.stage === Component.ADDED) - this.build(); + attach: function (parentNode, beforeNode) { + var self = this; + if (self.stage === Component.ADDED) // not built + self.build(); - this._requireStage(Component.BUILT); - if (this.isAttached) + var parent = self.parent; + + self._requireStage(Component.BUILT); + if (self.isAttached) throw new Error("Component already attached"); - if ((! parent) || ! parent.nodeType) + if ((! parentNode) || ! parentNode.nodeType) throw new Error("first argument of attach must be a Node"); - if (before && ! before.nodeType) + if (beforeNode && ! beforeNode.nodeType) throw new Error("second argument of attach must be a Node" + " if given"); - var frag = this._detachedContent; + var frag = self._detachedContent; - if (DomUtils.wrapFragmentForContainer(frag, parent)) { - this._start = frag.firstChild; - this._end = frag.lastChild; + if (DomUtils.wrapFragmentForContainer(frag, parentNode)) + self.setBounds(frag.firstChild, frag.lastChild); + + parentNode.insertBefore(frag, beforeNode); + self._detachedContent = null; + + self.isAttached = true; + + if (parent && parent.stage === Component.BUILT) { + if (parent._start === EMPTY) { + parent.setBounds(self); + } else { + if (parent.firstNode() === self.lastNode().nextSibling) + parent.setStart(self); + if (parent.lastNode() === self.firstNode().previousSibling) + parent.setEnd(self); + } } - parent.insertBefore(frag, before); - this._detachedContent = null; - this.isAttached = true; - - if (! _silent) - this.attached(); + self.attached(); }, - detach: function (_silent) { - this._requireStage(Component.BUILT); - if (! this.isAttached) + detach: function (_duringSwap) { + var self = this; + var parent = self.parent; + + if (parent) + parent._requireStage(Component.BUILT); + self._requireStage(Component.BUILT); + if (! self.isAttached) throw new Error("Component not attached"); - this._detachedContent = document.createDocumentFragment(); + if (parent) { + if (parent._start === comp) { + if (parent._end === comp) { + if (_duringSwap) + parent._start = parent._end = EMPTY; + else + throw new Error("Can't detach entire contents of " + + "Component; use swapInChild instead"); + } else { + var newFirstNode = comp.lastNode().nextSibling; + var foundComp = null; + for (var k in parent.children) { + if (parent.children.hasOwnProperty(k) && + parent.children[k].firstNode() === newFirstNode) { + foundComp = parent.children[k]; + break; + } + } + parent.setStart(foundComp || newFirstNode); + } + } else if (parent._end === comp) { + var newLastNode = comp.firstNode().previousSibling; + var foundComp = null; + for (var k in parent.children) { + if (parent.children.hasOwnProperty(k) && + parent.children[k].lastNode() === newLastNode) { + foundComp = parent.children[k]; + break; + } + } + parent.setEnd(foundComp || newLastNode); + } + } - var start = this.firstNode(); - var end = this.lastNode(); - var frag = this._detachedContent; - // extract start..end into frag - var parent = start.parentNode; - var before = start.previousSibling; - var after = end.nextSibling; - var n; - while ((n = (before ? before.nextSibling : parent.firstChild)) && - (n !== after)) - frag.appendChild(n); + self._detachedContent = document.createDocumentFragment(); - this.isAttached = false; + DomUtils.extractRange(self.firstNode(), self.lastNode(), + self._detachedContent); - if (! _silent) - this.detached(); + self.isAttached = false; + + self.detached(); + }, + swapInChild: function (toAttach, toDetach) { + var parentNode = toDetach.parentNode(); + var beforeNode = toDetach.lastNode().nextSibling; + toDetach.detach(true); + toAttach.attach(parentNode, beforeNode); } }); @@ -259,7 +313,8 @@ _.extend(Component.prototype, { hasChild: function (key) { return this.children.hasOwnProperty(key); }, - addChild: function (key, childComponent) { + addChild: function (key, childComponent, attachParentNode, + attachBeforeNode) { if (key instanceof Component) { // omitted key arg childComponent = key; @@ -273,10 +328,10 @@ _.extend(Component.prototype, { if (! (childComponent instanceof Component)) throw new Error("not a Component: " + childComponent); - // XXX later: also work if we are BUILT, and build the - // child... maybe attach it too based on extra arguments - // to addChild like parentNode and beforeNode - this._requireStage(Component.ADDED); + if (this.stage === Component.DESTROYED) + throw new Error("parent Component already destroyed"); + if (this.stage === Component.UNADDED) + throw new Error("parent Component is unadded"); childComponent._requireStage(Component.UNADDED); if (this.hasChild(key)) @@ -285,32 +340,36 @@ _.extend(Component.prototype, { this.children[key] = childComponent; childComponent._added(key, this); + + if (attachParentNode) { + if (this.stage !== Component.BUILT) + throw new Error("Attaching new child requires built " + + "parent Component"); + childComponent.attach(attachParentNode, attachBeforeNode); + } }, removeChild: function (key) { + // note: must work if child is destroyed + key = String(key); - // XXX later: also work if we are BUILT, and detach - // the child first if so. - this._requireStage(Component.ADDED); + if (this.stage === Component.DESTROYED) + throw new Error("parent Component already destroyed"); + if (this.stage === Component.UNADDED) + throw new Error("parent Component is unadded"); if (! this.hasChild(key)) throw new Error("No such child component: " + key); var childComponent = this.children[key]; + if (childComponent.stage === Component.BUILT && + childComponent.isAttached) + childComponent.detach(); - if (childComponent.isDestroyed) { - // shouldn't be possible, because destroying a component - // deletes it from the parent's children dictionary, - // but just in case... - delete this.children[key]; - } else { + delete this.children[key]; + childComponent.parent = null; - // XXX - //if (childComponent.isAttached) - //childComponent.detach(); - - childComponent.destroy(); - } + childComponent.destroy(); } }); @@ -661,7 +720,6 @@ Component.extend = function (options) { // each old function! var oldFunction = v; if ({init:1, - render:1, destroyed:1, updated:1, attached:1, diff --git a/packages/ui/renderbuffer.js b/packages/ui/renderbuffer.js index 5ac84d5b64..2acb8bc715 100644 --- a/packages/ui/renderbuffer.js +++ b/packages/ui/renderbuffer.js @@ -1,5 +1,11 @@ - +// TODO +// +// Make the function argument to component +// run reactively. +// +// Then make openTag attributes +// reactive too. RenderBuffer = function (component) { this._component = component; @@ -97,20 +103,24 @@ _.extend(RenderBuffer.prototype, { if (typeof stringOrFunction === 'function') { var func = stringOrFunction; this.component(function () { - return new TextComponent({text: func()}); + return TextComponent.create({text: func()}); }); } else { + if (typeof stringOrFunction !== 'string') + throw new Error("string required"); var text = stringOrFunction; - this._htmlBuf.push(this.encodeEntities(text)); + this._htmlBuf.push(this._encodeEntities(text)); } }, rawHtml: function (stringOrFunction) { if (typeof stringOrFunction === 'function') { var func = stringOrFunction; this.component(function () { - return new RawHtmlComponent({html: func()}); + return RawHtmlComponent.create({html: func()}); }); } else { + if (typeof stringOrFunction !== 'string') + throw new Error("string required"); var html = stringOrFunction; this._htmlBuf.push(html); } @@ -123,8 +133,6 @@ _.extend(RenderBuffer.prototype, { var childKey = (options && options.childKey || null); this._component.addChild(childKey, comp); - // build it detached - comp._build(); var commentString = this.builderId + '_' + (this._nextNum++); @@ -132,13 +140,47 @@ _.extend(RenderBuffer.prototype, { this._childrenToAttach[commentString] = comp; }, - toFragment: function () { + build: function () { var html = this._htmlBuf.join(''); var frag = DomUtils.htmlToFragment(html); + if (! frag.firstChild) + frag.appendChild(document.createComment("empty")); - + var components = this._childrenToAttach; + var start = frag.firstChild; + var end = frag.lastChild; - return frag; + var replaceCommentsWithComponents = function (parent) { + var n = parent.firstChild; + while (n) { + var next = n.nextSibling; + if (n.nodeType === 8) { // COMMENT + var comp = components[n.nodeValue]; + if (comp) { + if (parent === frag) { + if (n === frag.firstChild) + start = comp; + if (n === frag.lastChild) + end = comp; + } + comp.attach(parent, n); + parent.removeChild(n); + } + } else if (n.nodeType === 1) { // ELEMENT + // recurse + replaceCommentsWithComponents(n); + } + n = next; + } + }; + + replaceCommentsWithComponents(frag); + + return { + fragment: frag, + start: start, + end: end + }; } }); From e35df7d2a92a8e42ce4abd9c042a0d8ff0f09b2e Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Tue, 21 May 2013 14:13:59 -0700 Subject: [PATCH 028/938] reactive child components (untested) --- packages/ui/component.js | 149 ++++++++++++++++++++++++++++-------- packages/ui/renderbuffer.js | 17 ++-- 2 files changed, 127 insertions(+), 39 deletions(-) diff --git a/packages/ui/component.js b/packages/ui/component.js index d7d99a76f8..cbdfefad80 100644 --- a/packages/ui/component.js +++ b/packages/ui/component.js @@ -24,7 +24,9 @@ Component = function (args) { this._end = null; // last Component or Node this.isAttached = false; this._detachedContent = null; // DocumentFragment - this._buildComputation = null; + + this._buildUpdater = null; + this._childUpdaters = {}; this.constructed(); }; @@ -56,20 +58,22 @@ _.extend(Component.prototype, { build: function () { var self = this; self._requireStage(Component.ADDED); - self._buildComputation = + self._buildUpdater = Deps.autorun(function (c) { var isRebuild = (self.stage === Component.BUILT); var oldFirstNode, oldLastNode; if (isRebuild) { oldFirstNode = self.firstNode(); oldLastNode = self.lastNode(); - for (var k in self.children) { - if (self.children.hasOwnProperty(k)) { - var child = self.children[k]; - child.destroy(); - self.removeChild(child.key); + Deps.nonreactive(function () { + for (var k in self.children) { + if (self.children.hasOwnProperty(k)) { + var child = self.children[k]; + child.destroy(); + self.removeChild(child.key); + } } - } + }); self.stage = Component.ADDED; } var buf = new RenderBuffer(self); @@ -88,11 +92,13 @@ _.extend(Component.prototype, { self._end = buildResult.end; self.stage = Component.BUILT; - if (c.firstRun) { - self.built(); - } else { - self.rebuilt(); - } + Deps.nonreactive(function () { + if (c.firstRun) { + self.built(); + } else { + self.rebuilt(); + } + }); }); }, destroy: function () { @@ -107,8 +113,15 @@ _.extend(Component.prototype, { if (oldStage === Component.UNADDED) return; - if (this._buildComputation) - this._buildComputation.stop(); + if (this._buildUpdater) + this._buildUpdater.stop(); + + for (var k in this._childUpdaters) { + if (this._childUpdaters.hasOwnProperty(k)) { + this._childUpdaters[k].stop(); + delete this._childUpdaters[k]; + } + } // maybe GC sooner this._start = null; @@ -161,7 +174,7 @@ _.extend(Component.prototype, { self.attached(); }, - detach: function (_duringSwap) { + detach: function (_allowTransientEmpty) { var self = this; var parent = self.parent; @@ -174,7 +187,7 @@ _.extend(Component.prototype, { if (parent) { if (parent._start === comp) { if (parent._end === comp) { - if (_duringSwap) + if (_allowTransientEmpty) parent._start = parent._end = EMPTY; else throw new Error("Can't detach entire contents of " + @@ -313,42 +326,79 @@ _.extend(Component.prototype, { hasChild: function (key) { return this.children.hasOwnProperty(key); }, - addChild: function (key, childComponent, attachParentNode, + addChild: function (key, childComponentOrFunc, + attachParentNode, attachBeforeNode) { - if (key instanceof Component) { + if ((key instanceof Component) || + ((typeof key) === 'function')) { // omitted key arg - childComponent = key; + childComponentOrFunc = key; key = null; } + // omitted key, generate unique child key if (key === null || typeof key === 'undefined') key = "__child#" + (this._uniqueIdCounter++) + "__"; key = String(key); + var self = this; + if (self.stage === Component.DESTROYED) + throw new Error("parent Component already destroyed"); + if (self.stage === Component.UNADDED) + throw new Error("parent Component is unadded"); + + if (self.hasChild(key)) + throw new Error("Already have a child with key: " + key); + + var childComponent; + if (typeof childComponentOrFunc === 'function') { + var func = childComponentOrFunc; + this._childUpdaters[key] = + Deps.autorun(function (c) { + if (c.firstRun) { + childComponent = func(); + return; + } + var oldChild = self.children[key]; + if ((! (oldChild instanceof Component)) || + oldChild.stage === Component.DESTROYED) { + // child shouldn't be missing, but may be + // destroyed + c.stop(); + return; + } + var newChild = func(); + if (! (newChild instanceof Component)) + throw new Error("not a Component: " + newChild); + if (oldChild.constructor === newChild.constructor) { + oldChild.update(newChild._args); + } else { + self.replaceChild(key, newChild); + } + }); + } else { + childComponent = childComponentOrFunc; + } + if (! (childComponent instanceof Component)) throw new Error("not a Component: " + childComponent); - if (this.stage === Component.DESTROYED) - throw new Error("parent Component already destroyed"); - if (this.stage === Component.UNADDED) - throw new Error("parent Component is unadded"); childComponent._requireStage(Component.UNADDED); - if (this.hasChild(key)) - throw new Error("Already have a child with key: " + key); + self.children[key] = childComponent; - this.children[key] = childComponent; - - childComponent._added(key, this); + childComponent._added(key, self); if (attachParentNode) { - if (this.stage !== Component.BUILT) + if (self.stage !== Component.BUILT) throw new Error("Attaching new child requires built " + "parent Component"); childComponent.attach(attachParentNode, attachBeforeNode); } + + return childComponent; }, - removeChild: function (key) { + removeChild: function (key, _allowTransientEmpty) { // note: must work if child is destroyed key = String(key); @@ -364,12 +414,47 @@ _.extend(Component.prototype, { var childComponent = this.children[key]; if (childComponent.stage === Component.BUILT && childComponent.isAttached) - childComponent.detach(); + childComponent.detach(_allowTransientEmpty); delete this.children[key]; + + if (this._childUpdaters[key]) { + this._childUpdaters[key].stop(); + delete this._childUpdaters[key]; + } + childComponent.parent = null; childComponent.destroy(); + }, + replaceChild: function (key, newChild) { + if (this.stage === Component.DESTROYED) + throw new Error("parent Component already destroyed"); + if (this.stage === Component.UNADDED) + throw new Error("parent Component is unadded"); + + if (! this.hasChild(key)) + throw new Error("No such child component: " + key); + + if (! (newChild instanceof Component)) + throw new Error("Component required"); + + var oldChild = this.children[key]; + + if (oldChild.constructor === newChild.constructor) { + oldChild.update(newChild._args); + } else if (this.stage !== Component.BUILT || + oldChild !== Component.BUILT || + ! oldChild.isAttached) { + this.removeChild(key); + this.addChild(key, newChild); + } else { + // swap attached child + var parentNode = oldChild.parentNode(); + var beforeNode = oldChild.lastNode().nextSibling; + this.removeChild(key, true); + this.addChild(key, newChild, parentNode, beforeNode); + } } }); diff --git a/packages/ui/renderbuffer.js b/packages/ui/renderbuffer.js index 2acb8bc715..9ee6730e5c 100644 --- a/packages/ui/renderbuffer.js +++ b/packages/ui/renderbuffer.js @@ -127,18 +127,21 @@ _.extend(RenderBuffer.prototype, { }, component: function (componentOrFunction, options) { var self = this; - var comp = (typeof componentOrFunction === 'function' ? - componentOrFunction() : componentOrFunction); + + if (! ((componentOrFunction instanceof Component) || + (typeof componentOrFunction === 'function'))) + throw new Error("Component or function required"); var childKey = (options && options.childKey || null); - this._component.addChild(childKey, comp); + var childComp = self._component.addChild( + childKey, componentOrFunction); - var commentString = this.builderId + '_' + - (this._nextNum++); - this._htmlBuf.push(''); + var commentString = self.builderId + '_' + + (self._nextNum++); + self._htmlBuf.push(''); - this._childrenToAttach[commentString] = comp; + self._childrenToAttach[commentString] = childComp; }, build: function () { var html = this._htmlBuf.join(''); From 497ce9f71f99c1e9026b859fde38be38b4710f96 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Tue, 21 May 2013 14:22:03 -0700 Subject: [PATCH 029/938] fixes --- packages/ui/component.js | 6 +++--- packages/ui/package.js | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/ui/component.js b/packages/ui/component.js index cbdfefad80..2abc4e9b17 100644 --- a/packages/ui/component.js +++ b/packages/ui/component.js @@ -306,15 +306,15 @@ _.extend(Component.prototype, { for (var k in args) { if (args.hasOwnProperty(k) && argDeps.hasOwnProperty(k) && - ! EJSON.equal(args[k], oldArgs[k])) { - argDeps[k].invalidate(); + ! EJSON.equals(args[k], oldArgs[k])) { + argDeps[k].changed(); delete oldArgs[k]; } } for (var k in oldArgs) { if (oldArgs.hasOwnProperty(k) && argDeps.hasOwnProperty(k)) { - argDeps[k].invalidate(); + argDeps[k].changed(); } } diff --git a/packages/ui/package.js b/packages/ui/package.js index a9580e513f..39bc826151 100644 --- a/packages/ui/package.js +++ b/packages/ui/package.js @@ -4,6 +4,7 @@ Package.describe({ Package.on_use(function (api) { api.use('underscore', 'client'); + api.use('ejson', 'client'); api.add_files(['chunk.js', 'component.js', 'renderbuffer.js'], 'client'); From a0468350eedaba3bfddfb8984237a1d6c49252bd Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Tue, 21 May 2013 14:27:15 -0700 Subject: [PATCH 030/938] remove old comments --- packages/ui/component.js | 292 ------------------------------------ packages/ui/renderbuffer.js | 45 +----- 2 files changed, 1 insertion(+), 336 deletions(-) diff --git a/packages/ui/component.js b/packages/ui/component.js index 2abc4e9b17..a935718585 100644 --- a/packages/ui/component.js +++ b/packages/ui/component.js @@ -470,292 +470,6 @@ _.extend(Component.prototype, { rebuilt: function () {} }); -////////////////////////////////////////////////// -/* - -Component = function (args) { - this.parent = null; - this.nameInParent = ''; - this.children = {}; - this.isInited = false; - this.isBuilt = false; - this.isAttached = false; - this.isDestroyed = false; - - this.dom = null; // Chunk, if built - this._fragment = null; // DocumentFragment, if built; empty when attached - this._uniqueIdCounter = 1; - - this._args = args; - this._argDeps = {}; -}; - -_.extend(Component.prototype, { - _requireAlive: function () { - if (this.isDestroyed) - throw new Error("Component was destroyed"); - }, - _forceInit: function () { - this._requireAlive(); - if (! this.isInited) { - this.init(); - this.isInited = true; - } - }, - _build: function () { - this._forceInit(); - if (this.isBuilt) - throw new Error("Component already built"); - - this._fragment = document.createDocumentFragment(); - - this.build(this._fragment); - - if (! this.dom) - throw new Error("build() must call setBounds()"); - this.isBuilt = true; - this.built(); - }, - attach: function (parent, before) { - this._forceInit(); - if (this.isAttached) - throw new Error("Component already attached"); - - if (! this.isBuilt) - this._build(); - - parent.insertBefore(this._fragment, before); - - this.isAttached = true; - - this.attached(); - - return this; - }, - detach: function () { - this._requireAlive(); - if (! this.isAttached) - throw new Error("Component not attached"); - - var start = this.dom.firstNode(); - var end = this.dom.lastNode(); - var frag = this._fragment; - // extract start..end into frag - var parent = start.parentNode; - var before = start.previousSibling; - var after = end.nextSibling; - var n; - while ((n = (before ? before.nextSibling : parent.firstChild)) && - (n !== after)) - frag.appendChild(n); - - this.isAttached = false; - - this.detached(); - - return this; - }, - destroy: function () { - if (! this.isDestroyed) { - this.isDestroyed = true; - - // maybe GC the DOM sooner - this.dom = null; - this._fragment = null; - - this.destroyed(); - - var children = this.children; - for (var k in children) - if (children.hasOwnProperty(k)) - children[k].destroy(); - - if (this.parent && ! this.parent.isDestroyed) - delete this.parent.children[this.nameInParent]; - - this.children = {}; - } - - return this; - }, - hasChild: function (name) { - return this.children.hasOwnProperty(name); - }, - addChild: function (name, childComponent) { - if (name instanceof Component) { - // omitted name arg - childComponent = name; - name = null; - } - // omitted name, generate unique child ID - if (name === null || typeof name === 'undefined') - name = "__child#" + (this._uniqueIdCounter++) + "__"; - name = String(name); - - if (! (childComponent instanceof Component)) - throw new Error("not a Component: " + childComponent); - - this._requireAlive(); - if (this.hasChild(name)) - throw new Error("Already have a child named: " + name); - - if (childComponent.isDestroyed) - throw new Error("Can't add a destroyed component"); - if (childComponent.isInited) - throw new Error("Can't add a previously added or built component"); - - this.children[name] = childComponent; - - childComponent._added(name, this); - }, - setChild: function (name, childClass, childArgs) { - name = String(name); - - this._requireAlive(); - if (this.hasChild(name)) { - var oldChild = this.children[name]; - if (oldChild.constructor === childClass) { - // old child present with same class - oldChild.update(childArgs); - } else { - var newChild = new childClass(childArgs); - if (oldChild.isAttached) { - var beforeNode = oldChild.lastNode().nextSibling; - var parentNode = oldChild.parentNode(); - this.removeChild(name); - this.addChild(name, newChild); - newChild.attach(parentNode, beforeNode); - } else { - this.addChild(newChild); - } - } - } else { - this.addChild(name, new childClass(childArgs)); - } - }, - _added: function (name, parent) { - name = String(name); - this.nameInParent = name; - this.parent = parent; - - this._forceInit(); - }, - removeChild: function (name) { - name = String(name); - this._requireAlive(); - if (! this.hasChild(name)) - throw new Error("No such child component: " + name); - - var childComponent = this.children[name]; - - if (childComponent.isDestroyed) { - // shouldn't be possible, because destroying a component - // deletes it from the parent's children dictionary, - // but just in case... - delete this.children[name]; - } else { - - if (childComponent.isAttached) - childComponent.detach(); - - childComponent.destroy(); - - } - }, - setBounds: function (start, end) { - end = end || start; - if (start instanceof Component) - start = start.dom; - if (end instanceof Component) - end = end.dom; - - if (! (start instanceof Chunk || (start && start.nodeType))) - throw new Error("setBounds: start must be a built Component or a Node"); - if (! (end instanceof Chunk || (end && end.nodeType))) - throw new Error("setBounds: end must be a built Component or a Node"); - - if (! this.dom) { - this.dom = new Chunk(start, end); - } else { - this.dom.set(start, end); - } - }, - setStart: function (start) { - if (start instanceof Component) - start = start.dom; - - if (! (start instanceof Chunk || (start && start.nodeType))) - throw new Error("setStart: start must be a built Component or a Node"); - if (! this.dom) - throw new Error("Can only call setStart after setBounds has been called"); - - this.dom.start = start; - }, - setEnd: function (end) { - if (end instanceof Component) - end = end.dom; - - if (! (end instanceof Chunk || (end && end.nodeType))) - throw new Error("setEnd: end must be a built Component or a Node"); - if (! this.dom) - throw new Error("Can only call setEnd after setBounds has been called"); - - this.dom.end = end; - }, - getArg: function (argName) { - var dep = (this._argDeps.hasOwnProperty(argName) ? - this._argDeps[argName] : - (this._argDeps[argName] = new Deps.Dependency)); - dep.depend(); - return this._args[argName]; - }, - update: function (args) { - var oldArgs = this._args; - this._args = args; - - var argDeps = this._argDeps; - - for (var k in args) { - if (args.hasOwnProperty(k) && - argDeps.hasOwnProperty(k) && - ! EJSON.equal(args[k], oldArgs[k])) { - argDeps[k].invalidate(); - delete oldArgs[k]; - } - } - for (var k in oldArgs) { - if (oldArgs.hasOwnProperty(k) && - argDeps.hasOwnProperty(k)) { - argDeps[k].invalidate(); - } - } - - this.updated(args, oldArgs); - }, - findOne: function (selector) { return this.dom.findOne(selector); }, - findAll: function (selector) { return this.dom.findAll(selector); }, - firstNode: function () { return this.dom.firstNode(); }, - lastNode: function () { return this.dom.lastNode(); }, - parentNode: function () { return this.dom.parentNode(); }, - // Above methods are NOT overridable. - // - // These are all overridable, with the behavior that all implementations - // are executed from super to sub. - init: function () {}, - build: function (frag) {}, - built: function () {}, - attached: function () {}, - detached: function () {}, - destroyed: function () {}, - updated: function (args, oldArgs) {}, - // This is overridable but should probably get normal override behavior; - // it has a return value and we only run one implementation. - toHtml: function () { - return ''; - } -}); -*/ -//////////////////// // Require ComponentClass.create(...) instead of // new CompomentClass(...) because a factory method gives @@ -854,9 +568,3 @@ RootComponent = Component.extend({ buf.component(bodyClass.create()); } }); - - -// need **rebuild**; "render" runs in a reactive context - -// What does RenderBuffer do to build a subcomponent? -// Assigning Chunks; build and rebuild \ No newline at end of file diff --git a/packages/ui/renderbuffer.js b/packages/ui/renderbuffer.js index 9ee6730e5c..004cf43585 100644 --- a/packages/ui/renderbuffer.js +++ b/packages/ui/renderbuffer.js @@ -1,11 +1,7 @@ // TODO // -// Make the function argument to component -// run reactively. -// -// Then make openTag attributes -// reactive too. +// Make openTag attributes reactive. RenderBuffer = function (component) { this._component = component; @@ -186,42 +182,3 @@ _.extend(RenderBuffer.prototype, { }; } }); - -// CHANGES TO COMPONENT NEEDED: -// -// - childKey, elementKey -- call things "key" -// - no attach/detach -- need to wire things up from HTML... - - -// openChunk, closeChunk -// -// Drop comemnts at start and finish. Comments may have -// to be fished out due to missing close tags (some fun -// logic there). Eventually, can produce cleaner HTML -// using attributes in some cases instead of comments. -// Chunks bound components, and also text/raw inclusions. -// Consecutive openChunks or closeChunks create Chunks -// defined in terms of each other. -// -// When building is finished, it produces HTML and some -// other data. We don't do materialization, because in -// the server-side rendering case, the browser does that! -// -// After materialization, we want to somehow: -// - recalculate all the helpers with deps tracking -// - assign elements to components -// - set bounds of components -// -// ... based on walking the DOM. -// -// The RenderBuffer probably has a tree of components -// with children and elements, if only to track comment -// references. - -// Component inclusions are also calculated, so their expressions -// must be sent down. Components must also be serialized on the -// wire. Argument change leads to update, of course. - -// Are Component class names in templates resolved? Maybe. -// Assuming so, the test for whether a class has changed is -// comparing the resolved constructor names. \ No newline at end of file From d370dbaf76a0db836681f9a006c207af654b970f Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 27 May 2013 17:55:29 -0700 Subject: [PATCH 031/938] registerElement --- packages/ui/component.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/ui/component.js b/packages/ui/component.js index a935718585..7805c7762b 100644 --- a/packages/ui/component.js +++ b/packages/ui/component.js @@ -27,6 +27,7 @@ Component = function (args) { this._buildUpdater = null; this._childUpdaters = {}; + this.elements = {}; this.constructed(); }; @@ -74,6 +75,7 @@ _.extend(Component.prototype, { } } }); + self.elements = {}; self.stage = Component.ADDED; } var buf = new RenderBuffer(self); @@ -455,6 +457,9 @@ _.extend(Component.prototype, { this.removeChild(key, true); this.addChild(key, newChild, parentNode, beforeNode); } + }, + registerElement: function (elementKey, element) { + this.elements[elementKey] = element; } }); From 762841cf0e4739f32da1c0525f3483eec5d38f0f Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 27 May 2013 19:06:39 -0700 Subject: [PATCH 032/938] reactive attributes in renderbuffer --- examples/unfinished/shark/client/shark.js | 5 + packages/ui/component.js | 2 +- packages/ui/renderbuffer.js | 109 +++++++++++++++------- 3 files changed, 82 insertions(+), 34 deletions(-) diff --git a/examples/unfinished/shark/client/shark.js b/examples/unfinished/shark/client/shark.js index e31aec2de3..340f6f0b9a 100644 --- a/examples/unfinished/shark/client/shark.js +++ b/examples/unfinished/shark/client/shark.js @@ -27,9 +27,14 @@ Meteor.startup(function () { RC = RootComponent.create({ bodyClass: Component.extend({ render: function (buf) { + buf.openTag('span', {style: function () { + return 'background-color:' + + Session.get('bgcolor'); + }}); buf.text(function () { return Session.get('foo') || ''; }); + buf.closeTag('span'); } }) }); diff --git a/packages/ui/component.js b/packages/ui/component.js index 7805c7762b..6160c6e9d7 100644 --- a/packages/ui/component.js +++ b/packages/ui/component.js @@ -570,6 +570,6 @@ RootComponent = Component.extend({ render: function (buf) { var bodyClass = this.getArg('bodyClass'); if (bodyClass) - buf.component(bodyClass.create()); + buf.component(bodyClass.create(), {key: 'body'}); } }); diff --git a/packages/ui/renderbuffer.js b/packages/ui/renderbuffer.js index 004cf43585..cda0e7ba44 100644 --- a/packages/ui/renderbuffer.js +++ b/packages/ui/renderbuffer.js @@ -1,14 +1,11 @@ -// TODO -// -// Make openTag attributes reactive. - RenderBuffer = function (component) { this._component = component; this._htmlBuf = []; this._builderId = Random.id(); this._nextNum = 1; + this._elementNextNums = {}; this._childrenToAttach = []; // comment string -> component }; @@ -39,29 +36,23 @@ var encodeEntities = function (text, isQuoted) { ESCAPED_CHARS_UNQUOTED_REGEX, escapeOne); }; +var updateDOMAttribute = function (component, elemKey, attrName, + newValue, oldValue) { + // XXX Do smart stuff here, like treat "class" attribute + // specially, and set JS properties instead of HTML attributes + // when appropriate. Don't get too crazy, though. Fancy + // manipulation of DOM elements can be done programmatically + // instead. + // + // Allow Components to hook into (i.e. replace) this update + // logic for attributes of their choice? + var elem = component.elements[elemKey]; + elem.setAttribute(attrName, newValue); +}; + + _.extend(RenderBuffer.prototype, { _encodeEntities: encodeEntities, - /*computeAttributeValue: function (expression) { - var self = this; - - if ((typeof expression) === 'string') - return expression; - - var initialValue; - Deps.autorun(function (c) { - if (c.firstRun) { - c.expression = expression; - c.component = self.currentComponent; - } else { - return; // XXX - } - - initialValue = - _.map(c.expression, evaluateStringOrHelper).join(''); - }); - - return initialValue; - },*/ openTag: function (tagName, attrs, options) { var self = this; @@ -72,6 +63,11 @@ _.extend(RenderBuffer.prototype, { attrs = attrs || {}; options = options || {}; + var isElementReactive = false; + // any reactive updaters here will close over this variable, + // which we will set to something non-null afterwards. + var elementKey = (options.key || null); + var buf = this._htmlBuf; buf.push('<', tagName); _.each(attrs, function (attrValue, attrName) { @@ -80,11 +76,50 @@ _.extend(RenderBuffer.prototype, { throw new Error("Illegal HTML attribute name: " + attrName); buf.push(' ', attrName, '="'); - var initialValue = (typeof attrValue === 'function' ? - attrValue() : attrValue); + var initialValue; + if (typeof attrValue === 'function') { + var func = attrValue; + // we assume we've been called from some Component build, + // so this autorun will be stopped when the Component + // is rebuilt or destroyed. + isElementReactive = true; + Deps.autorun(function (c) { + if (c.firstRun) { + var newValue = attrValue(); + initialValue = newValue; + c.oldValue = newValue; + } else { + var newValue = attrValue(); + var comp = self._component; + if (comp && comp.stage === Component.BUILT) { + var elem = elementKey && comp.elements[elementKey]; + if (elem) { + updateDOMAttribute(comp, elementKey, attrName, + newValue, c.oldValue); + } + } + c.oldValue = newValue; + } + }); + } else { + initialValue = attrValue; + } buf.push(self._encodeEntities(initialValue, true)); buf.push('"'); }); + + if (isElementReactive) { + if (! elementKey) { + if (! this._elementNextNums[tagName]) + this._elementNextNums[tagName] = 1; + elementKey = tagName + + (this._elementNextNums[tagName]++); + } + + buf.push(' data-meteorui-id="' + + self._encodeEntities(elementKey, true) + '"'); + } + if (options.selfClose) buf.push('/'); buf.push('>'); @@ -128,7 +163,7 @@ _.extend(RenderBuffer.prototype, { (typeof componentOrFunction === 'function'))) throw new Error("Component or function required"); - var childKey = (options && options.childKey || null); + var childKey = (options && options.key || null); var childComp = self._component.addChild( childKey, componentOrFunction); @@ -140,16 +175,20 @@ _.extend(RenderBuffer.prototype, { self._childrenToAttach[commentString] = childComp; }, build: function () { - var html = this._htmlBuf.join(''); + var self = this; + + var html = self._htmlBuf.join(''); var frag = DomUtils.htmlToFragment(html); if (! frag.firstChild) frag.appendChild(document.createComment("empty")); - var components = this._childrenToAttach; + var components = self._childrenToAttach; var start = frag.firstChild; var end = frag.lastChild; - var replaceCommentsWithComponents = function (parent) { + // wireUpDOM = replace comments with Components and register + // keyed elements + var wireUpDOM = function (parent) { var n = parent.firstChild; while (n) { var next = n.nextSibling; @@ -166,14 +205,18 @@ _.extend(RenderBuffer.prototype, { parent.removeChild(n); } } else if (n.nodeType === 1) { // ELEMENT + var elemKey = n.getAttribute('data-meteorui-id'); + if (elemKey) + self._component.registerElement(elemKey, n); + // recurse - replaceCommentsWithComponents(n); + wireUpDOM(n); } n = next; } }; - replaceCommentsWithComponents(frag); + wireUpDOM(frag); return { fragment: frag, From 19568674bd7738dafd07758877476d2fa35d42e0 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 27 May 2013 20:17:43 -0700 Subject: [PATCH 033/938] fix empty Component using Component.create() --- packages/ui/component.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui/component.js b/packages/ui/component.js index 6160c6e9d7..872aa296b0 100644 --- a/packages/ui/component.js +++ b/packages/ui/component.js @@ -485,7 +485,8 @@ _.extend(Component.prototype, { Component.create = function (/*args*/) { constructorsLocked = false; var comp = new this; - Component.apply(comp, arguments); + if (this !== Component) + Component.apply(comp, arguments); return comp; }; From 31bc8c55a4f6a19e04fa990f21d0ec31782de287 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 27 May 2013 20:18:00 -0700 Subject: [PATCH 034/938] uncomment "Each" component (needs rewrite) --- examples/unfinished/shark/client/shark.js | 26 ++++++++++++----------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/examples/unfinished/shark/client/shark.js b/examples/unfinished/shark/client/shark.js index 340f6f0b9a..1591644c90 100644 --- a/examples/unfinished/shark/client/shark.js +++ b/examples/unfinished/shark/client/shark.js @@ -1,6 +1,5 @@ - IfComponent = Component.extend({ arguments: { positional: ['condition'], @@ -133,7 +132,7 @@ UL = DebugComponent.extend({ ""; } }); - +*/ // Function equal to LocalCollection._idStringify, or the identity // function if we don't have LiveData. Converts item keys (i.e. DDP @@ -161,7 +160,7 @@ var applyChanges = function (doc, changeFields) { }); }; -EmptyComponent = Component.extend({ +/*EmptyComponent = Component.extend({ build: function (frag) { var comment = document.createComment('empty'); frag.appendChild(comment); @@ -170,14 +169,19 @@ EmptyComponent = Component.extend({ toHtml: function () { return ''; } -}); +});*/ Each = DebugComponent.extend({ - items: new OrderedDict(idStringify), - init: function () { + // XXX what is init() good for if render lets you reactively + // depend on args, but init doesn't? (you can access them + // but your code only ever runs once) + + render: function () { var self = this; var cursor = self.getArg('list'); // XXX support arrays too + + self.items = new OrderedDict(idStringify); var items = self.items; // Templates should have access to data and methods added by the @@ -194,6 +198,8 @@ Each = DebugComponent.extend({ return doc; }; + // because we're in render(), rebuild or destroy will + // stop this handle. self.cursorHandle = cursor.observeChanges({ addedBefore: function (id, item, beforeId) { var doc = EJSON.clone(item); @@ -236,10 +242,6 @@ Each = DebugComponent.extend({ } }, - updated: function (args, oldArgs) { - // XXXX whhaaaaaaa - }, - _itemChildId: function (id) { return 'item:' + idStringify(id); }, @@ -337,7 +339,7 @@ Each = DebugComponent.extend({ }); -MyLI = DebugComponent.extend({ +/*MyLI = DebugComponent.extend({ init: function () { this.setChild('1', LI, {text: this.getArg('data').text || ''}); }, @@ -374,4 +376,4 @@ Meteor.startup(function () { LIST.attach(ul); }); - */ \ No newline at end of file +*/ \ No newline at end of file From e873e636fef3404d37530543a85ebd620becf1ba Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Tue, 28 May 2013 10:29:15 -0700 Subject: [PATCH 035/938] "Each" wip --- examples/unfinished/shark/client/shark.js | 64 +++++++++++++---------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/examples/unfinished/shark/client/shark.js b/examples/unfinished/shark/client/shark.js index 1591644c90..4ecedaf79a 100644 --- a/examples/unfinished/shark/client/shark.js +++ b/examples/unfinished/shark/client/shark.js @@ -177,10 +177,15 @@ Each = DebugComponent.extend({ // depend on args, but init doesn't? (you can access them // but your code only ever runs once) - render: function () { + render: function (buf) { var self = this; - var cursor = self.getArg('list'); // XXX support arrays too + // XXX support arrays too. + // For now, we assume `list` is a database cursor. + var cursor = self.getArg('list'); + // OrderedDict from id string or object (which is + // stringified internally by the dict) to untransformed + // document. self.items = new OrderedDict(idStringify); var items = self.items; @@ -189,9 +194,9 @@ Each = DebugComponent.extend({ // it here. // // NOTE: this is a little bit of an abstraction violation. Ideally, - // the only thing Spark should know about Minimongo is the contract of - // observeChanges. In theory, anything that implements observeChanges - // could be passed to Spark.list. But meh. + // the only thing we should know about Minimongo is the contract of + // observeChanges. In theory, we could allow anything that implements + // observeChanges to be passed to us. var transformedDoc = function (doc) { if (cursor.getTransform && cursor.getTransform()) return cursor.getTransform()(EJSON.clone(doc)); @@ -205,46 +210,51 @@ Each = DebugComponent.extend({ var doc = EJSON.clone(item); doc._id = id; items.putBefore(id, doc, beforeId); - var tdoc = transformedDoc(doc); + //var tdoc = transformedDoc(doc); - self.itemAddedBefore(id, tdoc, beforeId); + //self.itemAddedBefore(id, tdoc, beforeId); }, removed: function (id) { - items.remove(id); + //items.remove(id); - self.itemRemoved(id); + //self.itemRemoved(id); }, movedBefore: function (id, beforeId) { - items.moveBefore(id, beforeId); + //items.moveBefore(id, beforeId); - self.itemMovedBefore(id, beforeId); + //self.itemMovedBefore(id, beforeId); }, changed: function (id, fields) { - var doc = items.get(id); - if (! doc) - throw new Error("Unknown id for changed: " + idStringify(id)); - applyChanges(doc, fields); - var tdoc = transformedDoc(doc); + //var doc = items.get(id); + //if (! doc) + //throw new Error("Unknown id for changed: " + idStringify(id)); + //applyChanges(doc, fields); + //var tdoc = transformedDoc(doc); - self.itemChanged(id, tdoc); + //self.itemChanged(id, tdoc); } }); - if (self.items.empty()) - self.initiallyEmpty(); - }, + if (items.empty) { + buf.component(function () { + return (self.getArg('elseClass') || Component).create( + { data: this.getArg('data') }); + }, { key: 'else' }); + } else { + items.forEach(function (doc, id) { + var tdoc = transformedDoc(doc); - destroyed: function () { - var self = this; - if (self.cursorHandle) { - self.cursorHandle.stop(); - self.cursorHandle = null; + buf.component(function () { + return self.getArg('bodyClass').create({ data: tdoc }); + }, { key: this._itemChildId(id) }); + }); } }, _itemChildId: function (id) { return 'item:' + idStringify(id); - }, + } + /* addItemChild: function (id, comp) { this.addChild(this._itemChildId(id), comp); }, @@ -336,7 +346,7 @@ Each = DebugComponent.extend({ } } // XXX toHtml - +*/ }); /*MyLI = DebugComponent.extend({ From 9494860d824417727f843f5041cc46bd6cbd2b83 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Tue, 28 May 2013 17:16:04 -0700 Subject: [PATCH 036/938] fix so it runs --- examples/unfinished/shark/client/shark.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/unfinished/shark/client/shark.js b/examples/unfinished/shark/client/shark.js index 4ecedaf79a..37f3ef1700 100644 --- a/examples/unfinished/shark/client/shark.js +++ b/examples/unfinished/shark/client/shark.js @@ -171,7 +171,7 @@ var applyChanges = function (doc, changeFields) { } });*/ -Each = DebugComponent.extend({ +Each = Component.extend({ // XXX what is init() good for if render lets you reactively // depend on args, but init doesn't? (you can access them From c27ecae3aa587ca31b88883ef1d42d857f7f7ddb Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 10 Jun 2013 13:30:42 -0700 Subject: [PATCH 037/938] WIP XXX --- examples/unfinished/shark/client/shark.js | 60 ++++++++++++++++++----- packages/ui/component.js | 12 +++-- 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/examples/unfinished/shark/client/shark.js b/examples/unfinished/shark/client/shark.js index 37f3ef1700..68d60266ec 100644 --- a/examples/unfinished/shark/client/shark.js +++ b/examples/unfinished/shark/client/shark.js @@ -13,6 +13,11 @@ IfComponent = Component.extend({ } }); +Items = new Meteor.Collection(null); +Items.insert({ text: 'Foo' }); +Items.insert({ text: 'Bar' }); +Items.insert({ text: 'Baz' }); + Meteor.startup(function () { /* RC = RootComponent.create({ @@ -23,7 +28,7 @@ Meteor.startup(function () { }) }); */ - RC = RootComponent.create({ + /*RC = RootComponent.create({ bodyClass: Component.extend({ render: function (buf) { buf.openTag('span', {style: function () { @@ -36,7 +41,25 @@ Meteor.startup(function () { buf.closeTag('span'); } }) + });*/ + + RC = RootComponent.create({ + bodyClass: Component.extend({ + render: function (buf) { + buf.component(Each.create({ + bodyClass: Component.extend({ + render: function (buf) { + buf.openTag('div'); + buf.text(this.getArg('data').text || ''); + buf.closeTag('div'); + } + }), + list: Items.find() + }), { key: 'body' }); + } + }) }); + RC.attach(document.body); }); @@ -210,9 +233,12 @@ Each = Component.extend({ var doc = EJSON.clone(item); doc._id = id; items.putBefore(id, doc, beforeId); - //var tdoc = transformedDoc(doc); - //self.itemAddedBefore(id, tdoc, beforeId); + if (self.stage === Component.BUILT) { + var tdoc = transformedDoc(doc); + + self.itemAddedBefore(id, tdoc, beforeId); + } }, removed: function (id) { //items.remove(id); @@ -235,10 +261,10 @@ Each = Component.extend({ } }); - if (items.empty) { + if (items.empty()) { buf.component(function () { return (self.getArg('elseClass') || Component).create( - { data: this.getArg('data') }); + { data: self.getArg('data') }); }, { key: 'else' }); } else { items.forEach(function (doc, id) { @@ -246,14 +272,14 @@ Each = Component.extend({ buf.component(function () { return self.getArg('bodyClass').create({ data: tdoc }); - }, { key: this._itemChildId(id) }); + }, { key: self._itemChildId(id) }); }); } }, _itemChildId: function (id) { return 'item:' + idStringify(id); - } + }, /* addItemChild: function (id, comp) { this.addChild(this._itemChildId(id), comp); @@ -284,11 +310,22 @@ Each = Component.extend({ this.setStart(comp); if (isLast) this.setEnd(comp); - }, + },*/ itemAddedBefore: function (id, doc, beforeId) { - var bodyClass = this.getArg('bodyClass'); - var comp = new bodyClass({data: doc}); + var self = this; + + var childId = self._itemChildId(id); + var comp = self.getArg('bodyClass').create({data: doc}); + + if (self.items.size() === 1) { + // was empty + self.replaceChild('else', comp, childId); + } else { + self.addChild(childId, comp // XXXXXX + } + + this.addItemChild(id, comp); if (this.isBuilt) { @@ -298,7 +335,8 @@ Each = Component.extend({ // was empty this.removeChild('else'); } - }, + } + /* itemRemoved: function (id) { if (this.items.size() === 1) { // making empty diff --git a/packages/ui/component.js b/packages/ui/component.js index 872aa296b0..8eede063b0 100644 --- a/packages/ui/component.js +++ b/packages/ui/component.js @@ -429,7 +429,7 @@ _.extend(Component.prototype, { childComponent.destroy(); }, - replaceChild: function (key, newChild) { + replaceChild: function (key, newChild, newKey) { if (this.stage === Component.DESTROYED) throw new Error("parent Component already destroyed"); if (this.stage === Component.UNADDED) @@ -441,21 +441,25 @@ _.extend(Component.prototype, { if (! (newChild instanceof Component)) throw new Error("Component required"); + if ((typeof newKey) !== 'string') + newKey = key; + var oldChild = this.children[key]; - if (oldChild.constructor === newChild.constructor) { + if (newKey === key && + oldChild.constructor === newChild.constructor) { oldChild.update(newChild._args); } else if (this.stage !== Component.BUILT || oldChild !== Component.BUILT || ! oldChild.isAttached) { this.removeChild(key); - this.addChild(key, newChild); + this.addChild(newKey, newChild); } else { // swap attached child var parentNode = oldChild.parentNode(); var beforeNode = oldChild.lastNode().nextSibling; this.removeChild(key, true); - this.addChild(key, newChild, parentNode, beforeNode); + this.addChild(newKey, newChild, parentNode, beforeNode); } }, registerElement: function (elementKey, element) { From e28f7f1646ea4afe5ccdb5ab2e6f623f33bc9ca6 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 10 Jun 2013 14:30:23 -0700 Subject: [PATCH 038/938] functional Each component --- examples/unfinished/shark/client/shark.js | 154 +++++++++------------- packages/ui/component.js | 10 +- 2 files changed, 64 insertions(+), 100 deletions(-) diff --git a/examples/unfinished/shark/client/shark.js b/examples/unfinished/shark/client/shark.js index 68d60266ec..1e3633409d 100644 --- a/examples/unfinished/shark/client/shark.js +++ b/examples/unfinished/shark/client/shark.js @@ -54,13 +54,17 @@ Meteor.startup(function () { buf.closeTag('div'); } }), - list: Items.find() + list: Items.find({}, { sort: { text: 1 }}) }), { key: 'body' }); } }) }); RC.attach(document.body); + + Items.insert({ text: 'Qux' }); + Items.remove({ text: 'Foo' }); + Items.update({ text: 'Bar' }, { text: 'Car' }); }); @@ -236,28 +240,31 @@ Each = Component.extend({ if (self.stage === Component.BUILT) { var tdoc = transformedDoc(doc); - self.itemAddedBefore(id, tdoc, beforeId); } }, removed: function (id) { - //items.remove(id); + items.remove(id); - //self.itemRemoved(id); + if (self.stage === Component.BUILT) + self.itemRemoved(id); }, movedBefore: function (id, beforeId) { - //items.moveBefore(id, beforeId); + items.moveBefore(id, beforeId); - //self.itemMovedBefore(id, beforeId); + if (self.stage === Component.BUILT) + self.itemMovedBefore(id, beforeId); }, changed: function (id, fields) { - //var doc = items.get(id); - //if (! doc) - //throw new Error("Unknown id for changed: " + idStringify(id)); - //applyChanges(doc, fields); - //var tdoc = transformedDoc(doc); + var doc = items.get(id); + if (! doc) + throw new Error("Unknown id for changed: " + idStringify(id)); + applyChanges(doc, fields); - //self.itemChanged(id, tdoc); + if (self.stage === Component.BUILT) { + var tdoc = transformedDoc(doc); + self.itemChanged(id, tdoc); + } } }); @@ -280,40 +287,10 @@ Each = Component.extend({ _itemChildId: function (id) { return 'item:' + idStringify(id); }, - /* - addItemChild: function (id, comp) { - this.addChild(this._itemChildId(id), comp); - }, - removeItemChild: function (id) { - this.removeChild(this._itemChildId(id)); - }, - getItemChild: function (id) { - return this.children[this._itemChildId(id)]; - }, - // Utility to attach a child component for an item in its - // appropriate position in the DOM, after it is already - // in the correct position in the items dict. - // Also adjusts the component's bounds. - attachItemChild: function (id, comp, beforeId) { - if (! this.isBuilt) - throw new Error("Component must be built"); - - var isFirst = !this.items.prev(id); - var isLast = !beforeId; - var beforeNode = - (beforeId ? this.getItemChild(beforeId).firstNode() : - this.lastNode().nextSibling); - - comp.attach(this.parentNode(), beforeNode); - - if (isFirst) - this.setStart(comp); - if (isLast) - this.setEnd(comp); - },*/ - itemAddedBefore: function (id, doc, beforeId) { var self = this; + if (self.stage !== Component.BUILT) + throw new Error("Component must be built"); var childId = self._itemChildId(id); var comp = self.getArg('bodyClass').create({data: doc}); @@ -322,69 +299,56 @@ Each = Component.extend({ // was empty self.replaceChild('else', comp, childId); } else { - self.addChild(childId, comp // XXXXXX + var beforeNode = + (beforeId ? + self.children[self._itemChildId(beforeId)].firstNode() : + (self.lastNode().nextSibling || null)); + var parentNode = self.parentNode(); + + self.addChild(childId, comp, parentNode, beforeNode); } - - - this.addItemChild(id, comp); - - if (this.isBuilt) { - this.attachItemChild(id, comp, beforeId); - - if (this.items.size() === 1) - // was empty - this.removeChild('else'); - } - } - /* + }, itemRemoved: function (id) { - if (this.items.size() === 1) { - // making empty - var elseClass = this.getArg('elseClass') || EmptyComponent; - var comp = new elseClass({data: this.getArg('data')}); - this.addChild('else', comp); + var self = this; + if (self.stage !== Component.BUILT) + throw new Error("Component must be built"); - if (this.isBuilt) { - comp.attach(this.parentNode(), this.firstNode()); - this.setBounds(comp); - } + var childId = self._itemChildId(id); + if (self.items.size() === 0) { + // made empty + var elseClass = self.getArg('elseClass') || Component; + var comp = elseClass.create({data: self.getArg('data')}); + self.replaceChild(childId, comp, 'else'); + } else { + self.removeChild(childId); } - this.removeItemChild(id); }, itemMovedBefore: function (id, beforeId) { - if (this.items.size() === 1) + var self = this; + if (self.stage !== Component.BUILT) + throw new Error("Component must be built"); + + if (self.items.size() === 1) return; // move is meaningless anyway - if (this.isBuilt) { - var comp = this.getItemChild(id); - comp.detach(); - this.attachItemChild(id, comp, beforeId); - } + var comp = self.children[self._itemChildId(id)]; + + var beforeNode = + (beforeId ? + self.children[self._itemChildId(beforeId)].firstNode() : + (self.lastNode().nextSibling || null)); + var parentNode = self.parentNode(); + + comp.detach(); + comp.attach(parentNode, beforeNode); }, itemChanged: function (id, doc) { - this.getItemChild(id).update({data: doc}); - }, - initiallyEmpty: function () { - var elseClass = this.getArg('elseClass') || EmptyComponent; - this.addChild('else', new elseClass({data: this.getArg('data')})); - }, - - build: function (frag) { var self = this; - if (self.items.empty()) { - var elseChild = self.children['else']; - elseChild.attach(frag); - self.setBounds(elseChild); - } else { - self.items.forEach(function (doc, id) { - self.getItemChild(id).attach(frag); - }); - self.setBounds(self.getItemChild(self.items.first()), - self.getItemChild(self.items.last())); - } + if (self.stage !== Component.BUILT) + throw new Error("Component must be built"); + + self.children[self._itemChildId(id)].update({data: doc}); } - // XXX toHtml -*/ }); /*MyLI = DebugComponent.extend({ diff --git a/packages/ui/component.js b/packages/ui/component.js index 8eede063b0..b018d5327c 100644 --- a/packages/ui/component.js +++ b/packages/ui/component.js @@ -187,15 +187,15 @@ _.extend(Component.prototype, { throw new Error("Component not attached"); if (parent) { - if (parent._start === comp) { - if (parent._end === comp) { + if (parent._start === self) { + if (parent._end === self) { if (_allowTransientEmpty) parent._start = parent._end = EMPTY; else throw new Error("Can't detach entire contents of " + "Component; use swapInChild instead"); } else { - var newFirstNode = comp.lastNode().nextSibling; + var newFirstNode = self.lastNode().nextSibling; var foundComp = null; for (var k in parent.children) { if (parent.children.hasOwnProperty(k) && @@ -206,8 +206,8 @@ _.extend(Component.prototype, { } parent.setStart(foundComp || newFirstNode); } - } else if (parent._end === comp) { - var newLastNode = comp.firstNode().previousSibling; + } else if (parent._end === self) { + var newLastNode = self.firstNode().previousSibling; var foundComp = null; for (var k in parent.children) { if (parent.children.hasOwnProperty(k) && From a23522b22591074d609ce3945b3d3df0875ee67d Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 10 Jun 2013 17:12:12 -0700 Subject: [PATCH 039/938] spacebars code gen wip --- packages/spacebars/spacebars.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/spacebars/spacebars.js b/packages/spacebars/spacebars.js index 6d5024470d..55488e61a8 100644 --- a/packages/spacebars/spacebars.js +++ b/packages/spacebars/spacebars.js @@ -487,13 +487,6 @@ var toJSLiteral = function (obj) { .replace(/\u2029/g, '\\u2029')); }; -var tagLiteral = function (tag) { - var lit = _.extend({}, tag); - delete lit.charPos; - delete lit.charLength; - return toJSLiteral(lit); -}; - Spacebars.compile = function (inputString) { var tree; if (typeof inputString === 'object') { @@ -502,6 +495,7 @@ Spacebars.compile = function (inputString) { tree = Spacebars.parse(inputString); } + // XXX rewrite interpolate var interpolate = function (strOrArray, indent) { if (typeof strOrArray === "string") return toJSLiteral(strOrArray); @@ -554,14 +548,18 @@ Spacebars.compile = function (inputString) { var tokensToFunc = function (tokens, indent) { var oldIndent = indent || ''; indent = oldIndent + ' '; - var js = 'function (html, env) {\n'; + var js = 'function (buf) {\n'; _.each(tokens, function (t) { switch (t.type) { case 'Characters': + // XXX what does interpolate do here? js += indent + 'html.text(' + interpolate(t.data, indent) +');\n'; break; case 'StartTag': - js += indent + 'html.open(' + interpolate(t.name, indent) + + // XXX take `t.data` and generate an appropriate + // attrs argument. + // Also, emit the `selfClose` option. + js += indent + 'html.openTag(' + toJSLiteral(t.name) + (t.data.length ? ', [' + _.map(t.data, function (kv) { return '[' + interpolate(kv.nodeName, indent) + ', ' + interpolate(kv.nodeValue, indent) + ']'; From 25dafeb38f889602e989e44d4d5d9bb09483ceb5 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 10 Jun 2013 20:55:41 -0700 Subject: [PATCH 040/938] more spacebars WIP --- packages/spacebars/spacebars.js | 77 +++++++++++++++++++++++++++------ packages/ui/renderbuffer.js | 29 +++++++++++++ 2 files changed, 93 insertions(+), 13 deletions(-) diff --git a/packages/spacebars/spacebars.js b/packages/spacebars/spacebars.js index 55488e61a8..af6614c255 100644 --- a/packages/spacebars/spacebars.js +++ b/packages/spacebars/spacebars.js @@ -487,6 +487,21 @@ var toJSLiteral = function (obj) { .replace(/\u2029/g, '\\u2029')); }; +// takes an object whose keys and values are strings of +// JavaScript source code and returns the source code +// of an object literal. +var makeObjectLiteral = function (obj) { + var buf = []; + buf.push('{'); + for (var k in obj) { + if (buf.length > 1) + buf.push(', '); + buf.push(k, ':', obj[k]); + } + buf.push('}'); + return buf.join(''); +}; + Spacebars.compile = function (inputString) { var tree; if (typeof inputString === 'object') { @@ -495,7 +510,25 @@ Spacebars.compile = function (inputString) { tree = Spacebars.parse(inputString); } - // XXX rewrite interpolate + // XXX rewrite interpolate. + // + // Should now return the source code of either a + // string or a (reactive) function. Ideally it is + // a simple string if possible. + // + // Oh snap, can't do components this way. + // Only double and triple stache. + // Should first write the logic that parses out + // block helpers and inclusions in a run of Characters, + // in tokensToFunc. These tags aren't allowed in an + // interpolation, only double-stache and triple-stache are. + // + // Will need to write the lookup rules for component names, + // helper functions, and values. Maybe it's the same? + // Delegates to `component.lookup(path)`? + // + // We will probably lose the `{{#if equal a b}}` convenience + // syntax (but maybe introduce new syntax for this later). var interpolate = function (strOrArray, indent) { if (typeof strOrArray === "string") return toJSLiteral(strOrArray); @@ -552,27 +585,45 @@ Spacebars.compile = function (inputString) { _.each(tokens, function (t) { switch (t.type) { case 'Characters': - // XXX what does interpolate do here? - js += indent + 'html.text(' + interpolate(t.data, indent) +');\n'; + js += indent + 'buf.text(' + interpolate(t.data, indent) +');\n'; break; case 'StartTag': - // XXX take `t.data` and generate an appropriate + // XXX Take `t.data` and generate an appropriate // attrs argument. - // Also, emit the `selfClose` option. - js += indent + 'html.openTag(' + toJSLiteral(t.name) + - (t.data.length ? ', [' + _.map(t.data, function (kv) { - return '[' + interpolate(kv.nodeName, indent) + ', ' + - interpolate(kv.nodeValue, indent) + ']'; - }).join(', ') + ']' : '') + ');\n'; + var attrs = null; + var dynamicAttrs = null; + _.each(t.data, function (kv) { + var name = kv.nodeName; + var value = kv.nodeValue; + if ((typeof name) !== 'string') { + dynamicAttrs = (dynamicAttrs || []); + dynamicAttrs.push([interpolate(name, indent), + interpolate(value, indent)]); + } else { + attrs = (attrs || {}); + attrs[toJSLiteral(name)] = + interpolate(value, indent); + } + }); + var options = null; + if (dynamicAttrs) { + options = (options || {}); + options['dynamicAttrs'] = makeObjectLiteral(dynamicAttrs); + } + // XXX Pass the `selfClose` option. + js += indent + 'buf.openTag(' + toJSLiteral(t.name) + + ((attrs || options) ? ', ' + makeObjectLiteral(attrs) : '') + + (options ? ', ' + makeObjectLiteral(options) : '') + + ');\n'; break; case 'EndTag': - js += indent + 'html.close(' + interpolate(t.name, indent) + ');\n'; + js += indent + 'buf.close(' + toJSLiteral(t.name) + ');\n'; break; case 'Comment': - js += indent + 'html.comment(' + interpolate(t.name, indent) + ');\n'; + js += indent + 'buf.comment(' + interpolate(t.name, indent) + ');\n'; break; case 'DocType': - js += indent + 'html.doctype(' + toJSLiteral(t.name) + ', ' + + js += indent + 'buf.doctype(' + toJSLiteral(t.name) + ', ' + toJSLiteral({correct: t.correct, publicId: t.publicId, systemId: t.systemId}) + ');\n'; break; diff --git a/packages/ui/renderbuffer.js b/packages/ui/renderbuffer.js index cda0e7ba44..07c562180f 100644 --- a/packages/ui/renderbuffer.js +++ b/packages/ui/renderbuffer.js @@ -53,6 +53,8 @@ var updateDOMAttribute = function (component, elemKey, attrName, _.extend(RenderBuffer.prototype, { _encodeEntities: encodeEntities, + // XXX implement dynamicAttrs option, + // takes [[k, v], ...] openTag: function (tagName, attrs, options) { var self = this; @@ -174,6 +176,33 @@ _.extend(RenderBuffer.prototype, { self._childrenToAttach[commentString] = childComp; }, + comment: function (stringOrFunction) { + // XXX making comments reactively update seems + // right; consider doing that. + + var self = this; + + var content; + if (typeof stringOrFunction === 'function') { + var func = stringOrFunction; + content = func(); + } else { + if (typeof stringOrFunction !== 'string') + throw new Error("string required"); + content = stringOrFunction; + } + + // comments can't have "--" in them in HTML. + // just strip those so that we don't run into trouble. + content = content.replace(/--/g, ''); + self._htmlBuf.push(''); + }, + doctype: function (name, options) { + var buf = this._htmlBuf; + buf.push(''); + }, build: function () { var self = this; From 5c975735dbe28d156fce8b8f903fae32b4a75fc9 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Tue, 11 Jun 2013 15:15:16 -0700 Subject: [PATCH 041/938] template parser tests --- packages/spacebars/spacebars.js | 138 +++++++++++------ packages/spacebars/spacebars_tests.js | 214 ++++++++++++++++++++++++++ 2 files changed, 301 insertions(+), 51 deletions(-) diff --git a/packages/spacebars/spacebars.js b/packages/spacebars/spacebars.js index af6614c255..43378053cf 100644 --- a/packages/spacebars/spacebars.js +++ b/packages/spacebars/spacebars.js @@ -238,29 +238,32 @@ var randomLetters = function () { return str; }; -var tokenizeHtml = function (html, preString, postString, tagInfoGetter) { +var MODE_ALL_STACHE = 0; +var MODE_NO_STACHE = 1; +var MODE_NO_COMPONENTS = 2; + +var tokenizeHtml = function (html, preString, postString, tagLookup) { var tokens = HTML5Tokenizer.tokenize(html); var out = []; - var noStache = function (chrs, customMessage) { - if (! chrs) - return chrs; - - var extracted = extractTags(chrs); - if (typeof extracted === "string") - return chrs; - - for (var i = 0; i < extracted.length; i++) - if (extracted[i].id) - throw new Error((customMessage || - "Can't use a stache tag at this position " + - "in an HTML tag") + ", at " + - tagInfoGetter(extracted[i].id).prettyOffset()); - return chrs; - }; - - var extractTags = function (str) { + var extractTags = function (str, mode, customErrorMessage) { + // Scan `str` for substrings that are actually our + // alphabetic markers that represent stache tags + // (or entire blocks, which have `.type` of `'block'` + // and `.isBlock` of `true`). + // + // Return either a single string (if there are no stache + // tags) or an array, each element of which is either a + // string or a tag or block. + // + // The `mode` flag can be used to restrict the allowed + // tag types, for example by setting it to MODE_NO_STACHE + // to disallow stache tags completely (and verify that + // there are none). If this flag is used, + // `customErrorMessage` may optionally be given to replace + // the default error message of "Can't use this stache tag + // at this position in an HTML tag". if (! str) return ''; @@ -274,9 +277,19 @@ var tokenizeHtml = function (html, preString, postString, tagInfoGetter) { var idEnd = str.indexOf(postString, idStart); if (idEnd < 0) throw new Error("error extracting tags"); // shouldn't happen - var x; - buf.push(x = {id: str.slice(idStart, idEnd)}); - x.ref = tagInfoGetter(x.id).ref(); + var tagId = str.slice(idStart, idEnd); + var tag = tagLookup.getTag(tagId); + if (mode) { + if (mode === MODE_NO_STACHE || + (mode === MODE_NO_COMPONENTS && + (tag.isBlock || tag.type === 'INCLUSION'))) + throw new Error( + (customErrorMessage || + "Can't use this stache tag at this position " + + "in an HTML tag") + ", at " + + tagLookup.prettyOffset(tagId)); + } + buf.push(tag); lastPos = idEnd + postString.length; } if (lastPos < str.length) @@ -288,12 +301,26 @@ var tokenizeHtml = function (html, preString, postString, tagInfoGetter) { return buf; }; + // Run extractTags(chrs) and make sure there are no stache tags, + // because they are illegal in this position (e.g. HTML tag + // name). + var noStache = function (str, customMessage) { + return extractTags(str, MODE_NO_STACHE, customMessage); + }; + + // Like `extractTags(str)`, but doesn't allow block helpers + // or inclusions. + var extractStringTags = function (str, customMessage) { + return extractTags(str, MODE_NO_COMPONENTS, customMessage); + }; + for (var i = 0; i < tokens.length; i++) { var tok = tokens[i]; if (tok.type === 'Characters' || tok.type === 'SpaceCharacters') { var s = tok.data; - // combine multiple adjacent "Characters" + // combine multiple adjacent "Characters"; this is + // necessary to make sure we extract the tags properly. while (tokens[i+1] && (tokens[i+1].type === 'Characters' || tokens[i+1].type === 'SpaceCharacters')) { @@ -309,18 +336,22 @@ var tokenizeHtml = function (html, preString, postString, tagInfoGetter) { out.push({type: 'DocType', name: noStache(tok.name), correct: tok.correct, - publicId: noStache(tok.publicId), - systemId: noStache(tok.systemId)}); + publicId: tok.publicId && noStache(tok.publicId), + systemId: tok.systemId && noStache(tok.systemId) + }); } else if (tok.type === 'Comment') { out.push({type: 'Comment', - data: extractTags(tok.data)}); + data: extractStringTags(tok.data)}); } else if (tok.type === 'StartTag') { out.push({ type: 'StartTag', name: noStache(tok.name), data: _.map(tok.data, function (kv) { - return { nodeName: extractTags(kv.nodeName), - nodeValue: extractTags(kv.nodeValue) }; - }) }); + return { + nodeName: extractStringTags(kv.nodeName), + nodeValue: extractStringTags(kv.nodeValue) }; + }), + self_closing: tok.self_closing + }); } else { // ignore (ParseError, EOF) } @@ -346,19 +377,24 @@ Spacebars.parse = function (inputString) { } } - // now build a tree where block contents put into an object - // with `type:'block'`. Also check that tags match. + // now build a tree where block contents are put into an object + // with `type:'block'`. Also check that block stache tags match. var parseBlock = function (openTagIndex) { var isTopLevel = (openTagIndex < 0); var block = { type: 'block', - isBlock: true, + isBlock: true, // always true for a block; just a type marker + // openTag, closeTag must be present except at top level openTag: null, - elseTag: null, closeTag: null, bodyChildren: [], // tags and blocks - elseChildren: [] + bodyTokens: null, // filled in by a subsequent recursive pass + // if elseTag is present, then elseChildren and elseTokens + // must be too. + elseTag: null, + elseChildren: null, + elseTokens: null }; var children = block.bodyChildren; // repointed to elseChildren later if (! isTopLevel) @@ -394,7 +430,8 @@ Spacebars.parse = function (inputString) { throw new Error("Duplicate `{{else}}` at " + prettyOffset(inputString, t.charPos)); block.elseTag = t; - children = block.elseChildren; + children = []; + block.elseChildren = children; } else { children.push(t); } @@ -420,23 +457,23 @@ Spacebars.parse = function (inputString) { var idLookup = {}; - var tagInfoGetter = function (id) { - var t = idLookup[id]; - return { - prettyOffset: function () { - return t ? prettyOffset( - inputString, (t.isBlock ? t.openTag : t).charPos) : - "(unknown)"; - }, - ref: function () { - return t; - } - }; + var tagLookup = { + prettyOffset: function (tagId) { + var t = idLookup[tagId]; + return t ? prettyOffset( + inputString, (t.isBlock ? t.openTag : t).charPos) : + "(unknown)"; + }, + getTag: function (tagId) { + return idLookup[tagId]; + } }; var tokenizeBlock = function (block) { - // replace all child tags and blocks in the HTML with random - // identifiers! + // Strategy: replace all child tags and blocks in the HTML + // with random identifiers before passing to the tokenizer! + // Because the random identifiers consist of ASCII letters, + // they will be parsed as tokens or substrings of tokens. var isTopLevel = ! block.openTag; var hasElse = !! block.elseTag; @@ -456,8 +493,7 @@ Spacebars.parse = function (inputString) { }); html += inputString.slice(pos, endPos); - return tokenizeHtml(html, preString, postString, - tagInfoGetter); + return tokenizeHtml(html, preString, postString, tagLookup); }; var bodyStart = (isTopLevel ? 0 : tagEnd(block.openTag)); diff --git a/packages/spacebars/spacebars_tests.js b/packages/spacebars/spacebars_tests.js index da1d1fc401..0e6ebd737d 100644 --- a/packages/spacebars/spacebars_tests.js +++ b/packages/spacebars/spacebars_tests.js @@ -106,3 +106,217 @@ Tinytest.add("spacebars - stache tags", function (test) { args: [['STRING', 'bar']]}); }); + +Tinytest.add("spacebars - parser", function (test) { + // check a block and reduce it to a slightly simpler form + // for writing tests. + var checkAndStripBlock = function (block, isTopLevel) { + test.equal(block.type, 'block'); + test.equal(block.isBlock, true); + delete block.isBlock; + if (isTopLevel) { + // top-level block has no bounding stache tags + // and no {{else}} + test.equal(block.openTag, null); + test.equal(block.closeTag, null); + test.equal(block.elseTag, null); + test.equal(block.elseChildren, null); + test.equal(block.elseTokens, null); + delete block.openTag; + delete block.closeTag; + delete block.elseTag; + delete block.elseChildren; + delete block.elseTokens; + } else { + test.isTrue(block.openTag); + test.isTrue(block.closeTag); + checkAndStripTag(block.openTag); + checkAndStripTag(block.closeTag); + if (block.elseTag) { + checkAndStripTag(block.elseTag); + } else { + // if no {{else}}, then no elseTag, elseChildren, + // elseTokens + test.equal(block.elseTag, null); + test.equal(block.elseChildren, null); + test.equal(block.elseTokens, null); + delete block.elseTag; + delete block.elseChildren; + delete block.elseTokens; + } + } + + var checkAndStripTokens = function (tokens, children) { + var nextChild = 0; + + _.each(tokens, function (tok) { + switch (tok.type) { + case 'StartTag': + test.equal(typeof tok.name, 'string'); + _.each(tok.data, function (nv) { + if (typeof nv.nodeName !== 'string') { + test.isTrue(_.isArray(nv.nodeName)); + _.each(nv.nodeName, function (tagOrStr) { + if (typeof tagOrStr !== 'string') { + checkAndStripTag(tagOrStr, true); + test.isTrue(children[nextChild++] === tagOrStr); + } + }); + } + if (typeof nv.nodeValue !== 'string') { + test.isTrue(_.isArray(nv.nodeValue)); + _.each(nv.nodeValue, function (tagOrStr) { + if (typeof tagOrStr !== 'string') { + checkAndStripTag(tagOrStr, true); + test.isTrue(children[nextChild++] === tagOrStr); + } + }); + } + }); + if (! tok.self_closing) + delete tok.self_closing; + break; + case 'Characters': + case 'Comment': + if (typeof tok.data !== 'string') { + test.isTrue(_.isArray(tok.data)); + _.each(tok.data, function (tagOrStr) { + if (typeof tagOrStr !== 'string') { + checkAndStripTag(tagOrStr); + test.isTrue(children[nextChild++] === tagOrStr); + } + }); + } + break; + case 'EndTag': + case 'DocType': + test.equal(typeof tok.name, 'string'); + break; + default: + test.fail("Unknown token type: " + tok.type); + } + }); + + test.equal(nextChild, children.length); + }; + + checkAndStripTokens(block.bodyTokens, block.bodyChildren); + if (block.elseTag) + checkAndStripTokens(block.elseTokens, block.elseChildren); + + // children already checked + delete block.bodyChildren; + delete block.elseChildren; + + return block; + }; + + var checkAndStripTag = function (tag, onlyStringStaches) { + if (tag.isBlock) { + if (onlyStringStaches) + test.fail("Can't have block here"); + checkAndStripBlock(tag); + } else { + if (onlyStringStaches) { + test.isFalse(tag.type === 'INCLUSION' || + tag.type === 'BLOCKOPEN' || + tag.type === 'BLOCKCLOSE' || + tag.type === 'ELSE'); + } + delete tag.charPos; + delete tag.charLength; + } + + return tag; + }; + + var run = function (input, expectedParse) { + test.equal(checkAndStripBlock(Spacebars.parse(input), + true), + expectedParse); + }; + + run('{{foo}}b', + {"type":"block", + "bodyTokens":[ + {"type":"StartTag", + "name":"a", + "data":[]}, + {"type":"Characters", + "data":[ + {"type":"DOUBLE","path":["foo"],"args":[]}, + "b"]}]}); + + run('', + {"type":"block", + "bodyTokens":[ + {"type":"StartTag", + "name":"a", + "data":[ + {"nodeName":[ + {"type":"DOUBLE", + "path":["foo"], + "args":[]}], + "nodeValue":[ + {"type":"DOUBLE", + "path":["bar"], + "args":[]}]}]}]}); + + run('
    ', + {"type":"block", + "bodyTokens":[ + {"type":"StartTag", + "name":"br", + "data":[], + "self_closing":true}]}); + + run('111{{#foo}}222{{#bar}}333{{/bar}}444{{/foo}}555', + {"type":"block", + "bodyTokens":[ + {"type":"Characters", + "data":["111", + {"type":"block", + "openTag":{ + "type":"BLOCKOPEN", + "path":["foo"], + "args":[]}, + "closeTag":{ + "type":"BLOCKCLOSE", + "path":["foo"]}, + "bodyTokens":[ + {"type":"Characters", + "data":["222", + {"type":"block", + "openTag":{ + "type":"BLOCKOPEN", + "path":["bar"], + "args":[]}, + "closeTag":{ + "type":"BLOCKCLOSE", + "path":["bar"]}, + "bodyTokens":[ + {"type":"Characters", + "data":"333"}]}, + "444"]}]}, + "555"]}]}); + + run('
    {{#foo x=y}}{{else}}
    {{/foo}}
    ', + {"type":"block", + "bodyTokens":[ + {"type":"StartTag", + "name":"div", + "data":[]}, + {"type":"Characters", + "data":[ + {"type":"block", + "openTag":{ + "type":"BLOCKOPEN", + "path":["foo"], + "args":[["PATH",["y"],"x"]]}, + "closeTag": {"type":"BLOCKCLOSE", "path":["foo"]}, + "bodyTokens":[], + "elseTag": {"type":"ELSE"}, + "elseTokens":[ + {"type":"StartTag","name":"hr","data":[]}]}]}, + {"type":"EndTag","name":"div"}]}); +}); \ No newline at end of file From c21fffe72a838fd71bb40d02c1df4c4762ef6782 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Wed, 12 Jun 2013 10:04:10 -0700 Subject: [PATCH 042/938] spacebars compiler minus components and helpers --- packages/spacebars/spacebars.js | 89 ++++++++++++++++++++++++++++++--- packages/ui/renderbuffer.js | 2 +- 2 files changed, 82 insertions(+), 9 deletions(-) diff --git a/packages/spacebars/spacebars.js b/packages/spacebars/spacebars.js index 43378053cf..d832707ed5 100644 --- a/packages/spacebars/spacebars.js +++ b/packages/spacebars/spacebars.js @@ -565,7 +565,7 @@ Spacebars.compile = function (inputString) { // // We will probably lose the `{{#if equal a b}}` convenience // syntax (but maybe introduce new syntax for this later). - var interpolate = function (strOrArray, indent) { + /*var interpolate = function (strOrArray, indent) { if (typeof strOrArray === "string") return toJSLiteral(strOrArray); @@ -612,20 +612,90 @@ Spacebars.compile = function (inputString) { }); return parts.join('+'); + };*/ + + // Return the source code of a string or (reactive) function + // (if necessary). + var interpolate = function (strOrArray, indent) { + if (typeof strOrArray === "string") + return toJSLiteral(strOrArray); + + var parts = []; + var isReactive = false; + _.each(strOrArray, function (strOrTag) { + if (typeof strOrTag === "string") { + parts.push(toJSLiteral(strOrTag)); + } else { + var tag = strOrTag; + switch (tag.type) { + case 'COMMENT': + // nothing to do + break; + case 'DOUBLE': // fall through + case 'TRIPLE': + isReactive = true; + parts.push('stuff()'); // XXXXXXXX + /* parts.push('env.' + + (tag.type === 'DOUBLE' ? 'dstache' : 'tstache') + + '(' + toJSLiteral(tag.path) + + (tag.args.length ? ', ' + toJSLiteral(tag.args) : '') + + ')');*/ + break; + default: + throw new Error("Unknown stache tag type: " + tag.type); + //parts.push('env.tag(' + tagLiteral(tag) + ')'); + } + } + }); + + if (isReactive) { + return 'function () { return ' + parts.join('+') + + '; }'; + } else { + return parts.join('+'); + } }; - var tokensToFunc = function (tokens, indent) { + var tokensToRenderFunc = function (tokens, indent) { var oldIndent = indent || ''; indent = oldIndent + ' '; var js = 'function (buf) {\n'; _.each(tokens, function (t) { switch (t.type) { case 'Characters': - js += indent + 'buf.text(' + interpolate(t.data, indent) +');\n'; + if (typeof t.data === 'string') { + js += indent + 'buf.text(' + toJSLiteral(t.data) + + ');\n'; + } else { + _.each(t.data, function (tagOrStr) { + if (typeof tagOrStr === 'string') { + js += indent + 'buf.text(' + toJSLiteral(tagOrStr) + + ');\n'; + } else { + // tag or block + var tag = tagOrStr; + if (tag.isBlock) { + // XXX implement + } else { + switch (tag.type) { + case 'INCLUSION': + // XXX implement + break; + case 'DOUBLE': + case 'TRIPLE': + // XXX implement + break; + case 'COMMENT': + break; + default: + throw new Error("Unexpected tag type: " + tag.type); + } + } + } + }); + } break; case 'StartTag': - // XXX Take `t.data` and generate an appropriate - // attrs argument. var attrs = null; var dynamicAttrs = null; _.each(t.data, function (kv) { @@ -646,14 +716,17 @@ Spacebars.compile = function (inputString) { options = (options || {}); options['dynamicAttrs'] = makeObjectLiteral(dynamicAttrs); } - // XXX Pass the `selfClose` option. + if (t.self_closing) { + options = (options || {}); + options['selfClose'] = 'true'; + } js += indent + 'buf.openTag(' + toJSLiteral(t.name) + ((attrs || options) ? ', ' + makeObjectLiteral(attrs) : '') + (options ? ', ' + makeObjectLiteral(options) : '') + ');\n'; break; case 'EndTag': - js += indent + 'buf.close(' + toJSLiteral(t.name) + ');\n'; + js += indent + 'buf.closeTag(' + toJSLiteral(t.name) + ');\n'; break; case 'Comment': js += indent + 'buf.comment(' + interpolate(t.name, indent) + ');\n'; @@ -672,5 +745,5 @@ Spacebars.compile = function (inputString) { return js; }; - return tokensToFunc(tree.bodyTokens); + return tokensToRenderFunc(tree.bodyTokens); }; \ No newline at end of file diff --git a/packages/ui/renderbuffer.js b/packages/ui/renderbuffer.js index 07c562180f..44e83b6d40 100644 --- a/packages/ui/renderbuffer.js +++ b/packages/ui/renderbuffer.js @@ -178,7 +178,7 @@ _.extend(RenderBuffer.prototype, { }, comment: function (stringOrFunction) { // XXX making comments reactively update seems - // right; consider doing that. + // right, for completeness; consider doing that. var self = this; From febd7589819418e72d52e462f180b9883a32d578 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Wed, 12 Jun 2013 10:07:44 -0700 Subject: [PATCH 043/938] fix empty interpolation --- packages/spacebars/spacebars.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/spacebars/spacebars.js b/packages/spacebars/spacebars.js index d832707ed5..23f9269bc5 100644 --- a/packages/spacebars/spacebars.js +++ b/packages/spacebars/spacebars.js @@ -652,7 +652,7 @@ Spacebars.compile = function (inputString) { return 'function () { return ' + parts.join('+') + '; }'; } else { - return parts.join('+'); + return parts.length ? parts.join('+') : '""'; } }; From 1e102fdcbd146b80d57c31f5d9248f161d902893 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Wed, 12 Jun 2013 10:14:25 -0700 Subject: [PATCH 044/938] compiler unit test harness --- packages/spacebars/spacebars_tests.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/spacebars/spacebars_tests.js b/packages/spacebars/spacebars_tests.js index 0e6ebd737d..a5333abf07 100644 --- a/packages/spacebars/spacebars_tests.js +++ b/packages/spacebars/spacebars_tests.js @@ -319,4 +319,28 @@ Tinytest.add("spacebars - parser", function (test) { "elseTokens":[ {"type":"StartTag","name":"hr","data":[]}]}]}, {"type":"EndTag","name":"div"}]}); -}); \ No newline at end of file +}); + +Tinytest.add("spacebars - compiler", function (test) { + + var run = function (input/*, expectedLines*/) { + var expectedLines = Array.prototype.slice.call(arguments, 1); + var expected = expectedLines.join('\n'); + var output = Spacebars.compile(input); + test.equal(output, expected); + }; + + run('abc', + + 'function (buf) {', + ' buf.text("abc");', + '}'); + + run('
    abc', + + 'function (buf) {', + ' buf.openTag("a", {"foo":"bar"});', + ' buf.text("abc");', + ' buf.closeTag("a");', + '}'); +}); From a6e1619f2650af5053898d1213078ea42caa3074 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Wed, 12 Jun 2013 13:39:54 -0700 Subject: [PATCH 045/938] towards helpers --- packages/spacebars/spacebars.js | 65 ++++++++++++++++++--------- packages/spacebars/spacebars_tests.js | 22 ++++++++- 2 files changed, 64 insertions(+), 23 deletions(-) diff --git a/packages/spacebars/spacebars.js b/packages/spacebars/spacebars.js index 23f9269bc5..beeb86f185 100644 --- a/packages/spacebars/spacebars.js +++ b/packages/spacebars/spacebars.js @@ -532,7 +532,7 @@ var makeObjectLiteral = function (obj) { for (var k in obj) { if (buf.length > 1) buf.push(', '); - buf.push(k, ':', obj[k]); + buf.push(k, ': ', obj[k]); } buf.push('}'); return buf.join(''); @@ -616,7 +616,7 @@ Spacebars.compile = function (inputString) { // Return the source code of a string or (reactive) function // (if necessary). - var interpolate = function (strOrArray, indent) { + var interpolate = function (strOrArray, funcInfo) { if (typeof strOrArray === "string") return toJSLiteral(strOrArray); @@ -659,18 +659,24 @@ Spacebars.compile = function (inputString) { var tokensToRenderFunc = function (tokens, indent) { var oldIndent = indent || ''; indent = oldIndent + ' '; - var js = 'function (buf) {\n'; + + var funcInfo = { + indent: indent, // read-only + usedSelf: false // read/write + }; + + var bodyLines = []; _.each(tokens, function (t) { switch (t.type) { case 'Characters': if (typeof t.data === 'string') { - js += indent + 'buf.text(' + toJSLiteral(t.data) + - ');\n'; + bodyLines.push('buf.text(' + toJSLiteral(t.data) + + ');'); } else { _.each(t.data, function (tagOrStr) { if (typeof tagOrStr === 'string') { - js += indent + 'buf.text(' + toJSLiteral(tagOrStr) + - ');\n'; + bodyLines.push('buf.text(' + toJSLiteral(tagOrStr) + + ');'); } else { // tag or block var tag = tagOrStr; @@ -703,46 +709,61 @@ Spacebars.compile = function (inputString) { var value = kv.nodeValue; if ((typeof name) !== 'string') { dynamicAttrs = (dynamicAttrs || []); - dynamicAttrs.push([interpolate(name, indent), - interpolate(value, indent)]); + dynamicAttrs.push([interpolate(name, funcInfo), + interpolate(value, funcInfo)]); } else { attrs = (attrs || {}); attrs[toJSLiteral(name)] = - interpolate(value, indent); + interpolate(value, funcInfo); } }); var options = null; if (dynamicAttrs) { options = (options || {}); - options['dynamicAttrs'] = makeObjectLiteral(dynamicAttrs); + options['dynamicAttrs'] = '[' + + _.map(dynamicAttrs, function (pair) { + return '[' + pair[0] + ', ' + pair[1] + ']'; + }).join(', ') + ']'; } if (t.self_closing) { options = (options || {}); options['selfClose'] = 'true'; } - js += indent + 'buf.openTag(' + toJSLiteral(t.name) + - ((attrs || options) ? ', ' + makeObjectLiteral(attrs) : '') + - (options ? ', ' + makeObjectLiteral(options) : '') + - ');\n'; + bodyLines.push( + 'buf.openTag(' + toJSLiteral(t.name) + + ((attrs || options) ? + ', ' + makeObjectLiteral(attrs) + : '') + + (options ? ', ' + makeObjectLiteral(options) : '') + + ');'); break; case 'EndTag': - js += indent + 'buf.closeTag(' + toJSLiteral(t.name) + ');\n'; + bodyLines.push('buf.closeTag(' + toJSLiteral(t.name) + + ');'); break; case 'Comment': - js += indent + 'buf.comment(' + interpolate(t.name, indent) + ');\n'; + bodyLines.push('buf.comment(' + + interpolate(t.name, funcInfo) + ');'); break; case 'DocType': - js += indent + 'buf.doctype(' + toJSLiteral(t.name) + ', ' + - toJSLiteral({correct: t.correct, publicId: t.publicId, - systemId: t.systemId}) + ');\n'; + bodyLines.push( + 'buf.doctype(' + toJSLiteral(t.name) + ', ' + + toJSLiteral({correct: t.correct, + publicId: t.publicId, + systemId: t.systemId}) + ');'); break; default: throw new Error("Unexpected token type: " + t.type); break; } }); - js += oldIndent + '}'; - return js; + + return 'function (buf) {' + + (bodyLines ? + (funcInfo.usedSelf ? + '\n' + indent + 'var self = this;' : '') + + '\n' + indent + bodyLines.join('\n' + indent) + '\n' : + '') + '}'; }; return tokensToRenderFunc(tree.bodyTokens); diff --git a/packages/spacebars/spacebars_tests.js b/packages/spacebars/spacebars_tests.js index a5333abf07..75fb0a5a7e 100644 --- a/packages/spacebars/spacebars_tests.js +++ b/packages/spacebars/spacebars_tests.js @@ -339,8 +339,28 @@ Tinytest.add("spacebars - compiler", function (test) { run('abc', 'function (buf) {', - ' buf.openTag("a", {"foo":"bar"});', + ' buf.openTag("a", {"foo": "bar"});', ' buf.text("abc");', ' buf.closeTag("a");', '}'); + + run('', + + 'function (buf) {', + ' var self = this;', + ' buf.openTag("a", {"foo": function () { return String(Spacebars.call(self.lookup("bar"))); }});', + '}'); + + run('', + + 'function (buf) {', + ' var self = this;', + ' buf.openTag("a", {"foo": function () { return String(Spacebars.call(Spacebars.index(self.lookup("bar"), "baz"))); }});', + '}'); }); + +// XXXX double and triple stache in various contexts: +// * comment +// * attribute value +// * dynamic attribute +// * regular level From 3df9d66bb58e7c67b177bd3a6cb71f9f61b37da8 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Wed, 12 Jun 2013 14:27:21 -0700 Subject: [PATCH 046/938] Annotations for linker and proper dependencies --- examples/unfinished/shark/.meteor/packages | 2 ++ packages/html5-tokenizer/.gitignore | 1 + packages/html5-tokenizer/html5_tokenizer.js | 2 +- packages/spacebars/.gitignore | 1 + packages/spacebars/package.js | 2 ++ packages/spacebars/spacebars.js | 1 + packages/ui/.gitignore | 1 + packages/ui/component.js | 4 ++++ packages/ui/package.js | 3 +++ packages/ui/renderbuffer.js | 2 +- 10 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 packages/html5-tokenizer/.gitignore create mode 100644 packages/spacebars/.gitignore create mode 100644 packages/ui/.gitignore diff --git a/examples/unfinished/shark/.meteor/packages b/examples/unfinished/shark/.meteor/packages index 50f082c829..6fe4fae18b 100644 --- a/examples/unfinished/shark/.meteor/packages +++ b/examples/unfinished/shark/.meteor/packages @@ -9,3 +9,5 @@ preserve-inputs ui html5-tokenizer spacebars +ordered-dict +ejson diff --git a/packages/html5-tokenizer/.gitignore b/packages/html5-tokenizer/.gitignore new file mode 100644 index 0000000000..677a6fc263 --- /dev/null +++ b/packages/html5-tokenizer/.gitignore @@ -0,0 +1 @@ +.build* diff --git a/packages/html5-tokenizer/html5_tokenizer.js b/packages/html5-tokenizer/html5_tokenizer.js index 2a78ee8dc7..ffdd9b5ad4 100644 --- a/packages/html5-tokenizer/html5_tokenizer.js +++ b/packages/html5-tokenizer/html5_tokenizer.js @@ -1,4 +1,4 @@ - +// @export HTML5Tokenizer HTML5Tokenizer = { tokenize: function (inputString) { var tokens = []; diff --git a/packages/spacebars/.gitignore b/packages/spacebars/.gitignore new file mode 100644 index 0000000000..677a6fc263 --- /dev/null +++ b/packages/spacebars/.gitignore @@ -0,0 +1 @@ +.build* diff --git a/packages/spacebars/package.js b/packages/spacebars/package.js index 84185bf3fc..f68316b9de 100644 --- a/packages/spacebars/package.js +++ b/packages/spacebars/package.js @@ -5,6 +5,7 @@ Package.describe({ Package.on_use(function (api, where) { where = where || ['client', 'server']; + api.use('random', where); api.use('underscore', where); api.use('jsparse', where); api.use('html5-tokenizer', where); @@ -12,6 +13,7 @@ Package.on_use(function (api, where) { }); Package.on_test(function (api) { + api.use('underscore'); api.use('spacebars'); api.use('tinytest'); api.add_files('spacebars_tests.js', ['client', 'server']); diff --git a/packages/spacebars/spacebars.js b/packages/spacebars/spacebars.js index beeb86f185..ddbd1221bb 100644 --- a/packages/spacebars/spacebars.js +++ b/packages/spacebars/spacebars.js @@ -1,4 +1,5 @@ +// @export Spacebars Spacebars = {}; var makeStacheTagStartRegex = function (r) { diff --git a/packages/ui/.gitignore b/packages/ui/.gitignore new file mode 100644 index 0000000000..677a6fc263 --- /dev/null +++ b/packages/ui/.gitignore @@ -0,0 +1 @@ +.build* diff --git a/packages/ui/component.js b/packages/ui/component.js index b018d5327c..f512f6353f 100644 --- a/packages/ui/component.js +++ b/packages/ui/component.js @@ -1,5 +1,6 @@ var constructorsLocked = true; +// @export Component Component = function (args) { if (constructorsLocked) throw new Error("To create a Component, " + @@ -556,18 +557,21 @@ Component.extend = function (options) { return newClass; }; +// @export TextComponent TextComponent = Component.extend({ render: function (buf) { buf.text(this.getArg('text')); } }); +// @export RawHtmlComponent RawHtmlComponent = Component.extend({ render: function (buf) { buf.rawHtml(this.getArg('html')); } }); +// @export RootComponent RootComponent = Component.extend({ constructed: function () { this.stage = Component.ADDED; diff --git a/packages/ui/package.js b/packages/ui/package.js index 39bc826151..92f31119d4 100644 --- a/packages/ui/package.js +++ b/packages/ui/package.js @@ -3,6 +3,9 @@ Package.describe({ }); Package.on_use(function (api) { + api.use('deps'); + api.use('random'); + api.use('domutils'); api.use('underscore', 'client'); api.use('ejson', 'client'); diff --git a/packages/ui/renderbuffer.js b/packages/ui/renderbuffer.js index 44e83b6d40..9f2ef2c70f 100644 --- a/packages/ui/renderbuffer.js +++ b/packages/ui/renderbuffer.js @@ -1,4 +1,4 @@ - +// @export RenderBuffer RenderBuffer = function (component) { this._component = component; this._htmlBuf = []; From 6ef51852c37fc6ba2d1e8ef6cc889b019be09e5c Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Wed, 12 Jun 2013 15:57:13 -0700 Subject: [PATCH 047/938] begin attempt at Component/template joint def --- packages/ui/component.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/ui/component.js b/packages/ui/component.js index f512f6353f..87817e22d6 100644 --- a/packages/ui/component.js +++ b/packages/ui/component.js @@ -1,5 +1,22 @@ +// @export UI +UI = { + isComponentClass: function (value) { + return (typeof value === 'function') && + (value === Component || + (value.protoype instanceof Component)); + }, + // Generated templates are put here. This object must + // be populated before any Components are actually + // created, i.e. as the compiled template scripts are + // evaluated. + _templates: {} +}; + var constructorsLocked = true; +var templatesAssigned = false; +var global = (function () { return this; })(); + // @export Component Component = function (args) { if (constructorsLocked) @@ -30,6 +47,19 @@ Component = function (args) { this._childUpdaters = {}; this.elements = {}; + if (! templatesAssigned) { + _.each(UI._templates, function (v, k) { + if (UI.isComponentClass(global[k])) { + if (k.prototype.hasOwnProperty('render')) + throw new Error( + 'Component "' + k + '" has both a render method ' + + 'implementation and a template of that name'); + k.prototype.render = v; + } + }); + templatesAssigned = true; + } + this.constructed(); }; From 8a31d50a2879cbbac9244af831f3464ce5bb065d Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Wed, 12 Jun 2013 16:50:50 -0700 Subject: [PATCH 048/938] comments about template init --- packages/ui/component.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/ui/component.js b/packages/ui/component.js index 87817e22d6..e697548d99 100644 --- a/packages/ui/component.js +++ b/packages/ui/component.js @@ -48,6 +48,28 @@ Component = function (args) { this.elements = {}; if (! templatesAssigned) { + // This code is run once ever, when the first Component + // instance is created. + // + // Assign the value of + // `UI._templates["FooComponent"]` to + // `FooComponent.prototype.render`, and so on for other + // entries in the `UI._templates` dictionary. + // + // This code lives here because we can't tie together + // templates and Component definitions any sooner. + // For example, nothing useful can happen when + // `FooComponent = Component.extend(...)` runs, because + // the implementation of `extend` doesn't even know the + // name of the Component class being defined. + // All we can do is collect the templates as they are + // declared, and then at some point sufficiently late + // (here) assign them to the appropriate Component + // classes. + // + // XXX does this use of the global scope work with linking? + // Can we actually find and access all Component classes + // this way? _.each(UI._templates, function (v, k) { if (UI.isComponentClass(global[k])) { if (k.prototype.hasOwnProperty('render')) From 1a9a5df8376e9b539c85bdc03c5c43b57be1cb31 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Thu, 13 Jun 2013 15:53:47 -0700 Subject: [PATCH 049/938] start of helper code gen --- packages/spacebars/spacebars.js | 71 ++++++++++++++++++--------- packages/spacebars/spacebars_tests.js | 6 --- packages/ui/renderbuffer.js | 6 ++- 3 files changed, 53 insertions(+), 30 deletions(-) diff --git a/packages/spacebars/spacebars.js b/packages/spacebars/spacebars.js index ddbd1221bb..d863a97585 100644 --- a/packages/spacebars/spacebars.js +++ b/packages/spacebars/spacebars.js @@ -239,9 +239,22 @@ var randomLetters = function () { return str; }; -var MODE_ALL_STACHE = 0; -var MODE_NO_STACHE = 1; -var MODE_NO_COMPONENTS = 2; +var ALLOW_ALL_STACHE = 0; +var ALLOW_NO_STACHE = 1; +var ALLOW_NO_COMPONENTS = 2; + +// Double- vs triple-stache is really only a sensible distinction +// at text level. In other contexts, we mandate one or the other +// or treat them the same. The reason is that Meteor UI's +// HTML-generation API is high-level and does the encoding for us. +// +// In a comment, allow either and perform no escaping. You can have +// any text in a comment except `--`. +var INTERPOLATE_COMMENT = 1; +// Only allow double in `` or ``. +var INTERPOLATE_ATTR_VALUE = 2; +// Only allow triple in ``. +var INTERPOLATE_DYNAMIC_ATTR = 3; var tokenizeHtml = function (html, preString, postString, tagLookup) { var tokens = HTML5Tokenizer.tokenize(html); @@ -259,7 +272,7 @@ var tokenizeHtml = function (html, preString, postString, tagLookup) { // string or a tag or block. // // The `mode` flag can be used to restrict the allowed - // tag types, for example by setting it to MODE_NO_STACHE + // tag types, for example by setting it to ALLOW_NO_STACHE // to disallow stache tags completely (and verify that // there are none). If this flag is used, // `customErrorMessage` may optionally be given to replace @@ -281,8 +294,8 @@ var tokenizeHtml = function (html, preString, postString, tagLookup) { var tagId = str.slice(idStart, idEnd); var tag = tagLookup.getTag(tagId); if (mode) { - if (mode === MODE_NO_STACHE || - (mode === MODE_NO_COMPONENTS && + if (mode === ALLOW_NO_STACHE || + (mode === ALLOW_NO_COMPONENTS && (tag.isBlock || tag.type === 'INCLUSION'))) throw new Error( (customErrorMessage || @@ -306,13 +319,13 @@ var tokenizeHtml = function (html, preString, postString, tagLookup) { // because they are illegal in this position (e.g. HTML tag // name). var noStache = function (str, customMessage) { - return extractTags(str, MODE_NO_STACHE, customMessage); + return extractTags(str, ALLOW_NO_STACHE, customMessage); }; // Like `extractTags(str)`, but doesn't allow block helpers // or inclusions. var extractStringTags = function (str, customMessage) { - return extractTags(str, MODE_NO_COMPONENTS, customMessage); + return extractTags(str, ALLOW_NO_COMPONENTS, customMessage); }; for (var i = 0; i < tokens.length; i++) { @@ -617,7 +630,7 @@ Spacebars.compile = function (inputString) { // Return the source code of a string or (reactive) function // (if necessary). - var interpolate = function (strOrArray, funcInfo) { + var interpolate = function (strOrArray, funcInfo, interpolateMode) { if (typeof strOrArray === "string") return toJSLiteral(strOrArray); @@ -635,18 +648,27 @@ Spacebars.compile = function (inputString) { case 'DOUBLE': // fall through case 'TRIPLE': isReactive = true; - parts.push('stuff()'); // XXXXXXXX - /* parts.push('env.' + - (tag.type === 'DOUBLE' ? 'dstache' : 'tstache') + - '(' + toJSLiteral(tag.path) + - (tag.args.length ? ', ' + toJSLiteral(tag.args) : '') + - ')');*/ - break; - default: - throw new Error("Unknown stache tag type: " + tag.type); - //parts.push('env.tag(' + tagLiteral(tag) + ')'); + if (interpolateMode === INTERPOLATE_ATTR_VALUE && + tag.type === 'TRIPLE') + throw new Error("Can't have a triple-stache in an attribute value"); + if (interpolateMode === INTERPOLATE_DYNAMIC_ATTR && + tag.type === 'DOUBLE') + throw new Error("Can only have triple-stache for dynamic attributes"); + + funcInfo.usedSelf = true; + var code = 'self.lookup(' + toJSLiteral(tag.path[0]) + ')'; + if (tag.path.length > 1) { + code = 'Spacebars.index(' + code + ', ' + + _.map(tag.path.slice(1), toJSLiteral).join(', ') + ')'; } + // XXX pass args to `call` + code = 'String(Spacebars.call(' + code + '))'; + parts.push(code); + break; + default: + throw new Error("Unknown stache tag type: " + tag.type); } + } }); if (isReactive) { @@ -710,12 +732,14 @@ Spacebars.compile = function (inputString) { var value = kv.nodeValue; if ((typeof name) !== 'string') { dynamicAttrs = (dynamicAttrs || []); - dynamicAttrs.push([interpolate(name, funcInfo), - interpolate(value, funcInfo)]); + dynamicAttrs.push([interpolate(name, funcInfo, + INTERPOLATE_DYNAMIC_ATTR), + interpolate(value, funcInfo, + INTERPOLATE_DYNAMIC_ATTR)]); } else { attrs = (attrs || {}); attrs[toJSLiteral(name)] = - interpolate(value, funcInfo); + interpolate(value, funcInfo, INTERPOLATE_ATTR_VALUE); } }); var options = null; @@ -744,7 +768,8 @@ Spacebars.compile = function (inputString) { break; case 'Comment': bodyLines.push('buf.comment(' + - interpolate(t.name, funcInfo) + ');'); + interpolate(t.name, funcInfo, + INTERPOLATE_COMMENT) + ');'); break; case 'DocType': bodyLines.push( diff --git a/packages/spacebars/spacebars_tests.js b/packages/spacebars/spacebars_tests.js index 75fb0a5a7e..9297a1ff04 100644 --- a/packages/spacebars/spacebars_tests.js +++ b/packages/spacebars/spacebars_tests.js @@ -358,9 +358,3 @@ Tinytest.add("spacebars - compiler", function (test) { ' buf.openTag("a", {"foo": function () { return String(Spacebars.call(Spacebars.index(self.lookup("bar"), "baz"))); }});', '}'); }); - -// XXXX double and triple stache in various contexts: -// * comment -// * attribute value -// * dynamic attribute -// * regular level diff --git a/packages/ui/renderbuffer.js b/packages/ui/renderbuffer.js index 9f2ef2c70f..60d86f8ab7 100644 --- a/packages/ui/renderbuffer.js +++ b/packages/ui/renderbuffer.js @@ -54,7 +54,11 @@ var updateDOMAttribute = function (component, elemKey, attrName, _.extend(RenderBuffer.prototype, { _encodeEntities: encodeEntities, // XXX implement dynamicAttrs option, - // takes [[k, v], ...] + // takes [[k, v], ...], does something fancy to parse out + // name=value dynamically. For example, `` + // leads to `{ dynamicAttrs: [[attrs(), ''] }`, and each time + // `attrs()` is evaluated, it is tokenized for attribute + // assignments. openTag: function (tagName, attrs, options) { var self = this; From eb040cdff50554117851145b800aaaa969cb237b Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 17 Jun 2013 11:03:27 -0700 Subject: [PATCH 050/938] implement Spacebars.{index,call} --- packages/spacebars/spacebars.js | 48 +++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/packages/spacebars/spacebars.js b/packages/spacebars/spacebars.js index d863a97585..807973e357 100644 --- a/packages/spacebars/spacebars.js +++ b/packages/spacebars/spacebars.js @@ -793,4 +793,52 @@ Spacebars.compile = function (inputString) { }; return tokensToRenderFunc(tree.bodyTokens); +}; + +Spacebars.index = function (value/*, identifiers*/) { + var identifiers = Array.prototype.slice.call(arguments, 1); + + // The object we got `curValue` from by indexing. + // For the value itself, we don't know the appropriate value + // of `this`, so we assume it is already bound. + var nextThis = null; + + _.each(identifiers, function (id) { + if (typeof value === 'function' && + ! UI.isComponentClass(value)) { + // Call a getter -- in `{{foo.bar}}`, call `foo()` if it + // is a function before indexing it with `bar`. + // + // In `{{foo blah=FooComponent.Bar}}`, treat + // `FooComponent` as a non-function. + value = value.call(nextThis); + } + nextThis = value; + if (value) + value = value[id]; + }); + + if (typeof value === 'function' && + ! UI.isComponentClass(value)) { + // bind `this` for resulting function, so it can be + // called with `Spacebars.call`. + value = _.bind(value, nextThis); + } + + return value; +}; + +Spacebars.call = function (value/*, args*/) { + if (typeof value !== 'function' || + UI.isComponentClass(value)) + return value; // ignore args + + var args = Array.prototype.slice.call(arguments, 1); + + // There is a correct value of `this` for any given + // call, but we don't know it here. It must be + // bound to the function in advance (so that `value` + // is actually a wrapper which ignores its `this` + // and supplies one). + return value.apply(null, args); }; \ No newline at end of file From cd8bf66822969c2c52370b83c901e5d33eff04be Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 17 Jun 2013 11:03:49 -0700 Subject: [PATCH 051/938] dummy comp.lookup for testing --- packages/ui/component.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/ui/component.js b/packages/ui/component.js index e697548d99..067c472d39 100644 --- a/packages/ui/component.js +++ b/packages/ui/component.js @@ -532,6 +532,13 @@ _.extend(Component.prototype, { rebuilt: function () {} }); +_.extend(Component.prototype, { + lookup: function (id) { + if (id === "foo") + return "David"; + return null; + } +}); // Require ComponentClass.create(...) instead of // new CompomentClass(...) because a factory method gives From 7eefb313ef70d1bb6dc1045a40f3889e58ef2249 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 17 Jun 2013 11:14:48 -0700 Subject: [PATCH 052/938] more helper support --- packages/spacebars/spacebars.js | 27 ++++++++++++++++++--------- packages/spacebars/spacebars_tests.js | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/packages/spacebars/spacebars.js b/packages/spacebars/spacebars.js index 807973e357..0cfc0dff8a 100644 --- a/packages/spacebars/spacebars.js +++ b/packages/spacebars/spacebars.js @@ -628,6 +628,18 @@ Spacebars.compile = function (inputString) { return parts.join('+'); };*/ + var codeGenBasicStache = function (tag, funcInfo) { + funcInfo.usedSelf = true; + var code = 'self.lookup(' + toJSLiteral(tag.path[0]) + ')'; + if (tag.path.length > 1) { + code = 'Spacebars.index(' + code + ', ' + + _.map(tag.path.slice(1), toJSLiteral).join(', ') + ')'; + } + // XXX pass args to `call` + code = 'String(Spacebars.call(' + code + '))'; + return code; + }; + // Return the source code of a string or (reactive) function // (if necessary). var interpolate = function (strOrArray, funcInfo, interpolateMode) { @@ -655,15 +667,7 @@ Spacebars.compile = function (inputString) { tag.type === 'DOUBLE') throw new Error("Can only have triple-stache for dynamic attributes"); - funcInfo.usedSelf = true; - var code = 'self.lookup(' + toJSLiteral(tag.path[0]) + ')'; - if (tag.path.length > 1) { - code = 'Spacebars.index(' + code + ', ' + - _.map(tag.path.slice(1), toJSLiteral).join(', ') + ')'; - } - // XXX pass args to `call` - code = 'String(Spacebars.call(' + code + '))'; - parts.push(code); + parts.push(codeGenBasicStache(tag, funcInfo)); break; default: throw new Error("Unknown stache tag type: " + tag.type); @@ -712,6 +716,11 @@ Spacebars.compile = function (inputString) { break; case 'DOUBLE': case 'TRIPLE': + bodyLines.push( + 'buf.' + + (tag.type === 'TRIPLE' ? 'rawHtml' : 'text') + + '(' + codeGenBasicStache(tag, funcInfo) + + ');'); // XXX implement break; case 'COMMENT': diff --git a/packages/spacebars/spacebars_tests.js b/packages/spacebars/spacebars_tests.js index 9297a1ff04..a44256e89a 100644 --- a/packages/spacebars/spacebars_tests.js +++ b/packages/spacebars/spacebars_tests.js @@ -357,4 +357,22 @@ Tinytest.add("spacebars - compiler", function (test) { ' var self = this;', ' buf.openTag("a", {"foo": function () { return String(Spacebars.call(Spacebars.index(self.lookup("bar"), "baz"))); }});', '}'); + + run('foo {{bar}} baz', + + 'function (buf) {', + ' var self = this;', + ' buf.text("foo ");', + ' buf.text(String(Spacebars.call(self.lookup("bar"))));', + ' buf.text(" baz");', + '}'); + + run('foo {{{bar}}} baz', + + 'function (buf) {', + ' var self = this;', + ' buf.text("foo ");', + ' buf.rawHtml(String(Spacebars.call(self.lookup("bar"))));', + ' buf.text(" baz");', + '}'); }); From 23ff6fbe0662377af16e1a69ef74138be3919f68 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 17 Jun 2013 12:05:12 -0700 Subject: [PATCH 053/938] reactive double/triple stache code gen --- packages/spacebars/spacebars.js | 66 +++++++++++++++++++++++---- packages/spacebars/spacebars_tests.js | 29 +++++++++++- 2 files changed, 84 insertions(+), 11 deletions(-) diff --git a/packages/spacebars/spacebars.js b/packages/spacebars/spacebars.js index 0cfc0dff8a..1e99b598ae 100644 --- a/packages/spacebars/spacebars.js +++ b/packages/spacebars/spacebars.js @@ -628,15 +628,63 @@ Spacebars.compile = function (inputString) { return parts.join('+'); };*/ - var codeGenBasicStache = function (tag, funcInfo) { + // `path` is an array of at least one string + var codeGenPath = function (path, funcInfo) { funcInfo.usedSelf = true; - var code = 'self.lookup(' + toJSLiteral(tag.path[0]) + ')'; - if (tag.path.length > 1) { + + var code = 'self.lookup(' + toJSLiteral(path[0]) + ')'; + if (path.length > 1) { code = 'Spacebars.index(' + code + ', ' + - _.map(tag.path.slice(1), toJSLiteral).join(', ') + ')'; + _.map(path.slice(1), toJSLiteral).join(', ') + ')'; } - // XXX pass args to `call` - code = 'String(Spacebars.call(' + code + '))'; + + return code; + }; + + var codeGenBasicStache = function (tag, funcInfo) { + var code = codeGenPath(tag.path, funcInfo); + + var options = null; // source -> source + var args = null; // [source] + + _.each(tag.args, function (arg) { + var argType = arg[0]; + var argValue = arg[1]; + + var argCode; + switch (argType) { + case 'STRING': + case 'NUMBER': + case 'BOOLEAN': + case 'NULL': + argCode = toJSLiteral(argValue); + break; + case 'PATH': + argCode = 'Spacebars.call(' + + codeGenPath(argValue, funcInfo) + ')'; + break; + default: + throw new Error("Unexpected arg type: " + argType); + } + + if (arg.length > 2) { + // keyword argument + options = (options || {}); + options[toJSLiteral(arg[2])] = argCode; + } else { + // positional argument + args = (args || []); + args.push(argCode); + } + }); + + if (options) { + args = (args || []); + args.push(makeObjectLiteral(options)); + } + + code = 'String(Spacebars.call(' + code + + (args ? ', ' + args.join(', ') : '') + '))'; return code; }; @@ -719,9 +767,9 @@ Spacebars.compile = function (inputString) { bodyLines.push( 'buf.' + (tag.type === 'TRIPLE' ? 'rawHtml' : 'text') + - '(' + codeGenBasicStache(tag, funcInfo) + - ');'); - // XXX implement + '(function () { return ' + + codeGenBasicStache(tag, funcInfo) + + '; });'); break; case 'COMMENT': break; diff --git a/packages/spacebars/spacebars_tests.js b/packages/spacebars/spacebars_tests.js index a44256e89a..d453e79200 100644 --- a/packages/spacebars/spacebars_tests.js +++ b/packages/spacebars/spacebars_tests.js @@ -363,7 +363,7 @@ Tinytest.add("spacebars - compiler", function (test) { 'function (buf) {', ' var self = this;', ' buf.text("foo ");', - ' buf.text(String(Spacebars.call(self.lookup("bar"))));', + ' buf.text(function () { return String(Spacebars.call(self.lookup("bar"))); });', ' buf.text(" baz");', '}'); @@ -372,7 +372,32 @@ Tinytest.add("spacebars - compiler", function (test) { 'function (buf) {', ' var self = this;', ' buf.text("foo ");', - ' buf.rawHtml(String(Spacebars.call(self.lookup("bar"))));', + ' buf.rawHtml(function () { return String(Spacebars.call(self.lookup("bar"))); });', ' buf.text(" baz");', '}'); + + run('foo {{bar "hello"}} baz', + + 'function (buf) {', + ' var self = this;', + ' buf.text("foo ");', + ' buf.text(function () { return String(Spacebars.call(self.lookup("bar"), "hello")); });', + ' buf.text(" baz");', + '}'); + + run('foo {{bar hello}} baz', + + 'function (buf) {', + ' var self = this;', + ' buf.text("foo ");', + ' buf.text(function () { return String(Spacebars.call(self.lookup("bar"), Spacebars.call(self.lookup("hello")))); });', + ' buf.text(" baz");', + '}'); + + run('{{foo.bar x.y abc=z.w 0 null "hi" z=123.4}}', + + 'function (buf) {', + ' var self = this;', + ' buf.text(function () { return String(Spacebars.call(Spacebars.index(self.lookup("foo"), "bar"), Spacebars.call(Spacebars.index(self.lookup("x"), "y")), 0, null, "hi", {"abc": Spacebars.call(Spacebars.index(self.lookup("z"), "w")), "z": 123.4})); });', + '}'); }); From 630b3b8213739efc4c001207268f7e52409ac29d Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 17 Jun 2013 12:18:01 -0700 Subject: [PATCH 054/938] kill old code --- packages/spacebars/spacebars.js | 68 --------------------------------- 1 file changed, 68 deletions(-) diff --git a/packages/spacebars/spacebars.js b/packages/spacebars/spacebars.js index 1e99b598ae..80f6cc6d30 100644 --- a/packages/spacebars/spacebars.js +++ b/packages/spacebars/spacebars.js @@ -560,74 +560,6 @@ Spacebars.compile = function (inputString) { tree = Spacebars.parse(inputString); } - // XXX rewrite interpolate. - // - // Should now return the source code of either a - // string or a (reactive) function. Ideally it is - // a simple string if possible. - // - // Oh snap, can't do components this way. - // Only double and triple stache. - // Should first write the logic that parses out - // block helpers and inclusions in a run of Characters, - // in tokensToFunc. These tags aren't allowed in an - // interpolation, only double-stache and triple-stache are. - // - // Will need to write the lookup rules for component names, - // helper functions, and values. Maybe it's the same? - // Delegates to `component.lookup(path)`? - // - // We will probably lose the `{{#if equal a b}}` convenience - // syntax (but maybe introduce new syntax for this later). - /*var interpolate = function (strOrArray, indent) { - if (typeof strOrArray === "string") - return toJSLiteral(strOrArray); - - var parts = []; - _.each(strOrArray, function (strOrTagRef) { - if (typeof strOrTagRef === "string") { - parts.push(toJSLiteral(strOrTagRef)); - } else { - var tagOrBlock = strOrTagRef.ref; - if (tagOrBlock.isBlock) { - var block = tagOrBlock; - var openTag = block.openTag; - parts.push('env.blockHelper(' + toJSLiteral(openTag.name) + - ', ' + toJSLiteral(openTag.args) + - ', ' + tokensToFunc(block.bodyTokens, indent) + - (block.elseTag ? ', ' + - tokensToFunc(block.elseTokens, indent) - : '') + ')'); - } else { - var tag = tagOrBlock; - switch (tag.type) { - case 'COMMENT': - // nothing to do - break; - case 'INCLUSION': - parts.push('env.include(' + toJSLiteral(tag.name) + - (tag.args.length ? ', ' +toJSLiteral(tag.args) : '') + - ')'); - break; - case 'DOUBLE': // fall through - case 'TRIPLE': - parts.push('env.' + - (tag.type === 'DOUBLE' ? 'dstache' : 'tstache') + - '(' + toJSLiteral(tag.path) + - (tag.args.length ? ', ' + toJSLiteral(tag.args) : '') + - ')'); - break; - default: - throw new Error("Unknown stache tag type: " + tag.type); - //parts.push('env.tag(' + tagLiteral(tag) + ')'); - } - } - } - }); - - return parts.join('+'); - };*/ - // `path` is an array of at least one string var codeGenPath = function (path, funcInfo) { funcInfo.usedSelf = true; From cbfd6e5f61f417c0d6e7d68b8838aa615d210f5c Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 17 Jun 2013 12:37:16 -0700 Subject: [PATCH 055/938] define EmptyComponent --- examples/unfinished/shark/client/shark.js | 4 ++-- packages/ui/component.js | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/unfinished/shark/client/shark.js b/examples/unfinished/shark/client/shark.js index 1e3633409d..0882a31b84 100644 --- a/examples/unfinished/shark/client/shark.js +++ b/examples/unfinished/shark/client/shark.js @@ -270,7 +270,7 @@ Each = Component.extend({ if (items.empty()) { buf.component(function () { - return (self.getArg('elseClass') || Component).create( + return (self.getArg('elseClass') || EmptyComponent).create( { data: self.getArg('data') }); }, { key: 'else' }); } else { @@ -316,7 +316,7 @@ Each = Component.extend({ var childId = self._itemChildId(id); if (self.items.size() === 0) { // made empty - var elseClass = self.getArg('elseClass') || Component; + var elseClass = self.getArg('elseClass') || EmptyComponent; var comp = elseClass.create({data: self.getArg('data')}); self.replaceChild(childId, comp, 'else'); } else { diff --git a/packages/ui/component.js b/packages/ui/component.js index 067c472d39..6a033261e3 100644 --- a/packages/ui/component.js +++ b/packages/ui/component.js @@ -616,6 +616,10 @@ Component.extend = function (options) { return newClass; }; +// @export EmptyComponent +EmptyComponent = Component.extend({}); +// (might be some optimizations possible in the future) + // @export TextComponent TextComponent = Component.extend({ render: function (buf) { From efa83a516755b2983fff23782ae7c169b6e4e7ba Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 17 Jun 2013 13:10:35 -0700 Subject: [PATCH 056/938] component inclusion code gen --- packages/spacebars/spacebars.js | 51 ++++++++++++++++++++------- packages/spacebars/spacebars_tests.js | 8 +++++ 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/packages/spacebars/spacebars.js b/packages/spacebars/spacebars.js index 80f6cc6d30..5ea9e69de2 100644 --- a/packages/spacebars/spacebars.js +++ b/packages/spacebars/spacebars.js @@ -573,13 +573,13 @@ Spacebars.compile = function (inputString) { return code; }; - var codeGenBasicStache = function (tag, funcInfo) { - var code = codeGenPath(tag.path, funcInfo); - + // returns: array of source strings, or null if no + // args at all + var codeGenArgs = function (tagArgs, funcInfo, forComponent) { var options = null; // source -> source var args = null; // [source] - _.each(tag.args, function (arg) { + _.each(tagArgs, function (arg, i) { var argType = arg[0]; var argValue = arg[1]; @@ -605,19 +605,40 @@ Spacebars.compile = function (inputString) { options[toJSLiteral(arg[2])] = argCode; } else { // positional argument - args = (args || []); - args.push(argCode); + if (forComponent) { + // for Components, only take one positional + // argument, and call it `data` + if (i === 0) { + options = (options || {}); + options[toJSLiteral('data')] = argCode; + } + } else { + args = (args || []); + args.push(argCode); + } } }); - if (options) { - args = (args || []); - args.push(makeObjectLiteral(options)); + if (forComponent) { + // components get one argument, the options dictionary + args = [options ? makeObjectLiteral(options) : '{}']; + } else { + // put options as dictionary at end of args + if (options) { + args = (args || []); + args.push(makeObjectLiteral(options)); + } } - code = 'String(Spacebars.call(' + code + - (args ? ', ' + args.join(', ') : '') + '))'; - return code; + return args; + }; + + var codeGenBasicStache = function (tag, funcInfo) { + var nameCode = codeGenPath(tag.path, funcInfo); + var argCode = codeGenArgs(tag.args, funcInfo); + + return 'String(Spacebars.call(' + nameCode + + (argCode ? ', ' + argCode.join(', ') : '') + '))'; }; // Return the source code of a string or (reactive) function @@ -692,7 +713,11 @@ Spacebars.compile = function (inputString) { } else { switch (tag.type) { case 'INCLUSION': - // XXX implement + var nameCode = codeGenPath(tag.path, funcInfo); + var argCode = + codeGenArgs(tag.args, funcInfo, true); + bodyLines.push('buf.component(function () { return ((' + nameCode + ') || EmptyComponent).create(' + + (argCode ? argCode.join(', ') : '') + '); });'); break; case 'DOUBLE': case 'TRIPLE': diff --git a/packages/spacebars/spacebars_tests.js b/packages/spacebars/spacebars_tests.js index d453e79200..14aa5b6d16 100644 --- a/packages/spacebars/spacebars_tests.js +++ b/packages/spacebars/spacebars_tests.js @@ -400,4 +400,12 @@ Tinytest.add("spacebars - compiler", function (test) { ' var self = this;', ' buf.text(function () { return String(Spacebars.call(Spacebars.index(self.lookup("foo"), "bar"), Spacebars.call(Spacebars.index(self.lookup("x"), "y")), 0, null, "hi", {"abc": Spacebars.call(Spacebars.index(self.lookup("z"), "w")), "z": 123.4})); });', '}'); + + run('{{> foo bar baz=x.y}}', + + 'function (buf) {', + ' var self = this;', + ' buf.component(function () { return ((self.lookup("foo")) || EmptyComponent).create({"data": Spacebars.call(self.lookup("bar")), "baz": Spacebars.call(Spacebars.index(self.lookup("x"), "y"))}); });', + '}'); + }); From 8d92d53bf8bcc4850cbb18c193201a3027f5211e Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 17 Jun 2013 13:46:08 -0700 Subject: [PATCH 057/938] complete Spacebars code gen --- packages/spacebars/spacebars.js | 62 +++++++++++++++++++++------ packages/spacebars/spacebars_tests.js | 42 +++++++++++++++++- 2 files changed, 89 insertions(+), 15 deletions(-) diff --git a/packages/spacebars/spacebars.js b/packages/spacebars/spacebars.js index 5ea9e69de2..cc39dc556b 100644 --- a/packages/spacebars/spacebars.js +++ b/packages/spacebars/spacebars.js @@ -427,14 +427,16 @@ Spacebars.parse = function (inputString) { while (stacheTags[i] !== b.closeTag) i++; } else if (t.type === 'BLOCKCLOSE') { + var name = t.path.join('.'); if (isTopLevel) - throw new Error("Unexpected close tag `" +t.name + "` at " + + throw new Error("Unexpected close tag `" + name + "` at " + prettyOffset(inputString, t.charPos)); - if (t.name !== block.openTag.name) + if (name !== block.openTag.path.join('.')) throw new Error("Close tag at " + prettyOffset(inputString, t.charPos) + - " doesn't match `" + block.openTag.name + - "`, found `" + t.name + "`"); + " doesn't match `" + + block.openTag.path.join('.') + + "`, found `" + name + "`"); block.closeTag = t; } else if (t.type === 'ELSE') { if (isTopLevel) @@ -574,11 +576,19 @@ Spacebars.compile = function (inputString) { }; // returns: array of source strings, or null if no - // args at all - var codeGenArgs = function (tagArgs, funcInfo, forComponent) { + // args at all. + // if forComponentWithOpts is truthy, perform + // component invocation argument handling. + // forComponentWithOpts is a map from name of keyword + // argument to source code. For example, + // `{ bodyClass: "Component.extend(..." }`. + var codeGenArgs = function (tagArgs, funcInfo, + forComponentWithOpts) { var options = null; // source -> source var args = null; // [source] + var forComponent = !! forComponentWithOpts; + _.each(tagArgs, function (arg, i) { var argType = arg[0]; var argValue = arg[1]; @@ -602,7 +612,10 @@ Spacebars.compile = function (inputString) { if (arg.length > 2) { // keyword argument options = (options || {}); - options[toJSLiteral(arg[2])] = argCode; + if (! (forComponentWithOpts && + (arg[2] in forComponentWithOpts))) { + options[toJSLiteral(arg[2])] = argCode; + } } else { // positional argument if (forComponent) { @@ -620,6 +633,11 @@ Spacebars.compile = function (inputString) { }); if (forComponent) { + _.each(forComponentWithOpts, function (v, k) { + options = (options || {}); + options[toJSLiteral(k)] = v; + }); + // components get one argument, the options dictionary args = [options ? makeObjectLiteral(options) : '{}']; } else { @@ -709,15 +727,33 @@ Spacebars.compile = function (inputString) { // tag or block var tag = tagOrStr; if (tag.isBlock) { - // XXX implement + var block = tag; + var nameCode = codeGenPath( + block.openTag.path, funcInfo); + var extraArgs = { + bodyClass: 'Component.extend({render: ' + + tokensToRenderFunc(block.bodyTokens, indent) + + '})' + }; + if (block.elseTokens) { + extraArgs.elseClass = + 'Component.extend({render: ' + + tokensToRenderFunc(block.elseTokens, indent) + + '})'; + } + var argCode = + codeGenArgs(block.openTag.args, funcInfo, + extraArgs)[0]; + bodyLines.push('buf.component(function () { return ((' + nameCode + ') || EmptyComponent).create(' + argCode + + '); });'); } else { switch (tag.type) { case 'INCLUSION': var nameCode = codeGenPath(tag.path, funcInfo); var argCode = - codeGenArgs(tag.args, funcInfo, true); - bodyLines.push('buf.component(function () { return ((' + nameCode + ') || EmptyComponent).create(' + - (argCode ? argCode.join(', ') : '') + '); });'); + codeGenArgs(tag.args, funcInfo, {})[0]; + bodyLines.push('buf.component(function () { return ((' + nameCode + ') || EmptyComponent).create(' + argCode + + '); });'); break; case 'DOUBLE': case 'TRIPLE': @@ -802,8 +838,8 @@ Spacebars.compile = function (inputString) { (bodyLines ? (funcInfo.usedSelf ? '\n' + indent + 'var self = this;' : '') + - '\n' + indent + bodyLines.join('\n' + indent) + '\n' : - '') + '}'; + '\n' + indent + bodyLines.join('\n' + indent) + '\n' + + oldIndent : '') + '}'; }; return tokensToRenderFunc(tree.bodyTokens); diff --git a/packages/spacebars/spacebars_tests.js b/packages/spacebars/spacebars_tests.js index 14aa5b6d16..901f31fa15 100644 --- a/packages/spacebars/spacebars_tests.js +++ b/packages/spacebars/spacebars_tests.js @@ -326,8 +326,24 @@ Tinytest.add("spacebars - compiler", function (test) { var run = function (input/*, expectedLines*/) { var expectedLines = Array.prototype.slice.call(arguments, 1); var expected = expectedLines.join('\n'); - var output = Spacebars.compile(input); - test.equal(output, expected); + if (arguments[1].fail) { + var expectedMessage = arguments[1].fail; + // test for error starting with expectedMessage + var msg = ''; + test.throws(function () { + try { + Spacebars.compile(input); + } catch (e) { + msg = e.message; + throw e; + } + }); + test.equal(msg.slice(0, expectedMessage.length), + expectedMessage); + } else { + var output = Spacebars.compile(input); + test.equal(output, expected); + } }; run('abc', @@ -408,4 +424,26 @@ Tinytest.add("spacebars - compiler", function (test) { ' buf.component(function () { return ((self.lookup("foo")) || EmptyComponent).create({"data": Spacebars.call(self.lookup("bar")), "baz": Spacebars.call(Spacebars.index(self.lookup("x"), "y"))}); });', '}'); + run('{{#foo.bar}}{{/foo.baz}}', {fail: 'Close tag'}); + run('{{/foo.bar}}{{#foo.bar}}', {fail: 'Unexpected close tag'}); + + run('{{#if foo}}bar{{/if}}', + + 'function (buf) {', + ' var self = this;', + ' buf.component(function () { return ((self.lookup("if")) || EmptyComponent).create({"data": Spacebars.call(self.lookup("foo")), "bodyClass": Component.extend({render: function (buf) {', + ' buf.text("bar");', + ' }})}); });', + '}'); + + run('{{#if foo}}bar{{else}}baz{{/if}}', + + 'function (buf) {', + ' var self = this;', + ' buf.component(function () { return ((self.lookup("if")) || EmptyComponent).create({"data": Spacebars.call(self.lookup("foo")), "bodyClass": Component.extend({render: function (buf) {', + ' buf.text("bar");', + ' }}), "elseClass": Component.extend({render: function (buf) {', + ' buf.text("baz");', + ' }})}); });', + '}'); }); From e0816b5f34c401e01260377c997b720f9d6ee9c3 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 17 Jun 2013 14:00:27 -0700 Subject: [PATCH 058/938] handle falsy helper result --- packages/spacebars/spacebars.js | 2 +- packages/spacebars/spacebars_tests.js | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/spacebars/spacebars.js b/packages/spacebars/spacebars.js index cc39dc556b..7d67e3981f 100644 --- a/packages/spacebars/spacebars.js +++ b/packages/spacebars/spacebars.js @@ -656,7 +656,7 @@ Spacebars.compile = function (inputString) { var argCode = codeGenArgs(tag.args, funcInfo); return 'String(Spacebars.call(' + nameCode + - (argCode ? ', ' + argCode.join(', ') : '') + '))'; + (argCode ? ', ' + argCode.join(', ') : '') + ') || "")'; }; // Return the source code of a string or (reactive) function diff --git a/packages/spacebars/spacebars_tests.js b/packages/spacebars/spacebars_tests.js index 901f31fa15..b90295fd6d 100644 --- a/packages/spacebars/spacebars_tests.js +++ b/packages/spacebars/spacebars_tests.js @@ -364,14 +364,14 @@ Tinytest.add("spacebars - compiler", function (test) { 'function (buf) {', ' var self = this;', - ' buf.openTag("a", {"foo": function () { return String(Spacebars.call(self.lookup("bar"))); }});', + ' buf.openTag("a", {"foo": function () { return String(Spacebars.call(self.lookup("bar")) || ""); }});', '}'); run('', 'function (buf) {', ' var self = this;', - ' buf.openTag("a", {"foo": function () { return String(Spacebars.call(Spacebars.index(self.lookup("bar"), "baz"))); }});', + ' buf.openTag("a", {"foo": function () { return String(Spacebars.call(Spacebars.index(self.lookup("bar"), "baz")) || ""); }});', '}'); run('foo {{bar}} baz', @@ -379,7 +379,7 @@ Tinytest.add("spacebars - compiler", function (test) { 'function (buf) {', ' var self = this;', ' buf.text("foo ");', - ' buf.text(function () { return String(Spacebars.call(self.lookup("bar"))); });', + ' buf.text(function () { return String(Spacebars.call(self.lookup("bar")) || ""); });', ' buf.text(" baz");', '}'); @@ -388,7 +388,7 @@ Tinytest.add("spacebars - compiler", function (test) { 'function (buf) {', ' var self = this;', ' buf.text("foo ");', - ' buf.rawHtml(function () { return String(Spacebars.call(self.lookup("bar"))); });', + ' buf.rawHtml(function () { return String(Spacebars.call(self.lookup("bar")) || ""); });', ' buf.text(" baz");', '}'); @@ -397,7 +397,7 @@ Tinytest.add("spacebars - compiler", function (test) { 'function (buf) {', ' var self = this;', ' buf.text("foo ");', - ' buf.text(function () { return String(Spacebars.call(self.lookup("bar"), "hello")); });', + ' buf.text(function () { return String(Spacebars.call(self.lookup("bar"), "hello") || ""); });', ' buf.text(" baz");', '}'); @@ -406,7 +406,7 @@ Tinytest.add("spacebars - compiler", function (test) { 'function (buf) {', ' var self = this;', ' buf.text("foo ");', - ' buf.text(function () { return String(Spacebars.call(self.lookup("bar"), Spacebars.call(self.lookup("hello")))); });', + ' buf.text(function () { return String(Spacebars.call(self.lookup("bar"), Spacebars.call(self.lookup("hello"))) || ""); });', ' buf.text(" baz");', '}'); @@ -414,7 +414,7 @@ Tinytest.add("spacebars - compiler", function (test) { 'function (buf) {', ' var self = this;', - ' buf.text(function () { return String(Spacebars.call(Spacebars.index(self.lookup("foo"), "bar"), Spacebars.call(Spacebars.index(self.lookup("x"), "y")), 0, null, "hi", {"abc": Spacebars.call(Spacebars.index(self.lookup("z"), "w")), "z": 123.4})); });', + ' buf.text(function () { return String(Spacebars.call(Spacebars.index(self.lookup("foo"), "bar"), Spacebars.call(Spacebars.index(self.lookup("x"), "y")), 0, null, "hi", {"abc": Spacebars.call(Spacebars.index(self.lookup("z"), "w")), "z": 123.4}) || ""); });', '}'); run('{{> foo bar baz=x.y}}', From db36afc1a4ef43da8576e74736c86c587a443be1 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 17 Jun 2013 14:49:51 -0700 Subject: [PATCH 059/938] fix typos; If/Each; make shark example work --- examples/unfinished/shark/client/shark.html | 3 + examples/unfinished/shark/client/shark.js | 200 +------------------- packages/spacebars/package.js | 1 + packages/ui/component.js | 40 +++- packages/ui/library.js | 193 +++++++++++++++++++ packages/ui/package.js | 7 +- 6 files changed, 245 insertions(+), 199 deletions(-) create mode 100644 packages/ui/library.js diff --git a/examples/unfinished/shark/client/shark.html b/examples/unfinished/shark/client/shark.html index 0f4947b0c9..97aa8c8a0b 100644 --- a/examples/unfinished/shark/client/shark.html +++ b/examples/unfinished/shark/client/shark.html @@ -3,4 +3,7 @@ +{{#each items}} +
    {{text}}
    +{{/each}} diff --git a/examples/unfinished/shark/client/shark.js b/examples/unfinished/shark/client/shark.js index 0882a31b84..59fb7aff98 100644 --- a/examples/unfinished/shark/client/shark.js +++ b/examples/unfinished/shark/client/shark.js @@ -1,23 +1,17 @@ -IfComponent = Component.extend({ - arguments: { - positional: ['condition'], - required: ['bodyClass'] - }, - render: function (buf) { - if (this.getArg('condition')) - buf.component(this.getArg('bodyClass').create()); - else if (this.getArg('elseClass')) - buf.component(this.getArg('elseClass')); - } -}); Items = new Meteor.Collection(null); Items.insert({ text: 'Foo' }); Items.insert({ text: 'Bar' }); Items.insert({ text: 'Baz' }); +Body = RootComponent.extend({ + items: function () { + return Items.find({}, { sort: { text: 1 }}); + } +}); + Meteor.startup(function () { /* RC = RootComponent.create({ @@ -43,6 +37,7 @@ Meteor.startup(function () { }) });*/ + /* RC = RootComponent.create({ bodyClass: Component.extend({ render: function (buf) { @@ -54,13 +49,13 @@ Meteor.startup(function () { buf.closeTag('div'); } }), - list: Items.find({}, { sort: { text: 1 }}) + list: Items.find({}, { sort: { text: 1 }}) }), { key: 'body' }); } }) }); - RC.attach(document.body); + RC.attach(document.body);*/ Items.insert({ text: 'Qux' }); Items.remove({ text: 'Foo' }); @@ -161,31 +156,6 @@ UL = DebugComponent.extend({ }); */ -// Function equal to LocalCollection._idStringify, or the identity -// function if we don't have LiveData. Converts item keys (i.e. DDP -// keys) to strings for storage in an OrderedDict. -var idStringify; - -if (typeof LocalCollection !== 'undefined') { - idStringify = function (id) { - if (id === null) - return id; - else - return LocalCollection._idStringify(id); - }; -} else { - idStringify = function (id) { return id; }; -} - -// XXX duplicated code from minimongo.js. It's small though. -var applyChanges = function (doc, changeFields) { - _.each(changeFields, function (value, key) { - if (value === undefined) - delete doc[key]; - else - doc[key] = value; - }); -}; /*EmptyComponent = Component.extend({ build: function (frag) { @@ -198,158 +168,6 @@ var applyChanges = function (doc, changeFields) { } });*/ -Each = Component.extend({ - - // XXX what is init() good for if render lets you reactively - // depend on args, but init doesn't? (you can access them - // but your code only ever runs once) - - render: function (buf) { - var self = this; - // XXX support arrays too. - // For now, we assume `list` is a database cursor. - var cursor = self.getArg('list'); - - // OrderedDict from id string or object (which is - // stringified internally by the dict) to untransformed - // document. - self.items = new OrderedDict(idStringify); - var items = self.items; - - // Templates should have access to data and methods added by the - // transformer, but observeChanges doesn't transform, so we have to do - // it here. - // - // NOTE: this is a little bit of an abstraction violation. Ideally, - // the only thing we should know about Minimongo is the contract of - // observeChanges. In theory, we could allow anything that implements - // observeChanges to be passed to us. - var transformedDoc = function (doc) { - if (cursor.getTransform && cursor.getTransform()) - return cursor.getTransform()(EJSON.clone(doc)); - return doc; - }; - - // because we're in render(), rebuild or destroy will - // stop this handle. - self.cursorHandle = cursor.observeChanges({ - addedBefore: function (id, item, beforeId) { - var doc = EJSON.clone(item); - doc._id = id; - items.putBefore(id, doc, beforeId); - - if (self.stage === Component.BUILT) { - var tdoc = transformedDoc(doc); - self.itemAddedBefore(id, tdoc, beforeId); - } - }, - removed: function (id) { - items.remove(id); - - if (self.stage === Component.BUILT) - self.itemRemoved(id); - }, - movedBefore: function (id, beforeId) { - items.moveBefore(id, beforeId); - - if (self.stage === Component.BUILT) - self.itemMovedBefore(id, beforeId); - }, - changed: function (id, fields) { - var doc = items.get(id); - if (! doc) - throw new Error("Unknown id for changed: " + idStringify(id)); - applyChanges(doc, fields); - - if (self.stage === Component.BUILT) { - var tdoc = transformedDoc(doc); - self.itemChanged(id, tdoc); - } - } - }); - - if (items.empty()) { - buf.component(function () { - return (self.getArg('elseClass') || EmptyComponent).create( - { data: self.getArg('data') }); - }, { key: 'else' }); - } else { - items.forEach(function (doc, id) { - var tdoc = transformedDoc(doc); - - buf.component(function () { - return self.getArg('bodyClass').create({ data: tdoc }); - }, { key: self._itemChildId(id) }); - }); - } - }, - - _itemChildId: function (id) { - return 'item:' + idStringify(id); - }, - itemAddedBefore: function (id, doc, beforeId) { - var self = this; - if (self.stage !== Component.BUILT) - throw new Error("Component must be built"); - - var childId = self._itemChildId(id); - var comp = self.getArg('bodyClass').create({data: doc}); - - if (self.items.size() === 1) { - // was empty - self.replaceChild('else', comp, childId); - } else { - var beforeNode = - (beforeId ? - self.children[self._itemChildId(beforeId)].firstNode() : - (self.lastNode().nextSibling || null)); - var parentNode = self.parentNode(); - - self.addChild(childId, comp, parentNode, beforeNode); - } - }, - itemRemoved: function (id) { - var self = this; - if (self.stage !== Component.BUILT) - throw new Error("Component must be built"); - - var childId = self._itemChildId(id); - if (self.items.size() === 0) { - // made empty - var elseClass = self.getArg('elseClass') || EmptyComponent; - var comp = elseClass.create({data: self.getArg('data')}); - self.replaceChild(childId, comp, 'else'); - } else { - self.removeChild(childId); - } - }, - itemMovedBefore: function (id, beforeId) { - var self = this; - if (self.stage !== Component.BUILT) - throw new Error("Component must be built"); - - if (self.items.size() === 1) - return; // move is meaningless anyway - - var comp = self.children[self._itemChildId(id)]; - - var beforeNode = - (beforeId ? - self.children[self._itemChildId(beforeId)].firstNode() : - (self.lastNode().nextSibling || null)); - var parentNode = self.parentNode(); - - comp.detach(); - comp.attach(parentNode, beforeNode); - }, - itemChanged: function (id, doc) { - var self = this; - if (self.stage !== Component.BUILT) - throw new Error("Component must be built"); - - self.children[self._itemChildId(id)].update({data: doc}); - } -}); /*MyLI = DebugComponent.extend({ init: function () { diff --git a/packages/spacebars/package.js b/packages/spacebars/package.js index f68316b9de..55fec49a81 100644 --- a/packages/spacebars/package.js +++ b/packages/spacebars/package.js @@ -9,6 +9,7 @@ Package.on_use(function (api, where) { api.use('underscore', where); api.use('jsparse', where); api.use('html5-tokenizer', where); + api.use('ui'); api.add_files(['spacebars.js'], where); }); diff --git a/packages/ui/component.js b/packages/ui/component.js index 6a033261e3..0207867364 100644 --- a/packages/ui/component.js +++ b/packages/ui/component.js @@ -2,8 +2,8 @@ UI = { isComponentClass: function (value) { return (typeof value === 'function') && - (value === Component || - (value.protoype instanceof Component)); + ((value === Component) || + (value.prototype instanceof Component)); }, // Generated templates are put here. This object must // be populated before any Components are actually @@ -72,11 +72,13 @@ Component = function (args) { // this way? _.each(UI._templates, function (v, k) { if (UI.isComponentClass(global[k])) { - if (k.prototype.hasOwnProperty('render')) + var cls = global[k]; + if (cls.prototype.hasOwnProperty('render') && + cls.prototype.render !== v) throw new Error( 'Component "' + k + '" has both a render method ' + 'implementation and a template of that name'); - k.prototype.render = v; + cls.prototype.render = v; } }); templatesAssigned = true; @@ -534,9 +536,33 @@ _.extend(Component.prototype, { _.extend(Component.prototype, { lookup: function (id) { - if (id === "foo") - return "David"; - return null; + var self = this; + + var result = null; + var thisToBind = null; + var data; + + if (id in self) { + result = self[id]; + thisToBind = self; + } else if (id === 'if') { + result = If; + } else if (id === 'each') { + result = Each; + } else if (id in global) { + result = global[id]; + thisToBind = self.getArg('data') || null; + } else if ((data = self.getArg('data'))) { + thisToBind = data; + result = data[id]; + } + + if (thisToBind && + typeof result === 'function' && + ! UI.isComponentClass(result)) + return _.bind(result, thisToBind); + + return result; } }); diff --git a/packages/ui/library.js b/packages/ui/library.js new file mode 100644 index 0000000000..6a896bfb4e --- /dev/null +++ b/packages/ui/library.js @@ -0,0 +1,193 @@ +// @export If +If = Component.extend({ + init: function () { + if (! this.getArg('bodyClass')) + throw new Error("If requires a body"); + }, + render: function (buf) { + if (this.getArg('data')) + buf.component(this.getArg('bodyClass').create()); + else if (this.getArg('elseClass')) + buf.component(this.getArg('elseClass')); + } +}); + +// @export Each +Each = Component.extend({ + + // XXX what is init() good for if render lets you reactively + // depend on args, but init doesn't? (you can access them + // but your code only ever runs once) + + render: function (buf) { + var self = this; + // XXX support arrays too. + // For now, we assume the data is a database cursor. + var cursor = self.getArg('data'); + + // OrderedDict from id string or object (which is + // stringified internally by the dict) to untransformed + // document. + self.items = new OrderedDict(idStringify); + var items = self.items; + + // Templates should have access to data and methods added by the + // transformer, but observeChanges doesn't transform, so we have to do + // it here. + // + // NOTE: this is a little bit of an abstraction violation. Ideally, + // the only thing we should know about Minimongo is the contract of + // observeChanges. In theory, we could allow anything that implements + // observeChanges to be passed to us. + var transformedDoc = function (doc) { + if (cursor.getTransform && cursor.getTransform()) + return cursor.getTransform()(EJSON.clone(doc)); + return doc; + }; + + // because we're in render(), rebuild or destroy will + // stop this handle. + self.cursorHandle = cursor.observeChanges({ + addedBefore: function (id, item, beforeId) { + var doc = EJSON.clone(item); + doc._id = id; + items.putBefore(id, doc, beforeId); + + if (self.stage === Component.BUILT) { + var tdoc = transformedDoc(doc); + self.itemAddedBefore(id, tdoc, beforeId); + } + }, + removed: function (id) { + items.remove(id); + + if (self.stage === Component.BUILT) + self.itemRemoved(id); + }, + movedBefore: function (id, beforeId) { + items.moveBefore(id, beforeId); + + if (self.stage === Component.BUILT) + self.itemMovedBefore(id, beforeId); + }, + changed: function (id, fields) { + var doc = items.get(id); + if (! doc) + throw new Error("Unknown id for changed: " + idStringify(id)); + applyChanges(doc, fields); + + if (self.stage === Component.BUILT) { + var tdoc = transformedDoc(doc); + self.itemChanged(id, tdoc); + } + } + }); + + if (items.empty()) { + buf.component(function () { + return (self.getArg('elseClass') || EmptyComponent).create( + { data: self.getArg('data') }); + }, { key: 'else' }); + } else { + items.forEach(function (doc, id) { + var tdoc = transformedDoc(doc); + + buf.component(function () { + return self.getArg('bodyClass').create({ data: tdoc }); + }, { key: self._itemChildId(id) }); + }); + } + }, + + _itemChildId: function (id) { + return 'item:' + idStringify(id); + }, + itemAddedBefore: function (id, doc, beforeId) { + var self = this; + if (self.stage !== Component.BUILT) + throw new Error("Component must be built"); + + var childId = self._itemChildId(id); + var comp = self.getArg('bodyClass').create({data: doc}); + + if (self.items.size() === 1) { + // was empty + self.replaceChild('else', comp, childId); + } else { + var beforeNode = + (beforeId ? + self.children[self._itemChildId(beforeId)].firstNode() : + (self.lastNode().nextSibling || null)); + var parentNode = self.parentNode(); + + self.addChild(childId, comp, parentNode, beforeNode); + } + }, + itemRemoved: function (id) { + var self = this; + if (self.stage !== Component.BUILT) + throw new Error("Component must be built"); + + var childId = self._itemChildId(id); + if (self.items.size() === 0) { + // made empty + var elseClass = self.getArg('elseClass') || EmptyComponent; + var comp = elseClass.create({data: self.getArg('data')}); + self.replaceChild(childId, comp, 'else'); + } else { + self.removeChild(childId); + } + }, + itemMovedBefore: function (id, beforeId) { + var self = this; + if (self.stage !== Component.BUILT) + throw new Error("Component must be built"); + + if (self.items.size() === 1) + return; // move is meaningless anyway + + var comp = self.children[self._itemChildId(id)]; + + var beforeNode = + (beforeId ? + self.children[self._itemChildId(beforeId)].firstNode() : + (self.lastNode().nextSibling || null)); + var parentNode = self.parentNode(); + + comp.detach(); + comp.attach(parentNode, beforeNode); + }, + itemChanged: function (id, doc) { + var self = this; + if (self.stage !== Component.BUILT) + throw new Error("Component must be built"); + + self.children[self._itemChildId(id)].update({data: doc}); + } +}); + +// Function equal to LocalCollection._idStringify, or the identity +// function if we don't have LiveData. Converts item keys (i.e. DDP +// keys) to strings for storage in an OrderedDict. +var idStringify; + +if (typeof LocalCollection !== 'undefined') { + idStringify = function (id) { + if (id === null) + return id; + else + return LocalCollection._idStringify(id); + }; +} else { + idStringify = function (id) { return id; }; +} + +// XXX duplicated code from minimongo.js. It's small though. +var applyChanges = function (doc, changeFields) { + _.each(changeFields, function (value, key) { + if (value === undefined) + delete doc[key]; + else + doc[key] = value; + }); +}; diff --git a/packages/ui/package.js b/packages/ui/package.js index 92f31119d4..e5222ea381 100644 --- a/packages/ui/package.js +++ b/packages/ui/package.js @@ -8,8 +8,13 @@ Package.on_use(function (api) { api.use('domutils'); api.use('underscore', 'client'); api.use('ejson', 'client'); + api.use('ordered-dict', 'client'); - api.add_files(['chunk.js', 'component.js', 'renderbuffer.js'], + // LocalCollection; weak dependency: + api.use('minimongo', 'client'); + + api.add_files(['chunk.js', 'component.js', 'renderbuffer.js', + 'library.js'], 'client'); }); From 8ffffaaeae624d2ee57d1b5b5500cea4f3cc5eda Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 17 Jun 2013 14:58:35 -0700 Subject: [PATCH 060/938] fix typo in replaceChild --- examples/unfinished/shark/client/shark.html | 2 ++ packages/ui/component.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/unfinished/shark/client/shark.html b/examples/unfinished/shark/client/shark.html index 97aa8c8a0b..41fd12222b 100644 --- a/examples/unfinished/shark/client/shark.html +++ b/examples/unfinished/shark/client/shark.html @@ -5,5 +5,7 @@ {{#each items}}
    {{text}}
    +{{else}} + Empty {{/each}} diff --git a/packages/ui/component.js b/packages/ui/component.js index 0207867364..d3d803d28b 100644 --- a/packages/ui/component.js +++ b/packages/ui/component.js @@ -505,7 +505,7 @@ _.extend(Component.prototype, { oldChild.constructor === newChild.constructor) { oldChild.update(newChild._args); } else if (this.stage !== Component.BUILT || - oldChild !== Component.BUILT || + oldChild.stage !== Component.BUILT || ! oldChild.isAttached) { this.removeChild(key); this.addChild(newKey, newChild); From 3d8bcef5ac03b8d33fc5ce44719a95c9a03add7c Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 17 Jun 2013 16:35:23 -0700 Subject: [PATCH 061/938] Components can gen "preview HTML" for ss rendering --- packages/ui/component.js | 10 +++++++++ packages/ui/library.js | 4 +++- packages/ui/renderbuffer.js | 42 ++++++++++++++++++++++++++++++------- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/packages/ui/component.js b/packages/ui/component.js index d3d803d28b..da21f182da 100644 --- a/packages/ui/component.js +++ b/packages/ui/component.js @@ -289,6 +289,12 @@ _.extend(Component.prototype, { var beforeNode = toDetach.lastNode().nextSibling; toDetach.detach(true); toAttach.attach(parentNode, beforeNode); + }, + getPreviewHtml: function () { + this._requireStage(Component.ADDED); + var buf = new RenderBuffer(this, { preview: true }); + this.render(buf); + return buf.getFullHtml(); } }); @@ -542,6 +548,10 @@ _.extend(Component.prototype, { var thisToBind = null; var data; + // XXX figure out what this should really do, + // and how custom component classes should + // hook into this behavior. + if (id in self) { result = self[id]; thisToBind = self; diff --git a/packages/ui/library.js b/packages/ui/library.js index 6a896bfb4e..363fc8f8f0 100644 --- a/packages/ui/library.js +++ b/packages/ui/library.js @@ -171,6 +171,8 @@ Each = Component.extend({ // keys) to strings for storage in an OrderedDict. var idStringify; +// XXX not clear if this is the right way to do a weak dependency +// now, on the linker branch if (typeof LocalCollection !== 'undefined') { idStringify = function (id) { if (id === null) @@ -182,7 +184,7 @@ if (typeof LocalCollection !== 'undefined') { idStringify = function (id) { return id; }; } -// XXX duplicated code from minimongo.js. It's small though. +// XXX duplicated code from minimongo.js. var applyChanges = function (doc, changeFields) { _.each(changeFields, function (value, key) { if (value === undefined) diff --git a/packages/ui/renderbuffer.js b/packages/ui/renderbuffer.js index 60d86f8ab7..315050f571 100644 --- a/packages/ui/renderbuffer.js +++ b/packages/ui/renderbuffer.js @@ -1,8 +1,20 @@ +// RenderBuffer is a friend class of Component that provides the +// API for implementations of comp.render(buf) and knows how to +// buffer HTML and then optionally wire it up as reactive DOM. +// +// Each Component creates its own instance of RenderBuffer during +// render (i.e. build or server-side HTML generation). + // @export RenderBuffer -RenderBuffer = function (component) { +RenderBuffer = function (component, options) { this._component = component; + if (! (component instanceof Component)) + throw new Error("Component required as first argument"); + this._htmlBuf = []; + this._isPreview = !! (options && options.preview); + this._builderId = Random.id(); this._nextNum = 1; this._elementNextNums = {}; @@ -94,6 +106,8 @@ _.extend(RenderBuffer.prototype, { var newValue = attrValue(); initialValue = newValue; c.oldValue = newValue; + if (self._isPreview) + c.stop(); } else { var newValue = attrValue(); var comp = self._component; @@ -114,7 +128,7 @@ _.extend(RenderBuffer.prototype, { buf.push('"'); }); - if (isElementReactive) { + if (isElementReactive && ! self._isPreview) { if (! elementKey) { if (! this._elementNextNums[tagName]) this._elementNextNums[tagName] = 1; @@ -174,11 +188,16 @@ _.extend(RenderBuffer.prototype, { var childComp = self._component.addChild( childKey, componentOrFunction); - var commentString = self.builderId + '_' + - (self._nextNum++); - self._htmlBuf.push(''); + if (self._isPreview) { + self._htmlBuf.push( + childComp.getPreviewHtml()); + } else { + var commentString = self.builderId + '_' + + (self._nextNum++); + self._htmlBuf.push(''); - self._childrenToAttach[commentString] = childComp; + self._childrenToAttach[commentString] = childComp; + } }, comment: function (stringOrFunction) { // XXX making comments reactively update seems @@ -210,6 +229,9 @@ _.extend(RenderBuffer.prototype, { build: function () { var self = this; + if (self._isPreview) + throw new Error("Can't build preview HTML as DOM"); + var html = self._htmlBuf.join(''); var frag = DomUtils.htmlToFragment(html); if (! frag.firstChild) @@ -242,7 +264,7 @@ _.extend(RenderBuffer.prototype, { if (elemKey) self._component.registerElement(elemKey, n); - // recurse + // recurse through DOM wireUpDOM(n); } n = next; @@ -256,5 +278,11 @@ _.extend(RenderBuffer.prototype, { start: start, end: end }; + }, + getFullHtml: function () { + if (! this._isPreview) + throw new Error("Can only get full HTML when previewing"); + + return this._htmlBuf.join(''); } }); From de5b3b7b1ff07ed786341a265fe470942d142be5 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 17 Jun 2013 17:13:27 -0700 Subject: [PATCH 062/938] clean up old app code --- examples/unfinished/shark/client/shark.js | 190 ---------------------- 1 file changed, 190 deletions(-) diff --git a/examples/unfinished/shark/client/shark.js b/examples/unfinished/shark/client/shark.js index 59fb7aff98..1449a5fd48 100644 --- a/examples/unfinished/shark/client/shark.js +++ b/examples/unfinished/shark/client/shark.js @@ -13,197 +13,7 @@ Body = RootComponent.extend({ }); Meteor.startup(function () { - /* - RC = RootComponent.create({ - bodyClass: Component.extend({ - render: function (buf) { - buf.text(Session.get('foo') || ''); - } - }) - }); - */ - /*RC = RootComponent.create({ - bodyClass: Component.extend({ - render: function (buf) { - buf.openTag('span', {style: function () { - return 'background-color:' + - Session.get('bgcolor'); - }}); - buf.text(function () { - return Session.get('foo') || ''; - }); - buf.closeTag('span'); - } - }) - });*/ - - /* - RC = RootComponent.create({ - bodyClass: Component.extend({ - render: function (buf) { - buf.component(Each.create({ - bodyClass: Component.extend({ - render: function (buf) { - buf.openTag('div'); - buf.text(this.getArg('data').text || ''); - buf.closeTag('div'); - } - }), - list: Items.find({}, { sort: { text: 1 }}) - }), { key: 'body' }); - } - }) - }); - - RC.attach(document.body);*/ - Items.insert({ text: 'Qux' }); Items.remove({ text: 'Foo' }); Items.update({ text: 'Bar' }, { text: 'Car' }); }); - - - -/*var debug = function (method, component) { - console.log(method, component.nameInParent); -}; - -// Utility to HTML-escape a string. -var escapeForHtml = (function() { - var escape_map = { - "<": "<", - ">": ">", - '"': """, - "'": "'", - "`": "`", // IE allows backtick-delimited attributes?? - "&": "&" - }; - var escape_one = function(c) { - return escape_map[c]; - }; - - return function (x) { - return x.replace(/[&<>"'`]/g, escape_one); - }; -})(); - -DebugComponent = Component.extend({ - init: function () { debug('init', this); }, - build: function (frag) { debug('build', this); }, - built: function () { debug('built', this); }, - attached: function () { debug('attached', this); }, - detached: function () { debug('detached', this); }, - destroyed: function () { debug('destroyed', this); }, - updated: function (args, oldArgs) { debug('updated', this); } -}); - -LI = DebugComponent.extend({ - build: function (frag) { - var li = document.createElement('LI'); - li.appendChild(document.createTextNode(this.getArg('text'))); - frag.appendChild(li); - this.setBounds(li); - this.textNode = li.firstChild; - }, - updated: function (args, oldArgs) { - if (this.isBuilt) - this.textNode.nodeValue = args.text; - }, - toHtml: function () { - return "
  • " + escapeForHtml(this.getArg('text')) + "
  • "; - } -}); - -UL = DebugComponent.extend({ - init: function () { - this.addChild(1, new LI({text: 'One'})); - this.addChild(2, new LI({text: 'Two'})); - this.addChild(3, new LI({text: 'Three'})); - this.numItems = 3; - }, - build: function (frag) { - var ul = document.createElement('UL'); - this.children[1].attach(ul); - this.children[2].attach(ul); - this.children[3].attach(ul); - frag.appendChild(ul); - this.setBounds(ul); - - var self = this; - self.timer = setInterval(function () { - if (self.isDestroyed || self.numItems >= 10) { - debug('stopping timer', self); - clearInterval(self.timer); - return; - } - var newItem = new LI({text: 'Another'}); - self.addChild(++self.numItems, newItem); - newItem.attach(ul); - - var hr = document.createElement('HR'); - self.parentNode().insertBefore( - hr, self.lastNode().nextSibling); - self.setBounds(ul, hr); - }, 2000); - }, - toHtml: function () { - return "
      " + - this.children[1].toHtml() + - this.children[2].toHtml() + - this.children[3].toHtml() + - "
    "; - } -}); -*/ - - -/*EmptyComponent = Component.extend({ - build: function (frag) { - var comment = document.createComment('empty'); - frag.appendChild(comment); - this.setBounds(comment, comment); - }, - toHtml: function () { - return ''; - } -});*/ - - -/*MyLI = DebugComponent.extend({ - init: function () { - this.setChild('1', LI, {text: this.getArg('data').text || ''}); - }, - build: function (frag) { - var c = this.children['1']; - c.attach(frag); - this.setBounds(c); - }, - updated: function (args, oldArgs) { - this.init(); // XXX not necessarily the right pattern - }, - toHtml: function () { - return this.children['1'].toHtml(); - } -}); - -Meteor.startup(function () { -// a = new Chunk($("li").get(0)); -// b = new Chunk($("li").get(1)); -// c = new Chunk($("li").get(2)); -// d = new Chunk(a, c); - -// L = new UL().attach(document.body); - - C = new LocalCollection(); - var ul = document.createElement("UL"); - document.body.appendChild(ul); - - C.insert({text: 'Foo'}); - C.insert({text: 'Bar'}); - C.insert({text: 'Baz'}); - LIST = new Each({list: C.find({}, {sort: {text: 1}}), - bodyClass: MyLI}); - - LIST.attach(ul); -}); -*/ \ No newline at end of file From 49d5d8feab482e2f516acc410a736c2aa4825aa0 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 17 Jun 2013 17:17:31 -0700 Subject: [PATCH 063/938] track attached RootComponents --- packages/ui/component.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/ui/component.js b/packages/ui/component.js index da21f182da..00542d53ef 100644 --- a/packages/ui/component.js +++ b/packages/ui/component.js @@ -670,14 +670,33 @@ RawHtmlComponent = Component.extend({ } }); +// A RootComponent is the root of its Component tree in terms +// of parent/child relationships. It's the only kind of +// component that can function without actually being added +// to another component first. + // @export RootComponent RootComponent = Component.extend({ constructed: function () { + // skip the UNADDED phase completely this.stage = Component.ADDED; + + this._uid = Random.id(); }, render: function (buf) { var bodyClass = this.getArg('bodyClass'); if (bodyClass) buf.component(bodyClass.create(), {key: 'body'}); + }, + attached: function () { + RootComponent._attachedInstances[this._uid] = this; + }, + detached: function () { + delete RootComponent._attachedInstances[this._uid]; + }, + destroyed: function () { + delete RootComponent._attachedInstances[this._uid]; } }); + +RootComponent._attachedInstances = {}; \ No newline at end of file From dc75e8877bdc31641118c88362c66b5d4034ad4c Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Thu, 13 Jun 2013 11:11:51 -0700 Subject: [PATCH 064/938] use spacebars for templating in build --- packages/templating/deftemplate.js | 2 ++ packages/templating/package.js | 19 +++++++++---------- packages/templating/plugin/html_scanner.js | 13 ++++++------- tools/packages.js | 3 ++- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/packages/templating/deftemplate.js b/packages/templating/deftemplate.js index 0ea4896f85..4b635824ed 100644 --- a/packages/templating/deftemplate.js +++ b/packages/templating/deftemplate.js @@ -1,3 +1,4 @@ +/* // @export Template Template = {}; @@ -205,3 +206,4 @@ Meteor._def_template = function (name, raw_func) { // useful for unnamed templates, like body return partial; }; +*/ \ No newline at end of file diff --git a/packages/templating/package.js b/packages/templating/package.js index 100f519fc8..5962948b17 100644 --- a/packages/templating/package.js +++ b/packages/templating/package.js @@ -10,7 +10,7 @@ Package.describe({ Package._transitional_registerBuildPlugin({ name: "compileTemplates", - use: ['underscore', 'handlebars'], + use: ['underscore', 'spacebars'], sources: [ 'plugin/html_scanner.js', 'plugin/compile-templates.js' @@ -21,10 +21,10 @@ Package.on_use(function (api) { // XXX would like to do the following only when the first html file // is encountered - api.use(['underscore', 'spark', 'handlebars'], 'client'); + api.use(['underscore', 'ui', 'spacebars'], 'client'); // provides the runtime logic to instantiate our templates - api.add_files('deftemplate.js', 'client'); + //api.add_files('deftemplate.js', 'client'); // html_scanner.js emits client code that calls Meteor.startup api.use('startup', 'client'); @@ -34,17 +34,16 @@ Package.on_test(function (api) { api.use('tinytest'); api.use('htmljs'); api.use('templating'); - api.use('handlebars'); api.use('underscore'); api.use(['test-helpers', 'domutils', 'session', 'deps', 'spark', 'minimongo'], 'client'); - api.use('handlebars', 'server'); - api.add_files([ - 'templating_tests.js', - 'templating_tests.html' - ], 'client'); +// api.use('handlebars', 'server'); +// api.add_files([ +// 'templating_tests.js', +// 'templating_tests.html' +// ], 'client'); api.add_files([ 'plugin/html_scanner.js', - 'scanner_tests.js', + 'scanner_tests.js' ], 'server'); }); diff --git a/packages/templating/plugin/html_scanner.js b/packages/templating/plugin/html_scanner.js index da69d13478..5e94f74cd4 100644 --- a/packages/templating/plugin/html_scanner.js +++ b/packages/templating/plugin/html_scanner.js @@ -138,23 +138,22 @@ html_scanner = { // or