Skip to content

Commit

Permalink
Add support for :lang() selector
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Jul 21, 2018
1 parent 0d224f6 commit 4c5b2a3
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 14 deletions.
6 changes: 6 additions & 0 deletions lib/any.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ var svg = require('property-information/svg')
var needsIndex = require('./pseudo').needsIndex
var test = require('./test')
var nest = require('./nest')
var enter = require('./enter-state')

var type = zwitch('type')
var handlers = type.handlers
Expand Down Expand Up @@ -43,6 +44,7 @@ function rule(query, tree, state) {
var collect = collector(state.one)
var opts = {
schema: state.space === 'svg' ? svg : html,
language: undefined,
iterator: match,
one: state.one,
shallow: state.shallow
Expand All @@ -57,6 +59,8 @@ function rule(query, tree, state) {
return collect.result

function match(query, node, index, parent, state) {
var exit = enter(state, node)

if (test(query, node, index, parent, state)) {
if (query.rule) {
nest(query.rule, node, index, parent, configure(query.rule, state))
Expand All @@ -65,6 +69,8 @@ function rule(query, tree, state) {
state.found = true
}
}

exit()
}

function configure(query, state) {
Expand Down
35 changes: 35 additions & 0 deletions lib/enter-state.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use strict'

var svg = require('property-information/svg')

module.exports = config

function config(state, node) {
var schema = state.schema
var language = state.language
var found
var lang

if (node.type === 'element') {
if (schema.space === 'html' && node.tagName === 'svg') {
state.schema = svg
found = true
}

lang = node.properties.xmlLang || node.properties.lang

if (lang !== undefined && lang !== null) {
state.language = lang
found = true
}
}

return found ? reset : noop

function reset() {
state.schema = schema
state.language = language
}
}

function noop() {}
15 changes: 3 additions & 12 deletions lib/nest.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict'

var zwitch = require('zwitch')
var svg = require('property-information/svg')
var enter = require('./enter-state')

module.exports = zwitch('nestingOperator')

Expand Down Expand Up @@ -98,15 +98,6 @@ function walkIterator(query, parent, state) {
var nodes = parent.children
var typeIndex = state.index ? createTypeIndex() : null
var delayed = []
var parentSchema = state.schema

if (
parentSchema.space === 'html' &&
parent.type === 'element' &&
parent.tagName === 'svg'
) {
state.schema = svg
}

return {
prefillTypeIndex: rangeDefaults(prefillTypeIndex),
Expand All @@ -118,8 +109,6 @@ function walkIterator(query, parent, state) {
var length = delayed.length
var index = -1

state.schema = parentSchema

while (++index < length) {
delayed[index]()

Expand Down Expand Up @@ -175,7 +164,9 @@ function walkIterator(query, parent, state) {
}

function pushNode() {
var exit = enter(state, child)
state.iterator(query, child, start, parent, state)
exit()
}
}

Expand Down
12 changes: 12 additions & 0 deletions lib/pseudo.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
'use strict'

var commaSeparated = require('comma-separated-tokens').parse
var filter = require('bcp-47-match').extendedFilter

module.exports = match

match.selectorPseudoSupport = ['any', 'matches', 'not']
Expand Down Expand Up @@ -52,6 +55,7 @@ handlers.empty = empty
handlers.enabled = not(disabled)
handlers['first-child'] = firstChild
handlers['first-of-type'] = firstOfType
handlers.lang = lang
handlers['last-child'] = lastChild
handlers['last-of-type'] = lastOfType
handlers.matches = matches
Expand Down Expand Up @@ -145,6 +149,14 @@ function firstChild(query, node, index, parent, state) {
return state.elementIndex === 0
}

function lang(query, node, index, parent, state) {
return (
state.language !== '' &&
state.language !== undefined &&
filter(state.language, commaSeparated(query.value)).length !== 0
)
}

function lastChild(query, node, index, parent, state) {
assertDeep(state, query)
return state.elementIndex === state.elementCount - 1
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"Titus Wormer <tituswormer@gmail.com> (http://wooorm.com)"
],
"dependencies": {
"bcp-47-match": "^1.0.0",
"comma-separated-tokens": "^1.0.2",
"css-selector-parser": "^1.3.0",
"hast-util-has-property": "^1.0.0",
Expand Down
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ Yields:
* [x] `:optional` (pseudo-class)
* [x] `:required` (pseudo-class)
* [x] `:root` (pseudo-class)
* [x] `:lang()` (pseudo-class)
* [x] `article p` (combinator: descendant selector)
* [x] `article > p` (combinator: child selector)
* [x] `h1 + p` (combinator: adjacent sibling selector)
Expand Down Expand Up @@ -210,7 +211,6 @@ Yields:
* [ ]`:indeterminate` (pseudo-class)
* [ ] § `:in-range` (pseudo-class)
* [ ] § `:invalid` (pseudo-class)
* [ ] § `:lang()` (pseudo-class)
* [ ]`:link` (pseudo-class)
* [ ]`:local-link` (pseudo-class)
* [ ]`nth-column()` (pseudo-class)
Expand Down
60 changes: 60 additions & 0 deletions test/matches.js
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,66 @@ test('select.matches()', function(t) {
sst.end()
})

st.test(':lang()', function(sst) {
sst.ok(
matches(':lang(de, en)', h('html', {xmlLang: 'en'})),
'true if the element has an `xml:lang` attribute'
)

sst.ok(
matches(':lang(de, en)', h('html', {lang: 'de'})),
'true if the element has a `lang` attribute'
)

sst.notOk(
matches(':lang(de, en)', h('html', {xmlLang: 'jp'})),
'false if the element has an different language set'
)

sst.notOk(
matches(':lang(de, en)', h('html', {xmlLang: 'jp', lang: 'de'})),
'should prefer `xmlLang` over `lang` (#1)'
)

sst.ok(
matches(':lang(de, en)', h('html', {xmlLang: 'de', lang: 'jp'})),
'should prefer `xmlLang` over `lang` (#2)'
)

sst.notOk(
matches(':lang(de, en)', h('html', {xmlLang: 'jp'})),
'false if the element has an different language set'
)

sst.ok(
matches(':lang("*")', h('html', {lang: 'en'})),
'should support wildcards'
)

sst.notOk(
matches(':lang(en)', h('html', {lang: ''})),
'false if [lang] is an empty string (means unknown language)'
)

sst.notOk(
matches(':lang(*)', h('html', {lang: ''})),
'false with wildcard if [lang] is an empty string (means unknown language)'
)

sst.ok(
matches(':lang("de-*-DE")', h('html', {lang: 'de-Latn-DE'})),
'should support non-primary wildcard subtags (#1)'
)

// Not supported by `css-selector-parser` yet :(
// sst.ok(
// matches(':lang("fr-BE", "de-*-DE")', h('html', {lang: 'de-Latn-DE'})),
// 'should support non-primary wildcard subtags (#2)'
// )

sst.end()
})

st.test(':root', function(sst) {
sst.ok(matches(':root', h('html')), 'true if `<html>` in HTML space')

Expand Down
39 changes: 39 additions & 0 deletions test/select-all.js
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,45 @@ test('select.selectAll()', function(t) {
sst.end()
})

st.test(':lang()', function(sst) {
sst.deepEqual(
selectAll(
'q:lang(en)',
u('root', [
h('div', {lang: 'en'}, h('p', {lang: ''}, h('q', '0'))),
h('p', {lang: 'fr'}, h('q', {lang: 'fr'}, 'A')),
h('p', {lang: 'fr'}, h('q', {lang: 'en'}, 'B')),
h('p', {lang: 'fr'}, h('q', {lang: 'en-GB'}, 'C')),
h('p', {lang: 'fr'}, h('q', {lang: ''}, 'D')),
h('p', {lang: 'fr'}, h('q', 'E')),
h('p', {lang: 'en'}, h('q', {lang: 'fr'}, 'F')),
h('p', {lang: 'en'}, h('q', {lang: 'en'}, 'G')),
h('p', {lang: 'en'}, h('q', {lang: 'en-GB'}, 'H')),
h('p', {lang: 'en'}, h('q', {lang: ''}, 'I')),
h('p', {lang: 'en'}, h('q', 'J')),
h('p', {lang: 'en-GB'}, h('q', {lang: 'fr'}, 'K')),
h('p', {lang: 'en-GB'}, h('q', {lang: 'en'}, 'L')),
h('p', {lang: 'en-GB'}, h('q', {lang: 'en-GB'}, 'M')),
h('p', {lang: 'en-GB'}, h('q', {lang: ''}, 'N')),
h('p', {lang: 'en-GB'}, h('q', 'O'))
])
),
[
h('q', {lang: 'en'}, 'B'),
h('q', {lang: 'en-GB'}, 'C'),
h('q', {lang: 'en'}, 'G'),
h('q', {lang: 'en-GB'}, 'H'),
h('q', 'J'),
h('q', {lang: 'en'}, 'L'),
h('q', {lang: 'en-GB'}, 'M'),
h('q', 'O')
],
'should return the correct matching elements'
)

sst.end()
})

st.test(':root', function(sst) {
sst.deepEqual(
selectAll(
Expand Down
19 changes: 19 additions & 0 deletions test/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,25 @@ test('select.select()', function(t) {
sst.end()
})

st.test(':lang()', function(sst) {
sst.deepEqual(
select(
'q:lang(en)',
u('root', [
h('div', {lang: 'en'}, h('p', {lang: ''}, h('q', '0'))),
h('p', {lang: 'fr'}, h('q', {lang: 'fr'}, 'A')),
h('p', {lang: 'fr'}, h('q', {lang: ''}, 'B')),
h('p', {lang: 'fr'}, h('q', {lang: 'en-GB'}, 'C')),
h('p', {lang: 'fr'}, h('q', {lang: 'en'}, 'D'))
])
),
h('q', {lang: 'en-GB'}, 'C'),
'should return the correct matching element'
)

sst.end()
})

st.test(':root', function(sst) {
sst.deepEqual(
select(
Expand Down
30 changes: 29 additions & 1 deletion test/svg.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,45 @@
var test = require('tape')
var u = require('unist-builder')
var s = require('hastscript/svg')
var h = require('hastscript')
var select = require('..').select
var selectAll = require('..').selectAll

test('svg', function(t) {
t.deepEqual(
select(
'[writing-mode]',
u('root', [s('svg', [s('text', {writingMode: 'lr-tb'}, '!')])])
u('root', [
s('svg', [s('text', {writingMode: 'lr-tb'}, '!')]),
s('p', [
h(
'text',
{writingMode: 'lr-tb'},
'this is a camelcased HTML attribute'
)
])
])
),
s('text', {writingMode: 'lr-tb'}, '!')
)

t.deepEqual(
selectAll(
'[writing-mode]',
u('root', [
s('svg', [s('text', {writingMode: 'lr-tb'}, '!')]),
s('p', [
h(
'text',
{writingMode: 'lr-tb'},
'this is a camelcased HTML attribute'
)
])
])
),
[s('text', {writingMode: 'lr-tb'}, '!')]
)

t.deepEqual(
select('[writing-mode]', s('text', {writingMode: 'lr-tb'}, '!')),
null
Expand Down

0 comments on commit 4c5b2a3

Please sign in to comment.