Related
The method I use I need to put +13 and -1 inside the calculation when searching the position of each part of the text (const Before and const After), is there a more reliable and correct way?
const PositionBefore = TextScript.indexOf(Before)+13;
const PositionAfter = TextScript.indexOf(After)-1;
My fear is that for some reason the search text changes and I forget to change the numbers for the calculation and this causes an error in the retrieved text.
The part of text i'm return is date and hour:
2021-08-31 19:12:08
function Clock() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Clock');
var url = 'https://int.soccerway.com/';
const contentText = UrlFetchApp.fetch(url).getContentText();
const $ = Cheerio.load(contentText);
const Before = '"timestamp":"';
const After = '});\n block.registerForCallbacks();';
var ElementSelect = $('script:contains(' + Before + ')');
var TextScript = ElementSelect.html().replace("\n","");
const PositionBefore = TextScript.indexOf(Before)+13;
const PositionAfter = TextScript.indexOf(After)-1;
sheet.getRange(1, 1).setValue(TextScript.substring(PositionBefore, PositionAfter));
}
Example full text colected in var TextScript:
(function() {
var block = new HomeMatchesBlock('block_home_matches_31', 'block_home_matches', {"block_service_id":"home_index_block_homematches","date":"2021-08-31","display":"all","timestamp":"2021-08-31 19:12:08"});
block.registerForCallbacks();
$('block_home_matches_31_1_1').observe('click', function() { block.filterContent({"display":"all"}); }.bind(block));
$('block_home_matches_31_1_2').observe('click', function() { block.filterContent({"display":"now_playing"}); }.bind(block));
block.setAttribute('colspan_left', 2);
block.setAttribute('colspan_right', 2);
TimestampFormatter.format('block_home_matches_31');
})();
There is no way to eliminate the risk of structural changes to the source content.
You can take some steps to minimize the likelihood that you forget to change your code - for example, by removing the need for hard-coded +13 and -1. But there can be other reasons for your code to fail, beyond that.
It's probably more important to make it extremely obvious when your code does fail.
Consider the following sample (which does not use Cheerio, for simplicity):
function demoHandler() {
var url = 'https://int.soccerway.com/';
const contentText = UrlFetchApp.fetch(url).getContentText();
var matchedJsonString = contentText.match(/{.*?"timestamp".*?}/)[0];
if ( matchedJsonString ) {
try {
var json = JSON.parse(matchedJsonString);
} catch(err) {
console.log( err ); // "SyntaxError..."
}
console.log(json.timestamp)
} else {
consle.log( 'Something went terribly wrong...' )
}
}
When you run the above function it prints the following to the console:
2021-08-31 23:18:46
It does this by assuming the key value of "timestamp" is part of a JSON string, starting with { and ending with }.
You can therefore extract this JSON string and convert it to a JavaScript object and then access the timestamp value directly, without needing to handle substrings.
If the JSON is not valid you will get an explicit error similar to this:
[SyntaxError: Unexpected token c in JSON at position 0]
Scraping web page data almost always has these types of risk: Your code can be brittle and break easily if the source structure changes without warning. Just try to make suc changes as noticeable as possible. In your case, write the errors to your spreadsheet and make it really obvious (red, bold, etc.).
And make good use of try...catch statements. See: try...catch
How would you convert from XML to JSON and then back to XML?
The following tools work quite well, but aren't completely consistent:
xml2json
Has anyone encountered this situation before?
I think this is the best one: Converting between XML and JSON
Be sure to read the accompanying article on the xml.com O'Reilly site, which goes into details of the problems with these conversions, which I think you will find enlightening. The fact that O'Reilly is hosting the article should indicate that Stefan's solution has merit.
https://github.com/abdmob/x2js - my own library (updated URL from http://code.google.com/p/x2js/):
This library provides XML to JSON (JavaScript Objects) and vice versa javascript conversion functions. The library is very small and doesn't require any other additional libraries.
API functions
new X2JS() - to create your instance to access all library functionality. Also you could specify optional configuration options here
X2JS.xml2json - Convert XML specified as DOM Object to JSON
X2JS.json2xml - Convert JSON to XML DOM Object
X2JS.xml_str2json - Convert XML specified as string to JSON
X2JS.json2xml_str - Convert JSON to XML string
Online Demo on http://jsfiddle.net/abdmob/gkxucxrj/1/
var x2js = new X2JS();
function convertXml2JSon() {
$("#jsonArea").val(JSON.stringify(x2js.xml_str2json($("#xmlArea").val())));
}
function convertJSon2XML() {
$("#xmlArea").val(x2js.json2xml_str($.parseJSON($("#jsonArea").val())));
}
convertXml2JSon();
convertJSon2XML();
$("#convertToJsonBtn").click(convertXml2JSon);
$("#convertToXmlBtn").click(convertJSon2XML);
These answers helped me a lot to make this function:
function xml2json(xml) {
try {
var obj = {};
if (xml.children.length > 0) {
for (var i = 0; i < xml.children.length; i++) {
var item = xml.children.item(i);
var nodeName = item.nodeName;
if (typeof (obj[nodeName]) == "undefined") {
obj[nodeName] = xml2json(item);
} else {
if (typeof (obj[nodeName].push) == "undefined") {
var old = obj[nodeName];
obj[nodeName] = [];
obj[nodeName].push(old);
}
obj[nodeName].push(xml2json(item));
}
}
} else {
obj = xml.textContent;
}
return obj;
} catch (e) {
console.log(e.message);
}
}
As long as you pass in a jquery dom/xml object: for me it was:
Jquery(this).find('content').eq(0)[0]
where content was the field I was storing my xml in.
I've created a recursive function based on regex, in case you don't want to install library and understand the logic behind what's happening:
const xmlSample = '<tag>tag content</tag><tag2>another content</tag2><tag3><insideTag>inside content</insideTag><emptyTag /></tag3>';
console.log(parseXmlToJson(xmlSample));
function parseXmlToJson(xml) {
const json = {};
for (const res of xml.matchAll(/(?:<(\w*)(?:\s[^>]*)*>)((?:(?!<\1).)*)(?:<\/\1>)|<(\w*)(?:\s*)*\/>/gm)) {
const key = res[1] || res[3];
const value = res[2] && parseXmlToJson(res[2]);
json[key] = ((value && Object.keys(value).length) ? value : res[2]) || null;
}
return json;
}
Regex explanation for each loop:
res[0] - return the xml (as is)
res[1] - return the xml tag name
res[2] - return the xml content
res[3] - return the xml tag name in case the tag closes itself. In example: <tag />
You can check how the regex works here:
https://regex101.com/r/ZJpCAL/1
Note: In case json has a key with an undefined value, it is being removed.
That's why I've inserted null at the end of line 9.
I was using xmlToJson just to get a single value of the xml.
I found doing the following is much easier (if the xml only occurs once..)
let xml =
'<person>' +
' <id>762384324</id>' +
' <firstname>Hank</firstname> ' +
' <lastname>Stone</lastname>' +
'</person>';
let getXmlValue = function(str, key) {
return str.substring(
str.lastIndexOf('<' + key + '>') + ('<' + key + '>').length,
str.lastIndexOf('</' + key + '>')
);
}
alert(getXmlValue(xml, 'firstname')); // gives back Hank
You can also use txml. It can parse into a DOM made of simple objects and stringify. In the result, the content will be trimmed. So formating of the original with whitespaces will be lost. But this could be used very good to minify HTML.
const xml = require('txml');
const data = `
<tag>tag content</tag>
<tag2>another content</tag2>
<tag3>
<insideTag>inside content</insideTag>
<emptyTag />
</tag3>`;
const dom = xml(data); // the dom can be JSON.stringified
xml.stringify(dom); // this will return the dom into an xml-string
Disclaimer: I am the author of txml, the fastest xml parser in javascript.
A while back I wrote this tool https://bitbucket.org/surenrao/xml2json for my TV Watchlist app, hope this helps too.
Synopsys: A library to not only convert xml to json, but is also easy to debug (without circular errors) and recreate json back to xml. Features :- Parse xml to json object. Print json object back to xml. Can be used to save xml in IndexedDB as X2J objects. Print json object.
Disclaimer: I've written fast-xml-parser
Fast XML Parser can help to convert XML to JSON and vice versa. Here is the example;
var options = {
attributeNamePrefix : "#_",
attrNodeName: "attr", //default is 'false'
textNodeName : "#text",
ignoreAttributes : true,
ignoreNameSpace : false,
allowBooleanAttributes : false,
parseNodeValue : true,
parseAttributeValue : false,
trimValues: true,
decodeHTMLchar: false,
cdataTagName: "__cdata", //default is 'false'
cdataPositionChar: "\\c",
};
if(parser.validate(xmlData)=== true){//optional
var jsonObj = parser.parse(xmlData,options);
}
If you want to parse JSON or JS object into XML then
//default options need not to set
var defaultOptions = {
attributeNamePrefix : "#_",
attrNodeName: "#", //default is false
textNodeName : "#text",
ignoreAttributes : true,
encodeHTMLchar: false,
cdataTagName: "__cdata", //default is false
cdataPositionChar: "\\c",
format: false,
indentBy: " ",
supressEmptyNode: false
};
var parser = new parser.j2xParser(defaultOptions);
var xml = parser.parse(json_or_js_obj);
Here' a good tool from a documented and very famous npm library that does the xml <-> js conversions very well: differently from some (maybe all) of the above proposed solutions, it converts xml comments also.
var obj = {name: "Super", Surname: "Man", age: 23};
var builder = new xml2js.Builder();
var xml = builder.buildObject(obj);
In 6 simple ES6 lines:
xml2json = xml => {
var el = xml.nodeType === 9 ? xml.documentElement : xml
var h = {name: el.nodeName}
h.content = Array.from(el.childNodes || []).filter(e => e.nodeType === 3).map(e => e.textContent).join('').trim()
h.attributes = Array.from(el.attributes || []).filter(a => a).reduce((h, a) => { h[a.name] = a.value; return h }, {})
h.children = Array.from(el.childNodes || []).filter(e => e.nodeType === 1).map(c => h[c.nodeName] = xml2json(c))
return h
}
Test with echo "xml2json_example()" | node -r xml2json.es6 with source at https://github.com/brauliobo/biochemical-db/blob/master/lib/xml2json.es6
I would personally recommend this tool. It is an XML to JSON converter.
It is very lightweight and is in pure JavaScript. It needs no dependencies. You can simply add the functions to your code and use it as you wish.
It also takes the XML attributes into considerations.
var xml = ‘<person id=”1234” age=”30”><name>John Doe</name></person>’;
var json = xml2json(xml);
console.log(json);
// prints ‘{“person”: {“id”: “1234”, “age”: “30”, “name”: “John Doe”}}’
Here's an online demo!
There is an open sourced library Xml-to-json with methods jsonToXml(json) and xmlToJson(xml).
Here's an online demo!
This function directly reads the DOM properties of the XMLDocument (or document node/element) to build the JSON completely and accurately without trying to guess or match. Pass it responseXML, not responseText from XMLHttpRequest.
xml2json(xmlDoc)
If you only have a string of XML and not an XMLDocument, jQuery will convert your text to one.
xml2json($(xmlString)[0])
Each node becomes an object. (All elements are nodes, not all nodes are elements (e.g. text within an element).)
Every object contains the node name and type.
If it has attributes, they appear as properties in an attributes object.
If it has children, they appear recursively as node->objects in a children array.
If it's a Text, CDATA, or Comment node (bare text between element tags) or a comment, it shouldn't have attributes or children but the text will be in a text property.
{
// Always present
"name": "FancyElement",
"type": "Element",
// If present
"attributes: {
"attr1": "val1",
"attr2": "val2"
},
"children": [...],
"text": "buncha fancy words"
}
Caveat: I'm not familiar with all the node types. It's probably not grabbing needed/useful info from all of them. It was tested on and behaves as expected for
Element
Text
CDATA
Comment
Document
function xml2json(xml) {
try {
const types = [null,
"Element",
"Attribute",
"Text",
"CDATA",
"EntityReference", // Deprecated
"Entity", // Deprecated
"ProcessingInstruction",
"Comment",
"Document",
"DocumentType",
"DocumentFragment",
"Notation" // Deprecated
];
var o = {};
o.name = xml.nodeName;
o.type = types[xml.nodeType];
if (xml.nodeType == 3 ||
xml.nodeType == 4 ||
xml.nodeType == 8 ) {
o.text = xml.textContent;
} else {
if (xml.attributes) {
o.attributes = {};
for (const a of xml.attributes) {
o.attributes[a.name] = a.value;
}
}
if (xml.childNodes.length) {
o.children = [];
for (const x of xml.childNodes) {
o.children.push(xml2json(x))
}
}
}
return (o);
} catch (e) {
alert('Error in xml2json. See console for details.');
console.log('Error in xml2json processing node:');
console.log(o);
console.log('Error:');
console.log(e);
}
}
var doc = document.getElementById("doc");
var out = document.getElementById("out");
out.innerText = JSON.stringify(xml2json(doc), null, 2);
/* Let's process the whole Code Snippet #document, why not?
* Yes, the JSON we just put in the document body and all
* this code is encoded in the JSON in the console.
* In that copy you can see why the XML DOM will all be one line.
* The JSON in the console has "\n" nodes all throughout.
*/
console.log(xml2json(document));
#doc,
#out {
border: 1px solid black;
}
<div id="doc"><!-- The XML DOM will all be on one line --><div personality="bubbly" relevance=42>This text is valid for HTML.<span>But it probably shouldn't be siblings to an element in XML.</span></div></div>
<pre id="out"></pre>
The best way to do it using server side as client side doesn't work well in all scenarios. I was trying to build online json to xml and xml to json converter using javascript and I felt almost impossible as it was not working in all scenarios. Ultimately I ended up doing it server side using Newtonsoft in ASP.MVC. Here is the online converter http://techfunda.com/Tools/XmlToJson
I have a website that uses AJAX to deliver a JSON formatted string to a HighCharts chart.
You can see this as the middle JSON code part at:
http://jsfiddle.net/1Loag7pv/
$('#container').highcharts(
//JSON Start
{
"plotOptions": {
"series": {"animation": {"duration": 500}}
,"pie": {
"allowPointSelect": true,
"cursor": "pointer",
"dataLabels": {"formatter":function(){return this.point.name+': '+this.percentage.toFixed(1) + '%';}}
}
},
"chart":{"renderTo":"divReportChart"}
,"title":{"text":"Sales Totals"}
,"xAxis":{"title":{"text":"Item"}, "categories":["Taxes","Discounts","NetSalesTotal"], "gridLineWidth":1}
,"yAxis":[{"title":{"text":"Amount"}, "gridLineWidth":1}]
,"series":[{"name":"Amount","type":"pie", "startAngle": -60,"yAxis": 0,"data":[["Taxes",17.8700],["Discounts",36.0000],["NetSalesTotal",377.9500]]}]
}
//JSON end
);
The problem is that the function part...
"dataLabels": {"formatter":function(){return this.point.name+': '+this.percentage.toFixed(1) + '%';}}
is not being transferred via the JSON
All research tells me that there is NO WAY to do this.
IE... Is it valid to define functions in JSON results?
Anybody got an idea on how to get around this limitation?
It is true that you cannot pass functions in JSON. Javascript is a superset of JSON.
A common approach is for the chart to be defined in javascript (e.g. during the page load), and the page then requests just the data via Ajax. When the data is returned it can be added to the chart object, either before it is rendered or afterwards using the highcharts API.
If you really want to pass the formatter function from the server with the chart, send it as a string, and then turn it into a function like this:
var fn = Function(mystring);
and use it in highcharts like:
chart.plotOptions.pie.dataLabels = {"formatter":fn};
I've re-factored your example to show the approach: http://jsfiddle.net/wo7zn0bw/
I had a similar conundrum. I wanted to create the JSON server side (ruby on rails) so I could create images of charts for a web API and also present it on the client web browser with the same code. This is similar to SteveP's answer.
To conform with JSON standards, I changed all formatter functions to strings
{"formatter": "function(){ return this.point.name+':'+this.percentage.toFixed(1) + '%';}"}
On the web side, I navigate the hash looking for formatter keys and replace them with the function using this code (may be a better way!?). javascript:
function HashNavigator(){
this.navigateAndReplace = function(hash, key){
if (!this.isObject(hash)){
//Nice if only navigated hashes and arrays
return;
}
var keys = Object.keys(hash);
for(var i = 0; i< keys.length; i++){
if (keys[i] == key){
//convert string to js function
hash[keys[i]] = this.parseFunction(hash[keys[i]]);
} else if (this.isObject(hash[keys[i]])){
//navigate hash tree
this.navigateAndReplace(hash[keys[i]], key);
} else {
//continue
}
}
};
this.isObject = function(testVar) {
return testVar !== null && typeof testVar === 'object'
}
//http://stackoverflow.com/questions/7650071/is-there-a-way-to-create-a-function-from-a-string-with-javascript
this.parseFunction = function(fstring){
var funcReg = /function *\(([^()]*)\)[ \n\t]*{(.*)}/gmi;
var match = funcReg.exec(fstring.replace(/\n/g, ' '));
if(match) {
return new Function(match[1].split(','), match[2]);
}
return null;
};
}
To use this, would be something similar to this javascript:
hashNavigator = new HashNavigator();
hashNavigator.navigateAndReplace(myHighchartsHash, "formatter")
At that point the hash/js-object is Highcharts ready
Similar idea was used for the web image API.
I was really hoping that hacking at the JSON was not the only solution, but it works!
I used a different approach. I created a JSON like below
{"formatter": "function(){ return this.point.name+':'+this.percentage.toFixed(1) + '%';}"}
When I came to evaluating the expression, I used (assuming that the value of the 'formatter' is formatterValueString)
formatterValueString = formatterValueString.replace('function()', '');
let opts = (new Function(formatterValueString)).call(this);
formatterValue = opts;
The reason to use this approach was it became hard to bind 'this' with the function. The eval() function did not go well with accessing variable this. I am sure there are ways to do it. Just thought this was quick.
How would you convert from XML to JSON and then back to XML?
The following tools work quite well, but aren't completely consistent:
xml2json
Has anyone encountered this situation before?
I think this is the best one: Converting between XML and JSON
Be sure to read the accompanying article on the xml.com O'Reilly site, which goes into details of the problems with these conversions, which I think you will find enlightening. The fact that O'Reilly is hosting the article should indicate that Stefan's solution has merit.
https://github.com/abdmob/x2js - my own library (updated URL from http://code.google.com/p/x2js/):
This library provides XML to JSON (JavaScript Objects) and vice versa javascript conversion functions. The library is very small and doesn't require any other additional libraries.
API functions
new X2JS() - to create your instance to access all library functionality. Also you could specify optional configuration options here
X2JS.xml2json - Convert XML specified as DOM Object to JSON
X2JS.json2xml - Convert JSON to XML DOM Object
X2JS.xml_str2json - Convert XML specified as string to JSON
X2JS.json2xml_str - Convert JSON to XML string
Online Demo on http://jsfiddle.net/abdmob/gkxucxrj/1/
var x2js = new X2JS();
function convertXml2JSon() {
$("#jsonArea").val(JSON.stringify(x2js.xml_str2json($("#xmlArea").val())));
}
function convertJSon2XML() {
$("#xmlArea").val(x2js.json2xml_str($.parseJSON($("#jsonArea").val())));
}
convertXml2JSon();
convertJSon2XML();
$("#convertToJsonBtn").click(convertXml2JSon);
$("#convertToXmlBtn").click(convertJSon2XML);
These answers helped me a lot to make this function:
function xml2json(xml) {
try {
var obj = {};
if (xml.children.length > 0) {
for (var i = 0; i < xml.children.length; i++) {
var item = xml.children.item(i);
var nodeName = item.nodeName;
if (typeof (obj[nodeName]) == "undefined") {
obj[nodeName] = xml2json(item);
} else {
if (typeof (obj[nodeName].push) == "undefined") {
var old = obj[nodeName];
obj[nodeName] = [];
obj[nodeName].push(old);
}
obj[nodeName].push(xml2json(item));
}
}
} else {
obj = xml.textContent;
}
return obj;
} catch (e) {
console.log(e.message);
}
}
As long as you pass in a jquery dom/xml object: for me it was:
Jquery(this).find('content').eq(0)[0]
where content was the field I was storing my xml in.
I've created a recursive function based on regex, in case you don't want to install library and understand the logic behind what's happening:
const xmlSample = '<tag>tag content</tag><tag2>another content</tag2><tag3><insideTag>inside content</insideTag><emptyTag /></tag3>';
console.log(parseXmlToJson(xmlSample));
function parseXmlToJson(xml) {
const json = {};
for (const res of xml.matchAll(/(?:<(\w*)(?:\s[^>]*)*>)((?:(?!<\1).)*)(?:<\/\1>)|<(\w*)(?:\s*)*\/>/gm)) {
const key = res[1] || res[3];
const value = res[2] && parseXmlToJson(res[2]);
json[key] = ((value && Object.keys(value).length) ? value : res[2]) || null;
}
return json;
}
Regex explanation for each loop:
res[0] - return the xml (as is)
res[1] - return the xml tag name
res[2] - return the xml content
res[3] - return the xml tag name in case the tag closes itself. In example: <tag />
You can check how the regex works here:
https://regex101.com/r/ZJpCAL/1
Note: In case json has a key with an undefined value, it is being removed.
That's why I've inserted null at the end of line 9.
I was using xmlToJson just to get a single value of the xml.
I found doing the following is much easier (if the xml only occurs once..)
let xml =
'<person>' +
' <id>762384324</id>' +
' <firstname>Hank</firstname> ' +
' <lastname>Stone</lastname>' +
'</person>';
let getXmlValue = function(str, key) {
return str.substring(
str.lastIndexOf('<' + key + '>') + ('<' + key + '>').length,
str.lastIndexOf('</' + key + '>')
);
}
alert(getXmlValue(xml, 'firstname')); // gives back Hank
You can also use txml. It can parse into a DOM made of simple objects and stringify. In the result, the content will be trimmed. So formating of the original with whitespaces will be lost. But this could be used very good to minify HTML.
const xml = require('txml');
const data = `
<tag>tag content</tag>
<tag2>another content</tag2>
<tag3>
<insideTag>inside content</insideTag>
<emptyTag />
</tag3>`;
const dom = xml(data); // the dom can be JSON.stringified
xml.stringify(dom); // this will return the dom into an xml-string
Disclaimer: I am the author of txml, the fastest xml parser in javascript.
A while back I wrote this tool https://bitbucket.org/surenrao/xml2json for my TV Watchlist app, hope this helps too.
Synopsys: A library to not only convert xml to json, but is also easy to debug (without circular errors) and recreate json back to xml. Features :- Parse xml to json object. Print json object back to xml. Can be used to save xml in IndexedDB as X2J objects. Print json object.
In 6 simple ES6 lines:
xml2json = xml => {
var el = xml.nodeType === 9 ? xml.documentElement : xml
var h = {name: el.nodeName}
h.content = Array.from(el.childNodes || []).filter(e => e.nodeType === 3).map(e => e.textContent).join('').trim()
h.attributes = Array.from(el.attributes || []).filter(a => a).reduce((h, a) => { h[a.name] = a.value; return h }, {})
h.children = Array.from(el.childNodes || []).filter(e => e.nodeType === 1).map(c => h[c.nodeName] = xml2json(c))
return h
}
Test with echo "xml2json_example()" | node -r xml2json.es6 with source at https://github.com/brauliobo/biochemical-db/blob/master/lib/xml2json.es6
Disclaimer: I've written fast-xml-parser
Fast XML Parser can help to convert XML to JSON and vice versa. Here is the example;
var options = {
attributeNamePrefix : "#_",
attrNodeName: "attr", //default is 'false'
textNodeName : "#text",
ignoreAttributes : true,
ignoreNameSpace : false,
allowBooleanAttributes : false,
parseNodeValue : true,
parseAttributeValue : false,
trimValues: true,
decodeHTMLchar: false,
cdataTagName: "__cdata", //default is 'false'
cdataPositionChar: "\\c",
};
if(parser.validate(xmlData)=== true){//optional
var jsonObj = parser.parse(xmlData,options);
}
If you want to parse JSON or JS object into XML then
//default options need not to set
var defaultOptions = {
attributeNamePrefix : "#_",
attrNodeName: "#", //default is false
textNodeName : "#text",
ignoreAttributes : true,
encodeHTMLchar: false,
cdataTagName: "__cdata", //default is false
cdataPositionChar: "\\c",
format: false,
indentBy: " ",
supressEmptyNode: false
};
var parser = new parser.j2xParser(defaultOptions);
var xml = parser.parse(json_or_js_obj);
Here' a good tool from a documented and very famous npm library that does the xml <-> js conversions very well: differently from some (maybe all) of the above proposed solutions, it converts xml comments also.
var obj = {name: "Super", Surname: "Man", age: 23};
var builder = new xml2js.Builder();
var xml = builder.buildObject(obj);
I would personally recommend this tool. It is an XML to JSON converter.
It is very lightweight and is in pure JavaScript. It needs no dependencies. You can simply add the functions to your code and use it as you wish.
It also takes the XML attributes into considerations.
var xml = ‘<person id=”1234” age=”30”><name>John Doe</name></person>’;
var json = xml2json(xml);
console.log(json);
// prints ‘{“person”: {“id”: “1234”, “age”: “30”, “name”: “John Doe”}}’
Here's an online demo!
There is an open sourced library Xml-to-json with methods jsonToXml(json) and xmlToJson(xml).
Here's an online demo!
This function directly reads the DOM properties of the XMLDocument (or document node/element) to build the JSON completely and accurately without trying to guess or match. Pass it responseXML, not responseText from XMLHttpRequest.
xml2json(xmlDoc)
If you only have a string of XML and not an XMLDocument, jQuery will convert your text to one.
xml2json($(xmlString)[0])
Each node becomes an object. (All elements are nodes, not all nodes are elements (e.g. text within an element).)
Every object contains the node name and type.
If it has attributes, they appear as properties in an attributes object.
If it has children, they appear recursively as node->objects in a children array.
If it's a Text, CDATA, or Comment node (bare text between element tags) or a comment, it shouldn't have attributes or children but the text will be in a text property.
{
// Always present
"name": "FancyElement",
"type": "Element",
// If present
"attributes: {
"attr1": "val1",
"attr2": "val2"
},
"children": [...],
"text": "buncha fancy words"
}
Caveat: I'm not familiar with all the node types. It's probably not grabbing needed/useful info from all of them. It was tested on and behaves as expected for
Element
Text
CDATA
Comment
Document
function xml2json(xml) {
try {
const types = [null,
"Element",
"Attribute",
"Text",
"CDATA",
"EntityReference", // Deprecated
"Entity", // Deprecated
"ProcessingInstruction",
"Comment",
"Document",
"DocumentType",
"DocumentFragment",
"Notation" // Deprecated
];
var o = {};
o.name = xml.nodeName;
o.type = types[xml.nodeType];
if (xml.nodeType == 3 ||
xml.nodeType == 4 ||
xml.nodeType == 8 ) {
o.text = xml.textContent;
} else {
if (xml.attributes) {
o.attributes = {};
for (const a of xml.attributes) {
o.attributes[a.name] = a.value;
}
}
if (xml.childNodes.length) {
o.children = [];
for (const x of xml.childNodes) {
o.children.push(xml2json(x))
}
}
}
return (o);
} catch (e) {
alert('Error in xml2json. See console for details.');
console.log('Error in xml2json processing node:');
console.log(o);
console.log('Error:');
console.log(e);
}
}
var doc = document.getElementById("doc");
var out = document.getElementById("out");
out.innerText = JSON.stringify(xml2json(doc), null, 2);
/* Let's process the whole Code Snippet #document, why not?
* Yes, the JSON we just put in the document body and all
* this code is encoded in the JSON in the console.
* In that copy you can see why the XML DOM will all be one line.
* The JSON in the console has "\n" nodes all throughout.
*/
console.log(xml2json(document));
#doc,
#out {
border: 1px solid black;
}
<div id="doc"><!-- The XML DOM will all be on one line --><div personality="bubbly" relevance=42>This text is valid for HTML.<span>But it probably shouldn't be siblings to an element in XML.</span></div></div>
<pre id="out"></pre>
The best way to do it using server side as client side doesn't work well in all scenarios. I was trying to build online json to xml and xml to json converter using javascript and I felt almost impossible as it was not working in all scenarios. Ultimately I ended up doing it server side using Newtonsoft in ASP.MVC. Here is the online converter http://techfunda.com/Tools/XmlToJson
what is the best practice for multilanguage website using DOM Manipulating with javascript? I build some dynamic parts of the website using javascript. My first thought was using an array with the text strings and the language code as index. Is this a good idea?
When I've built multi-lingual sites before (not very large ones, so this might not scale too well), I keep a series of "language" files:
lang.en.js
lang.it.js
lang.fr.js
Each of the files declares an object which is basically just a map from key word to language phrase:
// lang.en.js
lang = {
greeting : "Hello"
};
// lang.fr.js
lang = {
greeting : "Bonjour"
};
Dynamically load one of those files and then all you need to do is reference the key from your map:
document.onload = function() {
alert(lang.greeting);
};
There are, of course, many other ways to do this, and many ways to do this style but better: encapsulating it all into a function so that a missing phrase from your "dictionary" can be handled gracefully, or even do the whole thing using OOP, and let it manage the dynamic including of the files, it could perhaps even draw language selectors for you, etc.
var l = new Language('en');
l.get('greeting');
There are a few things you need to keep in mind when designing multilanguage support:
1 - Separate code from data (i.e. don't hard-code strings right into your functions)
2 - create a formatting hook function to deal with localization differences. Allowing formattable strings ("{0}") is better than concatenating ("Welcome to" + value), for a lot of reasons:
in some languages, a number is formatted like 1.234.678,00 instead of 1,234,567.00
pluralization is often not as simple as appending an "s" at the end of the singular
grammar rules are different and can affect the order of things so you should allow dynamic data to be appended after the translation hook: for example, "Welcome to {0}" turns into "{0} he youkoso" in japanese (this happens in pretty much every language, mind you).
3 - Make sure that you can actually format strings after the translation hook runs, so you can reuse keys.
4 - Do not, under any circunstance, hook database outputs to the translator utility. If you have multilingual data, create separate tables / rows in your database. I've seen people get this no-brainer wrong fairly often (usually for countries and states/provinces in forms).
5 - Create explicit coding practices rules for creating keys. The formatter utility function (which will look something like translate("hello world") will take a key as a parameter, and keys with slight variations make maintainance very annoying. For instance, you might end up with three keys in the following example: "enter you name", "enter your name:", "enter your name: ". Choose one format (e.g. no colon, trimmed) and catch discrepancies in code reviews. Don't do this filtering programmatically, as it can trigger false positives.
6 - Be mindful that HTML markup could potentially be needed in the translation table (e.g. if you need to bold a word in a sentence, or have footnote medical references). Test for this extensively.
7 - There are several ways of importing language strings. Ideally, you should have multiple versions of a language.lang.js file, switch between them with server side code, and reference the file from the bottom of the HTML file. Pulling the file via AJAX is also an alternative, but could introduce delays. Merging language.js into your main code file is not advisable, since you lose the benefits of file caching.
8 - Test with your target languages. This sounds silly, but I've seen a serious bug once because the programmer didn't bother to check for the existence of "é" in the key.
function Language(lang)
{
var __construct = function() {
if (eval('typeof ' + lang) == 'undefined')
{
lang = "en";
}
return;
}()
this.getStr = function(str, defaultStr) {
var retStr = eval('eval(lang).' + str);
if (typeof retStr != 'undefined')
{
return retStr;
} else {
if (typeof defaultStr != 'undefined')
{
return defaultStr;
} else {
return eval('en.' + str);
}
}
}
}
After adding this to your page, you can work with it like this:
var en = {
SelPlace:"Select this place?",
Save:"Saved."
};
var tr = {
SelPlace:"Burayı seçmek istiyor musunuz?"
};
var translator = new Language("en");
alert(translator.getStr("SelPlace")); // result: Select this place?
alert(translator.getStr("Save")); // result: Saved.
alert(translator.getStr("DFKASFASDFJK", "Default string for non-existent string")); // result: Default string for non-existent string
var translator = new Language("tr");
alert(translator.getStr("SelPlace")); // result: Burayı seçmek istiyor musunuz?
alert(translator.getStr("Save")); // result: Saved. (because it doesn't exist in this language, borrowed from english as default)
alert(translator.getStr("DFKASFASDFJK", "Default string for non-existent string")); // result: Default string for non-existent string
If you call the class with a language that you haven't defined, English(en) will be selected.
Just found a nice article about i18n in javascript:
http://24ways.org/2007/javascript-internationalisation
Although a simple google search with i18n + javascript reveals plenty of alternatives.
In the end, it depends on how deep you want it to be. For a couple of languages, a single file is enough.
You could use a framework like Jquery, use a span to identify the text (with a class) and then use the id of each span to find the corresponding text in the chosen language.
1 Line of Jquery, done.
After reading the great answers by nickf and Leo, I created the following CommonJS style language.js to manage all my strings (and optionally, Mustache to format them):
var Mustache = require('mustache');
var LANGUAGE = {
general: {
welcome: "Welcome {{name}}!"
}
};
function _get_string(key) {
var parts = key.split('.');
var result = LANGUAGE, i;
for (i = 0; i < parts.length; ++i) {
result = result[parts[i]];
}
return result;
}
module.exports = function(key, params) {
var str = _get_string(key);
if (!params || _.isEmpty(params)) {
return str;
}
return Mustache.render(str, params);
};
And this is how I get a string:
var L = require('language');
var the_string = L('general.welcome', {name='Joe'});
This way you can use one js code for multi language by multi word :
var strings = new Object();
if(navigator.browserLanguage){
lang = navigator.browserLanguage;
}else{
lang = navigator.language;
}
lang = lang.substr(0,2).toLowerCase();
if(lang=='fa'){/////////////////////////////Persian////////////////////////////////////////////////////
strings["Contents"] = "فهرست";
strings["Index"] = "شاخص";
strings["Search"] = "جستجو";
strings["Bookmark"] = "ذخیره";
strings["Loading the data for search..."] = "در حال جسنجوی متن...";
strings["Type in the word(s) to search for:"] = "لغت مد نظر خود را اینجا تایپ کنید:";
strings["Search title only"] = "جستجو بر اساس عنوان";
strings["Search previous results"] = "جستجو در نتایج قبلی";
strings["Display"] = "نمایش";
strings["No topics found!"] = "موردی یافت نشد!";
strings["Type in the keyword to find:"] = "کلیدواژه برای یافتن تایپ کنید";
strings["Show all"] = "نمایش همه";
strings["Hide all"] = "پنهان کردن";
strings["Previous"] = "قبلی";
strings["Next"] = "بعدی";
strings["Loading table of contents..."] = "در حال بارگزاری جدول فهرست...";
strings["Topics:"] = "عنوان ها";
strings["Current topic:"] = "عنوان جاری:";
strings["Remove"] = "پاک کردن";
strings["Add"] = "افزودن";
}else{//////////////////////////////////////English///////////////////////////////////////////////////
strings["Contents"] = "Contents";
strings["Index"] = "Index";
strings["Search"] = "Search";
strings["Bookmark"] = "Bookmark";
strings["Loading the data for search..."] = "Loading the data for search...";
strings["Type in the word(s) to search for:"] = "Type in the word(s) to search for:";
strings["Search title only"] = "Search title only";
strings["Search previous results"] = "Search previous results";
strings["Display"] = "Display";
strings["No topics found!"] = "No topics found!";
strings["Type in the keyword to find:"] = "Type in the keyword to find:";
strings["Show all"] = "Show all";
strings["Hide all"] = "Hide all";
strings["Previous"] = "Previous";
strings["Next"] = "Next";
strings["Loading table of contents..."] = "Loading table of contents...";
strings["Topics:"] = "Topics:";
strings["Current topic:"] = "Current topic:";
strings["Remove"] = "Remove";
strings["Add"] = "Add";
}
you can add another lang in this code and set objects on your html code.
I used Persian For Farsi language and English, you can use any type language just create copy of this part of code by If-Else statement.
You should look into what has been done in classic JS components - take things like Dojo, Ext, FCKEditor, TinyMCE, etc. You'll find lots of good ideas.
Usually it ends up being some kind of attributes you set on tags, and then you replace the content of the tag with the translation found in your translation file, based on the value of the attribute.
One thing to keep in mind, is the evolution of the language set (when your code evolves, will you need to retranslate the whole thing or not). We keep the translations in PO Files (Gnu Gettext), and we have a script that transforms the PO File into ready to use JS Files.
In addition:
Always use UTF-8 - this sounds silly, but if you are not in utf-8 from start (HTML head + JS encoding), you'll be bust quickly.
Use the english string as a key to your translations - this way you won't end up with things like: lang.Greeting = 'Hello world' - but lang['Hello world'] = 'Hello world';
You can use a google translator:
<div id="google_translate_element" style = "float: left; margin-left: 10px;"></div>
<script type="text/javascript">
function googleTranslateElementInit() {
new google.translate.TranslateElement({pageLanguage: 'en', layout: google.translate.TranslateElement.InlineLayout.HORIZONTAL}, 'google_translate_element');
}
</script>
<script type="text/javascript" src="//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script>
</div><input type = "text" style = "display: inline; margin-left: 8%;" class = "sear" placeholder = "Search people..."><button class = "bar">🔎</button>
class Language {
constructor(lang) {
var __construct = function (){
if (eval('typeof ' + lang) == 'undefined'){
lang = "en";
}
return;
};
this.getStr = function (str){
var retStr = eval('eval(lang).' + str);
if (typeof retStr != 'undefined'){
return retStr;
} else {
return str;
}
};
}
}
var en = {
Save:"Saved."
};
var fa = {
Save:"ذخیره"
};
var translator = new Language("fa");
console.log(translator.getStr("Save"));
For Spring bundles and JavaScript there are simple solution: generate i18n array in template (e.g. JSP) and use it in JavaScript:
JSP:
<html>
<script type="text/javascript">
var i18n = [];
<c:forEach var='key' items='<%=new String[]{"common.deleted","common.saved","common.enabled","common.disabled","...}%>'>
i18n['${key}'] = '<spring:message code="${key}"/>';
</c:forEach>
</script>
</html>
And in JS:
alert(i18n["common.deleted"]);
See also Resolving spring:messages in javascript for i18n internationalization