Dropped support for namespaces and updated docs and tests

This commit is contained in:
Patrick Steele-Idem 2014-04-14 13:44:38 -06:00
parent bd8f760afa
commit da274538ab
173 changed files with 2110 additions and 2948 deletions

384
README.md
View File

@ -1,7 +1,7 @@
raptor-templates
================
Raptor Templates is a streaming, asynchronous, [high performance](https://github.com/raptorjs3/templating-benchmarks), _HTML-based_ templating language that can be used in Node.js or in the browser. Because the Raptor Templates compiler understands the structure of the HTML document, the directives in template files are less obtrusive and more powerful .
Raptor Templates is an extensible, streaming, asynchronous, [high performance](https://github.com/raptorjs3/templating-benchmarks), _HTML-based_ templating language that can be used in Node.js or in the browser. Raptor Templates was founded on the philosophy that an HTML-based templating language is more natural and intuitive for generating HTML. Because the Raptor Templates compiler understands the structure of the HTML document, the directives in template files are less obtrusive and more powerful. In addition, Raptor Templates allows you to introduce your own custom tags and custom attributes to extend the HTML grammar (much like [Web Components](http://www.html5rocks.com/en/tutorials/webcomponents/customelements/)--only you can use it now).
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
@ -50,12 +50,12 @@ Raptor Templates is a streaming, asynchronous, [high performance](https://github
- [Tag Renderer](#tag-renderer)
- [raptor-taglib.json](#raptor-taglibjson)
- [Sample Taglib](#sample-taglib)
- [Taglib Namespace](#taglib-namespace)
- [Defining Tags](#defining-tags)
- [Defining Attributes](#defining-attributes)
- [Scanning for Tags](#scanning-for-tags)
- [Nested Tags](#nested-tags)
- [Taglib Discovery](#taglib-discovery)
- [FAQ](#faq)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@ -65,12 +65,12 @@ A basic template with text replacement, looping and conditionals is shown below:
```html
Hello ${data.name}!
<ul c:if="notEmpty(data.colors)">
<li style="color: $color" c:for="color in data.colors">
<ul c-if="notEmpty(data.colors)">
<li style="color: $color" c-for="color in data.colors">
$color
</li>
</ul>
<div c:else>
<div c-else>
No colors!
</div>
```
@ -122,17 +122,17 @@ Raptor Templates also supports custom tags so you can easily extend the HTML gra
```html
Welcome to Raptor Templates!
<ui:tabs>
<ui:tab label="Home">
<ui-tabs>
<ui-tab label="Home">
Content for Home
</ui:tab>
<ui:tab label="Profile">
</ui-tab>
<ui-tab label="Profile">
Content for Profile
</ui:tab>
<ui:tab label="Messages">
</ui-tab>
<ui-tab label="Messages">
Content for Messages
</ui:tab>
</ui:tabs>
</ui-tab>
</ui-tabs>
```
The above template is a very simple way to generate the much more complicated HTML output shown below:
@ -396,14 +396,14 @@ Almost all of the Raptor templating directives can be used as either an attribut
_Applying directives using attributes:_
```html
<!-- Colors available -->
<ul c:if="notEmpty(colors)">
<li c:for="color in colors">
<ul c-if="notEmpty(colors)">
<li c-for="color in colors">
$color
</li>
</ul>
<!-- No colors available -->
<div c:if="empty(colors)">
<div c-if="empty(colors)">
No colors!
</div>
```
@ -411,25 +411,25 @@ _Applying directives using attributes:_
_Applying directives using elements:_
```html
<!-- Colors available -->
<c:if test="notEmpty(colors)">
<c-if test="notEmpty(colors)">
<ul>
<c:for each="color in colors">
<c-for each="color in colors">
<li>
$color
</li>
</c:for>
</c-for>
</ul>
</c:if>
</c-if>
<!-- No colors available -->
<c:if test="empty(colors)">
<c-if test="empty(colors)">
<div>
No colors!
</div>
</c:if>
</c-if>
```
The disadvantage of using elements to control structural logic is that they change the nesting of the elements which can impact readability. For this reason it is often more suitable to use attributes.
The disadvantage of using elements to control structural logic is that they change the nesting of the elements which can impact readability. For this reason it is often more suitable to apply directives as attributes.
## Text Replacement
@ -472,12 +472,26 @@ JavaScript Operator | Raptor Equivalent
`<=` | `le`
`>=` | `ge`
## Includes
Other Raptor Template files can be included using the `<c:include>` tag and a relative path. For example:
For example, both of the following are valid and equivalent:
```html
<c:include template="./greeting" name="Frank" count="30"/>
<div c-if="searchResults.length > 100">
Show More
</div>
```
```html
<div c-if="searchResults.length gt 100">
Show More
</div>
```
## Includes
Other Raptor Template files can be included using the `<c-include>` tag and a relative path. For example:
```html
<c-include template="./greeting.rhtml" name="Frank" count="30"/>
```
## Variables
@ -485,102 +499,102 @@ Other Raptor Template files can be included using the `<c:include>` tag and a re
Input data passed to a template is made available using a special `data` variable. It's possible to declare your own variables as shown in the following sample code:
```html
<c:var name="name" value="data.name.toUpperCase()" />
<c-var name="name" value="data.name.toUpperCase()" />
```
## Conditionals
### if...else-if...else
Any element or fragment of HTML can be made conditional using the c:if, c:else-if or c:else directive.
Any element or fragment of HTML can be made conditional using the `c-if`, `c-else-if` or `c-else` directive.
_Applied as an attribute:_
_Applied as attributes:_
```html
<!--Simple if-->
<div c:if="someCondition">
<div c-if="someCondition">
Hello World
</div>
<!--Complex if-->
<div c:if="test === 'a'">
<div c-if="test === 'a'">
A
</div>
<div c:else-if="test === 'b'">
<div c-else-if="test === 'b'">
B
</div>
<div c:else-if="test === 'c'">
<div c-else-if="test === 'c'">
C
</div>
<div c:else>
<div c-else>
Something else
</div>
```
_Applied as an element:_
_Applied as elements:_
```html
<!-- Colors available -->
<!--Simple if-->
<c:if test="someCondition">
<c-if test="someCondition">
<div>
Hello World
</div>
</c:if>
</c-if>
<!--Complex if-->
<c:if test="test === 'a'">
<c-if test="test === 'a'">
<div>
A
</div>
</c:if>
<c:else-if test="test === 'b'">
</c-if>
<c-else-if test="test === 'b'">
<div>
B
</div>
</c:else-if>
<c:else-if test="test === 'c'">
</c-else-if>
<c-else-if test="test === 'c'">
<div>
C
</div>
</c:else-if>
<c:else>
</c-else-if>
<c-else>
<div>
Something else
</div>
</c:else>
</c-else>
```
### choose…when…otherwise
The `c:choose` directive, in combination with the directives `c:when` and `c:otherwise` provides advanced conditional processing for rendering one of several alternatives. The first matching `c:when` branch is rendered, or, if no `c:when` branch matches, the `c:otherwise` branch is rendered.
The `c-choose` directive, in combination with the directives `c-when` and `c-otherwise` provides advanced conditional processing for rendering one of several alternatives. The first matching `c-when` branch is rendered, or, if no `c-when` branch matches, the `c-otherwise` branch is rendered.
_Applied as an attribute:_
```html
<c:choose>
<c:when test="myVar === 'A'">
<c-choose>
<c-when test="myVar === 'A'">
<div>A</div>
</c:when>
<c:when test="myVar === 'B'">
</c-when>
<c-when test="myVar === 'B'">
<div>B</div>
</c:when>
<c:otherwise>
</c-when>
<c-otherwise>
<div>Something else</div>
</c:otherwise>
<c:choose>
</c-otherwise>
<c-choose>
```
_Applied as an element:_
```html
<c:choose>
<div c:when="myVar === 'A'">
<c-choose>
<div c-when="myVar === 'A'">
A
</div>
<div c:when="myVar === 'B'">
<div c-when="myVar === 'B'">
B
</div>
<div c:otherwise="">
<div c-otherwise="">
Something else
</div>
<c:choose>
<c-choose>
```
### Shorthand conditionals
@ -623,21 +637,21 @@ With a value of `false` for `active`, the output would be the following:
### for
Any element can be repeated for every item in an array using the `c:for` directive. The directive can be applied as an element or as an attribute.
Any element can be repeated for every item in an array using the `c-for` directive. The directive can be applied as an element or as an attribute.
_Applied as an attribute:_
```html
<ul>
<li c:for="item in items">${item}</li>
<li c-for="item in items">${item}</li>
</ul>
```
_Applied as an element:_
```html
<ul>
<c:for each="item in items">
<c-for each="item in items">
<li>${item}</li>
</c:for>
</c-for>
</ul>
```
@ -658,14 +672,14 @@ The output would be the following:
#### Loop Status Variable
The `c:for` directive also supports a loop status variable in case you need to know the current loop index. For example:
The `c-for` directive also supports a loop status variable in case you need to know the current loop index. For example:
```html
<ul>
<li c:for="color in colors; status-var=loop">
<li c-for="color in colors; status-var=loop">
${loop.getIndex()+1}) $color
<c:if test="loop.isFirst()"> - FIRST</c:if>
<c:if test="loop.isLast()"> - LAST</c:if>
<c-if test="loop.isFirst()"> - FIRST</c-if>
<c-if test="loop.isLast()"> - LAST</c-if>
</li>
</ul>
```
@ -673,10 +687,10 @@ The `c:for` directive also supports a loop status variable in case you need to k
#### Loop Separator
```html
<c:for each="color in colors" separator=", ">$color</c:for>
<c-for each="color in colors" separator=", ">$color</c-for>
<div>
<span c:for="color in colors; separator=', '" style="color: $color">$color</span>
<span c-for="color in colors; separator=', '" style="color: $color">$color</span>
</div>
```
@ -684,7 +698,7 @@ The `c:for` directive also supports a loop status variable in case you need to k
```html
<ul>
<li c:for="(name,value) in settings">
<li c-for="(name,value) in settings">
<b>$name</b>:
$value
</li>
@ -693,24 +707,24 @@ The `c:for` directive also supports a loop status variable in case you need to k
## Macros
Parameterized macros allow for reusable fragments within an HTML template. A macro can be defined using the `<c:def>` directive.
Parameterized macros allow for reusable fragments within an HTML template. A macro can be defined using the `<c-def>` directive.
### def
The `<c:def>` directive can be used to define a reusable function within a template.
The `<c-def>` directive can be used to define a reusable function within a template.
```html
<c:def function="greeting(name, count)">
<c-def function="greeting(name, count)">
Hello $name! You have $count new messages.
</c:def>
</c-def>
```
The above macro can then be invoked as part of any expression. Alternatively, the [`<c:invoke>`](#invoke) directive can be used invoke a macro function using named attributes. The following sample template shows how to use macro functions inside expressions:
The above macro can then be invoked as part of any expression. Alternatively, the [`<c-invoke>`](#invoke) directive can be used invoke a macro function using named attributes. The following sample template shows how to use macro functions inside expressions:
```html
<c:def function="greeting(name, count)">
<c-def function="greeting(name, count)">
Hello $name! You have $count new messages.
</c:def>
</c-def>
<p>
${greeting("John", 10)}
@ -722,15 +736,15 @@ The above macro can then be invoked as part of any expression. Alternatively, th
### invoke
The `<c:invoke>` directive can be used to invoke a function defined using the `<c:def>` directive or a function that is part of the input to a template. The `<c:invoke>` directive allows arguments to be passed using element attributes, but that format is only supported for functions that were previously defined using the `<c:def>` directive.
The `<c-invoke>` directive can be used to invoke a function defined using the `<c-def>` directive or a function that is part of the input to a template. The `<c-invoke>` directive allows arguments to be passed using element attributes, but that format is only supported for functions that were previously defined using the `<c-def>` directive.
```html
<c:def function="greeting(name, count)">
<c-def function="greeting(name, count)">
Hello ${name}! You have ${count} new messages.
</c:def>
</c-def>
<c:invoke function="greeting" name="John" count="${10}"/>
<c:invoke function="greeting('Frank', 20)"/>
<c-invoke function="greeting" name="John" count="${10}"/>
<c-invoke function="greeting('Frank', 20)"/>
```
The output for the above template would be the following:
@ -744,7 +758,7 @@ The output for the above template would be the following:
</p>
```
_NOTE:_ By default, the arguments will be of type "string" when using `<c:invoke>.` However, argument attributes support JavaScript expressions which allow for other types of arguments. Example:
_NOTE:_ By default, the arguments will be of type "string" when using `<c-invoke>.` However, argument attributes support JavaScript expressions which allow for other types of arguments. Example:
```html
count="10" <!-- string argument -->
count="${10}" <!-- number argument -->
@ -755,10 +769,10 @@ count="${10}" <!-- number argument -->
### attrs
The `c:attrs` attribute allows attributes to be dynamically added to an element at runtime. The value of the c:attrs attribute should be an expression that resolves to an object with properties that correspond to the dynamic attributes. For example:
The `c-attrs` attribute allows attributes to be dynamically added to an element at runtime. The value of the c-attrs attribute should be an expression that resolves to an object with properties that correspond to the dynamic attributes. For example:
```html
<div c:attrs="myAttrs">
<div c-attrs="myAttrs">
Hello World!
</div>
```
@ -783,7 +797,7 @@ This directive replaces any nested content with the result of evaluating the exp
```html
<ul>
<li c:content="myExpr">Hello</li>
<li c-content="myExpr">Hello</li>
</ul>
```
@ -801,7 +815,7 @@ This directive replaces the element itself with the result of evaluating the exp
```html
<div>
<span c:replace="myExpr">Hello</span>
<span c-replace="myExpr">Hello</span>
</div>
```
@ -819,7 +833,7 @@ This directive conditionally strips the top-level element from the output. If th
```html
<div>
<span c:strip="true"><b>Hello</b></span>
<span c-strip="true"><b>Hello</b></span>
</div>
```
@ -842,6 +856,19 @@ Example comments:
<h1>Hello</h1>
```
If you would like for your HTML comment to show up in the final output then you can use the custom `html-comment` tag:
```html
<html-comment>This is a comment that *will* be rendered</html-comment>
<h1>Hello</h1>
```
Output:
```html
<!--This is a comment that *will* be rendered-->
<h1>Hello</h1>
```
## Helpers
Since Raptor Template files compile into CommonJS modules, any Node.js module can be "imported" into a template for use as a helper module. For example, given the following helper module:
@ -861,26 +888,26 @@ The above module can then be imported into a template as shown in the following
_src/template.rhtml_:
```html
<c:require module="./util" var="util" />
<c-require module="./util" var="util" />
<div>${util.reverse('reverse test')}</div>
```
The `c:require` directive is just short-hand for the following:
The `c-require` directive is just short-hand for the following:
```html
<c:var name="util" value="require('./util')" />
<c-var name="util" value="require('./util')" />
<div>${util.reverse('reverse test')}</div>
```
## Custom Tags and Attributes
Raptor Templates supports extending the language with custom tags and attributes. A custom tag or a custom attribute should be prefixed with a namespace that matches a module name that exports the custom tag or custom attribute. Alternatively, the namespace can be a shorter alias defined by the taglib.
Raptor Templates supports extending the language with custom tags and attributes. A custom tag or a custom attribute __must have at least one dash__ to indicate that is not part of the standard HTML grammar.
Below illustrates how to use a simple custom tag:
```html
<div>
<app:hello name="World"/>
<my-hello name="World"/>
</div>
```
@ -905,19 +932,19 @@ _default-layout.rhtml:_
<html lang="en">
<head>
<meta charset="UTF-8">
<title><layout:placeholder name="title"/></title>
<title><layout-placeholder name="title"/></title>
</head>
<body>
<h1 c:if="data.showHeader !== false">
<layout:placeholder name="title"/>
<h1 c-if="data.showHeader !== false">
<layout-placeholder name="title"/>
</h1>
<p>
<layout:placeholder name="body"/>
<layout-placeholder name="body"/>
</p>
<div>
<layout:placeholder name="footer">
<layout-placeholder name="footer">
Default Footer
</layout:placeholder>
</layout-placeholder>
</div>
</body>
</html>
@ -926,10 +953,10 @@ _default-layout.rhtml:_
_Usage of `default-layout.rhtml`:_
```html
<layout:use template="./default-layout.rhtml" show-header="$true">
<layout:put into="title">My Page</layout:put>
<layout:put into="body">BODY CONTENT</layout:put>
</layout:use>
<layout-use template="./default-layout.rhtml" show-header="$true">
<layout-put into="title">My Page</layout-put>
<layout-put into="body">BODY CONTENT</layout-put>
</layout-use>
```
# Custom Taglibs
@ -965,9 +992,8 @@ A tag renderer should be mapped to a custom tag by creating a `raptor-taglib.jso
```json
{
"namespace": "my-taglib",
"tags": {
"hello": {
"my-hello": {
"renderer": "./hello-renderer.js",
"attributes": {
"name": "string"
@ -977,41 +1003,20 @@ A tag renderer should be mapped to a custom tag by creating a `raptor-taglib.jso
}
```
### Taglib Namespace
Every taglib should be associated with one or more namespaces as shown below:
```json
{
"namespace": "my-taglib",
...
}
```
Multiple aliases can be offered:
```json
{
"namespace": ["my-taglib", "my"],
...
}
```
### Defining Tags
Tags can be defined by adding a `"tags"` property to your `raptor-taglib.json`:
```json
{
"namespace": "my-taglib",
"tags": {
"hello": {
"my-hello": {
"renderer": "./hello-renderer.js",
"attributes": {
"name": "string"
}
},
"foo": {
"my-foo": {
"renderer": "./foo-renderer.js",
"attributes": {
"*": "string"
@ -1049,7 +1054,6 @@ With this approach, `raptor-taglib.json` will be much simpler:
```json
{
"namespace": "my-taglib",
"tags-dir": "./components"
}
```
@ -1057,20 +1061,20 @@ With this approach, `raptor-taglib.json` will be much simpler:
Given the following directory structure:
* __components/__
* __hello/__
* __my-hello/__
* renderer.js
* __foo/__
* __my-foo/__
* renderer.js
* __bar/__
* __my-bar/__
* renderer.js
* raptor-tag.json
* raptor-taglib.json
The following three tags will be exported as part of the "my-taglib" namespace:
The following three tags will be exported:
* `<my-taglib:hello>`
* `<my-taglib:foo>`
* `<my-taglib:bar>`
* `<my-hello>`
* `<my-foo>`
* `<my-bar>`
Directory scanning only supports one tag per directory and it will only look at directories one level deep. The tag definition can be embedded into the `renderer.js` file or it can be put into a separate `raptor-tag.json`. For example:
@ -1101,24 +1105,23 @@ _NOTE: It is not necessary to declare the `renderer` since the scanner will auto
It is often necessary for tags to have a parent/child or ancestor/descendent relationship. For example:
```html
<ui:tabs>
<ui:tab label="Overview"></ui:tab>
<ui:tab label="Language Guide"></ui:tab>
<ui:tab label="JavaScript API"></ui:tab>
</ui:tabs>
<ui-tabs>
<ui-tab label="Overview"></ui-tab>
<ui-tab label="Language Guide"></ui-tab>
<ui-tab label="JavaScript API"></ui-tab>
</ui-tabs>
```
Raptor Templates supports this by leveraging JavaScript closures in the compiled output. A tag can introduce scoped variables that are available to nested tags. This is shown in the sample `raptor-taglib.json` below:
```json
{
"namespace": "ui",
"tags": {
"tabs": {
"ui-tabs": {
"renderer": "./tabs-tag.js",
"var": "tabs"
},
"tab": {
"ui-tab": {
"renderer": "./tab-tag.js",
"import-var": {
"tabs": "tabs"
@ -1131,7 +1134,7 @@ Raptor Templates supports this by leveraging JavaScript closures in the compiled
}
```
In the above example, the `<ui:tabs>` tag will introduce a scoped variable named `tabs` that is then automatically imported by the nested `<ui:tab>` tags. When the nested `<ui:tab>` tags render they can use the scoped variable to communicate with the renderer for the `<ui:tabs>` tag.
In the above example, the `<ui-tabs>` tag will introduce a scoped variable named `tabs` that is then automatically imported by the nested `<ui-tab>` tags. When the nested `<ui-tab>` tags render they can use the scoped variable to communicate with the renderer for the `<ui-tabs>` tag.
The complete code for this example is shown below:
@ -1143,7 +1146,7 @@ var raptorTemplates = require('raptor-templates');
module.exports = function render(input, context) {
var nestedTabs = [];
// Invoke the body function to discover nested <ui:tab> tags
// Invoke the body function to discover nested <ui-tab> tags
input.invokeBody({ // Invoke the body with the scoped "tabs" variable
addTab: function(tab) {
tab.id = tab.id || ("tab" + tabs.length);
@ -1173,15 +1176,15 @@ _components/tabs/template.rhtml:_
```html
<div class="tabs">
<ul class="nav nav-tabs">
<li class="tab" c:for="tab in data.tabs">
<li class="tab" c-for="tab in data.tabs">
<a href="#${tab.id}" data-toggle="tab">
${tab.title}
</a>
</li>
</ul>
<div class="tab-content">
<div id="${tab.id}" class="tab-pane" c:for="tab in data.tabs">
<c:invoke function="tab.invokeBody()"/>
<div id="${tab.id}" class="tab-pane" c-for="tab in data.tabs">
<c-invoke function="tab.invokeBody()"/>
</div>
</div>
</div>
@ -1203,3 +1206,84 @@ As an example, given a template at path `/my-project/src/pages/login/template.rh
7. `/my-project/raptor-taglib.json`
8. `/my-project/node_modules/*/raptor-taglib.json`
# FAQ
__Question:__ _Is Raptor Templates ready for production use?_
__Answer__: Yes, Raptor Templates has been battle-tested at [eBay](http://www.ebay.com/) and other companies for well over a year and has been designed with high performance, scalability, security and stability in mind. However, the latest version of Raptor Templates is still being marked as beta as we nail down the final feature set as part of the [RaptorJS 3 initiative](https://github.com/raptorjs/raptorjs/wiki/RaptorJS-3-Plan).
<hr>
__Question:__ _Can templates be compiled on the client?_
__Answer__: Possibly, but it is not recommended and it will likely not work in older browsers. The compiler is optimized to produce small, high performance compiled templates, but the compiler itself is not small and it comes bundled with some heavyweight modules such as a [JavaScript HTML parser](https://github.com/fb55/htmlparser2). In short, always compile your templates on the server. The [RaptorJS Optimizer](https://github.com/raptorjs3/raptor-optimizer) is recommended for including compiled templates as part of a web page.
<hr>
__Question:__ _Which web browsers are supported?_
__Answer__: The runtime for template rendering is supported in all web browsers. If you find an issue please report a bug.
<hr>
__Question:__ _How can Raptor Templates be used with Express?_
__Answer__: The recommended way to use Raptor Templates with Express is to bypass the Express view engine and instead directly pipe the rendering output stream to the response stream as shown in the following code:
```javascript
var raptorTemplates = require('raptor-templates');
var templatePath = require.resolve('./template.rhtml');
app.get('/profile', function(req, res) {
raptorTemplates
.stream(templatePath, {
name: 'Frank'
})
.pipe(res);
});
```
With this approach, you can benefit from streaming and there is no middleman (less complexity).
Alternatively, you can use the independent [view-engine](https://github.com/patrick-steele-idem/view-engine) module to provide an abstraction that allows pluggable view engines and provides full support for streaming. This is shown in the following sample code:
```javascript
var template = require('view-engine').load(require.resolve('./template.rhtml'));
app.get('/profile', function(req, res) {
template.stream({
name: 'Frank'
})
.pipe(res);
});
```
<hr>
__Question:__ _I heard Raptor Templates is XML-based. What is that about?_
__Answer__: Raptor Templates started out using an XML parser. This required that templates be well-formed XML (a major source of problems). This is no longer the case, as the compiler has been updated to use the awesome [htmlparser2](https://github.com/fb55/htmlparser2) module by [Felix Boehm](https://github.com/fb55). Also, XML namespaces are no longer used and all taglibs are now defined using simple JSON. If you are coming from the old XML-based version of Raptor Templates, please see the [Migration Guide](migration.md).
<hr>
__Question:__ _What is the recommended directory structure for templates and "partials"_
__Answer__: Your templates should be organized just like all other JavaScript modules. You should put your templates right next to the code that refers to them. That is, do not create a separate "templates" directory. For a sample Express app that uses Raptor Templates, please see [raptorjs-express-app](https://github.com/raptorjs3/samples/tree/master/raptorjs-express-app).
<hr>
__Question:__ _How is Raptor Templates related to [RaptorJS](http://raptorjs.org)?_
__Answer__: Raptor Templates is one of the modules that is part of the RaptorJS toolkit. It used to be a submodule, but now it has been split out into its own top-level Node.js module (for history, please see the [RaptorJS 3 Plan](https://github.com/raptorjs/raptorjs/wiki/RaptorJS-3-Plan) page).
# Discuss
Please post questions or comments on the [RaptorJS Google Groups Discussion Forum](http://groups.google.com/group/raptorjs).
# Contributors
* [Patrick Steele-Idem](https://github.com/patrick-steele-idem) (Twitter: [@psteeleidem](http://twitter.com/psteeleidem))
* [Phillip Gates-Idem](https://github.com/philidem/) (Twitter: [@philidem](https://twitter.com/philidem))
* Ramesh Mahadevan
# Contribute
Pull Requests welcome. Please submit Github issues for any feature enhancements, bugs or documentation problems.
# License
Apache License v2.0

View File

@ -31,6 +31,12 @@ function ElementNode(localName, namespace, prefix) {
this.attributesByNS = {};
this.prefix = prefix;
this.localName = this.tagName = localName;
if (this.prefix) {
this.qName = this.prefix + ':' + localName;
} else {
this.qName = localName;
}
this.namespace = namespace;
this.allowSelfClosing = false;
this.startTagOnly = false;

View File

@ -19,6 +19,8 @@ var Expression = require('./Expression');
var strings = require('raptor-strings');
var stringify = require('raptor-json/stringify');
var regexp = require('raptor-regexp');
var ok = require('assert').ok;
var endingTokens = {
'${': '}',
'$!{': '}',
@ -247,6 +249,9 @@ function ExpressionParser() {
* @param thisObj
*/
ExpressionParser.parse = function (str, callback, thisObj, options) {
ok(str != null, '"str" is required');
if (!options) {
options = {};
}

View File

@ -16,6 +16,8 @@
'use strict';
var createError = require('raptor-util').createError;
var forEachEntry = require('raptor-util').forEachEntry;
var extend = require('raptor-util').extend;
var isArray = Array.isArray;
var isEmpty = require('raptor-objects').isEmpty;
function Node(nodeType) {
@ -69,95 +71,31 @@ Node.prototype = {
compiler.addError(error + ' (' + this.toString() + ')', pos);
},
resolveNamespace: function(namespace) {
if (namespace == null) {
namespace = '';
}
return this.compiler ?
this.compiler.taglibs.resolveNamespace(namespace) || namespace :
namespace;
return namespace || '';
},
setProperty: function (name, value) {
this.setPropertyNS(null, name, value);
},
setPropertyNS: function (namespace, name, value) {
namespace = this.resolveNamespace(namespace);
var namespacedProps = this.properties[namespace];
if (!namespacedProps) {
namespacedProps = this.properties[namespace] = {};
}
namespacedProps[name] = value;
this.properties[name] = value;
},
setProperties: function (props) {
this.setPropertiesNS(null, props);
},
setPropertiesNS: function (namespace, props) {
namespace = this.resolveNamespace(namespace);
forEachEntry(props, function (name, value) {
this.setPropertyNS(namespace, name, value);
}, this);
},
getPropertyNamespaces: function () {
return Object.keys(this.properties);
if (!props) {
return;
}
extend(this.properties, props);
},
getProperties: function () {
return this.getPropertiesNS(null);
},
hasProperty: function (name) {
return this.hasPropertyNS('', name);
},
hasPropertyNS: function (namespace, name) {
namespace = this.resolveNamespace(namespace);
var namespaceProps = this.properties[namespace];
return namespaceProps.hasOwnProperty(name);
},
getPropertiesByNS: function () {
return this.properties;
},
getPropertiesNS: function (namespace) {
namespace = this.resolveNamespace(namespace);
return this.properties[namespace];
hasProperty: function (name) {
return this.properties.hasOwnProperty(name);
},
forEachProperty: function (callback, thisObj) {
forEachEntry(this.properties, function (namespace, properties) {
forEachEntry(properties, function (name, value) {
callback.call(thisObj, namespace, name, value);
}, this);
}, this);
forEachEntry(this.properties, callback, this);
},
getProperty: function (name) {
return this.getPropertyNS(null, name);
},
getPropertyNS: function (namespace, name) {
namespace = this.resolveNamespace(namespace);
var namespaceProps = this.properties[namespace];
return namespaceProps ? namespaceProps[name] : undefined;
return this.properties[name];
},
removeProperty: function (name) {
this.removePropertyNS('', name);
},
removePropertyNS: function (namespace, name) {
namespace = this.resolveNamespace(namespace);
var namespaceProps = this.properties[namespace];
if (namespaceProps) {
delete namespaceProps[name];
}
if (isEmpty(namespaceProps)) {
delete this.properties[namespace];
}
},
removePropertiesNS: function (namespace) {
namespace = this.resolveNamespace(namespace);
delete this.properties[namespace];
},
forEachPropertyNS: function (namespace, callback, thisObj) {
namespace = this.resolveNamespace(namespace);
var props = this.properties[namespace];
if (props) {
forEachEntry(props, function (name, value) {
callback.call(thisObj, name, value);
}, this);
}
delete this.properties[name];
},
forEachChild: function (callback, thisObj) {
if (!this.firstChild) {
@ -443,7 +381,7 @@ Node.prototype = {
} else {
//There is a strip expression
if (!this.generateBeforeCode || !this.generateAfterCode) {
this.addError('The c:strip directive is not supported for node ' + this);
this.addError('The c-strip directive is not supported for node ' + this);
this.generateCodeForChildren(template);
return;
}

View File

@ -110,8 +110,7 @@ ParseTreeBuilder.prototype = {
function getNS(node) {
if (node.namespace) {
return node.namespace;
}
else if (node.prefix) {
} else if (node.prefix) {
if (node.prefix === 'xml') {
return 'http://www.w3.org/XML/1998/namespace';
}
@ -121,11 +120,8 @@ ParseTreeBuilder.prototype = {
return '';
}
}
var taglibs = this.taglibs;
var elNS = getNS(el);
elNS = taglibs.resolveNamespace(elNS) || elNS;
var elementNode = new ElementNode(
el.localName,
@ -140,20 +136,15 @@ ParseTreeBuilder.prototype = {
elementNode.setRoot(true);
if (!el.namespace && el.localName === 'template') {
elementNode.namespace = 'core';
if (!elNS && el.localName === 'template') {
elementNode.localName = 'c-template';
}
this.rootNode = elementNode;
}
attributes.forEach(function (attr) {
if (attr.prefix === 'xmlns') {
return; // Skip xmlns attributes
}
var attrNS = getNS(attr);
attrNS = taglibs.resolveNamespace(attrNS) || attrNS;
var attrLocalName = attr.localName;
var attrPrefix = attr.prefix;
elementNode.setAttributeNS(attrNS, attrLocalName, attr.value, attrPrefix);

View File

@ -52,7 +52,7 @@ ParseTreeBuilderHtml.prototype = {
var _this = this;
// Create a pseudo root node
this.handleStartElement(splitName('c:template'), []);
this.handleStartElement(splitName('c-template'), []);
var parser = this.parser = new htmlparser.Parser({
onopentag: function(name, attribs){

View File

@ -15,23 +15,18 @@
*/
'use strict';
var createError = require('raptor-util').createError;
var forEachEntry = require('raptor-util').forEachEntry;
var ok = require('assert').ok;
var makeClass = require('raptor-util').makeClass;
function Taglib(id) {
ok(id, '"id" expected');
this.id = id;
this.dirname = null;
this.namespace = null;
this.namespaces = [];
this.tags = {};
this.textTransformers = [];
this.attributeMap = {};
this.functions = [];
this.helperObject = null;
this.attributes = {};
this.patternAttributes = [];
this.importPaths = [];
this.inputFilesLookup = {};
}
@ -46,19 +41,16 @@ Taglib.prototype = {
},
addAttribute: function (attribute) {
if (attribute.namespace) {
throw createError(new Error('"namespace" is not allowed for taglib attributes'));
}
if (attribute.pattern) {
this.patternAttributes.push(attribute);
} else if (attribute.name) {
this.attributeMap[attribute.name] = attribute;
this.attributes[attribute.name] = attribute;
} else {
throw new Error('Invalid attribute: ' + require('util').inspect(attribute));
}
},
getAttribute: function (name) {
var attribute = this.attributeMap[name];
var attribute = this.attributes[name];
if (!attribute) {
for (var i = 0, len = this.patternAttributes.length; i < len; i++) {
var patternAttribute = this.patternAttributes[i];
@ -72,9 +64,8 @@ Taglib.prototype = {
addTag: function (tag) {
ok(arguments.length === 1, 'Invalid args');
ok(tag.name, '"tag.name" is required');
var key = (tag.namespace == null ? this.id : tag.namespace) + ':' + tag.name;
this.tags[key] = tag;
this.tags[tag.name] = tag;
tag.taglibId = this.id;
},
addTextTransformer: function (transformer) {
this.textTransformers.push(transformer);
@ -83,247 +74,184 @@ Taglib.prototype = {
forEachEntry(this.tags, function (key, tag) {
callback.call(thisObj, tag);
}, this);
},
addFunction: function (func) {
this.functions.push(func);
},
setHelperObject: function (helperObject) {
this.helperObject = helperObject;
},
getHelperObject: function () {
return this.helperObject;
},
addNamespace: function (ns) {
this.namespaces.push(ns);
}
};
Taglib.Tag = (function () {
function Tag(taglib) {
ok(taglib, '"taglib" expected');
this.taglib = taglib;
Taglib.Tag = makeClass({
$init: function(taglib) {
this.taglibId = taglib ? taglib.id : null;
this.renderer = null;
this.nodeClass = null;
this.template = null;
this.attributeMap = {};
this.attributes = {};
this.transformers = {};
this.nestedVariables = {};
this.importedVariables = {};
this.patternAttributes = [];
}
Tag.prototype = {
inheritFrom: function (superTag) {
var subTag = this;
/*
* Have the sub tag inherit any properties from the super tag that are not in the sub tag
*/
forEachEntry(superTag, function (k, v) {
if (subTag[k] === undefined) {
subTag[k] = v;
},
inheritFrom: function (superTag) {
var subTag = this;
/*
* Have the sub tag inherit any properties from the super tag that are not in the sub tag
*/
forEachEntry(superTag, function (k, v) {
if (subTag[k] === undefined) {
subTag[k] = v;
}
});
function inheritProps(sub, sup) {
forEachEntry(sup, function (k, v) {
if (!sub[k]) {
sub[k] = v;
}
});
function inheritProps(sub, sup) {
forEachEntry(sup, function (k, v) {
if (!sub[k]) {
sub[k] = v;
}
});
}
[
'attributeMap',
'transformers',
'nestedVariables',
'importedVariables'
].forEach(function (propName) {
inheritProps(subTag[propName], superTag[propName]);
});
subTag.patternAttributes = superTag.patternAttributes.concat(subTag.patternAttributes);
},
forEachVariable: function (callback, thisObj) {
forEachEntry(this.nestedVariables, function (key, variable) {
callback.call(thisObj, variable);
});
},
forEachImportedVariable: function (callback, thisObj) {
forEachEntry(this.importedVariables, function (key, importedVariable) {
callback.call(thisObj, importedVariable);
});
},
forEachTransformer: function (callback, thisObj) {
forEachEntry(this.transformers, function (key, transformer) {
callback.call(thisObj, transformer);
});
},
hasTransformers: function () {
/*jshint unused:false */
for (var k in this.transformers) {
return true;
}
return false;
},
addAttribute: function (attr) {
if (attr.pattern) {
this.patternAttributes.push(attr);
} else {
var namespace = attr.namespace;
if (namespace == null) {
namespace = this.taglib.id;
}
if (attr.name === '*') {
attr.dynamicAttribute = true;
if (attr.targetProperty === null || attr.targetProperty === '') {
attr.targetProperty = null;
}
else if (!attr.targetProperty) {
attr.targetProperty = '*';
}
}
this.attributeMap[namespace + ':' + attr.name] = attr;
}
},
getAttribute: function (tagNS, localName) {
if (tagNS == null) {
tagNS = this.taglib.id;
}
var attr = this.attributeMap[tagNS + ':' + localName] || this.attributeMap[tagNS + ':*'] || this.attributeMap['*:' + localName] || this.attributeMap['*:*'];
if (!attr && this.patternAttributes.length) {
for (var i = 0, len = this.patternAttributes.length; i < len; i++) {
var patternAttribute = this.patternAttributes[i];
var attrNS = patternAttribute.namespace;
if (attrNS == null) {
attrNS = tagNS;
}
if (attrNS === tagNS && patternAttribute.pattern.test(localName)) {
attr = patternAttribute;
break;
}
}
}
return attr;
},
toString: function () {
var qName = this.namespace ? this.namespace + ':' + this.name : this.name;
return '[Tag: <' + qName + '@' + this.taglib.id + '>]';
},
forEachAttribute: function (callback, thisObj) {
for (var attrName in this.attributeMap) {
if (this.attributeMap.hasOwnProperty(attrName)) {
callback.call(thisObj, this.attributeMap[attrName]);
}
}
},
addNestedVariable: function (nestedVariable) {
var key = nestedVariable.nameFromAttribute ? 'attr:' + nestedVariable.nameFromAttribute : nestedVariable.name;
this.nestedVariables[key] = nestedVariable;
},
addImportedVariable: function (importedVariable) {
var key = importedVariable.targetProperty;
this.importedVariables[key] = importedVariable;
},
addTransformer: function (transformer) {
var key = transformer.path;
transformer.taglib = this.taglib;
this.transformers[key] = transformer;
}
};
return Tag;
}());
Taglib.Attribute = (function () {
function Attribute(namespace, name) {
this.namespace = namespace;
[
'attributes',
'transformers',
'nestedVariables',
'importedVariables'
].forEach(function (propName) {
inheritProps(subTag[propName], superTag[propName]);
});
subTag.patternAttributes = superTag.patternAttributes.concat(subTag.patternAttributes);
},
forEachVariable: function (callback, thisObj) {
forEachEntry(this.nestedVariables, function (key, variable) {
callback.call(thisObj, variable);
});
},
forEachImportedVariable: function (callback, thisObj) {
forEachEntry(this.importedVariables, function (key, importedVariable) {
callback.call(thisObj, importedVariable);
});
},
forEachTransformer: function (callback, thisObj) {
forEachEntry(this.transformers, function (key, transformer) {
callback.call(thisObj, transformer);
});
},
hasTransformers: function () {
/*jshint unused:false */
for (var k in this.transformers) {
if (this.transformers.hasOwnProperty(k)) {
return true;
}
}
return false;
},
addAttribute: function (attr) {
if (attr.pattern) {
this.patternAttributes.push(attr);
} else {
if (attr.name === '*') {
attr.dynamicAttribute = true;
if (attr.targetProperty === null || attr.targetProperty === '') {
attr.targetProperty = null;
}
else if (!attr.targetProperty) {
attr.targetProperty = '*';
}
}
this.attributes[attr.name] = attr;
}
},
toString: function () {
return '[Tag: <' + this.name + '@' + this.taglibId + '>]';
},
forEachAttribute: function (callback, thisObj) {
for (var attrName in this.attributes) {
if (this.attributes.hasOwnProperty(attrName)) {
callback.call(thisObj, this.attributes[attrName]);
}
}
},
addNestedVariable: function (nestedVariable) {
var key = nestedVariable.nameFromAttribute ? 'attr:' + nestedVariable.nameFromAttribute : nestedVariable.name;
this.nestedVariables[key] = nestedVariable;
},
addImportedVariable: function (importedVariable) {
var key = importedVariable.targetProperty;
this.importedVariables[key] = importedVariable;
},
addTransformer: function (transformer) {
var key = transformer.path;
transformer.taglibId = this.taglibId;
this.transformers[key] = transformer;
}
});
Taglib.Attribute = makeClass({
$init: function(name) {
this.name = name;
this.type = null;
this.required = false;
this.type = 'string';
this.allowExpressions = true;
}
Attribute.prototype = {};
return Attribute;
}());
Taglib.Property = (function () {
function Property() {
});
Taglib.Property = makeClass({
$init: function() {
this.name = null;
this.type = 'string';
this.value = undefined;
}
Property.prototype = {};
return Property;
}());
Taglib.NestedVariable = (function () {
function NestedVariable() {
});
Taglib.NestedVariable = makeClass({
$init: function() {
this.name = null;
}
NestedVariable.prototype = {};
return NestedVariable;
}());
Taglib.ImportedVariable = (function () {
function ImportedVariable() {
});
Taglib.ImportedVariable = makeClass({
$init: function() {
this.targetProperty = null;
this.expression = null;
}
ImportedVariable.prototype = {};
return ImportedVariable;
}());
Taglib.Transformer = (function () {
var uniqueId = 0;
function Transformer() {
this.id = uniqueId++;
});
var nextTransformerId = 0;
Taglib.Transformer = makeClass({
$init: function() {
this.id = nextTransformerId++;
this.name = null;
this.tag = null;
this.path = null;
this.after = null;
this.before = null;
this.priority = null;
this.instance = null;
this.properties = {};
}
Transformer.prototype = {
getInstance: function () {
if (!this.path) {
throw createError(new Error('Transformer class not defined for tag transformer (tag=' + this.tag + ')'));
}
if (!this.instance) {
var Clazz = require(this.path);
if (Clazz.process) {
return Clazz;
}
},
if (typeof Clazz !== 'function') {
console.error('Invalid transformer: ', Clazz);
throw new Error('Invalid transformer at path "' + this.path + '": ' + Clazz);
}
this.instance = new Clazz();
this.instance.id = this.id;
}
return this.instance;
},
toString: function () {
return '[Taglib.Transformer: ' + this.filename + ']';
getInstance: function () {
if (!this.path) {
throw new Error('Transformer class not defined for tag transformer (tag=' + this.tag + ')');
}
};
return Transformer;
}());
Taglib.Function = (function () {
function Func() {
this.name = null;
this.path = null;
this.bindToContext = false;
if (!this.instance) {
var Clazz = require(this.path);
if (Clazz.process) {
return Clazz;
}
if (typeof Clazz !== 'function') {
console.error('Invalid transformer: ', Clazz);
throw new Error('Invalid transformer at path "' + this.path + '": ' + Clazz);
}
this.instance = new Clazz();
this.instance.id = this.id;
}
return this.instance;
},
toString: function () {
return '[Taglib.Transformer: ' + this.path + ']';
}
Func.prototype = {};
return Func;
}());
Taglib.HelperObject = (function () {
function HelperObject() {
this.path = null;
}
HelperObject.prototype = {};
return HelperObject;
}());
});
module.exports = Taglib;

View File

@ -1,341 +1,214 @@
var ok = require('assert').ok;
var createError = require('raptor-util').createError;
var forEachEntry = require('raptor-util').forEachEntry;
function transformerComparator(a, b) {
a = a.priority;
b = b.priority;
if (a == null) {
a = Number.MAX_VALUE;
}
if (b == null) {
b = Number.MAX_VALUE;
}
return a - b;
}
function merge(target, source) {
for (var k in source) {
if (source.hasOwnProperty(k)) {
if (target[k] && typeof target[k] === 'object' &&
source[k] && typeof source[k] === 'object') {
if (Array.isArray(target[k]) || Array.isArray(source[k])) {
var targetArray = target[k];
var sourceArray = source[k];
if (!Array.isArray(targetArray)) {
targetArray = [targetArray];
}
if (!Array.isArray(sourceArray)) {
sourceArray = [sourceArray];
}
target[k] = [].concat(targetArray).concat(sourceArray);
} else {
var Ctor = target[k].constructor;
var newTarget = new Ctor();
merge(newTarget, target[k]);
merge(newTarget, source[k]);
target[k] = newTarget;
}
} else {
target[k] = source[k];
}
}
}
return target;
}
function TaglibLookup() {
this.namespaces = {};
this.tagTransformers = {};
this.tags = {};
this.textTransformers = [];
this.functions = {};
this.attributes = {};
this.nestedTags = {};
this.merged = {};
this.taglibsById = {};
this.unresolvedAttributes = [];
this._inputFiles = null;
}
TaglibLookup.prototype = {
resolveNamespaceWithDefault: function(namespace, defaultTaglibId) {
if (namespace === '*' || namespace === '') {
return namespace;
}
if (namespace == null) {
return defaultTaglibId;
}
var target = this.namespaces[namespace];
if (!target) {
throw new Error('Invalid namespace of "' + namespace + '" in taglib "' + defaultTaglibId + '"');
}
return target;
},
resolveNamespace: function(namespace) {
if (namespace == null || namespace === '') {
return '';
}
return this.taglibsById[namespace] ? namespace : this.namespaces[namespace];
},
resolveNamespaceForTag: function(tag) {
return tag.namespace ? this.resolveNamespace(tag.namespace) : tag.taglib.id;
},
isTaglib: function(namespace) {
return this.taglibsById[namespace] !=null || this.namespaces[namespace] != null;
},
addTaglib: function (taglib) {
ok(taglib, '"taglib" is required');
ok(taglib.id, '"taglib.id" expected');
var id = taglib.id;
if (this.taglibsById[id]) {
// The taglib has already been added
if (this.taglibsById.hasOwnProperty(taglib.id)) {
return;
}
var _this = this;
this.taglibsById[taglib.id] = taglib;
taglib.namespaces.forEach(function(ns) {
_this.namespaces[ns] = id;
});
merge(this.merged, taglib);
},
getTag: function (element) {
if (typeof element === 'string') {
element = {
localName: element
};
}
var tags = this.merged.tags;
if (!tags) {
return;
}
/*
* Index all of the tags in the taglib by registering them
* based on the tag URI and the tag name
*/
taglib.forEachTag(function (tag, i) {
var tagKey = element.namespace ? element.namespace + ':' + element.localName : element.localName;
return tags[tagKey];
},
// Use the fully resolved namespace for the tag
// For example:
// core --> /development/raptor-templates/taglibs/core/core.rtld
var tagNS = this.resolveNamespaceWithDefault(tag.namespace, id);
tag.taglibId = id;
var name = tag.name;
var key = name.indexOf('*') === -1 ? tagNS + ':' + name : name;
getAttribute: function (element, attr) {
//The taglib will be registered using the combination of URI and tag name
this.tags[key] = tag;
if (typeof element === 'string') {
element = {
localName: element
};
}
//Register the tag using the combination of URI and tag name so that it can easily be looked up
if (tag.hasTransformers()) {
var tagTransformersForTags = this.tagTransformers[key] || (this.tagTransformers[key] = []);
//A reference to the array of the tag transformers with the same key
//Now add all of the transformers for the node (there will typically only be one...)
tag.forEachTransformer(function (transformer) {
if (!transformer) {
throw createError(new Error('Transformer is null'));
if (typeof attr === 'string') {
attr = {
localName: attr
};
}
var tags = this.merged.tags;
if (!tags) {
return;
}
var tagKey = element.namespace ? element.namespace + ':' + element.localName : element.localName;
var tag = tags[tagKey];
if (!tag) {
tag = tags['*'];
if (!tag) {
return;
}
}
var attrKey = attr.namespace ? attr.namespace + ':' + attr.localName : attr.localName;
function findAttribute(attributes) {
var attribute = attributes[attrKey];
if (attribute === undefined) {
if (tag.patternAttributes) {
for (var i = 0, len = tag.patternAttributes.length; i < len; i++) {
var patternAttribute = tag.patternAttributes[i];
if (patternAttribute.pattern.test(attrKey)) {
attribute = patternAttribute;
break;
}
}
tagTransformersForTags.push(transformer);
}, this);
}
tag.forEachAttribute(function (attr) {
if (attr.namespace) {
this.unresolvedAttributes.push({
tag: tag,
attr: attr
});
} else {
this.addAttribute(tag, attr);
}
}, this);
}, this);
/*
* Now register all of the text transformers that are part of the provided taglibs
*/
taglib.textTransformers.forEach(function (textTransformer) {
this.textTransformers.push(textTransformer);
}, this);
taglib.functions.forEach(function (func) {
if (!func.name) {
throw createError(new Error('Function name not set.'));
}
this.functions[taglib.id + ':' + func.name] = func;
}, this);
this.taglibsById[id] = taglib;
},
finish: function() {
for (var i=0; i<this.unresolvedAttributes.length; i++) {
var unresolvedAttribute = this.unresolvedAttributes[i];
this.addAttribute(unresolvedAttribute.tag, unresolvedAttribute.attr);
}
this.unresolvedAttributes.length = 0;
},
addAttribute: function(tag, attr) {
var attrNS = this.resolveNamespaceWithDefault(attr.namespace, tag.taglibId);
var tagNS = this.resolveNamespaceWithDefault(tag.namespace, tag.taglibId);
if (attrNS !== '*' && attrNS === tagNS) {
attrNS = '';
}
if (attr.name) {
this.attributes[tagNS + ':' + tag.name + ':' + attrNS + ':' + attr.name] = attr;
}
},
getAttribute: function (tagNS, tagName, attrNS, attrName) {
var tags = this.tags;
tagNS = this.resolveNamespace(tagNS);
attrNS = this.resolveNamespace(attrNS);
var attributes = this.attributes;
function _findAttrForTag(tagLookupKey) {
var attr = attributes[tagLookupKey + ':' + attrNS + ':' + attrName] ||
attributes[tagLookupKey + ':' + attrNS + ':*'] ||
attributes[tagLookupKey + ':*:' + attrName] ||
attributes[tagLookupKey + ':*:*'];
if (!attr) {
var tag = tags[tagLookupKey];
if (tag) {
attr = tag.getAttribute(attrNS, attrName);
if (attribute === undefined) {
attribute = tag.attributes['*'];
}
}
return attr;
return attribute;
}
var attr = _findAttrForTag(tagNS + ':' + tagName) ||
_findAttrForTag(tagNS + ':*') ||
_findAttrForTag('*:*');
if (attr && attr.namespace && attr.namespace !== '*') {
var sourceTaglibId = this.resolveNamespace(attr.namespace);
var taglib = this.taglibsById[sourceTaglibId];
if (!taglib) {
throw new Error('Taglib with namespace "' + attr.namespace + '" not found for imported attribute with name "' + attrName + '"');
}
var importedAttr = taglib.getAttribute(attrName);
if (!importedAttr) {
throw new Error('Attribute "' + attrName + '" imported from taglib with namespace "' + attr.namespace + '" not found in taglib "' + taglib.id + '".');
}
var attribute = findAttribute(tag.attributes);
attr = importedAttr;
if (attribute === null) {
// This is an imported attribute
attribute = findAttribute(this.merged.attributes);
}
return attr;
},
forEachTag: function (namespace, callback, thisObj) {
var taglibId = this.resolveNamespace(namespace);
var taglib = this.taglibsById[taglibId];
if (!taglib) {
return;
}
forEachEntry(taglib.tags, function (key, tag) {
callback.call(thisObj, tag, taglib);
});
return attribute;
},
forEachNodeTransformer: function (node, callback, thisObj) {
/*
* Based on the type of node we have to choose how to transform it
*/
if (node.isElementNode()) {
this.forEachTagTransformer(node.namespace, node.localName, callback, thisObj);
this.forEachTagTransformer(node, callback, thisObj);
} else if (node.isTextNode()) {
this.forEachTextTransformer(callback, thisObj);
}
},
forEachTagTransformer: function (tagNS, tagName, callback, thisObj) {
forEachTagTransformer: function (element, callback, thisObj) {
if (typeof element === 'string') {
element = {
localName: element
};
}
var tagKey = element.namespace ? element.namespace + ':' + element.localName : element.localName;
/*
* If the node is an element node then we need to find all matching
* transformers based on the URI and the local name of the element.
*/
tagNS = this.resolveNamespace(tagNS);
var _this = this;
function resolveBeforeAfterName(name) {
var parts = name.split(/[:\/]/);
parts[0] = _this.resolveNamespace(parts[0]);
return parts.join(':');
}
var matchingTransformersByName = {};
var matchingTransformers = [];
var handled = {};
var before = {};
function _addTransformers(transformers) {
if (!transformers) {
return;
var transformers = [];
function addTransformer(transformer) {
if (!transformer || !transformer.getInstance) {
throw createError(new Error('Invalid transformer'));
}
transformers.forEach(function (transformer) {
if (!transformer) {
throw createError(new Error('Invalid transformer'));
}
if (transformer.name) {
var fullName = transformer.taglib.id + ':' + transformer.name;
matchingTransformersByName[fullName] = transformer;
}
matchingTransformers.push(transformer);
if (transformer.before) {
var beforeName = resolveBeforeAfterName(transformer.before);
(before[beforeName] || (before[beforeName] = [])).push(transformer);
}
});
transformers.push(transformer);
}
/*
* Handle all of the transformers for all possible matching transformers.
*
* Start with the least specific and end with the most specific.
*/
_addTransformers(this.tagTransformers['*']);
_addTransformers(this.tagTransformers['*:*']);
//Wildcard for both URI and tag name (i.e. transformers that apply to every element)
_addTransformers(this.tagTransformers[tagNS + ':*']);
//Wildcard for tag name but matching URI (i.e. transformers that apply to every element with a URI, regadless of tag name)
_addTransformers(this.tagTransformers[tagNS + ':' + tagName]);
function _handleTransformer(transformer) {
if (!handled[transformer.id]) {
handled[transformer.id] = true;
if (transformer.after) {
var afterName = resolveBeforeAfterName(transformer.after);
//Check if this transformer is required to run
if (!matchingTransformersByName[afterName]) {
throw createError(new Error('After transformers not found for "' + transformer.after + '"'));
}
_handleTransformer(matchingTransformersByName[afterName]); //Handle any transformers that this transformer is supposed to run after
}
if (transformer.name) {
var transformerId = transformer.taglib.id + ':' + transformer.name;
if (before[transformerId]) {
before[transformerId].forEach(_handleTransformer);
}
}
//Handle any transformers that are configured to run before this transformer
callback.call(thisObj, transformer);
}
if (this.merged.tags[tagKey]) {
this.merged.tags[tagKey].forEachTransformer(addTransformer);
}
matchingTransformers.forEach(_handleTransformer, this);
if (this.merged.tags['*']) {
this.merged.tags['*'].forEachTransformer(addTransformer);
}
transformers.sort(transformerComparator);
transformers.forEach(callback, thisObj);
},
forEachTextTransformer: function (callback, thisObj) {
this.textTransformers.forEach(function(textTransformer) {
var keepGoing = callback.call(thisObj, textTransformer);
if (keepGoing === false) {
return false;
}
return true;
});
this.merged.textTransformers.sort(transformerComparator);
this.merged.textTransformers.forEach(callback, thisObj);
},
getTag: function (namespace, localName) {
namespace = this.resolveNamespace(namespace);
var tag = this.tags[namespace + ':' + localName];
if (!tag) {
tag = this.tags[namespace + ':*']; //See if there was a wildcard tag definition in the taglib
}
return tag;
},
getNestedTag: function (parentTagNS, parentTagName, nestedTagNS, nestedTagName) {
parentTagNS = this.resolveNamespace(parentTagNS);
nestedTagNS = this.resolveNamespace(nestedTagNS);
return this.nestedTags[parentTagNS + ':' + parentTagName + ':' + nestedTagNS + ':' + nestedTagName];
},
getFunction: function (namespace, functionName) {
namespace = this.resolveNamespace(namespace);
return this.functions[namespace + ':' + functionName];
},
getHelperObject: function (namespace) {
namespace = this.resolveNamespace(namespace);
var taglib = this.taglibsById[namespace];
if (!taglib) {
throw new Error('Invalid taglib URI: ' + namespace);
}
return taglib.getHelperObject();
},
getInputFiles: function() {
if (!this._inputFiles) {
var inputFilesSet = {};

View File

@ -1,406 +0,0 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
var createError = require('raptor-util').createError;
var objectMapper = require('raptor-xml/object-mapper');
var regexp = require('raptor-regexp');
var Taglib = require('./Taglib');
var Tag = Taglib.Tag;
var Attribute = Taglib.Attribute;
var NestedVariable = Taglib.NestedVariable;
var ImportedVariable = Taglib.ImportedVariable;
var Transformer = Taglib.Transformer;
var Func = Taglib.Function;
var HelperObject = Taglib.HelperObject;
var nodePath = require('path');
var fs = require('fs');
var STRING = 'string';
var BOOLEAN = 'boolean';
var OBJECT = 'object';
function TaglibXmlLoader(src, filePath) {
this.src = src;
this.filePath = filePath;
}
TaglibXmlLoader.load = function (src, filePath) {
var loader = new TaglibXmlLoader(src, filePath);
return loader.load();
};
TaglibXmlLoader.prototype = {
load: function () {
var src = this.src;
var filePath = this.filePath;
var dirname = nodePath.dirname(filePath);
var tagsById = {};
function resolvePath(path) {
var resolvedPath = nodePath.resolve(dirname, path);
if (!resolvedPath.endsWith('.js')) {
resolvedPath += '.js';
}
if (!fs.existsSync(resolvedPath)) {
throw new Error('File does not exist: ' + resolvedPath);
}
return resolvedPath;
}
function handleTagExtends(subTag) {
var extendsId = subTag['extends'];
if (!extendsId) {
return;
}
delete subTag['extends'];
var superTag = tagsById[extendsId];
if (!superTag) {
throw createError(new Error('Parent tag with ID "' + extendsId + '" not found in taglib at path "' + filePath + '"'));
}
if (superTag['extends']) {
handleTagExtends(superTag);
}
subTag.inheritFrom(superTag);
}
var taglib;
var attributeHandler = {
_type: OBJECT,
_begin: function () {
return new Attribute();
},
_end: function (attr, parent) {
parent.addAttribute(attr);
},
'name': { _type: STRING },
'pattern': {
_type: STRING,
_set: function (parent, name, value) {
var patternRegExp = regexp.simple(value);
parent.pattern = patternRegExp;
}
},
'target-property': {
_type: STRING,
_targetProp: 'targetProperty'
},
'namespace': { _type: STRING },
'deprecated': { _type: STRING },
'required': { _type: BOOLEAN },
'type': { _type: STRING },
'allow-expressions': {
_type: BOOLEAN,
_targetProp: 'allowExpressions'
},
'preserve-name': {
_type: BOOLEAN,
_targetProp: 'preserveName'
},
'description': { _type: STRING },
'remove-dashes': {
_type: BOOLEAN,
_targetProp: 'removeDashes'
}
};
var importVariableHandler = {
_type: OBJECT,
_begin: function () {
return new ImportedVariable();
},
_end: function (importedVariable, tag) {
if (importedVariable.name) {
if (!importedVariable.targetProperty) {
importedVariable.targetProperty = importedVariable.name;
}
importedVariable.expression = importedVariable.name;
delete importedVariable.name;
}
if (!importedVariable.targetProperty) {
throw createError(new Error('The "target-property" attribute is required for an imported variable'));
}
if (!importedVariable.expression) {
throw createError(new Error('The "expression" attribute is required for an imported variable'));
}
tag.addImportedVariable(importedVariable);
},
'name': { _type: STRING },
'target-property': {
_type: STRING,
_targetProp: 'targetProperty'
},
'expression': { _type: STRING }
};
var variableHandler = {
_type: OBJECT,
_begin: function () {
return new NestedVariable();
},
_end: function (nestedVariable, tag) {
if (!nestedVariable.name && !nestedVariable.nameFromAttribute) {
throw createError(new Error('The "name" or "name-from-attribute" attribute is required for a nested variable'));
}
tag.addNestedVariable(nestedVariable);
},
'name': {
_type: STRING,
_targetProp: 'name'
},
'name-from-attribute': {
_type: STRING,
_targetProp: 'nameFromAttribute'
},
'name-from-attr': {
_type: STRING,
_targetProp: 'nameFromAttribute'
}
};
var handlers = {
'raptor-taglib': {
_type: OBJECT,
_begin: function () {
var newTaglib = new Taglib(filePath);
if (!taglib) {
taglib = newTaglib;
}
taglib.addInputFile(filePath);
return newTaglib;
},
'attribute': attributeHandler,
'tlib-version': {
_type: STRING,
_targetProp: 'version'
},
'uri': {
_type: STRING,
_set: function (taglib, name, value, context) {
taglib.addNamespace(value);
}
},
'namespace': {
_type: STRING,
_set: function (taglib, name, value, context) {
taglib.addNamespace(value);
}
},
'short-name': {
_type: STRING,
_set: function (taglib, name, value, context) {
taglib.addNamespace(value);
}
},
'prefix': {
_type: STRING,
_set: function (taglib, name, value, context) {
taglib.addNamespace(value);
}
},
'tag': {
_type: OBJECT,
_begin: function () {
return new Tag(taglib);
},
_end: function (tag) {
if (tag.namespace === undefined) {
tag.namespace = taglib.namespace;
}
tag.filename = filePath;
tag.dirname = dirname;
taglib.addTag(tag);
if (tag.id) {
tagsById[tag.id] = tag;
}
},
'name': {
_type: STRING,
_targetProp: 'name'
},
'namespace': {
_type: STRING,
_set: function (tag, name, value, context) {
tag.namespace = value || '';
}
},
'id': { _type: STRING },
'preserveSpace': {
_type: BOOLEAN,
_targetProp: 'preserveWhitespace'
},
'preserve-space': {
_type: BOOLEAN,
_targetProp: 'preserveWhitespace'
},
'preserve-whitespace': {
_type: BOOLEAN,
_targetProp: 'preserveWhitespace'
},
'preserveWhitespace': {
_type: BOOLEAN,
_targetProp: 'preserveWhitespace'
},
'extends': {
_type: STRING,
_targetProp: 'extends'
},
'handler-class': {
_type: STRING,
_set: function (tag, name, value, context) {
tag.renderer = resolvePath(value);
}
},
'renderer': {
_type: STRING,
_targetProp: 'renderer',
_set: function (tag, name, value, context) {
tag.renderer = resolvePath(value);
}
},
'template': {
_type: STRING,
_targetProp: 'template'
},
'node-class': {
_type: STRING,
_set: function (tag, name, path) {
tag.nodeClass = resolvePath(path);
}
},
'<attribute>': attributeHandler,
'nested-variable': variableHandler,
'variable': variableHandler,
'imported-variable': importVariableHandler,
'import-variable': importVariableHandler,
'transformer-path': {
_type: STRING,
_set: function (tag, name, path) {
var transformer = new Transformer();
transformer.dirname = dirname;
transformer.path = resolvePath(path);
tag.addTransformer(transformer);
}
},
'transformer': {
_type: OBJECT,
_begin: function () {
return new Transformer();
},
_end: function (transformer, tag) {
transformer.dirname = dirname;
tag.addTransformer(transformer);
},
'path': {
_type: STRING,
_set: function (transformer, name, path) {
transformer.path = resolvePath(path);
}
},
'after': {
_type: STRING,
_targetProp: 'after'
},
'before': {
_type: STRING,
_targetProp: 'before'
},
'name': {
_type: STRING,
_targetProp: 'name'
},
'<properties>': {
_type: OBJECT,
_begin: function (parent) {
return (parent.properties = {});
},
'<*>': { _type: STRING }
}
}
},
'text-transformer': {
_type: OBJECT,
_begin: function () {
return new Transformer();
},
_end: function (textTransformer) {
taglib.addTextTransformer(textTransformer);
},
'path': {
_type: STRING,
_set: function (transformer, name, path) {
transformer.path = resolvePath(path);
}
}
},
'import-taglib': {
_type: OBJECT,
_begin: function () {
return {};
},
_end: function (importedTaglib) {
var path = resolvePath(importedTaglib.path);
taglib.importPaths.push(path);
if (!fs.existsSync(path)) {
throw createError(new Error('Imported taglib with path "' + path + '" not found in taglib at path "' + filePath + '"'));
}
var importedXmlSource = fs.readFileSync(path);
require('../work-dir').recordLoadedTaglib(path);
var oldDirname = dirname;
dirname = nodePath.dirname(path);
objectMapper.read(importedXmlSource, path, handlers);
dirname = oldDirname;
},
'path': { _type: STRING }
},
'function': {
_type: OBJECT,
_begin: function () {
return new Func();
},
_end: function (func) {
taglib.addFunction(func);
},
'name': { _type: STRING },
'path': {
_type: STRING,
_set: function (func, name, path) {
func.path = resolvePath(path);
}
},
'bind-to-context': {
_type: BOOLEAN,
_targetProp: 'bindToContext'
}
},
'helper-object': {
_type: OBJECT,
_begin: function () {
return new HelperObject();
},
_end: function (helperObject) {
taglib.setHelperObject(helperObject);
},
'path': {
_type: STRING
}
}
}
};
objectMapper.read(src, filePath, handlers);
taglib.forEachTag(function (tag) {
handleTagExtends(tag);
});
taglib.dirname = dirname;
return taglib;
}
};
module.exports = TaglibXmlLoader;

View File

@ -21,6 +21,7 @@ var Expression = require('./Expression');
var TypeConverter = require('./TypeConverter');
var taglibLookup = require('./taglib-lookup');
var nodePath = require('path');
var ok = require('assert').ok;
function TemplateCompiler(path, options) {
this.dirname = nodePath.dirname(path);
@ -159,8 +160,10 @@ TemplateCompiler.prototype = {
getErrors: function () {
return this.errors;
},
getNodeClass: function (ns, localName) {
var tag = this.taglibs.getTag(ns, localName);
getNodeClass: function (tagName) {
ok(arguments.length === 1, 'Invalid args');
var tag = this.taglibs.getTag(tagName);
if (tag && tag.nodeClass) {
var nodeClass = require(tag.nodeClass);
if (nodeClass.prototype.constructor !== nodeClass) {
@ -168,13 +171,17 @@ TemplateCompiler.prototype = {
}
return nodeClass;
}
throw createError(new Error('Node class not found for namespace "' + ns + '" and localName "' + localName + '"'));
throw createError(new Error('Node class not found for tag "' + tagName + '"'));
},
createTag: function () {
var Taglib = require('./Taglib');
return new Taglib.Tag();
},
checkUpToDate: function(sourceFile, targetFile) {
if (this.options.checkUpToDate === false) {
return false;
}
var fs = require('fs');

View File

@ -31,7 +31,8 @@ var defaultOptions = {
'meta': true,
'link': true,
'hr': true
}
},
checkUpToDate: true
};
extend(exports, {
@ -86,3 +87,5 @@ extend(exports, {
EscapeXmlContext: require('./EscapeXmlContext'),
defaultOptions: defaultOptions
});
exports.TemplateCompiler = require('./TemplateCompiler');

View File

@ -6,67 +6,13 @@ var cache = {};
var forEachEntry = require('raptor-util').forEachEntry;
var raptorRegexp = require('raptor-regexp');
var tagDefFromCode = require('./tag-def-from-code');
function removeDashes(str) {
return str.replace(/-([a-z])/g, function (match, lower) {
return lower.toUpperCase();
});
}
function invokeHandlers(config, handlers, path) {
if (!config) {
throw new Error('"config" argument is required');
}
if (typeof config !== 'object') {
throw new Error('Object expected for ' + path);
}
for (var k in config) {
if (config.hasOwnProperty(k)) {
var value = config[k];
k = removeDashes(k);
var handler = handlers[k];
if (!handler) {
throw new Error('Invalid option of "' + k + '" for ' + path + '. Allowed: ' + Object.keys(handlers).join(', '));
}
try {
handler(value);
}
catch(e) {
if (!e.invokeHandlerError) {
var error = new Error('Error while applying option of "' + k + '" for ' + path + '. Exception: ' + (e.stack || e));
error.invokeHandlerError = e;
throw error;
}
else {
throw e;
}
}
}
}
if (handlers._end) {
try {
handlers._end();
}
catch(e) {
if (!e.invokeHandlerError) {
var error = new Error('Error for option ' + path + '. Exception: ' + (e.stack || e));
error.invokeHandlerError = e;
throw error;
}
else {
throw e;
}
}
}
}
var resolve = require('raptor-modules/resolver').serverResolveRequire;
var propertyHandlers = require('property-handlers');
function buildAttribute(attr, attrProps, path) {
invokeHandlers(attrProps, {
propertyHandlers(attrProps, {
type: function(value) {
attr.type = value;
},
@ -76,9 +22,6 @@ function buildAttribute(attr, attrProps, path) {
defaultValue: function(value) {
attr.defaultValue = value;
},
namespace: function(value) {
attr.namespace = value;
},
pattern: function(value) {
if (value === true) {
var patternRegExp = raptorRegexp.simple(attr.name);
@ -90,6 +33,12 @@ function buildAttribute(attr, attrProps, path) {
},
preserveName: function(value) {
attr.preserveName = value;
},
required: function(value) {
attr.required = value === true;
},
removeDashes: function(value) {
attr.removeDashes = value === true;
}
}, path);
@ -98,21 +47,7 @@ function buildAttribute(attr, attrProps, path) {
function handleAttributes(value, parent, path) {
forEachEntry(value, function(attrName, attrProps) {
var parts = attrName.split(':');
var namespace = null;
var localName = null;
if (parts.length === 2) {
namespace = parts[0];
localName = parts[1];
} else if (parts.length === 1) {
localName = attrName;
} else {
throw new Error('Invalid attribute name: ' + attrName);
}
var attr = new Taglib.Attribute(namespace, localName);
var attr = new Taglib.Attribute(attrName);
if (attrProps == null) {
attrProps = {
@ -139,18 +74,13 @@ function buildTag(tagObject, path, taglib, dirname) {
var tag = new Taglib.Tag(taglib);
invokeHandlers(tagObject, {
propertyHandlers(tagObject, {
name: function(value) {
tag.name = value;
},
renderer: function(value) {
var ext = nodePath.extname(value);
if (ext === '') {
value += '.js';
}
var path = nodePath.resolve(dirname, value);
var path = resolve(value, dirname);
if (!fs.existsSync(path)) {
throw new Error('Renderer at path "' + path + '" does not exist.');
}
@ -169,7 +99,7 @@ function buildTag(tagObject, path, taglib, dirname) {
handleAttributes(value, tag, path);
},
nodeClass: function(value) {
var path = nodePath.resolve(dirname, value);
var path = resolve(value, dirname);
if (!fs.existsSync(path)) {
throw new Error('Node module at path "' + path + '" does not exist.');
}
@ -185,14 +115,9 @@ function buildTag(tagObject, path, taglib, dirname) {
};
}
invokeHandlers(value, {
propertyHandlers(value, {
path: function(value) {
var ext = nodePath.extname(value);
if (ext === '') {
value += '.js';
}
var path = nodePath.resolve(dirname, value);
var path = resolve(value, dirname);
if (!fs.existsSync(path)) {
throw new Error('Transformer at path "' + path + '" does not exist.');
}
@ -200,16 +125,21 @@ function buildTag(tagObject, path, taglib, dirname) {
transformer.path = path;
},
before: function(value) {
transformer.before = value;
},
after: function(value) {
transformer.after = value;
priority: function(value) {
transformer.priority = value;
},
name: function(value) {
transformer.name = value;
},
properties: function(value) {
var properties = transformer.properties || (transformer.properties = {});
for (var k in value) {
if (value.hasOwnProperty(k)) {
properties[k] = value[k];
}
}
}
}, 'transformer in ' + path);
@ -218,6 +148,7 @@ function buildTag(tagObject, path, taglib, dirname) {
tag.addTransformer(transformer);
},
'var': function(value) {
tag.addNestedVariable({
name: value
@ -225,10 +156,34 @@ function buildTag(tagObject, path, taglib, dirname) {
},
vars: function(value) {
if (value) {
value.forEach(function(varName) {
tag.addNestedVariable({
name: varName
});
value.forEach(function(v, i) {
var nestedVariable;
if (typeof v === 'string') {
nestedVariable = {
name: v
};
} else {
nestedVariable = {};
propertyHandlers(v, {
name: function(value) {
nestedVariable.name = value;
},
nameFromAttribute: function(value) {
nestedVariable.nameFromAttribute = value;
}
}, 'var at index ' + i);
if (!nestedVariable.name && !nestedVariable.nameFromAttribute) {
throw new Error('The "name" or "name-from-attribute" attribute is required for a nested variable');
}
}
tag.addNestedVariable(nestedVariable);
});
}
},
@ -326,17 +281,6 @@ function load(path) {
taglib.addInputFile(path);
var dirname = nodePath.dirname(path);
function handleNS(ns) {
if (Array.isArray(ns)) {
ns.forEach(function(ns) {
taglib.addNamespace(ns);
});
}
else {
taglib.addNamespace(ns);
}
}
var taglibObject;
try {
@ -346,13 +290,11 @@ function load(path) {
throw new Error('Unable to parse taglib JSON at path "' + path + '". Exception: ' + e);
}
invokeHandlers(taglibObject, {
'namespace': handleNS,
'namespaces': handleNS,
'attributes': function(value) {
propertyHandlers(taglibObject, {
attributes: function(value) {
handleAttributes(value, taglib, path);
},
'tags': function(tags) {
tags: function(tags) {
forEachEntry(tags, function(tagName, path) {
ok(path, 'Invalid tag definition for "' + tagName + '"');
var tagObject;
@ -399,6 +341,31 @@ function load(path) {
} else {
scanTagsDir(path, dirname, dir, taglib);
}
},
textTransformer: function(value) {
var transformer = new Taglib.Transformer();
if (typeof value === 'string') {
value = {
path: value
};
}
propertyHandlers(value, {
path: function(value) {
var path = resolve(value, dirname);
if (!fs.existsSync(path)) {
throw new Error('Transformer at path "' + path + '" does not exist.');
}
transformer.path = path;
}
}, 'text-transformer in ' + path);
ok(transformer.path, '"path" is required for transformer');
taglib.addTextTransformer(transformer);
}
}, path);

View File

@ -92,10 +92,11 @@ function buildLookup(dirname) {
var lookup = lookupCache[lookupCacheKey];
if (lookup === undefined) {
lookup = new TaglibLookup();
for (var i=taglibs.length-1; i>=0; i--) {
for (var i=taglibs.length-1; i>=0; i--) {
lookup.addTaglib(taglibs[i]);
}
lookup.finish(); // Handle all of the imports
lookupCache[lookupCacheKey] = lookup;
}
@ -106,10 +107,10 @@ function addCoreTaglib(taglib) {
exports.coreTaglibs.push(taglib);
}
addCoreTaglib(taglibLoader.loadTaglibXmlFromFile(nodePath.join(__dirname, '../../taglibs/core/core.rtld')));
addCoreTaglib(taglibLoader.loadTaglibXmlFromFile(nodePath.join(__dirname, '../../taglibs/html/html.rtld')));
addCoreTaglib(taglibLoader.loadTaglibXmlFromFile(nodePath.join(__dirname, '../../taglibs/caching/caching.rtld')));
addCoreTaglib(taglibLoader.loadTaglibXmlFromFile(nodePath.join(__dirname, '../../taglibs/layout/layout.rtld')));
addCoreTaglib(taglibLoader.loadTaglibXmlFromFile(nodePath.join(__dirname, '../../taglibs/async/async.rtld')));
addCoreTaglib(taglibLoader.load(nodePath.join(__dirname, '../../taglibs/core/raptor-taglib.json')));
addCoreTaglib(taglibLoader.load(nodePath.join(__dirname, '../../taglibs/html/raptor-taglib.json')));
addCoreTaglib(taglibLoader.load(nodePath.join(__dirname, '../../taglibs/caching/raptor-taglib.json')));
addCoreTaglib(taglibLoader.load(nodePath.join(__dirname, '../../taglibs/layout/raptor-taglib.json')));
addCoreTaglib(taglibLoader.load(nodePath.join(__dirname, '../../taglibs/async/raptor-taglib.json')));
exports.buildLookup = buildLookup;

8
migration.md Normal file
View File

@ -0,0 +1,8 @@
Raptor Templates Migration Guide
================================
# Migration from XML-based Raptor Templates
# Migration from Dust

View File

@ -1,57 +1,58 @@
{
"name": "raptor-templates",
"description": "Raptor Templates",
"keywords": [
"templating",
"template",
"async",
"streaming"
],
"repository": {
"type": "git",
"url": "https://github.com/raptorjs3/raptor-templates.git"
},
"scripts": {
"test": "node_modules/.bin/mocha --ui bdd --reporter spec ./test"
},
"author": "Patrick Steele-Idem <pnidem@gmail.com>",
"maintainers": [
"Patrick Steele-Idem <pnidem@gmail.com>"
],
"dependencies": {
"raptor-detect": "^0.2.0-beta",
"raptor-logging": "^0.2.0-beta",
"raptor-strings": "^0.2.0-beta",
"raptor-regexp": "^0.2.0-beta",
"raptor-util": "^0.2.0-beta",
"raptor-arrays": "^0.2.0-beta",
"raptor-json": "^0.2.0-beta",
"raptor-modules": "^0.2.0-beta",
"raptor-render-context": "^0.2.0-beta",
"raptor-data-providers": "^0.2.0-beta",
"raptor-xml": "^0.2.0-beta",
"raptor-objects": "^0.2.0-beta",
"raptor-ecma": "^0.2.0-beta",
"raptor-files": "^0.2.0-beta",
"htmlparser2": "~3.5.1",
"char-props": "~0.1.5",
"raptor-promises": "^0.2.0-beta",
"raptor-args": "^0.1.9-beta",
"minimatch": "^0.2.14"
},
"devDependencies": {
"mocha": "~1.15.1",
"chai": "~1.8.1",
"raptor-cache": "^0.2.0-beta"
},
"license": "Apache License v2.0",
"bin": {
"rhtmlc": "bin/rhtmlc"
},
"main": "runtime/lib/raptor-templates.js",
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"ebay": {},
"version": "0.2.30-beta"
}
"name": "raptor-templates",
"description": "Raptor Templates",
"keywords": [
"templating",
"template",
"async",
"streaming"
],
"repository": {
"type": "git",
"url": "https://github.com/raptorjs3/raptor-templates.git"
},
"scripts": {
"test": "node_modules/.bin/mocha --ui bdd --reporter spec ./test"
},
"author": "Patrick Steele-Idem <pnidem@gmail.com>",
"maintainers": [
"Patrick Steele-Idem <pnidem@gmail.com>"
],
"dependencies": {
"raptor-detect": "^0.2.0-beta",
"raptor-logging": "^0.2.0-beta",
"raptor-strings": "^0.2.0-beta",
"raptor-regexp": "^0.2.0-beta",
"raptor-util": "^0.2.0-beta",
"raptor-arrays": "^0.2.0-beta",
"raptor-json": "^0.2.0-beta",
"raptor-modules": "^0.2.0-beta",
"raptor-render-context": "^0.2.0-beta",
"raptor-data-providers": "^0.2.0-beta",
"raptor-xml": "^0.2.0-beta",
"raptor-objects": "^0.2.0-beta",
"raptor-ecma": "^0.2.0-beta",
"raptor-files": "^0.2.0-beta",
"htmlparser2": "~3.5.1",
"char-props": "~0.1.5",
"raptor-promises": "^0.2.0-beta",
"raptor-args": "^0.1.9-beta",
"minimatch": "^0.2.14",
"property-handlers": "^0.2.1-beta"
},
"devDependencies": {
"mocha": "~1.15.1",
"chai": "~1.8.1",
"raptor-cache": "^0.2.0-beta"
},
"license": "Apache License v2.0",
"bin": {
"rhtmlc": "bin/rhtmlc"
},
"main": "runtime/lib/raptor-templates.js",
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"ebay": {},
"version": "0.2.30-beta"
}

106
raptor-templates-dust.md Normal file
View File

@ -0,0 +1,106 @@
Raptor Templates vs Dust
========================
The philosophy for Raptor Templates is the following:
* Syntax should not be cryptic
* Stay as close to JavaScript for better performance and easier learning
- JavaScript expressions throughout (no new expression language)
- Utilized closures for scoped variables and data
* Stay as close to HTML
* The templating language should not be restrictive
- Whether to go "less logic" or "more logic" is up to the developer
* High performance based on the following criteria:
- Fast and lightweight runtime
- Small compiled JavaScript output
* Must support asynchronous rendering
- Allow additional data to be asynchronously loaded after rendering begins
* Must support streaming
- Stream out bytes as they are generated
* Modular and extensible architecture
- Support custom tags
- Provide ability to generate custom JavaScript at compile-time
* Embrace Node.js and npm
# Data Passing
Dust allows data to be passed to template as a context object that supports lookup by simple names or complex paths.
# Syntax
## Dynamic Text
### Dust
* Simple key lookup:
```
{name}
```
Dust supports the following syntax for :
```html
Hello {name}!
```
In comparison, Raptor Templates uses the following syntax for expressions:
```html
Hello $name
```
Alternatively:
```html
Hello ${name.toUpperCase()}
```
Raptor Templates is an HTML-based templating language that understands the HTML structure of a template. This allows Raptor Templates to recognize templating directives applied as both HTML attributes and HTML tags. Dust, however, is a text-based templating language that does not understand the HTML structure of the document. In most cases, an HTML-based templating language allows for less code and less obtrusive code as shown in the following sample code:
## Looping and Conditionals
### Raptor Templates
```html
<ul c:if="notEmpty(data.colors)">
<li class="color" c:for="color in data.colors">
${color}
</li>
</ul>
<div c:else>
No colors!
</div>
```
### Dust
```html
{?colors}
<ul>
{#colors}
<li class="color">
{.}
</li>
{/colors}
</ul>
{/colors}
{^colors}
<div>
No colors!
</div>
{/colors}
```
## Expressions
Raptor Templates allows JavaScript expressions wherever expressions are allowed.
# Whitespace
# Custom Tags
Raptor Templates allows you to extend the
has a very different syntax from Dust

View File

@ -114,5 +114,6 @@ module.exports = {
}
runtime.render(path, data, context);
return this;
}
},
xt: extend
};

View File

@ -21,7 +21,6 @@
* in the {@link raptor/templating/compiler} module.
*/
var renderContext = require('raptor-render-context');
var createError = require('raptor-util/createError');
var Context = renderContext.Context;
var helpers = require('./helpers');
var loader = require('./loader');
@ -56,12 +55,7 @@ exports.render = function (templatePath, data, callback, context) {
}
try {
templateFunc(data || {}, context); //Invoke the template rendering function with the required arguments
} catch (e) {
// context.emit('error', e);
throw createError(new Error('Unable to render template with name "' + templatePath + '". Exception: ' + e), e);
}
templateFunc(data || {}, context); //Invoke the template rendering function with the required arguments
if (callback) {
context

View File

@ -12,10 +12,12 @@ module.exports = {
node.addError('Either "var" or "data-provider" is required');
return;
}
var argProps = [];
var propsToRemove = [];
node.forEachProperty(function (namespace, name, value) {
if (namespace === '' && name.startsWith('arg-')) {
node.forEachProperty(function (name, value) {
if (name.startsWith('arg-')) {
var argName = name.substring('arg-'.length);
argProps.push(JSON.stringify(argName) + ': ' + value);
propsToRemove.push(name);
@ -30,7 +32,8 @@ module.exports = {
}
var arg = node.getProperty('arg');
if (arg) {
argString = 'require("raptor").extend(' + arg + ', ' + argString + ')';
var extendFuncName = template.getStaticHelperFunction('extend', 'xt');
argString = extendFuncName + '(' + arg + ', ' + argString + ')';
}
if (argString) {
node.setProperty('arg', template.makeExpression(argString));

View File

@ -10,7 +10,7 @@ module.exports = {
var arg = input.arg || {};
arg.context = context;
var asyncContext =context.beginAsync(input.timeout);
var asyncContext = context.beginAsync(input.timeout);
function onError(e) {
asyncContext.error(e || 'Async fragment failed');
@ -26,6 +26,12 @@ module.exports = {
onError(e);
}
}
var method = input.method;
if (method) {
dataProvider = dataProvider[method].bind(dataProvider);
}
try {
dataProviders.requestData(dataProvider, arg, function(err, data) {
if (err) {

View File

@ -1,32 +0,0 @@
<raptor-taglib>
<tlib-version>1.0</tlib-version>
<namespace>raptor-templates/async</namespace>
<namespace>http://raptorjs.org/templates/async</namespace>
<namespace>async</namespace>
<tag>
<name>fragment</name>
<handler-class>./async-fragment-tag</handler-class>
<attribute name="dependency" type="string" target-property="dataProvider" deprecated="Use 'data-provider' instead"/>
<attribute name="data-provider" type="string" />
<attribute name="arg" type="expression" preserve-name="true"/>
<attribute pattern="arg-*" type="string" preserve-name="true"/>
<attribute name="var" type="identifier"/>
<attribute name="timeout" type="integer" />
<variable name="context"/>
<variable name-from-attribute="var OR dependency OR data-provider|keep"/>
<transformer>
<path>./AsyncFragmentTagTransformer</path>
<after>core/CoreTagTransformer</after>
</transformer>
</tag>
</raptor-taglib>

View File

@ -0,0 +1,34 @@
{
"tags": {
"async-fragment": {
"renderer": "./async-fragment-tag",
"attributes": {
"data-provider": {
"type": "string"
},
"arg": {
"type": "expression",
"preserve-name": true
},
"arg-*": {
"pattern": true,
"type": "string",
"preserve-name": true
},
"var": {
"type": "identifier"
},
"timeout": {
"type": "integer"
}
},
"vars": [
"context",
{
"name-from-attribute": "var OR dependency OR data-provider|keep"
}
],
"transformer": "./AsyncFragmentTagTransformer"
}
}
}

View File

@ -1,20 +0,0 @@
<raptor-taglib>
<tlib-version>1.0</tlib-version>
<namespace>http://raptorjs.org/templates/caching</namespace>
<namespace>caching</namespace>
<namespace>raptor-templates/caching</namespace>
<tag>
<name>cached-fragment</name>
<renderer>./CachedFragmentTag</renderer>
<attribute name="cache-key"/>
<attribute name="cache-name"/>
</tag>
</raptor-taglib>

View File

@ -0,0 +1,15 @@
{
"tags": {
"cached-fragment": {
"renderer": "./CachedFragmentTag",
"attributes": {
"cache-key": {
"type": "string"
},
"cache-name": {
"type": "string"
}
}
}
}
}

View File

@ -50,45 +50,24 @@ CoreTagTransformer.prototype = {
this.findNestedAttrs(node, compiler, template);
var inputAttr;
var forEachNode;
var namespace;
var tag;
var coreNS = compiler.taglibs.resolveNamespace('core');
function forEachProp(callback, thisObj) {
var foundProps = {};
node.forEachAttributeAnyNS(function (attr) {
if (attr.namespace === 'xml' || attr.namespace === 'http://www.w3.org/2000/xmlns/' || attr.namespace === 'http://www.w3.org/XML/1998/namespace' || attr.prefix == 'xmlns') {
return; //Skip xmlns attributes
}
var prefix = attr.prefix;
var attrUri = attr.namespace;
var resolvedAttrNamespace = attrUri ? compiler.taglibs.resolveNamespace(attrUri) : null;
attrUri = attr.prefix && resolvedAttrNamespace === tag.taglib.id ? null : attr.namespace;
var attrDef = compiler.taglibs.getAttribute(namespace, node.localName, attrUri, attr.localName);
var attrDef = compiler.taglibs.getAttribute(node, attr);
var type = attrDef ? attrDef.type || 'string' : 'string';
var taglibIdForTag = compiler.taglibs.resolveNamespaceForTag(tag);
var value;
// Check if the attribute and tag are part of the same taglib
if (attrUri && compiler.taglibs.resolveNamespace(attrUri) === taglibIdForTag) {
// If so, then don't repeat prefix
prefix = '';
}
if (!attrDef) {
// var isAttrForTaglib = compiler.taglibs.isTaglib(attrUri);
//Tag doesn't allow dynamic attributes
node.addError('The tag "' + tag.name + '" in taglib "' + taglibIdForTag + '" does not support attribute "' + attr + '"');
node.addError('The tag "' + tag.name + '" in taglib "' + tag.taglibId + '" does not support attribute "' + attr + '"');
return;
}
if (attr.value instanceof Expression) {
value = attr.value;
} else {
@ -102,7 +81,7 @@ CoreTagTransformer.prototype = {
}
var propName;
if (attrDef.dynamicAttribute) {
propName = attr.localName;
propName = attr.qName;
} else {
if (attrDef.targetProperty) {
propName = attrDef.targetProperty;
@ -114,38 +93,34 @@ CoreTagTransformer.prototype = {
}
foundProps[propName] = true;
callback.call(thisObj, attrUri, propName, value, prefix, attrDef);
callback.call(thisObj, propName, value, attrDef);
});
tag.forEachAttribute(function (attr) {
if (attr.hasOwnProperty('defaultValue') && !foundProps[attr.name]) {
callback.call(thisObj, '', attr.name, template.makeExpression(JSON.stringify(attr.defaultValue)), '', attr);
callback.call(thisObj, attr.name, template.makeExpression(JSON.stringify(attr.defaultValue)), '', attr);
}
});
}
namespace = node.namespace;
if (!namespace && node.isRoot() && node.localName === 'template') {
namespace = 'core';
}
tag = node.tag || compiler.taglibs.getTag(namespace, node.localName);
tag = node.tag || compiler.taglibs.getTag(node);
var coreAttrHandlers = {
'space': function(attr) {
this.whitespace(attr);
'c-space': function(attr) {
this['c-whitespace'](attr);
},
'whitespace': function(attr) {
'c-whitespace': function(attr) {
if (attr.value === 'preserve') {
node.setPreserveWhitespace(true);
}
},
'escape-xml': function(attr) {
'c-escape-xml': function(attr) {
node.setEscapeXmlBodyText(attr.value !== 'false');
},
'parse-body-text': function(attr) {
'c-parse-body-text': function(attr) {
node.parseBodyText = attr.value !== 'false';
},
'when': function(attr) {
'c-when': function(attr) {
var whenNode = new WhenNode({
test: new Expression(attr.value),
pos: node.getPosition()
@ -153,18 +128,18 @@ CoreTagTransformer.prototype = {
node.parentNode.replaceChild(whenNode, node);
whenNode.appendChild(node);
},
'otherwise': function(attr) {
'c-otherwise': function(attr) {
var otherwiseNode = new OtherwiseNode({ pos: node.getPosition() });
node.parentNode.replaceChild(otherwiseNode, node);
otherwiseNode.appendChild(node);
},
'attrs': function(attr) {
'c-attrs': function(attr) {
node.dynamicAttributesExpression = attr.value;
},
'for-each': function(attr) {
'c-for-each': function(attr) {
this['for'](attr);
},
'for': function(attr) {
'c-for': function(attr) {
var forEachProps = AttributeSplitter.parse(attr.value, {
each: { type: 'custom' },
separator: { type: 'expression' },
@ -189,7 +164,7 @@ CoreTagTransformer.prototype = {
node.parentNode.replaceChild(forEachNode, node);
forEachNode.appendChild(node);
},
'if': function(attr) {
'c-if': function(attr) {
var ifNode = new IfNode({
test: new Expression(attr.value),
pos: node.getPosition()
@ -199,7 +174,7 @@ CoreTagTransformer.prototype = {
node.parentNode.replaceChild(ifNode, node);
ifNode.appendChild(node);
},
'else-if': function(attr) {
'c-else-if': function(attr) {
var elseIfNode = new ElseIfNode({
test: new Expression(attr.value),
pos: node.getPosition()
@ -209,14 +184,14 @@ CoreTagTransformer.prototype = {
node.parentNode.replaceChild(elseIfNode, node);
elseIfNode.appendChild(node);
},
'else': function(attr) {
'c-else': function(attr) {
var elseNode = new ElseNode({ pos: node.getPosition() });
//Surround the existing node with an "if" node by replacing the current
//node with the new "if" node and then adding the current node as a child
node.parentNode.replaceChild(elseNode, node);
elseNode.appendChild(node);
},
'with': function(attr) {
'c-with': function(attr) {
var withNode = new WithNode({
vars: attr.value,
pos: node.getPosition()
@ -224,10 +199,10 @@ CoreTagTransformer.prototype = {
node.parentNode.replaceChild(withNode, node);
withNode.appendChild(node);
},
'body-content': function(attr) {
'c-body-content': function(attr) {
this.content(attr);
},
'content': function(attr) {
'c-content': function(attr) {
var newChild = new WriteNode({
expression: attr.value,
pos: node.getPosition()
@ -235,18 +210,18 @@ CoreTagTransformer.prototype = {
node.removeChildren();
node.appendChild(newChild);
},
'trim-body-indent': function(attr) {
'c-trim-body-indent': function(attr) {
if (attr.value === 'true') {
node.trimBodyIndent = true;
}
},
'strip': function(attr) {
'c-strip': function(attr) {
if (!node.setStripExpression) {
node.addError('The c:strip directive is not allowed for target node');
}
node.setStripExpression(attr.value);
},
'replace': function(attr) {
'c-replace': function(attr) {
var replaceWriteNode = new WriteNode({
expression: attr.value,
pos: node.getPosition()
@ -255,26 +230,17 @@ CoreTagTransformer.prototype = {
node.parentNode.replaceChild(replaceWriteNode, node);
node = replaceWriteNode;
},
'input': function(attr) {
'c-input': function(attr) {
inputAttr = attr.value;
}
};
node.forEachAttributeAnyNS(function(attr) {
var attrNS = attr.namespace;
if (!attrNS) {
return;
}
attrNS = compiler.taglibs.resolveNamespace(attrNS);
if (attrNS === coreNS) {
node.removeAttributeNS(attr.namespace, attr.localName);
var handler = coreAttrHandlers[attr.localName];
if (!handler) {
node.addError('Unsupported attribute: ' + attr.qName);
}
coreAttrHandlers[attr.localName](attr);
node.forEachAttributeNS('', function(attr) {
var handler = coreAttrHandlers[attr.qName];
if (handler) {
node.removeAttribute(attr.localName);
coreAttrHandlers[attr.localName](attr);
}
});
@ -299,35 +265,32 @@ CoreTagTransformer.prototype = {
IncludeNode.convertNode(node, templatePath);
}
forEachProp(function (namespace, name, value, prefix, attrDef) {
forEachProp(function (name, value, attrDef) {
if (attrDef.dynamicAttribute && attrDef.targetProperty) {
if (attrDef.removeDashes === true) {
name = removeDashes(name);
}
node.addDynamicAttribute(prefix ? prefix + ':' + name : name, value);
node.addDynamicAttribute(name, value);
node.setDynamicAttributesProperty(attrDef.targetProperty);
} else {
node.setPropertyNS(namespace, name, value);
node.setProperty(name, value);
}
});
} else if (tag.nodeClass) {
var NodeCompilerClass = require(tag.nodeClass);
extend(node, NodeCompilerClass.prototype);
NodeCompilerClass.call(node);
node.setNodeClass(NodeCompilerClass);
forEachProp(function (namespace, name, value) {
node.setPropertyNS(namespace, name, value);
forEachProp(function (name, value) {
node.setProperty(name, value);
});
}
} else if (namespace && compiler.isTaglib(namespace)) {
node.addError('Tag ' + node.toString() + ' is not allowed for taglib "' + namespace + '"');
}
},
findNestedAttrs: function (node, compiler, template) {
var coreNS = compiler.taglibs.resolveNamespace('core');
node.forEachChild(function (child) {
if (compiler.taglibs.resolveNamespace(child.namespace) === coreNS && child.localName === 'attr') {
if (child.qName === 'c-attr') {
this.handleAttr(child, compiler, template);
}
}, this);

View File

@ -15,7 +15,7 @@
*/
'use strict';
function ElseIfNode(props) {
ElseIfNode.$super.call(this, 'http://raptorjs.org/templates/core', 'else-if', 'c');
ElseIfNode.$super.call(this, 'c-else-if');
if (props) {
this.setProperties(props);
}

View File

@ -15,7 +15,7 @@
*/
'use strict';
function ElseNode(props) {
ElseNode.$super.call(this, 'else', 'http://raptorjs.org/templates/core', 'c');
ElseNode.$super.call(this, 'c-else');
if (props) {
this.setProperties(props);
}

View File

@ -21,8 +21,8 @@ ElseTagTransformer.prototype = {
process: function (node, compiler) {
var curNode = node.previousSibling;
var matchingNode;
var IfNode = compiler.getNodeClass('http://raptorjs.org/templates/core', 'if');
var ElseIfNode = compiler.getNodeClass('http://raptorjs.org/templates/core', 'else-if');
var IfNode = compiler.getNodeClass('c-if');
var ElseIfNode = compiler.getNodeClass('c-else-if');
var whitespaceNodes = [];
while (curNode) {
if (curNode.getNodeClass() === ElseIfNode || curNode.getNodeClass() === IfNode) {
@ -42,7 +42,7 @@ ElseTagTransformer.prototype = {
curNode = curNode.previousSibling;
}
if (!matchingNode) {
node.addError('<c:if> or <c:else-if> node not found immediately before ' + node.toString() + ' tag.');
node.addError('<c-if> or <c-else-if> node not found immediately before ' + node.toString() + ' tag.');
return;
}
whitespaceNodes.forEach(function (whitespaceNode) {

View File

@ -15,7 +15,7 @@
*/
'use strict';
function IfNode(props) {
IfNode.$super.call(this, 'if', 'http://raptorjs.org/templates/core', 'c');
IfNode.$super.call(this, 'c-if');
if (props) {
this.setProperties(props);
}

View File

@ -36,6 +36,7 @@ IncludeNode.prototype = {
var templateData = this.getProperty('templateData') || this.getProperty('template-data');
var resourcePath;
var _this = this;
if (templatePath) {
this.removeProperty('template');
@ -46,15 +47,18 @@ IncludeNode.prototype = {
dataExpression = {
toString: function () {
var propParts = [];
_this.forEachPropertyNS('', function (name, value) {
_this.forEachProperty(function (name, value) {
name = name.replace(/-([a-z])/g, function (match, lower) {
return lower.toUpperCase();
});
propParts.push(stringify(name) + ': ' + value);
}, _this);
if (_this.hasChildren()) {
propParts.push(stringify('invokeBody') + ': ' + _this.getBodyContentFunctionExpression(template, false));
}
return '{' + propParts.join(', ') + '}';
}
};

View File

@ -58,7 +58,7 @@ InvokeNode.prototype = {
* VALIDATION:
* Loop over all of the provided attributes and make sure they are allowed
*/
this.forEachPropertyNS('', function (name, value) {
this.forEachProperty(function (name, value) {
if (name === 'function') {
return;
}

View File

@ -100,15 +100,13 @@ TagHandlerNode.prototype = {
this.tag.forEachImportedVariable(function (importedVariable) {
this.setProperty(importedVariable.targetProperty, new Expression(importedVariable.expression));
}, this);
var namespacedProps = extend({}, this.getPropertiesByNS());
delete namespacedProps[''];
var hasNamespacedProps = !objects.isEmpty(namespacedProps);
var _this = this;
var variableNames = [];
_this.tag.forEachVariable(function (v) {
_this.tag.forEachVariable(function (nestedVar) {
var varName;
if (v.nameFromAttribute) {
var possibleNameAttributes = v.nameFromAttribute.split(/\s+or\s+|\s*,\s*/i);
if (nestedVar.nameFromAttribute) {
var possibleNameAttributes = nestedVar.nameFromAttribute.split(/\s+or\s+|\s*,\s*/i);
for (var i = 0, len = possibleNameAttributes.length; i < len; i++) {
var attrName = possibleNameAttributes[i];
var keep = false;
@ -130,7 +128,7 @@ TagHandlerNode.prototype = {
varName = '_var'; // Let it continue with errors
}
} else {
varName = v.name;
varName = nestedVar.name;
if (!varName) {
this.addError('Variable name is required');
varName = '_var'; // Let it continue with errors
@ -168,25 +166,10 @@ TagHandlerNode.prototype = {
template.code(',\n').line('function(' + bodyParams.join(',') + ') {').indent(function () {
_this.generateCodeForChildren(template);
}).indent().code('}');
} else {
if (hasNamespacedProps) {
template.code(',\n').indent().code('null');
}
}
if (hasNamespacedProps) {
template.code(',\n').line('{').indent(function () {
var first = true;
forEachEntry(namespacedProps, function (namespace, props) {
if (!first) {
template.code(',\n');
}
template.code(template.indentStr() + '"' + namespace + '": ' + getPropsStr(props, template));
first = false;
});
}).indent().code('}');
}
});
});
if (_this.postInvokeCode.length) {
_this.postInvokeCode.forEach(function (code) {
template.indent().code(code).code('\n');

View File

@ -1,503 +0,0 @@
<raptor-taglib>
<tlib-version>1.0</tlib-version>
<namespace>http://raptorjs.org/templates/core</namespace>
<namespace>core</namespace>
<namespace>c</namespace>
<tag id="template">
<name>template</name>
<attribute>
<name>name</name>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>params</name>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>imports</name>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<namespace>*</namespace>
<name>functions</name>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<namespace>*</namespace>
<name>import-functions</name>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<namespace>*</namespace>
<name>import-helper-object</name>
<allow-expressions>false</allow-expressions>
</attribute>
<node-class>./TemplateNode</node-class>
</tag>
<tag extends="template">
<namespace></namespace>
<name>template</name>
</tag>
<tag>
<name>*</name>
<namespace>*</namespace> <!-- Register attributes supported by all tags in all namespaces -->
<attribute>
<name>space</name>
<type>custom</type>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>whitespace</name>
<type>custom</type>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>for</name>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>for-each</name>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>if</name>
<type>expression</type>
</attribute>
<attribute>
<name>else</name>
<type>empty</type>
</attribute>
<attribute>
<name>else-if</name>
<type>expression</type>
</attribute>
<attribute>
<name>attrs</name>
<type>expression</type>
</attribute>
<attribute>
<name>when</name>
<type>expression</type>
</attribute>
<attribute>
<name>with</name>
<type>custom</type>
</attribute>
<attribute>
<name>otherwise</name>
<type>empty</type>
</attribute>
<attribute>
<name>parse-body-text</name>
<type>boolean</type>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>trim-body-indent</name>
<type>boolean</type>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>strip</name>
<type>boolean</type>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>bodyContent</name>
<type>expression</type>
<deprecated>Use "content" attribute instead. This will be removed in the future.</deprecated>
</attribute>
<attribute>
<name>content</name>
<type>expression</type>
</attribute>
<attribute>
<name>replace</name>
<type>expression</type>
</attribute>
<attribute>
<name>input</name>
<type>expression</type>
</attribute>
<!-- Compiler that applies to all tags as well -->
<transformer>
<path>./CoreTagTransformer</path>
<name>CoreTagTransformer</name>
</transformer>
</tag>
<tag>
<name>for</name>
<node-class>./ForNode</node-class>
<attribute>
<name>each</name>
<required>false</required>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>separator</name>
<type>string</type>
</attribute>
<attribute>
<name>status-var</name>
<type>identifier</type>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>varStatus</name>
<type>identifier</type>
<allow-expressions>false</allow-expressions>
<deprecated>Use status-var instead. This will be removed in the future.</deprecated>
</attribute>
<attribute>
<name>for-loop</name>
<type>boolean</type>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>iterator</name>
<type>expression</type>
</attribute>
</tag>
<tag>
<name>write</name>
<node-class>./WriteNode</node-class>
<attribute>
<name>value</name>
<required>true</required>
<type>expression</type>
</attribute>
<attribute>
<name>escapeXml</name>
<type>boolean</type>
<allow-expressions>false</allow-expressions>
<deprecated>Use escape-xml instead. This will be removed in the future.</deprecated>
</attribute>
<attribute>
<name>escape-xml</name>
<type>boolean</type>
<allow-expressions>false</allow-expressions>
</attribute>
</tag>
<tag>
<name>if</name>
<node-class>./IfNode</node-class>
<attribute>
<name>test</name>
<type>expression</type>
</attribute>
</tag>
<tag>
<name>else</name>
<node-class>./ElseNode</node-class>
<transformer>
<path>./ElseTagTransformer</path>
<name>ElseTagTransformer</name>
<after>core/CoreTagTransformer</after>
<properties>
<type>else</type>
</properties>
</transformer>
</tag>
<tag>
<name>else-if</name>
<attribute name="test" type="expression"/>
<node-class>./ElseIfNode</node-class>
<transformer>
<path>./ElseTagTransformer</path>
<name>ElseIfTagTransformer</name>
<after>core/CoreTagTransformer</after>
<properties>
<type>else-if</type>
</properties>
</transformer>
</tag>
<tag>
<name>invoke</name>
<node-class>./InvokeNode</node-class>
<attribute>
<name>function</name>
<type>custom</type>
<allow-expressions>false</allow-expressions>
<required>true</required>
</attribute>
<attribute>
<name>*</name>
<namespace></namespace>
<type>string</type>
<allow-expressions>true</allow-expressions>
</attribute>
</tag>
<tag>
<name>choose</name>
<node-class>./ChooseNode</node-class>
</tag>
<tag>
<name>when</name>
<node-class>./WhenNode</node-class>
<attribute>
<name>test</name>
<type>expression</type>
</attribute>
</tag>
<tag>
<name>otherwise</name>
<node-class>./OtherwiseNode</node-class>
</tag>
<tag>
<name>def</name>
<node-class>./DefNode</node-class>
<attribute>
<name>function</name>
<type>custom</type>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>body-param</name>
<type>custom</type>
<allow-expressions>false</allow-expressions>
</attribute>
</tag>
<tag>
<name>with</name>
<node-class>./WithNode</node-class>
<attribute>
<name>vars</name>
<type>custom</type>
<allow-expressions>false</allow-expressions>
</attribute>
</tag>
<tag>
<name>include</name>
<node-class>./IncludeNode</node-class>
<attribute>
<name>template</name>
<type>string</type>
</attribute>
<attribute>
<name>templateData</name>
<type>expression</type>
<deprecated>Use template-data instead. This will be removed in the future.</deprecated>
</attribute>
<attribute>
<name>template-data</name>
<type>expression</type>
</attribute>
<attribute>
<name>resource</name>
<type>string</type>
</attribute>
<attribute>
<name>static</name>
<type>boolean</type>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>*</name>
<type>string</type>
</attribute>
</tag>
<tag>
<name>attr</name>
<attribute>
<name>name</name>
<type>string</type>
</attribute>
<attribute>
<name>value</name>
<type>string</type>
</attribute>
<attribute>
<name>namespace</name>
<type>string</type>
</attribute>
<attribute>
<name>prefix</name>
<type>string</type>
</attribute>
</tag>
<tag>
<name>var</name>
<node-class>./VarNode</node-class>
<attribute>
<name>name</name>
<type>custom</type>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>value</name>
<type>expression</type>
</attribute>
<attribute>
<name>static</name>
<type>boolean</type>
</attribute>
<attribute>
<name>string-value</name>
<type>string</type>
</attribute>
<attribute>
<name>boolean-value</name>
<type>boolean</type>
</attribute>
<attribute>
<name>number-value</name>
<type>number</type>
</attribute>
</tag>
<tag>
<name>require</name>
<node-class>./RequireNode</node-class>
<attribute>
<name>module</name>
<type>string</type>
</attribute>
<attribute>
<name>var</name>
<type>custom</type>
<allow-expressions>false</allow-expressions>
</attribute>
</tag>
<tag>
<name>assign</name>
<node-class>./AssignNode</node-class>
<attribute>
<name>var</name>
<type>custom</type>
<allow-expressions>false</allow-expressions>
</attribute>
<attribute>
<name>value</name>
<type>expression</type>
</attribute>
</tag>
<text-transformer path="./CoreTextTransformer"/>
</raptor-taglib>

View File

@ -0,0 +1,291 @@
{
"tags": {
"c-template": {
"attributes": {
"name": {
"allow-expressions": false,
"type": "string"
},
"params": {
"allow-expressions": false,
"type": "string"
}
},
"node-class": "./TemplateNode"
},
"*": {
"attributes": {
"c-space": {
"type": "custom",
"allow-expressions": false
},
"c-whitespace": {
"type": "custom",
"allow-expressions": false
},
"c-for": {
"allow-expressions": false,
"type": "string"
},
"c-for-each": {
"allow-expressions": false,
"type": "string"
},
"c-if": {
"type": "expression"
},
"c-else": {
"type": "empty"
},
"c-else-if": {
"type": "expression"
},
"c-attrs": {
"type": "expression"
},
"c-when": {
"type": "expression"
},
"c-with": {
"type": "custom"
},
"c-otherwise": {
"type": "empty"
},
"c-parse-body-text": {
"type": "boolean",
"allow-expressions": false
},
"c-trim-body-indent": {
"type": "boolean",
"allow-expressions": false
},
"c-strip": {
"type": "boolean",
"allow-expressions": false
},
"c-content": {
"type": "expression"
},
"c-replace": {
"type": "expression"
},
"c-input": {
"type": "expression"
}
},
"transformer": {
"path": "./CoreTagTransformer",
"priority": 0
}
},
"c-for": {
"node-class": "./ForNode",
"attributes": {
"each": {
"required": false,
"allow-expressions": false,
"type": "string"
},
"separator": {
"type": "string"
},
"status-var": {
"type": "identifier",
"allow-expressions": false
},
"for-loop": {
"type": "boolean",
"allow-expressions": false
},
"iterator": {
"type": "expression"
}
}
},
"c-write": {
"node-class": "./WriteNode",
"attributes": {
"value": {
"required": true,
"type": "expression"
},
"escape-xml": {
"type": "boolean",
"allow-expressions": false
}
}
},
"c-if": {
"node-class": "./IfNode",
"attributes": {
"test": {
"type": "expression"
}
}
},
"c-else": {
"node-class": "./ElseNode",
"transformer": {
"path": "./ElseTagTransformer",
"name": "ElseTagTransformer",
"properties": {
"type": "else"
}
}
},
"c-else-if": {
"attributes": {
"test": {
"type": "expression"
}
},
"node-class": "./ElseIfNode",
"transformer": {
"path": "./ElseTagTransformer",
"name": "ElseIfTagTransformer",
"properties": {
"type": "else-if"
}
}
},
"c-invoke": {
"node-class": "./InvokeNode",
"attributes": {
"function": {
"type": "custom",
"allow-expressions": false,
"required": true
},
"*": {
"type": "string",
"allow-expressions": true
}
}
},
"c-choose": {
"node-class": "./ChooseNode"
},
"c-when": {
"node-class": "./WhenNode",
"attributes": {
"test": {
"type": "expression"
}
}
},
"c-otherwise": {
"node-class": "./OtherwiseNode"
},
"c-def": {
"node-class": "./DefNode",
"attributes": {
"function": {
"type": "custom",
"allow-expressions": false
},
"body-param": {
"type": "custom",
"allow-expressions": false
}
}
},
"c-with": {
"node-class": "./WithNode",
"attributes": {
"vars": {
"type": "custom",
"allow-expressions": false
}
}
},
"c-include": {
"node-class": "./IncludeNode",
"attributes": {
"template": {
"type": "string"
},
"template-data": {
"type": "expression"
},
"resource": {
"type": "string"
},
"static": {
"type": "boolean",
"allow-expressions": false
},
"*": {
"type": "string"
}
}
},
"c-attr": {
"attributes": {
"name": {
"type": "string"
},
"value": {
"type": "string"
},
"namespace": {
"type": "string"
},
"prefix": {
"type": "string"
}
}
},
"c-var": {
"node-class": "./VarNode",
"attributes": {
"name": {
"type": "custom",
"allow-expressions": false
},
"value": {
"type": "expression"
},
"static": {
"type": "boolean"
},
"string-value": {
"type": "string"
},
"boolean-value": {
"type": "boolean"
},
"number-value": {
"type": "number"
}
}
},
"c-require": {
"node-class": "./RequireNode",
"attributes": {
"module": {
"type": "string"
},
"var": {
"type": "custom",
"allow-expressions": false
}
}
},
"c-assign": {
"node-class": "./AssignNode",
"attributes": {
"var": {
"type": "custom",
"allow-expressions": false
},
"value": {
"type": "expression"
}
}
}
},
"text-transformer": {
"path": "./CoreTextTransformer"
}
}

View File

@ -25,6 +25,7 @@ HtmlTagTransformer.prototype = {
var allowSelfClosing = options.allowSelfClosing || {};
var startTagOnly = options.startTagOnly || {};
var lookupKey = node.namespace ? node.namespace + ':' + node.localName : node.localName;
if (node.isPreserveWhitespace() == null) {
if (preserveWhitespace[lookupKey] === true) {
node.setPreserveWhitespace(true);
@ -36,16 +37,15 @@ HtmlTagTransformer.prototype = {
if (compiler.options.xhtml !== true && startTagOnly[lookupKey] === true) {
node.setStartTagOnly(true);
}
if (node.getQName() === 'html' && node.hasAttributeNS('raptor-templates/html', 'doctype')) {
var doctype = node.getAttributeNS('raptor-templates/html', 'doctype');
if (node.getQName() === 'html' && node.hasAttribute('html-doctype')) {
var doctype = node.getAttribute('html-doctype');
var docTypeNode = new DocTypeNode({
value: doctype,
pos: node.getPosition()
});
node.parentNode.insertBefore(docTypeNode, node);
node.removeAttributeNS('raptor-templates/html', 'doctype');
node.removeAttribute('html-doctype');
}
}
}

View File

@ -1,57 +0,0 @@
<raptor-taglib>
<tlib-version>1.0</tlib-version>
<namespace>raptor-templates/html</namespace>
<namespace>http://raptorjs.org/templates/html</namespace>
<namespace>html</namespace>
<tag>
<name>pre</name>
<namespace></namespace> <!-- Register attributes supported by all tags in all namespaces -->
<!-- Compiler that applies to all tags as well -->
<transformer>
<path>./HtmlTagTransformer</path>
</transformer>
</tag>
<tag>
<name>html</name>
<namespace></namespace> <!-- Register attributes supported by all tags in all namespaces -->
<attribute name="doctype" type="string"/>
<!-- Compiler that applies to all tags as well -->
<transformer>
<path>./HtmlTagTransformer</path>
</transformer>
</tag>
<tag>
<name>doctype</name>
<attribute name="value" type="custom"/>
<node-class>./DocTypeNode</node-class>
</tag>
<tag>
<name>*</name>
<namespace>*</namespace> <!-- Register attributes supported by all tags in all namespaces -->
<!-- Compiler that applies to all tags as well -->
<transformer>
<path>./HtmlTagTransformer</path>
</transformer>
</tag>
<tag>
<name>comment</name>
<renderer>./CommentTag</renderer>
</tag>
</raptor-taglib>

View File

@ -0,0 +1,25 @@
{
"tags": {
"html-html": {
"attributes": {
"doctype": {
"type": "string"
}
}
},
"html-doctype": {
"attributes": {
"value": {
"type": "custom"
}
},
"node-class": "./DocTypeNode"
},
"*": {
"transformer": "./HtmlTagTransformer"
},
"html-comment": {
"renderer": "./CommentTag"
}
}
}

View File

@ -0,0 +1,42 @@
{
"tags": {
"layout-use": {
"renderer": "./UseTag",
"attributes": {
"template": {
"type": "path"
},
"*": {
"remove-dashes": true,
"type": "string"
}
},
"var": "_layout"
},
"layout-put": {
"renderer": "./PutTag",
"attributes": {
"into": {
"type": "string"
},
"value": {
"type": "string"
}
},
"import-var": {
"_layout": "_layout"
}
},
"layout-placeholder": {
"renderer": "./PlaceholderTag",
"attributes": {
"name": {
"type": "string"
}
},
"import-var": {
"content": "data.layoutContent"
}
}
}
}

View File

@ -40,7 +40,8 @@ function testRender(path, data, done, options) {
var Context = raptorTemplates.Context;
var context = options.context || new Context(new StringBuilder());
require('../compiler').defaultOptions.checkUpToDate = false;
if (options.dataProviders) {
var dataProviders = require('raptor-data-providers').forContext(context);
dataProviders.register(options.dataProviders);

View File

@ -27,6 +27,9 @@ function testRender(path, data, done, options) {
var raptorTemplates = require('../');
require('../compiler').defaultOptions.checkUpToDate = false;
var Context = raptorTemplates.Context;
var context = options.context || new Context(new StringBuilder());

View File

@ -24,7 +24,7 @@ function testRender(path, data, done, options) {
// console.log('\nCompiled (' + inputPath + '):\n---------\n' + compiledSrc);
require('../compiler').defaultOptions.checkUpToDate = false;
var raptorTemplates = require('../');
var Context = raptorTemplates.Context;

View File

@ -27,7 +27,7 @@ describe('raptor-templates/taglib-lookup' , function() {
var taglibLookup = require('../compiler/lib/taglib-lookup');
var lookup = taglibLookup.buildLookup(nodePath.join(__dirname, 'test-project'));
// console.log('LOOKUP: ', Object.keys(lookup.attributes));
var ifAttr = lookup.getAttribute('', 'div', 'c', 'if');
var ifAttr = lookup.getAttribute('div', 'c-if');
expect(ifAttr != null).to.equal(true);
expect(ifAttr.type).to.equal('expression');
});
@ -35,44 +35,42 @@ describe('raptor-templates/taglib-lookup' , function() {
it('should lookup core tag for top-level template', function() {
var taglibLookup = require('../compiler/lib/taglib-lookup');
var lookup = taglibLookup.buildLookup(nodePath.join(__dirname, 'test-project'));
var ifTag = lookup.getTag('c', 'if');
var ifTag = lookup.getTag('c-if');
expect(ifTag != null).to.equal(true);
expect(ifTag.name).to.equal('if');
expect(ifTag.namespace).to.equal(null);
expect(ifTag.name).to.equal('c-if');
});
it('should lookup core template for top-level template', function() {
var taglibLookup = require('../compiler/lib/taglib-lookup');
var lookup = taglibLookup.buildLookup(nodePath.join(__dirname, 'test-project'));
// console.log(Object.keys(lookup.tags));
var templateTag = lookup.getTag('c', 'template');
var templateTag = lookup.getTag('c-template');
expect(templateTag != null).to.equal(true);
expect(templateTag.name).to.equal('template');
expect(templateTag.namespace).to.equal(null);
expect(templateTag.name).to.equal('c-template');
});
it('should lookup custom tag for top-level template', function() {
var taglibLookup = require('../compiler/lib/taglib-lookup');
var lookup = taglibLookup.buildLookup(nodePath.join(__dirname, 'test-project'));
var tag = lookup.getTag('test', 'hello');
var tag = lookup.getTag('test-hello');
// console.log(Object.keys(lookup.tags));
expect(tag != null).to.equal(true);
expect(tag.name).to.equal('hello');
expect(tag.namespace).to.equal(undefined);
expect(tag.name).to.equal('test-hello');
});
it('should lookup custom attributes for top-level template', function() {
var taglibLookup = require('../compiler/lib/taglib-lookup');
var lookup = taglibLookup.buildLookup(nodePath.join(__dirname, 'test-project'));
// console.log(Object.keys(lookup.attributes));
var attr = lookup.getAttribute('test', 'hello', '', 'name');
var attr = lookup.getAttribute('test-hello', 'name');
expect(attr != null).to.equal(true);
expect(attr.type).to.equal('string');
var attr2 = lookup.getAttribute('test', 'hello', 'test', 'name');
expect(attr2).to.equal(attr);
var attr2 = lookup.getAttribute('test-hello', 'splat');
expect(attr2 != null).to.equal(true);
expect(attr2.type).to.equal('number');
attr = lookup.getAttribute('test', 'hello', '', 'expr');
attr = lookup.getAttribute('test-hello', 'expr');
expect(attr != null).to.equal(true);
expect(attr.type).to.equal('expression');
});
@ -81,7 +79,7 @@ describe('raptor-templates/taglib-lookup' , function() {
var taglibLookup = require('../compiler/lib/taglib-lookup');
var lookup = taglibLookup.buildLookup(nodePath.join(__dirname, 'test-project'));
// console.log(Object.keys(lookup.attributes));
var attr = lookup.getAttribute('test', 'hello', '', 'DYNAMIC');
var attr = lookup.getAttribute('test-hello', 'DYNAMIC');
expect(attr != null).to.equal(true);
expect(attr.type).to.equal('number');
});
@ -100,18 +98,17 @@ describe('raptor-templates/taglib-lookup' , function() {
it('should lookup nested tags', function() {
var taglibLookup = require('../compiler/lib/taglib-lookup');
var lookup = taglibLookup.buildLookup(nodePath.join(__dirname, 'test-project/nested'));
var tag = lookup.getTag('nested', 'foo');
var tag = lookup.getTag('nested-foo');
expect(tag != null).to.equal(true);
expect(tag.name).to.equal('foo');
expect(tag.namespace).to.equal(undefined);
expect(tag.name).to.equal('nested-foo');
});
it('should lookup attributes for nested tags', function() {
var taglibLookup = require('../compiler/lib/taglib-lookup');
var lookup = taglibLookup.buildLookup(nodePath.join(__dirname, 'test-project/nested'));
// console.log(Object.keys(lookup.attributes));
var attr = lookup.getAttribute('nested', 'foo', '', 'attr1');
var attr = lookup.getAttribute('nested-foo', 'attr1');
expect(attr != null).to.equal(true);
expect(attr.type).to.equal('string');
});
@ -122,7 +119,7 @@ describe('raptor-templates/taglib-lookup' , function() {
var taglibLookup = require('../compiler/lib/taglib-lookup');
var lookup = taglibLookup.buildLookup(nodePath.join(__dirname, 'test-project/nested'));
lookup.forEachTagTransformer('', 'div', function(transformer) {
lookup.forEachTagTransformer('div', function(transformer) {
transformers.push(transformer);
});
@ -133,36 +130,37 @@ describe('raptor-templates/taglib-lookup' , function() {
var transformers;
var taglibLookup = require('../compiler/lib/taglib-lookup');
var lookup = taglibLookup.buildLookup(nodePath.join(__dirname, 'test-project/nested'));
var lookup;
// lookup = taglibLookup.buildLookup(nodePath.join(__dirname, 'test-project/nested'));
transformers = [];
lookup.forEachTagTransformer('nested', 'foo', function(transformer) {
transformers.push(transformer);
});
// transformers = [];
// lookup.forEachTagTransformer('nested-foo', function(transformer) {
// transformers.push(transformer);
// });
expect(transformers.length).to.equal(2);
// expect(transformers.length).to.equal(2);
lookup = taglibLookup.buildLookup(nodePath.join(__dirname, 'test-project/transformers'));
transformers = [];
lookup.forEachTagTransformer('transform', 'foo', function(transformer) {
lookup.forEachTagTransformer('transform-foo', function(transformer) {
transformers.push(transformer);
});
expect(transformers.length).to.equal(3);
expect(transformers[0].path.indexOf('HtmlTagTransformer')).to.not.equal(-1);
expect(transformers[1].name).to.equal('foo');
expect(transformers[2].name).to.equal('CoreTagTransformer');
expect(transformers.length).to.equal(3);
expect(transformers[0].path.indexOf('foo')).to.not.equal(-1);
expect(transformers[1].path.indexOf('CoreTagTransformer')).to.not.equal(-1);
expect(transformers[2].path.indexOf('HtmlTagTransformer')).to.not.equal(-1);
transformers = [];
lookup.forEachTagTransformer('transform', 'bar', function(transformer) {
lookup.forEachTagTransformer('transform-bar', function(transformer) {
transformers.push(transformer);
});
expect(transformers.length).to.equal(3);
expect(transformers[0].path.indexOf('HtmlTagTransformer')).to.not.equal(-1);
expect(transformers[1].name).to.equal('CoreTagTransformer');
expect(transformers[2].name).to.equal('bar');
expect(transformers[0].path.indexOf('CoreTagTransformer')).to.not.equal(-1);
expect(transformers[1].path.indexOf('bar')).to.not.equal(-1);
expect(transformers[2].path.indexOf('HtmlTagTransformer')).to.not.equal(-1);
});
it('should lookup tag transformers core tag with custom node', function() {
@ -171,14 +169,14 @@ describe('raptor-templates/taglib-lookup' , function() {
var taglibLookup = require('../compiler/lib/taglib-lookup');
var lookup = taglibLookup.buildLookup(nodePath.join(__dirname, 'test-project/nested'));
lookup.forEachTagTransformer('c', 'else', function(transformer) {
lookup.forEachTagTransformer('c-else', function(transformer) {
transformers.push(transformer);
});
expect(transformers.length).to.equal(3);
// expect(transformers[0].name).to.equal('HTagTransformer');
expect(transformers[1].name).to.equal('CoreTagTransformer');
expect(transformers[2].name).to.equal('ElseTagTransformer');
expect(transformers[0].path.indexOf('CoreTagTransformer')).to.not.equal(-1);
expect(transformers[1].path.indexOf('ElseTagTransformer')).to.not.equal(-1);
expect(transformers[2].path.indexOf('HtmlTagTransformer')).to.not.equal(-1);
});
});

View File

@ -1 +1 @@
<test:hello name="World"/>
<test-hello name="World"/>

View File

@ -1,11 +1,10 @@
{
"namespace": "nested",
"tags": {
"foo": {
"renderer": "./foo-renderer.js",
"attributes": {
"attr1": "string"
}
}
"nested-foo": {
"renderer": "./foo-renderer.js",
"attributes": {
"attr1": "string"
}
}
}
}

View File

@ -1,7 +1,7 @@
<c:var name="tag" value="data.tag"/>
<c:var name="content" value="data.content"/>
<c:var name="title" value="data.title"/>
<c-var name="tag" value="data.tag"/>
<c-var name="content" value="data.content"/>
<c-var name="title" value="data.title"/>
<span title="$title" data-content="$content">
<c:invoke function="tag.invokeBody()" c:if="tag.invokeBody"/>
<c-invoke function="tag.invokeBody()" c-if="tag.invokeBody"/>
</span>

View File

@ -1,19 +1,18 @@
{
"namespace": ["test", "http://raptorjs.org/templates/test"],
"tags": {
"hello": "hello-tag.json",
"simpleHello": {
"test-hello": "hello-tag.json",
"test-simpleHello": {
"renderer": "./simple-hello-tag.js",
"attributes": {
"name": "string",
"adult": "boolean"
}
},
"tabs": {
"test-tabs": {
"renderer": "./tabs-tag.js",
"var": "tabs"
},
"tab": {
"test-tab": {
"renderer": "./tab-tag.js",
"import-var": {
"tabs": "tabs"
@ -22,7 +21,7 @@
"title": "string"
}
},
"dynamic-attributes": {
"test-dynamic-attributes": {
"renderer": "./dynamic-attributes-tag.js",
"attributes": {
"test": "string",
@ -32,14 +31,14 @@
}
}
},
"dynamic-attributes2": {
"test-dynamic-attributes2": {
"renderer": "./dynamic-attributes-tag2.js",
"attributes": {
"test": "string",
"*": "string"
}
},
"dynamic-attributes3": {
"test-dynamic-attributes3": {
"renderer": "./dynamic-attributes-tag3.js",
"attributes": {
"test": "string",
@ -49,21 +48,20 @@
}
}
},
"popover": {
"test-popover": {
"renderer": "./popover-tag.js",
"attributes": {
"title": "string",
"content": "string"
}
},
"page-title": {
"test-page-title": {
"template": "./page-title-tag.rhtml",
"attributes": {
"title": "string"
}
},
"default-attributes": {
"test-default-attributes": {
"renderer": "./default-attributes-tag.js",
"attributes": {
"prop1": "string",

View File

@ -1,6 +1,6 @@
<ul>
<li c:for="userId in [0, 1, 2, 3]">
<async:fragment dependency="userInfo" var="userInfo" arg-userId="$userId">
<li c-for="userId in [0, 1, 2, 3]">
<async-fragment data-provider="userInfo" var="userInfo" arg-userId="$userId">
<ul>
<li>
<b>Name:</b> $userInfo.name
@ -12,6 +12,6 @@
<b>Occupation:</b> $userInfo.occupation
</li>
</ul>
</async:fragment>
</async-fragment>
</li>
</ul>

View File

@ -1,7 +1,7 @@
<async:fragment dependency="contextData" var="d1">
<async-fragment data-provider="contextData" var="d1">
$d1.name
</async:fragment>
</async-fragment>
<async:fragment dependency="sharedData" var="d2">
<async-fragment data-provider="sharedData" var="d2">
$d2.name
</async:fragment>
</async-fragment>

View File

@ -1,3 +1,3 @@
<async:fragment data-provider="${data.userInfo}" var="userInfo">
<async-fragment data-provider="${data.userInfo}" var="userInfo">
Hello $userInfo.name
</async:fragment>
</async-fragment>

View File

@ -1,7 +1,7 @@
<c:def function="asyncMacro(num)">
<c-def function="asyncMacro(num)">
$num
</c:def>1
<async:fragment dependency="D1">
<c:invoke function="asyncMacro" num="2"/>
</async:fragment>
</c-def>1
<async-fragment data-provider="D1">
<c-invoke function="asyncMacro" num="2"/>
</async-fragment>
3

View File

@ -1,17 +1,17 @@
1
<async:fragment data-provider="D1" var="d1">
<async-fragment data-provider="D1" var="d1">
2
<async:fragment data-provider="D2" var="d2">
<async-fragment data-provider="D2" var="d2">
3
</async:fragment>
</async-fragment>
4
<async:fragment data-provider="D3" var="d3">
<async-fragment data-provider="D3" var="d3">
5
</async:fragment>
</async-fragment>
6
</async:fragment>
</async-fragment>
7
<async:fragment data-provider="D4" var="d4">
<async-fragment data-provider="D4" var="d4">
8
</async:fragment>
</async-fragment>
9

View File

@ -1,33 +1,33 @@
1
<async:fragment dependency="D1">
<async-fragment data-provider="D1">
2
<async:fragment dependency="D2" var="d2">
<async-fragment data-provider="D2" var="d2">
3
</async:fragment>
</async-fragment>
4
<async:fragment dependency="D3" var="d3">
<async-fragment data-provider="D3" var="d3">
5
<async:fragment dependency="D4" var="d4">
<async-fragment data-provider="D4" var="d4">
6
</async:fragment>
</async-fragment>
7
</async:fragment>
</async-fragment>
8
</async:fragment>
</async-fragment>
9
<async:fragment dependency="D5" var="d5">
<async-fragment data-provider="D5" var="d5">
10
<async:fragment dependency="D6" var="d6">
<async-fragment data-provider="D6" var="d6">
11
</async:fragment>
</async-fragment>
12
<async:fragment dependency="D7" var="d7">
<async-fragment data-provider="D7" var="d7">
13
</async:fragment>
</async-fragment>
14
<async:fragment dependency="D7" var="d7">
<async-fragment data-provider="D7" var="d7">
15
</async:fragment>
</async-fragment>
16
</async:fragment>
</async-fragment>
17

View File

@ -1,3 +1,3 @@
<async:fragment dependency="promiseData" var="promiseData">
<async-fragment data-provider="promiseData" var="promiseData">
$promiseData
</async:fragment>
</async-fragment>

View File

@ -1,21 +1,21 @@
A
<async:fragment dependency="user" var="user">
<async-fragment data-provider="user" var="user">
asyncB1
Hello $user.name!
<async:fragment dependency="messages" arg-user="${user}" var="messages">
<async-fragment data-provider="messages" arg-user="${user}" var="messages">
You have $messages.length new messages. Messages:
<ul>
<li c:for="message in messages">$message.text</li>
<li c-for="message in messages">$message.text</li>
</ul>
</async:fragment>
</async-fragment>
asyncB2
</async:fragment>
</async-fragment>
B
<async:fragment dependency="todos" var="todos">
<async-fragment data-provider="todos" var="todos">
asyncA1
<ul>
<li c:for="todo in todos">$todo.text (status: $todo.status)</li>
<li c-for="todo in todos">$todo.text (status: $todo.status)</li>
</ul>
asyncA2
</async:fragment>
</async-fragment>
C

View File

@ -1,5 +1,5 @@
<c:var name="myAttrs" value="data.myAttrs"/>
<c-var name="myAttrs" value="data.myAttrs"/>
<div c:attrs="myAttrs" data-encoding="&quot;hello&quot;">
<div c-attrs="myAttrs" data-encoding="&quot;hello&quot;">
Hello World!
</div>

View File

@ -1,3 +1,3 @@
<c:var name="helper" value="data.helper"/>
<c-var name="helper" value="data.helper"/>
A<c:invoke function="helper.beginAsync(context)"/>C
A<c-invoke function="helper.beginAsync(context)"/>C

View File

@ -1,15 +1,15 @@
<c:var name="count" value="0"/>
<c-var name="count" value="0"/>
<c:def function="foo(cacheName, cacheKey)">
<caching:cached-fragment cache-key="$cacheKey" cache-name="$cacheName">
<c-def function="foo(cacheName, cacheKey)">
<cached-fragment cache-key="$cacheKey" cache-name="$cacheName">
Count: ${count++}
</caching:cached-fragment>
</c:def>
</cached-fragment>
</c-def>
<c:invoke function="foo('cacheA', 'keyA')"/>
<c:invoke function="foo('cacheA', 'keyA')"/>
<c:invoke function="foo('cacheA', 'keyB')"/>
<c-invoke function="foo('cacheA', 'keyA')"/>
<c-invoke function="foo('cacheA', 'keyA')"/>
<c-invoke function="foo('cacheA', 'keyB')"/>
<c:invoke function="foo('cacheB', 'keyA')"/>
<c:invoke function="foo('cacheB', 'keyA')"/>
<c:invoke function="foo('cacheB', 'keyB')"/>
<c-invoke function="foo('cacheB', 'keyA')"/>
<c-invoke function="foo('cacheB', 'keyA')"/>
<c-invoke function="foo('cacheB', 'keyB')"/>

View File

@ -1,14 +1,14 @@
<div id="one">
<c:choose>
<div c:when="false">A</div>
<div c:when="false">B</div>
<div c:otherwise="">TRUE</div>
</c:choose>
<c-choose>
<div c-when="false">A</div>
<div c-when="false">B</div>
<div c-otherwise="">TRUE</div>
</c-choose>
</div>
<div id="two">
<c:choose>
<div c:when="false">A</div>
<div c:when="true">TRUE</div>
<div c:otherwise="">C</div>
</c:choose>
<c-choose>
<div c-when="false">A</div>
<div c-when="true">TRUE</div>
<div c-otherwise="">C</div>
</c-choose>
</div>

View File

@ -1,12 +1,12 @@
<c:choose>
<c:when test="0">A</c:when>
<c:when test="false">B</c:when>
<c:when test="true">TRUE</c:when>
<c:otherwise>C</c:otherwise>
</c:choose>,
<c-choose>
<c-when test="0">A</c-when>
<c-when test="false">B</c-when>
<c-when test="true">TRUE</c-when>
<c-otherwise>C</c-otherwise>
</c-choose>,
<c:choose>
<c:when test="false">A</c:when>
<c:when test="false">B</c:when>
<c:otherwise>TRUE</c:otherwise>
</c:choose>
<c-choose>
<c-when test="false">A</c-when>
<c-when test="false">B</c-when>
<c-otherwise>TRUE</c-otherwise>
</c-choose>

View File

@ -1 +1 @@
<div c:content="'Hello'"><b>This content will be replaced with the text "Hello"</b></div>
<div c-content="'Hello'"><b>This content will be replaced with the text "Hello"</b></div>

View File

@ -2,15 +2,15 @@
<div>${bind:data.person.name}</div>
<div>${bind:data.person.name; watch=data; event=update}</div>
<div><span bind:content="data.person.name"></span></div>
<div><span bind:content="data.person.name; watch=data; event=update"></span></div>
<div><span bind-content="data.person.name"></span></div>
<div><span bind-content="data.person.name; watch=data; event=update"></span></div>
<div bind:class="data.divClass">
<div bind-class="data.divClass">
</div>
<div bind:attrs="data.divAttrs">
<div bind-attrs="data.divAttrs">
</div>
<div bind:fragment="watch=data; event=update">
<div bind-fragment="watch=data; event=update">
</div>

View File

@ -1,12 +1,12 @@
<c:def function="greeting(name)">
<c-def function="greeting(name)">
<p class="greeting">Hello, ${name}!</p>
</c:def>
</c-def>
${greeting("World")},
${greeting("Frank")}
<c:def function="section(url, title, body)" body-param="body">
<c-def function="section(url, title, body)" body-param="body">
<div class="section">
<h1>
<a href="$url">
@ -17,10 +17,10 @@ ${greeting("Frank")}
${body}
</p>
</div>
</c:def>
</c-def>
<c:invoke function="section" url="http://www.ebay.com/" title="ebay">
<c-invoke function="section" url="http://www.ebay.com/" title="ebay">
<i>
Visit eBay
</i>
</c:invoke>
</c-invoke>

View File

@ -1,2 +1,2 @@
<test:default-attributes prop1="foo" prop2="50"/>
<test:default-attributes prop1="foo" prop2="50" default1="bar"/>
<test-default-attributes prop1="foo" prop2="50"/>
<test-default-attributes prop1="foo" prop2="50" default1="bar"/>

View File

@ -1,6 +1,6 @@
<html:doctype value="HTML PUBLIC &quot;-//W3C//DTD HTML 4.01 Transitional//EN&quot; &quot;http://www.w3.org/TR/html4/loose.dtd&quot;"/>
<html-doctype value="HTML PUBLIC &quot;-//W3C//DTD HTML 4.01 Transitional//EN&quot; &quot;http://www.w3.org/TR/html4/loose.dtd&quot;"/>
<html html:doctype="html">
<html html-doctype="html">
<head>
<title>DOCTYPE Test</title>
</head>

View File

@ -1 +1 @@
<test:dynamic-attributes test="Hello" class="my-class" id="myId"/>
<test-dynamic-attributes test="Hello" class="my-class" id="myId"/>

View File

@ -1 +1 @@
<test:dynamic-attributes2 test="World" class="my-class" id="myId"/>
<test-dynamic-attributes2 test="World" class="my-class" id="myId"/>

View File

@ -1 +1 @@
<test:dynamic-attributes3 test="World" class="my-class" id="myId"/>
<test-dynamic-attributes3 test="World" class="my-class" id="myId"/>

View File

@ -1,6 +1,6 @@
<div data-attr="Hello &lt;John&gt; &lt;hello&gt;">
<c:attr name="data-nested-attr">Hello &lt;John&gt; <![CDATA[<hello>]]></c:attr>
<c:attr name="data-nested-attr2" value="Hello &lt;John&gt; &lt;hello&gt;"/>
<c-attr name="data-nested-attr">Hello &lt;John&gt; <![CDATA[<hello>]]></c-attr>
<c-attr name="data-nested-attr2" value="Hello &lt;John&gt; &lt;hello&gt;"/>
Hello &lt;John&gt;© <![CDATA[<hello>]]>
${startTag:START}
</div>

View File

@ -8,78 +8,78 @@ ${greeting("World")
<c:for>
<c-for>
Missing each attribute
</c:for>
</c-for>
<c:for each="item">
</c:for>
<c-for each="item">
</c-for>
<c:for each="item in items" invalid="true">
<c-for each="item in items" invalid="true">
Invalid attribute
</c:for>
</c-for>
<c:for each="item in items" separator="${;">
<c-for each="item in items" separator="${;">
Invalid separator
</c:for>
</c-for>
<c:invalidTag>
</c:invalidTag>
<c-invalidTag>
</c-invalidTag>
<div c:for="item in items; invalid=true">
<div c-for="item in items; invalid=true">
</div>
<c:choose>
<c:when test="true">A</c:when>
<c:otherwise>INVALID</c:otherwise>
<c:when test="true">B</c:when>
</c:choose>
<c-choose>
<c-when test="true">A</c-when>
<c-otherwise>INVALID</c-otherwise>
<c-when test="true">B</c-when>
</c-choose>
<c:choose>
<c-choose>
<c:when test="false">A</c:when>
<c-when test="false">A</c-when>
INVALID TEXT
<c:when test="true">TRUE</c:when>
<c:otherwise>C</c:otherwise>
</c:choose>
<c-when test="true">TRUE</c-when>
<c-otherwise>C</c-otherwise>
</c-choose>
<c:def>
</c:def>
<c-def>
</c-def>
<c:def function="invalid-function-name()">
<c:invalidTag2></c:invalidTag2>
</c:def>
<c-def function="invalid-function-name()">
<c-invalidTag2></c-invalidTag2>
</c-def>
<c:include>Missing template attribute</c:include>
<c-include>Missing template attribute</c-include>
<c:with vars="x=1;b=2;1">
</c:with>
<c-with vars="x=1;b=2;1">
</c-with>
<c:if test="false">
<c-if test="false">
A
</c:if>
</c-if>
INVALID
<c:else>
<c-else>
C
</c:else>
</c-else>
<c:if test="false">
<c-if test="false">
A
</c:if>
</c-if>
INVALID
<c:else-if test="false">
<c-else-if test="false">
A
</c:else-if>
<c:else>
</c-else-if>
<c-else>
C
</c:else>
</c-else>
<div class="test">
<c:attr name="class" value="duplicate"/>
<c-attr name="class" value="duplicate"/>
</div>
<test:popover title="Popover Title" invalidAttr1="invalidValue1">
<c:attr name="invalidAttr2" value="invalidValue2"/>
<c:attr name="invalidAttr3">invalidValue3</c:attr>
<c:attr name="invalidAttr4" c:if="invalidIf">invalidValue4</c:attr>
</test:popover>
<test-popover title="Popover Title" invalidAttr1="invalidValue1">
<c-attr name="invalidAttr2" value="invalidValue2"/>
<c-attr name="invalidAttr3">invalidValue3</c-attr>
<c-attr name="invalidAttr4" c-if="invalidIf">invalidValue4</c-attr>
</test-popover>

View File

@ -1,4 +1,4 @@
<div c:escape-xml="false">
<div c-escape-xml="false">
&lt; &gt; &amp;
<div>&lt; &gt; &amp;</div>
</div>

View File

@ -1,4 +1,4 @@
<div c:escape-xml="true">
<div c-escape-xml="true">
&lt; &gt; &amp;
<div>&lt; &gt; &amp;</div>
</div>

View File

@ -1,33 +1,33 @@
<c:if test="true">
<c-if test="true">
A
</c:if>
<c:else>
</c-if>
<c-else>
B
</c:else>
</c-else>
,
<c:if test="false">
<c-if test="false">
A
</c:if>
<c:else>
</c-if>
<c-else>
B
</c:else>
</c-else>
,
<c:if test="false">
<c-if test="false">
A
</c:if>
<c:else-if test="false">
</c-if>
<c-else-if test="false">
B
</c:else-if>
<c:else>
</c-else-if>
<c-else>
C
</c:else>
</c-else>
,
<div c:if="false">
<div c-if="false">
A
</div>
<div c:else-if="false">
<div c-else-if="false">
B
</div>
<div c:else>
<div c-else>
C
</div>

View File

@ -1,6 +1,6 @@
<c:var name="name" value="data.name"/>
<c:var name="count" value="data.count"/>
<c:var name="invokeBody" value="data.invokeBody"/>
<c-var name="name" value="data.name"/>
<c-var name="count" value="data.count"/>
<c-var name="invokeBody" value="data.invokeBody"/>
<div class="nested">
<h1>

View File

@ -1,3 +1,3 @@
BEGIN
<c:include resource="include-resource-target.txt" static="true"/>
<c-include resource="include-resource-target.txt" static="true"/>
END

View File

@ -1,4 +1,4 @@
<c:var name="name" value="data.name"/>
<c:var name="count" value="data.count"/>
<c-var name="name" value="data.name"/>
<c-var name="count" value="data.count"/>
Hello $name! You have $count new messages.

View File

@ -1,10 +1,10 @@
<c:include template="./include-target.rhtml" name="${'Frank'}" count="20"/>
<c:include template="./include-target.rhtml" name="Frank" count="${20}"/>
<c:include template="./include-target.rhtml" template-data="{name: 'Frank', count: 20}"/>
<c:include template="./include-nested-content.rhtml" name="Frank" count="${20}">
<c-include template="./include-target.rhtml" name="${'Frank'}" count="20"/>
<c-include template="./include-target.rhtml" name="Frank" count="${20}"/>
<c-include template="./include-target.rhtml" template-data="{name: 'Frank', count: 20}"/>
<c-include template="./include-nested-content.rhtml" name="Frank" count="${20}">
Have a
<b>
wonderful
</b>
day!
</c:include>
</c-include>

View File

@ -1,4 +1,4 @@
<c:var name="name" value="data.name"/>
<c-var name="name" value="data.name"/>
<html>
<head>

View File

@ -12,16 +12,16 @@
A
<p>
<c:invoke function="test('World')"/>
<c:write value="test2('World')"/>
<c-invoke function="test('World')"/>
<c-write value="test2('World')"/>
</p>
B
<p>
<c:def function="greeting(name, count)">
<c-def function="greeting(name, count)">
Hello ${name}! You have ${count} new messages.
</c:def>
<c:invoke function="greeting" name="Frank" count="${10}"/>
<c:invoke function="greeting('John', 20)"/>
</c-def>
<c-invoke function="greeting" name="Frank" count="${10}"/>
<c-invoke function="greeting('John', 20)"/>
</p>

View File

@ -1,10 +1,10 @@
<h1 c:if="data.showHeader !== false">
<layout:placeholder name="header">
<h1 c-if="data.showHeader !== false">
<layout-placeholder name="header">
DEFAULT TITLE
</layout:placeholder>
</layout-placeholder>
</h1>
<div>
<layout:placeholder name="body"/>
<layout-placeholder name="body"/>
</div>
<layout:placeholder name="footer"/>
<layout:placeholder name="empty"/>
<layout-placeholder name="footer"/>
<layout-placeholder name="empty"/>

View File

@ -1,18 +1,18 @@
<layout:use template="./layout-default.rhtml" show-header="$false">
<layout:put into="body">BODY CONTENT</layout:put>
<layout:put into="footer">FOOTER CONTENT</layout:put>
</layout:use>
<layout:use template="./layout-default.rhtml" show-header="$true">
<layout:put into="header">HEADER CONTENT</layout:put>
<layout:put into="body">BODY CONTENT</layout:put>
<layout:put into="footer">FOOTER CONTENT</layout:put>
</layout:use>
<layout:use template="./layout-default.rhtml" show-header="$true">
<layout:put into="header" value="VALUE HEADER"/>
<layout:put into="body">BODY CONTENT</layout:put>
<layout:put into="footer">FOOTER CONTENT</layout:put>
</layout:use>
<layout:use template="${require.resolve('./layout-default.rhtml')}" show-header="$true">
<layout:put into="body">BODY CONTENT</layout:put>
<layout:put into="footer">FOOTER CONTENT</layout:put>
</layout:use>
<layout-use template="./layout-default.rhtml" show-header="$false">
<layout-put into="body">BODY CONTENT</layout-put>
<layout-put into="footer">FOOTER CONTENT</layout-put>
</layout-use>
<layout-use template="./layout-default.rhtml" show-header="$true">
<layout-put into="header">HEADER CONTENT</layout-put>
<layout-put into="body">BODY CONTENT</layout-put>
<layout-put into="footer">FOOTER CONTENT</layout-put>
</layout-use>
<layout-use template="./layout-default.rhtml" show-header="$true">
<layout-put into="header" value="VALUE HEADER"/>
<layout-put into="body">BODY CONTENT</layout-put>
<layout-put into="footer">FOOTER CONTENT</layout-put>
</layout-use>
<layout-use template="${require.resolve('./layout-default.rhtml')}" show-header="$true">
<layout-put into="body">BODY CONTENT</layout-put>
<layout-put into="footer">FOOTER CONTENT</layout-put>
</layout-use>

View File

@ -1,17 +1,17 @@
<c:for each="item in ['a', 'b', 'c']" iterator="data.reverseIterator">
<c-for each="item in ['a', 'b', 'c']" iterator="data.reverseIterator">
${item}
</c:for>
</c-for>
<c:for each="item in ['a', 'b', 'c']" iterator="data.reverseIterator" status-var="status">
<c-for each="item in ['a', 'b', 'c']" iterator="data.reverseIterator" status-var="status">
${status.index}-${item}
</c:for>
</c-for>
<div c:for="item in ['a', 'b', 'c']; iterator=data.reverseIterator">
<div c-for="item in ['a', 'b', 'c']; iterator=data.reverseIterator">
${item}
</div>
<div c:for="item in ['a', 'b', 'c']; iterator=data.reverseIterator; status-var=status">
<div c-for="item in ['a', 'b', 'c']; iterator=data.reverseIterator; status-var=status">
${status.index}-${item}
</div>

View File

@ -1,9 +1,9 @@
<c:for each="(name,value) in {'foo': 'low', 'bar': 'high'}">
<c-for each="(name,value) in {'foo': 'low', 'bar': 'high'}">
[$name=$value]
</c:for>
</c-for>
<ul>
<li c:for="(name, value) in {'foo': 'low', 'bar': 'high'}">
<li c-for="(name, value) in {'foo': 'low', 'bar': 'high'}">
[$name=$value]
</li>
</ul>

View File

@ -1,9 +1,9 @@
<c:for each="item in ['a', 'b', 'c']">
<c-for each="item in ['a', 'b', 'c']">
${item}
</c:for>
<c:for each="item in ['a', 'b', 'c']" separator=", " status-var="loop">
</c-for>
<c-for each="item in ['a', 'b', 'c']" separator=", " status-var="loop">
${item} - ${loop.isFirst()} - ${loop.isLast()} - ${loop.getIndex()} - ${loop.getLength()}
</c:for>
<div c:for="item in ['red', 'green', 'blue']; separator = ', '; status-var=loop" >
</c-for>
<div c-for="item in ['red', 'green', 'blue']; separator = ', '; status-var=loop" >
${item} - ${loop.isFirst()} - ${loop.isLast()} - ${loop.getIndex()} - ${loop.getLength()}
</div>

View File

@ -1,19 +1,19 @@
<c:var name="active" value="data.active"/>
<c-var name="active" value="data.active"/>
<test:popover>
<c:attr name="title">Popover Title</c:attr>
<c:attr name="content">Popover Content</c:attr>
<test-popover>
<c-attr name="title">Popover Title</c-attr>
<c-attr name="content">Popover Content</c-attr>
Link Text
</test:popover>
</test-popover>
<div>
<c:attr name="class" value="{?active;tab-active}"/>
<c:attr name="align">center</c:attr>
<c-attr name="class" value="{?active;tab-active}"/>
<c-attr name="align">center</c-attr>
</div>
<div>
<c:attr name="title">
<c:for each="color in ['red', 'green', 'blue']"> $color! </c:for>
</c:attr>
<c-attr name="title">
<c-for each="color in ['red', 'green', 'blue']"> $color! </c-for>
</c-attr>
</div>

View File

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

View File

@ -1,15 +1,15 @@
<optimizer:page name="optimizer-dynamic">
<optimizer-page name="optimizer-dynamic">
<dependencies>
<module name="${data.dependency}"/>
<module name="test/optimizer/mixedB" c:if="data.mixedBEnabled"/>
<module name="test/optimizer/mixedB" c-if="data.mixedBEnabled"/>
</dependencies>
</optimizer:page>
</optimizer-page>
<html>
<head>
<optimizer:slot name="head"/>
<optimizer-slot name="head"/>
</head>
<body>
<optimizer:slot name="body"/>
<optimizer-slot name="body"/>
</body>
</html>

View File

@ -1,15 +1,15 @@
<optimizer:page name="optimizer">
<optimizer-page name="optimizer">
<dependencies>
<module name="test/optimizer/mixedA"/>
<module name="test/optimizer/mixedB"/>
</dependencies>
</optimizer:page>
</optimizer-page>
<html>
<head>
<optimizer:slot name="head"/>
<optimizer-slot name="head"/>
</head>
<body>
<optimizer:slot name="body"/>
<optimizer-slot name="body"/>
</body>
</html>

View File

@ -1,4 +1,4 @@
<c:var name="message" value="data.message"/>
<c-var name="message" value="data.message"/>
<div c:replace="'Hello'"><b>This content and parent DIV will be replaced with the text "Hello"</b></div>,
<div c:replace="message"><b>This content and parent DIV will be replaced with the value of the "message" variable</b></div>
<div c-replace="'Hello'"><b>This content and parent DIV will be replaced with the text "Hello"</b></div>,
<div c-replace="message"><b>This content and parent DIV will be replaced with the value of the "message" variable</b></div>

View File

@ -1,3 +1,3 @@
<c:require module="./test-helpers" var="testHelpers" />
<c-require module="./test-helpers" var="testHelpers" />
Hello ${testHelpers.upperCase("world")}!
Hello ${testHelpers.trim(" World ")}!

View File

@ -1,5 +1,5 @@
<c:var name="name" value="data.name"/>
<c:var name="count" value="data.count"/>
<c-var name="name" value="data.name"/>
<c-var name="count" value="data.count"/>
<div class="{?count>50;over-50}"></div>
<div class="{?count lt 50;under-50}"></div>

View File

@ -1,13 +1,13 @@
<c:var name="dynamic" value="data.dynamic"/>
<c-var name="dynamic" value="data.dynamic"/>
<ul>
<li>
<test:simpleHello name="world"/>
<test-simpleHello name="world"/>
</li>
<li>
<test:simpleHello name="${dynamic}" adult="true"/>
<test-simpleHello name="${dynamic}" adult="true"/>
</li>
<li>
<test:simpleHello name="Dynamic: ${dynamic}" adult="false"/>
<test-simpleHello name="Dynamic: ${dynamic}" adult="false"/>
</li>
</ul>

View File

@ -1,13 +1,13 @@
<c:var name="rootClass" value="data.rootClass"/>
<c:var name="colors" value="data.colors"/>
<c:var name="message" value="data.message"/>
<c-var name="rootClass" value="data.rootClass"/>
<c-var name="colors" value="data.colors"/>
<c-var name="message" value="data.message"/>
<div class="hello-world ${rootClass}">${message}</div>
<ul c:if="notEmpty(colors)">
<li class="color" c:for="color in colors">${color}</li>
<ul c-if="notEmpty(colors)">
<li class="color" c-for="color in colors">${color}</li>
</ul>
<div c:if="empty(colors)">
<div c-if="empty(colors)">
No colors!
</div>

View File

@ -1,4 +1,4 @@
<c:var name="name" value="data.name"/>
<c:var name="count" value="data.count"/>
<c-var name="name" value="data.name"/>
<c-var name="count" value="data.count"/>
${name != null ? "Hello ${name.toUpperCase()}! You have $count new messages." : null}

View File

@ -1,13 +1,13 @@
<div>
<span c:strip="true"><b>A</b></span>
<span c-strip="true"><b>A</b></span>
</div>
<div>
<span c:strip="false"><b>B</b></span>
<span c-strip="false"><b>B</b></span>
</div>
<div>
<span c:strip="1 === 1"><b>c</b></span>
<span c-strip="1 === 1"><b>c</b></span>
</div>
<div>
<span c:strip="1 === 2"><b>d</b></span>
<span c-strip="1 === 2"><b>d</b></span>
</div>

View File

@ -15,7 +15,7 @@
</script>
</head>
<body id="body"
style="position:absolute; z-index:0; border:1px solid black; left:5%; top:5%; width:90%; height:90%;">
style="position:absolute; z-index-0; border-1px solid black; left-5%; top-5%; width-90%; height-90%;">
<form>
<fieldset>
<legend>HTML Form</legend>
@ -28,7 +28,7 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
viewBox="0 0 100 100" preserveAspectRatio="xMidYMid slice"
style="width:100%; height:100%; position:absolute; top:0; left:0; z-index:-1;">
style="width:100%; height-100%; position-absolute; top-0; left-0; z-index--1;">
<linearGradient id="gradient">
<stop class="begin" offset="0%"/>
<stop class="end" offset="100%"/>

View File

@ -1 +1 @@
<test:simpleHello c:input="data"/>
<test-simpleHello c-input="data"/>

View File

@ -1,3 +1,3 @@
<div>
<test:page-title title="My Page Title"/>
<test-page-title title="My Page Title"/>
</div>

View File

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

View File

@ -1,11 +1,11 @@
<c:var name="colors" value="['red', 'green', 'blue']"/>
<c-var name="colors" value="['red', 'green', 'blue']"/>
<div c:for="color in colors">
<div c-for="color in colors">
$color
</div>
<c:assign var="colors" value="['orange', 'purple', 'yellow']"/>
<c-assign var="colors" value="['orange', 'purple', 'yellow']"/>
<div c:for="color in colors">
<div c-for="color in colors">
$color
</div>

View File

@ -22,14 +22,14 @@ should <!-- This whitespace should be normalized --> retain spacing between l
begin <!-- this whitespace should not be normalized --> end
</pre>
<div c:space="preserve">
<div c-space="preserve">
begin <!-- this whitespace should not be normalized --> end
</div>
<div xml:space="preserve">
<div c-space="preserve">
begin <!-- this whitespace should not be normalized --> end
</div>
<c:if test="true">begin <!-- this whitespace should be preserved -->end</c:if>
<c-if test="true">begin <!-- this whitespace should be preserved -->end</c-if>
<!--
- In not "xml:space" === "preserve":
- newline followed by whitespace should be removed

Some files were not shown because too many files have changed in this diff Show More