1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2025-09-09 22:36:05 +00:00

Refactor the HTML element parser

The purpose is to allow attributes to be specified as macro
invocations. For example `<div myattr=<<mymacro param1 param3>>>`. The
parser needed sprucing up in order to copy with the nesting of angle
brackets. The refactoring has been done with an eye on using the same
technique in the filter expression parser (which is pretty messy at the
moment -- it throws exceptions for syntax errors, which is bad). Later
I'm hoping to extend the technique to create a more flexible table
parser.
This commit is contained in:
Jeremy Ruston
2013-06-15 15:12:05 +01:00
parent aaed4faf7e
commit 18f8b7266e
2 changed files with 558 additions and 60 deletions

View File

@@ -3,7 +3,7 @@ title: test-html-parser.js
type: application/javascript
tags: [[$:/tags/test-spec]]
Tests the parse rule for HTML elements
Tests for the internal components of the HTML tag parser
\*/
(function(){
@@ -12,6 +12,195 @@ Tests the parse rule for HTML elements
/*global $tw: false */
"use strict";
function FakeParser() {
}
$tw.utils.extend(FakeParser.prototype,require("$:/core/modules/parsers/wikiparser/rules/html.js"));
describe("HTML tag new parser tests", function() {
var parser = new FakeParser();
it("should parse whitespace", function() {
expect(parser.parseWhiteSpace("p ",0)).toEqual(
null
);
expect(parser.parseWhiteSpace("p ",1)).toEqual(
{ type : 'whitespace', start : 1, end : 3 }
);
});
it("should parse string tokens", function() {
expect(parser.parseTokenString("p= ",0,"=")).toEqual(
null
);
expect(parser.parseTokenString("p= ",1,"=")).toEqual(
{ type : 'token', value : '=', start : 1, end : 2 }
);
});
it("should parse regexp tokens", function() {
expect(parser.parseTokenRegExp("p=' ",0,/(=(?:'|"))/)).toEqual(
null
);
expect(parser.parseTokenRegExp("p=' ",1,/(=(?:'|"))/g).match[0]).toEqual(
'=\''
);
expect(parser.parseTokenRegExp("p=blah ",2,/([^\s>]+)/g).match[0]).toEqual(
'blah'
);
});
it("should parse string literals", function() {
expect(parser.parseStringLiteral("p='blah' ",0)).toEqual(
null
);
expect(parser.parseStringLiteral("p='blah' ",2)).toEqual(
{ type : 'string', start : 2, value : 'blah', end : 8 }
);
expect(parser.parseStringLiteral("p='' ",2)).toEqual(
{ type : 'string', start : 2, value : '', end : 4 }
);
expect(parser.parseStringLiteral("p=\"blah' ",2)).toEqual(
null
);
expect(parser.parseStringLiteral("p=\"\" ",2)).toEqual(
{ type : 'string', start : 2, value : '', end : 4 }
);
});
it("should parse macro parameters", function() {
expect(parser.parseMacroParameter("me",0)).toEqual(
{ type : 'macro-parameter', start : 0, value : 'me', end : 2 }
);
expect(parser.parseMacroParameter("me:one",0)).toEqual(
{ type : 'macro-parameter', start : 0, value : 'one', name : 'me', end : 6 }
);
expect(parser.parseMacroParameter("me:'one two three'",0)).toEqual(
{ type : 'macro-parameter', start : 0, value : 'one two three', name : 'me', end : 18 }
);
expect(parser.parseMacroParameter("'one two three'",0)).toEqual(
{ type : 'macro-parameter', start : 0, value : 'one two three', end : 15 }
);
expect(parser.parseMacroParameter("me:[[one two three]]",0)).toEqual(
{ type : 'macro-parameter', start : 0, value : 'one two three', name : 'me', end : 20 }
);
expect(parser.parseMacroParameter("[[one two three]]",0)).toEqual(
{ type : 'macro-parameter', start : 0, value : 'one two three', end : 17 }
);
expect(parser.parseMacroParameter("myparam>",0)).toEqual(
{ type : 'macro-parameter', start : 0, value : 'myparam', end : 7 }
);
});
it("should parse macro invocations", function() {
expect(parser.parseMacroInvocation("<<mymacro",0)).toEqual(
null
);
expect(parser.parseMacroInvocation("<<mymacro>>",0)).toEqual(
{ type : 'macro-invocation', start : 0, parameters : [ ], name : 'mymacro', end : 11 }
);
expect(parser.parseMacroInvocation("<<mymacro one two three>>",0)).toEqual(
{ type : 'macro-invocation', start : 0, parameters : [ { type : 'macro-parameter', start : 9, value : 'one', end : 13 }, { type : 'macro-parameter', start : 13, value : 'two', end : 17 }, { type : 'macro-parameter', start : 17, value : 'three', end : 23 } ], name : 'mymacro', end : 25 }
);
expect(parser.parseMacroInvocation("<<mymacro p:one q:two three>>",0)).toEqual(
{ type : 'macro-invocation', start : 0, parameters : [ { type : 'macro-parameter', start : 9, value : 'one', name : 'p', end : 15 }, { type : 'macro-parameter', start : 15, value : 'two', name : 'q', end : 21 }, { type : 'macro-parameter', start : 21, value : 'three', end : 27 } ], name : 'mymacro', end : 29 }
);
expect(parser.parseMacroInvocation("<<mymacro 'one two three'>>",0)).toEqual(
{ type : 'macro-invocation', start : 0, parameters : [ { type : 'macro-parameter', start : 9, value : 'one two three', end : 25 } ], name : 'mymacro', end : 27 }
);
expect(parser.parseMacroInvocation("<<mymacro r:'one two three'>>",0)).toEqual(
{ type : 'macro-invocation', start : 0, parameters : [ { type : 'macro-parameter', start : 9, value : 'one two three', name : 'r', end : 27 } ], name : 'mymacro', end : 29 }
);
expect(parser.parseMacroInvocation("<<myMacro one:two three:'four and five'>>",0)).toEqual(
{ type : 'macro-invocation', start : 0, parameters : [ { type : 'macro-parameter', start : 9, value : 'two', name : 'one', end : 17 }, { type : 'macro-parameter', start : 17, value : 'four and five', name : 'three', end : 39 } ], name : 'myMacro', end : 41 }
);
});
it("should parse HTML attributes", function() {
expect(parser.parseAttribute("p='blah' ",1)).toEqual(
null
);
expect(parser.parseAttribute("p='blah' ",0)).toEqual(
{ type : 'string', start : 0, name : 'p', value : 'blah', end : 8 }
);
expect(parser.parseAttribute("p=\"blah\" ",0)).toEqual(
{ type : 'string', start : 0, name : 'p', value : 'blah', end : 8 }
);
expect(parser.parseAttribute("p=blah ",0)).toEqual(
{ type : 'string', start : 0, name : 'p', value : 'blah', end : 6 }
);
expect(parser.parseAttribute("p =blah ",0)).toEqual(
{ type : 'string', start : 0, name : 'p', value : 'blah', end : 7 }
);
expect(parser.parseAttribute("p= blah ",0)).toEqual(
{ type : 'string', start : 0, name : 'p', value : 'blah', end : 7 }
);
expect(parser.parseAttribute("p = blah ",0)).toEqual(
{ type : 'string', start : 0, name : 'p', value : 'blah', end : 8 }
);
expect(parser.parseAttribute("p = >blah ",0)).toEqual(
{ type : 'string', value : 'true', start : 0, name : 'p', end : 4 }
);
expect(parser.parseAttribute(" attrib1>",0)).toEqual(
{ type : 'string', value : 'true', start : 0, name : 'attrib1', end : 8 }
);
});
it("should parse HTML tags", function() {
expect(parser.parseTag("<mytag>",1)).toEqual(
null
);
expect(parser.parseTag("</mytag>",0)).toEqual(
null
);
expect(parser.parseTag("<mytag>",0)).toEqual(
{ type : 'element', start : 0, attributes : [ ], tag : 'mytag', end : 7 }
);
expect(parser.parseTag("<mytag attrib1>",0)).toEqual(
{ type : 'element', start : 0, attributes : { attrib1 : { type : 'string', value : 'true', start : 6, name : 'attrib1', end : 14 } }, tag : 'mytag', end : 15 }
);
expect(parser.parseTag("<mytag attrib1/>",0)).toEqual(
{ type : 'element', start : 0, attributes : { attrib1 : { type : 'string', value : 'true', start : 6, name : 'attrib1', end : 14 } }, tag : 'mytag', isSelfClosing : true, end : 16 }
);
expect(parser.parseTag("<$view field=\"title\" format=\"link\"/>",0)).toEqual(
{ type : 'element', start : 0, attributes : { field : { start : 6, name : 'field', type : 'string', value : 'title', end : 20 }, format : { start : 20, name : 'format', type : 'string', value : 'link', end : 34 } }, tag : '$view', isSelfClosing : true, end : 36 }
);
expect(parser.parseTag("<mytag attrib1='something'>",0)).toEqual(
{ type : 'element', start : 0, attributes : { attrib1 : { type : 'string', start : 6, name : 'attrib1', value : 'something', end : 26 } }, tag : 'mytag', end : 27 }
);
expect(parser.parseTag("<$mytag attrib1='something' attrib2=else thing>",0)).toEqual(
{ type : 'element', start : 0, attributes : { attrib1 : { type : 'string', start : 7, name : 'attrib1', value : 'something', end : 27 }, attrib2 : { type : 'string', start : 27, name : 'attrib2', value : 'else', end : 40 }, thing : { type : 'string', start : 40, name : 'thing', value : 'true', end : 46 } }, tag : '$mytag', end : 47 }
);
expect(parser.parseTag("< $mytag attrib1='something' attrib2=else thing>",0)).toEqual(
null
);
expect(parser.parseTag("<$mytag attrib3=<<myMacro one:two three:'four and five'>>>",0)).toEqual(
{ type : 'element', start : 0, attributes : { attrib3 : { type : 'macro', start : 7, name : 'attrib3', value : { type : 'macro-invocation', start : 16, parameters : [ { type : 'macro-parameter', start : 25, value : 'two', name : 'one', end : 33 }, { type : 'macro-parameter', start : 33, value : 'four and five', name : 'three', end : 55 } ], name : 'myMacro', end : 57 }, end : 57 } }, tag : '$mytag', end : 58 }
);
expect(parser.parseTag("<$mytag attrib1='something' attrib2=else thing attrib3=<<myMacro one:two three:'four and five'>>>",0)).toEqual(
{ type : 'element', start : 0, attributes : { attrib1 : { type : 'string', start : 7, name : 'attrib1', value : 'something', end : 27 }, attrib2 : { type : 'string', start : 27, name : 'attrib2', value : 'else', end : 40 }, thing : { type : 'string', start : 40, name : 'thing', value : 'true', end : 47 }, attrib3 : { type : 'macro', start : 47, name : 'attrib3', value : { type : 'macro-invocation', start : 55, parameters : [ { type : 'macro-parameter', start : 64, value : 'two', name : 'one', end : 72 }, { type : 'macro-parameter', start : 72, value : 'four and five', name : 'three', end : 94 } ], name : 'myMacro', end : 96 }, end : 96 } }, tag : '$mytag', end : 97 }
);
});
it("should find and parse HTML tags", function() {
expect(parser.findNextTag("<something <mytag>",1)).toEqual(
{ type : 'element', start : 11, attributes : { }, tag : 'mytag', end : 18 }
);
expect(parser.findNextTag("something else </mytag>",0)).toEqual(
null
);
expect(parser.findNextTag("<<some other stuff>> <mytag>",0)).toEqual(
{ type : 'element', start : 1, attributes : { other : { type : 'string', value : 'true', start : 6, name : 'other', end : 13 }, stuff : { type : 'string', value : 'true', start : 13, name : 'stuff', end : 18 } }, tag : 'some', end : 19 }
);
expect(parser.findNextTag("<<some other stuff>> <mytag>",2)).toEqual(
{ type : 'element', start : 21, attributes : { }, tag : 'mytag', end : 28 }
);
});
});
describe("HTML tag parser tests", function() {
// Create a wiki
@@ -25,7 +214,7 @@ describe("HTML tag parser tests", function() {
it("should parse unclosed tags", function() {
expect(parse("<br>")).toEqual(
[ { type : 'element', tag : 'p', children : [ { type : 'element', tag : 'br', isBlock : false, attributes : { }, children : [ ] } ] } ]
[ { type : 'element', tag : 'p', children : [ { type : 'element', tag : 'br', isBlock : false, attributes : { }, start : 0, end : 4 } ] } ]
);
expect(parse("</br>")).toEqual(
@@ -35,42 +224,57 @@ describe("HTML tag parser tests", function() {
);
expect(parse("<div>")).toEqual(
[ { type : 'element', tag : 'p', children : [ { type : 'element', tag : 'div', isBlock : false, attributes : { }, children : [ ] } ] } ]
[ { type : 'element', tag : 'p', children : [ { type : 'element', tag : 'div', isBlock : false, attributes : { }, children : [ ], start : 0, end : 5 } ] } ]
);
expect(parse("<div/>")).toEqual(
[ { type : 'element', tag : 'p', children : [ { type : 'element', tag : 'div', isBlock : false, attributes : { }, children : [ ] } ] } ]
[ { type : 'element', tag : 'p', children : [ { type : 'element', tag : 'div', isSelfClosing : true, isBlock : false, attributes : { }, start : 0, end : 6 } ] } ]
);
expect(parse("<div></div>")).toEqual(
[ { type : 'element', tag : 'p', children : [ { type : 'element', tag : 'div', isBlock : false, attributes : { }, children : [ ] } ] } ]
[ { type : 'element', tag : 'p', children : [ { type : 'element', tag : 'div', isBlock : false, attributes : { }, children : [ ], start : 0, end : 5 } ] } ]
);
expect(parse("<div>some text</div>")).toEqual(
[ { type : 'element', tag : 'p', children : [ { type : 'element', tag : 'div', isBlock : false, attributes : { }, children : [ { type : 'text', text : 'some text' } ] } ] } ]
[ { type : 'element', tag : 'p', children : [ { type : 'element', tag : 'div', isBlock : false, attributes : { }, children : [ { type : 'text', text : 'some text' } ], start : 0, end : 5 } ] } ]
);
expect(parse("<div attribute>some text</div>")).toEqual(
[ { type : 'element', tag : 'p', children : [ { type : 'element', tag : 'div', isBlock : false, attributes : { attribute : { type : 'string', value : 'true' } }, children : [ { type : 'text', text : 'some text' } ] } ] } ]
[ { type : 'element', tag : 'p', children : [ { type : 'element', tag : 'div', isBlock : false, attributes : { attribute : { type : 'string', value : 'true', start : 4, end : 14, name: 'attribute' } }, children : [ { type : 'text', text : 'some text' } ], start : 0, end : 15 } ] } ]
);
expect(parse("<div attribute='value'>some text</div>")).toEqual(
[ { type : 'element', tag : 'p', children : [ { type : 'element', tag : 'div', isBlock : false, attributes : { attribute : { type : 'string', value : 'value' } }, children : [ { type : 'text', text : 'some text' } ] } ] } ]
[ { type : 'element', tag : 'p', children : [ { type : 'element', tag : 'div', isBlock : false, attributes : { attribute : { type : 'string', name: 'attribute', value : 'value', start: 4, end: 22 } }, children : [ { type : 'text', text : 'some text' } ], start: 0, end: 23 } ] } ]
);
expect(parse("<div attribute={{TiddlerTitle}}>some text</div>")).toEqual(
[ { type : 'element', tag : 'p', children : [ { type : 'element', tag : 'div', isBlock : false, attributes : { attribute : { type : 'indirect', textReference : 'TiddlerTitle' } }, children : [ { type : 'text', text : 'some text' } ] } ] } ]
[ { type : 'element', tag : 'p', children : [ { type : 'element', tag : 'div', isBlock : false, attributes : { attribute : { type : 'indirect', name: 'attribute', textReference : 'TiddlerTitle', start : 4, end : 31 } }, children : [ { type : 'text', text : 'some text' } ], start : 0, end : 32 } ] } ]
);
expect(parse("<$reveal state='$:/temp/search' type='nomatch' text=''>")).toEqual(
[ { type : 'element', tag : 'p', children : [ { type : 'element', start : 0, attributes : { state : { start : 8, name : 'state', type : 'string', value : '$:/temp/search', end : 31 }, type : { start : 31, name : 'type', type : 'string', value : 'nomatch', end : 46 }, text : { start : 46, name : 'text', type : 'string', value : '', end : 54 } }, tag : '$reveal', end : 55, children : [ ], isBlock : false } ] } ]
);
expect(parse("<div attribute={{TiddlerTitle!!field}}>some text</div>")).toEqual(
[ { type : 'element', tag : 'p', children : [ { type : 'element', tag : 'div', isBlock : false, attributes : { attribute : { type : 'indirect', textReference : 'TiddlerTitle!!field' } }, children : [ { type : 'text', text : 'some text' } ] } ] } ]
[ { type : 'element', tag : 'p', children : [ { type : 'element', tag : 'div', isBlock : false, attributes : { attribute : { type : 'indirect', name : 'attribute', textReference : 'TiddlerTitle!!field', start : 4, end : 38 } }, children : [ { type : 'text', text : 'some text' } ], start : 0, end : 39 } ] } ]
);
expect(parse("<div attribute={{TiddlerTitle!!field}}>\nsome text</div>")).toEqual(
[ { type : 'element', start : 0, attributes : { attribute : { start : 4, name : 'attribute', type : 'indirect', textReference : 'TiddlerTitle!!field', end : 38 } }, tag : 'div', end : 39, isBlock : true, children : [ { type : 'element', tag : 'p', children : [ { type : 'text', text : 'some text' } ] } ] } ]
);
expect(parse("<div><div attribute={{TiddlerTitle!!field}}>\nsome text</div></div>")).toEqual(
[ { type : 'element', tag : 'p', children : [ { type : 'element', start : 0, attributes : { }, tag : 'div', end : 5, isBlock : false, children : [ { type : 'element', start : 5, attributes : { attribute : { start : 9, name : 'attribute', type : 'indirect', textReference : 'TiddlerTitle!!field', end : 43 } }, tag : 'div', end : 44, isBlock : true, children : [ { type : 'element', tag : 'p', children : [ { type : 'text', text : 'some text' } ] } ] } ] } ] } ]
);
});