1
0

css-tree.js 103 KB

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