I have a bit of javascript code to find and replace text into an image. I then gather the font size of the original text and use that to set the size of the new image.
Problem is, I keep getting the error: Could not convert JavaScript argument arg 0 [nsIDOMWindow.getComputedStyle]
Code:
function findAndReplace(searchText, replacement, searchNode) {
if (!searchText || typeof replacement === 'undefined') {
// Throw error here if you want...
return;
}
var regex = typeof searchText === 'string' ?
new RegExp(searchText, 'g') : searchText,
childNodes = (searchNode || $("body").get(0)).childNodes,
excludes = 'html,head,style,title,link,meta,script,object,iframe';
var cnLength = childNodes.length;
while (cnLength--) {
var currentNode = childNodes[cnLength];
if (currentNode.nodeType === 1 &&
(excludes + ',').indexOf(currentNode.nodeName.toLowerCase() + ',') === -1) {
arguments.callee(searchText, replacement, currentNode);
}
if (currentNode.nodeType !== 3 || !regex.test(currentNode.data) ) {
continue;
}
var parent = currentNode.parentNode;
var frag = (function(){
var html = currentNode.data.replace(regex, replacement);
var wrap = document.createElement('div');
var frag = document.createDocumentFragment();
wrap.innerHTML = html;
while (wrap.firstChild) {
frag.appendChild(wrap.firstChild);
}
console.log(currentNode);
var jQNode = $(currentNode);
console.log("yay");
// var fontSize = jQNode.css('font-size');
if (!currentNode || currentNode == document) currentNode = document.body
var fontSize = getStyle(currentNode, 'font-size');
console.log("tast");
var heightPixels = fontSizeToPixels(fontSize);
$(".InLogo",frag).each(function(){
$(this).css("height", heightPixels+"px");
});
return frag;
})();
parent.insertBefore(frag, currentNode);
parent.removeChild(currentNode);
}
}
function getStyle(el,styleProp) {
var camelize = function (str) {
return str.replace(/\-(\w)/g, function(str, letter){
return letter.toUpperCase();
});
};
if (el.currentStyle) {
return el.currentStyle[camelize(styleProp)];
} else if (document.defaultView && document.defaultView.getComputedStyle) {
return document.defaultView.getComputedStyle(el,null)
.getPropertyValue(styleProp);
} else {
return el.style[camelize(styleProp)];
}
}
The error occurs at this line return document.defaultView.getComputedStyle(el,null).getPropertyValue(styleProp); of getStyle()
something.childNodes includes textNodes as well as Elements, and that's a problem for the getStyle() function.
Nodes don't have a style (Elements do), so who knows what will happen when you feed getStyle something that has .data; a plain Node.
Check for the existence of style to avoid the run-time error:
FIX:
var fontSize = currentNode.style ? getStyle(currentNode, 'font-size') : 0;
Related
I need to store a large json into a cell of a spreadsheet. Since there's a 50k character limit I thought of compressing the json string. I've managed to store the compressed blob as a base64 encoded string, but I'm failing to restore it to the original json. When the readLargeJson function is called I get the following error: "Blob object must have non-null content type for this operation".
The insertLargeJson function below seems to be working correctly.
I tried also storing without the base64 encoding, but didn't help.
function insertLargeJson()
{
var obj = {};
obj["dummy"] = [];
// Just to make the json huge
for (var i = 0; i < 10000; i++)
{
obj["dummy"].push("value");
}
var activeSheet = SpreadsheetApp.getActiveSheet();
var str = JSON.stringify(obj);
var blob = Utilities.newBlob(str, 'application/octet-stream');
var compressedBlob = Utilities.zip([blob]);
var encoded = Utilities.base64Encode(compressedBlob.getDataAsString());
activeSheet.getRange(1, 1).setValue(encoded);
}
function readLargeJson()
{
var activeSheet = SpreadsheetApp.getActiveSheet();
var values = activeSheet.getSheetValues(1, 1, 1, 1);
var value = values[0, 0];
var decoded = Utilities.base64Decode(value);
var blob = Utilities.newBlob(decoded);
var unzipped = Utilities.unzip(blob);
var obj = JSON.parse(unzipped.getDataAsString());
Browser.msgBox('Test Json array size', "" + obj["dummy"].length, Browser.Buttons.OK);
}
I don't necessarily need to use the blob interface to compress the json, any solution that would compress the json string and that would be storable in a cell for later retrieving the original json would work.
It is an interesting concept so I search a bit and get something working.
In your code you have to use compressedBlob.getBytes() to encode data and not compressedBlob.getDataAsString(). To create a blob in your read function you must use bytes in input.
Then in your read function the unzip return an array and before to get the data you have to use the getAs() function. So you must have unzipped[0].getAs('application/octet-stream').getDataAsString() and not unzipped.getDataAsString()
I made a single function to ease testing, so you just have to split it depending your need.
function insertReadLargeJson(){
var obj = {};
obj["dummy"] = [];
// Just to make the json huge
for (var i = 0; i < 10000; i++)
{
obj["dummy"].push("value");
}
// Logger.log(obj)
var activeSheet = SpreadsheetApp.getActiveSheet();
var str = JSON.stringify(obj);
var blob = Utilities.newBlob(str, 'application/octet-stream');
var compressedBlob = Utilities.zip([blob]);
var encoded = Utilities.base64Encode(compressedBlob.getBytes());
activeSheet.getRange(1, 1).setValue(encoded);
var decoded = Utilities.base64Decode(encoded);
var blob = Utilities.newBlob(decoded,'application/zip');
var unzipped = Utilities.unzip(blob);
var obj = JSON.parse(unzipped[0].getAs('application/octet-stream').getDataAsString());
// Logger.log(JSON.stringify(obj))
Logger.log(obj.dummy.length)
// Logger.log('Test Json array size', "" + obj["dummy"].length)
}
I was able to do it with the help of an external library.
I converted the code to be readable by Apps Script:
JSONPACK.gs
var TOKEN_TRUE = -1;
var TOKEN_FALSE = -2;
var TOKEN_NULL = -3;
var TOKEN_EMPTY_STRING = -4;
var TOKEN_UNDEFINED = -5;
function pack(json, options) {
// Canonizes the options
options = options || {};
// A shorthand for debugging
var verbose = options.verbose || false;
verbose && console.log('Normalize the JSON Object');
// JSON as Javascript Object (Not string representation)
json = typeof json === 'string' ? this.JSON.parse(json) : json;
verbose && console.log('Creating a empty dictionary');
// The dictionary
var dictionary = {
strings : [],
integers : [],
floats : []
};
verbose && console.log('Creating the AST');
// The AST
var ast = (function recursiveAstBuilder(item) {
verbose && console.log('Calling recursiveAstBuilder with ' + this.JSON.stringify(item));
// The type of the item
var type = typeof item;
// Case 7: The item is null
if (item === null) {
return {
type : 'null',
index : TOKEN_NULL
};
}
//add undefined
if (typeof item === 'undefined') {
return {
type : 'undefined',
index : TOKEN_UNDEFINED
};
}
// Case 1: The item is Array Object
if ( item instanceof Array) {
// Create a new sub-AST of type Array (#)
var ast = ['#'];
// Add each items
for (var i in item) {
if (!item.hasOwnProperty(i)) continue;
ast.push(recursiveAstBuilder(item[i]));
}
// And return
return ast;
}
// Case 2: The item is Object
if (type === 'object') {
// Create a new sub-AST of type Object ($)
var ast = ['$'];
// Add each items
for (var key in item) {
if (!item.hasOwnProperty(key))
continue;
ast.push(recursiveAstBuilder(key));
ast.push(recursiveAstBuilder(item[key]));
}
// And return
return ast;
}
// Case 3: The item empty string
if (item === '') {
return {
type : 'empty',
index : TOKEN_EMPTY_STRING
};
}
// Case 4: The item is String
if (type === 'string') {
// The index of that word in the dictionary
var index = indexOf.call(dictionary.strings, item);
// If not, add to the dictionary and actualize the index
if (index == -1) {
dictionary.strings.push(encode(item));
index = dictionary.strings.length - 1;
}
// Return the token
return {
type : 'strings',
index : index
};
}
// Case 5: The item is integer
if (type === 'number' && item % 1 === 0) {
// The index of that number in the dictionary
var index = indexOf.call(dictionary.integers, item);
// If not, add to the dictionary and actualize the index
if (index == -1) {
dictionary.integers.push(base10To36(item));
index = dictionary.integers.length - 1;
}
// Return the token
return {
type : 'integers',
index : index
};
}
// Case 6: The item is float
if (type === 'number') {
// The index of that number in the dictionary
var index = indexOf.call(dictionary.floats, item);
// If not, add to the dictionary and actualize the index
if (index == -1) {
// Float not use base 36
dictionary.floats.push(item);
index = dictionary.floats.length - 1;
}
// Return the token
return {
type : 'floats',
index : index
};
}
// Case 7: The item is boolean
if (type === 'boolean') {
return {
type : 'boolean',
index : item ? TOKEN_TRUE : TOKEN_FALSE
};
}
// Default
throw new Error('Unexpected argument of type ' + typeof (item));
})(json);
// A set of shorthands proxies for the length of the dictionaries
var stringLength = dictionary.strings.length;
var integerLength = dictionary.integers.length;
var floatLength = dictionary.floats.length;
verbose && console.log('Parsing the dictionary');
// Create a raw dictionary
var packed = dictionary.strings.join('|');
packed += '^' + dictionary.integers.join('|');
packed += '^' + dictionary.floats.join('|');
verbose && console.log('Parsing the structure');
// And add the structure
packed += '^' + (function recursiveParser(item) {
verbose && console.log('Calling a recursiveParser with ' + this.JSON.stringify(item));
// If the item is Array, then is a object of
// type [object Object] or [object Array]
if ( item instanceof Array) {
// The packed resulting
var packed = item.shift();
for (var i in item) {
if (!item.hasOwnProperty(i))
continue;
packed += recursiveParser(item[i]) + '|';
}
return (packed[packed.length - 1] === '|' ? packed.slice(0, -1) : packed) + ']';
}
// A shorthand proxies
var type = item.type, index = item.index;
if (type === 'strings') {
// Just return the base 36 of index
return base10To36(index);
}
if (type === 'integers') {
// Return a base 36 of index plus stringLength offset
return base10To36(stringLength + index);
}
if (type === 'floats') {
// Return a base 36 of index plus stringLength and integerLength offset
return base10To36(stringLength + integerLength + index);
}
if (type === 'boolean') {
return item.index;
}
if (type === 'null') {
return TOKEN_NULL;
}
if (type === 'undefined') {
return TOKEN_UNDEFINED;
}
if (type === 'empty') {
return TOKEN_EMPTY_STRING;
}
throw new TypeError('The item is alien!');
})(ast);
verbose && console.log('Ending parser');
// If debug, return a internal representation of dictionary and stuff
if (options.debug)
return {
dictionary : dictionary,
ast : ast,
packed : packed
};
return packed;
};
function unpack(packed, options) {
// Canonizes the options
options = options || {};
// A raw buffer
var rawBuffers = packed.split('^');
// Create a dictionary
options.verbose && console.log('Building dictionary');
var dictionary = [];
// Add the strings values
var buffer = rawBuffers[0];
if (buffer !== '') {
buffer = buffer.split('|');
options.verbose && console.log('Parse the strings dictionary');
for (var i=0, n=buffer.length; i<n; i++){
dictionary.push((buffer[i]));
}
}
// Add the integers values
buffer = rawBuffers[1];
if (buffer !== '') {
buffer = buffer.split('|');
options.verbose && console.log('Parse the integers dictionary');
for (var i=0, n=buffer.length; i<n; i++){
dictionary.push(base36To10(buffer[i]));
}
}
// Add the floats values
buffer = rawBuffers[2];
if (buffer !== '') {
buffer = buffer.split('|')
options.verbose && console.log('Parse the floats dictionary');
for (var i=0, n=buffer.length; i<n; i++){
dictionary.push(parseFloat(buffer[i]));
}
}
// Free memory
buffer = null;
options.verbose && console.log('Tokenizing the structure');
// Tokenizer the structure
var number36 = '';
var tokens = [];
var len=rawBuffers[3].length;
for (var i = 0; i < len; i++) {
var symbol = rawBuffers[3].charAt(i);
if (symbol === '|' || symbol === '$' || symbol === '#' || symbol === ']') {
if (number36) {
tokens.push(base36To10(number36));
number36 = '';
}
symbol !== '|' && tokens.push(symbol);
} else {
number36 += symbol;
}
}
// A shorthand proxy for tokens.length
var tokensLength = tokens.length;
// The index of the next token to read
var tokensIndex = 0;
options.verbose && console.log('Starting recursive parser');
return (function recursiveUnpackerParser() {
// Maybe '$' (object) or '#' (array)
var type = tokens[tokensIndex++];
options.verbose && console.log('Reading collection type ' + (type === '$' ? 'object' : 'Array'));
// Parse an array
if (type === '#') {
var node = [];
for (; tokensIndex < tokensLength; tokensIndex++) {
var value = tokens[tokensIndex];
options.verbose && console.log('Read ' + value + ' symbol');
if (value === ']')
return node;
if (value === '#' || value === '$') {
node.push(recursiveUnpackerParser());
} else {
switch(value) {
case TOKEN_TRUE:
node.push(true);
break;
case TOKEN_FALSE:
node.push(false);
break;
case TOKEN_NULL:
node.push(null);
break;
case TOKEN_UNDEFINED:
node.push(undefined);
break;
case TOKEN_EMPTY_STRING:
node.push('');
break;
default:
node.push(dictionary[value]);
}
}
}
options.verbose && console.log('Parsed ' + this.JSON.stringify(node));
return node;
}
// Parse a object
if (type === '$') {
var node = {};
for (; tokensIndex < tokensLength; tokensIndex++) {
var key = tokens[tokensIndex];
if (key === ']')
return node;
if (key === TOKEN_EMPTY_STRING)
key = '';
else
key = dictionary[key];
var value = tokens[++tokensIndex];
if (value === '#' || value === '$') {
node[key] = recursiveUnpackerParser();
} else {
switch(value) {
case TOKEN_TRUE:
node[key] = true;
break;
case TOKEN_FALSE:
node[key] = false;
break;
case TOKEN_NULL:
node[key] = null;
break;
case TOKEN_UNDEFINED:
node[key] = undefined;
break;
case TOKEN_EMPTY_STRING:
node[key] = '';
break;
default:
node[key] = dictionary[value];
}
}
}
options.verbose && console.log('Parsed ' + this.JSON.stringify(node));
return node;
}
throw new TypeError('Bad token ' + type + ' isn\'t a type');
})();
}
function indexOfDictionary (dictionary, value) {
// The type of the value
var type = typeof value;
// If is boolean, return a boolean token
if (type === 'boolean')
return value ? TOKEN_TRUE : TOKEN_FALSE;
// If is null, return a... yes! the null token
if (value === null)
return TOKEN_NULL;
//add undefined
if (typeof value === 'undefined')
return TOKEN_UNDEFINED;
if (value === '') {
return TOKEN_EMPTY_STRING;
}
if (type === 'string') {
value = encode(value);
var index = indexOf.call(dictionary.strings, value);
if (index === -1) {
dictionary.strings.push(value);
index = dictionary.strings.length - 1;
}
}
// If has an invalid JSON type (example a function)
if (type !== 'string' && type !== 'number') {
throw new Error('The type is not a JSON type');
};
if (type === 'string') {// string
value = encode(value);
} else if (value % 1 === 0) {// integer
value = base10To36(value);
} else {// float
}
// If is number, "serialize" the value
value = type === 'number' ? base10To36(value) : encode(value);
// Retrieve the index of that value in the dictionary
var index = indexOf.call(dictionary[type], value);
// If that value is not in the dictionary
if (index === -1) {
// Push the value
dictionary[type].push(value);
// And return their index
index = dictionary[type].length - 1;
}
// If the type is a number, then add the '+' prefix character
// to differentiate that they is a number index. If not, then
// just return a 36-based representation of the index
return type === 'number' ? '+' + index : index;
};
function encode(str) {
if ( typeof str !== 'string')
return str;
return str.replace(/[\+ \|\^\%]/g, function(a) {
return ({
' ' : '+',
'+' : '%2B',
'|' : '%7C',
'^' : '%5E',
'%' : '%25'
})[a]
});
};
function decode(str) {
if ( typeof str !== 'string')
return str;
return str.replace(/\+|%2B|%7C|%5E|%25/g, function(a) {
return ({
'+' : ' ',
'%2B' : '+',
'%7C' : '|',
'%5E' : '^',
'%25' : '%'
})[a]
})
};
function base10To36(number) {
return Number.prototype.toString.call(number, 36).toUpperCase();
};
function base36To10(number) {
return parseInt(number, 36);
};
function indexOf(obj, start) {
for (var i = (start || 0), j = this.length; i < j; i++) {
if (this[i] === obj) {
return i;
}
}
return -1;
};
With that, your script can now be:
Code.gs
function insertLargeJson()
{
var obj = {};
obj["dummy"] = [];
// Just to make the json huge
for (var i = 0; i < 10000; i++)
{
obj["dummy"].push("value");
}
var activeSheet = SpreadsheetApp.getActiveSheet();
var packed = pack(obj);
var value = JSON.stringify(packed);
activeSheet.getRange(1, 1).setValue(value);
}
function readLargeJson()
{
var activeSheet = SpreadsheetApp.getActiveSheet();
var value = activeSheet.getRange(1,1).getValue();
var packed = JSON.parse(value);
var obj = unpack(packed);
Browser.msgBox('Test Json array size', "" + obj["dummy"].length, Browser.Buttons.OK);
}
Hope this helps!
I am currently using the code below to detect if a URL is pasted into a contenteditable div. If a URL is pasted, it will automatically be converted into a link (surrounded by a tags).
How would I change this so that if the user pastes an image URL, it would be converted to <img src="https://example.com/image.jpg"> whilst also converting non-image URL's to standard links (surrounded by a tags).
var saveSelection, restoreSelection;
if (window.getSelection && document.createRange) {
saveSelection = function(containerEl) {
var range = window.getSelection().getRangeAt(0);
var preSelectionRange = range.cloneRange();
preSelectionRange.selectNodeContents(containerEl);
preSelectionRange.setEnd(range.startContainer, range.startOffset);
var start = preSelectionRange.toString().length;
return {
start: start,
end: start + range.toString().length
}
};
} else if (document.selection) {
}
function createLink(matchedTextNode) {
var el = document.createElement("a");
el.href = matchedTextNode.data;
el.appendChild(matchedTextNode);
return el;
}
function shouldLinkifyContents(el) {
return el.tagName != "A";
}
function surroundInElement(el, regex, surrounderCreateFunc, shouldSurroundFunc) {
var child = el.lastChild;
while (child) {
if (child.nodeType == 1 && shouldSurroundFunc(el)) {
surroundInElement(child, regex, createLink, shouldSurroundFunc);
} else if (child.nodeType == 3) {
surroundMatchingText(child, regex, surrounderCreateFunc);
}
child = child.previousSibling;
}
}
function surroundMatchingText(textNode, regex, surrounderCreateFunc) {
var parent = textNode.parentNode;
var result, surroundingNode, matchedTextNode, matchLength, matchedText;
while ( textNode && (result = regex.exec(textNode.data)) ) {
matchedTextNode = textNode.splitText(result.index);
matchedText = result[0];
matchLength = matchedText.length;
textNode = (matchedTextNode.length > matchLength) ?
matchedTextNode.splitText(matchLength) : null;
surroundingNode = surrounderCreateFunc(matchedTextNode.cloneNode(true));
parent.insertBefore(surroundingNode, matchedTextNode);
parent.removeChild(matchedTextNode);
}
}
var textbox = $('.editable')[0];
var urlRegex = /http(s?):\/\/($|[^\s]+)/;
function updateLinks() {
var savedSelection = saveSelection(textbox);
surroundInElement(textbox, urlRegex, createLink, shouldLinkifyContents);
restoreSelection(textbox, savedSelection);
}
var $textbox = $(textbox);
$(document).ready(function () {
$textbox.focus();
var keyTimer = null, keyDelay = 1000;
$textbox.keyup(function() {
if (keyTimer) {
window.clearTimeout(keyTimer);
}
keyTimer = window.setTimeout(function() {
updateLinks();
keyTimer = null;
}, keyDelay);
});
});
Did you try to parse the pasted url, and search for ending extension (jpg,gif,png) ?
It should be simple, if the ending is matching one of those, then you wrap the url into an href propriety.
Did You wrote this code by yourself?
Here you can read about strings methods to do this:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String
I wrote the following function to linearize the text content of an arbitrary string of HTML:
html2text(html) {
function _html2text(element, accum) {
return Array.prototype.slice.call(element.childNodes).reduce((accum, node) => {
return (node.nodeType === 3)
? `${accum} ${node.textContent}`
: _html2text(node, accum);
}, accum);
}
const div = document.createElement('div');
div.innerHTML = html;
return _html2text(div, '');
}
But now I am not able to transform it into a trail-recusive style so that it can be optimized.
My problem is that the recursion recurs within the reduction.
I will write this out as loops, but it's been killing me that I've not been able tail recurse this.
Here is my loop version:
function html2text(html) {
var div = document.createElement('div');
div.innerHTML = html;
var accum = '';
var stack = [];
stack.push([div, 0]);
while (stack.length !== 0) {
var frame = stack.pop();
var el = frame[0];
var i = frame[1];
for (; i < el.childNodes.length; i++) {
var node = el.childNodes[i];
if (node.nodeType === Node.ELEMENT_NODE) {
stack.push([el, i+1]);
stack.push([node, 0]);
break;
} else if (node.nodeType === Node.TEXT_NODE) {
accum += ' ' + node.textContent;
}
}
}
return accum;
}
Here's the function above written with a recursive tail call, passing the stack:
function html2text(html) {
function recurse(stack, accum) {
if (!stack.length) {
return accum;
}
var frame = stack.pop();
var el = frame[0];
var i = frame[1];
for (; i < el.childNodes.length; i++) {
var node = el.childNodes[i];
if (node.nodeType === Node.ELEMENT_NODE) {
stack.push([el, i+1]);
stack.push([node, 0]);
break;
} else if (node.nodeType === Node.TEXT_NODE) {
accum += ' ' + node.textContent;
}
}
return recurse(stack, accum)
}
var div = document.createElement('div');
div.innerHTML = html;
var stack = [];
stack.push([div, 0]);
return recurse(stack, '');
}
The inner loop can be converted to another recursion, but without using immutable datastructures none of this really makes too much sense for me.
function html2text(current, text = "") {
"use strict";
if (current === null) return text;
var nextNode = current.nextSibling || current.parentNode.nextSibling,
more = current.nodeType === 3 ? current.textContent : "";
return html2text(nextNode, text + more);
}
You were right, you can do the concatenation before the tail call. I think this fits all the criteria, but ES6 is new, so please do double check.
I have a function that preprocesses a Text Node in the HTML DOM.
The purpose is essentially to perform some interpolation or string templating.
The function basically checks for occurrences that match the regular expressions /\${([^}]*)}/g and /{{([^}]*)}/g.
Examples: ${foo + 1} and {{foo + 2}}
That is all working.
But my goal is to replace these occurrences with new nodes (e.g. span) consisting of Knockout binding expressions containing the inner values of the matches from the regular expression. With positions being correct. Preserving whitespaces where they occur.
Like so:
<span ko-text="foo" /> for ${foo} (Note: Custom binding syntax)
I just cant wrap my head around it with TextNode.splitText.
How do I achieve this?
This is the code I've got so far:
preprocessNode(node: Element) {
if ("nodeValue" in node && node.nodeValue !== null) {
var value = node.nodeValue;
var match = value.matchAll(/\${([^}]*)}/g);
if (!match) {
match = value.matchAll(/{{([^}]*)}/g);
}
if (match !== null && match.length > 0) {
var parentNode = node.parentNode;
for (let entry of match) {
var offset = node.nodeValue.indexOf(entry[0]);
var oldNode = node;
node = node.splitText(offset);
var newNode = document.createElement("span");
newNode.setAttribute("ko-text", entry[1]);
node.parentNode.appendChild(newNode);
}
return [parentNode, parentNode];
}
}
return null;
}
The function matchAll is a custom function.
Disclaimer: I don't know JavaScript! This is just a suggestion.
# /^\$\{([^{}]+)\}|\{\{([^{}]+)\}\}|((?:(?!^\$\{[^{}]+\}|\{\{[^{}]+\}\})[\S\s])+)/
^ \$\{
( [^{}]+ ) # (1)
\}
|
\{\{
( [^{}]+ ) # (2)
\}\}
|
( # (3 start)
(?:
(?!
^ \$\{ [^{}]+ \}
| \{\{ [^{}]+ \}\}
)
[\S\s]
)+
) # (3 end)
Pseudo-code:
if ( regex_find ( /^\$\{[^{}]+\}|\{\{[^{}]+\}\}/, value ) )
{
var found = false;
while ( regex_search ( /^\$\{([^{}]+)\}|\{\{([^{}]+)\}\}|((?:(?!^\$\{[^{}]+\}|\{\{[^{}]+\}\})[\S\s])+)/g, match, value ) )
{
if ( match[1] != null )
{
// Found '${..}' form
found = true;
var newNode = document.createElement("span");
newNode.setAttribute("ko-text", match[1]);
// Append newNode
}
else
if ( match[2] != null )
{
// Found '{{..}}' form
found = true;
var newNode = document.createElement("span");
newNode.setAttribute("ko-text", match[2]);
// Append newNode
}
else
if ( match[3] != null )
{
// Found '...' normal text
found = true;
var newNode = document.createTextNode( match[3] );
// Append newNode
}
}
if ( found )
{
// Clear or delete the original text node
// ...
}
}
After many considerations, this is how I solved it:
if (match !== null && match.length > 0) {
var parentNode = node.parentNode;
var nodes = [];
var textString = value;
var i = 0;
for (let entry of match) {
var startOffset = node.nodeValue.indexOf(entry[0]);
var endOffset = startOffset + entry[0].length;
var length = startOffset - i;
if (length > 0) {
var str = textString.substr(i, length);
var textNode = document.createTextNode(str);
parentNode.insertBefore(textNode, node);
}
var newNode2 = document.createElement("span");
newNode2.setAttribute("ko-text", entry[1]);
parentNode.insertBefore(newNode2, node);
nodes.push(newNode2);
i = endOffset;
}
var length = textString.length - i;
if (length > 0) {
var str = textString.substr(i, length);
var textNode = document.createTextNode(str);
parentNode.insertBefore(textNode, node);
}
parentNode.removeChild(node);
return nodes;
}
It can certainly be improved.
var textNodesWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
var node;
while (node = textNodesWalker.nextNode()) {
var newNodes = [];
var execResult = MY_REG_EXP.exec(node.nodeValue);
var start = 0;
while (execResult) {
newNodes.push(document.createTextNode(node.nodeValue.substring(start, execResult.index)));
var generatedElement = ...; // create element (document.createElement) based on execResult
newNodes.push(generatedElement);
start = execResult.index + execResult[0].length;
execResult = MY_REG_EXP.exec(node.nodeValue);
}
if (newNodes.length == 0) {
continue;
}
newNodes.push(document.createTextNode(node.nodeValue.substring(start, node.nodeValue.length)));
for (var i=0; i<newNodes.length; i++) {
node.parentNode.insertBefore(newNodes[i], node)
}
node.parentNode.removeChild(node);
}
Let's assume we have pattern /asdf/g than need to be made bold. My code will transform following html:
<p>Begin asdf End</p>
to
<p>Begin <b>asdf</b> End</p>
I'm making a code that removes a videoplayer from the page and then places it back when needed (even if the element doesn't have an id).
I'm finding issues with IE7
Here is my code:
var weboElem, weboElemPar, weboElemIndex, weboStored;
function weboRemoveVideoplayer(vpId){
weboElem = document.getElementById(vpId);
if(!weboElem) return false;
weboElemPar = weboElem.parentNode;
weboElemIndex = 0;
var child = weboElem;
while( (child = child.previousSibling) != null )
weboElemIndex++;
weboElemPar.removeChild(weboElem);
return true;
}
function weboPlaceVideoplayerBack(){
if(weboElemPar.insertBefore !== undefined && weboElemPar.childNodes !== undefined)
{
weboElemPar.insertBefore(weboElem, weboElemPar.childNodes[weboElemIndex]);
return true;
}
return false;
}
var result = document.evaluate(
'//*/param[contains(#value, "autoplay=1")]/..', // XPath expression
document, // context node
null, // namespace resolver
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE
);
if(result.snapshotLength > 0)
{
var node = result.snapshotItem(0);
node.id = "webo";
document.getElementById('info').innerHTML = node.nodeName.toLowerCase()+" -> "+node.id;
} else document.getElementById('info').innerHTML = "not found";
(Note that document.evaluate WORKS because I imported javascript-xpath library)
On IE7 if the XPath finds an IFRAME there are no problems and it works but if it finds an OBJECT does nothing and stops at weboElem = document.getElementById(vpId); as if it didn't find the id.
I tried modifying the code like this:
if(result.snapshotLength > 0)
{
var node = result.snapshotItem(0);
node.id = "webo";
node.parentNode.removeChild(node);
document.getElementById('info').innerHTML = node.nodeName.toLowerCase()+" -> "+node.id;
if(node.nodeName.toLowerCase() == "object") weboStored = node;
else weboStored = null;
} else document.getElementById('info').innerHTML = "not found";
and it works, the videoplayer disappears at page load. I want to use the function though, so I edited everything like this (storing the node into a global var that later I get in the weboRemoveVideoplayer function):
var weboElem, weboElemPar, weboElemIndex, weboStored;
function weboRemoveVideoplayer(vpId){
if(!weboStored) weboElem = document.getElementById(vpId);
else weboElem = weboStored;
if(!weboElem) return false;
weboElemPar = weboElem.parentNode;
weboElemIndex = 0;
var child = weboElem;
while( (child = child.previousSibling) != null )
weboElemIndex++;
weboElemPar.removeChild(weboElem);
alert("5");
return true;
}
function weboPlaceVideoplayerBack(){
if(weboElemPar.insertBefore !== undefined && weboElemPar.childNodes !== undefined)
{
weboElemPar.insertBefore(weboElem, weboElemPar.childNodes[weboElemIndex]);
return true;
}
return false;
}
// bind XPath methods to document and window objects
// NOTE: This will overwrite native XPath implementation if it exists
//XPathJS.bindDomLevel3XPath(); //solo per xpathJs
var result = document.evaluate(
'//*/param[contains(#value, "autoplay=1")]/..', // XPath expression
document, // context node
null, // namespace resolver
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE
);
if(result.snapshotLength > 0)
{
var node = result.snapshotItem(0);
node.id = "webo";
node.parentNode.removeChild(node);
document.getElementById('info').innerHTML = node.nodeName.toLowerCase()+" -> "+node.id;
if(node.nodeName.toLowerCase() == "object") weboStored = node;
else weboStored = null;
} else document.getElementById('info').innerHTML = "not found";
This way the code blocks itself when trying to retrieve the parent node.
Could someone suggest me what to do here?
PS: with chrome and firefox the code works perfectly in the first version I posted.
Fixed it!
I solved the issue by wrapping the OBJECT inside a div with an id of my choice which I can retrieve whenever I want. I do this in the resolveXpath function.
Here the code:
var weboElem, weboElemPar, ieObject = false;
var weboElemIndex = 0;
function weboRemoveVideoplayer(vpId){
var child;
if(!ieObject) weboElem = document.getElementById(vpId);
else weboElem = document.getElementById('my_usage');
if(!weboElem) return false;
weboElemPar = weboElem.parentNode;
weboElemIndex = 0;
child = weboElem;
while( (child = child.previousSibling) != null ) weboElemIndex++;
if(typeof weboElemPar.removeChild !== 'undefined') weboElemPar.removeChild(weboElem);
else return false;
return true;
}
function weboPlaceVideoplayerBack(){
if(typeof weboElemPar.insertBefore !== 'undefined' && typeof weboElemPar.childNodes !== 'undefined' && typeof weboElemPar.appendChild !== 'undefined'){
if(weboElemPar.childNodes.length > 0 && weboElemIndex < weboElemPar.childNodes.length) weboElemPar.insertBefore(weboElem, weboElemPar.childNodes[weboElemIndex]);
else weboElemPar.appendChild(weboElem);
return true;
}
return false;
}
function resolveXpath(path)
{
//XPathJS.bindDomLevel3XPath(); //solo per xpathJs
var result = document.evaluate(path,document,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
if(result.snapshotLength > 0){
var child, node = result.snapshotItem(0);
if(node.nodeName.toLowerCase() == 'object'){
ieObject = true;
child = node;
while( (child = child.previousSibling) != null ) weboElemIndex++;
var div = document.createElement('div');
div.id = 'my_usage';
if(typeof node.parentNode.insertBefore !== 'undefined' && typeof node.parentNode.childNodes !== 'undefined' && typeof node.parentNode.appendChild !== 'undefined'){
if(node.parentNode.childNodes.length > 0 && weboElemIndex < node.parentNode.childNodes.length) node.parentNode.insertBefore(div,node.parentNode.childNodes[weboElemIndex]);
else node.parentNode.appendChild(div);
div.appendChild(node);
} else return false;
} else node.id = 'my_usage';
return true;
} else return false;
}
resolveXpath('//*/param[contains(#src, "autoplay=1")]/..');