2018-09-22 16:53:38 +03:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
const parseFrontMatter = require("../utils/front-matter");
|
2018-11-04 18:03:07 +03:00
|
|
|
const { HTML_ELEMENT_ATTRIBUTES, HTML_TAGS } = require("./utils");
|
|
|
|
const { hasPragma } = require("./pragma");
|
|
|
|
const createError = require("../common/parser-create-error");
|
|
|
|
const { Node } = require("./ast");
|
2018-11-15 07:35:28 +03:00
|
|
|
const { parseIeConditionalComment } = require("./conditional-comment");
|
2018-09-22 16:53:38 +03:00
|
|
|
|
2018-11-26 07:09:19 +03:00
|
|
|
function ngHtmlParser(
|
|
|
|
input,
|
2018-11-29 06:03:22 +03:00
|
|
|
{
|
|
|
|
recognizeSelfClosing,
|
|
|
|
normalizeTagName,
|
|
|
|
normalizeAttributeName,
|
2018-12-13 05:31:48 +03:00
|
|
|
allowHtmComponentClosingTags,
|
|
|
|
isTagNameCaseSensitive
|
2018-11-29 06:03:22 +03:00
|
|
|
}
|
2018-11-26 07:09:19 +03:00
|
|
|
) {
|
2018-11-04 18:03:07 +03:00
|
|
|
const parser = require("angular-html-parser");
|
|
|
|
const {
|
|
|
|
RecursiveVisitor,
|
|
|
|
visitAll,
|
|
|
|
Attribute,
|
|
|
|
CDATA,
|
|
|
|
Comment,
|
|
|
|
DocType,
|
|
|
|
Element,
|
|
|
|
Text
|
|
|
|
} = 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");
|
2018-09-22 16:53:38 +03:00
|
|
|
|
2018-11-23 08:12:43 +03:00
|
|
|
const { rootNodes, errors } = parser.parse(input, {
|
2018-11-29 06:03:22 +03:00
|
|
|
canSelfClose: recognizeSelfClosing,
|
2018-12-13 05:31:48 +03:00
|
|
|
allowHtmComponentClosingTags,
|
|
|
|
isTagNameCaseSensitive
|
2018-11-23 08:12:43 +03:00
|
|
|
});
|
2018-11-04 18:03:07 +03:00
|
|
|
|
|
|
|
if (errors.length !== 0) {
|
|
|
|
const { msg, span } = errors[0];
|
|
|
|
const { line, col } = span.start;
|
2018-11-27 12:45:17 +03:00
|
|
|
throw createError(msg, { start: { line: line + 1, column: col + 1 } });
|
2018-11-04 18:03:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
const addType = node => {
|
|
|
|
if (node instanceof Attribute) {
|
|
|
|
node.type = "attribute";
|
|
|
|
} else if (node instanceof CDATA) {
|
|
|
|
node.type = "cdata";
|
|
|
|
} else if (node instanceof Comment) {
|
|
|
|
node.type = "comment";
|
|
|
|
} else if (node instanceof DocType) {
|
|
|
|
node.type = "docType";
|
|
|
|
} else if (node instanceof Element) {
|
|
|
|
node.type = "element";
|
|
|
|
} else if (node instanceof Text) {
|
|
|
|
node.type = "text";
|
|
|
|
} else {
|
|
|
|
throw new Error(`Unexpected node ${JSON.stringify(node)}`);
|
2018-09-22 16:53:38 +03:00
|
|
|
}
|
2018-11-04 18:03:07 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
const restoreName = node => {
|
|
|
|
const namespace = node.name.startsWith(":")
|
|
|
|
? node.name.slice(1).split(":")[0]
|
|
|
|
: null;
|
|
|
|
const rawName = node.nameSpan ? node.nameSpan.toString() : node.name;
|
|
|
|
const hasExplicitNamespace = rawName.startsWith(`${namespace}:`);
|
|
|
|
const name = hasExplicitNamespace
|
|
|
|
? rawName.slice(namespace.length + 1)
|
|
|
|
: rawName;
|
|
|
|
|
|
|
|
node.name = name;
|
|
|
|
node.namespace = namespace;
|
|
|
|
node.hasExplicitNamespace = hasExplicitNamespace;
|
|
|
|
};
|
|
|
|
|
|
|
|
const restoreNameAndValue = node => {
|
|
|
|
if (node instanceof Element) {
|
|
|
|
restoreName(node);
|
|
|
|
node.attrs.forEach(attr => {
|
|
|
|
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);
|
|
|
|
}
|
2018-09-22 16:53:38 +03:00
|
|
|
}
|
2018-11-04 18:03:07 +03:00
|
|
|
});
|
|
|
|
} else if (node instanceof Comment) {
|
|
|
|
node.value = node.sourceSpan
|
|
|
|
.toString()
|
|
|
|
.slice("<!--".length, -"-->".length);
|
|
|
|
} else if (node instanceof Text) {
|
|
|
|
node.value = node.sourceSpan.toString();
|
2018-09-22 16:53:38 +03:00
|
|
|
}
|
2018-11-04 18:03:07 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
const lowerCaseIfFn = (text, fn) => {
|
|
|
|
const lowerCasedText = text.toLowerCase();
|
|
|
|
return fn(lowerCasedText) ? lowerCasedText : text;
|
|
|
|
};
|
|
|
|
const normalizeName = node => {
|
|
|
|
if (node instanceof Element) {
|
|
|
|
if (
|
2018-11-23 08:12:43 +03:00
|
|
|
normalizeTagName &&
|
|
|
|
(!node.namespace ||
|
|
|
|
node.namespace === node.tagDefinition.implicitNamespacePrefix)
|
2018-11-04 18:03:07 +03:00
|
|
|
) {
|
|
|
|
node.name = lowerCaseIfFn(
|
|
|
|
node.name,
|
|
|
|
lowerCasedName => lowerCasedName in HTML_TAGS
|
|
|
|
);
|
2018-10-13 08:55:38 +03:00
|
|
|
}
|
2018-11-04 18:03:07 +03:00
|
|
|
|
2018-11-26 07:09:19 +03:00
|
|
|
if (normalizeAttributeName) {
|
|
|
|
const CURRENT_HTML_ELEMENT_ATTRIBUTES =
|
|
|
|
HTML_ELEMENT_ATTRIBUTES[node.name] || Object.create(null);
|
|
|
|
node.attrs.forEach(attr => {
|
|
|
|
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)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2018-10-13 08:55:38 +03:00
|
|
|
}
|
2018-11-04 18:03:07 +03:00
|
|
|
};
|
2018-09-22 16:53:38 +03:00
|
|
|
|
2018-11-04 18:03:07 +03:00
|
|
|
const fixSourceSpan = node => {
|
|
|
|
if (node.sourceSpan && node.endSourceSpan) {
|
|
|
|
node.sourceSpan = new ParseSourceSpan(
|
|
|
|
node.sourceSpan.start,
|
|
|
|
node.endSourceSpan.end
|
|
|
|
);
|
2018-10-13 08:55:38 +03:00
|
|
|
}
|
2018-11-04 18:03:07 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
const addTagDefinition = node => {
|
|
|
|
if (node instanceof Element) {
|
2018-12-13 05:31:48 +03:00
|
|
|
const tagDefinition = getHtmlTagDefinition(
|
|
|
|
isTagNameCaseSensitive ? node.name : node.name.toLowerCase()
|
|
|
|
);
|
2018-11-04 18:03:07 +03:00
|
|
|
if (
|
|
|
|
!node.namespace ||
|
|
|
|
node.namespace === tagDefinition.implicitNamespacePrefix
|
|
|
|
) {
|
|
|
|
node.tagDefinition = tagDefinition;
|
|
|
|
} else {
|
|
|
|
node.tagDefinition = getHtmlTagDefinition(""); // the default one
|
2018-10-13 08:55:38 +03:00
|
|
|
}
|
2018-09-22 16:53:38 +03:00
|
|
|
}
|
2018-11-04 18:03:07 +03:00
|
|
|
};
|
2018-09-22 16:53:38 +03:00
|
|
|
|
2018-11-04 18:03:07 +03:00
|
|
|
visitAll(
|
2019-04-12 23:41:35 +03:00
|
|
|
new (class extends RecursiveVisitor {
|
2018-11-04 18:03:07 +03:00
|
|
|
visit(node) {
|
|
|
|
addType(node);
|
|
|
|
restoreNameAndValue(node);
|
|
|
|
addTagDefinition(node);
|
|
|
|
normalizeName(node);
|
|
|
|
fixSourceSpan(node);
|
|
|
|
}
|
2019-04-12 23:41:35 +03:00
|
|
|
})(),
|
2018-11-04 18:03:07 +03:00
|
|
|
rootNodes
|
2018-10-13 08:55:38 +03:00
|
|
|
);
|
2018-09-22 16:53:38 +03:00
|
|
|
|
2018-11-04 18:03:07 +03:00
|
|
|
return rootNodes;
|
|
|
|
}
|
|
|
|
|
2018-11-23 08:12:43 +03:00
|
|
|
function _parse(text, options, parserOptions, shouldParseFrontMatter = true) {
|
2018-11-04 18:03:07 +03:00
|
|
|
const { frontMatter, content } = shouldParseFrontMatter
|
|
|
|
? parseFrontMatter(text)
|
|
|
|
: { frontMatter: null, content: text };
|
|
|
|
|
|
|
|
const rawAst = {
|
|
|
|
type: "root",
|
|
|
|
sourceSpan: { start: { offset: 0 }, end: { offset: text.length } },
|
2018-11-23 08:12:43 +03:00
|
|
|
children: ngHtmlParser(content, parserOptions)
|
2018-11-04 18:03:07 +03:00
|
|
|
};
|
|
|
|
|
2018-09-22 16:53:38 +03:00
|
|
|
if (frontMatter) {
|
2018-11-04 18:03:07 +03:00
|
|
|
rawAst.children.unshift(frontMatter);
|
2018-09-22 16:53:38 +03:00
|
|
|
}
|
|
|
|
|
2018-11-04 18:03:07 +03:00
|
|
|
const ast = new Node(rawAst);
|
2018-10-13 08:55:38 +03:00
|
|
|
|
2018-11-04 18:03:07 +03:00
|
|
|
const parseSubHtml = (subContent, startSpan) => {
|
|
|
|
const { offset } = startSpan;
|
|
|
|
const fakeContent = text.slice(0, offset).replace(/[^\r\n]/g, " ");
|
|
|
|
const realContent = subContent;
|
|
|
|
const subAst = _parse(
|
|
|
|
fakeContent + realContent,
|
|
|
|
options,
|
2018-11-23 08:12:43 +03:00
|
|
|
parserOptions,
|
2018-11-04 18:03:07 +03:00
|
|
|
false
|
|
|
|
);
|
|
|
|
const ParseSourceSpan = subAst.children[0].sourceSpan.constructor;
|
|
|
|
subAst.sourceSpan = new ParseSourceSpan(
|
|
|
|
startSpan,
|
|
|
|
subAst.children[subAst.children.length - 1].sourceSpan.end
|
|
|
|
);
|
|
|
|
const firstText = subAst.children[0];
|
|
|
|
if (firstText.length === offset) {
|
|
|
|
subAst.children.shift();
|
|
|
|
} else {
|
|
|
|
firstText.sourceSpan = new ParseSourceSpan(
|
|
|
|
firstText.sourceSpan.start.moveBy(offset),
|
|
|
|
firstText.sourceSpan.end
|
|
|
|
);
|
|
|
|
firstText.value = firstText.value.slice(offset);
|
|
|
|
}
|
|
|
|
return subAst;
|
|
|
|
};
|
|
|
|
|
|
|
|
const isFakeElement = node => node.type === "element" && !node.nameSpan;
|
|
|
|
return ast.map(node => {
|
|
|
|
if (node.children && node.children.some(isFakeElement)) {
|
|
|
|
const newChildren = [];
|
|
|
|
|
|
|
|
for (const child of node.children) {
|
|
|
|
if (isFakeElement(child)) {
|
|
|
|
Array.prototype.push.apply(newChildren, child.children);
|
|
|
|
} else {
|
|
|
|
newChildren.push(child);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return node.clone({ children: newChildren });
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node.type === "comment") {
|
|
|
|
const ieConditionalComment = parseIeConditionalComment(
|
|
|
|
node,
|
|
|
|
parseSubHtml
|
|
|
|
);
|
|
|
|
if (ieConditionalComment) {
|
|
|
|
return ieConditionalComment;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return node;
|
2018-10-13 08:55:38 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-09-22 16:53:38 +03:00
|
|
|
function locStart(node) {
|
2018-11-04 18:03:07 +03:00
|
|
|
return node.sourceSpan.start.offset;
|
2018-09-22 16:53:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
function locEnd(node) {
|
2018-11-04 18:03:07 +03:00
|
|
|
return node.sourceSpan.end.offset;
|
|
|
|
}
|
|
|
|
|
2018-11-23 08:12:43 +03:00
|
|
|
function createParser({
|
|
|
|
recognizeSelfClosing = false,
|
2018-11-26 07:09:19 +03:00
|
|
|
normalizeTagName = false,
|
2018-11-29 06:03:22 +03:00
|
|
|
normalizeAttributeName = false,
|
2018-12-13 05:31:48 +03:00
|
|
|
allowHtmComponentClosingTags = false,
|
|
|
|
isTagNameCaseSensitive = false
|
2018-11-23 08:12:43 +03:00
|
|
|
} = {}) {
|
2018-11-04 18:03:07 +03:00
|
|
|
return {
|
|
|
|
parse: (text, parsers, options) =>
|
2018-11-23 08:12:43 +03:00
|
|
|
_parse(text, options, {
|
|
|
|
recognizeSelfClosing,
|
2018-11-26 07:09:19 +03:00
|
|
|
normalizeTagName,
|
2018-11-29 06:03:22 +03:00
|
|
|
normalizeAttributeName,
|
2018-12-13 05:31:48 +03:00
|
|
|
allowHtmComponentClosingTags,
|
|
|
|
isTagNameCaseSensitive
|
2018-11-23 08:12:43 +03:00
|
|
|
}),
|
2018-11-04 18:03:07 +03:00
|
|
|
hasPragma,
|
|
|
|
astFormat: "html",
|
|
|
|
locStart,
|
|
|
|
locEnd
|
|
|
|
};
|
2018-09-22 16:53:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
parsers: {
|
2018-11-26 07:09:19 +03:00
|
|
|
html: createParser({
|
2018-11-29 06:03:22 +03:00
|
|
|
recognizeSelfClosing: true,
|
2018-11-26 07:09:19 +03:00
|
|
|
normalizeTagName: true,
|
2018-11-29 06:03:22 +03:00
|
|
|
normalizeAttributeName: true,
|
|
|
|
allowHtmComponentClosingTags: true
|
2018-11-26 07:09:19 +03:00
|
|
|
}),
|
2018-11-23 08:12:43 +03:00
|
|
|
angular: createParser(),
|
2018-12-13 05:31:48 +03:00
|
|
|
vue: createParser({
|
|
|
|
recognizeSelfClosing: true,
|
|
|
|
isTagNameCaseSensitive: true
|
2019-02-01 08:58:50 +03:00
|
|
|
}),
|
|
|
|
lwc: createParser()
|
2018-09-22 16:53:38 +03:00
|
|
|
}
|
|
|
|
};
|