Skip to content

Commit bb0231e

Browse files
authored
Revise Write Xml (#31)
* Update dependencies * Create new JSON to XML action * Add help links
1 parent 522f382 commit bb0231e

File tree

10 files changed

+1841
-1619
lines changed

10 files changed

+1841
-1619
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
## 1.3.0 (April 23, 2020)
2+
* Update dependencies
3+
* Create new JSON to XML action
4+
* Add help links
5+
16
## 1.2.1 (March 30, 2020)
27

3-
* Minor logs impovements in "XML to JSON" action
8+
* Minor logs improvements in "XML to JSON" action
49

510
## 1.2.0 (January 30, 2020)
611

README.md

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,45 @@
11
# XML Component [![CircleCI](https://circleci.com/gh/elasticio/xml-component.svg?style=svg)](https://circleci.com/gh/elasticio/xml-component)
22

33
## Description
4-
elastic.io iPaaS component to convert between XML and JSON data.
4+
iPaaS component to convert between XML and JSON data.
55

66
### Purpose
7-
Allows users to convert XML attachments and strings to and from JSON
8-
This component has 3 actions allowing users to pass in either generic but well format XML/JSON string or XML attachment
9-
and produces a generic string of the other file type. The output then can be maped and used in other components.
7+
Allows users to convert XML attachments and strings to and from JSON.
8+
This component has 3 actions allowing users to pass in either generic but well formatted XML/JSON strings or XML attachments
9+
and produces a generic string or attachment of the other file type. The output then can be mapped and used in other components.
1010

11-
### Requirements
11+
### Requirements and Conversion Behavior
1212
Provided XML document (for `XML to JSON`) should be [well-formed](https://en.wikipedia.org/wiki/Well-formed_document)
13-
in order to be parsed correctly. You will get an error otherwise.
13+
in order to be parsed correctly. You will get an error otherwise.
14+
15+
JSON inputs must be objects with exactly one field as XML documents must be contained in a single 'root' tag.
16+
[JSON inputs can not have any field names which are not valid as XML tag names:](https://www.w3schools.com/xml/xml_elements.asp)
17+
* They must start with a letter or underscore
18+
* They cannot start with the letters xml (or XML, or Xml, etc)
19+
* They must only contain letters, digits, hyphens, underscores, and periods
20+
21+
XML attributes on a tag can be read and set by setting an `_attr` sub-object in the JSON.
22+
The inner-text of an XML element can also be controlled with `#` sub-object.
23+
24+
For example:
25+
```json
26+
{
27+
"someTag": {
28+
"_attr": {
29+
"id": "my id"
30+
},
31+
"_": "my inner text"
32+
}
33+
}
34+
```
35+
is equivalent to
36+
```xml
37+
<someTag id="my id">my inner text</someTag>
38+
```
1439

1540
#### Environment variables
16-
No environment variables need to be set.
41+
* `MAX_FILE_SIZE`: *optional* - Controls the maximum size of an attachment to be written in MB.
42+
Defaults to 10 MB where 1 MB = 1024 * 1024 bytes.
1743

1844
## Actions
1945

@@ -26,13 +52,36 @@ and produces one outbound message per matching attachment. As input, the user ca
2652
files by name or leave this field empty for processing all incoming *.xml files.
2753

2854
### JSON to XML
29-
Treats incoming message body as JSON and converts it to a generic XML string.
55+
Provides an input where a user provides a JSONata expression that should evaluate to an object to convert to JSON.
56+
See [Requirements & Conversion Behavior](#requirements-and-conversion-behavior) for details on conversion logic.
57+
The following options are supported:
58+
* **Upload XML as file to attachments**: When checked, the resulting XML will be placed directly into an attachment.
59+
The attachment information will be provided in both the message's attachments section as well as `attachmentUrl` and `attachmentSize`
60+
will be populated. The attachment size will be described in bytes.
61+
When this box is not checked, the resulting XML will be provided in the `xmlString` field.
62+
* **Exclude XML Header/Description**: When checked, no XML header of the form `<?xml version="1.0" encoding="UTF-8" standalone="no"?>` will be prepended to the XML output.
63+
* **Is the XML file standalone**: When checked, the xml header/description will have a value of `yes` for standalone. Otherwise, the value will be `no`. Has no effect when XML header/description is excluded.
64+
65+
The incoming message should have a single field `input`. When using integrator mode, this appears as the input **JSON to convert** When building mappings in developper mode, one must set the `input` property. E.g.:
66+
```
67+
{
68+
"input": {
69+
"someTag": {
70+
"_attr": {
71+
"id": "my id"
72+
},
73+
"_": "my inner text"
74+
}
75+
}
76+
}
77+
```
3078

3179
## Known limitations
3280
- The maximum size of incoming file for processing is 5 MiB. If the size of incoming file will be more than 5 MiB,
3381
action will throw error `Attachment *.xml is to large to be processed by XML component. File limit is: 5242880 byte,
3482
file given was: * byte.`.
35-
- `XML Attachemnt to JSON` action does not support local agents due to current platform limitations.
83+
- All actions involving attachments are not supported on local agents due to current platform limitations.
84+
- When creating XML files with invalid XML tags, the name of the potentially invalid tag will not be reported.
3685

3786
## Additional Info
3887
Icon made by Freepik from www.flaticon.com

component.json

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
{
22
"title": "XML",
3-
"description": "Component to work with XML files",
3+
"help": {
4+
"link": "/components/xml/",
5+
"description": "Component to convert between XML and JSON data"
6+
},
7+
"description": "Component to convert between XML and JSON data",
48
"buildType": "docker",
59
"actions": {
610
"xmlToJson": {
@@ -14,11 +18,69 @@
1418
},
1519
"jsonToXml": {
1620
"title": "JSON to XML",
17-
"main": "./lib/actions/jsonToXml.js",
21+
"main": "./lib/actions/jsonToXmlOld.js",
22+
"deprecated": true,
1823
"description": "Takes the body of message passed into the component and converts to generic XML string",
1924
"metadata": {
2025
"in": {},
21-
"out": "./lib/schemas/jsonToXml.out.json"
26+
"out": "./lib/schemas/jsonToXmlOld.out.json"
27+
}
28+
},
29+
"jsonToXmlV2": {
30+
"title": "JSON to XML",
31+
"main": "./lib/actions/jsonToXml.js",
32+
"help": {
33+
"link": "#json-to-xml",
34+
"description": "Takes the result of a JSONata expression and creates corresponding XML as either a string or an attachment"
35+
},
36+
"fields": {
37+
"uploadToAttachment": {
38+
"order": 3,
39+
"label": "Upload XML as file to attachments",
40+
"viewClass": "CheckBoxView"
41+
},
42+
"excludeXmlHeader": {
43+
"order": 2,
44+
"label": "Exclude XML Header/Description",
45+
"viewClass": "CheckBoxView"
46+
},
47+
"headerStandalone": {
48+
"order": 1,
49+
"label": "Is the XML file standalone",
50+
"viewClass": "CheckBoxView"
51+
}
52+
},
53+
"metadata": {
54+
"in": {
55+
"type": "object",
56+
"properties": {
57+
"input": {
58+
"title": "JSON to convert",
59+
"type": "object",
60+
"required": true
61+
}
62+
}
63+
},
64+
"out": {
65+
"type": "object",
66+
"properties": {
67+
"xmlString": {
68+
"type": "string",
69+
"required": false,
70+
"title": "XML String"
71+
},
72+
"attachmentUrl": {
73+
"title": "Attachment URL",
74+
"type": "string",
75+
"required": false
76+
},
77+
"attachmentSize": {
78+
"title": "Attachment Size (in bytes)",
79+
"type": "number",
80+
"required": false
81+
}
82+
}
83+
}
2284
}
2385
},
2486
"attachmentToJson": {

lib/actions/jsonToXml.js

Lines changed: 51 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,69 @@
1-
/* eslint no-invalid-this: 0 no-console: 0 */
2-
const eioUtils = require('elasticio-node').messages;
1+
const { AttachmentProcessor } = require('@elastic.io/component-commons-library');
2+
const { messages } = require('elasticio-node');
33
const xml2js = require('xml2js');
44
const _ = require('lodash');
55

6-
const ERROR = 'Prop name is invalid for XML tag';
6+
const MB_TO_BYTES = 1024 * 1024;
7+
const MAX_FILE_SIZE = process.env.MAX_FILE_SIZE * MB_TO_BYTES || 10 * MB_TO_BYTES;
78

8-
/**
9-
* Checks whether property name is valid
10-
* @param {String} key - propName
11-
* @returns {Boolean} - valid prop or not
12-
*/
13-
const propNameIsInvalid = (key) => /^\d/.test(key);
9+
module.exports.process = async function process(msg, cfg) {
10+
const { input } = msg.body;
11+
const { uploadToAttachment, excludeXmlHeader, headerStandalone } = cfg;
1412

15-
/**
16-
* Checks whether object contains properties
17-
* that startsWith number
18-
* @see https://github.com/elasticio/xml-component/issues/1
19-
* @param {Object|Number|String} value
20-
* @param {String} key
21-
*/
22-
function validateJsonPropNames(value, key) {
23-
if (propNameIsInvalid(key)) {
24-
const message = 'Can\'t create XML element from prop that starts with digit.'
25-
+ 'See XML naming rules https://www.w3schools.com/xml/xml_elements.asp';
26-
throw new Error(`${ERROR}: ${key}. ${message}`);
27-
}
28-
29-
if (!_.isPlainObject(value)) {
30-
return;
31-
}
13+
this.logger.info('Message received.');
3214

33-
Object.keys(value).forEach((prop) => {
34-
validateJsonPropNames(value[prop], prop);
35-
});
36-
}
37-
38-
/**
39-
* This method will be called from elastic.io platform providing following data
40-
*
41-
* @param msg incoming message object that contains ``body`` with payload
42-
* @param cfg configuration that is account information and configuration field values
43-
*/
44-
function processAction(msg, cfg) {
45-
this.logger.debug('Action started, message=%j cfg=%j', msg, cfg);
4615
const options = {
4716
trim: false,
4817
normalize: false,
4918
explicitArray: false,
5019
normalizeTags: false,
5120
attrkey: '_attr',
52-
tagNameProcessors: [
53-
(name) => name.replace(':', '-'),
54-
],
21+
explicitRoot: false,
22+
xmldec: {
23+
standalone: headerStandalone,
24+
encoding: 'UTF-8',
25+
},
26+
headless: excludeXmlHeader,
5527
};
5628
const builder = new xml2js.Builder(options);
5729

58-
const jsonToTransform = msg.body;
30+
// Check to make sure that input has at most one key
31+
// https://github.com/Leonidas-from-XIV/node-xml2js/issues/564
32+
if (!_.isPlainObject(input) || Object.keys(input).length !== 1) {
33+
throw new Error('Input must be an object with exactly one key.');
34+
}
5935

60-
validateJsonPropNames(jsonToTransform);
36+
const xmlString = builder.buildObject(input);
6137

62-
const result = builder.buildObject(jsonToTransform);
63-
this.logger.debug('Successfully converted body to XML result=%s', result);
64-
return eioUtils.newMessageWithBody({
65-
xmlString: result,
66-
});
67-
}
38+
if (!uploadToAttachment) {
39+
this.logger.info('Sending XML data in message.');
40+
await this.emit('data', messages.newMessageWithBody({
41+
xmlString,
42+
}));
43+
return;
44+
}
45+
46+
const attachmentSize = Buffer.byteLength(xmlString);
47+
if (attachmentSize > MAX_FILE_SIZE) {
48+
throw new Error(`XML data is ${attachmentSize} bytes, and is too large to upload as an attachment. Max attachment size is ${MAX_FILE_SIZE} bytes`);
49+
}
50+
this.logger.info(`Will create XML attachment of size ${attachmentSize} byte(s)`);
6851

69-
module.exports.process = processAction;
52+
const attachmentProcessor = new AttachmentProcessor();
53+
const uploadResult = await attachmentProcessor.uploadAttachment(xmlString);
54+
const attachmentUrl = uploadResult.config.url;
55+
this.logger.info(`Successfully created attachment at ${attachmentUrl}`);
56+
57+
const outboundMessage = messages.newEmptyMessage();
58+
outboundMessage.attachments = {
59+
'jsonToXml.xml': {
60+
url: attachmentUrl,
61+
size: attachmentSize,
62+
},
63+
};
64+
outboundMessage.body = {
65+
attachmentUrl,
66+
attachmentSize,
67+
};
68+
await this.emit('data', outboundMessage);
69+
};

lib/actions/jsonToXmlOld.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/* eslint no-invalid-this: 0 no-console: 0 */
2+
const eioUtils = require('elasticio-node').messages;
3+
const xml2js = require('xml2js');
4+
const _ = require('lodash');
5+
6+
const ERROR = 'Prop name is invalid for XML tag';
7+
8+
/**
9+
* Checks whether property name is valid
10+
* @param {String} key - propName
11+
* @returns {Boolean} - valid prop or not
12+
*/
13+
const propNameIsInvalid = (key) => /^\d/.test(key);
14+
15+
/**
16+
* Checks whether object contains properties
17+
* that startsWith number
18+
* @see https://github.com/elasticio/xml-component/issues/1
19+
* @param {Object|Number|String} value
20+
* @param {String} key
21+
*/
22+
function validateJsonPropNames(value, key) {
23+
if (propNameIsInvalid(key)) {
24+
const message = 'Can\'t create XML element from prop that starts with digit.'
25+
+ 'See XML naming rules https://www.w3schools.com/xml/xml_elements.asp';
26+
throw new Error(`${ERROR}: ${key}. ${message}`);
27+
}
28+
29+
if (!_.isPlainObject(value)) {
30+
return;
31+
}
32+
33+
Object.keys(value).forEach((prop) => {
34+
validateJsonPropNames(value[prop], prop);
35+
});
36+
}
37+
38+
/**
39+
* This method will be called from elastic.io platform providing following data
40+
*
41+
* @param msg incoming message object that contains ``body`` with payload
42+
* @param cfg configuration that is account information and configuration field values
43+
*/
44+
function processAction(msg, cfg) {
45+
this.logger.debug('Action started, message=%j cfg=%j', msg, cfg);
46+
const options = {
47+
trim: false,
48+
normalize: false,
49+
explicitArray: false,
50+
normalizeTags: false,
51+
attrkey: '_attr',
52+
tagNameProcessors: [
53+
(name) => name.replace(':', '-'),
54+
],
55+
};
56+
const builder = new xml2js.Builder(options);
57+
58+
const jsonToTransform = msg.body;
59+
60+
validateJsonPropNames(jsonToTransform);
61+
62+
const result = builder.buildObject(jsonToTransform);
63+
this.logger.debug('Successfully converted body to XML result=%s', result);
64+
return eioUtils.newMessageWithBody({
65+
xmlString: result,
66+
});
67+
}
68+
69+
module.exports.process = processAction;
File renamed without changes.

0 commit comments

Comments
 (0)