validator/engine/htmlparser.js
/**
   * Given a SAX-like {@code amp.htmlparser.HtmlSaxHandler} parses a
   * {@code htmlText} and lets the {@code handler} know the structure while
   * visiting the nodes. If the provided handler is an implementation of
   * {@code amp.htmlparser.HtmlSaxHandlerWithLocation}, then its
   * {@code setDocLocator} method will get called prior to
   * {@code startDoc}, and the {@code getLine} / {@code getCol} methods will
   * reflect the current line / column while a SAX callback (e.g.,
   * {@code startTag}) is active.
   *
   * @param {amp.htmlparser.HtmlSaxHandler|
   *     amp.htmlparser.HtmlSaxHandlerWithLocation} handler The
   *         HtmlSaxHandler that will receive the events.
   * @param {string} htmlText The html text.
   */
Given a SAX-like  amp.htmlparser.HtmlSaxHandler parses a
htmlText and lets the  handler know the structure while
visiting the nodes. If the provided handler is an implementation of
amp.htmlparser.HtmlSaxHandlerWithLocation, then its
setDocLocator method will get called prior to
startDoc, and the  getLine /  getCol methods will
reflect the current line / column while a SAX callback  is active.

@param {amp.htmlparser.HtmlSaxHandler|
amp.htmlparser.HtmlSaxHandlerWithLocation} handler The
HtmlSaxHandler that will receive the events.
@param {string} htmlText The html text.
Given a SAX - like amp . htmlparser . HtmlSaxHandler parses a htmlText and lets the handler know the structure while visiting the nodes . If the provided handler is an implementation of amp . htmlparser . HtmlSaxHandlerWithLocation then its setDocLocator method will get called prior to startDoc and the getLine / getCol methods will reflect the current line / column while a SAX callback is active . @param { amp . htmlparser . HtmlSaxHandler| amp . htmlparser . HtmlSaxHandlerWithLocation } handler The HtmlSaxHandler that will receive the events . @param { string } htmlText The html text .
parse(handler, htmlText) {
    let htmlUpper = null;
    let inTag = false;   
    const attribs = [];  
    let tagName;         
    let eflags;          
    let openTag;         
    let tagStack = new TagNameStack(handler);
    let locator = null;
    if (handler instanceof amp.htmlparser.HtmlSaxHandlerWithLocation) {
      locator = new amp.htmlparser.HtmlParser.DocLocatorImpl(htmlText);
      handler.setDocLocator(locator);
    }
    handler.startDoc();
    while (htmlText) {
      const regex = inTag ? amp.htmlparser.HtmlParser.INSIDE_TAG_TOKEN_ :
                            amp.htmlparser.HtmlParser.OUTSIDE_TAG_TOKEN_;
      const m = htmlText.match(regex);
      if (locator) {
        locator.advancePos(m[0]);
      }
      htmlText = htmlText.substring(m[0].length);
      if (inTag) {
        if (m[1]) {  
          const attribName = amp.htmlparser.toLowerCase(m[1]);
          let decodedValue = '';
          if (m[2]) {
            let encodedValue = m[3];
            switch (encodedValue.charCodeAt(0)) {  
              case 34:                             
              case 39:                             
                encodedValue =
                    encodedValue.substring(1, encodedValue.length - 1);
                break;
            }
            decodedValue =
                this.unescapeEntities_(this.stripNULs_(encodedValue));
          }
          attribs.push(attribName, decodedValue);
        } else if (m[4]) {
          if (eflags !== void 0) {  
            if (openTag) {
              tagStack.startTag( (tagName), attribs);
            } else {
              tagStack.endTag( (tagName));
            }
          }
          if (openTag && (eflags & (amp.htmlparser.HtmlParser.EFlags.CDATA |
                                    amp.htmlparser.HtmlParser.EFlags.RCDATA))) {
            if (htmlUpper === null) {
              htmlUpper = amp.htmlparser.toUpperCase(htmlText);
            } else {
              htmlUpper =
                  htmlUpper.substring(htmlUpper.length - htmlText.length);
            }
            let dataEnd = htmlUpper.indexOf('</' + tagName);
            if (dataEnd < 0) {
              dataEnd = htmlText.length;
            }
            if (eflags & amp.htmlparser.HtmlParser.EFlags.CDATA) {
              if (handler.cdata) {
                handler.cdata(htmlText.substring(0, dataEnd));
              }
            } else if (handler.rcdata) {
              handler.rcdata(
                  this.normalizeRCData_(htmlText.substring(0, dataEnd)));
            }
            if (locator) {
              locator.advancePos(htmlText.substring(0, dataEnd));
            }
            htmlText = htmlText.substring(dataEnd);
          }
          tagName = eflags = openTag = void 0;
          attribs.length = 0;
          if (inTag && locator) {
            locator.snapshotPos();
          }
          inTag = false;
        }
      } else {
        if (m[1]) {  
          tagStack.pcdata(m[0]);
        } else if (m[3]) {  
          openTag = !m[2];
          if (!inTag && locator) {
            locator.snapshotPos();
          }
          inTag = true;
          tagName = amp.htmlparser.toUpperCase(m[3]);
          eflags = amp.htmlparser.HtmlParser.Elements.hasOwnProperty(tagName) ?
              amp.htmlparser.HtmlParser.Elements[tagName] :
              amp.htmlparser.HtmlParser.EFlags.UNKNOWN_OR_CUSTOM;
        } else if (m[4]) {  
          if (!inTag && locator) {
            locator.snapshotPos();
          }
          tagStack.pcdata(m[4]);
        } else if (m[5]) {  
          switch (m[5]) {
            case '<':
              tagStack.pcdata('&lt;');
              break;
            case '>':
              tagStack.pcdata('&gt;');
              break;
            default:
              tagStack.pcdata('&amp;');
              break;
          }
        }
      }
    }
    if (!inTag && locator) {
      locator.snapshotPos();
    }
    tagStack.exitRemainingTags();
    handler.effectiveBodyTag(tagStack.effectiveBodyAttribs());
    handler.endDoc();
  }
parse ( handler , htmlText ) { let htmlUpper = null ; let inTag = false ; const attribs = [ ] ; let tagName ; let eflags ; let openTag ; let tagStack = new TagNameStack ( handler ) ; let locator = null ; if ( handler instanceof amp . htmlparser . HtmlSaxHandlerWithLocation ) { locator = new amp . htmlparser . HtmlParser . DocLocatorImpl ( htmlText ) ; handler . setDocLocator ( locator ) ; } handler . startDoc ( ) ; while ( htmlText ) { const regex = inTag ? amp . htmlparser . HtmlParser . INSIDE_TAG_TOKEN_ : amp . htmlparser . HtmlParser . OUTSIDE_TAG_TOKEN_ ; const m = htmlText . match ( regex ) ; if ( locator ) { locator . advancePos ( m [ 0 ] ) ; } htmlText = htmlText . substring ( m [ 0 ] . length ) ; if ( inTag ) { if ( m [ 1 ] ) { const attribName = amp . htmlparser . toLowerCase ( m [ 1 ] ) ; let decodedValue = '' ; if ( m [ 2 ] ) { let encodedValue = m [ 3 ] ; switch ( encodedValue . charCodeAt ( 0 ) ) { case 34 : case 39 : encodedValue = encodedValue . substring ( 1 , encodedValue . length - 1 ) ; break ; } decodedValue = this . unescapeEntities_ ( this . stripNULs_ ( encodedValue ) ) ; } attribs . push ( attribName , decodedValue ) ; } else if ( m [ 4 ] ) { if ( eflags !== void 0 ) { if ( openTag ) { tagStack . startTag ( ( tagName ) , attribs ) ; } else { tagStack . endTag ( ( tagName ) ) ; } } if ( openTag && ( eflags & ( amp . htmlparser . HtmlParser . EFlags . CDATA | amp . htmlparser . HtmlParser . EFlags . RCDATA ) ) ) { if ( htmlUpper === null ) { htmlUpper = amp . htmlparser . toUpperCase ( htmlText ) ; } else { htmlUpper = htmlUpper . substring ( htmlUpper . length - htmlText . length ) ; } let dataEnd = htmlUpper . indexOf ( '</' + tagName ) ; if ( dataEnd < 0 ) { dataEnd = htmlText . length ; } if ( eflags & amp . htmlparser . HtmlParser . EFlags . CDATA ) { if ( handler . cdata ) { handler . cdata ( htmlText . substring ( 0 , dataEnd ) ) ; } } else if ( handler . rcdata ) { handler . rcdata ( this . normalizeRCData_ ( htmlText . substring ( 0 , dataEnd ) ) ) ; } if ( locator ) { locator . advancePos ( htmlText . substring ( 0 , dataEnd ) ) ; } htmlText = htmlText . substring ( dataEnd ) ; } tagName = eflags = openTag = void 0 ; attribs . length = 0 ; if ( inTag && locator ) { locator . snapshotPos ( ) ; } inTag = false ; } } else { if ( m [ 1 ] ) { tagStack . pcdata ( m [ 0 ] ) ; } else if ( m [ 3 ] ) { openTag = ! m [ 2 ] ; if ( ! inTag && locator ) { locator . snapshotPos ( ) ; } inTag = true ; tagName = amp . htmlparser . toUpperCase ( m [ 3 ] ) ; eflags = amp . htmlparser . HtmlParser . Elements . hasOwnProperty ( tagName ) ? amp . htmlparser . HtmlParser . Elements [ tagName ] : amp . htmlparser . HtmlParser . EFlags . UNKNOWN_OR_CUSTOM ; } else if ( m [ 4 ] ) { if ( ! inTag && locator ) { locator . snapshotPos ( ) ; } tagStack . pcdata ( m [ 4 ] ) ; } else if ( m [ 5 ] ) { switch ( m [ 5 ] ) { case '<' : tagStack . pcdata ( '&lt;' ) ; break ; case '>' : tagStack . pcdata ( '&gt;' ) ; break ; default : tagStack . pcdata ( '&amp;' ) ; break ; } } } } if ( ! inTag && locator ) { locator . snapshotPos ( ) ; } tagStack . exitRemainingTags ( ) ; handler . effectiveBodyTag ( tagStack . effectiveBodyAttribs ( ) ) ; handler . endDoc ( ) ; }
Given a SAX-like {@code amp.htmlparser.HtmlSaxHandler} parses a
 {@code htmlText} and lets the {@code handler} know the structure while
 visiting the nodes.
Given a SAX - like { @code amp . htmlparser . HtmlSaxHandler } parses a { @code htmlText } and lets the { @code handler } know the structure while visiting the nodes .
// True iff we're currently processing a tag. // Accumulates attribute names and values. // The name of the tag currently being processed. // The element flags for the current tag. // True if the current tag is an open tag. // Only provide location information if the handler implements the // setDocLocator method. // Lets the handler know that we are starting to parse the document. // Consumes tokens from the htmlText and stops once all tokens are // processed. // Gets the next token // And removes it from the string // TODO(goto): cleanup this code breaking it into separate methods. // Attribute. // SetAttribute with uppercase names doesn't work on IE6. // Use empty string as value for valueless attribs, so //   <input type=checkbox checked> // gets attributes ['type', 'checkbox', 'checked', ''] // Strip quotes. // double quote " // single quote ' // False if not in whitelist. /** @type {string} */ /** @type {string} */ // Entity. // Tag. // Text. // Cruft. // Lets the handler know that we are done parsing the document.