Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F95395162
parse.js
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Sun, Dec 15, 08:51
Size
15 KB
Mime Type
text/x-java
Expires
Tue, Dec 17, 08:51 (1 d, 20 h)
Engine
blob
Format
Raw Data
Handle
22944452
Attached To
rOACCT Open Access Compliance Check Tool (OACCT)
parse.js
View Options
import
{
SelectorType
,
AttributeAction
,
}
from
"./types"
;
const
reName
=
/^[^\\#]?(?:\\(?:[\da-f]{1,6}\s?|.)|[\w\-\u00b0-\uFFFF])+/
;
const
reEscape
=
/\\([\da-f]{1,6}\s?|(\s)|.)/gi
;
const
actionTypes
=
new
Map
([
[
126
/* Tilde */
,
AttributeAction
.
Element
],
[
94
/* Circumflex */
,
AttributeAction
.
Start
],
[
36
/* Dollar */
,
AttributeAction
.
End
],
[
42
/* Asterisk */
,
AttributeAction
.
Any
],
[
33
/* ExclamationMark */
,
AttributeAction
.
Not
],
[
124
/* Pipe */
,
AttributeAction
.
Hyphen
],
]);
// Pseudos, whose data property is parsed as well.
const
unpackPseudos
=
new
Set
([
"has"
,
"not"
,
"matches"
,
"is"
,
"where"
,
"host"
,
"host-context"
,
]);
/**
* Checks whether a specific selector is a traversal.
* This is useful eg. in swapping the order of elements that
* are not traversals.
*
* @param selector Selector to check.
*/
export
function
isTraversal
(
selector
)
{
switch
(
selector
.
type
)
{
case
SelectorType
.
Adjacent
:
case
SelectorType
.
Child
:
case
SelectorType
.
Descendant
:
case
SelectorType
.
Parent
:
case
SelectorType
.
Sibling
:
case
SelectorType
.
ColumnCombinator
:
return
true
;
default
:
return
false
;
}
}
const
stripQuotesFromPseudos
=
new
Set
([
"contains"
,
"icontains"
]);
// Unescape function taken from https://github.com/jquery/sizzle/blob/master/src/sizzle.js#L152
function
funescape
(
_
,
escaped
,
escapedWhitespace
)
{
const
high
=
parseInt
(
escaped
,
16
)
-
0x10000
;
// NaN means non-codepoint
return
high
!==
high
||
escapedWhitespace
?
escaped
:
high
<
0
?
// BMP codepoint
String
.
fromCharCode
(
high
+
0x10000
)
:
// Supplemental Plane codepoint (surrogate pair)
String
.
fromCharCode
((
high
>>
10
)
|
0xd800
,
(
high
&
0x3ff
)
|
0xdc00
);
}
function
unescapeCSS
(
str
)
{
return
str
.
replace
(
reEscape
,
funescape
);
}
function
isQuote
(
c
)
{
return
c
===
39
/* SingleQuote */
||
c
===
34
/* DoubleQuote */
;
}
function
isWhitespace
(
c
)
{
return
(
c
===
32
/* Space */
||
c
===
9
/* Tab */
||
c
===
10
/* NewLine */
||
c
===
12
/* FormFeed */
||
c
===
13
/* CarriageReturn */
);
}
/**
* Parses `selector`, optionally with the passed `options`.
*
* @param selector Selector to parse.
* @param options Options for parsing.
* @returns Returns a two-dimensional array.
* The first dimension represents selectors separated by commas (eg. `sub1, sub2`),
* the second contains the relevant tokens for that selector.
*/
export
function
parse
(
selector
)
{
const
subselects
=
[];
const
endIndex
=
parseSelector
(
subselects
,
`
$
{
selector
}
`
,
0
);
if
(
endIndex
<
selector
.
length
)
{
throw
new
Error
(
`
Unmatched
selector
:
$
{
selector
.
slice
(
endIndex
)}
`
);
}
return
subselects
;
}
function
parseSelector
(
subselects
,
selector
,
selectorIndex
)
{
let
tokens
=
[];
function
getName
(
offset
)
{
const
match
=
selector
.
slice
(
selectorIndex
+
offset
).
match
(
reName
);
if
(
!
match
)
{
throw
new
Error
(
`
Expected
name
,
found
$
{
selector
.
slice
(
selectorIndex
)}
`
);
}
const
[
name
]
=
match
;
selectorIndex
+=
offset
+
name
.
length
;
return
unescapeCSS
(
name
);
}
function
stripWhitespace
(
offset
)
{
selectorIndex
+=
offset
;
while
(
selectorIndex
<
selector
.
length
&&
isWhitespace
(
selector
.
charCodeAt
(
selectorIndex
)))
{
selectorIndex
++
;
}
}
function
readValueWithParenthesis
()
{
selectorIndex
+=
1
;
const
start
=
selectorIndex
;
let
counter
=
1
;
for
(;
counter
>
0
&&
selectorIndex
<
selector
.
length
;
selectorIndex
++
)
{
if
(
selector
.
charCodeAt
(
selectorIndex
)
===
40
/* LeftParenthesis */
&&
!
isEscaped
(
selectorIndex
))
{
counter
++
;
}
else
if
(
selector
.
charCodeAt
(
selectorIndex
)
===
41
/* RightParenthesis */
&&
!
isEscaped
(
selectorIndex
))
{
counter
--
;
}
}
if
(
counter
)
{
throw
new
Error
(
"Parenthesis not matched"
);
}
return
unescapeCSS
(
selector
.
slice
(
start
,
selectorIndex
-
1
));
}
function
isEscaped
(
pos
)
{
let
slashCount
=
0
;
while
(
selector
.
charCodeAt
(
--
pos
)
===
92
/* BackSlash */
)
slashCount
++
;
return
(
slashCount
&
1
)
===
1
;
}
function
ensureNotTraversal
()
{
if
(
tokens
.
length
>
0
&&
isTraversal
(
tokens
[
tokens
.
length
-
1
]))
{
throw
new
Error
(
"Did not expect successive traversals."
);
}
}
function
addTraversal
(
type
)
{
if
(
tokens
.
length
>
0
&&
tokens
[
tokens
.
length
-
1
].
type
===
SelectorType
.
Descendant
)
{
tokens
[
tokens
.
length
-
1
].
type
=
type
;
return
;
}
ensureNotTraversal
();
tokens
.
push
({
type
});
}
function
addSpecialAttribute
(
name
,
action
)
{
tokens
.
push
({
type
:
SelectorType
.
Attribute
,
name
,
action
,
value
:
getName
(
1
),
namespace
:
null
,
ignoreCase
:
"quirks"
,
});
}
/**
* We have finished parsing the current part of the selector.
*
* Remove descendant tokens at the end if they exist,
* and return the last index, so that parsing can be
* picked up from here.
*/
function
finalizeSubselector
()
{
if
(
tokens
.
length
&&
tokens
[
tokens
.
length
-
1
].
type
===
SelectorType
.
Descendant
)
{
tokens
.
pop
();
}
if
(
tokens
.
length
===
0
)
{
throw
new
Error
(
"Empty sub-selector"
);
}
subselects
.
push
(
tokens
);
}
stripWhitespace
(
0
);
if
(
selector
.
length
===
selectorIndex
)
{
return
selectorIndex
;
}
loop
:
while
(
selectorIndex
<
selector
.
length
)
{
const
firstChar
=
selector
.
charCodeAt
(
selectorIndex
);
switch
(
firstChar
)
{
// Whitespace
case
32
/* Space */
:
case
9
/* Tab */
:
case
10
/* NewLine */
:
case
12
/* FormFeed */
:
case
13
/* CarriageReturn */
:
{
if
(
tokens
.
length
===
0
||
tokens
[
0
].
type
!==
SelectorType
.
Descendant
)
{
ensureNotTraversal
();
tokens
.
push
({
type
:
SelectorType
.
Descendant
});
}
stripWhitespace
(
1
);
break
;
}
// Traversals
case
62
/* GreaterThan */
:
{
addTraversal
(
SelectorType
.
Child
);
stripWhitespace
(
1
);
break
;
}
case
60
/* LessThan */
:
{
addTraversal
(
SelectorType
.
Parent
);
stripWhitespace
(
1
);
break
;
}
case
126
/* Tilde */
:
{
addTraversal
(
SelectorType
.
Sibling
);
stripWhitespace
(
1
);
break
;
}
case
43
/* Plus */
:
{
addTraversal
(
SelectorType
.
Adjacent
);
stripWhitespace
(
1
);
break
;
}
// Special attribute selectors: .class, #id
case
46
/* Period */
:
{
addSpecialAttribute
(
"class"
,
AttributeAction
.
Element
);
break
;
}
case
35
/* Hash */
:
{
addSpecialAttribute
(
"id"
,
AttributeAction
.
Equals
);
break
;
}
case
91
/* LeftSquareBracket */
:
{
stripWhitespace
(
1
);
// Determine attribute name and namespace
let
name
;
let
namespace
=
null
;
if
(
selector
.
charCodeAt
(
selectorIndex
)
===
124
/* Pipe */
)
{
// Equivalent to no namespace
name
=
getName
(
1
);
}
else
if
(
selector
.
startsWith
(
"*|"
,
selectorIndex
))
{
namespace
=
"*"
;
name
=
getName
(
2
);
}
else
{
name
=
getName
(
0
);
if
(
selector
.
charCodeAt
(
selectorIndex
)
===
124
/* Pipe */
&&
selector
.
charCodeAt
(
selectorIndex
+
1
)
!==
61
/* Equal */
)
{
namespace
=
name
;
name
=
getName
(
1
);
}
}
stripWhitespace
(
0
);
// Determine comparison operation
let
action
=
AttributeAction
.
Exists
;
const
possibleAction
=
actionTypes
.
get
(
selector
.
charCodeAt
(
selectorIndex
));
if
(
possibleAction
)
{
action
=
possibleAction
;
if
(
selector
.
charCodeAt
(
selectorIndex
+
1
)
!==
61
/* Equal */
)
{
throw
new
Error
(
"Expected `=`"
);
}
stripWhitespace
(
2
);
}
else
if
(
selector
.
charCodeAt
(
selectorIndex
)
===
61
/* Equal */
)
{
action
=
AttributeAction
.
Equals
;
stripWhitespace
(
1
);
}
// Determine value
let
value
=
""
;
let
ignoreCase
=
null
;
if
(
action
!==
"exists"
)
{
if
(
isQuote
(
selector
.
charCodeAt
(
selectorIndex
)))
{
const
quote
=
selector
.
charCodeAt
(
selectorIndex
);
let
sectionEnd
=
selectorIndex
+
1
;
while
(
sectionEnd
<
selector
.
length
&&
(
selector
.
charCodeAt
(
sectionEnd
)
!==
quote
||
isEscaped
(
sectionEnd
)))
{
sectionEnd
+=
1
;
}
if
(
selector
.
charCodeAt
(
sectionEnd
)
!==
quote
)
{
throw
new
Error
(
"Attribute value didn't end"
);
}
value
=
unescapeCSS
(
selector
.
slice
(
selectorIndex
+
1
,
sectionEnd
));
selectorIndex
=
sectionEnd
+
1
;
}
else
{
const
valueStart
=
selectorIndex
;
while
(
selectorIndex
<
selector
.
length
&&
((
!
isWhitespace
(
selector
.
charCodeAt
(
selectorIndex
))
&&
selector
.
charCodeAt
(
selectorIndex
)
!==
93
/* RightSquareBracket */
)
||
isEscaped
(
selectorIndex
)))
{
selectorIndex
+=
1
;
}
value
=
unescapeCSS
(
selector
.
slice
(
valueStart
,
selectorIndex
));
}
stripWhitespace
(
0
);
// See if we have a force ignore flag
const
forceIgnore
=
selector
.
charCodeAt
(
selectorIndex
)
|
0x20
;
// If the forceIgnore flag is set (either `i` or `s`), use that value
if
(
forceIgnore
===
115
/* LowerS */
)
{
ignoreCase
=
false
;
stripWhitespace
(
1
);
}
else
if
(
forceIgnore
===
105
/* LowerI */
)
{
ignoreCase
=
true
;
stripWhitespace
(
1
);
}
}
if
(
selector
.
charCodeAt
(
selectorIndex
)
!==
93
/* RightSquareBracket */
)
{
throw
new
Error
(
"Attribute selector didn't terminate"
);
}
selectorIndex
+=
1
;
const
attributeSelector
=
{
type
:
SelectorType
.
Attribute
,
name
,
action
,
value
,
namespace
,
ignoreCase
,
};
tokens
.
push
(
attributeSelector
);
break
;
}
case
58
/* Colon */
:
{
if
(
selector
.
charCodeAt
(
selectorIndex
+
1
)
===
58
/* Colon */
)
{
tokens
.
push
({
type
:
SelectorType
.
PseudoElement
,
name
:
getName
(
2
).
toLowerCase
(),
data
:
selector
.
charCodeAt
(
selectorIndex
)
===
40
/* LeftParenthesis */
?
readValueWithParenthesis
()
:
null
,
});
continue
;
}
const
name
=
getName
(
1
).
toLowerCase
();
let
data
=
null
;
if
(
selector
.
charCodeAt
(
selectorIndex
)
===
40
/* LeftParenthesis */
)
{
if
(
unpackPseudos
.
has
(
name
))
{
if
(
isQuote
(
selector
.
charCodeAt
(
selectorIndex
+
1
)))
{
throw
new
Error
(
`
Pseudo
-
selector
$
{
name
}
cannot
be
quoted
`
);
}
data
=
[];
selectorIndex
=
parseSelector
(
data
,
selector
,
selectorIndex
+
1
);
if
(
selector
.
charCodeAt
(
selectorIndex
)
!==
41
/* RightParenthesis */
)
{
throw
new
Error
(
`
Missing
closing
parenthesis
in
:
$
{
name
}
(
$
{
selector
})
`
);
}
selectorIndex
+=
1
;
}
else
{
data
=
readValueWithParenthesis
();
if
(
stripQuotesFromPseudos
.
has
(
name
))
{
const
quot
=
data
.
charCodeAt
(
0
);
if
(
quot
===
data
.
charCodeAt
(
data
.
length
-
1
)
&&
isQuote
(
quot
))
{
data
=
data
.
slice
(
1
,
-
1
);
}
}
data
=
unescapeCSS
(
data
);
}
}
tokens
.
push
({
type
:
SelectorType
.
Pseudo
,
name
,
data
});
break
;
}
case
44
/* Comma */
:
{
finalizeSubselector
();
tokens
=
[];
stripWhitespace
(
1
);
break
;
}
default
:
{
if
(
selector
.
startsWith
(
"/*"
,
selectorIndex
))
{
const
endIndex
=
selector
.
indexOf
(
"*/"
,
selectorIndex
+
2
);
if
(
endIndex
<
0
)
{
throw
new
Error
(
"Comment was not terminated"
);
}
selectorIndex
=
endIndex
+
2
;
// Remove leading whitespace
if
(
tokens
.
length
===
0
)
{
stripWhitespace
(
0
);
}
break
;
}
let
namespace
=
null
;
let
name
;
if
(
firstChar
===
42
/* Asterisk */
)
{
selectorIndex
+=
1
;
name
=
"*"
;
}
else
if
(
firstChar
===
124
/* Pipe */
)
{
name
=
""
;
if
(
selector
.
charCodeAt
(
selectorIndex
+
1
)
===
124
/* Pipe */
)
{
addTraversal
(
SelectorType
.
ColumnCombinator
);
stripWhitespace
(
2
);
break
;
}
}
else
if
(
reName
.
test
(
selector
.
slice
(
selectorIndex
)))
{
name
=
getName
(
0
);
}
else
{
break
loop
;
}
if
(
selector
.
charCodeAt
(
selectorIndex
)
===
124
/* Pipe */
&&
selector
.
charCodeAt
(
selectorIndex
+
1
)
!==
124
/* Pipe */
)
{
namespace
=
name
;
if
(
selector
.
charCodeAt
(
selectorIndex
+
1
)
===
42
/* Asterisk */
)
{
name
=
"*"
;
selectorIndex
+=
2
;
}
else
{
name
=
getName
(
1
);
}
}
tokens
.
push
(
name
===
"*"
?
{
type
:
SelectorType
.
Universal
,
namespace
}
:
{
type
:
SelectorType
.
Tag
,
name
,
namespace
});
}
}
}
finalizeSubselector
();
return
selectorIndex
;
}
Event Timeline
Log In to Comment