Page 1 of 1

XML to Json converter (javascript + webviewgadget)

Posted: Thu Oct 17, 2024 9:40 am
by tatanas
Hello,

Wanting to work with an XML file from the Powershell command "Get-GPOReport -All -Domain $domain -ReportType XML -Path C:\GPOReportsAll.xml", I quickly realized that it would be quite difficult with this file format and the XML library of Purebasic.
I therefore looked for a way to convert this XML file to Json. I couldn't find an algorithm in Purebasic and after a few hours of searching on the web, I found one in Javascript.
From there, I tried to include this script in a WebViewGadget and here's the result:

Code: Select all

Enumeration Window
  #mainForm
EndEnumeration

Enumeration Gadget
  #HideWeb
EndEnumeration

Global HTML.s

Procedure.s HtmlCode()
	HTML = ~"<input type=\"file\" id=\"file-input\" />" + #CRLF$ +
; 			~"<button id=\"btnCopy\">Copy to clipboard</button>"+ #CRLF$ +
			~"<h3>Contents of the xml file :</h3>" + #CRLF$ +
			~"<pre id=\"file-content\"></pre>"
 
	SetGadgetItemText(#HideWeb, #PB_Web_HtmlCode , HTML)

EndProcedure

Procedure PushJSScript()
	Protected script$ = PeekS(?JSFile, ?JSFileEnd - ?JSFile, #PB_UTF8 | #PB_ByteLength)
	WebViewExecuteScript(#HideWeb, script$)
EndProcedure

OpenWindow(#mainForm, 88, 244, 1000, 600, "Xml2Json", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
WebViewGadget(#HideWeb, 0, 0, 1000, 600, #PB_WebView_Debug)

HtmlCode()

PushJSScript()

Repeat
	event = WaitWindowEvent()
Until event = #PB_Event_CloseWindow


DataSection
	JSFile:
	IncludeBinary "xmltojson.js"
	JSFileEnd:
EndDataSection
The javascript code (xmltojson.js)

Code: Select all

var globaljsonString = '';

document.getElementById('file-input')
.addEventListener('change', readSingleFile, false);


// https://stackoverflow.com/questions/3582671/how-to-open-a-local-disk-file-with-javascript
function readSingleFile(e) {
 	var file = e.target.files[0];
 	if (!file) {return;}
	
	var reader = new FileReader();
 	reader.onload = function(e) {
		var contents = e.target.result;
		displayContents(contents);
		
		// convert Xml data to Json data
		const parser = new DOMParser();
		const srcDOM = parser.parseFromString(contents, "application/xml");
		var jsonObject = xml2json(srcDOM);
		
		// transform object into string
		var jsonString = JSON.stringify(jsonObject, null, 2);
		globaljsonString = jsonString; // for the copy to clipboard function

		// starting a download file operation of our json data
		DownloadJsonData(jsonString);
 	};
 	reader.readAsText(file);
}

function displayContents(contents) {
 	var element = document.getElementById('file-content');
 	element.textContent = contents;
 	return element.textContent;
}


// convert Xml data to Json
// https://dev.to/niinpatel/converting-xml-to-json-using-recursion-2k4j
function xml2json(srcDOM) {
	let children = [...srcDOM.children];

 	if (!children.length) {
		if (srcDOM.hasAttributes()) {
			var attrs = srcDOM.attributes;
			var output = {};
			for(var i = attrs.length - 1; i >= 0; i--) {
				output[attrs[i].name] = attrs[i].value;
			}
			output.value = srcDOM.innerHTML;
			return output;
		} else {
		return srcDOM.innerHTML;
		}
	}

 	let jsonResult = {};
 	for (let child of children) {

		let childIsArray = children.filter(eachChild => eachChild.nodeName === child.nodeName).length > 1;

		if (childIsArray) {
			if (jsonResult[child.nodeName] === undefined) {
				jsonResult[child.nodeName] = [xml2json(child)];
			} else {
				jsonResult[child.nodeName].push(xml2json(child));
			}
		} else {
			jsonResult[child.nodeName] = xml2json(child);
		}
	}
	return jsonResult;
}


// save the json file in download directory
function DownloadJsonData(jsonData) {
	const a = document.createElement('a');
	a.href = URL.createObjectURL(new Blob([jsonData], {type: 'text/plain'}));
	a.setAttribute('download', 'data.json');
	document.body.appendChild(a);
	a.click();
	document.body.removeChild(a);
}


// copy to clipboard
/* var btn = document.getElementById('btnCopy');
var clipboard =
{
	data      : '',
 	intercept : false,
 	hook      : function (evt)
 	{
 		if (clipboard.intercept)
 		{
			evt.preventDefault();
			evt.clipboardData.setData('text/plain', clipboard.data);
			clipboard.intercept = false;
			clipboard.data      = '';
 		}
 	}
};

window.addEventListener('copy', clipboard.hook);
btn.addEventListener('click', onButtonClick);

function onButtonClick()
{
 	clipboard.data = globaljsonString;
 	if (window.clipboardData)
 	{
 		window.clipboardData.setData('Text', clipboard.data);
 	}
 	else
 	{
 		clipboard.intercept = true;
 		document.execCommand('copy');
 	}
} */

Here is another version of the JavaScript that better manages namespaces :

Code: Select all

//var globaljsonString = '';

document.getElementById('file-input');
document.addEventListener('change', readSingleFile, false);


// https://stackoverflow.com/questions/3582671/how-to-open-a-local-disk-file-with-javascript
function readSingleFile(e) {
 	var file = e.target.files[0];
 	if (!file) {return;}
	
	var reader = new FileReader();
 	reader.onload = function(e) {
		var contents = e.target.result;
		displayContents(contents);
		
		// convert Xml data to Json data
		const parser = new DOMParser();
		const srcDOM = parser.parseFromString(contents, "application/xml");
		var jsonObject = xml2json(srcDOM);
		
		// transform object into string
		var jsonString = JSON.stringify(jsonObject, null, 2);
//		globaljsonString = jsonString; // for the copy to clipboard function

		// starting a download file operation of our json data
		DownloadJsonData(jsonString);
 	};
 	reader.readAsText(file);
}

function displayContents(contents) {
 	var element = document.getElementById('file-content');
 	element.textContent = contents;
	console.log(element.textContent);
 	return element.textContent;
}


// convert Xml data to Json
function xml2json(srcDOM) {
    // Crée un objet JSON
    let obj = {};

    // Si l'élément a des attributs, les ajouter à l'objet
    if (srcDOM.attributes) {
        for (let i = 0; i < srcDOM.attributes.length; i++) {
            const attr = srcDOM.attributes[i];
            obj[attr.nodeName] = attr.nodeValue;
        }
    }

    // Si l'élément a des enfants
    if (srcDOM.hasChildNodes()) {
        for (let i = 0; i < srcDOM.childNodes.length; i++) {
            const item = srcDOM.childNodes[i];
            const nodeName = item.nodeName;

            // Gérer les espaces de noms
            const localName = nodeName.split(':').pop(); // Récupérer le nom local sans le préfixe

            // Si c'est un élément texte
            if (item.nodeType === 3) {
                const textContent = item.nodeValue.trim();
                if (textContent) {
                    obj['#text'] = textContent;
                }
            } else {
                // Appel récursif pour les éléments enfants
                const childJson = xml2json(item);
                if (obj[localName]) {
                    // Si l'élément existe déjà, le convertir en tableau
                    if (!Array.isArray(obj[localName])) {
                        obj[localName] = [obj[localName]];
                    }
                    obj[localName].push(childJson);
                } else {
                    obj[localName] = childJson;
                }
            }
        }
    }

    // Simplifier les valeurs
    for (const key in obj) {
        if (typeof obj[key] === 'object' && obj[key] !== null) {
            // Si l'objet a un seul enfant qui est un texte, le convertir en valeur simple
            if (Object.keys(obj[key]).length === 1 && obj[key]['#text']) {
                obj[key] = obj[key]['#text'];
            } else if (Array.isArray(obj[key]) && obj[key].length === 1) {
                // Si c'est un tableau avec un seul élément, le convertir en objet
                obj[key] = obj[key][0];
            }
        }
    }

    return obj;
}


// save the json file in download directory
function DownloadJsonData(jsonData) {
	const a = document.createElement('a');
	a.href = URL.createObjectURL(new Blob([jsonData], {type: 'text/plain'}));
	a.setAttribute('download', 'data.json');
	document.body.appendChild(a);
	a.click();
	document.body.removeChild(a);
}


// copy to clipboard
/* var btn = document.getElementById('btnCopy');
var clipboard =
{
	data      : '',
 	intercept : false,
 	hook      : function (evt)
 	{
 		if (clipboard.intercept)
 		{
			evt.preventDefault();
			evt.clipboardData.setData('text/plain', clipboard.data);
			clipboard.intercept = false;
			clipboard.data      = '';
 		}
 	}
};

window.addEventListener('copy', clipboard.hook);
btn.addEventListener('click', onButtonClick);

function onButtonClick()
{
 	clipboard.data = globaljsonString;
 	if (window.clipboardData)
 	{
 		window.clipboardData.setData('Text', clipboard.data);
 	}
 	else
 	{
 		clipboard.intercept = true;
 		document.execCommand('copy');
 	}
} */

I save the result of the conversion in a file "data.json" but you can also copy the result to the clipboard by uncommenting the last paragraph of the javascript and the corresponding line for the "btnCopy" button in the Purebasic code.


Don't hesitate to share your comments to correct and improve this code. (I'm a beginner in Javascript and web interface)