I recently learned in the chat section that if you use a bookmark you can render LaTeX:
http://meta.math.stackexchange.com/a/3297
The stackexchange sites all render code like this. Anything in between `` gets rendered as code.
I find this feature quite nice and useful. I was wondering if anyone knows how to get the javascript that does this in the stackexchange sites and put it as a bookmark just like the mathjax bookmarks
found here:
http://www.math.ucla.edu/~robjohn/math/mathjax.html
That way if I write something with the backtick escapes say in facebook or any other site I could just click on my bookmark that says renderCode and the javascript will do what the same as it is doing in this site.
Anyone know if this has been asked before and/or how to achieve this? Thanks
Here's a start for you:
var replacer = function(s, match) {return "<code>" + match + "</code>";};
"some `delimited` code `sections` here". replace(/`([^`]*)`/g, replacer);
// ==> "some <code>delimited</code> code <code>sections</code> here"
You can use whatever markup you like in place of "< code >" and "< /code >" to create the effect you like.
You could also use a function like this one:
processTextNodes = function(element, fn) {
var children = element.childNodes;
for (var i = 0; i < children.length; i++) {
var node = children[i];
var type = node.nodeType;
if (type == 1) processTextNodes(node, fn);
if (type == 3) fn.call(this, node);
}
}
Like this:
processTextNodes(someElement, function(node) {
node.value = node.value.replace(/`([^`]*)`/g, replacer);
});
In order to apply this to all the text nodes inside an element.
You would still have to turn this into a bookmark, and figure out how to find the right element. And you'll need the markup and CSS to display the output as you like it. But this might be a good start.
Related
I've been working on creating an HTML parser and formatter, and I've just added a feature to optionally render whitespace visible, by replacing spaces with · (middle dot) characters, adding arrows for tabs and newlines, etc.
The full in-progress source code is here: https://github.com/kshetline/html-parser, and the most relevant file that's doing the CSS styling of HTML is here: https://github.com/kshetline/html-parser/blob/master/src/stylizer.ts.
While it's nice to be able to visualize whitespace when you want to, you wouldn't want spaces to be turned into middle dot characters if you select and copy the text. But that's just what happens, at least without some intervention.
I have found a crude way to fix the problem with a bit of JavaScript, which I've put into a Code Pen here: https://codepen.io/kshetline/pen/NWKYZJg.
document.body.addEventListener('copy', (event) => {
let selection = document.getSelection().toString();
selection = selection.replace(/·|↵\n|↵/g, ch => ch === '·' ? ' ' : '\n');
event.clipboardData.setData('text/plain', selection);
event.preventDefault();
});
I'm wondering, however, if there's a better way to do this.
My first choice would be something that didn't rely on JavaScript at all, like if there's some way via CSS or perhaps some accessibility-related HTML attribute that would essentially say, "this is the real text that should be copied, not what you see on the screen".
My second choice would be if someone can point me to more detailed documentation of the JavaScript clipboard feature than I've been able to find, because if I have to rely on JavaScript, I'd at least like my JavaScript to be smarter. The quick-and-dirty solution turns every middle dot character into a space, even if it was truly supposed to be a middle dot in the first place.
Is there enough info in the clipboard object to figure out which of the selected text has what CSS styling, so I could know to convert only the text that's inside <span>s which have my whitespace class, and still also find the rest of the non-whitespace text, in proper order, to piece it all back together again?
I still couldn't find much documentation on how selection objects work, but I played around with them in the web console, and eventually figured out enough to get by.
This is the JavaScript I came up with:
function restoreWhitespaceStrict(s) {
return s.replace(/·|[\u2400-\u241F]|\S/g, ch => ch === '·' ? ' ' :
ch.charCodeAt(0) >= 0x2400 ? String.fromCharCode(ch.charCodeAt(0) - 0x2400) : '');
}
const wsReplacements = {
'·': ' ',
'→\t': '\t',
'↵\n': '\n',
'␍\r': '\r',
'␍↵\r\n': '\r\n'
}
function restoreWhitespace(s) {
return s.replace(/·|→\t|↵\n|␍\r|␍↵\r\n|→|↵|␍|[\u2400-\u241F]/g, ws =>
wsReplacements[ws] || (ws.charCodeAt(0) >= 0x2400 ? String.fromCharCode(ws.charCodeAt(0) - 0x2400) : ''));
}
document.body.addEventListener('copy', (event) => {
const selection = document.getSelection();
let newSelection;
let copied = false;
if (selection.anchorNode && selection.getRangeAt) {
try {
const nodes = selection.getRangeAt(0).cloneContents().childNodes;
let parts = [];
// nodes isn't a "real" array - no forEach!
for (let i = 0; i < nodes.length; ++i) {
const node = nodes[i];
if (node.classList && node.classList.contains('whitespace'))
parts.push(restoreWhitespaceStrict(node.innerText));
else if (node.localName === 'span')
parts.push(node.innerText);
else
parts.push(node.nodeValue);
}
newSelection = parts.join('');
copied = true;
}
catch (err) {}
}
if (!copied)
newSelection = restoreWhitespace(selection.toString());
event.clipboardData.setData('text/plain', newSelection);
event.preventDefault();
});
I've tried this on three browsers (Chrome, Firefox, and Safari), and it's working on all of them, but I still took the precaution of both testing for the presence of some of the expected object properties, and then also using try/catch, just in case I hit an incompatible browser, in which case the not-so-smart version of fixing the clipboard takes over.
It looks like the selection is handled as a list of regular DOM nodes. Chrome's selection object has both an anchorNode and an extentNode to mark the start and end of the selection, but Firefox only has the anchorNode (I didn't check Safari for extentNode). I couldn't find any way to get the full list of nodes directly, however. I could only get the full list using the cloneContents() method. The first and last nodes obtained this way are altered from the original start and end nodes by being limited to the portion of the text content that was selected in each node.
If i had a string:
hey user, what are you doing?
How, with regex could I say: look for user, but not inside of < or > characters? So the match would grab the user between the <a></a> but not the one inside of the href
I'd like this to work for any tag, so it wont matter what tags.
== Update ==
Why i can't use .text() or innerText is because this is being used to highlight results much like the native cmd/ctrl+f functionality in browsers and I dont want to lose formatting. For example, if i search for strong here:
Some <strong>strong</strong> text.
If i use .text() itll return "Some strong text" and then I'll wrap strong with a <span> which has a class for styling, but now when I go back and try to insert this into the DOM it'll be missing the <strong> tags.
If you plan to replace the HTML using html() again then you will loose all event handlers that might be bound to inner elements and their data (as I said in my comment).
Whenever you set the content of an element as HTML string, you are creating new elements.
It might be better to recursively apply this function to every text node only. Something like:
$.fn.highlight = function(word) {
var pattern = new RegExp(word, 'g'),
repl = '<span class="high">' + word + '</span>';
this.each(function() {
$(this).contents().each(function() {
if(this.nodeType === 3 && pattern.test(this.nodeValue)) {
$(this).replaceWith(this.nodeValue.replace(pattern, repl));
}
else if(!$(this).hasClass('high')) {
$(this).highlight(word);
}
});
});
return this;
};
DEMO
It could very well be that this is not very efficient though.
To emulate Ctrl-F (which I assume is what you're doing), you can use window.find for Firefox, Chrome, and Safari and TextRange.findText for IE.
You should use a feature detect to choose which method you use:
function highlightText(str) {
if (window.find)
window.find(str);
else if (window.TextRange && window.TextRange.prototype.findText) {
var bodyRange = document.body.createTextRange();
bodyRange.findText(str);
bodyRange.select();
}
}
Then, after you the text is selected, you can style the selection with CSS using the ::selection selector.
Edit: To search within a certain DOM object, you could use a roundabout method: use window.find and see whether the selection is in a certain element. (Perhaps say s = window.getSelection().anchorNode and compare s.parentNode == obj, s.parentNode.parentNode == obj, etc.). If it's not in the correct element, repeat the process. IE is a lot easier: instead of document.body.createTextRange(), you can use obj.createTextRange().
$("body > *").each(function (index, element) {
var parts = $(element).text().split("needle");
if (parts.length > 1)
$(element).html(parts.join('<span class="highlight">needle</span>'));
});
jsbin demo
at this point it's evolving to be more and more like Felix's, so I think he's got the winner
original:
If you're doing this in javascript, you already have a handy parsed version of the web page in the DOM.
// gives "user"
alert(document.getElementById('user').innerHTML);
or with jQuery you can do lots of nice shortcuts:
alert($('#user').html()); // same as above
$("a").each(function (index, element) {
alert(element.innerHTML); // shows label text of every link in page
});
I like regexes, but because tags can be nested, you will have to use a parser. I recommend http://simplehtmldom.sourceforge.net/ it is really powerful and easy to use. If you have wellformed xhtml you can also use SimpleXML from php.
edit: Didn't see the javascript tag.
Try this:
/[(<.+>)(^<)]*user[(^>)(<.*>)]/
It means:
Before the keyword, you can have as many <...> or non-<.
Samewise after it.
EDIT:
The correct one would be:
/((<.+>)|(^<))*user((^>)|(<.*>))*/
Here is what works, I tried it on your JS Bin:
var s = 'hey user, what are you doing?';
s = s.replace(/(<[^>]*)user([^<]>)/g,'$1NEVER_WRITE_THAT_ANYWHERE_ELSE$2');
s = s.replace(/user/g,'Mr Smith');
s = s.replace(/NEVER_WRITE_THAT_ANYWHERE_ELSE/g,'user');
document.body.innerHTML = s;
It may be a tiny little bit complicated, but it works!
Explanation:
You replace "user" that is in the tag (which is easy to find) with a random string of your choice that you must never use again... ever. A good use would be to replace it with its hashcode (md5, sha-1, ...)
Replace every remaining occurence of "user" with the text you want.
Replace back your unique string with "user".
this code will strip all tags from sting
var s = 'hey user, what are you doing?';
s = s.replace(/<[^<>]+>/g,'');
I'm making a highlighting plugin for a client to find things in a page and I decided to test it with a help viewer im still building but I'm having an issue that'll (probably) require some regex.
I do not want to parse HTML, and im totally open on how to do this differently, this just seems like the the best/right way.
http://oscargodson.com/labs/help-viewer
http://oscargodson.com/labs/help-viewer/js/jquery.jhighlight.js
Type something in the search... ok, refresh the page, now type, like, class or class=" or type <a you'll notice it'll search the actual HTML (as expected). How can I only search the text?
If i do .text() it'll vaporize all the HTML and what i get back will just be a big blob of text, but i still want the HTML so I dont lose formatting, links, images, etc. I want this to work like CMD/CTRL+F.
You'd use this plugin like:
$('article').jhighlight({find:'class'});
To remove them:
.jhighlight('remove')
==UPDATE==
While Mike Samuel's idea below does in fact work, it's a tad heavy for this plugin. It's mainly for a client looking to erase bad words and/or MS Word characters during a "publishing" process of a form. I'm looking for a more lightweight fix, any ideas?
You really don't want to use eval, mess with innerHTML or parse the markup "manually". The best way, in my opinion, is to deal with text nodes directly and keep a cache of the original html to erase the highlights. Quick rewrite, with comments:
(function($){
$.fn.jhighlight = function(opt) {
var options = $.extend($.fn.jhighlight.defaults, opt)
, txtProp = this[0].textContent ? 'textContent' : 'innerText';
if ($.trim(options.find.length) < 1) return this;
return this.each(function(){
var self = $(this);
// use a cache to clear the highlights
if (!self.data('htmlCache'))
self.data('htmlCache', self.html());
if(opt === 'remove'){
return self.html( self.data('htmlCache') );
}
// create Tree Walker
// https://developer.mozilla.org/en/DOM/treeWalker
var walker = document.createTreeWalker(
this, // walk only on target element
NodeFilter.SHOW_TEXT,
null,
false
);
var node
, matches
, flags = 'g' + (!options.caseSensitive ? 'i' : '')
, exp = new RegExp('('+options.find+')', flags) // capturing
, expSplit = new RegExp(options.find, flags) // no capturing
, highlights = [];
// walk this wayy
// and save matched nodes for later
while(node = walker.nextNode()){
if (matches = node.nodeValue.match(exp)){
highlights.push([node, matches]);
}
}
// must replace stuff after the walker is finished
// otherwise replacing a node will halt the walker
for(var nn=0,hln=highlights.length; nn<hln; nn++){
var node = highlights[nn][0]
, matches = highlights[nn][1]
, parts = node.nodeValue.split(expSplit) // split on matches
, frag = document.createDocumentFragment(); // temporary holder
// add text + highlighted parts in between
// like a .join() but with elements :)
for(var i=0,ln=parts.length; i<ln; i++){
// non-highlighted text
if (parts[i].length)
frag.appendChild(document.createTextNode(parts[i]));
// highlighted text
// skip last iteration
if (i < ln-1){
var h = document.createElement('span');
h.className = options.className;
h[txtProp] = matches[i];
frag.appendChild(h);
}
}
// replace the original text node
node.parentNode.replaceChild(frag, node);
};
});
};
$.fn.jhighlight.defaults = {
find:'',
className:'jhighlight',
color:'#FFF77B',
caseSensitive:false,
wrappingTag:'span'
};
})(jQuery);
If you're doing any manipulation on the page, you might want to replace the caching with another clean-up mechanism, not trivial though.
You can see the code working here: http://jsbin.com/anace5/2/
You also need to add display:block to your new html elements, the layout is broken on a few browsers.
In the javascript code prettifier, I had this problem. I wanted to search the text but preserve tags.
What I did was start with HTML, and decompose that into two bits.
The text content
Pairs of (index into text content where a tag occurs, the tag content)
So given
Lorem <b>ipsum</b>
I end up with
text = 'Lorem ipsum'
tags = [6, '<b>', 10, '</b>']
which allows me to search on the text, and then based on the result start and end indices, produce HTML including only the tags (and only balanced tags) in that range.
Have a look here: getElementsByTagName() equivalent for textNodes.
You can probably adapt one of the proposed solutions to your needs (i.e. iterate over all text nodes, replacing the words as you go - this won't work in cases such as <tag>wo</tag>rd but it's better than nothing, I guess).
I believe you could just do:
$('#article :not(:has(*))').jhighlight({find : 'class'});
Since it grabs all leaf nodes in the article it would require valid xhtml, that is, it would only match link in the following example:
<p>This is some paragraph content with a link</p>
DOM traversal / selector application could slow things down a bit so it might be good to do:
article_nodes = article_nodes || $('#article :not(:has(*))');
article_nodes.jhighlight({find : 'class'});
May be something like that could be helpful
>+[^<]*?(s(<[\s\S]*?>)?e(<[\s\S]*?>)?e)[^>]*?<+
The first part >+[^<]*? finds > of the last preceding tag
The third part [^>]*?<+ finds < of the first subsequent tag
In the middle we have (<[\s\S]*?>)? between characters of our search phrase (in this case - "see").
After regular expression searching you could use the result of the middle part to highlight search phrase for user.
Greetings!
Is it possible to convert an HTML string to an array or JSON using Javascript?
Something like this:
var stringweb = '<html><head>hi</head><body>my body</body></html>';
And as result, I can have this:
var myarray = {[html,
[head,
[hi]
]
[etc...]
]}
Thanks in advance! :)
As you can tell from the comments above, this doesn't seem like the most robust idea... Anyhow, here is a solution that I think gets you what you asked for. It was fun to write, anyhow.
function htmlStringToArray(str) {
var temp = document.createElement('iframe');
temp.style.display = "none";
document.body.appendChild(temp);
var doc = temp.contentWindow.document;
doc.open();
doc.write(str);
doc.close();
var array = htmlNodeToArray(doc.documentElement);
temp.parentNode.removeChild(temp);
return array;
}
function htmlNodeToArray(node) {
if (node.nodeType == 1) {
var array = [node.tagName];
if (node.childNodes.length) {
for (var i=0, child; child = node.childNodes[i]; i++) {
if (child.nodeType == 1 || child.nodeType == 3) {
array.push(htmlNodeToArray(child));
}
}
} else if (node.innerText) {
array.push([node.innerText]);
}
return array;
} else if (node.nodeType == 3) {
return [node.nodeValue];
}
}
I tried it out in the latest chrome, firefox and IE. Here it is running on jsbin: http://jsbin.com/uqize3/7/edit
BTW your HTML string is invalid. Browsers will move "hi" from inside the <head> into the <body>. I assumed you intended to have a <title> in there.
You can do that in JavaScript, because JavaScript is a sufficiently expressive language as to allow just about anything. However, it's not going to be particularly easy: you're going to have to implement (or find) as complete an HTML parser as is necessary to recognize the particular HTML documents that you want to convert. HTML itself is pretty complicated, and that complexity is greatly magnified by the fact that most of the world's stock of existing HTML documents are badly erroneous. Thus, if you've got well-constrained HTML that you know to be valid, or at least consistently invalid, that might make the task a little easier.
edit — #Hemlock points out, quite wisely, that if you're doing this in a browser (that is, if this code is going to run from inside a web page served to browsers), then you've got it a lot easier. You can hand your HTML over to the browser, perhaps as the content document for an <iframe> element you add to the page. If it's not too awful for the browser to parse (and browsers can cope with surprisingly weird HTML), then once the DOM is ready in the <iframe> you can just walk the DOM and generate whatever sort of different representation you want.
Unusual situation. I have a client, let's call them "BuyNow." They would like for every instance of their name throughout the copy of their site to be stylized like "BuyNow," where the second half of their name is in bold.
I'd really hate to spend a day adding <strong> tags to all the copy. Is there a good way to do this using jQuery?
I've seen the highlight plugin for jQuery and it's very close, but I need to bold just the second half of that word.
To do it reliably you'd have to iterate over each element in the document looking for text nodes, and searching for text in those. (This is what the plugin noted in the question does.)
Here's a plain JavaScript/DOM one that allows a RegExp pattern to match. jQuery doesn't really give you much help here since selectors can only select elements, and the ‘:contains’ selector is recursive so not too useful to us.
// Find text in descendents of an element, in reverse document order
// pattern must be a regexp with global flag
//
function findText(element, pattern, callback) {
for (var childi= element.childNodes.length; childi-->0;) {
var child= element.childNodes[childi];
if (child.nodeType==1) {
findText(child, pattern, callback);
} else if (child.nodeType==3) {
var matches= [];
var match;
while (match= pattern.exec(child.data))
matches.push(match);
for (var i= matches.length; i-->0;)
callback.call(window, child, matches[i]);
}
}
}
findText(document.body, /\bBuyNow\b/g, function(node, match) {
var span= document.createElement('span');
span.className= 'highlight';
node.splitText(match.index+6);
span.appendChild(node.splitText(match.index+3));
node.parentNode.insertBefore(span, node.nextSibling);
});
Regular Expressions and replace() spring to mind. Something like
var text = $([selector]).html();
text = text.replace(/Now/g,'<strong>Now<\strong>');
$([selector]).html(text);
A word of caution in using html() to do this. Firstly, there is the potential to replace matched strings in href attributes of <a> elements and other attributes that may cause the page to then incorrectly function. It might be possible to write a better regular expression to overcome some of the potential problems, but performance may suffer (I'm no regular expression guru). Secondly, using html() to replace content will cause non-serializable data such as event handlers bound to elements markup that is replaced, form data, etc. to be lost. Writing a function to target only text nodes may be the better/safer option, it just depends on how complex the pages are.
If you have access to the HMTL files, it would probably be better to do a find and replace on the words they want to change the appearance of in the files if the content is static. NotePad++'s Find in Files option is performant for this job in most cases.
Going with SingleShot's suggestion and using a <span> with a CSS class will afford more flexibility than using a <strong> element.
I wrote a little plugin to do just that. Take a look at my answer to a similar question.
Instead of downloading the plugin suggested in the accepted answer, I strongly recommend that you use the plugin I've written--it's a lot faster.
var Run=Run || {};
Run.makestrong= function(hoo, Rx){
if(hoo.data){
var X= document.createElement('strong');
X.style.color= 'red'; // testing only, easier to spot changes
var pa= hoo.parentNode;
var res, el, tem;
var str= hoo.data;
while(str && (res= Rx.exec(str))!= null){
var tem= res[1];
el= X.cloneNode(true);
el.appendChild(document.createTextNode(tem));
hoo.replaceData(res.index, tem.length,'');
hoo= hoo.splitText(res.index);
str= hoo.data;
if(str) pa.insertBefore(el, hoo);
else{
pa.appendChild(el);
return;
}
}
}
}
Run.godeep= function(hoo, fun, arg){
var A= [];
if(hoo){
hoo= hoo.firstChild;
while(hoo!= null){
if(hoo.nodeType== 3){
if(hoo.data) A[A.length]= fun(hoo, arg);
}
else A= A.concat(arguments.callee(hoo, fun, arg));
hoo= hoo.nextSibling;
}
}
return A;
}
//test
**Run.godeep(document.body, Run.makestrong,/([Ee]+)/g);**
This is not a jQuery script but pure javaScript, i believe it can be altered a little.
Link.