mirror of
https://github.com/marko-js/marko.git
synced 2025-12-08 19:26:05 +00:00
280 lines
10 KiB
JavaScript
280 lines
10 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 objects = require('raptor-objects');
|
|
var escapeXmlAttr = require('raptor-xml/util').escapeXmlAttr;
|
|
var XML_URI = 'http://www.w3.org/XML/1998/namespace';
|
|
var XML_URI_ALT = 'http://www.w3.org/XML/1998/namespace';
|
|
var forEachEntry = require('raptor-util').forEachEntry;
|
|
|
|
function ElementNode(localName, namespace, prefix) {
|
|
ElementNode.$super.call(this, 'element');
|
|
if (!this._elementNode) {
|
|
this._elementNode = true;
|
|
this.dynamicAttributesExpression = null;
|
|
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;
|
|
}
|
|
}
|
|
ElementNode.prototype = {
|
|
getQName: function () {
|
|
return this.localName ? (this.prefix ? this.prefix + ':' : '') + this.localName : null;
|
|
},
|
|
setStartTagOnly: function (startTagOnly) {
|
|
this.startTagOnly = true;
|
|
},
|
|
setAllowSelfClosing: function (allowSelfClosing) {
|
|
this.allowSelfClosing = allowSelfClosing;
|
|
},
|
|
isElementNode: function () {
|
|
return true;
|
|
},
|
|
isTextNode: function () {
|
|
return false;
|
|
},
|
|
getAllAttributes: function () {
|
|
var allAttrs = [];
|
|
forEachEntry(this.attributesByNS, function (namespace, attrs) {
|
|
forEachEntry(attrs, function (name, attr) {
|
|
allAttrs.push(attr);
|
|
});
|
|
}, this);
|
|
return allAttrs;
|
|
},
|
|
forEachAttributeAnyNS: function (callback, thisObj) {
|
|
var attributes = [];
|
|
forEachEntry(this.attributesByNS, function (namespace, attrs) {
|
|
forEachEntry(attrs, function (name, attr) {
|
|
attributes.push(attr);
|
|
});
|
|
});
|
|
|
|
attributes.forEach(callback, thisObj);
|
|
},
|
|
forEachAttributeNS: function (namespace, callback, thisObj) {
|
|
namespace = this.resolveNamespace(namespace);
|
|
|
|
var attrs = this.attributesByNS[namespace];
|
|
if (attrs) {
|
|
forEachEntry(attrs, function (name, attr) {
|
|
callback.call(thisObj, attr);
|
|
});
|
|
}
|
|
},
|
|
getAttributes: function () {
|
|
var attributes = [];
|
|
forEachEntry(this.attributes, function (name, attr) {
|
|
attributes.push(attr);
|
|
}, this);
|
|
return attributes;
|
|
},
|
|
getAttribute: function (name) {
|
|
return this.getAttributeNS(null, name);
|
|
},
|
|
getAttributeNS: function (namespace, localName) {
|
|
namespace = this.resolveNamespace(namespace);
|
|
var attrNS = this.attributesByNS[namespace];
|
|
var attr = attrNS ? attrNS[localName] : undefined;
|
|
return attr ? attr.value : undefined;
|
|
},
|
|
setAttribute: function (localName, value, escapeXml) {
|
|
this.setAttributeNS(null, localName, value, null, escapeXml);
|
|
},
|
|
setAttributeNS: function (namespace, localName, value, prefix, escapeXml) {
|
|
namespace = this.resolveNamespace(namespace);
|
|
var attrNS = this.attributesByNS[namespace] || (this.attributesByNS[namespace] = {});
|
|
attrNS[localName] = {
|
|
localName: localName,
|
|
value: value,
|
|
prefix: prefix,
|
|
namespace: namespace,
|
|
escapeXml: escapeXml,
|
|
qName: prefix ? prefix + ':' + localName : localName,
|
|
name: namespace ? namespace + ':' + localName : localName,
|
|
toString: function () {
|
|
return this.name;
|
|
}
|
|
};
|
|
},
|
|
setEmptyAttribute: function (name) {
|
|
this.setAttribute(name, null);
|
|
},
|
|
removeAttribute: function (localName) {
|
|
this.removeAttributeNS(null, localName);
|
|
},
|
|
removeAttributeNS: function (namespace, localName) {
|
|
namespace = this.resolveNamespace(namespace);
|
|
var attrNS = this.attributesByNS[namespace] || (this.attributesByNS[namespace] = {});
|
|
if (attrNS) {
|
|
delete attrNS[localName];
|
|
if (objects.isEmpty(attrNS)) {
|
|
delete this.attributesByNS[namespace];
|
|
}
|
|
}
|
|
},
|
|
removeAttributesNS: function (namespace) {
|
|
namespace = this.resolveNamespace(namespace);
|
|
delete this.attributesByNS[namespace];
|
|
},
|
|
isPreserveWhitespace: function () {
|
|
var preserveSpace = ElementNode.$super.prototype.isPreserveWhitespace.call(this);
|
|
if (preserveSpace === true) {
|
|
return true;
|
|
}
|
|
var preserveAttr = this.getAttributeNS(XML_URI, 'space') || this.getAttributeNS(XML_URI_ALT, 'space') || this.getAttribute('xml:space') === 'preserve';
|
|
if (preserveAttr === 'preserve') {
|
|
return true;
|
|
}
|
|
return preserveSpace;
|
|
},
|
|
hasAttributesAnyNS: function () {
|
|
return !objects.isEmpty(this.attributesByNS);
|
|
},
|
|
hasAttributes: function () {
|
|
return this.hasAttributesNS('');
|
|
},
|
|
hasAttributesNS: function (namespace) {
|
|
namespace = this.resolveNamespace(namespace);
|
|
return this.attributesByNS[namespace] !== undefined;
|
|
},
|
|
hasAttribute: function (localName) {
|
|
return this.hasAttributeNS('', localName);
|
|
},
|
|
hasAttributeNS: function (namespace, localName) {
|
|
namespace = this.resolveNamespace(namespace);
|
|
var attrsNS = this.attributesByNS[namespace];
|
|
return attrsNS ? attrsNS.hasOwnProperty(localName) : false;
|
|
},
|
|
removePreserveSpaceAttr: function () {
|
|
this.removeAttributeNS(XML_URI, 'space');
|
|
this.removeAttributeNS(XML_URI_ALT, 'space');
|
|
this.removeAttribute('space');
|
|
},
|
|
setStripExpression: function (stripExpression) {
|
|
this.stripExpression = stripExpression;
|
|
},
|
|
doGenerateCode: function (template) {
|
|
this.generateBeforeCode(template);
|
|
this.generateCodeForChildren(template);
|
|
this.generateAfterCode(template);
|
|
},
|
|
generateBeforeCode: function (template) {
|
|
var preserveWhitespace = this.preserveWhitespace = this.isPreserveWhitespace();
|
|
var name = this.prefix ? this.prefix + ':' + this.localName : this.localName;
|
|
if (preserveWhitespace) {
|
|
this.removePreserveSpaceAttr();
|
|
}
|
|
template.text('<' + name);
|
|
this.forEachAttributeAnyNS(function (attr) {
|
|
var prefix = attr.prefix;
|
|
if (!prefix && attr.namespace) {
|
|
prefix = this.resolveNamespacePrefix(attr.namespace);
|
|
}
|
|
if (prefix) {
|
|
name = prefix + (attr.localName ? ':' + attr.localName : '');
|
|
} else {
|
|
name = attr.localName;
|
|
}
|
|
if (attr.value === null || attr.value === undefined) {
|
|
template.text(' ' + name);
|
|
} else if (template.isExpression(attr.value)) {
|
|
template.attr(name, attr.value, attr.escapeXml !== false);
|
|
} else {
|
|
var attrParts = [];
|
|
var hasExpression = false;
|
|
var invalidAttr = false;
|
|
template.parseExpression(attr.value, {
|
|
text: function (text, escapeXml) {
|
|
attrParts.push({
|
|
text: text,
|
|
escapeXml: escapeXml !== false
|
|
});
|
|
},
|
|
expression: function (expression, escapeXml) {
|
|
hasExpression = true;
|
|
attrParts.push({
|
|
expression: expression,
|
|
escapeXml: escapeXml !== false
|
|
});
|
|
},
|
|
error: function (message) {
|
|
invalidAttr = true;
|
|
this.addError('Invalid expression found in attribute "' + name + '". ' + message);
|
|
}
|
|
}, this);
|
|
|
|
if (invalidAttr) {
|
|
template.text(name + '="' + escapeXmlAttr(attr.value) + '"');
|
|
} else {
|
|
if (hasExpression && attrParts.length === 1) {
|
|
template.attr(name, attrParts[0].expression, attrParts[0].escapeXml !== false);
|
|
} else {
|
|
template.text(' ' + name + '="');
|
|
attrParts.forEach(function (part) {
|
|
if (part.text) {
|
|
template.text(part.escapeXml !== false ? escapeXmlAttr(part.text) : part.text);
|
|
} else if (part.expression) {
|
|
template.write(part.expression, { escapeXmlAttr: part.escapeXml !== false });
|
|
} else {
|
|
throw createError(new Error('Illegal state'));
|
|
}
|
|
});
|
|
template.text('"');
|
|
}
|
|
}
|
|
}
|
|
}, this);
|
|
if (this.dynamicAttributesExpression) {
|
|
template.attrs(this.dynamicAttributesExpression);
|
|
}
|
|
if (this.hasChildren()) {
|
|
template.text('>');
|
|
} else {
|
|
if (this.startTagOnly) {
|
|
template.text('>');
|
|
} else if (this.allowSelfClosing) {
|
|
template.text('/>');
|
|
}
|
|
}
|
|
},
|
|
generateAfterCode: function (template) {
|
|
var name = this.prefix ? this.prefix + ':' + this.localName : this.localName;
|
|
if (this.hasChildren()) {
|
|
template.text('</' + name + '>');
|
|
} else {
|
|
if (!this.startTagOnly && !this.allowSelfClosing) {
|
|
template.text('></' + name + '>');
|
|
}
|
|
}
|
|
},
|
|
toString: function () {
|
|
return '<' + (this.prefix ? this.prefix + ':' + this.localName : this.localName) + '>';
|
|
}
|
|
};
|
|
require('raptor-util').inherit(ElementNode, require('./Node'));
|
|
module.exports = ElementNode; |