/* * Copyright (C) 2019 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ HTMLFormatter = class HTMLFormatter { constructor(sourceText, sourceType, builder, indentString = " ") { console.assert(typeof sourceText === "string"); console.assert(Object.values(HTMLFormatter.SourceType).includes(sourceType)); this._sourceType = sourceType; this._success = false; let dom = (function() { try { let options = { isXML: sourceType === HTMLFormatter.SourceType.XML, }; let parser = new HTMLParser; let treeBuilder = new HTMLTreeBuilderFormatter(options); parser.parseDocument(sourceText, treeBuilder, options); return treeBuilder.dom; } catch (e) { console.error("Unexpected HTMLFormatter Error", e); return null; } })(); if (!dom) return; this._sourceText = sourceText; this._builder = builder; if (!this._builder) { this._builder = new FormatterContentBuilder(indentString); this._builder.setOriginalLineEndings(this._sourceText.lineEndings()); } this._walkArray(dom, null); this._builder.appendNewline(); this._builder.appendMapping(this._sourceText.length); this._success = true; } // Public get success() { return this._success; } get formattedText() { if (!this._success) return null; return this._builder.formattedContent; } get sourceMapData() { if (!this._success) return null; return this._builder.sourceMapData; } // Private _walk(node, parent) { if (!node) return; this._before(node, parent); this._walkArray(node.children, node); this._after(node, parent); } _walkArray(children, parent) { if (!children) return; this._previousSiblingNode = null; for (let child of children) { this._walk(child, parent); this._previousSiblingNode = child; } } _shouldHaveNoChildren(node) { switch (this._sourceType) { case HTMLFormatter.SourceType.HTML: return HTMLTreeBuilderFormatter.TagNamesWithoutChildren.has(node.lowercaseName); case HTMLFormatter.SourceType.XML: return false; } console.assert(false, "Unknown source type", this._sourceType); return false; } _shouldHaveInlineContent(node) { if (node.__shouldHaveNoChildren) return true; let children = node.children; if (!children) return true; if (!children.length) return true; if (children.length === 1 && node.children[0].type === HTMLTreeBuilderFormatter.NodeType.Text) return true; return false; } _hasMultipleNewLines(text) { let firstIndex = text.indexOf("\n"); if (firstIndex === -1) return false; let secondIndex = text.indexOf("\n", firstIndex + 1); if (secondIndex === -1) return false; return true; } _buildAttributeString(attr) { this._builder.appendSpace(); let {name, value, quote, namePos, valuePos} = attr; if (value !== undefined) { let q; switch (quote) { case HTMLParser.AttrQuoteType.None: q = ``; break; case HTMLParser.AttrQuoteType.Single: q = `'`; break; case HTMLParser.AttrQuoteType.Double: q = `"`; break; default: console.assert(false, "Unexpected quote type", quote); q = ``; break; } this._builder.appendToken(name, namePos); this._builder.appendNonToken("="); if (q) this._builder.appendStringWithPossibleNewlines(q + value + q, valuePos); else this._builder.appendToken(value, valuePos); return; } console.assert(quote === HTMLParser.AttrQuoteType.None); this._builder.appendToken(name, namePos); } _before(node, parent) { if (node.type === HTMLTreeBuilderFormatter.NodeType.Node) { node.__shouldHaveNoChildren = this._shouldHaveNoChildren(node); node.__inlineContent = this._shouldHaveInlineContent(node); if (this._previousSiblingNode && this._previousSiblingNode.type === HTMLTreeBuilderFormatter.NodeType.Text) this._builder.appendNewline(); this._builder.appendToken("<" + node.name, node.pos); if (node.attributes) { for (let attr of node.attributes) this._buildAttributeString(attr); } if (node.selfClose) this._builder.appendNonToken("/"); this._builder.appendNonToken(">"); if (node.selfClose || node.__shouldHaveNoChildren) this._builder.appendNewline(); if (!node.__inlineContent) { if (node.lowercaseName !== "html" || this._sourceType === HTMLFormatter.SourceType.XML) this._builder.indent(); this._builder.appendNewline(); } return; } if (node.type === HTMLTreeBuilderFormatter.NodeType.Text) { //