Skip to content

Commit c4ec206

Browse files
committed
impelement '~'. closes #14
1 parent 37ea673 commit c4ec206

4 files changed

Lines changed: 72 additions & 21 deletions

File tree

JSONSelect.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,6 @@ class functions, and more blue sky dreaming.
7474
<tr><td>T:contains(S)</td><td>A node of type T with a string value contains the substring S</td><td>3</td></tr>
7575
</table>
7676

77-
**NOTE:** Not all of the constructs on the above table are necessarily implemented in the reference implementation at the moment
78-
(specifically, the `~` operator is pending implementation).
79-
8077
## Grouping<a name="grouping"></a>
8178

8279
## Selectors<a name="selectors"></a>

site/index.html

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,6 @@
7979
<div class="title"> Choose A Selector... </div>
8080
<div class="selector">.languagesSpoken .lang</div>
8181
<div class="selector">.drinkPreference :first-child</div>
82-
<div class="selector">:has(.preferred) > .lang</div>
83-
<div class="selector">:has(.lang:val("Spanish")) > .level</div>
84-
<div class="selector">.seatingPreference :nth-child(1)</div>
85-
<div class="selector">string:first-child</div>
8682
<div class="selector">."weight"</div>
8783
<div class="selector">.lang</div>
8884
<div class="selector">.favoriteColor</div>
@@ -93,8 +89,12 @@
9389
<div class="selector">string:nth-last-child(1)</div>
9490
<div class="selector">:root</div>
9591
<div class="selector">number</div>
96-
<div class="selector">object</div>
97-
<div class="selector">string</div>
92+
<div class="selector">:has(:root > .preferred)</div>
93+
<div class="selector">.preferred ~ .lang</div>
94+
<div class="selector">:has(.lang:val("Spanish")) > .level</div>
95+
<div class="selector">.lang:val("Bulgarian") ~ .level</div>
96+
<div class="selector">.seatingPreference :nth-child(1)</div>
97+
<div class="selector">.weight:expr(x<180) ~ .name .first</div>
9898
</div>
9999
</div>
100100
<div style="display: none" id="cred" class="content">
@@ -115,7 +115,7 @@
115115
</div>
116116
<div style="display: none" id="code" class="content">
117117
<p>Several different implementations of JSONSelect are available:</p>
118-
<p class="item"><b>JavaScript:</b> Get it <a href="js/jsonselect.js">documented</a>, or <a href="js/jsonselect.min.js">minified</a> (<i>2.7k</i> minified and gzipped).
118+
<p class="item"><b>JavaScript:</b> Get it <a href="js/jsonselect.js">documented</a>, or <a href="js/jsonselect.min.js">minified</a> (<i>2.9k</i> minified and gzipped).
119119
The code is <a href="https://github.com/lloyd/JSONSelect">on github</a>.</p>
120120
<p class="item"><b>node.js:</b> <tt>npm install JSONSelect</tt></p>
121121
<p class="item"><b>ruby:</b> A gem by <a href="https://github.com/fd">Simon Menke</a>: <a href="https://github.com/fd/json_select">https://github.com/fd/json_select</a></p>

src/jsonselect.js

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141

4242
// throw an error message
4343
function te(ec) {
44-
throw new Error(errorCodes[ec]);
44+
throw new Error(errorCodes[ec]);
4545
}
4646

4747
// THE LEXER
@@ -52,7 +52,7 @@
5252
str: 4 // string
5353
};
5454

55-
var pat = /^(?:([\r\n\t\ ]+)|([*.,>\)\(])|(string|boolean|null|array|object|number)|(:(?:root|first-child|last-child|only-child))|(:(?:nth-child|nth-last-child|has|expr|val|contains))|(:\w+)|(\"(?:[^\\]|\\[^\"])*\")|(\")|((?:[_a-zA-Z]|[^\0-\0177]|\\[^\r\n\f0-9a-fA-F])(?:[_a-zA-Z0-9\-]|[^\u0000-\u0177]|(?:\\[^\r\n\f0-9a-fA-F]))*))/;
55+
var pat = /^(?:([\r\n\t\ ]+)|([~*.,>\)\(])|(string|boolean|null|array|object|number)|(:(?:root|first-child|last-child|only-child))|(:(?:nth-child|nth-last-child|has|expr|val|contains))|(:\w+)|(\"(?:[^\\]|\\[^\"])*\")|(\")|((?:[_a-zA-Z]|[^\0-\0177]|\\[^\r\n\f0-9a-fA-F])(?:[_a-zA-Z0-9\-]|[^\u0000-\u0177]|(?:\\[^\r\n\f0-9a-fA-F]))*))/;
5656
var nthPat = /^\s*\(\s*(?:([+\-]?)([0-9]*)n\s*(?:([+\-])\s*([0-9]))?|(odd|even)|([+\-]?[0-9]+))\s*\)/;
5757
function lex(str, off) {
5858
if (!off) off = 0;
@@ -190,19 +190,22 @@
190190

191191
// THE PARSER
192192

193-
function parse(str, off, nested) {
193+
function parse(str, off, nested, hints) {
194+
if (!nested) hints = {};
195+
194196
var a = [], am, readParen;
195197
if (!off) off = 0;
196198

197199
while (true) {
198-
var s = parse_selector(str, off);
200+
var s = parse_selector(str, off, hints);
199201
a.push(s[1]);
200202
s = lex(str, off = s[0]);
201203
if (s && s[1] === " ") s = lex(str, off = s[0]);
202204
if (!s) break;
203205
// now we've parsed a selector, and have something else...
204-
if (s[1] === ">") {
205-
a.push(">");
206+
if (s[1] === ">" || s[1] === "~") {
207+
if (s[1] === "~") hints.usesSiblingOp = true;
208+
a.push(s[1]);
206209
off = s[0];
207210
} else if (s[1] === ",") {
208211
if (am === undefined) am = [ ",", a ];
@@ -218,10 +221,63 @@
218221
}
219222
if (nested && !readParen) te("mcp");
220223
if (am) am.push(a);
221-
return [off, am ? am : a];
224+
var rv;
225+
if (!nested && hints.usesSiblingOp) {
226+
rv = normalize(am ? am : a);
227+
} else {
228+
rv = am ? am : a;
229+
}
230+
return [off, rv];
222231
}
223232

224-
function parse_selector(str, off) {
233+
function normalizeOne(sel) {
234+
var sels = [], s;
235+
for (var i = 0; i < sel.length; i++) {
236+
if (sel[i] === '~') {
237+
// `A ~ B` maps to `:has(:root > A) > B`
238+
// `Z A ~ B` maps to `Z :has(:root > A) > B, Z:has(:root > A) > B`
239+
// This first clause, takes care of the first case, and the first half of the latter case.
240+
if (i < 2 || sel[i-2] != '>') {
241+
s = sel.slice(0,i-1);
242+
s = s.concat([{has:[[{pc: ":root"}, ">", sel[i-1]]]}, ">"]);
243+
s = s.concat(sel.slice(i+1));
244+
sels.push(s);
245+
}
246+
// here we take care of the second half of above:
247+
// (`Z A ~ B` maps to `Z :has(:root > A) > B, Z :has(:root > A) > B`)
248+
// and a new case:
249+
// Z > A ~ B maps to Z:has(:root > A) > B
250+
if (i > 1) {
251+
var at = sel[i-2] === '>' ? i-3 : i-2;
252+
s = sel.slice(0,at);
253+
var z = {};
254+
for (var k in sel[at]) if (sel[at].hasOwnProperty(k)) z[k] = sel[at][k];
255+
if (!z.has) z.has = [];
256+
z.has.push([{pc: ":root"}, ">", sel[i-1]]);
257+
s = s.concat(z, '>', sel.slice(i+1));
258+
sels.push(s);
259+
}
260+
break;
261+
}
262+
}
263+
if (i == sel.length) return sel;
264+
return sels.length > 1 ? [','].concat(sels) : sels[0];
265+
}
266+
267+
function normalize(sels) {
268+
if (sels[0] === ',') {
269+
var r = [","];
270+
for (var i = i; i < sels.length; i++) {
271+
var s = normalizeOne(s[i]);
272+
r = r.concat(s[0] === "," ? s.slice(1) : s);
273+
}
274+
return r;
275+
} else {
276+
return normalizeOne(sels);
277+
}
278+
}
279+
280+
function parse_selector(str, off, hints) {
225281
var soff = off;
226282
var s = { };
227283
var l = lex(str, off);
@@ -441,5 +497,3 @@
441497
};
442498
exports.compile = compile;
443499
})(typeof exports === "undefined" ? (window.JSONSelect = {}) : exports);
444-
445-

0 commit comments

Comments
 (0)