Fixes #211 - Marko v3: Support concise (Jade-like) syntax

This commit is contained in:
Patrick Steele-Idem 2016-02-01 16:37:13 -07:00
parent 8b51e818a3
commit 266e15fa9c
55 changed files with 280 additions and 113 deletions

View File

@ -4,71 +4,75 @@ var htmljs = require('htmljs-parser');
class HtmlJsParser { class HtmlJsParser {
parse(src, handlers) { parse(src, handlers) {
var listeners = { var listeners = {
ontext(event) { onText(event) {
handlers.handleCharacters(event.text); handlers.handleCharacters(event.value);
}, },
oncontentplaceholder(event) { onPlaceholder(event) {
// placeholder within content if (event.withinBody) {
handlers.handleBodyTextPlaceholder(event.expression, event.escape); if (!event.withinString) {
}, handlers.handleBodyTextPlaceholder(event.value, event.escape);
}
onnestedcontentplaceholder(event) { } else if (event.withinOpenTag) {
// placeholder within string that is within content placeholder // Don't escape placeholder for dynamic attributes. For example: <div ${data.myAttrs}></div>
},
onattributeplaceholder(event) {
// placeholder within attribute
if (event.escape) {
event.expression = '$escapeXml(' + event.expression + ')';
} else { } else {
event.expression = '$noEscapeXml(' + event.expression + ')'; // placeholder within attribute
if (event.escape) {
event.value = '$escapeXml(' + event.value + ')';
} else {
event.value = '$noEscapeXml(' + event.value + ')';
}
}
// placeholder within content
},
onCDATA(event) {
handlers.handleCharacters(event.value);
},
onOpenTag(event, parser) {
event.selfClosed = false; // Don't allow self-closed tags
handlers.handleStartElement(event);
var newParserState = handlers.getParserStateForTag(event);
if (newParserState) {
if (newParserState === 'parsed-text') {
parser.enterParsedTextContentState();
} else if (newParserState === 'static-text') {
parser.enterStaticTextContentState();
}
} }
}, },
oncdata(event) { onCloseTag(event) {
handlers.handleCharacters(event.text);
},
onopentag(event) {
handlers.handleStartElement(event);
},
onclosetag(event) {
var tagName = event.tagName; var tagName = event.tagName;
handlers.handleEndElement(tagName); handlers.handleEndElement(tagName);
}, },
ondtd(event) { onDocumentType(event) {
// DTD (e.g. <DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0//EN">)
handlers.handleCharacters(event.dtd); // Document type: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
// NOTE: The value will be all of the text between "<!" and ">""
handlers.handleCharacters('<!' + event.value + '>');
}, },
ondeclaration(event) { onDeclaration(event) {
// Declaration (e.g. <?xml version="1.0" encoding="UTF-8" ?>) // Declaration (e.g. <?xml version="1.0" encoding="UTF-8" ?>)
handlers.handleCharacters(event.declaration); handlers.handleCharacters('<?' + event.value + '?>');
}, },
oncomment(event) { onComment(event) {
// Text within XML comment // Text within XML comment
handlers.handleComment(event.comment); handlers.handleComment(event.value);
}, },
onerror(event) { onError(event) {
handlers.handleError(event); handlers.handleError(event);
} }
}; };
var options = { var parser = this.parser = htmljs.createParser(listeners);
parserStateProvider(event) {
if (event.type === 'opentag') {
return handlers.getParserStateForTag(event);
}
}
};
var parser = this.parser = htmljs.createParser(listeners, options);
parser.parse(src); parser.parse(src);
} }
} }

View File

@ -64,7 +64,6 @@ class Parser {
} else { } else {
var escape = false; var escape = false;
this.prevTextNode = builder.text(builder.literal(text), escape); this.prevTextNode = builder.text(builder.literal(text), escape);
this.prevTextNode.pos = text.pos;
this.parentNode.appendChild(this.prevTextNode); this.parentNode.appendChild(this.prevTextNode);
} }
} }
@ -77,6 +76,10 @@ class Parser {
var attributes = el.attributes; var attributes = el.attributes;
var argument = el.argument; // e.g. For <for(color in colors)>, argument will be "color in colors" var argument = el.argument; // e.g. For <for(color in colors)>, argument will be "color in colors"
if (argument) {
argument = argument.value;
}
if (tagName === 'compiler-options') { if (tagName === 'compiler-options') {
attributes.forEach(function (attr) { attributes.forEach(function (attr) {
let attrName = attr.name; let attrName = attr.name;
@ -117,11 +120,12 @@ class Parser {
name: attr.name, name: attr.name,
value: isLiteral ? value: isLiteral ?
builder.literal(attr.literalValue) : builder.literal(attr.literalValue) :
attr.expression == null ? undefined : builder.parseExpression(attr.expression) attr.value == null ? undefined : builder.parseExpression(attr.value)
}; };
if (attr.argument) { if (attr.argument) {
attrDef.argument = attr.argument; // TODO Do something with the argument pos
attrDef.argument = attr.argument.value;
} }
return attrDef; return attrDef;

View File

@ -30,7 +30,7 @@
"char-props": "~0.1.5", "char-props": "~0.1.5",
"esprima": "^2.7.0", "esprima": "^2.7.0",
"events": "^1.0.2", "events": "^1.0.2",
"htmljs-parser": "^1.0.6", "htmljs-parser": "^1.3.0",
"jsonminify": "^0.2.3", "jsonminify": "^0.2.3",
"minimatch": "^0.2.14", "minimatch": "^0.2.14",
"property-handlers": "^1.0.0", "property-handlers": "^1.0.0",

View File

@ -1 +0,0 @@
<div class="tabs"><ul class="nav nav-tabs"><li class="active"><a href="#tab0" data-toggle="tab">Tab 1</a></li><li><a href="#tab1" data-toggle="tab">Tab 2</a></li></ul><div class="tab-content"><div id="tab0" class="tab-pane active">Tab 1 content</div><div id="tab1" class="tab-pane">Tab 2 content</div></div></div><div class="tabs"><ul class="nav nav-tabs"><li class="active"><a href="#tab0" data-toggle="tab">Tab 1</a></li><li><a href="#tab1" data-toggle="tab">Tab 2</a></li></ul><div class="tab-content"><div id="tab0" class="tab-pane active">Tab 1 content</div><div id="tab1" class="tab-pane">Tab 2 content</div></div></div>

View File

@ -1,29 +0,0 @@
<var name="showConditionalTab" value="data.showConditionalTab"/>
<test-tabs>
<test-tab title="Tab 1">
Tab 1 content
</test-tab>
<test-tab title="Tab 2">
Tab 2 content
</test-tab>
<test-tab title="Tab 3" if="showConditionalTab">
Tab 3 content
</test-tab>
</test-tabs>
<test-tabs-new>
<test-tab-new title="Tab 1">
Tab 1 content
</test-tab-new>
<test-tab-new title="Tab 2">
Tab 2 content
</test-tab-new>
<test-tab-new title="Tab 3" if="showConditionalTab">
Tab 3 content
</test-tab-new>
</test-tabs-new>

View File

@ -1 +1 @@
A ${'B'} C - A ${'B'} C

View File

@ -1 +1,3 @@
A ${'B'} C ---
A ${'B'} C
---

View File

@ -1,3 +1,3 @@
<!--This comment should not be preserved--> <!--This comment should not be preserved-->
Hello World! - Hello World!
<!--This comment should not be preserved--> <!--This comment should not be preserved-->

View File

@ -1,4 +1,6 @@
---
<compiler-options preserve-whitespace /> <compiler-options preserve-whitespace />
A A
B B
C C
---

View File

@ -0,0 +1 @@
<div class="foo"></div>

View File

@ -0,0 +1 @@
<div class="foo"/>

View File

@ -0,0 +1 @@
exports.templateData = {};

View File

@ -0,0 +1 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0//EN">

View File

@ -0,0 +1 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0//EN">

View File

@ -0,0 +1 @@
exports.templateData = {};

View File

@ -1,4 +1,4 @@
<div data-attr="Hello \"John\" <foo>"> <div data-attr="Hello \"John\" <foo>">
Hello &lt;John&gt;© <![CDATA[<hello>]]> Hello &lt;John&gt;© <![CDATA[<hello>]]>
</div> </div>
&copy; - &copy;

View File

@ -1 +1 @@
<test-invalid-attr invalid="true"> <test-invalid-attr invalid="true"/>

View File

@ -1 +1 @@
<test-missing-required-attr> <test-missing-required-attr/>

View File

@ -1 +1 @@
Global: ${out.global.foo} - Global: ${out.global.foo}

View File

@ -1 +1 @@
Hello ${data.name}! Hello $!{data.name}! Hello $!{data.missing}! - Hello ${data.name}! Hello $!{data.name}! Hello $!{data.missing}!

View File

@ -1 +1 @@
Hello John - Hello John

View File

@ -0,0 +1 @@
A

View File

@ -0,0 +1,3 @@
<if(true)>
A
</if>

View File

@ -0,0 +1 @@
exports.templateData = {};

View File

@ -1,3 +1,4 @@
-------
<layout-use("./layout-default.marko", {showHeader: false}) show-footer=false> <layout-use("./layout-default.marko", {showHeader: false}) show-footer=false>
<layout-put into="body">BODY CONTENT</layout-put> <layout-put into="body">BODY CONTENT</layout-put>
<layout-put into="footer">FOOTER CONTENT</layout-put> <layout-put into="footer">FOOTER CONTENT</layout-put>
@ -6,4 +7,5 @@
<layout-use("./layout-default.marko", {showHeader: false}) show-header=true> <layout-use("./layout-default.marko", {showHeader: false}) show-header=true>
<layout-put into="body">BODY CONTENT</layout-put> <layout-put into="body">BODY CONTENT</layout-put>
<layout-put into="footer">FOOTER CONTENT</layout-put> <layout-put into="footer">FOOTER CONTENT</layout-put>
</layout-use> </layout-use>
-------

View File

@ -1,3 +1,4 @@
---------
<layout-use("./layout-default.marko", {showHeader: false})> <layout-use("./layout-default.marko", {showHeader: false})>
<layout-put into="body">BODY CONTENT</layout-put> <layout-put into="body">BODY CONTENT</layout-put>
<layout-put into="footer">FOOTER CONTENT</layout-put> <layout-put into="footer">FOOTER CONTENT</layout-put>
@ -12,4 +13,5 @@
<layout-put into="header">My Title</layout-put> <layout-put into="header">My Title</layout-put>
<layout-put into="body">BODY CONTENT2</layout-put> <layout-put into="body">BODY CONTENT2</layout-put>
<layout-put into="footer">FOOTER CONTENT2</layout-put> <layout-put into="footer">FOOTER CONTENT2</layout-put>
</layout-use> </layout-use>
---------

View File

@ -1 +1,3 @@
Hello ${({name: "World"}).name}! ---
Hello ${({name: "World"}).name}!
---

View File

@ -1,6 +1,8 @@
---
<template-init> <template-init>
var testHelpers = require('./test-helpers') var testHelpers = require('./test-helpers')
</template-init> </template-init>
Hello ${testHelpers.upperCase("world")}! Hello ${testHelpers.upperCase("world")}!
Hello ${testHelpers.trim(" World ")}! Hello ${testHelpers.trim(" World ")}!
---

View File

@ -0,0 +1 @@
<div class="tabs"><ul class="nav nav-tabs"><li class="active"><a href="#tab0" data-toggle="tab">Tab 1</a></li><li><a href="#tab1" data-toggle="tab">Tab 2</a></li></ul><div class="tab-content"><div id="tab0" class="tab-pane active">Tab 1 content</div><div id="tab1" class="tab-pane">Tab 2 content</div></div></div>

View File

@ -0,0 +1,15 @@
<var showConditionalTab=data.showConditionalTab/>
<test-tabs-new>
<test-tab-new title="Tab 1">
Tab 1 content
</test-tab-new>
<test-tab-new title="Tab 2">
Tab 2 content
</test-tab-new>
<test-tab-new title="Tab 3" if(showConditionalTab)>
Tab 3 content
</test-tab-new>
</test-tabs-new>

View File

@ -0,0 +1 @@
<div class="tabs"><ul class="nav nav-tabs"><li class="active"><a href="#tab0" data-toggle="tab">Tab 1</a></li><li><a href="#tab1" data-toggle="tab">Tab 2</a></li></ul><div class="tab-content"><div id="tab0" class="tab-pane active">Tab 1 content</div><div id="tab1" class="tab-pane">Tab 2 content</div></div></div>

View File

@ -0,0 +1,15 @@
<var showConditionalTab=data.showConditionalTab/>
<test-tabs>
<test-tab title="Tab 1">
Tab 1 content
</test-tab>
<test-tab title="Tab 2">
Tab 2 content
</test-tab>
<test-tab title="Tab 3" if(showConditionalTab)>
Tab 3 content
</test-tab>
</test-tabs>

View File

@ -0,0 +1,3 @@
exports.templateData = {
"showConditionalTab": false
};

View File

@ -1 +1 @@
<test-tag-code-generator-return-self name="Frank" foo="bar"/><test-tag-code-generator-return-self name="John" foo="bar"/> <test-tag-code-generator-return-self name="Frank" foo="bar"></test-tag-code-generator-return-self><test-tag-code-generator-return-self name="John" foo="bar"></test-tag-code-generator-return-self>

View File

@ -1,3 +1,5 @@
---
<var person=data.person/> <var person=data.person/>
Hello ${person.name}. You are from ${person.address.city}, ${person.address.state} Zero: ${data.zero} Hello ${person.name}. You are from ${person.address.city}, ${person.address.state} Zero: ${data.zero}
---

View File

@ -1,3 +1,4 @@
---
<unless(true)> <unless(true)>
A A
</unless> </unless>
@ -31,3 +32,4 @@
<div else> <div else>
C C
</div> </div>
---

View File

@ -3,4 +3,6 @@
${y} ${y}
${z} ${z}
</var> </var>
${typeof x} ---
${typeof x}
---

View File

@ -1 +1 @@
<p>A <i>B</i> C</p> --- <p>D <i>E</i> F</p> --- <p>G <i>H</i> I</p> --- <p>J <div>K</div> L <div>M</div> N</p> --- <p><div>O</div><div>P</div><span>Q</span> <span>R</span> </p> <p>A <i>B</i> C</p> --- <p>D <i>E</i> F</p> --- <p>G <i>H</i> I</p> --- <p>J <div>K</div> L <div>M</div> N</p> --- <p><div>O</div><div>P</div><span>Q</span> <span>R</span></p>

View File

@ -1,3 +1,4 @@
------
<p>A <i>B</i> C</p> <p>A <i>B</i> C</p>
--- ---
<p> <p>
@ -6,7 +7,7 @@
</p> </p>
--- ---
<p> <p>
G G
<i>H</i> <i>H</i>
I I
</p> </p>
@ -15,14 +16,15 @@
J J
<div>K</div> <div>K</div>
L L
<div>M</div> <div>M</div>
N N
</p> </p>
--- ---
<p> <p>
<div>O</div> <div>O</div>
<div>P</div> <div>P</div>
<span>Q</span> <span>R</span> <span>Q</span> <span>R</span>
</p> </p>
------

View File

@ -1,3 +1,4 @@
---
${"BEGIN this whitespace should be retained END"} ${"BEGIN this whitespace should be retained END"}
test <!-- text should be normalized to one space --> test <!-- text should be normalized to one space -->
@ -30,12 +31,4 @@ should <!-- This whitespace should be normalized --> retain spacing between l
begin <!-- this whitespace should not be normalized --> end begin <!-- this whitespace should not be normalized --> end
</div> </div>
<if(true)>begin <!-- this whitespace should be preserved -->end</if> <if(true)>begin <!-- this whitespace should be preserved -->end</if>
<!-- ---
- In not "xml:space" === "preserve":
- newline followed by whitespace should be removed
- More than one whitespace should be normalized into a single space (or new line character?)
- Certain elements should preserve whitespace (e.g. PRE elements)
- Allow for a xml:space="preserve" attribute
- http://xhtml.com/en/css/reference/white-space/
- When should whitespace removal happen? When generating code?
-->

View File

@ -1 +1,3 @@
scanned-b: Hello ${data.name} ---
scanned-b: Hello ${data.name}
---

View File

@ -8,4 +8,6 @@ TAG = {
} }
} }
--> -->
scanned-d: Hello ${data.NAME} ---
scanned-d: Hello ${data.NAME}
---

View File

@ -0,0 +1,6 @@
{
"import-var": {
"tabs": "__tabsHelper"
},
"@title": "string"
}

View File

@ -0,0 +1,4 @@
exports.render = function(input, out) {
var tabs = input.tabs;
tabs.addTab(input);
};

View File

@ -0,0 +1,6 @@
{
"import-var": {
"tabs": "tabs"
},
"@title": "string"
}

View File

@ -0,0 +1,4 @@
exports.render = function(input, out) {
var tabs = input.tabs;
tabs.addTab(input);
};

View File

@ -0,0 +1,3 @@
{
"body-function": "buildTabs(__tabsHelper)"
}

View File

@ -0,0 +1,33 @@
var template = require('./template.marko');
exports.render = function(input, out) {
var tabs = [],
activeFound = false;
if (input.buildTabs) {
input.buildTabs({
addTab: function(tab) {
if (tab.active) {
tab.activeFound = true;
}
tab.id = "tab" + tabs.length;
tabs.push(tab);
}
});
}
if (!activeFound && tabs.length) {
tabs[0].active = true;
}
tabs.forEach(function(tab) {
tab.liClass = tab.active ? "active" : "";
tab.divClass = tab.active ? "tab-pane active" : "tab-pane";
});
template.render({
tabs: tabs
}, out);
};

View File

@ -0,0 +1,16 @@
<var tabs=data.tabs/>
<div class="tabs">
<ul class="nav nav-tabs">
<li class="${tab.liClass}" for(tab in tabs)>
<a href="#${tab.id}" data-toggle="tab">
${tab.title}
</a>
</li>
</ul>
<div class="tab-content">
<div id="${tab.id}" class="${tab.divClass}" for(tab in tabs)>
<invoke tab.renderBody(out) if(tab.renderBody) />
</div>
</div>
</div>

View File

@ -0,0 +1,3 @@
{
"var": "tabs"
}

View File

@ -0,0 +1,31 @@
var template = require('./template.marko');
exports.render = function(input, out) {
var tabs = [],
activeFound = false;
input.renderBody(out, {
addTab: function(tab) {
if (tab.active) {
tab.activeFound = true;
}
tab.id = "tab" + tabs.length;
tabs.push(tab);
}
});
if (!activeFound && tabs.length) {
tabs[0].active = true;
}
tabs.forEach(function(tab) {
tab.liClass = tab.active ? "active" : "";
tab.divClass = tab.active ? "tab-pane active" : "tab-pane";
});
template.render({
tabs: tabs
}, out);
};

View File

@ -0,0 +1,16 @@
<var tabs=data.tabs/>
<div class="tabs">
<ul class="nav nav-tabs">
<li class="${tab.liClass}" for(tab in tabs)>
<a href="#${tab.id}" data-toggle="tab">
${tab.title}
</a>
</li>
</ul>
<div class="tab-content">
<div id=tab.id class=tab.divClass for(tab in tabs)>
<invoke tab.renderBody(out) />
</div>
</div>
</div>

View File

@ -1 +1,3 @@
Hello ${data.name}! ---
Hello ${data.name}!
---

View File

@ -1 +1,3 @@
Hello ${data.name}! ---
Hello ${data.name}!
---