| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720 |
- /*
- Modded by tophf <github.com/tophf>
- ========== Original disclaimer:
- Parser-Lib
- Copyright (c) 2009-2016 Nicholas C. Zakas. All rights reserved.
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- THE SOFTWARE.
- */
- 'use strict';
- /* eslint-disable class-methods-use-this */
- self.parserlib = (() => {
- //#region Properties
- // Global keywords that can be set for any property are conveniently listed in `all` prop:
- // https://drafts.csswg.org/css-cascade/#all-shorthand
- const GlobalKeywords = ['initial', 'inherit', 'revert', 'unset'];
- const isGlobalKeyword = RegExp.prototype.test.bind(
- new RegExp(`^(${GlobalKeywords.join('|')})$`, 'i'));
- const Properties = {
- 'accent-color': 'auto | <color>',
- 'align-items': 'normal | stretch | <baseline-position> | [ <overflow-position>? <self-position> ]',
- 'align-content': '<align-content>',
- 'align-self': '<align-self>',
- 'all': GlobalKeywords.join(' | '),
- 'alignment-adjust': 'auto | baseline | before-edge | text-before-edge | middle | central | ' +
- 'after-edge | text-after-edge | ideographic | alphabetic | hanging | ' +
- 'mathematical | <len-pct>',
- 'alignment-baseline': 'auto | baseline | use-script | before-edge | text-before-edge | ' +
- 'after-edge | text-after-edge | central | middle | ideographic | alphabetic | ' +
- 'hanging | mathematical',
- 'animation': '[ <time> || <single-timing-function> || <time> || [ infinite | <number> ] || ' +
- '<single-animation-direction> || <single-animation-fill-mode> || ' +
- '[ running | paused ] || [ none | <ident> | <string> ] ]#',
- 'animation-delay': '<time>#',
- 'animation-direction': '<single-animation-direction>#',
- 'animation-duration': '<time>#',
- 'animation-fill-mode': '<single-animation-fill-mode>#',
- 'animation-iteration-count': '[ <number> | infinite ]#',
- 'animation-name': '[ none | <single-animation-name> ]#',
- 'animation-play-state': '[ running | paused ]#',
- 'animation-timing-function': '<single-timing-function>#',
- 'appearance': 'none | auto',
- '-moz-appearance':
- 'none | button | button-arrow-down | button-arrow-next | button-arrow-previous | ' +
- 'button-arrow-up | button-bevel | button-focus | caret | checkbox | checkbox-container | ' +
- 'checkbox-label | checkmenuitem | dualbutton | groupbox | listbox | listitem | ' +
- 'menuarrow | menubar | menucheckbox | menuimage | menuitem | menuitemtext | menulist | ' +
- 'menulist-button | menulist-text | menulist-textfield | menupopup | menuradio | ' +
- 'menuseparator | meterbar | meterchunk | progressbar | progressbar-vertical | ' +
- 'progresschunk | progresschunk-vertical | radio | radio-container | radio-label | ' +
- 'radiomenuitem | range | range-thumb | resizer | resizerpanel | scale-horizontal | ' +
- 'scalethumbend | scalethumb-horizontal | scalethumbstart | scalethumbtick | ' +
- 'scalethumb-vertical | scale-vertical | scrollbarbutton-down | scrollbarbutton-left | ' +
- 'scrollbarbutton-right | scrollbarbutton-up | scrollbarthumb-horizontal | ' +
- 'scrollbarthumb-vertical | scrollbartrack-horizontal | scrollbartrack-vertical | ' +
- 'searchfield | separator | sheet | spinner | spinner-downbutton | spinner-textfield | ' +
- 'spinner-upbutton | splitter | statusbar | statusbarpanel | tab | tabpanel | tabpanels | ' +
- 'tab-scroll-arrow-back | tab-scroll-arrow-forward | textfield | textfield-multiline | ' +
- 'toolbar | toolbarbutton | toolbarbutton-dropdown | toolbargripper | toolbox | tooltip | ' +
- 'treeheader | treeheadercell | treeheadersortarrow | treeitem | treeline | treetwisty | ' +
- 'treetwistyopen | treeview | -moz-mac-unified-toolbar | -moz-win-borderless-glass | ' +
- '-moz-win-browsertabbar-toolbox | -moz-win-communicationstext | ' +
- '-moz-win-communications-toolbox | -moz-win-exclude-glass | -moz-win-glass | ' +
- '-moz-win-mediatext | -moz-win-media-toolbox | -moz-window-button-box | ' +
- '-moz-window-button-box-maximized | -moz-window-button-close | ' +
- '-moz-window-button-maximize | -moz-window-button-minimize | -moz-window-button-restore | ' +
- '-moz-window-frame-bottom | -moz-window-frame-left | -moz-window-frame-right | ' +
- '-moz-window-titlebar | -moz-window-titlebar-maximized',
- '-ms-appearance':
- 'none | icon | window | desktop | workspace | document | tooltip | dialog | button | ' +
- 'push-button | hyperlink | radio | radio-button | checkbox | menu-item | tab | menu | ' +
- 'menubar | pull-down-menu | pop-up-menu | list-menu | radio-group | checkbox-group | ' +
- 'outline-tree | range | field | combo-box | signature | password | normal',
- '-webkit-appearance':
- 'auto | none | button | button-bevel | caps-lock-indicator | caret | checkbox | ' +
- 'default-button | listbox | listitem | media-fullscreen-button | media-mute-button | ' +
- 'media-play-button | media-seek-back-button | media-seek-forward-button | media-slider | ' +
- 'media-sliderthumb | menulist | menulist-button | menulist-text | menulist-textfield | ' +
- 'push-button | radio | searchfield | searchfield-cancel-button | searchfield-decoration | ' +
- 'searchfield-results-button | searchfield-results-decoration | slider-horizontal | ' +
- 'slider-vertical | sliderthumb-horizontal | sliderthumb-vertical | square-button | ' +
- 'textarea | textfield | scrollbarbutton-down | scrollbarbutton-left | ' +
- 'scrollbarbutton-right | scrollbarbutton-up | scrollbargripper-horizontal | ' +
- 'scrollbargripper-vertical | scrollbarthumb-horizontal | scrollbarthumb-vertical | ' +
- 'scrollbartrack-horizontal | scrollbartrack-vertical',
- '-o-appearance':
- 'none | window | desktop | workspace | document | tooltip | dialog | button | ' +
- 'push-button | hyperlink | radio | radio-button | checkbox | menu-item | tab | menu | ' +
- 'menubar | pull-down-menu | pop-up-menu | list-menu | radio-group | checkbox-group | ' +
- 'outline-tree | range | field | combo-box | signature | password | normal',
- 'aspect-ratio': 'auto || [ <num0+> / <num0+> ]',
- 'azimuth': '<azimuth>',
- 'backdrop-filter': '<filter-function-list> | none',
- 'backface-visibility': 'visible | hidden',
- 'background': '[ <bg-layer> , ]* <final-bg-layer>',
- 'background-attachment': '<attachment>#',
- 'background-blend-mode': '<blend-mode>',
- 'background-clip': '[ <box> | text ]#',
- 'background-color': '<color>',
- 'background-image': '<bg-image>#',
- 'background-origin': '<box>#',
- 'background-position': '<bg-position>#',
- 'background-position-x': '[ center | [ left | right ]? <len-pct>? ]#',
- 'background-position-y': '[ center | [ top | bottom ]? <len-pct>? ]#',
- 'background-repeat': '<repeat-style>#',
- 'background-size': '<bg-size>#',
- 'baseline-shift': 'baseline | sub | super | <len-pct>',
- 'behavior': 1,
- 'binding': 1,
- 'bleed': '<length>',
- 'block-size': '<width>',
- 'bookmark-label': '<content-list>',
- 'bookmark-level': 'none | <integer>',
- 'bookmark-state': 'open | closed',
- 'bookmark-target': 'none | <uri>',
- 'border-boundary': 'none | parent | display',
- 'border-collapse': 'collapse | separate',
- 'border-image': '[ none | <image> ] || <border-image-slice> ' +
- '[ / <border-image-width> | / <border-image-width>? / <border-image-outset> ]? || ' +
- '<border-image-repeat>',
- 'border-image-outset': '<border-image-outset>',
- 'border-image-repeat': '<border-image-repeat>',
- 'border-image-slice': '<border-image-slice>',
- 'border-image-source': '<image> | none',
- 'border-image-width': '<border-image-width>',
- 'border-spacing': '<length>{1,2}',
- 'border-bottom-left-radius': '<len-pct>{1,2}',
- 'border-bottom-right-radius': '<len-pct>{1,2}',
- 'border-end-end-radius': '<len-pct>{1,2}',
- 'border-end-start-radius': '<len-pct>{1,2}',
- 'border-radius': '<border-radius>',
- 'border-start-end-radius': '<len-pct>{1,2}',
- 'border-start-start-radius': '<len-pct>{1,2}',
- 'border-top-left-radius': '<len-pct>{1,2}',
- 'border-top-right-radius': '<len-pct>{1,2}',
- 'bottom': '<width>',
- 'box-decoration-break': 'slice | clone',
- 'box-shadow': '<box-shadow>',
- 'box-sizing': 'content-box | border-box',
- 'break-after': 'auto | always | avoid | left | right | page | column | avoid-page | avoid-column',
- 'break-before': 'auto | always | avoid | left | right | page | column | avoid-page | avoid-column',
- 'break-inside': 'auto | avoid | avoid-page | avoid-column',
- '-moz-box-align': 1,
- '-moz-box-decoration-break': 1,
- '-moz-box-direction': 1,
- '-moz-box-flex': 1,
- '-moz-box-flex-group': 1,
- '-moz-box-lines': 1,
- '-moz-box-ordinal-group': 1,
- '-moz-box-orient': 1,
- '-moz-box-pack': 1,
- '-o-box-decoration-break': 1,
- '-webkit-box-align': 1,
- '-webkit-box-decoration-break': 1,
- '-webkit-box-direction': 1,
- '-webkit-box-flex': 1,
- '-webkit-box-flex-group': 1,
- '-webkit-box-lines': 1,
- '-webkit-box-ordinal-group': 1,
- '-webkit-box-orient': 1,
- '-webkit-box-pack': 1,
- 'caret-color': 'auto | <color>',
- 'caption-side': 'top | bottom | inline-start | inline-end',
- 'clear': 'none | right | left | both | inline-start | inline-end',
- 'clip': 'rect( [ <length> | auto ]#{4} ) | auto',
- 'clip-path': '<clip-source> | <clip-path> | none',
- 'clip-rule': 'nonzero | evenodd',
- 'color': '<color>',
- 'color-adjust': 'economy | exact',
- 'color-interpolation': 'auto | sRGB | linearRGB',
- 'color-interpolation-filters': 'auto | sRGB | linearRGB',
- 'color-profile': 1,
- 'color-rendering': 'auto | optimizeSpeed | optimizeQuality',
- 'color-scheme': 'normal | [ light | dark ]+',
- 'column-count': '<integer> | auto',
- 'column-fill': 'auto | balance',
- 'column-gap': '<column-gap>',
- 'column-rule': '<border-shorthand>',
- 'column-rule-color': '<color>',
- 'column-rule-style': '<border-style>',
- 'column-rule-width': '<border-width>',
- 'column-span': 'none | all',
- 'column-width': '<length> | auto',
- 'columns': 1,
- 'contain': 'none | strict | content | [ size || layout || style || paint ]',
- 'contain-intrinsic-size': 'none | <length>{1,2}',
- 'content': 'normal | none | <content-list> [ / <string> ]?',
- 'content-visibility': 'visible | auto | hidden',
- 'counter-increment': '<counter>',
- 'counter-reset': '<counter>',
- 'counter-set': '<counter>',
- 'cue': 'cue-after | cue-before',
- 'cue-after': 1,
- 'cue-before': 1,
- 'cursor': '[ <uri> [ <number> <number> ]? , ]* ' +
- '[ auto | default | none | context-menu | help | pointer | progress | wait | ' +
- 'cell | crosshair | text | vertical-text | alias | copy | move | no-drop | ' +
- 'not-allowed | grab | grabbing | e-resize | n-resize | ne-resize | nw-resize | ' +
- 's-resize | se-resize | sw-resize | w-resize | ew-resize | ns-resize | ' +
- 'nesw-resize | nwse-resize | col-resize | row-resize | all-scroll | ' +
- 'zoom-in | zoom-out ]',
- 'direction': 'ltr | rtl',
- 'display': '[ <display-outside> || <display-inside> ] | ' +
- '<display-listitem> | <display-internal> | <display-box> | <display-legacy> | ' +
- // deprecated and nonstandard
- '-webkit-box | -webkit-inline-box | -ms-flexbox',
- 'dominant-baseline': 'auto | use-script | no-change | reset-size | ideographic | alphabetic | ' +
- 'hanging | mathematical | central | middle | text-after-edge | text-before-edge',
- 'drop-initial-after-adjust': 'central | middle | after-edge | text-after-edge | ideographic | ' +
- 'alphabetic | mathematical | <len-pct>',
- 'drop-initial-after-align': 'baseline | use-script | before-edge | text-before-edge | ' +
- 'after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | ' +
- 'mathematical',
- 'drop-initial-before-adjust': 'before-edge | text-before-edge | central | middle | ' +
- 'hanging | mathematical | <len-pct>',
- 'drop-initial-before-align': 'caps-height | baseline | use-script | before-edge | ' +
- 'text-before-edge | after-edge | text-after-edge | central | middle | ideographic | ' +
- 'alphabetic | hanging | mathematical',
- 'drop-initial-size': 'auto | line | <len-pct>',
- 'drop-initial-value': '<integer>',
- 'elevation': '<angle> | below | level | above | higher | lower',
- 'empty-cells': 'show | hide',
- 'enable-background': 1,
- 'fill': '<paint>',
- 'fill-opacity': '<opacity-value>',
- 'fill-rule': 'nonzero | evenodd',
- 'filter': '<filter-function-list> | <ie-function> | none',
- 'fit': 'fill | hidden | meet | slice',
- 'fit-position': 1,
- 'flex': '<flex-shorthand>',
- 'flex-basis': '<width>',
- 'flex-direction': 'row | row-reverse | column | column-reverse',
- 'flex-flow': '<flex-direction> || <flex-wrap>',
- 'flex-grow': '<number>',
- 'flex-shrink': '<number>',
- 'flex-wrap': 'nowrap | wrap | wrap-reverse',
- 'float': 'left | right | none | inline-start | inline-end',
- 'float-offset': 1,
- 'flood-color': 1,
- 'flood-opacity': '<opacity-value>',
- // matching no-pct first because Matcher doesn't retry for a longer match in nested definitions
- 'font': '<font-short-tweak-no-pct>? <font-short-core> | ' +
- '[ <font-short-tweak-no-pct> || <pct> ]? <font-short-core> | ' +
- 'caption | icon | menu | message-box | small-caption | status-bar',
- 'font-family': '<font-family>',
- 'font-feature-settings': '<feature-tag-value># | normal',
- 'font-kerning': 'auto | normal | none',
- 'font-language-override': 'normal | <string>',
- 'font-optical-sizing': 'auto | none',
- 'font-palette': 'none | normal | light | dark | <ident>',
- 'font-size': '<font-size>',
- 'font-size-adjust': '<number> | none',
- 'font-stretch': '<font-stretch>',
- 'font-style': '<font-style>',
- 'font-synthesis': 'none | [ weight || style ]',
- 'font-synthesis-style': 'auto | none',
- 'font-synthesis-weight': 'auto | none',
- 'font-synthesis-small-caps': 'auto | none',
- 'font-variant': '<font-variant>',
- 'font-variant-alternates': '<font-variant-alternates> | normal',
- 'font-variant-caps': '<font-variant-caps> | normal',
- 'font-variant-east-asian': '<font-variant-east-asian> | normal',
- 'font-variant-emoji': 'auto | text | emoji | unicode',
- 'font-variant-ligatures': '<font-variant-ligatures> | normal | none',
- 'font-variant-numeric': '<font-variant-numeric> | normal',
- 'font-variant-position': 'normal | sub | super',
- 'font-variation-settings': 'normal | [ <string> <number> ]#',
- 'font-weight': '<font-weight>',
- 'forced-color-adjust': 'auto | none',
- '-ms-flex-align': 1,
- '-ms-flex-order': 1,
- '-ms-flex-pack': 1,
- 'gap': '<row-gap> <column-gap>?',
- 'glyph-orientation-horizontal': '<glyph-angle>',
- 'glyph-orientation-vertical': 'auto | <glyph-angle>',
- 'grid': '<grid-template> | <grid-template-rows> / [ auto-flow && dense? ] <grid-auto-columns>? | ' +
- '[ auto-flow && dense? ] <grid-auto-rows>? / <grid-template-columns>',
- 'grid-area': '<grid-line> [ / <grid-line> ]{0,3}',
- 'grid-auto-columns': '<grid-auto-columns>',
- 'grid-auto-flow': '[ row | column ] || dense',
- 'grid-auto-rows': '<grid-auto-rows>',
- 'grid-column': '<grid-line> [ / <grid-line> ]?',
- 'grid-column-start': '<grid-line>',
- 'grid-column-end': '<grid-line>',
- 'grid-row': '<grid-line> [ / <grid-line> ]?',
- 'grid-row-start': '<grid-line>',
- 'grid-row-end': '<grid-line>',
- 'grid-template': 'none | [ <grid-template-rows> / <grid-template-columns> ] | ' +
- '[ <line-names>? <string> <track-size>? <line-names>? ]+ [ / <explicit-track-list> ]?',
- 'grid-template-areas': 'none | <string>+',
- 'grid-template-columns': '<grid-template-columns>',
- 'grid-template-rows': '<grid-template-rows>',
- 'grid-row-gap': '<row-gap>',
- 'grid-column-gap': '<column-gap>',
- 'grid-gap': '<row-gap> <column-gap>?',
- 'hanging-punctuation': 'none | [ first || [ force-end | allow-end ] || last ]',
- 'height': 'auto | <width-height>',
- 'hyphenate-after': '<integer> | auto',
- 'hyphenate-before': '<integer> | auto',
- 'hyphenate-character': '<string> | auto',
- 'hyphenate-lines': 'no-limit | <integer>',
- 'hyphenate-resource': 1,
- 'hyphens': 'none | manual | auto',
- 'icon': 1,
- 'image-orientation': 'from-image | none | [ <angle> || flip ]',
- 'image-rendering': 'auto | smooth | high-quality | crisp-edges | pixelated | ' +
- 'optimizeSpeed | optimizeQuality',
- 'image-resolution': 1,
- 'ime-mode': 'auto | normal | active | inactive | disabled',
- 'inline-box-align': 'last | <integer>',
- 'inline-size': '<width>',
- 'inset': '<width>{1,4}',
- 'inset-block': '<width>{1,2}',
- 'inset-block-end': '<width>',
- 'inset-block-start': '<width>',
- 'inset-inline': '<width>{1,2}',
- 'inset-inline-end': '<width>',
- 'inset-inline-start': '<width>',
- 'isolation': 'auto | isolate',
- 'justify-content': '<justify-content>',
- 'justify-items': 'normal | stretch | <baseline-position> | ' +
- '[ <overflow-position>? <self-position> ] | ' +
- '[ legacy || [ left | right | center ] ]',
- 'justify-self': '<justify-self>',
- 'kerning': 'auto | <length>',
- 'left': '<width>',
- 'letter-spacing': '<length> | normal',
- 'line-height': '<line-height>',
- 'line-break': 'auto | loose | normal | strict | anywhere',
- 'line-stacking': 1,
- 'line-stacking-ruby': 'exclude-ruby | include-ruby',
- 'line-stacking-shift': 'consider-shifts | disregard-shifts',
- 'line-stacking-strategy': 'inline-line-height | block-line-height | max-height | grid-height',
- 'list-style': 1,
- 'list-style-image': '<uri> | none',
- 'list-style-position': 'inside | outside',
- 'list-style-type': '<string> | disc | circle | square | decimal | decimal-leading-zero | ' +
- 'lower-roman | upper-roman | lower-greek | lower-latin | upper-latin | armenian | ' +
- 'georgian | lower-alpha | upper-alpha | none',
- 'margin': '<width>{1,4}',
- 'margin-bottom': '<width>',
- 'margin-left': '<width>',
- 'margin-right': '<width>',
- 'margin-top': '<width>',
- 'margin-block': '<width>{1,2}',
- 'margin-block-end': '<width>',
- 'margin-block-start': '<width>',
- 'margin-inline': '<width>{1,2}',
- 'margin-inline-end': '<width>',
- 'margin-inline-start': '<width>',
- 'mark': 1,
- 'mark-after': 1,
- 'mark-before': 1,
- 'marker': 1,
- 'marker-end': 1,
- 'marker-mid': 1,
- 'marker-start': 1,
- 'marks': 1,
- 'marquee-direction': 1,
- 'marquee-play-count': 1,
- 'marquee-speed': 1,
- 'marquee-style': 1,
- 'mask': 1,
- 'mask-image': '[ none | <image> | <uri> ]#',
- 'max-height': 'none | <width-height>',
- 'max-width': 'none | <width-height>',
- 'min-height': 'auto | <width-height>',
- 'min-width': 'auto | <width-height>',
- 'max-block-size': '<len-pct> | none',
- 'max-inline-size': '<len-pct> | none',
- 'min-block-size': '<len-pct>',
- 'min-inline-size': '<len-pct>',
- 'mix-blend-mode': '<blend-mode>',
- 'move-to': 1,
- 'nav-down': 1,
- 'nav-index': 1,
- 'nav-left': 1,
- 'nav-right': 1,
- 'nav-up': 1,
- 'object-fit': 'fill | contain | cover | none | scale-down',
- 'object-position': '<position>',
- 'opacity': '<opacity-value> | <pct>',
- 'order': '<integer>',
- 'orphans': '<integer>',
- 'outline': '[ <color> | invert ] || [ auto | <border-style> ] || <border-width>',
- 'outline-color': '<color> | invert',
- 'outline-offset': '<length>',
- 'outline-style': '<border-style> | auto',
- 'outline-width': '<border-width>',
- 'overflow': '<overflow>{1,2}',
- 'overflow-anchor': 'auto | none',
- 'overflow-block': '<overflow>',
- 'overflow-clip-margin': '<len0+>',
- 'overflow-inline': '<overflow>',
- 'overflow-style': 1,
- 'overflow-wrap': 'normal | break-word | anywhere',
- 'overflow-x': '<overflow>',
- 'overflow-y': '<overflow>',
- 'overscroll-behavior': '<overscroll>{1,2}',
- 'overscroll-behavior-block': '<overscroll>',
- 'overscroll-behavior-inline': '<overscroll>',
- 'overscroll-behavior-x': '<overscroll>',
- 'overscroll-behavior-y': '<overscroll>',
- 'padding': '<len-pct0+>{1,4}',
- 'padding-block': '<len-pct0+>{1,2}',
- 'padding-block-end': '<len-pct0+>',
- 'padding-block-start': '<len-pct0+>',
- 'padding-bottom': '<len-pct0+>',
- 'padding-inline': '<len-pct0+>{1,2}',
- 'padding-inline-end': '<len-pct0+>',
- 'padding-inline-start': '<len-pct0+>',
- 'padding-left': '<len-pct0+>',
- 'padding-right': '<len-pct0+>',
- 'padding-top': '<len-pct0+>',
- 'page': 1,
- 'page-break-after': 'auto | always | avoid | left | right | recto | verso',
- 'page-break-before': 'auto | always | avoid | left | right | recto | verso',
- 'page-break-inside': 'auto | avoid',
- 'page-policy': 1,
- 'pause': 1,
- 'pause-after': 1,
- 'pause-before': 1,
- 'perspective': 'none | <len0+>',
- 'perspective-origin': '<position>',
- 'phonemes': 1,
- 'pitch': 1,
- 'pitch-range': 1,
- 'place-content': '<align-content> <justify-content>?',
- 'place-items': '[ normal | stretch | <baseline-position> | <self-position> ] ' +
- '[ normal | stretch | <baseline-position> | <self-position> ]?',
- 'place-self': '<align-self> <justify-self>?',
- 'play-during': 1,
- 'pointer-events': 'auto | none | visiblePainted | visibleFill | visibleStroke | visible | ' +
- 'painted | fill | stroke | all',
- 'position': 'static | relative | absolute | fixed | sticky | -webkit-sticky',
- 'presentation-level': 1,
- 'punctuation-trim': 1,
- 'quotes': 1,
- 'rendering-intent': 1,
- 'resize': 'none | both | horizontal | vertical | block | inline',
- 'rest': 1,
- 'rest-after': 1,
- 'rest-before': 1,
- 'richness': 1,
- 'right': '<width>',
- 'rotate': 'none | [ x | y | z | <number>{3} ]? && <angle>',
- 'rotation': 1,
- 'rotation-point': 1,
- 'row-gap': '<row-gap>',
- 'ruby-align': 1,
- 'ruby-overhang': 1,
- 'ruby-position': 1,
- 'ruby-span': 1,
- 'scale': 'none | <num-pct>{1,3}',
- 'scroll-behavior': 'auto | smooth',
- 'scroll-margin': '<length>{1,4}',
- 'scroll-margin-bottom': '<length>',
- 'scroll-margin-left': '<length>',
- 'scroll-margin-right': '<length>',
- 'scroll-margin-top': '<length>',
- 'scroll-margin-block': '<length>{1,2}',
- 'scroll-margin-block-end': '<length>',
- 'scroll-margin-block-start': '<length>',
- 'scroll-margin-inline': '<length>{1,2}',
- 'scroll-margin-inline-end': '<length>',
- 'scroll-margin-inline-start': '<length>',
- 'scroll-padding': '<width>{1,4}',
- 'scroll-padding-left': '<width>',
- 'scroll-padding-right': '<width>',
- 'scroll-padding-top': '<width>',
- 'scroll-padding-bottom': '<width>',
- 'scroll-padding-block': '<width>{1,2}',
- 'scroll-padding-block-end': '<width>',
- 'scroll-padding-block-start': '<width>',
- 'scroll-padding-inline': '<width>{1,2}',
- 'scroll-padding-inline-end': '<width>',
- 'scroll-padding-inline-start': '<width>',
- 'scroll-snap-align': '[ none | start | end | center ]{1,2}',
- 'scroll-snap-stop': 'normal | always',
- 'scroll-snap-type': 'none | [ x | y | block | inline | both ] [ mandatory | proximity ]?',
- 'scrollbar-color': 'auto | dark | light | <color>{2}',
- 'scrollbar-width': 'auto | thin | none',
- 'shape-inside': 'auto | outside-shape | [ <basic-shape> || shape-box ] | <image> | display',
- 'shape-rendering': 'auto | optimizeSpeed | crispEdges | geometricPrecision',
- 'size': 1,
- 'speak': 'normal | none | spell-out',
- 'speak-header': 'once | always',
- 'speak-numeral': 'digits | continuous',
- 'speak-punctuation': 'code | none',
- 'speech-rate': 1,
- 'stop-color': 1,
- 'stop-opacity': '<opacity-value>',
- 'stress': 1,
- 'string-set': 1,
- 'stroke': '<paint>',
- 'stroke-dasharray': 'none | <dasharray>',
- 'stroke-dashoffset': '<len-pct> | <number>',
- 'stroke-linecap': 'butt | round | square',
- 'stroke-linejoin': 'miter | miter-clip | round | bevel | arcs',
- 'stroke-miterlimit': '<num0+>',
- 'stroke-opacity': '<opacity-value>',
- 'stroke-width': '<len-pct> | <number>',
- 'table-layout': 'auto | fixed',
- 'tab-size': '<number> | <length>',
- 'target': 1,
- 'target-name': 1,
- 'target-new': 1,
- 'target-position': 1,
- 'text-align': '<text-align> | justify-all',
- 'text-align-all': '<text-align>',
- 'text-align-last': '<text-align> | auto',
- 'text-anchor': 'start | middle | end',
- 'text-decoration': '<text-decoration-line> || <text-decoration-style> || <color>',
- 'text-decoration-color': '<color>',
- 'text-decoration-line': '<text-decoration-line>',
- 'text-decoration-skip': 'none | ' +
- '[ objects || [ spaces | [ leading-spaces || trailing-spaces ] ] || edges || box-decoration ]',
- 'text-decoration-style': '<text-decoration-style>',
- 'text-emphasis': '<text-emphasis-style> || <color>',
- 'text-emphasis-style': '<text-emphasis-style>',
- 'text-emphasis-position': '[ over | under ] && [ right | left ]?',
- 'text-height': 1,
- 'text-indent': '<len-pct> && hanging? && each-line?',
- 'text-justify': 'auto | none | inter-word | inter-character',
- 'text-outline': 1,
- 'text-overflow': 'clip | ellipsis',
- 'text-rendering': 'auto | optimizeSpeed | optimizeLegibility | geometricPrecision',
- 'text-shadow': 'none | [ <color>? && <length>{2,3} ]#',
- 'text-transform': 'none | [ capitalize | uppercase | lowercase ] || full-width || full-size-kana',
- 'text-underline-position': 'auto | [ under || [ left | right ] ]',
- 'text-wrap': 'normal | none | avoid',
- 'top': '<width>',
- 'touch-action': 'auto | none | ' +
- 'pan-x | pan-y | pan-left | pan-right | pan-up | pan-down | manipulation',
- 'transform': 'none | <transform-function>+',
- 'transform-box': 'border-box | fill-box | view-box',
- 'transform-origin': '<transform-origin>',
- 'transform-style': 'flat | preserve-3d',
- 'transition': '<transition>#',
- 'transition-delay': '<time>#',
- 'transition-duration': '<time>#',
- 'transition-property': 'none | [ all | <ident> ]#',
- 'transition-timing-function': '<single-timing-function>#',
- 'translate': 'none | <len-pct> [ <len-pct> <length>? ]?',
- 'unicode-range': '<unicode-range>#',
- 'unicode-bidi': 'normal | embed | isolate | bidi-override | isolate-override | plaintext',
- 'user-modify': 'read-only | read-write | write-only',
- 'user-select': 'auto | text | none | contain | all',
- 'vertical-align': 'auto | use-script | baseline | sub | super | top | text-top | ' +
- 'central | middle | bottom | text-bottom | <len-pct>',
- 'visibility': 'visible | hidden | collapse',
- 'voice-balance': 1,
- 'voice-duration': 1,
- 'voice-family': 1,
- 'voice-pitch': 1,
- 'voice-pitch-range': 1,
- 'voice-rate': 1,
- 'voice-stress': 1,
- 'voice-volume': 1,
- 'volume': 1,
- 'white-space': 'normal | pre | nowrap | pre-wrap | break-spaces | pre-line',
- 'white-space-collapse': 1,
- 'widows': '<integer>',
- 'width': 'auto | <width-height>',
- 'will-change': '<will-change>',
- 'word-break': 'normal | keep-all | break-all | break-word',
- 'word-spacing': '<length> | normal',
- 'word-wrap': 'normal | break-word | anywhere',
- 'writing-mode': 'horizontal-tb | vertical-rl | vertical-lr | ' +
- 'lr-tb | rl-tb | tb-rl | bt-rl | tb-lr | bt-lr | lr-bt | rl-bt | lr | rl | tb',
- 'z-index': '<integer> | auto',
- 'zoom': '<number> | <pct> | normal',
- // nonstandard https://compat.spec.whatwg.org/
- '-webkit-box-reflect': '[ above | below | right | left ]? <length>? <image>?',
- '-webkit-text-fill-color': '<color>',
- '-webkit-text-stroke': '<border-width> || <color>',
- '-webkit-text-stroke-color': '<color>',
- '-webkit-text-stroke-width': '<border-width>',
- };
- const ScopedProperties = {
- '@font-face': Object.assign({
- 'ascent-override': '[ normal | <pct0+> ]{1,2}',
- 'descent-override': '[ normal | <pct0+> ]{1,2}',
- 'font-display': 'auto | block | swap | fallback | optional',
- 'font-stretch': 'auto | <font-stretch>{1,2}',
- 'font-style': 'auto | normal | italic | oblique <angle>{0,2}',
- 'font-weight': 'auto | [ normal | bold | <num1-1000> ]{1,2}',
- 'line-gap-override': '[ normal | <pct0+> ]{1,2}',
- 'size-adjust': '<pct0+>',
- 'src': '[ url() [ format( <string># ) ]? | local( <family-name> ) ]#',
- }, ...[
- 'font-family',
- 'font-size',
- 'font-variant',
- 'font-variation-settings',
- 'unicode-range',
- ].map(p => ({[p]: Properties[p]}))),
- };
- for (const [k, reps] of Object.entries({
- 'border': '{1,4}',
- 'border-bottom': '',
- 'border-left': '',
- 'border-right': '',
- 'border-top': '',
- 'border-block': '{1,2}',
- 'border-block-end': '',
- 'border-block-start': '',
- 'border-inline': '{1,2}',
- 'border-inline-end': '',
- 'border-inline-start': '',
- })) {
- Properties[k] = '<border-shorthand>';
- Properties[`${k}-color`] = '<color>' + reps;
- Properties[`${k}-style`] = '<border-style>' + reps;
- Properties[`${k}-width`] = '<border-width>' + reps;
- }
- //#endregion
- //#region Types
- const TYPES = /** @namespace Parser */ {
- DEFAULT_TYPE: 0,
- COMBINATOR_TYPE: 1,
- MEDIA_FEATURE_TYPE: 2,
- MEDIA_QUERY_TYPE: 3,
- PROPERTY_NAME_TYPE: 4,
- PROPERTY_VALUE_TYPE: 5,
- PROPERTY_VALUE_PART_TYPE: 6,
- SELECTOR_TYPE: 7,
- SELECTOR_PART_TYPE: 8,
- SELECTOR_SUB_PART_TYPE: 9,
- };
- const UNITS = {
- em: 'length',
- rem: 'length',
- ex: 'length',
- px: 'length',
- cm: 'length',
- mm: 'length',
- in: 'length',
- pt: 'length',
- pc: 'length',
- ch: 'length',
- vh: 'length',
- vw: 'length',
- vmax: 'length',
- vmin: 'length',
- fr: 'length',
- q: 'length',
- deg: 'angle',
- rad: 'angle',
- grad: 'angle',
- turn: 'angle',
- ms: 'time',
- s: 'time',
- hz: 'frequency',
- khz: 'frequency',
- dpi: 'resolution',
- dpcm: 'resolution',
- dppx: 'resolution',
- x: 'resolution',
- };
- // Sticky `y` flag must be used in expressions used with peekTest and readMatch
- const rxIdentStart = /[-\\_a-zA-Z\u00A0-\uFFFF]/u;
- const rxNameChar = /[-\\_\da-zA-Z\u00A0-\uFFFF]/u;
- const rxNameCharNoEsc = /[-_\da-zA-Z\u00A0-\uFFFF]+/yu; // must not match \\
- const rxUnquotedUrlCharNoEsc = /[-!#$%&*-[\]-~\u00A0-\uFFFF]+/yu; // must not match \\
- const rxVendorPrefix = /^-(webkit|moz|ms|o)-(.+)/i;
- const rxCalc = /^(?:-(webkit|moz|ms|o)-)?(calc|min|max|clamp)\(/i;
- const lowercaseCache = new Map();
- //#endregion
- //#region ValidationTypes - definitions
- /** Allowed syntax: text, |, <syntax>, func() */
- const VTSimple = {
- '<absolute-size>': 'xx-small | x-small | small | medium | large | x-large | xx-large',
- '<animateable-feature>': 'scroll-position | contents | <animateable-feature-name>',
- '<animateable-feature-name>': p => vtIsIdent(p) && !isGlobalKeyword(p) &&
- !/^(will-change|auto|scroll-position|contents)$/i.test(p),
- '<angle>': p => p.type === 'angle' || p.isCalc,
- '<angle-or-0>': p => p.text === '0' || p.type === 'angle' || p.isCalc,
- '<attr>': vtIsAttr,
- '<attachment>': 'scroll | fixed | local',
- '<bg-image>': '<image> | none',
- '<blend-mode>': 'normal | multiply | screen | overlay | darken | lighten | color-dodge | ' +
- 'color-burn | hard-light | soft-light | difference | exclusion | hue | ' +
- 'saturation | color | luminosity',
- '<border-style>': 'none | ' +
- 'hidden | dotted | dashed | solid | double | groove | ridge | inset | outset',
- '<border-width>': '<length> | thin | medium | thick',
- '<box>': 'padding-box | border-box | content-box',
- '<clip-source>': '<uri>',
- '<column-gap>': 'normal | <len-pct>',
- '<content-distribution>': 'space-between | space-around | space-evenly | stretch',
- '<content-position>': 'center | start | end | flex-start | flex-end',
- '<display-box>': 'contents | none',
- '<display-inside>': 'flow | flow-root | table | flex | grid | ruby',
- '<display-internal>': 'table-row-group | table-header-group | table-footer-group | ' +
- 'table-row | table-cell | table-column-group | table-column | table-caption | ' +
- 'ruby-base | ruby-text | ruby-base-container | ruby-text-container',
- '<display-legacy>': 'inline-block | inline-table | inline-flex | inline-grid',
- '<display-outside>': 'block | inline | run-in',
- '<feature-tag-value>': p => p.type === 'function' && /^[A-Z0-9]{4}$/i.test(p),
- '<flex>': p => p.type === 'grid' && p.value >= 0 || p.isCalc,
- '<flex-basis>': '<width>',
- '<flex-direction>': 'row | row-reverse | column | column-reverse',
- '<flex-grow>': '<number>',
- '<flex-shrink>': '<number>',
- '<flex-wrap>': 'nowrap | wrap | wrap-reverse',
- '<font-size>': '<absolute-size> | <relative-size> | <len-pct0+>',
- '<font-stretch>': '<font-stretch-named> | <pct>',
- '<font-stretch-named>': 'normal | ultra-condensed | extra-condensed | condensed | ' +
- 'semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded',
- '<font-variant-caps>':
- 'small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps',
- '<font-variant-css21>': 'normal | small-caps',
- '<font-weight>': 'normal | bold | bolder | lighter | <num1-1000>',
- '<generic-family>': 'serif | sans-serif | cursive | fantasy | monospace | system-ui | ' +
- 'emoji | math | fangsong | ui-serif | ui-sans-serif | ui-monospace | ui-rounded',
- '<geometry-box>': '<shape-box> | fill-box | stroke-box | view-box',
- '<glyph-angle>': p => p.type === 'angle' && p.units === 'deg',
- '<gradient>': 'radial-gradient() | linear-gradient() | conic-gradient() | gradient() | ' +
- 'repeating-radial-gradient() | repeating-linear-gradient() | repeating-conic-gradient() | ' +
- 'repeating-gradient()',
- '<hex-color>': p => p.tokenType === Tokens.HASH, //eslint-disable-line no-use-before-define
- '<icccolor>': 'cielab() | cielch() | cielchab() | icc-color() | icc-named-color()',
- '<ident>': vtIsIdent,
- '<ident-for-grid>': p => vtIsIdent(p) && !isGlobalKeyword(p.value) &&
- !/^(span|auto|default)$/i.test(p.value),
- '<ident-not-generic-family>': p => vtIsIdent(p) && !VTSimple['<generic-family>'](p),
- '<ident-not-none>': p => vtIsIdent(p) && !lowerCmp(p.value, 'none'),
- '<ie-function>': p => p.tokenType === Tokens.IE_FUNCTION, //eslint-disable-line no-use-before-define
- '<image>': '<uri> | <gradient> | cross-fade()',
- '<inflexible-breadth>': '<len-pct> | min-content | max-content | auto',
- '<integer>': p => p.isInt || p.isCalc,
- '<length>': vtIsLength,
- '<len0+>': p =>
- p.value >= 0 && vtIsLength(p) || p.isCalc,
- '<len-pct>': p => vtIsLength(p) || vtIsPct(p),
- '<len-pct0+>': p =>
- p.value >= 0 && (p.type === 'percentage' || vtIsLength(p)) || p.isCalc,
- '<line>': p => p.isInt,
- '<line-height>': '<number> | <len-pct> | normal',
- '<line-names>': p =>
- p.tokenType === Tokens.LBRACKET && // eslint-disable-line no-use-before-define
- p.text.endsWith(']') && (
- !p.expr ||
- !p.expr.parts.length ||
- p.expr.parts.every(VTSimple['<ident-for-grid>'], VTSimple)
- ),
- //eslint-disable-next-line no-use-before-define
- '<named-color>': p => p.text in Colors || ColorsLC.has(lower(p.text)),
- '<number>': p => p.type === 'number' || p.isCalc,
- '<num0+>': p =>
- p.value >= 0 && p.type === 'number' || p.isCalc,
- '<num1-1000>': p => (p.type === 'number' && p.value >= 1 && p.value <= 1000) || p.isCalc,
- '<num-pct>': p => p.type === 'number' || p.type === 'percentage' || p.isCalc,
- '<num-pct0+>': p =>
- p.value >= 0 && (p.type === 'number' || p.type === 'percentage') || p.isCalc,
- '<opacity-value>': p => p.type === 'number' && p.value >= 0 && p.value <= 1 || p.isCalc,
- '<overflow>': 'visible | hidden | clip | scroll | auto',
- '<overflow-position>': 'unsafe | safe',
- '<pct>': vtIsPct,
- '<pct0+>': p =>
- p.value >= 0 && p.type === 'percentage' || p.isCalc,
- '<integer1+>': p => p.isInt && p.value > 0 || p.isCalc,
- '<relative-size>': 'smaller | larger',
- '<row-gap>': '<column-gap>',
- '<self-position>': 'center | start | end | self-start | self-end | flex-start | flex-end',
- '<shape-box>': '<box> | margin-box',
- '<single-animation-direction>': 'normal | reverse | alternate | alternate-reverse',
- '<single-animation-fill-mode>': 'none | forwards | backwards | both',
- '<single-animation-name>': p => vtIsIdent(p) && !isGlobalKeyword(p) &&
- /^-?[a-z_][-a-z0-9_]+$/i.test(p),
- '<string>': p => p.type === 'string',
- '<text-align>': 'start | end | left | right | center | justify | match-parent',
- '<text-decoration-style>': 'solid | double | dotted | dashed | wavy',
- '<time>': p => p.type === 'time',
- '<track-breadth>': '<len-pct> | <flex> | min-content | max-content | auto',
- '<unicode-range>': p => /^U\+[0-9a-f?]{1,6}(-[0-9a-f?]{1,6})?\s*$/i.test(p),
- '<unit>': p => p.text === '%' || p in UNITS || lower(p) in UNITS,
- '<uri>': p => p.type === 'uri',
- '<width>': p => vtIsLength(p) || vtIsPct(p) || lowerCmp(p.text, 'auto'),
- };
- const VTComplex = {
- '<align-content>': 'normal | <baseline-position> | <content-distribution> | ' +
- '<overflow-position>? <content-position>',
- '<align-self>':
- 'auto | normal | stretch | <baseline-position> | <overflow-position>? <self-position>',
- '<auto-repeat>':
- 'repeat( [ auto-fill | auto-fit ] , [ <line-names>? <fixed-size> ]+ <line-names>? )',
- '<auto-track-list>':
- '[ <line-names>? [ <fixed-size> | <fixed-repeat> ] ]* <line-names>? <auto-repeat> ' +
- '[ <line-names>? [ <fixed-size> | <fixed-repeat> ] ]* <line-names>?',
- '<azimuth>':
- '<angle> | [ [ left-side | far-left | left | center-left | center | center-right | ' +
- 'right | far-right | right-side ] || behind ] | leftwards | rightwards',
- '<baseline-position>': '[ first | last ]? baseline',
- '<basic-shape>':
- 'inset( <len-pct>{1,4} [ round <border-radius> ]? ) | ' +
- 'circle( [ <len-pct> | closest-side | farthest-side ]? [ at <position> ]? ) | ' +
- 'ellipse( [ [ <len-pct> | closest-side | farthest-side ]{2} ]? [ at <position> ]? ) | ' +
- 'path( [ [ nonzero | evenodd ] , ]? <string> ) | ' +
- 'polygon( [ [ nonzero | evenodd | inherit ] , ]? [ <len-pct> <len-pct> ]# )',
- '<bg-layer>':
- '<bg-image> || <bg-position> [ / <bg-size> ]? || <repeat-style> || <attachment> || <box>{1,2}',
- '<bg-position>':
- '[ center | [ left | right ] <len-pct>? ] && [ center | [ top | bottom ] <len-pct>? ] | ' +
- '[ left | center | right | <len-pct> ] [ top | center | bottom | <len-pct> ] | ' +
- '[ left | center | right | top | bottom | <len-pct> ]',
- '<bg-size>': '[ <len-pct> | auto ]{1,2} | cover | contain',
- '<border-image-outset>': '[ <length> | <number> ]{1,4}',
- '<border-image-repeat>': '[ stretch | repeat | round | space ]{1,2}',
- '<border-image-slice>': Matcher =>
- // [<number> | <pct>]{1,4} && fill?
- // but 'fill' can appear between any of the numbers
- Matcher.many(
- [true],
- Matcher.parse('<num-pct0+>'),
- Matcher.parse('<num-pct0+>'),
- Matcher.parse('<num-pct0+>'),
- Matcher.parse('<num-pct0+>'),
- 'fill'),
- '<border-image-width>': '[ <len-pct> | <number> | auto ]{1,4}',
- '<border-radius>': '<len-pct0+>{1,4} [ / <len-pct0+>{1,4} ]?',
- '<border-shorthand>': '<border-width> || <border-style> || <color>',
- '<box-shadow>': 'none | <shadow>#',
- '<clip-path>': '<basic-shape> || <geometry-box>',
- '<color>': '<hex-color> | <named-color> | rgb( <rgb-color> ) | rgba( <rgb-color> ) | ' +
- 'hsl( <hsl-color> ) | hsla( <hsl-color> )',
- '<content-list>':
- '[ <string> | <image> | <attr> | ' +
- 'content( text | before | after | first-letter | marker ) | ' +
- 'counter() | counters() | leader() | ' +
- '[ open-quote | close-quote | no-open-quote | no-close-quote ] | ' +
- '[ target-counter() | target-counters() | target-text() ] ]+',
- '<counter>': '[ <ident-not-none> <integer>? ]+ | none',
- '<cubic-bezier-timing-function>': 'ease | ease-in | ease-out | ease-in-out | ' +
- 'cubic-bezier( <number>#{4} )',
- '<dasharray>': Matcher =>
- Matcher.parse('<len-pct0+> | <num0+>')
- .braces(1, Infinity, '#', Matcher.parse(',').braces(0, 1, '?')),
- '<display-listitem>': '<display-outside>? && [ flow | flow-root ]? && list-item',
- '<explicit-track-list>': '[ <line-names>? <track-size> ]+ <line-names>?',
- '<family-name>': '<string> | <ident-not-generic-family> <ident>*',
- // https://drafts.fxtf.org/filter-effects/#supported-filter-functions
- // Value may be omitted in which case the default is used
- '<filter-function>':
- 'blur( <length>? ) | ' +
- 'brightness( <num-pct>? ) | ' +
- 'contrast( <num-pct>? ) | ' +
- 'drop-shadow( [ <length>{2,3} && <color>? ]? ) | ' +
- 'grayscale( <num-pct>? ) | ' +
- 'hue-rotate( <angle-or-0>? ) | ' +
- 'invert( <num-pct>? ) | ' +
- 'opacity( <num-pct>? ) | ' +
- 'saturate( <num-pct>? ) | ' +
- 'sepia( <num-pct>? )',
- '<filter-function-list>': '[ <filter-function> | <uri> ]+',
- '<final-bg-layer>': '<color> || <bg-image> || <bg-position> [ / <bg-size> ]? || ' +
- '<repeat-style> || <attachment> || <box>{1,2}',
- '<fixed-repeat>':
- 'repeat( [ <integer1+> ] , [ <line-names>? <fixed-size> ]+ <line-names>? )',
- '<fixed-size>': '<len-pct> | ' +
- 'minmax( <len-pct> , <track-breadth> ) | ' +
- 'minmax( <inflexible-breadth> , <len-pct> )',
- '<flex-shorthand>': 'none | [ <flex-grow> <flex-shrink>? || <flex-basis> ]',
- '<font-family>': '[ <generic-family> | <family-name> ]#',
- '<font-style>': 'normal | italic | oblique <angle>?',
- '<font-short-core>': '<font-size> [ / <line-height> ]? <font-family>',
- '<font-short-tweak-no-pct>':
- '<font-style> || <font-variant-css21> || <font-weight> || <font-stretch-named>',
- '<font-variant-alternates>': 'stylistic() || historical-forms || styleset() || ' +
- 'character-variant() || swash() || ornaments() || annotation()',
- '<font-variant-ligatures>': '[ common-ligatures | no-common-ligatures ] || ' +
- '[ discretionary-ligatures | no-discretionary-ligatures ] || ' +
- '[ historical-ligatures | no-historical-ligatures ] || ' +
- '[ contextual | no-contextual ]',
- '<font-variant-numeric>': '[ lining-nums | oldstyle-nums ] || ' +
- '[ proportional-nums | tabular-nums ] || ' +
- '[ diagonal-fractions | stacked-fractions ] || ' +
- 'ordinal || slashed-zero',
- '<font-variant-east-asian>': '[ jis78 | jis83 | jis90 | jis04 | simplified | traditional ] || ' +
- '[ full-width | proportional-width ] || ruby',
- '<font-variant>': 'normal | none | [ ' +
- '<font-variant-ligatures> || <font-variant-alternates> || ' +
- '<font-variant-caps> || <font-variant-numeric> || <font-variant-east-asian> ]',
- '<grid-auto-columns>': '<track-size>+',
- '<grid-auto-rows>': '<track-size>+',
- '<grid-line>': 'auto | [ <integer> && <ident-for-grid>? ] | <ident-for-grid> | ' +
- '[ span && [ <integer> || <ident-for-grid> ] ]',
- '<grid-template>': 'none | [ <grid-template-rows> / <grid-template-columns> ] | ' +
- '[ <line-names>? <string> <track-size>? <line-names>? ]+ ' +
- '[ / <explicit-track-list> ]?',
- '<grid-template-columns>': 'none | <track-list> | <auto-track-list>',
- '<grid-template-rows>': '<grid-template-columns>',
- '<hsl-color>': '[ <number> | <angle> ] <pct>{2} [ / <num-pct0+> ]? | ' +
- '[ <number> | <angle> ] , <pct>#{2} [ , <num-pct0+> ]?',
- '<justify-content>': 'normal | <content-distribution> | ' +
- '<overflow-position>? [ <content-position> | left | right ]',
- '<justify-self>': 'auto | normal | stretch | <baseline-position> | <overflow-position>? ' +
- '[ <self-position> | left | right ]',
- '<overscroll>': 'contain | none | auto',
- '<paint>': 'none | <color> | <uri> [ none | <color> ]? | context-fill | context-stroke',
- // Because our `alt` combinator is ordered, we need to test these
- // in order from longest possible match to shortest.
- '<position>':
- '[ [ left | right ] <len-pct> ] && [ [ top | bottom ] <len-pct> ] | ' +
- '[ left | center | right | <len-pct> ] ' +
- '[ top | center | bottom | <len-pct> ]? | ' +
- '[ left | center | right ] || [ top | center | bottom ]',
- '<repeat-style>': 'repeat-x | repeat-y | [ repeat | space | round | no-repeat ]{1,2}',
- '<rgb-color>':
- '[ <number>{3} | <pct>{3} ] [ / <num-pct0+> ]? | ' +
- '[ <number>#{3} | <pct>#{3} ] [ , <num-pct0+> ]?',
- '<shadow>': 'inset? && [ <length>{2,4} && <color>? ]',
- '<single-timing-function>':
- 'linear | <cubic-bezier-timing-function> | <step-timing-function> | frames( <integer> )',
- '<step-timing-function>': 'step-start | step-end | ' +
- 'steps( <integer> [ , [ jump-start | jump-end | jump-none | jump-both | start | end ] ]? )',
- '<text-decoration-line>': 'none | [ underline || overline || line-through || blink ]',
- '<text-emphasis-style>': 'none | ' +
- '[ [ filled | open ] || [ dot | circle | double-circle | triangle | sesame ] ] | ' +
- '<string>',
- '<track-list>': '[ <line-names>? [ <track-size> | <track-repeat> ] ]+ <line-names>?',
- '<track-repeat>': 'repeat( [ <integer1+> ] , [ <line-names>? <track-size> ]+ <line-names>? )',
- '<track-size>': '<track-breadth> | minmax( <inflexible-breadth> , <track-breadth> ) | ' +
- 'fit-content( <len-pct> )',
- '<transform-function>':
- 'matrix( <number>#{6} ) | ' +
- 'matrix3d( <number>#{16} ) | ' +
- 'perspective( <len0+> | none ) | ' +
- 'rotate( <angle-or-0> ) | ' +
- 'rotate3d( <number>#{3} , <angle-or-0> ) | ' +
- 'rotateX( <angle-or-0> ) | ' +
- 'rotateY( <angle-or-0> ) | ' +
- 'rotateZ( <angle-or-0> ) | ' +
- 'scale( [ <num-pct> ]#{1,2} ) | ' +
- 'scale3d( <num-pct>#{3} ) | ' +
- 'scaleX( <num-pct> ) | ' +
- 'scaleY( <num-pct> ) | ' +
- 'scaleZ( <num-pct> ) | ' +
- 'skew( <angle-or-0> [ , <angle-or-0> ]? ) | ' +
- 'skewX( <angle-or-0> ) | ' +
- 'skewY( <angle-or-0> ) | ' +
- 'translate( <len-pct>#{1,2} ) | ' +
- 'translate3d( <len-pct>#{2} , <length> ) | ' +
- 'translateX( <len-pct> ) | ' +
- 'translateY( <len-pct> ) | ' +
- 'translateZ( <length> )',
- '<transform-origin>': '[ left | center | right | <len-pct> ] ' +
- '[ top | center | bottom | <len-pct> ] <length>? | ' +
- '[ left | center | right | top | bottom | <len-pct> ] | ' +
- '[ [ center | left | right ] && [ center | top | bottom ] ] <length>?',
- '<transition>': '[ none | [ all | <ident> ]# ] || <time> || <single-timing-function> || <time>',
- '<width-height>': '<len-pct> | min-content | max-content | ' +
- 'fit-content | fit-content( <len-pct> ) | -moz-available | -webkit-fill-available',
- '<will-change>': 'auto | <animateable-feature>#',
- };
- //#endregion
- //#region Colors
- const Colors = Object.assign(Object.create(null), {
- // 'currentColor' color keyword
- // https://www.w3.org/TR/css3-color/#currentcolor
- currentColor: '',
- transparent: '#0000',
- aliceblue: '#f0f8ff',
- antiquewhite: '#faebd7',
- aqua: '#00ffff',
- aquamarine: '#7fffd4',
- azure: '#f0ffff',
- beige: '#f5f5dc',
- bisque: '#ffe4c4',
- black: '#000000',
- blanchedalmond: '#ffebcd',
- blue: '#0000ff',
- blueviolet: '#8a2be2',
- brown: '#a52a2a',
- burlywood: '#deb887',
- cadetblue: '#5f9ea0',
- chartreuse: '#7fff00',
- chocolate: '#d2691e',
- coral: '#ff7f50',
- cornflowerblue: '#6495ed',
- cornsilk: '#fff8dc',
- crimson: '#dc143c',
- cyan: '#00ffff',
- darkblue: '#00008b',
- darkcyan: '#008b8b',
- darkgoldenrod: '#b8860b',
- darkgray: '#a9a9a9',
- darkgrey: '#a9a9a9',
- darkgreen: '#006400',
- darkkhaki: '#bdb76b',
- darkmagenta: '#8b008b',
- darkolivegreen: '#556b2f',
- darkorange: '#ff8c00',
- darkorchid: '#9932cc',
- darkred: '#8b0000',
- darksalmon: '#e9967a',
- darkseagreen: '#8fbc8f',
- darkslateblue: '#483d8b',
- darkslategray: '#2f4f4f',
- darkslategrey: '#2f4f4f',
- darkturquoise: '#00ced1',
- darkviolet: '#9400d3',
- deeppink: '#ff1493',
- deepskyblue: '#00bfff',
- dimgray: '#696969',
- dimgrey: '#696969',
- dodgerblue: '#1e90ff',
- firebrick: '#b22222',
- floralwhite: '#fffaf0',
- forestgreen: '#228b22',
- fuchsia: '#ff00ff',
- gainsboro: '#dcdcdc',
- ghostwhite: '#f8f8ff',
- gold: '#ffd700',
- goldenrod: '#daa520',
- gray: '#808080',
- grey: '#808080',
- green: '#008000',
- greenyellow: '#adff2f',
- honeydew: '#f0fff0',
- hotpink: '#ff69b4',
- indianred: '#cd5c5c',
- indigo: '#4b0082',
- ivory: '#fffff0',
- khaki: '#f0e68c',
- lavender: '#e6e6fa',
- lavenderblush: '#fff0f5',
- lawngreen: '#7cfc00',
- lemonchiffon: '#fffacd',
- lightblue: '#add8e6',
- lightcoral: '#f08080',
- lightcyan: '#e0ffff',
- lightgoldenrodyellow: '#fafad2',
- lightgray: '#d3d3d3',
- lightgrey: '#d3d3d3',
- lightgreen: '#90ee90',
- lightpink: '#ffb6c1',
- lightsalmon: '#ffa07a',
- lightseagreen: '#20b2aa',
- lightskyblue: '#87cefa',
- lightslategray: '#778899',
- lightslategrey: '#778899',
- lightsteelblue: '#b0c4de',
- lightyellow: '#ffffe0',
- lime: '#00ff00',
- limegreen: '#32cd32',
- linen: '#faf0e6',
- magenta: '#ff00ff',
- maroon: '#800000',
- mediumaquamarine: '#66cdaa',
- mediumblue: '#0000cd',
- mediumorchid: '#ba55d3',
- mediumpurple: '#9370db',
- mediumseagreen: '#3cb371',
- mediumslateblue: '#7b68ee',
- mediumspringgreen: '#00fa9a',
- mediumturquoise: '#48d1cc',
- mediumvioletred: '#c71585',
- midnightblue: '#191970',
- mintcream: '#f5fffa',
- mistyrose: '#ffe4e1',
- moccasin: '#ffe4b5',
- navajowhite: '#ffdead',
- navy: '#000080',
- oldlace: '#fdf5e6',
- olive: '#808000',
- olivedrab: '#6b8e23',
- orange: '#ffa500',
- orangered: '#ff4500',
- orchid: '#da70d6',
- palegoldenrod: '#eee8aa',
- palegreen: '#98fb98',
- paleturquoise: '#afeeee',
- palevioletred: '#db7093',
- papayawhip: '#ffefd5',
- peachpuff: '#ffdab9',
- peru: '#cd853f',
- pink: '#ffc0cb',
- plum: '#dda0dd',
- powderblue: '#b0e0e6',
- purple: '#800080',
- rebeccapurple: '#663399',
- red: '#ff0000',
- rosybrown: '#bc8f8f',
- royalblue: '#4169e1',
- saddlebrown: '#8b4513',
- salmon: '#fa8072',
- sandybrown: '#f4a460',
- seagreen: '#2e8b57',
- seashell: '#fff5ee',
- sienna: '#a0522d',
- silver: '#c0c0c0',
- skyblue: '#87ceeb',
- slateblue: '#6a5acd',
- slategray: '#708090',
- slategrey: '#708090',
- snow: '#fffafa',
- springgreen: '#00ff7f',
- steelblue: '#4682b4',
- tan: '#d2b48c',
- teal: '#008080',
- thistle: '#d8bfd8',
- tomato: '#ff6347',
- turquoise: '#40e0d0',
- violet: '#ee82ee',
- wheat: '#f5deb3',
- white: '#ffffff',
- whitesmoke: '#f5f5f5',
- yellow: '#ffff00',
- yellowgreen: '#9acd32',
- // old = CSS2 system colors: https://www.w3.org/TR/css3-color/#css2-system
- // new = CSS4 system colors: https://drafts.csswg.org/css-color-4/#css-system-colors
- ActiveBorder: '',
- ActiveCaption: '',
- ActiveText: '', // new
- AppWorkspace: '',
- Background: '',
- ButtonBorder: '', // new
- ButtonFace: '', // old+new
- ButtonHighlight: '',
- ButtonShadow: '',
- ButtonText: '', // old+new
- Canvas: '', // new
- CanvasText: '', // new
- CaptionText: '',
- Field: '', // new
- FieldText: '', // new
- GrayText: '', // old+new
- Highlight: '', // old+new
- HighlightText: '', // old+new
- InactiveBorder: '',
- InactiveCaption: '',
- InactiveCaptionText: '',
- InfoBackground: '',
- InfoText: '',
- LinkText: '', // new
- Mark: '', // new
- MarkText: '', // new
- Menu: '',
- MenuText: '',
- Scrollbar: '',
- ThreeDDarkShadow: '',
- ThreeDFace: '',
- ThreeDHighlight: '',
- ThreeDLightShadow: '',
- ThreeDShadow: '',
- VisitedText: '', // new
- Window: '',
- WindowFrame: '',
- WindowText: '',
- });
- const ColorsLC = new Set(Object.keys(Colors).map(lower));
- //#endregion
- //#region Tokens
- /* https://www.w3.org/TR/css3-syntax/#lexical */
- /** @type {Object<string,number|Object>} */
- const Tokens = Object.assign([], {
- EOF: {}, // must be the first token
- }, {
- // HTML-style comments
- CDC: {},
- CDO: {},
- // ignorables
- COMMENT: {hide: true},
- S: {},
- // attribute equality
- DASHMATCH: {text: '|='},
- INCLUDES: {text: '~='},
- PREFIXMATCH: {text: '^='},
- SUBSTRINGMATCH: {text: '*='},
- SUFFIXMATCH: {text: '$='},
- // identifier types
- HASH: {},
- IDENT: {},
- STRING: {},
- // at-keywords
- CHARSET_SYM: {text: '@charset'},
- DOCUMENT_SYM: {text: ['@document', '@-moz-document']},
- FONT_FACE_SYM: {text: '@font-face'},
- IMPORT_SYM: {text: '@import'},
- KEYFRAMES_SYM: {text: ['@keyframes', '@-webkit-keyframes', '@-moz-keyframes', '@-o-keyframes']},
- MEDIA_SYM: {text: '@media'},
- NAMESPACE_SYM: {text: '@namespace'},
- PAGE_SYM: {text: '@page'},
- SUPPORTS_SYM: {text: '@supports'},
- UNKNOWN_SYM: {},
- VIEWPORT_SYM: {text: ['@viewport', '@-ms-viewport', '@-o-viewport']},
- // measurements
- ANGLE: {},
- DIMENSION: {},
- FREQ: {},
- LENGTH: {},
- NUMBER: {},
- PERCENTAGE: {},
- TIME: {},
- // functions
- FUNCTION: {},
- URI: {},
- // Unicode ranges
- UNICODE_RANGE: {},
- // invalid string
- INVALID: {},
- // combinators
- COLUMN: {text: '||'},
- COMMA: {text: ','},
- GREATER: {text: '>'},
- PLUS: {text: '+'},
- TILDE: {text: '~'},
- // modifier
- ANY: {text: ['any', '-webkit-any', '-moz-any']},
- IS: {},
- NOT: {},
- WHERE: {},
- // CSS3 Paged Media
- BOTTOMCENTER_SYM: {text: '@bottom-center'},
- BOTTOMLEFTCORNER_SYM: {text: '@bottom-left-corner'},
- BOTTOMLEFT_SYM: {text: '@bottom-left'},
- BOTTOMRIGHTCORNER_SYM: {text: '@bottom-right-corner'},
- BOTTOMRIGHT_SYM: {text: '@bottom-right'},
- LEFTBOTTOM_SYM: {text: '@left-bottom'},
- LEFTMIDDLE_SYM: {text: '@left-middle'},
- LEFTTOP_SYM: {text: '@left-top'},
- RIGHTBOTTOM_SYM: {text: '@right-bottom'},
- RIGHTMIDDLE_SYM: {text: '@right-middle'},
- RIGHTTOP_SYM: {text: '@right-top'},
- TOPCENTER_SYM: {text: '@top-center'},
- TOPLEFTCORNER_SYM: {text: '@top-left-corner'},
- TOPLEFT_SYM: {text: '@top-left'},
- TOPRIGHTCORNER_SYM: {text: '@top-right-corner'},
- TOPRIGHT_SYM: {text: '@top-right'},
- /* CSS3 Media Queries */
- RESOLUTION: {state: 'media'},
- /*
- * The following token names are not defined in any CSS specification.
- */
- CHAR: {},
- COLON: {text: ':'},
- DOT: {text: '.'},
- EQUALS: {text: '='},
- IE_FUNCTION: {},
- IMPORTANT: {},
- LBRACE: {text: '{', endChar: '}'},
- LBRACKET: {text: '[', endChar: ']'},
- LPAREN: {text: '(', endChar: ')'},
- MINUS: {text: '-'},
- PIPE: {text: '|'},
- RBRACE: {text: '}'},
- RBRACKET: {text: ']'},
- RPAREN: {text: ')'},
- SEMICOLON: {text: ';'},
- SLASH: {text: '/'},
- STAR: {text: '*'},
- USO_VAR: {},
- });
- // make Tokens an array of tokens, store the index in original prop, add 'name' to each token
- const typeMap = new Map();
- for (const [k, val] of Object.entries(Tokens)) {
- const index = Tokens[k] = Tokens.length;
- val.name = k;
- Tokens.push(val);
- const {text} = val;
- if (text) {
- for (const item of Array.isArray(text) ? text : [text]) {
- typeMap.set(item, index);
- }
- }
- }
- Tokens.UNKNOWN = -1;
- Tokens.name = index => (Tokens[index] || {}).name;
- Tokens.type = text => typeMap.get(text) || Tokens.UNKNOWN;
- const TT = {
- attrMatch: [
- Tokens.PREFIXMATCH,
- Tokens.SUFFIXMATCH,
- Tokens.SUBSTRINGMATCH,
- Tokens.EQUALS,
- Tokens.INCLUDES,
- Tokens.DASHMATCH,
- ],
- combinator: [
- Tokens.PLUS,
- Tokens.GREATER,
- Tokens.TILDE,
- Tokens.COLUMN,
- ],
- cruft: [
- Tokens.S,
- Tokens.CDO,
- Tokens.CDC,
- ],
- expression: [
- Tokens.PLUS,
- Tokens.MINUS,
- Tokens.DIMENSION,
- Tokens.NUMBER,
- Tokens.STRING,
- Tokens.IDENT,
- Tokens.LENGTH,
- Tokens.FREQ,
- Tokens.ANGLE,
- Tokens.TIME,
- Tokens.RESOLUTION,
- Tokens.SLASH,
- ],
- identString: [
- Tokens.IDENT,
- Tokens.STRING,
- Tokens.USO_VAR,
- ],
- LParenBracket: [
- Tokens.LPAREN,
- Tokens.LBRACKET,
- ],
- LParenBracketBrace: [
- Tokens.LPAREN,
- Tokens.LBRACKET,
- Tokens.LBRACE,
- ],
- margins: [
- Tokens.TOPLEFTCORNER_SYM,
- Tokens.TOPLEFT_SYM,
- Tokens.TOPCENTER_SYM,
- Tokens.TOPRIGHT_SYM,
- Tokens.TOPRIGHTCORNER_SYM,
- Tokens.BOTTOMLEFTCORNER_SYM,
- Tokens.BOTTOMLEFT_SYM,
- Tokens.BOTTOMCENTER_SYM,
- Tokens.BOTTOMRIGHT_SYM,
- Tokens.BOTTOMRIGHTCORNER_SYM,
- Tokens.LEFTTOP_SYM,
- Tokens.LEFTMIDDLE_SYM,
- Tokens.LEFTBOTTOM_SYM,
- Tokens.RIGHTTOP_SYM,
- Tokens.RIGHTMIDDLE_SYM,
- Tokens.RIGHTBOTTOM_SYM,
- ],
- op: [
- Tokens.SLASH,
- Tokens.COMMA,
- ],
- opInFunc: [
- Tokens.SLASH,
- Tokens.COMMA,
- Tokens.PLUS,
- Tokens.STAR,
- Tokens.MINUS,
- ],
- plusMinus: [
- Tokens.MINUS,
- Tokens.PLUS,
- ],
- pseudo: [
- Tokens.FUNCTION,
- Tokens.IDENT,
- ],
- semiS: [
- Tokens.SEMICOLON,
- Tokens.S,
- ],
- stringUri: [
- Tokens.STRING,
- Tokens.URI,
- Tokens.USO_VAR,
- ],
- term: [
- Tokens.NUMBER,
- Tokens.PERCENTAGE,
- Tokens.LENGTH,
- Tokens.ANGLE,
- Tokens.TIME,
- Tokens.DIMENSION,
- Tokens.FREQ,
- Tokens.STRING,
- Tokens.IDENT,
- Tokens.URI,
- Tokens.UNICODE_RANGE,
- Tokens.USO_VAR,
- ],
- usoS: [
- Tokens.USO_VAR,
- Tokens.S,
- ],
- };
- //#endregion
- //#region StringReader
- class StringReader {
- constructor(text) {
- this._input = text.replace(/\r\n?/g, '\n');
- this._line = 1;
- this._col = 1;
- this._cursor = 0;
- }
- eof() {
- return this._cursor >= this._input.length;
- }
- peek(count = 1) {
- return this._input[this._cursor + count - 1] || null;
- }
- peekTest(stickyRx) {
- stickyRx.lastIndex = this._cursor;
- return stickyRx.test(this._input);
- }
- read() {
- const c = this._input[this._cursor];
- if (!c) return null;
- if (c === '\n') {
- this._line++;
- this._col = 1;
- } else {
- this._col++;
- }
- this._cursor++;
- return c;
- }
- mark() {
- this._bookmark = {
- cursor: this._cursor,
- line: this._line,
- col: this._col,
- };
- }
- reset() {
- if (this._bookmark) {
- this._cursor = this._bookmark.cursor;
- this._line = this._bookmark.line;
- this._col = this._bookmark.col;
- delete this._bookmark;
- }
- }
- /**
- * Reads up to and including the given string.
- * @param {String} pattern The string to read.
- * @return {String} The string when it is found.
- * @throws Error when the string pattern is not found.
- */
- readTo(pattern) {
- const i = this._input.indexOf(pattern, this._cursor);
- if (i < 0) throw new Error(`Expected '${pattern}'.`);
- return this.readCount(i - this._cursor + pattern.length);
- }
- /**
- * Reads characters that match either text or a regular expression and returns those characters.
- * If a match is found, the row and column are adjusted.
- * @param {String|RegExp} matcher
- * @return {String} string or null if there was no match.
- */
- readMatch(matcher) {
- if (matcher.sticky) {
- matcher.lastIndex = this._cursor;
- return matcher.test(this._input) ?
- this.readCount(RegExp.lastMatch.length) :
- null;
- }
- if (typeof matcher === 'string') {
- if (this._input[this._cursor] === matcher[0] &&
- this._input.substr(this._cursor, matcher.length) === matcher) {
- return this.readCount(matcher.length);
- }
- } else if (matcher instanceof RegExp) {
- if (matcher.test(this._input.substr(this._cursor))) {
- return this.readCount(RegExp.lastMatch.length);
- }
- }
- return null;
- }
- /**
- * Reads a given number of characters. If the end of the input is reached,
- * it reads only the remaining characters and does not throw an error.
- * @param {int} count The number of characters to read.
- * @return {String} string or null if already at EOF
- */
- readCount(count) {
- const len = this._input.length;
- if (this._cursor >= len) return null;
- if (!count) return '';
- const text = this._input.substr(this._cursor, count);
- this._cursor = Math.min(this._cursor + count, len);
- let prev = -1;
- for (let i = 0; (i = text.indexOf('\n', i)) >= 0; prev = i, i++) this._line++;
- this._col = prev < 0 ? this._col + count : count - prev;
- return text;
- }
- }
- //#endregion
- //#region Matcher
- /**
- * Reuses a Matcher for a ValidationTypes definition string instead of reparsing it.
- * @type {Map<string, Matcher>}
- */
- const matcherCache = new Map();
- /**
- * This class implements a combinator library for matcher functions.
- * https://developer.mozilla.org/docs/Web/CSS/Value_definition_syntax#Component_value_combinators
- */
- class Matcher {
- constructor(matchFunc, toString, options) {
- this.matchFunc = matchFunc;
- /** @type {function(?number):string} */
- this.toString = typeof toString === 'function' ? toString : () => toString;
- /** @type {?Matcher[]} */
- this.options = options;
- }
- /**
- * @param {PropertyValueIterator} e
- * @return {?boolean}
- */
- match(e) {
- e._marks.push(e._i);
- return e.popMark(this.matchFunc(e));
- }
- braces(min, max, marker, sep) {
- return new Matcher(Matcher.funcBraces, Matcher.toStringBraces, {
- min, max, marker,
- sep: sep && Matcher.seq(sep, this),
- embraced: this,
- });
- }
- static parse(str) {
- let m = matcherCache.get(str);
- if (m) return m;
- m = Matcher.doParse(str);
- matcherCache.set(str, m);
- return m;
- }
- /** Simple recursive-descent grammar to build matchers from strings. */
- static doParse(str) {
- const reader = new StringReader(str);
- const result = Matcher.parseGrammar(reader);
- if (!reader.eof()) {
- throw new Error('Internal grammar error. ' +
- `Expected end of string at ${reader._cursor}: ${reader._input}.`);
- }
- return result;
- }
- static cast(m) {
- return m instanceof Matcher ? m : Matcher.parse(m);
- }
- // Matcher for a single type.
- static fromType(type) {
- let m = matcherCache.get(type);
- if (m) return m;
- m = new Matcher(Matcher.funcFromType, type, type);
- matcherCache.set(type, m);
- return m;
- }
- /**
- * @param {string} name - functio name
- * @param {Matcher} body - matcher for function body
- * @returns {Matcher}
- */
- static func(name, body) {
- return new Matcher(Matcher.funcFunc, Matcher.toStringFunc, {name, body});
- }
- // Matcher for one or more juxtaposed words, which all must occur, in the given order.
- static seq(...args) {
- const ms = args.map(Matcher.cast);
- if (ms.length === 1) return ms[0];
- return new Matcher(Matcher.funcSeq, Matcher.toStringSeq, ms);
- }
- // Matcher for one or more alternatives, where exactly one must occur.
- static alt(...args) {
- const ms = args.map(Matcher.cast);
- if (ms.length === 1) return ms[0];
- return new Matcher(Matcher.funcAlt, Matcher.toStringAlt, ms);
- }
- /**
- * Matcher for two or more options: double bar (||) and double ampersand (&&) operators,
- * as well as variants of && where some of the alternatives are optional.
- * This will backtrack through even successful matches to try to
- * maximize the number of items matched.
- */
- static many(required, ...args) {
- const ms = args.map(Matcher.cast);
- const m = new Matcher(Matcher.funcMany, Matcher.toStringMany, ms);
- m.required = required === true ? Array(ms.length).fill(true) : required;
- return m;
- }
- /**************************** matchFunc **********************/
- /**
- * @this {Matcher}
- * @param {PropertyValueIterator} expr
- */
- static funcAlt(expr) {
- return this.options.some(Matcher.invoke, expr);
- }
- /**
- * @this {Matcher}
- * @param {PropertyValueIterator} expr
- */
- static funcBraces(expr) {
- const {min, max, sep, embraced} = this.options;
- let i = 0;
- while (i < max && (i && sep || embraced).match(expr)) {
- i++;
- }
- return i >= min;
- }
- /**
- * @this {Matcher}
- * @param {PropertyValueIterator} expr
- */
- static funcFromType(expr) {
- const part = expr.peek();
- if (!part) return;
- const type = this.options;
- let result, m;
- if (part.isVar) {
- result = true;
- } else if (!type.startsWith('<')) {
- result = vtIsLiteral(type, part);
- } else if ((m = VTSimple[type])) {
- result = m.call(VTSimple, part);
- } else {
- m = VTComplex[type];
- return m instanceof Matcher ?
- m.match(expr) :
- m.call(VTComplex, expr);
- }
- if (!result && expr.tryAttr && part.isAttr) {
- result = vtIsAttr(part);
- }
- if (result) expr.next();
- return result;
- }
- /**
- * @this {Matcher}
- * @param {PropertyValueIterator} expr
- */
- static funcFunc(expr) {
- const p = expr.peek();
- if (p && p.expr && p.tokenType === Tokens.FUNCTION && lowerCmp(p.name, this.options.name)) {
- let res = hasVarParts(p.expr);
- if (!res) {
- const vi = new PropertyValueIterator(p.expr); // eslint-disable-line no-use-before-define
- res = this.options.body.match(vi) && !vi.hasNext;
- }
- return res && expr.next();
- }
- }
- /**
- * @this {PropertyValueIterator}
- * @param {Matcher} m
- */
- static invoke(m) {
- return m.match(this);
- }
- /**
- * @this {Matcher}
- * @param {PropertyValueIterator} expr
- */
- static funcMany(expr) {
- const seen = [];
- const {/** @type {Matcher[]} */options: ms, required} = this;
- let max = 0;
- let pass = 0;
- // If couldn't get a complete match, retrace our steps to make the
- // match with the maximum # of required elements.
- if (!tryMatch(0)) {
- pass++;
- tryMatch(0);
- }
- if (required === false) {
- return max > 0;
- }
- // Use finer-grained specification of which matchers are required.
- for (let i = 0; i < ms.length; i++) {
- if (required[i] && !seen[i]) {
- return false;
- }
- }
- return true;
- function tryMatch(matchCount) {
- for (let i = 0; i < ms.length; i++) {
- if (seen[i]) continue;
- expr.mark();
- if (!ms[i].matchFunc(expr)) {
- expr.popMark(true);
- continue;
- }
- seen[i] = true;
- // Increase matchCount if this was a required element
- // (or if all the elements are optional)
- if (tryMatch(matchCount + (required === false || required[i] ? 1 : 0))) {
- expr.popMark(true);
- return true;
- }
- // Backtrack: try *not* matching using this rule, and
- // let's see if it leads to a better overall match.
- expr.popMark();
- seen[i] = false;
- }
- if (pass === 0) {
- max = Math.max(matchCount, max);
- return matchCount === ms.length;
- } else {
- return matchCount === max;
- }
- }
- }
- /**
- * @this {Matcher}
- * @param {PropertyValueIterator} expr
- */
- static funcSeq(expr) {
- return this.options.every(Matcher.invoke, expr);
- }
- /**************************** toStringFunc **********************/
- /** @this {Matcher} */
- static toStringAlt(prec) {
- const p = Matcher.prec.ALT;
- const s = this.options.map(m => m.toString(p)).join(' | ');
- return prec > p ? `[ ${s} ]` : s;
- }
- /** @this {Matcher} */
- static toStringBraces() {
- const {marker, min, max, embraced} = this.options;
- return embraced.toString(Matcher.prec.MOD) + (
- !marker || marker === '#'
- ? `${marker || ''}{${min}${min === max ? '' : ',' + max}}`
- : marker);
- }
- /** @this {Matcher} */
- static toStringFunc() {
- const {name, body} = this.options;
- return `${name}( ${body} )`;
- }
- /** @this {Matcher} */
- static toStringMany(prec) {
- const {options: ms, required} = this;
- const p = required === false ? Matcher.prec.OROR : Matcher.prec.ANDAND;
- const s = ms.map((m, i) => {
- if (required !== false && !required[i]) {
- const str = m.toString(Matcher.prec.MOD);
- return str.endsWith('?') ? str : str + '?';
- }
- return m.toString(p);
- }).join(required === false ? ' || ' : ' && ');
- return prec > p ? `[ ${s} ]` : s;
- }
- /** @this {Matcher} */
- static toStringSeq(prec) {
- const p = Matcher.prec.SEQ;
- const s = this.options.map(m => m.toString(p)).join(' ');
- return prec > p ? `[ ${s} ]` : s;
- }
- }
- // Precedence table of combinators.
- Matcher.prec = {
- MOD: 5,
- SEQ: 4,
- ANDAND: 3,
- OROR: 2,
- ALT: 1,
- };
- Matcher.parseGrammar = (() => {
- /** @type {StringReader} */
- let reader;
- return newReader => {
- reader = newReader;
- return alt();
- };
- function alt() {
- // alt = oror (" | " oror)*
- const m = [oror()];
- while (reader.readMatch(' | ')) {
- m.push(oror());
- }
- return m.length === 1 ? m[0] : Matcher.alt(...m);
- }
- // Matcher for two or more options in any order, at least one must be present.
- function oror() {
- // oror = andand ( " || " andand)*
- const m = [andand()];
- while (reader.readMatch(' || ')) {
- m.push(andand());
- }
- return m.length === 1 ? m[0] : Matcher.many(false, ...m);
- }
- // Matcher for two or more options in any order, all mandatory.
- function andand() {
- // andand = seq ( " && " seq)*
- const m = [seq()];
- let reqPrev = !isOptional(m[0]);
- const required = [reqPrev];
- while (reader.readMatch(' && ')) {
- const item = seq();
- const req = !isOptional(item);
- // Matcher.many apparently can't handle optional items first
- if (req && !reqPrev) {
- m.unshift(item);
- required.unshift(req);
- } else {
- m.push(item);
- required.push(req);
- reqPrev = req;
- }
- }
- return m.length === 1 ? m[0] : Matcher.many(required, ...m);
- }
- function seq() {
- // seq = mod ( " " mod)*
- const ms = [mod()];
- while (reader.readMatch(/\s(?![&|)\]])/y)) {
- ms.push(mod());
- }
- return Matcher.seq(...ms);
- }
- function mod() {
- // mod = term ( "?" | "*" | "+" | "#" | "{<num>,<num>}" )?
- // term = <nt> | literal | "[ " expression " ]" | fn "( " alt " )"
- let m, fn;
- if (reader.readMatch('[ ')) {
- m = alt();
- eat(' ]');
- } else if ((fn = reader.readMatch(/[-\w]+(?=\(\s)/y))) {
- reader.readCount(2);
- m = alt();
- eat(' )');
- return Matcher.func(fn, m);
- } else {
- m = Matcher.fromType(eat(/<[^>]+>|[^\s?*+#{]+/y));
- }
- reader.mark();
- let hash;
- switch (reader.read()) {
- case '?': return m.braces(0, 1, '?');
- case '*': return m.braces(0, Infinity, '*');
- case '+': return m.braces(1, Infinity, '+');
- case '#':
- if (reader.peek() !== '{') return m.braces(1, Infinity, '#', ',');
- reader.read();
- hash = '#';
- // fallthrough
- case '{': {
- const [min, max] = eat(/\s*\d+\s*(,\s*\d+\s*)?}/y).trim().split(/\s+|,|}/);
- return m.braces(min | 0, max | min | 0, hash, hash && ',');
- }
- default:
- reader.reset();
- }
- return m;
- }
- function eat(pattern) {
- const s = reader.readMatch(pattern);
- if (s != null) return s;
- throw new Error('Internal grammar error. ' +
- `Expected ${pattern} at ${reader._cursor} in ${reader._input}`);
- }
- function isOptional({options}) {
- return options && options.marker === '?';
- }
- })();
- //#endregion
- //#region EventTarget
- class EventTarget {
- constructor() {
- this._listeners = new Map();
- }
- addListener(type, fn) {
- let list = this._listeners.get(type);
- if (!list) this._listeners.set(type, (list = new Set()));
- list.add(fn);
- }
- fire(event) {
- if (typeof event === 'string') {
- event = {type: event};
- }
- event.target = this;
- const list = this._listeners.get(event.type);
- if (list) {
- for (const fn of list) {
- fn.call(this, event);
- }
- }
- }
- removeListener(type, fn) {
- const list = this._listeners.get(type);
- if (list) list.delete(fn);
- }
- }
- //#endregion
- //#region Syntax units
- /**
- * @property {boolean|number} [_isAttr]
- * @property {boolean|number} [_isCalc]
- * @property {boolean|number} [_isVar]
- */
- class SyntaxUnit {
- constructor(text, pos, type, extras) {
- this.col = pos.col;
- this.line = pos.line;
- this.offset = pos.offset;
- this.text = text;
- this.type = type;
- if (extras) Object.assign(this, extras);
- }
- valueOf() {
- return this.text;
- }
- toString() {
- return this.text;
- }
- get isAttr() {
- let res = this._isAttr;
- if (res === 0) res = this._isAttr = lowerCmp(this.name, 'attr');
- return res;
- }
- get isCalc() {
- let res = this._isCalc;
- if (res === 0) res = this._isCalc = rxCalc.test(this.text);
- return res;
- }
- get isVar() {
- let res = this._isVar;
- if (res === 0) {
- const pp = this.expr && this.expr.parts;
- res = this._isVar = pp && pp.length > 0 && (
- (pp.length === 1 || pp[1].text === ',') && (
- pp[0].type === 'custom-property' && lowerCmp(this.name, 'var') ||
- pp[0].type === 'identifier' && lowerCmp(this.name, 'env')));
- }
- return res;
- }
- static fromToken(token) {
- return token && new SyntaxUnit(token.value, token);
- }
- /**
- * @param {SyntaxUnit} unit
- * @param {SyntaxUnit|parserlib.Token} token
- * @returns {SyntaxUnit}
- */
- static addFuncInfo(unit, {expr, name} = unit) {
- const isColor = expr && expr.parts && /^(rgb|hsl)a?$/i.test(name);
- if (isColor) unit.type = 'color';
- unit._isAttr =
- unit._isCalc =
- unit._isVar = isColor ? false : 0;
- return unit;
- }
- }
- class SyntaxError extends Error {
- constructor(message, pos) {
- super();
- this.name = this.constructor.name;
- this.col = pos.col;
- this.line = pos.line;
- this.message = message;
- }
- }
- class ValidationError extends Error {
- constructor(message, pos) {
- super();
- this.col = pos.col;
- this.line = pos.line;
- this.message = message;
- }
- }
- // individual media query
- class MediaQuery extends SyntaxUnit {
- constructor(modifier, mediaType, features, pos) {
- const text = (modifier ? modifier + ' ' : '') +
- (mediaType ? mediaType : '') +
- (mediaType && features.length > 0 ? ' and ' : '') +
- features.join(' and ');
- super(text, pos, TYPES.MEDIA_QUERY_TYPE);
- this.modifier = modifier;
- this.mediaType = mediaType;
- this.features = features;
- }
- }
- // e.g. max-width:500.
- class MediaFeature extends SyntaxUnit {
- constructor(name, value) {
- const text = `(${name}${value != null ? ':' + value : ''})`;
- super(text, name, TYPES.MEDIA_FEATURE_TYPE);
- this.name = name;
- this.value = value;
- }
- }
- /**
- * An entire single selector, including all parts but not
- * including multiple selectors (those separated by commas).
- */
- class Selector extends SyntaxUnit {
- constructor(parts, pos) {
- super(parts.join(' '), pos, TYPES.SELECTOR_TYPE);
- this.parts = parts;
- // eslint-disable-next-line no-use-before-define
- this.specificity = Specificity.calculate(this);
- }
- }
- /**
- * A single part of a selector string i.e. element name and modifiers.
- * Does not include combinators such as spaces, +, >, etc.
- */
- class SelectorPart extends SyntaxUnit {
- constructor(elementName, modifiers, text, pos) {
- super(text, pos, TYPES.SELECTOR_PART_TYPE);
- this.elementName = elementName;
- this.modifiers = modifiers;
- }
- }
- /**
- * Selector modifier string
- */
- class SelectorSubPart extends SyntaxUnit {
- constructor(text, type, pos) {
- super(text, pos, TYPES.SELECTOR_SUB_PART_TYPE);
- this.type = type;
- // Some subparts have arguments
- this.args = [];
- }
- }
- /**
- * A selector combinator (whitespace, +, >).
- */
- class Combinator extends SyntaxUnit {
- constructor(token) {
- const {value} = token;
- super(value, token, TYPES.COMBINATOR_TYPE);
- this.type =
- value === '>' ? 'child' :
- value === '+' ? 'adjacent-sibling' :
- value === '~' ? 'sibling' :
- value === '||' ? 'column' :
- !value.trim() ? 'descendant' :
- 'unknown';
- }
- }
- /**
- * A selector specificity.
- */
- class Specificity {
- /**
- * @param {int} a Should be 1 for inline styles, zero for stylesheet styles
- * @param {int} b Number of ID selectors
- * @param {int} c Number of classes and pseudo classes
- * @param {int} d Number of element names and pseudo elements
- */
- constructor(a, b, c, d) {
- this.a = a;
- this.b = b;
- this.c = c;
- this.d = d;
- this.constructor = Specificity;
- }
- /**
- * @param {Specificity} other The other specificity to compare to.
- * @return {int} -1 if the other specificity is larger, 1 if smaller, 0 if equal.
- */
- compare(other) {
- const comps = ['a', 'b', 'c', 'd'];
- for (let i = 0, len = comps.length; i < len; i++) {
- if (this[comps[i]] < other[comps[i]]) {
- return -1;
- } else if (this[comps[i]] > other[comps[i]]) {
- return 1;
- }
- }
- return 0;
- }
- valueOf() {
- return this.a * 1000 + this.b * 100 + this.c * 10 + this.d;
- }
- toString() {
- return `${this.a},${this.b},${this.c},${this.d}`;
- }
- /**
- * Calculates the specificity of the given selector.
- * @param {Selector} selector The selector to calculate specificity for.
- * @return {Specificity} The specificity of the selector.
- */
- static calculate(selector) {
- let b = 0;
- let c = 0;
- let d = 0;
- selector.parts.forEach(updateValues);
- return new Specificity(0, b, c, d);
- function updateValues(part) {
- if (!(part instanceof SelectorPart)) return;
- const elementName = part.elementName ? part.elementName.text : '';
- if (elementName && !elementName.endsWith('*')) {
- d++;
- }
- for (const modifier of part.modifiers) {
- switch (modifier.type) {
- case 'class':
- case 'attribute':
- c++;
- break;
- case 'id':
- b++;
- break;
- case 'pseudo':
- if (isPseudoElement(modifier.text)) {
- d++;
- } else {
- c++;
- }
- break;
- case 'not':
- modifier.args.forEach(updateValues);
- }
- }
- }
- }
- }
- class PropertyName extends SyntaxUnit {
- constructor(text, hack, pos) {
- super(text, pos, TYPES.PROPERTY_NAME_TYPE);
- this.hack = hack;
- }
- toString() {
- return (this.hack || '') + this.text;
- }
- }
- /**
- * A single value between ":" and ";", that is if there are multiple values
- * separated by commas, this type represents just one of the values.
- */
- class PropertyValue extends SyntaxUnit {
- constructor(parts, pos) {
- super(parts.join(' '), pos, TYPES.PROPERTY_VALUE_TYPE);
- this.parts = parts;
- }
- }
- /**
- * A single part of a value
- * e.g. '1px solid rgb(1, 2, 3)' has 3 parts
- * @property {PropertyValue} expr
- */
- class PropertyValuePart extends SyntaxUnit {
- /** @param {parserlib.Token} token */
- constructor(token) {
- const {value, type} = token;
- super(value, token, TYPES.PROPERTY_VALUE_PART_TYPE);
- this.tokenType = type;
- this.expr = token.expr || null;
- switch (type) {
- case Tokens.ANGLE:
- case Tokens.DIMENSION:
- case Tokens.FREQ:
- case Tokens.LENGTH:
- case Tokens.NUMBER:
- case Tokens.PERCENTAGE:
- case Tokens.TIME:
- this.value = token.number;
- this.units = token.units;
- this.type = token.unitsType;
- this.isInt = this.type === 'number' && !value.includes('.');
- break;
- case Tokens.HASH:
- this.type = 'color';
- this.value = value;
- break;
- case Tokens.IDENT:
- if (value.startsWith('--')) {
- this.type = 'custom-property';
- this.value = value;
- } else {
- const namedColor = Colors[value] || Colors[lower(value)];
- this.type = namedColor ? 'color' : 'identifier';
- this.value = namedColor || value;
- }
- break;
- case Tokens.FUNCTION: {
- this.name = token.name;
- SyntaxUnit.addFuncInfo(this, token);
- break;
- }
- case Tokens.STRING:
- this.type = 'string';
- this.value = parseString(value);
- break;
- case Tokens.URI:
- this.type = 'uri';
- this.name = token.name;
- this.uri = token.uri;
- break;
- case Tokens.USO_VAR:
- this._isVar = true;
- break;
- default:
- if (value === ',' || value === '/') {
- this.type = 'operator';
- this.value = value;
- } else {
- this.type = 'unknown';
- }
- }
- }
- }
- class PropertyValueIterator {
- /**
- * @param {PropertyValue} value
- */
- constructor(value) {
- this._i = 0;
- this._parts = value.parts;
- this._marks = [];
- this.value = value;
- this.hasNext = this._parts.length > 0;
- }
- /** @returns {PropertyValuePart|null} */
- peek(count) {
- return this._parts[this._i + (count || 0)] || null;
- }
- /** @returns {?PropertyValuePart} */
- next() {
- if (this.hasNext) {
- this.hasNext = this._i + 1 < this._parts.length;
- return this._parts[this._i++];
- }
- }
- /** @returns {PropertyValueIterator} */
- mark() {
- this._marks.push(this._i);
- return this;
- }
- popMark(success) {
- const i = this._marks.pop();
- if (!success && i != null) {
- this._i = i;
- this.hasNext = i < this._parts.length;
- }
- return success;
- }
- resetTo(i) {
- this._i = i;
- this.hasNext = this._parts.length > i;
- }
- }
- //#endregion
- //#region ValidationTypes - implementation
- for (const obj of [VTSimple, VTComplex]) {
- const action = obj === VTSimple
- ? rule => vtIsLiteral.bind(obj, rule)
- : Matcher.parse;
- for (const [id, rule] of Object.entries(obj)) {
- if (typeof rule === 'string') {
- obj[id] = Object.defineProperty(action(rule), 'originalText', {value: rule});
- } else if (/^Matcher\s/.test(rule)) {
- obj[id] = rule(Matcher);
- }
- }
- }
- function vtDescribe(type) {
- const complex = VTComplex[type];
- const text = complex instanceof Matcher ? complex.toString(0) : type;
- return vtExplode(text);
- }
- function vtExplode(text) {
- if (!text.includes('<')) return text;
- return text.replace(/(<.*?>)([{#?]?)/g, (s, rule, mod) => {
- const ref = VTSimple[rule] || VTComplex[rule];
- if (!ref || !ref.originalText) return s;
- const full = vtExplode(ref.originalText);
- const brace = mod || full.includes(' ');
- return ((brace ? '[ ' : '') + full + (brace ? ' ]' : '')) + mod;
- });
- }
- /** @param {PropertyValuePart} p */
- function vtIsAttr(p) {
- return p.isAttr && (p = p.expr) && (p = p.parts) && p.length && vtIsIdent(p[0]);
- }
- /** @param {PropertyValuePart} p */
- function vtIsIdent(p) {
- return p.tokenType === Tokens.IDENT;
- }
- /** @param {PropertyValuePart} p */
- function vtIsLength(p) {
- return p.text === '0' || p.type === 'length' || p.isCalc;
- }
- /**
- * @param {string} literals
- * @param {PropertyValuePart} part
- * @return {?boolean}
- */
- function vtIsLiteral(literals, part) {
- let text;
- for (const arg of literals.includes(' | ') ? literals.split(' | ') : [literals]) {
- if (arg.startsWith('<')) {
- const vt = VTSimple[arg];
- if (vt && vt(part)) {
- return true;
- }
- continue;
- }
- if (arg.endsWith('()') &&
- part.name &&
- part.name.length === arg.length - 2 &&
- lowerCmp(part.name, arg.slice(0, -2))) {
- return true;
- }
- if ((text || part.text) === arg ||
- (text || part.text).length >= arg.length &&
- lowerCmp(arg, text || (text = rxVendorPrefix.test(part.text) ? RegExp.$2 : part.text))) {
- return true;
- }
- }
- }
- /** @param {PropertyValuePart} p */
- function vtIsPct(p) {
- return p.text === '0' || p.type === 'percentage' || p.isCalc;
- }
- //#endregion
- //#region Validation
- const validationCache = new Map();
- function validateProperty(name, property, value, Props = Properties) {
- if (isGlobalKeyword(value.parts[0])) {
- if (value.parts.length > 1) {
- throwEndExpected(value.parts[1], true);
- }
- return;
- }
- const prop = lower(name);
- const spec = Props[prop] || rxVendorPrefix.test(prop) && Props[RegExp.$2];
- if (typeof spec === 'number' || !spec && prop.startsWith('-')) {
- return;
- }
- if (!spec) {
- const problem = Props === Properties || !Properties[prop] ? 'Unknown' : 'Misplaced';
- throw new ValidationError(`${problem} property '${name}'.`, value);
- }
- if (hasVarParts(value)) {
- return;
- }
- let known = validationCache.get(prop);
- if (known && known.has(value.text)) {
- return;
- }
- // Property-specific validation.
- const expr = new PropertyValueIterator(value);
- const m = Matcher.parse(spec);
- let result = m.match(expr);
- if (/\battr\(/i.test(value.text)) {
- if (!result) {
- expr.tryAttr = true;
- expr.resetTo(0);
- result = m.match(expr);
- }
- for (let p; (p = expr.peek()) && p.isAttr && vtIsAttr(p);) {
- expr.next();
- }
- }
- if (result) {
- if (expr.hasNext) throwEndExpected(expr.next());
- } else if (expr.hasNext && expr._i) {
- throwEndExpected(expr.peek());
- } else {
- const {text} = expr.value;
- throw new ValidationError(`Expected '${vtDescribe(spec)}' but found '${text}'.`,
- expr.value);
- }
- if (!known) validationCache.set(prop, (known = new Set()));
- known.add(value.text);
- function throwEndExpected(unit, force) {
- if (force || !unit.isVar) {
- throw new ValidationError(`Expected end of value but found '${unit.text}'.`, unit);
- }
- }
- }
- //#endregion
- //#region TokenStreamBase
- /** lookup table size for TokenStreamBase */
- const LT_SIZE = 5;
- /**
- * Generic TokenStream providing base functionality.
- * @typedef TokenStream
- */
- class TokenStreamBase {
- constructor(input) {
- this._reader = new StringReader(input ? input.toString() : '');
- this.resetLT();
- }
- resetLT() {
- /** @type {parserlib.Token} Last consumed token object */
- this._token = null;
- // Lookahead token buffer.
- this._lt = Array(LT_SIZE).fill(null);
- this._ltIndex = 0;
- this._ltAhead = 0;
- this._ltShift = 0;
- }
- /**
- * Consumes the next token if that matches any of the given token type(s).
- * @param {int|int[]} tokenTypes
- * @param {string|string[]} [values]
- * @return {parserlib.Token|boolean} token or `false`
- */
- match(tokenTypes, values) {
- const isArray = typeof tokenTypes === 'object';
- for (let token, tt; (tt = (token = this.get(true)).type);) {
- if ((isArray ? tokenTypes.includes(tt) : tt === tokenTypes) &&
- (!values || values.some(lowerCmpThis, token.value))) {
- return token;
- }
- if (tt !== Tokens.COMMENT) {
- break;
- }
- }
- this.unget();
- return false;
- }
- /**
- * Consumes the next token if that matches the given token type(s).
- * Otherwise an error is thrown.
- * @param {int|int[]} tokenTypes
- * @throws {SyntaxError}
- */
- mustMatch(tokenTypes) {
- return this.match(tokenTypes) ||
- this.throwUnexpected(this.LT(1), tokenTypes);
- }
- /**
- * Keeps reading until one of the specified token types is found or EOF.
- * @param {int|int[]} tokenTypes
- */
- advance(tokenTypes) {
- while (this.LA(0) !== 0 && !this.match(tokenTypes)) {
- this.get();
- }
- return this.LA(0);
- }
- /**
- * Consumes the next token from the token stream.
- * @param {boolean} [asToken]
- * @return {int|parserlib.Token} The token type
- */
- get(asToken) {
- const i = this._ltIndex;
- const next = i + 1;
- const slot = (i + this._ltShift) % LT_SIZE;
- if (i < this._ltAhead) {
- this._ltIndex = next;
- const token = this._token = this._lt[slot];
- return asToken ? token : token.type;
- }
- const token = this._getToken();
- const {type} = token;
- const hide = type && (Tokens[type] || {}).hide;
- if (type >= 0 && !hide) {
- this._token = token;
- this._lt[slot] = token;
- if (this._ltAhead < LT_SIZE) {
- this._ltIndex = next;
- this._ltAhead++;
- } else {
- this._ltShift = (this._ltShift + 1) % LT_SIZE;
- }
- }
- // Skip to the next token if the token type is marked as hidden.
- return hide ? this.get(asToken) :
- asToken ? token : type;
- }
- /**
- * Looks ahead a certain number of tokens and returns the token type at that position.
- * @param {int} index The index of the token type to retrieve.
- * 0 for the current token, 1 for the next, -1 for the previous, etc.
- * @return {int} The token type
- * @throws if you lookahead past EOF, past the size of the lookahead buffer,
- * or back past the first token in the lookahead buffer.
- */
- LA(index) {
- return (index ? this.LT(index) : this._token).type;
- }
- /**
- * Looks ahead a certain number of tokens and returns the token at that position.
- * @param {int} index The index of the token type to retrieve.
- * 0 for the current token, 1 for the next, -1 for the previous, etc.
- * @param {boolean} [forceCache] won't call get() so it's useful in fast tentative checks
- * @return {Object} The token
- * @throws if you lookahead past EOF, past the size of the lookahead buffer,
- * or back past the first token in the lookahead buffer.
- */
- LT(index, forceCache) {
- if (!index) {
- return this._token;
- }
- let i = index + this._ltIndex - (index > 0);
- if (index < 0 ? i >= 0 : i < this._ltAhead) {
- return this._lt[(i + this._ltShift) % LT_SIZE];
- } else if (forceCache) {
- return false;
- }
- if (index < 0) {
- throw new Error('Too much lookbehind.');
- }
- if (index > LT_SIZE) {
- throw new Error('Too much lookahead.');
- }
- i = index;
- const oldToken = this._token;
- while (i && i--) this.get();
- const token = this._token;
- this._ltIndex -= index;
- this._token = oldToken;
- return token;
- }
- /** Returns the token type for the next token in the stream without consuming it. */
- peek() {
- return this.LT(1).type;
- }
- /** Restores the last consumed token to the token stream. */
- unget() {
- if (this._ltIndex) {
- this._ltIndex--;
- this._token = this._lt[(this._ltIndex - 1 + this._ltShift + LT_SIZE) % LT_SIZE];
- } else {
- throw new Error('Too much lookahead.');
- }
- }
- throwUnexpected(token = this._token, expected = []) {
- expected = (Array.isArray(expected) ? expected : [expected])
- .map(e => typeof e === 'string' ? e : Tokens.name(e))
- .join(', ');
- const msg = expected
- ? `Expected ${expected} but found '${token.value}'.`
- : `Unexpected '${token.value}'.`;
- throw new SyntaxError(msg, token);
- }
- }
- //#endregion
- //#region TokenStream
- class TokenStream extends TokenStreamBase {
- /**
- * @param {Number|Number[]} tokenTypes
- * @param {Boolean} [skipCruftBefore=true] - skip comments/whitespace before matching
- * @returns {Object} token
- */
- mustMatch(tokenTypes, skipCruftBefore = true) {
- if (skipCruftBefore && tokenTypes !== Tokens.S) {
- this.skipComment(true);
- }
- return super.mustMatch(tokenTypes);
- }
- /**
- * @param {Boolean} [skipWS] - skip whitespace too
- * @param {Boolean} [skipUsoVar] - skip USO_VAR too
- */
- skipComment(skipWS, skipUsoVar) {
- const tt = this.LT(1, true).type;
- if (skipWS && tt === Tokens.S ||
- skipUsoVar && tt === Tokens.USO_VAR ||
- tt === Tokens.COMMENT ||
- tt == null && this._ltIndex === this._ltAhead && (
- skipWS && this._reader.readMatch(/\s+/y),
- this._reader.peekTest(/\/\*/y))) {
- while (this.match(skipUsoVar ? TT.usoS : Tokens.S)) { /*NOP*/ }
- }
- }
- /**
- * @returns {Object} token
- */
- _getToken() {
- const reader = this._reader;
- /** @namespace parserlib.Token */
- const tok = {
- value: '',
- type: Tokens.CHAR,
- col: reader._col,
- line: reader._line,
- offset: reader._cursor,
- };
- let a = tok.value = reader.read();
- let b = reader.peek();
- if (a === '\\') {
- if (b === '\n' || b === '\f') return tok;
- a = this.readEscape();
- b = reader.peek();
- }
- switch (a) {
- case ' ':
- case '\n':
- case '\r':
- case '\t':
- case '\f':
- tok.type = Tokens.S;
- if (/\s/.test(b)) {
- tok.value += reader.readMatch(/\s+/y) || '';
- }
- return tok;
- case '{':
- tok.type = Tokens.LBRACE;
- tok.endChar = '}';
- return tok;
- case '(':
- tok.type = Tokens.LPAREN;
- tok.endChar = ')';
- return tok;
- case '[':
- tok.type = Tokens.LBRACKET;
- tok.endChar = ']';
- return tok;
- case '/':
- if (b === '*') {
- const str = tok.value = this.readComment(a);
- tok.type = str.startsWith('/*[[') && str.endsWith(']]*/')
- ? Tokens.USO_VAR
- : Tokens.COMMENT;
- } else {
- tok.type = Tokens.SLASH;
- }
- return tok;
- case '|':
- case '~':
- case '^':
- case '$':
- case '*':
- if (b === '=') {
- tok.value = a + reader.read();
- tok.type = typeMap.get(tok.value) || Tokens.CHAR;
- } else if (a === '|' && b === '|') {
- reader.read();
- tok.value = '||';
- tok.type = Tokens.COLUMN;
- } else {
- tok.type = typeMap.get(a) || Tokens.CHAR;
- }
- return tok;
- case '"':
- case "'":
- return this.stringToken(a, tok);
- case '#':
- if (rxNameChar.test(b)) {
- tok.type = Tokens.HASH;
- tok.value = this.readName(a);
- }
- return tok;
- case '.':
- if (b >= '0' && b <= '9') {
- this.numberToken(a, tok);
- } else {
- tok.type = Tokens.DOT;
- }
- return tok;
- case '-':
- // could be closing HTML-style comment or CSS variable
- if (b === '-') {
- if (reader.peekTest(/-\w/yu)) {
- this.identOrFunctionToken(a, tok);
- } else if (reader.readMatch('->')) {
- tok.type = Tokens.CDC;
- tok.value = '-->';
- }
- } else if (b >= '0' && b <= '9' || b === '.' && reader.peekTest(/\.\d/y)) {
- this.numberToken(a, tok);
- } else if (rxIdentStart.test(b)) {
- this.identOrFunctionToken(a, tok);
- } else {
- tok.type = Tokens.MINUS;
- }
- return tok;
- case '+':
- if (b >= '0' && b <= '9' || b === '.' && reader.peekTest(/\.\d/y)) {
- this.numberToken(a, tok);
- } else {
- tok.type = Tokens.PLUS;
- }
- return tok;
- case '!':
- return this.importantToken(a, tok);
- case '@':
- return this.atRuleToken(a, tok);
- case ':': {
- const func = /[-niw]/i.test(b) &&
- reader.readMatch(/(not|is|where|(-(moz|webkit)-)?any)\(/iy);
- if (func) {
- const first = b.toLowerCase();
- tok.type =
- first === 'n' ? Tokens.NOT :
- first === 'i' ? Tokens.IS :
- first === 'w' ? Tokens.WHERE : Tokens.ANY;
- tok.value += func;
- } else {
- tok.type = Tokens.COLON;
- }
- return tok;
- }
- case '<':
- if (b === '!' && reader.readMatch('!--')) {
- tok.type = Tokens.CDO;
- tok.value = '<!--';
- }
- return tok;
- // EOF
- case null:
- tok.type = Tokens.EOF;
- return tok;
- case 'U':
- case 'u':
- return b === '+'
- ? this.unicodeRangeToken(a, tok)
- : this.identOrFunctionToken(a, tok);
- }
- if (a >= '0' && a <= '9') {
- this.numberToken(a, tok);
- } else if (rxIdentStart.test(a)) {
- this.identOrFunctionToken(a, tok);
- } else {
- tok.type = typeMap.get(a) || Tokens.CHAR;
- }
- return tok;
- }
- atRuleToken(first, token) {
- this._reader.mark();
- let rule = first + this.readName();
- let tt = Tokens.type(lower(rule));
- // if it's not valid, use the first character only and reset the reader
- if (tt === Tokens.CHAR || tt === Tokens.UNKNOWN) {
- if (rule.length > 1) {
- tt = Tokens.UNKNOWN_SYM;
- } else {
- tt = Tokens.CHAR;
- rule = first;
- this._reader.reset();
- }
- }
- token.type = tt;
- token.value = rule;
- return token;
- }
- identOrFunctionToken(first, token) {
- const reader = this._reader;
- const name = this.readChunksWithEscape(first, rxNameCharNoEsc);
- const next = reader.peek();
- token.value = name;
- // might be a URI or function
- if (next === '(') {
- reader.read();
- if (/^(url(-prefix)?|domain)$/i.test(name)) {
- reader.mark();
- const uri = this.readURI(name + '(');
- if (uri) {
- token.type = Tokens.URI;
- token.value = uri.text;
- token.name = name;
- token.uri = uri.value;
- return token;
- }
- reader.reset();
- }
- token.type = Tokens.FUNCTION;
- token.value += '(';
- } else if (next === ':' && lowerCmp(name, 'progid')) {
- token.type = Tokens.IE_FUNCTION;
- token.value += reader.readTo('(');
- } else {
- token.type = Tokens.IDENT;
- }
- return token;
- }
- importantToken(first, token) {
- const reader = this._reader;
- let text = first;
- reader.mark();
- for (let pass = 1; pass++ <= 2;) {
- const important = reader.readMatch(/\s*important\b/iy);
- if (important) {
- token.type = Tokens.IMPORTANT;
- token.value = text + important;
- return token;
- }
- const comment = reader.readMatch('/*');
- if (!comment) break;
- text += this.readComment(comment);
- }
- reader.reset();
- return token;
- }
- numberToken(first, token) {
- const reader = this._reader;
- const value = first + (
- this._reader.readMatch(
- first === '.' ?
- /\d+(e[+-]?\d+)?/iy :
- first >= '0' && first <= '9' ?
- /\d*\.?\d*(e[+-]?\d+)?/iy :
- /(\d*\.\d+|\d+\.?\d*)(e[+-]?\d+)?/iy
- ) || '');
- let tt = Tokens.NUMBER;
- let units, type;
- const c = reader.peek();
- if (rxIdentStart.test(c)) {
- units = this.readName(reader.read());
- type = UNITS[units] || UNITS[lower(units)];
- tt = type && Tokens[type.toUpperCase()] ||
- type === 'frequency' && Tokens.FREQ ||
- Tokens.DIMENSION;
- } else if (c === '%') {
- units = reader.read();
- type = 'percentage';
- tt = Tokens.PERCENTAGE;
- } else {
- type = 'number';
- }
- token.type = tt;
- token.value = units ? value + units : value;
- token.number = parseFloat(value);
- if (units) token.units = units;
- if (type) token.unitsType = type;
- return token;
- }
- stringToken(first, token) {
- const delim = first;
- const string = first ? [first] : [];
- const reader = this._reader;
- let tt = Tokens.STRING;
- let c;
- while (true) {
- c = reader.readMatch(/[^\n\r\f\\'"]+|./y);
- if (!c) break;
- string.push(c);
- if (c === '\\') {
- c = reader.read();
- if (c == null) {
- break; // premature EOF after backslash
- } else if (/[^\r\n\f0-9a-f]/i.test(c)) {
- // single-character escape
- string.push(c);
- } else {
- // read up to six hex digits + newline
- string.push(c, reader.readMatch(/[0-9a-f]{1,6}\n?/yi));
- }
- } else if (c === delim) {
- break; // delimiter found.
- } else if (reader.peekTest(/[\n\r\f]/y)) {
- // newline without an escapement: it's an invalid string
- tt = Tokens.INVALID;
- break;
- }
- }
- token.type = c ? tt : Tokens.INVALID; // if the string wasn't closed
- token.value = fastJoin(string);
- return token;
- }
- unicodeRangeToken(first, token) {
- const reader = this._reader;
- reader.mark();
- token.value += reader.read(); // +
- let chunk = this.readUnicodeRangePart(true);
- if (!chunk) {
- reader.reset();
- return token;
- }
- token.value += chunk;
- // if there's a ? in the first part, there can't be a second part
- if (!token.value.includes('?') && reader.peek() === '-') {
- reader.mark();
- reader.read();
- chunk = this.readUnicodeRangePart(false);
- if (!chunk) {
- reader.reset();
- } else {
- token.value += '-' + chunk;
- }
- }
- token.type = Tokens.UNICODE_RANGE;
- return token;
- }
- readUnicodeRangePart(allowQuestionMark) {
- const reader = this._reader;
- let part = reader.readMatch(/[0-9a-f]{1,6}/iy);
- while (allowQuestionMark && part.length < 6 && reader.peek() === '?') {
- part += reader.read();
- }
- return part;
- }
- // returns null w/o resetting reader if string is invalid.
- readString(first = this._reader.read()) {
- const token = this.stringToken(first, {});
- return token.type !== Tokens.INVALID ? token.value : null;
- }
- // returns null w/o resetting reader if URI is invalid.
- readURI(first) {
- const reader = this._reader;
- const uri = first;
- let value = '';
- this._reader.readMatch(/\s+/y);
- if (reader.peekTest(/['"]/y)) {
- value = this.readString();
- if (value == null) return null;
- value = parseString(value);
- } else {
- value = this.readChunksWithEscape('', rxUnquotedUrlCharNoEsc);
- }
- this._reader.readMatch(/\s+/y);
- // Ensure argument to URL is always double-quoted
- // (This simplifies later processing in PropertyValuePart.)
- return reader.peek() !== ')' ? null : {
- value,
- text: uri + serializeString(value) + reader.read(),
- };
- }
- readName(first) {
- return this.readChunksWithEscape(first, rxNameCharNoEsc);
- }
- readEscape() {
- let res = this._reader.readMatch(/[0-9a-f]{1,6}\s?/iy);
- if (res) {
- res = parseInt(res, 16);
- res = String.fromCodePoint(res && res <= 0x10FFFF ? res : 0xFFFD);
- } else {
- res = this._reader.read();
- }
- return res;
- }
- /**
- * @param {?string} first
- * @param {RegExp} rx - must not match \\
- * @returns {string}
- */
- readChunksWithEscape(first, rx) {
- const reader = this._reader;
- const url = first ? [first] : [];
- while (true) {
- const chunk = reader.readMatch(rx);
- if (chunk) url.push(chunk);
- if (reader.peekTest(/\\[^\r\n\f]/y)) {
- reader.read();
- url.push(this.readEscape());
- } else {
- break;
- }
- }
- return fastJoin(url);
- }
- readComment(first) {
- return first +
- this._reader.readCount(2 - first.length) +
- this._reader.readMatch(/([^*]|\*(?!\/))*(\*\/|$)/y);
- }
- /**
- * @param {boolean} [omitComments]
- * @param {string} [stopOn] - goes to the parent if used at the top nesting level of the value,
- specifying an empty string will stop after consuming the first encountered top block.
- * @returns {?string}
- */
- readDeclValue({omitComments, stopOn = ';!})'} = {}) {
- const reader = this._reader;
- const value = [];
- const endings = [];
- let end = stopOn;
- const rx = stopOn.includes(';')
- ? /([^;!'"{}()[\]/\\]|\/(?!\*))+/y
- : /([^'"{}()[\]/\\]|\/(?!\*))+/y;
- while (!reader.eof()) {
- const chunk = reader.readMatch(rx);
- if (chunk) {
- value.push(chunk);
- }
- reader.mark();
- const c = reader.read();
- if (!endings.length && stopOn.includes(c)) {
- reader.reset();
- break;
- }
- value.push(c);
- if (c === '\\') {
- value[value.length - 1] = this.readEscape();
- } else if (c === '/') {
- value[value.length - 1] = this.readComment(c);
- if (omitComments) value.pop();
- } else if (c === '"' || c === "'") {
- value[value.length - 1] = this.readString(c);
- } else if (c === '{' || c === '(' || c === '[') {
- endings.push(end);
- end = c === '{' ? '}' : c === '(' ? ')' : ']';
- } else if (c === '}' || c === ')' || c === ']') {
- if (!end.includes(c)) {
- reader.reset();
- return null;
- }
- end = endings.pop();
- if (!end && !stopOn) {
- break;
- }
- }
- }
- return fastJoin(value);
- }
- readUnknownSym() {
- const reader = this._reader;
- const prelude = [];
- let block;
- while (true) {
- if (reader.eof()) this.throwUnexpected();
- const c = reader.peek();
- if (c === '{') {
- block = this.readDeclValue({stopOn: ''});
- break;
- } else if (c === ';') {
- reader.read();
- break;
- } else {
- prelude.push(this.readDeclValue({omitComments: true, stopOn: ';{'}));
- }
- }
- return {prelude, block};
- }
- }
- //#endregion
- //#region parserCache
- /**
- * Caches the results and reuses them on subsequent parsing of the same code
- */
- const parserCache = (() => {
- const MAX_DURATION = 10 * 60e3;
- const TRIM_DELAY = 10e3;
- // all blocks since page load; key = text between block start and { inclusive
- const data = new Map();
- // nested block stack
- const stack = [];
- // performance.now() of the current parser
- let generation = null;
- // performance.now() of the first parser after reset or page load,
- // used for weighted sorting in getBlock()
- let generationBase = null;
- // true on page load, first run is pure analysis
- let firstRun = true;
- let parser = null;
- let stream = null;
- return {
- start(newParser) {
- parser = newParser;
- if (!parser) {
- data.clear();
- stack.length = 0;
- generationBase = performance.now();
- return;
- }
- if (firstRun) firstRun = false;
- stream = parser._tokenStream;
- generation = performance.now();
- trim();
- },
- addEvent(event) {
- if (!parser) return;
- for (let i = stack.length; --i >= 0;) {
- const {offset, endOffset, events} = stack[i];
- if (event.offset >= offset && (!endOffset || event.offset <= endOffset)) {
- events.push(event);
- return;
- }
- }
- },
- findBlock(token = getToken()) {
- if (!parser || firstRun || !token) return;
- const reader = stream._reader;
- const input = reader._input;
- let start = token.offset;
- const c = input[start];
- if (c === ' ' || c === '\t' || c === '\n' || c === '\f' || c === '\r') {
- const rx = /\s*/y;
- rx.lastIndex = start;
- rx.exec(input);
- start = rx.lastIndex;
- }
- const key = input.slice(start, input.indexOf('{', start) + 1);
- const blocks = data.get(key);
- if (!blocks) return;
- const block = getBlock(blocks, input, start, key);
- if (!block) return;
- reader.readCount(start - reader._cursor);
- shiftBlock(reader, start, block);
- shiftStream(reader, block);
- parser._ws();
- return true;
- },
- startBlock(start = getToken()) {
- if (!parser) return;
- stack.push({
- text: '',
- events: [],
- generation: generation,
- line: start.line,
- col: start.col,
- offset: start.offset,
- endLine: undefined,
- endCol: undefined,
- endOffset: undefined,
- });
- },
- adjustBlockStart(start = getToken()) {
- if (!parser) return;
- const block = stack[stack.length - 1];
- block.line = start.line;
- block.col = start.col;
- block.offset = start.offset;
- },
- endBlock(end = getToken()) {
- if (!parser) return;
- const block = stack.pop();
- block.endLine = end.line;
- block.endCol = end.col + end.value.length;
- block.endOffset = end.offset + end.value.length;
- const input = stream._reader._input;
- const key = input.slice(block.offset, input.indexOf('{', block.offset) + 1);
- block.text = input.slice(block.offset, block.endOffset);
- let blocks = data.get(key);
- if (!blocks) data.set(key, (blocks = []));
- blocks.push(block);
- },
- cancelBlock: () => stack.pop(),
- feedback({messages}) {
- messages = new Set(messages);
- for (const blocks of data.values()) {
- for (const block of blocks) {
- if (!block.events.length) continue;
- if (block.generation !== generation) continue;
- const {
- line: L1,
- col: C1,
- endLine: L2,
- endCol: C2,
- } = block;
- let isClean = true;
- for (const msg of messages) {
- const {line, col} = msg;
- if (L1 === L2 && line === L1 && C1 <= col && col <= C2 ||
- line === L1 && col >= C1 ||
- line === L2 && col <= C2 ||
- line > L1 && line < L2) {
- messages.delete(msg);
- isClean = false;
- }
- }
- if (isClean) block.events.length = 0;
- }
- }
- },
- };
- /**
- * Removes old entries from the cache.
- * 'Old' means older than MAX_DURATION or half the blocks from the previous generation(s).
- * @param {Boolean} [immediately] - set internally when debounced by TRIM_DELAY
- */
- function trim(immediately) {
- if (!immediately) {
- clearTimeout(trim.timer);
- trim.timer = setTimeout(trim, TRIM_DELAY, true);
- return;
- }
- const cutoff = performance.now() - MAX_DURATION;
- for (const [key, blocks] of data.entries()) {
- const halfLen = blocks.length >> 1;
- const newBlocks = blocks
- .sort((a, b) => a.time - b.time)
- .filter((block, i) => block.generation > cutoff ||
- block.generation !== generation && i < halfLen);
- if (!newBlocks.length) {
- data.delete(key);
- } else if (newBlocks.length !== blocks.length) {
- data.set(key, newBlocks);
- }
- }
- }
- // gets the matching block
- function getBlock(blocks, input, start, key) {
- // extracted to prevent V8 deopt
- const keyLast = Math.max(key.length - 1);
- const check1 = input[start];
- const check2 = input[start + keyLast];
- const generationSpan = performance.now() - generationBase;
- blocks = blocks
- .filter(({text, offset, endOffset}) =>
- text[0] === check1 &&
- text[keyLast] === check2 &&
- text[text.length - 1] === input[start + text.length - 1] &&
- text.startsWith(key) &&
- text === input.substr(start, endOffset - offset))
- .sort((a, b) =>
- // newest and closest will be the first element
- (a.generation - b.generation) / generationSpan +
- (Math.abs(a.offset - start) - Math.abs(b.offset - start)) / input.length);
- // identical blocks may produce different reports in CSSLint
- // so we need to either hijack an older generation block or make a clone
- const block = blocks.find(b => b.generation !== generation);
- return block || deepCopy(blocks[0]);
- }
- // Shifts positions of the block and its events, also fires the events
- function shiftBlock(reader, start, block) {
- // extracted to prevent V8 deopt
- const deltaLines = reader._line - block.line;
- const deltaCols = block.col === 1 && reader._col === 1 ? 0 : reader._col - block.col;
- const deltaOffs = reader._cursor - block.offset;
- const hasDelta = deltaLines || deltaCols || deltaOffs;
- const shifted = new Set();
- for (const e of block.events) {
- if (hasDelta) {
- applyDelta(e, shifted, block.line, deltaLines, deltaCols, deltaOffs);
- }
- parser.fire(e, false);
- }
- block.generation = generation;
- block.endCol += block.endLine === block.line ? deltaCols : 0;
- block.endLine += deltaLines;
- block.endOffset = reader._cursor + block.text.length;
- block.line += deltaLines;
- block.col += deltaCols;
- block.offset = reader._cursor;
- }
- function shiftStream(reader, block) {
- reader._line = block.endLine;
- reader._col = block.endCol;
- reader._cursor = block.endOffset;
- stream.resetLT();
- }
- // Recursively applies the delta to the event and all its nested parts
- function applyDelta(obj, seen, line, lines, cols, offs) {
- if (seen.has(obj)) return;
- seen.add(obj);
- if (Array.isArray(obj)) {
- for (const item of obj) {
- if ((typeof item === 'object' || Array.isArray(item)) && item) {
- applyDelta(item, seen, line, lines, cols, offs);
- }
- }
- return;
- }
- // applyDelta may get surpisingly slow on complex objects so we're using an array
- // because in js an array lookup is much faster than a property lookup
- const keys = Object.keys(obj);
- if (cols !== 0) {
- if (keys.includes('col') && obj.line === line) obj.col += cols;
- if (keys.includes('endCol') && obj.endLine === line) obj.endCol += cols;
- }
- if (lines !== 0) {
- if (keys.includes('line')) obj.line += lines;
- if (keys.includes('endLine')) obj.endLine += lines;
- }
- if (offs !== 0 && keys.includes('offset')) obj.offset += offs;
- for (const k of keys) {
- if (k !== 'col' && k !== 'endCol' &&
- k !== 'line' && k !== 'endLine' &&
- k !== 'offset') {
- const v = obj[k];
- if (v && typeof v === 'object') {
- applyDelta(v, seen, line, lines, cols, offs);
- }
- }
- }
- }
- // returns next token if it's already seen or the current token
- function getToken() {
- return parser && (stream.LT(1, true) || stream._token);
- }
- function deepCopy(obj) {
- if (!obj || typeof obj !== 'object') {
- return obj;
- }
- if (Array.isArray(obj)) {
- return obj.map(deepCopy);
- }
- const copy = {};
- for (const k in obj) {
- copy[k] = deepCopy(obj[k]);
- }
- return copy;
- }
- })();
- //#endregion
- //#region Parser
- class Parser extends EventTarget {
- /**
- * @param {Object} [options]
- * @param {boolean} [options.ieFilters] - accepts IE < 8 filters instead of throwing
- * @param {boolean} [options.skipValidation] - skip syntax validation
- * @param {boolean} [options.starHack] - allows IE6 star hack
- * @param {boolean} [options.strict] - stop on errors instead of reporting them and continuing
- * @param {boolean} [options.topDocOnly] - quickly extract all top-level @-moz-document,
- their {}-block contents is retrieved as text using _simpleBlock()
- * @param {boolean} [options.underscoreHack] - interprets leading _ as IE6-7 for known props
- */
- constructor(options) {
- super();
- this.options = options || {};
- /** @type {TokenStream|TokenStreamBase} */
- this._tokenStream = null;
- }
- /**
- * @param {string|Object} event
- * @param {parserlib.Token|SyntaxUnit} [token=this._tokenStream._token] - sets the position
- */
- fire(event, token = this._tokenStream._token) {
- if (typeof event === 'string') {
- event = {type: event};
- }
- if (event.offset === undefined && token) {
- event.offset = token.offset;
- if (event.line === undefined) event.line = token.line;
- if (event.col === undefined) event.col = token.col;
- }
- if (token !== false) parserCache.addEvent(event);
- super.fire(event);
- }
- _stylesheet() {
- const stream = this._tokenStream;
- this.fire('startstylesheet');
- this._sheetGlobals();
- const {topDocOnly} = this.options;
- const allowedActions = topDocOnly ? Parser.ACTIONS.topDoc : Parser.ACTIONS.stylesheet;
- for (let tt, token; (tt = (token = stream.get(true)).type); this._skipCruft()) {
- try {
- const action = allowedActions.get(tt);
- if (action) {
- action.call(this, token);
- continue;
- }
- if (topDocOnly) {
- stream.readDeclValue({stopOn: '{}'});
- if (stream._reader.peek() === '{') {
- stream.readDeclValue({stopOn: ''});
- }
- continue;
- }
- stream.unget();
- if (!this._ruleset() && stream.peek() !== Tokens.EOF) {
- stream.throwUnexpected(stream.get(true));
- }
- } catch (ex) {
- if (ex instanceof SyntaxError && !this.options.strict) {
- this.fire(Object.assign({}, ex, {type: 'error', error: ex}));
- } else {
- ex.message = ex.stack;
- ex.line = token.line;
- ex.col = token.col;
- throw ex;
- }
- }
- }
- this.fire('endstylesheet');
- }
- _sheetGlobals() {
- const stream = this._tokenStream;
- this._skipCruft();
- for (const [type, fn, max = Infinity] of [
- [Tokens.CHARSET_SYM, this._charset, 1],
- [Tokens.IMPORT_SYM, this._import],
- [Tokens.NAMESPACE_SYM, this._namespace],
- ]) {
- for (let i = 0; i++ < max && stream.peek() === type;) {
- fn.call(this, stream.get(true));
- this._skipCruft();
- }
- }
- }
- _charset(start) {
- const stream = this._tokenStream;
- const charset = stream.mustMatch(Tokens.STRING).value;
- stream.mustMatch(Tokens.SEMICOLON);
- this.fire({type: 'charset', charset}, start);
- }
- _import(start) {
- const stream = this._tokenStream;
- const token = stream.mustMatch(TT.stringUri);
- const uri = token.uri || token.value.replace(/^["']|["']$/g, '');
- this._ws();
- const media = this._mediaQueryList();
- stream.mustMatch(Tokens.SEMICOLON);
- this.fire({type: 'import', media, uri}, start);
- this._ws();
- }
- _namespace(start) {
- const stream = this._tokenStream;
- this._ws();
- const prefix = stream.match(Tokens.IDENT).value;
- if (prefix) this._ws();
- const token = stream.mustMatch(TT.stringUri);
- const uri = token.uri || token.value.replace(/^["']|["']$/g, '');
- stream.mustMatch(Tokens.SEMICOLON);
- this.fire({type: 'namespace', prefix, uri}, start);
- this._ws();
- }
- _supports(start) {
- const stream = this._tokenStream;
- this._ws();
- this._supportsCondition();
- stream.mustMatch(Tokens.LBRACE);
- this.fire('startsupports', start);
- this._ws();
- for (;; stream.skipComment()) {
- const action = Parser.ACTIONS.supports.get(stream.peek());
- if (action) {
- action.call(this, stream.get(true));
- } else if (!this._ruleset()) {
- break;
- }
- }
- stream.mustMatch(Tokens.RBRACE);
- this.fire('endsupports');
- this._ws();
- }
- _supportsCondition() {
- const stream = this._tokenStream;
- if (stream.match(Tokens.IDENT, ['not'])) {
- stream.mustMatch(Tokens.S);
- this._supportsConditionInParens();
- } else {
- this._supportsConditionInParens();
- while (stream.match(Tokens.IDENT, ['and', 'or'])) {
- this._ws();
- this._supportsConditionInParens();
- }
- }
- }
- _supportsConditionInParens() {
- const stream = this._tokenStream;
- const next = stream.LT(1);
- if (next.type === Tokens.LPAREN) {
- stream.get();
- this._ws();
- const {type, value} = stream.LT(1);
- if (type === Tokens.IDENT) {
- if (lowerCmp(value, 'not')) {
- this._supportsCondition();
- stream.mustMatch(Tokens.RPAREN);
- } else {
- this._supportsDecl(false);
- }
- } else {
- this._supportsCondition();
- stream.mustMatch(Tokens.RPAREN);
- }
- } else if (stream.match(Tokens.FUNCTION, ['selector('])) {
- this._ws();
- const selector = this._selector();
- this.fire({type: 'supportsSelector', selector}, selector);
- stream.mustMatch(Tokens.RPAREN);
- } else {
- this._supportsDecl();
- }
- this._ws();
- }
- _supportsDecl(requireStartParen = true) {
- const stream = this._tokenStream;
- if (requireStartParen) {
- stream.mustMatch(Tokens.LPAREN);
- }
- this._ws();
- this._declaration();
- stream.mustMatch(Tokens.RPAREN);
- }
- _media(start) {
- const stream = this._tokenStream;
- this._ws();
- const mediaList = this._mediaQueryList();
- stream.mustMatch(Tokens.LBRACE);
- this.fire({
- type: 'startmedia',
- media: mediaList,
- }, start);
- this._ws();
- let action;
- do action = Parser.ACTIONS.media.get(stream.peek());
- while (action ? action.call(this, stream.get(true)) || true : this._ruleset());
- stream.mustMatch(Tokens.RBRACE);
- this.fire({
- type: 'endmedia',
- media: mediaList,
- });
- this._ws();
- }
- _mediaQueryList() {
- const stream = this._tokenStream;
- const mediaList = [];
- this._ws();
- if ([Tokens.IDENT, Tokens.LPAREN].includes(stream.peek())) {
- mediaList.push(this._mediaQuery());
- }
- while (stream.match(Tokens.COMMA)) {
- this._ws();
- mediaList.push(this._mediaQuery());
- }
- return mediaList;
- }
- _mediaQuery() {
- const stream = this._tokenStream;
- const expressions = [];
- let type = null;
- const token = stream.match(Tokens.IDENT, ['only', 'not']);
- const ident = token.value || null;
- this._ws();
- const next = stream.LT(1);
- switch (next.type) {
- case Tokens.IDENT:
- type = this._mediaFeature();
- break;
- case Tokens.LPAREN:
- expressions.push(this._mediaExpression());
- break;
- default:
- return;
- }
- this._ws();
- while (stream.match(Tokens.IDENT)) {
- if (lowerCmp(stream._token.value, 'and')) {
- this._ws();
- expressions.push(this._mediaExpression());
- } else {
- stream.throwUnexpected(undefined, ["'and'"]);
- }
- }
- return new MediaQuery(ident, type, expressions, token || next);
- }
- _mediaExpression() {
- const stream = this._tokenStream;
- let token;
- let expression = null;
- stream.mustMatch(Tokens.LPAREN);
- const feature = this._mediaFeature();
- this._ws();
- if (stream.match(Tokens.COLON)) {
- this._ws();
- token = stream.LT(1);
- expression = this._expression({calc: true});
- }
- stream.mustMatch(Tokens.RPAREN);
- this._ws();
- return new MediaFeature(feature, expression ? new SyntaxUnit(expression, token) : null);
- }
- _mediaFeature() {
- this._tokenStream.mustMatch(Tokens.IDENT);
- return SyntaxUnit.fromToken(this._tokenStream._token);
- }
- _page(start) {
- const stream = this._tokenStream;
- this._ws();
- const id = stream.match(Tokens.IDENT).value || null;
- if (id && lowerCmp(id, 'auto')) {
- stream.throwUnexpected();
- }
- const pseudo = stream.match(Tokens.COLON)
- ? stream.mustMatch(Tokens.IDENT, false).value
- : null;
- this._ws();
- this.fire({type: 'startpage', id, pseudo}, start);
- this._readDeclarations({readMargins: true});
- this.fire({type: 'endpage', id, pseudo});
- }
- _margin() {
- const margin = SyntaxUnit.fromToken(this._tokenStream.match(TT.margins));
- if (!margin) return false;
- this.fire({type: 'startpagemargin', margin});
- this._readDeclarations();
- this.fire({type: 'endpagemargin', margin});
- return true;
- }
- _fontFace(start) {
- this.fire('startfontface', start);
- this._ws();
- this._readDeclarations({Props: ScopedProperties['@font-face']});
- this.fire('endfontface');
- }
- _viewport(start) {
- // only viewport-fit is allowed but we're reusing MediaQuery syntax unit,
- // and accept anything for the sake of simplicity since the spec isn't yet final:
- // https://drafts.csswg.org/css-round-display/#extending-viewport-rule
- const descriptors = this._mediaQueryList();
- this.fire({type: 'startviewport', descriptors}, start);
- this._ws();
- this._readDeclarations();
- this.fire({type: 'endviewport', descriptors});
- }
- _document(start) {
- const stream = this._tokenStream;
- const functions = [];
- const prefix = start.value.split('-')[1] || '';
- do {
- this._ws();
- const uri = stream.match(Tokens.URI);
- const fn = uri ? new PropertyValuePart(uri) : this._function() || stream.LT(1);
- functions.push(fn);
- if (uri) this._ws();
- } while (stream.match(Tokens.COMMA));
- for (const fn of functions) {
- if ((fn.type !== 'function' || !/^(url(-prefix)?|domain|regexp)$/i.test(fn.name)) &&
- fn.type !== 'uri') {
- this.fire({
- type: 'error',
- message: 'Expected url( or url-prefix( or domain( or regexp(, instead saw ' +
- Tokens.name(fn.tokenType || fn.type) + ' ' + (fn.text || fn.value),
- }, fn);
- }
- }
- stream.mustMatch(Tokens.LBRACE);
- this.fire({type: 'startdocument', functions, prefix}, start);
- if (this.options.topDocOnly) {
- stream.readDeclValue({stopOn: '}'});
- } else {
- /* We allow @import and such inside document sections because the final generated CSS for
- * a given page may be valid e.g. if this section is the first one that matched the URL */
- this._sheetGlobals();
- this._ws();
- let action;
- do action = Parser.ACTIONS.document.get(stream.peek());
- while (action ? action.call(this, stream.get(true)) || true : this._ruleset());
- }
- stream.mustMatch(Tokens.RBRACE);
- this.fire({type: 'enddocument', functions, prefix});
- this._ws();
- }
- _documentMisplaced(start) {
- this.fire({
- type: 'error',
- message: 'Nested @document produces broken code',
- }, start);
- this._document(start);
- }
- _combinator() {
- const token = this._tokenStream.match(TT.combinator);
- if (token) {
- this._ws();
- return new Combinator(token);
- }
- }
- _property() {
- const stream = this._tokenStream;
- let token = stream.get(true);
- let value = null;
- let hack = null;
- let start;
- if (this.options.starHack && token.type === Tokens.STAR) {
- hack = '*';
- start = token;
- token = stream.get(true);
- }
- if (token.type === Tokens.IDENT) {
- let tokenValue = token.value;
- // check for underscore hack - no error if not allowed because it's valid CSS syntax
- if (this.options.underscoreHack && tokenValue.startsWith('_')) {
- hack = '_';
- tokenValue = tokenValue.slice(1);
- }
- value = new PropertyName(tokenValue, hack, start || token);
- this._ws();
- } else {
- stream.unget();
- }
- return value;
- }
- _ruleset() {
- const stream = this._tokenStream;
- let braceOpened;
- try {
- stream.skipComment(undefined, true);
- if (parserCache.findBlock()) {
- return true;
- }
- parserCache.startBlock();
- const selectors = this._selectorsGroup();
- if (!selectors) {
- parserCache.cancelBlock();
- return false;
- }
- parserCache.adjustBlockStart(selectors[0]);
- this.fire({type: 'startrule', selectors}, selectors[0]);
- this._readDeclarations({stopAfterBrace: true});
- braceOpened = true;
- this.fire({type: 'endrule', selectors});
- parserCache.endBlock();
- this._ws();
- return true;
- } catch (ex) {
- parserCache.cancelBlock();
- if (!(ex instanceof SyntaxError) || this.options.strict) throw ex;
- this.fire(Object.assign({}, ex, {type: 'error', error: ex}));
- // if there's a right brace, the rule is finished so don't do anything
- // otherwise, rethrow the error because it wasn't handled properly
- if (braceOpened && stream.advance(Tokens.RBRACE) !== Tokens.RBRACE) throw ex;
- // If even a single selector fails to parse, the entire ruleset should be thrown away,
- // so we let the parser continue with the next one
- return true;
- }
- }
- _selectorsGroup() {
- const stream = this._tokenStream;
- const selectors = [];
- let comma;
- for (let sel; (sel = this._selector());) {
- selectors.push(sel);
- this._ws(null, true);
- comma = stream.match(Tokens.COMMA);
- if (!comma) break;
- this._ws(null, true);
- }
- if (comma) stream.throwUnexpected(stream.LT(1));
- return selectors.length ? selectors : null;
- }
- _selector() {
- const stream = this._tokenStream;
- const sel = [];
- let nextSel = null;
- let combinator = null;
- nextSel = this._simpleSelectorSequence();
- if (!nextSel) {
- return null;
- }
- sel.push(nextSel);
- while (true) {
- combinator = this._combinator();
- if (combinator) {
- sel.push(combinator);
- nextSel = this._simpleSelectorSequence();
- if (nextSel) {
- sel.push(nextSel);
- continue;
- }
- stream.throwUnexpected(stream.LT(1));
- break;
- }
- if (!this._ws(null, true)) {
- break;
- }
- // make a fallback whitespace combinator
- const ws = new Combinator(stream._token);
- // look for an explicit combinator
- combinator = this._combinator();
- // selector is required if there's a combinator
- nextSel = this._simpleSelectorSequence();
- if (nextSel) {
- sel.push(combinator || ws);
- sel.push(nextSel);
- } else if (combinator) {
- stream.throwUnexpected(stream.LT(1));
- }
- }
- return new Selector(sel, sel[0]);
- }
- _simpleSelectorSequence() {
- const stream = this._tokenStream;
- const start = stream.LT(1);
- const modifiers = [];
- const seq = [];
- const ns = this._namespacePrefix(start.type);
- const elementName = this._typeSelector(ns) || this._universal(ns);
- if (elementName) {
- seq.push(elementName);
- } else if (ns) {
- stream.unget();
- }
- while (true) {
- const token = stream.get(true);
- const action = Parser.ACTIONS.simpleSelectorSequence.get(token.type);
- const component = action ? action.call(this, token) : (stream.unget(), 0);
- if (!component) break;
- modifiers.push(component);
- seq.push(component);
- }
- const text = fastJoin(seq);
- return text && new SelectorPart(elementName, modifiers, text, start);
- }
- _typeSelector(ns) {
- const stream = this._tokenStream;
- const nsSupplied = ns !== undefined;
- if (!nsSupplied) ns = this._namespacePrefix();
- const name = stream.match(Tokens.IDENT) &&
- new SelectorSubPart(stream._token.value, 'elementName', stream._token);
- if (!name) {
- if (!nsSupplied && ns && ns.length > 0) stream.unget();
- if (!nsSupplied && ns && ns.length > 1) stream.unget();
- return null;
- }
- if (ns) {
- name.text = ns + name.text;
- name.col -= ns.length;
- }
- return name;
- }
- _hash(start) {
- return new SelectorSubPart(start.value, 'id', start);
- }
- _class(start) {
- const name = this._tokenStream.mustMatch(Tokens.IDENT, false).value;
- return new SelectorSubPart('.' + name, 'class', start);
- }
- _namespacePrefix(next) {
- const stream = this._tokenStream;
- if (!next) next = stream.LA(1);
- return next === Tokens.PIPE ? '|' :
- (next === Tokens.IDENT || next === Tokens.STAR) && stream.LA(2) === Tokens.PIPE
- ? stream.get().value + stream.get().value
- : null;
- }
- _universal(ns = this._namespacePrefix()) {
- return `${ns || ''}${this._tokenStream.match(Tokens.STAR).value || ''}` || null;
- }
- _attrib(start) {
- const stream = this._tokenStream;
- const value = [
- start.value,
- this._ws(),
- this._namespacePrefix() || '',
- stream.mustMatch(Tokens.IDENT, false).value,
- this._ws(),
- ];
- if (stream.match(TT.attrMatch)) {
- value.push(
- stream._token.value,
- this._ws(),
- stream.mustMatch(TT.identString).value,
- this._ws());
- if (stream.match(Tokens.IDENT, ['i', 's'])) {
- value.push(
- stream._token.value,
- this._ws());
- }
- }
- value.push(stream.mustMatch(Tokens.RBRACKET).value);
- return new SelectorSubPart(fastJoin(value), 'attribute', start);
- }
- _pseudo(start) {
- const stream = this._tokenStream;
- const colons = start.value + (stream.match(Tokens.COLON).value || '');
- const t = stream.mustMatch(TT.pseudo);
- const pseudo = t.type === Tokens.IDENT ? t.value :
- t.value +
- this._ws() +
- (this._expression({list: true}) || '') +
- stream.mustMatch(Tokens.RPAREN).value;
- return new SelectorSubPart(colons + pseudo, 'pseudo', {
- line: t.line,
- col: t.col - colons.length,
- offset: t.offset - colons.length,
- });
- }
- _expression({calc, list} = {}) {
- const chunks = [];
- const stream = this._tokenStream;
- while (stream.get()) {
- const {type, value} = stream._token;
- if (calc && type === Tokens.FUNCTION) {
- if (!rxCalc.test(value)) {
- stream.throwUnexpected();
- }
- chunks.push(value,
- this._expr('calc').text,
- stream.mustMatch(Tokens.RPAREN).value);
- } else if (TT.expression.includes(type) || list && type === Tokens.COMMA) {
- chunks.push(value, this._ws());
- } else if (type !== Tokens.COMMENT) {
- stream.unget();
- break;
- }
- }
- return fastJoin(chunks) || null;
- }
- _is(start) {
- let args;
- const value =
- start.value +
- this._ws() +
- (args = this._selectorsGroup()) +
- this._ws() +
- this._tokenStream.mustMatch(Tokens.RPAREN).value;
- const type = lower(Tokens.name(start.type));
- return Object.assign(new SelectorSubPart(value, type, start), {args});
- }
- _negation(start) {
- const stream = this._tokenStream;
- const value = [start.value, this._ws()];
- const args = this._selectorsGroup();
- if (!args) stream.throwUnexpected(stream.LT(1));
- value.push(...args, this._ws(), stream.mustMatch(Tokens.RPAREN).value);
- return Object.assign(new SelectorSubPart(fastJoin(value), 'not', start), {args});
- }
- _declaration(consumeSemicolon, Props) {
- const stream = this._tokenStream;
- const property = this._property();
- if (!property) {
- return false;
- }
- stream.mustMatch(Tokens.COLON);
- const value = property.text.startsWith('--')
- ? this._customProperty() // whitespace is a part of custom property value
- : (this._ws(), this._expr());
- // if there's no parts for the value, it's an error
- if (!value || value.length === 0) {
- stream.throwUnexpected(stream.LT(1));
- }
- let invalid;
- if (!this.options.skipValidation) {
- try {
- /* If hacks are allowed, then only check the root property.
- Otherwise treat `_property` or `*property` as invalid */
- const name =
- this.options.starHack && property.hack === '*' ||
- this.options.underscoreHack && property.hack === '_'
- ? property.text
- : property.toString();
- validateProperty(name, property, value, Props);
- } catch (ex) {
- if (!(ex instanceof ValidationError)) {
- ex.message = ex.stack;
- }
- invalid = ex;
- }
- }
- const event = {
- type: 'property',
- important: stream.match(Tokens.IMPORTANT),
- property,
- value,
- };
- this._ws();
- if (invalid) {
- event.invalid = invalid;
- event.message = invalid.message;
- }
- this.fire(event, property);
- if (consumeSemicolon) {
- while (stream.match(TT.semiS)) {/*NOP*/}
- }
- this._ws();
- return true;
- }
- _expr(inFunction, endToken = Tokens.RPAREN) {
- const stream = this._tokenStream;
- const values = [];
- while (true) {
- let value = this._term(inFunction);
- if (!value && !values.length) {
- return null;
- }
- // get everything inside the parens and let validateProperty handle that
- if (!value && inFunction && stream.peek() !== endToken) {
- stream.get();
- value = new PropertyValuePart(stream._token);
- } else if (!value) {
- break;
- }
- // TODO: remove this hack
- const last = values[values.length - 1];
- if (last && last.offset === value.offset && last.text === value.text) {
- break;
- }
- values.push(value);
- this._ws();
- const operator = this._tokenStream.match(inFunction ? TT.opInFunc : TT.op);
- if (operator) {
- this._ws();
- values.push(new PropertyValuePart(operator));
- }
- }
- return values[0] ? new PropertyValue(values, values[0]) : null;
- }
- _customProperty() {
- const value = this._tokenStream.readDeclValue();
- if (value) {
- const token = this._tokenStream._token;
- token.value = value;
- token.type = Tokens.IDENT;
- return new PropertyValue([new PropertyValuePart(token)], token);
- }
- }
- _term(inFunction) {
- const stream = this._tokenStream;
- const unary = stream.match(TT.plusMinus) && stream._token;
- const finalize = (token, value) => {
- if (!token && unary) stream.unget();
- if (!token) return null;
- if (token instanceof SyntaxUnit) return token;
- if (unary) {
- token.line = unary.line;
- token.col = unary.col;
- token.value = unary.value + (value || token.value);
- } else if (value) {
- token.value = value;
- }
- return new PropertyValuePart(token);
- };
- const next = this.options.ieFilters && stream.LT(1);
- if (next && next.type === Tokens.IE_FUNCTION) {
- return finalize(next, this._ieFunction());
- }
- // see if it's a simple block
- if (stream.match(inFunction ? TT.LParenBracketBrace : TT.LParenBracket)) {
- const token = stream._token;
- const endToken = Tokens.type(token.endChar);
- token.expr = this._expr(inFunction, endToken);
- stream.mustMatch(endToken);
- return finalize(token, token.value + (token.expr || '') + token.endChar);
- }
- return finalize(
- // see if there's a simple match
- stream.match(TT.term) && stream._token ||
- this._hexcolor() ||
- this._function({asText: Boolean(unary)}));
- }
- _function({asText} = {}) {
- const stream = this._tokenStream;
- if (!stream.match(Tokens.FUNCTION)) return null;
- const start = stream._token;
- const name = start.value.slice(0, -1);
- this._ws();
- const expr = this._expr(lower(name));
- const ieFilter = this.options.ieFilters && stream.peek() === Tokens.EQUALS ?
- this._functionIeFilter() : '';
- const text = name + '(' + (expr || '') + ieFilter + ')';
- stream.mustMatch(Tokens.RPAREN);
- this._ws();
- if (asText) {
- return text;
- }
- const m = rxVendorPrefix.exec(name) || [];
- return SyntaxUnit.addFuncInfo(
- new SyntaxUnit(text, start, 'function', {
- expr,
- name: m[2] || name,
- prefix: m[1] || '',
- tokenType: Tokens.FUNCTION,
- }));
- }
- _functionIeFilter() {
- const stream = this._tokenStream;
- const text = [];
- do {
- if (this._ws()) {
- text.push(stream._token.value);
- }
- // might be second time in the loop
- if (stream.LA(0) === Tokens.COMMA) {
- text.push(stream._token.value);
- }
- stream.match(Tokens.IDENT);
- text.push(stream._token.value);
- stream.match(Tokens.EQUALS);
- text.push(stream._token.value);
- let lt = stream.peek();
- while (lt !== Tokens.COMMA &&
- lt !== Tokens.S &&
- lt !== Tokens.RPAREN &&
- lt !== Tokens.EOF) {
- stream.get();
- text.push(stream._token.value);
- lt = stream.peek();
- }
- } while (stream.match([Tokens.COMMA, Tokens.S]));
- return fastJoin(text);
- }
- _ieFunction() {
- const stream = this._tokenStream;
- const text = [];
- // IE function can begin like a regular function, too
- if (stream.match([Tokens.IE_FUNCTION, Tokens.FUNCTION])) {
- text.push(stream._token.value);
- do {
- text.push(
- stream._token.value === ',' ? ',' : '', // subsequent loops
- this._ws(),
- stream.match(Tokens.IDENT).value || '',
- stream.match(Tokens.EQUALS).value || '');
- // functionText.push(this._term());
- while (!/^([,)]|\s+)$/.test(stream.LT(1).value)) {
- text.push(stream.get(true).value);
- }
- } while (stream.match([Tokens.COMMA, Tokens.S]));
- text.push(stream.match(Tokens.RPAREN).value);
- this._ws();
- }
- return fastJoin(text) || null;
- }
- _hexcolor() {
- const stream = this._tokenStream;
- if (!stream.match(Tokens.HASH)) return null;
- const token = stream._token;
- const color = token.value;
- const len = color.length;
- if (len !== 4 && len !== 5 && len !== 7 && len !== 9 ||
- !/^#([a-f\d]{3}(?:[a-f\d](?:[a-f\d]{2}){0,2})?)$/i.test(color)) {
- throw new SyntaxError(`Expected a hex color but found '${color}'.`, token);
- }
- this._ws();
- return token;
- }
- _keyframes(start) {
- const stream = this._tokenStream;
- const prefix = rxVendorPrefix.test(start.value) ? RegExp.$1 : '';
- this._ws();
- const name = this._keyframeName();
- stream.mustMatch(Tokens.LBRACE);
- this.fire({type: 'startkeyframes', name, prefix}, start);
- // check for key
- while (true) {
- this._ws();
- const tt = stream.peek();
- if (tt !== Tokens.IDENT && tt !== Tokens.PERCENTAGE) break;
- this._keyframeRule();
- }
- stream.mustMatch(Tokens.RBRACE);
- this.fire({type: 'endkeyframes', name, prefix});
- this._ws();
- }
- _keyframeName() {
- const stream = this._tokenStream;
- stream.mustMatch(TT.identString);
- return SyntaxUnit.fromToken(stream._token);
- }
- _keyframeRule() {
- const keys = this._keyList();
- this.fire({type: 'startkeyframerule', keys}, keys[0]);
- this._readDeclarations();
- this.fire({type: 'endkeyframerule', keys});
- }
- _keyList() {
- const stream = this._tokenStream;
- const keyList = [];
- // must be least one key
- keyList.push(this._key());
- this._ws();
- while (stream.match(Tokens.COMMA)) {
- this._ws();
- keyList.push(this._key());
- this._ws();
- }
- return keyList;
- }
- _key() {
- const stream = this._tokenStream;
- if (stream.match(Tokens.PERCENTAGE)) {
- return SyntaxUnit.fromToken(stream._token);
- }
- if (stream.match(Tokens.IDENT)) {
- if (/^(from|to)$/i.test(stream._token.value)) {
- return SyntaxUnit.fromToken(stream._token);
- }
- stream.unget();
- }
- // if it gets here, there wasn't a valid token, so time to explode
- stream.throwUnexpected(stream.LT(1), ['%', "'from'", "'to'"]);
- }
- _skipCruft() {
- while (this._tokenStream.match(TT.cruft)) { /*NOP*/ }
- }
- /**
- * @param {{}} [_]
- * @param {Boolean} [_.checkStart] - check for the left brace at the beginning.
- * @param {Boolean} [_.readMargins] - check for margin patterns.
- * @param {Boolean} [_.stopAfterBrace] - stop after the final } without consuming whitespace
- * @param {{}} [_.Props] - definitions of valid properties
- */
- _readDeclarations({
- checkStart = true,
- readMargins = false,
- stopAfterBrace = false,
- Props,
- } = {}) {
- const stream = this._tokenStream;
- if (checkStart) stream.mustMatch(Tokens.LBRACE);
- let next, tt;
- while ((next = stream.get(true)).value !== '}' && (tt = next.type)) {
- try {
- // Pre-check to avoid calling _ws too much as it's wasteful
- if (tt === Tokens.S ||
- tt === Tokens.COMMENT ||
- tt === Tokens.USO_VAR) {
- this._ws(next, true);
- tt = 0;
- }
- if (tt === Tokens.SEMICOLON ||
- readMargins && this._margin() ||
- (tt && stream.unget(), this._declaration(true, Props)) ||
- (next = stream.LT(1)).value === ';' ||
- this._ws(next, true)) {
- continue;
- }
- break;
- } catch (ex) {
- this._readDeclarationsRecovery(ex, arguments[0]);
- }
- }
- if (next.value !== '}') stream.mustMatch(Tokens.RBRACE);
- if (!stopAfterBrace) this._ws();
- }
- _readDeclarationsRecovery(ex) {
- if (ex) {
- if (this.options.strict || !(ex instanceof SyntaxError)) {
- throw ex; // if not a syntax error, rethrow it
- }
- this.fire(Object.assign({}, ex, {
- type: ex.type || 'error',
- recoverable: true,
- error: ex,
- }));
- }
- switch (this._tokenStream.advance([Tokens.SEMICOLON, Tokens.RBRACE])) {
- case Tokens.SEMICOLON:
- return true; // continue to the next declaration
- case Tokens.RBRACE:
- this._tokenStream.unget();
- return;
- default:
- throw ex;
- }
- }
- _ws(start, skipUsoVar) {
- const stream = this._tokenStream;
- const tokens = skipUsoVar ? TT.usoS : Tokens.S;
- let ws = start ? start.value : '';
- for (let t; (t = stream.LT(1, true)) && t.type === Tokens.S;) {
- ws += stream.get(true).value;
- }
- if (stream._ltIndex === stream._ltAhead) {
- ws += stream._reader.readMatch(/\s+/y) || '';
- if (!stream._reader.peekTest(/\/\*/y)) {
- return ws;
- }
- }
- while (stream.match(tokens)) {
- ws += stream._token.value;
- }
- return ws;
- }
- _unknownSym(start) {
- if (this.options.strict) {
- throw new SyntaxError('Unknown @ rule.', start);
- }
- const {prelude, block} = this._tokenStream.readUnknownSym();
- this.fire({type: 'unknown-at-rule', name: start.value, prelude, block}, start);
- this._ws();
- }
- _verifyEnd() {
- const stream = this._tokenStream;
- if (stream.peek() !== Tokens.EOF) {
- stream.throwUnexpected(stream.LT(1));
- }
- }
- parse(input, {reuseCache} = {}) {
- this._tokenStream = new TokenStream(input);
- parserCache.start(reuseCache && this);
- this._stylesheet();
- }
- parseStyleSheet(input) {
- return this.parse(input);
- }
- parseMediaQuery(input, {reuseCache} = {}) {
- this._tokenStream = new TokenStream(input);
- parserCache.start(reuseCache && this);
- const result = this._mediaQuery();
- this._verifyEnd();
- return result;
- }
- /**
- * Parses a property value (everything after the semicolon).
- * @return {PropertyValue} The property value.
- * @throws parserlib.util.SyntaxError If an unexpected token is found.
- */
- parsePropertyValue(input) {
- this._tokenStream = new TokenStream(input);
- this._ws();
- const result = this._expr();
- this._ws();
- this._verifyEnd();
- return result;
- }
- /**
- * Parses a complete CSS rule, including selectors and
- * properties.
- * @param {String} input The text to parser.
- * @return {Boolean} True if the parse completed successfully, false if not.
- */
- parseRule(input, {reuseCache} = {}) {
- this._tokenStream = new TokenStream(input);
- parserCache.start(reuseCache && this);
- this._ws();
- const result = this._ruleset();
- this._ws();
- this._verifyEnd();
- return result;
- }
- /**
- * Parses a single CSS selector (no comma)
- * @param {String} input The text to parse as a CSS selector.
- * @return {Selector} An object representing the selector.
- * @throws parserlib.util.SyntaxError If an unexpected token is found.
- */
- parseSelector(input) {
- this._tokenStream = new TokenStream(input);
- this._ws();
- const result = this._selector();
- this._ws();
- this._verifyEnd();
- return result;
- }
- /**
- * Parses an HTML style attribute: a set of CSS declarations
- * separated by semicolons.
- * @param {String} input The text to parse as a style attribute
- * @return {void}
- */
- parseStyleAttribute(input) {
- // help error recovery in _readDeclarations()
- this._tokenStream = new TokenStream(input + '}');
- this._readDeclarations({checkStart: false});
- }
- }
- Object.assign(Parser, TYPES);
- Object.assign(Parser.prototype, TYPES);
- Parser.prototype._readWhitespace = Parser.prototype._ws;
- const symDocument = [Tokens.DOCUMENT_SYM, Parser.prototype._document];
- const symDocMisplaced = [Tokens.DOCUMENT_SYM, Parser.prototype._documentMisplaced];
- const symFontFace = [Tokens.FONT_FACE_SYM, Parser.prototype._fontFace];
- const symKeyframes = [Tokens.KEYFRAMES_SYM, Parser.prototype._keyframes];
- const symMedia = [Tokens.MEDIA_SYM, Parser.prototype._media];
- const symPage = [Tokens.PAGE_SYM, Parser.prototype._page];
- const symSupports = [Tokens.SUPPORTS_SYM, Parser.prototype._supports];
- const symUnknown = [Tokens.UNKNOWN_SYM, Parser.prototype._unknownSym];
- const symViewport = [Tokens.VIEWPORT_SYM, Parser.prototype._viewport];
- Parser.ACTIONS = {
- stylesheet: new Map([
- symMedia,
- symDocument,
- symSupports,
- symPage,
- symFontFace,
- symKeyframes,
- symViewport,
- symUnknown,
- [Tokens.S, Parser.prototype._ws],
- ]),
- topDoc: new Map([
- symDocument,
- symUnknown,
- [Tokens.S, Parser.prototype._ws],
- ]),
- document: new Map([
- symMedia,
- symDocMisplaced,
- symSupports,
- symPage,
- symFontFace,
- symViewport,
- symKeyframes,
- symUnknown,
- ]),
- supports: new Map([
- symKeyframes,
- symMedia,
- symSupports,
- symDocMisplaced,
- symViewport,
- symUnknown,
- ]),
- media: new Map([
- symKeyframes,
- symMedia,
- symDocMisplaced,
- symSupports,
- symPage,
- symFontFace,
- symViewport,
- symUnknown,
- ]),
- simpleSelectorSequence: new Map([
- [Tokens.HASH, Parser.prototype._hash],
- [Tokens.DOT, Parser.prototype._class],
- [Tokens.LBRACKET, Parser.prototype._attrib],
- [Tokens.COLON, Parser.prototype._pseudo],
- [Tokens.IS, Parser.prototype._is],
- [Tokens.ANY, Parser.prototype._is],
- [Tokens.WHERE, Parser.prototype._is],
- [Tokens.NOT, Parser.prototype._negation],
- ]),
- };
- //#endregion
- //#region Helper functions
- function escapeChar(c) {
- return c === '"' ? '\\' + c : `\\${c.codePointAt(0).toString(16)} `;
- }
- function fastJoin(arr) {
- return !arr.length ? '' :
- arr.length === 1 ? `${arr[0]}` :
- arr.length === 2 ? `${arr[0]}${arr[1]}` :
- arr.join('');
- }
- /**
- * vars can span any number of grammar parts so not gonna try to guess. KISS.
- * @param {PropertyValue} value
- */
- function hasVarParts(value) {
- return value.parts.some(p => p.isVar);
- }
- function isPseudoElement(pseudo) {
- return pseudo.startsWith('::') ||
- /^:(first-(letter|line)|before|after)$/i.test(pseudo);
- }
- function lower(text) {
- if (typeof text !== 'string') text = `${text}`;
- let result = lowercaseCache.get(text);
- if (result) return result;
- result = text.toLowerCase();
- lowercaseCache.set(text, result);
- return result;
- }
- function lowerCmp(a, b) {
- return a.length === b.length && (a === b || lower(a) === lower(b));
- }
- /** @this {String} */
- function lowerCmpThis(a) {
- return a.length === this.length && (a === this || lower(a) === lower(this));
- }
- function parseString(str) {
- return str.slice(1, -1) // strip surrounding quotes
- .replace(/\\(\r\n|[^\r0-9a-f]|[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?)/ig, unescapeChar);
- }
- function serializeString(value) {
- return `"${value.replace(/["\r\n\f]/g, escapeChar)}"`;
- }
- function unescapeChar(m, c) {
- if (c === '\n' || c === '\r\n' || c === '\r' || c === '\f') {
- return '';
- }
- m = /^[0-9a-f]{1,6}/i.exec(c);
- return m ? String.fromCodePoint(parseInt(m[0], 16)) : c;
- }
- //#endregion
- //#region PUBLIC API
- /** @namespace parserlib */
- return {
- css: {
- Colors,
- Combinator,
- GlobalKeywords,
- Matcher,
- MediaFeature,
- MediaQuery,
- Parser,
- Properties,
- PropertyName,
- PropertyValue,
- PropertyValuePart,
- ScopedProperties,
- Selector,
- SelectorPart,
- SelectorSubPart,
- Specificity,
- TokenStream,
- Tokens,
- ValidationError,
- },
- util: {
- EventTarget,
- StringReader,
- SyntaxError,
- SyntaxUnit,
- TokenStreamBase,
- fastJoin,
- isPseudoElement,
- lower,
- rxVendorPrefix,
- describeProp: vtExplode,
- },
- cache: parserCache,
- };
- //#endregion
- })();
|