123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611 |
- /**
- * Copyright 2011 Rackspace
- *
- * 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.
- *
- */
-
- var sprintf = require('./sprintf').sprintf;
-
- var utils = require('./utils');
- var ElementPath = require('./elementpath');
- var TreeBuilder = require('./treebuilder').TreeBuilder;
- var get_parser = require('./parser').get_parser;
- var constants = require('./constants');
-
- var element_ids = 0;
-
- function Element(tag, attrib)
- {
- this._id = element_ids++;
- this.tag = tag;
- this.attrib = {};
- this.text = null;
- this.tail = null;
- this._children = [];
-
- if (attrib) {
- this.attrib = utils.merge(this.attrib, attrib);
- }
- }
-
- Element.prototype.toString = function()
- {
- return sprintf("<Element %s at %s>", this.tag, this._id);
- };
-
- Element.prototype.makeelement = function(tag, attrib)
- {
- return new Element(tag, attrib);
- };
-
- Element.prototype.len = function()
- {
- return this._children.length;
- };
-
- Element.prototype.getItem = function(index)
- {
- return this._children[index];
- };
-
- Element.prototype.setItem = function(index, element)
- {
- this._children[index] = element;
- };
-
- Element.prototype.delItem = function(index)
- {
- this._children.splice(index, 1);
- };
-
- Element.prototype.getSlice = function(start, stop)
- {
- return this._children.slice(start, stop);
- };
-
- Element.prototype.setSlice = function(start, stop, elements)
- {
- var i;
- var k = 0;
- for (i = start; i < stop; i++, k++) {
- this._children[i] = elements[k];
- }
- };
-
- Element.prototype.delSlice = function(start, stop)
- {
- this._children.splice(start, stop - start);
- };
-
- Element.prototype.append = function(element)
- {
- this._children.push(element);
- };
-
- Element.prototype.extend = function(elements)
- {
- this._children.concat(elements);
- };
-
- Element.prototype.insert = function(index, element)
- {
- this._children[index] = element;
- };
-
- Element.prototype.remove = function(element)
- {
- this._children = this._children.filter(function(e) {
- /* TODO: is this the right way to do this? */
- if (e._id === element._id) {
- return false;
- }
- return true;
- });
- };
-
- Element.prototype.getchildren = function() {
- return this._children;
- };
-
- Element.prototype.find = function(path)
- {
- return ElementPath.find(this, path);
- };
-
- Element.prototype.findtext = function(path, defvalue)
- {
- return ElementPath.findtext(this, path, defvalue);
- };
-
- Element.prototype.findall = function(path, defvalue)
- {
- return ElementPath.findall(this, path, defvalue);
- };
-
- Element.prototype.clear = function()
- {
- this.attrib = {};
- this._children = [];
- this.text = null;
- this.tail = null;
- };
-
- Element.prototype.get = function(key, defvalue)
- {
- if (this.attrib[key] !== undefined) {
- return this.attrib[key];
- }
- else {
- return defvalue;
- }
- };
-
- Element.prototype.set = function(key, value)
- {
- this.attrib[key] = value;
- };
-
- Element.prototype.keys = function()
- {
- return Object.keys(this.attrib);
- };
-
- Element.prototype.items = function()
- {
- return utils.items(this.attrib);
- };
-
- /*
- * In python this uses a generator, but in v8 we don't have em,
- * so we use a callback instead.
- **/
- Element.prototype.iter = function(tag, callback)
- {
- var self = this;
- var i, child;
-
- if (tag === "*") {
- tag = null;
- }
-
- if (tag === null || this.tag === tag) {
- callback(self);
- }
-
- for (i = 0; i < this._children.length; i++) {
- child = this._children[i];
- child.iter(tag, function(e) {
- callback(e);
- });
- }
- };
-
- Element.prototype.itertext = function(callback)
- {
- this.iter(null, function(e) {
- if (e.text) {
- callback(e.text);
- }
-
- if (e.tail) {
- callback(e.tail);
- }
- });
- };
-
-
- function SubElement(parent, tag, attrib) {
- var element = parent.makeelement(tag, attrib);
- parent.append(element);
- return element;
- }
-
- function Comment(text) {
- var element = new Element(Comment);
- if (text) {
- element.text = text;
- }
- return element;
- }
-
- function CData(text) {
- var element = new Element(CData);
- if (text) {
- element.text = text;
- }
- return element;
- }
-
- function ProcessingInstruction(target, text)
- {
- var element = new Element(ProcessingInstruction);
- element.text = target;
- if (text) {
- element.text = element.text + " " + text;
- }
- return element;
- }
-
- function QName(text_or_uri, tag)
- {
- if (tag) {
- text_or_uri = sprintf("{%s}%s", text_or_uri, tag);
- }
- this.text = text_or_uri;
- }
-
- QName.prototype.toString = function() {
- return this.text;
- };
-
- function ElementTree(element)
- {
- this._root = element;
- }
-
- ElementTree.prototype.getroot = function() {
- return this._root;
- };
-
- ElementTree.prototype._setroot = function(element) {
- this._root = element;
- };
-
- ElementTree.prototype.parse = function(source, parser) {
- if (!parser) {
- parser = get_parser(constants.DEFAULT_PARSER);
- parser = new parser.XMLParser(new TreeBuilder());
- }
-
- parser.feed(source);
- this._root = parser.close();
- return this._root;
- };
-
- ElementTree.prototype.iter = function(tag, callback) {
- this._root.iter(tag, callback);
- };
-
- ElementTree.prototype.find = function(path) {
- return this._root.find(path);
- };
-
- ElementTree.prototype.findtext = function(path, defvalue) {
- return this._root.findtext(path, defvalue);
- };
-
- ElementTree.prototype.findall = function(path) {
- return this._root.findall(path);
- };
-
- /**
- * Unlike ElementTree, we don't write to a file, we return you a string.
- */
- ElementTree.prototype.write = function(options) {
- var sb = [];
- options = utils.merge({
- encoding: 'utf-8',
- xml_declaration: null,
- default_namespace: null,
- method: 'xml'}, options);
-
- if (options.xml_declaration !== false) {
- sb.push("<?xml version='1.0' encoding='"+options.encoding +"'?>\n");
- }
-
- if (options.method === "text") {
- _serialize_text(sb, self._root, encoding);
- }
- else {
- var qnames, namespaces, indent, indent_string;
- var x = _namespaces(this._root, options.encoding, options.default_namespace);
- qnames = x[0];
- namespaces = x[1];
-
- if (options.hasOwnProperty('indent')) {
- indent = 0;
- indent_string = new Array(options.indent + 1).join(' ');
- }
- else {
- indent = false;
- }
-
- if (options.method === "xml") {
- _serialize_xml(function(data) {
- sb.push(data);
- }, this._root, options.encoding, qnames, namespaces, indent, indent_string);
- }
- else {
- /* TODO: html */
- throw new Error("unknown serialization method "+ options.method);
- }
- }
-
- return sb.join("");
- };
-
- var _namespace_map = {
- /* "well-known" namespace prefixes */
- "http://www.w3.org/XML/1998/namespace": "xml",
- "http://www.w3.org/1999/xhtml": "html",
- "http://www.w3.org/1999/02/22-rdf-syntax-ns#": "rdf",
- "http://schemas.xmlsoap.org/wsdl/": "wsdl",
- /* xml schema */
- "http://www.w3.org/2001/XMLSchema": "xs",
- "http://www.w3.org/2001/XMLSchema-instance": "xsi",
- /* dublic core */
- "http://purl.org/dc/elements/1.1/": "dc",
- };
-
- function register_namespace(prefix, uri) {
- if (/ns\d+$/.test(prefix)) {
- throw new Error('Prefix format reserved for internal use');
- }
-
- if (_namespace_map.hasOwnProperty(uri) && _namespace_map[uri] === prefix) {
- delete _namespace_map[uri];
- }
-
- _namespace_map[uri] = prefix;
- }
-
-
- function _escape(text, encoding, isAttribute, isText) {
- if (text) {
- text = text.toString();
- text = text.replace(/&/g, '&');
- text = text.replace(/</g, '<');
- text = text.replace(/>/g, '>');
- if (!isText) {
- text = text.replace(/\n/g, '
');
- text = text.replace(/\r/g, '
');
- }
- if (isAttribute) {
- text = text.replace(/"/g, '"');
- }
- }
- return text;
- }
-
- /* TODO: benchmark single regex */
- function _escape_attrib(text, encoding) {
- return _escape(text, encoding, true);
- }
-
- function _escape_cdata(text, encoding) {
- return _escape(text, encoding, false);
- }
-
- function _escape_text(text, encoding) {
- return _escape(text, encoding, false, true);
- }
-
- function _namespaces(elem, encoding, default_namespace) {
- var qnames = {};
- var namespaces = {};
-
- if (default_namespace) {
- namespaces[default_namespace] = "";
- }
-
- function encode(text) {
- return text;
- }
-
- function add_qname(qname) {
- if (qname[0] === "{") {
- var tmp = qname.substring(1).split("}", 2);
- var uri = tmp[0];
- var tag = tmp[1];
- var prefix = namespaces[uri];
-
- if (prefix === undefined) {
- prefix = _namespace_map[uri];
- if (prefix === undefined) {
- prefix = "ns" + Object.keys(namespaces).length;
- }
- if (prefix !== "xml") {
- namespaces[uri] = prefix;
- }
- }
-
- if (prefix) {
- qnames[qname] = sprintf("%s:%s", prefix, tag);
- }
- else {
- qnames[qname] = tag;
- }
- }
- else {
- if (default_namespace) {
- throw new Error('cannot use non-qualified names with default_namespace option');
- }
-
- qnames[qname] = qname;
- }
- }
-
-
- elem.iter(null, function(e) {
- var i;
- var tag = e.tag;
- var text = e.text;
- var items = e.items();
-
- if (tag instanceof QName && qnames[tag.text] === undefined) {
- add_qname(tag.text);
- }
- else if (typeof(tag) === "string") {
- add_qname(tag);
- }
- else if (tag !== null && tag !== Comment && tag !== CData && tag !== ProcessingInstruction) {
- throw new Error('Invalid tag type for serialization: '+ tag);
- }
-
- if (text instanceof QName && qnames[text.text] === undefined) {
- add_qname(text.text);
- }
-
- items.forEach(function(item) {
- var key = item[0],
- value = item[1];
- if (key instanceof QName) {
- key = key.text;
- }
-
- if (qnames[key] === undefined) {
- add_qname(key);
- }
-
- if (value instanceof QName && qnames[value.text] === undefined) {
- add_qname(value.text);
- }
- });
- });
- return [qnames, namespaces];
- }
-
- function _serialize_xml(write, elem, encoding, qnames, namespaces, indent, indent_string) {
- var tag = elem.tag;
- var text = elem.text;
- var items;
- var i;
-
- var newlines = indent || (indent === 0);
- write(Array(indent + 1).join(indent_string));
-
- if (tag === Comment) {
- write(sprintf("<!--%s-->", _escape_cdata(text, encoding)));
- }
- else if (tag === ProcessingInstruction) {
- write(sprintf("<?%s?>", _escape_cdata(text, encoding)));
- }
- else if (tag === CData) {
- text = text || '';
- write(sprintf("<![CDATA[%s]]>", text));
- }
- else {
- tag = qnames[tag];
- if (tag === undefined) {
- if (text) {
- write(_escape_text(text, encoding));
- }
- elem.iter(function(e) {
- _serialize_xml(write, e, encoding, qnames, null, newlines ? indent + 1 : false, indent_string);
- });
- }
- else {
- write("<" + tag);
- items = elem.items();
-
- if (items || namespaces) {
- items.sort(); // lexical order
-
- items.forEach(function(item) {
- var k = item[0],
- v = item[1];
-
- if (k instanceof QName) {
- k = k.text;
- }
-
- if (v instanceof QName) {
- v = qnames[v.text];
- }
- else {
- v = _escape_attrib(v, encoding);
- }
- write(sprintf(" %s=\"%s\"", qnames[k], v));
- });
-
- if (namespaces) {
- items = utils.items(namespaces);
- items.sort(function(a, b) { return a[1] < b[1]; });
-
- items.forEach(function(item) {
- var k = item[1],
- v = item[0];
-
- if (k) {
- k = ':' + k;
- }
-
- write(sprintf(" xmlns%s=\"%s\"", k, _escape_attrib(v, encoding)));
- });
- }
- }
-
- if (text || elem.len()) {
- if (text && text.toString().match(/^\s*$/)) {
- text = null;
- }
-
- write(">");
- if (!text && newlines) {
- write("\n");
- }
-
- if (text) {
- write(_escape_text(text, encoding));
- }
- elem._children.forEach(function(e) {
- _serialize_xml(write, e, encoding, qnames, null, newlines ? indent + 1 : false, indent_string);
- });
-
- if (!text && indent) {
- write(Array(indent + 1).join(indent_string));
- }
- write("</" + tag + ">");
- }
- else {
- write(" />");
- }
- }
- }
-
- if (newlines) {
- write("\n");
- }
- }
-
- function parse(source, parser) {
- var tree = new ElementTree();
- tree.parse(source, parser);
- return tree;
- }
-
- function tostring(element, options) {
- return new ElementTree(element).write(options);
- }
-
- exports.PI = ProcessingInstruction;
- exports.Comment = Comment;
- exports.CData = CData;
- exports.ProcessingInstruction = ProcessingInstruction;
- exports.SubElement = SubElement;
- exports.QName = QName;
- exports.ElementTree = ElementTree;
- exports.ElementPath = ElementPath;
- exports.Element = function(tag, attrib) {
- return new Element(tag, attrib);
- };
-
- exports.XML = function(data) {
- var et = new ElementTree();
- return et.parse(data);
- };
-
- exports.parse = parse;
- exports.register_namespace = register_namespace;
- exports.tostring = tostring;
|