| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- '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, don’t 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` doesn’t accept a second argument, but it’s
- * 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
- }
- /* Don’t add a `=` for unquoted empties. */
- value = value ? EQ + value : value
- }
- return value
- }
|