├── README ├── dohighlight-min.js └── dohighlight.js /README: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dohighlight-min.js: -------------------------------------------------------------------------------- 1 | // Author: Raymond Hill 2 | // Version: 2011-01-17 3 | // Title: HTML text hilighter 4 | // Permalink: http://www.raymondhill.net/blog/?p=272 5 | // Purpose: Hilight portions of text inside a specified element, according to a search expression. 6 | // Key feature: Can safely hilight text across HTML tags. 7 | // History: 8 | // 2012-01-29 9 | // fixed a bug which caused special regex characters in the 10 | // search string to break the highlighter 11 | // Notes: Minified using YUI Compressor (http://refresh-sf.com/yui/), 12 | 13 | function doHighlight(A,c,z,s){var G=document;if(typeof A==="string"){A=G.getElementById(A)}if(typeof z==="string"){z=new RegExp(z.replace(/[.*+?|()\[\]{}\\$^]/g,"\\$&"),"ig") 14 | }s=s||0;var j=[],u=[],B=0,o=A.childNodes.length,v,w=0,l=[],k,d,h;for(;;){while(B=0){continue}if(k.tagName.search(/^(a|b|basefont|bdo|big|em|font|i|s|small|span|strike|strong|su[bp]|tt|u)$/i)<0){u.push(" "); 16 | w++}d=k.childNodes.length;if(d){l.push({n:A,l:o,i:B});A=k;o=d;B=0}}}}if(!l.length){break}h=l.pop();A=h.n;o=h.l;B=h.i}if(!j.length){return}u=u.join("");j.push({i:u.length}); 17 | var p,r,E,y,D,g,F,f,b,m,e,a,t,q,C,n,x;for(;;){r=z.exec(u);if(!r||r.length<=s||!r[s].length){break}E=r.index;for(p=1;p>1;if(E=j[D+1].i){g=D+1}else{g=F=D}}}f=g;while(f0){C=v.substring(0,t)}n=v.substring(t,q);x=null;if(q=0){ 56 | continue; 57 | } 58 | // add extra space for tags which fall naturally on word boundaries 59 | if (child.tagName.search(/^(a|b|basefont|bdo|big|em|font|i|s|small|span|strike|strong|su[bp]|tt|u)$/i)<0){ 60 | text.push(' '); 61 | textLength++; 62 | } 63 | // save parent's loop state 64 | nChildren = child.childNodes.length; 65 | if (nChildren){ 66 | stack.push({n:node, l:nNodes, i:iNode}); 67 | // initialize child's loop 68 | node = child; 69 | nNodes = nChildren; 70 | iNode = 0; 71 | } 72 | } 73 | } 74 | // restore parent's loop state 75 | if (!stack.length){ 76 | break; 77 | } 78 | state = stack.pop(); 79 | node = state.n; 80 | nNodes = state.l; 81 | iNode = state.i; 82 | } 83 | 84 | // quit if found nothing 85 | if (!indices.length){ 86 | return; 87 | } 88 | 89 | // morph array of text into contiguous text 90 | text = text.join(''); 91 | 92 | // sentinel 93 | indices.push({i:text.length}); 94 | 95 | // find and hilight all matches 96 | var iMatch, matchingText, 97 | iTextStart, iTextEnd, 98 | i, iLeft, iRight, 99 | iEntry, entry, 100 | parentNode, nextNode, newNode, 101 | iNodeTextStart, iNodeTextEnd, 102 | textStart, textMiddle, textEnd; 103 | 104 | // loop until no more matches 105 | for (;;){ 106 | 107 | // find matching text, stop if none 108 | matchingText = searchFor.exec(text); 109 | if (!matchingText || matchingText.length<=which || !matchingText[which].length){ 110 | break; 111 | } 112 | 113 | // calculate a span from the absolute indices 114 | // for start and end of match 115 | iTextStart = matchingText.index; 116 | for (iMatch=1; iMatch < which; iMatch++){ 117 | iTextStart += matchingText[iMatch].length; 118 | } 119 | iTextEnd = iTextStart + matchingText[which].length; 120 | 121 | // find entry in indices array (using binary search) 122 | iLeft = 0; 123 | iRight = indices.length; 124 | while (iLeft < iRight) { 125 | i=iLeft + iRight >> 1; 126 | if (iTextStart < indices[i].i){iRight = i;} 127 | else if (iTextStart >= indices[i+1].i){iLeft = i + 1;} 128 | else {iLeft = iRight = i;} 129 | } 130 | iEntry = iLeft; 131 | 132 | // for every entry which intersect with the span of the 133 | // match, extract the intersecting text, and put it into 134 | // a span tag with specified class 135 | while (iEntry < indices.length){ 136 | entry = indices[iEntry]; 137 | node = entry.n; 138 | nodeText = node.nodeValue; 139 | parentNode = node.parentNode; 140 | nextNode = node.nextSibling; 141 | iNodeTextStart = iTextStart - entry.i; 142 | iNodeTextEnd = Math.min(iTextEnd,indices[iEntry+1].i) - entry.i; 143 | 144 | // slice of text before hilighted slice 145 | textStart = null; 146 | if (iNodeTextStart > 0){ 147 | textStart = nodeText.substring(0,iNodeTextStart); 148 | } 149 | 150 | // hilighted slice 151 | textMiddle = nodeText.substring(iNodeTextStart,iNodeTextEnd); 152 | 153 | // slice of text after hilighted slice 154 | textEnd = null; 155 | if (iNodeTextEnd < nodeText.length){ 156 | textEnd = nodeText.substr(iNodeTextEnd); 157 | } 158 | 159 | // update DOM according to found slices of text 160 | if (textStart){ 161 | node.nodeValue = textStart; 162 | } 163 | else { 164 | parentNode.removeChild(node); 165 | } 166 | newNode = doc.createElement('span'); 167 | newNode.appendChild(doc.createTextNode(textMiddle)); 168 | newNode.className = className; 169 | parentNode.insertBefore(newNode,nextNode); 170 | if (textEnd){ 171 | newNode = doc.createTextNode(textEnd); 172 | parentNode.insertBefore(newNode,nextNode); 173 | indices[iEntry] = {n:newNode,i:iTextEnd}; // important: make a copy, do not overwrite 174 | } 175 | 176 | // if the match doesn't intersect with the following 177 | // index-node pair, this means this match is completed 178 | iEntry++; 179 | if (iTextEnd <= indices[iEntry].i){ 180 | break; 181 | } 182 | } 183 | } 184 | } 185 | --------------------------------------------------------------------------------