Files
30-seconds-of-code/node_modules/hast-util-to-html/lib/element.js
2019-08-20 15:52:05 +02:00

236 lines
5.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict'
var xtend = require('xtend')
var svg = require('property-information/svg')
var find = require('property-information/find')
var spaces = require('space-separated-tokens').stringify
var commas = require('comma-separated-tokens').stringify
var entities = require('stringify-entities')
var ccount = require('ccount')
var all = require('./all')
var constants = require('./constants')
module.exports = element
/* Constants. */
var EMPTY = ''
/* Characters. */
var SPACE = ' '
var DQ = '"'
var SQ = "'"
var EQ = '='
var LT = '<'
var GT = '>'
var SO = '/'
/* Stringify an element `node`. */
function element(ctx, node, index, parent) {
var parentSchema = ctx.schema
var name = node.tagName
var value = ''
var selfClosing
var close
var omit
var root = node
var content
var attrs
if (parentSchema.space === 'html' && name === 'svg') {
ctx.schema = svg
}
attrs = attributes(ctx, node.properties)
if (ctx.schema.space === 'svg') {
omit = false
close = true
selfClosing = ctx.closeEmpty
} else {
omit = ctx.omit
close = ctx.close
selfClosing = ctx.voids.indexOf(name.toLowerCase()) !== -1
if (name === 'template') {
root = node.content
}
}
content = all(ctx, root)
/* If the node is categorised as void, but it has
* children, remove the categorisation. This
* enables for example `menuitem`s, which are
* void in W3C HTML but not void in WHATWG HTML, to
* be stringified properly. */
selfClosing = content ? false : selfClosing
if (attrs || !omit || !omit.opening(node, index, parent)) {
value = LT + name + (attrs ? SPACE + attrs : EMPTY)
if (selfClosing && close) {
if (!ctx.tightClose || attrs.charAt(attrs.length - 1) === SO) {
value += SPACE
}
value += SO
}
value += GT
}
value += content
if (!selfClosing && (!omit || !omit.closing(node, index, parent))) {
value += LT + SO + name + GT
}
ctx.schema = parentSchema
return value
}
/* Stringify all attributes. */
function attributes(ctx, props) {
var values = []
var key
var value
var result
var length
var index
var last
for (key in props) {
value = props[key]
if (value == null) {
continue
}
result = attribute(ctx, key, value)
if (result) {
values.push(result)
}
}
length = values.length
index = -1
while (++index < length) {
result = values[index]
last = null
if (ctx.schema.space === 'html' && ctx.tight) {
last = result.charAt(result.length - 1)
}
/* In tight mode, dont add a space after quoted attributes. */
if (index !== length - 1 && last !== DQ && last !== SQ) {
values[index] = result + SPACE
}
}
return values.join(EMPTY)
}
/* Stringify one attribute. */
function attribute(ctx, key, value) {
var schema = ctx.schema
var space = schema.space
var info = find(schema, key)
var name = info.attribute
if (info.overloadedBoolean && (value === name || value === '')) {
value = true
} else if (
info.boolean ||
(info.overloadedBoolean && typeof value !== 'string')
) {
value = Boolean(value)
}
if (
value == null ||
value === false ||
(typeof value === 'number' && isNaN(value))
) {
return EMPTY
}
name = attributeName(ctx, name)
if (value === true) {
if (space === 'html') {
return name
}
value = name
}
return name + attributeValue(ctx, key, value, info)
}
/* Stringify the attribute name. */
function attributeName(ctx, name) {
// Always encode without parse errors in non-HTML.
var valid = ctx.schema.space === 'html' ? ctx.valid : 1
var subset = constants.name[valid][ctx.safe]
return entities(name, xtend(ctx.entities, {subset: subset}))
}
/* Stringify the attribute value. */
function attributeValue(ctx, key, value, info) {
var options = ctx.entities
var quote = ctx.quote
var alternative = ctx.alternative
var space = ctx.schema.space
var unquoted
var subset
if (typeof value === 'object' && 'length' in value) {
/* `spaces` doesnt accept a second argument, but its
* given here just to keep the code cleaner. */
value = (info.commaSeparated ? commas : spaces)(value, {
padLeft: !ctx.tightLists
})
}
value = String(value)
if (space !== 'html' || value || !ctx.collapseEmpty) {
unquoted = value
/* Check unquoted value. */
if (space === 'html' && ctx.unquoted) {
subset = constants.unquoted[ctx.valid][ctx.safe]
unquoted = entities(
value,
xtend(options, {subset: subset, attribute: true})
)
}
/* If `value` contains entities when unquoted... */
if (space !== 'html' || !ctx.unquoted || unquoted !== value) {
/* If the alternative is less common than `quote`, switch. */
if (alternative && ccount(value, quote) > ccount(value, alternative)) {
quote = alternative
}
subset = quote === SQ ? constants.single : constants.double
// Always encode without parse errors in non-HTML.
subset = subset[space === 'html' ? ctx.valid : 1][ctx.safe]
value = entities(value, xtend(options, {subset: subset, attribute: true}))
value = quote + value + quote
}
/* Dont add a `=` for unquoted empties. */
value = value ? EQ + value : value
}
return value
}