stew-select
Version:
CSS selectors that allow regular expressions. Stew is a meatier soup.
653 lines (500 loc) • 36.8 kB
HTML
<html>
<head>
<title>stew.coffee</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0; maximum-scale=1.0; user-scalable=0;">
<link rel="stylesheet" media="all" href="docco.css" />
</head>
<body>
<div id="container">
<div id="background"></div>
<ul id="jump_to">
<li>
<a class="large" href="javascript:void(0);">Jump To …</a>
<a class="small" href="javascript:void(0);">+</a>
<div id="jump_wrapper">
<div id="jump_page">
<a class="source" href="dom-util.html">
dom-util.coffee
</a>
<a class="source" href="predicate-factory.html">
predicate-factory.coffee
</a>
<a class="source" href="stew.html">
stew.coffee
</a>
</div>
</li>
</ul>
<ul class="sections">
<li id="title">
<div class="annotation">
<h1>stew.coffee</h1>
</div>
</li>
<li id="section-1">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-1">¶</a>
</div>
</div>
<div class="content"><div class='highlight'><pre>fs = require <span class="string">'fs'</span>
path = require <span class="string">'path'</span>
HOMEDIR = path.join(__dirname,<span class="string">'..'</span>)
LIB_DIR = <span class="keyword">if</span> fs.existsSync(path.join(HOMEDIR,<span class="string">'lib-cov'</span>)) <span class="keyword">then</span> path.join(HOMEDIR,<span class="string">'lib-cov'</span>) <span class="keyword">else</span> path.join(HOMEDIR,<span class="string">'lib'</span>)
DOMUtil = require(path.join(LIB_DIR,<span class="string">'dom-util'</span>)).DOMUtil
PredicateFactory = require(path.join(LIB_DIR,<span class="string">'predicate-factory'</span>)).PredicateFactory</pre></div></div>
</li>
<li id="section-2">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-2">¶</a>
</div>
<p><strong>Stew</strong> is a DOM selection engine that
supports the full CSS selector syntax
as well as CSS selectors extended with
regular expressions.</p>
<p>Method names that start with <code>_</code> are subject
to change without notice. Other methods may be
considered a part of the public API.</p>
</div>
<div class="content"><div class='highlight'><pre><span class="class"><span class="keyword">class</span> <span class="title">Stew</span></span></pre></div></div>
</li>
<li id="section-3">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-3">¶</a>
</div>
<p><strong>The Stew constructor</strong> accepts an optional <code>DOMUtil</code> instance
(allowing callers to configure the <code>DOMUtil</code> used by <code>Stew</code>).</p>
</div>
<div class="content"><div class='highlight'><pre> constructor:(dom_util)->
<span class="property">@factory</span> = <span class="keyword">new</span> PredicateFactory()
<span class="property">@dom_util</span> = dom_util ? <span class="keyword">new</span> DOMUtil()</pre></div></div>
</li>
<li id="section-4">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-4">¶</a>
</div>
<p><strong>select</strong> selects nodes from the given <code>dom</code>
that match the given <code>selector</code>.</p>
<p>If <code>selector</code> is a string, it will be parsed as
described in the README. Otherwise <code>selector</code>
is assumed to be a predicate function
(like those generated by <code>PredicateFactory</code>).</p>
<p>If <code>dom</code> is a string, it will be parsed as HTML
(using <code>DOMUtil.parse_html</code>, which see). If <code>dom</code>
is a single node, the given <code>selector</code> will be
applied to it. If <code>dom</code> is an array of nodes,
the given <code>selector</code> will be each element in turn.</p>
<p>This results in an array of matching nodes.</p>
<p>If a <code>callback</code> is provided, the resulting array is
passed to it (assuming the signature
<code>callback(err,nodeset)</code>). Otherwise
the resulting array is returned by this function.</p>
<p>Note that when <code>dom</code> is a string, a callback method
<em>must</em> be provided. (Since our HTML parsing
is asynchronous.) When <code>dom</code> is an object, the
callback method is optional (but will be used
when present)</p>
</div>
<div class="content"><div class='highlight'><pre> select:(dom,selector,callback)->
<span class="keyword">if</span> <span class="keyword">typeof</span> selector <span class="keyword">is</span> <span class="string">'string'</span>
selector = <span class="property">@_parse_selectors</span>(selector)
<span class="keyword">if</span> <span class="keyword">typeof</span> dom <span class="keyword">is</span> <span class="string">'string'</span>
<span class="keyword">if</span> callback?
<span class="property">@dom_util</span>.parse_html dom, (err, dom)=>
<span class="keyword">if</span> err?
callback(err)
<span class="keyword">else</span>
callback(<span class="literal">null</span>,<span class="property">@_unguarded_select</span>(dom,selector))
<span class="keyword">else</span>
<span class="keyword">throw</span> <span class="keyword">new</span> Error(<span class="string">'When select is invoked on a string object, the `callback(err,nodeset)` parameter is required.'</span>)
<span class="keyword">else</span>
nodeset = <span class="property">@_unguarded_select</span>(dom,selector)
callback?(<span class="literal">null</span>,nodeset)
<span class="keyword">return</span> nodeset</pre></div></div>
</li>
<li id="section-5">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-5">¶</a>
</div>
<p><strong>_unguarded_select</strong> is the "inner" method
for <code>select</code>. It assumes <code>dom</code> is a node or
array of nodes and that <code>predicate</code> is a
predicate function. It returns an array of
matching nodes. (Generally this method
will not be directly called by clients.)</p>
</div>
<div class="content"><div class='highlight'><pre> _unguarded_select:(dom,predicate)->
result = []
<span class="function"><span class="title">visit</span></span> = (node,parent,path,siblings,sib_index)->
<span class="keyword">if</span> predicate(node,parent,path,siblings,sib_index)
result.push node
<span class="keyword">return</span> { <span class="string">'continue'</span>:<span class="literal">true</span>, <span class="string">'visit_children'</span>:<span class="literal">true</span> }
<span class="property">@dom_util</span>.walk_dom dom, visit:visit
<span class="keyword">return</span> result</pre></div></div>
</li>
<li id="section-6">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-6">¶</a>
</div>
<p><strong>select_first</strong> selects the first node in the
given <code>dom</code> that matches the given <code>selector</code>.</p>
<p>It behaves exactly like <code>select</code> (which see)
save that it aborts processing as soon as
the first matching node is found, and returns
a single node rather than an array of nodes.</p>
</div>
<div class="content"><div class='highlight'><pre> select_first:(dom,selector,callback)->
<span class="keyword">if</span> <span class="keyword">typeof</span> selector <span class="keyword">is</span> <span class="string">'string'</span>
selector = <span class="property">@_parse_selectors</span>(selector)
<span class="keyword">if</span> <span class="keyword">typeof</span> dom <span class="keyword">is</span> <span class="string">'string'</span>
<span class="keyword">if</span> callback?
<span class="property">@dom_util</span>.parse_html dom, (err, dom)=>
<span class="keyword">if</span> err?
callback(err)
<span class="keyword">else</span>
callback(<span class="literal">null</span>,<span class="property">@_unguarded_select_first</span>(dom,selector))
<span class="keyword">else</span>
<span class="keyword">throw</span> <span class="keyword">new</span> Error(<span class="string">'When select_first is invoked on a string object, the `callback(err,node)` parameter is required.'</span>)
<span class="keyword">else</span>
node = <span class="property">@_unguarded_select_first</span>(dom,selector)
callback?(<span class="literal">null</span>,node)
<span class="keyword">return</span> node</pre></div></div>
</li>
<li id="section-7">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-7">¶</a>
</div>
<p><strong>_unguarded_select_first</strong> is the "inner" method for <code>select_first</code>.
(Generally this method will not be directly called by clients.)</p>
</div>
<div class="content"><div class='highlight'><pre> _unguarded_select_first:(dom,predicate)->
result = <span class="literal">null</span>
<span class="function"><span class="title">visit</span></span> = (node,parent,path,siblings,sib_index)->
<span class="keyword">if</span> predicate(node,parent,path,siblings,sib_index)
result = node
<span class="keyword">return</span> { <span class="string">'continue'</span>:<span class="literal">false</span>, <span class="string">'visit_children'</span>:<span class="literal">false</span> }
<span class="keyword">else</span>
<span class="keyword">return</span> { <span class="string">'continue'</span>:<span class="literal">true</span>, <span class="string">'visit_children'</span>:<span class="literal">true</span> }
<span class="property">@dom_util</span>.walk_dom dom, visit:visit
<span class="keyword">return</span> result</pre></div></div>
</li>
<li id="section-8">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-8">¶</a>
</div>
<p><strong>_SPLIT_ON_WS_REGEXP</strong> is regular expression that is
used to split a string of CSS selectors into individual
selectors. It is similiar to <code>str.split(/\s/)</code>, but:
- treats "quoted phrases" (and <code>/regular expressions/</code>) as a single token
- also splits on the CSS "operators" of <code>></code>, <code>+</code>, <code>,</code> and <code>~</code>
(Shout-out to
<a href="http://stackoverflow.com/questions/2817646/javascript-split-string-on-space-or-on-quotes-to-array">http://stackoverflow.com/questions/2817646/javascript-split-string-on-space-or-on-quotes-to-array</a>
from which this expression was originally derived.)</p>
</div>
<div class="content"><div class='highlight'><pre> _SPLIT_ON_WS_REGEXP = <span class="regexp">/([^\"\/\s,\+>]|(\"[^\"]+\")|(\/[^\/]+\/)|(\[[^\]]*\]))+|[,\+~>]/g</span></pre></div></div>
</li>
<li id="section-9">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-9">¶</a>
</div>
<p><strong>_split_on_ws_respecting_quotes</strong> is used to split a string of
CSS selectors into individual selectors.</p>
</div>
<div class="content"><div class='highlight'><pre> _split_on_ws_respecting_quotes:(selector)->
result = []
<span class="keyword">while</span> <span class="literal">true</span>
token = _SPLIT_ON_WS_REGEXP.exec(selector)
<span class="keyword">if</span> token?[<span class="number">0</span>]?
result.push(token[<span class="number">0</span>])
<span class="keyword">else</span>
<span class="keyword">break</span>
<span class="keyword">return</span> result</pre></div></div>
</li>
<li id="section-10">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-10">¶</a>
</div>
<p><strong>_parse_selectors</strong> accepts a string containing one
or more CSS selectors and returns the corresponding
predicate (a boolean-valued function with the signature
<code>(node,node_metadata,all_metadata)</code>)</p>
</div>
<div class="content"><div class='highlight'><pre> _parse_selectors:(selectors)->
result = []
<span class="keyword">if</span> <span class="keyword">typeof</span> selectors <span class="keyword">is</span> <span class="string">'string'</span>
selectors = <span class="property">@_split_on_ws_respecting_quotes</span>(selectors)
child_operator = <span class="literal">false</span> <span class="comment"># TODO there is probably a more elegant way to handle `>`, `+` and `,` here.</span>
adjacent_operator = <span class="literal">false</span>
preceding_sibling_operator = <span class="literal">false</span>
or_operator = <span class="literal">false</span>
<span class="keyword">for</span> selector <span class="keyword">in</span> selectors
<span class="keyword">if</span> selector <span class="keyword">is</span> <span class="string">'>'</span>
child_operator = <span class="literal">true</span>
<span class="keyword">else</span> <span class="keyword">if</span> selector <span class="keyword">is</span> <span class="string">'+'</span>
adjacent_operator = <span class="literal">true</span>
<span class="keyword">else</span> <span class="keyword">if</span> selector <span class="keyword">is</span> <span class="string">'~'</span>
preceding_sibling_operator = <span class="literal">true</span>
<span class="keyword">else</span> <span class="keyword">if</span> selector <span class="keyword">is</span> <span class="string">','</span>
or_operator = <span class="literal">true</span>
<span class="keyword">else</span>
predicate = <span class="property">@_parse_selector</span>(selector)
<span class="keyword">if</span> child_operator
result.push( <span class="property">@factory</span>.direct_descendant_predicate( result.pop(), predicate ) )
child_operator = <span class="literal">false</span>
<span class="keyword">else</span> <span class="keyword">if</span> adjacent_operator
result.push( <span class="property">@factory</span>.adjacent_sibling_predicate( result.pop(), predicate ) )
adjacent_operator = <span class="literal">false</span>
<span class="keyword">else</span> <span class="keyword">if</span> preceding_sibling_operator
result.push( <span class="property">@factory</span>.preceding_sibling_predicate( result.pop(), predicate ) )
preceding_sibling_operator = <span class="literal">false</span>
<span class="keyword">else</span> <span class="keyword">if</span> or_operator
result.push( <span class="property">@factory</span>.or_predicate( [ result.pop(), predicate ] ) )
or_operator = <span class="literal">false</span>
<span class="keyword">else</span>
result.push( predicate )
<span class="keyword">if</span> result.length > <span class="number">0</span>
result = <span class="property">@factory</span>.descendant_predicate(result)
<span class="keyword">return</span> result</pre></div></div>
</li>
<li id="section-11">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-11">¶</a>
</div>
<p><strong>_CSS_SELECTOR_REGEXP</strong> is a regular expression for parsing an individual CSS selector
(which might include a tag name, an ID, one or more classes, one or more attributes and a pseudo class).</p>
<p><code>"tag#id.class-one.class-two[name~=\"value with spaces\"]".match(_CSS_SELECTOR_REGEXP)</code></p>
</div>
<div class="content"><div class='highlight'><pre> <span class="comment">#{ TODO: Combine the `id` and `class` rules to make them order-indepedent? (I think CSS specifies the order, but still.)</span>
<span class="comment">#{############################################################################################################################################################################################################################################################################</span>
<span class="comment">#{ 11 1 11 11 1 112 2 2 2 2 2 22 22 3 33 3 3 3 #</span>
<span class="comment">#{ 12 3 4 56 7 89 01 2 34 56 7 890 1 2 3 4 5 67 89 0 12 3 4 5 #</span>
_CSS_SELECTOR_REGEXP: <span class="regexp">/((\/[^\/]*\/[gmi]*)|(\*|[\w-]+))?(\#((\/[^\/]*\/[gmi]*)|([\w-]+)))?((\.((\/[^\/]*\/[gmi]*)|([\w-]+)))*)((\[((\/[^\/]*\/[gmi]*)|([\w-]+))(((=)|(~=)|(\|=)|(\*=)|(\^=)|(\$=))(("(([^\\"]|(\\"))*)")|((\/[^\/]*\/[gmi]*)|([\w- :]+))))?\])*)(:([\w-]+))?/</span> <span class="comment">#</span>
<span class="comment">#{ \-name--------------------------/|\-id-----------------------------/\-class(es)-----------------------/|| \-attr-name-----------------/|\-operator----------------------/\-value-----------------------------------------------/| | |\-pseduo--/ #</span>
<span class="comment">#{ || \-operator-and-value---------------------------------------------------------------------/ | | #</span>
<span class="comment">#{ |\-attr-clause-([])----------------------------------------------------------------------------------------------------------/ | #</span>
<span class="comment">#{ \-attr-clauses-([][]...)-------------------------------------------------------------------------------------------------------/ #</span>
<span class="comment">#{############################################################################################################################################################################################################################################################################</span></pre></div></div>
</li>
<li id="section-12">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-12">¶</a>
</div>
<p>Indices of the important captured groups.</p>
</div>
<div class="content"><div class='highlight'><pre> _NAME = <span class="number">1</span>
_ID = <span class="number">4</span>
_CLASSES = <span class="number">8</span>
_ATTRIBUTES = <span class="number">13</span>
_PSEUDO_CLASS = <span class="number">35</span></pre></div></div>
</li>
<li id="section-13">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-13">¶</a>
</div>
<p><strong>_ATTRIBUTE_CLAUSE_REGEXP</strong> is a regular expression used to
split one or more <code>[<name> <op> <value>]</code> expressions
into individual components.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="comment">#{###########################################################################################################################################################</span>
<span class="comment">#{ 1 1 1 11 11 1 11 2 #</span>
<span class="comment">#{ 1 23 4 567 8 9 0 1 2 34 56 7 89 0 #</span>
_ATTRIBUTE_CLAUSE_REGEXP: <span class="regexp">/(\[((\/[^\/]*\/[gmi]*)|([\w-]+))(((=)|(~=)|(\|=)|(\*=)|(\^=)|(\$=))(("(([^\\"]|(\\"))*)")|((\/[^\/]*\/[gmi]*)|([\w- :]+))))?\])/g</span> <span class="comment">#</span>
<span class="comment">#{ \-name----------------------/|\-operator-----------------------/\-value-----------------------------------------------/| #</span>
<span class="comment">#{ \-operator-and-value----------------------------------------------------------------------/ #</span>
<span class="comment">#{###########################################################################################################################################################</span></pre></div></div>
</li>
<li id="section-14">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-14">¶</a>
</div>
<p>Indices of the important captured groups.</p>
</div>
<div class="content"><div class='highlight'><pre> _ATTR_NAME = <span class="number">2</span>
_OPERATOR = <span class="number">6</span>
_DEQUOTED_ATTR_VALUE = <span class="number">15</span>
_NEVERQUOTED_ATTR_VALUE = <span class="number">18</span></pre></div></div>
</li>
<li id="section-15">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-15">¶</a>
</div>
<p><strong>_parse_selector</strong> returns a (possibly compound) predicate
that matches the provided <code>selector</code> (string).</p>
</div>
<div class="content"><div class='highlight'><pre> _parse_selector:(selector)->
match = <span class="property">@_CSS_SELECTOR_REGEXP</span>.exec(selector)
clauses = []</pre></div></div>
</li>
<li id="section-16">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-16">¶</a>
</div>
<p>The name part.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> match[_NAME]?
<span class="keyword">if</span> match[_NAME] <span class="keyword">is</span> <span class="string">'*'</span>
clauses.push(<span class="property">@factory</span>.any_tag_predicate())
<span class="keyword">else</span>
clauses.push(<span class="property">@factory</span>.by_tag_predicate(<span class="property">@_to_string_or_regex</span>(match[_NAME])))</pre></div></div>
</li>
<li id="section-17">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-17">¶</a>
</div>
<p>The ID part.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> match[_ID]?
clauses.push(<span class="property">@factory</span>.by_id_predicate(<span class="property">@_to_string_or_regex</span>(match[_ID].substring(<span class="number">1</span>))))</pre></div></div>
</li>
<li id="section-18">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-18">¶</a>
</div>
<p>One or more class parts.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> match[_CLASSES]?.length > <span class="number">0</span> <span class="comment"># match[CLASSES] contains something like `.foo.bar`</span>
cs = match[_CLASSES].split(<span class="string">'.'</span>) <span class="comment"># split the string into individual class names</span>
cs.shift() <span class="comment"># and skip the first (empty) token that is included</span>
<span class="keyword">for</span> c <span class="keyword">in</span> cs
clauses.push(<span class="property">@factory</span>.by_class_predicate(<span class="property">@_to_string_or_regex</span>(c)))</pre></div></div>
</li>
<li id="section-19">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-19">¶</a>
</div>
<p>TODO FIXME Support for <code>*=</code>, <code>^=</code> and <code>$=</code> is kinda hacked-in here. Refactor to be more DRY.
One or more attribute parts.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> match[_ATTRIBUTES]?.length > <span class="number">0</span> <span class="comment"># match[_ATTRIBUTES] contains one or more `[name=value]` (or `[name]`) strings</span>
attr_match = <span class="property">@_ATTRIBUTE_CLAUSE_REGEXP</span>.exec(match[_ATTRIBUTES])
<span class="keyword">while</span> attr_match?
<span class="keyword">if</span> attr_match[_ATTR_NAME]? <span class="keyword">and</span> (<span class="keyword">not</span> attr_match[_OPERATOR]?)
clauses.push(<span class="property">@factory</span>.by_attr_exists_predicate(<span class="property">@_to_string_or_regex</span>(attr_match[_ATTR_NAME])))
<span class="keyword">if</span> attr_match[_ATTR_NAME]? <span class="keyword">and</span> attr_match[_OPERATOR]? <span class="keyword">and</span> (attr_match[_DEQUOTED_ATTR_VALUE]? <span class="keyword">or</span> attr_match[_NEVERQUOTED_ATTR_VALUE]?)
delim = <span class="literal">null</span>
<span class="keyword">if</span> attr_match[_OPERATOR] <span class="keyword">is</span> <span class="string">'~='</span>
delim = <span class="regexp">/\s+/</span>
<span class="keyword">if</span> attr_match[_OPERATOR] <span class="keyword">is</span> <span class="string">'|='</span>
clauses.push(
<span class="property">@factory</span>.by_attr_value_pipe_equals(
<span class="property">@_to_string_or_regex</span>(attr_match[_ATTR_NAME]),
<span class="property">@_to_string_or_regex</span>(attr_match[_DEQUOTED_ATTR_VALUE] ? attr_match[_NEVERQUOTED_ATTR_VALUE])
)
)
<span class="keyword">else</span> <span class="keyword">if</span> attr_match[_OPERATOR] <span class="keyword">is</span> <span class="string">'^='</span> <span class="comment"># starts with</span>
aval = <span class="property">@_to_string_or_regex</span>(attr_match[_DEQUOTED_ATTR_VALUE] ? attr_match[_NEVERQUOTED_ATTR_VALUE])
<span class="keyword">if</span> <span class="keyword">typeof</span> aval <span class="keyword">is</span> <span class="string">'string'</span>
regexp_source = <span class="property">@factory</span>._escape_for_regexp(aval)
aval = <span class="keyword">new</span> RegExp(<span class="string">"^<span class="subst">#{regexp_source}</span>"</span>)
<span class="keyword">else</span>
regexp_source = aval.source
modifier = <span class="string">''</span>
modifier += <span class="string">'i'</span> <span class="keyword">if</span> aval.ignoreCase
modifier += <span class="string">'g'</span> <span class="keyword">if</span> aval.global
modifier += <span class="string">'m'</span> <span class="keyword">if</span> aval.multiline
<span class="keyword">unless</span> <span class="regexp">/^\^/</span>.test regexp_source
aval = <span class="keyword">new</span> RegExp(<span class="string">"^<span class="subst">#{regexp_source}</span>"</span>)
clauses.push(<span class="property">@factory</span>.by_attr_value_predicate(<span class="property">@_to_string_or_regex</span>(attr_match[_ATTR_NAME]),aval))
<span class="keyword">else</span> <span class="keyword">if</span> attr_match[_OPERATOR] <span class="keyword">is</span> <span class="string">'$='</span> <span class="comment"># ends with</span>
aval = <span class="property">@_to_string_or_regex</span>(attr_match[_DEQUOTED_ATTR_VALUE] ? attr_match[_NEVERQUOTED_ATTR_VALUE])
<span class="keyword">if</span> <span class="keyword">typeof</span> aval <span class="keyword">is</span> <span class="string">'string'</span>
regexp_source = <span class="property">@factory</span>._escape_for_regexp(aval)
aval = <span class="keyword">new</span> RegExp(<span class="string">"<span class="subst">#{regexp_source}</span>$"</span>)
<span class="keyword">else</span>
regexp_source = aval.source
modifier = <span class="string">''</span>
modifier += <span class="string">'i'</span> <span class="keyword">if</span> aval.ignoreCase
modifier += <span class="string">'g'</span> <span class="keyword">if</span> aval.global
modifier += <span class="string">'m'</span> <span class="keyword">if</span> aval.multiline
<span class="keyword">unless</span> <span class="regexp">/\$$/</span>.test regexp_source
aval = <span class="keyword">new</span> RegExp(<span class="string">"<span class="subst">#{regexp_source}</span>$"</span>)
clauses.push(<span class="property">@factory</span>.by_attr_value_predicate(<span class="property">@_to_string_or_regex</span>(attr_match[_ATTR_NAME]),aval))
<span class="keyword">else</span> <span class="keyword">if</span> attr_match[_OPERATOR] <span class="keyword">is</span> <span class="string">'*='</span> <span class="comment"># contains</span>
aval = <span class="property">@_to_string_or_regex</span>(attr_match[_DEQUOTED_ATTR_VALUE] ? attr_match[_NEVERQUOTED_ATTR_VALUE])
<span class="keyword">if</span> <span class="keyword">typeof</span> aval <span class="keyword">is</span> <span class="string">'string'</span>
regexp_source = <span class="property">@factory</span>._escape_for_regexp(aval)
aval = <span class="keyword">new</span> RegExp(regexp_source)
clauses.push(<span class="property">@factory</span>.by_attr_value_predicate(<span class="property">@_to_string_or_regex</span>(attr_match[_ATTR_NAME]),aval))
<span class="keyword">else</span>
clauses.push(
<span class="property">@factory</span>.by_attr_value_predicate(
<span class="property">@_to_string_or_regex</span>(attr_match[_ATTR_NAME]),
<span class="property">@_to_string_or_regex</span>(attr_match[_DEQUOTED_ATTR_VALUE] ? attr_match[_NEVERQUOTED_ATTR_VALUE]),
delim
)
)
attr_match = <span class="property">@_ATTRIBUTE_CLAUSE_REGEXP</span>.exec(match[_ATTRIBUTES])</pre></div></div>
</li>
<li id="section-20">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-20">¶</a>
</div>
<p>The pseudo-class part.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> match[_PSEUDO_CLASS]?
<span class="keyword">if</span> match[_PSEUDO_CLASS] <span class="keyword">is</span> <span class="string">'first-child'</span>
clauses.push(<span class="property">@factory</span>.first_child_predicate())</pre></div></div>
</li>
<li id="section-21">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-21">¶</a>
</div>
<p>Combine them with <code>and</code> if needed.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> clauses.length > <span class="number">0</span>
clauses = <span class="property">@factory</span>.and_predicate(clauses)
<span class="keyword">return</span> clauses</pre></div></div>
</li>
<li id="section-22">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-22">¶</a>
</div>
<p><strong>_to_string_or_regex</strong> converts a string that starts and ends with <code>/</code>
(with an optional <code>g</code>, <code>m</code> or <code>i</code> suffix) into a regular expression,
and otherwise returns the original <code>str</code> value.</p>
</div>
<div class="content"><div class='highlight'><pre> _to_string_or_regex:(str)->
match = str.match <span class="regexp">/^\/(.*)\/([gmi]*)$/</span>
<span class="keyword">if</span> match?[<span class="number">1</span>]?
<span class="keyword">return</span> <span class="keyword">new</span> RegExp(match[<span class="number">1</span>],match[<span class="number">2</span>])
<span class="keyword">else</span>
<span class="keyword">return</span> str</pre></div></div>
</li>
<li id="section-23">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-23">¶</a>
</div>
<p>Public API includes <code>Stew</code> and <code>DOMUtil</code></p>
</div>
<div class="content"><div class='highlight'><pre>exports = exports ? <span class="keyword">this</span>
exports.Stew = Stew
exports.DOMUtil = DOMUtil</pre></div></div>
</li>
</ul>
</div>
</body>
</html>