Skip to content

Commit

Permalink
Committed by npm script.
Browse files Browse the repository at this point in the history
  • Loading branch information
nashwaan committed Dec 17, 2017
1 parent cdef0a7 commit 16bbc00
Show file tree
Hide file tree
Showing 6 changed files with 468 additions and 31 deletions.
41 changes: 38 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,15 @@ This library store them in `attributes`, but most importantly, you can change th
By setting `{addParent: true}` option, an extra property named `parent` will be generated along each element so that its parent can be referenced.
Therefore, anywhere during the traversal of an element, its children **and** its parent can be easily accessed.

* **Portable Code**:
Written purely in JavaScript which means it can be used in Node environment and **browser** environment (via bundlers like browserify/JSPM/Webpack).

* **Support Command Line**:
To quickly convert xml or json files, this module can be installed globally or locally (i.e. use it as [script](https://docs.npmjs.com/misc/scripts) in package.json).

* **Customize Processing using Callback Hooks**:
[Custom functions](#options-for-custom-processing-functions) can be supplied to do additional processing for different parts of xml or json (like cdata, comments, elements, attributes ...etc).

* **Portable Code**:
Written purely in JavaScript which means it can be used in Node environment and **browser** environment (via bundlers like browserify/JSPM/Webpack).

* **Typings Info Included**:
Support type checking and code suggestion via intellisense.
Thanks to the wonderful efforts by [DenisCarriere](https://github.com/DenisCarriere)
Expand Down Expand Up @@ -234,6 +237,38 @@ Two default values mean the first is used for *non-compact* output and the secon
> **TIP**: You probably want to set `{textKey: 'value', cdataKey: 'value', commentKey: 'value'}` for *non-compact* output
> to make it more consistent and easier for your client code to go through the contents of text, cdata, and comment.
## Options for Custom Processing Functions

For XML → JS object / JSON, following custom callback functions can be supplied:

| Option | Signature | Description |
|:--------------------|:----------|:------------|
| `doctypeFn` | `(value, parentElement)` | To perform additional processing for DOCTYPE. For example, `{doctypeFn: function(val) {return val.toUpperCase();}` |
| `instructionFn` | `(instructionValue, instructionName, parentElement)` | To perform additional processing for content of Processing Instruction value. For example, `{instructionFn: function(val) {return val.toUpperCase();}`. Note: `instructionValue` will be an object if `instructionHasAttributes` is enabled. |
| `cdataFn` | `(value, parentElement)` | To perform additional processing for CData. For example, `{cdataFn: function(val) {return val.toUpperCase();}`. |
| `commentFn` | `(value, parentElement)` | To perform additional processing for comments. For example, `{commentFn: function(val) {return val.toUpperCase();}`. |
| `textFn` | `(value, parentElement)` | To perform additional processing for texts inside elements. For example, `{textFn: function(val) {return val.toUpperCase();}`. |
| `instructionNameFn` | `(instructionName, instructionValue, parentElement)` | To perform additional processing for Processing Instruction name. For example, `{instructionNameFn: function(val) {return val.toUpperCase();}`. Note: `instructionValue` will be an object if `instructionHasAttributes` is enabled. |
| `elementNameFn` | `(value, parentElement)` | To perform additional processing for element name. For example, `{elementNameFn: function(val) {return val.toUpperCase();}`. |
| `attributeNameFn` | `(attributeName, attributeValue, parentElement)` | To perform additional processing for attribute name. For example, `{attributeNameFn: function(val) {return val.toUpperCase();}`. |
| `attributeValueFn` | `(attributeValue, attributeName, parentElement)` | To perform additional processing for attributeValue. For example, `{attributeValueFn: function(val) {return val.toUpperCase();}`. |
| `attributesFn` | `(value, parentElement)` | To perform additional processing for attributes object. For example, `{attributesFn: function(val) {return val.toUpperCase();}`. |

For JS object / JSON → XML, following custom callback functions can be supplied:

| Option | Signature | Description |
|:--------------------|:----------|:------------|
| `doctypeFn` | `(value, currentElementName, currentElementObj)` | To perform additional processing for DOCTYPE. For example, `{doctypeFn: function(val) {return val.toUpperCase();}` |
| `instructionFn` | `(instructionValue, instructionName, currentElementName, currentElementObj)` | To perform additional processing for content of Processing Instruction value. For example, `{instructionFn: function(val) {return val.toUpperCase();}`. Note: `instructionValue` will be an object if `instructionHasAttributes` is enabled. |
| `cdataFn` | `(value, currentElementName, currentElementObj)` | To perform additional processing for CData. For example, `{cdataFn: function(val) {return val.toUpperCase();}`. |
| `commentFn` | `(value, currentElementName, currentElementObj)` | To perform additional processing for comments. For example, `{commentFn: function(val) {return val.toUpperCase();}`. |
| `textFn` | `(value, currentElementName, currentElementObj)` | To perform additional processing for texts inside elements. For example, `{textFn: function(val) {return val.toUpperCase();}`. |
| `instructionNameFn` | `(instructionName, instructionValue, currentElementName, currentElementObj)` | To perform additional processing for Processing Instruction name. For example, `{instructionNameFn: function(val) {return val.toUpperCase();}`. Note: `instructionValue` will be an object if `instructionHasAttributes` is enabled. |
| `elementNameFn` | `(value, currentElementName, currentElementObj)` | To perform additional processing for element name. For example, `{elementNameFn: function(val) {return val.toUpperCase();}`. |
| `attributeNameFn` | `(attributeName, attributeValue, currentElementName, currentElementObj)` | To perform additional processing for attribute name. For example, `{attributeNameFn: function(val) {return val.toUpperCase();}`. |
| `attributeValueFn` | `(attributeValue, attributeName, currentElementName, currentElementObj)` | To perform additional processing for attributeValue. For example, `{attributeValueFn: function(val) {return val.toUpperCase();}`. |
| `attributesFn` | `(value, currentElementName, currentElementObj)` | To perform additional processing for attributes object. For example, `{attributesFn: function(val) {return val.toUpperCase();}`. |

# Command Line

Because any good library should support command line usage, this library is no different.
Expand Down
71 changes: 54 additions & 17 deletions lib/js2xml.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
var helper = require('./options-helper');

var currentElement, currentElementName;

function validateOptions(userOptions) {
var options = helper.copyOptions(userOptions);
helper.ensureFlagExists('ignoreDeclaration', options);
Expand Down Expand Up @@ -30,6 +32,16 @@ function validateOptions(userOptions) {
helper.ensureKeyExists('type', options);
helper.ensureKeyExists('name', options);
helper.ensureKeyExists('elements', options);
helper.checkFnExists('doctype', options);
helper.checkFnExists('instruction', options);
helper.checkFnExists('cdata', options);
helper.checkFnExists('comment', options);
helper.checkFnExists('text', options);
helper.checkFnExists('instructionName', options);
helper.checkFnExists('elementName', options);
helper.checkFnExists('attributeName', options);
helper.checkFnExists('attributeValue', options);
helper.checkFnExists('attributes', options);
return options;
}

Expand All @@ -41,13 +53,18 @@ function writeAttributes(attributes, options, depth) {
if (options.ignoreAttributes) {
return '';
}
var key, attr, quote, result = '';
if ('attributesFn' in options) {
attributes = options.attributesFn(attributes, currentElementName, currentElement);
}
var key, attr, attrName, quote, result = '';
for (key in attributes) {
if (attributes.hasOwnProperty(key)) {
quote = options.noQuotesForNativeAttributes && typeof attributes[key] !== 'string' ? '' : '"';
attr = '' + attributes[key]; // ensure Number and Boolean are converted to String
attr = '' + attributes[key]; // ensure number and boolean are converted to String
attr = attr.replace(/"/g, '"');
attrName = 'attributeNameFn' in options ? options.attributeNameFn(key, attr, currentElementName, currentElement) : key;
result += (options.spaces && options.indentAttributes? writeIndentation(options, depth+1, false) : ' ');
result += key + '=' + quote + attr.replace(/"/g, '"') + quote;
result += attrName + '=' + quote + ('attributeValueFn' in options ? options.attributeValueFn(attr, key, currentElementName, currentElement) : attr) + quote;
}
}
if (attributes && Object.keys(attributes).length && options.spaces && options.indentAttributes) {
Expand All @@ -57,7 +74,9 @@ function writeAttributes(attributes, options, depth) {
}

function writeDeclaration(declaration, options, depth) {
return options.ignoreDeclaration ? '' : '<?xml' + writeAttributes(declaration[options.attributesKey], options, depth) + '?>';
currentElement = declaration;
currentElementName = 'xml';
return options.ignoreDeclaration ? '' : '<?' + 'xml' + writeAttributes(declaration[options.attributesKey], options, depth) + '?>';
}

function writeInstruction(instruction, options, depth) {
Expand All @@ -70,29 +89,36 @@ function writeInstruction(instruction, options, depth) {
break;
}
}
var instructionName = 'instructionNameFn' in options ? options.instructionNameFn(key, instruction[key], currentElementName, currentElement) : key;
if (typeof instruction[key] === 'object') {
return '<?' + key + writeAttributes(instruction[key][options.attributesKey], options, depth) + '?>';
currentElement = instruction;
currentElementName = instructionName;
return '<?' + instructionName + writeAttributes(instruction[key][options.attributesKey], options, depth) + '?>';
} else {
return '<?' + key + (instruction[key] ? ' ' + instruction[key] : '') + '?>';
var instructionValue = instruction[key] ? instruction[key] : '';
if ('instructionFn' in options) instructionValue = options.instructionFn(instructionValue, key, currentElementName, currentElement);
return '<?' + instructionName + (instructionValue ? ' ' + instructionValue : '') + '?>';
}
}

function writeComment(comment, options) {
return options.ignoreComment ? '' : '<!--' + comment + '-->';
return options.ignoreComment ? '' : '<!--' + ('commentFn' in options ? options.commentFn(comment, currentElementName, currentElement) : comment) + '-->';
}

function writeCdata(cdata, options) {
return options.ignoreCdata ? '' : '<![CDATA[' + cdata + ']]>';
return options.ignoreCdata ? '' : '<![CDATA[' + ('cdataFn' in options ? options.cdataFn(cdata, currentElementName, currentElement) : cdata) + ']]>';
}

function writeDoctype(doctype, options) {
return options.ignoreDoctype ? '' : '<!DOCTYPE ' + doctype + '>';
return options.ignoreDoctype ? '' : '<!DOCTYPE ' + ('doctypeFn' in options ? options.doctypeFn(doctype, currentElementName, currentElement) : doctype) + '>';
}

function writeText(text, options) {
if (options.ignoreText) return '';
text = '' + text; // ensure Number and Boolean are converted to String
text = text.replace(/&amp;/g, '&'); // desanitize to avoid double sanitization
return options.ignoreText ? '' : text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
return 'textFn' in options ? options.textFn(text, currentElementName, currentElement) : text;
}

function hasContent(element, options) {
Expand Down Expand Up @@ -128,18 +154,22 @@ function hasContent(element, options) {
}

function writeElement(element, options, depth) {
var xml = '';
xml += '<' + element.name;
currentElement = element;
currentElementName = element.name;
var xml = '', elementName = 'elementNameFn' in options ? options.elementNameFn(element.name, element) : element.name;
xml += '<' + elementName;
if (element[options.attributesKey]) {
xml += writeAttributes(element[options.attributesKey], options, depth);
}
if (options.fullTagEmptyElement || (element[options.elementsKey] && element[options.elementsKey].length) || (element[options.attributesKey] && element[options.attributesKey]['xml:space'] === 'preserve')) {
xml += '>';
if (element[options.elementsKey] && element[options.elementsKey].length) {
xml += writeElements(element[options.elementsKey], options, depth + 1);
currentElement = element;
currentElementName = element.name;
}
xml += options.spaces && hasContent(element, options) ? '\n' + Array(depth + 1).join(options.spaces) : '';
xml += '</' + element.name + '>';
xml += '</' + elementName + '>';
} else {
xml += '/>';
}
Expand Down Expand Up @@ -198,14 +228,17 @@ function hasContentCompact(element, options, anyContent) {
}

function writeElementCompact(element, name, options, depth, indent) {
currentElement = element;
currentElementName = name;
var elementName = 'elementNameFn' in options ? options.elementNameFn(name, element) : name;
if (typeof element === 'undefined' || element === null) {
return options.fullTagEmptyElement ? '<' + name + '></' + name + '>' : '<' + name + '/>';
return options.fullTagEmptyElement ? '<' + elementName + '></' + elementName + '>' : '<' + elementName + '/>';
}
var xml = '';
if (name) {
xml += '<' + name;
xml += '<' + elementName;
if (typeof element !== 'object') {
xml += '>' + writeText(element,options) + '</' + name + '>';
xml += '>' + writeText(element,options) + '</' + elementName + '>';
return xml;
}
if (element[options.attributesKey]) {
Expand All @@ -219,8 +252,10 @@ function writeElementCompact(element, name, options, depth, indent) {
}
}
xml += writeElementsCompact(element, options, depth + 1, false);
currentElement = element;
currentElementName = name;
if (name) {
xml += (indent ? writeIndentation(options, depth, false) : '') + '</' + name + '>';
xml += (indent ? writeIndentation(options, depth, false) : '') + '</' + elementName + '>';
}
return xml;
}
Expand Down Expand Up @@ -251,6 +286,8 @@ function writeElementsCompact(element, options, depth, firstLine) {
module.exports = function (js, options) {
options = validateOptions(options);
var xml = '';
currentElement = js;
currentElementName = '_root_';
if (options.compact) {
xml = writeElementsCompact(js, options, 0, true);
} else {
Expand Down
18 changes: 9 additions & 9 deletions lib/xml2js.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ function addField(type, value) {
currentElement[options[type + 'Key']] = [currentElement[options[type + 'Key']]];
}
if (type + 'Fn' in options && typeof value === 'string') {
value = options[type + 'Fn'](value);
value = options[type + 'Fn'](value, currentElement);
}
if (type === 'instruction' && ('instructionFn' in options || 'instructionNameFn' in options)) {
for (key in value) {
Expand All @@ -82,7 +82,7 @@ function addField(type, value) {
} else {
var temp = value[key];
delete value[key];
value[options.instructionNameFn(key, currentElement)] = temp;
value[options.instructionNameFn(key, temp, currentElement)] = temp;
}
}
}
Expand All @@ -104,21 +104,21 @@ function addField(type, value) {
break;
}
}
element[options.nameKey] = 'instructionNameFn' in options ? options.instructionNameFn(key, currentElement) : key;
element[options.nameKey] = 'instructionNameFn' in options ? options.instructionNameFn(key, value, currentElement) : key;
if (options.instructionHasAttributes) {
element[options.attributesKey] = value[key][options.attributesKey];
if ('attributeNameFn' in options) {
element[options.attributesKey] = options[type + 'Fn'](element[options.attributesKey]);
if ('instructionFn' in options) {
element[options.attributesKey] = options.instructionFn(element[options.attributesKey], key, currentElement);
}
} else {
if (type + 'Fn' in options) {
value[key] = options[type + 'Fn'](value[key]);
if ('instructionFn' in options) {
value[key] = options.instructionFn(value[key], key, currentElement);
}
element[options[type + 'Key']] = value[key];
element[options.instructionKey] = value[key];
}
} else {
if (type + 'Fn' in options) {
value = options[type + 'Fn'](value);
value = options[type + 'Fn'](value, currentElement);
}
element[options[type + 'Key']] = value;
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "xml-js",
"version": "1.5.2",
"version": "1.6.0",
"description": "A convertor between XML text and Javascript object / JSON text.",
"repository": {
"type": "git",
Expand Down
Loading

0 comments on commit 16bbc00

Please sign in to comment.