'use strict' var xtend = require('xtend') var svg = require('property-information/svg') var find = require('property-information/find') var spaces = require('space-separated-tokens') var commas = require('comma-separated-tokens') var entities = require('stringify-entities') var ccount = require('ccount') var all = require('./all') var constants = require('./constants') module.exports = serializeElement function serializeElement(ctx, node, index, parent) { var schema = ctx.schema var omit = schema.space === 'svg' ? false : ctx.omit var parts = [] var selfClosing = schema.space === 'svg' ? ctx.closeEmpty : ctx.voids.indexOf(node.tagName.toLowerCase()) > -1 var attrs var content var last if (schema.space === 'html' && node.tagName === 'svg') { ctx.schema = svg } attrs = serializeAttributes(ctx, node.properties) content = all( ctx, schema.space === 'html' && node.tagName === 'template' ? node.content : node ) ctx.schema = schema // 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. if (content) selfClosing = false if (attrs || !omit || !omit.opening(node, index, parent)) { parts.push('<', node.tagName, attrs ? ' ' + attrs : '') if (selfClosing && (schema.space === 'svg' || ctx.close)) { last = attrs.charAt(attrs.length - 1) if ( !ctx.tightClose || last === '/' || (schema.space === 'svg' && last && last !== '"' && last !== "'") ) { parts.push(' ') } parts.push('/') } parts.push('>') } parts.push(content) if (!selfClosing && (!omit || !omit.closing(node, index, parent))) { parts.push('') } return parts.join('') } function serializeAttributes(ctx, props) { var values = [] var index = -1 var key var value var last for (key in props) { if (props[key] != null) { value = serializeAttribute(ctx, key, props[key]) if (value) values.push(value) } } while (++index < values.length) { last = ctx.tight ? values[index].charAt(values[index].length - 1) : null // In tight mode, don’t add a space after quoted attributes. if (index !== values.length - 1 && last !== '"' && last !== "'") { values[index] += ' ' } } return values.join('') } function serializeAttribute(ctx, key, value) { var info = find(ctx.schema, key) var quote = ctx.quote var result var name if (info.overloadedBoolean && (value === info.attribute || value === '')) { value = true } else if ( info.boolean || (info.overloadedBoolean && typeof value !== 'string') ) { value = Boolean(value) } if ( value == null || value === false || (typeof value === 'number' && value !== value) ) { return '' } name = entities( info.attribute, xtend(ctx.entities, { // Always encode without parse errors in non-HTML. subset: constants.name[ctx.schema.space === 'html' ? ctx.valid : 1][ctx.safe] }) ) // No value. // 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: if (value === true) return name value = typeof value === 'object' && 'length' in value ? // `spaces` doesn’t accept a second argument, but it’s given here just to // keep the code cleaner. (info.commaSeparated ? commas.stringify : spaces.stringify)(value, { padLeft: !ctx.tightLists }) : String(value) if (ctx.collapseEmpty && !value) return name // Check unquoted value. if (ctx.unquoted) { result = entities( value, xtend(ctx.entities, { subset: constants.unquoted[ctx.valid][ctx.safe], attribute: true }) ) } // If we don’t want unquoted, or if `value` contains character references when // unquoted… if (result !== value) { // If the alternative is less common than `quote`, switch. if (ctx.smart && ccount(value, quote) > ccount(value, ctx.alternative)) { quote = ctx.alternative } result = quote + entities( value, xtend(ctx.entities, { // Always encode without parse errors in non-HTML. subset: (quote === "'" ? constants.single : constants.double)[ ctx.schema.space === 'html' ? ctx.valid : 1 ][ctx.safe], attribute: true }) ) + quote } // Don’t add a `=` for unquoted empties. return name + (result ? '=' + result : result) }