@@ -175,17 +175,122 @@ function reresolve (node, baseurl) {
175175}
176176exports . reresolve = reresolve ;
177177
178+ var HTML_TAGS = new Set ( [
179+ 'a' , 'abbr' , 'acronym' , 'address' , 'applet' , 'area' , 'article' , 'aside' , 'audio' ,
180+ 'b' , 'base' , 'basefont' , 'bdi' , 'bdo' , 'big' , 'blink' , 'blockquote' , 'body' , 'br' , 'button' ,
181+ 'canvas' , 'caption' , 'center' , 'cite' , 'code' , 'col' , 'colgroup' ,
182+ 'data' , 'datalist' , 'dd' , 'del' , 'details' , 'dfn' , 'dialog' , 'dir' , 'div' , 'dl' , 'dt' ,
183+ 'em' , 'embed' ,
184+ 'fieldset' , 'figcaption' , 'figure' , 'font' , 'footer' , 'form' , 'frame' , 'frameset' ,
185+ 'h1' , 'h2' , 'h3' , 'h4' , 'h5' , 'h6' , 'head' , 'header' , 'hgroup' , 'hr' , 'html' ,
186+ 'i' , 'iframe' , 'img' , 'input' , 'ins' , 'isindex' ,
187+ 'kbd' ,
188+ 'label' , 'legend' , 'li' , 'link' , 'listing' ,
189+ 'main' , 'map' , 'mark' , 'marquee' , 'menu' , 'menuitem' , 'meta' , 'meter' , 'multicol' ,
190+ 'nav' , 'nextid' , 'nobr' , 'noembed' , 'noframes' , 'noscript' ,
191+ 'object' , 'ol' , 'optgroup' , 'option' , 'output' ,
192+ 'p' , 'param' , 'picture' , 'plaintext' , 'pre' , 'progress' ,
193+ 'q' ,
194+ 'rb' , 'rp' , 'rt' , 'rtc' , 'ruby' ,
195+ 's' , 'samp' , 'script' , 'section' , 'select' , 'slot' , 'small' , 'source' , 'spacer' , 'span' , 'strike' , 'strong' , 'style' , 'sub' , 'summary' , 'sup' ,
196+ 'table' , 'tbody' , 'td' , 'template' , 'textarea' , 'tfoot' , 'th' ,
197+ 'thead' , 'time' , 'title' , 'tr' , 'track' , 'tt' ,
198+ 'u' , 'ul' ,
199+ 'var' , 'video' ,
200+ 'wbr' ,
201+ 'xmp'
202+ ] ) ;
203+
204+ /*
205+ * Scan markup starting at str[i] (which must be '<') and return its length
206+ * and type if it is recognized markup, or null if it isn't.
207+ * Recognized types:
208+ * { alwaysStrip: true, len } - comments, doctypes, PIs
209+ * { tagName, len } - opening or closing HTML tags
210+ *
211+ * Respects quoted attribute values so that an attribute like title="1 > 0"
212+ * doesn't cause a premature close.
213+ *
214+ * @param {string } str
215+ * @param {number } i
216+ * @returns {Object|null }
217+ * @private
218+ */
219+ function readMarkupAt ( str , i ) {
220+ // HTML comment: <!-- ... -->
221+ if ( str . slice ( i , i + 4 ) === '<!--' ) {
222+ var commentEnd = str . indexOf ( '-->' , i + 4 ) ;
223+ return commentEnd !== - 1 ? { alwaysStrip : true , len : commentEnd + 3 - i } : null ;
224+ }
225+
226+ // Processing instruction: <? ... ?>
227+ if ( str [ i + 1 ] === '?' ) {
228+ var piEnd = str . indexOf ( '?>' , i + 2 ) ;
229+ return piEnd !== - 1 ? { alwaysStrip : true , len : piEnd + 2 - i } : null ;
230+ }
231+
232+ // Doctype / other <! declarations: <! ... >
233+ if ( str [ i + 1 ] === '!' ) {
234+ var declEnd = str . indexOf ( '>' , i + 2 ) ;
235+ return declEnd !== - 1 ? { alwaysStrip : true , len : declEnd + 1 - i } : null ;
236+ }
237+
238+ // Closing tag or opening tag: </tagName ...> or <tagName ...>
239+ var isClosing = str [ i + 1 ] === '/' ;
240+ var j = isClosing ? i + 2 : i + 1 ;
241+ var nameStart = j ;
242+ while ( j < str . length ) {
243+ var code = str . charCodeAt ( j ) ;
244+ var isLetter = ( code >= 97 && code <= 122 ) || ( code >= 65 && code <= 90 ) ;
245+ var isDigit = code >= 48 && code <= 57 ;
246+ if ( j === nameStart ? ! isLetter : ! ( isLetter || isDigit ) ) break ;
247+ j ++ ;
248+ }
249+ var tagName = str . slice ( nameStart , j ) . toLowerCase ( ) ;
250+ if ( ! tagName ) return null ;
251+
252+ // Scan for >, respecting quoted attribute values
253+ var quote = null ;
254+ while ( j < str . length ) {
255+ var ch = str [ j ] ;
256+ if ( quote ) {
257+ if ( ch === quote ) quote = null ;
258+ } else if ( ch === '"' || ch === '\'' ) {
259+ quote = ch ;
260+ } else if ( ch === '>' ) {
261+ return { tagName : tagName , len : j + 1 - i } ;
262+ }
263+ j ++ ;
264+ }
265+ return null ; // unclosed tag
266+ }
267+
178268/*
179- * Aggressivly strip HTML tags
180- * Pulled out of node-resanitize because it was all that was being used
181- * and it's way lighter...
269+ * Strip HTML tags, leaving bare text content.
270+ * Scans the string for markup - HTML tags, comments, doctypes, and processing
271+ * instructions - and removes them. Only tags with known HTML element names
272+ * are stripped; unknown angle-bracket content like <<<NotHTML>>> is preserved.
182273 *
183274 * @param {string } str
184275 * @returns {string }
185276 * @private
186277 */
187278function stripHtml ( str ) {
188- return str . replace ( / < .* ?> / g, '' ) ;
279+ var out = '' ;
280+ var i = 0 ;
281+ while ( i < str . length ) {
282+ if ( str [ i ] === '<' ) {
283+ var markup = readMarkupAt ( str , i ) ;
284+ if ( markup && ( markup . alwaysStrip || HTML_TAGS . has ( markup . tagName ) ) ) {
285+ i += markup . len ;
286+ continue ;
287+ }
288+ }
289+ out += str [ i ] ;
290+ i ++ ;
291+ }
292+ return out ;
189293}
190294
295+ exports . HTML_TAGS = HTML_TAGS ;
191296exports . stripHtml = stripHtml ;
0 commit comments