document.evaluate returns null with iterateNext - javascript

Ive been following the examples from w3 schools on using a xpath to navigate through my xml document however all i get back from iterateNext() is null. Below is my blog.xml file.
<blog
xmlns ="http://www.w3schools.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="blogschema.xsd">
<Title>My blog</Title>
<Entry>
<Heading id="101">week1</Heading>
<body>
<text>enter text right here</text>
<pictures>pictures in the body</pictures>
</body>
<labels>Seperate labels with commas</labels>
<date> 20121119</date>
</Entry>
</blog>
This is my html script, the while statement is never reached as result always returns null, this maybe something that im overlooking, but i assumed that if it was on w3 schools it should really work.
xmlDoc=loadXMLDoc("blog.xml");//loads xml file
//loadXmlContent(xmlDoc); using xml dom
path="/blog/Title"
if(document.implementation && document.implementation.createDocument)
{
var nodes = xmlDoc.evaluate(path, xmlDoc, null, 5, null);
alert(nodes);
var result = nodes.iterateNext();
while (result)
{document.write(result.childNodes[0].nodeValue);}
}
</script>

There is a default namespace declaration xmlns="http://www.w3schools.com" in the input that you need to take into account with e.g.
var nodes = xmlDoc.evaluate("df:blog/df:Title", xmlDoc, function(prefix) { if (prefix === "df") return "http://www.w3schools.com"; }, 5, null);
var result;
while ((result = nodes.iterateNext()) != null)
{
document.write(result.textContent);
}

Related

Inserting element inside an XML document using Javascript

Have a XML string like below:
<stages>
<params/>
<test description=""/>
</stages>
I want to add the following XML string after <test desc.../> tag OR before the end of stages i.e. </stages>
<stage id="myId" level="1">
and all subsequent stages.
Post-addition it should like
<stages>
<params/>
<test description=""/>
<stage id="myId" level="1"/>
<stage id=.../>
...
</stages>
I am trying to do something like this:
var stageNode = document.createTextNode(
"<stage id=\"myId\" level=\"1\">")
);
var root = document.getElementsByTagName("test").parentNode;
console.log(document.getElementsByTagName("test")); //<-- giving [] in console log and root is undefined; though the element is there
var stages = root.getElementsByTagName("stage");
root.insertBefore(stageNode,stages.nextSibling);
How can I do this in JavaScript or JQuery?
Using DocumentBuilder or DocumentBuilderFactory is giving me "Unexpected identifier" error. Is there an easier way to do the above using document builder in Javascript (as in Java)?
With jQuery it might look like so:
function addStage(xmlDocument, stageElement){
var parent = $(xmlDocument).find("test").parent(); //picks <test>'s parent
parent.append(stageElement); //appends stageElement to parent
}
var xmlString = '<stages><params/><test description=""/></stages>'; //your XML string
var xmlDocument = $.parseXML(xmlString); //parses your XML string and returns XML document
$("#addStage").click(function(){
var id = $("#stageId").val();
var stageElement = $("<stage id=\"" + id + "\"></stage>"); //creates new element
addStage(xmlDocument, stageElement);
console.log(xmlDocument);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
Stage ID: <input type="text" id="stageId" /> <input type="button" id="addStage" value="Add stage" />
Open browser's console to see the results.
Using Plain Old JavaScript:
var text = "<stages><params/><test description=\"\"/></stages>";
var parser = new DOMParser();
var xmlDoc = parser.parseFromString(text,"text/xml");
var newNode = xmlDoc.createElement("stage");
newNode.setAttribute("id", "myId");
newNode.setAttribute("level", "1");
xmlDoc.documentElement.appendChild(newNode);
newNode = xmlDoc.createElement("stage");
newNode.setAttribute("id", "myId2");
newNode.setAttribute("level", "2");
xmlDoc.documentElement.appendChild(newNode);
console.log(new XMLSerializer().serializeToString(xmlDoc.documentElement));
For reference on adding node at a particular position, refer XML DOM Add Nodes

Replace XML nodeValue via Javascript

OK, I have an XML/XSLT pairing (inserted into my HTML via Javascript from 2 external files) that creates a button on the page with the nodeValue taken from a tag called 'JobID' (a generated GUID).
<button id="5f8294ca-fe5a-4da9-847b-da99df999000" onclick="markFinished(this.id)" type="button">Finished</button>
Thus returning the id of the button to a function...
function markFinished(clicked_id)
{
cid = clicked_id;
document.write(cid)
}
The XML look like this...
<?xml version="1.0" encoding="utf-8"?>
<CurrentJobs>
<Job>
<JobID>25657287-cc52-415b-8781-be37d5098656</JobID>
<Status>a-current</Status>
</Job>
<Job>
<JobID>5f8294ca-fe5a-4da9-847b-da99df999000</JobID>
<Status>a-current</Status>
</Job>
<Job>
<JobID>05a84779-5801-4645-a7f9-74529ea5298b</JobID>
<Status>a-current</Status>
</Job>
<Job>
<JobID>07df3deb-4935-4504-8822-a73ccea038ae</JobID>
<Status>b-complete</Status>
</Job>
<Job>
<JobID>078c496d-ac60-48e7-b9fe-a0e1f78ff2c5</JobID>
<Status>c-upcoming</Status>
</Job>
<Job>
<JobID>07ec868e-d294-4bb3-807d-00df66f5bab2</JobID>
<Status>c-upcoming</Status>
</Job>
<Job>
<JobID>8bdeee5f-2bf6-4e44-8af8-69f600048dfe</JobID>
<Status>a-current</Status>
</Job>
</CurrentJobs>
I need a way for my function to match the clicked_id to the JobID and replace the nextSibling (Status) with 'b-finished'. Any help here would be greatly appreciated.
You'd do that by parsing the XML and traversing it nodes.
As the value you're looking for is the text, you have to iterate and check each element until a match is found, then get the next element and replace the text content of that
var parser = new DOMParser();
var doc = parser.parseFromString(xml, "application/xml");
function markFinished(clicked_id) {
var cid = clicked_id;
var nodes = doc.getElementsByTagName('JobID'),
match = null;
for (var i=nodes.length; i--;) {
if ( nodes[i].textContent.trim() === clicked_id ) {
match = nodes[i];
break;
}
}
if (match) {
match.nextSibling.textContent = 'b-finished'
}
}
FIDDLE

How to select tags with namespaces in CasperJS?

I'm refactoring a RSS so I decided to write some tests with CasperJS.
One of the elements of the RSS is "atom:link" (")
I tried this three codes, but none works
test.assertExists("//atom:link", "atom:link tag exists.");
test.assertExists({
type: 'xpath',
path: "//atom:link"
}, "atom:link element exists.");
//even this...
test.assertExists({
type: 'xpath',
namespace: "xmlns:atom",
path: "//atom:link"
}, "atom:link element exists.");
The RSS code is:
<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0" xml:base="http://example.org/" xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/"
xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>RSS Title</title>
<description>RSS description</description>
<link>http://example.org</link>
<lastBuildDate>Mon, 10 Nov 2014 11:37:02 +0000</lastBuildDate>
<language>es-ES</language>
<atom:link rel="self" href="http://example.org/rss/feed.xml"/>
<item></item>
<item></item>
</channel>
</rss>
I saw that in a demo in this page http://www.freeformatter.com/xpath-tester.html, foo:singers is accesible by:
//foo:singers
But in CasperJS seems that this don't work...
Anyone know how to select this kind of elements with a namespace?
The function which CasperJS uses to resolve elements by XPath is document.evaluate:
var xpathResult = document.evaluate(
xpathExpression,
contextNode,
namespaceResolver,
resultType,
result
);
When you look into the source code the namespaceResolver is always null. That means that CasperJS cannot use XPaths with prefixes. If you try it, you get
[error] [remote] findAll(): invalid selector provided "xpath selector: //atom:link":Error: NAMESPACE_ERR: DOM Exception 14
You would have to create your own method to retrieve elements with a user defined nsResolver.
casper.myXpathExists = function(selector){
return this.evaluate(function(selector){
function nsResolver(prefix) {
var ns = {
'atom' : 'http://www.w3.org/2005/Atom'
};
return ns[prefix] || null;
}
return !!document.evaluate(selector,
document,
nsResolver,
XPathResult.ANY_TYPE,
null).iterateNext(); // retrieve first element
}, selector);
};
// and later
test.assertTrue(casper.myXpathExists("//atom:link"), "atom:link tag exists.");

Accessing lastNode value in XML using JavaScript is not working in Chrome

I am a novice to JavaScript programming with XML. I tried the following example from the book "Inside XML", but couldn't able to get it running.
Following is the HTML code with JavaScript:
<HTML>
<HEAD>
<TITLE>
Reading XML element values
</TITLE>
<SCRIPT LANGUAGE="JavaScript">
function readXMLDocument()
{
var xmldoc, meetingsNode, meetingNode, peopleNode
var first_nameNode, last_nameNode, outputText
<!--xmldoc = new ActiveXObject("Microsoft.XMLDOM")-->
<!--xmldoc.load("meetings.xml")-->
parser=new DOMParser();
xmldoc=parser.parseFromString("meetings.xml","text/xml");
meetingsNode = xmldoc.documentElement
meetingNode = meetingsNode.firstChild
peopleNode = meetingNode.lastChild
personNode = xmldoc.getElementsByTagName("PEOPLE").lastChild
first_nameNode = personNode.firstChild
last_nameNode = first_nameNode.nextSibling
outputText = "Third name: " +
first_nameNode.lastChild.nodeValue + ' '
+ last_nameNode.lastChild.nodeValue
messageDIV.innerHTML=outputText
}
</SCRIPT>
</HEAD>
<BODY>
<CENTER>
<H1>
Reading XML element values
</H1>
<INPUT TYPE="BUTTON" VALUE="Get the name of the third person"
ONCLICK="readXMLDocument()">
<P>
<DIV ID="messageDIV"></DIV>
</CENTER>
</BODY>
</HTML>
Following is the XML code used:
<?xml version="1.0"?>
<MEETINGS>
<MEETING TYPE="informal">
<MEETING_TITLE>XML In The Real World</MEETING_TITLE>
<MEETING_NUMBER>2079</MEETING_NUMBER>
<SUBJECT>XML</SUBJECT>
<DATE>6/1/2002</DATE>
<PEOPLE>
<PERSON ATTENDANCE="present">
<FIRST_NAME>Edward</FIRST_NAME>
<LAST_NAME>Samson</LAST_NAME>
</PERSON>
<PERSON ATTENDANCE="absent">
<FIRST_NAME>Ernestine</FIRST_NAME>
<LAST_NAME>Johnson</LAST_NAME>
</PERSON>
<PERSON ATTENDANCE="present">
<FIRST_NAME>Betty</FIRST_NAME>
<LAST_NAME>Richardson</LAST_NAME>
</PERSON>
</PEOPLE>
</MEETING>
</MEETINGS>
But, when I run the code in Chrome, I get the following error:
Uncaught TypeError: Cannot read property 'nodeValue' of null
Please help me to resolve this issue. Thank you in advance.
I used Firefox with the firebug plugin to debug but you can use Chrome without any plugins. After loading the page you can press F12 to open the devtools. In the console tab you can see the output of your logs, warnings, errors and there is a command line to execute JavaScript.
Sometimes I make a variable global (check in the code for variable a) so I can type a. in the command line and see what properties it has.
One of the problems was that a node includes whitespace so the last node is a textNode containing whitespace in many cases. You should also try to end your statements with ;
Here is the code half debugged, hope it helps:
<textarea id="txt">your xml content</textarea>
function readXMLDocument(){
var xmldoc, meetingsNode, meetingNode, peopleNode,
first_nameNode, last_nameNode, outputText;
<!--xmldoc = new ActiveXObject("Microsoft.XMLDOM")-->
<!--xmldoc.load("meetings.xml")-->
parser=new DOMParser();
xmldoc=parser.parseFromString(
document.getElementById("txt").value,"text/xml");
meetingsNode = xmldoc.documentElement;
meetingNode = meetingsNode.firstChild;
console.log("meetingNode is:",meetingNode);
peopleNode = meetingNode.lastChild;
console.log("peoplenode is:",peopleNode);
//use console.log to figure out what the variable could be
console.log(xmldoc.getElementsByTagName("PEOPLE"));
//set a global variable named a to the what you want to inspect
//in the commandline you can type a. and after the dot the devtools
//will produce a list of attributes.
a = xmldoc.getElementsByTagName("PEOPLE");
personNode = xmldoc.getElementsByTagName("PEOPLE")
.item(0).lastElementChild;
a = personNode;
//in the commandline I can see a.lastChild is textnode
//the intellisense gives me an option lastElementChild
//didn't look it up but it could be Firefox specific
first_nameNode = personNode.lastElementChild;
last_nameNode = first_nameNode.nextSibling;
console.log("there are still some things to fix:",
meetingsNode, meetingNode, peopleNode,
first_nameNode, last_nameNode);
return;
outputText = "Third name: " +
first_nameNode.lastChild.nodeValue + ' '
+ last_nameNode.lastChild.nodeValue;
}
readXMLDocument();

Create a DOM document from string, without JQuery

We're looking for ways to create a DOM document in javascript from a string, but without using Jquery.
Is there a way to do so? [I would assume so, since Jquery can do it!]
For those curious, we can't use Jquery, becase we're doing this in the context of a Chrome application's content script, and using Jquery would just make our content script too heavy.
https://developer.mozilla.org/en-US/docs/Web/API/DOMParser
var parser = new DOMParser();
var doc = parser.parseFromString("<html_string>", "text/html");
(the resulting doc variable is a documentFragment Object).
In case you're still looking for an anwer, and for anyone else coming accross it, I just have been trying to do the same thing myself. It seems you want to be looking at javascript's DOMImplementation:
http://reference.sitepoint.com/javascript/DOMImplementation
There are few references to compatibility as well here, but it's fairly well supported.
In essence, to create a new document to manipulate, you want to create a new Doctype object (if you're going to output some standards based stuff) and then create the new Document using the newly created Doctype variable.
There are multiple options to be put into both the doctype and the document, but if you're creating an HTML5 document, it seems you want to leave most of them as blank strings.
Example (New HTML5 DOM Document):
var doctype = document.implementation.createDocumentType( 'html', '', '');
var dom = document.implementation.createDocument('', 'html', doctype);
The new Document now looks like this:
<!DOCTYPE html>
<html>
</html>
Example (New XHTML DOM Document):
var doctype = document.implementation.createDocumentType(
'html',
'-//W3C//DTD XHTML 1.0 Strict//EN',
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'
);
var dom = document.implementation.createDocument(
'http://www.w3.org/1999/xhtml',
'html',
doctype
);
So it's up to you to populate the rest of it. You could do this as simply as changing
dom.documentElement.innerHTML = '<head></head><body></body>';
Or go with the more rigorous:
var head = dom.createElement( 'head' );
var body = dom.createElement( 'body' );
dom.documentElement.appendChild(head);
dom.documentElement.appendChild(body);
All yours.
createDocumentFragment may help you.
https://developer.mozilla.org/En/DOM/DocumentFragment
Browsers always create document by themselves with empty page (about:blank).
Maybe, in Chrome application there're some functions available (like XUL in FF), but there's no such function in ordinary javascript.
Solution - works with all browsers since IE 4.0
var doc = (new DOMParser).parseFromString(htmlString, "text/html");
Or
var doc = document.implementation.createHTMLDocument();
1)Example: new DOMParser()
var htmlString = `<body><header class="text-1">Hello World</header><div id="table"><!--TABLE HERE--></div></body>`;
var insertTableString = `<table class="table"><thead><tr><th>th cell</th></tr></thead><tbody><tr><td>td cell</td></tr></tbody></table>`;
var doc = (new DOMParser).parseFromString(htmlString, "text/html");
doc.getElementById('table').insertAdjacentHTML('beforeend', tableString);
console.log(doc);
2)Example: createHTMLDocument()
var htmlString = `<body><header class="text-1">Hello World</header><div id="table"><!--TABLE HERE--></div></body>`;
var insertTableString = `<table class="table"><thead><tr><th>th cell</th></tr></thead><tbody><tr><td>td cell</td></tr></tbody></table>`;
var doc = document.implementation.createHTMLDocument();
doc.open();
doc.write(htmlString);
doc.getElementById('table').insertAdjacentHTML('beforeend', tableString);
doc.close();
console.log(doc);
I tried some of the other ways here but there where issues when creating script elements such as Chrome refusing to load the actual .js file pointed to by the src attribute. Below is what works best for me.
It's up to 3x faster than jQuery and 3.5x faster than using DOMParser, but 2x slower than programmatically creating the element.
https://www.measurethat.net/Benchmarks/Show/2149/0
Object.defineProperty(HTMLElement, 'From', {
enumerable: false,
value: (function (document) {
//https://www.measurethat.net/Benchmarks/Show/2149/0/element-creation-speed
var rgx = /(\S+)=(["'])(.*?)(?:\2)|(\w+)/g;
return function CreateElementFromHTML(html) {
html = html.trim();
var bodystart = html.indexOf('>') + 1, bodyend = html.lastIndexOf('<');
var elemStart = html.substr(0, bodystart);
var innerHTML = html.substr(bodystart, bodyend - bodystart);
rgx.lastIndex = 0;
var elem = document.createElement(rgx.exec(elemStart)[4]);
var match; while ((match = rgx.exec(elemStart))) {
if (match[1] === undefined) {
elem.setAttribute(match[4], "");
} else {
elem.setAttribute(match[1], match[3]);
}
}
elem.innerHTML = innerHTML;
return elem;
};
}(window.document))
});
Usage Examples:
HTMLElement.From(`<div id='elem with quotes' title='Here is "double quotes" in single quotes' data-alt="Here is 'single quotes' in double quotes"><span /></div>`);
HTMLElement.From(`<link id="reddit_css" type="text/css" rel="stylesheet" async href="https://localhost/.js/sites/reddit/zinject.reddit.css">`);
HTMLElement.From(`<script id="reddit_js" type="text/javascript" async defer src="https://localhost/.js/sites/reddit/zinject.reddit.js"></script>`);
HTMLElement.From(`<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">`);
HTMLElement.From(`<div id='Sidebar' class='sidebar' display=""><div class='sb-handle'></div><div class='sb-track'></div></div>`);
I was able to do this by writing the html string on an iframe
const html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
</body>
</html>`
const iframe = document.createElement('iframe')
iframe.contentDocument.open()
iframe.contentDocument.write(html)
iframe.contentDocument.close()
iframe.addEventListener('load', () => {
myDocumentObject = iframe.contentDocument
})
fetch("index.html", { // or any valid URL
method: "get"
}).then(function(e) {
return e.text().then(e => {
var t = document.implementation.createHTMLDocument("");
t.open();
t.write(e);
t.close();
return t;
});
}).then(e => {
// e will contain the document fetched and parsed.
console.log(e);
});
The DOM element has the property innerHTML that allows to change completely its contents. So you can create a container and fill it with a new HTML content.
function createElementFromStr(htmlContent) {
var wrapperElm = document.createElement("div");
wrapperElm.innerHTML = htmlContent; // Ex: "<p id='example'>HTML string</p>"
console.assert(wrapperElm.children.length == 1); //Only one child at first level.
return wrapperElm.children[0];
}
*
I know it is an old question, but i hope to help someone else.
var dom = '<html><head>....</head><body>...</body></html>';
document.write(dom);
document.close();
HTML would like this:
<html>
<head></head>
<body>
<div id="toolbar_wrapper"></div>
</body>
</html>
JS would look like this:
var data = '<div class="toolbar">'+
'<button type="button" class="new">New</button>'+
'<button type="button" class="upload">Upload</button>'+
'<button type="button" class="undo disabled">Undo</button>'+
'<button type="button" class="redo disabled">Redo</button>'+
'<button type="button" class="save disabled">Save</button>'+
'</div>';
document.getElementById("toolbar_wrapper").innerHTML = data;

Categories

Resources