2019-09-04 03:18:21 -07:00
import { Builder , Parser } from 'xml2js' ;
2023-03-09 09:13:15 -08:00
import type {
IExecuteFunctions ,
INodeExecutionData ,
INodeType ,
INodeTypeDescription ,
} from 'n8n-workflow' ;
2024-08-29 06:55:53 -07:00
import { NodeConnectionType , NodeOperationError , deepCopy } from 'n8n-workflow' ;
2019-09-04 03:18:21 -07:00
export class Xml implements INodeType {
description : INodeTypeDescription = {
displayName : 'XML' ,
name : 'xml' ,
icon : 'fa:file-code' ,
2024-06-06 04:34:30 -07:00
iconColor : 'purple' ,
2019-09-04 03:18:21 -07:00
group : [ 'transform' ] ,
version : 1 ,
subtitle : '={{$parameter["mode"]==="jsonToxml" ? "JSON to XML" : "XML to JSON"}}' ,
description : 'Convert data from and to XML' ,
defaults : {
name : 'XML' ,
color : '#333377' ,
} ,
2024-08-29 06:55:53 -07:00
inputs : [ NodeConnectionType . Main ] ,
outputs : [ NodeConnectionType . Main ] ,
2019-09-04 03:18:21 -07:00
properties : [
{
displayName : 'Mode' ,
name : 'mode' ,
type : 'options' ,
options : [
{
name : 'JSON to XML' ,
value : 'jsonToxml' ,
2022-05-06 14:01:25 -07:00
description : 'Converts data from JSON to XML' ,
2019-09-04 03:18:21 -07:00
} ,
{
name : 'XML to JSON' ,
value : 'xmlToJson' ,
description : 'Converts data from XML to JSON' ,
} ,
] ,
default : 'xmlToJson' ,
2022-05-06 14:01:25 -07:00
description : 'From and to what format the data should be converted' ,
2019-09-04 03:18:21 -07:00
} ,
2024-01-03 03:08:16 -08:00
{
displayName :
2024-02-21 08:04:57 -08:00
"If your XML is inside a binary file, use the 'Extract from File' node to convert it to text first" ,
2024-01-03 03:08:16 -08:00
name : 'xmlNotice' ,
type : 'notice' ,
default : '' ,
displayOptions : {
show : {
mode : [ 'xmlToJson' ] ,
} ,
} ,
} ,
2019-09-04 03:18:21 -07:00
// ----------------------------------
// option:jsonToxml
// ----------------------------------
{
displayName : 'Property Name' ,
name : 'dataPropertyName' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
mode : [ 'jsonToxml' ] ,
2019-09-04 03:18:21 -07:00
} ,
} ,
default : 'data' ,
required : true ,
2022-05-06 14:01:25 -07:00
description : 'Name of the property to which to contains the converted XML data' ,
2019-09-04 03:18:21 -07:00
} ,
{
displayName : 'Options' ,
name : 'options' ,
type : 'collection' ,
2024-07-29 05:27:23 -07:00
placeholder : 'Add option' ,
2019-09-04 03:18:21 -07:00
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
mode : [ 'jsonToxml' ] ,
2019-09-04 03:18:21 -07:00
} ,
} ,
default : { } ,
options : [
{
displayName : 'Allow Surrogate Chars' ,
name : 'allowSurrogateChars' ,
type : 'boolean' ,
default : false ,
2022-06-20 07:54:01 -07:00
description : 'Whether to allow using characters from the Unicode surrogate blocks' ,
2019-09-04 03:18:21 -07:00
} ,
{
displayName : 'Attribute Key' ,
name : 'attrkey' ,
type : 'string' ,
default : '$' ,
2022-05-06 14:01:25 -07:00
d escription : 'Prefix that is used to access the attributes' ,
2019-09-04 03:18:21 -07:00
} ,
2020-02-11 08:03:24 -08:00
{
2022-06-03 10:23:49 -07:00
displayName : 'Cdata' ,
2020-02-11 08:03:24 -08:00
name : 'cdata' ,
type : 'boolean' ,
default : false ,
2022-08-17 08:50:24 -07:00
description :
'Whether to wrap text nodes in <![CDATA[ ... ]]> instead of escaping when necessary. Does not add <![CDATA[ ... ]]> if it is not required.' ,
2020-02-11 08:03:24 -08:00
} ,
2019-09-04 03:18:21 -07:00
{
displayName : 'Character Key' ,
name : 'charkey' ,
type : 'string' ,
default : '_' ,
2022-05-06 14:01:25 -07:00
description : 'Prefix that is used to access the character content' ,
2019-09-04 03:18:21 -07:00
} ,
{
displayName : 'Headless' ,
name : 'headless' ,
type : 'boolean' ,
default : false ,
2022-06-20 07:54:01 -07:00
description : 'Whether to omit the XML header' ,
2019-09-04 03:18:21 -07:00
} ,
2020-02-11 08:03:24 -08:00
{
displayName : 'Root Name' ,
name : 'rootName' ,
type : 'string' ,
default : 'root' ,
2022-05-06 14:01:25 -07:00
description : 'Root element name to be used' ,
2020-02-11 08:03:24 -08:00
} ,
2019-09-04 03:18:21 -07:00
] ,
} ,
// ----------------------------------
// option:xmlToJson
// ----------------------------------
{
displayName : 'Property Name' ,
name : 'dataPropertyName' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
mode : [ 'xmlToJson' ] ,
2019-09-04 03:18:21 -07:00
} ,
} ,
default : 'data' ,
required : true ,
2022-05-06 14:01:25 -07:00
description : 'Name of the property which contains the XML data to convert' ,
2019-09-04 03:18:21 -07:00
} ,
{
displayName : 'Options' ,
name : 'options' ,
type : 'collection' ,
2024-07-29 05:27:23 -07:00
placeholder : 'Add option' ,
2019-09-04 03:18:21 -07:00
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
mode : [ 'xmlToJson' ] ,
2019-09-04 03:18:21 -07:00
} ,
} ,
default : { } ,
options : [
{
displayName : 'Attribute Key' ,
name : 'attrkey' ,
type : 'string' ,
default : '$' ,
2022-05-06 14:01:25 -07:00
description : 'Prefix that is used to access the attributes' ,
2019-09-04 03:18:21 -07:00
} ,
{
displayName : 'Character Key' ,
name : 'charkey' ,
type : 'string' ,
default : '_' ,
2022-05-06 14:01:25 -07:00
description : 'Prefix that is used to access the character content' ,
2019-09-04 03:18:21 -07:00
} ,
{
displayName : 'Explicit Array' ,
name : 'explicitArray' ,
type : 'boolean' ,
default : false ,
2022-08-17 08:50:24 -07:00
description :
'Whether to always put child nodes in an array if true; otherwise an array is created only if there is more than one' ,
2019-09-04 03:18:21 -07:00
} ,
{
displayName : 'Explicit Root' ,
name : 'explicitRoot' ,
type : 'boolean' ,
default : true ,
2022-08-17 08:50:24 -07:00
description :
'Whether to set this if you want to get the root node in the resulting object' ,
2019-09-04 03:18:21 -07:00
} ,
{
displayName : 'Ignore Attributes' ,
name : 'ignoreAttrs' ,
type : 'boolean' ,
default : false ,
2022-06-20 07:54:01 -07:00
description : 'Whether to ignore all XML attributes and only create text nodes' ,
2019-09-04 03:18:21 -07:00
} ,
{
displayName : 'Merge Attributes' ,
name : 'mergeAttrs' ,
type : 'boolean' ,
default : true ,
2022-08-17 08:50:24 -07:00
description :
'Whether to merge attributes and child elements as properties of the parent, instead of keying attributes off a child attribute object. This option is ignored if ignoreAttrs is true.' ,
2019-09-04 03:18:21 -07:00
} ,
{
displayName : 'Normalize' ,
name : 'normalize' ,
type : 'boolean' ,
default : false ,
2022-06-20 07:54:01 -07:00
description : 'Whether to trim whitespaces inside text nodes' ,
2019-09-04 03:18:21 -07:00
} ,
{
displayName : 'Normalize Tags' ,
name : 'normalizeTags' ,
type : 'boolean' ,
default : false ,
2022-06-20 07:54:01 -07:00
description : 'Whether to normalize all tag names to lowercase' ,
2019-09-04 03:18:21 -07:00
} ,
{
displayName : 'Trim' ,
name : 'trim' ,
type : 'boolean' ,
default : false ,
2022-06-20 07:54:01 -07:00
description : 'Whether to trim the whitespace at the beginning and end of text nodes' ,
2019-09-04 03:18:21 -07:00
} ,
] ,
} ,
2020-10-22 06:46:03 -07:00
] ,
2019-09-04 03:18:21 -07:00
} ;
async execute ( this : IExecuteFunctions ) : Promise < INodeExecutionData [ ] [ ] > {
const items = this . getInputData ( ) ;
const mode = this . getNodeParameter ( 'mode' , 0 ) as string ;
2023-01-06 06:09:32 -08:00
const dataPropertyName = this . getNodeParameter ( 'dataPropertyName' , 0 ) ;
2022-12-02 12:54:28 -08:00
const options = this . getNodeParameter ( 'options' , 0 , { } ) ;
2019-09-04 03:18:21 -07:00
let item : INodeExecutionData ;
2022-02-04 03:16:34 -08:00
const returnData : INodeExecutionData [ ] = [ ] ;
2019-09-04 03:18:21 -07:00
for ( let itemIndex = 0 ; itemIndex < items . length ; itemIndex ++ ) {
2021-07-19 23:58:54 -07:00
try {
item = items [ itemIndex ] ;
2019-09-04 03:18:21 -07:00
2021-07-19 23:58:54 -07:00
if ( mode === 'xmlToJson' ) {
2022-08-17 08:50:24 -07:00
const parserOptions = Object . assign (
{
mergeAttrs : true ,
explicitArray : false ,
} ,
options ,
) ;
2019-09-04 03:18:21 -07:00
2021-07-19 23:58:54 -07:00
const parser = new Parser ( parserOptions ) ;
2019-09-04 03:18:21 -07:00
2021-07-19 23:58:54 -07:00
if ( item . json [ dataPropertyName ] === undefined ) {
2022-08-17 08:50:24 -07:00
throw new NodeOperationError (
this . getNode ( ) ,
2023-02-23 00:33:43 -08:00
` Item has no JSON property called " ${ dataPropertyName } " ` ,
2022-08-17 08:50:24 -07:00
{ itemIndex } ,
) ;
2021-07-19 23:58:54 -07:00
}
2019-09-04 03:18:21 -07:00
2023-02-09 02:23:25 -08:00
const json = await parser . parseStringPromise ( item . json [ dataPropertyName ] as string ) ;
2023-06-29 06:30:39 -07:00
returnData . push ( { json : deepCopy ( json ) } ) ;
2021-07-19 23:58:54 -07:00
} else if ( mode === 'jsonToxml' ) {
const builder = new Builder ( options ) ;
2022-02-04 03:16:34 -08:00
returnData . push ( {
2021-07-19 23:58:54 -07:00
json : {
[ dataPropertyName ] : builder . buildObject ( items [ itemIndex ] . json ) ,
} ,
2022-06-03 08:25:07 -07:00
pairedItem : {
item : itemIndex ,
} ,
2022-02-04 03:16:34 -08:00
} ) ;
2021-07-19 23:58:54 -07:00
} else {
2022-08-17 08:50:24 -07:00
throw new NodeOperationError ( this . getNode ( ) , ` The operation " ${ mode } " is not known! ` , {
itemIndex ,
} ) ;
2021-07-19 23:58:54 -07:00
}
} catch ( error ) {
2024-08-30 00:59:30 -07:00
if ( this . continueOnFail ( ) ) {
2022-08-17 08:50:24 -07:00
items [ itemIndex ] = {
2022-06-03 08:25:07 -07:00
json : {
error : error.message ,
} ,
pairedItem : {
item : itemIndex ,
} ,
2022-08-17 08:50:24 -07:00
} ;
2021-07-19 23:58:54 -07:00
continue ;
}
throw error ;
2019-09-04 03:18:21 -07:00
}
}
2023-09-05 03:59:02 -07:00
return [ returnData ] ;
2019-09-04 03:18:21 -07:00
}
}