Skip to content
Toggle navigation
Toggle navigation
This project
Loading...
Sign in
brainfood
/
astro-wt
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
ebe8b358
authored
2024-06-07 15:57:51 -0500
by
Adam Heath
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
Improved types, nth-child(n of selector).
1 parent
2b0bc0ee
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
134 additions
and
61 deletions
lib/astro.ts
lib/html.ts
lib/node.astro
lib/replace.astro
src/layouts/base.astro
src/pages/index.astro
tsconfig.json
lib/astro.ts
View file @
ebe8b35
...
...
@@ -5,3 +5,7 @@ import Node from './node.astro'
import
Replace
from
'./replace.astro'
export
{
Children
,
Custom
,
Match
,
Node
,
Replace
}
import
type
{
NodeType
}
from
'ultrahtml'
type
SlotHandler
=
(
string
,
NodeType
)
=>
Promise
<
Any
>
type
Replacements
=
Record
<
string
,
string
>
...
...
lib/html.ts
View file @
ebe8b35
...
...
@@ -32,71 +32,94 @@ export const appendClasses = (node, extraClasses) => {
}
// TODO: implement a parent/child/element cache
const
filterChildElements
=
(
parent
)
=>
parent
?.
children
?.
filter
(
n
=>
n
.
type
===
ELEMENT_NODE
)
||
[]
const
nthChildPos
=
(
node
,
parent
)
=>
filterChildElements
(
parent
).
findIndex
((
child
)
=>
child
===
node
);
const
filterChildElementsMatcher
=
(
context
,
child
,
parent
,
i
)
=>
child
.
type
===
ELEMENT_NODE
type
Matcher
=
(
context
,
node
,
parent
,
i
,
debug
)
=>
boolean
type
AttrValueMatch
=
(
string
)
=>
string
type
ParentFilter
=
(
context
,
node
,
parent
,
i
)
=>
boolean
const
makeNthChildPosMatcher
=
(
ast
:
AST
):
Matcher
=>
{
const
{
argument
}
=
ast
const
n
=
Number
(
argument
)
if
(
!
Number
.
isNaN
(
n
))
{
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
{
return
i
===
n
}
const
compileMatcher
=
(
ast
:
AST
,
selector
:
string
):
Matcher
=>
{
let
counter
=
0
const
neededContext
=
[]
const
selectorCacheCounter
=
counter
++
neededContext
[
selectorCacheCounter
]
=
()
=>
new
WeakMap
()
const
findChildren
=
(
context
,
parent
:
NodeType
,
selector
:
string
,
matcher
:
Matcher
):
array
[
NodeType
]
=>
{
if
(
parent
===
null
)
console
.
log
(
'null parent'
,
new
Error
())
if
(
parent
===
null
)
return
[]
let
selectorCache
=
context
[
selectorCacheCounter
].
get
(
parent
)
if
(
!
selectorCache
)
context
[
selectorCacheCounter
].
set
(
parent
,
selectorCache
=
{})
const
selectorResult
=
selectorCache
[
selector
]
if
(
selectorResult
)
return
selectorResult
const
newResult
=
parent
?.
children
?.
filter
((
child
,
index
)
=>
matcher
(
context
,
child
,
parent
,
index
))
||
[]
selectorCache
[
selector
]
=
newResult
return
newResult
}
switch
(
argument
)
{
case
'odd'
:
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
Math
.
abs
(
i
%
2
)
===
1
case
'even'
:
return
(
context
,
node
,
parent
,
i
)
=>
i
%
2
===
0
default
:
{
if
(
!
argument
)
throw
new
Error
(
`Unsupported empty nth-child selector!`
)
let
[
_
,
A
=
'1'
,
B
=
'0'
]
=
/^
\s
*
(?:(
-
?(?:\d
+
)?)
n
)?\s
*
\+?\s
*
(\d
+
)?\s
*$/gm
.
exec
(
argument
)
??
[]
if
(
A
.
length
===
0
)
A
=
'1'
const
a
=
Number
.
parseInt
(
A
===
'-'
?
'-1'
:
A
)
const
b
=
Number
.
parseInt
(
B
)
const
nth
=
(
index
)
=>
(
a
*
n
)
+
b
return
(
context
,
node
,
parent
,
i
)
=>
{
const
elements
=
filterChildElements
(
parent
)
for
(
let
index
=
0
;
index
<
elements
.
length
;
index
++
)
{
const
n
=
nth
(
index
)
if
(
n
>
elements
.
length
)
return
false
if
(
n
===
i
)
return
true
const
makeNthChildPosMatcher
=
(
argument
:
string
):
Matcher
=>
{
const
n
=
Number
(
argument
)
if
(
!
Number
.
isNaN
(
n
))
{
// Simple variant, just a number
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
{
return
i
===
n
}
}
switch
(
argument
)
{
case
'odd'
:
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
Math
.
abs
(
i
%
2
)
===
1
case
'even'
:
return
(
context
,
node
,
parent
,
i
)
=>
i
%
2
===
0
default
:
{
if
(
!
argument
)
throw
new
Error
(
`Unsupported empty nth-child selector!`
)
let
[
_
,
A
,
B
=
'0'
]
=
/^
\s
*
(?:(
-
?(?:\d
+
)?)
n
)?\s
*
\+?\s
*
(\d
+
)?\s
*$/gm
.
exec
(
argument
)
??
[]
const
b
=
Number
.
parseInt
(
B
)
// (index) => (index - b) / a
let
nMatch
if
(
A
===
undefined
||
A
===
'0'
||
A
===
'-0'
)
{
nMatch
=
(
i
)
=>
(
i
-
b
)
===
0
}
else
{
const
a
=
A
===
''
?
1
:
A
===
'-'
?
-
1
:
Number
.
parseInt
(
A
)
if
(
a
<
0
)
{
nMatch
=
(
i
)
=>
{
const
n
=
-
(
i
-
b
)
/
a
;
return
n
!==
0
&&
Math
.
floor
(
n
)
===
n
}
}
else
{
nMatch
=
(
i
)
=>
{
const
n
=
(
i
-
b
)
/
a
;
return
n
!==
0
&&
Math
.
floor
(
n
)
===
n
}
}
}
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
{
const
r
=
nMatch
(
i
)
console
.
log
(
'foo'
,
{
argument
,
A
,
B
,
debug
,
i
,
r
})
return
r
}
return
false
}
}
}
}
const
getAttrValueMatch
=
(
value
:
string
,
operator
:
string
=
'='
,
caseSenstive
:
boolean
):
AttrValueMatch
=>
{
if
(
value
===
undefined
)
return
(
attrValue
)
=>
attrValue
!==
undefined
const
isCaseInsenstive
=
caseSensitive
===
'i'
if
(
isCaseInsensitive
)
value
=
value
.
toLowerCase
()
const
adjustMatcher
=
(
matcher
)
=>
isCaseInsensitive
?
(
attrValue
)
=>
matcher
(
attrValue
.
toLowerCase
())
:
matcher
switch
(
operator
)
{
case
'='
:
return
(
attrValue
)
=>
value
===
attrValue
case
'~='
:
{
const
keys
=
value
.
split
(
/
\s
+/g
).
reduce
((
keys
,
item
)
=>
{
keys
[
item
]
=
true
return
keys
},
{})
return
adjustMatcher
((
attrValue
)
=>
keys
[
attrValue
])
const
getAttrValueMatch
=
(
value
:
string
,
operator
:
string
=
'='
,
caseSenstive
:
boolean
):
AttrValueMatch
=>
{
if
(
value
===
undefined
)
return
(
attrValue
)
=>
attrValue
!==
undefined
const
isCaseInsenstive
=
caseSensitive
===
'i'
if
(
isCaseInsensitive
)
value
=
value
.
toLowerCase
()
const
adjustMatcher
=
(
matcher
)
=>
isCaseInsensitive
?
(
attrValue
)
=>
matcher
(
attrValue
.
toLowerCase
())
:
matcher
switch
(
operator
)
{
case
'='
:
return
(
attrValue
)
=>
value
===
attrValue
case
'~='
:
{
const
keys
=
value
.
split
(
/
\s
+/g
).
reduce
((
keys
,
item
)
=>
{
keys
[
item
]
=
true
return
keys
},
{})
return
adjustMatcher
((
attrValue
)
=>
keys
[
attrValue
])
}
case
'|='
:
return
adjustMatcher
((
attrValue
)
=>
value
.
startsWith
(
attrValue
+
'-'
))
case
'*='
:
return
adjustMatcher
((
attrValue
)
=>
value
.
indexOf
(
attrValue
)
>
-
1
)
case
'$='
:
return
adjustMatcher
((
attrValue
)
=>
value
.
endsWith
(
attrValue
))
case
'^='
:
return
adjustMatcher
((
attrValue
)
=>
value
.
startsWith
(
attrValue
))
}
case
'|='
:
return
adjustMatcher
((
attrValue
)
=>
value
.
startsWith
(
attrValue
+
'-'
))
case
'*='
:
return
adjustMatcher
((
attrValue
)
=>
value
.
indexOf
(
attrValue
)
>
-
1
)
case
'$='
:
return
adjustMatcher
((
attrValue
)
=>
value
.
endsWith
(
attrValue
))
case
'^='
:
return
adjustMatcher
((
attrValue
)
=>
value
.
startsWith
(
attrValue
))
return
(
attrValue
)
=>
false
}
return
(
attrValue
)
=>
false
}
const
compileMatcher
=
(
ast
:
AST
,
selector
:
string
):
Matcher
=>
{
let
counter
=
0
const
neededContext
=
[]
const
makeMatcher
=
(
ast
:
AST
)
=>
{
//console.log('makeMatcher', ast)
switch
(
ast
.
type
)
{
...
...
@@ -215,16 +238,38 @@ const compileMatcher = (ast: AST, selector: string): Matcher => {
case
'only-child'
:
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
{
// TODO: This can break-early after it finds the second element
return
fi
lterChildElements
(
parent
).
length
===
1
return
fi
ndChildren
(
context
,
parent
,
'ELEMENT'
,
filterChildElementsMatcher
).
length
===
1
}
// case 'nth-of-type':
// case 'nth-last-of-type':
// case 'nth-last-child':
case
'nth-child'
:
{
const
nthChildMatcher
=
makeNthChildPosMatcher
(
ast
)
console
.
log
(
'nth-child:ast'
,
ast
)
const
argument
=
ast
.
subtree
?
ast
.
argument
.
replace
(
/
\s
*of
\s
+.*$/
,
''
)
:
ast
.
argument
const
nthChildMatcher
=
makeNthChildPosMatcher
(
argument
)
let
subDebug
,
childSelector
,
childMatcher
if
(
ast
.
subtree
)
{
subDebug
=
true
childSelector
=
ast
.
content
childMatcher
=
makeMatcher
(
ast
.
subtree
)
}
else
{
subDebug
=
false
childSelector
=
'ELEMENT'
childMatcher
=
filterChildElementsMatcher
}
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
{
const
pos
=
nthChildPos
(
node
,
parent
)
+
1
return
nthChildMatcher
(
context
,
node
,
parent
,
pos
,
debug
)
const
children
=
findChildren
(
context
,
parent
,
childSelector
,
childMatcher
)
const
pos
=
children
.
indexOf
(
node
)
if
(
parent
?.
name
===
'body'
&&
pos
!==
-
1
)
{
console
.
log
(
'nth-child:debug'
,
{
parent
,
childSelector
,
children
,
node
,
pos
})
}
if
(
pos
===
-
1
)
return
false
return
nthChildMatcher
(
context
,
node
,
parent
,
pos
+
1
,
debug
||
parent
?.
name
===
'body'
)
}
}
default
:
console
.
error
(
'pseudo-class'
,
nodeUtil
.
inspect
({
selector
,
ast
},
{
depth
:
null
,
colors
:
true
}))
throw
new
Error
(
`Unknown pseudo-class:
${
ast
.
name
}
`
)
}
case
'attribute'
:
...
...
@@ -256,9 +301,9 @@ const compileMatcher = (ast: AST, selector: string): Matcher => {
export
const
createMatcher
=
(
selector
:
string
)
=>
{
const
matcherCreater
=
selectorCache
.
get
(
selector
)
if
(
false
&&
matcherCreater
)
return
matcherCreater
()
if
(
matcherCreater
)
return
matcherCreater
()
const
ast
=
elParse
(
selector
)
console
.
log
(
'createMatcher'
,
nodeUtil
.
inspect
({
selector
,
ast
},
{
depth
:
null
,
colors
:
true
}))
//
console.log('createMatcher', nodeUtil.inspect({ selector, ast }, { depth: null, colors: true }))
const
newMatcherCreater
=
compileMatcher
(
ast
,
selector
)
selectorCache
.
set
(
selector
,
newMatcherCreater
)
return
newMatcherCreater
()
...
...
lib/node.astro
View file @
ebe8b35
---
import { ELEMENT_NODE, TEXT_NODE } from 'ultrahtml'
import type { NodeType } from 'ultrahtml'
import Children from './children.astro'
interface Props {
html?: string,
debug?: boolean,
xpath?: string,
replacements?: Replacements,
slotHandler
parent?: NodeType,
node: NodeType,
index?: number,
}
const { props: { parent = null, node, index = 0, debug = false, replacers, slotHandler } } = Astro
const { name: Name, attributes } = node
...
...
lib/replace.astro
View file @
ebe8b35
...
...
@@ -3,6 +3,14 @@ import html from '@resources/provider-portal.html?raw'
import { walkSync } from 'ultrahtml'
import { parseHtml, createMatcher, findNode } from './html.ts'
import Match from './match.astro'
import type { SlotHandler, Replacements } from './astro.ts'
interface Props {
html?: string,
debug?: boolean,
xpath?: string,
replacements?: Replacements,
}
const { props } = Astro
const { html, debug = false, xpath, replacements = {} } = props
...
...
@@ -11,7 +19,7 @@ const doc = props.node ? props.node : parseHtml(props.html)
const node = xpath ? findNode(doc, xpath) : doc
const replacers = Object.entries(replacements).map(([ selector, handler ]) => [ createMatcher(selector), handler ])
const slotHandler = (slotName, node) => {
const slotHandler
: SlotHandler
= (slotName, node) => {
return Astro.slots.render(slotName, [ node, { slotHandler, replacers } ] )
}
---
...
...
src/layouts/base.astro
View file @
ebe8b35
...
...
@@ -10,9 +10,10 @@ const layoutReplacements = {
['.hero-section']: 'content',
['.content-section']: 'DELETE',
['.content-section-4']: 'DELETE',
//['a.w-webflow-badge']: 'DELETE',
//['.navbar.w-nav:nth-child(n + 1)']: 'DELETE',
// The following 2 are not implemented yet
//
[':nth-child(n + 1 of .navbar.w-nav)']: 'DELETE',
[':nth-child(n + 1 of .navbar.w-nav)']: 'DELETE',
//
The following is not implemented yet
//['.navbar.w-nav:nth-of-type(n + 1)']: 'DELETE',
}
---
...
...
src/pages/index.astro
View file @
ebe8b35
...
...
@@ -4,7 +4,6 @@ import { Replace } from '@lib/astro.ts'
import BaseLayout from '@layouts/base.astro'
import { Demographics } from '@components/EventDetails.jsx'
const { data: layoutHtml } = await getSitePage('msd', '/')
const { data: pageHtml } = await getSitePage('msd', '/provider-portal')
const pageReplacements = {
['#w-node-_3615b991-cc00-f776-58c2-a728a0fba1a9-70cc1f5d']: 'Demographics',
...
...
tsconfig.json
View file @
ebe8b35
{
"extends"
:
"astro/tsconfigs/base"
,
"compilerOptions"
:
{
"alwaysStrict"
:
true
,
"noImplicitAny"
:
true
,
"baseUrl"
:
"."
,
"paths"
:
{
"@lib/*"
:
[
"lib/*"
],
...
...
Please
register
or
sign in
to post a comment