286 lines
6.9 KiB
JavaScript
286 lines
6.9 KiB
JavaScript
'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 IN_TEMPLATE_MODE = 'IN_TEMPLATE_MODE'
|
||
var DATA_MODE = 'DATA_STATE'
|
||
var CHARACTER_TOKEN = 'CHARACTER_TOKEN'
|
||
var START_TAG_TOKEN = 'START_TAG_TOKEN'
|
||
var END_TAG_TOKEN = 'END_TAG_TOKEN'
|
||
var COMMENT_TOKEN = 'COMMENT_TOKEN'
|
||
var DOCTYPE_TOKEN = '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(IN_TEMPLATE_MODE)
|
||
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 data mode: some elements, like
|
||
// textareas and iframes, change the state.
|
||
// See syntax-tree/hast-util-raw/issues/7.
|
||
// See https://github.com/inikulin/parse5/blob/2528196/packages/parse5/lib/tokenizer/index.js#L222
|
||
tokenizer.state = DATA_MODE
|
||
}
|
||
}
|
||
|
||
function text(node) {
|
||
parser._processToken({
|
||
type: CHARACTER_TOKEN,
|
||
chars: node.value,
|
||
location: createParse5Location(node)
|
||
})
|
||
}
|
||
|
||
function doctype(node) {
|
||
var p5 = toParse5(node)
|
||
|
||
parser._processToken({
|
||
type: DOCTYPE_TOKEN,
|
||
name: p5.name,
|
||
forceQuirks: false,
|
||
publicId: p5.publicId,
|
||
systemId: p5.systemId,
|
||
location: createParse5Location(node)
|
||
})
|
||
}
|
||
|
||
function comment(node) {
|
||
parser._processToken({
|
||
type: COMMENT_TOKEN,
|
||
data: node.value,
|
||
location: createParse5Location(node)
|
||
})
|
||
}
|
||
|
||
function raw(node) {
|
||
var start = pos.start(node)
|
||
var token
|
||
|
||
// Reset preprocessor:
|
||
// https://github.com/inikulin/parse5/blob/0491902/packages/parse5/lib/tokenizer/preprocessor.js
|
||
preprocessor.html = null
|
||
preprocessor.endOfChunkHit = false
|
||
preprocessor.lastChunkWritten = false
|
||
preprocessor.lastCharPos = -1
|
||
preprocessor.pos = -1
|
||
|
||
// Reset preprocessor mixin:
|
||
// https://github.com/inikulin/parse5/blob/0491902/packages/parse5/lib/extensions/position-tracking/preprocessor-mixin.js
|
||
posTracker.droppedBufferSize = 0
|
||
posTracker.line = start.line
|
||
posTracker.col = 1
|
||
posTracker.offset = 0
|
||
posTracker.lineStartPos = -start.column + 1
|
||
posTracker.droppedBufferSize = start.offset
|
||
|
||
// Reset location tracker:
|
||
// https://github.com/inikulin/parse5/blob/0491902/packages/parse5/lib/extensions/location-info/tokenizer-mixin.js
|
||
locationTracker.currentAttrLocation = null
|
||
locationTracker.ctLoc = createParse5Location(node)
|
||
|
||
// See the code for `parse` and `parseFragment`:
|
||
// https://github.com/inikulin/parse5/blob/0491902/packages/parse5/lib/parser/index.js#L371
|
||
tokenizer.write(node.value)
|
||
parser._runParsingLoop(null)
|
||
|
||
// Process final characters if they’re still there after hibernating.
|
||
// Similar to:
|
||
// https://github.com/inikulin/parse5/blob/3bfa7d9/packages/parse5/lib/extensions/location-info/tokenizer-mixin.js#L95
|
||
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:
|
||
// https://github.com/inikulin/parse5/blob/8b0048e/packages/parse5/lib/tokenizer/index.js#L215
|
||
tokenizer.currentToken = null
|
||
tokenizer.currentCharacterToken = null
|
||
tokenizer.currentAttr = null
|
||
}
|
||
}
|
||
|
||
function startTag(node) {
|
||
var location = createParse5Location(node)
|
||
|
||
location.startTag = xtend(location)
|
||
|
||
return {
|
||
type: START_TAG_TOKEN,
|
||
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: END_TAG_TOKEN,
|
||
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
|
||
}
|
||
}
|