Error in javascript recursive function - javascript

I have the following piece of code (for the present case it may be considered as a function to remove the attributes from a valid html string fed as input):
function parse(htmlStr)
{
console.log(htmlStr);
result+="<"+htmlStr.tagName.toLowerCase()+">";
var nodes=htmlStr.childNodes;
for(i=0;i<nodes.length;i++) {
var node=nodes[i];
if(node.nodeType==3) {
var text=$.trim(node.nodeValue);
if(text!=="") {
result+=text;
}
}
else if(node.nodeType==1) {
result+=parse(node);
}
}
result+="</"+htmlStr.tagName.toLowerCase()+">";
return result;
}
But it is not working as expected. For example, in the following case when I feed it the following html as input:
<div id="t2">
Hi I am
<b>
Test
</b>
</div>
it returns <div>Hi I am<div>Hi I am<b>Test</b></div>.
Also the page crashes if some large input is given to the function.
NOTE: I know there are better implementations of removing attributes from a string using jQuery, but I need to work with the above function here & also the complete code is not for removing attributes, the above is just a shortened part of the code

There is something wrong with your result variable. It is undefined and global. In each recursion you would append the same string to itself, which also makes it crashing for huge inputs. (I can't reproduce anything, it crashes right away with a Undefined variable Error)
BTW: Your argument is no htmlStr, it is a domNode. And you're not parsing anything. Please don't use wrong self-documenting variable names.
Corrected version:
function serialize(domElement) {
var tagname = domElement.tagName.toLowerCase();
var result = "<"+tagname+">";
// ^^^ ^ not a +=
var children = domElement.childNodes;
for (var i=0; i<children.length ;i++) {
// ^^^ was also missing
if (children[i].nodeType == 3) {
result += children[i].data;
} else if (children[i].nodeType == 1) {
result += serialize(children[i]);
// ^^ add a child's result here
}
}
result += "</"+tagname+">";
return result;
}
I would not use trim(), that would produce <div>Hi<b>I</b>am</div> from <div>Hi <b>I</b> am</div>. You might do something like .replace(/\s+/g, " ").

This result+=parse(node); -> In you case you shouldn't merge the result inside recursion like that..
What happens is the return result from <b> recursion call appends the existing result with returned result. Where the existing result is <div>Hi I am and the returned result is <div>Hi I am<b>Test and so at the end of recursion you have <div>Hi I am<div>Hi I am<b>Test.
var result = '';
function parse(htmlStr) {
result += "<" + htmlStr.tagName.toLowerCase() + ">";
var nodes = htmlStr.childNodes;
for (i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (node.nodeType == 3) {
var text = $.trim(node.nodeValue);
if (text !== "") {
result += text;
}
} else if (node.nodeType == 1) {
parse(node);
}
}
console.log(result);
result += "</" + htmlStr.tagName.toLowerCase() + ">";
return result;
}
Fixed fiddle: http://jsfiddle.net/FBnYT/

Change
result+="<"+htmlStr.tagName.toLowerCase()+">";
to:
var result="<"+htmlStr.tagName.toLowerCase()+">";
WOrks fine in demo: http://jsfiddle.net/qtuUA/

The crash occurs because the loop control variable is not locally scoped. So in addition to the other recommended changes:
for (var i = 0; i < nodes.length; i++)
...

Related

Inefficient/ugly replacement function

I wrote a function that is supposed to replace code in between of two delimiters with the value, it returns (The string I'm applying this to is the .outerHTML of a HTML-Object).
This will be used similar to how it is used in e.g. Vue.js or Angular.
It looks like this:
static elemSyntaxParse(elem) {
let elem = _elem.outerHTML;
if (elem.includes("{{") || elem.includes("}}")) {
let out = "";
if ((elem.match(/{{/g) || []).length === (elem.match(/}}/g) || []).length) {
let occurs = elem.split("{{"),
key,
temp;
for (let i = 1; i < occurs.length; i++) {
if (occurs[i].includes("}}")) {
key = occurs[i].substring(0, occurs[i].indexOf("}}"));
temp = eval(key) + occurs[i].substring(occurs[i].indexOf("}}") + 2);
out += temp;
} else {
ModularCore.err("Insert-Delimiters \"{{\" and \"}}\" do not match.");
break;
return elem;
}
}
return occurs[0] + out;
} else {
ModularCore.err("Insert-Delimiters \"{{\" and \"}}\" do not match.");
return elem;
}
}
return elem;
}
(The function is inside of a class and refers to some external functions.)
Example use:
<body>
<p id="test">{{ Test }}</p>
<script>
let Test = 27;
document.getElementById("test").outerHTML = elemSyntaxParse(document.getElementById("test"));
</script>
</body>
Returns this string:
<p id="test">27</p>
It works but it is rather ugly and kinda slow.
How would I go about cleaning this up a bit? I am open to ES6.
PS: I now "eval() is evil" but this is the only occurrence in my code and it is (as far as i know) not replaceable in this situation.
Thanks!
I think you can omit a few checks and end up at:
const text = elem.outerHTML.split("{{");
let result = text.shift();
for(const part of text) {
const [key, rest, overflow] = part.split("}}");
if(!key || rest == undefined || overflow) {
ModularCore.err("Insert-Delimiters \"{{\" and \"}}\" do not match.");
return elem.outerHTML;
}
result += eval(key) + rest;
}
return result;
Invert the testing logic to get rid of nesting and else clauses
if (! elem.includes("{{") || !elem.includes("}}")) {
ModularCore.err("Insert-Delimiters \"{{\" and \"}}\" do not match.");
return elem;
}
// original loop code here
Don't double check - as #Bergi comment says.
test for return values indicating "not found, etc"
// if is removed. next line...
let occurs = elem.split("{{"), key, temp;
// if the separator is not in the string,
// it returns a one-element array with the original string in it.
if(occurs[0] === elem) return "no substring found";
The above should eliminate 2 nesting levels. You can then do a similar thing in that inner for loop.
Simplify compound logic.
!a || !b is equivalent to !(a && b). This is De Morgan's law

After continue statement i dont see any console printing

I am new to js.After continue statement i dont see any console printing.
I thought continue will move to next number or something else will happencan you guys tell me why its not working.I am providing my code below.
var String = "paa"
//var String = "pak"
var splittedString = String.split()
console.log("outside outer loop");
for(i=0; i<splittedString.length; i++) {
var c = splittedString[i];
console.log("outside inner loop");
for(j=0; j<splittedString.length; j++) {
console.log("inside inner loop");
if( i === j)
{
console.log("inside if condition");
continue;
}
console.log("c ---------->" + c);
console.log("splittedString[j]---------->" + splittedString[j]);
//console.log("inside inner loop");
if( c === splittedString[j] ) {
//else if( c === splittedString[j] ) {
console.log("inside comparison");
console.log("not unique string");
break;
}
else {
console.log("its an unique string")
}
}
}
Your code works "fine". The reason there is no console log is, that you have only one iteration. In that iteration both i and j equalst to 0.
Try console.log(splittedString). You will get your string in an array. I guess what you wanted to do was var splittedString = String.split('') (split it with "empty" character").
Although looping through array of characters is equivalent to looping through string itself. No need to split the string. You can do splittedString = String and the result in this case will be the same as if you split it.
And as others mentioned try to avoid using variable names that could incline variable type (in your case String). Even if the code works, it is harder to read.

Get string position of current element?

Any way to get the ".indexOf()" string position of the start and end of a jQuery-selected element relative to the parent?
Example:
<ul><li>Coffee</li><li>Coffee</li><li>Coffee</li></ul>
After selecting the 2nd <li> item via jquery, the function would return 15 (or 29 with an argument switch).
I am thinking about a verbose javascript workaround but it gets nasty and unreliable very fast. Also it might not work if there are other tag types or text mixed in between. Bad. Not to take seriously.
//http://stackoverflow.com/a/14482123
function nthIndex(str, pat, n){
var L= str.length, i= -1;
while(n-- && i++<L){
i= str.indexOf(pat, i);
}
return i;
}
function strpos(jelem, tag) {
var nth = jelem.index();
var contents = jelem.parent().text();
var pos = nthIndex(contents, tag, nth+1);
if (tag.indexOf('/') == 1) {
return pos+4; //because ending of '</li>' is +4 (very bad)
} else {
return pos;
}
}
strpos(jelem, '<li>'); //or '</li>' to return the ending position
Like adeneo said, this seems like an XY problem, but you really need a parser and I doubt you feel like writing your own.
One possibility would be to use the native .outerHTML and sum the .length of all the .previousSibling nodes, and then the .length of current one if you need the end position.
However, you should know that this has nothing to do with the original HTML. These will be HTML strings rendered by the browser after analysing the current state of the DOM.
Here's a quick example that receives an element and returns the start (or optionally the end) position:
function getPos(origElem, doEnding) {
var sum = 0;
var elem = origElem;
while ((elem = elem.previousSibling)) {
sum += elem.outerHTML.length;
}
if (doEnding) {
sum += origElem.outerHTML.length-1;
}
return sum;
}
var li = document.querySelector("li:nth-child(2)");
var p = document.querySelector("pre");
p.textContent = "Position is: " + getPos(li);
p.textContent += "\nEnd position is: " + getPos(li, true);
<ul><li>Coffee</li><li>Coffee</li><li>Coffee</li></ul>
<pre></pre>

jquery indexOf replacement

Here I have a hidden input field
<input type="hidden" value="php,php mysql" id="tags"/>
and I also have a normal input box which users could add their new tags, now I want to check if the new tag which user wants to add is already added for him or not, if it's already added, alert('already there');
here is my code:
var already_added = $('#tags_final').val().toLowerCase();
new_tag = new_tag.toLowerCase();
if (already_added.indexOf(new_tag) < 0){
// add it, everything is OK
}else{
alert('already there');
}
the above works just fine for normal values, for example now if i try to add "php", this is gonna alert('already there'), the problem is, if I add "mysql", this also sends the alert since it finds it in "php mysql", but "mysql" is another tag and it needs to be added. what's solutions come to mind for this?
thanks for your help
I would think you'd want to break this up into separate pieces and perform a full text comparison on the actual tag itself.
var tags = $('#tags_final').val().toLowerCase().split(',');
new_tag = new_tag.toLowerCase();
if ($.inArray(new_tag, tags) < 0) {
// add it
} else {
alert('already there');
}
var tags = $('#tags_final').val().toLowerCase().split(',');
new_tag = new_tag.toLowerCase();
if (tags.indexOf(new_tag) < 0) {
// add it
} else {
alert('already there');
}
Edit: Courtesy of #nybbler, this would be more accurate using the jquery inArray method as linked in his comment.
I think using indexOf directly on the string is faster than either a regex or splitting into an array (even more so when relying on $.inArray).
Simply wrap your already_added string of tags with commas, then do the same for the tag when searching for it with indexOf:
var already_added = ',' + $('#tags_final').val().toLowerCase() + ',';
new_tag = new_tag.toLowerCase();
if (already_added.indexOf(',' + new_tag + ',') < 0) {
// add it
} else {
alert('already there');
}
I got this idea from a similar trick used by jQuery.
Save the tags between braces [php][php mysql]
and search for it
var already_added = $('#tags_final').val().toLowerCase();
new_tag = new_tag.toLowerCase();
if (already_added.indexOf("["+new_tag+"]") < 0){
// add it, everything is OK
}else{
alert('already there');
}
You can use a function like this to look for a key in a comma separated string of keys:
function contains(keys, key) {
var i = 0, k = key.length;
for (var i = 0;(i = keys.indexOf(key, i)) != -1; i += k) {
if ((i == 0 || keys.charAt(i - 1) == ',') && (i + k == keys.length || keys.charAt(i + k) == ',')) {
return true;
}
}
return false;
}
Demo: http://jsfiddle.net/Guffa/UHH9V/

Converting html to textual representation with preserved whitespace meaning of tags -- how?

Consider such html piece:
<p>foo</p><p>bar</p>
If you run (for example) jQuery text for it you will get "foobar" -- so it is raw text actually, not textual representation.
I am looking for some ready to use library to get textual representation, in this case it should be -- "foo\nbar". Or clever hints how to make this as easy as possible ;-).
NOTE: I am not looking for beautiful output text, but just preserved meaning of whitespaces, so for:
<tr><td>foo</td><td>bar</td></tr>
<tr><td>1</td><td>2</td></tr>
I will be happy with
foo bar
1 2
it does NOT have to be:
foo bar
1 2
(but of course no harm done).
Have you looked at the innerText or textContent properties?
function getText(element){
var s = "";
if(element.innerText){
s = element.innerText;
}else if(element.textContent){
s = element.textContent;
}
return s;
}
Example
Adds a PRE tag to the body and appends the body text.
document.body.appendChild(
document.createElement('pre')
)
.appendChild(
document.createTextNode(
getText(document.body)
)
);
Edit
Does using a range work with firefox?
var r = document.createRange();
r.selectNode(document.body);
console.log(r.toString());
Edit
It looks like you're stuck with a parsing function like this then.
var parse = function(element){
var s = "";
for(var i = 0; i < element.childNodes.length; i++){
if(/^(iframe|noscript|script|style)$/i.test(element.childNodes[i].nodeName)){
continue;
}else if(/^(tr|br|p|hr)$/i.test(element.childNodes[i].nodeName)){
s+='\n';
}else if(/^(td|th)$/.test(element.childNodes[i].nodeName)){
s+='\t';
}
if(element.childNodes[i].nodeType == 3){
s+=element.childNodes[i].nodeValue.replace(/[\r\n]+/, "");
}else{
s+=parse(element.childNodes[i]);
}
}
return s;
}
console.log(parse(document.body));
I started writing my own function probably at the same time as Zapthedingbat, so just for the record:
var NodeTypeEnum = { Element : 1,Attribute : 2, Text: 3, Comment :8,Document :9};
function doTextualRepresentation(elem)
{
if (elem.nodeType==NodeTypeEnum.Text)
return elem.nodeValue;
else if (elem.nodeType==NodeTypeEnum.Element || elem.nodeType==NodeTypeEnum.Document)
{
var s = "";
var child = elem.firstChild;
while (child!=null)
{
s += doTextualRepresentation(child);
child = child.nextSibling;
}
if (['P','DIV','TABLE','TR','BR','HR'].indexOf(elem.tagName)>-1)
s = "\n"+s+"\n";
else if (['TD','TR'].indexOf(elem.tagName)>-1)
s = "\t"+s+"\t";
return s;
}
return "";
}
function TextualRepresentation(elem)
{
return doTextualRepresentation(elem).replace(/\n[\s]+/g,"\n").replace(/\t{2,}/g,"\t");
}
One thing I am surprised with -- I couldn't get
for (var child in elem.childNodes)
working, and it is a pity, because I spend most time in C# and I like this syntax, theoretically it should work in JS, but it doesn't.

Categories