PageSourceSearch

https://www.larousse.fr/library-web/larcom/cnil/htmlParser.js

js larousse.fr collected 2026-10-01 08:31:29 UTC 9,461 bytes, 357 lines download raw bytes

1// An html parser written in JavaScript
2// Based on http://ejohn.org/blog/pure-javascript-html-parser/
3
4(function() {
5  var supports = (function() {
6    var supports = {};
7
8    var html;
9    var work = this.document.createElement('div');
10
11    html = "<P><I></P></I>";
12    work.innerHTML = html;
13    supports.tagSoup = work.innerHTML !== html;
14
15    work.innerHTML = "<P><i><P></P></i></P>";
16    supports.selfClose = work.childNodes.length === 2;
17
18    return supports;
19  })();
20
21
22
23  // Regular Expressions for parsing tags and attributes
24  var startTag = /^<([\-A-Za-z0-9_]+)((?:\s+[\w\-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
25  var endTag = /^<\/([\-A-Za-z0-9_]+)[^>]*>/;
26  var attr = /([\-A-Za-z0-9_]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
27  var fillAttr = /^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noresize|noshade|nowrap|readonly|selected)$/i;
28
29  var DEBUG = false;
30
31  function htmlParser(stream, options) {
32    stream = stream || '';
33
34    // Options
35    options = options || {};
36
37    for(var key in supports) {
38      if(supports.hasOwnProperty(key)) {
39        if(options.autoFix) {
40          options['fix_'+key] = true;//!supports[key];
41        }
42        options.fix = options.fix || options['fix_'+key];
43      }
44    }
45
46    var stack = [];
47
48    var append = function(str) {
49      stream += str;
50    };
51
52    var prepend = function(str) {
53      stream = str + stream;
54    };
55
56    // Order of detection matters: detection of one can only
57    // succeed if detection of previous didn't
58    var detect = {
59      comment: /^<!--/,
60      endTag: /^<\//,
61      atomicTag: /^<\s*(script|style|noscript)[\s>]/i,
62      startTag: /^</,
63      chars: /^[^<]/
64    };
65
66    // Detection has already happened when a reader is called.
67    var reader = {
68
69      comment: function() {
70        var index = stream.indexOf("-->");
71        if ( index >= 0 ) {
72          return {
73            content: stream.substr(4, index),
74            length: index + 3
75          };
76        }
77      },
78
79      endTag: function() {
80        var match = stream.match( endTag );
81
82        if ( match ) {
83          return {
84            tagName: match[1],
85            length: match[0].length
86          };
87        }
88      },
89
90      atomicTag: function() {
91        var start = reader.startTag();
92        if(start) {
93          var rest = stream.slice(start.length);
94          // for optimization, we check first just for the end tag
95          if(rest.match(new RegExp("<\/\\s*" + start.tagName + "\\s*>", "i"))) {
96            // capturing the content is inefficient, so we do it inside the if
97            var match = rest.match(new RegExp("([\\s\\S]*?)<\/\\s*" + start.tagName + "\\s*>", "i"));
98            if(match) {
99              // good to go
100              return {
101                tagName: start.tagName,
102                attrs: start.attrs,
103                content: match[1],
104                length: match[0].length + start.length
105              };
106            }
107          }
108        }
109      },
110
111      startTag: function() {
112        var match = stream.match( startTag );
113
114        if ( match ) {
115          var attrs = {};
116
117          match[2].replace(attr, function(match, name) {
118            var value = arguments[2] || arguments[3] || arguments[4] ||
119              fillAttr.test(name) && name || null;
120
121            attrs[name] = value;
122          });
123
124          return {
125            tagName: match[1],
126            attrs: attrs,
127            unary: !!match[3],
128            length: match[0].length
129          };
130        }
131      },
132
133      chars: function() {
134        var index = stream.indexOf("<");
135        return {
136          length: index >= 0 ? index : stream.length
137        };
138      }
139    };
140
141    var readToken = function() {
142
143      // Enumerate detects in order
144      for (var type in detect) {
145
146        if(detect[type].test(stream)) {
147          if(DEBUG) { console.log('suspected ' + type); }
148
149          var token = reader[type]();
150          if(token) {
151            if(DEBUG) { console.log('parsed ' + type, token); }
152            // Type
153            token.type = token.type || type;
154            // Entire text
155            token.text = stream.substr(0, token.length);
156            // Update the stream
157            stream = stream.slice(token.length);
158
159            return token;
160          }
161          return null;
162        }
163      }
164    };
165
166    var readTokens = function(handlers) {
167      var tok;
168      while(tok = readToken()) {
169        // continue until we get an explicit "false" return
170        if(handlers[tok.type] && handlers[tok.type](tok) === false) {
171          return;
172        }
173      }
174    };
175
176    var clear = function() {
177      var rest = stream;
178      stream = '';
179      return rest;
180    };
181
182    var rest = function() {
183      return stream;
184    };
185
186    if(options.fix) {
187      (function() {
188        // Empty Elements - HTML 4.01
189        var EMPTY = /^(AREA|BASE|BASEFONT|BR|COL|FRAME|HR|IMG|INPUT|ISINDEX|LINK|META|PARAM|EMBED)$/i;
190
191        // Elements that you can| intentionally| leave open
192        // (and which close themselves)
193        var CLOSESELF = /^(COLGROUP|DD|DT|LI|OPTIONS|P|TD|TFOOT|TH|THEAD|TR)$/i;
194
195
196        var stack = [];
197        stack.last = function() {
198          return this[this.length - 1];
199        };
200        stack.lastTagNameEq = function(tagName) {
201          var last = this.last();
202          return last && last.tagName &&
203            last.tagName.toUpperCase() === tagName.toUpperCase();
204        };
205
206        stack.containsTagName = function(tagName) {
207          for(var i = 0, tok; tok = this[i]; i++) {
208            if(tok.tagName === tagName) {
209              return true;
210            }
211          }
212          return false;
213        };
214
215        var correct = function(tok) {
216          if(tok && tok.type === 'startTag') {
217            // unary
218            tok.unary = EMPTY.test(tok.tagName) || tok.unary;
219          }
220          return tok;
221        };
222
223        var readTokenImpl = readToken;
224
225        var peekToken = function() {
226          var tmp = stream;
227          var tok = correct(readTokenImpl());
228          stream = tmp;
229          return tok;
230        };
231
232        var closeLast = function() {
233          var tok = stack.pop();
234
235          // prepend close tag to stream.
236          prepend('</'+tok.tagName+'>');
237        };
238
239        var handlers = {
240          startTag: function(tok) {
241            var tagName = tok.tagName;
242            // Fix tbody
243            if(tagName.toUpperCase() === 'TR' && stack.lastTagNameEq('TABLE')) {
244              prepend('<TBODY>');
245              prepareNextToken();
246            } else if(options.fix_selfClose &&
247              CLOSESELF.test(tagName) &&
248              stack.containsTagName(tagName)) {
249                if(stack.lastTagNameEq(tagName)) {
250                  closeLast();
251                } else {
252                  prepend('</'+tok.tagName+'>');
253                  prepareNextToken();
254                }
255            } else if (!tok.unary) {
256              stack.push(tok);
257            }
258          },
259
260          endTag: function(tok) {
261            var last = stack.last();
262            if(last) {
263              if(options.fix_tagSoup && !stack.lastTagNameEq(tok.tagName)) {
264                // cleanup tag soup
265                closeLast();
266              } else {
267                stack.pop();
268              }
269            } else if (options.fix_tagSoup) {
270              // cleanup tag soup part 2: skip this token
271              skipToken();
272            }
273          }
274        };
275
276        var skipToken = function() {
277          // shift the next token
278          readTokenImpl();
279
280          prepareNextToken();
281        };
282
283        var prepareNextToken = function() {
284          var tok = peekToken();
285          if(tok && handlers[tok.type]) {
286            handlers[tok.type](tok);
287          }
288        };
289
290        // redefine readToken
291        readToken = function() {
292          prepareNextToken();
293          return correct(readTokenImpl());
294        };
295      })();
296    }
297
298    return {
299      append: append,
300      readToken: readToken,
301      readTokens: readTokens,
302      clear: clear,
303      rest: rest,
304      stack: stack
305    };
306
307  }
308
309  htmlParser.supports = supports;
310
311  htmlParser.tokenToString = function(tok) {
312    var handler = {
313      comment: function(tok) {
314        return '<--' + tok.content + '-->';
315      },
316      endTag: function(tok) {
317        return '</'+tok.tagName+'>';
318      },
319      atomicTag: function(tok) {
320        console.log(tok);
321        return handler.startTag(tok) +
322              tok.content +
323              handler.endTag(tok);
324      },
325      startTag: function(tok) {
326        var str = '<'+tok.tagName;
327        for (var key in tok.attrs) {
328          var val = tok.attrs[key];
329          // escape quotes
330          str += ' '+key+'="'+(val ? val.replace(/(^|[^\\])"/g, '$1\\\"') : '')+'"';
331        }
332        return str + (tok.unary ? '/>' : '>');
333      },
334      chars: function(tok) {
335        return tok.text;
336      }
337    };
338    return handler[tok.type](tok);
339  };
340
341  htmlParser.escapeAttributes = function(attrs) {
342    var escapedAttrs = {};
343    // escape double-quotes for writing html as a string
344
345    for(var name in attrs) {
346      var value = attrs[name];
347      escapedAttrs[name] = value && value.replace(/(^|[^\\])"/g, '$1\\\"');
348    }
349    return escapedAttrs;
350  };
351
352  for(var key in supports) {
353    htmlParser.browserHasFlaw = htmlParser.browserHasFlaw || (!supports[key]) && key;
354  }
355
356  this.htmlParser = htmlParser;
357})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.