mirror of
https://github.com/sern-handler/website
synced 2026-06-27 18:22:22 +00:00
119 lines
2.7 KiB
JavaScript
119 lines
2.7 KiB
JavaScript
/**
|
|
* @typedef {import('css-selector-parser').AstAttribute} AstAttribute
|
|
*
|
|
* @typedef {import('hast').Element} Element
|
|
* @typedef {import('hast').Properties} Properties
|
|
*
|
|
* @typedef {import('property-information').Info} Info
|
|
* @typedef {import('property-information').Schema} Schema
|
|
*/
|
|
|
|
import {stringify as commas} from 'comma-separated-tokens'
|
|
import {ok as assert} from 'devlop'
|
|
import {find} from 'property-information'
|
|
import * as spaces from 'space-separated-tokens'
|
|
|
|
/**
|
|
* @param {AstAttribute} query
|
|
* Query.
|
|
* @param {Element} element
|
|
* Element.
|
|
* @param {Schema} schema
|
|
* Schema of element.
|
|
* @returns {boolean}
|
|
* Whether `element` matches `query`.
|
|
*/
|
|
export function attribute(query, element, schema) {
|
|
const info = find(schema, query.name)
|
|
const propertyValue = element.properties[info.property]
|
|
let value = normalizeValue(propertyValue, info)
|
|
|
|
// Exists.
|
|
if (!query.value) {
|
|
return value !== undefined
|
|
}
|
|
|
|
assert(query.value.type === 'String', 'expected plain string')
|
|
let key = query.value.value
|
|
|
|
// Case-sensitivity.
|
|
if (query.caseSensitivityModifier === 'i') {
|
|
key = key.toLowerCase()
|
|
|
|
if (value) {
|
|
value = value.toLowerCase()
|
|
}
|
|
}
|
|
|
|
if (value !== undefined) {
|
|
switch (query.operator) {
|
|
// Exact.
|
|
case '=': {
|
|
return key === value
|
|
}
|
|
|
|
// Ends.
|
|
case '$=': {
|
|
return key === value.slice(-key.length)
|
|
}
|
|
|
|
// Contains.
|
|
case '*=': {
|
|
return value.includes(key)
|
|
}
|
|
|
|
// Begins.
|
|
case '^=': {
|
|
return key === value.slice(0, key.length)
|
|
}
|
|
|
|
// Exact or prefix.
|
|
case '|=': {
|
|
return (
|
|
key === value ||
|
|
(key === value.slice(0, key.length) &&
|
|
value.charAt(key.length) === '-')
|
|
)
|
|
}
|
|
|
|
// Space-separated list.
|
|
case '~=': {
|
|
return (
|
|
// For all other values (including comma-separated lists), return whether this
|
|
// is an exact match.
|
|
key === value ||
|
|
// If this is a space-separated list, and the query is contained in it, return
|
|
// true.
|
|
spaces.parse(value).includes(key)
|
|
)
|
|
}
|
|
// Other values are not yet supported by CSS.
|
|
// No default
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {Properties[keyof Properties]} value
|
|
* @param {Info} info
|
|
* @returns {string | undefined}
|
|
*/
|
|
function normalizeValue(value, info) {
|
|
if (value === null || value === undefined) {
|
|
// Empty.
|
|
} else if (typeof value === 'boolean') {
|
|
if (value) {
|
|
return info.attribute
|
|
}
|
|
} else if (Array.isArray(value)) {
|
|
if (value.length > 0) {
|
|
return (info.commaSeparated ? commas : spaces.stringify)(value)
|
|
}
|
|
} else {
|
|
return String(value)
|
|
}
|
|
}
|