src/language-html/parser-html.js/shouldParseAsHTML
function ngHtmlParser(
  input,
  {
    recognizeSelfClosing,
    normalizeTagName,
    normalizeAttributeName,
    allowHtmComponentClosingTags,
    isTagNameCaseSensitive,
    getTagContentType,
  },
  options
) {
  const parser = require("angular-html-parser");
  const {
    RecursiveVisitor,
    visitAll,
  } = require("angular-html-parser/lib/compiler/src/ml_parser/ast");
  const {
    ParseSourceSpan,
  } = require("angular-html-parser/lib/compiler/src/parse_util");
  const {
    getHtmlTagDefinition,
  } = require("angular-html-parser/lib/compiler/src/ml_parser/html_tags");

  let { rootNodes, errors } = parser.parse(input, {
    canSelfClose: recognizeSelfClosing,
    allowHtmComponentClosingTags,
    isTagNameCaseSensitive,
    getTagContentType,
  });

  if (options.parser === "vue") {
    const isVueHtml = rootNodes.some(
      (node) =>
        (node.type === "docType" && node.value === "html") ||
        (node.type === "element" && node.name.toLowerCase() === "html")
    );

    if (!isVueHtml) {
      const shouldParseAsHTML = (/** @type {AstNode} */ node) => {
        /* istanbul ignore next */
        if (!node) {
          return false;
        }
        if (node.type !== "element" || node.name !== "template") {
          return false;
        }
        const langAttr = node.attrs.find((attr) => attr.name === "lang");
        const langValue = langAttr && langAttr.value;
        return (
          !langValue || inferParserByLanguage(langValue, options) === "html"
        );
      };
      if (rootNodes.some(shouldParseAsHTML)) {
        /** @type {ParserTreeResult | undefined} */
        let secondParseResult;
        const doSecondParse = () =>
          parser.parse(input, {
            canSelfClose: recognizeSelfClosing,
            allowHtmComponentClosingTags,
            isTagNameCaseSensitive,
          });
        const getSecondParse = () =>
          secondParseResult || (secondParseResult = doSecondParse());
        const getSameLocationNode = (node) =>
          getSecondParse().rootNodes.find(
            ({ startSourceSpan }) =>
              startSourceSpan &&
              startSourceSpan.start.offset === node.startSourceSpan.start.offset
          );
        for (let i = 0; i < rootNodes.length; i++) {
          const node = rootNodes[i];
          const { endSourceSpan, startSourceSpan } = node;
          const isUnclosedNode = endSourceSpan === null;
          if (isUnclosedNode) {
            const result = getSecondParse();
            errors = result.errors;
            rootNodes[i] = getSameLocationNode(node) || node;
          } else if (shouldParseAsHTML(node)) {
            const result = getSecondParse();
            const startOffset = startSourceSpan.end.offset;
            const endOffset = endSourceSpan.start.offset;
            for (const error of result.errors) {
              const { offset } = error.span.start;
              /* istanbul ignore next */
              if (startOffset < offset && offset < endOffset) {
                errors = [error];
                break;
              }
            }
            rootNodes[i] = getSameLocationNode(node) || node;
          }
        }
      }
    } else {
      // If not Vue SFC, treat as html
      recognizeSelfClosing = true;
      normalizeTagName = true;
      normalizeAttributeName = true;
      allowHtmComponentClosingTags = true;
      isTagNameCaseSensitive = false;
      const htmlParseResult = parser.parse(input, {
        canSelfClose: recognizeSelfClosing,
        allowHtmComponentClosingTags,
        isTagNameCaseSensitive,
      });

      rootNodes = htmlParseResult.rootNodes;
      errors = htmlParseResult.errors;
    }
  }

  if (errors.length > 0) {
    const {
      msg,
      span: { start, end },
    } = errors[0];
    throw createError(msg, {
      start: { line: start.line + 1, column: start.col + 1 },
      end: { line: end.line + 1, column: end.col + 1 },
    });
  }

  /**
   * @param {Attribute | Element} node
   */
  const restoreName = (node) => {
    const namespace = node.name.startsWith(":")
      ? node.name.slice(1).split(":")[0]
      : null;
    const rawName = node.nameSpan.toString();
    const hasExplicitNamespace =
      namespace !== null && rawName.startsWith(`${namespace}:`);
    const name = hasExplicitNamespace
      ? rawName.slice(namespace.length + 1)
      : rawName;

    node.name = name;
    node.namespace = namespace;
    node.hasExplicitNamespace = hasExplicitNamespace;
  };

  /**
   * @param {AstNode} node
   */
  const restoreNameAndValue = (node) => {
    switch (node.type) {
      case "element":
        restoreName(node);
        for (const attr of node.attrs) {
          restoreName(attr);
          if (!attr.valueSpan) {
            attr.value = null;
          } else {
            attr.value = attr.valueSpan.toString();
            if (/["']/.test(attr.value[0])) {
              attr.value = attr.value.slice(1, -1);
            }
          }
        }
        break;
      case "comment":
        node.value = node.sourceSpan
          .toString()
          .slice("<!--".length, -"-->".length);
        break;
      case "text":
        node.value = node.sourceSpan.toString();
        break;
      // No default
    }
  };

  const lowerCaseIfFn = (text, fn) => {
    const lowerCasedText = text.toLowerCase();
    return fn(lowerCasedText) ? lowerCasedText : text;
  };
  const normalizeName = (node) => {
    if (node.type === "element") {
      if (
        normalizeTagName &&
        (!node.namespace ||
          node.namespace === node.tagDefinition.implicitNamespacePrefix ||
          isUnknownNamespace(node))
      ) {
        node.name = lowerCaseIfFn(
          node.name,
          (lowerCasedName) => lowerCasedName in HTML_TAGS
        );
      }

      if (normalizeAttributeName) {
        const CURRENT_HTML_ELEMENT_ATTRIBUTES =
          HTML_ELEMENT_ATTRIBUTES[node.name] || Object.create(null);
        for (const attr of node.attrs) {
          if (!attr.namespace) {
            attr.name = lowerCaseIfFn(
              attr.name,
              (lowerCasedAttrName) =>
                node.name in HTML_ELEMENT_ATTRIBUTES &&
                (lowerCasedAttrName in HTML_ELEMENT_ATTRIBUTES["*"] ||
                  lowerCasedAttrName in CURRENT_HTML_ELEMENT_ATTRIBUTES)
            );
          }
        }
      }
    }
  };

  const fixSourceSpan = (node) => {
    if (node.sourceSpan && node.endSourceSpan) {
      node.sourceSpan = new ParseSourceSpan(
        node.sourceSpan.start,
        node.endSourceSpan.end
      );
    }
  };

  /**
   * @param {AstNode} node
   */
  const addTagDefinition = (node) => {
    if (node.type === "element") {
      const tagDefinition = getHtmlTagDefinition(
        isTagNameCaseSensitive ? node.name : node.name.toLowerCase()
      );
      if (
        !node.namespace ||
        node.namespace === tagDefinition.implicitNamespacePrefix ||
        isUnknownNamespace(node)
      ) {
        node.tagDefinition = tagDefinition;
      } else {
        node.tagDefinition = getHtmlTagDefinition(""); // the default one
      }
    }
  };

  visitAll(
    new (class extends RecursiveVisitor {
      visit(node) {
        restoreNameAndValue(node);
        addTagDefinition(node);
        normalizeName(node);
        fixSourceSpan(node);
      }
    })(),
    rootNodes
  );

  return rootNodes;
}