├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── Makefile ├── benchmarks ├── comparison.js └── data.json ├── bower.json ├── index.js ├── jspath.min.js ├── lib └── jspath.js ├── package-lock.json ├── package.json ├── readme.md ├── test.js └── test ├── arithmetic-operators-test.js ├── comparison-operators-test.js ├── concat-expressions-test.js ├── data.js ├── descendants-test.js ├── logical-expressions-test.js ├── multi-predicates-test.js ├── nested-predicates-test.js ├── object-predicates-test.js ├── parser-errors-test.js ├── path-test.js ├── positional-predicates-test.js ├── root-test.js ├── substs-test.js ├── test.js └── undefined-and-null-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | /benchmarks 3 | /test 4 | /Makefile 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 4 5 | - 6 6 | - 8 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Dmitry Filatov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | ./node_modules/.bin/nodeunit test/test.js 3 | 4 | benchmark: 5 | node benchmarks/comparison.js 6 | 7 | min: 8 | node ./node_modules/uglify-js/bin/uglifyjs lib/jspath.js -o jspath.min.js -c -m 9 | 10 | .PHONY: test benchmark min 11 | -------------------------------------------------------------------------------- /benchmarks/comparison.js: -------------------------------------------------------------------------------- 1 | var cliff = require('cliff'), 2 | benchmark = require('benchmark'), 3 | fs = require('fs'), 4 | no = require('nommon'), 5 | //jpath = require('jpath'), 6 | //jsonpath = require('JSONPath'), 7 | jspath = require('../index'), 8 | data = JSON.parse(fs.readFileSync(__dirname + '/data.json', 'utf8')), 9 | tests = { 10 | '.objects.categoryId' : { 11 | 'no.jpath' : '.objects.categoryId', 12 | // 'artjock/jpath' : '.objects.categoryId', 13 | //'jsonpath' : '$.objects[*].categoryId', 14 | 'jspath' : '.objects.categoryId' 15 | }, 16 | '.objects.hotspots.id' : { 17 | 'no.jpath' : '.objects.hotspots.id', 18 | //'artjock/jpath' : '.objects.hotspots.id', 19 | //'jsonpath' : '$.objects[*].hotspots[*].id', 20 | 'jspath' : '.objects.hotspots.id' 21 | }, 22 | 23 | '.objects{.categoryId}' : { 24 | 'no.jpath' : '.objects[.categoryId]', 25 | // 'artjock/jpath' : '.objects[.categoryId]', 26 | // 'jsonpath' : '$.objects[?(@.categoryId)]', 27 | 'jspath' : '.objects{.categoryId}' 28 | }, 29 | 30 | '.objects{.categoryId === "adm"}' : { 31 | 'no.jpath' : '.objects[.categoryId == "adm"]', 32 | // 'artjock/jpath' : '.objects[.categoryId == "adm"]', 33 | // 'jsonpath' : '$.objects[?(@.categoryId == "adm")]', 34 | 'jspath' : '.objects{.categoryId === "adm"}' 35 | }, 36 | 37 | '.objects{.categoryId === "adm" || .geometry.type === "polygon"}' : { 38 | 'no.jpath' : '.objects[.categoryId == "adm" || .geometry.type == "polygon"]', 39 | // 'artjock/jpath' : '.objects[.categoryId == "adm" || .geometry.type == "polygon"]', 40 | // 'jsonpath' : '$.objects[?(@.categoryId == "adm" || @.geometry.type == "polygon")]', 41 | 'jspath' : '.objects{.categoryId === "adm" || .geometry.type === "polygon"}' 42 | }, 43 | 44 | '.objects[10]' : { 45 | 'no.jpath' : '.objects[10]', 46 | // 'artjock/jpath' : '.objects[10]', 47 | // 'jsonpath' : '$.objects[10]', 48 | 'jspath' : '.objects[10]' 49 | } 50 | }, 51 | testFns = { 52 | 'no.jpath' : function(path) { 53 | no.jpath(path, data); 54 | }, 55 | 56 | 'artjock/jpath' : function(path) { 57 | jpath(data, path); 58 | }, 59 | 60 | 'jsonpath' : function(path) { 61 | jsonpath.eval(data, path); 62 | }, 63 | 64 | 'jspath' : function(path) { 65 | jspath.apply(path, data); 66 | } 67 | }, 68 | results = [], 69 | onTestCompleted = function(name) { 70 | results.push({ 71 | '' : name, 72 | 'mean time' : (this.stats.mean * 1000).toFixed(3) + 'ms', 73 | 'ops/sec' : (1 / this.stats.mean).toFixed(0) 74 | }); 75 | }; 76 | 77 | Object.keys(tests).forEach(function(testName) { 78 | var suite = new benchmark.Suite( 79 | testName, 80 | { 81 | onStart : function() { 82 | console.log('Starts \'' + testName + '\'\n'); 83 | }, 84 | 85 | onComplete : function() { 86 | console.log(cliff.stringifyObjectRows( 87 | results, 88 | ['', 'mean time', 'ops/sec'], 89 | ['red', 'green', 'blue']) + '\n'); 90 | results = []; 91 | } 92 | }), 93 | test = tests[testName]; 94 | 95 | Object.keys(test).forEach(function(name) { 96 | var i = 0; 97 | suite.add( 98 | name, 99 | function() { 100 | testFns[name](test[name], data); 101 | }, 102 | { 103 | onStart : function() { 104 | //console.log(name + ' \n'); 105 | }, 106 | onCycle : function() { 107 | console.log('\033[1A' + new Array(i++).join('.')); 108 | }, 109 | onComplete : function() { 110 | console.log(''); 111 | onTestCompleted.call(this, name); 112 | } 113 | }); 114 | }); 115 | 116 | suite.run(); 117 | }); 118 | -------------------------------------------------------------------------------- /benchmarks/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo" : 1, 3 | "objects":[{"id":1343234,"categoryId":"adm","title":"Западный административный округ (ЗАО)","hotspots":[{"id":1343234,"offsets":[[7,159],[46,190],[46,181],[56,185],[75,178],[87,179],[97,186],[136,184],[156,169],[161,159],[234,169],[278,150],[321,208],[323,224],[316,248],[297,268],[244,293],[240,307],[268,335],[286,343],[311,338],[346,308],[355,289],[356,226],[363,204],[380,200],[397,202],[425,216],[466,248],[493,313],[504,322],[540,319],[609,275],[631,278],[642,292],[643,316],[616,367],[556,413],[547,431],[547,461],[556,477],[568,491],[599,498],[532,585],[460,657],[496,688],[432,765],[298,875],[300,855],[236,761],[219,752],[178,757],[179,769],[173,770],[176,781],[183,780],[186,794],[174,804],[180,821],[184,820],[183,870],[179,869],[173,886],[169,885],[159,914],[128,910],[127,918],[118,918],[116,923],[99,920],[98,900],[64,938],[70,944],[66,947],[50,937],[35,910],[25,913],[18,904],[18,894],[9,886],[-8,880],[-32,896],[-38,883],[-60,885],[-54,871],[-70,861],[-65,856],[-67,822],[-49,796],[-31,788],[-36,775],[-29,782],[-21,777],[-33,761],[-32,756],[-19,761],[-23,747],[0,755],[31,733],[41,734],[47,744],[49,764],[62,764],[73,759],[77,737],[81,735],[96,735],[102,748],[118,754],[140,751],[181,734],[174,720],[149,704],[148,696],[164,677],[162,674],[188,650],[173,600],[94,485],[50,339],[42,266],[46,191],[2,155],[-26,204],[-86,212],[-113,200],[-73,190],[-2,89],[17,78],[44,92],[7,159]]}],"geometry":{"type":"polygon","data":[[[[37.356186565,55.776186544],[37.369383034,55.770121632],[37.369490322,55.771858125],[37.372837719,55.771145998],[37.379446682,55.772382761],[37.383652386,55.772204355],[37.387128529,55.770888964],[37.400314269,55.771276027],[37.407207546,55.774257486],[37.408822236,55.776029320],[37.433886138,55.774110837],[37.449036595,55.777779909],[37.463871558,55.766581881],[37.464630120,55.763499267],[37.462094930,55.758825354],[37.455482782,55.755022140],[37.437424139,55.750118116],[37.435960994,55.747510188],[37.445784584,55.742063533],[37.452046201,55.740586916],[37.460449730,55.741531394],[37.472313308,55.747245339],[37.475601864,55.750973488],[37.476004698,55.763238590],[37.478263286,55.767430937],[37.484282833,55.768261829],[37.490044889,55.767883053],[37.499444690,55.765147592],[37.513822672,55.758891145],[37.523007897,55.746406999],[37.526879833,55.744633908],[37.539224700,55.745136690],[37.553470080,55.751025016],[37.562903576,55.753645850],[37.570387442,55.753060643],[37.573971209,55.750417551],[37.574470100,55.745679277],[37.565068790,55.735802233],[37.544680984,55.727036759],[37.541499213,55.723502258],[37.541336940,55.717667768],[37.544414774,55.714600293],[37.548822985,55.711847639],[37.559267674,55.710382367],[37.536469233,55.693528441],[37.511547153,55.679718830],[37.524143812,55.673653630],[37.502000836,55.658770623],[37.456116957,55.637277491],[37.456605789,55.641118017],[37.434814350,55.659527834],[37.429007535,55.661225111],[37.414851171,55.660173965],[37.415057198,55.657811245],[37.412913442,55.657613927],[37.414078527,55.655586287],[37.416402326,55.655815175],[37.417412848,55.653027653],[37.413489614,55.651087171],[37.415439245,55.647770817],[37.416981180,55.648082416],[37.417167761,55.641959860],[37.416345664,55.638336650],[37.415066921,55.638470063],[37.413097006,55.635277714],[37.411818095,55.635392930],[37.408410516,55.629730073],[37.397797518,55.630602510],[37.397419830,55.628877059],[37.394134627,55.629033266],[37.393359971,55.627914860],[37.387738564,55.628518740],[37.387460117,55.632376060],[37.375571226,55.625062171],[37.377597802,55.623969262],[37.376404554,55.623258598],[37.370922455,55.625351282],[37.365693320,55.630583921],[37.362281215,55.629945648],[37.359855995,55.631595655],[37.359852140,55.633681158],[37.356694844,55.635119067],[37.350830027,55.636360350],[37.342813239,55.633322217],[37.340755817,55.635681870],[37.332995181,55.635356611],[37.332591341,55.636193457],[37.335298863,55.636678393],[37.335205824,55.638132975],[37.329678629,55.639963927],[37.331482918,55.640972098],[37.330833656,55.647552213],[37.336819005,55.652697991],[37.342886497,55.654274054],[37.341408768,55.656727120],[37.343901378,55.655340146],[37.346629855,55.656343181],[37.342323569,55.659543945],[37.342654989,55.660479209],[37.347015590,55.659454767],[37.345797197,55.662166494],[37.353715413,55.660646093],[37.364376020,55.664936590],[37.367719394,55.664714957],[37.369916291,55.662728243],[37.370650043,55.658932775],[37.374945600,55.658861223],[37.378708907,55.659749214],[37.380219159,55.664203268],[37.381663528,55.664444331],[37.386476752,55.664412493],[37.388709021,55.661899451],[37.394074109,55.660841879],[37.401799542,55.661397583],[37.414867599,55.664246857],[37.415704784,55.664743004],[37.413307560,55.667443845],[37.404683587,55.670612718],[37.404461467,55.672124393],[37.405381129,55.673644441],[37.409936023,55.675786996],[37.409164217,55.676392503],[37.418070492,55.681046780],[37.413124499,55.690756683],[37.385841069,55.713040297],[37.370820698,55.741230946],[37.368159947,55.755463812],[37.369318661,55.769957580],[37.354298290,55.776972652],[37.344642338,55.767441522],[37.324085888,55.765784221],[37.314837632,55.768245952],[37.328752932,55.770057373],[37.353053746,55.789644947],[37.359619793,55.791760623],[37.368739304,55.789143212],[37.356186565,55.776186544]]]]}},{"id":2024456,"categoryId":"adm","title":"Северо-Западный административный округ (СЗАО)","hotspots":[{"id":2024456,"offsets":[[244,-372],[212,-358],[162,-386],[164,-406],[150,-412],[149,-437],[128,-487],[109,-495],[108,-546],[92,-539],[71,-540],[40,-517],[47,-501],[26,-488],[40,-474],[39,-454],[56,-458],[67,-444],[58,-433],[52,-399],[85,-369],[147,-362],[168,-330],[143,-299],[121,-244],[93,-251],[69,-249],[65,-265],[75,-273],[49,-300],[23,-311],[-18,-295],[-40,-311],[-53,-310],[-35,-285],[-42,-250],[-61,-200],[-21,-118],[-13,-92],[25,-79],[40,-108],[74,-122],[119,-117],[100,-6],[88,30],[56,89],[48,115],[46,181],[55,185],[87,178],[98,186],[135,184],[160,161],[234,171],[278,153],[328,223],[317,251],[301,268],[252,292],[243,302],[265,329],[295,340],[318,331],[350,295],[356,258],[354,221],[362,202],[382,197],[415,202],[468,251],[487,236],[444,154],[436,68],[388,-38],[391,-71],[407,-86],[401,-102],[363,-99],[353,-117],[361,-127],[327,-209],[291,-321],[244,-372]]}],"geometry":{"type":"polygon","data":[[[[37.437380888,55.878732922],[37.426566222,55.876030912],[37.409400084,55.881482990],[37.409872153,55.885203712],[37.405301669,55.886485078],[37.404733040,55.891273992],[37.397753932,55.900902031],[37.391174474,55.902436402],[37.390774825,55.912263148],[37.385343351,55.911013144],[37.378195264,55.911112950],[37.367270627,55.906680941],[37.369818055,55.903644211],[37.362508700,55.901161311],[37.367290408,55.898322318],[37.366922107,55.894518552],[37.373052128,55.895399001],[37.376773693,55.892603388],[37.373607346,55.890506969],[37.371470966,55.883972430],[37.383028605,55.878144889],[37.404121497,55.876821028],[37.411288359,55.870698710],[37.402791121,55.864629723],[37.395109274,55.854064036],[37.385451310,55.855302021],[37.377509960,55.854995098],[37.375918069,55.858122678],[37.379596048,55.859601896],[37.370485254,55.864749446],[37.361747959,55.867055773],[37.347663679,55.863921940],[37.340106555,55.866891943],[37.335426771,55.866832462],[37.341647484,55.861994606],[37.339433321,55.855213858],[37.332627215,55.845510489],[37.346602865,55.829615489],[37.349291779,55.824634920],[37.362236120,55.822153361],[37.367562988,55.827680219],[37.379026749,55.830549405],[37.394637206,55.829584729],[37.388071158,55.808080888],[37.383908369,55.801168130],[37.372761109,55.789672338],[37.370132544,55.784678888],[37.369402983,55.771835635],[37.372707465,55.771182474],[37.383489945,55.772367831],[37.387346961,55.770928464],[37.400001624,55.771303431],[37.408756354,55.775681800],[37.433947661,55.773746670],[37.449053862,55.777278210],[37.466319241,55.763767124],[37.462532968,55.758290893],[37.456952465,55.755021006],[37.440042646,55.750428330],[37.437089533,55.748368265],[37.444541045,55.743183016],[37.452240326,55.741076796],[37.454992273,55.741161531],[37.462721058,55.742841066],[37.467743494,55.745107566],[37.473834791,55.749727912],[37.475816944,55.756882767],[37.475139015,55.764120989],[37.477850058,55.767868121],[37.484845259,55.768739074],[37.496260741,55.767795540],[37.514424660,55.758310555],[37.520915606,55.761214368],[37.506270745,55.777084709],[37.503411510,55.793722244],[37.487047353,55.814219157],[37.487791666,55.820598058],[37.493342497,55.823497212],[37.491196730,55.826589404],[37.478450873,55.826009637],[37.474824526,55.829584729],[37.477721312,55.831420460],[37.465919592,55.847262093],[37.453431227,55.868888973],[37.437380888,55.878732922]]]]}},{"id":2348296,"categoryId":"adm-region-city","title":"Хорошёво-Мнёвники","hotspots":[{"id":2348296,"offsets":[[167,78],[142,145],[154,159],[235,169],[278,151],[321,209],[323,225],[313,251],[296,268],[244,294],[240,307],[268,334],[289,344],[311,338],[346,308],[355,289],[356,226],[363,204],[397,201],[467,249],[492,233],[446,153],[437,78],[424,94],[389,102],[294,78],[293,69],[268,71],[251,94],[229,106],[212,107],[200,100],[180,77],[167,78]]}],"geometry":{"type":"polygon","data":[[[[37.410904300,55.791747212],[37.402578723,55.778882112],[37.406612766,55.776124750],[37.434250248,55.774141263],[37.449056041,55.777721142],[37.463947666,55.766496634],[37.464623582,55.763417794],[37.461179626,55.758306017],[37.455445063,55.754999640],[37.437383068,55.750065264],[37.436031234,55.747487401],[37.445708644,55.742173799],[37.452961338,55.740261210],[37.460439336,55.741465668],[37.472391260,55.747263494],[37.475577724,55.750985023],[37.476049793,55.763109291],[37.478367221,55.767416005],[37.490126026,55.767942213],[37.514180113,55.758614537],[37.522494961,55.761711906],[37.506863010,55.777189018],[37.503644359,55.791868104],[37.499309909,55.788700600],[37.487207782,55.787177204],[37.454506290,55.791843926],[37.454334629,55.793488025],[37.445837390,55.793197895],[37.439764869,55.788712690],[37.432233226,55.786355030],[37.426611316,55.786185756],[37.422330511,55.787515742],[37.415496242,55.792037353],[37.410904300,55.791747212]]]]}},{"id":1977914,"categoryId":"adm-region-city","title":"Строгино","hotspots":[{"id":1977914,"offsets":[[156,-98],[143,-108],[119,-115],[96,10],[51,101],[46,181],[56,184],[87,178],[98,186],[135,184],[162,161],[144,143],[146,125],[167,79],[181,77],[201,100],[216,107],[230,105],[251,93],[268,70],[269,2],[249,-11],[215,-13],[194,-24],[180,-40],[175,-69],[156,-98]]}],"geometry":{"type":"polygon","data":[[[[37.407233362,55.825871016],[37.402931098,55.827737118],[37.394680623,55.829047562],[37.386580353,55.805032821],[37.371082549,55.787318042],[37.369402145,55.771927390],[37.372876947,55.771226605],[37.379392032,55.772464877],[37.383482401,55.772354507],[37.387465482,55.770925724],[37.400058788,55.771320913],[37.409154494,55.775671123],[37.403089349,55.779251239],[37.403757219,55.782702167],[37.411127929,55.791667404],[37.415752057,55.792024036],[37.422554139,55.787574967],[37.427714710,55.786093846],[37.432708983,55.786501916],[37.439741735,55.788856545],[37.445826326,55.793356088],[37.446172331,55.806510953],[37.439237480,55.809035808],[37.427532319,55.809307694],[37.420456652,55.811600523],[37.415420805,55.814692910],[37.413617019,55.820272173],[37.407233362,55.825871016]]]]}},{"id":1343334,"categoryId":"adm-region-city","title":"Кунцево","hotspots":[{"id":1343334,"offsets":[[71,403],[66,387],[59,384],[62,372],[45,306],[45,231],[42,221],[36,220],[45,199],[40,186],[200,320],[214,304],[223,310],[232,329],[258,352],[250,362],[264,380],[274,414],[176,413],[109,396],[71,403]]}],"geometry":{"type":"polygon","data":[[[[37.378057466,55.728883875],[37.376407907,55.731988912],[37.374109254,55.732643489],[37.374946103,55.734962038],[37.369173990,55.747696461],[37.369131074,55.762217324],[37.368254159,55.764103692],[37.366198078,55.764412376],[37.369190082,55.768328076],[37.367307172,55.770936308],[37.422217355,55.744948987],[37.427327634,55.748060305],[37.430184857,55.746935479],[37.433380709,55.743139422],[37.442333923,55.738690800],[37.439640985,55.736862783],[37.444391177,55.733375239],[37.447607146,55.726825317],[37.414149270,55.726982740],[37.391275392,55.730143174],[37.378057466,55.728883875]]]]}},{"id":3232553,"categoryId":"adm-region-city","title":"Крылатское","hotspots":[{"id":3232553,"offsets":[[200,320],[224,295],[241,298],[299,266],[313,252],[321,235],[322,218],[300,176],[277,153],[244,167],[162,161],[129,185],[99,186],[75,178],[56,185],[46,181],[46,191],[200,320]]}],"geometry":{"type":"polygon","data":[[[[37.422266305,55.745019057],[37.430496496,55.749811872],[37.436526101,55.749291469],[37.456352991,55.755481396],[37.461030763,55.758149477],[37.463820260,55.761337624],[37.464270872,55.764610193],[37.456782144,55.772823670],[37.448928636,55.777250243],[37.437427324,55.774480670],[37.409296316,55.775653832],[37.397848647,55.771118216],[37.387597245,55.770903517],[37.379452717,55.772385974],[37.372760270,55.771141840],[37.369427961,55.771801616],[37.369385045,55.769890485],[37.422266305,55.745019057]]]]}},{"id":3832880,"categoryId":"vegetation-forest","title":"Серебряноборское лесничество","hotspots":[{"id":3832880,"offsets":[[162,75],[155,73],[147,83],[119,140],[101,140],[101,118],[76,97],[63,93],[51,107],[48,183],[87,178],[97,185],[96,193],[85,202],[91,203],[89,210],[101,218],[96,229],[86,220],[82,225],[88,247],[81,254],[83,267],[74,275],[54,264],[67,214],[54,214],[46,225],[47,311],[63,372],[69,374],[72,384],[110,374],[111,364],[106,362],[123,341],[109,330],[119,318],[115,315],[127,302],[130,305],[151,281],[125,259],[147,233],[140,208],[145,204],[139,203],[135,184],[157,164],[144,157],[138,141],[162,75]]}],"geometry":{"type":"polygon","data":[[[[37.409227752,55.792285936],[37.406996154,55.792721141],[37.404077911,55.790762681],[37.394486331,55.779826131],[37.388499641,55.779807991],[37.388370895,55.783991936],[37.379820012,55.788102886],[37.375367545,55.788804122],[37.371194028,55.786089784],[37.370233797,55.771505371],[37.371161842,55.771012473],[37.379401588,55.772479051],[37.383467817,55.772385312],[37.386868858,55.771133430],[37.386606001,55.769621441],[37.382888459,55.767855363],[37.384857201,55.767634597],[37.384347581,55.766331148],[37.388295793,55.764767557],[37.386579179,55.762650409],[37.383199595,55.764259452],[37.381697558,55.763279516],[37.383756154,55.759116025],[37.381643914,55.757713979],[37.382110619,55.755284857],[37.379138731,55.753614936],[37.372084522,55.755787029],[37.376805209,55.765572044],[37.372384929,55.765572044],[37.369466686,55.763273467],[37.369938754,55.746695494],[37.375389003,55.734905098],[37.377406024,55.734566101],[37.378307246,55.732513899],[37.391557359,55.734457137],[37.391653919,55.736406332],[37.389937305,55.736769526],[37.396031284,55.740861268],[37.391181850,55.743088545],[37.394625806,55.745249123],[37.393284702,55.745920876],[37.397168540,55.748498843],[37.398477458,55.747857393],[37.405558490,55.752516741],[37.396567725,55.756806480],[37.404153013,55.761876108],[37.401889228,55.766630552],[37.403514647,55.767395686],[37.401301824,55.767621744],[37.399888300,55.771187861],[37.407521867,55.775097586],[37.403155231,55.776542846],[37.400912904,55.779572273],[37.409227752,55.792285936]]]]}},{"id":1326527,"categoryId":"vegetation-forest","title":"","hotspots":[{"id":1326527,"offsets":[[-68,293],[-54,298],[-49,309],[-53,323],[-33,335],[-29,353],[-22,358],[-2,342],[6,346],[2,373],[12,382],[40,373],[46,376],[46,387],[59,383],[61,373],[42,293],[42,222],[26,220],[14,194],[21,173],[-5,150],[-25,174],[-24,182],[-14,187],[-16,193],[-26,198],[-45,195],[-61,209],[-69,260],[-68,293]]},{"id":0,"offsets":[[-17,290],[-13,288],[-12,293],[-17,293],[-17,290]]},{"id":0,"offsets":[[0,269],[1,268],[2,269],[0,271],[0,269]]}],"geometry":{"type":"polygon","data":[[[[37.330174838,55.750097032],[37.335035001,55.749268021],[37.336778437,55.746989659],[37.335646544,55.744396462],[37.342234050,55.742108737],[37.343886291,55.738586103],[37.346042787,55.737635784],[37.353075539,55.740677327],[37.355634366,55.739899563],[37.354507838,55.734699941],[37.357860600,55.732983731],[37.367543374,55.734693887],[37.369533573,55.734012861],[37.369541620,55.732025709],[37.373841201,55.732847521],[37.374506389,55.734627298],[37.368133460,55.750254361],[37.367981915,55.763890372],[37.362589334,55.764406800],[37.358612959,55.769278116],[37.360826284,55.773509497],[37.352010366,55.777909821],[37.345067468,55.773148159],[37.345332504,55.771737548],[37.348871511,55.770783032],[37.348276899,55.769542721],[37.344849707,55.768616881],[37.338081655,55.769165281],[37.332871129,55.766391162],[37.329978031,55.756550958],[37.330174838,55.750097032]],[[37.347840537,55.750846418],[37.349170912,55.751100559],[37.349428405,55.750205007],[37.347733248,55.750096087],[37.347840537,55.750846418]],[[37.353548278,55.754888276],[37.354084719,55.755033484],[37.354320754,55.754815671],[37.353805770,55.754537353],[37.353548278,55.754888276]]]]}},{"id":22643276,"categoryId":"adm-region-city","title":"отдельная площадка «Рублёво-Архангельское»","hotspots":[{"id":22643276,"offsets":[[-130,188],[-115,196],[-72,184],[-65,168],[-14,110],[-11,83],[-20,62],[23,36],[12,15],[-16,0],[-49,22],[-58,36],[-91,30],[-113,39],[-115,45],[-134,50],[-130,49],[-129,56],[-122,52],[-120,58],[-98,46],[-89,51],[-115,68],[-128,71],[-114,97],[-119,98],[-125,116],[-125,139],[-120,142],[-118,138],[-119,106],[-74,134],[-89,156],[-112,167],[-125,162],[-130,188]]}],"geometry":{"type":"polygon","data":[[[[37.308924031,55.770495661],[37.314331365,55.769056225],[37.328965497,55.771257694],[37.331497502,55.774475001],[37.348899674,55.785673019],[37.349757981,55.790920223],[37.346925569,55.794969997],[37.361473870,55.799901690],[37.357868981,55.803910211],[37.348062825,55.806915409],[37.336819005,55.802584847],[37.333828174,55.799925863],[37.322377321,55.801096462],[37.314727661,55.799259294],[37.314255592,55.798171457],[37.307678816,55.797186334],[37.309116480,55.797355560],[37.309309599,55.796074263],[37.311830875,55.796914363],[37.312656996,55.795705580],[37.319909689,55.798050585],[37.323065475,55.797101721],[37.314300016,55.793668695],[37.309836821,55.793124702],[37.314493135,55.788059153],[37.312958912,55.787829435],[37.310856060,55.784386519],[37.310732678,55.779976065],[37.312430516,55.779451625],[37.313098386,55.780136579],[37.312851624,55.786354369],[37.328328305,55.781003856],[37.323006970,55.776687406],[37.315298301,55.774661617],[37.310835105,55.775453804],[37.308924031,55.770495661]]]]}},{"id":2738793,"categoryId":"hydro-reservoir","title":"Москва","hotspots":[{"id":2738793,"offsets":[[121,-118],[124,-104],[149,-95],[158,-86],[171,-66],[170,-57],[165,-57],[170,-43],[184,-27],[227,-5],[226,1],[213,11],[231,10],[235,-3],[246,-9],[266,6],[269,18],[265,67],[255,84],[240,97],[221,104],[205,97],[187,75],[168,69],[138,142],[143,156],[158,165],[235,172],[276,157],[290,172],[317,218],[318,235],[294,267],[250,287],[240,295],[245,299],[301,268],[321,243],[327,210],[340,206],[322,198],[304,170],[278,148],[232,166],[161,157],[150,149],[147,132],[167,83],[181,82],[198,106],[217,111],[246,101],[270,79],[274,7],[278,7],[274,-3],[261,-12],[217,-16],[193,-29],[183,-44],[182,-65],[163,-97],[147,-110],[121,-118]]}],"geometry":{"type":"polygon","data":[[[[37.395059653,55.829626434],[37.396111079,55.827053853],[37.404892631,55.825260192],[37.407764607,55.823423604],[37.412284799,55.819581227],[37.412011214,55.817991089],[37.410401889,55.817829504],[37.412193604,55.815227445],[37.416766771,55.812160022],[37.431467958,55.807883953],[37.431398221,55.806726872],[37.426838466,55.804666395],[37.432986089,55.804998738],[37.434284278,55.807457982],[37.438135930,55.808569726],[37.441976854,55.807741963],[37.444927283,55.805717795],[37.445946523,55.803439725],[37.444616147,55.793909050],[37.441343852,55.790596638],[37.436022350,55.788087962],[37.429585048,55.786703587],[37.423963138,55.788190731],[37.417954990,55.792361681],[37.411217281,55.793624968],[37.401121446,55.779496786],[37.402795144,55.776787842],[37.407816240,55.774982784],[37.434514948,55.773558632],[37.448333689,55.776582245],[37.453327962,55.773558632],[37.462490388,55.764704139],[37.462898084,55.761362389],[37.454529592,55.755227852],[37.439659425,55.751355455],[37.436011621,55.749745876],[37.437685319,55.749116550],[37.457179614,55.755076593],[37.464024612,55.759777424],[37.465998717,55.766310451],[37.470290252,55.767114906],[37.464088985,55.768524172],[37.458155938,55.773900690],[37.449073979,55.778287837],[37.433495709,55.774775571],[37.409028598,55.776603315],[37.405112573,55.778024332],[37.404189893,55.781295493],[37.411185094,55.790823129],[37.415895053,55.791098165],[37.421726175,55.786458558],[37.428292223,55.785382453],[37.438007184,55.787292821],[37.446461507,55.791551517],[37.447925993,55.805451737],[37.449020334,55.805515184],[37.447625586,55.807475920],[37.443269678,55.809143524],[37.428335138,55.809898756],[37.420020291,55.812563098],[37.416608521,55.815432648],[37.416190096,55.819443621],[37.409774252,55.825586130],[37.404324003,55.828092380],[37.395059653,55.829626434]]]]}},{"id":3831974,"categoryId":"other","title":"Олимпийский спортивный центр «Крылатское»","hotspots":[{"id":3831974,"offsets":[[227,289],[241,294],[289,269],[314,241],[307,220],[276,173],[241,189],[228,185],[225,192],[220,205],[232,267],[227,289]]}],"geometry":{"type":"polygon","data":[[[[37.431497963,55.750873444],[37.436304482,55.749941579],[37.452741059,55.754794291],[37.461324128,55.760275553],[37.458985241,55.764304332],[37.448535355,55.773473386],[37.436433228,55.770255996],[37.432077321,55.770993843],[37.430768403,55.769760060],[37.429116162,55.767304472],[37.433472069,55.755157313],[37.431497963,55.750873444]]]]}},{"id":20525,"categoryId":"vegetation-natpark","title":"Серебряный бор","hotspots":[{"id":20525,"offsets":[[265,86],[237,106],[216,111],[196,104],[181,82],[169,82],[147,132],[150,149],[161,157],[232,166],[256,157],[271,147],[266,129],[265,86]]}],"geometry":{"type":"polygon","data":[[[[37.444806246,55.790273320],[37.435099332,55.786402907],[37.427961974,55.785364585],[37.420907764,55.786821553],[37.415955736,55.791099189],[37.411619274,55.790925025],[37.404256610,55.781259391],[37.405165879,55.778083462],[37.409009485,55.776576279],[37.433283476,55.774764879],[37.441716342,55.776543020],[37.446769623,55.778396380],[37.444913535,55.781960751],[37.444806246,55.790273320]]]]}},{"id":715720,"categoryId":"adm-region-city","title":"Рублёво","hotspots":[{"id":715720,"offsets":[[44,89],[45,93],[38,95],[32,89],[25,93],[6,70],[1,51],[-12,55],[-15,58],[-8,90],[-15,114],[-56,162],[-73,191],[-100,195],[-96,206],[-62,200],[-61,180],[-39,188],[-45,195],[-26,198],[-19,193],[-15,187],[-24,182],[-25,174],[-16,164],[-8,175],[-3,170],[-4,149],[19,123],[20,115],[47,98],[44,89]]}],"geometry":{"type":"polygon","data":[[[[37.368691192,55.789664121],[37.369184718,55.788962900],[37.366824374,55.788576015],[37.364549861,55.789748750],[37.362296806,55.788854089],[37.355816588,55.793416815],[37.354089246,55.797054409],[37.349561677,55.796308465],[37.348370776,55.795689431],[37.351031527,55.789404187],[37.348649726,55.784743218],[37.334466204,55.775546590],[37.328794171,55.769972795],[37.319357489,55.769175015],[37.320894394,55.767046766],[37.332327143,55.768145588],[37.332717404,55.772002797],[37.340152488,55.770508986],[37.338232026,55.769208658],[37.344663963,55.768625008],[37.347061858,55.769505015],[37.348681912,55.770635992],[37.345355973,55.771694363],[37.344948277,55.773138237],[37.348166928,55.775224578],[37.351095900,55.773049036],[37.352522836,55.774065010],[37.352297530,55.778152825],[37.360204683,55.783032242],[37.360472903,55.784579983],[37.369699702,55.787874774],[37.370032296,55.788745277],[37.368691192,55.789664121]]]]}},{"id":716395,"categoryId":"poi-service-municipal","title":"Рублевская станция водоподготовки","hotspots":[{"id":716395,"offsets":[[-40,159],[-26,175],[5,138],[-2,129],[0,123],[-8,120],[-15,130],[-20,122],[-62,168],[-73,190],[-124,199],[-113,211],[-108,230],[-116,240],[-108,248],[-88,252],[-82,250],[-74,235],[-67,237],[-61,214],[-62,179],[-40,159]]}],"geometry":{"type":"polygon","data":[[[[37.340125665,55.776123994],[37.344626412,55.772943109],[37.355403528,55.780199500],[37.352943943,55.781930251],[37.353601084,55.783128883],[37.351047621,55.783621627],[37.348512933,55.781720146],[37.346675620,55.783195389],[37.332284227,55.774386181],[37.328720745,55.770074760],[37.311097458,55.768337999],[37.314838302,55.766113304],[37.316602525,55.762393602],[37.313791067,55.760477848],[37.316675447,55.758935479],[37.323341743,55.758150612],[37.325451803,55.758556801],[37.328119595,55.761334410],[37.330723014,55.760986948],[37.332696785,55.765556071],[37.332559992,55.772276833],[37.340125665,55.776123994]]]]}},{"id":66775,"categoryId":"hydro-reservoir","title":"Троице-Лыковская пойма (Строгинский затон)","hotspots":[{"id":66775,"offsets":[[175,48],[191,27],[213,19],[222,24],[233,14],[246,13],[254,24],[256,20],[264,25],[261,61],[257,63],[256,52],[235,73],[230,71],[218,79],[210,77],[214,83],[228,86],[251,74],[249,82],[235,90],[215,90],[206,79],[198,85],[187,75],[187,68],[207,72],[191,56],[175,48]]}],"geometry":{"type":"polygon","data":[[[[37.413733696,55.797566996],[37.419216130,55.801667443],[37.426962350,55.803187248],[37.430041526,55.802238509],[37.433839534,55.804250774],[37.438055967,55.804274945],[37.440802549,55.802141822],[37.441521381,55.803018048],[37.444225048,55.801960532],[37.443302368,55.795004373],[37.441811059,55.794720299],[37.441424821,55.796914268],[37.434461807,55.792786119],[37.432659362,55.793064163],[37.428453659,55.791565118],[37.425822412,55.792062287],[37.427105178,55.790792527],[37.431852688,55.790230169],[37.439870649,55.792542167],[37.439243515,55.791102321],[37.434331552,55.789438001],[37.427355965,55.789511958],[37.424478792,55.791643604],[37.421507408,55.790524667],[37.417945769,55.792368481],[37.417738234,55.793718466],[37.424880956,55.792931186],[37.419242953,55.796092300],[37.413733696,55.797566996]]]]}},{"id":3027,"categoryId":"vegetation-park","title":"Ландшафтный заказник «Крылатские холмы»","hotspots":[{"id":3027,"offsets":[[180,193],[204,194],[217,203],[220,211],[231,267],[225,293],[213,305],[186,278],[174,274],[175,267],[202,264],[193,256],[199,241],[181,229],[194,219],[189,221],[175,204],[180,193]]}],"geometry":{"type":"polygon","data":[[[[37.415453827,55.769522106],[37.423892057,55.769413240],[37.428116536,55.767674370],[37.429210877,55.766125954],[37.432974017,55.755191535],[37.431007957,55.750250375],[37.426804936,55.747936520],[37.417588866,55.753063275],[37.413532025,55.753857416],[37.413758671,55.755242963],[37.422974741,55.755887316],[37.419976032,55.757339340],[37.422153985,55.760279523],[37.415958083,55.762602474],[37.420315331,55.764528330],[37.418651020,55.764169177],[37.413608467,55.767435458],[37.415453827,55.769522106]]]]}},{"id":484039,"categoryId":"poi-med-clinic","title":"Центральная клиническая больница (ЦКБ) Управления делами Президента РФ","hotspots":[{"id":484039,"offsets":[[96,237],[116,255],[112,260],[115,262],[119,257],[149,283],[131,305],[126,302],[116,315],[112,311],[111,318],[103,322],[69,295],[66,288],[65,275],[78,273],[83,267],[81,254],[96,237]]}],"geometry":{"type":"polygon","data":[[[[37.386612036,55.760990824],[37.393670102,55.757628051],[37.392168064,55.756650973],[37.393122931,55.756133687],[37.394689341,55.757119852],[37.404706218,55.752091108],[37.398521044,55.747873373],[37.397097965,55.748361457],[37.393426021,55.745998319],[37.392133196,55.746624670],[37.391717453,55.745308129],[37.389102300,55.744509559],[37.377218940,55.749782562],[37.376423498,55.751150763],[37.376171370,55.753649726],[37.380554938,55.754093305],[37.382141967,55.755278712],[37.381666378,55.757700840],[37.386612036,55.760990824]]]]}},{"id":1978472,"categoryId":"vegetation","title":"Строгинский парк","hotspots":[{"id":1978472,"offsets":[[116,-52],[110,-53],[121,-112],[124,-104],[152,-91],[171,-64],[170,-57],[165,-58],[170,-43],[186,-25],[226,-5],[226,1],[218,5],[176,-19],[143,-59],[123,-66],[116,-52]]}],"geometry":{"type":"polygon","data":[[[[37.393350248,55.816877821],[37.391590719,55.817216099],[37.395109777,55.828474248],[37.396132705,55.827072254],[37.406004910,55.824440052],[37.412394267,55.819220412],[37.411918511,55.817970607],[37.410299127,55.818004397],[37.412077431,55.815186385],[37.417545282,55.811671700],[37.431396880,55.807774817],[37.431270984,55.806683160],[37.428536137,55.805893498],[37.414105181,55.810564764],[37.402855997,55.818230916],[37.395925169,55.819656448],[37.393350248,55.816877821]]]]}},{"id":12732798,"categoryId":"adm-district","title":"Серебряный бор","hotspots":[{"id":12732798,"offsets":[[246,104],[222,120],[195,108],[182,82],[177,95],[183,96],[177,103],[181,108],[178,116],[166,119],[169,111],[165,106],[156,116],[156,127],[159,135],[198,130],[196,119],[219,124],[221,133],[227,133],[226,128],[241,135],[252,122],[256,127],[265,117],[264,87],[246,104]]}],"geometry":{"type":"polygon","data":[[[[37.438177504,55.786724274],[37.429897525,55.783604717],[37.420493700,55.786053228],[37.416180708,55.790937696],[37.414592840,55.788471358],[37.416427471,55.788259778],[37.414592840,55.786941908],[37.415794470,55.786047182],[37.414914705,55.784426950],[37.410601713,55.783816323],[37.411717512,55.785460763],[37.410236933,55.786415957],[37.407329418,55.784529728],[37.407114841,55.782236336],[37.408273556,55.780730826],[37.421588041,55.781655906],[37.421105244,55.783798185],[37.428937294,55.782812696],[37.429709770,55.781222561],[37.431469299,55.781198376],[37.431372740,55.782056937],[37.436350920,55.780781181],[37.440123950,55.783242056],[37.441583071,55.782238415],[37.444630061,55.784173121],[37.444351111,55.789976663],[37.438177504,55.786724274]]]]}},{"id":428492,"categoryId":"vegetation-forest","title":"Парк Москворецкий","hotspots":[{"id":428492,"offsets":[[97,187],[85,202],[91,203],[89,210],[115,205],[119,220],[101,218],[95,230],[123,254],[129,254],[147,233],[141,208],[145,204],[137,206],[125,200],[122,185],[97,187]]}],"geometry":{"type":"polygon","data":[[[[37.386861647,55.770681715],[37.382919473,55.767858387],[37.384867427,55.767635731],[37.384404744,55.766330000],[37.393278163,55.767124640],[37.394397315,55.764320319],[37.388364692,55.764711227],[37.386341298,55.762310506],[37.395898680,55.757643634],[37.397988791,55.757721906],[37.404194082,55.761873447],[37.401951755,55.766623355],[37.403553034,55.767391513],[37.400589193,55.767004412],[37.396681214,55.768177799],[37.395627106,55.771095995],[37.386861647,55.770681715]]]]}},{"id":2645854,"categoryId":"vegetation-forest","title":"Рублевский лес","hotspots":[{"id":2645854,"offsets":[[38,178],[44,171],[44,99],[18,117],[20,123],[5,138],[1,136],[-2,147],[29,176],[38,178]]}],"geometry":{"type":"polygon","data":[[[[37.366691605,55.772459585],[37.368730083,55.773777947],[37.368912474,55.787760103],[37.359846607,55.784229517],[37.360696365,55.783080421],[37.355237399,55.780261570],[37.354120259,55.780652507],[37.352905218,55.778423132],[37.363730614,55.772747792],[37.366691605,55.772459585]]]]}},{"id":5673659,"categoryId":"poi-recreation-sanatorium","title":"оздоровительный пансионат РЖД","hotspots":[{"id":5673659,"offsets":[[136,183],[156,165],[140,149],[145,119],[133,113],[97,171],[107,185],[136,183]]}],"geometry":{"type":"polygon","data":[[[[37.400419878,55.771449981],[37.407329249,55.774993825],[37.401621508,55.778126167],[37.403509783,55.783783434],[37.399282621,55.784968406],[37.387094664,55.773775570],[37.390420603,55.771054132],[37.400419878,55.771449981]]]]}},{"id":17181367,"categoryId":"adm-district","title":"микрорайон Северное Крылатское","hotspots":[{"id":17181367,"offsets":[[144,222],[170,218],[180,229],[189,226],[194,219],[189,221],[174,198],[196,191],[214,198],[215,190],[197,181],[135,184],[144,222]]}],"geometry":{"type":"polygon","data":[[[[37.402974181,55.763945569],[37.411963605,55.764683535],[37.415596657,55.762638972],[37.418412306,55.763238591],[37.420344167,55.764562558],[37.418619506,55.764081670],[37.413491123,55.768534946],[37.415457182,55.769526845],[37.421049588,55.769875365],[37.427135520,55.768536458],[37.427596860,55.770139210],[37.421411686,55.771939958],[37.400030960,55.771221785],[37.402974181,55.763945569]]]]}},{"id":151117,"categoryId":"hydro-reservoir","title":"Большой Рублевский карьер (Живописная бухта)","hotspots":[{"id":151117,"offsets":[[14,65],[0,43],[15,29],[11,27],[13,19],[30,28],[39,-2],[42,24],[53,26],[51,33],[40,47],[14,65]]}],"geometry":{"type":"polygon","data":[[[[37.358373237,55.794242619],[37.353711557,55.798515659],[37.355718855,55.800389405],[37.358963825,55.801221480],[37.357598916,55.801603991],[37.358196211,55.803259573],[37.363962960,55.801534306],[37.367235255,55.807271807],[37.368072104,55.802256450],[37.371741366,55.801779051],[37.371253204,55.800580995],[37.367471290,55.797820642],[37.358373237,55.794242619]]]]}},{"id":711027,"categoryId":"relief","title":"Щукинский полуостров","hotspots":[{"id":711027,"offsets":[[206,79],[198,84],[211,101],[221,104],[245,94],[265,68],[267,35],[260,63],[255,52],[236,73],[210,78],[214,84],[229,86],[250,74],[252,79],[241,88],[217,90],[206,79]]}],"geometry":{"type":"polygon","data":[[[[37.424561270,55.791614419],[37.421557196,55.790538456],[37.426074036,55.787358753],[37.429571637,55.786687718],[37.437875756,55.788785420],[37.444527634,55.793817630],[37.445246466,55.800178739],[37.442923170,55.794720960],[37.441844419,55.794630015],[37.441178393,55.796754201],[37.434585859,55.792829185],[37.432492060,55.793005986],[37.429117841,55.791627831],[37.425863483,55.791878682],[37.427120601,55.790699588],[37.432216127,55.790227336],[37.439624724,55.792542734],[37.440057733,55.791657864],[37.436353099,55.789882495],[37.428102457,55.789420622],[37.424561270,55.791614419]]]]}},{"id":17181169,"categoryId":"adm-district","title":"5-й, 6-й и 7-й микрорайоны Крылатского","hotspots":[{"id":17181169,"offsets":[[160,253],[160,284],[197,315],[203,312],[207,302],[172,272],[175,265],[190,260],[193,250],[160,253]]}],"geometry":{"type":"polygon","data":[[[[37.408510261,55.757935463],[37.408714109,55.752003087],[37.421293669,55.745918511],[37.423450165,55.746550916],[37.424866371,55.748435965],[37.412839346,55.754341659],[37.413880043,55.755681811],[37.418960147,55.756667989],[37.420016937,55.758425501],[37.408510261,55.757935463]]]]}},{"id":60515,"categoryId":"vegetation-forest","title":"урочище Липовая роща","hotspots":[{"id":60515,"offsets":[[15,70],[37,80],[52,79],[60,76],[70,58],[53,40],[15,70]]}],"geometry":{"type":"polygon","data":[[[[37.359006238,55.793298761],[37.366248202,55.791479359],[37.371451688,55.791606297],[37.374155355,55.792259113],[37.377631497,55.795643912],[37.371966672,55.799215763],[37.359006238,55.793298761]]]]}},{"id":3624,"categoryId":"hydro-reservoir","title":"Гребной канал","hotspots":[{"id":3624,"offsets":[[264,179],[277,176],[254,277],[245,282],[238,280],[264,179]]},{"id":0,"offsets":[[269,180],[273,179],[245,275],[269,180]]}],"geometry":{"type":"polygon","data":[[[[37.444165366,55.772191017],[37.448800559,55.772792000],[37.440833727,55.753200927],[37.437781709,55.752370753],[37.435405271,55.752718670],[37.444165366,55.772191017]],[[37.446155902,55.772105703],[37.447450068,55.772262942],[37.438107264,55.753584777],[37.437898722,55.753764025],[37.446155902,55.772105703]]]]}},{"id":3083985,"categoryId":"town-town-cottage","title":"Мякинино","hotspots":[{"id":3083985,"offsets":[[57,19],[51,36],[53,26],[42,24],[37,-2],[29,28],[14,16],[26,7],[24,-5],[39,-2],[56,-10],[53,18],[57,19]]}],"geometry":{"type":"polygon","data":[[[[37.373285816,55.803145420],[37.371343896,55.799897252],[37.371756956,55.801773669],[37.368103788,55.802269197],[37.366527990,55.807286818],[37.363745366,55.801460656],[37.358611618,55.803696732],[37.362471316,55.805438520],[37.361939233,55.807881121],[37.367246822,55.807235837],[37.372835204,55.808816221],[37.371960804,55.803408283],[37.373285816,55.803145420]]]]}},{"id":17181074,"categoryId":"adm-district","title":"8-й и 9-й микрорайоны Крылатского","hotspots":[{"id":17181074,"offsets":[[154,278],[158,271],[154,222],[149,222],[148,234],[129,257],[154,278]]}],"geometry":{"type":"polygon","data":[[[[37.406471782,55.753056010],[37.407893353,55.754480913],[37.406466417,55.763912394],[37.404996567,55.763867027],[37.404497676,55.761677049],[37.397835069,55.757151899],[37.406471782,55.753056010]]]]}},{"id":17367145,"categoryId":"vegetation-park","title":"","hotspots":[{"id":17367145,"offsets":[[154,137],[154,142],[179,153],[205,146],[196,131],[154,137]]}],"geometry":{"type":"polygon","data":[[[[37.406426352,55.780474233],[37.406512183,55.779410055],[37.415202540,55.777354174],[37.418678683,55.777438830],[37.421747130,55.778890049],[37.424236220,55.778563529],[37.421060484,55.781623029],[37.406426352,55.780474233]]]]}},{"id":518089,"categoryId":"adm-private","title":"Мякинино","hotspots":[{"id":518089,"offsets":[[78,-21],[78,-11],[71,-8],[70,0],[74,2],[70,13],[53,18],[62,-27],[78,-21]]}],"geometry":{"type":"polygon","data":[[[[37.380425186,55.810858730],[37.380561978,55.809046193],[37.378043384,55.808441994],[37.377684638,55.806950341],[37.378956676,55.806516051],[37.377646417,55.804454053],[37.371981592,55.803420747],[37.374974937,55.812175786],[37.380425186,55.810858730]]]]}},{"id":6975458,"categoryId":"vegetation-forest","title":"","hotspots":[{"id":6975458,"offsets":[[-11,51],[-35,37],[-10,30],[-3,14],[10,16],[1,40],[-7,48],[-4,53],[-11,51]]}],"geometry":{"type":"polygon","data":[[[[37.350042295,55.796960636],[37.341652345,55.799680257],[37.350364160,55.801018845],[37.352644038,55.804118874],[37.357246709,55.803774438],[37.353985142,55.799112174],[37.351217103,55.797537816],[37.352354359,55.796679597],[37.350042295,55.796960636]]]]}},{"id":12545516,"categoryId":"vegetation-park","title":"","hotspots":[{"id":12545516,"offsets":[[243,172],[201,177],[197,174],[172,181],[151,173],[159,165],[243,172]]}],"geometry":{"type":"polygon","data":[[[[37.437046953,55.773603705],[37.422674168,55.772669362],[37.421335243,55.773312481],[37.412874047,55.771904144],[37.405579110,55.773499576],[37.408428621,55.774909461],[37.437046953,55.773603705]]]]}},{"id":2426275,"categoryId":"poi-med-clinic","title":"ФГУ РКНПК Росмедтехнологий","hotspots":[{"id":2426275,"offsets":[[54,264],[70,275],[78,273],[87,238],[83,235],[54,264]]}],"geometry":{"type":"polygon","data":[[[[37.372131963,55.755806881],[37.377678771,55.753734529],[37.380511184,55.754109660],[37.383590360,55.760764631],[37.382029314,55.761487534],[37.372131963,55.755806881]]]]}},{"id":17180593,"categoryId":"adm-district","title":"1-й, 2-й и 3-й микрорайоны Крылатского","hotspots":[{"id":17180593,"offsets":[[157,222],[173,220],[194,242],[159,245],[157,222]]}],"geometry":{"type":"polygon","data":[[[[37.407713310,55.764003129],[37.413095162,55.764360015],[37.420303264,55.760019690],[37.408281602,55.759491862],[37.407713310,55.764003129]]]]}},{"id":2827207,"categoryId":"hydro","title":"Дровяной Рублевский карьер","hotspots":[{"id":2827207,"offsets":[[-13,5],[-12,11],[-5,8],[-5,15],[14,16],[26,8],[26,-3],[14,-5],[-13,5]]},{"id":0,"offsets":[[5,2],[6,2],[10,1],[9,1],[5,2]]}],"geometry":{"type":"polygon","data":[[[[37.349154819,55.806001129],[37.349601407,55.804779033],[37.352101226,55.805277543],[37.351951023,55.804026722],[37.358506341,55.803714011],[37.362532337,55.805383287],[37.362564524,55.807452789],[37.358338703,55.807924076],[37.349154819,55.806001129]],[[37.355420292,55.806535595],[37.355758251,55.806511426],[37.357222737,55.806725928],[37.356734575,55.806762180],[37.355420292,55.806535595]]]]}},{"id":14048675,"categoryId":"vegetation-park","title":"","hotspots":[{"id":14048675,"offsets":[[244,9],[266,8],[257,-4],[246,-9],[235,-3],[231,10],[219,14],[213,14],[218,5],[208,1],[210,15],[215,15],[244,9]]}],"geometry":{"type":"polygon","data":[[[[37.437456326,55.805146119],[37.445056365,55.805392729],[37.442013399,55.807706560],[37.438136601,55.808550751],[37.434354686,55.807455527],[37.432985586,55.804957006],[37.429036871,55.804207720],[37.426706535,55.804179395],[37.428521887,55.805895953],[37.425248418,55.806672396],[37.425627951,55.803918897],[37.427660563,55.803924279],[37.437456326,55.805146119]]]]}},{"id":17367301,"categoryId":"vegetation-park","title":"","hotspots":[{"id":17367301,"offsets":[[213,152],[212,163],[227,164],[240,158],[233,157],[239,148],[236,143],[246,151],[252,145],[237,137],[221,142],[223,150],[226,143],[230,148],[224,155],[213,152]]}],"geometry":{"type":"polygon","data":[[[[37.426775434,55.777535580],[37.426432111,55.775407033],[37.431517579,55.775165145],[37.436002233,55.776241534],[37.433641889,55.776422945],[37.435701825,55.778237007],[37.434607484,55.779156099],[37.438083627,55.777668610],[37.440036275,55.778926328],[37.434950807,55.780365398],[37.429414727,55.779325404],[37.430187203,55.777874201],[37.431281545,55.779131913],[37.432697751,55.778333755],[37.430723645,55.776930891],[37.426775434,55.777535580]]]]}},{"id":54688,"categoryId":"poi-shop-market-construction","title":"Синдика-О","hotspots":[{"id":54688,"offsets":[[79,-13],[71,-8],[70,-1],[73,6],[83,8],[90,20],[97,-1],[95,-9],[91,1],[91,-11],[79,-13]]}],"geometry":{"type":"polygon","data":[[[[37.380783764,55.809366980],[37.378123012,55.808400267],[37.377785054,55.806992448],[37.378675547,55.805675215],[37.382242885,55.805318708],[37.384689060,55.803046657],[37.387135235,55.806998490],[37.386258152,55.808637793],[37.384957280,55.806626890],[37.384849992,55.808938004],[37.380783764,55.809366980]]]]}},{"id":3083892,"categoryId":"town-town","title":"ЖК «Резиденция Рублево»","hotspots":[{"id":3083892,"offsets":[[75,32],[89,20],[78,5],[61,16],[75,32]]}],"geometry":{"type":"polygon","data":[[[[37.379596718,55.800778627],[37.384403237,55.803056855],[37.380524763,55.805845536],[37.374543437,55.803715522],[37.379596718,55.800778627]]]]}},{"id":570019,"categoryId":"poi-culture","title":"усадьба \"Троице-Лыково\"","hotspots":[{"id":570019,"offsets":[[158,82],[148,83],[134,113],[145,117],[158,82]]}],"geometry":{"type":"polygon","data":[[[[37.408093008,55.790968472],[37.404482754,55.790868733],[37.399803473,55.784942145],[37.403435186,55.784233296],[37.408093008,55.790968472]]]]}},{"id":3253098,"categoryId":"tr-depot","title":"","hotspots":[{"id":3253098,"offsets":[[65,194],[96,193],[96,185],[75,178],[65,194]]}],"geometry":{"type":"polygon","data":[[[[37.376061732,55.769384417],[37.384204919,55.769118299],[37.386640365,55.769626341],[37.386739608,55.771020411],[37.379468140,55.772353941],[37.376061732,55.769384417]]]]}},{"id":362068,"categoryId":"vegetation","title":"Строгинская коса","hotspots":[{"id":362068,"offsets":[[181,54],[191,56],[208,73],[189,67],[187,75],[172,71],[181,54]]}],"geometry":{"type":"polygon","data":[[[[37.415906756,55.796391082],[37.419302433,55.796070754],[37.424951165,55.792734348],[37.418696254,55.793949268],[37.417972057,55.792442701],[37.412859767,55.793193726],[37.415906756,55.796391082]]]]}},{"id":12545120,"categoryId":"relief-island","title":"","hotspots":[{"id":12545120,"offsets":[[246,275],[249,273],[273,179],[269,180],[246,275]]}],"geometry":{"type":"polygon","data":[[[[37.438225784,55.753577876],[37.439275869,55.754012758],[37.447315958,55.772317466],[37.446166296,55.772095970],[37.438225784,55.753577876]]]]}},{"id":844146,"categoryId":"hydro-reservoir","title":"Залив Чистый","hotspots":[{"id":844146,"offsets":[[206,79],[229,86],[248,74],[252,77],[226,92],[214,89],[206,79]]}],"geometry":{"type":"polygon","data":[[[[37.424582560,55.791620464],[37.432200034,55.790230169],[37.438937743,55.792551328],[37.440160830,55.792019408],[37.436834891,55.790193900],[37.431384642,55.789166259],[37.427114566,55.789649859],[37.424582560,55.791620464]]]]}},{"id":17180412,"categoryId":"adm-district","title":"4-й микрорайон Крылатского","hotspots":[{"id":17180412,"offsets":[[159,245],[194,242],[193,250],[160,253],[159,245]]}],"geometry":{"type":"polygon","data":[[[[37.408260480,55.759457076],[37.420351878,55.759995492],[37.420416251,55.759696037],[37.420062200,55.758504243],[37.408496514,55.757974882],[37.408260480,55.759457076]]]]}},{"id":2373791,"categoryId":"poi-recreation-beach","title":"","hotspots":[{"id":2373791,"offsets":[[176,159],[175,153],[209,153],[206,163],[176,159]]}],"geometry":{"type":"polygon","data":[[[[37.414020524,55.776162733],[37.413875685,55.777260258],[37.425499708,55.777357008],[37.424367816,55.775355443],[37.414020524,55.776162733]]]]}},{"id":2843661,"categoryId":"poi-service-municipal","title":"Черепковские очистные сооружения","hotspots":[{"id":2843661,"offsets":[[118,220],[115,206],[92,210],[102,219],[118,220]]}],"geometry":{"type":"polygon","data":[[[[37.394254821,55.764349604],[37.393214124,55.767083609],[37.385392802,55.766212620],[37.388815301,55.764531071],[37.394254821,55.764349604]]]]}},{"id":524991,"categoryId":"poi-gov-police","title":"Центр специального назначения (ЦСН) СОР ГУ МВД РФ по г. Москве","hotspots":[{"id":524991,"offsets":[[88,50],[99,63],[108,55],[107,47],[99,44],[88,50]]}],"geometry":{"type":"polygon","data":[[[[37.384019513,55.797297955],[37.383880039,55.797092466],[37.383933683,55.796947415],[37.387602945,55.794654758],[37.390810867,55.796244342],[37.390499730,55.797832635],[37.387678047,55.798388650],[37.387141605,55.798418868],[37.384019513,55.797297955]]]]}},{"id":536467,"categoryId":"hydro-reservoir","title":"Озеро «Бездонное»","hotspots":[{"id":536467,"offsets":[[207,163],[209,152],[202,151],[210,148],[206,145],[197,134],[204,138],[197,125],[205,135],[215,132],[209,138],[221,144],[221,154],[214,152],[213,164],[207,163]]},{"id":0,"offsets":[[207,143],[209,141],[214,146],[208,146],[207,143]]}],"geometry":{"type":"polygon","data":[[[[37.424594127,55.775364230],[37.425473890,55.777444390],[37.423034422,55.777593308],[37.425838672,55.778339329],[37.424250802,55.778738395],[37.421482763,55.780963501],[37.423816287,55.780262137],[37.421289644,55.781979268],[37.421219908,55.782605053],[37.424148880,55.780718639],[37.427405080,55.781302093],[37.425404154,55.780278765],[37.429593765,55.778962136],[37.429507932,55.777178327],[37.427018842,55.777516952],[37.426678204,55.775210026],[37.424594127,55.775364230]],[[37.424895876,55.779314350],[37.425512784,55.779510862],[37.427165024,55.778591778],[37.425137274,55.778697595],[37.424895876,55.779314350]]]]}},{"id":17367318,"categoryId":"vegetation-park","title":"","hotspots":[{"id":17367318,"offsets":[[241,135],[227,128],[227,133],[219,130],[209,138],[214,142],[241,135]]}],"geometry":{"type":"polygon","data":[[[[37.436324098,55.780752366],[37.431517579,55.782034169],[37.431496121,55.781199793],[37.428985574,55.781659307],[37.425595262,55.780280749],[37.427268960,55.779410055],[37.436324098,55.780752366]]]]}},{"id":2645809,"categoryId":"vegetation","title":"","hotspots":[{"id":2645809,"offsets":[[15,76],[41,87],[45,93],[32,89],[25,92],[15,76]]}],"geometry":{"type":"polygon","data":[[[[37.358694431,55.792264685],[37.367717382,55.790112768],[37.369090673,55.788985380],[37.367127296,55.788655923],[37.364563105,55.789707758],[37.362218854,55.789012583],[37.358694431,55.792264685]]]]}},{"id":17367079,"categoryId":"vegetation-park","title":"","hotspots":[{"id":17367079,"offsets":[[173,112],[178,115],[168,121],[169,111],[162,107],[175,102],[169,102],[178,94],[179,99],[182,96],[179,108],[173,112]]}],"geometry":{"type":"polygon","data":[[[[37.413164061,55.785153852],[37.414709013,55.784561374],[37.411533278,55.783557767],[37.410567683,55.784367909],[37.411769312,55.785480316],[37.409280222,55.786121145],[37.413657587,55.787173049],[37.411640566,55.787100504],[37.414773386,55.788684354],[37.415202540,55.787789668],[37.416253966,55.788357917],[37.415073794,55.785915597],[37.413164061,55.785153852]]]]}},{"id":5834451,"categoryId":"adm-garden","title":"СНТ «Дружба»","hotspots":[{"id":5834451,"offsets":[[37,188],[23,192],[16,184],[22,175],[37,188]]}],"geometry":{"type":"polygon","data":[[[[37.366330680,55.770590538],[37.361685094,55.769665195],[37.359174547,55.771267900],[37.361320314,55.772973347],[37.366330680,55.770590538]]]]}},{"id":52168,"categoryId":"tr-depot","title":"Краснопресненское трамвайное депо","hotspots":[{"id":52168,"offsets":[[82,62],[91,55],[98,63],[98,71],[86,69],[82,62]]}],"geometry":{"type":"polygon","data":[[[[37.382006013,55.794851665],[37.384854519,55.796302232],[37.387477719,55.794715671],[37.387295329,55.793116958],[37.383069509,55.793567263],[37.382006013,55.794851665]]]]}},{"id":17789942,"categoryId":"vegetation-lawn","title":"","hotspots":[{"id":17789942,"offsets":[[243,247],[262,182],[260,180],[249,220],[243,247]]}],"geometry":{"type":"polygon","data":[[[[37.437120881,55.759015827],[37.443627920,55.771693985],[37.442978826,55.772153612],[37.439191547,55.764296880],[37.437120881,55.759015827]]]]}},{"id":3334238,"categoryId":"adm-private","title":"Екатериновка","hotspots":[{"id":3334238,"offsets":[[83,217],[93,212],[101,218],[100,226],[96,228],[83,217]]}],"geometry":{"type":"polygon","data":[[[[37.382040879,55.764871412],[37.385549208,55.765827114],[37.388317248,55.764762534],[37.388145587,55.763250296],[37.386643550,55.762681680],[37.382040879,55.764871412]]]]}},{"id":4796848,"categoryId":"vegetation","title":"","hotspots":[{"id":4796848,"offsets":[[210,178],[232,177],[217,193],[208,182],[210,178]]}],"geometry":{"type":"polygon","data":[[[[37.425633313,55.772360068],[37.433465364,55.772604997],[37.428353073,55.769475233],[37.424952032,55.771722036],[37.425633313,55.772360068]]]]}},{"id":2039911,"categoryId":"poi-recreation-beach","title":"пляж № 3 Серебряного Бора","hotspots":[{"id":2039911,"offsets":[[170,158],[155,149],[147,132],[151,149],[170,158]]}],"geometry":{"type":"polygon","data":[[[[37.412046919,55.776360949],[37.406963965,55.777985958],[37.404267507,55.781265532],[37.405369392,55.777988792],[37.412046919,55.776360949]]]]}},{"id":271910,"categoryId":"other","title":"Бизнес-парк «Крылатские Холмы»","hotspots":[{"id":271910,"offsets":[[209,187],[203,182],[189,185],[190,192],[205,193],[209,187]]}],"geometry":{"type":"polygon","data":[[[[37.425297031,55.770759865],[37.423274646,55.771698790],[37.418570051,55.771053187],[37.419023345,55.769745317],[37.424073944,55.769580507],[37.425297031,55.770759865]]]]}},{"id":312978,"categoryId":"hydro-reservoir","title":"Большой Крылатский пруд","hotspots":[{"id":312978,"offsets":[[245,227],[241,240],[231,238],[236,223],[245,227]]}],"geometry":{"type":"polygon","data":[[[[37.437645754,55.762964005],[37.436551413,55.760483789],[37.432839236,55.760786263],[37.434877714,55.763798773],[37.437645754,55.762964005]]]]}},{"id":107752,"categoryId":"vegetation-park","title":"парк «Ветеран»","hotspots":[{"id":107752,"offsets":[[163,97],[168,101],[178,92],[168,84],[163,97]]}],"geometry":{"type":"polygon","data":[[[[37.409586830,55.788211402],[37.411464377,55.787381125],[37.414919062,55.788989144],[37.413620873,55.789672324],[37.412569447,55.790258208],[37.411496563,55.790695796],[37.410144730,55.789598651],[37.409586830,55.788211402]]]]}},{"id":2783030,"categoryId":"other","title":"\"Карусель\" - стационарная буксировка для вейкборда","hotspots":[{"id":2783030,"offsets":[[218,13],[214,8],[231,2],[231,9],[218,13]]}],"geometry":{"type":"polygon","data":[[[[37.428680945,55.804288620],[37.427157450,55.805267522],[37.432929564,55.806524345],[37.433079767,55.805086245],[37.428680945,55.804288620]]]]}},{"id":725317,"categoryId":"poi-edu-university","title":"Институт лесоведения РАН","hotspots":[{"id":725317,"offsets":[[122,185],[123,198],[113,196],[109,186],[122,185]]}],"geometry":{"type":"polygon","data":[[[[37.395612521,55.771097885],[37.395950480,55.768609116],[37.392377777,55.768962937],[37.391224427,55.770946688],[37.395612521,55.771097885]]]]}},{"id":883922,"categoryId":"adm-plant","title":"ПГУ ТЭС «Строгино»","hotspots":[{"id":883922,"offsets":[[85,73],[97,78],[97,89],[83,81],[85,73]]}],"geometry":{"type":"polygon","data":[[[[37.382872029,55.792811604],[37.387040181,55.791781010],[37.386991902,55.789749964],[37.382051776,55.791241523],[37.382872029,55.792811604]]]]}},{"id":2706453,"categoryId":"poi-auto-garage","title":"","hotspots":[{"id":2706453,"offsets":[[105,-8],[96,14],[102,17],[108,-6],[105,-8]]}],"geometry":{"type":"polygon","data":[[[[37.389829681,55.808340696],[37.386643217,55.804080823],[37.388831899,55.803579276],[37.390618251,55.808089951],[37.389829681,55.808340696]]]]}},{"id":17565615,"categoryId":"other","title":"","hotspots":[{"id":17565615,"offsets":[[77,194],[77,201],[89,210],[91,203],[85,202],[90,195],[77,194]]}],"geometry":{"type":"polygon","data":[[[[37.380206585,55.769318941],[37.380228043,55.768085105],[37.384326458,55.766331054],[37.384798527,55.767619382],[37.382824421,55.767849220],[37.384508849,55.769173786],[37.380206585,55.769318941]]]]}},{"id":518138,"categoryId":"hydro-reservoir","title":"Черный пруд","hotspots":[{"id":518138,"offsets":[[56,39],[65,48],[70,43],[62,31],[57,35],[62,44],[56,39]]}],"geometry":{"type":"polygon","data":[[[[37.372839899,55.799340126],[37.372872085,55.798850604],[37.376165838,55.797575401],[37.377732248,55.798566558],[37.375039310,55.800802611],[37.373201996,55.800030583],[37.375074178,55.798380717],[37.372839899,55.799340126]]]]}},{"id":2038673,"categoryId":"adm-district","title":"Янтарный город","hotspots":[{"id":2038673,"offsets":[[169,17],[172,9],[179,11],[173,27],[168,25],[169,17]]}],"geometry":{"type":"polygon","data":[[[[37.411631849,55.803646785],[37.412886757,55.805139968],[37.415075470,55.804729087],[37.415011066,55.804173149],[37.414002556,55.802795391],[37.413215358,55.801695581],[37.411311325,55.802113779],[37.412203632,55.803471813],[37.411631849,55.803646785]]]]}},{"id":132017,"categoryId":"other","title":"Живописный мост","hotspots":[{"id":132017,"offsets":[[245,173],[275,149],[295,144],[272,149],[245,173]]}],"geometry":{"type":"polygon","data":[[[[37.437758574,55.773387399],[37.448152134,55.778099618],[37.454806024,55.778958436],[37.446965928,55.778146859],[37.437758574,55.773387399]]]]}},{"id":17367412,"categoryId":"vegetation-park","title":"","hotspots":[{"id":17367412,"offsets":[[205,158],[200,154],[194,158],[195,153],[179,156],[208,162],[205,158]]}],"geometry":{"type":"polygon","data":[[[[37.424221971,55.776302004],[37.422398068,55.777160674],[37.420273759,55.776289910],[37.420595624,55.777257424],[37.415231206,55.776664825],[37.425080277,55.775624731],[37.424221971,55.776302004]]]]}},{"id":17367434,"categoryId":"vegetation-park","title":"","hotspots":[{"id":17367434,"offsets":[[221,112],[223,119],[246,104],[245,102],[221,112]]}],"geometry":{"type":"polygon","data":[[[[37.429629304,55.785238491],[37.430101373,55.783775419],[37.438105084,55.786798236],[37.437675931,55.787064232],[37.429629304,55.785238491]]]]}},{"id":17367083,"categoryId":"vegetation-park","title":"","hotspots":[{"id":17367083,"offsets":[[162,110],[164,121],[167,120],[160,124],[155,119],[162,110]]}],"geometry":{"type":"polygon","data":[[[[37.409344595,55.785540772],[37.409859579,55.783424757],[37.411125582,55.783606134],[37.408786696,55.782844344],[37.406941336,55.783799602],[37.409344595,55.785540772]]]]}},{"id":14557722,"categoryId":"vegetation-forest","title":"","hotspots":[{"id":14557722,"offsets":[[224,185],[236,184],[240,176],[228,178],[224,185]]}],"geometry":{"type":"polygon","data":[[[[37.430526000,55.771067660],[37.433637362,55.771031373],[37.434377652,55.771212808],[37.434710246,55.771370051],[37.435471993,55.772059495],[37.436072808,55.772881972],[37.432028037,55.772446545],[37.430526000,55.771067660]]]]}},{"id":723680,"categoryId":"urban-house-territory","title":"ЖК «Долина грёз»","hotspots":[{"id":723680,"offsets":[[122,186],[130,185],[133,192],[123,198],[122,186]]}],"geometry":{"type":"polygon","data":[[[[37.395676894,55.770970785],[37.398316188,55.771040336],[37.399480267,55.769782362],[37.395961209,55.768602975],[37.395676894,55.770970785]]]]}},{"id":3724330,"categoryId":"vegetation-park","title":"","hotspots":[{"id":3724330,"offsets":[[130,185],[135,185],[140,204],[137,205],[130,185]]}],"geometry":{"type":"polygon","data":[[[[37.398338316,55.771039485],[37.399947642,55.771175562],[37.401626705,55.767492257],[37.400596736,55.767126326],[37.398338316,55.771039485]]]]}},{"id":17367425,"categoryId":"vegetation-park","title":"","hotspots":[{"id":17367425,"offsets":[[196,104],[194,106],[222,120],[221,112],[196,104]]}],"geometry":{"type":"polygon","data":[[[[37.420874574,55.786737782],[37.420166471,55.786338783],[37.429908254,55.783690776],[37.429607846,55.785238491],[37.420874574,55.786737782]]]]}},{"id":15759530,"categoryId":"vegetation-lawn","title":"","hotspots":[{"id":15759530,"offsets":[[110,34],[129,40],[150,38],[158,24],[149,36],[129,39],[110,34]]}],"geometry":{"type":"polygon","data":[[[[37.391420231,55.800398281],[37.397866417,55.799072608],[37.405285240,55.799610286],[37.406686694,55.800271655],[37.408006508,55.802280905],[37.404723652,55.799820577],[37.397888881,55.799369777],[37.391420231,55.800398281]]]]}},{"id":15847799,"categoryId":"vegetation-lawn","title":"","hotspots":[{"id":15847799,"offsets":[[131,-26],[140,-22],[162,5],[160,20],[163,4],[141,-23],[131,-26]]}],"geometry":{"type":"polygon","data":[[[[37.398658843,55.811888156],[37.401797027,55.811120880],[37.409253568,55.805850729],[37.408499868,55.802929104],[37.409621031,55.806068256],[37.401974053,55.811339887],[37.398658843,55.811888156]]]]}},{"id":17367348,"categoryId":"vegetation-park","title":"","hotspots":[{"id":17367348,"offsets":[[214,125],[216,130],[206,135],[196,120],[205,129],[214,125]]}],"geometry":{"type":"polygon","data":[[[[37.427118756,55.782723423],[37.427719571,55.781731861],[37.424350717,55.780703995],[37.420960404,55.783594042],[37.424200513,55.781852785],[37.427118756,55.782723423]]]]}},{"id":15017388,"categoryId":"vegetation-lawn","title":"","hotspots":[{"id":15017388,"offsets":[[168,180],[149,182],[144,178],[150,169],[148,174],[154,180],[168,180]]}],"geometry":{"type":"polygon","data":[[[[37.411342338,55.772037288],[37.411752716,55.771869464],[37.404829935,55.771766653],[37.402979210,55.772418291],[37.405189351,55.774234052],[37.404660955,55.773219595],[37.406412438,55.772013097],[37.411342338,55.772037288]]]]}},{"id":3125282,"categoryId":"adm-private","title":"коттеджный поселок «Троице-Лыково»","hotspots":[{"id":3125282,"offsets":[[111,119],[107,131],[102,130],[102,119],[111,119]]}],"geometry":{"type":"polygon","data":[[[[37.391681411,55.783923070],[37.390589752,55.781619534],[37.388564684,55.781792606],[37.388637104,55.783927604],[37.391681411,55.783923070]]]]}},{"id":2499516,"categoryId":"poi-sport-stadium","title":"Спорткомплекс «Рублёво»","hotspots":[{"id":2499516,"offsets":[[3,92],[3,97],[-5,98],[-5,85],[3,92]]}],"geometry":{"type":"polygon","data":[[[[37.354756278,55.789170982],[37.354831380,55.788173540],[37.351859492,55.787871279],[37.351875585,55.790479708],[37.354756278,55.789170982]]]]}},{"id":17367167,"categoryId":"vegetation-park","title":"","hotspots":[{"id":17367167,"offsets":[[154,143],[159,144],[173,154],[160,151],[154,143]]}],"geometry":{"type":"polygon","data":[[[[37.406555098,55.779313310],[37.408293169,55.779071447],[37.413164061,55.777027642],[37.408486288,55.777704891],[37.406555098,55.779313310]]]]}},{"id":2036391,"categoryId":"vegetation-lawn","title":"Строгинский бульвар","hotspots":[{"id":2036391,"offsets":[[123,8],[118,8],[157,22],[157,20],[123,8]]}],"geometry":{"type":"polygon","data":[[[[37.395984848,55.805410384],[37.394587417,55.805732146],[37.394363452,55.805380927],[37.407303770,55.802612607],[37.407405693,55.802632247],[37.407491524,55.802687011],[37.407582719,55.802814101],[37.407588084,55.802886804],[37.407523711,55.802944967],[37.395984848,55.805410384]]]]}},{"id":7294162,"categoryId":"adm-plant","title":"Автобаза \"УМИТАТ\"","hotspots":[{"id":7294162,"offsets":[[83,81],[94,86],[96,89],[80,88],[83,81]]}],"geometry":{"type":"polygon","data":[[[[37.382058315,55.791241066],[37.385839560,55.790242543],[37.386632823,55.789609716],[37.381170504,55.789797865],[37.382058315,55.791241066]]]]}},{"id":12320013,"categoryId":"urban-construction-area","title":"","hotspots":[{"id":12320013,"offsets":[[62,82],[66,86],[73,73],[68,72],[62,82]]}],"geometry":{"type":"polygon","data":[[[[37.375080213,55.791011272],[37.376281843,55.790267761],[37.378706560,55.792854874],[37.377043590,55.792885096],[37.375080213,55.791011272]]]]}},{"id":19780704,"categoryId":"vegetation-park","title":"","hotspots":[{"id":19780704,"offsets":[[100,32],[90,36],[95,43],[102,35],[100,32]]}],"geometry":{"type":"polygon","data":[[[[37.388116755,55.800672588],[37.384694256,55.799883938],[37.386180200,55.798651073],[37.388787307,55.800113585],[37.388116755,55.800672588]]]]}},{"id":1622088,"categoryId":"poi-sport-stadium","title":"стадион технических видов спорта","hotspots":[{"id":1622088,"offsets":[[233,241],[231,247],[242,248],[242,244],[233,241]]}],"geometry":{"type":"polygon","data":[[[[37.433553711,55.760156089],[37.432867065,55.759166219],[37.436863557,55.758884151],[37.436712012,55.759690271],[37.433553711,55.760156089]]]]}},{"id":3724491,"categoryId":"vegetation-lawn","title":"","hotspots":[{"id":3724491,"offsets":[[133,255],[129,257],[138,265],[143,260],[133,255]]}],"geometry":{"type":"polygon","data":[[[[37.399384380,55.757578234],[37.398048640,55.757124107],[37.401184140,55.755581683],[37.402796148,55.756635929],[37.399384380,55.757578234]]]]}},{"id":2600288,"categoryId":"adm-plant","title":"РТС «Крылатское»","hotspots":[{"id":2600288,"offsets":[[149,188],[150,196],[140,196],[141,189],[149,188]]}],"geometry":{"type":"polygon","data":[[[[37.404945435,55.770460966],[37.405197563,55.769000360],[37.401839437,55.768970119],[37.402086200,55.770276503],[37.404945435,55.770460966]]]]}},{"id":2039980,"categoryId":"poi-recreation-beach","title":"пляж № 2 Серебряного Бора","hotspots":[{"id":2039980,"offsets":[[170,82],[175,88],[180,89],[181,82],[170,82]]}],"geometry":{"type":"polygon","data":[[[[37.412102072,55.790978106],[37.413754313,55.789793317],[37.415564804,55.789599879],[37.415943666,55.791074444],[37.412102072,55.790978106]]]]}},{"id":102562,"categoryId":"poi-shop-trade","title":"Европарк","hotspots":[{"id":102562,"offsets":[[77,203],[73,205],[79,211],[90,211],[77,203]]}],"geometry":{"type":"polygon","data":[[[[37.380082028,55.767694594],[37.378617542,55.767168382],[37.380811589,55.766153740],[37.380926924,55.766121229],[37.381052988,55.766112912],[37.384711521,55.766034280],[37.384754436,55.766134082],[37.383102196,55.766454658],[37.380556779,55.767694594],[37.380082028,55.767694594]]]]}},{"id":10054890,"categoryId":"urban-construction-area","title":"гостиничный комплекс (строящийся)","hotspots":[{"id":10054890,"offsets":[[109,25],[112,17],[104,15],[102,22],[109,25]]}],"geometry":{"type":"polygon","data":[[[[37.391116303,55.802062507],[37.392097991,55.803500719],[37.389370352,55.804059768],[37.388550938,55.802620350],[37.391116303,55.802062507]]]]}},{"id":2645810,"categoryId":"poi-auto-garage","title":"","hotspots":[{"id":2645810,"offsets":[[38,102],[36,97],[45,93],[44,89],[47,98],[38,102]]}],"geometry":{"type":"polygon","data":[[[[37.366751787,55.787144616],[37.366054413,55.788178357],[37.369224784,55.788927952],[37.368688342,55.789586859],[37.369986531,55.788740554],[37.369686124,55.787882142],[37.366751787,55.787144616]]]]}},{"id":22157770,"categoryId":"vegetation-lawn","title":"","hotspots":[{"id":22157770,"offsets":[[54,263],[56,261],[69,246],[62,247],[54,263]]}],"geometry":{"type":"polygon","data":[[[[37.372337655,55.756072901],[37.373038382,55.756290329],[37.377348692,55.759236641],[37.374913246,55.759089936],[37.372337655,55.756072901]]]]}},{"id":14559238,"categoryId":"poi-auto-garage","title":"","hotspots":[{"id":14559238,"offsets":[[134,201],[134,205],[124,197],[132,193],[134,201]]}],"geometry":{"type":"polygon","data":[[[[37.399567608,55.767925297],[37.399861310,55.767168869],[37.396383826,55.768778103],[37.399033849,55.769485737],[37.399567608,55.767925297]]]]}},{"id":290916,"categoryId":"poi-sport-sport","title":"СК «Крылатское»","hotspots":[{"id":290916,"offsets":[[234,210],[234,203],[243,211],[238,213],[234,210]]}],"geometry":{"type":"polygon","data":[[[[37.434105741,55.766164798],[37.434084283,55.767643656],[37.437018620,55.765962169],[37.435414659,55.765714173],[37.434105741,55.766164798]]]]}},{"id":17367105,"categoryId":"vegetation-park","title":"","hotspots":[{"id":17367105,"offsets":[[154,126],[149,132],[159,135],[154,126]]}],"geometry":{"type":"polygon","data":[[[[37.406404894,55.782469489],[37.404774111,55.781417458],[37.405932825,55.780522605],[37.408121508,55.780728181],[37.406404894,55.782469489]]]]}},{"id":19376092,"categoryId":"poi-sport","title":"Норд Вест","hotspots":[{"id":19376092,"offsets":[[218,19],[223,24],[228,18],[227,15],[218,19]]}],"geometry":{"type":"polygon","data":[[[[37.428622470,55.803186115],[37.430285439,55.802219248],[37.432077155,55.803376464],[37.431733832,55.803932399],[37.428622470,55.803186115]]]]}},{"id":2711729,"categoryId":"poi-auto-parking","title":"","hotspots":[{"id":2711729,"offsets":[[228,207],[232,197],[227,195],[223,205],[228,207]]}],"geometry":{"type":"polygon","data":[[[[37.431931811,55.766907352],[37.433256822,55.768857944],[37.431491928,55.769066606],[37.430081087,55.767155340],[37.431931811,55.766907352]]]]}},{"id":313304,"categoryId":"hydro-reservoir","title":"Малый Крылатский пруд","hotspots":[{"id":313304,"offsets":[[235,216],[244,214],[247,218],[245,225],[235,216]]}],"geometry":{"type":"polygon","data":[[[[37.434500194,55.765034824],[37.437291703,55.765425915],[37.438514790,55.764724256],[37.437913975,55.763308802],[37.437946162,55.764198004],[37.435221037,55.764137514],[37.434500194,55.765034824]]]]}},{"id":10481090,"categoryId":"poi-auto-auto","title":"БалтАвтоТрейд-М","hotspots":[{"id":10481090,"offsets":[[67,196],[77,194],[76,201],[74,202],[67,196]]}],"geometry":{"type":"polygon","data":[[[[37.376672943,55.769046397],[37.380081527,55.769261295],[37.379826047,55.767944576],[37.379077375,55.767875587],[37.376672943,55.769046397]]]]}},{"id":15789772,"categoryId":"poi-auto-parking","title":"","hotspots":[{"id":15789772,"offsets":[[95,92],[103,92],[104,98],[94,100],[95,92]]},{"id":0,"offsets":[[96,98],[98,93],[102,93],[99,97],[96,98]]}],"geometry":{"type":"polygon","data":[[[[37.386260499,55.789103353],[37.388953437,55.789035346],[37.389503290,55.787850498],[37.385911812,55.787613222],[37.386260499,55.789103353]],[[37.386636008,55.787938154],[37.387178451,55.788964107],[37.388575523,55.788927986],[37.387834805,55.788068020],[37.386636008,55.787938154]]]]}},{"id":3192643,"categoryId":"urban-construction-area","title":"Московский государственный институт электроники и математики","hotspots":[{"id":3192643,"offsets":[[164,15],[170,18],[167,25],[161,22],[164,15]]}],"geometry":{"type":"polygon","data":[[[[37.409860077,55.803995705],[37.412146799,55.803483066],[37.411187446,55.802125347],[37.408900724,55.802638003],[37.409860077,55.803995705]]]]}},{"id":481888,"categoryId":"other","title":"Отстойники ливневой канализации","hotspots":[{"id":481888,"offsets":[[169,56],[166,63],[172,66],[175,58],[169,56]]}],"geometry":{"type":"polygon","data":[[[[37.411838714,55.796141785],[37.410685365,55.794694234],[37.412745301,55.794203614],[37.413920109,55.795649200],[37.411838714,55.796141785]]]]}},{"id":15018428,"categoryId":"vegetation-lawn","title":"","hotspots":[{"id":15018428,"offsets":[[172,196],[172,193],[178,193],[182,187],[170,189],[172,196]]}],"geometry":{"type":"polygon","data":[[[[37.412821576,55.768880356],[37.412746475,55.769464002],[37.414956615,55.769485170],[37.416206524,55.770788513],[37.411973998,55.770256295],[37.412821576,55.768880356]]]]}},{"id":3274556,"categoryId":"poi-auto-parking","title":"","hotspots":[{"id":3274556,"offsets":[[71,205],[74,211],[86,213],[79,211],[71,205]]}],"geometry":{"type":"polygon","data":[[[[37.378012033,55.767239924],[37.378980311,55.765984851],[37.383290621,55.765635540],[37.380698769,55.766039289],[37.378012033,55.767239924]]]]}},{"id":13253686,"categoryId":"tr-busterminal","title":"конечная автобусная станция Рублёво","hotspots":[{"id":13253686,"offsets":[[15,111],[15,95],[-1,95],[-1,111],[15,111]]}],"geometry":{"type":"point","data":[[37.356037870,55.787044490]]}},{"id":13253686,"categoryId":"tr-busterminal","title":"конечная автобусная станция Рублёво","hotspots":[{"id":13253686,"offsets":[[15,111],[15,95],[-1,95],[-1,111],[15,111]]}],"geometry":{"type":"point","data":[[37.356037870,55.787044490]]}}] 4 | } -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "jspath", 3 | "version" : "0.4.0", 4 | "main" : "lib/jspath.js", 5 | "ignore" : ["**/.*", "node_modules", "benchmarks", "test", "Makefile", "package.json"] 6 | } 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/jspath'); -------------------------------------------------------------------------------- /jspath.min.js: -------------------------------------------------------------------------------- 1 | "use strict";!function(){var r={PATH:1,SELECTOR:2,OBJ_PRED:3,POS_PRED:4,LOGICAL_EXPR:5,COMPARISON_EXPR:6,MATH_EXPR:7,CONCAT_EXPR:8,UNARY_EXPR:9,POS_EXPR:10,LITERAL:11},n=function(){var n,t,e,u,o={ID:1,NUM:2,STR:3,BOOL:4,NULL:5,PUNCT:6,EOP:7},i={UNEXP_TOKEN:'Unexpected token "%0"',UNEXP_EOP:"Unexpected end of path"};function a(){for(var n,t=f();P("|");)L(),(n||(n=[t])).push(f());return n?{type:r.CONCAT_EXPR,args:n}:t}function f(){return P("(")?s():c()}function s(){A("(");var n=a();A(")");for(var t,e=[];t=p();)e.push(t);return e.length?n.type===r.PATH?(n.parts=n.parts.concat(e),n):(e.unshift(n),{type:r.PATH,parts:e}):n}function p(){return P("[")?function(){A("[");var n=function(){if(P(":"))return L(),{type:r.POS_EXPR,toIdx:y()};var n=y();if(P(":"))return L(),P("]")?{type:r.POS_EXPR,fromIdx:n}:{type:r.POS_EXPR,fromIdx:n,toIdx:y()};return{type:r.POS_EXPR,idx:n}}();return A("]"),{type:r.POS_PRED,arg:n}}():P("{")?function(){A("{");var n=l();return A("}"),{type:r.OBJ_PRED,arg:n}}():P("(")?s():void 0}function c(){O()||N(L());var n,t=!1;P("^")?(L(),t=!0):R()&&(n=L().val.substr(1));for(var e,u=[];e=E()?function(){var n,t=L().val,e=d();(P("*")||e.type===o.ID||e.type===o.STR)&&(n=L().val);return{type:r.SELECTOR,selector:t,prop:n}}():p();)u.push(e);return{type:r.PATH,fromRoot:t,subst:n,parts:u}}function l(){for(var n,t=h();P("||");)L(),(n||(n=[t])).push(h());return n?{type:r.LOGICAL_EXPR,op:"||",args:n}:t}function h(){for(var n,t=g();P("&&");)L(),(n||(n=[t])).push(g());return n?{type:r.LOGICAL_EXPR,op:"&&",args:n}:t}function g(){for(var n=function n(){var t=function(){var n=v();for(;P("+")||P("-");)n={type:r.MATH_EXPR,op:L().val,args:[n,v()]};return n}();for(;P("<")||P(">")||P("<=")||P(">=");)t={type:r.COMPARISON_EXPR,op:L().val,args:[t,n()]};return t}();P("==")||P("!=")||P("===")||P("!==")||P("^==")||P("==^")||P("^=")||P("=^")||P("$==")||P("==$")||P("$=")||P("=$")||P("*==")||P("==*")||P("*=")||P("=*");)n={type:r.COMPARISON_EXPR,op:L().val,args:[n,g()]};return n}function v(){for(var n=y();P("*")||P("/")||P("%");)n={type:r.MATH_EXPR,op:L().val,args:[n,v()]};return n}function y(){return P("!")||P("-")?{type:r.UNARY_EXPR,op:L().val,arg:y()}:function(){var n=d().type;if(n===o.STR||n===o.NUM||n===o.BOOL||n===o.NULL)return{type:r.LITERAL,val:L().val};if(O())return c();if(P("("))return function(){A("(");var r=l();return A(")"),r}();return N(L())}()}function P(r){var n=d();return n.type===o.PUNCT&&n.val===r}function O(){return E()||R()||P("^")}function E(){var r=d();if(r.type===o.PUNCT){var n=r.val;return"."===n||".."===n}return!1}function R(){var r=d();return r.type===o.ID&&"$"===r.val[0]}function A(r){var n=L();n.type===o.PUNCT&&n.val===r||N(n)}function d(){if(null!==e)return e;var r=t;return e=T(),t=r,e}function T(){for(;r=n[t]," \r\n\t".indexOf(r)>-1;)++t;var r;if(t>=u)return{type:o.EOP,range:[t,t]};var e=function(){var r=t,e=n[t],u=n[t+1];if("."===e){if(C(u))return;return"."===n[++t]?{type:o.PUNCT,val:"..",range:[r,++t]}:{type:o.PUNCT,val:".",range:[r,t]}}if("="===u){var i=n[t+2];if("="===i){if("=!^$*".indexOf(e)>=0)return{type:o.PUNCT,val:e+u+i,range:[r,t+=3]}}else if("^$*".indexOf(i)>=0){if("="===e)return{type:o.PUNCT,val:e+u+i,range:[r,t+=3]}}else if("=!^$*><".indexOf(e)>=0)return{type:o.PUNCT,val:e+u,range:[r,t+=2]}}else if("="===e&&"^$*".indexOf(u)>=0)return{type:o.PUNCT,val:e+u,range:[r,t+=2]};if(e===u&&("|"===e||"&"===e))return{type:o.PUNCT,val:e+u,range:[r,t+=2]};if(":{}()[]^+-*/%!><|".indexOf(e)>=0)return{type:o.PUNCT,val:e,range:[r,++t]}}();if(e||(e=function(){var r=n[t];if(!_(r))return;var e=t,i=r;for(;++t="0"&&a<="9");)i+=r;var a;switch(i){case"true":case"false":return{type:o.BOOL,val:"true"===i,range:[e,t]};case"null":return{type:o.NULL,val:null,range:[e,t]};default:return{type:o.ID,val:i,range:[e,t]}}}())||(e=function(){if('"'!==n[t]&&"'"!==n[t])return;var r,e=n[t],i=++t,a="",f=!1;for(;t=u?e.type=o.EOP:e.val=n[t],N(e)}function L(){var r;return e?(t=e.range[1],r=e,e=null,r):T()}function C(r){return"0123456789".indexOf(r)>=0}function _(r){return"$"===r||"@"===r||"_"===r||r>="a"&&r<="z"||r>="A"&&r<="Z"}function N(r){r.type===o.EOP&&S(r,i.UNEXP_EOP),S(r,i.UNEXP_TOKEN,r.val)}function S(r,n){var t=Array.prototype.slice.call(arguments,2),e=n.replace(/%(\d)/g,function(r,n){return t[n]||""}),u=new Error(e);throw u.column=r.range[0],u}return function(r){n=r.split(""),t=0,e=null,u=n.length;var i=a(),f=L();return f.type!==o.EOP&&N(f),i}}(),t=function(){var n,t,e,u;function o(){if(u.length)return u.shift();var r="v"+ ++e;return t.push(r),r}function i(){for(var r=arguments,n=r.length;n--;)u.push(r[n])}function a(r,t,e){if(r.prop){var u=g(r.prop),a=o(),f=o(),s=o(),p=o(),c=o(),l=o(),h=o();n.push(a,"= [];",f,"= 0;",s,"=",e,".length;",h,"= [];","while(",f,"<",s,") {",p,"=",e,"[",f,"++];","if(",p,"!= null) {"),"*"===r.prop?(n.push("if(typeof ",p,'=== "object") {',"if(isArr(",p,")) {",a,"=",a,".concat(",p,");","}","else {","for(",c," in ",p,") {","if(",p,".hasOwnProperty(",c,")) {",l,"=",p,"[",c,"];"),v(a,l),n.push("}","}","}","}")):(n.push(l,"=",p,"[",u,"];"),v(a,l,h,s)),n.push("}","}",t,"=",s,"> 1 &&",h,".length?",h,".length > 1?","concat.apply(",a,",",h,") :",a,".concat(",h,"[0]) :",a,";"),i(a,f,s,p,c,l,h)}}function f(r,t,e){var u=r.prop,a=o(),f=o(),s=o(),p=o(),c=o(),l=o(),h=o(),g=o();n.push(a,"=",e,".slice(),",g,"= [];","while(",a,".length) {",f,"=",a,".shift();"),u?n.push("if(typeof ",f,'=== "object" &&',f,") {"):n.push("if(typeof ",f,"!= null) {"),n.push(s,"= [];","if(isArr(",f,")) {",p,"= 0,",h,"=",f,".length;","while(",p,"<",h,") {",l,"=",f,"[",p,"++];"),u&&n.push("if(typeof ",l,'=== "object") {'),v(s,l),u&&n.push("}"),n.push("}","}","else {"),u?"*"!==u&&(n.push(l,"=",f,'["'+u+'"];'),v(g,l)):(v(g,f),n.push("if(typeof ",f,'=== "object") {')),n.push("for(",c," in ",f,") {","if(",f,".hasOwnProperty(",c,")) {",l,"=",f,"[",c,"];"),v(s,l),"*"===u&&v(g,l),n.push("}","}"),u||n.push("}"),n.push("}",s,".length &&",a,".unshift.apply(",a,",",s,");","}","}",t,"=",g,";"),i(a,f,s,p,c,l,h,g)}function s(r,t,e){var u=o(),a=o(),f=o(),s=o(),p=o();n.push(u,"= [];",a,"= 0;",f,"=",e,".length;","while(",a,"<",f,") {",p,"=",e,"[",a,"++];"),c(r.arg,s,p),n.push(P(r.arg,s),"&&",u,".push(",p,");","}",t,"=",u,";"),i(u,a,f,p,s)}function p(r,t,e){var u,a,f=r.arg;if(f.idx){var s=o();return c(f.idx,s,e),n.push(s,"< 0 && (",s,"=",e,".length +",s,");",t,"=",e,"[",s,"] == null? [] : [",e,"[",s,"]];"),i(s),!1}f.fromIdx?f.toIdx?(c(f.fromIdx,u=o(),e),c(f.toIdx,a=o(),e),n.push(t,"=",e,".slice(",u,",",a,");"),i(u,a)):(c(f.fromIdx,u=o(),e),n.push(t,"=",e,".slice(",u,");"),i(u)):(c(f.toIdx,a=o(),e),n.push(t,"=",e,".slice(0,",a,");"),i(a))}function c(t,e,u){switch(t.type){case r.PATH:!function(t,e,u){var o=t.parts,i=0,c=o.length;for(n.push(e,"=",t.fromRoot?"data":t.subst?"subst."+t.subst:u,";","isArr("+e+") || ("+e+" = ["+e+"]);");i 1?"),y(e,t),n.push(":")),n.push(r,"=",r,".length?",r,".concat(",t,") :",t,".slice()",";","}","else {"),e&&n.push("if(",e,".length) {",r,"= concat.apply(",r,",",e,");",e,"= [];","}"),y(r,t),n.push(";","}","}")}function y(r,t){n.push(r,".length?",r,".push(",t,") :",r,"[0] =",t)}function P(n,t){switch(n.type){case r.LOGICAL_EXPR:return t;case r.LITERAL:return"!!"+t;case r.PATH:return t+".length > 0";default:return["(typeof ",t,'=== "boolean"?',t,":","isArr(",t,")?",t,".length > 0 : !!",t,")"].join("")}}function O(n,t){switch(n.type){case r.LITERAL:return t;case r.PATH:return t+"[0]";default:return["(isArr(",t,")?",t,"[0] : ",t,")"].join("")}}function E(r,n){return["typeof ",r,'=== "string" && typeof ',n,'=== "string" &&',r,".indexOf(",n,") === 0"].join("")}function R(r,n){return[r,"!= null &&",n,"!= null &&",r,".toString().toLowerCase().indexOf(",n,".toString().toLowerCase()) === 0"].join("")}function A(r,n){return["typeof ",r,'=== "string" && typeof ',n,'=== "string" &&',r,".length >=",n,".length &&",r,".lastIndexOf(",n,") ===",r,".length -",n,".length"].join("")}function d(r,n){return[r,"!= null &&",n,"!= null &&","(",r,"=",r,".toString()).length >=","(",n,"=",n,".toString()).length &&","(",r,".toLowerCase()).lastIndexOf(","(",n,".toLowerCase())) ===",r,".length -",n,".length"].join("")}function T(r,n){return["typeof ",r,'=== "string" && typeof ',n,'=== "string" &&',r,".indexOf(",n,") > -1"].join("")}function L(r,n){return[r,"!= null && ",n,"!= null &&",r,".toString().toLowerCase().indexOf(",n,".toString().toLowerCase()) > -1"].join("")}var C={"===":function(r,n){return r+"==="+n},"==":function(r,n){return["typeof ",r,'=== "string" && typeof ',n,'=== "string"?',r,".toLowerCase() ===",n,".toLowerCase() :"+r,"==",n].join("")},">=":function(r,n){return r+">="+n},">":function(r,n){return r+">"+n},"<=":function(r,n){return r+"<="+n},"<":function(r,n){return r+"<"+n},"!==":function(r,n){return r+"!=="+n},"!=":function(r,n){return r+"!="+n},"^==":E,"==^":function(r,n){return E(n,r)},"^=":R,"=^":function(r,n){return R(n,r)},"$==":A,"==$":function(r,n){return A(n,r)},"$=":d,"=$":function(r,n){return d(n,r)},"*==":T,"==*":function(r,n){return T(n,r)},"=*":function(r,n){return L(n,r)},"*=":L,"+":function(r,n){return r+"+"+n},"-":function(r,n){return r+"-"+n},"*":function(r,n){return r+"*"+n},"/":function(r,n){return r+"/"+n},"%":function(r,n){return r+"%"+n}};return function(o){if(n=[],t=["res"],e=0,u=[],c(o,"res","data"),n.unshift("var ",Array.isArray?"isArr = Array.isArray":'toStr = Object.prototype.toString, isArr = function(o) { return toStr.call(o) === "[object Array]"; }',", concat = Array.prototype.concat",",",t.join(","),";"),o.type===r.PATH){var i=o.parts[o.parts.length-1];i&&i.type===r.POS_PRED&&"idx"in i.arg&&n.push("res = res[0];")}return n.push("return res;"),n.join("")}}();function e(r){return Function("data,subst",t(n(r)))}var u={},o=[],i={cacheSize:100},a={cacheSize:function(r,n){if(nn)for(var t=o.splice(0,o.length-n),e=t.length;e--;)delete u[t[e]]}},f=function(r,n,t){return u[r]||(u[r]=e(r),o.push(r)>i.cacheSize&&delete u[o.shift()]),u[r](n,t||{})};f.version="0.3.4",f.params=function(r){if(!arguments.length)return i;for(var n in r)r.hasOwnProperty(n)&&(a[n]&&a[n](i[n],r[n]),i[n]=r[n])},f.compile=e,f.apply=f,"object"==typeof module&&"object"==typeof module.exports?module.exports=f:"object"==typeof modules?modules.define("jspath",function(r){r(f)}):"function"==typeof define?define(function(r,n,t){t.exports=f}):window.JSPath=f}(); -------------------------------------------------------------------------------- /lib/jspath.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * JSPath 4 | * 5 | * Copyright (c) 2012 Filatov Dmitry (dfilatov@yandex-team.ru) 6 | * With parts by Marat Dulin (mdevils@gmail.com) 7 | * Dual licensed under the MIT and GPL licenses: 8 | * http://www.opensource.org/licenses/mit-license.php 9 | * http://www.gnu.org/licenses/gpl.html 10 | * 11 | * @version 0.4.0 12 | */ 13 | 14 | (function() { 15 | 16 | var SYNTAX = { 17 | PATH : 1, 18 | SELECTOR : 2, 19 | OBJ_PRED : 3, 20 | POS_PRED : 4, 21 | LOGICAL_EXPR : 5, 22 | COMPARISON_EXPR : 6, 23 | MATH_EXPR : 7, 24 | CONCAT_EXPR : 8, 25 | UNARY_EXPR : 9, 26 | POS_EXPR : 10, 27 | LITERAL : 11 28 | }; 29 | 30 | // parser 31 | 32 | var parse = (function() { 33 | 34 | var TOKEN = { 35 | ID : 1, 36 | NUM : 2, 37 | STR : 3, 38 | BOOL : 4, 39 | NULL : 5, 40 | PUNCT : 6, 41 | EOP : 7 42 | }, 43 | MESSAGES = { 44 | UNEXP_TOKEN : 'Unexpected token "%0"', 45 | UNEXP_EOP : 'Unexpected end of path' 46 | }; 47 | 48 | var path, idx, buf, len; 49 | 50 | function parse(_path) { 51 | path = _path.split(''); 52 | idx = 0; 53 | buf = null; 54 | len = path.length; 55 | 56 | var res = parsePathConcatExpr(), 57 | token = lex(); 58 | 59 | if(token.type !== TOKEN.EOP) { 60 | throwUnexpected(token); 61 | } 62 | 63 | return res; 64 | } 65 | 66 | function parsePathConcatExpr() { 67 | var expr = parsePathConcatPartExpr(), 68 | operands; 69 | 70 | while(match('|')) { 71 | lex(); 72 | (operands || (operands = [expr])).push(parsePathConcatPartExpr()); 73 | } 74 | 75 | return operands? 76 | { 77 | type : SYNTAX.CONCAT_EXPR, 78 | args : operands 79 | } : 80 | expr; 81 | } 82 | 83 | function parsePathConcatPartExpr() { 84 | return match('(')? 85 | parsePathGroupExpr() : 86 | parsePath(); 87 | } 88 | 89 | function parsePathGroupExpr() { 90 | expect('('); 91 | var expr = parsePathConcatExpr(); 92 | expect(')'); 93 | 94 | var parts = [], 95 | part; 96 | while((part = parsePredicate())) { 97 | parts.push(part); 98 | } 99 | 100 | if(!parts.length) { 101 | return expr; 102 | } 103 | else if(expr.type === SYNTAX.PATH) { 104 | expr.parts = expr.parts.concat(parts); 105 | return expr; 106 | } 107 | 108 | parts.unshift(expr); 109 | 110 | return { 111 | type : SYNTAX.PATH, 112 | parts : parts 113 | }; 114 | } 115 | 116 | function parsePredicate() { 117 | if(match('[')) { 118 | return parsePosPredicate(); 119 | } 120 | 121 | if(match('{')) { 122 | return parseObjectPredicate(); 123 | } 124 | 125 | if(match('(')) { 126 | return parsePathGroupExpr(); 127 | } 128 | } 129 | 130 | function parsePath() { 131 | if(!matchPath()) { 132 | throwUnexpected(lex()); 133 | } 134 | 135 | var fromRoot = false, 136 | subst; 137 | 138 | if(match('^')) { 139 | lex(); 140 | fromRoot = true; 141 | } 142 | else if(matchSubst()) { 143 | subst = lex().val.substr(1); 144 | } 145 | 146 | var parts = [], 147 | part; 148 | while((part = parsePathPart())) { 149 | parts.push(part); 150 | } 151 | 152 | return { 153 | type : SYNTAX.PATH, 154 | fromRoot : fromRoot, 155 | subst : subst, 156 | parts : parts 157 | }; 158 | } 159 | 160 | function parsePathPart() { 161 | return matchSelector()? 162 | parseSelector() : 163 | parsePredicate(); 164 | } 165 | 166 | function parseSelector() { 167 | var selector = lex().val, 168 | token = lookahead(), 169 | prop; 170 | 171 | if(match('*') || token.type === TOKEN.ID || token.type === TOKEN.STR) { 172 | prop = lex().val; 173 | } 174 | 175 | return { 176 | type : SYNTAX.SELECTOR, 177 | selector : selector, 178 | prop : prop 179 | }; 180 | } 181 | 182 | function parsePosPredicate() { 183 | expect('['); 184 | var expr = parsePosExpr(); 185 | expect(']'); 186 | 187 | return { 188 | type : SYNTAX.POS_PRED, 189 | arg : expr 190 | }; 191 | } 192 | 193 | function parseObjectPredicate() { 194 | expect('{'); 195 | var expr = parseLogicalORExpr(); 196 | expect('}'); 197 | 198 | return { 199 | type : SYNTAX.OBJ_PRED, 200 | arg : expr 201 | }; 202 | } 203 | 204 | function parseLogicalORExpr() { 205 | var expr = parseLogicalANDExpr(), 206 | operands; 207 | 208 | while(match('||')) { 209 | lex(); 210 | (operands || (operands = [expr])).push(parseLogicalANDExpr()); 211 | } 212 | 213 | return operands? 214 | { 215 | type : SYNTAX.LOGICAL_EXPR, 216 | op : '||', 217 | args : operands 218 | } : 219 | expr; 220 | } 221 | 222 | function parseLogicalANDExpr() { 223 | var expr = parseEqualityExpr(), 224 | operands; 225 | 226 | while(match('&&')) { 227 | lex(); 228 | (operands || (operands = [expr])).push(parseEqualityExpr()); 229 | } 230 | 231 | return operands? 232 | { 233 | type : SYNTAX.LOGICAL_EXPR, 234 | op : '&&', 235 | args : operands 236 | } : 237 | expr; 238 | } 239 | 240 | function parseEqualityExpr() { 241 | var expr = parseRelationalExpr(); 242 | 243 | while( 244 | match('==') || match('!=') || match('===') || match('!==') || 245 | match('^==') || match('==^') ||match('^=') || match('=^') || 246 | match('$==') || match('==$') || match('$=') || match('=$') || 247 | match('*==') || match('==*')|| match('*=') || match('=*') 248 | ) { 249 | expr = { 250 | type : SYNTAX.COMPARISON_EXPR, 251 | op : lex().val, 252 | args : [expr, parseEqualityExpr()] 253 | }; 254 | } 255 | 256 | return expr; 257 | } 258 | 259 | function parseRelationalExpr() { 260 | var expr = parseAdditiveExpr(); 261 | 262 | while(match('<') || match('>') || match('<=') || match('>=')) { 263 | expr = { 264 | type : SYNTAX.COMPARISON_EXPR, 265 | op : lex().val, 266 | args : [expr, parseRelationalExpr()] 267 | }; 268 | } 269 | 270 | return expr; 271 | } 272 | 273 | function parseAdditiveExpr() { 274 | var expr = parseMultiplicativeExpr(); 275 | 276 | while(match('+') || match('-')) { 277 | expr = { 278 | type : SYNTAX.MATH_EXPR, 279 | op : lex().val, 280 | args : [expr, parseMultiplicativeExpr()] 281 | }; 282 | } 283 | 284 | return expr; 285 | } 286 | 287 | function parseMultiplicativeExpr() { 288 | var expr = parseUnaryExpr(); 289 | 290 | while(match('*') || match('/') || match('%')) { 291 | expr = { 292 | type : SYNTAX.MATH_EXPR, 293 | op : lex().val, 294 | args : [expr, parseMultiplicativeExpr()] 295 | }; 296 | } 297 | 298 | return expr; 299 | } 300 | 301 | function parsePosExpr() { 302 | if(match(':')) { 303 | lex(); 304 | return { 305 | type : SYNTAX.POS_EXPR, 306 | toIdx : parseUnaryExpr() 307 | }; 308 | } 309 | 310 | var fromExpr = parseUnaryExpr(); 311 | if(match(':')) { 312 | lex(); 313 | if(match(']')) { 314 | return { 315 | type : SYNTAX.POS_EXPR, 316 | fromIdx : fromExpr 317 | }; 318 | } 319 | 320 | return { 321 | type : SYNTAX.POS_EXPR, 322 | fromIdx : fromExpr, 323 | toIdx : parseUnaryExpr() 324 | }; 325 | } 326 | 327 | return { 328 | type : SYNTAX.POS_EXPR, 329 | idx : fromExpr 330 | }; 331 | } 332 | 333 | function parseUnaryExpr() { 334 | if(match('!') || match('-')) { 335 | return { 336 | type : SYNTAX.UNARY_EXPR, 337 | op : lex().val, 338 | arg : parseUnaryExpr() 339 | }; 340 | } 341 | 342 | return parsePrimaryExpr(); 343 | } 344 | 345 | function parsePrimaryExpr() { 346 | var token = lookahead(), 347 | type = token.type; 348 | 349 | if(type === TOKEN.STR || type === TOKEN.NUM || type === TOKEN.BOOL || type === TOKEN.NULL) { 350 | return { 351 | type : SYNTAX.LITERAL, 352 | val : lex().val 353 | }; 354 | } 355 | 356 | if(matchPath()) { 357 | return parsePath(); 358 | } 359 | 360 | if(match('(')) { 361 | return parseGroupExpr(); 362 | } 363 | 364 | return throwUnexpected(lex()); 365 | } 366 | 367 | function parseGroupExpr() { 368 | expect('('); 369 | var expr = parseLogicalORExpr(); 370 | expect(')'); 371 | 372 | return expr; 373 | } 374 | 375 | function match(val) { 376 | var token = lookahead(); 377 | return token.type === TOKEN.PUNCT && token.val === val; 378 | } 379 | 380 | function matchPath() { 381 | return matchSelector() || matchSubst() || match('^'); 382 | } 383 | 384 | function matchSelector() { 385 | var token = lookahead(); 386 | if(token.type === TOKEN.PUNCT) { 387 | var val = token.val; 388 | return val === '.' || val === '..'; 389 | } 390 | 391 | return false; 392 | } 393 | 394 | function matchSubst() { 395 | var token = lookahead(); 396 | return token.type === TOKEN.ID && token.val[0] === '$'; 397 | } 398 | 399 | function expect(val) { 400 | var token = lex(); 401 | if(token.type !== TOKEN.PUNCT || token.val !== val) { 402 | throwUnexpected(token); 403 | } 404 | } 405 | 406 | function lookahead() { 407 | if(buf !== null) { 408 | return buf; 409 | } 410 | 411 | var pos = idx; 412 | buf = advance(); 413 | idx = pos; 414 | 415 | return buf; 416 | } 417 | 418 | function advance() { 419 | while(isWhiteSpace(path[idx])) { 420 | ++idx; 421 | } 422 | 423 | if(idx >= len) { 424 | return { 425 | type : TOKEN.EOP, 426 | range : [idx, idx] 427 | }; 428 | } 429 | 430 | var token = scanPunctuator(); 431 | if(token || 432 | (token = scanId()) || 433 | (token = scanString()) || 434 | (token = scanNumeric())) { 435 | return token; 436 | } 437 | 438 | token = { range : [idx, idx] }; 439 | idx >= len? 440 | token.type = TOKEN.EOP : 441 | token.val = path[idx]; 442 | 443 | throwUnexpected(token); 444 | } 445 | 446 | function lex() { 447 | var token; 448 | 449 | if(buf) { 450 | idx = buf.range[1]; 451 | token = buf; 452 | buf = null; 453 | return token; 454 | } 455 | 456 | return advance(); 457 | } 458 | 459 | function isDigit(ch) { 460 | return '0123456789'.indexOf(ch) >= 0; 461 | } 462 | 463 | function isWhiteSpace(ch) { 464 | return ' \r\n\t'.indexOf(ch) > -1; 465 | } 466 | 467 | function isIdStart(ch) { 468 | return ch === '$' || ch === '@' || ch === '_' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); 469 | } 470 | 471 | function isIdPart(ch) { 472 | return isIdStart(ch) || (ch >= '0' && ch <= '9'); 473 | } 474 | 475 | function scanId() { 476 | var ch = path[idx]; 477 | 478 | if(!isIdStart(ch)) { 479 | return; 480 | } 481 | 482 | var start = idx, 483 | id = ch; 484 | 485 | while(++idx < len) { 486 | ch = path[idx]; 487 | if(!isIdPart(ch)) { 488 | break; 489 | } 490 | id += ch; 491 | } 492 | 493 | switch(id) { 494 | case 'true': 495 | case 'false': 496 | return { 497 | type : TOKEN.BOOL, 498 | val : id === 'true', 499 | range : [start, idx] 500 | }; 501 | 502 | case 'null': 503 | return { 504 | type : TOKEN.NULL, 505 | val : null, 506 | range : [start, idx] 507 | }; 508 | 509 | default: 510 | return { 511 | type : TOKEN.ID, 512 | val : id, 513 | range : [start, idx] 514 | }; 515 | } 516 | } 517 | 518 | function scanString() { 519 | if(path[idx] !== '"' && path[idx] !== '\'') { 520 | return; 521 | } 522 | 523 | var orig = path[idx], 524 | start = ++idx, 525 | str = '', 526 | eosFound = false, 527 | ch; 528 | 529 | while(idx < len) { 530 | ch = path[idx++]; 531 | if(ch === '\\') { 532 | ch = path[idx++]; 533 | } 534 | else if((ch === '"' || ch === '\'') && ch === orig) { 535 | eosFound = true; 536 | break; 537 | } 538 | str += ch; 539 | } 540 | 541 | if(eosFound) { 542 | return { 543 | type : TOKEN.STR, 544 | val : str, 545 | range : [start, idx] 546 | }; 547 | } 548 | } 549 | 550 | function scanNumeric() { 551 | var start = idx, 552 | ch = path[idx], 553 | isFloat = ch === '.'; 554 | 555 | if(isFloat || isDigit(ch)) { 556 | var num = ch; 557 | while(++idx < len) { 558 | ch = path[idx]; 559 | if(ch === '.') { 560 | if(isFloat) { 561 | return; 562 | } 563 | isFloat = true; 564 | } 565 | else if(!isDigit(ch)) { 566 | break; 567 | } 568 | 569 | num += ch; 570 | } 571 | 572 | return { 573 | type : TOKEN.NUM, 574 | val : isFloat? parseFloat(num) : parseInt(num, 10), 575 | range : [start, idx] 576 | }; 577 | } 578 | } 579 | 580 | function scanPunctuator() { 581 | var start = idx, 582 | ch1 = path[idx], 583 | ch2 = path[idx + 1]; 584 | 585 | if(ch1 === '.') { 586 | if(isDigit(ch2)) { 587 | return; 588 | } 589 | 590 | return path[++idx] === '.'? 591 | { 592 | type : TOKEN.PUNCT, 593 | val : '..', 594 | range : [start, ++idx] 595 | } : 596 | { 597 | type : TOKEN.PUNCT, 598 | val : '.', 599 | range : [start, idx] 600 | }; 601 | } 602 | 603 | if(ch2 === '=') { 604 | var ch3 = path[idx + 2]; 605 | if(ch3 === '=') { 606 | if('=!^$*'.indexOf(ch1) >= 0) { 607 | return { 608 | type : TOKEN.PUNCT, 609 | val : ch1 + ch2 + ch3, 610 | range : [start, idx += 3] 611 | }; 612 | } 613 | } 614 | else if('^$*'.indexOf(ch3) >= 0) { 615 | if(ch1 === '=') { 616 | return { 617 | type : TOKEN.PUNCT, 618 | val : ch1 + ch2 + ch3, 619 | range : [start, idx += 3] 620 | }; 621 | } 622 | } 623 | else if('=!^$*><'.indexOf(ch1) >= 0) { 624 | return { 625 | type : TOKEN.PUNCT, 626 | val : ch1 + ch2, 627 | range : [start, idx += 2] 628 | }; 629 | } 630 | } 631 | else if(ch1 === '=' && '^$*'.indexOf(ch2) >= 0) { 632 | return { 633 | type : TOKEN.PUNCT, 634 | val : ch1 + ch2, 635 | range : [start, idx += 2] 636 | }; 637 | } 638 | 639 | if(ch1 === ch2 && (ch1 === '|' || ch1 === '&')) { 640 | return { 641 | type : TOKEN.PUNCT, 642 | val : ch1 + ch2, 643 | range : [start, idx += 2] 644 | }; 645 | } 646 | 647 | if(':{}()[]^+-*/%!><|'.indexOf(ch1) >= 0) { 648 | return { 649 | type : TOKEN.PUNCT, 650 | val : ch1, 651 | range : [start, ++idx] 652 | }; 653 | } 654 | } 655 | 656 | function throwUnexpected(token) { 657 | if(token.type === TOKEN.EOP) { 658 | throwError(token, MESSAGES.UNEXP_EOP); 659 | } 660 | 661 | throwError(token, MESSAGES.UNEXP_TOKEN, token.val); 662 | } 663 | 664 | function throwError(token, messageFormat) { 665 | var args = Array.prototype.slice.call(arguments, 2), 666 | msg = messageFormat.replace( 667 | /%(\d)/g, 668 | function(_, idx) { 669 | return args[idx] || ''; 670 | }), 671 | error = new Error(msg); 672 | 673 | error.column = token.range[0]; 674 | 675 | throw error; 676 | } 677 | 678 | return parse; 679 | })(); 680 | 681 | // translator 682 | 683 | var translate = (function() { 684 | 685 | var body, vars, lastVarId, unusedVars; 686 | 687 | function acquireVar() { 688 | if(unusedVars.length) { 689 | return unusedVars.shift(); 690 | } 691 | 692 | var varName = 'v' + ++lastVarId; 693 | vars.push(varName); 694 | return varName; 695 | } 696 | 697 | function releaseVars() { 698 | var args = arguments, i = args.length; 699 | while(i--) { 700 | unusedVars.push(args[i]); 701 | } 702 | } 703 | 704 | function translate(ast) { 705 | body = []; 706 | vars = ['res']; 707 | lastVarId = 0; 708 | unusedVars = []; 709 | 710 | translateExpr(ast, 'res', 'data'); 711 | 712 | body.unshift( 713 | 'var ', 714 | Array.isArray? 715 | 'isArr = Array.isArray' : 716 | 'toStr = Object.prototype.toString, isArr = function(o) { return toStr.call(o) === "[object Array]"; }', 717 | ', concat = Array.prototype.concat', 718 | ',', vars.join(','), ';'); 719 | 720 | if(ast.type === SYNTAX.PATH) { 721 | var lastPart = ast.parts[ast.parts.length - 1]; 722 | if(lastPart && lastPart.type === SYNTAX.POS_PRED && 'idx' in lastPart.arg) { 723 | body.push('res = res[0];'); 724 | } 725 | } 726 | 727 | body.push('return res;'); 728 | 729 | return body.join(''); 730 | } 731 | 732 | function translatePath(path, dest, ctx) { 733 | var parts = path.parts, 734 | i = 0, len = parts.length; 735 | 736 | body.push( 737 | dest, '=', path.fromRoot? 'data' : path.subst? 'subst.' + path.subst : ctx, ';', 738 | 'isArr(' + dest + ') || (' + dest + ' = [' + dest + ']);'); 739 | 740 | while(i < len) { 741 | var item = parts[i++]; 742 | switch(item.type) { 743 | case SYNTAX.SELECTOR: 744 | item.selector === '..'? 745 | translateDescendantSelector(item, dest, dest) : 746 | translateSelector(item, dest, dest); 747 | break; 748 | 749 | case SYNTAX.OBJ_PRED: 750 | translateObjectPredicate(item, dest, dest); 751 | break; 752 | 753 | case SYNTAX.POS_PRED: 754 | translatePosPredicate(item, dest, dest); 755 | break; 756 | 757 | case SYNTAX.CONCAT_EXPR: 758 | translateConcatExpr(item, dest, dest); 759 | break; 760 | } 761 | } 762 | } 763 | 764 | function translateSelector(sel, dest, ctx) { 765 | if(sel.prop) { 766 | var propStr = escapeStr(sel.prop), 767 | res = acquireVar(), i = acquireVar(), len = acquireVar(), 768 | curCtx = acquireVar(), 769 | j = acquireVar(), val = acquireVar(), tmpArr = acquireVar(); 770 | 771 | body.push( 772 | res, '= [];', i, '= 0;', len, '=', ctx, '.length;', tmpArr, '= [];', 773 | 'while(', i, '<', len, ') {', 774 | curCtx, '=', ctx, '[', i, '++];', 775 | 'if(', curCtx, '!= null) {'); 776 | if(sel.prop === '*') { 777 | body.push( 778 | 'if(typeof ', curCtx, '=== "object") {', 779 | 'if(isArr(', curCtx, ')) {', 780 | res, '=', res, '.concat(', curCtx, ');', 781 | '}', 782 | 'else {', 783 | 'for(', j, ' in ', curCtx, ') {', 784 | 'if(', curCtx, '.hasOwnProperty(', j, ')) {', 785 | val, '=', curCtx, '[', j, '];'); 786 | inlineAppendToArray(res, val); 787 | body.push( 788 | '}', 789 | '}', 790 | '}', 791 | '}'); 792 | } 793 | else { 794 | body.push( 795 | val, '=', curCtx, '[', propStr, '];'); 796 | inlineAppendToArray(res, val, tmpArr, len); 797 | } 798 | body.push( 799 | '}', 800 | '}', 801 | dest, '=', len, '> 1 &&', tmpArr, '.length?', tmpArr, '.length > 1?', 802 | 'concat.apply(', res, ',', tmpArr, ') :', res, '.concat(', tmpArr, '[0]) :', res, ';'); 803 | 804 | releaseVars(res, i, len, curCtx, j, val, tmpArr); 805 | } 806 | } 807 | 808 | function translateDescendantSelector(sel, dest, baseCtx) { 809 | var prop = sel.prop, 810 | ctx = acquireVar(), curCtx = acquireVar(), childCtxs = acquireVar(), 811 | i = acquireVar(), j = acquireVar(), val = acquireVar(), 812 | len = acquireVar(), res = acquireVar(); 813 | 814 | body.push( 815 | ctx, '=', baseCtx, '.slice(),', res, '= [];', 816 | 'while(', ctx, '.length) {', 817 | curCtx, '=', ctx, '.shift();'); 818 | prop? 819 | body.push( 820 | 'if(typeof ', curCtx, '=== "object" &&', curCtx, ') {') : 821 | body.push( 822 | 'if(typeof ', curCtx, '!= null) {'); 823 | body.push( 824 | childCtxs, '= [];', 825 | 'if(isArr(', curCtx, ')) {', 826 | i, '= 0,', len, '=', curCtx, '.length;', 827 | 'while(', i, '<', len, ') {', 828 | val, '=', curCtx, '[', i, '++];'); 829 | prop && body.push( 830 | 'if(typeof ', val, '=== "object") {'); 831 | inlineAppendToArray(childCtxs, val); 832 | prop && body.push( 833 | '}'); 834 | body.push( 835 | '}', 836 | '}', 837 | 'else {'); 838 | if(prop) { 839 | if(prop !== '*') { 840 | body.push( 841 | val, '=', curCtx, '["' + prop + '"];'); 842 | inlineAppendToArray(res, val); 843 | } 844 | } 845 | else { 846 | inlineAppendToArray(res, curCtx); 847 | body.push( 848 | 'if(typeof ', curCtx, '=== "object") {'); 849 | } 850 | 851 | body.push( 852 | 'for(', j, ' in ', curCtx, ') {', 853 | 'if(', curCtx, '.hasOwnProperty(', j, ')) {', 854 | val, '=', curCtx, '[', j, '];'); 855 | inlineAppendToArray(childCtxs, val); 856 | prop === '*' && inlineAppendToArray(res, val); 857 | body.push( 858 | '}', 859 | '}'); 860 | prop || body.push( 861 | '}'); 862 | body.push( 863 | '}', 864 | childCtxs, '.length &&', ctx, '.unshift.apply(', ctx, ',', childCtxs, ');', 865 | '}', 866 | '}', 867 | dest, '=', res, ';'); 868 | 869 | releaseVars(ctx, curCtx, childCtxs, i, j, val, len, res); 870 | } 871 | 872 | function translateObjectPredicate(expr, dest, ctx) { 873 | var resVar = acquireVar(), i = acquireVar(), len = acquireVar(), 874 | cond = acquireVar(), curItem = acquireVar(); 875 | 876 | body.push( 877 | resVar, '= [];', 878 | i, '= 0;', 879 | len, '=', ctx, '.length;', 880 | 'while(', i, '<', len, ') {', 881 | curItem, '=', ctx, '[', i, '++];'); 882 | translateExpr(expr.arg, cond, curItem); 883 | body.push( 884 | convertToBool(expr.arg, cond), '&&', resVar, '.push(', curItem, ');', 885 | '}', 886 | dest, '=', resVar, ';'); 887 | 888 | releaseVars(resVar, i, len, curItem, cond); 889 | } 890 | 891 | function translatePosPredicate(item, dest, ctx) { 892 | var arrayExpr = item.arg, fromIdx, toIdx; 893 | if(arrayExpr.idx) { 894 | var idx = acquireVar(); 895 | translateExpr(arrayExpr.idx, idx, ctx); 896 | body.push( 897 | idx, '< 0 && (', idx, '=', ctx, '.length +', idx, ');', 898 | dest, '=', ctx, '[', idx, '] == null? [] : [', ctx, '[', idx, ']];'); 899 | releaseVars(idx); 900 | return false; 901 | } 902 | else if(arrayExpr.fromIdx) { 903 | if(arrayExpr.toIdx) { 904 | translateExpr(arrayExpr.fromIdx, fromIdx = acquireVar(), ctx); 905 | translateExpr(arrayExpr.toIdx, toIdx = acquireVar(), ctx); 906 | body.push(dest, '=', ctx, '.slice(', fromIdx, ',', toIdx, ');'); 907 | releaseVars(fromIdx, toIdx); 908 | } 909 | else { 910 | translateExpr(arrayExpr.fromIdx, fromIdx = acquireVar(), ctx); 911 | body.push(dest, '=', ctx, '.slice(', fromIdx, ');'); 912 | releaseVars(fromIdx); 913 | } 914 | } 915 | else { 916 | translateExpr(arrayExpr.toIdx, toIdx = acquireVar(), ctx); 917 | body.push(dest, '=', ctx, '.slice(0,', toIdx, ');'); 918 | releaseVars(toIdx); 919 | } 920 | } 921 | 922 | function translateExpr(expr, dest, ctx) { 923 | switch(expr.type) { 924 | case SYNTAX.PATH: 925 | translatePath(expr, dest, ctx); 926 | break; 927 | 928 | case SYNTAX.CONCAT_EXPR: 929 | translateConcatExpr(expr, dest, ctx); 930 | break; 931 | 932 | case SYNTAX.COMPARISON_EXPR: 933 | translateComparisonExpr(expr, dest, ctx); 934 | break; 935 | 936 | case SYNTAX.MATH_EXPR: 937 | translateMathExpr(expr, dest, ctx); 938 | break; 939 | 940 | case SYNTAX.LOGICAL_EXPR: 941 | translateLogicalExpr(expr, dest, ctx); 942 | break; 943 | 944 | case SYNTAX.UNARY_EXPR: 945 | translateUnaryExpr(expr, dest, ctx); 946 | break; 947 | 948 | case SYNTAX.LITERAL: 949 | body.push(dest, '='); 950 | translateLiteral(expr.val); 951 | body.push(';'); 952 | break; 953 | } 954 | } 955 | 956 | function translateLiteral(val) { 957 | body.push(typeof val === 'string'? escapeStr(val) : val === null? 'null' : val); 958 | } 959 | 960 | function translateComparisonExpr(expr, dest, ctx) { 961 | var val1 = acquireVar(), val2 = acquireVar(), 962 | isVal1Array = acquireVar(), isVal2Array = acquireVar(), 963 | i = acquireVar(), j = acquireVar(), 964 | len1 = acquireVar(), len2 = acquireVar(), 965 | leftArg = expr.args[0], rightArg = expr.args[1]; 966 | 967 | body.push(dest, '= false;'); 968 | 969 | translateExpr(leftArg, val1, ctx); 970 | translateExpr(rightArg, val2, ctx); 971 | 972 | var isLeftArgPath = leftArg.type === SYNTAX.PATH, 973 | isRightArgLiteral = rightArg.type === SYNTAX.LITERAL; 974 | 975 | body.push(isVal1Array, '='); 976 | isLeftArgPath? body.push('true;') : body.push('isArr(', val1, ');'); 977 | 978 | body.push(isVal2Array, '='); 979 | isRightArgLiteral? body.push('false;') : body.push('isArr(', val2, ');'); 980 | 981 | body.push( 982 | 'if('); 983 | isLeftArgPath || body.push(isVal1Array, '&&'); 984 | body.push(val1, '.length === 1) {', 985 | val1, '=', val1, '[0];', 986 | isVal1Array, '= false;', 987 | '}'); 988 | isRightArgLiteral || body.push( 989 | 'if(', isVal2Array, '&&', val2, '.length === 1) {', 990 | val2, '=', val2, '[0];', 991 | isVal2Array, '= false;', 992 | '}'); 993 | 994 | body.push(i, '= 0;', 995 | 'if(', isVal1Array, ') {', 996 | len1, '=', val1, '.length;'); 997 | 998 | if(!isRightArgLiteral) { 999 | body.push( 1000 | 'if(', isVal2Array, ') {', 1001 | len2, '=', val2, '.length;', 1002 | 'while(', i, '<', len1, '&& !', dest, ') {', 1003 | j, '= 0;', 1004 | 'while(', j, '<', len2, ') {'); 1005 | writeCondition(expr.op, [val1, '[', i, ']'].join(''), [val2, '[', j, ']'].join('')); 1006 | body.push( 1007 | dest, '= true;', 1008 | 'break;', 1009 | '}', 1010 | '++', j, ';', 1011 | '}', 1012 | '++', i, ';', 1013 | '}', 1014 | '}', 1015 | 'else {'); 1016 | } 1017 | body.push( 1018 | 'while(', i, '<', len1, ') {'); 1019 | writeCondition(expr.op, [val1, '[', i, ']'].join(''), val2); 1020 | body.push( 1021 | dest, '= true;', 1022 | 'break;', 1023 | '}', 1024 | '++', i, ';', 1025 | '}'); 1026 | 1027 | isRightArgLiteral || body.push( 1028 | '}'); 1029 | 1030 | body.push( 1031 | '}'); 1032 | 1033 | if(!isRightArgLiteral) { 1034 | body.push( 1035 | 'else if(', isVal2Array,') {', 1036 | len2, '=', val2, '.length;', 1037 | 'while(', i, '<', len2, ') {'); 1038 | writeCondition(expr.op, val1, [val2, '[', i, ']'].join('')); 1039 | body.push( 1040 | dest, '= true;', 1041 | 'break;', 1042 | '}', 1043 | '++', i, ';', 1044 | '}', 1045 | '}'); 1046 | } 1047 | 1048 | body.push( 1049 | 'else {', 1050 | dest, '=', binaryOperators[expr.op](val1, val2), ';', 1051 | '}'); 1052 | 1053 | releaseVars(val1, val2, isVal1Array, isVal2Array, i, j, len1, len2); 1054 | } 1055 | 1056 | function writeCondition(op, val1Expr, val2Expr) { 1057 | body.push('if(', binaryOperators[op](val1Expr, val2Expr), ') {'); 1058 | } 1059 | 1060 | function translateLogicalExpr(expr, dest, ctx) { 1061 | var conditionVars = [], 1062 | args = expr.args, len = args.length, 1063 | i = 0, val; 1064 | 1065 | body.push(dest, '= false;'); 1066 | switch(expr.op) { 1067 | case '&&': 1068 | while(i < len) { 1069 | conditionVars.push(val = acquireVar()); 1070 | translateExpr(args[i], val, ctx); 1071 | body.push('if(', convertToBool(args[i++], val), ') {'); 1072 | } 1073 | body.push(dest, '= true;'); 1074 | break; 1075 | 1076 | case '||': 1077 | while(i < len) { 1078 | conditionVars.push(val = acquireVar()); 1079 | translateExpr(args[i], val, ctx); 1080 | body.push( 1081 | 'if(', convertToBool(args[i], val), ') {', 1082 | dest, '= true;', 1083 | '}'); 1084 | if(i++ + 1 < len) { 1085 | body.push('else {'); 1086 | } 1087 | } 1088 | --len; 1089 | break; 1090 | } 1091 | 1092 | while(len--) { 1093 | body.push('}'); 1094 | } 1095 | 1096 | releaseVars.apply(null, conditionVars); 1097 | } 1098 | 1099 | function translateMathExpr(expr, dest, ctx) { 1100 | var val1 = acquireVar(), 1101 | val2 = acquireVar(), 1102 | args = expr.args; 1103 | 1104 | translateExpr(args[0], val1, ctx); 1105 | translateExpr(args[1], val2, ctx); 1106 | 1107 | body.push( 1108 | dest, '=', 1109 | binaryOperators[expr.op]( 1110 | convertToSingleValue(args[0], val1), 1111 | convertToSingleValue(args[1], val2)), 1112 | ';'); 1113 | 1114 | releaseVars(val1, val2); 1115 | } 1116 | 1117 | function translateUnaryExpr(expr, dest, ctx) { 1118 | var val = acquireVar(), 1119 | arg = expr.arg; 1120 | 1121 | translateExpr(arg, val, ctx); 1122 | 1123 | switch(expr.op) { 1124 | case '!': 1125 | body.push(dest, '= !', convertToBool(arg, val) + ';'); 1126 | break; 1127 | 1128 | case '-': 1129 | body.push(dest, '= -', convertToSingleValue(arg, val) + ';'); 1130 | break; 1131 | } 1132 | 1133 | releaseVars(val); 1134 | } 1135 | 1136 | function translateConcatExpr(expr, dest, ctx) { 1137 | var argVars = [], 1138 | args = expr.args, 1139 | len = args.length, 1140 | i = 0; 1141 | 1142 | while(i < len) { 1143 | argVars.push(acquireVar()); 1144 | translateExpr(args[i], argVars[i++], ctx); 1145 | } 1146 | 1147 | body.push(dest, '= concat.call(', argVars.join(','), ');'); 1148 | 1149 | releaseVars.apply(null, argVars); 1150 | } 1151 | 1152 | function escapeStr(s) { 1153 | return '\'' + s.replace(/\\/g, '\\\\').replace(/'/g, '\\\'') + '\''; 1154 | } 1155 | 1156 | function inlineAppendToArray(res, val, tmpArr, len) { 1157 | body.push( 1158 | 'if(typeof ', val, '!== "undefined") {', 1159 | 'if(isArr(', val, ')) {'); 1160 | if(tmpArr) { 1161 | body.push( 1162 | len, '> 1?'); 1163 | inlinePushToArray(tmpArr, val); 1164 | body.push( 1165 | ':'); 1166 | } 1167 | body.push( 1168 | res, '=', res, '.length?', res, '.concat(', val, ') :', val, '.slice()', ';', 1169 | '}', 1170 | 'else {'); 1171 | tmpArr && body.push( 1172 | 'if(', tmpArr, '.length) {', 1173 | res, '= concat.apply(', res, ',', tmpArr, ');', 1174 | tmpArr, '= [];', 1175 | '}'); 1176 | inlinePushToArray(res, val); 1177 | body.push(';', 1178 | '}', 1179 | '}'); 1180 | } 1181 | 1182 | function inlinePushToArray(res, val) { 1183 | body.push(res, '.length?', res, '.push(', val, ') :', res, '[0] =', val); 1184 | } 1185 | 1186 | function convertToBool(arg, varName) { 1187 | switch(arg.type) { 1188 | case SYNTAX.LOGICAL_EXPR: 1189 | return varName; 1190 | 1191 | case SYNTAX.LITERAL: 1192 | return '!!' + varName; 1193 | 1194 | case SYNTAX.PATH: 1195 | return varName + '.length > 0'; 1196 | 1197 | default: 1198 | return ['(typeof ', varName, '=== "boolean"?', 1199 | varName, ':', 1200 | 'isArr(', varName, ')?', varName, '.length > 0 : !!', varName, ')'].join(''); 1201 | } 1202 | } 1203 | 1204 | function convertToSingleValue(arg, varName) { 1205 | switch(arg.type) { 1206 | case SYNTAX.LITERAL: 1207 | return varName; 1208 | 1209 | case SYNTAX.PATH: 1210 | return varName + '[0]'; 1211 | 1212 | default: 1213 | return ['(isArr(', varName, ')?', varName, '[0] : ', varName, ')'].join(''); 1214 | } 1215 | } 1216 | 1217 | function startsWithStrict(val1, val2) { 1218 | return ['typeof ', val1, '=== "string" && typeof ', val2, '=== "string" &&', 1219 | val1, '.indexOf(', val2, ') === 0'].join(''); 1220 | } 1221 | 1222 | function startsWith(val1, val2) { 1223 | return [val1, '!= null &&', val2, '!= null &&', 1224 | val1, '.toString().toLowerCase().indexOf(', val2, '.toString().toLowerCase()) === 0'].join(''); 1225 | } 1226 | 1227 | function endsWithStrict(val1, val2) { 1228 | return ['typeof ', val1, '=== "string" && typeof ', val2, '=== "string" &&', 1229 | val1, '.length >=', val2, '.length &&', 1230 | val1, '.lastIndexOf(', val2, ') ===', val1, '.length -', val2, '.length'].join(''); 1231 | } 1232 | 1233 | function endsWith(val1, val2) { 1234 | return [val1, '!= null &&', val2, '!= null &&', 1235 | '(', val1, '=', val1, '.toString()).length >=', '(', val2, '=', val2, '.toString()).length &&', 1236 | '(', val1, '.toLowerCase()).lastIndexOf(', '(', val2, '.toLowerCase())) ===', 1237 | val1, '.length -', val2, '.length'].join(''); 1238 | } 1239 | 1240 | function containsStrict(val1, val2) { 1241 | return ['typeof ', val1, '=== "string" && typeof ', val2, '=== "string" &&', 1242 | val1, '.indexOf(', val2, ') > -1'].join(''); 1243 | } 1244 | 1245 | function contains(val1, val2) { 1246 | return [val1, '!= null && ', val2, '!= null &&', 1247 | val1, '.toString().toLowerCase().indexOf(', val2, '.toString().toLowerCase()) > -1'].join(''); 1248 | } 1249 | 1250 | var binaryOperators = { 1251 | '===' : function(val1, val2) { 1252 | return val1 + '===' + val2; 1253 | }, 1254 | 1255 | '==' : function(val1, val2) { 1256 | return ['typeof ', val1, '=== "string" && typeof ', val2, '=== "string"?', 1257 | val1, '.toLowerCase() ===', val2, '.toLowerCase() :' + 1258 | val1, '==', val2].join(''); 1259 | }, 1260 | 1261 | '>=' : function(val1, val2) { 1262 | return val1 + '>=' + val2; 1263 | }, 1264 | 1265 | '>' : function(val1, val2) { 1266 | return val1 + '>' + val2; 1267 | }, 1268 | 1269 | '<=' : function(val1, val2) { 1270 | return val1 + '<=' + val2; 1271 | }, 1272 | 1273 | '<' : function(val1, val2) { 1274 | return val1 + '<' + val2; 1275 | }, 1276 | 1277 | '!==' : function(val1, val2) { 1278 | return val1 + '!==' + val2; 1279 | }, 1280 | 1281 | '!=' : function(val1, val2) { 1282 | return val1 + '!=' + val2; 1283 | }, 1284 | 1285 | '^==' : startsWithStrict, 1286 | 1287 | '==^' : function(val1, val2) { 1288 | return startsWithStrict(val2, val1); 1289 | }, 1290 | 1291 | '^=' : startsWith, 1292 | 1293 | '=^' : function(val1, val2) { 1294 | return startsWith(val2, val1); 1295 | }, 1296 | 1297 | '$==' : endsWithStrict, 1298 | 1299 | '==$' : function(val1, val2) { 1300 | return endsWithStrict(val2, val1); 1301 | }, 1302 | 1303 | '$=' : endsWith, 1304 | 1305 | '=$' : function(val1, val2) { 1306 | return endsWith(val2, val1); 1307 | }, 1308 | 1309 | '*==' : containsStrict, 1310 | 1311 | '==*' : function(val1, val2) { 1312 | return containsStrict(val2, val1); 1313 | }, 1314 | 1315 | '=*' : function(val1, val2) { 1316 | return contains(val2, val1); 1317 | }, 1318 | 1319 | '*=' : contains, 1320 | 1321 | '+' : function(val1, val2) { 1322 | return val1 + '+' + val2; 1323 | }, 1324 | 1325 | '-' : function(val1, val2) { 1326 | return val1 + '-' + val2; 1327 | }, 1328 | 1329 | '*' : function(val1, val2) { 1330 | return val1 + '*' + val2; 1331 | }, 1332 | 1333 | '/' : function(val1, val2) { 1334 | return val1 + '/' + val2; 1335 | }, 1336 | 1337 | '%' : function(val1, val2) { 1338 | return val1 + '%' + val2; 1339 | } 1340 | }; 1341 | 1342 | return translate; 1343 | })(); 1344 | 1345 | function compile(path) { 1346 | return Function('data,subst', translate(parse(path))); 1347 | } 1348 | 1349 | var cache = {}, 1350 | cacheKeys = [], 1351 | params = { 1352 | cacheSize : 100 1353 | }, 1354 | setParamsHooks = { 1355 | cacheSize : function(oldVal, newVal) { 1356 | if(newVal < oldVal && cacheKeys.length > newVal) { 1357 | var removedKeys = cacheKeys.splice(0, cacheKeys.length - newVal), 1358 | i = removedKeys.length; 1359 | 1360 | while(i--) { 1361 | delete cache[removedKeys[i]]; 1362 | } 1363 | } 1364 | } 1365 | }; 1366 | 1367 | var decl = function(path, ctx, substs) { 1368 | if(!cache[path]) { 1369 | cache[path] = compile(path); 1370 | if(cacheKeys.push(path) > params.cacheSize) { 1371 | delete cache[cacheKeys.shift()]; 1372 | } 1373 | } 1374 | 1375 | return cache[path](ctx, substs || {}); 1376 | }; 1377 | 1378 | decl.version = '0.3.4'; 1379 | 1380 | decl.params = function(_params) { 1381 | if(!arguments.length) { 1382 | return params; 1383 | } 1384 | 1385 | for(var name in _params) { 1386 | if(_params.hasOwnProperty(name)) { 1387 | setParamsHooks[name] && setParamsHooks[name](params[name], _params[name]); 1388 | params[name] = _params[name]; 1389 | } 1390 | } 1391 | }; 1392 | 1393 | decl.compile = compile; 1394 | 1395 | decl.apply = decl; 1396 | 1397 | if(typeof module === 'object' && typeof module.exports === 'object') { 1398 | module.exports = decl; 1399 | } 1400 | else if(typeof modules === 'object') { 1401 | modules.define('jspath', function(provide) { 1402 | provide(decl); 1403 | }); 1404 | } 1405 | else if(typeof define === 'function') { 1406 | define(function(require, exports, module) { 1407 | module.exports = decl; 1408 | }); 1409 | } 1410 | else { 1411 | window.JSPath = decl; 1412 | } 1413 | 1414 | })(); 1415 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jspath", 3 | "version": "0.4.0", 4 | "homepage": "https://github.com/dfilatov/jspath", 5 | "description": "DSL that enables you to navigate and find data within your JSON documents", 6 | "keywords": [ 7 | "json", 8 | "path", 9 | "filter", 10 | "selection", 11 | "jspath", 12 | "jpath", 13 | "jsonpath" 14 | ], 15 | "author": "Dmitry Filatov ", 16 | "contributors": [ 17 | { 18 | "name": "Dmitry Filatov", 19 | "email": "dfilatov@yandex-team.ru" 20 | }, 21 | { 22 | "name": "Marat Dulin", 23 | "email": "mdevils@gmail.com" 24 | } 25 | ], 26 | "license": "MIT", 27 | "repository": { 28 | "type": "git", 29 | "url": "http://github.com/dfilatov/jspath.git" 30 | }, 31 | "dependencies": {}, 32 | "devDependencies": { 33 | "nodeunit": "0.11.2", 34 | "istanbul": "0.4.5", 35 | "cliff": "0.1.10", 36 | "benchmark": "2.1.4", 37 | "uglify-js": "3.3.4" 38 | }, 39 | "main": "index", 40 | "engines": { 41 | "node": ">= 0.4.0" 42 | }, 43 | "scripts": { 44 | "test": "./node_modules/istanbul/lib/cli.js test test.js" 45 | }, 46 | "enb": { 47 | "sources": [ 48 | "lib/jspath.js" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | JSPath [![NPM Version](https://img.shields.io/npm/v/jspath.svg?style=flat-square)](https://www.npmjs.com/package/jspath) [![Build Status](https://img.shields.io/travis/dfilatov/jspath/master.svg?style=flat-square)](https://travis-ci.org/dfilatov/jspath/branches) 2 | ============ 3 | 4 | JSPath is a [domain-specific language (DSL)](https://en.wikipedia.org/wiki/Domain-specific_language) that enables you to navigate and find data within your JSON documents. Using JSPath, you can select items of JSON in order to retrieve the data they contain. 5 | 6 | JSPath for JSON is like [XPath](https://en.wikipedia.org/wiki/XPath) for XML. 7 | 8 | It's heavily optimized both for Node.js and modern browsers. 9 | 10 | Table of Contents 11 | ----------------- 12 | * [Getting Started](#getting-started) 13 | * [In the Node.js](#in-the-nodejs) 14 | * [In the Browsers](#in-the-browsers) 15 | * [Usage](#usage) 16 | * [Quick example](#quick-example) 17 | * [Documentation](#documentation) 18 | * [Location path](#location-path) 19 | * [Predicates](#predicates) 20 | * [Object predicates](#object-predicates) 21 | * [Comparison operators](#comparison-operators) 22 | * [String comparison operators](#string-comparison-operators) 23 | * [Logical operators](#logical-operators) 24 | * [Arithmetic operators](#arithmetic-operators) 25 | * [Operator precedence](#operator-precedence) 26 | * [Positional predicates](#positional-predicates) 27 | * [Multiple predicates](#multiple-predicates) 28 | * [Substitutions](#substitutions) 29 | * [Result](#result) 30 | 31 | Getting Started 32 | --------------- 33 | 34 | ### In the Node.js 35 | 36 | You can install using Node Package Manager (npm): 37 | 38 | npm install jspath 39 | 40 | ### In the Browsers 41 | 42 | ```html 43 | 44 | ``` 45 | It also supports RequireJS module format and [YM module](https://github.com/ymaps/modules) format. 46 | 47 | JSPath has been tested in IE6+, Mozilla Firefox 3+, Chrome 5+, Safari 5+, Opera 10+. 48 | 49 | Usage 50 | ----- 51 | ```javascript 52 | JSPath.apply(path, json[, substs]); 53 | ``` 54 | where: 55 | 56 | parameter | data type | description 57 | ------------- | ------------- | ------------- 58 | `path` | string | [path expression](#documentation) 59 | `json` | any valid JSON | input JSON document 60 | `substs` | object | [substitutions (*optional*)](#substitutions) 61 | 62 | ### Quick example 63 | 64 | ```javascript 65 | JSPath.apply( 66 | '.automobiles{.maker === "Honda" && .year > 2009}.model', 67 | { 68 | "automobiles" : [ 69 | { "maker" : "Nissan", "model" : "Teana", "year" : 2011 }, 70 | { "maker" : "Honda", "model" : "Jazz", "year" : 2010 }, 71 | { "maker" : "Honda", "model" : "Civic", "year" : 2007 }, 72 | { "maker" : "Toyota", "model" : "Yaris", "year" : 2008 }, 73 | { "maker" : "Honda", "model" : "Accord", "year" : 2011 } 74 | ], 75 | "motorcycles" : [{ "maker" : "Honda", "model" : "ST1300", "year" : 2012 }] 76 | }); 77 | ``` 78 | Result will be: 79 | ```javascript 80 | ['Jazz', 'Accord'] 81 | ``` 82 | 83 | Documentation 84 | ------------- 85 | A JSPath path expression consists of two types of top-level expressions: 86 | 87 | 1. the required [location path](#location-path) and 88 | 2. one or more optional [predicates](#predicates) 89 | 90 | This means, a path expression like 91 | 92 | ```javascript 93 | .automobiles{.maker === "Honda" && .year > 2009}.model 94 | ``` 95 | 96 | can be split into 97 | 98 | the location path | one predicate | and the continued location path 99 | ------------- | ------------- | ------------- 100 | `.automobiles` | `{.maker === "Honda" && .year > 2009}` | `.model` 101 | 102 | ### Location path 103 | 104 | To select items in JSPath, you use a location path which consists of one or more location steps. 105 | 106 | Every location step starts with one period (`.`) or two periods (`..`), depending on the item you're trying to select: 107 | 108 | location step | description 109 | ------------- | ------------- 110 | `.property` | locates property immediately descended from context items 111 | `..property` | locates property deeply descended from context items 112 | `.` | locates context items itself 113 | 114 | You can use the wildcard symbol (`*`) instead of exact name of property: 115 | 116 | location step | description 117 | ------------- | ------------- 118 | `.*` | locates all properties immediately descended from the context items 119 | `..*` | locates all properties deeply descended from the context items 120 | 121 | Property must be a sequence of alphanumerical characters including `_`, `$` and `@` symbols, that cannot start with a number. 122 | If you need to locate properties containing any other characters, you have to quote them: 123 | 124 | location step | description 125 | ------------- | ------------- 126 | `."property with non-alphanumerical characters"` | locates a property containing non-alphanumerical characters 127 | 128 | Also JSPath allows to join several properties: 129 | 130 | location step | description 131 | ------------- | ------------- 132 | `(.property1 | .property2 | .propertyN)` | locates `property1`, `property2`, `propertyN` immediately descended from context items 133 | `(.property1 | .property2.property2_1.property2_1_1)` | locates `.property1`, `.property2.property2_1.property2_1_1` immediately descended from context items 134 | 135 | Location paths can be absolute or relative. 136 | If location path starts with the caret (`^`) you are using an absolute location path. This syntax is used to locate a property when another context is already used in the location path and/or the object predicates. 137 | 138 | Consider the following JSON: 139 | 140 | ```javascript 141 | var doc = { 142 | "books" : [ 143 | { 144 | "id" : 1, 145 | "title" : "Clean Code", 146 | "author" : { "name" : "Robert C. Martin" }, 147 | "price" : 17.96 148 | }, 149 | { 150 | "id" : 2, 151 | "title" : "Maintainable JavaScript", 152 | "author" : { "name" : "Nicholas C. Zakas" }, 153 | "price" : 10 154 | }, 155 | { 156 | "id" : 3, 157 | "title" : "Agile Software Development", 158 | "author" : { "name" : "Robert C. Martin" }, 159 | "price" : 20 160 | }, 161 | { 162 | "id" : 4, 163 | "title" : "JavaScript: The Good Parts", 164 | "author" : { "name" : "Douglas Crockford" }, 165 | "price" : 15.67 166 | } 167 | ] 168 | }; 169 | ``` 170 | 171 | #### Examples 172 | 173 | ```javascript 174 | // find all books authors 175 | JSPath.apply('.books.author', doc); 176 | /* [{ name : 'Robert C. Martin' }, { name : 'Nicholas C. Zakas' }, { name : 'Robert C. Martin' }, { name : 'Douglas Crockford' }] */ 177 | 178 | // find all books author names 179 | JSPath.apply('.books.author.name', doc); 180 | /* ['Robert C. Martin', 'Nicholas C. Zakas', 'Robert C. Martin', 'Douglas Crockford' ] */ 181 | 182 | // find all names in books 183 | JSPath.apply('.books..name', doc); 184 | /* ['Robert C. Martin', 'Nicholas C. Zakas', 'Robert C. Martin', 'Douglas Crockford' ] */ 185 | ``` 186 | 187 | ### Predicates 188 | 189 | JSPath predicates allow you to write very specific rules about items you'd like to select when constructing your path expression. Predicates are filters that restrict the items selected by the location path. There are two possible types of predicates: [object](#object-predicates) and [positional predicates](#positional-predicates). 190 | 191 | ### Object predicates 192 | 193 | Object predicates can be used in a path expression to filter a subset of items according to boolean expressions working on the properties of each item. All object predicates are parenthesized by curly brackets (`{` and `}`). 194 | 195 | In JSPath these basic expressions can be used inside an object predicate: 196 | * numeric literals (e.g. `1.23`) 197 | * string literals (e.g. `"John Gold"`) 198 | * boolean literals (`true` and `false`) 199 | * null literal (`null`) 200 | * subpaths (e.g. `.nestedProp.deeplyNestedProp`) 201 | * nested predicates (e.g. `.prop{.nestedProp{.deeplyNestedProp{.stillMore || .yetAnother} || .otherDeeplyNested}}` 202 | 203 | Furthermore, the following types of operators are valid inside an object predicate: 204 | * [comparison operators](#comparison-operators) 205 | * [string comparison operators](#string-comparison-operators) 206 | * [logical operators](#logical-operators) 207 | * [arithmetic operators](#arithmetic-operators) 208 | 209 | #### Comparison operators 210 | 211 | operator | description | example 212 | ------------- | ------------- | ------------- 213 | `==` | returns `true` if both operands are equal | `.books{.id == "1"}` 214 | `===` | returns `true` if both operands are strictly equal with no type conversion | `.books{.id === 1}` 215 | `!=` | returns `true` if the operands are not equal | `.books{.id != "1"}` 216 | `!==` | returns `true` if the operands are not equal and_or not of the same data type | `.books{.id !== 1}` 217 | `>` | returns `true` if the left operand is greater than the right operand | `.books{.id > 1}` 218 | `>=` | returns `true` if the left operand is greater than or equal to the right operand | `.books{.id >= 1}` 219 | `<` | returns `true` if the left operand is smaller than the right operand | `.books{.id < 1}` 220 | `<=` | returns `true` if the left operand is smaller than or equal to the right operand | `.books{.id <= 1}` 221 | 222 | JSPath uses the following rules to compare arrays and objects of different types: 223 | * if both operands to be compared are arrays, then the comparison will be 224 | true if there is an element in the first array and an element in the 225 | second array such that the result of performing the comparison of two elements is true 226 | * if one operand is array and another is not, then the comparison will be true if there is element in 227 | array such that the result of performing the comparison of element and another operand is true 228 | * primitives to be compared as usual javascript primitives 229 | 230 | #### String comparison operators 231 | 232 | If both operands are strings, there're also available additional comparison operators: 233 | 234 | operator | description | example 235 | ------------- | ------------- | ------------- 236 | `==` | returns `true` if both strings are equal | `.books{.title == "clean code"}` 237 | `^==` | case sensitive; returns `true` if the left operand starts with the right operand | `.books{.title ^== "Javascript"}` 238 | `^=` | case insensitive; returns `true` if the left operand starts with the right operand | `.books{.title ^= "javascript"}` 239 | `==^` | case sensitive; returns `true` if the right operand starts with the left operand | `.books{.title ==^ "Javascript"}` 240 | `=^` | case insensitive; returns `true` if the right operand starts with the left operand | `.books{.title =^ "javascript"}` 241 | `$==` | case sensitive; returns `true` if the left operand ends with the right operand | `.books{.title $== "Javascript"}` 242 | `$=` | case insensitive; returns `true` if the left operand ends with the right operand | `.books{.title $= "javascript"}` 243 | `==$` | case sensitive; returns `true` if the right operand ends with the left operand | `.books{.title ==$ "Javascript"}` 244 | `=$` | case insensitive; returns `true` if the right operand ends with the left operand | `.books{.title =$ "javascript"}` 245 | `*==` | case sensitive; returns `true` if the left operand contains right operand | `.books{.title *== "Javascript"}` 246 | `*=` | case insensitive; returns `true` if the left operand contains right operand | `.books{.title *= "javascript"}` 247 | `==*` | case sensitive; returns `true` if the right operand contains left operand | `.books{.title ==* "Javascript"}` 248 | `=*` | case insensitive; returns `true` if the right operand contains left operand | `.books{.title =* "javascript"}` 249 | 250 | #### Logical operators 251 | 252 | operator | description | example 253 | ------------- | ---------------------------------------------------- | ------------- 254 | `&&` | returns `true` if both operands are `true` | `.books{.price > 19 && .author.name === "Robert C. Martin"}` 255 | `\|\|` | returns `true` if either or both operands are `true` | `.books{.title === "Maintainable JavaScript" \|\| .title === "Clean Code"}` 256 | `!` | returns `true` if operand is false | `.books{!.title}` 257 | 258 | In JSPath logical operators convert their operands to boolean values using following rules: 259 | 260 | * if an operand is an array with a length greater than `0` the result will be `true` else `false` 261 | * a casting with double NOT javascript operator (`!!`) is used in any other cases 262 | 263 | #### Arithmetic operators 264 | 265 | operator | description 266 | ------------ | ------------- 267 | `+` | addition 268 | `-` | subtraction 269 | `*` | multiplication 270 | `/` | division 271 | `%` | modulus 272 | 273 | #### Operator precedence 274 | 275 | precedence | operator 276 | ------------ | ------------- 277 | 1 (highest) | `!`, unary `-` 278 | 2 | `*`, `/`, `%` 279 | 3 | `+`, binary `-` 280 | 4 | `<`, `<=`, `>`, `>=` 281 | 5 | `==`, `===`, `!=`, `!==`, `^=`, `^==`, `$==`, `$=`, `*=`, `*==`, `=^`, `==^`, `=$`, `==$`, `=*`, `==*` 282 | 6 | `&&` 283 | 7 (lowest) | `\|\|` 284 | 285 | Parentheses (`(` and `)`) are used to explicitly denote precedence by grouping parts of an expression that should be evaluated first. 286 | 287 | #### Examples 288 | 289 | ```javascript 290 | // find all book titles whose author is Robert C. Martin 291 | JSPath.apply('.books{.author.name === "Robert C. Martin"}.title', doc); 292 | /* ['Clean Code', 'Agile Software Development'] */ 293 | 294 | // find all book titles with price less than 17 295 | JSPath.apply('.books{.price < 17}.title', doc); 296 | /* ['Maintainable JavaScript', 'JavaScript: The Good Parts'] */ 297 | ``` 298 | 299 | ### Positional predicates 300 | 301 | Positional predicates allow you to filter items by their context position. All positional predicates are parenthesized by square brackets (`[` and `]`). 302 | 303 | JSPath supports four types of positional predicates – also known as slicing methods: 304 | 305 | operator | description | example 306 | ------------- | ------------- | ------------- 307 | `[index]` | returns item in context at index `index` – the first item has index `0`, positional predicates are zero-based | `[3]` returns fourth item in context 308 | `[start:]` | returns range of items whose index in context is greater or equal to `start` | `[2:]` returns items whose index is greater or equal to `2` 309 | `[:end]` | returns range of items whose index in context is smaller than `end` | `[:5]` returns first five items in context 310 | `[start:end]` | returns range of items whose index in context is greater or equal to `start` and smaller than `end` | `[2:5]` returns three items on the indices `2`, `3` and `4` 311 | 312 | `index`, `start` or `end` may be a negative number, which means JSPath counts from the end instead of the beginning: 313 | 314 | example | description 315 | -------- | ------------- 316 | `[-1]` | returns last item in context 317 | `[-3:]` | returns last three items in context 318 | 319 | 320 | #### Examples 321 | 322 | ```javascript 323 | // find first book title 324 | JSPath.apply('.books[0].title', doc); 325 | /* ['Clean Code'] */ 326 | 327 | // find first title of books 328 | JSPath.apply('.books.title[0]', doc); 329 | /* 'Clean Code' */ 330 | 331 | // find last book title 332 | JSPath.apply('.books[-1].title', doc); 333 | /* ['JavaScript: The Good Parts'] */ 334 | 335 | // find two first book titles 336 | JSPath.apply('.books[:2].title', doc); 337 | /* ['Clean Code', 'Maintainable JavaScript'] */ 338 | 339 | // find two last book titles 340 | JSPath.apply('.books[-2:].title', doc); 341 | /* ['Agile Software Development', 'JavaScript: The Good Parts'] */ 342 | 343 | // find two book titles from second position 344 | JSPath.apply('.books[1:3].title', doc); 345 | /* ['Maintainable JavaScript', 'Agile Software Development'] */ 346 | ``` 347 | 348 | ### Multiple predicates 349 | 350 | You can use more than one predicate – any combination of [object](#object-predicates) and [positional predicates](#positional-predicates). The result will contain only items that match all predicates. 351 | 352 | #### Examples 353 | 354 | ```javascript 355 | // find first book name whose price less than 15 and greater than 5 356 | JSPath.apply('.books{.price < 15}{.price > 5}[0].title', doc); 357 | /* ['Maintainable JavaScript'] */ 358 | ``` 359 | 360 | ### Nested predicates 361 | 362 | You can nest predicates as deeply as you like — saves having to repeat deep subpaths each time, shortening query length. Similar to JavaScript's "with" operator, all properties of the object become first-level properties inside the nested predicate. 363 | 364 | #### Examples 365 | 366 | ```javascript 367 | // long subpaths: find books by various authors, for under $20 368 | JSPath.apply('.books{.price < 20 && (.author.name *== "Zakas" || .author.name *== "Martin")}.title', doc); 369 | /* ['Clean Code', 'Maintainable JavaScript'] */ 370 | 371 | // nested predicates: same query, however ".author.name" isn't repeated. For JSON with many levels, enables much more compact queries. 372 | JSPath.apply('.books{.price < 20 && .author{.name *== "Zakas" || .name *== "Martin"}}.title', doc); 373 | /* ['Clean Code', 'Maintainable JavaScript'] */ 374 | ``` 375 | 376 | ### Substitutions 377 | 378 | Substitutions allow you to use runtime-evaluated values in predicates and pathes (as a path root). 379 | 380 | #### Examples 381 | 382 | ```javascript 383 | var path = '.books{.author.name === $author}.title'; 384 | 385 | // find book name whose author Nicholas C. Zakas 386 | JSPath.apply(path, doc, { author : 'Nicholas C. Zakas' }); 387 | /* ['Maintainable JavaScript'] */ 388 | 389 | // find books name whose authors Robert C. Martin or Douglas Crockford 390 | JSPath.apply(path, doc, { author : ['Robert C. Martin', 'Douglas Crockford'] }); 391 | /* ['Clean Code', 'Agile Software Development', 'JavaScript: The Good Parts'] */ 392 | ``` 393 | 394 | ### Result 395 | 396 | If the last predicate in an expression is a positional predicate using an index (e.g. `[0]`, `[5]`, `[-1]`), the result is the item at the specified index or `undefined` if the index is out of range. 397 | In any other cases the result of applying `JSPath.apply()` is always an array – empty (`[]`), if found nothing. 398 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | require('nodeunit').reporters.default.run(['test/test.js']); -------------------------------------------------------------------------------- /test/arithmetic-operators-test.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { path : '.books{.idx + 1 > 3}.idx', res : [3, 4] }, 3 | { path : '.books{.idx - 2 < 0}.idx', res : [0, 1] }, 4 | { path : '.books{.idx * 2 === 4}.idx', res : [2] }, 5 | { path : '.books{.idx / 2 > 1.1}.idx', res : [3, 4] }, 6 | { path : '.books{.idx % 2 === 1}.idx', res : [1, 3] }, 7 | { path : '.books{.idx % 2 === 1 || .idx % 3 === 1}.idx', res : [1, 3, 4] }, 8 | { path : '.books{.idx + 1 + 2 === 3}.idx', res : [0] }, 9 | { path : '.books{.idx + 2 * 3 === 7}.idx', res : [1] }, 10 | { path : '.books{.idx * 2 + 3 === 7}.idx', res : [2] }, 11 | { path : '.books{.idx * (2 + 3) === 10}.idx', res : [2] }, 12 | { path : '.books{.idx * 2 / 3 === 2}.idx', res : [3] }, 13 | { path : '.books{.idx * 2 + 3 * .idx + 2 * 1 === 17}.idx', res : [3] }, 14 | { path : '.books{.idx * 2 * 3 === 6}.idx', res : [1] }, 15 | { path : '.books{.idx * 2 / 3 === 2 && .idx * (4 - 2) === (2 * .idx)}.idx', res : [3] }, 16 | { path : '.books{.idx - 5 + 5 === 0}.idx', res : [0] } 17 | ]; 18 | -------------------------------------------------------------------------------- /test/comparison-operators-test.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { path : '.books{.author.name === "Robert c. Martin"}.idx', res : [] }, 3 | { path : '.books{.author.name == "Robert c. Martin"}.idx', res : [0, 2] }, 4 | { path : '.books{.author.name == "John Resig"}', res : [] }, 5 | { path : '.books{.favorite === true}.idx', res : [1] }, 6 | { path : '.books{.idx != "1"}.idx', res : [0, 2, 3, 4] }, 7 | { path : '.books{.idx !== "1"}.idx', res : [0, 1, 2, 3, 4] }, 8 | { path : '.books{.name ^= "Javascript"}.idx', res : [3, 4] }, 9 | { path : '.books{.name ^== "Javascript"}.idx', res : [] }, 10 | { path : '.books{.name ^== "JavaScript"}.idx', res : [3, 4] }, 11 | { path : '.books{.name =^ "Clean code test"}.idx', res : [0] }, 12 | { path : '.books{.name ==^ "Clean code test"}.idx', res : [] }, 13 | { path : '.books{.name ==^ "Clean Code test"}.idx', res : [0] }, 14 | { path : '.{.a === null}', data : [{ a : 1 }, { a : null }], res : [{ a : null }] }, 15 | { path : '.{.a !== null}', data : [{ a : 1 }, { a : null }], res : [{ a : 1 }] }, 16 | { path : '.{.a ^= "test"}', data : { a : null }, res : [] }, 17 | { path : '.{.a =^ "test"}', data : { a : null }, res : [] }, 18 | { path : '.books{.name $= "Javascript"}.idx', res : [1] }, 19 | { path : '.books{.name $== "Javascript"}.idx', res : [] }, 20 | { path : '.books{.name $== "JavaScript"}.idx', res : [1] }, 21 | { path : '.{.a $= "test"}', data : { a : null }, res : [] }, 22 | { path : '.books{.name =$ "Super Clean code"}.idx', res : [0] }, 23 | { path : '.books{.name ==$ "Super Clean code"}.idx', res : [] }, 24 | { path : '.books{.name ==$ "Super Clean Code"}.idx', res : [0] }, 25 | { path : '.{.a =$ "test"}', data : { a : null }, res : [] }, 26 | { path : '.books{.name *= "Javascript"}.idx', res : [1, 3, 4] }, 27 | { path : '.books{.name *== "Javascript"}.idx', res : [] }, 28 | { path : '.books{.name *== "JavaScript"}.idx', res : [1, 3, 4] }, 29 | { path : '.books{.name =* "Super clean code test"}.idx', res : [0] }, 30 | { path : '.books{.name ==* "Super clean code test"}.idx', res : [] }, 31 | { path : '.books{.name ==* "Super Clean Code test"}.idx', res : [0] }, 32 | { path : '.books.keywords{. $= "cript"}', res : ['javascript', 'javascript', 'javascript'] }, 33 | { path : '.books.keywords{. $== "cript"}', res : ['javascript', 'javascript', 'javascript'] }, 34 | { path : '.{.a *= "test"}', data : { a : null }, res : [] }, 35 | { path : '.booksCount{. > 4}', res : [5] }, 36 | { path : '.booksCount{. > 10}', res : [] }, 37 | { path : '.booksCount{. >= 5}', res : [5] }, 38 | { path : '.booksCount{. == 5}', res : [5] }, 39 | { path : '.booksCount{. === "5"}', res : [] }, 40 | { path : '.booksCount{. === 5}', res : [5] }, 41 | { path : '.books{.price > 16}.idx', res : [0, 2, 4] }, 42 | { path : '.books{.price > 17.97}.idx', res : [2, 4] }, 43 | { path : '.books{.price < 16}.idx', res : [1, 3] }, 44 | { path : '.books{.price == 10}.idx', res : [1] }, 45 | { path : '.books{.price <= 10}.idx', res : [1] }, 46 | { path : '.books{.price >= 10}.idx', res : [0, 1, 2, 3, 4] }, 47 | { path : '.books{.oldPrices > .price}.idx', res : [1, 4] }, 48 | { path : '.books{.oldPrices === .price}.idx', res : [1] }, 49 | { path : '.books{.keywords === "javascript"}.idx', res : [1, 3 ,4] }, 50 | { path : '.books{.keywords === \'javascript\'}.idx', res : [1, 3 ,4] } 51 | ]; 52 | -------------------------------------------------------------------------------- /test/concat-expressions-test.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { path : '.(.name | .booksCount)', res : ['books and authors', 5]}, 3 | { path : '.books(.idx{. === 1} | .idx{. === 2})', res : [1, 2]}, 4 | { path : '.books(.idx{. === 3} | (((.author[0].name)) | .author[0].name))', res : [3, 'Robert C. Martin', 'Robert C. Martin']}, 5 | { path : '.name | .booksCount', res : ['books and authors', 5]}, 6 | { path : '(.name | .booksCount)', res : ['books and authors', 5]}, 7 | { path : '.name | (.booksCount | .booksCount)', res : ['books and authors', 5, 5]}, 8 | { path : '(.name | .booksCount)[0]', res : 'books and authors'}, 9 | { path : '.name | .booksCount[0]', res : ['books and authors', 5]} 10 | ]; -------------------------------------------------------------------------------- /test/data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name : 'books and authors', 3 | info : { 4 | description : 'books about javascript', 5 | content : 'content' 6 | }, 7 | booksCount : 5, 8 | books : [ 9 | { 10 | idx : 0, 11 | name : 'Clean Code', 12 | author : { name : 'Robert C. Martin' }, 13 | keywords : ['code', 'refactoring'], 14 | price : 17.96, 15 | oldPrices : [10, 15] 16 | }, 17 | { 18 | idx : 1, 19 | favorite : true, 20 | name : 'Maintainable JavaScript', 21 | author : { name : 'Nicholas C. Zakas' }, 22 | keywords : ['javascript', 'code', 'patterns'], 23 | price : 10, 24 | oldPrices : [20, 10, 5] 25 | }, 26 | { 27 | idx : 2, 28 | name : 'Agile Software Development', 29 | favorite : false, 30 | author : { name : 'Robert C. Martin' }, 31 | keywords : ['agile', 'c#'], 32 | price : 20, 33 | oldPrices : [] 34 | }, 35 | { 36 | idx : 3, 37 | name : 'JavaScript: The Good Parts', 38 | author : { name : 'Douglas Crockford' }, 39 | keywords : ['javascript', 'code'], 40 | description : { 41 | notes : 'must read' 42 | }, 43 | price : 15.67, 44 | oldPrices : [12.23, 15] 45 | }, 46 | { 47 | idx : 4, 48 | name : 'JavaScript: The Good Parts', 49 | author : { name : 'Douglas Crockford' }, 50 | keywords : ['javascript', 'code'], 51 | price : 25, 52 | oldPrices : [50, 30.33, 27] 53 | } 54 | ], 55 | authors : [ 56 | { name : 'Nicholas C. Zakas', skills : ['javascript', 'php'] }, 57 | { name : 'Douglas Crockford' }, 58 | { name : 'Robert C. Martin' }, 59 | { name : 'John Resig' } 60 | ] 61 | }; -------------------------------------------------------------------------------- /test/descendants-test.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { path : '..', data : [[[['a'], { b : 'b' }]], 'c'], res : ['a', { b : 'b' }, 'b', 'c']}, 3 | { path : '..id', res : [1, 2], data : [[[[{ id : 1, data : [[[{ id : 2 }]]]}]]]]}, 4 | { path : '..name', res : [ 5 | 'books and authors', 'Clean Code', 'Robert C. Martin', 'Maintainable JavaScript', 6 | 'Nicholas C. Zakas', 'Agile Software Development', 'Robert C. Martin', 'JavaScript: The Good Parts', 7 | 'Douglas Crockford', 'JavaScript: The Good Parts', 'Douglas Crockford', 'Nicholas C. Zakas', 8 | 'Douglas Crockford', 'Robert C. Martin', 'John Resig' 9 | ]}, 10 | { path : '.books{..name{. === "Douglas Crockford"}}.idx', res : [3, 4]}, 11 | { 12 | path : '..', 13 | data : { object : { object : { object : 'object' }, arr : [[{ object : ['arr-object', { arr : 'object' }] }]]}}, 14 | res : [ 15 | { object : { object : { object : 'object' }, arr : [[{ object : ['arr-object', { arr : 'object' }] }]]}}, 16 | { object : { object : 'object' }, arr : [[{ object : ['arr-object', { arr : 'object' }] }]]}, 17 | { object : 'object' }, 18 | 'object', 19 | { object : ['arr-object', { arr : 'object' }] }, 20 | 'arr-object', 21 | { arr : 'object' }, 22 | 'object' 23 | ] 24 | }, 25 | { 26 | path : '..object', 27 | data : { object : { object : { object : 'object' }, arr : [[{ object : 'arr-object' }]]}}, 28 | res : [ 29 | { object : { object : 'object' }, arr : [[{ object : 'arr-object' }]]}, 30 | { object : 'object' }, 31 | 'object', 32 | 'arr-object' 33 | ] 34 | }, 35 | { 36 | path : '.authors..', 37 | res : [ 38 | { name : 'Nicholas C. Zakas', skills : ['javascript', 'php'] }, 39 | 'Nicholas C. Zakas', 40 | 'javascript', 41 | 'php', 42 | { name : 'Douglas Crockford' }, 43 | 'Douglas Crockford', 44 | { name : 'Robert C. Martin' }, 45 | 'Robert C. Martin', 46 | { name : 'John Resig' }, 47 | 'John Resig' 48 | ] 49 | }, 50 | { 51 | path : '.authors..*', 52 | res : [ 53 | 'Nicholas C. Zakas', 54 | 'javascript', 55 | 'php', 56 | 'Douglas Crockford', 57 | 'Robert C. Martin', 58 | 'John Resig' 59 | ] 60 | } 61 | ]; -------------------------------------------------------------------------------- /test/logical-expressions-test.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { path : '.books{.keywords === "javascript" || .keywords === "c#"}.idx', res : [1, 2, 3, 4]}, 3 | { path : '.books{.keywords === "javascript" || .keywords === "c#" || .keywords === "code"}.idx', res : [0, 1, 2, 3, 4]}, 4 | { path : '.books{.keywords === "javascript" && .keywords === "code"}.idx', res : [1, 3, 4]}, 5 | { path : '.books{.keywords === "javascript" && .keywords === "code" && .keywords === "patterns"}.idx', res : [1]}, 6 | { path : '.books{!.description}.idx', res : [0, 1, 2, 4]}, 7 | { path : '.books{!.description && .keywords == "refactoring"}.idx', res : [0]}, 8 | { path : '.books{!.description || !(.price > 15)}.idx', res : [0, 1, 2, 4]}, 9 | { path : '.books{.description || .price > 15 && .keywords == "javascript"}.idx', res : [3, 4]}, 10 | { path : '.books{(.keywords === "javascript" && .keywords === "patterns") || .keywords === "agile"}.idx', res : [1, 2]}, 11 | { path : '.books{(.keywords === "javascript" || .keywords === "code") && .favorite === true}.idx', res : [1]}, 12 | { path : '.books{!(.keywords === "javascript")}.idx', res : [0, 2]} 13 | ]; 14 | -------------------------------------------------------------------------------- /test/multi-predicates-test.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { path : '.books{.price > 10}{.price < 20}.idx', res : [0, 3]}, 3 | { path : '.books[1:]{.price > 10}{.price < 20}.idx', res : [3]}, 4 | { path : '.books{.price > 10}[0].idx', res : [0] }, 5 | { path : '.books.idx[0]{. === 0}', res : [0] }, 6 | { path : '(.books.keywords[1])[0]', res : 'refactoring' } 7 | ]; 8 | -------------------------------------------------------------------------------- /test/nested-predicates-test.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { path : '.books..{.price<20 && (.author.name*=="Zakas" || .author.name*=="Martin")}.name', 3 | res : ["Clean Code", "Maintainable JavaScript"]}, 4 | { path : '.books..{.price<20 && .author{.name*=="Zakas" || .name*=="Martin"}}.name', 5 | res : ["Clean Code", "Maintainable JavaScript"]}, 6 | ]; 7 | -------------------------------------------------------------------------------- /test/object-predicates-test.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { path : '.{.{.}}', data : 'test', res : ['test']}, 3 | { path : '.{.data}', data : { data : 0 }, res : [{ data : 0 }]}, 4 | { path : '.{.data}', data : { data : false }, res : [{ data : false }]}, 5 | { path : '.{.data}', data : [{ data : false }], res : [{ data : false }]}, 6 | { path : '.', data : null, res : [null] }, 7 | { path : '.data', data : [{ data : 1 }, { data : null }, { data : 3 }], res : [1, null, 3] }, 8 | { path : '.info{.description}', res : [{ description : 'books about javascript', content : 'content' }]}, 9 | { path : '.info{.noexists}', res : []}, 10 | { path : '.books{.favorite}.idx', res : [1, 2]}, 11 | { path : '.books{.author.name}.idx', res : [0, 1, 2, 3, 4]}, 12 | { path : '.books{.description.notes}.idx', res : [3]}, 13 | { path : '.books{.author.noexists}.idx', res : []} 14 | ]; 15 | -------------------------------------------------------------------------------- /test/parser-errors-test.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { path : '', message : 'Unexpected end of path' }, 3 | { path : 'id', message : 'Unexpected token "id"' }, 4 | { path : '.#', message : 'Unexpected token "#"' }, 5 | { path : '.[.] id', message : 'Unexpected token "id"' }, 6 | { path : '{}', message : 'Unexpected token "{"' }, 7 | { path : '[]', message : 'Unexpected token "["' }, 8 | { path : '.{.a > }', message : 'Unexpected token "}"' }, 9 | { path : '.{.a >', message : 'Unexpected end of path' }, 10 | { path : '.(.a || .b)', message : 'Unexpected token "||"' }, 11 | { path : '.[:2:]', message : 'Unexpected token ":"' }, 12 | { path : '.[1:2:3]', message : 'Unexpected token ":"' }, 13 | { path : '.{.2.3}', message : 'Unexpected token "."' }, 14 | { path : '."aaa', message : 'Unexpected end of path' }, 15 | { path : '.\'aaa', message : 'Unexpected end of path' }, 16 | { path : '.\'aaa"', message : 'Unexpected end of path' }, 17 | { path : '."aaa\'', message : 'Unexpected end of path' } 18 | ]; -------------------------------------------------------------------------------- /test/path-test.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { path : '.', data : [{ test : 'test' }], res : [{ test : 'test' }]}, 3 | { path : '.', data : 'test', res : ['test']}, 4 | { path : '.', data : 5, res : [5]}, 5 | { path : '.', data : true, res : [true]}, 6 | { path : '.', data : [], res : []}, 7 | { path : '.data', data : { data : []}, res : []}, 8 | { path : '.data', data : [{ data : []}], res : []}, 9 | { path : '.data', data : [{ data : [1]}], res : [1]}, 10 | { path : '.name', res : ['books and authors']}, 11 | { path : '.noexists', res : []}, 12 | { path : '."test.test"', data : { 'test.test' : 'test' }, res : ['test']}, 13 | { path : '.\'test.test\'', data : { 'test.test' : 'test' }, res : ['test']}, 14 | { path : '."te\\"st"', data : { 'te"st' : 'test' }, res : ['test']}, 15 | { path : '.authors.name', res : ['Nicholas C. Zakas', 'Douglas Crockford', 'Robert C. Martin', 'John Resig']}, 16 | { path : '.books.author.name', res : ['Robert C. Martin', 'Nicholas C. Zakas', 'Robert C. Martin', 'Douglas Crockford', 'Douglas Crockford']}, 17 | { path : '.info.*', res : ['books about javascript', 'content']}, 18 | { path : '.data', data : [{ data : [1, 2]}, { data : 3}, { data : [4, 5] }, { data : 6 }], res : [1, 2, 3, 4, 5, 6]}, 19 | { path : '.*', data : [{ data : [1, 2]}, { data : 3}, { data : [4, 5] }, { data : 6 }], res : [1, 2, 3, 4, 5, 6]}, 20 | { path : '.data.*', data : [{ data : [1, 2]}, { data : [[3, 4]]}, { data : [[[5, 6]]] }], res : [3, 4, [5, 6]]}, 21 | { path : '.data.*.*', data : [{ data : [1, 2]}, { data : [[3, 4]]}, { data : [[[5, 6]]] }], res : [5, 6]}, 22 | { path : '.data.*', data : { data : 'string' }, res : []}, 23 | { path : '((.name))', res : ['books and authors']} 24 | ]; 25 | -------------------------------------------------------------------------------- /test/positional-predicates-test.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { path : '.books[0].idx', res : [0]}, 3 | { path : '.books[2].idx', res : [2]}, 4 | { path : '.books[6].idx', res : []}, 5 | { path : '.books[-1].idx', res : [4]}, 6 | { path : '.books[2:].idx', res : [2, 3, 4]}, 7 | { path : '.books[10:].idx', res : [] }, 8 | { path : '.books[-2:].idx', res : [3, 4]}, 9 | { path : '.books[1:3].idx', res : [1, 2]}, 10 | { path : '.books[10:20].idx', res : [] }, 11 | { path : '.books[2:-1].idx', res : [2, 3]}, 12 | { path : '.books[-3:-1].idx', res : [2, 3]}, 13 | { path : '.books[:2].idx', res : [0, 1]}, 14 | { path : '.books[:-2].idx', res : [0, 1, 2]}, 15 | { path : '.books[:0].idx', res : []}, 16 | { path : '.books[1:][0].idx', res : [1]}, 17 | { path : '.booksCount[10][10].idx', res : []}, 18 | { path : '.books[^.books[3].idx].idx', res : [3]}, 19 | { path : '.books[1].keywords[1:]', res : ['code', 'patterns']}, 20 | { path : '.books.idx[0]', res : 0 }, 21 | { path : '.books.idx[20]', res : undefined }, 22 | { path : '(.name)[0]', res : 'books and authors' } 23 | ]; -------------------------------------------------------------------------------- /test/root-test.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { path : '^.name', res : ['books and authors']}, 3 | { path : '.name{^.name === "books and authors"}', res : ['books and authors']}, 4 | { path : '.authors{.name === ^.books.author.name}.name', res : ['Nicholas C. Zakas', 'Douglas Crockford', 'Robert C. Martin']}, 5 | { path : '.authors{!(.name === ^.books.author.name)}.name', res : ['John Resig']} 6 | ]; -------------------------------------------------------------------------------- /test/substs-test.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { path : '.books{.idx === $idx}.idx', substs : { idx : 1 }, res : [1]}, 3 | { path : '.books{.idx === $idx}.idx', substs : { idx : "1" }, res : [] }, 4 | { path : '.books{.idx == $idx}.idx', substs : { idx : "1" }, res : [1]}, 5 | { path : '.books{.idx === $idx}.idx', substs : { idx : [1, 2] }, res : [1, 2]}, 6 | { path : '.books{.oldPrices === $prices}.idx', substs : { prices : [10, 15] }, res : [0, 1, 3]}, 7 | { path : '.books[$idx].idx', substs : { idx : 1 }, res : [1] }, 8 | { path : '.books[$idx[0]].idx', substs : { idx : 1 }, res : [1] }, 9 | { path : '.books[$idx.nested].idx', substs : { idx : { nested : 1 } }, res : [1] }, 10 | { path : '.books[$idxFrom:$idxTo].idx', substs : { idxFrom : 1, idxTo : 3 }, res : [1, 2]}, 11 | { path : '$idx', substs : { idx : 1 }, res : [1]}, 12 | { path : '$idx..b', substs : { idx : { a : { b : 3 } } }, res : [3]} 13 | ]; 14 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var jspath = require('../index'), 2 | testCounter = 0; 3 | 4 | // parser tests 5 | require('./parser-errors-test').forEach(function(desc) { 6 | exports[++testCounter + '. ' + desc.path] = function(test) { 7 | test.throws( 8 | function() { 9 | jspath.apply(desc.path, {}); 10 | }, 11 | function(err) { 12 | return err.message === desc.message; 13 | }); 14 | test.done(); 15 | }; 16 | }); 17 | 18 | 19 | // applying tests 20 | var testData = require('./data'), 21 | testDataCopy = JSON.parse(JSON.stringify(testData)), 22 | tests = [ 23 | 'path', 24 | 'descendants', 25 | 'object-predicates', 26 | 'positional-predicates', 27 | 'multi-predicates', 28 | 'nested-predicates', 29 | 'comparison-operators', 30 | 'arithmetic-operators', 31 | 'logical-expressions', 32 | 'concat-expressions', 33 | 'undefined-and-null', 34 | 'root', 35 | 'substs' 36 | ].reduce( 37 | function(tests, name) { 38 | return tests.concat(require('./' + name + '-test')); 39 | }, 40 | []); 41 | 42 | 43 | tests.forEach(function(testItem) { 44 | exports[++testCounter + '. ' + testItem.path] = function(test) { 45 | test.deepEqual( 46 | jspath( 47 | testItem.path, 48 | 'data' in testItem? testItem.data : testData, 49 | testItem.substs), 50 | testItem.res); 51 | test.done(); 52 | }; 53 | }); 54 | 55 | exports[++testCounter + '. jspath.apply should be alias to jspath'] = function(test) { 56 | test.strictEqual(jspath, jspath.apply); 57 | test.done(); 58 | }; 59 | 60 | // immutable test 61 | exports[++testCounter + '. immutable data'] = function(test) { 62 | test.deepEqual(testData, testDataCopy); 63 | test.done(); 64 | }; 65 | -------------------------------------------------------------------------------- /test/undefined-and-null-test.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { path : '.', data : null, res : [null]}, 3 | { path : '.', data : [null, { test : [null] }], res : [null, { test : [null] }]}, 4 | { path : '.', data : undefined, res : [undefined]}, 5 | { path : '.', data : [undefined, { test : [undefined] }], res : [undefined, { test : [undefined] }]}, 6 | { path : '.item.idx', data : [{ item : { idx : 0 }}, null], res : [0]}, 7 | { path : '..item..idx', data : [{ item : { idx : 0 }}, null], res : [0]}, 8 | { path : '.item.idx', data : [{ item : { idx : 0 }}, undefined], res : [0]}, 9 | { path : '..item..idx', data : [{ item : { idx : 0 }}, undefined], res : [0]} 10 | ]; --------------------------------------------------------------------------------