'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 = serializeElement var space = ' ' var quotationMark = '"' var apostrophe = "'" var equalsTo = '=' var lessThan = '<' var greaterThan = '>' var slash = '/' // eslint-disable-next-line complexity function serializeElement(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 var last if (parentSchema.space === 'html' && name === 'svg') { ctx.schema = svg } attrs = serializeAttributes(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 = lessThan + name + (attrs ? space + attrs : '') if (selfClosing && close) { last = attrs.charAt(attrs.length - 1) if ( !ctx.tightClose || last === slash || (ctx.schema.space === 'svg' && last && last !== quotationMark && last !== apostrophe) ) { value += space } value += slash } value += greaterThan } value += content if (!selfClosing && (!omit || !omit.closing(node, index, parent))) { value += lessThan + slash + name + greaterThan } ctx.schema = parentSchema return value } function serializeAttributes(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 || value === undefined) { continue } result = serializeAttribute(ctx, key, value) if (result) { values.push(result) } } length = values.length index = -1 while (++index < length) { result = values[index] last = null if (ctx.tight) { last = result.charAt(result.length - 1) } // In tight mode, don’t add a space after quoted attributes. if (index !== length - 1 && last !== quotationMark && last !== apostrophe) { values[index] = result + space } } return values.join('') } function serializeAttribute(ctx, key, value) { var schema = ctx.schema 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 === undefined || value === false || (typeof value === 'number' && isNaN(value)) ) { return '' } name = serializeAttributeName(ctx, name) if (value === true) { // There is currently only one boolean property in SVG: `[download]` on // ``. // This property does not seem to work in browsers (FF, Sa, Ch), so I can’t // test if dropping the value works. // But I assume that it should: // // ```html // // // // // // // ``` // // See: return name } return name + serializeAttributeValue(ctx, key, value, info) } function serializeAttributeName(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})) } function serializeAttributeValue(ctx, key, value, info) { var options = ctx.entities var quote = ctx.quote var alternative = ctx.alternative var smart = ctx.smart 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 (value || !ctx.collapseEmpty) { unquoted = value // Check unquoted value. if (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 (!ctx.unquoted || unquoted !== value) { // If the alternative is less common than `quote`, switch. if (smart && ccount(value, quote) > ccount(value, alternative)) { quote = alternative } subset = quote === apostrophe ? constants.single : constants.double // Always encode without parse errors in non-HTML. subset = subset[ctx.schema.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 ? equalsTo + value : value } return value }