| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- /*
- * Utilities: A classic collection of JavaScript utilities
- * Copyright 2112 Matthew Eernisse ([email protected])
- *
- * 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 core = require('./core')
- , inflection = require('./inflection')
- /**
- @name xml
- @namespace xml
- */
- exports.XML = new (function () {
- // Default indention level
- var indentLevel = 4
- , tagFromType
- , obj2xml;
- tagFromType = function (item, prev) {
- var ret
- , type
- , types;
- if (item instanceof Array) {
- ret = 'array';
- } else {
- types = ['string', 'number', 'boolean', 'object'];
- for (var i = 0, ii = types.length; i < ii; i++) {
- type = types[i];
- if (typeof item == type) {
- ret = type;
- }
- }
- }
- if (prev && ret != prev) {
- return 'record'
- } else {
- return ret;
- }
- };
- obj2xml = function (o, opts) {
- var name = opts.name
- , level = opts.level
- , arrayRoot = opts.arrayRoot
- , pack
- , item
- , n
- , currentIndent = (new Array(level * indentLevel)).join(' ')
- , nextIndent = (new Array((level + 1) * indentLevel)).join(' ')
- , xml = '';
- switch (typeof o) {
- case 'string':
- case 'number':
- case 'boolean':
- xml = o.toString();
- break;
- case 'object':
- // Arrays
- if (o instanceof Array) {
- // Pack the processed version of each item into an array that
- // can be turned into a tag-list with a `join` method below
- // As the list gets iterated, if all items are the same type,
- // that's the tag-name for the individual tags. If the items are
- // a mixed, the tag-name is 'record'
- pack = [];
- for (var i = 0, ii = o.length; i < ii; i++) {
- item = o[i];
- if (!name) {
- // Pass any previous tag-name, so it's possible to know if
- // all items are the same type, or it's mixed types
- n = tagFromType(item, n);
- }
- pack.push(obj2xml(item, {
- name: name
- , level: level + 1
- , arrayRoot: arrayRoot
- }));
- }
- // If this thing is attached to a named property on an object,
- // use the name for the containing tag-name
- if (name) {
- n = name;
- }
- // If this is a top-level item, wrap in a top-level containing tag
- if (level == 0) {
- xml += currentIndent + '<' + inflection.pluralize(n) + ' type="array">\n'
- }
- xml += nextIndent + '<' + n + '>' +
- pack.join('</' + n + '>\n' + nextIndent +
- '<' + n + '>') + '</' + n + '>\n';
- // If this is a top-level item, close the top-level containing tag
- if (level == 0) {
- xml += currentIndent + '</' + inflection.pluralize(n) + '>';
- }
- }
- // Generic objects
- else {
- n = name || 'object';
- // If this is a top-level item, wrap in a top-level containing tag
- if (level == 0) {
- xml += currentIndent + '<' + n;
- // Lookahead hack to allow tags to have attributes
- for (var p in o) {
- if (p.indexOf('attr:') == 0) {
- xml += ' ' + p.replace(/^attr:/, '') + '="' +
- o[p] + '"'
- }
- }
- xml += '>\n';
- }
- for (var p in o) {
- item = o[p];
- // Data properties only
- if (typeof item == 'function') {
- continue;
- }
- // No attr hack properties
- if (p.indexOf('attr:') == 0) {
- continue;
- }
- xml += nextIndent;
- if (p == '#cdata') {
- xml += '<![CDATA[' + item + ']]>\n';
- }
- else {
- // Complex values, going to have items with multiple tags
- // inside
- if (typeof item == 'object') {
- if (item instanceof Array) {
- if (arrayRoot) {
- xml += '<' + p + ' type="array">\n'
- }
- }
- else {
- xml += '<' + p;
- // Lookahead hack to allow tags to have attributes
- for (var q in item) {
- if (q.indexOf('attr:') == 0) {
- xml += ' ' + q.replace(/^attr:/, '') + '="' +
- item[q] + '"'
- }
- }
- xml += '>\n';
- }
- }
- // Scalars, just a value and closing tag
- else {
- xml += '<' + p + '>'
- }
- xml += obj2xml(item, {
- name: p
- , level: level + 1
- , arrayRoot: arrayRoot
- });
- // Objects and Arrays, need indentation before closing tag
- if (typeof item == 'object') {
- if (item instanceof Array) {
- if (arrayRoot) {
- xml += nextIndent;
- xml += '</' + p + '>\n';
- }
- }
- else {
- xml += nextIndent;
- xml += '</' + p + '>\n';
- }
- }
- // Scalars, just close
- else {
- xml += '</' + p + '>\n';
- }
- }
- }
- // If this is a top-level item, close the top-level containing tag
- if (level == 0) {
- xml += currentIndent + '</' + n + '>\n';
- }
- }
- break;
- default:
- // No default
- }
- return xml;
- }
- /*
- * XML configuration
- *
- */
- this.config = {
- whitespace: true
- , name: null
- , fragment: false
- , level: 0
- , arrayRoot: true
- };
- /**
- @name xml#setIndentLevel
- @public
- @function
- @return {Number} Return the given `level`
- @description SetIndentLevel changes the indent level for XML.stringify and returns it
- @param {Number} level The indent level to use
- */
- this.setIndentLevel = function (level) {
- if(!level) {
- return;
- }
- return indentLevel = level;
- };
- /**
- @name xml#stringify
- @public
- @function
- @return {String} Return the XML entities of the given `obj`
- @description Stringify returns an XML representation of the given `obj`
- @param {Object} obj The object containing the XML entities to use
- @param {Object} opts
- @param {Boolean} [opts.whitespace=true] Don't insert indents and newlines after xml entities
- @param {String} [opts.name=typeof obj] Use custom name as global namespace
- @param {Boolean} [opts.fragment=false] If true no header fragment is added to the top
- @param {Number} [opts.level=0] Remove this many levels from the output
- @param {Boolean} [opts.arrayRoot=true]
- */
- this.stringify = function (obj, opts) {
- var config = core.mixin({}, this.config)
- , xml = '';
- core.mixin(config, (opts || {}));
- if (!config.whitespace) {
- indentLevel = 0;
- }
- if (!config.fragment) {
- xml += '<?xml version="1.0" encoding="UTF-8"?>\n';
- }
- xml += obj2xml(obj, {
- name: config.name
- , level: config.level
- , arrayRoot: config.arrayRoot
- });
- if (!config.whitespace) {
- xml = xml.replace(/>\n/g, '>');
- }
- return xml;
- };
- })();
|