css-tree.js 103 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773
  1. /*
  2. * Copyright (C) 2016 by Roman Dvornov
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. * THE SOFTWARE.
  21. */
  22. // derived from https://github.com/csstree/csstree
  23. this.cssTree = this.cssTree || (() => {
  24. function createItem(data) {
  25. return {
  26. prev: null,
  27. next: null,
  28. data: data
  29. };
  30. }
  31. function allocateCursor(node, prev, next) {
  32. let cursor;
  33. if (cursors !== null) {
  34. cursor = cursors;
  35. cursors = cursors.cursor;
  36. cursor.prev = prev;
  37. cursor.next = next;
  38. cursor.cursor = node.cursor;
  39. } else {
  40. cursor = {
  41. prev: prev,
  42. next: next,
  43. cursor: node.cursor
  44. };
  45. }
  46. node.cursor = cursor;
  47. return cursor;
  48. }
  49. function releaseCursor(node) {
  50. const cursor = node.cursor;
  51. node.cursor = cursor.cursor;
  52. cursor.prev = null;
  53. cursor.next = null;
  54. cursor.cursor = cursors;
  55. cursors = cursor;
  56. }
  57. let cursors = null;
  58. function List() {
  59. this.cursor = null;
  60. this.head = null;
  61. this.tail = null;
  62. }
  63. List.createItem = createItem;
  64. List.prototype.createItem = createItem;
  65. List.prototype.updateCursors = function (prevOld, prevNew, nextOld, nextNew) {
  66. let cursor = this.cursor;
  67. while (cursor !== null) {
  68. if (cursor.prev === prevOld) {
  69. cursor.prev = prevNew;
  70. }
  71. if (cursor.next === nextOld) {
  72. cursor.next = nextNew;
  73. }
  74. cursor = cursor.cursor;
  75. }
  76. };
  77. List.prototype.getSize = function () {
  78. let size = 0;
  79. let cursor = this.head;
  80. while (cursor) {
  81. size++;
  82. cursor = cursor.next;
  83. }
  84. return size;
  85. };
  86. List.prototype.fromArray = function (array) {
  87. let cursor = null;
  88. this.head = null;
  89. for (let i = 0; i < array.length; i++) {
  90. const item = createItem(array[i]);
  91. if (cursor !== null) {
  92. cursor.next = item;
  93. } else {
  94. this.head = item;
  95. }
  96. item.prev = cursor;
  97. cursor = item;
  98. }
  99. this.tail = cursor;
  100. return this;
  101. };
  102. List.prototype.toArray = function () {
  103. let cursor = this.head;
  104. const result = [];
  105. while (cursor) {
  106. result.push(cursor.data);
  107. cursor = cursor.next;
  108. }
  109. return result;
  110. };
  111. List.prototype.toJSON = List.prototype.toArray;
  112. List.prototype.isEmpty = function () {
  113. return this.head === null;
  114. };
  115. List.prototype.first = function () {
  116. return this.head && this.head.data;
  117. };
  118. List.prototype.last = function () {
  119. return this.tail && this.tail.data;
  120. };
  121. List.prototype.each = function (fn, context) {
  122. let item;
  123. if (context === undefined) {
  124. context = this;
  125. }
  126. // push cursor
  127. const cursor = allocateCursor(this, null, this.head);
  128. while (cursor.next !== null) {
  129. item = cursor.next;
  130. cursor.next = item.next;
  131. fn.call(context, item.data, item, this);
  132. }
  133. // pop cursor
  134. releaseCursor(this);
  135. };
  136. List.prototype.forEach = List.prototype.each;
  137. List.prototype.eachRight = function (fn, context) {
  138. let item;
  139. if (context === undefined) {
  140. context = this;
  141. }
  142. // push cursor
  143. const cursor = allocateCursor(this, this.tail, null);
  144. while (cursor.prev !== null) {
  145. item = cursor.prev;
  146. cursor.prev = item.prev;
  147. fn.call(context, item.data, item, this);
  148. }
  149. // pop cursor
  150. releaseCursor(this);
  151. };
  152. List.prototype.forEachRight = List.prototype.eachRight;
  153. List.prototype.nextUntil = function (start, fn, context) {
  154. if (start === null) {
  155. return;
  156. }
  157. let item;
  158. if (context === undefined) {
  159. context = this;
  160. }
  161. // push cursor
  162. const cursor = allocateCursor(this, null, start);
  163. while (cursor.next !== null) {
  164. item = cursor.next;
  165. cursor.next = item.next;
  166. if (fn.call(context, item.data, item, this)) {
  167. break;
  168. }
  169. }
  170. // pop cursor
  171. releaseCursor(this);
  172. };
  173. List.prototype.prevUntil = function (start, fn, context) {
  174. if (start === null) {
  175. return;
  176. }
  177. let item;
  178. if (context === undefined) {
  179. context = this;
  180. }
  181. // push cursor
  182. const cursor = allocateCursor(this, start, null);
  183. while (cursor.prev !== null) {
  184. item = cursor.prev;
  185. cursor.prev = item.prev;
  186. if (fn.call(context, item.data, item, this)) {
  187. break;
  188. }
  189. }
  190. // pop cursor
  191. releaseCursor(this);
  192. };
  193. List.prototype.some = function (fn, context) {
  194. let cursor = this.head;
  195. if (context === undefined) {
  196. context = this;
  197. }
  198. while (cursor !== null) {
  199. if (fn.call(context, cursor.data, cursor, this)) {
  200. return true;
  201. }
  202. cursor = cursor.next;
  203. }
  204. return false;
  205. };
  206. List.prototype.map = function (fn, context) {
  207. const result = new List();
  208. let cursor = this.head;
  209. if (context === undefined) {
  210. context = this;
  211. }
  212. while (cursor !== null) {
  213. result.appendData(fn.call(context, cursor.data, cursor, this));
  214. cursor = cursor.next;
  215. }
  216. return result;
  217. };
  218. List.prototype.filter = function (fn, context) {
  219. const result = new List();
  220. let cursor = this.head;
  221. if (context === undefined) {
  222. context = this;
  223. }
  224. while (cursor !== null) {
  225. if (fn.call(context, cursor.data, cursor, this)) {
  226. result.appendData(cursor.data);
  227. }
  228. cursor = cursor.next;
  229. }
  230. return result;
  231. };
  232. List.prototype.clear = function () {
  233. this.head = null;
  234. this.tail = null;
  235. };
  236. List.prototype.copy = function () {
  237. const result = new List();
  238. let cursor = this.head;
  239. while (cursor !== null) {
  240. result.insert(createItem(cursor.data));
  241. cursor = cursor.next;
  242. }
  243. return result;
  244. };
  245. List.prototype.prepend = function (item) {
  246. // head
  247. // ^
  248. // item
  249. this.updateCursors(null, item, this.head, item);
  250. // insert to the beginning of the list
  251. if (this.head !== null) {
  252. // new item <- first item
  253. this.head.prev = item;
  254. // new item -> first item
  255. item.next = this.head;
  256. } else {
  257. // if list has no head, then it also has no tail
  258. // in this case tail points to the new item
  259. this.tail = item;
  260. }
  261. // head always points to new item
  262. this.head = item;
  263. return this;
  264. };
  265. List.prototype.prependData = function (data) {
  266. return this.prepend(createItem(data));
  267. };
  268. List.prototype.append = function (item) {
  269. return this.insert(item);
  270. };
  271. List.prototype.appendData = function (data) {
  272. return this.insert(createItem(data));
  273. };
  274. List.prototype.insert = function (item, before) {
  275. if (before !== undefined && before !== null) {
  276. // prev before
  277. // ^
  278. // item
  279. this.updateCursors(before.prev, item, before, item);
  280. if (before.prev === null) {
  281. // insert to the beginning of list
  282. if (this.head !== before) {
  283. throw new Error("before doesn\"t belong to list");
  284. }
  285. // since head points to before therefore list doesn"t empty
  286. // no need to check tail
  287. this.head = item;
  288. before.prev = item;
  289. item.next = before;
  290. this.updateCursors(null, item);
  291. } else {
  292. // insert between two items
  293. before.prev.next = item;
  294. item.prev = before.prev;
  295. before.prev = item;
  296. item.next = before;
  297. }
  298. } else {
  299. // tail
  300. // ^
  301. // item
  302. this.updateCursors(this.tail, item, null, item);
  303. // insert to the ending of the list
  304. if (this.tail !== null) {
  305. // last item -> new item
  306. this.tail.next = item;
  307. // last item <- new item
  308. item.prev = this.tail;
  309. } else {
  310. // if list has no tail, then it also has no head
  311. // in this case head points to new item
  312. this.head = item;
  313. }
  314. // tail always points to new item
  315. this.tail = item;
  316. }
  317. return this;
  318. };
  319. List.prototype.insertData = function (data, before) {
  320. return this.insert(createItem(data), before);
  321. };
  322. List.prototype.remove = function (item) {
  323. // item
  324. // ^
  325. // prev next
  326. this.updateCursors(item, item.prev, item, item.next);
  327. if (item.prev !== null) {
  328. item.prev.next = item.next;
  329. } else {
  330. if (this.head !== item) {
  331. throw new Error("item doesn\"t belong to list");
  332. }
  333. this.head = item.next;
  334. }
  335. if (item.next !== null) {
  336. item.next.prev = item.prev;
  337. } else {
  338. if (this.tail !== item) {
  339. throw new Error("item doesn\"t belong to list");
  340. }
  341. this.tail = item.prev;
  342. }
  343. item.prev = null;
  344. item.next = null;
  345. return item;
  346. };
  347. List.prototype.push = function (data) {
  348. this.insert(createItem(data));
  349. };
  350. List.prototype.pop = function () {
  351. if (this.tail !== null) {
  352. return this.remove(this.tail);
  353. }
  354. };
  355. List.prototype.unshift = function (data) {
  356. this.prepend(createItem(data));
  357. };
  358. List.prototype.shift = function () {
  359. if (this.head !== null) {
  360. return this.remove(this.head);
  361. }
  362. };
  363. List.prototype.prependList = function (list) {
  364. return this.insertList(list, this.head);
  365. };
  366. List.prototype.appendList = function (list) {
  367. return this.insertList(list);
  368. };
  369. List.prototype.insertList = function (list, before) {
  370. // ignore empty lists
  371. if (list.head === null) {
  372. return this;
  373. }
  374. if (before !== undefined && before !== null) {
  375. this.updateCursors(before.prev, list.tail, before, list.head);
  376. // insert in the middle of dist list
  377. if (before.prev !== null) {
  378. // before.prev <-> list.head
  379. before.prev.next = list.head;
  380. list.head.prev = before.prev;
  381. } else {
  382. this.head = list.head;
  383. }
  384. before.prev = list.tail;
  385. list.tail.next = before;
  386. } else {
  387. this.updateCursors(this.tail, list.tail, null, list.head);
  388. // insert to end of the list
  389. if (this.tail !== null) {
  390. // if destination list has a tail, then it also has a head,
  391. // but head doesn"t change
  392. // dest tail -> source head
  393. this.tail.next = list.head;
  394. // dest tail <- source head
  395. list.head.prev = this.tail;
  396. } else {
  397. // if list has no a tail, then it also has no a head
  398. // in this case points head to new item
  399. this.head = list.head;
  400. }
  401. // tail always start point to new item
  402. this.tail = list.tail;
  403. }
  404. list.head = null;
  405. list.tail = null;
  406. return this;
  407. };
  408. List.prototype.replace = function (oldItem, newItemOrList) {
  409. if ("head" in newItemOrList) {
  410. this.insertList(newItemOrList, oldItem);
  411. } else {
  412. this.insert(newItemOrList, oldItem);
  413. }
  414. this.remove(oldItem);
  415. };
  416. // ---
  417. function createCustomError(name, message) {
  418. // use Object.create(), because some VMs prevent setting line/column otherwise
  419. // (iOS Safari 10 even throws an exception)
  420. const error = Object.create(SyntaxError.prototype);
  421. const errorStack = new Error();
  422. error.name = name;
  423. error.message = message;
  424. Object.defineProperty(error, "stack", {
  425. get: function () {
  426. return (errorStack.stack || "").replace(/^(.+\n){1,3}/, name + ": " + message + "\n");
  427. }
  428. });
  429. return error;
  430. }
  431. // ---
  432. const MAX_LINE_LENGTH = 100;
  433. const OFFSET_CORRECTION = 60;
  434. const TAB_REPLACEMENT = " ";
  435. function sourceFragment(error, extraLines) {
  436. function processLines(start, end) {
  437. return lines.slice(start, end).map(function (line, idx) {
  438. let num = String(start + idx + 1);
  439. while (num.length < maxNumLength) {
  440. num = " " + num;
  441. }
  442. return num + " |" + line;
  443. }).join("\n");
  444. }
  445. const lines = error.source.split(/\r\n?|\n|\f/);
  446. let line = error.line;
  447. let column = error.column;
  448. const startLine = Math.max(1, line - extraLines) - 1;
  449. const endLine = Math.min(line + extraLines, lines.length + 1);
  450. const maxNumLength = Math.max(4, String(endLine).length) + 1;
  451. let cutLeft = 0;
  452. // column correction according to replaced tab before column
  453. column += (TAB_REPLACEMENT.length - 1) * (lines[line - 1].substr(0, column - 1).match(/\t/g) || []).length;
  454. if (column > MAX_LINE_LENGTH) {
  455. cutLeft = column - OFFSET_CORRECTION + 3;
  456. column = OFFSET_CORRECTION - 2;
  457. }
  458. for (let i = startLine; i <= endLine; i++) {
  459. if (i >= 0 && i < lines.length) {
  460. lines[i] = lines[i].replace(/\t/g, TAB_REPLACEMENT);
  461. lines[i] =
  462. (cutLeft > 0 && lines[i].length > cutLeft ? "\u2026" : "") +
  463. lines[i].substr(cutLeft, MAX_LINE_LENGTH - 2) +
  464. (lines[i].length > cutLeft + MAX_LINE_LENGTH - 1 ? "\u2026" : "");
  465. }
  466. }
  467. return [
  468. processLines(startLine, line),
  469. new Array(column + maxNumLength + 2).join("-") + "^",
  470. processLines(line, endLine)
  471. ].filter(Boolean).join("\n");
  472. }
  473. function CssSyntaxError(message, source, offset, line, column) {
  474. const error = createCustomError("CssSyntaxError", message);
  475. error.source = source;
  476. error.offset = offset;
  477. error.line = line;
  478. error.column = column;
  479. error.sourceFragment = function (extraLines) {
  480. return sourceFragment(error, isNaN(extraLines) ? 0 : extraLines);
  481. };
  482. Object.defineProperty(error, "formattedMessage", {
  483. get: function () {
  484. return (
  485. "Parse error: " + error.message + "\n" +
  486. sourceFragment(error, 2)
  487. );
  488. }
  489. });
  490. // for backward capability
  491. error.parseError = {
  492. offset: offset,
  493. line: line,
  494. column: column
  495. };
  496. return error;
  497. }
  498. // ---
  499. // token types (note: value shouldn't intersect with used char codes)
  500. const WHITESPACE = 1;
  501. const IDENTIFIER = 2;
  502. const NUMBER = 3;
  503. const STRING = 4;
  504. const COMMENT = 5;
  505. const PUNCTUATOR = 6;
  506. const CDO = 7;
  507. const CDC = 8;
  508. const ATKEYWORD = 14;
  509. const FUNCTION = 15;
  510. const URL = 16;
  511. const RAW = 17;
  512. const TAB = 9;
  513. const NEW_LINE = 10;
  514. const F = 12;
  515. const R = 13;
  516. const SPACE = 32;
  517. const TYPE = {
  518. WhiteSpace: WHITESPACE,
  519. Identifier: IDENTIFIER,
  520. Number: NUMBER,
  521. String: STRING,
  522. Comment: COMMENT,
  523. Punctuator: PUNCTUATOR,
  524. CDO: CDO,
  525. CDC: CDC,
  526. AtKeyword: ATKEYWORD,
  527. Function: FUNCTION,
  528. Url: URL,
  529. Raw: RAW,
  530. ExclamationMark: 33, // !
  531. QuotationMark: 34, // "
  532. NumberSign: 35, // #
  533. DollarSign: 36, // $
  534. PercentSign: 37, // %
  535. Ampersand: 38, // &
  536. Apostrophe: 39, // '
  537. LeftParenthesis: 40, // (
  538. RightParenthesis: 41, // )
  539. Asterisk: 42, // *
  540. PlusSign: 43, // +
  541. Comma: 44, // ,
  542. HyphenMinus: 45, // -
  543. FullStop: 46, // .
  544. Solidus: 47, // /
  545. Colon: 58, // :
  546. Semicolon: 59, // ;
  547. LessThanSign: 60, // <
  548. EqualsSign: 61, // =
  549. GreaterThanSign: 62, // >
  550. QuestionMark: 63, // ?
  551. CommercialAt: 64, // @
  552. LeftSquareBracket: 91, // [
  553. Backslash: 92, // \
  554. RightSquareBracket: 93, // ]
  555. CircumflexAccent: 94, // ^
  556. LowLine: 95, // _
  557. GraveAccent: 96, // `
  558. LeftCurlyBracket: 123, // {
  559. VerticalLine: 124, // |
  560. RightCurlyBracket: 125, // }
  561. Tilde: 126 // ~
  562. };
  563. const NAME = Object.keys(TYPE).reduce(function (result, key) {
  564. result[TYPE[key]] = key;
  565. return result;
  566. }, {});
  567. // https://drafts.csswg.org/css-syntax/#tokenizer-definitions
  568. // > non-ASCII code point
  569. // > A code point with a value equal to or greater than U+0080 <control>
  570. // > name-start code point
  571. // > A letter, a non-ASCII code point, or U+005F LOW LINE (_).
  572. // > name code point
  573. // > A name-start code point, a digit, or U+002D HYPHEN-MINUS (-)
  574. // That means only ASCII code points has a special meaning and we a maps for 0..127 codes only
  575. const SafeUint32Array = typeof Uint32Array !== "undefined" ? Uint32Array : Array; // fallback on Array when TypedArray is not supported
  576. const SYMBOL_TYPE = new SafeUint32Array(0x80);
  577. const PUNCTUATION = new SafeUint32Array(0x80);
  578. const STOP_URL_RAW = new SafeUint32Array(0x80);
  579. for (let i = 0; i < SYMBOL_TYPE.length; i++) {
  580. SYMBOL_TYPE[i] = IDENTIFIER;
  581. }
  582. // fill categories
  583. [
  584. TYPE.ExclamationMark, // !
  585. TYPE.QuotationMark, // "
  586. TYPE.NumberSign, // #
  587. TYPE.DollarSign, // $
  588. TYPE.PercentSign, // %
  589. TYPE.Ampersand, // &
  590. TYPE.Apostrophe, // '
  591. TYPE.LeftParenthesis, // (
  592. TYPE.RightParenthesis, // )
  593. TYPE.Asterisk, // *
  594. TYPE.PlusSign, // +
  595. TYPE.Comma, // ,
  596. TYPE.HyphenMinus, // -
  597. TYPE.FullStop, // .
  598. TYPE.Solidus, // /
  599. TYPE.Colon, // :
  600. TYPE.Semicolon, // ;
  601. TYPE.LessThanSign, // <
  602. TYPE.EqualsSign, // =
  603. TYPE.GreaterThanSign, // >
  604. TYPE.QuestionMark, // ?
  605. TYPE.CommercialAt, // @
  606. TYPE.LeftSquareBracket, // [
  607. // TYPE.Backslash, // \
  608. TYPE.RightSquareBracket, // ]
  609. TYPE.CircumflexAccent, // ^
  610. // TYPE.LowLine, // _
  611. TYPE.GraveAccent, // `
  612. TYPE.LeftCurlyBracket, // {
  613. TYPE.VerticalLine, // |
  614. TYPE.RightCurlyBracket, // }
  615. TYPE.Tilde // ~
  616. ].forEach(function (key) {
  617. SYMBOL_TYPE[Number(key)] = PUNCTUATOR;
  618. PUNCTUATION[Number(key)] = PUNCTUATOR;
  619. });
  620. for (let i = 48; i <= 57; i++) {
  621. SYMBOL_TYPE[i] = NUMBER;
  622. }
  623. SYMBOL_TYPE[SPACE] = WHITESPACE;
  624. SYMBOL_TYPE[TAB] = WHITESPACE;
  625. SYMBOL_TYPE[NEW_LINE] = WHITESPACE;
  626. SYMBOL_TYPE[R] = WHITESPACE;
  627. SYMBOL_TYPE[F] = WHITESPACE;
  628. SYMBOL_TYPE[TYPE.Apostrophe] = STRING;
  629. SYMBOL_TYPE[TYPE.QuotationMark] = STRING;
  630. STOP_URL_RAW[SPACE] = 1;
  631. STOP_URL_RAW[TAB] = 1;
  632. STOP_URL_RAW[NEW_LINE] = 1;
  633. STOP_URL_RAW[R] = 1;
  634. STOP_URL_RAW[F] = 1;
  635. STOP_URL_RAW[TYPE.Apostrophe] = 1;
  636. STOP_URL_RAW[TYPE.QuotationMark] = 1;
  637. STOP_URL_RAW[TYPE.LeftParenthesis] = 1;
  638. STOP_URL_RAW[TYPE.RightParenthesis] = 1;
  639. // whitespace is punctuation ...
  640. PUNCTUATION[SPACE] = PUNCTUATOR;
  641. PUNCTUATION[TAB] = PUNCTUATOR;
  642. PUNCTUATION[NEW_LINE] = PUNCTUATOR;
  643. PUNCTUATION[R] = PUNCTUATOR;
  644. PUNCTUATION[F] = PUNCTUATOR;
  645. // ... hyper minus is not
  646. PUNCTUATION[TYPE.HyphenMinus] = 0;
  647. const constants = {
  648. TYPE: TYPE,
  649. NAME: NAME,
  650. SYMBOL_TYPE: SYMBOL_TYPE,
  651. PUNCTUATION: PUNCTUATION,
  652. STOP_URL_RAW: STOP_URL_RAW
  653. };
  654. // ---
  655. const BACK_SLASH = 92;
  656. const E = 101; // 'e'.charCodeAt(0)
  657. function firstCharOffset(source) {
  658. // detect BOM (https://en.wikipedia.org/wiki/Byte_order_mark)
  659. if (source.charCodeAt(0) === 0xFEFF || // UTF-16BE
  660. source.charCodeAt(0) === 0xFFFE) { // UTF-16LE
  661. return 1;
  662. }
  663. return 0;
  664. }
  665. function isHex(code) {
  666. return (code >= 48 && code <= 57) || // 0 .. 9
  667. (code >= 65 && code <= 70) || // A .. F
  668. (code >= 97 && code <= 102); // a .. f
  669. }
  670. function isNumber(code) {
  671. return code >= 48 && code <= 57;
  672. }
  673. function isWhiteSpace(code) {
  674. return code === SPACE || code === TAB || isNewline(code);
  675. }
  676. function isNewline(code) {
  677. return code === R || code === NEW_LINE || code === F;
  678. }
  679. function getNewlineLength(source, offset, code) {
  680. if (isNewline(code)) {
  681. if (code === R && offset + 1 < source.length && source.charCodeAt(offset + 1) === NEW_LINE) {
  682. return 2;
  683. }
  684. return 1;
  685. }
  686. return 0;
  687. }
  688. function cmpChar(testStr, offset, referenceCode) {
  689. let code = testStr.charCodeAt(offset);
  690. // code.toLowerCase() for A..Z
  691. if (code >= 65 && code <= 90) {
  692. code = code | 32;
  693. }
  694. return code === referenceCode;
  695. }
  696. function cmpStr(testStr, start, end, referenceStr) {
  697. if (end - start !== referenceStr.length) {
  698. return false;
  699. }
  700. if (start < 0 || end > testStr.length) {
  701. return false;
  702. }
  703. for (let i = start; i < end; i++) {
  704. let testCode = testStr.charCodeAt(i);
  705. const refCode = referenceStr.charCodeAt(i - start);
  706. // testCode.toLowerCase() for A..Z
  707. if (testCode >= 65 && testCode <= 90) {
  708. testCode = testCode | 32;
  709. }
  710. if (testCode !== refCode) {
  711. return false;
  712. }
  713. }
  714. return true;
  715. }
  716. function findWhiteSpaceStart(source, offset) {
  717. while (offset >= 0 && isWhiteSpace(source.charCodeAt(offset))) {
  718. offset--;
  719. }
  720. return offset + 1;
  721. }
  722. function findWhiteSpaceEnd(source, offset) {
  723. while (offset < source.length && isWhiteSpace(source.charCodeAt(offset))) {
  724. offset++;
  725. }
  726. return offset;
  727. }
  728. function findCommentEnd(source, offset) {
  729. const commentEnd = source.indexOf("*/", offset);
  730. if (commentEnd === -1) {
  731. return source.length;
  732. }
  733. return commentEnd + 2;
  734. }
  735. function findStringEnd(source, offset, quote) {
  736. for (; offset < source.length; offset++) {
  737. const code = source.charCodeAt(offset);
  738. // TODO: bad string
  739. if (code === BACK_SLASH) {
  740. offset++;
  741. } else if (code === quote) {
  742. offset++;
  743. break;
  744. }
  745. }
  746. return offset;
  747. }
  748. function findDecimalNumberEnd(source, offset) {
  749. while (offset < source.length && isNumber(source.charCodeAt(offset))) {
  750. offset++;
  751. }
  752. return offset;
  753. }
  754. function findNumberEnd(source, offset, allowFraction) {
  755. let code;
  756. offset = findDecimalNumberEnd(source, offset);
  757. // fraction: .\d+
  758. if (allowFraction && offset + 1 < source.length && source.charCodeAt(offset) === FULLSTOP) {
  759. code = source.charCodeAt(offset + 1);
  760. if (isNumber(code)) {
  761. offset = findDecimalNumberEnd(source, offset + 1);
  762. }
  763. }
  764. // exponent: e[+-]\d+
  765. if (offset + 1 < source.length) {
  766. if ((source.charCodeAt(offset) | 32) === E) { // case insensitive check for `e`
  767. code = source.charCodeAt(offset + 1);
  768. if (code === PLUSSIGN || code === HYPHENMINUS) {
  769. if (offset + 2 < source.length) {
  770. code = source.charCodeAt(offset + 2);
  771. }
  772. }
  773. if (isNumber(code)) {
  774. offset = findDecimalNumberEnd(source, offset + 2);
  775. }
  776. }
  777. }
  778. return offset;
  779. }
  780. // skip escaped unicode sequence that can ends with space
  781. // [0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?
  782. function findEscapeEnd(source, offset) {
  783. for (let i = 0; i < 7 && offset + i < source.length; i++) {
  784. const code = source.charCodeAt(offset + i);
  785. if (i !== 6 && isHex(code)) {
  786. continue;
  787. }
  788. if (i > 0) {
  789. offset += i - 1 + getNewlineLength(source, offset + i, code);
  790. if (code === SPACE || code === TAB) {
  791. offset++;
  792. }
  793. }
  794. break;
  795. }
  796. return offset;
  797. }
  798. function findIdentifierEnd(source, offset) {
  799. for (; offset < source.length; offset++) {
  800. const code = source.charCodeAt(offset);
  801. if (code === BACK_SLASH) {
  802. offset = findEscapeEnd(source, offset + 1);
  803. } else if (code < 0x80 && PUNCTUATION[code] === PUNCTUATOR) {
  804. break;
  805. }
  806. }
  807. return offset;
  808. }
  809. function findUrlRawEnd(source, offset) {
  810. for (; offset < source.length; offset++) {
  811. const code = source.charCodeAt(offset);
  812. if (code === BACK_SLASH) {
  813. offset = findEscapeEnd(source, offset + 1);
  814. } else if (code < 0x80 && STOP_URL_RAW[code] === 1) {
  815. break;
  816. }
  817. }
  818. return offset;
  819. }
  820. const utils = {
  821. firstCharOffset: firstCharOffset,
  822. isHex: isHex,
  823. isNumber: isNumber,
  824. isWhiteSpace: isWhiteSpace,
  825. isNewline: isNewline,
  826. getNewlineLength: getNewlineLength,
  827. cmpChar: cmpChar,
  828. cmpStr: cmpStr,
  829. findWhiteSpaceStart: findWhiteSpaceStart,
  830. findWhiteSpaceEnd: findWhiteSpaceEnd,
  831. findCommentEnd: findCommentEnd,
  832. findStringEnd: findStringEnd,
  833. findDecimalNumberEnd: findDecimalNumberEnd,
  834. findNumberEnd: findNumberEnd,
  835. findEscapeEnd: findEscapeEnd,
  836. findIdentifierEnd: findIdentifierEnd,
  837. findUrlRawEnd: findUrlRawEnd
  838. };
  839. // ---
  840. const STAR = TYPE.Asterisk;
  841. const SLASH = TYPE.Solidus;
  842. const FULLSTOP = TYPE.FullStop;
  843. const PLUSSIGN = TYPE.PlusSign;
  844. const HYPHENMINUS = TYPE.HyphenMinus;
  845. const GREATERTHANSIGN = TYPE.GreaterThanSign;
  846. const LESSTHANSIGN = TYPE.LessThanSign;
  847. const EXCLAMATIONMARK = TYPE.ExclamationMark;
  848. const COMMERCIALAT = TYPE.CommercialAt;
  849. const QUOTATIONMARK = TYPE.QuotationMark;
  850. const APOSTROPHE = TYPE.Apostrophe;
  851. const LEFTPARENTHESIS = TYPE.LeftParenthesis;
  852. const RIGHTPARENTHESIS = TYPE.RightParenthesis;
  853. const LEFTCURLYBRACKET = TYPE.LeftCurlyBracket;
  854. const RIGHTCURLYBRACKET = TYPE.RightCurlyBracket;
  855. const LEFTSQUAREBRACKET = TYPE.LeftSquareBracket;
  856. const RIGHTSQUAREBRACKET = TYPE.RightSquareBracket;
  857. const NUMBERSIGN = TYPE.NumberSign;
  858. const COMMA = TYPE.Comma;
  859. const SOLIDUS = TYPE.Solidus;
  860. const ASTERISK = TYPE.Asterisk;
  861. const PERCENTSIGN = TYPE.PercentSign;
  862. const BACKSLASH = TYPE.Backslash;
  863. const VERTICALLINE = TYPE.VerticalLine;
  864. const TILDE = TYPE.Tilde;
  865. const SEMICOLON = TYPE.Semicolon;
  866. const COLON = TYPE.Colon;
  867. const DOLLARSIGN = TYPE.DollarSign;
  868. const EQUALSSIGN = TYPE.EqualsSign;
  869. const CIRCUMFLEXACCENT = TYPE.CircumflexAccent;
  870. const TYPE_CDC = TYPE.CDC;
  871. const TYPE_CDO = TYPE.CDO;
  872. const QUESTIONMARK = TYPE.QuestionMark;
  873. const NULL = 0;
  874. const MIN_BUFFER_SIZE = 16 * 1024;
  875. const OFFSET_MASK = 0x00FFFFFF;
  876. const TYPE_SHIFT = 24;
  877. function computeLinesAndColumns(tokenizer, source) {
  878. const sourceLength = source.length;
  879. const start = firstCharOffset(source);
  880. let lines = tokenizer.lines;
  881. let line = tokenizer.startLine;
  882. let columns = tokenizer.columns;
  883. let column = tokenizer.startColumn;
  884. if (lines === null || lines.length < sourceLength + 1) {
  885. lines = new SafeUint32Array(Math.max(sourceLength + 1024, MIN_BUFFER_SIZE));
  886. columns = new SafeUint32Array(lines.length);
  887. }
  888. let i;
  889. for (i = start; i < sourceLength; i++) {
  890. const code = source.charCodeAt(i);
  891. lines[i] = line;
  892. columns[i] = column++;
  893. if (code === NEW_LINE || code === R || code === F) {
  894. if (code === R && i + 1 < sourceLength && source.charCodeAt(i + 1) === NEW_LINE) {
  895. i++;
  896. lines[i] = line;
  897. columns[i] = column;
  898. }
  899. line++;
  900. column = 1;
  901. }
  902. }
  903. lines[i] = line;
  904. columns[i] = column;
  905. tokenizer.linesAnsColumnsComputed = true;
  906. tokenizer.lines = lines;
  907. tokenizer.columns = columns;
  908. }
  909. function tokenLayout(tokenizer, source, startPos) {
  910. const sourceLength = source.length;
  911. let offsetAndType = tokenizer.offsetAndType;
  912. let balance = tokenizer.balance;
  913. let tokenCount = 0;
  914. let prevType = 0;
  915. let offset = startPos;
  916. let anchor = 0;
  917. let balanceCloseCode = 0;
  918. let balanceStart = 0;
  919. let balancePrev = 0;
  920. if (offsetAndType === null || offsetAndType.length < sourceLength + 1) {
  921. offsetAndType = new SafeUint32Array(sourceLength + 1024);
  922. balance = new SafeUint32Array(sourceLength + 1024);
  923. }
  924. while (offset < sourceLength) {
  925. let code = source.charCodeAt(offset);
  926. let type = code < 0x80 ? SYMBOL_TYPE[code] : IDENTIFIER;
  927. balance[tokenCount] = sourceLength;
  928. switch (type) {
  929. case WHITESPACE:
  930. offset = findWhiteSpaceEnd(source, offset + 1);
  931. break;
  932. case PUNCTUATOR:
  933. switch (code) {
  934. case balanceCloseCode:
  935. balancePrev = balanceStart & OFFSET_MASK;
  936. balanceStart = balance[balancePrev];
  937. balanceCloseCode = balanceStart >> TYPE_SHIFT;
  938. balance[tokenCount] = balancePrev;
  939. balance[balancePrev++] = tokenCount;
  940. for (; balancePrev < tokenCount; balancePrev++) {
  941. if (balance[balancePrev] === sourceLength) {
  942. balance[balancePrev] = tokenCount;
  943. }
  944. }
  945. break;
  946. case LEFTSQUAREBRACKET:
  947. balance[tokenCount] = balanceStart;
  948. balanceCloseCode = RIGHTSQUAREBRACKET;
  949. balanceStart = (balanceCloseCode << TYPE_SHIFT) | tokenCount;
  950. break;
  951. case LEFTCURLYBRACKET:
  952. balance[tokenCount] = balanceStart;
  953. balanceCloseCode = RIGHTCURLYBRACKET;
  954. balanceStart = (balanceCloseCode << TYPE_SHIFT) | tokenCount;
  955. break;
  956. case LEFTPARENTHESIS:
  957. balance[tokenCount] = balanceStart;
  958. balanceCloseCode = RIGHTPARENTHESIS;
  959. balanceStart = (balanceCloseCode << TYPE_SHIFT) | tokenCount;
  960. break;
  961. }
  962. // /*
  963. if (code === STAR && prevType === SLASH) {
  964. type = COMMENT;
  965. offset = findCommentEnd(source, offset + 1);
  966. tokenCount--; // rewrite prev token
  967. break;
  968. }
  969. // edge case for -.123 and +.123
  970. if (code === FULLSTOP && (prevType === PLUSSIGN || prevType === HYPHENMINUS)) {
  971. if (offset + 1 < sourceLength && isNumber(source.charCodeAt(offset + 1))) {
  972. type = NUMBER;
  973. offset = findNumberEnd(source, offset + 2, false);
  974. tokenCount--; // rewrite prev token
  975. break;
  976. }
  977. }
  978. // <!--
  979. if (code === EXCLAMATIONMARK && prevType === LESSTHANSIGN) {
  980. if (offset + 2 < sourceLength &&
  981. source.charCodeAt(offset + 1) === HYPHENMINUS &&
  982. source.charCodeAt(offset + 2) === HYPHENMINUS) {
  983. type = CDO;
  984. offset = offset + 3;
  985. tokenCount--; // rewrite prev token
  986. break;
  987. }
  988. }
  989. // -->
  990. if (code === HYPHENMINUS && prevType === HYPHENMINUS) {
  991. if (offset + 1 < sourceLength && source.charCodeAt(offset + 1) === GREATERTHANSIGN) {
  992. type = CDC;
  993. offset = offset + 2;
  994. tokenCount--; // rewrite prev token
  995. break;
  996. }
  997. }
  998. // ident(
  999. if (code === LEFTPARENTHESIS && prevType === IDENTIFIER) {
  1000. offset = offset + 1;
  1001. tokenCount--; // rewrite prev token
  1002. balance[tokenCount] = balance[tokenCount + 1];
  1003. balanceStart--;
  1004. // 4 char length identifier and equal to `url(` (case insensitive)
  1005. if (offset - anchor === 4 && cmpStr(source, anchor, offset, "url(")) {
  1006. // special case for url() because it can contain any symbols sequence with few exceptions
  1007. anchor = findWhiteSpaceEnd(source, offset);
  1008. code = source.charCodeAt(anchor);
  1009. if (code !== LEFTPARENTHESIS &&
  1010. code !== RIGHTPARENTHESIS &&
  1011. code !== QUOTATIONMARK &&
  1012. code !== APOSTROPHE) {
  1013. // url(
  1014. offsetAndType[tokenCount++] = (URL << TYPE_SHIFT) | offset;
  1015. balance[tokenCount] = sourceLength;
  1016. // ws*
  1017. if (anchor !== offset) {
  1018. offsetAndType[tokenCount++] = (WHITESPACE << TYPE_SHIFT) | anchor;
  1019. balance[tokenCount] = sourceLength;
  1020. }
  1021. // raw
  1022. type = RAW;
  1023. offset = findUrlRawEnd(source, anchor);
  1024. } else {
  1025. type = URL;
  1026. }
  1027. } else {
  1028. type = FUNCTION;
  1029. }
  1030. break;
  1031. }
  1032. type = code;
  1033. offset = offset + 1;
  1034. break;
  1035. case NUMBER:
  1036. offset = findNumberEnd(source, offset + 1, prevType !== FULLSTOP);
  1037. // merge number with a preceding dot, dash or plus
  1038. if (prevType === FULLSTOP ||
  1039. prevType === HYPHENMINUS ||
  1040. prevType === PLUSSIGN) {
  1041. tokenCount--; // rewrite prev token
  1042. }
  1043. break;
  1044. case STRING:
  1045. offset = findStringEnd(source, offset + 1, code);
  1046. break;
  1047. default:
  1048. anchor = offset;
  1049. offset = findIdentifierEnd(source, offset);
  1050. // merge identifier with a preceding dash
  1051. if (prevType === HYPHENMINUS) {
  1052. // rewrite prev token
  1053. tokenCount--;
  1054. // restore prev prev token type
  1055. // for case @-prefix-ident
  1056. prevType = tokenCount === 0 ? 0 : offsetAndType[tokenCount - 1] >> TYPE_SHIFT;
  1057. }
  1058. if (prevType === COMMERCIALAT) {
  1059. // rewrite prev token and change type to <at-keyword-token>
  1060. tokenCount--;
  1061. type = ATKEYWORD;
  1062. }
  1063. }
  1064. offsetAndType[tokenCount++] = (type << TYPE_SHIFT) | offset;
  1065. prevType = type;
  1066. }
  1067. // finalize arrays
  1068. offsetAndType[tokenCount] = offset;
  1069. balance[tokenCount] = sourceLength;
  1070. balance[sourceLength] = sourceLength; // prevents false positive balance match with any token
  1071. while (balanceStart !== 0) {
  1072. balancePrev = balanceStart & OFFSET_MASK;
  1073. balanceStart = balance[balancePrev];
  1074. balance[balancePrev] = sourceLength;
  1075. }
  1076. tokenizer.offsetAndType = offsetAndType;
  1077. tokenizer.tokenCount = tokenCount;
  1078. tokenizer.balance = balance;
  1079. }
  1080. //
  1081. // tokenizer
  1082. //
  1083. function Tokenizer(source, startOffset, startLine, startColumn) {
  1084. this.offsetAndType = null;
  1085. this.balance = null;
  1086. this.lines = null;
  1087. this.columns = null;
  1088. this.setSource(source, startOffset, startLine, startColumn);
  1089. }
  1090. Tokenizer.prototype = {
  1091. setSource: function (source, startOffset, startLine, startColumn) {
  1092. const safeSource = String(source || "");
  1093. const start = firstCharOffset(safeSource);
  1094. this.source = safeSource;
  1095. this.firstCharOffset = start;
  1096. this.startOffset = typeof startOffset === "undefined" ? 0 : startOffset;
  1097. this.startLine = typeof startLine === "undefined" ? 1 : startLine;
  1098. this.startColumn = typeof startColumn === "undefined" ? 1 : startColumn;
  1099. this.linesAnsColumnsComputed = false;
  1100. this.eof = false;
  1101. this.currentToken = -1;
  1102. this.tokenType = 0;
  1103. this.tokenStart = start;
  1104. this.tokenEnd = start;
  1105. tokenLayout(this, safeSource, start);
  1106. this.next();
  1107. },
  1108. lookupType: function (offset) {
  1109. offset += this.currentToken;
  1110. if (offset < this.tokenCount) {
  1111. return this.offsetAndType[offset] >> TYPE_SHIFT;
  1112. }
  1113. return NULL;
  1114. },
  1115. lookupNonWSType: function (offset) {
  1116. offset += this.currentToken;
  1117. for (let type; offset < this.tokenCount; offset++) {
  1118. type = this.offsetAndType[offset] >> TYPE_SHIFT;
  1119. if (type !== WHITESPACE) {
  1120. return type;
  1121. }
  1122. }
  1123. return NULL;
  1124. },
  1125. lookupValue: function (offset, referenceStr) {
  1126. offset += this.currentToken;
  1127. if (offset < this.tokenCount) {
  1128. return cmpStr(
  1129. this.source,
  1130. this.offsetAndType[offset - 1] & OFFSET_MASK,
  1131. this.offsetAndType[offset] & OFFSET_MASK,
  1132. referenceStr
  1133. );
  1134. }
  1135. return false;
  1136. },
  1137. getTokenStart: function (tokenNum) {
  1138. if (tokenNum === this.currentToken) {
  1139. return this.tokenStart;
  1140. }
  1141. if (tokenNum > 0) {
  1142. return tokenNum < this.tokenCount
  1143. ? this.offsetAndType[tokenNum - 1] & OFFSET_MASK
  1144. : this.offsetAndType[this.tokenCount] & OFFSET_MASK;
  1145. }
  1146. return this.firstCharOffset;
  1147. },
  1148. getOffsetExcludeWS: function () {
  1149. if (this.currentToken > 0) {
  1150. if ((this.offsetAndType[this.currentToken - 1] >> TYPE_SHIFT) === WHITESPACE) {
  1151. return this.currentToken > 1
  1152. ? this.offsetAndType[this.currentToken - 2] & OFFSET_MASK
  1153. : this.firstCharOffset;
  1154. }
  1155. }
  1156. return this.tokenStart;
  1157. },
  1158. getRawLength: function (startToken, endTokenType1, endTokenType2, includeTokenType2) {
  1159. let cursor = startToken;
  1160. let balanceEnd;
  1161. loop:
  1162. for (; cursor < this.tokenCount; cursor++) {
  1163. balanceEnd = this.balance[cursor];
  1164. // belance end points to offset before start
  1165. if (balanceEnd < startToken) {
  1166. break loop;
  1167. }
  1168. // check token is stop type
  1169. switch (this.offsetAndType[cursor] >> TYPE_SHIFT) {
  1170. case endTokenType1:
  1171. break loop;
  1172. case endTokenType2:
  1173. if (includeTokenType2) {
  1174. cursor++;
  1175. }
  1176. break loop;
  1177. default:
  1178. // fast forward to the end of balanced block
  1179. if (this.balance[balanceEnd] === cursor) {
  1180. cursor = balanceEnd;
  1181. }
  1182. }
  1183. }
  1184. return cursor - this.currentToken;
  1185. },
  1186. isBalanceEdge: function (pos) {
  1187. const balanceStart = this.balance[this.currentToken];
  1188. return balanceStart < pos;
  1189. },
  1190. getTokenValue: function () {
  1191. return this.source.substring(this.tokenStart, this.tokenEnd);
  1192. },
  1193. substrToCursor: function (start) {
  1194. return this.source.substring(start, this.tokenStart);
  1195. },
  1196. skipWS: function () {
  1197. let skipTokenCount = 0;
  1198. for (let i = this.currentToken; i < this.tokenCount; i++ , skipTokenCount++) {
  1199. if ((this.offsetAndType[i] >> TYPE_SHIFT) !== WHITESPACE) {
  1200. break;
  1201. }
  1202. }
  1203. if (skipTokenCount > 0) {
  1204. this.skip(skipTokenCount);
  1205. }
  1206. },
  1207. skipSC: function () {
  1208. while (this.tokenType === WHITESPACE || this.tokenType === COMMENT) {
  1209. this.next();
  1210. }
  1211. },
  1212. skip: function (tokenCount) {
  1213. let next = this.currentToken + tokenCount;
  1214. if (next < this.tokenCount) {
  1215. this.currentToken = next;
  1216. this.tokenStart = this.offsetAndType[next - 1] & OFFSET_MASK;
  1217. next = this.offsetAndType[next];
  1218. this.tokenType = next >> TYPE_SHIFT;
  1219. this.tokenEnd = next & OFFSET_MASK;
  1220. } else {
  1221. this.currentToken = this.tokenCount;
  1222. this.next();
  1223. }
  1224. },
  1225. next: function () {
  1226. let next = this.currentToken + 1;
  1227. if (next < this.tokenCount) {
  1228. this.currentToken = next;
  1229. this.tokenStart = this.tokenEnd;
  1230. next = this.offsetAndType[next];
  1231. this.tokenType = next >> TYPE_SHIFT;
  1232. this.tokenEnd = next & OFFSET_MASK;
  1233. } else {
  1234. this.currentToken = this.tokenCount;
  1235. this.eof = true;
  1236. this.tokenType = NULL;
  1237. this.tokenStart = this.tokenEnd = this.source.length;
  1238. }
  1239. },
  1240. eat: function (tokenType) {
  1241. if (this.tokenType !== tokenType) {
  1242. let offset = this.tokenStart;
  1243. let message = NAME[tokenType] + " is expected";
  1244. // tweak message and offset
  1245. if (tokenType === IDENTIFIER) {
  1246. // when identifier is expected but there is a function or url
  1247. if (this.tokenType === FUNCTION || this.tokenType === URL) {
  1248. offset = this.tokenEnd - 1;
  1249. message += " but function found";
  1250. }
  1251. } else {
  1252. // when test type is part of another token show error for current position + 1
  1253. // e.g. eat(HYPHENMINUS) will fail on "-foo", but pointing on "-" is odd
  1254. if (this.source.charCodeAt(this.tokenStart) === tokenType) {
  1255. offset = offset + 1;
  1256. }
  1257. }
  1258. this.error(message, offset);
  1259. }
  1260. this.next();
  1261. },
  1262. eatNonWS: function (tokenType) {
  1263. this.skipWS();
  1264. this.eat(tokenType);
  1265. },
  1266. consume: function (tokenType) {
  1267. const value = this.getTokenValue();
  1268. this.eat(tokenType);
  1269. return value;
  1270. },
  1271. consumeFunctionName: function () {
  1272. const name = this.source.substring(this.tokenStart, this.tokenEnd - 1);
  1273. this.eat(FUNCTION);
  1274. return name;
  1275. },
  1276. consumeNonWS: function (tokenType) {
  1277. this.skipWS();
  1278. return this.consume(tokenType);
  1279. },
  1280. expectIdentifier: function (name) {
  1281. if (this.tokenType !== IDENTIFIER || cmpStr(this.source, this.tokenStart, this.tokenEnd, name) === false) {
  1282. this.error("Identifier `" + name + "` is expected");
  1283. }
  1284. this.next();
  1285. },
  1286. getLocation: function (offset, filename) {
  1287. if (!this.linesAnsColumnsComputed) {
  1288. computeLinesAndColumns(this, this.source);
  1289. }
  1290. return {
  1291. source: filename,
  1292. offset: this.startOffset + offset,
  1293. line: this.lines[offset],
  1294. column: this.columns[offset]
  1295. };
  1296. },
  1297. getLocationRange: function (start, end, filename) {
  1298. if (!this.linesAnsColumnsComputed) {
  1299. computeLinesAndColumns(this, this.source);
  1300. }
  1301. return {
  1302. source: filename,
  1303. start: {
  1304. offset: this.startOffset + start,
  1305. line: this.lines[start],
  1306. column: this.columns[start]
  1307. },
  1308. end: {
  1309. offset: this.startOffset + end,
  1310. line: this.lines[end],
  1311. column: this.columns[end]
  1312. }
  1313. };
  1314. },
  1315. error: function (message, offset) {
  1316. const location = typeof offset !== "undefined" && offset < this.source.length
  1317. ? this.getLocation(offset)
  1318. : this.eof
  1319. ? this.getLocation(findWhiteSpaceStart(this.source, this.source.length - 1))
  1320. : this.getLocation(this.tokenStart);
  1321. throw new CssSyntaxError(
  1322. message || "Unexpected input",
  1323. this.source,
  1324. location.offset,
  1325. location.line,
  1326. location.column
  1327. );
  1328. },
  1329. dump: function () {
  1330. let offset = 0;
  1331. return Array.prototype.slice.call(this.offsetAndType, 0, this.tokenCount).map(function (item, idx) {
  1332. const start = offset;
  1333. const end = item & OFFSET_MASK;
  1334. offset = end;
  1335. return {
  1336. idx: idx,
  1337. type: NAME[item >> TYPE_SHIFT],
  1338. chunk: this.source.substring(start, end),
  1339. balance: this.balance[idx]
  1340. };
  1341. }, this);
  1342. }
  1343. };
  1344. // extend with error class
  1345. Tokenizer.CssSyntaxError = CssSyntaxError;
  1346. // extend tokenizer with constants
  1347. Object.keys(constants).forEach(function (key) {
  1348. Tokenizer[key] = constants[key];
  1349. });
  1350. // extend tokenizer with static methods from utils
  1351. Object.keys(utils).forEach(function (key) {
  1352. Tokenizer[key] = utils[key];
  1353. });
  1354. // warm up tokenizer to elimitate code branches that never execute
  1355. // fix soft deoptimizations (insufficient type feedback)
  1356. new Tokenizer("\n\r\r\n\f<!---->//\"\"''/*\r\n\f*/1a;.\\31\t+2{url(a);func();+1.2e3 -.4e-5 .6e+7}").getLocation();
  1357. // ---
  1358. const sequence = function readSequence(recognizer) {
  1359. const children = this.createList();
  1360. let child = null;
  1361. const context = {
  1362. recognizer: recognizer,
  1363. space: null,
  1364. ignoreWS: false,
  1365. ignoreWSAfter: false
  1366. };
  1367. this.scanner.skipSC();
  1368. while (!this.scanner.eof) {
  1369. switch (this.scanner.tokenType) {
  1370. case COMMENT:
  1371. this.scanner.next();
  1372. continue;
  1373. case WHITESPACE:
  1374. if (context.ignoreWS) {
  1375. this.scanner.next();
  1376. } else {
  1377. context.space = this.WhiteSpace();
  1378. }
  1379. continue;
  1380. }
  1381. child = recognizer.getNode.call(this, context);
  1382. if (child === undefined) {
  1383. break;
  1384. }
  1385. if (context.space !== null) {
  1386. children.push(context.space);
  1387. context.space = null;
  1388. }
  1389. children.push(child);
  1390. if (context.ignoreWSAfter) {
  1391. context.ignoreWSAfter = false;
  1392. context.ignoreWS = true;
  1393. } else {
  1394. context.ignoreWS = false;
  1395. }
  1396. }
  1397. return children;
  1398. };
  1399. // ---
  1400. const noop = function () { };
  1401. function createParseContext(name) {
  1402. return function () {
  1403. return this[name]();
  1404. };
  1405. }
  1406. function processConfig(config) {
  1407. const parserConfig = {
  1408. context: {},
  1409. scope: {},
  1410. atrule: {},
  1411. pseudo: {}
  1412. };
  1413. if (config.parseContext) {
  1414. for (let name in config.parseContext) {
  1415. switch (typeof config.parseContext[name]) {
  1416. case "function":
  1417. parserConfig.context[name] = config.parseContext[name];
  1418. break;
  1419. case "string":
  1420. parserConfig.context[name] = createParseContext(config.parseContext[name]);
  1421. break;
  1422. }
  1423. }
  1424. }
  1425. if (config.scope) {
  1426. for (let name in config.scope) {
  1427. parserConfig.scope[name] = config.scope[name];
  1428. }
  1429. }
  1430. if (config.atrule) {
  1431. for (let name in config.atrule) {
  1432. const atrule = config.atrule[name];
  1433. if (atrule.parse) {
  1434. parserConfig.atrule[name] = atrule.parse;
  1435. }
  1436. }
  1437. }
  1438. if (config.pseudo) {
  1439. for (let name in config.pseudo) {
  1440. const pseudo = config.pseudo[name];
  1441. if (pseudo.parse) {
  1442. parserConfig.pseudo[name] = pseudo.parse;
  1443. }
  1444. }
  1445. }
  1446. if (config.node) {
  1447. for (let name in config.node) {
  1448. parserConfig[name] = config.node[name].parse;
  1449. }
  1450. }
  1451. return parserConfig;
  1452. }
  1453. function createParser(config) {
  1454. const parser = {
  1455. scanner: new Tokenizer(),
  1456. filename: "<unknown>",
  1457. needPositions: false,
  1458. onParseError: noop,
  1459. onParseErrorThrow: false,
  1460. parseAtrulePrelude: true,
  1461. parseRulePrelude: true,
  1462. parseValue: true,
  1463. parseCustomProperty: false,
  1464. readSequence: sequence,
  1465. createList: function () {
  1466. return new List();
  1467. },
  1468. createSingleNodeList: function (node) {
  1469. return new List().appendData(node);
  1470. },
  1471. getFirstListNode: function (list) {
  1472. return list && list.first();
  1473. },
  1474. getLastListNode: function (list) {
  1475. return list.last();
  1476. },
  1477. parseWithFallback: function (consumer, fallback) {
  1478. const startToken = this.scanner.currentToken;
  1479. try {
  1480. return consumer.call(this);
  1481. } catch (e) {
  1482. if (this.onParseErrorThrow) {
  1483. throw e;
  1484. }
  1485. const fallbackNode = fallback.call(this, startToken);
  1486. this.onParseErrorThrow = true;
  1487. this.onParseError(e, fallbackNode);
  1488. this.onParseErrorThrow = false;
  1489. return fallbackNode;
  1490. }
  1491. },
  1492. getLocation: function (start, end) {
  1493. if (this.needPositions) {
  1494. return this.scanner.getLocationRange(
  1495. start,
  1496. end,
  1497. this.filename
  1498. );
  1499. }
  1500. return null;
  1501. },
  1502. getLocationFromList: function (list) {
  1503. if (this.needPositions) {
  1504. const head = this.getFirstListNode(list);
  1505. const tail = this.getLastListNode(list);
  1506. return this.scanner.getLocationRange(
  1507. head !== null ? head.loc.start.offset - this.scanner.startOffset : this.scanner.tokenStart,
  1508. tail !== null ? tail.loc.end.offset - this.scanner.startOffset : this.scanner.tokenStart,
  1509. this.filename
  1510. );
  1511. }
  1512. return null;
  1513. }
  1514. };
  1515. config = processConfig(config || {});
  1516. for (let key in config) {
  1517. parser[key] = config[key];
  1518. }
  1519. return function (source, options) {
  1520. options = options || {};
  1521. const context = options.context || "default";
  1522. let ast;
  1523. parser.scanner.setSource(source, options.offset, options.line, options.column);
  1524. parser.filename = options.filename || "<unknown>";
  1525. parser.needPositions = Boolean(options.positions);
  1526. parser.onParseError = typeof options.onParseError === "function" ? options.onParseError : noop;
  1527. parser.onParseErrorThrow = false;
  1528. parser.parseAtrulePrelude = "parseAtrulePrelude" in options ? Boolean(options.parseAtrulePrelude) : true;
  1529. parser.parseRulePrelude = "parseRulePrelude" in options ? Boolean(options.parseRulePrelude) : true;
  1530. parser.parseValue = "parseValue" in options ? Boolean(options.parseValue) : true;
  1531. parser.parseCustomProperty = "parseCustomProperty" in options ? Boolean(options.parseCustomProperty) : false;
  1532. if (!parser.context.hasOwnProperty(context)) {
  1533. throw new Error("Unknown context `" + context + "`");
  1534. }
  1535. ast = parser.context[context].call(parser, options);
  1536. if (!parser.scanner.eof) {
  1537. parser.scanner.error();
  1538. }
  1539. return ast;
  1540. };
  1541. }
  1542. // ---
  1543. const U = 117; // 'u'.charCodeAt(0)
  1544. const getNode = function defaultRecognizer(context) {
  1545. switch (this.scanner.tokenType) {
  1546. case NUMBERSIGN:
  1547. return this.HexColor();
  1548. case COMMA:
  1549. context.space = null;
  1550. context.ignoreWSAfter = true;
  1551. return this.Operator();
  1552. case SOLIDUS:
  1553. case ASTERISK:
  1554. case PLUSSIGN:
  1555. case HYPHENMINUS:
  1556. return this.Operator();
  1557. case LEFTPARENTHESIS:
  1558. return this.Parentheses(this.readSequence, context.recognizer);
  1559. case LEFTSQUAREBRACKET:
  1560. return this.Brackets(this.readSequence, context.recognizer);
  1561. case STRING:
  1562. return this.String();
  1563. case NUMBER:
  1564. switch (this.scanner.lookupType(1)) {
  1565. case PERCENTSIGN:
  1566. return this.Percentage();
  1567. case IDENTIFIER:
  1568. // edge case: number with folowing \0 and \9 hack shouldn"t to be a Dimension
  1569. if (cmpChar(this.scanner.source, this.scanner.tokenEnd, BACKSLASH)) {
  1570. return this.Number();
  1571. } else {
  1572. return this.Dimension();
  1573. }
  1574. default:
  1575. return this.Number();
  1576. }
  1577. case FUNCTION:
  1578. return this.Function(this.readSequence, context.recognizer);
  1579. case URL:
  1580. return this.Url();
  1581. case IDENTIFIER:
  1582. // check for unicode range, it should start with u+ or U+
  1583. if (cmpChar(this.scanner.source, this.scanner.tokenStart, U) &&
  1584. cmpChar(this.scanner.source, this.scanner.tokenStart + 1, PLUSSIGN)) {
  1585. return this.UnicodeRange();
  1586. } else {
  1587. return this.Identifier();
  1588. }
  1589. }
  1590. };
  1591. // ---
  1592. const AtrulePrelude = {
  1593. getNode: getNode
  1594. };
  1595. // ---
  1596. function Selector_getNode(context) {
  1597. switch (this.scanner.tokenType) {
  1598. case PLUSSIGN:
  1599. case GREATERTHANSIGN:
  1600. case TILDE:
  1601. context.space = null;
  1602. context.ignoreWSAfter = true;
  1603. return this.Combinator();
  1604. case SOLIDUS: // /deep/
  1605. return this.Combinator();
  1606. case FULLSTOP:
  1607. return this.ClassSelector();
  1608. case LEFTSQUAREBRACKET:
  1609. return this.AttributeSelector();
  1610. case NUMBERSIGN:
  1611. return this.IdSelector();
  1612. case COLON:
  1613. if (this.scanner.lookupType(1) === COLON) {
  1614. return this.PseudoElementSelector();
  1615. } else {
  1616. return this.PseudoClassSelector();
  1617. }
  1618. case IDENTIFIER:
  1619. case ASTERISK:
  1620. case VERTICALLINE:
  1621. return this.TypeSelector();
  1622. case NUMBER:
  1623. return this.Percentage();
  1624. }
  1625. }
  1626. const Selector = {
  1627. getNode: Selector_getNode
  1628. };
  1629. // ---
  1630. const Value_getNode = function defaultRecognizer(context) {
  1631. switch (this.scanner.tokenType) {
  1632. case NUMBERSIGN:
  1633. return this.HexColor();
  1634. case COMMA:
  1635. context.space = null;
  1636. context.ignoreWSAfter = true;
  1637. return this.Operator();
  1638. case SOLIDUS:
  1639. case ASTERISK:
  1640. case PLUSSIGN:
  1641. case HYPHENMINUS:
  1642. return this.Operator();
  1643. case LEFTPARENTHESIS:
  1644. return this.Parentheses(this.readSequence, context.recognizer);
  1645. case LEFTSQUAREBRACKET:
  1646. return this.Brackets(this.readSequence, context.recognizer);
  1647. case STRING:
  1648. return this.String();
  1649. case NUMBER:
  1650. switch (this.scanner.lookupType(1)) {
  1651. case PERCENTSIGN:
  1652. return this.Percentage();
  1653. case IDENTIFIER:
  1654. // edge case: number with folowing \0 and \9 hack shouldn't to be a Dimension
  1655. if (cmpChar(this.scanner.source, this.scanner.tokenEnd, BACKSLASH)) {
  1656. return this.Number();
  1657. } else {
  1658. return this.Dimension();
  1659. }
  1660. default:
  1661. return this.Number();
  1662. }
  1663. case FUNCTION:
  1664. return this.Function(this.readSequence, context.recognizer);
  1665. case URL:
  1666. return this.Url();
  1667. case IDENTIFIER:
  1668. // check for unicode range, it should start with u+ or U+
  1669. if (cmpChar(this.scanner.source, this.scanner.tokenStart, U) &&
  1670. cmpChar(this.scanner.source, this.scanner.tokenStart + 1, PLUSSIGN)) {
  1671. return this.UnicodeRange();
  1672. } else {
  1673. return this.Identifier();
  1674. }
  1675. }
  1676. };
  1677. // ---
  1678. // https://drafts.csswg.org/css-images-4/#element-notation
  1679. // https://developer.mozilla.org/en-US/docs/Web/CSS/element
  1680. const Value_Element = function () {
  1681. this.scanner.skipSC();
  1682. const children = this.createSingleNodeList(
  1683. this.IdSelector()
  1684. );
  1685. this.scanner.skipSC();
  1686. return children;
  1687. };
  1688. // ---
  1689. // legacy IE function
  1690. // expression '(' raw ')'
  1691. const Value_expression = function () {
  1692. return this.createSingleNodeList(
  1693. this.Raw(this.scanner.currentToken, 0, 0, false, false)
  1694. );
  1695. };
  1696. // ---
  1697. // let '(' ident (',' <value>? )? ')'
  1698. const Value_var = function () {
  1699. const children = this.createList();
  1700. this.scanner.skipSC();
  1701. const identStart = this.scanner.tokenStart;
  1702. this.scanner.eat(HYPHENMINUS);
  1703. if (this.scanner.source.charCodeAt(this.scanner.tokenStart) !== HYPHENMINUS) {
  1704. this.scanner.error("HyphenMinus is expected");
  1705. }
  1706. this.scanner.eat(IDENTIFIER);
  1707. children.push({
  1708. type: "Identifier",
  1709. loc: this.getLocation(identStart, this.scanner.tokenStart),
  1710. name: this.scanner.substrToCursor(identStart)
  1711. });
  1712. this.scanner.skipSC();
  1713. if (this.scanner.tokenType === COMMA) {
  1714. children.push(this.Operator());
  1715. children.push(this.parseCustomProperty
  1716. ? this.Value(null)
  1717. : this.Raw(this.scanner.currentToken, EXCLAMATIONMARK, SEMICOLON, false, false)
  1718. );
  1719. }
  1720. return children;
  1721. };
  1722. // ---
  1723. const Value = {
  1724. getNode: Value_getNode,
  1725. "-moz-element": Value_Element,
  1726. "element": Value_Element,
  1727. "expression": Value_expression,
  1728. "let": Value_var
  1729. };
  1730. // ---
  1731. const scope = {
  1732. AtrulePrelude: AtrulePrelude,
  1733. Selector: Selector,
  1734. Value: Value
  1735. };
  1736. // ---
  1737. const fontFace = {
  1738. parse: {
  1739. prelude: null,
  1740. block: function () {
  1741. return this.Block(true);
  1742. }
  1743. }
  1744. };
  1745. // ---
  1746. const _import = {
  1747. parse: {
  1748. prelude: function () {
  1749. const children = this.createList();
  1750. this.scanner.skipSC();
  1751. switch (this.scanner.tokenType) {
  1752. case STRING:
  1753. children.push(this.String());
  1754. break;
  1755. case URL:
  1756. children.push(this.Url());
  1757. break;
  1758. default:
  1759. this.scanner.error("String or url() is expected");
  1760. }
  1761. if (this.scanner.lookupNonWSType(0) === IDENTIFIER ||
  1762. this.scanner.lookupNonWSType(0) === LEFTPARENTHESIS) {
  1763. children.push(this.WhiteSpace());
  1764. children.push(this.MediaQueryList());
  1765. }
  1766. return children;
  1767. },
  1768. block: null
  1769. }
  1770. };
  1771. // ---
  1772. const media = {
  1773. parse: {
  1774. prelude: function () {
  1775. return this.createSingleNodeList(
  1776. this.MediaQueryList()
  1777. );
  1778. },
  1779. block: function () {
  1780. return this.Block(false);
  1781. }
  1782. }
  1783. };
  1784. // ---
  1785. const page = {
  1786. parse: {
  1787. prelude: function () {
  1788. return this.createSingleNodeList(
  1789. this.SelectorList()
  1790. );
  1791. },
  1792. block: function () {
  1793. return this.Block(true);
  1794. }
  1795. }
  1796. };
  1797. // ---
  1798. function supports_consumeRaw() {
  1799. return this.createSingleNodeList(
  1800. this.Raw(this.scanner.currentToken, 0, 0, false, false)
  1801. );
  1802. }
  1803. function parentheses() {
  1804. let index = 0;
  1805. this.scanner.skipSC();
  1806. // TODO: make it simplier
  1807. if (this.scanner.tokenType === IDENTIFIER) {
  1808. index = 1;
  1809. } else if (this.scanner.tokenType === HYPHENMINUS &&
  1810. this.scanner.lookupType(1) === IDENTIFIER) {
  1811. index = 2;
  1812. }
  1813. if (index !== 0 && this.scanner.lookupNonWSType(index) === COLON) {
  1814. return this.createSingleNodeList(
  1815. this.Declaration()
  1816. );
  1817. }
  1818. return readSequence.call(this);
  1819. }
  1820. function readSequence() {
  1821. const children = this.createList();
  1822. let space = null;
  1823. let child;
  1824. this.scanner.skipSC();
  1825. scan:
  1826. while (!this.scanner.eof) {
  1827. switch (this.scanner.tokenType) {
  1828. case WHITESPACE:
  1829. space = this.WhiteSpace();
  1830. continue;
  1831. case COMMENT:
  1832. this.scanner.next();
  1833. continue;
  1834. case FUNCTION:
  1835. child = this.Function(supports_consumeRaw, this.scope.AtrulePrelude);
  1836. break;
  1837. case IDENTIFIER:
  1838. child = this.Identifier();
  1839. break;
  1840. case LEFTPARENTHESIS:
  1841. child = this.Parentheses(parentheses, this.scope.AtrulePrelude);
  1842. break;
  1843. default:
  1844. break scan;
  1845. }
  1846. if (space !== null) {
  1847. children.push(space);
  1848. space = null;
  1849. }
  1850. children.push(child);
  1851. }
  1852. return children;
  1853. }
  1854. const supports = {
  1855. parse: {
  1856. prelude: function () {
  1857. const children = readSequence.call(this);
  1858. if (this.getFirstListNode(children) === null) {
  1859. this.scanner.error("Condition is expected");
  1860. }
  1861. return children;
  1862. },
  1863. block: function () {
  1864. return this.Block(false);
  1865. }
  1866. }
  1867. };
  1868. // ---
  1869. const atrule = {
  1870. "font-face": fontFace,
  1871. "import": _import,
  1872. media: media,
  1873. page: page,
  1874. supports: supports
  1875. };
  1876. // ---
  1877. const dir = {
  1878. parse: function () {
  1879. return this.createSingleNodeList(
  1880. this.Identifier()
  1881. );
  1882. }
  1883. };
  1884. // ---
  1885. const has = {
  1886. parse: function () {
  1887. return this.createSingleNodeList(
  1888. this.SelectorList()
  1889. );
  1890. }
  1891. };
  1892. // ---
  1893. const lang = {
  1894. parse: function () {
  1895. return this.createSingleNodeList(
  1896. this.Identifier()
  1897. );
  1898. }
  1899. };
  1900. // ---
  1901. const matches = {
  1902. parse: function selectorList() {
  1903. return this.createSingleNodeList(
  1904. this.SelectorList()
  1905. );
  1906. }
  1907. };
  1908. const not = matches;
  1909. // ---
  1910. const ALLOW_OF_CLAUSE = true;
  1911. const nthChild = {
  1912. parse: function nthWithOfClause() {
  1913. return this.createSingleNodeList(
  1914. this.Nth(ALLOW_OF_CLAUSE)
  1915. );
  1916. }
  1917. };
  1918. const nthLastChild = nthChild;
  1919. // ---
  1920. const DISALLOW_OF_CLAUSE = false;
  1921. const nthLastOfType = {
  1922. parse: function nth() {
  1923. return this.createSingleNodeList(
  1924. this.Nth(DISALLOW_OF_CLAUSE)
  1925. );
  1926. }
  1927. };
  1928. const nthOfType = nthLastOfType;
  1929. // ---
  1930. const slotted = {
  1931. parse: function compoundSelector() {
  1932. return this.createSingleNodeList(
  1933. this.Selector()
  1934. );
  1935. }
  1936. };
  1937. // ---
  1938. const pseudo = {
  1939. dir: dir,
  1940. has: has,
  1941. lang: lang,
  1942. matches: matches,
  1943. not: not,
  1944. "nth-child": nthChild,
  1945. "nth-last-child": nthLastChild,
  1946. "nth-last-of-type": nthLastOfType,
  1947. "nth-of-type": nthOfType,
  1948. slotted: slotted
  1949. };
  1950. // ---
  1951. const AnPlusB_N = 110; // 'n'.charCodeAt(0)
  1952. const DISALLOW_SIGN = true;
  1953. const ALLOW_SIGN = false;
  1954. function checkTokenIsInteger(scanner, disallowSign) {
  1955. let pos = scanner.tokenStart;
  1956. if (scanner.source.charCodeAt(pos) === PLUSSIGN ||
  1957. scanner.source.charCodeAt(pos) === HYPHENMINUS) {
  1958. if (disallowSign) {
  1959. scanner.error();
  1960. }
  1961. pos++;
  1962. }
  1963. for (; pos < scanner.tokenEnd; pos++) {
  1964. if (!isNumber(scanner.source.charCodeAt(pos))) {
  1965. scanner.error("Unexpected input", pos);
  1966. }
  1967. }
  1968. }
  1969. // An+B microsyntax https://www.w3.org/TR/css-syntax-3/#anb
  1970. const AnPlusB = {
  1971. name: "AnPlusB",
  1972. structure: {
  1973. a: [String, null],
  1974. b: [String, null]
  1975. },
  1976. parse: function () {
  1977. const start = this.scanner.tokenStart;
  1978. let end = start;
  1979. let prefix = "";
  1980. let a = null;
  1981. let b = null;
  1982. if (this.scanner.tokenType === NUMBER ||
  1983. this.scanner.tokenType === PLUSSIGN) {
  1984. checkTokenIsInteger(this.scanner, ALLOW_SIGN);
  1985. prefix = this.scanner.getTokenValue();
  1986. this.scanner.next();
  1987. end = this.scanner.tokenStart;
  1988. }
  1989. if (this.scanner.tokenType === IDENTIFIER) {
  1990. let bStart = this.scanner.tokenStart;
  1991. if (cmpChar(this.scanner.source, bStart, HYPHENMINUS)) {
  1992. if (prefix === "") {
  1993. prefix = "-";
  1994. bStart++;
  1995. } else {
  1996. this.scanner.error("Unexpected hyphen minus");
  1997. }
  1998. }
  1999. if (!cmpChar(this.scanner.source, bStart, AnPlusB_N)) {
  2000. this.scanner.error();
  2001. }
  2002. a = prefix === "" ? "1" :
  2003. prefix === "+" ? "+1" :
  2004. prefix === "-" ? "-1" :
  2005. prefix;
  2006. const len = this.scanner.tokenEnd - bStart;
  2007. if (len > 1) {
  2008. // ..n-..
  2009. if (this.scanner.source.charCodeAt(bStart + 1) !== HYPHENMINUS) {
  2010. this.scanner.error("Unexpected input", bStart + 1);
  2011. }
  2012. if (len > 2) {
  2013. // ..n-{number}..
  2014. this.scanner.tokenStart = bStart + 2;
  2015. } else {
  2016. // ..n- {number}
  2017. this.scanner.next();
  2018. this.scanner.skipSC();
  2019. }
  2020. checkTokenIsInteger(this.scanner, DISALLOW_SIGN);
  2021. b = "-" + this.scanner.getTokenValue();
  2022. this.scanner.next();
  2023. end = this.scanner.tokenStart;
  2024. } else {
  2025. prefix = "";
  2026. this.scanner.next();
  2027. end = this.scanner.tokenStart;
  2028. this.scanner.skipSC();
  2029. if (this.scanner.tokenType === HYPHENMINUS ||
  2030. this.scanner.tokenType === PLUSSIGN) {
  2031. prefix = this.scanner.getTokenValue();
  2032. this.scanner.next();
  2033. this.scanner.skipSC();
  2034. }
  2035. if (this.scanner.tokenType === NUMBER) {
  2036. checkTokenIsInteger(this.scanner, prefix !== "");
  2037. if (!isNumber(this.scanner.source.charCodeAt(this.scanner.tokenStart))) {
  2038. prefix = this.scanner.source.charAt(this.scanner.tokenStart);
  2039. this.scanner.tokenStart++;
  2040. }
  2041. if (prefix === "") {
  2042. // should be an operator before number
  2043. this.scanner.error();
  2044. } else if (prefix === "+") {
  2045. // plus is using by default
  2046. prefix = "";
  2047. }
  2048. b = prefix + this.scanner.getTokenValue();
  2049. this.scanner.next();
  2050. end = this.scanner.tokenStart;
  2051. } else {
  2052. if (prefix) {
  2053. this.scanner.eat(NUMBER);
  2054. }
  2055. }
  2056. }
  2057. } else {
  2058. if (prefix === "" || prefix === "+") { // no number
  2059. this.scanner.error(
  2060. "Number or identifier is expected",
  2061. this.scanner.tokenStart + (
  2062. this.scanner.tokenType === PLUSSIGN ||
  2063. this.scanner.tokenType === HYPHENMINUS
  2064. )
  2065. );
  2066. }
  2067. b = prefix;
  2068. }
  2069. return {
  2070. type: "AnPlusB",
  2071. loc: this.getLocation(start, end),
  2072. a: a,
  2073. b: b
  2074. };
  2075. },
  2076. generate: function (node) {
  2077. const a = node.a !== null && node.a !== undefined;
  2078. let b = node.b !== null && node.b !== undefined;
  2079. if (a) {
  2080. this.chunk(
  2081. node.a === "+1" ? "+n" :
  2082. node.a === "1" ? "n" :
  2083. node.a === "-1" ? "-n" :
  2084. node.a + "n"
  2085. );
  2086. if (b) {
  2087. b = String(node.b);
  2088. if (b.charAt(0) === "-" || b.charAt(0) === "+") {
  2089. this.chunk(b.charAt(0));
  2090. this.chunk(b.substr(1));
  2091. } else {
  2092. this.chunk("+");
  2093. this.chunk(b);
  2094. }
  2095. }
  2096. } else {
  2097. this.chunk(String(node.b));
  2098. }
  2099. }
  2100. };
  2101. // ---
  2102. function Atrule_consumeRaw(startToken) {
  2103. return this.Raw(startToken, SEMICOLON, LEFTCURLYBRACKET, false, true);
  2104. }
  2105. function isDeclarationBlockAtrule() {
  2106. for (let offset = 1, type; type = this.scanner.lookupType(offset); offset++) { // eslint-disable-line no-cond-assign
  2107. if (type === RIGHTCURLYBRACKET) {
  2108. return true;
  2109. }
  2110. if (type === LEFTCURLYBRACKET ||
  2111. type === ATKEYWORD) {
  2112. return false;
  2113. }
  2114. }
  2115. return false;
  2116. }
  2117. const Atrule = {
  2118. name: "Atrule",
  2119. structure: {
  2120. name: String,
  2121. prelude: ["AtrulePrelude", "Raw", null],
  2122. block: ["Block", null]
  2123. },
  2124. parse: function () {
  2125. const start = this.scanner.tokenStart;
  2126. let name;
  2127. let nameLowerCase;
  2128. let prelude = null;
  2129. let block = null;
  2130. this.scanner.eat(ATKEYWORD);
  2131. name = this.scanner.substrToCursor(start + 1);
  2132. nameLowerCase = name.toLowerCase();
  2133. this.scanner.skipSC();
  2134. // parse prelude
  2135. if (this.scanner.eof === false &&
  2136. this.scanner.tokenType !== LEFTCURLYBRACKET &&
  2137. this.scanner.tokenType !== SEMICOLON) {
  2138. if (this.parseAtrulePrelude) {
  2139. prelude = this.parseWithFallback(this.AtrulePrelude.bind(this, name), Atrule_consumeRaw);
  2140. // turn empty AtrulePrelude into null
  2141. if (prelude.type === "AtrulePrelude" && prelude.children.head === null) {
  2142. prelude = null;
  2143. }
  2144. } else {
  2145. prelude = Atrule_consumeRaw.call(this, this.scanner.currentToken);
  2146. }
  2147. this.scanner.skipSC();
  2148. }
  2149. switch (this.scanner.tokenType) {
  2150. case SEMICOLON:
  2151. this.scanner.next();
  2152. break;
  2153. case LEFTCURLYBRACKET:
  2154. if (this.atrule.hasOwnProperty(nameLowerCase) &&
  2155. typeof this.atrule[nameLowerCase].block === "function") {
  2156. block = this.atrule[nameLowerCase].block.call(this);
  2157. } else {
  2158. // TODO: should consume block content as Raw?
  2159. block = this.Block(isDeclarationBlockAtrule.call(this));
  2160. }
  2161. break;
  2162. }
  2163. return {
  2164. type: "Atrule",
  2165. loc: this.getLocation(start, this.scanner.tokenStart),
  2166. name: name,
  2167. prelude: prelude,
  2168. block: block
  2169. };
  2170. },
  2171. generate: function (node) {
  2172. this.chunk("@");
  2173. this.chunk(node.name);
  2174. if (node.prelude !== null) {
  2175. this.chunk(" ");
  2176. this.node(node.prelude);
  2177. }
  2178. if (node.block) {
  2179. this.node(node.block);
  2180. } else {
  2181. this.chunk(";");
  2182. }
  2183. },
  2184. walkContext: "atrule"
  2185. };
  2186. // ---
  2187. const Syntax_AtrulePrelude = {
  2188. name: "AtrulePrelude",
  2189. structure: {
  2190. children: [[]]
  2191. },
  2192. parse: function (name) {
  2193. let children = null;
  2194. if (name !== null) {
  2195. name = name.toLowerCase();
  2196. }
  2197. this.scanner.skipSC();
  2198. if (this.atrule.hasOwnProperty(name) &&
  2199. typeof this.atrule[name].prelude === "function") {
  2200. // custom consumer
  2201. children = this.atrule[name].prelude.call(this);
  2202. } else {
  2203. // default consumer
  2204. children = this.readSequence(this.scope.AtrulePrelude);
  2205. }
  2206. this.scanner.skipSC();
  2207. if (this.scanner.eof !== true &&
  2208. this.scanner.tokenType !== LEFTCURLYBRACKET &&
  2209. this.scanner.tokenType !== SEMICOLON) {
  2210. this.scanner.error("Semicolon or block is expected");
  2211. }
  2212. if (children === null) {
  2213. children = this.createList();
  2214. }
  2215. return {
  2216. type: "AtrulePrelude",
  2217. loc: this.getLocationFromList(children),
  2218. children: children
  2219. };
  2220. },
  2221. generate: function (node) {
  2222. this.children(node);
  2223. },
  2224. walkContext: "atrulePrelude"
  2225. };
  2226. // ---
  2227. function getAttributeName() {
  2228. if (this.scanner.eof) {
  2229. this.scanner.error("Unexpected end of input");
  2230. }
  2231. const start = this.scanner.tokenStart;
  2232. let expectIdentifier = false;
  2233. let checkColon = true;
  2234. if (this.scanner.tokenType === ASTERISK) {
  2235. expectIdentifier = true;
  2236. checkColon = false;
  2237. this.scanner.next();
  2238. } else if (this.scanner.tokenType !== VERTICALLINE) {
  2239. this.scanner.eat(IDENTIFIER);
  2240. }
  2241. if (this.scanner.tokenType === VERTICALLINE) {
  2242. if (this.scanner.lookupType(1) !== EQUALSSIGN) {
  2243. this.scanner.next();
  2244. this.scanner.eat(IDENTIFIER);
  2245. } else if (expectIdentifier) {
  2246. this.scanner.error("Identifier is expected", this.scanner.tokenEnd);
  2247. }
  2248. } else if (expectIdentifier) {
  2249. this.scanner.error("Vertical line is expected");
  2250. }
  2251. if (checkColon && this.scanner.tokenType === COLON) {
  2252. this.scanner.next();
  2253. this.scanner.eat(IDENTIFIER);
  2254. }
  2255. return {
  2256. type: "Identifier",
  2257. loc: this.getLocation(start, this.scanner.tokenStart),
  2258. name: this.scanner.substrToCursor(start)
  2259. };
  2260. }
  2261. function getOperator() {
  2262. const start = this.scanner.tokenStart;
  2263. const tokenType = this.scanner.tokenType;
  2264. if (tokenType !== EQUALSSIGN && // =
  2265. tokenType !== TILDE && // ~=
  2266. tokenType !== CIRCUMFLEXACCENT && // ^=
  2267. tokenType !== DOLLARSIGN && // $=
  2268. tokenType !== ASTERISK && // *=
  2269. tokenType !== VERTICALLINE // |=
  2270. ) {
  2271. this.scanner.error("Attribute selector (=, ~=, ^=, $=, *=, |=) is expected");
  2272. }
  2273. if (tokenType === EQUALSSIGN) {
  2274. this.scanner.next();
  2275. } else {
  2276. this.scanner.next();
  2277. this.scanner.eat(EQUALSSIGN);
  2278. }
  2279. return this.scanner.substrToCursor(start);
  2280. }
  2281. // "[" S* attrib_name "]"
  2282. // "[" S* attrib_name S* attrib_matcher S* [ IDENT | STRING ] S* attrib_flags? S* "]"
  2283. const AttributeSelector = {
  2284. name: "AttributeSelector",
  2285. structure: {
  2286. name: "Identifier",
  2287. matcher: [String, null],
  2288. value: ["String", "Identifier", null],
  2289. flags: [String, null]
  2290. },
  2291. parse: function () {
  2292. const start = this.scanner.tokenStart;
  2293. let name;
  2294. let matcher = null;
  2295. let value = null;
  2296. let flags = null;
  2297. this.scanner.eat(LEFTSQUAREBRACKET);
  2298. this.scanner.skipSC();
  2299. name = getAttributeName.call(this);
  2300. this.scanner.skipSC();
  2301. if (this.scanner.tokenType !== RIGHTSQUAREBRACKET) {
  2302. // avoid case `[name i]`
  2303. if (this.scanner.tokenType !== IDENTIFIER) {
  2304. matcher = getOperator.call(this);
  2305. this.scanner.skipSC();
  2306. value = this.scanner.tokenType === STRING
  2307. ? this.String()
  2308. : this.Identifier();
  2309. this.scanner.skipSC();
  2310. }
  2311. // attribute flags
  2312. if (this.scanner.tokenType === IDENTIFIER) {
  2313. flags = this.scanner.getTokenValue();
  2314. this.scanner.next();
  2315. this.scanner.skipSC();
  2316. }
  2317. }
  2318. this.scanner.eat(RIGHTSQUAREBRACKET);
  2319. return {
  2320. type: "AttributeSelector",
  2321. loc: this.getLocation(start, this.scanner.tokenStart),
  2322. name: name,
  2323. matcher: matcher,
  2324. value: value,
  2325. flags: flags
  2326. };
  2327. },
  2328. generate: function (node) {
  2329. let flagsPrefix = " ";
  2330. this.chunk("[");
  2331. this.node(node.name);
  2332. if (node.matcher !== null) {
  2333. this.chunk(node.matcher);
  2334. if (node.value !== null) {
  2335. this.node(node.value);
  2336. // space between string and flags is not required
  2337. if (node.value.type === "String") {
  2338. flagsPrefix = "";
  2339. }
  2340. }
  2341. }
  2342. if (node.flags !== null) {
  2343. this.chunk(flagsPrefix);
  2344. this.chunk(node.flags);
  2345. }
  2346. this.chunk("]");
  2347. }
  2348. };
  2349. // ---
  2350. function Block_consumeRaw(startToken) {
  2351. return this.Raw(startToken, 0, 0, false, true);
  2352. }
  2353. function consumeRule() {
  2354. return this.parseWithFallback(this.Rule, Block_consumeRaw);
  2355. }
  2356. function consumeRawDeclaration(startToken) {
  2357. return this.Raw(startToken, 0, SEMICOLON, true, true);
  2358. }
  2359. function consumeDeclaration() {
  2360. if (this.scanner.tokenType === SEMICOLON) {
  2361. return consumeRawDeclaration.call(this, this.scanner.currentToken);
  2362. }
  2363. const node = this.parseWithFallback(this.Declaration, consumeRawDeclaration);
  2364. if (this.scanner.tokenType === SEMICOLON) {
  2365. this.scanner.next();
  2366. }
  2367. return node;
  2368. }
  2369. const Block = {
  2370. name: "Block",
  2371. structure: {
  2372. children: [[
  2373. "Atrule",
  2374. "Rule",
  2375. "Declaration"
  2376. ]]
  2377. },
  2378. parse: function (isDeclaration) {
  2379. const consumer = isDeclaration ? consumeDeclaration : consumeRule;
  2380. const start = this.scanner.tokenStart;
  2381. const children = this.createList();
  2382. this.scanner.eat(LEFTCURLYBRACKET);
  2383. scan:
  2384. while (!this.scanner.eof) {
  2385. switch (this.scanner.tokenType) {
  2386. case RIGHTCURLYBRACKET:
  2387. break scan;
  2388. case WHITESPACE:
  2389. case COMMENT:
  2390. this.scanner.next();
  2391. break;
  2392. case ATKEYWORD:
  2393. children.push(this.parseWithFallback(this.Atrule, Block_consumeRaw));
  2394. break;
  2395. default:
  2396. children.push(consumer.call(this));
  2397. }
  2398. }
  2399. if (!this.scanner.eof) {
  2400. this.scanner.eat(RIGHTCURLYBRACKET);
  2401. }
  2402. return {
  2403. type: "Block",
  2404. loc: this.getLocation(start, this.scanner.tokenStart),
  2405. children: children
  2406. };
  2407. },
  2408. generate: function (node) {
  2409. this.chunk("{");
  2410. this.children(node, function (prev) {
  2411. if (prev.type === "Declaration") {
  2412. this.chunk(";");
  2413. }
  2414. });
  2415. this.chunk("}");
  2416. },
  2417. walkContext: "block"
  2418. };
  2419. // ---
  2420. const Brackets = {
  2421. name: "Brackets",
  2422. structure: {
  2423. children: [[]]
  2424. },
  2425. parse: function (readSequence, recognizer) {
  2426. const start = this.scanner.tokenStart;
  2427. let children = null;
  2428. this.scanner.eat(LEFTSQUAREBRACKET);
  2429. children = readSequence.call(this, recognizer);
  2430. if (!this.scanner.eof) {
  2431. this.scanner.eat(RIGHTSQUAREBRACKET);
  2432. }
  2433. return {
  2434. type: "Brackets",
  2435. loc: this.getLocation(start, this.scanner.tokenStart),
  2436. children: children
  2437. };
  2438. },
  2439. generate: function (node) {
  2440. this.chunk("[");
  2441. this.children(node);
  2442. this.chunk("]");
  2443. }
  2444. };
  2445. // ---
  2446. const Syntax_CDC = {
  2447. name: "CDC",
  2448. structure: [],
  2449. parse: function () {
  2450. const start = this.scanner.tokenStart;
  2451. this.scanner.eat(TYPE_CDC); // -->
  2452. return {
  2453. type: "CDC",
  2454. loc: this.getLocation(start, this.scanner.tokenStart)
  2455. };
  2456. },
  2457. generate: function () {
  2458. this.chunk("-->");
  2459. }
  2460. };
  2461. // ---
  2462. const Syntax_CDO = {
  2463. name: "CDO",
  2464. structure: [],
  2465. parse: function () {
  2466. const start = this.scanner.tokenStart;
  2467. this.scanner.eat(TYPE_CDO); // <!--
  2468. return {
  2469. type: "CDO",
  2470. loc: this.getLocation(start, this.scanner.tokenStart)
  2471. };
  2472. },
  2473. generate: function () {
  2474. this.chunk("<!--");
  2475. }
  2476. };
  2477. // ---
  2478. // '.' ident
  2479. const ClassSelector = {
  2480. name: "ClassSelector",
  2481. structure: {
  2482. name: String
  2483. },
  2484. parse: function () {
  2485. this.scanner.eat(FULLSTOP);
  2486. return {
  2487. type: "ClassSelector",
  2488. loc: this.getLocation(this.scanner.tokenStart - 1, this.scanner.tokenEnd),
  2489. name: this.scanner.consume(IDENTIFIER)
  2490. };
  2491. },
  2492. generate: function (node) {
  2493. this.chunk(".");
  2494. this.chunk(node.name);
  2495. }
  2496. };
  2497. // ---
  2498. // + | > | ~ | /deep/
  2499. const Combinator = {
  2500. name: "Combinator",
  2501. structure: {
  2502. name: String
  2503. },
  2504. parse: function () {
  2505. const start = this.scanner.tokenStart;
  2506. switch (this.scanner.tokenType) {
  2507. case GREATERTHANSIGN:
  2508. case PLUSSIGN:
  2509. case TILDE:
  2510. this.scanner.next();
  2511. break;
  2512. case SOLIDUS:
  2513. this.scanner.next();
  2514. this.scanner.expectIdentifier("deep");
  2515. this.scanner.eat(SOLIDUS);
  2516. break;
  2517. default:
  2518. this.scanner.error("Combinator is expected");
  2519. }
  2520. return {
  2521. type: "Combinator",
  2522. loc: this.getLocation(start, this.scanner.tokenStart),
  2523. name: this.scanner.substrToCursor(start)
  2524. };
  2525. },
  2526. generate: function (node) {
  2527. this.chunk(node.name);
  2528. }
  2529. };
  2530. // ---
  2531. // '/*' .* '*/'
  2532. const Syntax_Comment = {
  2533. name: "Comment",
  2534. structure: {
  2535. value: String
  2536. },
  2537. parse: function () {
  2538. const start = this.scanner.tokenStart;
  2539. let end = this.scanner.tokenEnd;
  2540. if ((end - start + 2) >= 2 &&
  2541. this.scanner.source.charCodeAt(end - 2) === ASTERISK &&
  2542. this.scanner.source.charCodeAt(end - 1) === SOLIDUS) {
  2543. end -= 2;
  2544. }
  2545. this.scanner.next();
  2546. return {
  2547. type: "Comment",
  2548. loc: this.getLocation(start, this.scanner.tokenStart),
  2549. value: this.scanner.source.substring(start + 2, end)
  2550. };
  2551. },
  2552. generate: function (node) {
  2553. this.chunk("/*");
  2554. this.chunk(node.value);
  2555. this.chunk("*/");
  2556. }
  2557. };
  2558. // ---
  2559. const hasOwnProperty = Object.prototype.hasOwnProperty;
  2560. const keywords = Object.create(null);
  2561. const properties = Object.create(null);
  2562. const NAMES_HYPHENMINUS = 45; // "-".charCodeAt()
  2563. function isCustomProperty(str, offset) {
  2564. offset = offset || 0;
  2565. return str.length - offset >= 2 &&
  2566. str.charCodeAt(offset) === NAMES_HYPHENMINUS &&
  2567. str.charCodeAt(offset + 1) === NAMES_HYPHENMINUS;
  2568. }
  2569. function getVendorPrefix(str, offset) {
  2570. offset = offset || 0;
  2571. // verdor prefix should be at least 3 chars length
  2572. if (str.length - offset >= 3) {
  2573. // vendor prefix starts with hyper minus following non-hyper minus
  2574. if (str.charCodeAt(offset) === NAMES_HYPHENMINUS &&
  2575. str.charCodeAt(offset + 1) !== NAMES_HYPHENMINUS) {
  2576. // vendor prefix should contain a hyper minus at the ending
  2577. const secondDashIndex = str.indexOf("-", offset + 2);
  2578. if (secondDashIndex !== -1) {
  2579. return str.substring(offset, secondDashIndex + 1);
  2580. }
  2581. }
  2582. }
  2583. return "";
  2584. }
  2585. function getKeywordDescriptor(keyword) {
  2586. if (hasOwnProperty.call(keywords, keyword)) {
  2587. return keywords[keyword];
  2588. }
  2589. const name = keyword.toLowerCase();
  2590. if (hasOwnProperty.call(keywords, name)) {
  2591. return keywords[keyword] = keywords[name];
  2592. }
  2593. const custom = names.isCustomProperty(name, 0);
  2594. const vendor = !custom ? getVendorPrefix(name, 0) : "";
  2595. return keywords[keyword] = Object.freeze({
  2596. basename: name.substr(vendor.length),
  2597. name: name,
  2598. vendor: vendor,
  2599. prefix: vendor,
  2600. custom: custom
  2601. });
  2602. }
  2603. function getPropertyDescriptor(property) {
  2604. if (hasOwnProperty.call(properties, property)) {
  2605. return properties[property];
  2606. }
  2607. let name = property;
  2608. let hack = property[0];
  2609. if (hack === "/") {
  2610. hack = property[1] === "/" ? "//" : "/";
  2611. } else if (hack !== "_" &&
  2612. hack !== "*" &&
  2613. hack !== "$" &&
  2614. hack !== "#" &&
  2615. hack !== "+") {
  2616. hack = "";
  2617. }
  2618. const custom = isCustomProperty(name, hack.length);
  2619. // re-use result when possible (the same as for lower case)
  2620. if (!custom) {
  2621. name = name.toLowerCase();
  2622. if (hasOwnProperty.call(properties, name)) {
  2623. return properties[property] = properties[name];
  2624. }
  2625. }
  2626. const vendor = !custom ? getVendorPrefix(name, hack.length) : "";
  2627. const prefix = name.substr(0, hack.length + vendor.length);
  2628. return properties[property] = Object.freeze({
  2629. basename: name.substr(prefix.length),
  2630. name: name.substr(hack.length),
  2631. hack: hack,
  2632. vendor: vendor,
  2633. prefix: prefix,
  2634. custom: custom
  2635. });
  2636. }
  2637. const names = {
  2638. keyword: getKeywordDescriptor,
  2639. property: getPropertyDescriptor,
  2640. isCustomProperty: isCustomProperty,
  2641. vendorPrefix: getVendorPrefix
  2642. };
  2643. // ---
  2644. function consumeValueRaw(startToken) {
  2645. return this.Raw(startToken, EXCLAMATIONMARK, SEMICOLON, false, true);
  2646. }
  2647. function consumeCustomPropertyRaw(startToken) {
  2648. return this.Raw(startToken, EXCLAMATIONMARK, SEMICOLON, false, false);
  2649. }
  2650. function consumeValue() {
  2651. const startValueToken = this.scanner.currentToken;
  2652. const value = this.Value();
  2653. if (value.type !== "Raw" &&
  2654. this.scanner.eof === false &&
  2655. this.scanner.tokenType !== SEMICOLON &&
  2656. this.scanner.tokenType !== EXCLAMATIONMARK &&
  2657. this.scanner.isBalanceEdge(startValueToken) === false) {
  2658. this.scanner.error();
  2659. }
  2660. return value;
  2661. }
  2662. const Declaration = {
  2663. name: "Declaration",
  2664. structure: {
  2665. important: [Boolean, String],
  2666. property: String,
  2667. value: ["Value", "Raw"]
  2668. },
  2669. parse: function () {
  2670. const start = this.scanner.tokenStart;
  2671. const startToken = this.scanner.currentToken;
  2672. const property = readProperty.call(this);
  2673. const customProperty = names.isCustomProperty(property);
  2674. const parseValue = customProperty ? this.parseCustomProperty : this.parseValue;
  2675. const consumeRaw = customProperty ? consumeCustomPropertyRaw : consumeValueRaw;
  2676. let important = false;
  2677. let value;
  2678. this.scanner.skipSC();
  2679. this.scanner.eat(COLON);
  2680. if (!customProperty) {
  2681. this.scanner.skipSC();
  2682. }
  2683. if (parseValue) {
  2684. value = this.parseWithFallback(consumeValue, consumeRaw);
  2685. } else {
  2686. value = consumeRaw.call(this, this.scanner.currentToken);
  2687. }
  2688. if (this.scanner.tokenType === EXCLAMATIONMARK) {
  2689. important = getImportant(this.scanner);
  2690. this.scanner.skipSC();
  2691. }
  2692. // Do not include semicolon to range per spec
  2693. // https://drafts.csswg.org/css-syntax/#declaration-diagram
  2694. if (this.scanner.eof === false &&
  2695. this.scanner.tokenType !== SEMICOLON &&
  2696. this.scanner.isBalanceEdge(startToken) === false) {
  2697. this.scanner.error();
  2698. }
  2699. return {
  2700. type: "Declaration",
  2701. loc: this.getLocation(start, this.scanner.tokenStart),
  2702. important: important,
  2703. property: property,
  2704. value: value
  2705. };
  2706. },
  2707. generate: function (node) {
  2708. this.chunk(node.property);
  2709. this.chunk(":");
  2710. this.node(node.value);
  2711. if (node.important) {
  2712. this.chunk(node.important === true ? "!important" : "!" + node.important);
  2713. }
  2714. },
  2715. walkContext: "declaration"
  2716. };
  2717. function readProperty() {
  2718. const start = this.scanner.tokenStart;
  2719. let prefix = 0;
  2720. // hacks
  2721. switch (this.scanner.tokenType) {
  2722. case ASTERISK:
  2723. case DOLLARSIGN:
  2724. case PLUSSIGN:
  2725. case NUMBERSIGN:
  2726. prefix = 1;
  2727. break;
  2728. // TODO: not sure we should support this hack
  2729. case SOLIDUS:
  2730. prefix = this.scanner.lookupType(1) === SOLIDUS ? 2 : 1;
  2731. break;
  2732. }
  2733. if (this.scanner.lookupType(prefix) === HYPHENMINUS) {
  2734. prefix++;
  2735. }
  2736. if (prefix) {
  2737. this.scanner.skip(prefix);
  2738. }
  2739. this.scanner.eat(IDENTIFIER);
  2740. return this.scanner.substrToCursor(start);
  2741. }
  2742. // ! ws* important
  2743. function getImportant(scanner) {
  2744. scanner.eat(EXCLAMATIONMARK);
  2745. scanner.skipSC();
  2746. const important = scanner.consume(IDENTIFIER);
  2747. // store original value in case it differ from `important`
  2748. // for better original source restoring and hacks like `!ie` support
  2749. return important === "important" ? true : important;
  2750. }
  2751. // ---
  2752. function DeclarationList_consumeRaw(startToken) {
  2753. return this.Raw(startToken, 0, SEMICOLON, true, true);
  2754. }
  2755. const DeclarationList = {
  2756. name: "DeclarationList",
  2757. structure: {
  2758. children: [[
  2759. "Declaration"
  2760. ]]
  2761. },
  2762. parse: function () {
  2763. const children = this.createList();
  2764. while (!this.scanner.eof) {
  2765. switch (this.scanner.tokenType) {
  2766. case WHITESPACE:
  2767. case COMMENT:
  2768. case SEMICOLON:
  2769. this.scanner.next();
  2770. break;
  2771. default:
  2772. children.push(this.parseWithFallback(this.Declaration, DeclarationList_consumeRaw));
  2773. }
  2774. }
  2775. return {
  2776. type: "DeclarationList",
  2777. loc: this.getLocationFromList(children),
  2778. children: children
  2779. };
  2780. },
  2781. generate: function (node) {
  2782. this.children(node, function (prev) {
  2783. if (prev.type === "Declaration") {
  2784. this.chunk(";");
  2785. }
  2786. });
  2787. }
  2788. };
  2789. // ---
  2790. // special reader for units to avoid adjoined IE hacks (i.e. '1px\9')
  2791. function readUnit(scanner) {
  2792. const unit = scanner.getTokenValue();
  2793. const backSlashPos = unit.indexOf("\\");
  2794. if (backSlashPos > 0) {
  2795. // patch token offset
  2796. scanner.tokenStart += backSlashPos;
  2797. // return part before backslash
  2798. return unit.substring(0, backSlashPos);
  2799. }
  2800. // no backslash in unit name
  2801. scanner.next();
  2802. return unit;
  2803. }
  2804. // number ident
  2805. const Dimension = {
  2806. name: "Dimension",
  2807. structure: {
  2808. value: String,
  2809. unit: String
  2810. },
  2811. parse: function () {
  2812. const start = this.scanner.tokenStart;
  2813. const value = this.scanner.consume(NUMBER);
  2814. const unit = readUnit(this.scanner);
  2815. return {
  2816. type: "Dimension",
  2817. loc: this.getLocation(start, this.scanner.tokenStart),
  2818. value: value,
  2819. unit: unit
  2820. };
  2821. },
  2822. generate: function (node) {
  2823. this.chunk(node.value);
  2824. this.chunk(node.unit);
  2825. }
  2826. };
  2827. // ---
  2828. // <function-token> <sequence> ')'
  2829. const Syntax_Function = {
  2830. name: "Function",
  2831. structure: {
  2832. name: String,
  2833. children: [[]]
  2834. },
  2835. parse: function (readSequence, recognizer) {
  2836. const start = this.scanner.tokenStart;
  2837. const name = this.scanner.consumeFunctionName();
  2838. const nameLowerCase = name.toLowerCase();
  2839. let children;
  2840. children = recognizer.hasOwnProperty(nameLowerCase)
  2841. ? recognizer[nameLowerCase].call(this, recognizer)
  2842. : readSequence.call(this, recognizer);
  2843. if (!this.scanner.eof) {
  2844. this.scanner.eat(RIGHTPARENTHESIS);
  2845. }
  2846. return {
  2847. type: "Function",
  2848. loc: this.getLocation(start, this.scanner.tokenStart),
  2849. name: name,
  2850. children: children
  2851. };
  2852. },
  2853. generate: function (node) {
  2854. this.chunk(node.name);
  2855. this.chunk("(");
  2856. this.children(node);
  2857. this.chunk(")");
  2858. },
  2859. walkContext: "function"
  2860. };
  2861. // ---
  2862. function consumeHexSequence(scanner, required) {
  2863. if (!isHex(scanner.source.charCodeAt(scanner.tokenStart))) {
  2864. if (required) {
  2865. scanner.error("Unexpected input", scanner.tokenStart);
  2866. } else {
  2867. return;
  2868. }
  2869. }
  2870. for (let pos = scanner.tokenStart + 1; pos < scanner.tokenEnd; pos++) {
  2871. const code = scanner.source.charCodeAt(pos);
  2872. // break on non-hex char
  2873. if (!isHex(code)) {
  2874. // break token, exclude symbol
  2875. scanner.tokenStart = pos;
  2876. return;
  2877. }
  2878. }
  2879. // token is full hex sequence, go to next token
  2880. scanner.next();
  2881. }
  2882. // # ident
  2883. const HexColor = {
  2884. name: "HexColor",
  2885. structure: {
  2886. value: String
  2887. },
  2888. parse: function () {
  2889. const start = this.scanner.tokenStart;
  2890. this.scanner.eat(NUMBERSIGN);
  2891. switch (this.scanner.tokenType) {
  2892. case NUMBER:
  2893. consumeHexSequence(this.scanner, true);
  2894. // if token is identifier then number consists of hex only,
  2895. // try to add identifier to result
  2896. if (this.scanner.tokenType === IDENTIFIER) {
  2897. consumeHexSequence(this.scanner, false);
  2898. }
  2899. break;
  2900. case IDENTIFIER:
  2901. consumeHexSequence(this.scanner, true);
  2902. break;
  2903. default:
  2904. this.scanner.error("Number or identifier is expected");
  2905. }
  2906. return {
  2907. type: "HexColor",
  2908. loc: this.getLocation(start, this.scanner.tokenStart),
  2909. value: this.scanner.substrToCursor(start + 1) // skip #
  2910. };
  2911. },
  2912. generate: function (node) {
  2913. this.chunk("#");
  2914. this.chunk(node.value);
  2915. }
  2916. };
  2917. // ---
  2918. const Identifier = {
  2919. name: "Identifier",
  2920. structure: {
  2921. name: String
  2922. },
  2923. parse: function () {
  2924. return {
  2925. type: "Identifier",
  2926. loc: this.getLocation(this.scanner.tokenStart, this.scanner.tokenEnd),
  2927. name: this.scanner.consume(IDENTIFIER)
  2928. };
  2929. },
  2930. generate: function (node) {
  2931. this.chunk(node.name);
  2932. }
  2933. };
  2934. // ---
  2935. // '#' ident
  2936. const IdSelector = {
  2937. name: "IdSelector",
  2938. structure: {
  2939. name: String
  2940. },
  2941. parse: function () {
  2942. this.scanner.eat(NUMBERSIGN);
  2943. return {
  2944. type: "IdSelector",
  2945. loc: this.getLocation(this.scanner.tokenStart - 1, this.scanner.tokenEnd),
  2946. name: this.scanner.consume(IDENTIFIER)
  2947. };
  2948. },
  2949. generate: function (node) {
  2950. this.chunk("#");
  2951. this.chunk(node.name);
  2952. }
  2953. };
  2954. // ---
  2955. const MediaFeature = {
  2956. name: "MediaFeature",
  2957. structure: {
  2958. name: String,
  2959. value: ["Identifier", "Number", "Dimension", "Ratio", null]
  2960. },
  2961. parse: function () {
  2962. const start = this.scanner.tokenStart;
  2963. let name;
  2964. let value = null;
  2965. this.scanner.eat(LEFTPARENTHESIS);
  2966. this.scanner.skipSC();
  2967. name = this.scanner.consume(IDENTIFIER);
  2968. this.scanner.skipSC();
  2969. if (this.scanner.tokenType !== RIGHTPARENTHESIS) {
  2970. this.scanner.eat(COLON);
  2971. this.scanner.skipSC();
  2972. switch (this.scanner.tokenType) {
  2973. case NUMBER:
  2974. if (this.scanner.lookupType(1) === IDENTIFIER) {
  2975. value = this.Dimension();
  2976. } else if (this.scanner.lookupNonWSType(1) === SOLIDUS) {
  2977. value = this.Ratio();
  2978. } else {
  2979. value = this.Number();
  2980. }
  2981. break;
  2982. case IDENTIFIER:
  2983. value = this.Identifier();
  2984. break;
  2985. default:
  2986. this.scanner.error("Number, dimension, ratio or identifier is expected");
  2987. }
  2988. this.scanner.skipSC();
  2989. }
  2990. this.scanner.eat(RIGHTPARENTHESIS);
  2991. return {
  2992. type: "MediaFeature",
  2993. loc: this.getLocation(start, this.scanner.tokenStart),
  2994. name: name,
  2995. value: value
  2996. };
  2997. },
  2998. generate: function (node) {
  2999. this.chunk("(");
  3000. this.chunk(node.name);
  3001. if (node.value !== null) {
  3002. this.chunk(":");
  3003. this.node(node.value);
  3004. }
  3005. this.chunk(")");
  3006. }
  3007. };
  3008. // ---
  3009. const MediaQuery = {
  3010. name: "MediaQuery",
  3011. structure: {
  3012. children: [[
  3013. "Identifier",
  3014. "MediaFeature",
  3015. "WhiteSpace"
  3016. ]]
  3017. },
  3018. parse: function () {
  3019. this.scanner.skipSC();
  3020. const children = this.createList();
  3021. let child = null;
  3022. let space = null;
  3023. scan:
  3024. while (!this.scanner.eof) {
  3025. switch (this.scanner.tokenType) {
  3026. case COMMENT:
  3027. this.scanner.next();
  3028. continue;
  3029. case WHITESPACE:
  3030. space = this.WhiteSpace();
  3031. continue;
  3032. case IDENTIFIER:
  3033. child = this.Identifier();
  3034. break;
  3035. case LEFTPARENTHESIS:
  3036. child = this.MediaFeature();
  3037. break;
  3038. default:
  3039. break scan;
  3040. }
  3041. if (space !== null) {
  3042. children.push(space);
  3043. space = null;
  3044. }
  3045. children.push(child);
  3046. }
  3047. if (child === null) {
  3048. this.scanner.error("Identifier or parenthesis is expected");
  3049. }
  3050. return {
  3051. type: "MediaQuery",
  3052. loc: this.getLocationFromList(children),
  3053. children: children
  3054. };
  3055. },
  3056. generate: function (node) {
  3057. this.children(node);
  3058. }
  3059. };
  3060. // ---
  3061. const MediaQueryList = {
  3062. name: "MediaQueryList",
  3063. structure: {
  3064. children: [[
  3065. "MediaQuery"
  3066. ]]
  3067. },
  3068. parse: function (relative) {
  3069. const children = this.createList();
  3070. this.scanner.skipSC();
  3071. while (!this.scanner.eof) {
  3072. children.push(this.MediaQuery(relative));
  3073. if (this.scanner.tokenType !== COMMA) {
  3074. break;
  3075. }
  3076. this.scanner.next();
  3077. }
  3078. return {
  3079. type: "MediaQueryList",
  3080. loc: this.getLocationFromList(children),
  3081. children: children
  3082. };
  3083. },
  3084. generate: function (node) {
  3085. this.children(node, function () {
  3086. this.chunk(",");
  3087. });
  3088. }
  3089. };
  3090. // ---
  3091. // https://drafts.csswg.org/css-syntax-3/#the-anb-type
  3092. const Nth = {
  3093. name: "Nth",
  3094. structure: {
  3095. nth: ["AnPlusB", "Identifier"],
  3096. selector: ["SelectorList", null]
  3097. },
  3098. parse: function (allowOfClause) {
  3099. this.scanner.skipSC();
  3100. const start = this.scanner.tokenStart;
  3101. let end = start;
  3102. let selector = null;
  3103. let query;
  3104. if (this.scanner.lookupValue(0, "odd") || this.scanner.lookupValue(0, "even")) {
  3105. query = this.Identifier();
  3106. } else {
  3107. query = this.AnPlusB();
  3108. }
  3109. this.scanner.skipSC();
  3110. if (allowOfClause && this.scanner.lookupValue(0, "of")) {
  3111. this.scanner.next();
  3112. selector = this.SelectorList();
  3113. if (this.needPositions) {
  3114. end = this.getLastListNode(selector.children).loc.end.offset;
  3115. }
  3116. } else {
  3117. if (this.needPositions) {
  3118. end = query.loc.end.offset;
  3119. }
  3120. }
  3121. return {
  3122. type: "Nth",
  3123. loc: this.getLocation(start, end),
  3124. nth: query,
  3125. selector: selector
  3126. };
  3127. },
  3128. generate: function (node) {
  3129. this.node(node.nth);
  3130. if (node.selector !== null) {
  3131. this.chunk(" of ");
  3132. this.node(node.selector);
  3133. }
  3134. }
  3135. };
  3136. // ---
  3137. const Syntax_Number = {
  3138. name: "Number",
  3139. structure: {
  3140. value: String
  3141. },
  3142. parse: function () {
  3143. return {
  3144. type: "Number",
  3145. loc: this.getLocation(this.scanner.tokenStart, this.scanner.tokenEnd),
  3146. value: this.scanner.consume(NUMBER)
  3147. };
  3148. },
  3149. generate: function (node) {
  3150. this.chunk(node.value);
  3151. }
  3152. };
  3153. // ---
  3154. // '/' | '*' | ',' | ':' | '+' | '-'
  3155. const Operator = {
  3156. name: "Operator",
  3157. structure: {
  3158. value: String
  3159. },
  3160. parse: function () {
  3161. const start = this.scanner.tokenStart;
  3162. this.scanner.next();
  3163. return {
  3164. type: "Operator",
  3165. loc: this.getLocation(start, this.scanner.tokenStart),
  3166. value: this.scanner.substrToCursor(start)
  3167. };
  3168. },
  3169. generate: function (node) {
  3170. this.chunk(node.value);
  3171. }
  3172. };
  3173. // ---
  3174. const Parentheses = {
  3175. name: "Parentheses",
  3176. structure: {
  3177. children: [[]]
  3178. },
  3179. parse: function (readSequence, recognizer) {
  3180. const start = this.scanner.tokenStart;
  3181. let children = null;
  3182. this.scanner.eat(LEFTPARENTHESIS);
  3183. children = readSequence.call(this, recognizer);
  3184. if (!this.scanner.eof) {
  3185. this.scanner.eat(RIGHTPARENTHESIS);
  3186. }
  3187. return {
  3188. type: "Parentheses",
  3189. loc: this.getLocation(start, this.scanner.tokenStart),
  3190. children: children
  3191. };
  3192. },
  3193. generate: function (node) {
  3194. this.chunk("(");
  3195. this.children(node);
  3196. this.chunk(")");
  3197. }
  3198. };
  3199. // ---
  3200. const Percentage = {
  3201. name: "Percentage",
  3202. structure: {
  3203. value: String
  3204. },
  3205. parse: function () {
  3206. const start = this.scanner.tokenStart;
  3207. const number = this.scanner.consume(NUMBER);
  3208. this.scanner.eat(PERCENTSIGN);
  3209. return {
  3210. type: "Percentage",
  3211. loc: this.getLocation(start, this.scanner.tokenStart),
  3212. value: number
  3213. };
  3214. },
  3215. generate: function (node) {
  3216. this.chunk(node.value);
  3217. this.chunk("%");
  3218. }
  3219. };
  3220. // ---
  3221. // : ident [ "(" .. ")" ]?
  3222. const PseudoClassSelector = {
  3223. name: "PseudoClassSelector",
  3224. structure: {
  3225. name: String,
  3226. children: [["Raw"], null]
  3227. },
  3228. parse: function () {
  3229. const start = this.scanner.tokenStart;
  3230. let children = null;
  3231. let name;
  3232. let nameLowerCase;
  3233. this.scanner.eat(COLON);
  3234. if (this.scanner.tokenType === FUNCTION) {
  3235. name = this.scanner.consumeFunctionName();
  3236. nameLowerCase = name.toLowerCase();
  3237. if (this.pseudo.hasOwnProperty(nameLowerCase)) {
  3238. this.scanner.skipSC();
  3239. children = this.pseudo[nameLowerCase].call(this);
  3240. this.scanner.skipSC();
  3241. } else {
  3242. children = this.createList();
  3243. children.push(
  3244. this.Raw(this.scanner.currentToken, 0, 0, false, false)
  3245. );
  3246. }
  3247. this.scanner.eat(RIGHTPARENTHESIS);
  3248. } else {
  3249. name = this.scanner.consume(IDENTIFIER);
  3250. }
  3251. return {
  3252. type: "PseudoClassSelector",
  3253. loc: this.getLocation(start, this.scanner.tokenStart),
  3254. name: name,
  3255. children: children
  3256. };
  3257. },
  3258. generate: function (node) {
  3259. this.chunk(":");
  3260. this.chunk(node.name);
  3261. if (node.children !== null) {
  3262. this.chunk("(");
  3263. this.children(node);
  3264. this.chunk(")");
  3265. }
  3266. },
  3267. walkContext: "function"
  3268. };
  3269. // ---
  3270. // :: ident [ "(" .. ")" ]?
  3271. const PseudoElementSelector = {
  3272. name: "PseudoElementSelector",
  3273. structure: {
  3274. name: String,
  3275. children: [["Raw"], null]
  3276. },
  3277. parse: function () {
  3278. const start = this.scanner.tokenStart;
  3279. let children = null;
  3280. let name;
  3281. let nameLowerCase;
  3282. this.scanner.eat(COLON);
  3283. this.scanner.eat(COLON);
  3284. if (this.scanner.tokenType === FUNCTION) {
  3285. name = this.scanner.consumeFunctionName();
  3286. nameLowerCase = name.toLowerCase();
  3287. if (this.pseudo.hasOwnProperty(nameLowerCase)) {
  3288. this.scanner.skipSC();
  3289. children = this.pseudo[nameLowerCase].call(this);
  3290. this.scanner.skipSC();
  3291. } else {
  3292. children = this.createList();
  3293. children.push(
  3294. this.Raw(this.scanner.currentToken, 0, 0, false, false)
  3295. );
  3296. }
  3297. this.scanner.eat(RIGHTPARENTHESIS);
  3298. } else {
  3299. name = this.scanner.consume(IDENTIFIER);
  3300. }
  3301. return {
  3302. type: "PseudoElementSelector",
  3303. loc: this.getLocation(start, this.scanner.tokenStart),
  3304. name: name,
  3305. children: children
  3306. };
  3307. },
  3308. generate: function (node) {
  3309. this.chunk("::");
  3310. this.chunk(node.name);
  3311. if (node.children !== null) {
  3312. this.chunk("(");
  3313. this.children(node);
  3314. this.chunk(")");
  3315. }
  3316. },
  3317. walkContext: "function"
  3318. };
  3319. // ---
  3320. // Terms of <ratio> should to be a positive number (not zero or negative)
  3321. // (see https://drafts.csswg.org/mediaqueries-3/#values)
  3322. // However, -o-min-device-pixel-ratio takes fractional values as a ratio"s term
  3323. // and this is using by letious sites. Therefore we relax checking on parse
  3324. // to test a term is unsigned number without exponent part.
  3325. // Additional checks may to be applied on lexer validation.
  3326. function consumeNumber(scanner) {
  3327. const value = scanner.consumeNonWS(NUMBER);
  3328. for (let i = 0; i < value.length; i++) {
  3329. const code = value.charCodeAt(i);
  3330. if (!isNumber(code) && code !== FULLSTOP) {
  3331. scanner.error("Unsigned number is expected", scanner.tokenStart - value.length + i);
  3332. }
  3333. }
  3334. if (Number(value) === 0) {
  3335. scanner.error("Zero number is not allowed", scanner.tokenStart - value.length);
  3336. }
  3337. return value;
  3338. }
  3339. // <positive-integer> S* "/" S* <positive-integer>
  3340. const Ratio = {
  3341. name: "Ratio",
  3342. structure: {
  3343. left: String,
  3344. right: String
  3345. },
  3346. parse: function () {
  3347. const start = this.scanner.tokenStart;
  3348. const left = consumeNumber(this.scanner);
  3349. let right;
  3350. this.scanner.eatNonWS(SOLIDUS);
  3351. right = consumeNumber(this.scanner);
  3352. return {
  3353. type: "Ratio",
  3354. loc: this.getLocation(start, this.scanner.tokenStart),
  3355. left: left,
  3356. right: right
  3357. };
  3358. },
  3359. generate: function (node) {
  3360. this.chunk(node.left);
  3361. this.chunk("/");
  3362. this.chunk(node.right);
  3363. }
  3364. };
  3365. // ---
  3366. const Raw = {
  3367. name: "Raw",
  3368. structure: {
  3369. value: String
  3370. },
  3371. parse: function (startToken, endTokenType1, endTokenType2, includeTokenType2, excludeWhiteSpace) {
  3372. const startOffset = this.scanner.getTokenStart(startToken);
  3373. let endOffset;
  3374. this.scanner.skip(
  3375. this.scanner.getRawLength(
  3376. startToken,
  3377. endTokenType1,
  3378. endTokenType2,
  3379. includeTokenType2
  3380. )
  3381. );
  3382. if (excludeWhiteSpace && this.scanner.tokenStart > startOffset) {
  3383. endOffset = this.scanner.getOffsetExcludeWS();
  3384. } else {
  3385. endOffset = this.scanner.tokenStart;
  3386. }
  3387. return {
  3388. type: "Raw",
  3389. loc: this.getLocation(startOffset, endOffset),
  3390. value: this.scanner.source.substring(startOffset, endOffset)
  3391. };
  3392. },
  3393. generate: function (node) {
  3394. this.chunk(node.value);
  3395. }
  3396. };
  3397. // ---
  3398. function Rule_consumeRaw(startToken) {
  3399. return this.Raw(startToken, LEFTCURLYBRACKET, 0, false, true);
  3400. }
  3401. function consumePrelude() {
  3402. const prelude = this.SelectorList();
  3403. if (prelude.type !== "Raw" &&
  3404. this.scanner.eof === false &&
  3405. this.scanner.tokenType !== LEFTCURLYBRACKET) {
  3406. this.scanner.error();
  3407. }
  3408. return prelude;
  3409. }
  3410. const Rule = {
  3411. name: "Rule",
  3412. structure: {
  3413. prelude: ["SelectorList", "Raw"],
  3414. block: ["Block"]
  3415. },
  3416. parse: function () {
  3417. const startToken = this.scanner.currentToken;
  3418. const startOffset = this.scanner.tokenStart;
  3419. let prelude;
  3420. let block;
  3421. if (this.parseRulePrelude) {
  3422. prelude = this.parseWithFallback(consumePrelude, Rule_consumeRaw);
  3423. } else {
  3424. prelude = Rule_consumeRaw.call(this, startToken);
  3425. }
  3426. block = this.Block(true);
  3427. return {
  3428. type: "Rule",
  3429. loc: this.getLocation(startOffset, this.scanner.tokenStart),
  3430. prelude: prelude,
  3431. block: block
  3432. };
  3433. },
  3434. generate: function (node) {
  3435. this.node(node.prelude);
  3436. this.node(node.block);
  3437. },
  3438. walkContext: "rule"
  3439. };
  3440. // ---
  3441. const Syntax_Selector = {
  3442. name: "Selector",
  3443. structure: {
  3444. children: [[
  3445. "TypeSelector",
  3446. "IdSelector",
  3447. "ClassSelector",
  3448. "AttributeSelector",
  3449. "PseudoClassSelector",
  3450. "PseudoElementSelector",
  3451. "Combinator",
  3452. "WhiteSpace"
  3453. ]]
  3454. },
  3455. parse: function () {
  3456. const children = this.readSequence(this.scope.Selector);
  3457. // nothing were consumed
  3458. if (this.getFirstListNode(children) === null) {
  3459. this.scanner.error("Selector is expected");
  3460. }
  3461. return {
  3462. type: "Selector",
  3463. loc: this.getLocationFromList(children),
  3464. children: children
  3465. };
  3466. },
  3467. generate: function (node) {
  3468. this.children(node);
  3469. }
  3470. };
  3471. // ---
  3472. const SelectorList = {
  3473. name: "SelectorList",
  3474. structure: {
  3475. children: [[
  3476. "Selector",
  3477. "Raw"
  3478. ]]
  3479. },
  3480. parse: function () {
  3481. const children = this.createList();
  3482. while (!this.scanner.eof) {
  3483. children.push(this.Selector());
  3484. if (this.scanner.tokenType === COMMA) {
  3485. this.scanner.next();
  3486. continue;
  3487. }
  3488. break;
  3489. }
  3490. return {
  3491. type: "SelectorList",
  3492. loc: this.getLocationFromList(children),
  3493. children: children
  3494. };
  3495. },
  3496. generate: function (node) {
  3497. this.children(node, function () {
  3498. this.chunk(",");
  3499. });
  3500. },
  3501. walkContext: "selector"
  3502. };
  3503. // ---
  3504. const Syntax_String = {
  3505. name: "String",
  3506. structure: {
  3507. value: String
  3508. },
  3509. parse: function () {
  3510. return {
  3511. type: "String",
  3512. loc: this.getLocation(this.scanner.tokenStart, this.scanner.tokenEnd),
  3513. value: this.scanner.consume(STRING)
  3514. };
  3515. },
  3516. generate: function (node) {
  3517. this.chunk(node.value);
  3518. }
  3519. };
  3520. // ---
  3521. function StyleSheet_consumeRaw(startToken) {
  3522. return this.Raw(startToken, 0, 0, false, false);
  3523. }
  3524. const Syntax_StyleSheet = {
  3525. name: "StyleSheet",
  3526. structure: {
  3527. children: [[
  3528. "Comment",
  3529. "CDO",
  3530. "CDC",
  3531. "Atrule",
  3532. "Rule",
  3533. "Raw"
  3534. ]]
  3535. },
  3536. parse: function () {
  3537. const start = this.scanner.tokenStart;
  3538. const children = this.createList();
  3539. let child;
  3540. while (!this.scanner.eof) {
  3541. switch (this.scanner.tokenType) {
  3542. case WHITESPACE:
  3543. this.scanner.next();
  3544. continue;
  3545. case COMMENT:
  3546. // ignore comments except exclamation comments (i.e. /*! .. */) on top level
  3547. if (this.scanner.source.charCodeAt(this.scanner.tokenStart + 2) !== EXCLAMATIONMARK) {
  3548. this.scanner.next();
  3549. continue;
  3550. }
  3551. child = this.Comment();
  3552. break;
  3553. case CDO: // <!--
  3554. child = this.CDO();
  3555. break;
  3556. case CDC: // -->
  3557. child = this.CDC();
  3558. break;
  3559. // CSS Syntax Module Level 3
  3560. // §2.2 Error handling
  3561. // At the "top level" of a stylesheet, an <at-keyword-token> starts an at-rule.
  3562. case ATKEYWORD:
  3563. child = this.parseWithFallback(this.Atrule, StyleSheet_consumeRaw);
  3564. break;
  3565. // Anything else starts a qualified rule ...
  3566. default:
  3567. child = this.parseWithFallback(this.Rule, StyleSheet_consumeRaw);
  3568. }
  3569. children.push(child);
  3570. }
  3571. return {
  3572. type: "StyleSheet",
  3573. loc: this.getLocation(start, this.scanner.tokenStart),
  3574. children: children
  3575. };
  3576. },
  3577. generate: function (node) {
  3578. this.children(node);
  3579. },
  3580. walkContext: "stylesheet"
  3581. };
  3582. // ---
  3583. function eatIdentifierOrAsterisk() {
  3584. if (this.scanner.tokenType !== IDENTIFIER &&
  3585. this.scanner.tokenType !== ASTERISK) {
  3586. this.scanner.error("Identifier or asterisk is expected");
  3587. }
  3588. this.scanner.next();
  3589. }
  3590. // ident
  3591. // ident|ident
  3592. // ident|*
  3593. // *
  3594. // *|ident
  3595. // *|*
  3596. // |ident
  3597. // |*
  3598. const TypeSelector = {
  3599. name: "TypeSelector",
  3600. structure: {
  3601. name: String
  3602. },
  3603. parse: function () {
  3604. const start = this.scanner.tokenStart;
  3605. if (this.scanner.tokenType === VERTICALLINE) {
  3606. this.scanner.next();
  3607. eatIdentifierOrAsterisk.call(this);
  3608. } else {
  3609. eatIdentifierOrAsterisk.call(this);
  3610. if (this.scanner.tokenType === VERTICALLINE) {
  3611. this.scanner.next();
  3612. eatIdentifierOrAsterisk.call(this);
  3613. }
  3614. }
  3615. return {
  3616. type: "TypeSelector",
  3617. loc: this.getLocation(start, this.scanner.tokenStart),
  3618. name: this.scanner.substrToCursor(start)
  3619. };
  3620. },
  3621. generate: function (node) {
  3622. this.chunk(node.name);
  3623. }
  3624. };
  3625. // ---
  3626. function scanUnicodeNumber(scanner) {
  3627. for (let pos = scanner.tokenStart + 1; pos < scanner.tokenEnd; pos++) {
  3628. const code = scanner.source.charCodeAt(pos);
  3629. // break on fullstop or hyperminus/plussign after exponent
  3630. if (code === FULLSTOP || code === PLUSSIGN) {
  3631. // break token, exclude symbol
  3632. scanner.tokenStart = pos;
  3633. return false;
  3634. }
  3635. }
  3636. return true;
  3637. }
  3638. // https://drafts.csswg.org/css-syntax-3/#urange
  3639. function scanUnicodeRange(scanner) {
  3640. const hexStart = scanner.tokenStart + 1; // skip +
  3641. let hexLength = 0;
  3642. scan: {
  3643. if (scanner.tokenType === NUMBER) {
  3644. if (scanner.source.charCodeAt(scanner.tokenStart) !== FULLSTOP && scanUnicodeNumber(scanner)) {
  3645. scanner.next();
  3646. } else if (scanner.source.charCodeAt(scanner.tokenStart) !== HYPHENMINUS) {
  3647. break scan;
  3648. }
  3649. } else {
  3650. scanner.next(); // PLUSSIGN
  3651. }
  3652. if (scanner.tokenType === HYPHENMINUS) {
  3653. scanner.next();
  3654. }
  3655. if (scanner.tokenType === NUMBER) {
  3656. scanner.next();
  3657. }
  3658. if (scanner.tokenType === IDENTIFIER) {
  3659. scanner.next();
  3660. }
  3661. if (scanner.tokenStart === hexStart) {
  3662. scanner.error("Unexpected input", hexStart);
  3663. }
  3664. }
  3665. // validate for U+x{1,6} or U+x{1,6}-x{1,6}
  3666. // where x is [0-9a-fA-F]
  3667. let i;
  3668. let wasHyphenMinus = false;
  3669. for (i = hexStart; i < scanner.tokenStart; i++) {
  3670. const code = scanner.source.charCodeAt(i);
  3671. if (isHex(code) === false && (code !== HYPHENMINUS || wasHyphenMinus)) {
  3672. scanner.error("Unexpected input", i);
  3673. }
  3674. if (code === HYPHENMINUS) {
  3675. // hex sequence shouldn"t be an empty
  3676. if (hexLength === 0) {
  3677. scanner.error("Unexpected input", i);
  3678. }
  3679. wasHyphenMinus = true;
  3680. hexLength = 0;
  3681. } else {
  3682. hexLength++;
  3683. // too long hex sequence
  3684. if (hexLength > 6) {
  3685. scanner.error("Too long hex sequence", i);
  3686. }
  3687. }
  3688. }
  3689. // check we have a non-zero sequence
  3690. if (hexLength === 0) {
  3691. scanner.error("Unexpected input", i - 1);
  3692. }
  3693. // U+abc???
  3694. if (!wasHyphenMinus) {
  3695. // consume as many U+003F QUESTION MARK (?) code points as possible
  3696. for (; hexLength < 6 && !scanner.eof; scanner.next()) {
  3697. if (scanner.tokenType !== QUESTIONMARK) {
  3698. break;
  3699. }
  3700. hexLength++;
  3701. }
  3702. }
  3703. }
  3704. const UnicodeRange = {
  3705. name: "UnicodeRange",
  3706. structure: {
  3707. value: String
  3708. },
  3709. parse: function () {
  3710. const start = this.scanner.tokenStart;
  3711. this.scanner.next(); // U or u
  3712. scanUnicodeRange(this.scanner);
  3713. return {
  3714. type: "UnicodeRange",
  3715. loc: this.getLocation(start, this.scanner.tokenStart),
  3716. value: this.scanner.substrToCursor(start)
  3717. };
  3718. },
  3719. generate: function (node) {
  3720. this.chunk(node.value);
  3721. }
  3722. };
  3723. // ---
  3724. // url "(" S* (string | raw) S* ")"
  3725. const Url = {
  3726. name: "Url",
  3727. structure: {
  3728. value: ["String", "Raw"]
  3729. },
  3730. parse: function () {
  3731. const start = this.scanner.tokenStart;
  3732. let value;
  3733. this.scanner.eat(URL);
  3734. this.scanner.skipSC();
  3735. switch (this.scanner.tokenType) {
  3736. case STRING:
  3737. value = this.String();
  3738. break;
  3739. case RAW:
  3740. value = this.Raw(this.scanner.currentToken, 0, RAW, true, false);
  3741. break;
  3742. default:
  3743. this.scanner.error("String or Raw is expected");
  3744. }
  3745. this.scanner.skipSC();
  3746. this.scanner.eat(RIGHTPARENTHESIS);
  3747. return {
  3748. type: "Url",
  3749. loc: this.getLocation(start, this.scanner.tokenStart),
  3750. value: value
  3751. };
  3752. },
  3753. generate: function (node) {
  3754. this.chunk("url");
  3755. this.chunk("(");
  3756. this.node(node.value);
  3757. this.chunk(")");
  3758. }
  3759. };
  3760. // ---
  3761. const Syntax_Value = {
  3762. name: "Value",
  3763. structure: {
  3764. children: [[]]
  3765. },
  3766. parse: function () {
  3767. const start = this.scanner.tokenStart;
  3768. const children = this.readSequence(this.scope.Value);
  3769. return {
  3770. type: "Value",
  3771. loc: this.getLocation(start, this.scanner.tokenStart),
  3772. children: children
  3773. };
  3774. },
  3775. generate: function (node) {
  3776. this.children(node);
  3777. }
  3778. };
  3779. // ---
  3780. const WhiteSpace_SPACE = Object.freeze({
  3781. type: "WhiteSpace",
  3782. loc: null,
  3783. value: " "
  3784. });
  3785. const WhiteSpace = {
  3786. name: "WhiteSpace",
  3787. structure: {
  3788. value: String
  3789. },
  3790. parse: function () {
  3791. this.scanner.eat(WHITESPACE);
  3792. return WhiteSpace_SPACE;
  3793. // return {
  3794. // type: "WhiteSpace",
  3795. // loc: this.getLocation(this.scanner.tokenStart, this.scanner.tokenEnd),
  3796. // value: this.scanner.consume(WHITESPACE)
  3797. // };
  3798. },
  3799. generate: function (node) {
  3800. this.chunk(node.value);
  3801. }
  3802. };
  3803. // ---
  3804. function processChildren(node, delimeter) {
  3805. const list = node.children;
  3806. let prev = null;
  3807. if (typeof delimeter !== "function") {
  3808. list.forEach(this.node, this);
  3809. } else {
  3810. list.forEach(function (node) {
  3811. if (prev !== null) {
  3812. delimeter.call(this, prev);
  3813. }
  3814. this.node(node);
  3815. prev = node;
  3816. }, this);
  3817. }
  3818. }
  3819. function createGenerator(config) {
  3820. function processNode(node) {
  3821. if (hasOwnProperty.call(types, node.type)) {
  3822. types[node.type].call(this, node);
  3823. } else {
  3824. throw new Error("Unknown node type: " + node.type);
  3825. }
  3826. }
  3827. const types = {};
  3828. if (config.node) {
  3829. for (const name in config.node) {
  3830. types[name] = config.node[name].generate;
  3831. }
  3832. }
  3833. return function (node, options) {
  3834. let buffer = "";
  3835. let handlers = {
  3836. children: processChildren,
  3837. node: processNode,
  3838. chunk: function (chunk) {
  3839. buffer += chunk;
  3840. },
  3841. result: function () {
  3842. return buffer;
  3843. }
  3844. };
  3845. if (options) {
  3846. if (typeof options.decorator === "function") {
  3847. handlers = options.decorator(handlers);
  3848. }
  3849. }
  3850. handlers.node(node);
  3851. return handlers.result();
  3852. };
  3853. }
  3854. // ---
  3855. const node = {
  3856. AnPlusB: AnPlusB,
  3857. Atrule: Atrule,
  3858. AtrulePrelude: Syntax_AtrulePrelude,
  3859. AttributeSelector: AttributeSelector,
  3860. Block: Block,
  3861. Brackets: Brackets,
  3862. CDC: Syntax_CDC,
  3863. CDO: Syntax_CDO,
  3864. ClassSelector: ClassSelector,
  3865. Combinator: Combinator,
  3866. Comment: Syntax_Comment,
  3867. Declaration: Declaration,
  3868. DeclarationList: DeclarationList,
  3869. Dimension: Dimension,
  3870. Function: Syntax_Function,
  3871. HexColor: HexColor,
  3872. Identifier: Identifier,
  3873. IdSelector: IdSelector,
  3874. MediaFeature: MediaFeature,
  3875. MediaQuery: MediaQuery,
  3876. MediaQueryList: MediaQueryList,
  3877. Nth: Nth,
  3878. Number: Syntax_Number,
  3879. Operator: Operator,
  3880. Parentheses: Parentheses,
  3881. Percentage: Percentage,
  3882. PseudoClassSelector: PseudoClassSelector,
  3883. PseudoElementSelector: PseudoElementSelector,
  3884. Ratio: Ratio,
  3885. Raw: Raw,
  3886. Rule: Rule,
  3887. Selector: Syntax_Selector,
  3888. SelectorList: SelectorList,
  3889. String: Syntax_String,
  3890. StyleSheet: Syntax_StyleSheet,
  3891. TypeSelector: TypeSelector,
  3892. UnicodeRange: UnicodeRange,
  3893. Url: Url,
  3894. Value: Syntax_Value,
  3895. WhiteSpace: WhiteSpace
  3896. };
  3897. // ---
  3898. const config = {
  3899. parseContext: {
  3900. default: "StyleSheet",
  3901. stylesheet: "StyleSheet",
  3902. atrule: "Atrule",
  3903. atrulePrelude: function (options) {
  3904. return this.AtrulePrelude(options.atrule ? String(options.atrule) : null);
  3905. },
  3906. mediaQueryList: "MediaQueryList",
  3907. mediaQuery: "MediaQuery",
  3908. rule: "Rule",
  3909. selectorList: "SelectorList",
  3910. selector: "Selector",
  3911. block: function () {
  3912. return this.Block(true);
  3913. },
  3914. declarationList: "DeclarationList",
  3915. declaration: "Declaration",
  3916. value: "Value"
  3917. },
  3918. scope: scope,
  3919. atrule: atrule,
  3920. pseudo: pseudo,
  3921. node: node
  3922. };
  3923. return {
  3924. parse: createParser(config),
  3925. generate: createGenerator(config)
  3926. };
  3927. })();