Removing '<and text within those>' from a string in javascript - javascript

I'm receiving an array of strings from a REST api call, and the strings have some html tags in them, for example:
Bard's opponents can also travel through his Magical Journey doorways. You can follow him, if you think it's safe.</li>
You can crush Bard's healing shrines just by walking over them. Don't let his allies take them without a fight.
Bard's ultimate, Tempered Fate, affects allies, enemies, monsters, and turrets alike. Sometimes it can be to your advantage to jump into it!</li>
Each line is a string, and two of them have an </li> tag at the end.
I tried writing a function that receives such an array, and returns a corrected array. Problem is, when I use it, the console in my website shows some weird errors with the strings from the array, and I've realized my function was the cause.
That is the function:
modal.removeBracketsFromArray = function (array) {
if (array == undefined)
return array;
function removeBracketsText(text) {
return text.replace(/<[^>]*>/g, '')
};
var newArray = [];
for (var i = 0; i < array.length; i++) {
newArray.push(removeBracketsText(array[i]));
}
return newArray;
};
It seems to do the work, but it somewhy messes up when using it in an ng-repeat attribute.
This is an use example:
<champion-ally-enemy-tips allytips="modalCtrl.removeBracketsFromArray(modalCtrl.champ.allytips)"
enemytips="modalCtrl.removeBracketsFromArray(modalCtrl.champ.enemytips)">
</champion-ally-enemy-tips>
which then moves to:
<ul>
<li ng-repeat="enemytip in enemytips"><h6>{{enemytip}}</h6></li>
</ul>
When I remove the method call (like so), it doesn't show an error, but the tags remain:
<champion-ally-enemy-tips allytips="modalCtrl.champ.allytips"
enemytips="modalCtrl.champ.enemytips">
</champion-ally-enemy-tips>
Is my function doing something weird without realizing it? thanks for helping
This is a pastebin of the errors I receive: LINK

Instead of messing with the array, how about creating a custom filter to strip out the HTML on display?
.filter('removeHTML', function() {
return function(input) {
return input.replace(/<[^>]*>/g, '');
}
})
Then change your display inside the ng-repeat to:
<h6>{{enemytip | removeHTML}}</h6>

Related

How to return multiple tokens with Jison lexer

I'm new to lexing and parsing so sorry if the title isn't clear enough.
Basically, I'm using Jison to parse some text and I am trying to get the lexer to comprehend indentation. Here's the bit in question:
(\r\n|\r|\n)+\s* %{
parser.indentCount = parser.indentCount || [0];
var indentation = yytext.replace(/^(\r\n|\r|\n)+/, '').length;
if (indentation > parser.indentCount[0]) {
parser.indentCount.unshift(indentation);
return 'INDENT';
}
var tokens = [];
while (indentation < parser.indentCount[0]) {
tokens.push('DEDENT');
parser.indentCount.shift();
}
if (tokens.length) {
return tokens;
}
if (!indentation.length) {
return 'NEWLINE';
}
%}
So far, almost all of that works as expected. The one problem is the line where I attempt to return an array of DEDENT tokens. It appears that Jison is just converting that array into a string which causes me to get a parse error like Expecting ........, got DEDENT,DEDENT.
What I'm hoping I can do to get around this is manually push some DEDENT tokens onto the stack. Maybe with a function like this.pushToken('DEDENT') or something along those lines. But the Jison documentation is not so great and I could use some help.
Any thoughts?
EDIT:
I seem to have been able to hack my way around this after looking at the generated parser code. Here's what seems to work...
if (tokens.length) {
var args = arguments;
tokens.slice(1).forEach(function () {
lexer.performAction.apply(this, args);
}.bind(this));
return 'DEDENT';
}
This tricks the lexer into performing another action using the exact same input for each DEDENT we have in the stack, thus allowing it to add in the proper dedents. However, it feels gross and I'm worried there could be unforeseen problems.
I would still love it if anyone had any ideas on a better way to do this.
After a couple of days I ended up figuring out a better answer. Here's what it looks like:
(\r\n|\r|\n)+[ \t]* %{
parser.indentCount = parser.indentCount || [0];
parser.forceDedent = parser.forceDedent || 0;
if (parser.forceDedent) {
parser.forceDedent -= 1;
this.unput(yytext);
return 'DEDENT';
}
var indentation = yytext.replace(/^(\r\n|\r|\n)+/, '').length;
if (indentation > parser.indentCount[0]) {
parser.indentCount.unshift(indentation);
return 'INDENT';
}
var dedents = [];
while (indentation < parser.indentCount[0]) {
dedents.push('DEDENT');
parser.indentCount.shift();
}
if (dedents.length) {
parser.forceDedent = dedents.length - 1;
this.unput(yytext);
return 'DEDENT';
}
return `NEWLINE`;
%}
Firstly, I modified my capture regex to make sure I wasn't inadvertently capturing extra newlines after a series of non-newline spaces.
Next, we make sure there are 2 "global" variables. indentCount will track our current indentation length. forceDedent will force us to return a DEDENT if it has a value above 0.
Next, we have a condition to test for a truthy value on forceDedent. If we have one, we'll decrement it by 1 and use the unput function to make sure we iterate on this same pattern at least one more time, but for this iteration, we'll return a DEDENT.
If we haven't returned, we get the length of our current indentation.
If the current indentation is greater than our most recent indentation, we'll track that on our indentCount variable and return an INDENT.
If we haven't returned, it's time to prepare to possible dedents. We'll make an array to track them.
When we detect a dedent, the user could be attempting to close 1 or more blocks all at once. So we need to include a DEDENT for as many blocks as the user is closing. We set up a loop and say that for as long as the current indentation is less than our most recent indentation, we'll add a DEDENT to our list and shift an item off of our indentCount.
If we tracked any dedents, we need to make sure all of them get returned by the lexer. Because the lexer can only return 1 token at a time, we'll return 1 here, but we'll also set our forceDedent variable to make sure we return the rest of them as well. To make sure we iterate on this pattern again and those dedents can be inserted, we'll use the unput function.
In any other case, we'll just return a NEWLINE.

AngularJs Filters

I'm writing a module, using a custom filter, and I noticed something weird. if I use console.log() inside a filter, it logs the value twice, even though I call it only once. Is there a way to log it only once? Does that mean the code inside the filter gets executed twice?
Here's the filter:
.filter('arrayToSentence', function($sce, $rootScope) {
return function(array, index) {
console.log(index);
var i, word, sentence = '<span style="color:red;">';
for (i = 0; i < array.length; i++) {
if (i < array.length - 1) {
sentence += array[i] + '&bnsp;';
} else {
sentence += array[i];
}
}
sentence = sentence += '</span>'
return $sce.sentence;
}
})
The console.log(index) is logging twice. I need to make sure that my filter logic will not be duplicating anywhere, as further down I need to compare two arrays (the one being filtered, and another one to colour the differences between them, like missing word, or word mismatch).
[EDIT]
It is pointed out to me that my question is a duplicate, of this
Yet the original question doesn't answer how to avoid this issue, but I believe #defaultcheckbox provided a fulfilling answer.
Are you using the filter in the DOM with piping, or are you using it in the controller? Using a filter in the DOM will always be "slower" than using one in the controller, but more importantly (and possibly related to your case) DOM filters always run twice.
source: https://toddmotto.com/use-controller-filters-to-prevent-digest-performance-issues/

Javascript Unnecessarily Compact Array Operations

So, for my own knowledge, and because I love looking at things from a different perspective...
I have the following bite of Javascript code, which for this problem's intents and purposes will only ever receive strings formatted like this: "wordone wordtwo":
function inName(inputName) {
return inputName.split(" ")[1].toUpperCase();
}
However, this only returns half of what I want ("WORDTWO"). I desire to return the original string with a single change: the space-separated second word returned through the toUpperCase(); and then re-concatenated to the untouched first word.
I also want to unnecessarily run all of the operations on the return line. My brain says this is possible, given how as the compiler reads the line from left to right and makes adjustments to the available member functions based on what has resolved. Also everything in Javascript is an object, correct?
Help me out for my own curiosity's sake, or bash me over the head with my own misconceptions.
Here is a solved version of the above question using 'normal' statements:
function inName(inputName) {
var nameArray=inputName.split(" ");
nameArray[1]=nameArray[1].toUpperCase();
return nameArray.join(" ");
}
One line with substr, indexOf and a variable on the fly ;-)
function inName(inputName) {
return inputName.substr(0, (index = inputName.indexOf(' '))) + inputName.substr(index).toUpperCase();
}
Here's another option which avoids the regular expression:
function inName(inputName) {
return inputName.split(' ').map(function(v,i){return i?v.toUpperCase():v;}).join(' ');
}
This does the same split as the original code, then maps the parts to a function which returns the value at index 0 unchanged but the value at index 1 in upper case. Then the two results are joined back together with a space.
As others have said, a longer, clearer version is better in practice than trying to come up with a clever one-liner. Defining a function inside the return statement feels like cheating anyway ;-)
Something like this almost seems like it belongs on Code Golf, but here's my take:
function inName(inputName) {
return inputName.replace(/ .*/,function(m) {return m.toUpperCase();});
}
Interesting. Here is my take on the problem
function justDoIt(str){
return [str = str.split(" ") , str.pop().toUpperCase()].join(" ");
}
Creates a new array, str is split and reassigned as an array, and the first item of the new array, then the second new array item pops the last word, makes it uppercase, puts it into the new array. Then joins the array [["wordOne"],"WORDTWO"].join(" ")

Sorting a HTML structure

Im trying to sort a div structure based on a paramter using a small javscript i found.
It seems to not perform exactly as expected. I understand the sorting function is not parsing the values perfectly...
This is the sorting logic is use...
<script type="text/javascript">
// Sorting Logic
$(function() {
$("a[href*=#]").click(function(e) {
e.preventDefault();
var liContents = [];
$('#ProductPrice span[id*="ABSPrice"]').each(function() {
liContents.push($(this).html());
});
liContents.sort(numOrdDesc);
$('#ProductPrice span[id*="ABSPrice"]').each(function() {
$(this).html(liContents.pop());
});
});
});
function numOrdDesc(a, b) {
return (parseInt(b) - parseInt(a));
}
// End
of Sorting Logic
Since i cannot exactly post the html i am going to add a link to the actual website where you can see this is action. Can anyone point out where i am going wrong?
http://www.absbiz.co.uk/Products/tabid/85/rvdsfcatid/Modems-437/Default.aspx
EDIT: Currently i think the sort is working, however i still cannot move the individual products. Only the prices get sorted and changed....
I took at look at your live site and injected the sorting function you used in your question. I noticed a few things. Firstly, the strings you are passing into your compare function look like this:
"£38.89 ex. VAT"
"£19.93 ex. VAT"
"£44.44 ex. VAT"
...
So parseInt("£38.89 ex. VAT") will return NaN. No compare. We can adjust this to remove all non-decimal information, and also parse a float instead, like so:
parseFloat("£38.89 ex. VAT".replace(/[^0-9\.]/g, "")); // --> 38.89
However, this will sort the above price strings, but we don't have association information on the products to properly sort them in the DOM (yet). For that we need to adjust what you put into your array.
The strategy is to find all the top item containers using $('.ProductListProductItem') and push their html into an array. Sort the array such that the prices are found by $('#ProductPrice span[id*="ABSPrice"]') and stripped of non-decimal information. Each top item container is then repopulated with the html from the sorted array.
Essential code:
var liContents = [];
// Push in all the product containers' innerHTML
$('.ProductListProductItem').each(function () {
liContents.push($(this).html());
});
// Use our new sorting routine
liContents.sort(numOrdDesc);
// Replace the html of each top item container
$('.ProductListProductItem').each(function () {
$(this).html(liContents.pop());
});
// Pass in jQuery elements, not strings
function numOrdDesc($a, $b) {
// Get the price further down in the DOM
var a = $('#ProductPrice span[id*="ABSPrice"]', $a).text();
var b = $('#ProductPrice span[id*="ABSPrice"]', $b).text();
return parseFloat(b.replace(/[^0-9\.]/g, "")) - parseFloat(a.replace(/[^0-9\.]/g, ""));
}
To verify this works, navigate to your live site and open DevTool > Console. Copy the above script and paste it into Console. Hit return, and note that the products are now sorted in ascending order. Enjoy.
Before:
After:
Your parseInt fails because of the pound sign. I'm guessing you want to strip that out in your sort function, and also use parseFloat instead since your prices are floating point numbers.
Try
function numOrdDesc(a, b) {
return parseFloat(b.replace(/[^0-9\.]/g, "")) - parseFloat(a.replace(/[^0-9\.]/g, ""))
}
The replace pretty much removes everything that's not a digit or a dot from the string before attempting to parse it
// "£298.73".replace(/[^0-9\.]/g, "") -> "298.73"
// parseFloat("298.73") -> 298.73
You are missing parameters to the function, you can do it like this.
liContents.sort(function(b, a) {
return (parseInt(b) - parseInt(a));
});

JQuery "Constrain" plugin - Weird Javascript Error

Background: Our web app uses the jquery.constrain.js plugin to handle data entry in some text boxes, to only allow valid characters to be added. This plugin allows various constraints upon the data, using regular expressions, whitelist/blacklist characters, etc. Until today, we were running the 1.0 release of this plugin, unmodified.
I noticed a few days ago that some of the text boxes still allowed invalid data entry. For example, a numeric-only textbox allowed alpha characters, etc. It also displayed a javascript error "Object doesn't support this property or method". I tracked it down to the following function in the jquery.constrain plugin.
function match(item, input, e) {
var arr = item.chars.split("");
for (var i in arr) {
var token = arr[i];
if (token.charCodeAt(0) == e.which) {
return true;
}
}
if (item.regex) {
var re = new RegExp(item.regex);
if (re.test(String.fromCharCode(e.which))) {
return true;
}
}
return false;
};
Debugging through this block of code, I determined the following:
item is an object with two string properties: chars and regex
item.chars is an empty string ("") at the time of failure.
arr, the result of item.chars.split("") is, as expected, an empty array.
Here's where it's weird. Even though arr is an empty array, the for loop assigns a valid value to i. The value is "remove". So we drop into the loop. token is obviously null, because arr["remove"] is null. So token.charCodeAt(0) throws.
I corrected the error by adding an if statement around the for loop, as follows:
if (arr.length > 0) {
for (var i in arr) {
var token = arr[i];
if (token.charCodeAt(0) == e.which) {
return true;
}
}
}
However, I'm completely baffled as to why this was even necessary - is this an IE bug, a bug in the plugin, or am I just holding my breath wrong when I compile the app?
You should never use for(i in arr) to loop over Arrays. If scripts add methods to the Array prototype these too will be iterated using the for(i in arr) loop. This is probably what is causing your errors. You probably have added a script that modifies the Array.prototype chain.
Also read here under "Why you should stop using for…in to iterate (or never take it up)"
http://www.prototypejs.org/api/array

Categories

Resources