So I have some xml....
<parentnode>
<childnode>
<babynode id="1">
<parameter>goes here</parameter>
<babynode />
<childnode />
<parentnode />
<parentnode>
<childnode>
<babynode id="2">
<parameter>goes here</parameter>
<babynode />
<childnode />
<parentnode />
...and, using e4x, I want to delete to delete a parent node where a babynode id is equal to something. For example where baby node id is 2, I want to delete the whole node starting from <parentnode>.
I know I can do var xml = the xml and then do delete xml.parentnode but I am not sure how I specify which parentnode to delete.
I am sure this is probably simple, and I am probably being silly, but could anyone point me in the right direction please?
Thanks
First off, if your environment is the browser or even a Firefox extension, I'd advise against continuing with E4X as they have deprecated it.
That being said, here is what I could find in testing around about what works (very little) and what does not, in attempting to do what you are trying to do.
As useful as it would be, and as well as it seems it would fit into the filter/accessor syntax, deleting a parent (or grand-parent in your case) does not seem to work (at least when I try with Mozilla's E4X engine--btw, as I recall, the function:: used below might only be supported in Spidermonkey).
var a = <a>
<b>
<c/>
</b>
</a>;
delete a.b.c.parent(); // Removing parent() will delete <c/>
alert(a); // <b/> is not deleted
Thus naturally, in taking your example:
var xml = <><parentnode>
<childnode>
<babynode id="1">
<parameter>goes here</parameter>
</babynode>
</childnode>
</parentnode>
<parentnode>
<childnode>
<babynode id="2">
<parameter>goes here</parameter>
</babynode>
</childnode>
</parentnode></>;
...while this (using the .. descendant selector):
alert(xml..*.(function::attribute('id') == "2")[0].parent().parent()); // I'm also not sure why the formatting of the attribute cannot be obtained by `xml..*.(#id == "2")` without giving an error since id is not a reserved word, but anyways...
...does get the <parentnode> you want, deleting it like in the following does not work:
delete xml..*.(function::attribute('id') == "2")[0].parent().parent();
...even while this:
delete xml..*.(function::attribute('id') == "2")[0];
...will at least delete the <babynode> part you want to delete.
Theoretically, I would think that the following (i.e., without the * mark which selects the filtered elements of descendants instead of just filtering the ancestor) should work (or at least it would be nice if it did!):
delete xml..(function::attribute('id') == "2")[0];
...yet it does not.
Even accessing an element in this manner (of not grabbing a descedant) does not seem to work:
alert(xml..(function::attribute('id') == "2")[0]);
And even if we avoid using an XMLList (<></> being the short-hand syntax used above if you were not familiar with it) and wrap the XML in some named element, say <container>, deleting or accessing still does not work:
delete xml.container..(function::attribute('id') == "2")[0];
You could take a look at http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-357.pdf to see if your reading should allow this, but in any case it does not work, at least as I've tried it.
So, as far as I can see, the only solution would be to iterate through the elements and track where you are manually rather than using filters.
Related
I use ReactJs function renderToStaticMarkup to create HTML Markup. The markup is on another place in the App set as the innerHTML property to other DOM node. Since I would like to prevent images and iframes from re-rednering, I would like to make comparison, if the current innerHTML is the different from the one it should be set to.
if (!domNode.innerHTML !== newMarkup);
domNode.innerHTML = newMarkup;
}
For some reason, Reacts renderToStaticMarkup creates HTML Markup for images as:
<img src="https://placebeard.it/200x150" alt="" class="content-media-image-linked"/>
but the DOM innerHTML has a value of
<img src="https://placebeard.it/200x150" alt="" class="content-media-image-linked">
So basically the difference IS in the trailing / (but this does not need to be the rule of thumb)
I wonder what would be the most efficient/fast way to determine, whether those two DOMStrings represent the same DOM Node.
1. String Comparison
It would be probably enough to replace/remove all occurrences of />
2. Parsing/converting to DOMNodes
This is more safe method, but also much more expensive. I would have to use something like document.createRange().createContextualFragment (see this post) and than use the isEqualNode method.
Has aonyone some better sugeestion ?
As I think you know, the / in /> at the end of a tag has no meaning whatsoever in HTML (it does in XHTML and JSX), so my temptation would be
Change the /> to > and compare; if they match, they're the same.
If they don't match, parse it and use isEqualNode
The first gives you the quick win in what I assume will be the majority case. The second works the slower but more robust way, allowing attributes to be in a different order without introducing a difference (unless it makes a difference), etc.
When replacing the /> with >, be sure of course to only do so in the right place, which may or may not be tricky depending on the strings you're dealing with (e.g., to they contain nested HTML, etc.). ("Tricky" as in "you can't just use a simple regular expression" if the HTML isn't of a single element like your img example.)
A quick fix to my issue was performing sanitization of the HTML Markup produced by the _ renderToStaticMarkup_ call. In my case the markup is generated only occasionally, but the Dom-Node equality check very often, so I went with just plain string quality-check.
I tried multiple libraries to achieve that:
sanitize-html lokked promissing, but was not removing the trailing /
html-minifier worked, but I had issues using it with es6 imports
I ended up using dompurify
Ok this one seems pretty simple (and it probably is). I am trying to use jQuery's replace with method but I don't feel like putting all of the html that will be replacing the html on the page into the method itself (its like 60 lines of HTML). So I want to put the html that will be the replacement in a variable named qOneSmall like so
var qOneSmall = qOneSmall.html('..........all the html');
but when I try this I get this error back
Uncaught SyntaxError: Unexpected token ILLEGAL
I don't see any reserved words in there..? Any help would be appreciated.
I think the solution is to only grab the element on the page you're interested in. You say you have like 60 lines. If you know exactly what you want to replace..place just that text in a div with an id='mySpecialText'. Then use jQuery to find and replace just that.
var replacementText = "....all the HTML";
$("#mySpecialText").text(replacementText);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="mySpecialText">Foo</div>
If you're only looking to replace text then jaj.laney's .text() approach can be used. However, that will not render the string as HTML.
The reason the way you're using .html() is likely illegal is that qSmallOne is not a JQuery object. The method cannot be performed on arbitrary variables. You can set the HTML string to a variable and pass that string to the .html() function like this:
var htmlstring = '<em>emphasis</em> and <strong>strong</strong>';
$('#target').html(htmlstring);
To see the difference between using .html() and .text() you can check out this short fiddle.
Edit after seeing the HTML
So there is a lot going on here. I'm just going to group these things into a list of issues
The HTML Strings
So I actually learned something here. Using the carriage return and tab keys in the HTML string is breaking the string. The illegal-ness is coming from the fact the string is never properly terminated since it thinks it ends at the first line. Strip out the white space in your strings and they're perfectly valid.
Variable Names
Minor thing, you've got a typo in qSmallOne. Be sure to check your spelling especially when working with these giant variables. A little diligence up front will save a bunch of headache later.
Selecting the Right Target
Your targets for the change in content are IDs that are in the strings in your variables and not in the actual DOM. While it looks like you're handling this, I found it rather confusing. I would use one containing element with a static ID and target that instead (that way you don't have to remember why you're handling multiple IDs for one container in the future).
Using replaceWith() and html()
.replaceWith() is used to replace an element with something else. This includes the element that is being targeted, so you need to be very aware of what you're wanting to replace. .html() may be a better way to go since it replaces the content within the target, not including the target itself.
I've made these updates and forked your fiddle here.
Today, I discovered something in Javascript that looked like "strange behavior" to me. Let's assume the following minimal example:
HTML:
<div id="test">
<span>1</span>
<span>2</span>
</div>
JS:
var div = document.getElementById('test');
var spans = div.getElementsByTagName('span');
div.removeChild(spans[0]);
div.removeChild(spans[1]);
(Fiddle: http://jsfiddle.net/SkYJg/)
Now, when running the script, I get an error:
TypeError: Argument 1 of Node.removeChild is not an object.
Looking closer, it turned out that spans[1] is null after the first one was removed. And indeed, the following code
var div = document.getElementById('test');
var spans = div.getElementsByTagName('span');
console.log(spans.length);
div.removeChild(spans[0]);
console.log(spans.length);
div.removeChild(spans[1]);
yields 2at the first log operation, but 1 the second time.
Now, it's pretty clear what happens here: after the first ?span? was removed from DOM, it's not part fo that HTMLCollection stored inside spans anymore either.
I always was under the impression, that the HTMLCollection-Object holds references to all objects that it contains. I didn't modify the collection anywhere after creating it. So I thought (but it was wrong, obviously) that the collection would behave like an array: references stay there until I delete/modify them manually.
I looked into the specification at MDN. And, indeed, richt at the top it says: HTMLCollections in the HTML DOM are live; they are automatically updated when the underlying document is changed.
The only way I could think of to prevent this is to loop over the collectino before doing anything with it, copying all references to an array, and use the array to access them afterwards. But that just looks so horribly bulky to me... is there a nicer solution? Like some way to make the collection static or to copy it without looping?
(in the minimal example I could just remove spans[0] twice, of course, but it isn't that simple in reality).
[Edit]: After seeing #Teemu's answer, I repeat: it's NOT that simple in my real code (that one is just too complex to show it here completely). I assure you, I really need random access to all elements that were inside that Collection, deleted or not.
A back-gate would be to use querySelectorAll() instead of getElementsByTagName(), it returns a non-live NodeList.
You're not using a "reference" when trying to remove the tag, just pointing the first or the second element of a collection. To use reference, you should create tags with ID and than point it by ID. The key of an Array is a third part, that's why it will be updated.
On the other hand, is a fact that JavaScript is objected-oriented sometimes, and other times it is just a script.
I have encountered a very strange bug in Firefox.
I have a javascript function in an external file that works perfectly on regular complexity websites. However I have been putting together a few demonstration examples and come across something odd.
With html formatted like this (in an editor):
<div><p>Q: Where's the rabbit?</p><p class="faq_answer">A: I don't know, honest</p></div>
The Javascript works as expected.
However when like this:
<div>
<p>Q: Where's the rabbit?</p>
<p class="faq_answer">A: I don't know, honest</p>
</div>
It fails at this line:
elementsList[i].parentNode.firstChild.appendChild(finalRender.cloneNode(true));
Why on Earth would formatting of html cause anything at all?
It is not a bug. The DOM has not only element nodes, but also text nodes [docs] (among others). In this example:
<div>
<p>Q: Where's the rabbit?</p>
you have at least two text nodes:
One between the <div> and the <p>, containing a line-break.
One text node inside the <p> element node, containing the text Where's the rabbit?.
Thus, if elementsList[i].parentNode refers to the <div> element,
elementsList[i].parentNode.firstChild
will refer to the first text node.
If you want to get the first element node, use
elementsList[i].parentNode.children[0]
Update: You mentioned Firefox 3.0, and indeed, the children property is not supported in this version.
Afaik the only solution to this is to loop over the children (or traversing them) and test whether it is a text node or not:
var firstChild = elementsList[i].parentNode.firstChild;
// a somehow shorthand loop
while(firstChild.nodeType !== 1 && (firstChild = firstChild.nextSibling));
if(firstChild) {
// exists and found
}
You might want to put this in an extra function:
function getFirstElementChild(element) {
var firstChild = null;
if(element.children) {
firstChild = element.children[0] || null;
}
else {
firstChild = element.firstChild;
while(firstChild.nodeType !== 1 && (firstChild = firstChild.nextSibling));
}
return firstChild;
}
You can (and should) also consider using a library that abstracts from all that, like jQuery.
It depends on what your code is actually doing, but if you run this method for every node, it would be something like:
$('.faq_answer').prev().append(finalRender.cloneNode(true));
(assuming the p element always comes before the .faq_answer element)
This is the whole code, you wouldn't have to loop over the elements anymore.
Because you have a text node between <div> and <p>.
As usual, the assumption of a browser bug is incorrect: this is, instead, a programmer bug!
Couldn't one achieve it by using ParentNode.children instead?
I have a real simple question that I can't seem to find an answer to.
I want to compress two XPath statements (that are getting attribute values). I learned about the | operator, hearing how it returns node sets.
var getdata = xmldoc.evaluate
(
'/foo/bar[#world=\''+hello+'\']/child::*/attribute::name
|/foo/bar[#world=\''hello+'\']/child::*/attribute::id',
xmldoc, null, XPathResult.ANY_TYPE, null
);
To anyone wondering, no I do not format my evaluation strings that way ... though, I sort of like it now that I typed it out. Anyways, this is how I tested it out.
alert(getItemData.iterateNext().childNodes[0].nodeValue);
That works! But it only returns the first one. While writing this, I just tried .length and made a break through ... it's only counting one item. Was I deceived about the concept of |? How can I get a set and then go through them?
XML document, as requested.
<?xml version="1.0" encoding="ISO-8859-1"?>
<foo>
<bar world="hello" id="1">
<subbar name="item1" id="2">
</subbar>
</bar>
<bar world="bye" id="3">
<subbar name="item2" id="4">
</subbar>
</bar>
</foo>
Edit: I am currently using a function that grabs the element rather than the attribute, but I would really like to know the other way. Unless what I am doing is the best way.
If JQuery is an option, it might be worth your while to check out their XML traversal library. A quick search pulled up an article here. I wrote up a very rough example of what the logic may look like after you import the xml document, which is explained in the link.
var hello = "foo";
$('bar[world=' + hello + '] > subbar').each(function () {
// You'd want to save these values somewhere else, obviously.
$(this).getAttribute(name);
$(this).getAttribute(id);
});
The key here is the XPathResult type you use.
I have implemented a working sample for the same.
Please refer the code at http://jsbin.com/eneso3/5/edit
Basically you have to use Iterator as result type sot hat we can iterate through them to get the text. Refer Xpath reference mentioned on the working code sample page.
Well your usage of the "pipe" is correct (http://www.tizag.com/xmlTutorial/xpathbar.php) so the only code that I can see might be off is a missing + in the second xpath command, but that might be pseudo code, so I would only count this as a half answer. As for the best practice, in my opinion I would grab the subbar element then grab it's attributes out where you need them an optimization like the one you've suggested obfuscates what data is being referenced. Seems too much of a mico-optimization, but this is just an opinion. Maybe you have a long list of attributes and you really are saving parsing time.