Initial commit

This commit is contained in:
robert committed 2025-05-30 18:13:30 +08:00
commit e49726988c
5445 files changed
+1089914

No files matched your search

+63
View File
@@ -0,0 +1,63 @@
# A simple XML to JSON converter
[![npm version](https://img.shields.io/npm/v/simple-xml-to-json.svg?style=flat-square)](https://www.npmjs.com/package/simple-xml-to-json)
[![Test CI](https://github.com/nirgit/simple-xml-to-json/actions/workflows/node.js.yml/badge.svg)](https://github.com/nirgit/simple-xml-to-json/actions/workflows/node.js.yml)
[![codecov](https://codecov.io/gh/nirgit/simple-xml-to-json/branch/master/graph/badge.svg?token=XW7CWWM4RV)](https://codecov.io/gh/nirgit/simple-xml-to-json)
## Install
Simply install using NPM in your project directory
> npm install simple-xml-to-json
## Usage and API
### 1. convertXML(xmlToConvert [,customConverter])
* `xmlToConvert` \<string\>
* `customConverter` \<function\>
* Returns: \<JSON\> by default or other if `customConverter` is used
### 2. createAST(xmlToConvert)
* `xmlToConvert` \<string\>
* Returns: An AST representation of the XML \<JSON\>
Code Example:
```javascript
const {convertXML, createAST} = require("simple-xml-to-json")
const myJson = convertXML(myXMLString)
const myYaml = convertXML(myXMLString, yamlConverter)
const myAst = createAST(myXMLString)
````
---
### ![TS](https://raw.githubusercontent.com/nirgit/assets/master/simple-xml-to-json/ts_icon_32.png) Typescript compatible
---
## Notes and how to use code
1. The easiest thing to start is to run `node example/example.js` in your terminal and see what happens.
2. There's the `xmlToJson.js` file for convenience. Just pass in the XML as a String.
3. It's MIT licensed so you can do whatever :)
4. Profit
## How this works in a nutshell
1. The library converts the XML to an AST
2. There is a JSON converter that takes the AST and spits out a JSON
3. You can write your own converters if you need XML-to-ANY-OTHER-FORMAT
## Benchmark
_Take these results with a grain of salt._\
According to a __simple__ benchmark test I performed in __April 2024__ with a random XML. _YMMV_.
![Benchmark Chart](https://github.com/nirgit/assets/blob/master/simple-xml-to-json/simple-xml-to-json-benchmark-2.png?raw=true)
* The chart results __may be__ outdated. [Run the test yourself](https://runkit.com/nirgit/6620cf27943a41000847221d)
## Current Drawbacks
1. All values are translated to strings in JSON
2. There are currently reserved words in the JSON converter:
* "content"
* "children"
so you cannot by default have an attribute with that name and free text as the content of the element or have nested elements as children.
*If you need to, you can write your own converter from the AST created by the parser, and pass it as a 2nd parameter after the xml string*
+505
View File
@@ -0,0 +1,505 @@
'use strict';
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
const convertToJSON = ast => {
return buildJSONFromNode(ast.value.children[0])
};
const buildJSONFromNode = node => {
if (!node) return null
const json = {};
switch (node.type) {
case "ELEMENT": {
let element = {};
const attribs = buildAttributes(node.value.attributes);
const children = buildJSONFromNode(node.value.children);
if (attribs) {
element = Object.assign(element, attribs);
}
if (children) {
const jsonChildren = buildChildren(node.value.children);
element = Object.assign(element, jsonChildren);
}
json[node.value.type] = element;
break
}
case "ATTRIBUTE": {
const attribNameAndValue = node.value;
json[attribNameAndValue.name] = attribNameAndValue.value;
break
}
case "CONTENT": {
return {content: node.value}
}
}
return json
};
const buildChildren = children => {
if (!children || !Array.isArray(children) || children.length === 0) return null
if (isContentChildren(children)) {
return {
content: children[0].value
}
}
return {
children: children.map(buildJSONFromNode)
}
};
const isContentChildren = children => children && Array.isArray(children) && children.length === 1 && children[0].type === "CONTENT";
const buildAttributes = arrayNodes => {
if (arrayNodes && Array.isArray(arrayNodes)) {
const jsonArray = arrayNodes.map(buildJSONFromNode);
return jsonArray.reduce((agg, j) => Object.assign(agg, j), {})
}
return null
};
var astToJson = {
convert: convertToJSON
};
const Token$2 = (type, value) => ({
type,
value
});
var model = {
Token: Token$2
};
const { Token: Token$1 } = model;
const EOF_TOKEN = Token$1("EOF");
const isCharBlank = (char) =>
char === ' ' || char === '\n' || char === '\r' || char === '\t';
const skipXMLDocumentHeader = (xmlAsString, pos) => {
if (xmlAsString.startsWith('<?xml', pos)) {
const len = xmlAsString.length;
while (pos < len) {
if (xmlAsString[pos] !== '?') {
pos++;
} else if (xmlAsString[pos + 1] === '>') {
return pos + 2 // skip "?>""
} else {
pos++;
}
}
}
return pos
};
const replaceQuotes = (str) => str.replace(/'/g, "'");
const getInitialPosForLexer = (xmlAsString) => {
let pos = 0;
while (pos < xmlAsString.length && isCharBlank(xmlAsString[pos])) pos++;
return skipXMLDocumentHeader(xmlAsString, pos)
};
function createLexer$1(xmlAsString) {
let currentToken = null;
let pos = getInitialPosForLexer(xmlAsString);
let scopingElement = [];
const peek = () => xmlAsString[pos];
const hasNext = () => currentToken !== EOF_TOKEN && pos < xmlAsString.length;
const isBlankSpace = () => isCharBlank(xmlAsString[pos]);
const skipQuotes = () => {
if (hasNext() && isQuote(peek())) pos++;
};
const isQuote = (char) => '"' === char || "'" === char;
const skipSpaces = () => {
while (hasNext() && isBlankSpace()) pos++;
};
const readAlphaNumericCharsOrBrackets = (areSpecialCharsSupported) => {
if (hasNext()) {
if (xmlAsString[pos] === '<') {
let buffer = '<';
pos++;
if (hasNext() && xmlAsString[pos] === '/') {
pos++;
buffer = '</';
} else if (
hasNext() &&
xmlAsString[pos] === '!' &&
xmlAsString[pos + 1] === '-' &&
xmlAsString[pos + 2] === '-'
) {
// its a comment
pos++;
pos++;
pos++;
buffer = '<!--';
}
return buffer
} else if (peek() === '/') {
let buffer = '/';
pos++;
if (hasNext() && peek() === '>') {
pos++;
buffer = '/>';
}
return buffer
} else if (xmlAsString[pos] === '=' || xmlAsString[pos] === '>') {
const buffer = xmlAsString[pos];
pos++;
return buffer
}
}
return readAlphaNumericChars(!!areSpecialCharsSupported)
};
const readAlphaNumericChars = (areSpecialCharsSupported) => {
const ELEMENT_TYPE_MATCHER = /[a-zA-Z0-9_:-]/;
const NAMES_VALS_CONTENT_MATCHER = /[^>=<]/u;
const matcher = areSpecialCharsSupported
? NAMES_VALS_CONTENT_MATCHER
: ELEMENT_TYPE_MATCHER;
let start = pos;
while (hasNext() && xmlAsString[pos].match(matcher)) pos++;
return replaceQuotes(xmlAsString.substring(start, pos))
};
const isElementBegin = () =>
currentToken && currentToken.type === "OPEN_BRACKET";
const isAssignToAttribute = () =>
currentToken && currentToken.type === "ASSIGN";
const next = () => {
const prevPos = pos;
skipSpaces();
const numOfSpacesSkipped = pos - prevPos;
if (!hasNext()) {
currentToken = EOF_TOKEN;
} else if (isElementBegin()) {
// starting new element
skipSpaces();
const buffer = readAlphaNumericCharsOrBrackets(false);
currentToken = Token$1("ELEMENT_TYPE", buffer);
scopingElement.push(buffer);
} else if (isAssignToAttribute()) {
// assign value to attribute
skipQuotes();
const openingQuote = xmlAsString[pos - 1];
let start = pos;
while (hasNext() && peek() !== openingQuote) pos++;
const buffer = replaceQuotes(xmlAsString.substring(start, pos));
pos++;
currentToken = Token$1("ATTRIB_VALUE", buffer);
} else {
skipSpaces();
let buffer = readAlphaNumericCharsOrBrackets(true);
switch (buffer) {
case '=': {
if (currentToken.type === "ATTRIB_NAME") {
currentToken = Token$1("ASSIGN");
} else {
currentToken = Token$1("CONTENT", buffer);
}
break
}
case '</': {
const start = pos;
while (xmlAsString[pos] !== '>') pos++;
currentToken = Token$1(
"CLOSE_ELEMENT",
xmlAsString.substring(start, pos)
);
pos++; // skip the ">"
scopingElement.pop();
break
}
case '/>': {
const scopingElementName = scopingElement.pop();
currentToken = Token$1(
"CLOSE_ELEMENT",
scopingElementName
);
break
}
case '<!--': {
// skipComment contents
const closingBuff = ['!', '-', '-'];
while (
hasNext() &&
(closingBuff[2] !== '>' ||
closingBuff[1] !== '-' ||
closingBuff[0] !== '-')
) {
closingBuff.shift();
closingBuff.push(xmlAsString[pos]);
pos++;
}
return next()
}
case '>': {
currentToken = Token$1("CLOSE_BRACKET");
break
}
case '<': {
currentToken = Token$1("OPEN_BRACKET");
break
}
default: {
// here we fall if we have alphanumeric string, which can be related to attributes, content or nothing
if (buffer && buffer.length > 0) {
if (currentToken.type === "CLOSE_BRACKET") {
let suffix = '';
if (peek() !== '<') {
suffix = readAlphaNumericChars(true);
}
currentToken = Token$1(
"CONTENT",
buffer + suffix
);
} else if (
currentToken.type !== "ATTRIB_NAME" &&
currentToken.type !== "CONTENT"
) {
if (
currentToken.type === "CLOSE_ELEMENT"
) {
// we're assuming this is content, part of unstructured data
buffer = ' '.repeat(numOfSpacesSkipped) + buffer;
currentToken = Token$1("CONTENT", buffer);
} else {
// it should be an attribute name token
currentToken = Token$1(
"ATTRIB_NAME",
buffer
);
}
} else {
const contentBuffer =
' '.repeat(numOfSpacesSkipped) + buffer; // spaces included as content
currentToken = Token$1(
"CONTENT",
contentBuffer
);
}
break
} else {
const errMsg =
'Unknown Syntax : "' + xmlAsString[pos] + '"';
throw new Error(errMsg)
}
}
}
}
return currentToken
};
return {
peek,
next,
hasNext,
// prettier-ignore
...({})
}
}
var lexer = {
createLexer: createLexer$1
};
const { createLexer } = lexer;
const { Token } = model;
// AST Node types
const [ROOT, ELEMENT, ATTRIBUTE, CONTENT] = [
'ROOT',
'ELEMENT',
'ATTRIBUTE',
'CONTENT'
];
const Node = (type, value) => ({
type,
value
});
const ContentNode = (value) => Node(CONTENT, value);
const ElementNode = (type, attributes, children) => {
return Node(ELEMENT, {
type,
attributes,
children
})
};
const AttribNode = (name, value) => {
return Node(ATTRIBUTE, {
name,
value
})
};
const parseXML = (lexer) => {
/*
How does the grammar look?
| expr: StructuredXML | UnstructuredXML | Content
| StructuredXML: (openBracket + ElementName) + (AttributeList)* + closeBracket + (expr)* + closeElement
| UnstructuredXML: Content* + expr* + Content*
| Content: String
| openBracket: <
| closeBracket: >
| closeElement: </ + ElementName + closeBracket
| ElementName: String
| AttributeList: AttributeName + "=" + AttributeValue + AttributeList*
| AttributeName: String
| AttributeValue: String
*/
const rootNode = Node(ROOT, {
children: parseExpr(lexer, Token(ROOT, 'ROOT'))
});
return rootNode
};
const parseExpr = (lexer, scopingElement) => {
const children = [];
while (lexer.hasNext()) {
const lexem = lexer.next();
switch (lexem.type) {
case "OPEN_BRACKET": {
const elementLexem = lexer.next();
const [elementAttributes, currentToken] =
parseElementAttributes(lexer);
let elementChildren = [];
if (currentToken.type !== "CLOSE_ELEMENT") {
elementChildren = parseExpr(lexer, elementLexem);
}
if (
elementChildren &&
elementChildren.length > 0 &&
elementChildren[0].type === "CONTENT"
) {
elementChildren = reduceChildrenElements(elementChildren);
}
children.push(
ElementNode(
elementLexem.value,
elementAttributes,
elementChildren
)
);
break
}
case "CLOSE_ELEMENT": {
if (lexem.value === scopingElement.value) return children
break
}
case "CONTENT": {
children.push(ContentNode(lexem.value));
break
}
case "EOF": {
return children
}
default: {
throw new Error(
`Unknown Lexem type: ${lexem.type} "${lexem.value}, scoping element: ${scopingElement.value}"`
)
}
}
}
return children
};
const parseElementAttributes = (lexer) => {
const attribs = [];
let currentToken = lexer.peek();
if (
!lexer.hasNext() ||
(currentToken && currentToken.type === "CLOSE_BRACKET") ||
(currentToken && currentToken.type === "CLOSE_ELEMENT")
) {
return [attribs, currentToken]
}
currentToken = lexer.next();
while (
lexer.hasNext() &&
currentToken &&
currentToken.type !== "CLOSE_BRACKET" &&
currentToken.type !== "CLOSE_ELEMENT"
) {
const attribName = currentToken;
lexer.next(); //assignment token
const attribValue = lexer.next();
const attributeNode = AttribNode(attribName.value, attribValue.value);
attribs.push(attributeNode);
currentToken = lexer.next();
}
return [attribs, currentToken]
};
function reduceChildrenElements(elementChildren) {
let reduced = [],
buffer = '';
elementChildren.forEach((child) => {
if (child.type === "CONTENT") {
buffer += child.value;
} else {
if (buffer.length) {
reduced.push(ContentNode(buffer));
buffer = '';
}
reduced.push(child);
}
});
if (buffer.length) reduced.push(ContentNode(buffer));
return reduced
}
function transpile$1(xmlAsString, astConverter) {
const lexer = createLexer(xmlAsString);
const ast = parseXML(lexer);
if (astConverter) {
return astConverter.convert(ast)
}
return ast
}
var transpiler = {
AttribNode,
ContentNode,
ElementNode,
Node,
transpile: transpile$1
};
const jsonConverter = astToJson;
const { transpile } = transpiler;
function convertXML(xmlAsString, customConverter) {
return transpile(xmlAsString, customConverter || jsonConverter)
}
function createAST(xmlAsString) {
return transpile(xmlAsString)
}
var xmlToJson = {
convertXML,
createAST
};
var xmlToJson$1 = /*@__PURE__*/getDefaultExportFromCjs(xmlToJson);
module.exports = xmlToJson$1;
+1
View File
@@ -0,0 +1 @@
"use strict";function e(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}const t=e=>{if(!e)return null;const n={};switch(e.type){case"ELEMENT":{let a={};const u=s(e.value.attributes),c=t(e.value.children);if(u&&(a=Object.assign(a,u)),c){const t=r(e.value.children);a=Object.assign(a,t)}n[e.value.type]=a;break}case"ATTRIBUTE":{const t=e.value;n[t.name]=t.value;break}case"CONTENT":return{content:e.value}}return n},r=e=>e&&Array.isArray(e)&&0!==e.length?n(e)?{content:e[0].value}:{children:e.map(t)}:null,n=e=>e&&Array.isArray(e)&&1===e.length&&"CONTENT"===e[0].type,s=e=>{if(e&&Array.isArray(e)){return e.map(t).reduce(((e,t)=>Object.assign(e,t)),{})}return null};var a={convert:e=>t(e.value.children[0])};var u={Token:(e,t)=>({type:e,value:t})};const{Token:c}=u,l=c("EOF"),E=e=>" "===e||"\n"===e||"\r"===e||"\t"===e,o=e=>e.replace(/'/g,"'");var T={createLexer:function(e){let t=null,r=(e=>{let t=0;for(;t<e.length&&E(e[t]);)t++;return((e,t)=>{if(e.startsWith("<?xml",t)){const r=e.length;for(;t<r;)if("?"!==e[t])t++;else{if(">"===e[t+1])return t+2;t++}}return t})(e,t)})(e),n=[];const s=()=>e[r],a=()=>t!==l&&r<e.length,u=e=>'"'===e||"'"===e,T=()=>{for(;a()&&E(e[r]);)r++},i=t=>{if(a()){if("<"===e[r]){let t="<";return r++,a()&&"/"===e[r]?(r++,t="</"):a()&&"!"===e[r]&&"-"===e[r+1]&&"-"===e[r+2]&&(r++,r++,r++,t="\x3c!--"),t}if("/"===s()){let e="/";return r++,a()&&">"===s()&&(r++,e="/>"),e}if("="===e[r]||">"===e[r]){const t=e[r];return r++,t}}return p(!!t)},p=t=>{const n=t?/[^>=<]/u:/[a-zA-Z0-9_:-]/;let s=r;for(;a()&&e[r].match(n);)r++;return o(e.substring(s,r))},N=()=>{const E=r;T();const f=r-E;if(a())if(t&&"OPEN_BRACKET"===t.type){T();const e=i(!1);t=c("ELEMENT_TYPE",e),n.push(e)}else if(t&&"ASSIGN"===t.type){a()&&u(s())&&r++;const n=e[r-1];let l=r;for(;a()&&s()!==n;)r++;const E=o(e.substring(l,r));r++,t=c("ATTRIB_VALUE",E)}else{T();let u=i(!0);switch(u){case"=":t="ATTRIB_NAME"===t.type?c("ASSIGN"):c("CONTENT",u);break;case"</":{const s=r;for(;">"!==e[r];)r++;t=c("CLOSE_ELEMENT",e.substring(s,r)),r++,n.pop();break}case"/>":{const e=n.pop();t=c("CLOSE_ELEMENT",e);break}case"\x3c!--":{const t=["!","-","-"];for(;a()&&(">"!==t[2]||"-"!==t[1]||"-"!==t[0]);)t.shift(),t.push(e[r]),r++;return N()}case">":t=c("CLOSE_BRACKET");break;case"<":t=c("OPEN_BRACKET");break;default:if(u&&u.length>0){if("CLOSE_BRACKET"===t.type){let e="";"<"!==s()&&(e=p(!0)),t=c("CONTENT",u+e)}else if("ATTRIB_NAME"!==t.type&&"CONTENT"!==t.type)"CLOSE_ELEMENT"===t.type?(u=" ".repeat(f)+u,t=c("CONTENT",u)):t=c("ATTRIB_NAME",u);else{const e=" ".repeat(f)+u;t=c("CONTENT",e)}break}{const t='Unknown Syntax : "'+e[r]+'"';throw new Error(t)}}}else t=l;return t};return{peek:s,next:N,hasNext:a}}};const{createLexer:i}=T,{Token:p}=u,[N,f,h,O]=["ROOT","ELEMENT","ATTRIBUTE","CONTENT"],y=(e,t)=>({type:e,value:t}),C=e=>y(O,e),v=(e,t,r)=>y(f,{type:e,attributes:t,children:r}),A=(e,t)=>y(h,{name:e,value:t}),L=(e,t)=>{const r=[];for(;e.hasNext();){const n=e.next();switch(n.type){case"OPEN_BRACKET":{const t=e.next(),[n,s]=_(e);let a=[];"CLOSE_ELEMENT"!==s.type&&(a=L(e,t)),a&&a.length>0&&"CONTENT"===a[0].type&&(a=b(a)),r.push(v(t.value,n,a));break}case"CLOSE_ELEMENT":if(n.value===t.value)return r;break;case"CONTENT":r.push(C(n.value));break;case"EOF":return r;default:throw new Error(`Unknown Lexem type: ${n.type} "${n.value}, scoping element: ${t.value}"`)}}return r},_=e=>{const t=[];let r=e.peek();if(!e.hasNext()||r&&"CLOSE_BRACKET"===r.type||r&&"CLOSE_ELEMENT"===r.type)return[t,r];for(r=e.next();e.hasNext()&&r&&"CLOSE_BRACKET"!==r.type&&"CLOSE_ELEMENT"!==r.type;){const n=r;e.next();const s=e.next(),a=A(n.value,s.value);t.push(a),r=e.next()}return[t,r]};function b(e){let t=[],r="";return e.forEach((e=>{"CONTENT"===e.type?r+=e.value:(r.length&&(t.push(C(r)),r=""),t.push(e))})),r.length&&t.push(C(r)),t}var x={AttribNode:A,ContentNode:C,ElementNode:v,Node:y,transpile:function(e,t){const r=(e=>y(N,{children:L(e,p(N,"ROOT"))}))(i(e));return t?t.convert(r):r}};const k=a,{transpile:d}=x;var g=e({convertXML:function(e,t){return d(e,t||k)},createAST:function(e){return d(e)}});module.exports=g;
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Nir Moav
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+75
View File
@@ -0,0 +1,75 @@
{
"name": "simple-xml-to-json",
"description": "Convert XML to JSON - Fast & Simple",
"keywords": [
"XML",
"xml",
"JSON",
"json",
"js",
"xml2json",
"xml2js",
"convertor",
"conversion",
"parsing",
"parser",
"AST",
"ast",
"simple",
"fast",
"lightweight",
"small"
],
"homepage": "https://github.com/nirgit/simple-xml-to-json",
"license": "MIT",
"version": "1.2.3",
"author": "Nir Moav <getnirm@gmail.com>",
"contributors": [
{
"name": "Nir Moav",
"email": "getnirm@gmail.com",
"url": "https://nirgit.github.io/"
}
],
"main": "lib/simpleXmlToJson.min.js",
"typings": "typings/simple-xml-to-json.d.ts",
"files": [
"lib",
"typings"
],
"scripts": {
"build": "npm run clean && rollup -c --inline",
"clean": "rm -rf ./lib/ && mkdir lib ",
"lint": "eslint ./src ./test",
"prettify": "prettier --write ./**/*.js",
"example": "node example/example.js",
"test": "jest ",
"make-new-release-lib": "npm run validate-lib-health && sh ./scripts/make-new-release.sh",
"validate-lib-health": "npm run prettify && npm run lint && npm test"
},
"repository": {
"type": "git",
"url": "https://github.com/nirgit/simple-xml-to-json.git"
},
"bugs": {
"url": "https://github.com/nirgit/simple-xml-to-json/issues"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-replace": "^5.0.5",
"@rollup/plugin-terser": "^0.4.4",
"acorn": "^8.11.3",
"acorn-walk": "^8.3.2",
"eslint": "^9.1.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-jest": "^28.3.0",
"globals": "^15.1.0",
"jest": "^29.7.0",
"magic-string": "^0.30.10",
"prettier": "3.2.5",
"rollup": "^4.17.1"
},
"engines": {
"node": ">=20.12.2"
}
}
+9
View File
@@ -0,0 +1,9 @@
declare module 'simple-xml-to-json' {
interface ASTConverter {
convert: (astAsJson: object) => any;
}
export function convertXML(xmlAsString: string, customConverter?: ASTConverter): any;
export function createAST(xmlAsString: string): object;
}