I'm using jQuery to parse some XML, like so:
function enumOptions(xml) {
$(xml).find("animal").each(function(){
alert($(this).text());
});
}
enumOptions("<root><animal>cow</animal><animal>squirrel</animal></root>");
This works great. However if I try and look for nodes called "option" then it doesn't work:
function enumOptions(xml) {
$(xml).find("option").each(function(){
alert($(this).text());
});
}
enumOptions("<root><option>cow</option><option>squirrel</option></root>");
There's no error, just nothing gets alerted, as if the find isn't finding anything. It only does it for nodes called option everything else I tested works ok!
I'm using the current version of jQuery - 1.4.2.
Anyone any idea?
TIA.
bg
Update
jQuery has this method built-in now. You can use
$.parseXML("..")
to construct the XML DOM from a string.
jQuery relies on the HTML DOM using innerHTML to parse the document which can have unreliable results when tag names collide with those in HTML.
Instead, you can use a proper XML parser to first parse the document, and then use jQuery for querying. The method below will parse a valid XML document in a cross-browser fashion:
// http://www.w3schools.com/dom/dom_parser.asp
function parseXML(text) {
var doc;
if(window.DOMParser) {
var parser = new DOMParser();
doc = parser.parseFromString(text, "text/xml");
}
else if(window.ActiveXObject) {
doc = new ActiveXObject("Microsoft.XMLDOM");
doc.async = "false";
doc.loadXML(text);
}
else {
throw new Error("Cannot parse XML");
}
return doc;
}
Once the XML DOM is constructed, jQuery can be used as normal - http://jsfiddle.net/Rz7Uv/
var text = "<root><option>cow</option><option>squirrel</option></root>";
var xml = parseXML(text);
$(xml).find("option"); // selects <option>cow</option>, <option>squirrel</option>
This is probably some special handling for the HTML <option> element, but I can't find that in the source.
On line 4448 of the unminified source for 1.4.2 is the culprit:
// ( div = a div node )
// ( elem = the xml you've passed to it )
div.innerHTML = wrap[1] + elem + wrap[2];
Consider this code:
var d = document.createElement('div');
d.innerHTML = "<foo><option>bar</option><b>blah</b></foo>";
alert(d.innerHTML); // <foo>bar<b>blah</b></foo>
// tested on Firefox 3.6
So, don't ask me why exactly, but it looks like something in the way the DOM handles it, not necessarily jQuery's fault.
Perhaps just use a different node name?
Related
Do you know any way to convert HTML tags to WordML only using JavaScript. I need to get the content of a DOM element and convert what is inside to WordML.
Looking on npm there doesn't seem to be a library for this already.
So I think you're going to have to make your own. That being said, WordML is just a particular flavor of XML, right? This is the WordML you are referring to?
So to get the content of of a DOM element is pretty easy, you can do that with jQuery.
var ele = $('#wordml-element');
From there you will now want to convert it into WordML compatible XML. You could try using the xml library on npm for this.
So you will be transforming tree structured DOM elements into tree structured XML elements. The recommended pattern for doing this is known as the Visitor Pattern.
From there you will be left with an XML structure which you can further manipulate using the same pattern. At the end you will convert the XML structure into a string and that is what you will save to a file.
Now since I don't really know the structure of the HTML you are trying to convert into WordML I can only give you a very general code solution to the problem, which may look something like this:
var xml = require('xml')
function onTransformButtonClick() {
var options = {} // see documentation
var ele = $('#wordml-element')[0]
var wordml = transformElement(ele)
var text = xml(wordml, options);
fileSave(text);
}
function transformElement(ele) {
switch(ele.tagName) { // You could use attributes or whatever
case 'word-document':
return transformDocument(ele);
case 'word-body':
return transformBody(ele);
case 'word-p':
return transformParagraph(ele);
case 'word-r':
return transformRun(ele);
case 'word-text':
return transformText(ele);
}
}
function transformDocument(ele) {
var wordDocument = xml.element({...})
ele.childNodes.forEach(function (child) {
wordDocument.push(transformElement(child))
})
return [wordDocument]
}
function transformBody(ele) {
// create new element via xml library...
}
function transformParagraph(ele) {
// create new element via xml library...
}
function transformRun(ele) {
// create new element via xml library...
}
function transformText(ele) {
// create new element via xml library...
}
The specific implementations of which I will leave up to you since I don't know enough details to give you a more detailed answer.
I am trying to alter a DOM structure in node.js. I can load the XML string and alter it with the native methods in xmldom (https://github.com/jindw/xmldom), but when I load XPath (https://github.com/goto100/xpath) and try to alter the DOM via that selector, it does not work.
Is there another way to do this out there? The requirements are:
Must work both in the browser and server side (pure js?)
Cannot use eval or other code execution stuff (for security)
Example code to show how I am trying today below, maybe I simply miss something basic?
var xpath = require('xpath'),
dom = require('xmldom').DOMParser;
var xml = '<!DOCTYPE html><html><head><title>blah</title></head><body id="test">blubb</body></html>';
var doc = new dom().parseFromString(xml);
var bodyByXpath = xpath.select('//*[#id = "test"]', doc);
var bodyById = doc.getElementById('test');
var h1 = doc.createElement('h1').appendChild(doc.createTextNode('title'));
// Works fine :)
bodyById.appendChild(h1);
// Does not work :(
bodyByXpath.appendChild(h1);
console.log(doc.toString());
bodyByXpath is not a single node. The fourth parameter to select, if true, will tell it to only return the first node; otherwise, it's a list.
As aredridel states, .select() will return an array by default when you are selecting nodes. So you would need to obtain your node from that array.
You can also use .select1() if you only want to select a single node:
var bodyByXpath = xpath.select1('//*[#id = "test"]', doc);
I'm doing a bit of advanced work in KnockoutJS, whereby I generate some html outside of the KO process, apply bindings to them, and then insert them in my page.
The problem is housing the new html. My html is a couple of table rows, and when I do
var div = document.createElement('div');
div.innerHTML = template(viewModel);
the div strips out all the table content (my tr and td tags), presumably since divs can't contain table rows.
My cheesy workaround for the moment is below: use a tbody. But I'd like something a bit more generalized. I thought to use a document fragment, but that doesn't seem to have an innerHTML property to set.
What's the preferred way to handle this?
var div = document.createElement('tbody');
div.innerHTML = template(viewModel);
ko.applyBindingsToDescendants(bindingContext, div);
$(element).after($(div).contents());
As a workaround you could fetch the type of the parent node, create an empty detached new node of that type to house your contents, and later fetch the items from there.
It might look something like this, assuming you don't mind inserting the content before element, rather than after it:
var container = document.createElement(element.parentNode.tagName),
frag = document.createDocumentFragment();
container.innerHTML = template(viewModel);
ko.applyBindingsToDescendants(bindingContext, container);
while (container.childNodes.length){
frag.appendChild(container.childNodes[0]);
}
element.parentNode.insertBefore(frag, element);
But it'd be better to figure out why your contents are stripped to begin with.
I think you will get this problem only with table parts.
Hence you can do this:
var templ = template(viewModel);
var newElement = document.createElement(
$(templ).is("tr, tbody, thead") ? 'table' : 'div'
);
newElement.innerHTML(templ);
Nit's answer works but its still a bit of a hack, I would use a custom template source instead.
First you need to create a engine that uses strings as source, like
var stringTemplateSource = function (template) {
this.template = template;
};
stringTemplateSource.prototype.text = function () {
return this.template;
};
var stringTemplateEngine = new ko.nativeTemplateEngine();
stringTemplateEngine.makeTemplateSource = function (template) {
return new stringTemplateSource(template);
};
Then you can use it from a custom binding like
ko.renderTemplate(template, bindingContext.createChildContext(data), { templateEngine: stringTemplateEngine }, element, "replaceChildren");
Were template is a string containing the actual html
I'm using jQuery to manipulate DOM in my project. I've got class method, that works like this:
<!-- language: lang-js -->
var template = this._Template.split('{0}');
var content = template[0] + this._Content + template[1];
if (!this._BlockNode) {
this._BlockNode = $(content);
this._ParentNode.append(this._BlockNode);
}
else {
this._BlockNode.replaceWith(content);
}
Everything is ok on the first call of this method, because it creates node and appends it to parent node. The second call (using replaceWith() method) works ok too. But after it property this._BlockNode[0].parentNode is null. So when I call it third time and replaceWith() works with new _.BlockNode without .parentNode property it does not replace content of node because of this check: if ( !isDisconnected( this[0] ) ) { //line 5910 in jQuery 1.8.3.
How to deal with it?
You need to ensure that _BlockNode always points to the current version of the content.
When you call replaceWith you correctly update the DOM structure, but fail to update the contents of your object. The original _BlockNode ends up orphaned, and all subsequent replaceWith calls work on that node and not on the newer content.
Try this:
var template = this._Template.split('{0}');
var $content = $(template[0] + this._Content + template[1]);
if (!this._BlockNode) {
this._ParentNode.append($content);
} else {
this._BlockNode.replaceWith($content);
}
this._BlockNode = $content;
It may be preferable to hold a native DOM element in _BlockNode rather than a jQuery object.
Problem
I'm creating a document with javascript and I'd like to execute XPath queries on this document.
I've tried this in safari/chrome
I've read up on createDocument / xpath searches and it really seems like this code should work
At this point it seems like it may be a webkit bug
My requirements:
I can use innerHTML() to setup the document
I can execute xpath searches w tagnames
The code:
If you copy/paste the following into the webkit inspector, you should be able to repro.
function search(query, root) {
var result = null;
result = document.evaluate(query, root, null, 7,null);
var nodes = [];
var node_count = result.snapshotLength;
for(var i = 0; i < node_count; i++) {
nodes.push(result.snapshotItem(i));
}
return nodes;
}
x = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', 'HTML');
body = x.createElement('body');
body.innerHTML = "<span class='mything'><a></a></span>";
xdoc = x.documentElement; //html tag
xdoc.appendChild(body);
console.log(search(".", xdoc)); // --> [<html>…</html>]
console.log(search("/*", xdoc)); // --> [<html>…</html>]
console.log(search("/html", xdoc)); // --> []
Best Guess
So I can definitely search using XPath, but I cannot search using tagnames. Is there something silly I'm missing about the namespace?
Have you tried:
console.log(search("//html", xdoc));
I'm not familiar with Safari specifically, but the problem might be that Safari is adding another node above HTML or something. If this was the case, the first two queries might be showing you that node plus it's children, which would make it look like they're working properly, while the third query would fail because there wouldn't be a root=>HTML node.
Just a thought.