Creating a regex to parse html to MXML syntax - javascript

I searched a lot over stackoverflow and found very interesting that's includes:
How to create a Regular Expression for a span attribute?
And
Javascript regex to replace text div and < >
But turns out that I couldn't really manage to parse my goal to replace div with the data-type attribute and remove the data-type attribute over the strings.
Here's how I did.
//Doesn't work with multi lines, just get first occurrency and nothing more.
// Regex: /\s?data\-type\=(?:['"])?(\d+)(?:['"])?/
var source_code = $("body").html();
var rdiv = /div/gm; // remove divs
var mxml = source_code.match(/\S?data\-type\=(?:['"])?(\w+)(?:['"])?/);
var rattr =source_code.match(/\S?data\-type\=(?:['"])?(\w+)(?:['"])/gm);
var outra = source_code.replace(rdiv,'s:'+mxml[1]);
var nestr = outra.replace(rattr[0],'');// worked with only first element
console.log(nestr);
console.log(mxml);
console.log(rattr);
Over this HTML sample page
<div id="app" data-type="Application">
<div data-type="Label"></div>
<div data-type="Button"></div>
<div data-type="VBox"></div>
<div data-type="Group"></div>
</div>
Any light on that specific thing? I may missing something, but I really have no clue, there's no left space otherwise asking here.
I've created a jsFiddle to show, just open the console of browser to see the results I have with me.
http://jsfiddle.net/uWCjV/
Feel free to answer over jsfiddle or a better explanation of my regex, why it's fails.
Until I get any feedback, I will keep trying to see if I can manage to replace the text.
Thanks in advance.

It would probably be easier to parse the markup into a tree of Objects and then convert that into MXML.
Something like this:
var source_code = $("body").html();
var openStartTagRx = /^\s*<div/i;
var closeStartTagRx = /^\s*>/i;
var closeTagRx = /^\s*<\/div>/i;
var attrsRx = new RegExp(
'^\\s+' +
'(?:(data-type)|([a-z-]+))' + // group 1 is "data-type" group 2 is any attribute
'\\=' +
'(?:\'|")' +
'(.*?)' + // group 3 is the data-type or attribute value
'(?:\'|")',
'mi');
function Thing() {
this.type = undefined;
this.attrs = undefined;
this.children = undefined;
}
Thing.prototype.addAttr = function(key, value) {
this.attrs = this.attrs || {};
this.attrs[key] = value;
};
Thing.prototype.addChild = function(child) {
this.children = this.children || [];
this.children.push(child);
};
function getErrMsg(expected, str) {
return 'Malformed source, expected: ' + expected + '\n"' + str.slice(0,20) + '"';
}
function parseElm(str) {
var result,
elm,
childResult;
if (!openStartTagRx.test(str)) {
return;
}
elm = new Thing();
str = str.replace(openStartTagRx, '');
// parse attributes
result = attrsRx.exec(str);
while (result) {
if (result[1]) {
elm.type = result[3];
} else {
elm.addAttr(result[2], result[3]);
}
str = str.replace(attrsRx, '');
result = attrsRx.exec(str);
}
// close off that tag
if (!closeStartTagRx.test(str)) {
throw new Error(getErrMsg('end of opening tag', str));
}
str = str.replace(closeStartTagRx, '');
// if it has child tags
childResult = parseElm(str);
while (childResult) {
str = childResult.str;
elm.addChild(childResult.elm);
childResult = parseElm(str);
}
// the tag should have a closing tag
if (!closeTagRx.test(str)) {
throw new Error(getErrMsg('closing tag for the element', str));
}
str = str.replace(closeTagRx, '');
return {
str: str,
elm: elm
};
}
console.log(parseElm(source_code).elm);
jsFiddle
This parses the markup you provided into the following:
{
"type" : "Application"
"attrs" : { "id" : "app" },
"children" : [
{ "type" : "Label" },
{ "type" : "Button" },
{ "type" : "VBox" },
{ "type" : "Group" }
],
}
It's recursive, so embedded groups are parsed, too.

Related

How to create template or placeholder in html and add them dynamically to the body?

I want to create a forum in html. As you have guessed I am new to web dev.
Let's say there is a template for messages posted by the users:
<div id="message">
<h3>#name of the user</h3>
<p>#message</p>
</div>
I wish to populate this template with the user's name and the message and then dynamically add it to the main body when the user posts it.
However as I told you I am very new to web development. I am not sure how to do this. All I need is your guideline and ideas. Maybe point me toward appropriate tutorial and references.
You can use Template Literals and just interpolate name and msg inside template.
let template = document.querySelector('div#message');
function createMessage(name,msg){
return (
`<div id="message">
<h3>${name}</h3>
<p>${msg}</p>
</div>`
)
}
let data = [{
name:"name 1",
message:"message 1",
},
{
name:"name 2",
message:"message 3",
},
{
name:"name 3",
message:"message 3",
},
]
let str = data.map(x => createMessage(x.name,x.message)).join('');
document.body.insertAdjacentHTML("afterend",str)
You can use format unicorn to do this like stackExchange does, example:
Your Html:
<div id="messageLoader">
</div>
<script type="text/template" id="templateMessage">
<h2>{title}</h2>
<p>{message}</p>
<br>
<span><Strong>{sign}</strong><span>
</script>
Your script:
String.prototype.formatUnicorn = function () {
"use strict";
var str = this.toString();
if (arguments.length) {
var t = typeof arguments[0];
var key;
var args = ("string" === t || "number" === t) ?
Array.prototype.slice.call(arguments)
: arguments[0];
for (key in args) {
str = str.replace(new RegExp("\\{" + key + "\\}", "gi"), args[key]);
}
}
return str;
};
var jsonMessage = {title:"Custom Template With Form Unicorn",message:"Stack Overflow Rocks bae !!",sign:"Stan Chacon"};
let myFirstUnicornTemplate = document.getElementById("templateMessage").text;
var template = myFirstUnicornTemplate.formatUnicorn(jsonMessage);
document.getElementById("messageLoader").innerHTML = template;
EDIT: fun fact you can use it here in stack overflow just copy and paste this on console :
"Hello {name}, welcome to StackOverflow {emoji}".formatUnicorn({name:"User",emoji:"=)"});
Or try the snippet:
String.prototype.formatUnicorn = function () {
"use strict";
var str = this.toString();
if (arguments.length) {
var t = typeof arguments[0];
var key;
var args = ("string" === t || "number" === t) ?
Array.prototype.slice.call(arguments)
: arguments[0];
for (key in args) {
str = str.replace(new RegExp("\\{" + key + "\\}", "gi"), args[key]);
}
}
return str;
};
var x = "Hello {name}, welcome to StackOverflow {emoji}".formatUnicorn({name:"User",emoji:"=)"});
console.log(x);
Hope it helps =)
If you're using javascript you can manipulate the content of an html tag like this:
document.getElementById("yourhtmltagid").innerHTML = "#message";
you can learn the basics on your own:
https://www.w3schools.com/jsref/prop_html_innerhtml.asp
Ignoring the notion of a forum in HTML (see comments on original post), Web Components provide this functionality and have been gaining support since the beginning of 2019.
Web Components include <slot>s that can be used as placeholders in <template>s, custom elements that can define values for those slots, and shadow DOMs to display the result (see Using templates and slots).

How to check and return data until there are no matches?

I have created a function, that will search for different tags, tags like [image] and [gallery] inside a JSON file. If there is a match, it will return and replace it with a new output. Like an image object or a slideshow.
A JSON object can contain multiple tags of the same type, or contain different ones at the same time. So an object can contain two [image] tags for e.g.
JSON
http://snippi.com/s/bzrx3xi
The problem was, if there are multiple [image] tags found, it was replaced with the same content. I was looking for a script that is searching for the tags, until there will no matches anymore. Many thanks to #debatanu for the solution.
Unfortunately, I have some extra wishes for the script, because now the tags like image, will be replaced for the last image object of the media array inside the JSON and it will only grab and replace the first tag.
I was wondering if it is possible to check each tag and replace each tag with the new output.
Search for tags
filterText: function(data) {
// Loop through JSON data
// Check if [tag] in data.text exists
var dataText = data.text;
var tags = {
"gallery": /\[gallery\]/ig,
"image": /\[image\]/ig
};
if (dataText != undefined) {
var raw_data = dataText;
for (var key in tags) {
if (tags.hasOwnProperty(key)) {
var tag = tags[key];
var tagArr = [];
// Check if [tag] in data.text exists
while ( (tagArr = tag.exec(dataText)) !== null ) {
// Call functions
if (key == "gallery") {
console.error('GALLERY');
parsedHTML = raw_data.replace(tagArr[0], shortcodeController.gallery(data));
raw_data = parsedHTML;
return parsedHTML;
}
if (key == "image") {
console.error('IMAGE');
parsedHTML = raw_data.replace(tagArr[0], shortcodeController.image(data));
raw_data = parsedHTML;
return parsedHTML;
// model.find('p').html(parsedHTML);
}
}
}
}
};
Call the filterText function
getDataPopup: function(data) {
if (data.text != undefined) {
$('.js-popup .article').html(data.text);
var parsed = dataController.filterText(data);
console.log('parsed: ', parsed);
$('.js-popup .article').html(parsed);
} else {
$('.js-popup .article').html(data.intro);
}
},
Above function will search for tags inside a while loop.
This script is called, when the user clicked on an item, that will open a popup screen.
The script that is called by the getDataPopup function when the user clicked on an item, will search for a match, when there is a match found, it will call the function shortcodeController.image(data) that return the new output to the variable: parsedHTML.
The function that generates a new output will look like this:
image: function(data) {
// Development
console.log('shortcodeController.image');
var raw_data = data.text;
var outputHTML;
if (data.media != undefined) {
for (var i = 0; i < data.media.length; i++) {
if (data.media[i].image != undefined) {
outputHTML = '<div class="image">';
// Extract filename
var url = data.media[i].image.src;
var filename = url.substring( url.lastIndexOf('/') + 1, url.lastIndexOf('.') );
// console.log(filename);
outputHTML += '<img src="' + url + '" alt="' + filename + '" />';
//data.media[i].image = undefined;
outputHTML +='</div>';
}
};
return outputHTML;
} else {
// If media doesn't exists return empty string
return '';
}
},
Debatanu has mentioned that I should use data.media[i].image = undefined; directly after the outputHTML that contains the actual image object, but this will result in an undefined error. The first [image] tag is replaced by undefined. When I comment this line out, it will be replaced by the last image object inside the media array, as mentioned before.
Maybe it does not work like properly, because the while loop, however, only looks for the gallery and image tags and if there is a match, running it once, because he already saw the tag. Then it will be called again and replaces the first image tag again with the second image object within the media array. Should there might be a while loop added in the if statements on whether it is a gallery or image, so for each tag within the text object is the function called?
Also I noticed, when I console logged tagArr it will give me an value of null when I past it after the while loop and a empty array [] when I paste it directly after the array is created. Beside that, when I console log tag directly after the while loop is started, it only console log tag once, while there are two image tags set inside the JSON.
You can use exec and loop through it using
var dataText = data.text;
var tags = {
"gallery": /\[gallery\]/ig,
"image": /\[image\]/ig,
};
if (dataText != undefined) {
var raw_data = dataText;
for (var key in tags) {
if (tags.hasOwnProperty(key)) {
var tag = tags[key];
var arr=[];
while ((arr= tag.exec(dataText)) !== null) {
//arr[0] will contain the first match
//var newTag=newTags[key];
//you need to replace the matched output
//so no need for newTag
if (key == "gallery") {
console.error('GALLERY');
//replace the matched output arr[0]
//instead tag or newTag
//Since u have [] to replace we need to ommit the regex literal format /<<exp>>/
parsedHTML = raw_data.replace(arr[0], Triptube.shortcodeController.gallery(data));
//Need to add this line for reflecting the changed data
raw_data=parsedHTML;
model.find('p').html(parsedHTML);
}
if (key == "image") {
console.error('IMAGE');
//replace the matched output arr[0]
//instead tag or newTag
//Since u have [] to replace we need to ommit the regex literal format /<<exp>>/
parsedHTML = raw_data.replace(arr[0], Triptube.shortcodeController.image(data));
console.log(parsedHTML);
//Need to add this line for reflecting the changed data
raw_data=parsedHTML;
model.find('p').html(parsedHTML);
}
}
}
}
}
You can find more about it in MDN
With each loop the exec will give you the next match untill no match is left.
EDIT
I have added the entire filter code from the beginning. You see the raw_data variable should be assigned before the loop. Once that is done, the code below with the image function should give you the proper result.
EDIT 2
First the filterText function will return the parsed html post completion of parsing the html
filterText: function(data) {
// Loop through JSON data
// Check if [tag] in data.text exists
var dataText = data.text;
var tags = {
"gallery": /\[gallery\]/ig,
"image": /\[image\]/ig
};
if (dataText != undefined) {
var raw_data = dataText,
newData=JSON.parse(JSON.stringify(data));//Copy of the data object
for (var key in tags) {
if (tags.hasOwnProperty(key)) {
var tag = tags[key];
var tagArr = [];
// Check if [tag] in data.text exists
while ( (tagArr = tag.exec(dataText)) !== null ) {
// Call functions
if (key == "gallery") {
console.error('GALLERY');
parsedHTML = raw_data.replace(tagArr[0], shortcodeController.gallery(newData));
raw_data = parsedHTML;
//return parsedHTML;
}
if (key == "image") {
console.error('IMAGE');
parsedHTML = raw_data.replace(tagArr[0], shortcodeController.image(newData));
raw_data = parsedHTML;
//return parsedHTML; we will return the parsed HTML only when all the tags have been replaced
// model.find('p').html(parsedHTML);
}
}
}
}
return parsedHTML; //return the parsed HTML here
};
Next is the image function which will parse through the images,
image: function(data) {
// Development
console.log('shortcodeController.image');
var raw_data = data.text;
var outputHTML;
if (data.media != undefined) {
for (var i = 0; i < data.media.length; i++) {
if (data.media[i].image != undefined) {
outputHTML = '<div class="image">';
// Extract filename
var url = data.media[i].image.src;
var filename = url.substring( url.lastIndexOf('/') + 1, url.lastIndexOf('.') );
// console.log(filename);
outputHTML += '<img src="' + url + '" alt="' + filename + '" />';
outputHTML +='</div>';
data.media[i].image = undefined;
//Uncommented the above code, because now the operation will be done on the copy of the original data object
}
};
return outputHTML;
} else {
// If media doesn't exists return empty string
return '';
}
}

Return bold/coloured search string from a function in javascript without jQuery

I am trying to write a name based search function in javascript. I have one function, where I am trying to match the search value with database. I have written regex for lower/upper case letter match. After the match is found (even the lower/upper) i need the matched string in BOLD/COLOURED letters, which is highlighted in main string.
e.g. In Bill if search string is, "bi" then returned value from main function should be Bill.
I have array of values, on which I am trying to execute my search function,
In my main function,
reg = new RegExp(searchStr, 'gi');
for(var i=0; i<$scope.Arr.length ; i++)
{
strMatch = $scope.Arr.name.match(reg);
tempName = $scope.Arr[i].name.replace(reg,dummy(strMatch[0]));
console.log(tempName);
}
I need help to write function dummy where I am trying to return Bold matched string,
I have tried it in this way,
function dummy(str)
{
console.log(str);
var newElement = "<span class='highlight'>" +str+ "</span>";
console.log(newElement);
return newElement;
}
I am getting this output,
<span class='highlight'>Bi</span>ll
I need Bi as bold/highlighted.
This is my highlight class definition,
.highlight
{
background-color:yellow;
}
Your code is almost there, except that you're missing the array index when setting strMatch.
strMatch = $scope.Arr[i].name.match(reg);
Edit:
You might also want to only perform the highlighting conditionally. I suggesting checking whether strMatch exists (using if(strMatch){...}) before performing the replacement on tempName.
I also don't recommend using console.log for testing this kind of code, since you're trying to generate HTML (which the console can't really display in a useful way). Why not append the generated HTML to an existing HTML element so you can see the real results?
Check out the updated example code below.
document.getElementById("input").addEventListener("keyup", function() {
document.getElementById("output").innerHTML = "";
var searchStr = this.value;
if (searchStr.length > 0) {
var reg = new RegExp(searchStr, 'gi');
var $scope = {
Arr: [{
name: "Bill Clinton"
}, {
name: "Bob Dole"
}, {
name: "Bilbo Baggins"
}, {
name: "Bing Crosby"
}, {
name: "Frodo Baggins"
}]
};
for (var i = 0; i < $scope.Arr.length; i++) {
var strMatch = $scope.Arr[i].name.match(reg);
var tempName = $scope.Arr[i].name;
if (strMatch) {
var tempName = tempName.replace(strMatch[0], dummy(strMatch[0]));
document.getElementById("output").insertAdjacentHTML("beforeend", tempName + "<br/>");
}
}
}
function dummy(str) {
return "<span class='highlight'>" + str + "</span>";
}
});
.highlight {
font-weight: bold;
background-color: yellow;
}
Type a name here:
<input id="input" type="text" />
<br/>
<div id="output" />
As I said I need to bind the returned value to the array of object, and it was not possible to get the element ID for each changing value of an object, the solution for my problem was writing a DIRECTIVE.
I managed to solve it as given below,
.directive('ngCreateBoldString', function() {
return {
restrict: 'AE',
scope: {
searchId : '#',
searchString : '#',
fromName : '#'
},
controller: ['$scope', function($scope) {
console.log('I am inside directive, this is value for search string:',$scope.searchString);
console.log('This is value of whole Name:', $scope.fromName);
var reg = new RegExp($scope.searchString, 'gi');
$scope.getTemp = function(name) {
var strMatch = name.match(reg);
if(strMatch == null)
{
return name;
}
console.log('I am matched string',strMatch[0]);
var strDummy = "<b style='color:red'>" + strMatch[0] + "</b>" ;
console.log(strDummy);
return name.replace(reg,strDummy);
};
}],
link: function(scope, iElement, iAttrs, ctrl) {
var dumCheck = scope.getTemp(scope.fromName);
console.log('I am a dumcheck!',dumCheck);
dumCheck= "<h3 style='font-size:180%'>"+dumCheck+"</h3>";
console.log('I am going to print this!!',dumCheck);
iAttrs.fromName = dumCheck;
scope.$watch('fromName', function(newVal) {
var m= document.getElementById(scope.searchId);
console.log('I am var m');
m.innerHTML =dumCheck;
});
}
};
})
I hope this will help someone.

Removing two words from text from string javascript

Hey I'm looking at removing words from a string, thing is the word to replace could be two different words.
e.g.
foo = "stringtest";
id = foo.replace('string', '');
or
foo = "paragraphtest";
id = foo.replace('paragraph', '');
at the moment I've approached the problem as so.
foo = "paragraphtest";
id = foo.replace('paragraph', '');
id = foo.replace('string', '');
I know this code could easily be improve but I don't know how :( thanks for your assistance.
Like this?
var foo = ["paragraph","string"];
var id = foo.replace(new RegExp(foo.join("|"),""));
The above creates array with strings you want to replace and joins it with | in RegExp constructor and replaces the match with empty string.
if(foo.indexOf("ph") > -1)
id = foo.replace('paragraph', '');
else
id = foo.replace('string', '');
Something like this maybe. Which will also give you the flexibility to modify what you replace each string with.
var replacementCharacters = [
{
searchString: "paragraph",
replacementString: ""
},
{
searchString: "string",
replacementString: ""
}
];
function runReplacement(str) {
var returnValue = str;
for (var i = 0; i < replacementCharacters.length; i++) {
returnValue = returnValue.replace(replacementCharacters[i].searchString, replacementCharacters[i].replacementString);
}
return returnValue;
}
var foo = "paragraphtest";
var id = runReplacement(foo);
Here is a demo http://jsfiddle.net/RfKt4/

Use SPAN to change color of a word if a definition is availible in the glossary

I'm teaching myself JavaScript and JQuery and working through a simple Glossary app as I go. Currently my glossary terms are in two json files (one for terms and one for acronyms). I have a page with text on it and code to make a definition display in an alert when I click on a word that is available in the glossary of terms or glossary of acronyms. That part is working. What I would like to do is to be able to change the style of each word in the text that has a matching definition (color, underline, etc). I think I need to use a loop to check if the word in in the glossary (I can already do that) and then apply but I'm not really sure the span works when doing it dynamically. The one span tag in my code is modified example that had been posted in another question here and I have it working for me, I'm just not too certain how it does what it does. Anyone have time to get me going in the right direction?
//breaks the paragraph html into word by word targets
var p = $('p#paragraph');
var words;
p.html(function(index, oldHtml) {
words = oldHtml.replace(/\b(\w+?)\b/g, '<span class="word">$1</span>')
return words;
});
//when word is clicked checks to see if word in the glossary, if so displays alert box with word and definition
p.click(function(event) {
if (this.id != event.target.id) {
var termNeeded = event.target.innerHTML;
//checks Terms json first
var checkAcronyms = true;
for (var i = 0; i < jsonTerms.GlossaryTerms.length; i++) {
var obj = jsonTerms.GlossaryTerms[i];
if (obj.term == termNeeded) {
alert(obj.term + ": " + obj.definition);
checkAcronyms = false;
break;
};
};
//if the word is not in the terms, then checks in the acronyms
if (checkAcronyms == true){
for (var i = 0; i < jsonAcronyms.GlossaryAcronyms.length; i++) {
var obj = jsonAcronyms.GlossaryAcronyms[i];
if (obj.term == termNeeded) {
alert(obj.term + ": " + obj.definition);
break;
};
};
};
};
});
//brings in the JSON data
var jsonTerms;
$.getJSON("GlossaryTerms.json", function(data) {
jsonTerms = data;
//console.log(jsonTerms);
});
var jsonAcronyms;
$.getJSON("GlossaryAcronyms.json", function(data) {
jsonAcronyms = data;
//console.log(jsonAcronyms);
});
Maybe something like this would do the trick:
I changed your code around a bit, and please beware that it is untested.
You would have to define a CSS style with the name "defined", which will indicate that the word has a definition.
I extracted your logic into a separate function for reuse. Also, created the addStyleToWords function, which should iterate over all your words, check if they have a definition, and if they do, then add an extra class to that element.
var jsonTerms;
var jsonAcronyms;
function checkWord(termNeeded) {
//checks Terms json first
for (var i = 0; i < jsonTerms.GlossaryTerms.length; i++) {
var obj = jsonTerms.GlossaryTerms[i];
if (obj.term == termNeeded) {
return obj;
}
}
//if the word is not in the terms, then checks in the acronyms
for (var i = 0; i < jsonAcronyms.GlossaryAcronyms.length; i++) {
var obj = jsonAcronyms.GlossaryAcronyms[i];
if (obj.term == termNeeded) {
return obj;
}
}
return null;
}
function addStyleToWords() {
$(".word").each(function() {
var el = $(this);
var obj = checkWord(el.text());
if (obj != null) el.addClass("defined");
});
}
//breaks the paragraph html into word by word targets
var p = $('p#paragraph');
p.html(function(index, oldHtml) {
return oldHtml.replace(/\b(\w+?)\b/g, '<span class="word">$1</span>');
});
//when word is clicked checks to see if word in the glossary, if so displays alert box with word and definition
p.click(function(event) {
if (this.id != event.target.id) {
var obj = checkWord(event.target.innerHTML);
if (obj != null) alert(obj.term + ": " + obj.definition);
});
//brings in the JSON data
$.getJSON("GlossaryTerms.json", function(data) {
jsonTerms = data;
$.getJSON("GlossaryAcronyms.json", function(data) {
jsonAcronyms = data;
addStyleToWords();
});
});
Once you have added in your spans and the JSON data has loaded you need to loop through each
word span testing them for matches as you go.
p.find('span.word').each(function(){
// "this" now refers to the span element
var txt=this.innerHTML;
if(isInGlossary(txt)){
$(this).addClass('in_glossary');
}
})
You will need to define the isInGlossary(term) function, pretty much what you have done already in your p.click code.
I don't get it...
To if I understand you correctly, look at: JQuery addClass
My Suggestions:
If you want to iterate over each work in the paragraph, then, in your click handler find each span tag using $('p#paragraph).find('span').each(function(){...});
In your each function, get the work with $(this).html()
To style your word, add a class or css to $(this). see:JQuery addClass
Rather return your JSONArray as a JSONObject (much like an associative array) with the word being the property and the description being the value, that way you can search through it like so: var definition = json[word].

Categories