xmlexact
v1.0.0
Published
XmlExact simplifies working with complex XML documents from JavaScript without the XML suck
Downloads
10
Readme
XmlExact
XmlExact simplifies working with complex XML documents from JavaScript without the XML suck.
Introduction
Working with XML documents can be a pain as the legacy of 20 years of over-engineering can quickly be felt. Typical XML documents requires you to understand the many overcomplicated standards(XML, XSD, WSDL, XPath, etc.) and the tooling that was supposed to help you, bothers you with details that forces you to spend more time on pleasing the XML parser/generator instead of actually getting the job of converting your data to and from XML.
XmlExact approach to solving this is by only caring about how the XML will look both as XML and as JavaScript objects. Schema validation, XPath queries, namespace specification and other painful stuff is left for other libraries.
To do this XmlExact needs a simple document definition that describes the order, where attributes and namespace should be inject and if it should do simple type conversion. This document definition can be written by hand, constructed from existing XML, or by using tools that convert WSDL/XSD to the format.
The reason XMLExact was created was to build a SOAP client that can generate SOAP for a number of picky and broken SOAP implementations, this work is still in progress, but a POC can be found here: WSDLUtils
Installation
$ npm install xmlexact
Features
- Construct complex XML documents from simple JavaScript object
- Keep XML document definition separately from data
- Ensure element order
- Type conversion between XSD XML and node types
- array -> array
- boolean -> boolean
- decimal, double, float -> number
- byte, short, int, integer, long -> number
- negativeInteger, nonNegativeInteger, nonPositiveInteger -> number
- unsignedByte, unsignedShort, unsignedInt, unsignedLong -> number
- base64Binary, hexBinary -> Buffer
- Generate sample JavasScript objects based on the definition
Limitation
- Only works in NodeJS as it depends on node-expat(libexpat) for XML parsing (pull requests to add other web safe XML parses will be very welcome)
- Does not handle cases with namespace collision
Example of usage
var xmlExact = require('xmlexact');
var definition = {
Envelope$namespace: "soap",
Envelope$attributes: {
"xmlns:myns1": "http://myns1",
"xmlns:soap": "http://www.w3.org/2003/05/soap-envelope/",
},
Envelope$order: ["Header", "Body"],
Envelope: {
Header$namespace: "soap",
Body$namespace: "soap",
Body: {
value$namespace: "myns1",
value$type: "int",
values$attributes: {
"xmlns:stuff": "http://www.w3.org/2003/05/soap-encoding"
},
values: {
value$namespace: "stuff",
value$type: [],
},
}
}
};
var obj = {
Envelope: {
Header: {},
Body: {
value: 10,
values: {
value: ["a", "b", "c"]
}
}
}
};
// Generate XML and with names spaces, converting types and ensure element order
var xml = xmlExact.toXml(obj, "Envelope", definition);
// Parse xml reconstructing the javascript object with the right types
var parsedObj = xmlExact.fromXml(xml, definition);
Output of xml:
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope/" xmlns:myns1="http://myns1">
<soap:Header />
<soap:Body>
<myns1:value>10</myns1:value>
<values xmlns:stuff="http://www.w3.org/2003/05/soap-encoding">
<stuff:value>a</stuff:value>
<stuff:value>b</stuff:value>
<stuff:value>c</stuff:value>
</values>
</soap:Body>
</soap:Envelope>
Definition format
{
element$namespace: "soap", // Set the namespace on "element"
element$attributes: { // Set two attributes on "element"
"xmlns:soap": "http://www.w3.org/2003/05/soap-envelope/",
"customAttibute": "1234",
},
element$order: ["subElement1", "subElement2"], // Ensure that subElement1 and subElement2 are first
element: {
subElement1$type: "int", // Ensure that subElement1 is treated as a number
subElemenn2$type: ["string"], // Ensure that subElement2 is treated as a string array
subElemenn3$type: [], // Ensure that subElement3 is treated as a array
subElement3: {
subSubElement$type: "base64Binary" // Ensures that subSubElement is treated as a Buffer
}
}
}
Supported types:
- [] -> array
- boolean -> boolean
- decimal, double, float -> number
- byte, short, int, integer, long -> number
- negativeInteger, nonNegativeInteger, nonPositiveInteger -> number
- unsignedByte, unsignedShort, unsignedInt, unsignedLong -> number
- base64Binary, hexBinary -> Buffer
Functions
toXml(obj, rootName, [definition, options])
Parameters:
- obj: Javascript object to be converted to XML
- rootName: Name of property in obj that will be used as the root element
- definition: definition used to build XML output
- options: Options when building XML output
- indentation: Set indentation level for xml output, default is 2
- convertTypes: Convert types based on the information in the definition, default is true
- optimizeEmpty: Use self closed tags when property is null, undefined or empty, default is true
fromXml(xml, [definition, options])
Parameters:
- xml: XML to be convert to JavaScript object
- definition: definition used to build the JavaScript object
- options: Options when building JavaScript object
- indentation: Set indentation level for xml output, default is 2
- convertTypes: Convert types based on the information in the definition, default is true
- inlineAttributes: Inline attributes in the object by prepending $, default is true
generateDefinition(xml, [type, namespaces]);
- xml: XML document (XML sample or XSD)
- type: "xml", "xsd"
- namespaces: Namespaces to inject root
Type conversion
const definition = {
complexAll: {
boolean1$type: "boolean",
boolean2$type: "boolean",
float$type: "float",
int$type: "int",
},
};
const obj = {
complexAll: {
boolean1: true,
boolean2: false,
float: 1.1,
int: 1,
}
};
// Generate XML
const xml = xmlExact.toXml(obj, "complexAll", definition);
// Parse xml reconstructing the javascript object with the right types
const parsedObj = xmlExact.fromXml(xml, definition);
Output of xml:
<complexAll>
<boolean1>true</boolean1>
<boolean2>false</boolean2>
<float>1.1</float>
<int>1</int>
</complexAll>
Generating definitions
The generator does some guess work to generate the definition and might not always find the right types, fx. in case of base64Binary where it could also be a string.
let generatedDefinition = xmlExact.generateDefinition(sampleXml);
Sample XML:
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope/" soap:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
<soap:Header />
<soap:Body>
<complexAll>
<boolean1>true</boolean1>
<boolean2>true</boolean2>
<float>1.00</float>
<int>1124</int>
</complexAll>
<simpleArray>
<item>value</item>
<item>value</item>
</simpleArray>
<complexArray>
<item>
<int>1124</int>
<string>value</string>
</item>
</complexArray>
</soap:Body>
</soap:Envelope>
Output of generatedDefinition:
{
Envelope$namespace: "soap",
Envelope$attributes: {
"xmlns:soap": "http://www.w3.org/2003/05/soap-envelope/",
"soap:encodingStyle": "http://www.w3.org/2003/05/soap-encoding"
},
Envelope$order: ["Header", "Body"],
Envelope: {
Header$namespace: "soap",
Body$namespace: "soap",
Body: {
complexAll: {
boolean1$type: "boolean",
boolean2$type: "boolean",
float$type: "float",
int$type: "int",
},
complexAll$order: ["boolean1", "boolean2", "float", "int"],
simpleArray: {
item$type: ["string"]
},
complexArray: {
item: {
int$type: "int",
string$type: "string",
},
item$order: ["int", "string"]
}
},
Body$order: ["complexAll", "simpleArray", "complexArray"]
}
}
Sample generation
const definition = {
complexAllLength: {
tickerSymbola$type: "string",
tickerSymbola$length: [10, 10], // String with minimum and maximum length of 10
tickerSymbolb$type: ["string", 2, 2], // String array with minimum and maximum length of 2
tickerSymbolb$length: [1, 1], // String array item with minimum and maximum length of 1
}
};
const sample = xmlExact.generateSample("complexAllLength", definition);
Output of sample:
{
complexAllLength: {
tickerSymbola: " ",
tickerSymbolb: [" ", " "]
}
};