'use strict' var Parser = require('parse5/lib/parser') var pos = require('unist-util-position') var fromParse5 = require('hast-util-from-parse5') var toParse5 = require('hast-util-to-parse5') var voids = require('html-void-elements') var ns = require('web-namespaces') var zwitch = require('zwitch') var xtend = require('xtend') module.exports = wrap var inTemplateMode = 'IN_TEMPLATE_MODE' var dataState = 'DATA_STATE' var characterToken = 'CHARACTER_TOKEN' var startTagToken = 'START_TAG_TOKEN' var endTagToken = 'END_TAG_TOKEN' var commentToken = 'COMMENT_TOKEN' var doctypeToken = 'DOCTYPE_TOKEN' var parseOptions = { sourceCodeLocationInfo: true, scriptingEnabled: false } function wrap(tree, file) { var parser = new Parser(parseOptions) var one = zwitch('type') var tokenizer var preprocessor var posTracker var locationTracker var result one.handlers.root = root one.handlers.element = element one.handlers.text = text one.handlers.comment = comment one.handlers.doctype = doctype one.handlers.raw = raw one.unknown = unknown result = fromParse5(documentMode(tree) ? document() : fragment(), file) // Unpack if possible and when not given a `root`. if (tree.type !== 'root' && result.children.length === 1) { return result.children[0] } return result function fragment() { var context var mock var doc context = { nodeName: 'template', tagName: 'template', attrs: [], namespaceURI: ns.html, childNodes: [] } mock = { nodeName: 'documentmock', tagName: 'documentmock', attrs: [], namespaceURI: ns.html, childNodes: [] } doc = { nodeName: '#document-fragment', childNodes: [] } parser._bootstrap(mock, context) parser._pushTmplInsertionMode(inTemplateMode) parser._initTokenizerForFragmentParsing() parser._insertFakeRootElement() parser._resetInsertionMode() parser._findFormInFragmentContext() tokenizer = parser.tokenizer preprocessor = tokenizer.preprocessor locationTracker = tokenizer.__mixins[0] posTracker = locationTracker.posTracker one(tree) parser._adoptNodes(mock.childNodes[0], doc) return doc } function document() { var doc = parser.treeAdapter.createDocument() parser._bootstrap(doc, null) tokenizer = parser.tokenizer preprocessor = tokenizer.preprocessor locationTracker = tokenizer.__mixins[0] posTracker = locationTracker.posTracker one(tree) return doc } function all(nodes) { var length = 0 var index = -1 /* istanbul ignore else - invalid nodes, see rehypejs/rehype-raw#7. */ if (nodes) { length = nodes.length } while (++index < length) { one(nodes[index]) } } function root(node) { all(node.children) } function element(node) { var empty = voids.indexOf(node.tagName) !== -1 parser._processToken(startTag(node), ns.html) all(node.children) if (!empty) { parser._processToken(endTag(node)) // Put the parser back in the data state: some elements, like textareas // and iframes, change the state. // See . // See . tokenizer.state = dataState } } function text(node) { parser._processToken({ type: characterToken, chars: node.value, location: createParse5Location(node) }) } function doctype(node) { var p5 = toParse5(node) parser._processToken({ type: doctypeToken, name: p5.name, forceQuirks: false, publicId: p5.publicId, systemId: p5.systemId, location: createParse5Location(node) }) } function comment(node) { parser._processToken({ type: commentToken, data: node.value, location: createParse5Location(node) }) } function raw(node) { var start = pos.start(node) var line = start.line || 1 var column = start.column || 1 var offset = start.offset || 0 var token // Reset preprocessor: // See: . preprocessor.html = null preprocessor.endOfChunkHit = false preprocessor.lastChunkWritten = false preprocessor.lastCharPos = -1 preprocessor.pos = -1 // Reset preprocessor mixin: // See: . posTracker.droppedBufferSize = 0 posTracker.line = line posTracker.col = 1 posTracker.offset = 0 posTracker.lineStartPos = -column + 1 posTracker.droppedBufferSize = offset // Reset location tracker: // See: . locationTracker.currentAttrLocation = null locationTracker.ctLoc = createParse5Location(node) // See the code for `parse` and `parseFragment`: // See: . tokenizer.write(node.value) parser._runParsingLoop(null) // Process final characters if they’re still there after hibernating. // Similar to: // See: . token = tokenizer.currentCharacterToken if (token) { token.location.endLine = posTracker.line token.location.endCol = posTracker.col + 1 token.location.endOffset = posTracker.offset + 1 parser._processToken(token) } // Reset tokenizer: // See: . tokenizer.currentToken = null tokenizer.currentCharacterToken = null tokenizer.currentAttr = null } } function startTag(node) { var location = createParse5Location(node) location.startTag = xtend(location) return { type: startTagToken, tagName: node.tagName, selfClosing: false, attrs: attributes(node), location: location } } function attributes(node) { return toParse5({ tagName: node.tagName, type: 'element', properties: node.properties }).attrs } function endTag(node) { var location = createParse5Location(node) location.endTag = xtend(location) return { type: endTagToken, tagName: node.tagName, attrs: [], location: location } } function unknown(node) { throw new Error('Cannot compile `' + node.type + '` node') } function documentMode(node) { var head = node.type === 'root' ? node.children[0] : node return head && (head.type === 'doctype' || head.tagName === 'html') } function createParse5Location(node) { var start = pos.start(node) var end = pos.end(node) return { startLine: start.line, startCol: start.column, startOffset: start.offset, endLine: end.line, endCol: end.column, endOffset: end.offset } }