1
0

css-tree.js 103 KB

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