/ cloudformation-templates / node_modules / parse5 / lib / parser / open-element-stack.js
open-element-stack.js
  1  'use strict';
  2  
  3  const HTML = require('../common/html');
  4  
  5  //Aliases
  6  const $ = HTML.TAG_NAMES;
  7  const NS = HTML.NAMESPACES;
  8  
  9  //Element utils
 10  
 11  //OPTIMIZATION: Integer comparisons are low-cost, so we can use very fast tag name length filters here.
 12  //It's faster than using dictionary.
 13  function isImpliedEndTagRequired(tn) {
 14      switch (tn.length) {
 15          case 1:
 16              return tn === $.P;
 17  
 18          case 2:
 19              return tn === $.RB || tn === $.RP || tn === $.RT || tn === $.DD || tn === $.DT || tn === $.LI;
 20  
 21          case 3:
 22              return tn === $.RTC;
 23  
 24          case 6:
 25              return tn === $.OPTION;
 26  
 27          case 8:
 28              return tn === $.OPTGROUP;
 29      }
 30  
 31      return false;
 32  }
 33  
 34  function isImpliedEndTagRequiredThoroughly(tn) {
 35      switch (tn.length) {
 36          case 1:
 37              return tn === $.P;
 38  
 39          case 2:
 40              return (
 41                  tn === $.RB ||
 42                  tn === $.RP ||
 43                  tn === $.RT ||
 44                  tn === $.DD ||
 45                  tn === $.DT ||
 46                  tn === $.LI ||
 47                  tn === $.TD ||
 48                  tn === $.TH ||
 49                  tn === $.TR
 50              );
 51  
 52          case 3:
 53              return tn === $.RTC;
 54  
 55          case 5:
 56              return tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD;
 57  
 58          case 6:
 59              return tn === $.OPTION;
 60  
 61          case 7:
 62              return tn === $.CAPTION;
 63  
 64          case 8:
 65              return tn === $.OPTGROUP || tn === $.COLGROUP;
 66      }
 67  
 68      return false;
 69  }
 70  
 71  function isScopingElement(tn, ns) {
 72      switch (tn.length) {
 73          case 2:
 74              if (tn === $.TD || tn === $.TH) {
 75                  return ns === NS.HTML;
 76              } else if (tn === $.MI || tn === $.MO || tn === $.MN || tn === $.MS) {
 77                  return ns === NS.MATHML;
 78              }
 79  
 80              break;
 81  
 82          case 4:
 83              if (tn === $.HTML) {
 84                  return ns === NS.HTML;
 85              } else if (tn === $.DESC) {
 86                  return ns === NS.SVG;
 87              }
 88  
 89              break;
 90  
 91          case 5:
 92              if (tn === $.TABLE) {
 93                  return ns === NS.HTML;
 94              } else if (tn === $.MTEXT) {
 95                  return ns === NS.MATHML;
 96              } else if (tn === $.TITLE) {
 97                  return ns === NS.SVG;
 98              }
 99  
100              break;
101  
102          case 6:
103              return (tn === $.APPLET || tn === $.OBJECT) && ns === NS.HTML;
104  
105          case 7:
106              return (tn === $.CAPTION || tn === $.MARQUEE) && ns === NS.HTML;
107  
108          case 8:
109              return tn === $.TEMPLATE && ns === NS.HTML;
110  
111          case 13:
112              return tn === $.FOREIGN_OBJECT && ns === NS.SVG;
113  
114          case 14:
115              return tn === $.ANNOTATION_XML && ns === NS.MATHML;
116      }
117  
118      return false;
119  }
120  
121  //Stack of open elements
122  class OpenElementStack {
123      constructor(document, treeAdapter) {
124          this.stackTop = -1;
125          this.items = [];
126          this.current = document;
127          this.currentTagName = null;
128          this.currentTmplContent = null;
129          this.tmplCount = 0;
130          this.treeAdapter = treeAdapter;
131      }
132  
133      //Index of element
134      _indexOf(element) {
135          let idx = -1;
136  
137          for (let i = this.stackTop; i >= 0; i--) {
138              if (this.items[i] === element) {
139                  idx = i;
140                  break;
141              }
142          }
143          return idx;
144      }
145  
146      //Update current element
147      _isInTemplate() {
148          return this.currentTagName === $.TEMPLATE && this.treeAdapter.getNamespaceURI(this.current) === NS.HTML;
149      }
150  
151      _updateCurrentElement() {
152          this.current = this.items[this.stackTop];
153          this.currentTagName = this.current && this.treeAdapter.getTagName(this.current);
154  
155          this.currentTmplContent = this._isInTemplate() ? this.treeAdapter.getTemplateContent(this.current) : null;
156      }
157  
158      //Mutations
159      push(element) {
160          this.items[++this.stackTop] = element;
161          this._updateCurrentElement();
162  
163          if (this._isInTemplate()) {
164              this.tmplCount++;
165          }
166      }
167  
168      pop() {
169          this.stackTop--;
170  
171          if (this.tmplCount > 0 && this._isInTemplate()) {
172              this.tmplCount--;
173          }
174  
175          this._updateCurrentElement();
176      }
177  
178      replace(oldElement, newElement) {
179          const idx = this._indexOf(oldElement);
180  
181          this.items[idx] = newElement;
182  
183          if (idx === this.stackTop) {
184              this._updateCurrentElement();
185          }
186      }
187  
188      insertAfter(referenceElement, newElement) {
189          const insertionIdx = this._indexOf(referenceElement) + 1;
190  
191          this.items.splice(insertionIdx, 0, newElement);
192  
193          if (insertionIdx === ++this.stackTop) {
194              this._updateCurrentElement();
195          }
196      }
197  
198      popUntilTagNamePopped(tagName) {
199          while (this.stackTop > -1) {
200              const tn = this.currentTagName;
201              const ns = this.treeAdapter.getNamespaceURI(this.current);
202  
203              this.pop();
204  
205              if (tn === tagName && ns === NS.HTML) {
206                  break;
207              }
208          }
209      }
210  
211      popUntilElementPopped(element) {
212          while (this.stackTop > -1) {
213              const poppedElement = this.current;
214  
215              this.pop();
216  
217              if (poppedElement === element) {
218                  break;
219              }
220          }
221      }
222  
223      popUntilNumberedHeaderPopped() {
224          while (this.stackTop > -1) {
225              const tn = this.currentTagName;
226              const ns = this.treeAdapter.getNamespaceURI(this.current);
227  
228              this.pop();
229  
230              if (
231                  tn === $.H1 ||
232                  tn === $.H2 ||
233                  tn === $.H3 ||
234                  tn === $.H4 ||
235                  tn === $.H5 ||
236                  (tn === $.H6 && ns === NS.HTML)
237              ) {
238                  break;
239              }
240          }
241      }
242  
243      popUntilTableCellPopped() {
244          while (this.stackTop > -1) {
245              const tn = this.currentTagName;
246              const ns = this.treeAdapter.getNamespaceURI(this.current);
247  
248              this.pop();
249  
250              if (tn === $.TD || (tn === $.TH && ns === NS.HTML)) {
251                  break;
252              }
253          }
254      }
255  
256      popAllUpToHtmlElement() {
257          //NOTE: here we assume that root <html> element is always first in the open element stack, so
258          //we perform this fast stack clean up.
259          this.stackTop = 0;
260          this._updateCurrentElement();
261      }
262  
263      clearBackToTableContext() {
264          while (
265              (this.currentTagName !== $.TABLE && this.currentTagName !== $.TEMPLATE && this.currentTagName !== $.HTML) ||
266              this.treeAdapter.getNamespaceURI(this.current) !== NS.HTML
267          ) {
268              this.pop();
269          }
270      }
271  
272      clearBackToTableBodyContext() {
273          while (
274              (this.currentTagName !== $.TBODY &&
275                  this.currentTagName !== $.TFOOT &&
276                  this.currentTagName !== $.THEAD &&
277                  this.currentTagName !== $.TEMPLATE &&
278                  this.currentTagName !== $.HTML) ||
279              this.treeAdapter.getNamespaceURI(this.current) !== NS.HTML
280          ) {
281              this.pop();
282          }
283      }
284  
285      clearBackToTableRowContext() {
286          while (
287              (this.currentTagName !== $.TR && this.currentTagName !== $.TEMPLATE && this.currentTagName !== $.HTML) ||
288              this.treeAdapter.getNamespaceURI(this.current) !== NS.HTML
289          ) {
290              this.pop();
291          }
292      }
293  
294      remove(element) {
295          for (let i = this.stackTop; i >= 0; i--) {
296              if (this.items[i] === element) {
297                  this.items.splice(i, 1);
298                  this.stackTop--;
299                  this._updateCurrentElement();
300                  break;
301              }
302          }
303      }
304  
305      //Search
306      tryPeekProperlyNestedBodyElement() {
307          //Properly nested <body> element (should be second element in stack).
308          const element = this.items[1];
309  
310          return element && this.treeAdapter.getTagName(element) === $.BODY ? element : null;
311      }
312  
313      contains(element) {
314          return this._indexOf(element) > -1;
315      }
316  
317      getCommonAncestor(element) {
318          let elementIdx = this._indexOf(element);
319  
320          return --elementIdx >= 0 ? this.items[elementIdx] : null;
321      }
322  
323      isRootHtmlElementCurrent() {
324          return this.stackTop === 0 && this.currentTagName === $.HTML;
325      }
326  
327      //Element in scope
328      hasInScope(tagName) {
329          for (let i = this.stackTop; i >= 0; i--) {
330              const tn = this.treeAdapter.getTagName(this.items[i]);
331              const ns = this.treeAdapter.getNamespaceURI(this.items[i]);
332  
333              if (tn === tagName && ns === NS.HTML) {
334                  return true;
335              }
336  
337              if (isScopingElement(tn, ns)) {
338                  return false;
339              }
340          }
341  
342          return true;
343      }
344  
345      hasNumberedHeaderInScope() {
346          for (let i = this.stackTop; i >= 0; i--) {
347              const tn = this.treeAdapter.getTagName(this.items[i]);
348              const ns = this.treeAdapter.getNamespaceURI(this.items[i]);
349  
350              if (
351                  (tn === $.H1 || tn === $.H2 || tn === $.H3 || tn === $.H4 || tn === $.H5 || tn === $.H6) &&
352                  ns === NS.HTML
353              ) {
354                  return true;
355              }
356  
357              if (isScopingElement(tn, ns)) {
358                  return false;
359              }
360          }
361  
362          return true;
363      }
364  
365      hasInListItemScope(tagName) {
366          for (let i = this.stackTop; i >= 0; i--) {
367              const tn = this.treeAdapter.getTagName(this.items[i]);
368              const ns = this.treeAdapter.getNamespaceURI(this.items[i]);
369  
370              if (tn === tagName && ns === NS.HTML) {
371                  return true;
372              }
373  
374              if (((tn === $.UL || tn === $.OL) && ns === NS.HTML) || isScopingElement(tn, ns)) {
375                  return false;
376              }
377          }
378  
379          return true;
380      }
381  
382      hasInButtonScope(tagName) {
383          for (let i = this.stackTop; i >= 0; i--) {
384              const tn = this.treeAdapter.getTagName(this.items[i]);
385              const ns = this.treeAdapter.getNamespaceURI(this.items[i]);
386  
387              if (tn === tagName && ns === NS.HTML) {
388                  return true;
389              }
390  
391              if ((tn === $.BUTTON && ns === NS.HTML) || isScopingElement(tn, ns)) {
392                  return false;
393              }
394          }
395  
396          return true;
397      }
398  
399      hasInTableScope(tagName) {
400          for (let i = this.stackTop; i >= 0; i--) {
401              const tn = this.treeAdapter.getTagName(this.items[i]);
402              const ns = this.treeAdapter.getNamespaceURI(this.items[i]);
403  
404              if (ns !== NS.HTML) {
405                  continue;
406              }
407  
408              if (tn === tagName) {
409                  return true;
410              }
411  
412              if (tn === $.TABLE || tn === $.TEMPLATE || tn === $.HTML) {
413                  return false;
414              }
415          }
416  
417          return true;
418      }
419  
420      hasTableBodyContextInTableScope() {
421          for (let i = this.stackTop; i >= 0; i--) {
422              const tn = this.treeAdapter.getTagName(this.items[i]);
423              const ns = this.treeAdapter.getNamespaceURI(this.items[i]);
424  
425              if (ns !== NS.HTML) {
426                  continue;
427              }
428  
429              if (tn === $.TBODY || tn === $.THEAD || tn === $.TFOOT) {
430                  return true;
431              }
432  
433              if (tn === $.TABLE || tn === $.HTML) {
434                  return false;
435              }
436          }
437  
438          return true;
439      }
440  
441      hasInSelectScope(tagName) {
442          for (let i = this.stackTop; i >= 0; i--) {
443              const tn = this.treeAdapter.getTagName(this.items[i]);
444              const ns = this.treeAdapter.getNamespaceURI(this.items[i]);
445  
446              if (ns !== NS.HTML) {
447                  continue;
448              }
449  
450              if (tn === tagName) {
451                  return true;
452              }
453  
454              if (tn !== $.OPTION && tn !== $.OPTGROUP) {
455                  return false;
456              }
457          }
458  
459          return true;
460      }
461  
462      //Implied end tags
463      generateImpliedEndTags() {
464          while (isImpliedEndTagRequired(this.currentTagName)) {
465              this.pop();
466          }
467      }
468  
469      generateImpliedEndTagsThoroughly() {
470          while (isImpliedEndTagRequiredThoroughly(this.currentTagName)) {
471              this.pop();
472          }
473      }
474  
475      generateImpliedEndTagsWithExclusion(exclusionTagName) {
476          while (isImpliedEndTagRequired(this.currentTagName) && this.currentTagName !== exclusionTagName) {
477              this.pop();
478          }
479      }
480  }
481  
482  module.exports = OpenElementStack;