소스 검색

added omission of start tags

Gildas 7 년 전
부모
커밋
bdea10d55b
1개의 변경된 파일62개의 추가작업 그리고 54개의 파일을 삭제
  1. 62 54
      lib/single-file/serializer.js

+ 62 - 54
lib/single-file/serializer.js

@@ -22,28 +22,32 @@
 
 this.serializer = this.serializer || (() => {
 
-	//omittedEndTag.followings && omittedEndTag.followings.includes(nextSibling.tagName.toLowerCase())
-
 	const SELF_CLOSED_TAG_NAMES = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"];
+
+	// see https://www.w3.org/TR/html5/syntax.html#optional-tags
+	const OMITTED_START_TAGS = [
+		{ tagName: "head", accept: element => !element.childNodes.length || element.childNodes[0].nodeType == Node.ELEMENT_NODE },
+		{ tagName: "body", accept: element => !element.childNodes.length },
+	];
 	const OMITTED_END_TAGS = [
-		{ tagName: "html", acceptNode: next => !next || next.nodeType != Node.COMMENT_NODE },
-		{ tagName: "head", acceptNode: next => !next || next.nodeType != Node.COMMENT_NODE && (next.nodeType != Node.TEXT_NODE || !spaceFirstCharacter(next.textContent)) },
-		{ tagName: "body", acceptNode: next => !next || next.nodeType != Node.COMMENT_NODE },
-		{ tagName: "li", acceptNode: next => !next || ["LI"].includes(next.tagName) },
-		{ tagName: "dt", acceptNode: next => !next || ["DT", "DD"].includes(next.tagName) },
-		{ tagName: "dd", acceptNode: next => !next || ["DT", "DD"].includes(next.tagName) },
-		{ tagName: "rt", acceptNode: next => !next || ["RT", "RP"].includes(next.tagName) },
-		{ tagName: "rp", acceptNode: next => !next || ["RT", "RP"].includes(next.tagName) },
-		{ tagName: "optgroup", acceptNode: next => !next || ["OPTGROUP"].includes(next.tagName) },
-		{ tagName: "option", acceptNode: next => !next || ["OPTION", "OPTGROUP"].includes(next.tagName) },
-		{ tagName: "colgroup", acceptNode: next => !next || next.nodeType != Node.COMMENT_NODE && (next.nodeType != Node.TEXT_NODE || !spaceFirstCharacter(next.textContent)) },
-		{ tagName: "caption", acceptNode: next => !next || next.nodeType != Node.COMMENT_NODE && (next.nodeType != Node.TEXT_NODE || !spaceFirstCharacter(next.textContent)) },
-		{ tagName: "thead", acceptNode: next => !next || ["TBODY", "TFOOT"].includes(next.tagName) },
-		{ tagName: "tbody", acceptNode: next => !next || ["TBODY", "TFOOT"].includes(next.tagName) },
-		{ tagName: "tfoot", acceptNode: next => !next },
-		{ tagName: "tr", acceptNode: next => !next || ["TR"].includes(next.tagName) },
-		{ tagName: "td", acceptNode: next => !next || ["TD", "TH"].includes(next.tagName) },
-		{ tagName: "th", acceptNode: next => !next || ["TD", "TH"].includes(next.tagName) },
+		{ tagName: "html", accept: next => !next || next.nodeType != Node.COMMENT_NODE },
+		{ tagName: "head", accept: next => !next || next.nodeType != Node.COMMENT_NODE && (next.nodeType != Node.TEXT_NODE || !spaceFirstCharacter(next.textContent)) },
+		{ tagName: "body", accept: next => !next || next.nodeType != Node.COMMENT_NODE },
+		{ tagName: "li", accept: next => !next || ["LI"].includes(next.tagName) },
+		{ tagName: "dt", accept: next => !next || ["DT", "DD"].includes(next.tagName) },
+		{ tagName: "dd", accept: next => !next || ["DT", "DD"].includes(next.tagName) },
+		{ tagName: "rt", accept: next => !next || ["RT", "RP"].includes(next.tagName) },
+		{ tagName: "rp", accept: next => !next || ["RT", "RP"].includes(next.tagName) },
+		{ tagName: "optgroup", accept: next => !next || ["OPTGROUP"].includes(next.tagName) },
+		{ tagName: "option", accept: next => !next || ["OPTION", "OPTGROUP"].includes(next.tagName) },
+		{ tagName: "colgroup", accept: next => !next || next.nodeType != Node.COMMENT_NODE && (next.nodeType != Node.TEXT_NODE || !spaceFirstCharacter(next.textContent)) },
+		{ tagName: "caption", accept: next => !next || next.nodeType != Node.COMMENT_NODE && (next.nodeType != Node.TEXT_NODE || !spaceFirstCharacter(next.textContent)) },
+		{ tagName: "thead", accept: next => !next || ["TBODY", "TFOOT"].includes(next.tagName) },
+		{ tagName: "tbody", accept: next => !next || ["TBODY", "TFOOT"].includes(next.tagName) },
+		{ tagName: "tfoot", accept: next => !next },
+		{ tagName: "tr", accept: next => !next || ["TR"].includes(next.tagName) },
+		{ tagName: "td", accept: next => !next || ["TD", "TH"].includes(next.tagName) },
+		{ tagName: "th", accept: next => !next || ["TD", "TH"].includes(next.tagName) },
 	];
 
 	return {
@@ -90,46 +94,50 @@ this.serializer = this.serializer || (() => {
 
 	function serializeElement(element) {
 		const tagName = element.tagName.toLowerCase();
-		let content = "<" + tagName;
-		Array.from(element.attributes).forEach(attribute => {
-			const name = attribute.name.replace(/["'>/=]/g, "");
-			let value = attribute.value;
-			if (name == "class") {
-				value = Array.from(element.classList).map(className => className.trim()).join(" ");
-			}
-			value = value.replace(/&/g, "&amp;").replace(/\u00a0/g, "&nbsp;").replace(/"/g, "&quot;");
-			const validUnquotedValue = value.match(/^[^ \t\n\f\r"'`=<>]+$/);
-			content += " ";
-			if (!attribute.namespace) {
-				content += name;
-			} else if (attribute.namespaceURI == "http://www.w3.org/XML/1998/namespace") {
-				content += "xml:" + name;
-			} else if (attribute.namespaceURI == "http://www.w3.org/2000/xmlns/") {
-				if (name !== "xmlns") {
-					content += "xmlns:";
+		const omittedStartTag = OMITTED_START_TAGS.find(omittedStartTag => tagName == omittedStartTag.tagName && omittedStartTag.accept(element));
+		let content = "";
+		if (!omittedStartTag || element.attributes.length) {
+			content = "<" + tagName;
+			Array.from(element.attributes).forEach(attribute => {
+				const name = attribute.name.replace(/["'>/=]/g, "");
+				let value = attribute.value;
+				if (name == "class") {
+					value = Array.from(element.classList).map(className => className.trim()).join(" ");
 				}
-				content += name;
-			} else if (attribute.namespaceURI == "http://www.w3.org/1999/xlink") {
-				content += "xlink:" + name;
-			} else {
-				content += name;
-			}
-			if (value != "") {
-				content += "=";
-				if (!validUnquotedValue) {
-					content += "\"";
+				value = value.replace(/&/g, "&amp;").replace(/\u00a0/g, "&nbsp;").replace(/"/g, "&quot;");
+				const validUnquotedValue = value.match(/^[^ \t\n\f\r"'`=<>]+$/);
+				content += " ";
+				if (!attribute.namespace) {
+					content += name;
+				} else if (attribute.namespaceURI == "http://www.w3.org/XML/1998/namespace") {
+					content += "xml:" + name;
+				} else if (attribute.namespaceURI == "http://www.w3.org/2000/xmlns/") {
+					if (name !== "xmlns") {
+						content += "xmlns:";
+					}
+					content += name;
+				} else if (attribute.namespaceURI == "http://www.w3.org/1999/xlink") {
+					content += "xlink:" + name;
+				} else {
+					content += name;
 				}
-				content += value;
-				if (!validUnquotedValue) {
-					content += "\"";
+				if (value != "") {
+					content += "=";
+					if (!validUnquotedValue) {
+						content += "\"";
+					}
+					content += value;
+					if (!validUnquotedValue) {
+						content += "\"";
+					}
 				}
-			}
-		});
-		content += ">";
+			});
+			content += ">";
+		}
 		Array.from(element.childNodes).forEach(childNode => content += serialize(childNode));
 		const omittedEndTag = OMITTED_END_TAGS.find(omittedEndTag => {
 			const nextSibling = element.nextSibling;
-			return tagName == omittedEndTag.tagName && omittedEndTag.acceptNode(nextSibling);
+			return tagName == omittedEndTag.tagName && omittedEndTag.accept(nextSibling);
 		});
 		if (!omittedEndTag && !SELF_CLOSED_TAG_NAMES.includes(tagName)) {
 			content += "</" + tagName + ">";