UNPKG

rehype-table-merge

Version:

A rehype plugin for merging cells of table elements.

1,985 lines (1,841 loc) 126 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); /** * @typedef {import('./info.js').Info} Info * @typedef {Record<string, Info>} Properties * @typedef {Record<string, string>} Normal */ class Schema { /** * @constructor * @param {Properties} property * @param {Normal} normal * @param {string} [space] */ constructor(property, normal, space) { this.property = property; this.normal = normal; if (space) { this.space = space; } } } /** @type {Properties} */ Schema.prototype.property = {}; /** @type {Normal} */ Schema.prototype.normal = {}; /** @type {string|null} */ Schema.prototype.space = null; /** * @typedef {import('./schema.js').Properties} Properties * @typedef {import('./schema.js').Normal} Normal */ /** * @param {Schema[]} definitions * @param {string} [space] * @returns {Schema} */ function merge(definitions, space) { /** @type {Properties} */ const property = {}; /** @type {Normal} */ const normal = {}; let index = -1; while (++index < definitions.length) { Object.assign(property, definitions[index].property); Object.assign(normal, definitions[index].normal); } return new Schema(property, normal, space) } /** * @param {string} value * @returns {string} */ function normalize(value) { return value.toLowerCase() } class Info { /** * @constructor * @param {string} property * @param {string} attribute */ constructor(property, attribute) { /** @type {string} */ this.property = property; /** @type {string} */ this.attribute = attribute; } } /** @type {string|null} */ Info.prototype.space = null; Info.prototype.boolean = false; Info.prototype.booleanish = false; Info.prototype.overloadedBoolean = false; Info.prototype.number = false; Info.prototype.commaSeparated = false; Info.prototype.spaceSeparated = false; Info.prototype.commaOrSpaceSeparated = false; Info.prototype.mustUseProperty = false; Info.prototype.defined = false; let powers = 0; const boolean = increment(); const booleanish = increment(); const overloadedBoolean = increment(); const number = increment(); const spaceSeparated = increment(); const commaSeparated = increment(); const commaOrSpaceSeparated = increment(); function increment() { return 2 ** ++powers } var types = /*#__PURE__*/Object.freeze({ __proto__: null, boolean: boolean, booleanish: booleanish, overloadedBoolean: overloadedBoolean, number: number, spaceSeparated: spaceSeparated, commaSeparated: commaSeparated, commaOrSpaceSeparated: commaOrSpaceSeparated }); /** @type {Array<keyof types>} */ // @ts-expect-error: hush. const checks = Object.keys(types); class DefinedInfo extends Info { /** * @constructor * @param {string} property * @param {string} attribute * @param {number|null} [mask] * @param {string} [space] */ constructor(property, attribute, mask, space) { let index = -1; super(property, attribute); mark(this, 'space', space); if (typeof mask === 'number') { while (++index < checks.length) { const check = checks[index]; mark(this, checks[index], (mask & types[check]) === types[check]); } } } } DefinedInfo.prototype.defined = true; /** * @param {DefinedInfo} values * @param {string} key * @param {unknown} value */ function mark(values, key, value) { if (value) { // @ts-expect-error: assume `value` matches the expected value of `key`. values[key] = value; } } /** * @typedef {import('./schema.js').Properties} Properties * @typedef {import('./schema.js').Normal} Normal * * @typedef {Record<string, string>} Attributes * * @typedef {Object} Definition * @property {Record<string, number|null>} properties * @property {(attributes: Attributes, property: string) => string} transform * @property {string} [space] * @property {Attributes} [attributes] * @property {Array<string>} [mustUseProperty] */ const own$3 = {}.hasOwnProperty; /** * @param {Definition} definition * @returns {Schema} */ function create(definition) { /** @type {Properties} */ const property = {}; /** @type {Normal} */ const normal = {}; /** @type {string} */ let prop; for (prop in definition.properties) { if (own$3.call(definition.properties, prop)) { const value = definition.properties[prop]; const info = new DefinedInfo( prop, definition.transform(definition.attributes || {}, prop), value, definition.space ); if ( definition.mustUseProperty && definition.mustUseProperty.includes(prop) ) { info.mustUseProperty = true; } property[prop] = info; normal[normalize(prop)] = prop; normal[normalize(info.attribute)] = prop; } } return new Schema(property, normal, definition.space) } const xlink = create({ space: 'xlink', transform(_, prop) { return 'xlink:' + prop.slice(5).toLowerCase() }, properties: { xLinkActuate: null, xLinkArcRole: null, xLinkHref: null, xLinkRole: null, xLinkShow: null, xLinkTitle: null, xLinkType: null } }); const xml = create({ space: 'xml', transform(_, prop) { return 'xml:' + prop.slice(3).toLowerCase() }, properties: {xmlLang: null, xmlBase: null, xmlSpace: null} }); /** * @param {Record<string, string>} attributes * @param {string} attribute * @returns {string} */ function caseSensitiveTransform(attributes, attribute) { return attribute in attributes ? attributes[attribute] : attribute } /** * @param {Record<string, string>} attributes * @param {string} property * @returns {string} */ function caseInsensitiveTransform(attributes, property) { return caseSensitiveTransform(attributes, property.toLowerCase()) } const xmlns = create({ space: 'xmlns', attributes: {xmlnsxlink: 'xmlns:xlink'}, transform: caseInsensitiveTransform, properties: {xmlns: null, xmlnsXLink: null} }); const aria = create({ transform(_, prop) { return prop === 'role' ? prop : 'aria-' + prop.slice(4).toLowerCase() }, properties: { ariaActiveDescendant: null, ariaAtomic: booleanish, ariaAutoComplete: null, ariaBusy: booleanish, ariaChecked: booleanish, ariaColCount: number, ariaColIndex: number, ariaColSpan: number, ariaControls: spaceSeparated, ariaCurrent: null, ariaDescribedBy: spaceSeparated, ariaDetails: null, ariaDisabled: booleanish, ariaDropEffect: spaceSeparated, ariaErrorMessage: null, ariaExpanded: booleanish, ariaFlowTo: spaceSeparated, ariaGrabbed: booleanish, ariaHasPopup: null, ariaHidden: booleanish, ariaInvalid: null, ariaKeyShortcuts: null, ariaLabel: null, ariaLabelledBy: spaceSeparated, ariaLevel: number, ariaLive: null, ariaModal: booleanish, ariaMultiLine: booleanish, ariaMultiSelectable: booleanish, ariaOrientation: null, ariaOwns: spaceSeparated, ariaPlaceholder: null, ariaPosInSet: number, ariaPressed: booleanish, ariaReadOnly: booleanish, ariaRelevant: null, ariaRequired: booleanish, ariaRoleDescription: spaceSeparated, ariaRowCount: number, ariaRowIndex: number, ariaRowSpan: number, ariaSelected: booleanish, ariaSetSize: number, ariaSort: null, ariaValueMax: number, ariaValueMin: number, ariaValueNow: number, ariaValueText: null, role: null } }); const html$1 = create({ space: 'html', attributes: { acceptcharset: 'accept-charset', classname: 'class', htmlfor: 'for', httpequiv: 'http-equiv' }, transform: caseInsensitiveTransform, mustUseProperty: ['checked', 'multiple', 'muted', 'selected'], properties: { // Standard Properties. abbr: null, accept: commaSeparated, acceptCharset: spaceSeparated, accessKey: spaceSeparated, action: null, allow: null, allowFullScreen: boolean, allowPaymentRequest: boolean, allowUserMedia: boolean, alt: null, as: null, async: boolean, autoCapitalize: null, autoComplete: spaceSeparated, autoFocus: boolean, autoPlay: boolean, capture: boolean, charSet: null, checked: boolean, cite: null, className: spaceSeparated, cols: number, colSpan: null, content: null, contentEditable: booleanish, controls: boolean, controlsList: spaceSeparated, coords: number | commaSeparated, crossOrigin: null, data: null, dateTime: null, decoding: null, default: boolean, defer: boolean, dir: null, dirName: null, disabled: boolean, download: overloadedBoolean, draggable: booleanish, encType: null, enterKeyHint: null, form: null, formAction: null, formEncType: null, formMethod: null, formNoValidate: boolean, formTarget: null, headers: spaceSeparated, height: number, hidden: boolean, high: number, href: null, hrefLang: null, htmlFor: spaceSeparated, httpEquiv: spaceSeparated, id: null, imageSizes: null, imageSrcSet: null, inputMode: null, integrity: null, is: null, isMap: boolean, itemId: null, itemProp: spaceSeparated, itemRef: spaceSeparated, itemScope: boolean, itemType: spaceSeparated, kind: null, label: null, lang: null, language: null, list: null, loading: null, loop: boolean, low: number, manifest: null, max: null, maxLength: number, media: null, method: null, min: null, minLength: number, multiple: boolean, muted: boolean, name: null, nonce: null, noModule: boolean, noValidate: boolean, onAbort: null, onAfterPrint: null, onAuxClick: null, onBeforePrint: null, onBeforeUnload: null, onBlur: null, onCancel: null, onCanPlay: null, onCanPlayThrough: null, onChange: null, onClick: null, onClose: null, onContextLost: null, onContextMenu: null, onContextRestored: null, onCopy: null, onCueChange: null, onCut: null, onDblClick: null, onDrag: null, onDragEnd: null, onDragEnter: null, onDragExit: null, onDragLeave: null, onDragOver: null, onDragStart: null, onDrop: null, onDurationChange: null, onEmptied: null, onEnded: null, onError: null, onFocus: null, onFormData: null, onHashChange: null, onInput: null, onInvalid: null, onKeyDown: null, onKeyPress: null, onKeyUp: null, onLanguageChange: null, onLoad: null, onLoadedData: null, onLoadedMetadata: null, onLoadEnd: null, onLoadStart: null, onMessage: null, onMessageError: null, onMouseDown: null, onMouseEnter: null, onMouseLeave: null, onMouseMove: null, onMouseOut: null, onMouseOver: null, onMouseUp: null, onOffline: null, onOnline: null, onPageHide: null, onPageShow: null, onPaste: null, onPause: null, onPlay: null, onPlaying: null, onPopState: null, onProgress: null, onRateChange: null, onRejectionHandled: null, onReset: null, onResize: null, onScroll: null, onSecurityPolicyViolation: null, onSeeked: null, onSeeking: null, onSelect: null, onSlotChange: null, onStalled: null, onStorage: null, onSubmit: null, onSuspend: null, onTimeUpdate: null, onToggle: null, onUnhandledRejection: null, onUnload: null, onVolumeChange: null, onWaiting: null, onWheel: null, open: boolean, optimum: number, pattern: null, ping: spaceSeparated, placeholder: null, playsInline: boolean, poster: null, preload: null, readOnly: boolean, referrerPolicy: null, rel: spaceSeparated, required: boolean, reversed: boolean, rows: number, rowSpan: number, sandbox: spaceSeparated, scope: null, scoped: boolean, seamless: boolean, selected: boolean, shape: null, size: number, sizes: null, slot: null, span: number, spellCheck: booleanish, src: null, srcDoc: null, srcLang: null, srcSet: null, start: number, step: null, style: null, tabIndex: number, target: null, title: null, translate: null, type: null, typeMustMatch: boolean, useMap: null, value: booleanish, width: number, wrap: null, // Legacy. // See: https://html.spec.whatwg.org/#other-elements,-attributes-and-apis align: null, // Several. Use CSS `text-align` instead, aLink: null, // `<body>`. Use CSS `a:active {color}` instead archive: spaceSeparated, // `<object>`. List of URIs to archives axis: null, // `<td>` and `<th>`. Use `scope` on `<th>` background: null, // `<body>`. Use CSS `background-image` instead bgColor: null, // `<body>` and table elements. Use CSS `background-color` instead border: number, // `<table>`. Use CSS `border-width` instead, borderColor: null, // `<table>`. Use CSS `border-color` instead, bottomMargin: number, // `<body>` cellPadding: null, // `<table>` cellSpacing: null, // `<table>` char: null, // Several table elements. When `align=char`, sets the character to align on charOff: null, // Several table elements. When `char`, offsets the alignment classId: null, // `<object>` clear: null, // `<br>`. Use CSS `clear` instead code: null, // `<object>` codeBase: null, // `<object>` codeType: null, // `<object>` color: null, // `<font>` and `<hr>`. Use CSS instead compact: boolean, // Lists. Use CSS to reduce space between items instead declare: boolean, // `<object>` event: null, // `<script>` face: null, // `<font>`. Use CSS instead frame: null, // `<table>` frameBorder: null, // `<iframe>`. Use CSS `border` instead hSpace: number, // `<img>` and `<object>` leftMargin: number, // `<body>` link: null, // `<body>`. Use CSS `a:link {color: *}` instead longDesc: null, // `<frame>`, `<iframe>`, and `<img>`. Use an `<a>` lowSrc: null, // `<img>`. Use a `<picture>` marginHeight: number, // `<body>` marginWidth: number, // `<body>` noResize: boolean, // `<frame>` noHref: boolean, // `<area>`. Use no href instead of an explicit `nohref` noShade: boolean, // `<hr>`. Use background-color and height instead of borders noWrap: boolean, // `<td>` and `<th>` object: null, // `<applet>` profile: null, // `<head>` prompt: null, // `<isindex>` rev: null, // `<link>` rightMargin: number, // `<body>` rules: null, // `<table>` scheme: null, // `<meta>` scrolling: booleanish, // `<frame>`. Use overflow in the child context standby: null, // `<object>` summary: null, // `<table>` text: null, // `<body>`. Use CSS `color` instead topMargin: number, // `<body>` valueType: null, // `<param>` version: null, // `<html>`. Use a doctype. vAlign: null, // Several. Use CSS `vertical-align` instead vLink: null, // `<body>`. Use CSS `a:visited {color}` instead vSpace: number, // `<img>` and `<object>` // Non-standard Properties. allowTransparency: null, autoCorrect: null, autoSave: null, disablePictureInPicture: boolean, disableRemotePlayback: boolean, prefix: null, property: null, results: number, security: null, unselectable: null } }); const svg$1 = create({ space: 'svg', attributes: { accentHeight: 'accent-height', alignmentBaseline: 'alignment-baseline', arabicForm: 'arabic-form', baselineShift: 'baseline-shift', capHeight: 'cap-height', className: 'class', clipPath: 'clip-path', clipRule: 'clip-rule', colorInterpolation: 'color-interpolation', colorInterpolationFilters: 'color-interpolation-filters', colorProfile: 'color-profile', colorRendering: 'color-rendering', crossOrigin: 'crossorigin', dataType: 'datatype', dominantBaseline: 'dominant-baseline', enableBackground: 'enable-background', fillOpacity: 'fill-opacity', fillRule: 'fill-rule', floodColor: 'flood-color', floodOpacity: 'flood-opacity', fontFamily: 'font-family', fontSize: 'font-size', fontSizeAdjust: 'font-size-adjust', fontStretch: 'font-stretch', fontStyle: 'font-style', fontVariant: 'font-variant', fontWeight: 'font-weight', glyphName: 'glyph-name', glyphOrientationHorizontal: 'glyph-orientation-horizontal', glyphOrientationVertical: 'glyph-orientation-vertical', hrefLang: 'hreflang', horizAdvX: 'horiz-adv-x', horizOriginX: 'horiz-origin-x', horizOriginY: 'horiz-origin-y', imageRendering: 'image-rendering', letterSpacing: 'letter-spacing', lightingColor: 'lighting-color', markerEnd: 'marker-end', markerMid: 'marker-mid', markerStart: 'marker-start', navDown: 'nav-down', navDownLeft: 'nav-down-left', navDownRight: 'nav-down-right', navLeft: 'nav-left', navNext: 'nav-next', navPrev: 'nav-prev', navRight: 'nav-right', navUp: 'nav-up', navUpLeft: 'nav-up-left', navUpRight: 'nav-up-right', onAbort: 'onabort', onActivate: 'onactivate', onAfterPrint: 'onafterprint', onBeforePrint: 'onbeforeprint', onBegin: 'onbegin', onCancel: 'oncancel', onCanPlay: 'oncanplay', onCanPlayThrough: 'oncanplaythrough', onChange: 'onchange', onClick: 'onclick', onClose: 'onclose', onCopy: 'oncopy', onCueChange: 'oncuechange', onCut: 'oncut', onDblClick: 'ondblclick', onDrag: 'ondrag', onDragEnd: 'ondragend', onDragEnter: 'ondragenter', onDragExit: 'ondragexit', onDragLeave: 'ondragleave', onDragOver: 'ondragover', onDragStart: 'ondragstart', onDrop: 'ondrop', onDurationChange: 'ondurationchange', onEmptied: 'onemptied', onEnd: 'onend', onEnded: 'onended', onError: 'onerror', onFocus: 'onfocus', onFocusIn: 'onfocusin', onFocusOut: 'onfocusout', onHashChange: 'onhashchange', onInput: 'oninput', onInvalid: 'oninvalid', onKeyDown: 'onkeydown', onKeyPress: 'onkeypress', onKeyUp: 'onkeyup', onLoad: 'onload', onLoadedData: 'onloadeddata', onLoadedMetadata: 'onloadedmetadata', onLoadStart: 'onloadstart', onMessage: 'onmessage', onMouseDown: 'onmousedown', onMouseEnter: 'onmouseenter', onMouseLeave: 'onmouseleave', onMouseMove: 'onmousemove', onMouseOut: 'onmouseout', onMouseOver: 'onmouseover', onMouseUp: 'onmouseup', onMouseWheel: 'onmousewheel', onOffline: 'onoffline', onOnline: 'ononline', onPageHide: 'onpagehide', onPageShow: 'onpageshow', onPaste: 'onpaste', onPause: 'onpause', onPlay: 'onplay', onPlaying: 'onplaying', onPopState: 'onpopstate', onProgress: 'onprogress', onRateChange: 'onratechange', onRepeat: 'onrepeat', onReset: 'onreset', onResize: 'onresize', onScroll: 'onscroll', onSeeked: 'onseeked', onSeeking: 'onseeking', onSelect: 'onselect', onShow: 'onshow', onStalled: 'onstalled', onStorage: 'onstorage', onSubmit: 'onsubmit', onSuspend: 'onsuspend', onTimeUpdate: 'ontimeupdate', onToggle: 'ontoggle', onUnload: 'onunload', onVolumeChange: 'onvolumechange', onWaiting: 'onwaiting', onZoom: 'onzoom', overlinePosition: 'overline-position', overlineThickness: 'overline-thickness', paintOrder: 'paint-order', panose1: 'panose-1', pointerEvents: 'pointer-events', referrerPolicy: 'referrerpolicy', renderingIntent: 'rendering-intent', shapeRendering: 'shape-rendering', stopColor: 'stop-color', stopOpacity: 'stop-opacity', strikethroughPosition: 'strikethrough-position', strikethroughThickness: 'strikethrough-thickness', strokeDashArray: 'stroke-dasharray', strokeDashOffset: 'stroke-dashoffset', strokeLineCap: 'stroke-linecap', strokeLineJoin: 'stroke-linejoin', strokeMiterLimit: 'stroke-miterlimit', strokeOpacity: 'stroke-opacity', strokeWidth: 'stroke-width', tabIndex: 'tabindex', textAnchor: 'text-anchor', textDecoration: 'text-decoration', textRendering: 'text-rendering', typeOf: 'typeof', underlinePosition: 'underline-position', underlineThickness: 'underline-thickness', unicodeBidi: 'unicode-bidi', unicodeRange: 'unicode-range', unitsPerEm: 'units-per-em', vAlphabetic: 'v-alphabetic', vHanging: 'v-hanging', vIdeographic: 'v-ideographic', vMathematical: 'v-mathematical', vectorEffect: 'vector-effect', vertAdvY: 'vert-adv-y', vertOriginX: 'vert-origin-x', vertOriginY: 'vert-origin-y', wordSpacing: 'word-spacing', writingMode: 'writing-mode', xHeight: 'x-height', // These were camelcased in Tiny. Now lowercased in SVG 2 playbackOrder: 'playbackorder', timelineBegin: 'timelinebegin' }, transform: caseSensitiveTransform, properties: { about: commaOrSpaceSeparated, accentHeight: number, accumulate: null, additive: null, alignmentBaseline: null, alphabetic: number, amplitude: number, arabicForm: null, ascent: number, attributeName: null, attributeType: null, azimuth: number, bandwidth: null, baselineShift: null, baseFrequency: null, baseProfile: null, bbox: null, begin: null, bias: number, by: null, calcMode: null, capHeight: number, className: spaceSeparated, clip: null, clipPath: null, clipPathUnits: null, clipRule: null, color: null, colorInterpolation: null, colorInterpolationFilters: null, colorProfile: null, colorRendering: null, content: null, contentScriptType: null, contentStyleType: null, crossOrigin: null, cursor: null, cx: null, cy: null, d: null, dataType: null, defaultAction: null, descent: number, diffuseConstant: number, direction: null, display: null, dur: null, divisor: number, dominantBaseline: null, download: boolean, dx: null, dy: null, edgeMode: null, editable: null, elevation: number, enableBackground: null, end: null, event: null, exponent: number, externalResourcesRequired: null, fill: null, fillOpacity: number, fillRule: null, filter: null, filterRes: null, filterUnits: null, floodColor: null, floodOpacity: null, focusable: null, focusHighlight: null, fontFamily: null, fontSize: null, fontSizeAdjust: null, fontStretch: null, fontStyle: null, fontVariant: null, fontWeight: null, format: null, fr: null, from: null, fx: null, fy: null, g1: commaSeparated, g2: commaSeparated, glyphName: commaSeparated, glyphOrientationHorizontal: null, glyphOrientationVertical: null, glyphRef: null, gradientTransform: null, gradientUnits: null, handler: null, hanging: number, hatchContentUnits: null, hatchUnits: null, height: null, href: null, hrefLang: null, horizAdvX: number, horizOriginX: number, horizOriginY: number, id: null, ideographic: number, imageRendering: null, initialVisibility: null, in: null, in2: null, intercept: number, k: number, k1: number, k2: number, k3: number, k4: number, kernelMatrix: commaOrSpaceSeparated, kernelUnitLength: null, keyPoints: null, // SEMI_COLON_SEPARATED keySplines: null, // SEMI_COLON_SEPARATED keyTimes: null, // SEMI_COLON_SEPARATED kerning: null, lang: null, lengthAdjust: null, letterSpacing: null, lightingColor: null, limitingConeAngle: number, local: null, markerEnd: null, markerMid: null, markerStart: null, markerHeight: null, markerUnits: null, markerWidth: null, mask: null, maskContentUnits: null, maskUnits: null, mathematical: null, max: null, media: null, mediaCharacterEncoding: null, mediaContentEncodings: null, mediaSize: number, mediaTime: null, method: null, min: null, mode: null, name: null, navDown: null, navDownLeft: null, navDownRight: null, navLeft: null, navNext: null, navPrev: null, navRight: null, navUp: null, navUpLeft: null, navUpRight: null, numOctaves: null, observer: null, offset: null, onAbort: null, onActivate: null, onAfterPrint: null, onBeforePrint: null, onBegin: null, onCancel: null, onCanPlay: null, onCanPlayThrough: null, onChange: null, onClick: null, onClose: null, onCopy: null, onCueChange: null, onCut: null, onDblClick: null, onDrag: null, onDragEnd: null, onDragEnter: null, onDragExit: null, onDragLeave: null, onDragOver: null, onDragStart: null, onDrop: null, onDurationChange: null, onEmptied: null, onEnd: null, onEnded: null, onError: null, onFocus: null, onFocusIn: null, onFocusOut: null, onHashChange: null, onInput: null, onInvalid: null, onKeyDown: null, onKeyPress: null, onKeyUp: null, onLoad: null, onLoadedData: null, onLoadedMetadata: null, onLoadStart: null, onMessage: null, onMouseDown: null, onMouseEnter: null, onMouseLeave: null, onMouseMove: null, onMouseOut: null, onMouseOver: null, onMouseUp: null, onMouseWheel: null, onOffline: null, onOnline: null, onPageHide: null, onPageShow: null, onPaste: null, onPause: null, onPlay: null, onPlaying: null, onPopState: null, onProgress: null, onRateChange: null, onRepeat: null, onReset: null, onResize: null, onScroll: null, onSeeked: null, onSeeking: null, onSelect: null, onShow: null, onStalled: null, onStorage: null, onSubmit: null, onSuspend: null, onTimeUpdate: null, onToggle: null, onUnload: null, onVolumeChange: null, onWaiting: null, onZoom: null, opacity: null, operator: null, order: null, orient: null, orientation: null, origin: null, overflow: null, overlay: null, overlinePosition: number, overlineThickness: number, paintOrder: null, panose1: null, path: null, pathLength: number, patternContentUnits: null, patternTransform: null, patternUnits: null, phase: null, ping: spaceSeparated, pitch: null, playbackOrder: null, pointerEvents: null, points: null, pointsAtX: number, pointsAtY: number, pointsAtZ: number, preserveAlpha: null, preserveAspectRatio: null, primitiveUnits: null, propagate: null, property: commaOrSpaceSeparated, r: null, radius: null, referrerPolicy: null, refX: null, refY: null, rel: commaOrSpaceSeparated, rev: commaOrSpaceSeparated, renderingIntent: null, repeatCount: null, repeatDur: null, requiredExtensions: commaOrSpaceSeparated, requiredFeatures: commaOrSpaceSeparated, requiredFonts: commaOrSpaceSeparated, requiredFormats: commaOrSpaceSeparated, resource: null, restart: null, result: null, rotate: null, rx: null, ry: null, scale: null, seed: null, shapeRendering: null, side: null, slope: null, snapshotTime: null, specularConstant: number, specularExponent: number, spreadMethod: null, spacing: null, startOffset: null, stdDeviation: null, stemh: null, stemv: null, stitchTiles: null, stopColor: null, stopOpacity: null, strikethroughPosition: number, strikethroughThickness: number, string: null, stroke: null, strokeDashArray: commaOrSpaceSeparated, strokeDashOffset: null, strokeLineCap: null, strokeLineJoin: null, strokeMiterLimit: number, strokeOpacity: number, strokeWidth: null, style: null, surfaceScale: number, syncBehavior: null, syncBehaviorDefault: null, syncMaster: null, syncTolerance: null, syncToleranceDefault: null, systemLanguage: commaOrSpaceSeparated, tabIndex: number, tableValues: null, target: null, targetX: number, targetY: number, textAnchor: null, textDecoration: null, textRendering: null, textLength: null, timelineBegin: null, title: null, transformBehavior: null, type: null, typeOf: commaOrSpaceSeparated, to: null, transform: null, u1: null, u2: null, underlinePosition: number, underlineThickness: number, unicode: null, unicodeBidi: null, unicodeRange: null, unitsPerEm: number, values: null, vAlphabetic: number, vMathematical: number, vectorEffect: null, vHanging: number, vIdeographic: number, version: null, vertAdvY: number, vertOriginX: number, vertOriginY: number, viewBox: null, viewTarget: null, visibility: null, width: null, widths: null, wordSpacing: null, writingMode: null, x: null, x1: null, x2: null, xChannelSelector: null, xHeight: number, y: null, y1: null, y2: null, yChannelSelector: null, z: null, zoomAndPan: null } }); /** * @typedef {import('./util/schema.js').Schema} Schema */ const valid = /^data[-\w.:]+$/i; const dash = /-[a-z]/g; const cap = /[A-Z]/g; /** * @param {Schema} schema * @param {string} value * @returns {Info} */ function find(schema, value) { const normal = normalize(value); let prop = value; let Type = Info; if (normal in schema.normal) { return schema.property[schema.normal[normal]] } if (normal.length > 4 && normal.slice(0, 4) === 'data' && valid.test(value)) { // Attribute or property. if (value.charAt(4) === '-') { // Turn it into a property. const rest = value.slice(5).replace(dash, camelcase); prop = 'data' + rest.charAt(0).toUpperCase() + rest.slice(1); } else { // Turn it into an attribute. const rest = value.slice(4); if (!dash.test(rest)) { let dashes = rest.replace(cap, kebab); if (dashes.charAt(0) !== '-') { dashes = '-' + dashes; } value = 'data' + dashes; } } Type = DefinedInfo; } return new Type(prop, value) } /** * @param {string} $0 * @returns {string} */ function kebab($0) { return '-' + $0.toLowerCase() } /** * @param {string} $0 * @returns {string} */ function camelcase($0) { return $0.charAt(1).toUpperCase() } /** * @typedef {import('./lib/util/info.js').Info} Info * @typedef {import('./lib/util/schema.js').Schema} Schema */ const html = merge([xml, xlink, xmlns, aria, html$1], 'html'); const svg = merge([xml, xlink, xmlns, aria, svg$1], 'svg'); var own$2 = {}.hasOwnProperty; /** * @callback Handler * @param {...unknown} value * @return {unknown} * * @typedef {Record<string, Handler>} Handlers * * @typedef {Object} Options * @property {Handler} [unknown] * @property {Handler} [invalid] * @property {Handlers} [handlers] */ /** * Handle values based on a property. * * @param {string} key * @param {Options} [options] */ function zwitch(key, options) { var settings = options || {}; /** * Handle one value. * Based on the bound `key`, a respective handler will be called. * If `value` is not an object, or doesn’t have a `key` property, the special * “invalid” handler will be called. * If `value` has an unknown `key`, the special “unknown” handler will be * called. * * All arguments, and the context object, are passed through to the handler, * and it’s result is returned. * * @param {...unknown} [value] * @this {unknown} * @returns {unknown} * @property {Handler} invalid * @property {Handler} unknown * @property {Handlers} handlers */ function one(value) { var fn = one.invalid; var handlers = one.handlers; if (value && own$2.call(value, key)) { fn = own$2.call(handlers, value[key]) ? handlers[value[key]] : one.unknown; } if (fn) { return fn.apply(this, arguments) } } one.handlers = settings.handlers || {}; one.invalid = settings.invalid; one.unknown = settings.unknown; return one } const rtlRange = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC'; const ltrRange = 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6' + '\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF\u200E\u2C00-\uFB1C' + '\uFE00-\uFE6F\uFEFD-\uFFFF'; /* eslint-disable no-misleading-character-class */ const rtl = new RegExp('^[^' + ltrRange + ']*[' + rtlRange + ']'); const ltr = new RegExp('^[^' + rtlRange + ']*[' + ltrRange + ']'); /* eslint-enable no-misleading-character-class */ /** * Detect the direction of text: left-to-right, right-to-left, or neutral * * @param {string} value * @returns {'rtl'|'ltr'|'neutral'} */ function direction(value) { const source = String(value || ''); return rtl.test(source) ? 'rtl' : ltr.test(source) ? 'ltr' : 'neutral' } /** * @typedef {import('unist').Node} Node * @typedef {import('unist').Parent} Parent * @typedef {import('hast').Element} Element * * @typedef {string} TagName * @typedef {null|undefined|TagName|TestFunctionAnything|Array.<TagName|TestFunctionAnything>} Test */ /** * @template {Element} T * @typedef {null|undefined|T['tagName']|TestFunctionPredicate<T>|Array.<T['tagName']|TestFunctionPredicate<T>>} PredicateTest */ /** * Check if an element passes a test * * @callback TestFunctionAnything * @param {Element} element * @param {number|null|undefined} [index] * @param {Parent|null|undefined} [parent] * @returns {boolean|void} */ /** * Check if an element passes a certain node test * * @template {Element} X * @callback TestFunctionPredicate * @param {Element} element * @param {number|null|undefined} [index] * @param {Parent|null|undefined} [parent] * @returns {element is X} */ /** * Check if a node is an element and passes a certain node test * * @callback AssertAnything * @param {unknown} [node] * @param {number|null|undefined} [index] * @param {Parent|null|undefined} [parent] * @returns {boolean} */ /** * Check if a node is an element and passes a certain node test * * @template {Element} Y * @callback AssertPredicate * @param {unknown} [node] * @param {number|null|undefined} [index] * @param {Parent|null|undefined} [parent] * @returns {node is Y} */ // Check if `node` is an `element` and whether it passes the given test. const isElement = /** * Check if a node is an element and passes a test. * When a `parent` node is known the `index` of node should also be given. * * @type {( * (() => false) & * (<T extends Element = Element>(node: unknown, test?: PredicateTest<T>, index?: number, parent?: Parent, context?: unknown) => node is T) & * ((node: unknown, test: Test, index?: number, parent?: Parent, context?: unknown) => boolean) * )} */ ( /** * Check if a node passes a test. * When a `parent` node is known the `index` of node should also be given. * * @param {unknown} [node] Node to check * @param {Test} [test] When nullish, checks if `node` is a `Node`. * When `string`, works like passing `function (node) {return node.type === test}`. * When `function` checks if function passed the node is true. * When `array`, checks any one of the subtests pass. * @param {number} [index] Position of `node` in `parent` * @param {Parent} [parent] Parent of `node` * @param {unknown} [context] Context object to invoke `test` with * @returns {boolean} Whether test passed and `node` is an `Element` (object with `type` set to `element` and `tagName` set to a non-empty string). */ // eslint-disable-next-line max-params function (node, test, index, parent, context) { const check = convertElement(test); if ( index !== undefined && index !== null && (typeof index !== 'number' || index < 0 || index === Number.POSITIVE_INFINITY) ) { throw new Error('Expected positive finite index for child node') } if ( parent !== undefined && parent !== null && (!parent.type || !parent.children) ) { throw new Error('Expected parent node') } // @ts-expect-error Looks like a node. if (!node || !node.type || typeof node.type !== 'string') { return false } if ( (parent === undefined || parent === null) !== (index === undefined || index === null) ) { throw new Error('Expected both parent and index') } return check.call(context, node, index, parent) } ); const convertElement = /** * @type {( * (<T extends Element>(test: T['tagName']|TestFunctionPredicate<T>) => AssertPredicate<T>) & * ((test?: Test) => AssertAnything) * )} */ ( /** * Generate an assertion from a check. * @param {Test} [test] * When nullish, checks if `node` is a `Node`. * When `string`, works like passing `function (node) {return node.type === test}`. * When `function` checks if function passed the node is true. * When `object`, checks that all keys in test are in node, and that they have (strictly) equal values. * When `array`, checks any one of the subtests pass. * @returns {AssertAnything} */ function (test) { if (test === undefined || test === null) { return element$1 } if (typeof test === 'string') { return tagNameFactory(test) } if (typeof test === 'object') { return anyFactory$1(test) } if (typeof test === 'function') { return castFactory$1(test) } throw new Error('Expected function, string, or array as test') } ); /** * @param {Array.<TagName|TestFunctionAnything>} tests * @returns {AssertAnything} */ function anyFactory$1(tests) { /** @type {Array.<AssertAnything>} */ const checks = []; let index = -1; while (++index < tests.length) { checks[index] = convertElement(tests[index]); } return castFactory$1(any) /** * @this {unknown} * @param {unknown[]} parameters * @returns {boolean} */ function any(...parameters) { let index = -1; while (++index < checks.length) { if (checks[index].call(this, ...parameters)) { return true } } return false } } /** * Utility to convert a string into a function which checks a given node’s tag * name for said string. * * @param {TagName} check * @returns {AssertAnything} */ function tagNameFactory(check) { return tagName /** * @param {unknown} node * @returns {boolean} */ function tagName(node) { return element$1(node) && node.tagName === check } } /** * @param {TestFunctionAnything} check * @returns {AssertAnything} */ function castFactory$1(check) { return assertion /** * @this {unknown} * @param {unknown} node * @param {Array.<unknown>} parameters * @returns {boolean} */ function assertion(node, ...parameters) { // @ts-expect-error: fine. return element$1(node) && Boolean(check.call(this, node, ...parameters)) } } /** * Utility to return true if this is an element. * @param {unknown} node * @returns {node is Element} */ function element$1(node) { return Boolean( node && typeof node === 'object' && // @ts-expect-error Looks like a node. node.type === 'element' && // @ts-expect-error Looks like an element. typeof node.tagName === 'string' ) } /** * @fileoverview * Get the plain-text value of a hast node. * @longdescription * ## Use * * ```js * import {h} from 'hastscript' * import {toString} from 'hast-util-to-string' * * toString(h('p', 'Alpha')) * //=> 'Alpha' * toString(h('div', [h('b', 'Bold'), ' and ', h('i', 'italic'), '.'])) * //=> 'Bold and italic.' * ``` * * ## API * * ### `toString(node)` * * Transform a node to a string. */ /** * @typedef {import('hast').Root} Root * @typedef {import('hast').Element} Element * @typedef {Root|Root['children'][number]} Node */ /** * Get the plain-text value of a hast node. * * @param {Node} node * @returns {string} */ function toString(node) { // “The concatenation of data of all the Text node descendants of the context // object, in tree order.” if ('children' in node) { return all(node) } // “Context object’s data.” return 'value' in node ? node.value : '' } /** * @param {Node} node * @returns {string} */ function one(node) { if (node.type === 'text') { return node.value } return 'children' in node ? all(node) : '' } /** * @param {Root|Element} node * @returns {string} */ function all(node) { let index = -1; /** @type {string[]} */ const result = []; while (++index < node.children.length) { result[index] = one(node.children[index]); } return result.join('') } /** * @typedef {import('unist').Node} Node * @typedef {import('unist').Parent} Parent * * @typedef {string} Type * @typedef {Object<string, unknown>} Props * * @typedef {null|undefined|Type|Props|TestFunctionAnything|Array.<Type|Props|TestFunctionAnything>} Test */ const convert = /** * @type {( * (<T extends Node>(test: T['type']|Partial<T>|TestFunctionPredicate<T>) => AssertPredicate<T>) & * ((test?: Test) => AssertAnything) * )} */ ( /** * Generate an assertion from a check. * @param {Test} [test] * When nullish, checks if `node` is a `Node`. * When `string`, works like passing `function (node) {return node.type === test}`. * When `function` checks if function passed the node is true. * When `object`, checks that all keys in test are in node, and that they have (strictly) equal values. * When `array`, checks any one of the subtests pass. * @returns {AssertAnything} */ function (test) { if (test === undefined || test === null) { return ok } if (typeof test === 'string') { return typeFactory(test) } if (typeof test === 'object') { return Array.isArray(test) ? anyFactory(test) : propsFactory(test) } if (typeof test === 'function') { return castFactory(test) } throw new Error('Expected function, string, or object as test') } ); /** * @param {Array.<Type|Props|TestFunctionAnything>} tests * @returns {AssertAnything} */ function anyFactory(tests) { /** @type {Array.<AssertAnything>} */ const checks = []; let index = -1; while (++index < tests.length) { checks[index] = convert(tests[index]); } return castFactory(any) /** * @this {unknown} * @param {unknown[]} parameters * @returns {boolean} */ function any(...parameters) { let index = -1; while (++index < checks.length) { if (checks[index].call(this, ...parameters)) return true } return false } } /** * Utility to assert each property in `test` is represented in `node`, and each * values are strictly equal. * * @param {Props} check * @returns {AssertAnything} */ function propsFactory(check) { return castFactory(all) /** * @param {Node} node * @returns {boolean} */ function all(node) { /** @type {string} */ let key; for (key in check) { // @ts-expect-error: hush, it sure works as an index. if (node[key] !== check[key]) return false } return true } } /** * Utility to convert a string into a function which checks a given node’s type * for said string. * * @param {Type} check * @returns {AssertAnything} */ function typeFactory(check) { return castFactory(type) /** * @param {Node} node */ function type(node) { return node && node.type === check } } /** * Utility to convert a string into a function which checks a given node’s type * for said string. * @param {TestFunctionAnything} check * @returns {AssertAnything} */ function castFactory(check) { return assertion /** * @this {unknown} * @param {Array.<unknown>} parameters * @returns {boolean} */ function assertion(...parameters) { // @ts-expect-error: spreading is fine. return Boolean(check.call(this, ...parameters)) } } // Utility to return true. function ok() { return true } /** * @param {string} d * @returns {string} */ function color(d) { return '\u001B[33m' + d + '\u001B[39m' } /** * @typedef {import('unist').Node} Node * @typedef {import('unist').Parent} Parent * @typedef {import('unist-util-is').Test} Test * @typedef {import('./complex-types').Action} Action * @typedef {import('./complex-types').Index} Index * @typedef {import('./complex-types').ActionTuple} ActionTuple * @typedef {import('./complex-types').VisitorResult} VisitorResult * @typedef {import('./complex-types').Visitor} Visitor */ /** * Continue traversing as normal */ const CONTINUE = true; /** * Do not traverse this node’s children */ const SKIP = 'skip'; /** * Stop traversing immediately */ const EXIT = false; /** * Visit children of tree which pass a test * * @param tree Abstract syntax tree to walk * @param test Test node, optional * @param visitor Function to run for each node * @param reverse Visit the tree in reverse order, defaults to false */ const visitParents = /** * @type {( * (<Tree extends Node, Check extends Test>(tree: Tree, test: Check, visitor: import('./complex-types').BuildVisitor<Tree, Check>, reverse?: boolean) => void) & * (<Tree extends Node>(tree: Tree, visitor: import('./complex-types').BuildVisitor<Tree>, reverse?: boolean) => void) * )} */ ( /** * @param {Node} tree * @param {Test} test * @param {import('./complex-types').Visitor<Node>} visitor * @param {boolean} [reverse] */ function (tree, test, visitor, reverse) { if (typeof test === 'function' && typeof visitor !== 'function') { reverse = visitor; // @ts-expect-error no visitor given, so `visitor` is test. visitor = test; test = null; } const is = convert(test); const step = reverse ? -1 : 1; factory(tree, null, [])(); /** * @param {Node} node * @param {number?} index * @param {Array.<Parent>} parents */ function factory(node, index, parents) { /** @type {Object.<string, unknown>} */ // @ts-expect-error: hush const value = typeof node === 'object' && node !== null ? node : {}; /** @type {string|undefined} */ let name; if (typeof value.type === 'string') { name = typeof value.tagName === 'string' ? value.tagName : typeof value.name === 'string' ? value.name : undefined; Object.defineProperty(visit, 'name', { value: 'node (' + color(value.type + (name ? '<' + name + '>' : '')) + ')' }); } return visit function visit() { /** @type {ActionTuple} */ let result = []; /** @type {ActionTuple} */ let subresult; /** @type {number} */ let offset; /** @type {Array.<Parent>} */ let grandparents; if (!test || is(node, index, parents[parents.length - 1] || null)) { result = toResult(visitor(node, parents)); if (result[0] === EXIT) { return result } } // @ts-expect-error looks like a parent. if (node.children && result[0] !== SKIP) { // @ts-expect-error looks like a parent. offset = (reverse ? node.children.length : -1) + step; // @ts-expect-error looks like a parent. grandparents = parents.concat(node); // @ts-expect-error looks like a parent. while (offset > -1 && offset < node.children.length) { // @ts-expect-error looks like a parent. subresult = factory(node.children[offset], offset, grandparents)(); if (subresult[0] === EXIT) { return subresult } offset = typeof subresult[1] === 'number' ? subresult[1] : offset + step; } } return result } } } ); /** * @param {VisitorResult} value * @returns {ActionTuple} */ function toResult(value) { if (Array.isArray(value)) { return value } if (typeof value === 'number') { return [CONTINUE, value] } return [value] } /** * @typedef {import('unist').Node} Node * @typedef {import('unist').Parent} Parent * @typedef {import('unist-util-is').Test} Test * @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult * @typedef {import('./complex-types').Visitor} Visitor */ /** * Visit children of tree which pass a test * * @param tree Abstract syntax tree to walk * @param test Test, optional * @param visitor Function to run for each node * @param reverse Fisit the tree in reverse, defaults to false */ const visit = /** * @type {( * (<Tree extends Node, Check extends Test>(tree: Tree, test: Check, visitor: import('./complex-types').BuildVisitor<Tree, Check>, reverse?: boolean) => void) & * (<Tree extends Node>(tree: Tree, visitor: import('./complex-types').BuildVisitor<Tree>, reverse?: boolean) => void) * )} */ ( /** * @param {Nod