js, get element from text-offset/text-position? - javascript

I am trying to parse and reformat some web page.
The text is well formatted but the DOM structure is not (generated from WYSIWYG editor).
Thus I would like to parse the text content, then find back corresponding element(s) of each portions of the text.
example problem:
//example.html
<div id="a">
ABC
<span id="b">
DEF
<span id="c">
GHI
</span>
<span id="d">
JKR
</span>
</span>
</div>
//script.js
let a = document.getElementById('a');
let text_pos=a.textContent.indexOf('J');
// good way to get element #d from text_pos?
I know one way is to loop through all child elements of #a, then subtract each text length until 0.
But are there better way?

From what I understood from you is that you want to find parent element of the text that you search for. So instead of looping through all the text we will use indexOf search term and then backtrack to get first tag after that we will forward search to get closing tag and return this part of string between first tag and last tag
Another way is to backtrack to find first id= instead of first html tag but Im not sure if all you elements have id attribute
var data = "<div>Data<div id='d'><br/>AB</div></div>";
console.log(getparentElementOf("AB", data))
function getparentElementOf(searchTerm, data){
var indexOfTerm = data.indexOf(searchTerm);
var indexOfFirstTag = getStartIndexOfParentTag(indexOfTerm);
var indexOfEndTag = getEndIndexOfParentTag(indexOfTerm + searchTerm.length, data.length);
var element = data.substr(0, indexOfEndTag +1);
element = data.substring(indexOfFirstTag, element.length);
return element;
}
function getStartIndexOfParentTag(startFromIndex){
var indexOfFirstTag = -1;
var flagClosingBracket = false, flagOpeningBracket = false;
// back track from that found position until you find the first tag
for(var i = startFromIndex; i >= 0; i--){
// If we have detected closing bracket
if(flagClosingBracket == true){
// If we have / then cancel detected closing bracket
if(data[i] == "/"){
flagClosingBracket = false;
}else if(data[i] == "<"){
// otherwise we have found index of our first tage
flagOpeningBracket = true;
indexOfFirstTag = i;
i = -1; // to exit loop
}
}else{
// Otherwise detect closing bracket
if(data[i] == ">"){
flagClosingBracket = true;
}
}
}
return indexOfFirstTag;
}
function getEndIndexOfParentTag(startFromIndex, to){
var indexOfFirstTag = -1;
var flagClosingBracket = false, flagOpeningBracket = false, flagSlash = false;;
// back track from that found position until you find the first tag
for(var i = startFromIndex; i < to; i++){
// If we have detected closing bracket
if(flagOpeningBracket == true){
// If we have / then cancel detected closing bracket
if(data[i] == ">"){
flagOpeningBracket = false;
}else if(data[i] == "/"){
// otherwise we have found index of our first tage
flagSlash = true;
}
}else{
// Otherwise detect closing bracket
if(data[i] == "<"){
flagOpeningBracket = true;
}
}
if(flagSlash == true)
{
if(data[i] == ">"){
flagClosingBracket = true;
indexOfFirstTag = i;
i = to; // to exit loop
}
}
}
return indexOfFirstTag;
}

Well I think the question is alittle confusing but as I undestoood you you want the text of the elements as they are nested you should loop them. As you comment at the question text. I leave you a fragment of a loop with no lenght evaluation:
var strResult = "";
let a = document.getElementById('a');
for(content_word in a.textContent.trim().split("\n")) {
var isaWord = /[aA-zZ]/.test(a.textContent.trim().split("\n")[content_word])
if (isaWord) {
strResult = strResult + a.textContent.trim().split("\n")[content_word].trim()
}
};
console.log(strResult)
I hope this could help.
Regards

Related

Javascript search text highlight with Swift

I'm making a text-searching mechanism (like ⌘ + F) for an iOS app and It's working but I have two issues.
Whenever someone searches something in Arabic, the word becomes disconnected.
Users can't search if there are diacritics in the text but their search does not (so basically I'm trying to make it diacritic-insensitive)
Here's the code for my highlighting (which I found from this):
var uiWebview_SearchResultCount = 0;
/*!
#method uiWebview_HighlightAllOccurencesOfStringForElement
#abstract // helper function, recursively searches in elements and their child nodes
#discussion // helper function, recursively searches in elements and their child nodes
element - HTML elements
keyword - string to search
*/
function uiWebview_HighlightAllOccurencesOfStringForElement(element,keyword) {
if (element) {
if (element.nodeType == 3) { // Text node
var count = 0;
var elementTmp = element;
while (true) {
var value = elementTmp.nodeValue; // Search for keyword in text node
var idx = value.toLowerCase().indexOf(keyword);
if (idx < 0) break;
count++;
elementTmp = document.createTextNode(value.substr(idx+keyword.length));
}
uiWebview_SearchResultCount += count;
var index = uiWebview_SearchResultCount;
while (true) {
var value = element.nodeValue; // Search for keyword in text node
var idx = value.toLowerCase().indexOf(keyword);
if (idx < 0) break; // not found, abort
//we create a SPAN element for every parts of matched keywords
var span = document.createElement("span");
var text = document.createTextNode(value.substr(idx,keyword.length));
var spacetxt = document.createTextNode("\u200D");//\u200D
span.appendChild(text);
span.appendChild(spacetxt);
span.setAttribute("class","uiWebviewHighlight");
span.style.backgroundColor="#007DC8a3";
span.style.borderRadius="3px";
index--;
span.setAttribute("id", "SEARCH WORD"+(index));
//span.setAttribute("id", "SEARCH WORD"+uiWebview_SearchResultCount);
//element.parentNode.setAttribute("id", "SEARCH WORD"+uiWebview_SearchResultCount);
//uiWebview_SearchResultCount++; // update the counter
text = document.createTextNode(value.substr(idx+keyword.length));
element.deleteData(idx, value.length - idx);
var next = element.nextSibling;
//alert(element.parentNode);
element.parentNode.insertBefore(span, next);
element.parentNode.insertBefore(text, next);
element = text;
}
} else if (element.nodeType == 1) { // Element node
if (element.style.display != "none" && element.nodeName.toLowerCase() != 'select') {
for (var i=element.childNodes.length-1; i>=0; i--) {
uiWebview_HighlightAllOccurencesOfStringForElement(element.childNodes[i],keyword);
}
}
}
}
}
// the main entry point to start the search
function uiWebview_HighlightAllOccurencesOfString(keyword) {
uiWebview_RemoveAllHighlights();
uiWebview_HighlightAllOccurencesOfStringForElement(document.body, keyword.toLowerCase());
}
// helper function, recursively removes the highlights in elements and their childs
function uiWebview_RemoveAllHighlightsForElement(element) {
if (element) {
if (element.nodeType == 1) {
if (element.getAttribute("class") == "uiWebviewHighlight") {
var text = element.removeChild(element.firstChild);
element.parentNode.insertBefore(text,element);
element.parentNode.removeChild(element);
return true;
} else {
var normalize = false;
for (var i=element.childNodes.length-1; i>=0; i--) {
if (uiWebview_RemoveAllHighlightsForElement(element.childNodes[i])) {
normalize = true;
}
}
if (normalize) {
element.normalize();
}
}
}
}
return false;
}
// the main entry point to remove the highlights
function uiWebview_RemoveAllHighlights() {
uiWebview_SearchResultCount = 0;
uiWebview_RemoveAllHighlightsForElement(document.body);
}
function uiWebview_ScrollTo(idx) {
var idkNum = uiWebview_SearchResultCount - idx
var scrollTo = document.getElementById("SEARCH WORD" + idkNum);
if (scrollTo) scrollTo.scrollIntoView();
}
and I also found this that actually does exactly what I want (does not disconnect words and is diacritic-insensitive) but it's in JQuery and I couldn't figure out how to implement it in my code.
Instead of using indexOf, you can convert the string to an NSString and then use range(of:options:):
var range = value.range(of: keyword, options: [.caseInsensitive, .diacriticInsensitive])

Set the scroll position to search string in Xamarin.Ios

I am working on a xamarin.ios application to show the content in a text file into webview. I could able to display the content.
Now I need to add search feature, so that the selected string needs to highlighted and SCROLL need to position in to the search text. I am using below Javascript to highlight the searched text and highlighting is working as expected.
string startSearch = "MyApp_HighlightAllOccurencesOfString('" + searchStr + "')";
this.webView.EvaluateJavascript (startSearch);
How can I move the scroll position to the searched string using this webview?
Thanks in advance
Roshil K
You can use the code snippet to reset the scroll position of the WebView, like this:
webView.ScrollView.ContentOffset = new CGPoint(0,50);
But you have to know the (x,y) point of the related string. I am not familiar with Javascript, maybe it can be returned by your JS code.
Also, I found a solution via JS which maybe help you here:https://stackoverflow.com/a/38317775/5474400.
I got is solved by adding the below javascript to my existing "MyApp_HighlightAllOccurencesOfString" javascript.
var desiredHeight = span.offsetTop - 140;
window.scrollTo(0,desiredHeight);
My Complete Javascript is below.
this.webView.EvaluateJavascript ("// We're using a global variable to store the
number of occurrences
var MyApp_SearchResultCount = 0;
// helper function, recursively searches in elements and their child nodes
function MyApp_HighlightAllOccurencesOfStringForElement(element,keyword) {
if (element) {
if (element.nodeType == 3) { // Text node
while (true) {
var value = element.nodeValue; // Search for keyword in text node
var idx = value.toLowerCase().indexOf(keyword);
if (idx < 0) break; // not found, abort
var span = document.createElement(\"span\");
var text = document.createTextNode(value.substr(idx,keyword.length));
span.appendChild(text);
span.setAttribute(\"class\",\"MyAppHighlight\");
span.style.backgroundColor=\"yellow\";
span.style.color=\"black\";
text = document.createTextNode(value.substr(idx+keyword.length));
element.deleteData(idx, value.length - idx);
var next = element.nextSibling;
element.parentNode.insertBefore(span, next);
element.parentNode.insertBefore(text, next);
element = text;
var desiredHeight = span.offsetTop - 140;
window.scrollTo(0,desiredHeight); MyApp_SearchResultCount++;\t// update the counter
}
} else if (element.nodeType == 1) { // Element node
if (element.style.display != \"none\" && element.nodeName.toLowerCase() != 'select') {
for (var i=element.childNodes.length-1; i>=0; i--) {
MyApp_HighlightAllOccurencesOfStringForElement(element.childNodes[i],keyword);
}
}
}
}
}
// the main entry point to start the search
function MyApp_HighlightAllOccurencesOfString(keyword) {
MyApp_RemoveAllHighlights();
MyApp_HighlightAllOccurencesOfStringForElement(document.body, keyword.toLowerCase());
}
// helper function, recursively removes the highlights in elements and their childs
function MyApp_RemoveAllHighlightsForElement(element) {
if (element) {
if (element.nodeType == 1) {
if (element.getAttribute(\"class\") == \"MyAppHighlight\") {
var text = element.removeChild(element.firstChild);
element.parentNode.insertBefore(text,element);
element.parentNode.removeChild(element);
return true;
} else {
var normalize = false;
for (var i=element.childNodes.length-1; i>=0; i--) {
if (MyApp_RemoveAllHighlightsForElement(element.childNodes[i])) {
normalize = true;
}
}
if (normalize) {
element.normalize();
}
}
}
}
return false;
}
// the main entry point to remove the highlights
function MyApp_RemoveAllHighlights() {
MyApp_SearchResultCount = 0;
MyApp_RemoveAllHighlightsForElement(document.body);
}");

JS - Style Change Issue

Having an issue with my script, Its meant to show a button if it gets into the ELSE meaning if the Season Episode name isnt found then show the button, But for some reason it always shows it once I change my tag's value. It's meant to only show if its not a value that can be found.
JS:
function season1episodesChange() {
var s1_episodes = JSON.parse(document.getElementById('s1_episodes').value);
var selectseason1episode = document.getElementById('selectseason1episode');
for (var i = 1; i <= s1_episodes.length; i++){
if (selectseason1episode.value == s1_episodes[i - 1]){
document.getElementById('season1episode' + i).style.display = 'inline-block';
} else {
document.getElementById(['season1episode' + i].join('')).style.display = 'none';
document.getElementById('notuploadedyet').style.display = 'inline-block';
}
}
}
Update:
Tried adding:
document.getElementById('notuploadedyet').style.display = 'none';
In the if {} bit but now it seems to only hide when im on the last value or first value it will open and stay open in all other values.
By the discussion, the logic is:
Loop through the possible options value, then:
Hide all not matched elements.
If no element match, show notuploaded button.
If an element match, show the episode if it exist, otherwise, show notupload button, so the code can be:
function season3episodesChange() {
var s1_episodes = JSON.parse(document.getElementById('s1_episodes').value);
var selectseason3episode = document.getElementById('selectseason3episode');
//Setup the Flag.
var notuploaded = true;
var targetEle;
for (var i = 1; i <= s1_episodes.length; i++){
// Get the element to show first.
targetEle = document.getElementById('season3episode' + i);
// Only check match if the element to match exist
// So even if it match, it won't see notuploaded to false.
if (targetEle !== null) {
if (selectseason3episode.value == s1_episodes[i - 1]){
document.getElementById('season3episode' + i).style.display = 'inline-block';
// Hide the button when : there's exist an element which match the select.
notuploaded = false;
} else {
// Hide the not matched but exist elements.
document.getElementById(['season3episode' + i].join('')).style.display = 'none';
}
}
}
//Using the flag decide to show the notuploaded button or not;
document.getElementById('notuploadedyet').style.display = notuploaded ? 'inline-block' : 'none';
}

Detect markup symbols inside textarea

I have a textarea on the page. And decide validate this element. I insert simple text and in js I check the length this element. But when I delete all text I can save without any problem. I see in firebug and in textarea I find
<ul></ul><br/>
}
// js
for(var i=0; length > i; i++){
value = textElements[i].value;
if(value == "" ||(value == "<br/>" && element.tagName == "TEXTAREA")){
full = false;
emptyElements.push(elements[i]);
} else {
empty = false;
elements[i].style.borderColor = "";
elements[i].style.border = "";
}
}
Ad HTML
<br /><div id="edit-contentFrame-form:editor" style="visibility:hidden"><textarea id="edit-contentFrame-form:editor_input" name="edit-contentFrame-form:editor_input"><h3></h3><h3></h3><h3><br/></h3><ul>
</ul></textarea></div>
I try to find existing way to resolve this problem. But nothing to find.
///EDITED
function isEmptyTextArea(){
var str = document.getElementById('edit-contentFrame-form:editor_input').value;
var regexp = new RegExp("(<+[\w]+>+)*", "g");
var matches_array_tags = str.replace(regexp, '');
if(matches_array_tags.length == 0){
return true;
}
return false;
}
You can use regex to replace and strip all the "markup symbols" tags from the textarea if you don't want people to be able to input html.
var txtarea = document.getElementById('txtarea');
if(escape(txtarea.value) === '')
// textarea is empty
Not including whitespaces:
if(escape(txtarea.value.trim()) === '')
// textarea is empty (not including whitespaces)
I resolve my problem with next js function
function isCompleteTagContent(str){
var re= /<\S[^><]*>/g;
var matches_array_tags = str.replace(re, "");
if(matches_array_tags.trim().length == 0){
return true;
}
return false;
}

Comparing two variables sting array to see if any value matches - javascript jquery

Hi I am trying to compare two arrays to each other and then hide a list element if any of the values match.
One array is tags that are attached to a list item and the other is user input.
I am having trouble as I seem to be able to cross reference one user input work and can't get multiple words against multiple tags.
The amount of user input words might change and the amount of tags might change. I have tried inArray but have had no luck. Any help would be much appreciated. See code below:
function query_searchvar() {
var searchquery=document.navsform.query.value.toLowerCase();
if (searchquery == '') {
alert("No Text Entered");
}
var snospace = searchquery.replace(/\s+/g, ',');
event.preventDefault();
var snospacearray = snospace.split(',');
$('li').each(function() {
var searchtags = $(this).attr('data-searchtags');
//alert(searchtags);
var searcharray = searchtags.split(',');
//alert(searcharray);
var searchtrue=-1;
for(var i = 0, len = searcharray.length; i < len; i++){
if(searcharray[i] == searchquery){
searchtrue = 0;
break;
}
}
if (searchtrue == 0) {
$(this).show("normal");
}
else {
$(this).hide("normal");
}
});
}
Okay so I've tried to implement the code below but have had no luck. It doesn't seem to check through both arrays.
function query_searchvar()
{
var searchquery=document.navsform.query.value.toLowerCase();
if(searchquery == '')
{alert("No Text Entered");
}
var snospace = searchquery.replace(/\s+/g, ' ');
event.preventDefault();
var snospacearray = snospace.split(' ');
alert(snospacearray[1]);
$('li').each(function() {
var searchtags = $(this).attr('data-searchtags');
alert(searchtags);
var searcharray = searchtags.split(' ');
alert(searcharray[0]);
jQuery.each(snospacearray, function(key1,val1){
jQuery.each(searcharray,function(key2,val2){
if(val1 !== val2) {$(this).hide('slow');}
});
});
});
}
Working code:
function query_searchvar()
{
var searchquery=document.navsform.query.value.toLowerCase();
if(searchquery == '')
{alert("No Text Entered");
}
var queryarray = searchquery.split(/,|\s+/);
event.preventDefault();
$('li').each(function() {
var searchtags = $(this).attr('data-searchtags');
//alert(searchtags);
var searcharray = searchtags.split(',');
//alert(searcharray);
var found = false;
for (var i=0; i<searcharray.length; i++)
if ($.inArray(searcharray[i], queryarray)>-1) {
found = true;
break;
}
if (found == true )
{
$(this).show("normal");
}
else {
$(this).hide("normal");
}
});
}
var snospace = searchquery.replace(/\s+/g, ',');
var snospacearray = snospace.split(',');
Note that you can split on regular expressions, so to the above would equal:
var queryarray = searchquery.split(/,|\s+/);
To find whether there is an item contained in both arrays, use the following code:
var found = searcharray.some(function(tag) {
return queryarray.indexOf(tag) > -1;
});
Although this will only work for ES5-compliant browsers :-) To support the others, use
var found = false;
for (var i=0; i<searcharray.length; i++)
if ($.inArray(searcharray[i], queryarray)>-1) {
found = true;
break;
}
In plain js, without jQuery.inArray:
var found = false;
outerloop: for (var i=0; i<searcharray.length; i++)
for (var j=0; j<queryarray.length; j++)
if (searcharray[i] == queryarray[j]) {
found = true;
break outerloop;
}
A little faster algorithm (only needed for really large arrays) would be to sort both arrays before running through them linear.
Here's psuedo code that should solve your problem.
get both arrays
for each item in array 1
for each element in array 2
check if its equal to current element in array 1
if its equal to then hide what you want
An example of this coude wise would be
jQuery.each(array1, function(key1,val1){
jQuery.each(array2,function(key2,val2){
if(val1 == val2) {$(your element to hide).hide();}
});
});
If there's anything you don't understand please ask :)

Categories