Skip to content

Instantly share code, notes, and snippets.

@johnathanesanders
Created September 6, 2019 10:47
Show Gist options
  • Save johnathanesanders/5bdfe54461f23ccaf6c0c962cd680db9 to your computer and use it in GitHub Desktop.
Save johnathanesanders/5bdfe54461f23ccaf6c0c962cd680db9 to your computer and use it in GitHub Desktop.
An XML to Object converter in Typescript
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