marko/compiler/lib/Node.js

509 lines
18 KiB
JavaScript

/*
* 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 forEachEntry = require('raptor-util').forEachEntry;
var extend = require('raptor-util').extend;
var isArray = Array.isArray;
var EscapeXmlContext = require('./EscapeXmlContext');
function Node(nodeType) {
if (!this.nodeType) {
this._isRoot = false;
this.preserveWhitespace = null;
this.wordWrapEnabled = null;
this.escapeXmlBodyText = null;
this.escapeXmlContext = null;
this.nodeType = nodeType;
this.parentNode = null;
this.previousSibling = null;
this.nextSibling = null;
this.firstChild = null;
this.lastChild = null;
this.namespaceMappings = {};
this.prefixMappings = {};
this.transformersApplied = {};
this.properties = {};
}
}
Node.isNode = function(node) {
return node.__NODE === true;
};
Node.prototype = {
setRoot: function (isRoot) {
this._isRoot = isRoot;
},
getPosition: function () {
var pos = this.pos || this.getProperty('pos') || {
toString: function () {
return '(unknown position)';
}
};
return pos;
},
setPosition: function (pos) {
this.pos = pos;
},
addError: function (error) {
var compiler = this.compiler;
var curNode = this;
while (curNode != null && !compiler) {
compiler = curNode.compiler;
if (compiler) {
break;
}
curNode = curNode.parentNode;
}
if (!compiler) {
throw createError(new Error('Template compiler not set for node ' + this));
}
var pos = this.getPosition();
compiler.addError(error + ' (' + this.toString() + ')', pos);
},
resolveNamespace: function(namespace) {
return namespace || '';
},
setProperty: function (name, value) {
this.properties[name] = value;
},
setProperties: function (props) {
if (!props) {
return;
}
extend(this.properties, props);
},
getProperties: function () {
return this.properties;
},
hasProperty: function (name) {
return this.properties.hasOwnProperty(name);
},
forEachProperty: function (callback, thisObj) {
forEachEntry(this.properties, callback, this);
},
getProperty: function (name) {
return this.properties[name];
},
removeProperty: function (name) {
delete this.properties[name];
},
forEachChild: function (callback, thisObj) {
if (!this.firstChild) {
return;
}
var children = [];
var curChild = this.firstChild;
while (curChild) {
children.push(curChild);
curChild = curChild.nextSibling;
}
for (var i = 0, len = children.length; i < len; i++) {
curChild = children[i];
if (curChild.parentNode === this) {
//Make sure the node is still a child of this node
if (false === callback.call(thisObj, curChild)) {
return;
}
}
}
},
getExpression: function (template, childrenOnly, escapeXml, asFunction) {
if (!template) {
throw createError(new Error('template argument is required'));
}
var _this = this;
var methodCall;
if (escapeXml !== false) {
methodCall = 'context.captureString(';
} else {
methodCall = '__helpers.c(context, ';
}
return template.makeExpression({
toString: function () {
return template.captureCode(function () {
if (asFunction) {
template.code('function() {\n').code(template.indentStr(2) + 'return ' + methodCall + 'function() {\n').indent(3, function () {
if (childrenOnly === true) {
_this.generateCodeForChildren(template);
} else {
_this.generateCode(template);
}
}).code(template.indentStr(2) + '});\n').code(template.indentStr() + '}');
} else {
template.code(methodCall + 'function() {\n').indent(function () {
if (childrenOnly === true) {
_this.generateCodeForChildren(template);
} else {
_this.generateCode(template);
}
}).code(template.indentStr() + '})');
}
});
}
});
},
getBodyContentExpression: function (template, escapeXml) {
return this.getExpression(template, true, escapeXml, false);
},
getBodyContentFunctionExpression: function (template, escapeXml) {
return this.getExpression(template, true, escapeXml, true);
},
isTransformerApplied: function (transformer) {
return this.transformersApplied[transformer.id] === true;
},
setTransformerApplied: function (transformer) {
this.transformersApplied[transformer.id] = true;
},
hasChildren: function () {
return this.firstChild != null;
},
appendChild: function (childNode) {
if (childNode.parentNode) {
childNode.parentNode.removeChild(childNode);
}
if (!this.firstChild) {
this.firstChild = this.lastChild = childNode;
childNode.nextSibling = null;
childNode.previousSibling = null;
} else {
this.lastChild.nextSibling = childNode;
childNode.previousSibling = this.lastChild;
this.lastChild = childNode;
}
childNode.parentNode = this;
},
appendChildren: function (childNodes) {
if (!childNodes) {
return;
}
childNodes.forEach(function (childNode) {
this.appendChild(childNode);
}, this);
},
isRoot: function () {
return this._isRoot === true;
},
detach: function () {
if (this.parentNode) {
this.parentNode.removeChild(this);
}
},
removeChild: function (childNode) {
if (childNode.parentNode !== this) {
//Check if the child node is a child of the parent
return null;
}
var previousSibling = childNode.previousSibling;
var nextSibling = childNode.nextSibling;
if (this.firstChild === childNode && this.lastChild === childNode) {
//The child node is the one and only child node being removed
this.firstChild = this.lastChild = null;
} else if (this.firstChild === childNode) {
//The child node being removed is the first child and there is another child after it
this.firstChild = this.firstChild.nextSibling;
//Make the next child the first child
this.firstChild.previousSibling = null;
} else if (this.lastChild === childNode) {
//The child node being removed is the last child and there is another child before it
this.lastChild = this.lastChild.previousSibling;
//Make the previous child the last child
this.lastChild.nextSibling = null;
} else {
previousSibling.nextSibling = nextSibling;
nextSibling.previousSibling = previousSibling;
}
//Make sure the removed node is completely detached
childNode.parentNode = null;
childNode.previousSibling = null;
childNode.nextSibling = null;
return childNode;
},
removeChildren: function () {
while (this.firstChild) {
this.removeChild(this.firstChild);
}
},
replaceChild: function (newChild, replacedChild) {
if (newChild === replacedChild) {
return false;
}
if (!replacedChild) {
return false;
}
if (replacedChild.parentNode !== this) {
return false; //The parent does not have the replacedChild as a child... nothing to do
}
if (this.firstChild === replacedChild && this.lastChild === replacedChild) {
this.firstChild = newChild;
this.lastChild = newChild;
newChild.previousSibling = null;
newChild.nextSibling = null;
} else if (this.firstChild === replacedChild) {
newChild.nextSibling = replacedChild.nextSibling;
replacedChild.nextSibling.previousSibling = newChild;
this.firstChild = newChild;
} else if (this.lastChild === replacedChild) {
newChild.previousSibling = replacedChild.previousSibling;
newChild.nextSibling = null;
replacedChild.previousSibling.nextSibling = newChild;
this.lastChild = newChild;
} else {
replacedChild.nextSibling.previousSibling = newChild;
replacedChild.previousSibling.nextSibling = newChild;
newChild.nextSibling = replacedChild.nextSibling;
newChild.previousSibling = replacedChild.previousSibling;
}
newChild.parentNode = this;
replacedChild.parentNode = null;
replacedChild.previousSibling = null;
replacedChild.nextSibling = null;
return true;
},
insertAfter: function (node, referenceNode) {
if (!node) {
return false;
}
if (referenceNode && referenceNode.parentNode !== this) {
return false;
}
if (isArray(node)) {
node.forEach(function (node) {
this.insertAfter(node, referenceNode);
referenceNode = node;
}, this);
return true;
}
if (node === referenceNode) {
return false;
}
if (referenceNode === this.lastChild) {
this.appendChild(node);
return true;
}
if (node.parentNode) {
node.parentNode.removeChild(node);
}
if (!referenceNode || referenceNode === this.lastChild) {
this.appendChild(node);
return true;
} else {
referenceNode.nextSibling.previousSibling = node;
node.nextSibling = referenceNode.nextSibling;
node.previousSibling = referenceNode;
referenceNode.nextSibling = node;
}
node.parentNode = this;
return true;
},
insertBefore: function (node, referenceNode) {
if (!node) {
return false;
}
if (referenceNode && referenceNode.parentNode !== this) {
return false;
}
if (isArray(node)) {
var nodes = node;
var i;
for (i = nodes.length - 1; i >= 0; i--) {
this.insertBefore(nodes[i], referenceNode);
referenceNode = nodes[i];
}
return true;
}
if (node === referenceNode) {
return false;
}
if (node.parentNode) {
node.parentNode.removeChild(node);
}
if (!referenceNode) {
this.appendChild(node);
} else if (this.firstChild === referenceNode) {
this.firstChild = node;
this.firstChild.nextSibling = referenceNode;
this.firstChild.previousSibling = null;
referenceNode.previousSibling = this.firstChild;
node.parentNode = this;
} else {
this.insertAfter(node, referenceNode.previousSibling);
}
return true;
},
__NODE: true,
isTextNode: function () {
return false;
},
isElementNode: function () {
return false;
},
setStripExpression: function (stripExpression) {
this.stripExpression = stripExpression;
},
generateCode: function (template) {
this.compiler = template.compiler;
var preserveWhitespace = this.isPreserveWhitespace();
if (preserveWhitespace == null) {
preserveWhitespace = template.options.preserveWhitespace;
if (preserveWhitespace === true || preserveWhitespace && preserveWhitespace['*']) {
this.setPreserveWhitespace(true);
} else {
this.setPreserveWhitespace(false);
}
}
var wordWrapEnabled = this.isWordWrapEnabled();
if (wordWrapEnabled == null) {
wordWrapEnabled = template.options.wordWrapEnabled;
if (wordWrapEnabled !== false) {
this.setWordWrapEnabled(true);
}
}
if (this.isEscapeXmlBodyText() == null) {
this.setEscapeXmlBodyText(true);
}
try {
if (!this.stripExpression || this.stripExpression.toString() === 'false') {
this.doGenerateCode(template);
} else if (this.stripExpression.toString() === 'true') {
this.generateCodeForChildren(template);
} else {
//There is a strip expression
if (!this.generateBeforeCode || !this.generateAfterCode) {
this.addError('The c-strip directive is not supported for node ' + this);
this.generateCodeForChildren(template);
return;
}
var nextStripVarId = template.getAttribute('nextStripVarId');
if (nextStripVarId == null) {
nextStripVarId = template.setAttribute('nextStripVarId', 0);
}
var varName = '__strip' + nextStripVarId++;
template.statement('var ' + varName + ' = !(' + this.stripExpression + ');');
template.statement('if (' + varName + ') {').indent(function () {
this.generateBeforeCode(template);
}, this).line('}');
this.generateCodeForChildren(template);
template.statement('if (' + varName + ') {').indent(function () {
this.generateAfterCode(template);
}, this).line('}');
}
} catch (e) {
throw createError(new Error('Unable to generate code for node ' + this + ' at position [' + this.getPosition() + ']. Exception: ' + e), e);
}
},
isPreserveWhitespace: function () {
return this.preserveWhitespace;
},
setPreserveWhitespace: function (preserve) {
this.preserveWhitespace = preserve;
},
isWordWrapEnabled: function () {
return this.wordWrapEnabled;
},
setWordWrapEnabled: function (enabled) {
this.wordWrapEnabled = enabled;
},
doGenerateCode: function (template) {
this.generateCodeForChildren(template);
},
generateCodeForChildren: function (template, indent) {
if (!template) {
throw createError(new Error('The "template" argument is required'));
}
if (indent === true) {
template.incIndent();
}
this.forEachChild(function (childNode) {
if (childNode.isPreserveWhitespace() == null) {
childNode.setPreserveWhitespace(this.isPreserveWhitespace() === true);
}
if (childNode.isWordWrapEnabled() == null) {
childNode.setWordWrapEnabled(this.isWordWrapEnabled() === true);
}
if (childNode.isEscapeXmlBodyText() == null) {
childNode.setEscapeXmlBodyText(this.isEscapeXmlBodyText() !== false);
}
if (childNode.getEscapeXmlContext() == null) {
childNode.setEscapeXmlContext(this.getEscapeXmlContext() || require('./EscapeXmlContext').ELEMENT);
}
childNode.generateCode(template);
}, this);
if (indent === true) {
template.decIndent();
}
},
addNamespaceMappings: function (namespaceMappings) {
if (!namespaceMappings) {
return;
}
var existingNamespaceMappings = this.namespaceMappings;
var prefixMappings = this.prefixMappings;
forEachEntry(namespaceMappings, function (prefix, namespace) {
existingNamespaceMappings[prefix] = namespace;
prefixMappings[namespace] = prefix;
});
},
hasNamespacePrefix: function (namespace) {
return this.prefixMappings.hasOwnProperty(namespace);
},
resolveNamespacePrefix: function (namespace) {
var prefix = this.prefixMappings[namespace];
return !prefix && this.parentNode ? this.parentNode.resolveNamespacePrefix() : prefix;
},
forEachNamespace: function (callback, thisObj) {
forEachEntry(this.namespaceMappings, callback, thisObj);
},
getNodeClass: function () {
return this.nodeClass || this.constructor;
},
setNodeClass: function (nodeClass) {
this.nodeClass = nodeClass;
},
prettyPrintTree: function () {
var out = [];
function printNode(node, indent) {
out.push(indent + node.toString() + '\n');
node.forEachChild(function (child) {
printNode(child, indent + ' ');
});
}
printNode(this, '');
return out.join('');
},
setEscapeXmlBodyText: function (escapeXmlBodyText) {
this.escapeXmlBodyText = escapeXmlBodyText;
},
isEscapeXmlBodyText: function () {
return this.escapeXmlBodyText;
},
setEscapeXmlContext: function (escapeXmlContext) {
if (typeof escapeXmlContext === 'string') {
escapeXmlContext = EscapeXmlContext[escapeXmlContext.toUpperCase()];
}
this.escapeXmlContext = escapeXmlContext;
},
getEscapeXmlContext: function () {
return this.escapeXmlContext;
}
};
module.exports = Node;