Created
September 6, 2019 10:47
-
-
Save johnathanesanders/5bdfe54461f23ccaf6c0c962cd680db9 to your computer and use it in GitHub Desktop.
An XML to Object converter in Typescript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export class XmlTools { | |
private readonly closingPattern: RegExp; | |
private readonly namePattern: RegExp; | |
private readonly nestPattern: RegExp; | |
private preparedStatement: object = {}; | |
private rawStatement: string; | |
constructor() { | |
this.closingPattern = /<\/([\w]+)[^>]*\>/g; | |
this.namePattern = /(?![</])([^ >][A-Za-z0-9]{1,}){1}/g; | |
this.nestPattern = /<\/?([\w]+)[^>]*\>/g; | |
} | |
private async buildObjectData(structure: object, offset?: number, rootNode?: string): Promise<any> { | |
let workingStructure: object; | |
if (rootNode) { | |
workingStructure = structure[rootNode]; | |
} else { | |
workingStructure = structure; | |
} | |
offset = offset ? offset : 0; | |
Object.keys(workingStructure).forEach(async (key) => { | |
if(workingStructure[key] === null) { | |
const beginExtract = this.rawStatement.indexOf(key, offset); | |
const endExtract = this.rawStatement.indexOf(key, (beginExtract + 1)); | |
const data = this.rawStatement.substring((beginExtract + 1 + key.length), (endExtract - 2)); | |
offset = endExtract; | |
workingStructure[key] = data; | |
} else { | |
const beginExtract = this.rawStatement.indexOf(key, offset); | |
if (Array.isArray(workingStructure[key])) { | |
// we have an array! | |
const endExtract = this.rawStatement.indexOf(key, (beginExtract + 1)); | |
workingStructure[key].forEach(async (element, index) => { | |
workingStructure[key][index] = await this.buildObjectData(element, (endExtract - beginExtract) * index) | |
}); | |
} else if (typeof workingStructure[key] === 'object') { | |
workingStructure[key] = await this.buildObjectData(workingStructure[key], beginExtract); | |
} | |
} | |
}); | |
return Promise.resolve(workingStructure); | |
} | |
private async buildObjectStructure(nodes: string[], appendNode: string): Promise<any> { | |
let processedNodes = []; | |
let structure = {}; | |
structure[appendNode] = {}; | |
// We need every millisecond we can get, so we loop the old fashioned way with for (i;i<x;i++) | |
for (let i=0;i<nodes.length;i++) { | |
if (!nodes[i].match(this.closingPattern)) { | |
const nodeName = nodes[i].match(this.namePattern)[0]; | |
const endObject = await this.getTagsBetween(nodes, i); | |
if (endObject > -1) { | |
const startFrom = i + 1; | |
const subObject = nodes.slice(startFrom, endObject); | |
if (subObject.length > 0) { | |
// This indicates that the node has sub objects, so we need to break them down one by one. | |
const buildResult = this.buildObjectStructure(subObject, nodeName); | |
if (processedNodes.indexOf(nodeName) > -1) { | |
if (Array.isArray(structure[appendNode][nodeName])) { | |
// It's already an array, so just push our new result to it... | |
structure[appendNode][nodeName].push(buildResult[nodeName]); | |
} else { | |
// We just found out we're an array for the firt time, so let's set it up | |
const tempStructure = structure[appendNode][nodeName]; | |
structure[appendNode][nodeName] = new Array(); | |
structure[appendNode][nodeName].push(tempStructure); // Push what should be the only result to our new array | |
structure[appendNode][nodeName].push(buildResult[nodeName]); // add our freshly minted result too... | |
} | |
processedNodes.push(nodeName); | |
} else { | |
// It's not an array, so do something else! | |
if(appendNode === nodeName) { | |
// Since we pass in our root as appendNode, we have to apply back to the actual structure when we reach the end vs. structure[appendNode] | |
structure = buildResult; | |
} else { | |
// Too tired to remember/figure out why we can't just use structure[appendNode] = buildResult, but it really creates a mess of things when we do... | |
structure[appendNode][nodeName] = buildResult[nodeName]; | |
} | |
processedNodes.push(nodeName); | |
} | |
// Now, to skip ahead within nodes so we don't process children of the node we just dealt with again... | |
i = endObject - 1; | |
} else { | |
// This indicates that the node doesn't have any sub objects, just sub elements (single dimension) | |
structure[appendNode][nodeName] = null; | |
processedNodes.push(nodeName); | |
} | |
} | |
else { | |
// Allegedly, there are no tags at all between the nodes (malformed?) - so we treat it as the above else {} | |
structure[appendNode][nodeName] = null; | |
processedNodes.push(nodeName); | |
} | |
} | |
} | |
return Promise.resolve(structure); | |
} | |
public async convertToObject(input: string, rootNode?: string): Promise<any> { | |
this.rawStatement = input; | |
const nodes = this.getNodes(input); | |
if (nodes.length > 0) { | |
if (!rootNode) { | |
rootNode = nodes[0]; | |
} | |
try { | |
const structure = await this.buildObjectStructure(nodes, rootNode); | |
this.preparedStatement = await this.buildObjectData(structure, 0, rootNode); | |
return Promise.resolve(this.preparedStatement); | |
} catch (error) { | |
return Promise.reject(error); | |
} | |
} else { | |
return Promise.resolve([]); | |
} | |
} | |
private getNodes(input: string): string[] { | |
return input.match(this.nestPattern); | |
} | |
private async getTagsBetween(haystack: string[], from: number):Promise<number> { | |
const needle = haystack[from].match(this.namePattern)[0]; | |
return Promise.resolve(haystack.indexOf('</' + needle + '>', from)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment