static parse(S, includeParent) {
    let pos = 0;

    const openBracket = '<';
    const openBracketCC = '<'.charCodeAt(0);
    const closeBracket = '>';
    const closeBracketCC = '>'.charCodeAt(0);
    const minusCC = '-'.charCodeAt(0);
    const slashCC = '/'.charCodeAt(0);
    const exclamationCC = '!'.charCodeAt(0);
    const singleQuoteCC = '\''.charCodeAt(0);
    const doubleQuoteCC = '"'.charCodeAt(0);
    const openCornerBracketCC = '['.charCodeAt(0);

    /**
     * parsing a list of entries
     * @param {string} tagName
     * @param {boolean=} preserveSpace
     * @return {!Array<shaka.extern.xml.Node | string>}
     */
    function parseChildren(tagName, preserveSpace = false) {
      /** @type {!Array<shaka.extern.xml.Node | string>} */
      const children = [];
      while (S[pos]) {
        if (S.charCodeAt(pos) == openBracketCC) {
          if (S.charCodeAt(pos + 1) === slashCC) {
            const closeStart = pos + 2;
            pos = S.indexOf(closeBracket, pos);

            const closeTag = S.substring(closeStart, pos);
            let indexOfCloseTag = closeTag.indexOf(tagName);
            if (indexOfCloseTag == -1) {
              // handle VTT closing tags like <c.lime></c>
              const indexOfPeriod = tagName.indexOf('.');
              if (indexOfPeriod > 0) {
                const shortTag = tagName.substring(0, indexOfPeriod);
                indexOfCloseTag = closeTag.indexOf(shortTag);
              }
            }
            if (indexOfCloseTag == -1) {
              const parsedText = S.substring(0, pos).split('\n');
              throw new Error(
                  'Unexpected close tag\nLine: ' + (parsedText.length - 1) +
                            '\nColumn: ' +
                            (parsedText[parsedText.length - 1].length + 1) +
                            '\nChar: ' + S[pos],
              );
            }

            if (pos + 1) {
              pos += 1;
            }

            return children;
          } else if (S.charCodeAt(pos + 1) === exclamationCC) {
            if (S.charCodeAt(pos + 2) == minusCC) {
              while (pos !== -1 && !(S.charCodeAt(pos) === closeBracketCC &&
                  S.charCodeAt(pos - 1) == minusCC &&
                  S.charCodeAt(pos - 2) == minusCC &&
                  pos != -1)) {
                pos = S.indexOf(closeBracket, pos + 1);
              }
              if (pos === -1) {
                pos = S.length;
              }
            } else if (
              S.charCodeAt(pos + 2) === openCornerBracketCC &&
                        S.charCodeAt(pos + 8) === openCornerBracketCC &&
                        S.substr(pos + 3, 5).toLowerCase() === 'cdata'
            ) {
              // cdata
              const cdataEndIndex = S.indexOf(']]>', pos);
              if (cdataEndIndex == -1) {
                children.push(S.substr(pos + 9));
                pos = S.length;
              } else {
                children.push(S.substring(pos + 9, cdataEndIndex));
                pos = cdataEndIndex + 3;
              }
              continue;
            }
            pos++;
            continue;
          }
          const node = parseNode(preserveSpace);
          children.push(node);
          if (typeof node === 'string') {
            return children;
          }
          if (node.tagName[0] === '?' && node.children) {
            children.push(...node.children);
            node.children = [];
          }
        } else {
          const text = parseText();
          if (preserveSpace) {
            if (text.length > 0) {
              children.push(text);
            }
          } else if (children.length &&
              text.length == 1 && text[0] == '\n') {
            children.push(text);
          } else {
            const trimmed = text.trim();
            if (trimmed.length > 0) {
              children.push(text);
            }
          }
          pos++;
        }
      }
      return children;
    }

    /**
     * returns the text outside of texts until the first '<'
     * @return {string}
     */
    function parseText() {
      const start = pos;
      pos = S.indexOf(openBracket, pos) - 1;
      if (pos === -2) {
        pos = S.length;
      }
      return S.slice(start, pos + 1);
    }
    /**
     *    returns text until the first nonAlphabetic letter
     */
    const nameSpacer = '\r\n\t>/= ';

    /**
     * Parse text in current context
     * @return {string}
     */
    function parseName() {
      const start = pos;
      while (nameSpacer.indexOf(S[pos]) === -1 && S[pos]) {
        pos++;
      }
      return S.slice(start, pos);
    }

    /**
     * Parse text in current context
     * @param {boolean} preserveSpace Preserve the space between nodes
     * @return {shaka.extern.xml.Node | string}
     */
    function parseNode(preserveSpace) {
      pos++;
      const tagName = parseName();
      const attributes = {};
      let children = [];

      // parsing attributes
      while (S.charCodeAt(pos) !== closeBracketCC && S[pos]) {
        const c = S.charCodeAt(pos);
        // abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
        if ((c > 64 && c < 91) || (c > 96 && c < 123)) {
          const name = parseName();
          // search beginning of the string
          let code = S.charCodeAt(pos);
          while (code && code !== singleQuoteCC && code !== doubleQuoteCC &&
                !((code > 64 && code < 91) || (code > 96 && code < 123)) &&
                code !== closeBracketCC) {
            pos++;
            code = S.charCodeAt(pos);
          }
          let value = parseString();
          if (code === singleQuoteCC || code === doubleQuoteCC) {
            if (pos === -1) {
              /** @type {shaka.extern.xml.Node} */
              const node = {
                tagName,
                attributes,
                children,
                parent: null,
              };
              if (includeParent) {
                for (let i = 0; i < children.length; i++) {
                  if (typeof children[i] !== 'string') {
                    children[i].parent = node;
                  }
                }
              }
              return node;
            }
          } else {
            value = null;
            pos--;
          }
          if (name.startsWith('xmlns:')) {
            const segments = name.split(':');
            shaka.util.TXml.setKnownNameSpace(
                /** @type {string} */ (value), segments[1]);
          }
          if (tagName === 'tt' &&
              name === 'xml:space' &&
              value === 'preserve') {
            preserveSpace = true;
          }
          attributes[name] = value;
        }
        pos++;
      }

      if (S.charCodeAt(pos - 1) !== slashCC) {
        pos++;
        const contents = parseChildren(tagName, preserveSpace);
        children = contents;
      } else {
        pos++;
      }
      /** @type {shaka.extern.xml.Node} */
      const node = {
        tagName,
        attributes,
        children,
        parent: null,
      };
      const childrenLength = children.length;
      for (let i = 0; i < childrenLength; i++) {
        const childrenValue = children[i];
        if (typeof childrenValue !== 'string') {
          if (includeParent) {
            childrenValue.parent = node;
          }
        } else if (i == childrenLength - 1 && childrenValue == '\n') {
          children.pop();
        }
      }
      return node;
    }

    /**
     * Parse string in current context
     * @return {string}
     */
    function parseString() {
      const startChar = S[pos];
      const startPos = pos + 1;
      pos = S.indexOf(startChar, startPos);
      return S.slice(startPos, pos);
    }

    return parseChildren('');
  }
parseXML / html into a DOM Object,
with no validation and some failure tolerance
@param {string} S your XML to parse
@param {boolean} includeParent
@return {Array<shaka.extern.xml.Node>}