csslint.js 309 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206
  1. define(function(require, exports, module) {
  2. /*!
  3. CSSLint
  4. Copyright (c) 2011 Nicole Sullivan and Nicholas C. Zakas. All rights reserved.
  5. Permission is hereby granted, free of charge, to any person obtaining a copy
  6. of this software and associated documentation files (the "Software"), to deal
  7. in the Software without restriction, including without limitation the rights
  8. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. copies of the Software, and to permit persons to whom the Software is
  10. furnished to do so, subject to the following conditions:
  11. The above copyright notice and this permission notice shall be included in
  12. all copies or substantial portions of the Software.
  13. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. THE SOFTWARE.
  20. */
  21. /* Build time: 17-January-2013 10:55:01 */
  22. /*!
  23. Parser-Lib
  24. Copyright (c) 2009-2011 Nicholas C. Zakas. All rights reserved.
  25. Permission is hereby granted, free of charge, to any person obtaining a copy
  26. of this software and associated documentation files (the "Software"), to deal
  27. in the Software without restriction, including without limitation the rights
  28. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  29. copies of the Software, and to permit persons to whom the Software is
  30. furnished to do so, subject to the following conditions:
  31. The above copyright notice and this permission notice shall be included in
  32. all copies or substantial portions of the Software.
  33. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  34. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  35. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  36. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  37. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  38. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  39. THE SOFTWARE.
  40. */
  41. /* Version v0.2.2, Build time: 17-January-2013 10:26:34 */
  42. var parserlib = {};
  43. (function(){
  44. /**
  45. * A generic base to inherit from for any object
  46. * that needs event handling.
  47. * @class EventTarget
  48. * @constructor
  49. */
  50. function EventTarget(){
  51. /**
  52. * The array of listeners for various events.
  53. * @type Object
  54. * @property _listeners
  55. * @private
  56. */
  57. this._listeners = {};
  58. }
  59. EventTarget.prototype = {
  60. //restore constructor
  61. constructor: EventTarget,
  62. /**
  63. * Adds a listener for a given event type.
  64. * @param {String} type The type of event to add a listener for.
  65. * @param {Function} listener The function to call when the event occurs.
  66. * @return {void}
  67. * @method addListener
  68. */
  69. addListener: function(type, listener){
  70. if (!this._listeners[type]){
  71. this._listeners[type] = [];
  72. }
  73. this._listeners[type].push(listener);
  74. },
  75. /**
  76. * Fires an event based on the passed-in object.
  77. * @param {Object|String} event An object with at least a 'type' attribute
  78. * or a string indicating the event name.
  79. * @return {void}
  80. * @method fire
  81. */
  82. fire: function(event){
  83. if (typeof event == "string"){
  84. event = { type: event };
  85. }
  86. if (typeof event.target != "undefined"){
  87. event.target = this;
  88. }
  89. if (typeof event.type == "undefined"){
  90. throw new Error("Event object missing 'type' property.");
  91. }
  92. if (this._listeners[event.type]){
  93. //create a copy of the array and use that so listeners can't chane
  94. var listeners = this._listeners[event.type].concat();
  95. for (var i=0, len=listeners.length; i < len; i++){
  96. listeners[i].call(this, event);
  97. }
  98. }
  99. },
  100. /**
  101. * Removes a listener for a given event type.
  102. * @param {String} type The type of event to remove a listener from.
  103. * @param {Function} listener The function to remove from the event.
  104. * @return {void}
  105. * @method removeListener
  106. */
  107. removeListener: function(type, listener){
  108. if (this._listeners[type]){
  109. var listeners = this._listeners[type];
  110. for (var i=0, len=listeners.length; i < len; i++){
  111. if (listeners[i] === listener){
  112. listeners.splice(i, 1);
  113. break;
  114. }
  115. }
  116. }
  117. }
  118. };
  119. /**
  120. * Convenient way to read through strings.
  121. * @namespace parserlib.util
  122. * @class StringReader
  123. * @constructor
  124. * @param {String} text The text to read.
  125. */
  126. function StringReader(text){
  127. /**
  128. * The input text with line endings normalized.
  129. * @property _input
  130. * @type String
  131. * @private
  132. */
  133. this._input = text.replace(/\n\r?/g, "\n");
  134. /**
  135. * The row for the character to be read next.
  136. * @property _line
  137. * @type int
  138. * @private
  139. */
  140. this._line = 1;
  141. /**
  142. * The column for the character to be read next.
  143. * @property _col
  144. * @type int
  145. * @private
  146. */
  147. this._col = 1;
  148. /**
  149. * The index of the character in the input to be read next.
  150. * @property _cursor
  151. * @type int
  152. * @private
  153. */
  154. this._cursor = 0;
  155. }
  156. StringReader.prototype = {
  157. //restore constructor
  158. constructor: StringReader,
  159. //-------------------------------------------------------------------------
  160. // Position info
  161. //-------------------------------------------------------------------------
  162. /**
  163. * Returns the column of the character to be read next.
  164. * @return {int} The column of the character to be read next.
  165. * @method getCol
  166. */
  167. getCol: function(){
  168. return this._col;
  169. },
  170. /**
  171. * Returns the row of the character to be read next.
  172. * @return {int} The row of the character to be read next.
  173. * @method getLine
  174. */
  175. getLine: function(){
  176. return this._line ;
  177. },
  178. /**
  179. * Determines if you're at the end of the input.
  180. * @return {Boolean} True if there's no more input, false otherwise.
  181. * @method eof
  182. */
  183. eof: function(){
  184. return (this._cursor == this._input.length);
  185. },
  186. //-------------------------------------------------------------------------
  187. // Basic reading
  188. //-------------------------------------------------------------------------
  189. /**
  190. * Reads the next character without advancing the cursor.
  191. * @param {int} count How many characters to look ahead (default is 1).
  192. * @return {String} The next character or null if there is no next character.
  193. * @method peek
  194. */
  195. peek: function(count){
  196. var c = null;
  197. count = (typeof count == "undefined" ? 1 : count);
  198. //if we're not at the end of the input...
  199. if (this._cursor < this._input.length){
  200. //get character and increment cursor and column
  201. c = this._input.charAt(this._cursor + count - 1);
  202. }
  203. return c;
  204. },
  205. /**
  206. * Reads the next character from the input and adjusts the row and column
  207. * accordingly.
  208. * @return {String} The next character or null if there is no next character.
  209. * @method read
  210. */
  211. read: function(){
  212. var c = null;
  213. //if we're not at the end of the input...
  214. if (this._cursor < this._input.length){
  215. //if the last character was a newline, increment row count
  216. //and reset column count
  217. if (this._input.charAt(this._cursor) == "\n"){
  218. this._line++;
  219. this._col=1;
  220. } else {
  221. this._col++;
  222. }
  223. //get character and increment cursor and column
  224. c = this._input.charAt(this._cursor++);
  225. }
  226. return c;
  227. },
  228. //-------------------------------------------------------------------------
  229. // Misc
  230. //-------------------------------------------------------------------------
  231. /**
  232. * Saves the current location so it can be returned to later.
  233. * @method mark
  234. * @return {void}
  235. */
  236. mark: function(){
  237. this._bookmark = {
  238. cursor: this._cursor,
  239. line: this._line,
  240. col: this._col
  241. };
  242. },
  243. reset: function(){
  244. if (this._bookmark){
  245. this._cursor = this._bookmark.cursor;
  246. this._line = this._bookmark.line;
  247. this._col = this._bookmark.col;
  248. delete this._bookmark;
  249. }
  250. },
  251. //-------------------------------------------------------------------------
  252. // Advanced reading
  253. //-------------------------------------------------------------------------
  254. /**
  255. * Reads up to and including the given string. Throws an error if that
  256. * string is not found.
  257. * @param {String} pattern The string to read.
  258. * @return {String} The string when it is found.
  259. * @throws Error when the string pattern is not found.
  260. * @method readTo
  261. */
  262. readTo: function(pattern){
  263. var buffer = "",
  264. c;
  265. /*
  266. * First, buffer must be the same length as the pattern.
  267. * Then, buffer must end with the pattern or else reach the
  268. * end of the input.
  269. */
  270. while (buffer.length < pattern.length || buffer.lastIndexOf(pattern) != buffer.length - pattern.length){
  271. c = this.read();
  272. if (c){
  273. buffer += c;
  274. } else {
  275. throw new Error("Expected \"" + pattern + "\" at line " + this._line + ", col " + this._col + ".");
  276. }
  277. }
  278. return buffer;
  279. },
  280. /**
  281. * Reads characters while each character causes the given
  282. * filter function to return true. The function is passed
  283. * in each character and either returns true to continue
  284. * reading or false to stop.
  285. * @param {Function} filter The function to read on each character.
  286. * @return {String} The string made up of all characters that passed the
  287. * filter check.
  288. * @method readWhile
  289. */
  290. readWhile: function(filter){
  291. var buffer = "",
  292. c = this.read();
  293. while(c !== null && filter(c)){
  294. buffer += c;
  295. c = this.read();
  296. }
  297. return buffer;
  298. },
  299. /**
  300. * Reads characters that match either text or a regular expression and
  301. * returns those characters. If a match is found, the row and column
  302. * are adjusted; if no match is found, the reader's state is unchanged.
  303. * reading or false to stop.
  304. * @param {String|RegExp} matchter If a string, then the literal string
  305. * value is searched for. If a regular expression, then any string
  306. * matching the pattern is search for.
  307. * @return {String} The string made up of all characters that matched or
  308. * null if there was no match.
  309. * @method readMatch
  310. */
  311. readMatch: function(matcher){
  312. var source = this._input.substring(this._cursor),
  313. value = null;
  314. //if it's a string, just do a straight match
  315. if (typeof matcher == "string"){
  316. if (source.indexOf(matcher) === 0){
  317. value = this.readCount(matcher.length);
  318. }
  319. } else if (matcher instanceof RegExp){
  320. if (matcher.test(source)){
  321. value = this.readCount(RegExp.lastMatch.length);
  322. }
  323. }
  324. return value;
  325. },
  326. /**
  327. * Reads a given number of characters. If the end of the input is reached,
  328. * it reads only the remaining characters and does not throw an error.
  329. * @param {int} count The number of characters to read.
  330. * @return {String} The string made up the read characters.
  331. * @method readCount
  332. */
  333. readCount: function(count){
  334. var buffer = "";
  335. while(count--){
  336. buffer += this.read();
  337. }
  338. return buffer;
  339. }
  340. };
  341. /**
  342. * Type to use when a syntax error occurs.
  343. * @class SyntaxError
  344. * @namespace parserlib.util
  345. * @constructor
  346. * @param {String} message The error message.
  347. * @param {int} line The line at which the error occurred.
  348. * @param {int} col The column at which the error occurred.
  349. */
  350. function SyntaxError(message, line, col){
  351. /**
  352. * The column at which the error occurred.
  353. * @type int
  354. * @property col
  355. */
  356. this.col = col;
  357. /**
  358. * The line at which the error occurred.
  359. * @type int
  360. * @property line
  361. */
  362. this.line = line;
  363. /**
  364. * The text representation of the unit.
  365. * @type String
  366. * @property text
  367. */
  368. this.message = message;
  369. }
  370. //inherit from Error
  371. SyntaxError.prototype = new Error();
  372. /**
  373. * Base type to represent a single syntactic unit.
  374. * @class SyntaxUnit
  375. * @namespace parserlib.util
  376. * @constructor
  377. * @param {String} text The text of the unit.
  378. * @param {int} line The line of text on which the unit resides.
  379. * @param {int} col The column of text on which the unit resides.
  380. */
  381. function SyntaxUnit(text, line, col, type){
  382. /**
  383. * The column of text on which the unit resides.
  384. * @type int
  385. * @property col
  386. */
  387. this.col = col;
  388. /**
  389. * The line of text on which the unit resides.
  390. * @type int
  391. * @property line
  392. */
  393. this.line = line;
  394. /**
  395. * The text representation of the unit.
  396. * @type String
  397. * @property text
  398. */
  399. this.text = text;
  400. /**
  401. * The type of syntax unit.
  402. * @type int
  403. * @property type
  404. */
  405. this.type = type;
  406. }
  407. /**
  408. * Create a new syntax unit based solely on the given token.
  409. * Convenience method for creating a new syntax unit when
  410. * it represents a single token instead of multiple.
  411. * @param {Object} token The token object to represent.
  412. * @return {parserlib.util.SyntaxUnit} The object representing the token.
  413. * @static
  414. * @method fromToken
  415. */
  416. SyntaxUnit.fromToken = function(token){
  417. return new SyntaxUnit(token.value, token.startLine, token.startCol);
  418. };
  419. SyntaxUnit.prototype = {
  420. //restore constructor
  421. constructor: SyntaxUnit,
  422. /**
  423. * Returns the text representation of the unit.
  424. * @return {String} The text representation of the unit.
  425. * @method valueOf
  426. */
  427. valueOf: function(){
  428. return this.toString();
  429. },
  430. /**
  431. * Returns the text representation of the unit.
  432. * @return {String} The text representation of the unit.
  433. * @method toString
  434. */
  435. toString: function(){
  436. return this.text;
  437. }
  438. };
  439. /*global StringReader, SyntaxError*/
  440. /**
  441. * Generic TokenStream providing base functionality.
  442. * @class TokenStreamBase
  443. * @namespace parserlib.util
  444. * @constructor
  445. * @param {String|StringReader} input The text to tokenize or a reader from
  446. * which to read the input.
  447. */
  448. function TokenStreamBase(input, tokenData){
  449. /**
  450. * The string reader for easy access to the text.
  451. * @type StringReader
  452. * @property _reader
  453. * @private
  454. */
  455. this._reader = input ? new StringReader(input.toString()) : null;
  456. /**
  457. * Token object for the last consumed token.
  458. * @type Token
  459. * @property _token
  460. * @private
  461. */
  462. this._token = null;
  463. /**
  464. * The array of token information.
  465. * @type Array
  466. * @property _tokenData
  467. * @private
  468. */
  469. this._tokenData = tokenData;
  470. /**
  471. * Lookahead token buffer.
  472. * @type Array
  473. * @property _lt
  474. * @private
  475. */
  476. this._lt = [];
  477. /**
  478. * Lookahead token buffer index.
  479. * @type int
  480. * @property _ltIndex
  481. * @private
  482. */
  483. this._ltIndex = 0;
  484. this._ltIndexCache = [];
  485. }
  486. /**
  487. * Accepts an array of token information and outputs
  488. * an array of token data containing key-value mappings
  489. * and matching functions that the TokenStream needs.
  490. * @param {Array} tokens An array of token descriptors.
  491. * @return {Array} An array of processed token data.
  492. * @method createTokenData
  493. * @static
  494. */
  495. TokenStreamBase.createTokenData = function(tokens){
  496. var nameMap = [],
  497. typeMap = {},
  498. tokenData = tokens.concat([]),
  499. i = 0,
  500. len = tokenData.length+1;
  501. tokenData.UNKNOWN = -1;
  502. tokenData.unshift({name:"EOF"});
  503. for (; i < len; i++){
  504. nameMap.push(tokenData[i].name);
  505. tokenData[tokenData[i].name] = i;
  506. if (tokenData[i].text){
  507. typeMap[tokenData[i].text] = i;
  508. }
  509. }
  510. tokenData.name = function(tt){
  511. return nameMap[tt];
  512. };
  513. tokenData.type = function(c){
  514. return typeMap[c];
  515. };
  516. return tokenData;
  517. };
  518. TokenStreamBase.prototype = {
  519. //restore constructor
  520. constructor: TokenStreamBase,
  521. //-------------------------------------------------------------------------
  522. // Matching methods
  523. //-------------------------------------------------------------------------
  524. /**
  525. * Determines if the next token matches the given token type.
  526. * If so, that token is consumed; if not, the token is placed
  527. * back onto the token stream. You can pass in any number of
  528. * token types and this will return true if any of the token
  529. * types is found.
  530. * @param {int|int[]} tokenTypes Either a single token type or an array of
  531. * token types that the next token might be. If an array is passed,
  532. * it's assumed that the token can be any of these.
  533. * @param {variant} channel (Optional) The channel to read from. If not
  534. * provided, reads from the default (unnamed) channel.
  535. * @return {Boolean} True if the token type matches, false if not.
  536. * @method match
  537. */
  538. match: function(tokenTypes, channel){
  539. //always convert to an array, makes things easier
  540. if (!(tokenTypes instanceof Array)){
  541. tokenTypes = [tokenTypes];
  542. }
  543. var tt = this.get(channel),
  544. i = 0,
  545. len = tokenTypes.length;
  546. while(i < len){
  547. if (tt == tokenTypes[i++]){
  548. return true;
  549. }
  550. }
  551. //no match found, put the token back
  552. this.unget();
  553. return false;
  554. },
  555. /**
  556. * Determines if the next token matches the given token type.
  557. * If so, that token is consumed; if not, an error is thrown.
  558. * @param {int|int[]} tokenTypes Either a single token type or an array of
  559. * token types that the next token should be. If an array is passed,
  560. * it's assumed that the token must be one of these.
  561. * @param {variant} channel (Optional) The channel to read from. If not
  562. * provided, reads from the default (unnamed) channel.
  563. * @return {void}
  564. * @method mustMatch
  565. */
  566. mustMatch: function(tokenTypes, channel){
  567. var token;
  568. //always convert to an array, makes things easier
  569. if (!(tokenTypes instanceof Array)){
  570. tokenTypes = [tokenTypes];
  571. }
  572. if (!this.match.apply(this, arguments)){
  573. token = this.LT(1);
  574. throw new SyntaxError("Expected " + this._tokenData[tokenTypes[0]].name +
  575. " at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol);
  576. }
  577. },
  578. //-------------------------------------------------------------------------
  579. // Consuming methods
  580. //-------------------------------------------------------------------------
  581. /**
  582. * Keeps reading from the token stream until either one of the specified
  583. * token types is found or until the end of the input is reached.
  584. * @param {int|int[]} tokenTypes Either a single token type or an array of
  585. * token types that the next token should be. If an array is passed,
  586. * it's assumed that the token must be one of these.
  587. * @param {variant} channel (Optional) The channel to read from. If not
  588. * provided, reads from the default (unnamed) channel.
  589. * @return {void}
  590. * @method advance
  591. */
  592. advance: function(tokenTypes, channel){
  593. while(this.LA(0) !== 0 && !this.match(tokenTypes, channel)){
  594. this.get();
  595. }
  596. return this.LA(0);
  597. },
  598. /**
  599. * Consumes the next token from the token stream.
  600. * @return {int} The token type of the token that was just consumed.
  601. * @method get
  602. */
  603. get: function(channel){
  604. var tokenInfo = this._tokenData,
  605. reader = this._reader,
  606. value,
  607. i =0,
  608. len = tokenInfo.length,
  609. found = false,
  610. token,
  611. info;
  612. //check the lookahead buffer first
  613. if (this._lt.length && this._ltIndex >= 0 && this._ltIndex < this._lt.length){
  614. i++;
  615. this._token = this._lt[this._ltIndex++];
  616. info = tokenInfo[this._token.type];
  617. //obey channels logic
  618. while((info.channel !== undefined && channel !== info.channel) &&
  619. this._ltIndex < this._lt.length){
  620. this._token = this._lt[this._ltIndex++];
  621. info = tokenInfo[this._token.type];
  622. i++;
  623. }
  624. //here be dragons
  625. if ((info.channel === undefined || channel === info.channel) &&
  626. this._ltIndex <= this._lt.length){
  627. this._ltIndexCache.push(i);
  628. return this._token.type;
  629. }
  630. }
  631. //call token retriever method
  632. token = this._getToken();
  633. //if it should be hidden, don't save a token
  634. if (token.type > -1 && !tokenInfo[token.type].hide){
  635. //apply token channel
  636. token.channel = tokenInfo[token.type].channel;
  637. //save for later
  638. this._token = token;
  639. this._lt.push(token);
  640. //save space that will be moved (must be done before array is truncated)
  641. this._ltIndexCache.push(this._lt.length - this._ltIndex + i);
  642. //keep the buffer under 5 items
  643. if (this._lt.length > 5){
  644. this._lt.shift();
  645. }
  646. //also keep the shift buffer under 5 items
  647. if (this._ltIndexCache.length > 5){
  648. this._ltIndexCache.shift();
  649. }
  650. //update lookahead index
  651. this._ltIndex = this._lt.length;
  652. }
  653. /*
  654. * Skip to the next token if:
  655. * 1. The token type is marked as hidden.
  656. * 2. The token type has a channel specified and it isn't the current channel.
  657. */
  658. info = tokenInfo[token.type];
  659. if (info &&
  660. (info.hide ||
  661. (info.channel !== undefined && channel !== info.channel))){
  662. return this.get(channel);
  663. } else {
  664. //return just the type
  665. return token.type;
  666. }
  667. },
  668. /**
  669. * Looks ahead a certain number of tokens and returns the token type at
  670. * that position. This will throw an error if you lookahead past the
  671. * end of input, past the size of the lookahead buffer, or back past
  672. * the first token in the lookahead buffer.
  673. * @param {int} The index of the token type to retrieve. 0 for the
  674. * current token, 1 for the next, -1 for the previous, etc.
  675. * @return {int} The token type of the token in the given position.
  676. * @method LA
  677. */
  678. LA: function(index){
  679. var total = index,
  680. tt;
  681. if (index > 0){
  682. //TODO: Store 5 somewhere
  683. if (index > 5){
  684. throw new Error("Too much lookahead.");
  685. }
  686. //get all those tokens
  687. while(total){
  688. tt = this.get();
  689. total--;
  690. }
  691. //unget all those tokens
  692. while(total < index){
  693. this.unget();
  694. total++;
  695. }
  696. } else if (index < 0){
  697. if(this._lt[this._ltIndex+index]){
  698. tt = this._lt[this._ltIndex+index].type;
  699. } else {
  700. throw new Error("Too much lookbehind.");
  701. }
  702. } else {
  703. tt = this._token.type;
  704. }
  705. return tt;
  706. },
  707. /**
  708. * Looks ahead a certain number of tokens and returns the token at
  709. * that position. This will throw an error if you lookahead past the
  710. * end of input, past the size of the lookahead buffer, or back past
  711. * the first token in the lookahead buffer.
  712. * @param {int} The index of the token type to retrieve. 0 for the
  713. * current token, 1 for the next, -1 for the previous, etc.
  714. * @return {Object} The token of the token in the given position.
  715. * @method LA
  716. */
  717. LT: function(index){
  718. //lookahead first to prime the token buffer
  719. this.LA(index);
  720. //now find the token, subtract one because _ltIndex is already at the next index
  721. return this._lt[this._ltIndex+index-1];
  722. },
  723. /**
  724. * Returns the token type for the next token in the stream without
  725. * consuming it.
  726. * @return {int} The token type of the next token in the stream.
  727. * @method peek
  728. */
  729. peek: function(){
  730. return this.LA(1);
  731. },
  732. /**
  733. * Returns the actual token object for the last consumed token.
  734. * @return {Token} The token object for the last consumed token.
  735. * @method token
  736. */
  737. token: function(){
  738. return this._token;
  739. },
  740. /**
  741. * Returns the name of the token for the given token type.
  742. * @param {int} tokenType The type of token to get the name of.
  743. * @return {String} The name of the token or "UNKNOWN_TOKEN" for any
  744. * invalid token type.
  745. * @method tokenName
  746. */
  747. tokenName: function(tokenType){
  748. if (tokenType < 0 || tokenType > this._tokenData.length){
  749. return "UNKNOWN_TOKEN";
  750. } else {
  751. return this._tokenData[tokenType].name;
  752. }
  753. },
  754. /**
  755. * Returns the token type value for the given token name.
  756. * @param {String} tokenName The name of the token whose value should be returned.
  757. * @return {int} The token type value for the given token name or -1
  758. * for an unknown token.
  759. * @method tokenName
  760. */
  761. tokenType: function(tokenName){
  762. return this._tokenData[tokenName] || -1;
  763. },
  764. /**
  765. * Returns the last consumed token to the token stream.
  766. * @method unget
  767. */
  768. unget: function(){
  769. //if (this._ltIndex > -1){
  770. if (this._ltIndexCache.length){
  771. this._ltIndex -= this._ltIndexCache.pop();//--;
  772. this._token = this._lt[this._ltIndex - 1];
  773. } else {
  774. throw new Error("Too much lookahead.");
  775. }
  776. }
  777. };
  778. parserlib.util = {
  779. StringReader: StringReader,
  780. SyntaxError : SyntaxError,
  781. SyntaxUnit : SyntaxUnit,
  782. EventTarget : EventTarget,
  783. TokenStreamBase : TokenStreamBase
  784. };
  785. })();
  786. /*
  787. Parser-Lib
  788. Copyright (c) 2009-2011 Nicholas C. Zakas. All rights reserved.
  789. Permission is hereby granted, free of charge, to any person obtaining a copy
  790. of this software and associated documentation files (the "Software"), to deal
  791. in the Software without restriction, including without limitation the rights
  792. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  793. copies of the Software, and to permit persons to whom the Software is
  794. furnished to do so, subject to the following conditions:
  795. The above copyright notice and this permission notice shall be included in
  796. all copies or substantial portions of the Software.
  797. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  798. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  799. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  800. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  801. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  802. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  803. THE SOFTWARE.
  804. */
  805. /* Version v0.2.2, Build time: 17-January-2013 10:26:34 */
  806. (function(){
  807. var EventTarget = parserlib.util.EventTarget,
  808. TokenStreamBase = parserlib.util.TokenStreamBase,
  809. StringReader = parserlib.util.StringReader,
  810. SyntaxError = parserlib.util.SyntaxError,
  811. SyntaxUnit = parserlib.util.SyntaxUnit;
  812. var Colors = {
  813. aliceblue :"#f0f8ff",
  814. antiquewhite :"#faebd7",
  815. aqua :"#00ffff",
  816. aquamarine :"#7fffd4",
  817. azure :"#f0ffff",
  818. beige :"#f5f5dc",
  819. bisque :"#ffe4c4",
  820. black :"#000000",
  821. blanchedalmond :"#ffebcd",
  822. blue :"#0000ff",
  823. blueviolet :"#8a2be2",
  824. brown :"#a52a2a",
  825. burlywood :"#deb887",
  826. cadetblue :"#5f9ea0",
  827. chartreuse :"#7fff00",
  828. chocolate :"#d2691e",
  829. coral :"#ff7f50",
  830. cornflowerblue :"#6495ed",
  831. cornsilk :"#fff8dc",
  832. crimson :"#dc143c",
  833. cyan :"#00ffff",
  834. darkblue :"#00008b",
  835. darkcyan :"#008b8b",
  836. darkgoldenrod :"#b8860b",
  837. darkgray :"#a9a9a9",
  838. darkgreen :"#006400",
  839. darkkhaki :"#bdb76b",
  840. darkmagenta :"#8b008b",
  841. darkolivegreen :"#556b2f",
  842. darkorange :"#ff8c00",
  843. darkorchid :"#9932cc",
  844. darkred :"#8b0000",
  845. darksalmon :"#e9967a",
  846. darkseagreen :"#8fbc8f",
  847. darkslateblue :"#483d8b",
  848. darkslategray :"#2f4f4f",
  849. darkturquoise :"#00ced1",
  850. darkviolet :"#9400d3",
  851. deeppink :"#ff1493",
  852. deepskyblue :"#00bfff",
  853. dimgray :"#696969",
  854. dodgerblue :"#1e90ff",
  855. firebrick :"#b22222",
  856. floralwhite :"#fffaf0",
  857. forestgreen :"#228b22",
  858. fuchsia :"#ff00ff",
  859. gainsboro :"#dcdcdc",
  860. ghostwhite :"#f8f8ff",
  861. gold :"#ffd700",
  862. goldenrod :"#daa520",
  863. gray :"#808080",
  864. green :"#008000",
  865. greenyellow :"#adff2f",
  866. honeydew :"#f0fff0",
  867. hotpink :"#ff69b4",
  868. indianred :"#cd5c5c",
  869. indigo :"#4b0082",
  870. ivory :"#fffff0",
  871. khaki :"#f0e68c",
  872. lavender :"#e6e6fa",
  873. lavenderblush :"#fff0f5",
  874. lawngreen :"#7cfc00",
  875. lemonchiffon :"#fffacd",
  876. lightblue :"#add8e6",
  877. lightcoral :"#f08080",
  878. lightcyan :"#e0ffff",
  879. lightgoldenrodyellow :"#fafad2",
  880. lightgray :"#d3d3d3",
  881. lightgreen :"#90ee90",
  882. lightpink :"#ffb6c1",
  883. lightsalmon :"#ffa07a",
  884. lightseagreen :"#20b2aa",
  885. lightskyblue :"#87cefa",
  886. lightslategray :"#778899",
  887. lightsteelblue :"#b0c4de",
  888. lightyellow :"#ffffe0",
  889. lime :"#00ff00",
  890. limegreen :"#32cd32",
  891. linen :"#faf0e6",
  892. magenta :"#ff00ff",
  893. maroon :"#800000",
  894. mediumaquamarine:"#66cdaa",
  895. mediumblue :"#0000cd",
  896. mediumorchid :"#ba55d3",
  897. mediumpurple :"#9370d8",
  898. mediumseagreen :"#3cb371",
  899. mediumslateblue :"#7b68ee",
  900. mediumspringgreen :"#00fa9a",
  901. mediumturquoise :"#48d1cc",
  902. mediumvioletred :"#c71585",
  903. midnightblue :"#191970",
  904. mintcream :"#f5fffa",
  905. mistyrose :"#ffe4e1",
  906. moccasin :"#ffe4b5",
  907. navajowhite :"#ffdead",
  908. navy :"#000080",
  909. oldlace :"#fdf5e6",
  910. olive :"#808000",
  911. olivedrab :"#6b8e23",
  912. orange :"#ffa500",
  913. orangered :"#ff4500",
  914. orchid :"#da70d6",
  915. palegoldenrod :"#eee8aa",
  916. palegreen :"#98fb98",
  917. paleturquoise :"#afeeee",
  918. palevioletred :"#d87093",
  919. papayawhip :"#ffefd5",
  920. peachpuff :"#ffdab9",
  921. peru :"#cd853f",
  922. pink :"#ffc0cb",
  923. plum :"#dda0dd",
  924. powderblue :"#b0e0e6",
  925. purple :"#800080",
  926. red :"#ff0000",
  927. rosybrown :"#bc8f8f",
  928. royalblue :"#4169e1",
  929. saddlebrown :"#8b4513",
  930. salmon :"#fa8072",
  931. sandybrown :"#f4a460",
  932. seagreen :"#2e8b57",
  933. seashell :"#fff5ee",
  934. sienna :"#a0522d",
  935. silver :"#c0c0c0",
  936. skyblue :"#87ceeb",
  937. slateblue :"#6a5acd",
  938. slategray :"#708090",
  939. snow :"#fffafa",
  940. springgreen :"#00ff7f",
  941. steelblue :"#4682b4",
  942. tan :"#d2b48c",
  943. teal :"#008080",
  944. thistle :"#d8bfd8",
  945. tomato :"#ff6347",
  946. turquoise :"#40e0d0",
  947. violet :"#ee82ee",
  948. wheat :"#f5deb3",
  949. white :"#ffffff",
  950. whitesmoke :"#f5f5f5",
  951. yellow :"#ffff00",
  952. yellowgreen :"#9acd32",
  953. //CSS2 system colors http://www.w3.org/TR/css3-color/#css2-system
  954. activeBorder :"Active window border.",
  955. activecaption :"Active window caption.",
  956. appworkspace :"Background color of multiple document interface.",
  957. background :"Desktop background.",
  958. buttonface :"The face background color for 3-D elements that appear 3-D due to one layer of surrounding border.",
  959. buttonhighlight :"The color of the border facing the light source for 3-D elements that appear 3-D due to one layer of surrounding border.",
  960. buttonshadow :"The color of the border away from the light source for 3-D elements that appear 3-D due to one layer of surrounding border.",
  961. buttontext :"Text on push buttons.",
  962. captiontext :"Text in caption, size box, and scrollbar arrow box.",
  963. graytext :"Grayed (disabled) text. This color is set to #000 if the current display driver does not support a solid gray color.",
  964. highlight :"Item(s) selected in a control.",
  965. highlighttext :"Text of item(s) selected in a control.",
  966. inactiveborder :"Inactive window border.",
  967. inactivecaption :"Inactive window caption.",
  968. inactivecaptiontext :"Color of text in an inactive caption.",
  969. infobackground :"Background color for tooltip controls.",
  970. infotext :"Text color for tooltip controls.",
  971. menu :"Menu background.",
  972. menutext :"Text in menus.",
  973. scrollbar :"Scroll bar gray area.",
  974. threeddarkshadow :"The color of the darker (generally outer) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
  975. threedface :"The face background color for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
  976. threedhighlight :"The color of the lighter (generally outer) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
  977. threedlightshadow :"The color of the darker (generally inner) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
  978. threedshadow :"The color of the lighter (generally inner) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
  979. window :"Window background.",
  980. windowframe :"Window frame.",
  981. windowtext :"Text in windows."
  982. };
  983. /*global SyntaxUnit, Parser*/
  984. /**
  985. * Represents a selector combinator (whitespace, +, >).
  986. * @namespace parserlib.css
  987. * @class Combinator
  988. * @extends parserlib.util.SyntaxUnit
  989. * @constructor
  990. * @param {String} text The text representation of the unit.
  991. * @param {int} line The line of text on which the unit resides.
  992. * @param {int} col The column of text on which the unit resides.
  993. */
  994. function Combinator(text, line, col){
  995. SyntaxUnit.call(this, text, line, col, Parser.COMBINATOR_TYPE);
  996. /**
  997. * The type of modifier.
  998. * @type String
  999. * @property type
  1000. */
  1001. this.type = "unknown";
  1002. //pretty simple
  1003. if (/^\s+$/.test(text)){
  1004. this.type = "descendant";
  1005. } else if (text == ">"){
  1006. this.type = "child";
  1007. } else if (text == "+"){
  1008. this.type = "adjacent-sibling";
  1009. } else if (text == "~"){
  1010. this.type = "sibling";
  1011. }
  1012. }
  1013. Combinator.prototype = new SyntaxUnit();
  1014. Combinator.prototype.constructor = Combinator;
  1015. /*global SyntaxUnit, Parser*/
  1016. /**
  1017. * Represents a media feature, such as max-width:500.
  1018. * @namespace parserlib.css
  1019. * @class MediaFeature
  1020. * @extends parserlib.util.SyntaxUnit
  1021. * @constructor
  1022. * @param {SyntaxUnit} name The name of the feature.
  1023. * @param {SyntaxUnit} value The value of the feature or null if none.
  1024. */
  1025. function MediaFeature(name, value){
  1026. SyntaxUnit.call(this, "(" + name + (value !== null ? ":" + value : "") + ")", name.startLine, name.startCol, Parser.MEDIA_FEATURE_TYPE);
  1027. /**
  1028. * The name of the media feature
  1029. * @type String
  1030. * @property name
  1031. */
  1032. this.name = name;
  1033. /**
  1034. * The value for the feature or null if there is none.
  1035. * @type SyntaxUnit
  1036. * @property value
  1037. */
  1038. this.value = value;
  1039. }
  1040. MediaFeature.prototype = new SyntaxUnit();
  1041. MediaFeature.prototype.constructor = MediaFeature;
  1042. /*global SyntaxUnit, Parser*/
  1043. /**
  1044. * Represents an individual media query.
  1045. * @namespace parserlib.css
  1046. * @class MediaQuery
  1047. * @extends parserlib.util.SyntaxUnit
  1048. * @constructor
  1049. * @param {String} modifier The modifier "not" or "only" (or null).
  1050. * @param {String} mediaType The type of media (i.e., "print").
  1051. * @param {Array} parts Array of selectors parts making up this selector.
  1052. * @param {int} line The line of text on which the unit resides.
  1053. * @param {int} col The column of text on which the unit resides.
  1054. */
  1055. function MediaQuery(modifier, mediaType, features, line, col){
  1056. SyntaxUnit.call(this, (modifier ? modifier + " ": "") + (mediaType ? mediaType : "") + (mediaType && features.length > 0 ? " and " : "") + features.join(" and "), line, col, Parser.MEDIA_QUERY_TYPE);
  1057. /**
  1058. * The media modifier ("not" or "only")
  1059. * @type String
  1060. * @property modifier
  1061. */
  1062. this.modifier = modifier;
  1063. /**
  1064. * The mediaType (i.e., "print")
  1065. * @type String
  1066. * @property mediaType
  1067. */
  1068. this.mediaType = mediaType;
  1069. /**
  1070. * The parts that make up the selector.
  1071. * @type Array
  1072. * @property features
  1073. */
  1074. this.features = features;
  1075. }
  1076. MediaQuery.prototype = new SyntaxUnit();
  1077. MediaQuery.prototype.constructor = MediaQuery;
  1078. /*global Tokens, TokenStream, SyntaxError, Properties, Validation, ValidationError, SyntaxUnit,
  1079. PropertyValue, PropertyValuePart, SelectorPart, SelectorSubPart, Selector,
  1080. PropertyName, Combinator, MediaFeature, MediaQuery, EventTarget */
  1081. /**
  1082. * A CSS3 parser.
  1083. * @namespace parserlib.css
  1084. * @class Parser
  1085. * @constructor
  1086. * @param {Object} options (Optional) Various options for the parser:
  1087. * starHack (true|false) to allow IE6 star hack as valid,
  1088. * underscoreHack (true|false) to interpret leading underscores
  1089. * as IE6-7 targeting for known properties, ieFilters (true|false)
  1090. * to indicate that IE < 8 filters should be accepted and not throw
  1091. * syntax errors.
  1092. */
  1093. function Parser(options){
  1094. //inherit event functionality
  1095. EventTarget.call(this);
  1096. this.options = options || {};
  1097. this._tokenStream = null;
  1098. }
  1099. //Static constants
  1100. Parser.DEFAULT_TYPE = 0;
  1101. Parser.COMBINATOR_TYPE = 1;
  1102. Parser.MEDIA_FEATURE_TYPE = 2;
  1103. Parser.MEDIA_QUERY_TYPE = 3;
  1104. Parser.PROPERTY_NAME_TYPE = 4;
  1105. Parser.PROPERTY_VALUE_TYPE = 5;
  1106. Parser.PROPERTY_VALUE_PART_TYPE = 6;
  1107. Parser.SELECTOR_TYPE = 7;
  1108. Parser.SELECTOR_PART_TYPE = 8;
  1109. Parser.SELECTOR_SUB_PART_TYPE = 9;
  1110. Parser.prototype = function(){
  1111. var proto = new EventTarget(), //new prototype
  1112. prop,
  1113. additions = {
  1114. //restore constructor
  1115. constructor: Parser,
  1116. //instance constants - yuck
  1117. DEFAULT_TYPE : 0,
  1118. COMBINATOR_TYPE : 1,
  1119. MEDIA_FEATURE_TYPE : 2,
  1120. MEDIA_QUERY_TYPE : 3,
  1121. PROPERTY_NAME_TYPE : 4,
  1122. PROPERTY_VALUE_TYPE : 5,
  1123. PROPERTY_VALUE_PART_TYPE : 6,
  1124. SELECTOR_TYPE : 7,
  1125. SELECTOR_PART_TYPE : 8,
  1126. SELECTOR_SUB_PART_TYPE : 9,
  1127. //-----------------------------------------------------------------
  1128. // Grammar
  1129. //-----------------------------------------------------------------
  1130. _stylesheet: function(){
  1131. /*
  1132. * stylesheet
  1133. * : [ CHARSET_SYM S* STRING S* ';' ]?
  1134. * [S|CDO|CDC]* [ import [S|CDO|CDC]* ]*
  1135. * [ namespace [S|CDO|CDC]* ]*
  1136. * [ [ ruleset | media | page | font_face | keyframes ] [S|CDO|CDC]* ]*
  1137. * ;
  1138. */
  1139. var tokenStream = this._tokenStream,
  1140. charset = null,
  1141. count,
  1142. token,
  1143. tt;
  1144. this.fire("startstylesheet");
  1145. //try to read character set
  1146. this._charset();
  1147. this._skipCruft();
  1148. //try to read imports - may be more than one
  1149. while (tokenStream.peek() == Tokens.IMPORT_SYM){
  1150. this._import();
  1151. this._skipCruft();
  1152. }
  1153. //try to read namespaces - may be more than one
  1154. while (tokenStream.peek() == Tokens.NAMESPACE_SYM){
  1155. this._namespace();
  1156. this._skipCruft();
  1157. }
  1158. //get the next token
  1159. tt = tokenStream.peek();
  1160. //try to read the rest
  1161. while(tt > Tokens.EOF){
  1162. try {
  1163. switch(tt){
  1164. case Tokens.MEDIA_SYM:
  1165. this._media();
  1166. this._skipCruft();
  1167. break;
  1168. case Tokens.PAGE_SYM:
  1169. this._page();
  1170. this._skipCruft();
  1171. break;
  1172. case Tokens.FONT_FACE_SYM:
  1173. this._font_face();
  1174. this._skipCruft();
  1175. break;
  1176. case Tokens.KEYFRAMES_SYM:
  1177. this._keyframes();
  1178. this._skipCruft();
  1179. break;
  1180. case Tokens.UNKNOWN_SYM: //unknown @ rule
  1181. tokenStream.get();
  1182. if (!this.options.strict){
  1183. //fire error event
  1184. this.fire({
  1185. type: "error",
  1186. error: null,
  1187. message: "Unknown @ rule: " + tokenStream.LT(0).value + ".",
  1188. line: tokenStream.LT(0).startLine,
  1189. col: tokenStream.LT(0).startCol
  1190. });
  1191. //skip braces
  1192. count=0;
  1193. while (tokenStream.advance([Tokens.LBRACE, Tokens.RBRACE]) == Tokens.LBRACE){
  1194. count++; //keep track of nesting depth
  1195. }
  1196. while(count){
  1197. tokenStream.advance([Tokens.RBRACE]);
  1198. count--;
  1199. }
  1200. } else {
  1201. //not a syntax error, rethrow it
  1202. throw new SyntaxError("Unknown @ rule.", tokenStream.LT(0).startLine, tokenStream.LT(0).startCol);
  1203. }
  1204. break;
  1205. case Tokens.S:
  1206. this._readWhitespace();
  1207. break;
  1208. default:
  1209. if(!this._ruleset()){
  1210. //error handling for known issues
  1211. switch(tt){
  1212. case Tokens.CHARSET_SYM:
  1213. token = tokenStream.LT(1);
  1214. this._charset(false);
  1215. throw new SyntaxError("@charset not allowed here.", token.startLine, token.startCol);
  1216. case Tokens.IMPORT_SYM:
  1217. token = tokenStream.LT(1);
  1218. this._import(false);
  1219. throw new SyntaxError("@import not allowed here.", token.startLine, token.startCol);
  1220. case Tokens.NAMESPACE_SYM:
  1221. token = tokenStream.LT(1);
  1222. this._namespace(false);
  1223. throw new SyntaxError("@namespace not allowed here.", token.startLine, token.startCol);
  1224. default:
  1225. tokenStream.get(); //get the last token
  1226. this._unexpectedToken(tokenStream.token());
  1227. }
  1228. }
  1229. }
  1230. } catch(ex) {
  1231. if (ex instanceof SyntaxError && !this.options.strict){
  1232. this.fire({
  1233. type: "error",
  1234. error: ex,
  1235. message: ex.message,
  1236. line: ex.line,
  1237. col: ex.col
  1238. });
  1239. } else {
  1240. throw ex;
  1241. }
  1242. }
  1243. tt = tokenStream.peek();
  1244. }
  1245. if (tt != Tokens.EOF){
  1246. this._unexpectedToken(tokenStream.token());
  1247. }
  1248. this.fire("endstylesheet");
  1249. },
  1250. _charset: function(emit){
  1251. var tokenStream = this._tokenStream,
  1252. charset,
  1253. token,
  1254. line,
  1255. col;
  1256. if (tokenStream.match(Tokens.CHARSET_SYM)){
  1257. line = tokenStream.token().startLine;
  1258. col = tokenStream.token().startCol;
  1259. this._readWhitespace();
  1260. tokenStream.mustMatch(Tokens.STRING);
  1261. token = tokenStream.token();
  1262. charset = token.value;
  1263. this._readWhitespace();
  1264. tokenStream.mustMatch(Tokens.SEMICOLON);
  1265. if (emit !== false){
  1266. this.fire({
  1267. type: "charset",
  1268. charset:charset,
  1269. line: line,
  1270. col: col
  1271. });
  1272. }
  1273. }
  1274. },
  1275. _import: function(emit){
  1276. /*
  1277. * import
  1278. * : IMPORT_SYM S*
  1279. * [STRING|URI] S* media_query_list? ';' S*
  1280. */
  1281. var tokenStream = this._tokenStream,
  1282. tt,
  1283. uri,
  1284. importToken,
  1285. mediaList = [];
  1286. //read import symbol
  1287. tokenStream.mustMatch(Tokens.IMPORT_SYM);
  1288. importToken = tokenStream.token();
  1289. this._readWhitespace();
  1290. tokenStream.mustMatch([Tokens.STRING, Tokens.URI]);
  1291. //grab the URI value
  1292. uri = tokenStream.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/, "$1");
  1293. this._readWhitespace();
  1294. mediaList = this._media_query_list();
  1295. //must end with a semicolon
  1296. tokenStream.mustMatch(Tokens.SEMICOLON);
  1297. this._readWhitespace();
  1298. if (emit !== false){
  1299. this.fire({
  1300. type: "import",
  1301. uri: uri,
  1302. media: mediaList,
  1303. line: importToken.startLine,
  1304. col: importToken.startCol
  1305. });
  1306. }
  1307. },
  1308. _namespace: function(emit){
  1309. /*
  1310. * namespace
  1311. * : NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S*
  1312. */
  1313. var tokenStream = this._tokenStream,
  1314. line,
  1315. col,
  1316. prefix,
  1317. uri;
  1318. //read import symbol
  1319. tokenStream.mustMatch(Tokens.NAMESPACE_SYM);
  1320. line = tokenStream.token().startLine;
  1321. col = tokenStream.token().startCol;
  1322. this._readWhitespace();
  1323. //it's a namespace prefix - no _namespace_prefix() method because it's just an IDENT
  1324. if (tokenStream.match(Tokens.IDENT)){
  1325. prefix = tokenStream.token().value;
  1326. this._readWhitespace();
  1327. }
  1328. tokenStream.mustMatch([Tokens.STRING, Tokens.URI]);
  1329. /*if (!tokenStream.match(Tokens.STRING)){
  1330. tokenStream.mustMatch(Tokens.URI);
  1331. }*/
  1332. //grab the URI value
  1333. uri = tokenStream.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/, "$1");
  1334. this._readWhitespace();
  1335. //must end with a semicolon
  1336. tokenStream.mustMatch(Tokens.SEMICOLON);
  1337. this._readWhitespace();
  1338. if (emit !== false){
  1339. this.fire({
  1340. type: "namespace",
  1341. prefix: prefix,
  1342. uri: uri,
  1343. line: line,
  1344. col: col
  1345. });
  1346. }
  1347. },
  1348. _media: function(){
  1349. /*
  1350. * media
  1351. * : MEDIA_SYM S* media_query_list S* '{' S* ruleset* '}' S*
  1352. * ;
  1353. */
  1354. var tokenStream = this._tokenStream,
  1355. line,
  1356. col,
  1357. mediaList;// = [];
  1358. //look for @media
  1359. tokenStream.mustMatch(Tokens.MEDIA_SYM);
  1360. line = tokenStream.token().startLine;
  1361. col = tokenStream.token().startCol;
  1362. this._readWhitespace();
  1363. mediaList = this._media_query_list();
  1364. tokenStream.mustMatch(Tokens.LBRACE);
  1365. this._readWhitespace();
  1366. this.fire({
  1367. type: "startmedia",
  1368. media: mediaList,
  1369. line: line,
  1370. col: col
  1371. });
  1372. while(true) {
  1373. if (tokenStream.peek() == Tokens.PAGE_SYM){
  1374. this._page();
  1375. } else if (!this._ruleset()){
  1376. break;
  1377. }
  1378. }
  1379. tokenStream.mustMatch(Tokens.RBRACE);
  1380. this._readWhitespace();
  1381. this.fire({
  1382. type: "endmedia",
  1383. media: mediaList,
  1384. line: line,
  1385. col: col
  1386. });
  1387. },
  1388. //CSS3 Media Queries
  1389. _media_query_list: function(){
  1390. /*
  1391. * media_query_list
  1392. * : S* [media_query [ ',' S* media_query ]* ]?
  1393. * ;
  1394. */
  1395. var tokenStream = this._tokenStream,
  1396. mediaList = [];
  1397. this._readWhitespace();
  1398. if (tokenStream.peek() == Tokens.IDENT || tokenStream.peek() == Tokens.LPAREN){
  1399. mediaList.push(this._media_query());
  1400. }
  1401. while(tokenStream.match(Tokens.COMMA)){
  1402. this._readWhitespace();
  1403. mediaList.push(this._media_query());
  1404. }
  1405. return mediaList;
  1406. },
  1407. /*
  1408. * Note: "expression" in the grammar maps to the _media_expression
  1409. * method.
  1410. */
  1411. _media_query: function(){
  1412. /*
  1413. * media_query
  1414. * : [ONLY | NOT]? S* media_type S* [ AND S* expression ]*
  1415. * | expression [ AND S* expression ]*
  1416. * ;
  1417. */
  1418. var tokenStream = this._tokenStream,
  1419. type = null,
  1420. ident = null,
  1421. token = null,
  1422. expressions = [];
  1423. if (tokenStream.match(Tokens.IDENT)){
  1424. ident = tokenStream.token().value.toLowerCase();
  1425. //since there's no custom tokens for these, need to manually check
  1426. if (ident != "only" && ident != "not"){
  1427. tokenStream.unget();
  1428. ident = null;
  1429. } else {
  1430. token = tokenStream.token();
  1431. }
  1432. }
  1433. this._readWhitespace();
  1434. if (tokenStream.peek() == Tokens.IDENT){
  1435. type = this._media_type();
  1436. if (token === null){
  1437. token = tokenStream.token();
  1438. }
  1439. } else if (tokenStream.peek() == Tokens.LPAREN){
  1440. if (token === null){
  1441. token = tokenStream.LT(1);
  1442. }
  1443. expressions.push(this._media_expression());
  1444. }
  1445. if (type === null && expressions.length === 0){
  1446. return null;
  1447. } else {
  1448. this._readWhitespace();
  1449. while (tokenStream.match(Tokens.IDENT)){
  1450. if (tokenStream.token().value.toLowerCase() != "and"){
  1451. this._unexpectedToken(tokenStream.token());
  1452. }
  1453. this._readWhitespace();
  1454. expressions.push(this._media_expression());
  1455. }
  1456. }
  1457. return new MediaQuery(ident, type, expressions, token.startLine, token.startCol);
  1458. },
  1459. //CSS3 Media Queries
  1460. _media_type: function(){
  1461. /*
  1462. * media_type
  1463. * : IDENT
  1464. * ;
  1465. */
  1466. return this._media_feature();
  1467. },
  1468. /**
  1469. * Note: in CSS3 Media Queries, this is called "expression".
  1470. * Renamed here to avoid conflict with CSS3 Selectors
  1471. * definition of "expression". Also note that "expr" in the
  1472. * grammar now maps to "expression" from CSS3 selectors.
  1473. * @method _media_expression
  1474. * @private
  1475. */
  1476. _media_expression: function(){
  1477. /*
  1478. * expression
  1479. * : '(' S* media_feature S* [ ':' S* expr ]? ')' S*
  1480. * ;
  1481. */
  1482. var tokenStream = this._tokenStream,
  1483. feature = null,
  1484. token,
  1485. expression = null;
  1486. tokenStream.mustMatch(Tokens.LPAREN);
  1487. feature = this._media_feature();
  1488. this._readWhitespace();
  1489. if (tokenStream.match(Tokens.COLON)){
  1490. this._readWhitespace();
  1491. token = tokenStream.LT(1);
  1492. expression = this._expression();
  1493. }
  1494. tokenStream.mustMatch(Tokens.RPAREN);
  1495. this._readWhitespace();
  1496. return new MediaFeature(feature, (expression ? new SyntaxUnit(expression, token.startLine, token.startCol) : null));
  1497. },
  1498. //CSS3 Media Queries
  1499. _media_feature: function(){
  1500. /*
  1501. * media_feature
  1502. * : IDENT
  1503. * ;
  1504. */
  1505. var tokenStream = this._tokenStream;
  1506. tokenStream.mustMatch(Tokens.IDENT);
  1507. return SyntaxUnit.fromToken(tokenStream.token());
  1508. },
  1509. //CSS3 Paged Media
  1510. _page: function(){
  1511. /*
  1512. * page:
  1513. * PAGE_SYM S* IDENT? pseudo_page? S*
  1514. * '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S*
  1515. * ;
  1516. */
  1517. var tokenStream = this._tokenStream,
  1518. line,
  1519. col,
  1520. identifier = null,
  1521. pseudoPage = null;
  1522. //look for @page
  1523. tokenStream.mustMatch(Tokens.PAGE_SYM);
  1524. line = tokenStream.token().startLine;
  1525. col = tokenStream.token().startCol;
  1526. this._readWhitespace();
  1527. if (tokenStream.match(Tokens.IDENT)){
  1528. identifier = tokenStream.token().value;
  1529. //The value 'auto' may not be used as a page name and MUST be treated as a syntax error.
  1530. if (identifier.toLowerCase() === "auto"){
  1531. this._unexpectedToken(tokenStream.token());
  1532. }
  1533. }
  1534. //see if there's a colon upcoming
  1535. if (tokenStream.peek() == Tokens.COLON){
  1536. pseudoPage = this._pseudo_page();
  1537. }
  1538. this._readWhitespace();
  1539. this.fire({
  1540. type: "startpage",
  1541. id: identifier,
  1542. pseudo: pseudoPage,
  1543. line: line,
  1544. col: col
  1545. });
  1546. this._readDeclarations(true, true);
  1547. this.fire({
  1548. type: "endpage",
  1549. id: identifier,
  1550. pseudo: pseudoPage,
  1551. line: line,
  1552. col: col
  1553. });
  1554. },
  1555. //CSS3 Paged Media
  1556. _margin: function(){
  1557. /*
  1558. * margin :
  1559. * margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S*
  1560. * ;
  1561. */
  1562. var tokenStream = this._tokenStream,
  1563. line,
  1564. col,
  1565. marginSym = this._margin_sym();
  1566. if (marginSym){
  1567. line = tokenStream.token().startLine;
  1568. col = tokenStream.token().startCol;
  1569. this.fire({
  1570. type: "startpagemargin",
  1571. margin: marginSym,
  1572. line: line,
  1573. col: col
  1574. });
  1575. this._readDeclarations(true);
  1576. this.fire({
  1577. type: "endpagemargin",
  1578. margin: marginSym,
  1579. line: line,
  1580. col: col
  1581. });
  1582. return true;
  1583. } else {
  1584. return false;
  1585. }
  1586. },
  1587. //CSS3 Paged Media
  1588. _margin_sym: function(){
  1589. /*
  1590. * margin_sym :
  1591. * TOPLEFTCORNER_SYM |
  1592. * TOPLEFT_SYM |
  1593. * TOPCENTER_SYM |
  1594. * TOPRIGHT_SYM |
  1595. * TOPRIGHTCORNER_SYM |
  1596. * BOTTOMLEFTCORNER_SYM |
  1597. * BOTTOMLEFT_SYM |
  1598. * BOTTOMCENTER_SYM |
  1599. * BOTTOMRIGHT_SYM |
  1600. * BOTTOMRIGHTCORNER_SYM |
  1601. * LEFTTOP_SYM |
  1602. * LEFTMIDDLE_SYM |
  1603. * LEFTBOTTOM_SYM |
  1604. * RIGHTTOP_SYM |
  1605. * RIGHTMIDDLE_SYM |
  1606. * RIGHTBOTTOM_SYM
  1607. * ;
  1608. */
  1609. var tokenStream = this._tokenStream;
  1610. if(tokenStream.match([Tokens.TOPLEFTCORNER_SYM, Tokens.TOPLEFT_SYM,
  1611. Tokens.TOPCENTER_SYM, Tokens.TOPRIGHT_SYM, Tokens.TOPRIGHTCORNER_SYM,
  1612. Tokens.BOTTOMLEFTCORNER_SYM, Tokens.BOTTOMLEFT_SYM,
  1613. Tokens.BOTTOMCENTER_SYM, Tokens.BOTTOMRIGHT_SYM,
  1614. Tokens.BOTTOMRIGHTCORNER_SYM, Tokens.LEFTTOP_SYM,
  1615. Tokens.LEFTMIDDLE_SYM, Tokens.LEFTBOTTOM_SYM, Tokens.RIGHTTOP_SYM,
  1616. Tokens.RIGHTMIDDLE_SYM, Tokens.RIGHTBOTTOM_SYM]))
  1617. {
  1618. return SyntaxUnit.fromToken(tokenStream.token());
  1619. } else {
  1620. return null;
  1621. }
  1622. },
  1623. _pseudo_page: function(){
  1624. /*
  1625. * pseudo_page
  1626. * : ':' IDENT
  1627. * ;
  1628. */
  1629. var tokenStream = this._tokenStream;
  1630. tokenStream.mustMatch(Tokens.COLON);
  1631. tokenStream.mustMatch(Tokens.IDENT);
  1632. //TODO: CSS3 Paged Media says only "left", "center", and "right" are allowed
  1633. return tokenStream.token().value;
  1634. },
  1635. _font_face: function(){
  1636. /*
  1637. * font_face
  1638. * : FONT_FACE_SYM S*
  1639. * '{' S* declaration [ ';' S* declaration ]* '}' S*
  1640. * ;
  1641. */
  1642. var tokenStream = this._tokenStream,
  1643. line,
  1644. col;
  1645. //look for @page
  1646. tokenStream.mustMatch(Tokens.FONT_FACE_SYM);
  1647. line = tokenStream.token().startLine;
  1648. col = tokenStream.token().startCol;
  1649. this._readWhitespace();
  1650. this.fire({
  1651. type: "startfontface",
  1652. line: line,
  1653. col: col
  1654. });
  1655. this._readDeclarations(true);
  1656. this.fire({
  1657. type: "endfontface",
  1658. line: line,
  1659. col: col
  1660. });
  1661. },
  1662. _operator: function(inFunction){
  1663. /*
  1664. * operator (outside function)
  1665. * : '/' S* | ',' S* | /( empty )/
  1666. * operator (inside function)
  1667. * : '/' S* | '+' S* | '*' S* | '-' S* /( empty )/
  1668. * ;
  1669. */
  1670. var tokenStream = this._tokenStream,
  1671. token = null;
  1672. if (tokenStream.match([Tokens.SLASH, Tokens.COMMA]) ||
  1673. (inFunction && tokenStream.match([Tokens.PLUS, Tokens.STAR, Tokens.MINUS]))){
  1674. token = tokenStream.token();
  1675. this._readWhitespace();
  1676. }
  1677. return token ? PropertyValuePart.fromToken(token) : null;
  1678. },
  1679. _combinator: function(){
  1680. /*
  1681. * combinator
  1682. * : PLUS S* | GREATER S* | TILDE S* | S+
  1683. * ;
  1684. */
  1685. var tokenStream = this._tokenStream,
  1686. value = null,
  1687. token;
  1688. if(tokenStream.match([Tokens.PLUS, Tokens.GREATER, Tokens.TILDE])){
  1689. token = tokenStream.token();
  1690. value = new Combinator(token.value, token.startLine, token.startCol);
  1691. this._readWhitespace();
  1692. }
  1693. return value;
  1694. },
  1695. _unary_operator: function(){
  1696. /*
  1697. * unary_operator
  1698. * : '-' | '+'
  1699. * ;
  1700. */
  1701. var tokenStream = this._tokenStream;
  1702. if (tokenStream.match([Tokens.MINUS, Tokens.PLUS])){
  1703. return tokenStream.token().value;
  1704. } else {
  1705. return null;
  1706. }
  1707. },
  1708. _property: function(){
  1709. /*
  1710. * property
  1711. * : IDENT S*
  1712. * ;
  1713. */
  1714. var tokenStream = this._tokenStream,
  1715. value = null,
  1716. hack = null,
  1717. tokenValue,
  1718. token,
  1719. line,
  1720. col;
  1721. //check for star hack - throws error if not allowed
  1722. if (tokenStream.peek() == Tokens.STAR && this.options.starHack){
  1723. tokenStream.get();
  1724. token = tokenStream.token();
  1725. hack = token.value;
  1726. line = token.startLine;
  1727. col = token.startCol;
  1728. }
  1729. if(tokenStream.match(Tokens.IDENT)){
  1730. token = tokenStream.token();
  1731. tokenValue = token.value;
  1732. //check for underscore hack - no error if not allowed because it's valid CSS syntax
  1733. if (tokenValue.charAt(0) == "_" && this.options.underscoreHack){
  1734. hack = "_";
  1735. tokenValue = tokenValue.substring(1);
  1736. }
  1737. value = new PropertyName(tokenValue, hack, (line||token.startLine), (col||token.startCol));
  1738. this._readWhitespace();
  1739. }
  1740. return value;
  1741. },
  1742. //Augmented with CSS3 Selectors
  1743. _ruleset: function(){
  1744. /*
  1745. * ruleset
  1746. * : selectors_group
  1747. * '{' S* declaration? [ ';' S* declaration? ]* '}' S*
  1748. * ;
  1749. */
  1750. var tokenStream = this._tokenStream,
  1751. tt,
  1752. selectors;
  1753. /*
  1754. * Error Recovery: If even a single selector fails to parse,
  1755. * then the entire ruleset should be thrown away.
  1756. */
  1757. try {
  1758. selectors = this._selectors_group();
  1759. } catch (ex){
  1760. if (ex instanceof SyntaxError && !this.options.strict){
  1761. //fire error event
  1762. this.fire({
  1763. type: "error",
  1764. error: ex,
  1765. message: ex.message,
  1766. line: ex.line,
  1767. col: ex.col
  1768. });
  1769. //skip over everything until closing brace
  1770. tt = tokenStream.advance([Tokens.RBRACE]);
  1771. if (tt == Tokens.RBRACE){
  1772. //if there's a right brace, the rule is finished so don't do anything
  1773. } else {
  1774. //otherwise, rethrow the error because it wasn't handled properly
  1775. throw ex;
  1776. }
  1777. } else {
  1778. //not a syntax error, rethrow it
  1779. throw ex;
  1780. }
  1781. //trigger parser to continue
  1782. return true;
  1783. }
  1784. //if it got here, all selectors parsed
  1785. if (selectors){
  1786. this.fire({
  1787. type: "startrule",
  1788. selectors: selectors,
  1789. line: selectors[0].line,
  1790. col: selectors[0].col
  1791. });
  1792. this._readDeclarations(true);
  1793. this.fire({
  1794. type: "endrule",
  1795. selectors: selectors,
  1796. line: selectors[0].line,
  1797. col: selectors[0].col
  1798. });
  1799. }
  1800. return selectors;
  1801. },
  1802. //CSS3 Selectors
  1803. _selectors_group: function(){
  1804. /*
  1805. * selectors_group
  1806. * : selector [ COMMA S* selector ]*
  1807. * ;
  1808. */
  1809. var tokenStream = this._tokenStream,
  1810. selectors = [],
  1811. selector;
  1812. selector = this._selector();
  1813. if (selector !== null){
  1814. selectors.push(selector);
  1815. while(tokenStream.match(Tokens.COMMA)){
  1816. this._readWhitespace();
  1817. selector = this._selector();
  1818. if (selector !== null){
  1819. selectors.push(selector);
  1820. } else {
  1821. this._unexpectedToken(tokenStream.LT(1));
  1822. }
  1823. }
  1824. }
  1825. return selectors.length ? selectors : null;
  1826. },
  1827. //CSS3 Selectors
  1828. _selector: function(){
  1829. /*
  1830. * selector
  1831. * : simple_selector_sequence [ combinator simple_selector_sequence ]*
  1832. * ;
  1833. */
  1834. var tokenStream = this._tokenStream,
  1835. selector = [],
  1836. nextSelector = null,
  1837. combinator = null,
  1838. ws = null;
  1839. //if there's no simple selector, then there's no selector
  1840. nextSelector = this._simple_selector_sequence();
  1841. if (nextSelector === null){
  1842. return null;
  1843. }
  1844. selector.push(nextSelector);
  1845. do {
  1846. //look for a combinator
  1847. combinator = this._combinator();
  1848. if (combinator !== null){
  1849. selector.push(combinator);
  1850. nextSelector = this._simple_selector_sequence();
  1851. //there must be a next selector
  1852. if (nextSelector === null){
  1853. this._unexpectedToken(tokenStream.LT(1));
  1854. } else {
  1855. //nextSelector is an instance of SelectorPart
  1856. selector.push(nextSelector);
  1857. }
  1858. } else {
  1859. //if there's not whitespace, we're done
  1860. if (this._readWhitespace()){
  1861. //add whitespace separator
  1862. ws = new Combinator(tokenStream.token().value, tokenStream.token().startLine, tokenStream.token().startCol);
  1863. //combinator is not required
  1864. combinator = this._combinator();
  1865. //selector is required if there's a combinator
  1866. nextSelector = this._simple_selector_sequence();
  1867. if (nextSelector === null){
  1868. if (combinator !== null){
  1869. this._unexpectedToken(tokenStream.LT(1));
  1870. }
  1871. } else {
  1872. if (combinator !== null){
  1873. selector.push(combinator);
  1874. } else {
  1875. selector.push(ws);
  1876. }
  1877. selector.push(nextSelector);
  1878. }
  1879. } else {
  1880. break;
  1881. }
  1882. }
  1883. } while(true);
  1884. return new Selector(selector, selector[0].line, selector[0].col);
  1885. },
  1886. //CSS3 Selectors
  1887. _simple_selector_sequence: function(){
  1888. /*
  1889. * simple_selector_sequence
  1890. * : [ type_selector | universal ]
  1891. * [ HASH | class | attrib | pseudo | negation ]*
  1892. * | [ HASH | class | attrib | pseudo | negation ]+
  1893. * ;
  1894. */
  1895. var tokenStream = this._tokenStream,
  1896. //parts of a simple selector
  1897. elementName = null,
  1898. modifiers = [],
  1899. //complete selector text
  1900. selectorText= "",
  1901. //the different parts after the element name to search for
  1902. components = [
  1903. //HASH
  1904. function(){
  1905. return tokenStream.match(Tokens.HASH) ?
  1906. new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) :
  1907. null;
  1908. },
  1909. this._class,
  1910. this._attrib,
  1911. this._pseudo,
  1912. this._negation
  1913. ],
  1914. i = 0,
  1915. len = components.length,
  1916. component = null,
  1917. found = false,
  1918. line,
  1919. col;
  1920. //get starting line and column for the selector
  1921. line = tokenStream.LT(1).startLine;
  1922. col = tokenStream.LT(1).startCol;
  1923. elementName = this._type_selector();
  1924. if (!elementName){
  1925. elementName = this._universal();
  1926. }
  1927. if (elementName !== null){
  1928. selectorText += elementName;
  1929. }
  1930. while(true){
  1931. //whitespace means we're done
  1932. if (tokenStream.peek() === Tokens.S){
  1933. break;
  1934. }
  1935. //check for each component
  1936. while(i < len && component === null){
  1937. component = components[i++].call(this);
  1938. }
  1939. if (component === null){
  1940. //we don't have a selector
  1941. if (selectorText === ""){
  1942. return null;
  1943. } else {
  1944. break;
  1945. }
  1946. } else {
  1947. i = 0;
  1948. modifiers.push(component);
  1949. selectorText += component.toString();
  1950. component = null;
  1951. }
  1952. }
  1953. return selectorText !== "" ?
  1954. new SelectorPart(elementName, modifiers, selectorText, line, col) :
  1955. null;
  1956. },
  1957. //CSS3 Selectors
  1958. _type_selector: function(){
  1959. /*
  1960. * type_selector
  1961. * : [ namespace_prefix ]? element_name
  1962. * ;
  1963. */
  1964. var tokenStream = this._tokenStream,
  1965. ns = this._namespace_prefix(),
  1966. elementName = this._element_name();
  1967. if (!elementName){
  1968. /*
  1969. * Need to back out the namespace that was read due to both
  1970. * type_selector and universal reading namespace_prefix
  1971. * first. Kind of hacky, but only way I can figure out
  1972. * right now how to not change the grammar.
  1973. */
  1974. if (ns){
  1975. tokenStream.unget();
  1976. if (ns.length > 1){
  1977. tokenStream.unget();
  1978. }
  1979. }
  1980. return null;
  1981. } else {
  1982. if (ns){
  1983. elementName.text = ns + elementName.text;
  1984. elementName.col -= ns.length;
  1985. }
  1986. return elementName;
  1987. }
  1988. },
  1989. //CSS3 Selectors
  1990. _class: function(){
  1991. /*
  1992. * class
  1993. * : '.' IDENT
  1994. * ;
  1995. */
  1996. var tokenStream = this._tokenStream,
  1997. token;
  1998. if (tokenStream.match(Tokens.DOT)){
  1999. tokenStream.mustMatch(Tokens.IDENT);
  2000. token = tokenStream.token();
  2001. return new SelectorSubPart("." + token.value, "class", token.startLine, token.startCol - 1);
  2002. } else {
  2003. return null;
  2004. }
  2005. },
  2006. //CSS3 Selectors
  2007. _element_name: function(){
  2008. /*
  2009. * element_name
  2010. * : IDENT
  2011. * ;
  2012. */
  2013. var tokenStream = this._tokenStream,
  2014. token;
  2015. if (tokenStream.match(Tokens.IDENT)){
  2016. token = tokenStream.token();
  2017. return new SelectorSubPart(token.value, "elementName", token.startLine, token.startCol);
  2018. } else {
  2019. return null;
  2020. }
  2021. },
  2022. //CSS3 Selectors
  2023. _namespace_prefix: function(){
  2024. /*
  2025. * namespace_prefix
  2026. * : [ IDENT | '*' ]? '|'
  2027. * ;
  2028. */
  2029. var tokenStream = this._tokenStream,
  2030. value = "";
  2031. //verify that this is a namespace prefix
  2032. if (tokenStream.LA(1) === Tokens.PIPE || tokenStream.LA(2) === Tokens.PIPE){
  2033. if(tokenStream.match([Tokens.IDENT, Tokens.STAR])){
  2034. value += tokenStream.token().value;
  2035. }
  2036. tokenStream.mustMatch(Tokens.PIPE);
  2037. value += "|";
  2038. }
  2039. return value.length ? value : null;
  2040. },
  2041. //CSS3 Selectors
  2042. _universal: function(){
  2043. /*
  2044. * universal
  2045. * : [ namespace_prefix ]? '*'
  2046. * ;
  2047. */
  2048. var tokenStream = this._tokenStream,
  2049. value = "",
  2050. ns;
  2051. ns = this._namespace_prefix();
  2052. if(ns){
  2053. value += ns;
  2054. }
  2055. if(tokenStream.match(Tokens.STAR)){
  2056. value += "*";
  2057. }
  2058. return value.length ? value : null;
  2059. },
  2060. //CSS3 Selectors
  2061. _attrib: function(){
  2062. /*
  2063. * attrib
  2064. * : '[' S* [ namespace_prefix ]? IDENT S*
  2065. * [ [ PREFIXMATCH |
  2066. * SUFFIXMATCH |
  2067. * SUBSTRINGMATCH |
  2068. * '=' |
  2069. * INCLUDES |
  2070. * DASHMATCH ] S* [ IDENT | STRING ] S*
  2071. * ]? ']'
  2072. * ;
  2073. */
  2074. var tokenStream = this._tokenStream,
  2075. value = null,
  2076. ns,
  2077. token;
  2078. if (tokenStream.match(Tokens.LBRACKET)){
  2079. token = tokenStream.token();
  2080. value = token.value;
  2081. value += this._readWhitespace();
  2082. ns = this._namespace_prefix();
  2083. if (ns){
  2084. value += ns;
  2085. }
  2086. tokenStream.mustMatch(Tokens.IDENT);
  2087. value += tokenStream.token().value;
  2088. value += this._readWhitespace();
  2089. if(tokenStream.match([Tokens.PREFIXMATCH, Tokens.SUFFIXMATCH, Tokens.SUBSTRINGMATCH,
  2090. Tokens.EQUALS, Tokens.INCLUDES, Tokens.DASHMATCH])){
  2091. value += tokenStream.token().value;
  2092. value += this._readWhitespace();
  2093. tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]);
  2094. value += tokenStream.token().value;
  2095. value += this._readWhitespace();
  2096. }
  2097. tokenStream.mustMatch(Tokens.RBRACKET);
  2098. return new SelectorSubPart(value + "]", "attribute", token.startLine, token.startCol);
  2099. } else {
  2100. return null;
  2101. }
  2102. },
  2103. //CSS3 Selectors
  2104. _pseudo: function(){
  2105. /*
  2106. * pseudo
  2107. * : ':' ':'? [ IDENT | functional_pseudo ]
  2108. * ;
  2109. */
  2110. var tokenStream = this._tokenStream,
  2111. pseudo = null,
  2112. colons = ":",
  2113. line,
  2114. col;
  2115. if (tokenStream.match(Tokens.COLON)){
  2116. if (tokenStream.match(Tokens.COLON)){
  2117. colons += ":";
  2118. }
  2119. if (tokenStream.match(Tokens.IDENT)){
  2120. pseudo = tokenStream.token().value;
  2121. line = tokenStream.token().startLine;
  2122. col = tokenStream.token().startCol - colons.length;
  2123. } else if (tokenStream.peek() == Tokens.FUNCTION){
  2124. line = tokenStream.LT(1).startLine;
  2125. col = tokenStream.LT(1).startCol - colons.length;
  2126. pseudo = this._functional_pseudo();
  2127. }
  2128. if (pseudo){
  2129. pseudo = new SelectorSubPart(colons + pseudo, "pseudo", line, col);
  2130. }
  2131. }
  2132. return pseudo;
  2133. },
  2134. //CSS3 Selectors
  2135. _functional_pseudo: function(){
  2136. /*
  2137. * functional_pseudo
  2138. * : FUNCTION S* expression ')'
  2139. * ;
  2140. */
  2141. var tokenStream = this._tokenStream,
  2142. value = null;
  2143. if(tokenStream.match(Tokens.FUNCTION)){
  2144. value = tokenStream.token().value;
  2145. value += this._readWhitespace();
  2146. value += this._expression();
  2147. tokenStream.mustMatch(Tokens.RPAREN);
  2148. value += ")";
  2149. }
  2150. return value;
  2151. },
  2152. //CSS3 Selectors
  2153. _expression: function(){
  2154. /*
  2155. * expression
  2156. * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
  2157. * ;
  2158. */
  2159. var tokenStream = this._tokenStream,
  2160. value = "";
  2161. while(tokenStream.match([Tokens.PLUS, Tokens.MINUS, Tokens.DIMENSION,
  2162. Tokens.NUMBER, Tokens.STRING, Tokens.IDENT, Tokens.LENGTH,
  2163. Tokens.FREQ, Tokens.ANGLE, Tokens.TIME,
  2164. Tokens.RESOLUTION, Tokens.SLASH])){
  2165. value += tokenStream.token().value;
  2166. value += this._readWhitespace();
  2167. }
  2168. return value.length ? value : null;
  2169. },
  2170. //CSS3 Selectors
  2171. _negation: function(){
  2172. /*
  2173. * negation
  2174. * : NOT S* negation_arg S* ')'
  2175. * ;
  2176. */
  2177. var tokenStream = this._tokenStream,
  2178. line,
  2179. col,
  2180. value = "",
  2181. arg,
  2182. subpart = null;
  2183. if (tokenStream.match(Tokens.NOT)){
  2184. value = tokenStream.token().value;
  2185. line = tokenStream.token().startLine;
  2186. col = tokenStream.token().startCol;
  2187. value += this._readWhitespace();
  2188. arg = this._negation_arg();
  2189. value += arg;
  2190. value += this._readWhitespace();
  2191. tokenStream.match(Tokens.RPAREN);
  2192. value += tokenStream.token().value;
  2193. subpart = new SelectorSubPart(value, "not", line, col);
  2194. subpart.args.push(arg);
  2195. }
  2196. return subpart;
  2197. },
  2198. //CSS3 Selectors
  2199. _negation_arg: function(){
  2200. /*
  2201. * negation_arg
  2202. * : type_selector | universal | HASH | class | attrib | pseudo
  2203. * ;
  2204. */
  2205. var tokenStream = this._tokenStream,
  2206. args = [
  2207. this._type_selector,
  2208. this._universal,
  2209. function(){
  2210. return tokenStream.match(Tokens.HASH) ?
  2211. new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) :
  2212. null;
  2213. },
  2214. this._class,
  2215. this._attrib,
  2216. this._pseudo
  2217. ],
  2218. arg = null,
  2219. i = 0,
  2220. len = args.length,
  2221. elementName,
  2222. line,
  2223. col,
  2224. part;
  2225. line = tokenStream.LT(1).startLine;
  2226. col = tokenStream.LT(1).startCol;
  2227. while(i < len && arg === null){
  2228. arg = args[i].call(this);
  2229. i++;
  2230. }
  2231. //must be a negation arg
  2232. if (arg === null){
  2233. this._unexpectedToken(tokenStream.LT(1));
  2234. }
  2235. //it's an element name
  2236. if (arg.type == "elementName"){
  2237. part = new SelectorPart(arg, [], arg.toString(), line, col);
  2238. } else {
  2239. part = new SelectorPart(null, [arg], arg.toString(), line, col);
  2240. }
  2241. return part;
  2242. },
  2243. _declaration: function(){
  2244. /*
  2245. * declaration
  2246. * : property ':' S* expr prio?
  2247. * | /( empty )/
  2248. * ;
  2249. */
  2250. var tokenStream = this._tokenStream,
  2251. property = null,
  2252. expr = null,
  2253. prio = null,
  2254. error = null,
  2255. invalid = null,
  2256. propertyName= "";
  2257. property = this._property();
  2258. if (property !== null){
  2259. tokenStream.mustMatch(Tokens.COLON);
  2260. this._readWhitespace();
  2261. expr = this._expr();
  2262. //if there's no parts for the value, it's an error
  2263. if (!expr || expr.length === 0){
  2264. this._unexpectedToken(tokenStream.LT(1));
  2265. }
  2266. prio = this._prio();
  2267. /*
  2268. * If hacks should be allowed, then only check the root
  2269. * property. If hacks should not be allowed, treat
  2270. * _property or *property as invalid properties.
  2271. */
  2272. propertyName = property.toString();
  2273. if (this.options.starHack && property.hack == "*" ||
  2274. this.options.underscoreHack && property.hack == "_") {
  2275. propertyName = property.text;
  2276. }
  2277. try {
  2278. this._validateProperty(propertyName, expr);
  2279. } catch (ex) {
  2280. invalid = ex;
  2281. }
  2282. this.fire({
  2283. type: "property",
  2284. property: property,
  2285. value: expr,
  2286. important: prio,
  2287. line: property.line,
  2288. col: property.col,
  2289. invalid: invalid
  2290. });
  2291. return true;
  2292. } else {
  2293. return false;
  2294. }
  2295. },
  2296. _prio: function(){
  2297. /*
  2298. * prio
  2299. * : IMPORTANT_SYM S*
  2300. * ;
  2301. */
  2302. var tokenStream = this._tokenStream,
  2303. result = tokenStream.match(Tokens.IMPORTANT_SYM);
  2304. this._readWhitespace();
  2305. return result;
  2306. },
  2307. _expr: function(inFunction){
  2308. /*
  2309. * expr
  2310. * : term [ operator term ]*
  2311. * ;
  2312. */
  2313. var tokenStream = this._tokenStream,
  2314. values = [],
  2315. //valueParts = [],
  2316. value = null,
  2317. operator = null;
  2318. value = this._term();
  2319. if (value !== null){
  2320. values.push(value);
  2321. do {
  2322. operator = this._operator(inFunction);
  2323. //if there's an operator, keep building up the value parts
  2324. if (operator){
  2325. values.push(operator);
  2326. } /*else {
  2327. //if there's not an operator, you have a full value
  2328. values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col));
  2329. valueParts = [];
  2330. }*/
  2331. value = this._term();
  2332. if (value === null){
  2333. break;
  2334. } else {
  2335. values.push(value);
  2336. }
  2337. } while(true);
  2338. }
  2339. //cleanup
  2340. /*if (valueParts.length){
  2341. values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col));
  2342. }*/
  2343. return values.length > 0 ? new PropertyValue(values, values[0].line, values[0].col) : null;
  2344. },
  2345. _term: function(){
  2346. /*
  2347. * term
  2348. * : unary_operator?
  2349. * [ NUMBER S* | PERCENTAGE S* | LENGTH S* | ANGLE S* |
  2350. * TIME S* | FREQ S* | function | ie_function ]
  2351. * | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor
  2352. * ;
  2353. */
  2354. var tokenStream = this._tokenStream,
  2355. unary = null,
  2356. value = null,
  2357. token,
  2358. line,
  2359. col;
  2360. //returns the operator or null
  2361. unary = this._unary_operator();
  2362. if (unary !== null){
  2363. line = tokenStream.token().startLine;
  2364. col = tokenStream.token().startCol;
  2365. }
  2366. //exception for IE filters
  2367. if (tokenStream.peek() == Tokens.IE_FUNCTION && this.options.ieFilters){
  2368. value = this._ie_function();
  2369. if (unary === null){
  2370. line = tokenStream.token().startLine;
  2371. col = tokenStream.token().startCol;
  2372. }
  2373. //see if there's a simple match
  2374. } else if (tokenStream.match([Tokens.NUMBER, Tokens.PERCENTAGE, Tokens.LENGTH,
  2375. Tokens.ANGLE, Tokens.TIME,
  2376. Tokens.FREQ, Tokens.STRING, Tokens.IDENT, Tokens.URI, Tokens.UNICODE_RANGE])){
  2377. value = tokenStream.token().value;
  2378. if (unary === null){
  2379. line = tokenStream.token().startLine;
  2380. col = tokenStream.token().startCol;
  2381. }
  2382. this._readWhitespace();
  2383. } else {
  2384. //see if it's a color
  2385. token = this._hexcolor();
  2386. if (token === null){
  2387. //if there's no unary, get the start of the next token for line/col info
  2388. if (unary === null){
  2389. line = tokenStream.LT(1).startLine;
  2390. col = tokenStream.LT(1).startCol;
  2391. }
  2392. //has to be a function
  2393. if (value === null){
  2394. /*
  2395. * This checks for alpha(opacity=0) style of IE
  2396. * functions. IE_FUNCTION only presents progid: style.
  2397. */
  2398. if (tokenStream.LA(3) == Tokens.EQUALS && this.options.ieFilters){
  2399. value = this._ie_function();
  2400. } else {
  2401. value = this._function();
  2402. }
  2403. }
  2404. /*if (value === null){
  2405. return null;
  2406. //throw new Error("Expected identifier at line " + tokenStream.token().startLine + ", character " + tokenStream.token().startCol + ".");
  2407. }*/
  2408. } else {
  2409. value = token.value;
  2410. if (unary === null){
  2411. line = token.startLine;
  2412. col = token.startCol;
  2413. }
  2414. }
  2415. }
  2416. return value !== null ?
  2417. new PropertyValuePart(unary !== null ? unary + value : value, line, col) :
  2418. null;
  2419. },
  2420. _function: function(){
  2421. /*
  2422. * function
  2423. * : FUNCTION S* expr ')' S*
  2424. * ;
  2425. */
  2426. var tokenStream = this._tokenStream,
  2427. functionText = null,
  2428. expr = null,
  2429. lt;
  2430. if (tokenStream.match(Tokens.FUNCTION)){
  2431. functionText = tokenStream.token().value;
  2432. this._readWhitespace();
  2433. expr = this._expr(true);
  2434. functionText += expr;
  2435. //START: Horrible hack in case it's an IE filter
  2436. if (this.options.ieFilters && tokenStream.peek() == Tokens.EQUALS){
  2437. do {
  2438. if (this._readWhitespace()){
  2439. functionText += tokenStream.token().value;
  2440. }
  2441. //might be second time in the loop
  2442. if (tokenStream.LA(0) == Tokens.COMMA){
  2443. functionText += tokenStream.token().value;
  2444. }
  2445. tokenStream.match(Tokens.IDENT);
  2446. functionText += tokenStream.token().value;
  2447. tokenStream.match(Tokens.EQUALS);
  2448. functionText += tokenStream.token().value;
  2449. //functionText += this._term();
  2450. lt = tokenStream.peek();
  2451. while(lt != Tokens.COMMA && lt != Tokens.S && lt != Tokens.RPAREN){
  2452. tokenStream.get();
  2453. functionText += tokenStream.token().value;
  2454. lt = tokenStream.peek();
  2455. }
  2456. } while(tokenStream.match([Tokens.COMMA, Tokens.S]));
  2457. }
  2458. //END: Horrible Hack
  2459. tokenStream.match(Tokens.RPAREN);
  2460. functionText += ")";
  2461. this._readWhitespace();
  2462. }
  2463. return functionText;
  2464. },
  2465. _ie_function: function(){
  2466. /* (My own extension)
  2467. * ie_function
  2468. * : IE_FUNCTION S* IDENT '=' term [S* ','? IDENT '=' term]+ ')' S*
  2469. * ;
  2470. */
  2471. var tokenStream = this._tokenStream,
  2472. functionText = null,
  2473. expr = null,
  2474. lt;
  2475. //IE function can begin like a regular function, too
  2476. if (tokenStream.match([Tokens.IE_FUNCTION, Tokens.FUNCTION])){
  2477. functionText = tokenStream.token().value;
  2478. do {
  2479. if (this._readWhitespace()){
  2480. functionText += tokenStream.token().value;
  2481. }
  2482. //might be second time in the loop
  2483. if (tokenStream.LA(0) == Tokens.COMMA){
  2484. functionText += tokenStream.token().value;
  2485. }
  2486. tokenStream.match(Tokens.IDENT);
  2487. functionText += tokenStream.token().value;
  2488. tokenStream.match(Tokens.EQUALS);
  2489. functionText += tokenStream.token().value;
  2490. //functionText += this._term();
  2491. lt = tokenStream.peek();
  2492. while(lt != Tokens.COMMA && lt != Tokens.S && lt != Tokens.RPAREN){
  2493. tokenStream.get();
  2494. functionText += tokenStream.token().value;
  2495. lt = tokenStream.peek();
  2496. }
  2497. } while(tokenStream.match([Tokens.COMMA, Tokens.S]));
  2498. tokenStream.match(Tokens.RPAREN);
  2499. functionText += ")";
  2500. this._readWhitespace();
  2501. }
  2502. return functionText;
  2503. },
  2504. _hexcolor: function(){
  2505. /*
  2506. * There is a constraint on the color that it must
  2507. * have either 3 or 6 hex-digits (i.e., [0-9a-fA-F])
  2508. * after the "#"; e.g., "#000" is OK, but "#abcd" is not.
  2509. *
  2510. * hexcolor
  2511. * : HASH S*
  2512. * ;
  2513. */
  2514. var tokenStream = this._tokenStream,
  2515. token = null,
  2516. color;
  2517. if(tokenStream.match(Tokens.HASH)){
  2518. //need to do some validation here
  2519. token = tokenStream.token();
  2520. color = token.value;
  2521. if (!/#[a-f0-9]{3,6}/i.test(color)){
  2522. throw new SyntaxError("Expected a hex color but found '" + color + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol);
  2523. }
  2524. this._readWhitespace();
  2525. }
  2526. return token;
  2527. },
  2528. //-----------------------------------------------------------------
  2529. // Animations methods
  2530. //-----------------------------------------------------------------
  2531. _keyframes: function(){
  2532. /*
  2533. * keyframes:
  2534. * : KEYFRAMES_SYM S* keyframe_name S* '{' S* keyframe_rule* '}' {
  2535. * ;
  2536. */
  2537. var tokenStream = this._tokenStream,
  2538. token,
  2539. tt,
  2540. name,
  2541. prefix = "";
  2542. tokenStream.mustMatch(Tokens.KEYFRAMES_SYM);
  2543. token = tokenStream.token();
  2544. if (/^@\-([^\-]+)\-/.test(token.value)) {
  2545. prefix = RegExp.$1;
  2546. }
  2547. this._readWhitespace();
  2548. name = this._keyframe_name();
  2549. this._readWhitespace();
  2550. tokenStream.mustMatch(Tokens.LBRACE);
  2551. this.fire({
  2552. type: "startkeyframes",
  2553. name: name,
  2554. prefix: prefix,
  2555. line: token.startLine,
  2556. col: token.startCol
  2557. });
  2558. this._readWhitespace();
  2559. tt = tokenStream.peek();
  2560. //check for key
  2561. while(tt == Tokens.IDENT || tt == Tokens.PERCENTAGE) {
  2562. this._keyframe_rule();
  2563. this._readWhitespace();
  2564. tt = tokenStream.peek();
  2565. }
  2566. this.fire({
  2567. type: "endkeyframes",
  2568. name: name,
  2569. prefix: prefix,
  2570. line: token.startLine,
  2571. col: token.startCol
  2572. });
  2573. this._readWhitespace();
  2574. tokenStream.mustMatch(Tokens.RBRACE);
  2575. },
  2576. _keyframe_name: function(){
  2577. /*
  2578. * keyframe_name:
  2579. * : IDENT
  2580. * | STRING
  2581. * ;
  2582. */
  2583. var tokenStream = this._tokenStream,
  2584. token;
  2585. tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]);
  2586. return SyntaxUnit.fromToken(tokenStream.token());
  2587. },
  2588. _keyframe_rule: function(){
  2589. /*
  2590. * keyframe_rule:
  2591. * : key_list S*
  2592. * '{' S* declaration [ ';' S* declaration ]* '}' S*
  2593. * ;
  2594. */
  2595. var tokenStream = this._tokenStream,
  2596. token,
  2597. keyList = this._key_list();
  2598. this.fire({
  2599. type: "startkeyframerule",
  2600. keys: keyList,
  2601. line: keyList[0].line,
  2602. col: keyList[0].col
  2603. });
  2604. this._readDeclarations(true);
  2605. this.fire({
  2606. type: "endkeyframerule",
  2607. keys: keyList,
  2608. line: keyList[0].line,
  2609. col: keyList[0].col
  2610. });
  2611. },
  2612. _key_list: function(){
  2613. /*
  2614. * key_list:
  2615. * : key [ S* ',' S* key]*
  2616. * ;
  2617. */
  2618. var tokenStream = this._tokenStream,
  2619. token,
  2620. key,
  2621. keyList = [];
  2622. //must be least one key
  2623. keyList.push(this._key());
  2624. this._readWhitespace();
  2625. while(tokenStream.match(Tokens.COMMA)){
  2626. this._readWhitespace();
  2627. keyList.push(this._key());
  2628. this._readWhitespace();
  2629. }
  2630. return keyList;
  2631. },
  2632. _key: function(){
  2633. /*
  2634. * There is a restriction that IDENT can be only "from" or "to".
  2635. *
  2636. * key
  2637. * : PERCENTAGE
  2638. * | IDENT
  2639. * ;
  2640. */
  2641. var tokenStream = this._tokenStream,
  2642. token;
  2643. if (tokenStream.match(Tokens.PERCENTAGE)){
  2644. return SyntaxUnit.fromToken(tokenStream.token());
  2645. } else if (tokenStream.match(Tokens.IDENT)){
  2646. token = tokenStream.token();
  2647. if (/from|to/i.test(token.value)){
  2648. return SyntaxUnit.fromToken(token);
  2649. }
  2650. tokenStream.unget();
  2651. }
  2652. //if it gets here, there wasn't a valid token, so time to explode
  2653. this._unexpectedToken(tokenStream.LT(1));
  2654. },
  2655. //-----------------------------------------------------------------
  2656. // Helper methods
  2657. //-----------------------------------------------------------------
  2658. /**
  2659. * Not part of CSS grammar, but useful for skipping over
  2660. * combination of white space and HTML-style comments.
  2661. * @return {void}
  2662. * @method _skipCruft
  2663. * @private
  2664. */
  2665. _skipCruft: function(){
  2666. while(this._tokenStream.match([Tokens.S, Tokens.CDO, Tokens.CDC])){
  2667. //noop
  2668. }
  2669. },
  2670. /**
  2671. * Not part of CSS grammar, but this pattern occurs frequently
  2672. * in the official CSS grammar. Split out here to eliminate
  2673. * duplicate code.
  2674. * @param {Boolean} checkStart Indicates if the rule should check
  2675. * for the left brace at the beginning.
  2676. * @param {Boolean} readMargins Indicates if the rule should check
  2677. * for margin patterns.
  2678. * @return {void}
  2679. * @method _readDeclarations
  2680. * @private
  2681. */
  2682. _readDeclarations: function(checkStart, readMargins){
  2683. /*
  2684. * Reads the pattern
  2685. * S* '{' S* declaration [ ';' S* declaration ]* '}' S*
  2686. * or
  2687. * S* '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S*
  2688. * Note that this is how it is described in CSS3 Paged Media, but is actually incorrect.
  2689. * A semicolon is only necessary following a delcaration is there's another declaration
  2690. * or margin afterwards.
  2691. */
  2692. var tokenStream = this._tokenStream,
  2693. tt;
  2694. this._readWhitespace();
  2695. if (checkStart){
  2696. tokenStream.mustMatch(Tokens.LBRACE);
  2697. }
  2698. this._readWhitespace();
  2699. try {
  2700. while(true){
  2701. if (tokenStream.match(Tokens.SEMICOLON) || (readMargins && this._margin())){
  2702. //noop
  2703. } else if (this._declaration()){
  2704. if (!tokenStream.match(Tokens.SEMICOLON)){
  2705. break;
  2706. }
  2707. } else {
  2708. break;
  2709. }
  2710. //if ((!this._margin() && !this._declaration()) || !tokenStream.match(Tokens.SEMICOLON)){
  2711. // break;
  2712. //}
  2713. this._readWhitespace();
  2714. }
  2715. tokenStream.mustMatch(Tokens.RBRACE);
  2716. this._readWhitespace();
  2717. } catch (ex) {
  2718. if (ex instanceof SyntaxError && !this.options.strict){
  2719. //fire error event
  2720. this.fire({
  2721. type: "error",
  2722. error: ex,
  2723. message: ex.message,
  2724. line: ex.line,
  2725. col: ex.col
  2726. });
  2727. //see if there's another declaration
  2728. tt = tokenStream.advance([Tokens.SEMICOLON, Tokens.RBRACE]);
  2729. if (tt == Tokens.SEMICOLON){
  2730. //if there's a semicolon, then there might be another declaration
  2731. this._readDeclarations(false, readMargins);
  2732. } else if (tt != Tokens.RBRACE){
  2733. //if there's a right brace, the rule is finished so don't do anything
  2734. //otherwise, rethrow the error because it wasn't handled properly
  2735. throw ex;
  2736. }
  2737. } else {
  2738. //not a syntax error, rethrow it
  2739. throw ex;
  2740. }
  2741. }
  2742. },
  2743. /**
  2744. * In some cases, you can end up with two white space tokens in a
  2745. * row. Instead of making a change in every function that looks for
  2746. * white space, this function is used to match as much white space
  2747. * as necessary.
  2748. * @method _readWhitespace
  2749. * @return {String} The white space if found, empty string if not.
  2750. * @private
  2751. */
  2752. _readWhitespace: function(){
  2753. var tokenStream = this._tokenStream,
  2754. ws = "";
  2755. while(tokenStream.match(Tokens.S)){
  2756. ws += tokenStream.token().value;
  2757. }
  2758. return ws;
  2759. },
  2760. /**
  2761. * Throws an error when an unexpected token is found.
  2762. * @param {Object} token The token that was found.
  2763. * @method _unexpectedToken
  2764. * @return {void}
  2765. * @private
  2766. */
  2767. _unexpectedToken: function(token){
  2768. throw new SyntaxError("Unexpected token '" + token.value + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol);
  2769. },
  2770. /**
  2771. * Helper method used for parsing subparts of a style sheet.
  2772. * @return {void}
  2773. * @method _verifyEnd
  2774. * @private
  2775. */
  2776. _verifyEnd: function(){
  2777. if (this._tokenStream.LA(1) != Tokens.EOF){
  2778. this._unexpectedToken(this._tokenStream.LT(1));
  2779. }
  2780. },
  2781. //-----------------------------------------------------------------
  2782. // Validation methods
  2783. //-----------------------------------------------------------------
  2784. _validateProperty: function(property, value){
  2785. Validation.validate(property, value);
  2786. },
  2787. //-----------------------------------------------------------------
  2788. // Parsing methods
  2789. //-----------------------------------------------------------------
  2790. parse: function(input){
  2791. this._tokenStream = new TokenStream(input, Tokens);
  2792. this._stylesheet();
  2793. },
  2794. parseStyleSheet: function(input){
  2795. //just passthrough
  2796. return this.parse(input);
  2797. },
  2798. parseMediaQuery: function(input){
  2799. this._tokenStream = new TokenStream(input, Tokens);
  2800. var result = this._media_query();
  2801. //if there's anything more, then it's an invalid selector
  2802. this._verifyEnd();
  2803. //otherwise return result
  2804. return result;
  2805. },
  2806. /**
  2807. * Parses a property value (everything after the semicolon).
  2808. * @return {parserlib.css.PropertyValue} The property value.
  2809. * @throws parserlib.util.SyntaxError If an unexpected token is found.
  2810. * @method parserPropertyValue
  2811. */
  2812. parsePropertyValue: function(input){
  2813. this._tokenStream = new TokenStream(input, Tokens);
  2814. this._readWhitespace();
  2815. var result = this._expr();
  2816. //okay to have a trailing white space
  2817. this._readWhitespace();
  2818. //if there's anything more, then it's an invalid selector
  2819. this._verifyEnd();
  2820. //otherwise return result
  2821. return result;
  2822. },
  2823. /**
  2824. * Parses a complete CSS rule, including selectors and
  2825. * properties.
  2826. * @param {String} input The text to parser.
  2827. * @return {Boolean} True if the parse completed successfully, false if not.
  2828. * @method parseRule
  2829. */
  2830. parseRule: function(input){
  2831. this._tokenStream = new TokenStream(input, Tokens);
  2832. //skip any leading white space
  2833. this._readWhitespace();
  2834. var result = this._ruleset();
  2835. //skip any trailing white space
  2836. this._readWhitespace();
  2837. //if there's anything more, then it's an invalid selector
  2838. this._verifyEnd();
  2839. //otherwise return result
  2840. return result;
  2841. },
  2842. /**
  2843. * Parses a single CSS selector (no comma)
  2844. * @param {String} input The text to parse as a CSS selector.
  2845. * @return {Selector} An object representing the selector.
  2846. * @throws parserlib.util.SyntaxError If an unexpected token is found.
  2847. * @method parseSelector
  2848. */
  2849. parseSelector: function(input){
  2850. this._tokenStream = new TokenStream(input, Tokens);
  2851. //skip any leading white space
  2852. this._readWhitespace();
  2853. var result = this._selector();
  2854. //skip any trailing white space
  2855. this._readWhitespace();
  2856. //if there's anything more, then it's an invalid selector
  2857. this._verifyEnd();
  2858. //otherwise return result
  2859. return result;
  2860. },
  2861. /**
  2862. * Parses an HTML style attribute: a set of CSS declarations
  2863. * separated by semicolons.
  2864. * @param {String} input The text to parse as a style attribute
  2865. * @return {void}
  2866. * @method parseStyleAttribute
  2867. */
  2868. parseStyleAttribute: function(input){
  2869. input += "}"; // for error recovery in _readDeclarations()
  2870. this._tokenStream = new TokenStream(input, Tokens);
  2871. this._readDeclarations();
  2872. }
  2873. };
  2874. //copy over onto prototype
  2875. for (prop in additions){
  2876. if (additions.hasOwnProperty(prop)){
  2877. proto[prop] = additions[prop];
  2878. }
  2879. }
  2880. return proto;
  2881. }();
  2882. /*
  2883. nth
  2884. : S* [ ['-'|'+']? INTEGER? {N} [ S* ['-'|'+'] S* INTEGER ]? |
  2885. ['-'|'+']? INTEGER | {O}{D}{D} | {E}{V}{E}{N} ] S*
  2886. ;
  2887. */
  2888. /*global Validation, ValidationTypes, ValidationError*/
  2889. var Properties = {
  2890. //A
  2891. "alignment-adjust" : "auto | baseline | before-edge | text-before-edge | middle | central | after-edge | text-after-edge | ideographic | alphabetic | hanging | mathematical | <percentage> | <length>",
  2892. "alignment-baseline" : "baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical",
  2893. "animation" : 1,
  2894. "animation-delay" : { multi: "<time>", comma: true },
  2895. "animation-direction" : { multi: "normal | alternate", comma: true },
  2896. "animation-duration" : { multi: "<time>", comma: true },
  2897. "animation-iteration-count" : { multi: "<number> | infinite", comma: true },
  2898. "animation-name" : { multi: "none | <ident>", comma: true },
  2899. "animation-play-state" : { multi: "running | paused", comma: true },
  2900. "animation-timing-function" : 1,
  2901. //vendor prefixed
  2902. "-moz-animation-delay" : { multi: "<time>", comma: true },
  2903. "-moz-animation-direction" : { multi: "normal | alternate", comma: true },
  2904. "-moz-animation-duration" : { multi: "<time>", comma: true },
  2905. "-moz-animation-iteration-count" : { multi: "<number> | infinite", comma: true },
  2906. "-moz-animation-name" : { multi: "none | <ident>", comma: true },
  2907. "-moz-animation-play-state" : { multi: "running | paused", comma: true },
  2908. "-ms-animation-delay" : { multi: "<time>", comma: true },
  2909. "-ms-animation-direction" : { multi: "normal | alternate", comma: true },
  2910. "-ms-animation-duration" : { multi: "<time>", comma: true },
  2911. "-ms-animation-iteration-count" : { multi: "<number> | infinite", comma: true },
  2912. "-ms-animation-name" : { multi: "none | <ident>", comma: true },
  2913. "-ms-animation-play-state" : { multi: "running | paused", comma: true },
  2914. "-webkit-animation-delay" : { multi: "<time>", comma: true },
  2915. "-webkit-animation-direction" : { multi: "normal | alternate", comma: true },
  2916. "-webkit-animation-duration" : { multi: "<time>", comma: true },
  2917. "-webkit-animation-iteration-count" : { multi: "<number> | infinite", comma: true },
  2918. "-webkit-animation-name" : { multi: "none | <ident>", comma: true },
  2919. "-webkit-animation-play-state" : { multi: "running | paused", comma: true },
  2920. "-o-animation-delay" : { multi: "<time>", comma: true },
  2921. "-o-animation-direction" : { multi: "normal | alternate", comma: true },
  2922. "-o-animation-duration" : { multi: "<time>", comma: true },
  2923. "-o-animation-iteration-count" : { multi: "<number> | infinite", comma: true },
  2924. "-o-animation-name" : { multi: "none | <ident>", comma: true },
  2925. "-o-animation-play-state" : { multi: "running | paused", comma: true },
  2926. "appearance" : "icon | window | desktop | workspace | document | tooltip | dialog | button | push-button | hyperlink | 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 | none | inherit",
  2927. "azimuth" : function (expression) {
  2928. var simple = "<angle> | leftwards | rightwards | inherit",
  2929. direction = "left-side | far-left | left | center-left | center | center-right | right | far-right | right-side",
  2930. behind = false,
  2931. valid = false,
  2932. part;
  2933. if (!ValidationTypes.isAny(expression, simple)) {
  2934. if (ValidationTypes.isAny(expression, "behind")) {
  2935. behind = true;
  2936. valid = true;
  2937. }
  2938. if (ValidationTypes.isAny(expression, direction)) {
  2939. valid = true;
  2940. if (!behind) {
  2941. ValidationTypes.isAny(expression, "behind");
  2942. }
  2943. }
  2944. }
  2945. if (expression.hasNext()) {
  2946. part = expression.next();
  2947. if (valid) {
  2948. throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
  2949. } else {
  2950. throw new ValidationError("Expected (<'azimuth'>) but found '" + part + "'.", part.line, part.col);
  2951. }
  2952. }
  2953. },
  2954. //B
  2955. "backface-visibility" : "visible | hidden",
  2956. "background" : 1,
  2957. "background-attachment" : { multi: "<attachment>", comma: true },
  2958. "background-clip" : { multi: "<box>", comma: true },
  2959. "background-color" : "<color> | inherit",
  2960. "background-image" : { multi: "<bg-image>", comma: true },
  2961. "background-origin" : { multi: "<box>", comma: true },
  2962. "background-position" : { multi: "<bg-position>", comma: true },
  2963. "background-repeat" : { multi: "<repeat-style>" },
  2964. "background-size" : { multi: "<bg-size>", comma: true },
  2965. "baseline-shift" : "baseline | sub | super | <percentage> | <length>",
  2966. "behavior" : 1,
  2967. "binding" : 1,
  2968. "bleed" : "<length>",
  2969. "bookmark-label" : "<content> | <attr> | <string>",
  2970. "bookmark-level" : "none | <integer>",
  2971. "bookmark-state" : "open | closed",
  2972. "bookmark-target" : "none | <uri> | <attr>",
  2973. "border" : "<border-width> || <border-style> || <color>",
  2974. "border-bottom" : "<border-width> || <border-style> || <color>",
  2975. "border-bottom-color" : "<color>",
  2976. "border-bottom-left-radius" : "<x-one-radius>",
  2977. "border-bottom-right-radius" : "<x-one-radius>",
  2978. "border-bottom-style" : "<border-style>",
  2979. "border-bottom-width" : "<border-width>",
  2980. "border-collapse" : "collapse | separate | inherit",
  2981. "border-color" : { multi: "<color> | inherit", max: 4 },
  2982. "border-image" : 1,
  2983. "border-image-outset" : { multi: "<length> | <number>", max: 4 },
  2984. "border-image-repeat" : { multi: "stretch | repeat | round", max: 2 },
  2985. "border-image-slice" : function(expression) {
  2986. var valid = false,
  2987. numeric = "<number> | <percentage>",
  2988. fill = false,
  2989. count = 0,
  2990. max = 4,
  2991. part;
  2992. if (ValidationTypes.isAny(expression, "fill")) {
  2993. fill = true;
  2994. valid = true;
  2995. }
  2996. while (expression.hasNext() && count < max) {
  2997. valid = ValidationTypes.isAny(expression, numeric);
  2998. if (!valid) {
  2999. break;
  3000. }
  3001. count++;
  3002. }
  3003. if (!fill) {
  3004. ValidationTypes.isAny(expression, "fill");
  3005. } else {
  3006. valid = true;
  3007. }
  3008. if (expression.hasNext()) {
  3009. part = expression.next();
  3010. if (valid) {
  3011. throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
  3012. } else {
  3013. throw new ValidationError("Expected ([<number> | <percentage>]{1,4} && fill?) but found '" + part + "'.", part.line, part.col);
  3014. }
  3015. }
  3016. },
  3017. "border-image-source" : "<image> | none",
  3018. "border-image-width" : { multi: "<length> | <percentage> | <number> | auto", max: 4 },
  3019. "border-left" : "<border-width> || <border-style> || <color>",
  3020. "border-left-color" : "<color> | inherit",
  3021. "border-left-style" : "<border-style>",
  3022. "border-left-width" : "<border-width>",
  3023. "border-radius" : function(expression) {
  3024. var valid = false,
  3025. numeric = "<length> | <percentage>",
  3026. slash = false,
  3027. fill = false,
  3028. count = 0,
  3029. max = 8,
  3030. part;
  3031. while (expression.hasNext() && count < max) {
  3032. valid = ValidationTypes.isAny(expression, numeric);
  3033. if (!valid) {
  3034. if (expression.peek() == "/" && count > 0 && !slash) {
  3035. slash = true;
  3036. max = count + 5;
  3037. expression.next();
  3038. } else {
  3039. break;
  3040. }
  3041. }
  3042. count++;
  3043. }
  3044. if (expression.hasNext()) {
  3045. part = expression.next();
  3046. if (valid) {
  3047. throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
  3048. } else {
  3049. throw new ValidationError("Expected (<'border-radius'>) but found '" + part + "'.", part.line, part.col);
  3050. }
  3051. }
  3052. },
  3053. "border-right" : "<border-width> || <border-style> || <color>",
  3054. "border-right-color" : "<color> | inherit",
  3055. "border-right-style" : "<border-style>",
  3056. "border-right-width" : "<border-width>",
  3057. "border-spacing" : { multi: "<length> | inherit", max: 2 },
  3058. "border-style" : { multi: "<border-style>", max: 4 },
  3059. "border-top" : "<border-width> || <border-style> || <color>",
  3060. "border-top-color" : "<color> | inherit",
  3061. "border-top-left-radius" : "<x-one-radius>",
  3062. "border-top-right-radius" : "<x-one-radius>",
  3063. "border-top-style" : "<border-style>",
  3064. "border-top-width" : "<border-width>",
  3065. "border-width" : { multi: "<border-width>", max: 4 },
  3066. "bottom" : "<margin-width> | inherit",
  3067. "box-align" : "start | end | center | baseline | stretch", //http://www.w3.org/TR/2009/WD-css3-flexbox-20090723/
  3068. "box-decoration-break" : "slice |clone",
  3069. "box-direction" : "normal | reverse | inherit",
  3070. "box-flex" : "<number>",
  3071. "box-flex-group" : "<integer>",
  3072. "box-lines" : "single | multiple",
  3073. "box-ordinal-group" : "<integer>",
  3074. "box-orient" : "horizontal | vertical | inline-axis | block-axis | inherit",
  3075. "box-pack" : "start | end | center | justify",
  3076. "box-shadow" : function (expression) {
  3077. var result = false,
  3078. part;
  3079. if (!ValidationTypes.isAny(expression, "none")) {
  3080. Validation.multiProperty("<shadow>", expression, true, Infinity);
  3081. } else {
  3082. if (expression.hasNext()) {
  3083. part = expression.next();
  3084. throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
  3085. }
  3086. }
  3087. },
  3088. "box-sizing" : "content-box | border-box | inherit",
  3089. "break-after" : "auto | always | avoid | left | right | page | column | avoid-page | avoid-column",
  3090. "break-before" : "auto | always | avoid | left | right | page | column | avoid-page | avoid-column",
  3091. "break-inside" : "auto | avoid | avoid-page | avoid-column",
  3092. //C
  3093. "caption-side" : "top | bottom | inherit",
  3094. "clear" : "none | right | left | both | inherit",
  3095. "clip" : 1,
  3096. "color" : "<color> | inherit",
  3097. "color-profile" : 1,
  3098. "column-count" : "<integer> | auto", //http://www.w3.org/TR/css3-multicol/
  3099. "column-fill" : "auto | balance",
  3100. "column-gap" : "<length> | normal",
  3101. "column-rule" : "<border-width> || <border-style> || <color>",
  3102. "column-rule-color" : "<color>",
  3103. "column-rule-style" : "<border-style>",
  3104. "column-rule-width" : "<border-width>",
  3105. "column-span" : "none | all",
  3106. "column-width" : "<length> | auto",
  3107. "columns" : 1,
  3108. "content" : 1,
  3109. "counter-increment" : 1,
  3110. "counter-reset" : 1,
  3111. "crop" : "<shape> | auto",
  3112. "cue" : "cue-after | cue-before | inherit",
  3113. "cue-after" : 1,
  3114. "cue-before" : 1,
  3115. "cursor" : 1,
  3116. //D
  3117. "direction" : "ltr | rtl | inherit",
  3118. "display" : "inline | block | list-item | inline-block | table | inline-table | table-row-group | table-header-group | table-footer-group | table-row | table-column-group | table-column | table-cell | table-caption | box | inline-box | grid | inline-grid | none | inherit | -moz-box | -moz-inline-block | -moz-inline-box | -moz-inline-grid | -moz-inline-stack | -moz-inline-table | -moz-grid | -moz-grid-group | -moz-grid-line | -moz-groupbox | -moz-deck | -moz-popup | -moz-stack | -moz-marker",
  3119. "dominant-baseline" : 1,
  3120. "drop-initial-after-adjust" : "central | middle | after-edge | text-after-edge | ideographic | alphabetic | mathematical | <percentage> | <length>",
  3121. "drop-initial-after-align" : "baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical",
  3122. "drop-initial-before-adjust" : "before-edge | text-before-edge | central | middle | hanging | mathematical | <percentage> | <length>",
  3123. "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",
  3124. "drop-initial-size" : "auto | line | <length> | <percentage>",
  3125. "drop-initial-value" : "initial | <integer>",
  3126. //E
  3127. "elevation" : "<angle> | below | level | above | higher | lower | inherit",
  3128. "empty-cells" : "show | hide | inherit",
  3129. //F
  3130. "filter" : 1,
  3131. "fit" : "fill | hidden | meet | slice",
  3132. "fit-position" : 1,
  3133. "float" : "left | right | none | inherit",
  3134. "float-offset" : 1,
  3135. "font" : 1,
  3136. "font-family" : 1,
  3137. "font-size" : "<absolute-size> | <relative-size> | <length> | <percentage> | inherit",
  3138. "font-size-adjust" : "<number> | none | inherit",
  3139. "font-stretch" : "normal | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded | inherit",
  3140. "font-style" : "normal | italic | oblique | inherit",
  3141. "font-variant" : "normal | small-caps | inherit",
  3142. "font-weight" : "normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | inherit",
  3143. //G
  3144. "grid-cell-stacking" : "columns | rows | layer",
  3145. "grid-column" : 1,
  3146. "grid-columns" : 1,
  3147. "grid-column-align" : "start | end | center | stretch",
  3148. "grid-column-sizing" : 1,
  3149. "grid-column-span" : "<integer>",
  3150. "grid-flow" : "none | rows | columns",
  3151. "grid-layer" : "<integer>",
  3152. "grid-row" : 1,
  3153. "grid-rows" : 1,
  3154. "grid-row-align" : "start | end | center | stretch",
  3155. "grid-row-span" : "<integer>",
  3156. "grid-row-sizing" : 1,
  3157. //H
  3158. "hanging-punctuation" : 1,
  3159. "height" : "<margin-width> | inherit",
  3160. "hyphenate-after" : "<integer> | auto",
  3161. "hyphenate-before" : "<integer> | auto",
  3162. "hyphenate-character" : "<string> | auto",
  3163. "hyphenate-lines" : "no-limit | <integer>",
  3164. "hyphenate-resource" : 1,
  3165. "hyphens" : "none | manual | auto",
  3166. //I
  3167. "icon" : 1,
  3168. "image-orientation" : "angle | auto",
  3169. "image-rendering" : 1,
  3170. "image-resolution" : 1,
  3171. "inline-box-align" : "initial | last | <integer>",
  3172. //L
  3173. "left" : "<margin-width> | inherit",
  3174. "letter-spacing" : "<length> | normal | inherit",
  3175. "line-height" : "<number> | <length> | <percentage> | normal | inherit",
  3176. "line-break" : "auto | loose | normal | strict",
  3177. "line-stacking" : 1,
  3178. "line-stacking-ruby" : "exclude-ruby | include-ruby",
  3179. "line-stacking-shift" : "consider-shifts | disregard-shifts",
  3180. "line-stacking-strategy" : "inline-line-height | block-line-height | max-height | grid-height",
  3181. "list-style" : 1,
  3182. "list-style-image" : "<uri> | none | inherit",
  3183. "list-style-position" : "inside | outside | inherit",
  3184. "list-style-type" : "disc | circle | square | decimal | decimal-leading-zero | lower-roman | upper-roman | lower-greek | lower-latin | upper-latin | armenian | georgian | lower-alpha | upper-alpha | none | inherit",
  3185. //M
  3186. "margin" : { multi: "<margin-width> | inherit", max: 4 },
  3187. "margin-bottom" : "<margin-width> | inherit",
  3188. "margin-left" : "<margin-width> | inherit",
  3189. "margin-right" : "<margin-width> | inherit",
  3190. "margin-top" : "<margin-width> | inherit",
  3191. "mark" : 1,
  3192. "mark-after" : 1,
  3193. "mark-before" : 1,
  3194. "marks" : 1,
  3195. "marquee-direction" : 1,
  3196. "marquee-play-count" : 1,
  3197. "marquee-speed" : 1,
  3198. "marquee-style" : 1,
  3199. "max-height" : "<length> | <percentage> | none | inherit",
  3200. "max-width" : "<length> | <percentage> | none | inherit",
  3201. "min-height" : "<length> | <percentage> | inherit",
  3202. "min-width" : "<length> | <percentage> | inherit",
  3203. "move-to" : 1,
  3204. //N
  3205. "nav-down" : 1,
  3206. "nav-index" : 1,
  3207. "nav-left" : 1,
  3208. "nav-right" : 1,
  3209. "nav-up" : 1,
  3210. //O
  3211. "opacity" : "<number> | inherit",
  3212. "orphans" : "<integer> | inherit",
  3213. "outline" : 1,
  3214. "outline-color" : "<color> | invert | inherit",
  3215. "outline-offset" : 1,
  3216. "outline-style" : "<border-style> | inherit",
  3217. "outline-width" : "<border-width> | inherit",
  3218. "overflow" : "visible | hidden | scroll | auto | inherit",
  3219. "overflow-style" : 1,
  3220. "overflow-x" : 1,
  3221. "overflow-y" : 1,
  3222. //P
  3223. "padding" : { multi: "<padding-width> | inherit", max: 4 },
  3224. "padding-bottom" : "<padding-width> | inherit",
  3225. "padding-left" : "<padding-width> | inherit",
  3226. "padding-right" : "<padding-width> | inherit",
  3227. "padding-top" : "<padding-width> | inherit",
  3228. "page" : 1,
  3229. "page-break-after" : "auto | always | avoid | left | right | inherit",
  3230. "page-break-before" : "auto | always | avoid | left | right | inherit",
  3231. "page-break-inside" : "auto | avoid | inherit",
  3232. "page-policy" : 1,
  3233. "pause" : 1,
  3234. "pause-after" : 1,
  3235. "pause-before" : 1,
  3236. "perspective" : 1,
  3237. "perspective-origin" : 1,
  3238. "phonemes" : 1,
  3239. "pitch" : 1,
  3240. "pitch-range" : 1,
  3241. "play-during" : 1,
  3242. "pointer-events" : "auto | none | visiblePainted | visibleFill | visibleStroke | visible | painted | fill | stroke | all | inherit",
  3243. "position" : "static | relative | absolute | fixed | inherit",
  3244. "presentation-level" : 1,
  3245. "punctuation-trim" : 1,
  3246. //Q
  3247. "quotes" : 1,
  3248. //R
  3249. "rendering-intent" : 1,
  3250. "resize" : 1,
  3251. "rest" : 1,
  3252. "rest-after" : 1,
  3253. "rest-before" : 1,
  3254. "richness" : 1,
  3255. "right" : "<margin-width> | inherit",
  3256. "rotation" : 1,
  3257. "rotation-point" : 1,
  3258. "ruby-align" : 1,
  3259. "ruby-overhang" : 1,
  3260. "ruby-position" : 1,
  3261. "ruby-span" : 1,
  3262. //S
  3263. "size" : 1,
  3264. "speak" : "normal | none | spell-out | inherit",
  3265. "speak-header" : "once | always | inherit",
  3266. "speak-numeral" : "digits | continuous | inherit",
  3267. "speak-punctuation" : "code | none | inherit",
  3268. "speech-rate" : 1,
  3269. "src" : 1,
  3270. "stress" : 1,
  3271. "string-set" : 1,
  3272. "table-layout" : "auto | fixed | inherit",
  3273. "tab-size" : "<integer> | <length>",
  3274. "target" : 1,
  3275. "target-name" : 1,
  3276. "target-new" : 1,
  3277. "target-position" : 1,
  3278. "text-align" : "left | right | center | justify | inherit" ,
  3279. "text-align-last" : 1,
  3280. "text-decoration" : 1,
  3281. "text-emphasis" : 1,
  3282. "text-height" : 1,
  3283. "text-indent" : "<length> | <percentage> | inherit",
  3284. "text-justify" : "auto | none | inter-word | inter-ideograph | inter-cluster | distribute | kashida",
  3285. "text-outline" : 1,
  3286. "text-overflow" : 1,
  3287. "text-rendering" : "auto | optimizeSpeed | optimizeLegibility | geometricPrecision | inherit",
  3288. "text-shadow" : 1,
  3289. "text-transform" : "capitalize | uppercase | lowercase | none | inherit",
  3290. "text-wrap" : "normal | none | avoid",
  3291. "top" : "<margin-width> | inherit",
  3292. "transform" : 1,
  3293. "transform-origin" : 1,
  3294. "transform-style" : 1,
  3295. "transition" : 1,
  3296. "transition-delay" : 1,
  3297. "transition-duration" : 1,
  3298. "transition-property" : 1,
  3299. "transition-timing-function" : 1,
  3300. //U
  3301. "unicode-bidi" : "normal | embed | bidi-override | inherit",
  3302. "user-modify" : "read-only | read-write | write-only | inherit",
  3303. "user-select" : "none | text | toggle | element | elements | all | inherit",
  3304. //V
  3305. "vertical-align" : "auto | use-script | baseline | sub | super | top | text-top | central | middle | bottom | text-bottom | <percentage> | <length>",
  3306. "visibility" : "visible | hidden | collapse | inherit",
  3307. "voice-balance" : 1,
  3308. "voice-duration" : 1,
  3309. "voice-family" : 1,
  3310. "voice-pitch" : 1,
  3311. "voice-pitch-range" : 1,
  3312. "voice-rate" : 1,
  3313. "voice-stress" : 1,
  3314. "voice-volume" : 1,
  3315. "volume" : 1,
  3316. //W
  3317. "white-space" : "normal | pre | nowrap | pre-wrap | pre-line | inherit | -pre-wrap | -o-pre-wrap | -moz-pre-wrap | -hp-pre-wrap", //http://perishablepress.com/wrapping-content/
  3318. "white-space-collapse" : 1,
  3319. "widows" : "<integer> | inherit",
  3320. "width" : "<length> | <percentage> | auto | inherit" ,
  3321. "word-break" : "normal | keep-all | break-all",
  3322. "word-spacing" : "<length> | normal | inherit",
  3323. "word-wrap" : 1,
  3324. //Z
  3325. "z-index" : "<integer> | auto | inherit",
  3326. "zoom" : "<number> | <percentage> | normal"
  3327. };
  3328. /*global SyntaxUnit, Parser*/
  3329. /**
  3330. * Represents a selector combinator (whitespace, +, >).
  3331. * @namespace parserlib.css
  3332. * @class PropertyName
  3333. * @extends parserlib.util.SyntaxUnit
  3334. * @constructor
  3335. * @param {String} text The text representation of the unit.
  3336. * @param {String} hack The type of IE hack applied ("*", "_", or null).
  3337. * @param {int} line The line of text on which the unit resides.
  3338. * @param {int} col The column of text on which the unit resides.
  3339. */
  3340. function PropertyName(text, hack, line, col){
  3341. SyntaxUnit.call(this, text, line, col, Parser.PROPERTY_NAME_TYPE);
  3342. /**
  3343. * The type of IE hack applied ("*", "_", or null).
  3344. * @type String
  3345. * @property hack
  3346. */
  3347. this.hack = hack;
  3348. }
  3349. PropertyName.prototype = new SyntaxUnit();
  3350. PropertyName.prototype.constructor = PropertyName;
  3351. PropertyName.prototype.toString = function(){
  3352. return (this.hack ? this.hack : "") + this.text;
  3353. };
  3354. /*global SyntaxUnit, Parser*/
  3355. /**
  3356. * Represents a single part of a CSS property value, meaning that it represents
  3357. * just everything single part between ":" and ";". If there are multiple values
  3358. * separated by commas, this type represents just one of the values.
  3359. * @param {String[]} parts An array of value parts making up this value.
  3360. * @param {int} line The line of text on which the unit resides.
  3361. * @param {int} col The column of text on which the unit resides.
  3362. * @namespace parserlib.css
  3363. * @class PropertyValue
  3364. * @extends parserlib.util.SyntaxUnit
  3365. * @constructor
  3366. */
  3367. function PropertyValue(parts, line, col){
  3368. SyntaxUnit.call(this, parts.join(" "), line, col, Parser.PROPERTY_VALUE_TYPE);
  3369. /**
  3370. * The parts that make up the selector.
  3371. * @type Array
  3372. * @property parts
  3373. */
  3374. this.parts = parts;
  3375. }
  3376. PropertyValue.prototype = new SyntaxUnit();
  3377. PropertyValue.prototype.constructor = PropertyValue;
  3378. /*global SyntaxUnit, Parser*/
  3379. /**
  3380. * A utility class that allows for easy iteration over the various parts of a
  3381. * property value.
  3382. * @param {parserlib.css.PropertyValue} value The property value to iterate over.
  3383. * @namespace parserlib.css
  3384. * @class PropertyValueIterator
  3385. * @constructor
  3386. */
  3387. function PropertyValueIterator(value){
  3388. /**
  3389. * Iterator value
  3390. * @type int
  3391. * @property _i
  3392. * @private
  3393. */
  3394. this._i = 0;
  3395. /**
  3396. * The parts that make up the value.
  3397. * @type Array
  3398. * @property _parts
  3399. * @private
  3400. */
  3401. this._parts = value.parts;
  3402. /**
  3403. * Keeps track of bookmarks along the way.
  3404. * @type Array
  3405. * @property _marks
  3406. * @private
  3407. */
  3408. this._marks = [];
  3409. /**
  3410. * Holds the original property value.
  3411. * @type parserlib.css.PropertyValue
  3412. * @property value
  3413. */
  3414. this.value = value;
  3415. }
  3416. /**
  3417. * Returns the total number of parts in the value.
  3418. * @return {int} The total number of parts in the value.
  3419. * @method count
  3420. */
  3421. PropertyValueIterator.prototype.count = function(){
  3422. return this._parts.length;
  3423. };
  3424. /**
  3425. * Indicates if the iterator is positioned at the first item.
  3426. * @return {Boolean} True if positioned at first item, false if not.
  3427. * @method isFirst
  3428. */
  3429. PropertyValueIterator.prototype.isFirst = function(){
  3430. return this._i === 0;
  3431. };
  3432. /**
  3433. * Indicates if there are more parts of the property value.
  3434. * @return {Boolean} True if there are more parts, false if not.
  3435. * @method hasNext
  3436. */
  3437. PropertyValueIterator.prototype.hasNext = function(){
  3438. return (this._i < this._parts.length);
  3439. };
  3440. /**
  3441. * Marks the current spot in the iteration so it can be restored to
  3442. * later on.
  3443. * @return {void}
  3444. * @method mark
  3445. */
  3446. PropertyValueIterator.prototype.mark = function(){
  3447. this._marks.push(this._i);
  3448. };
  3449. /**
  3450. * Returns the next part of the property value or null if there is no next
  3451. * part. Does not move the internal counter forward.
  3452. * @return {parserlib.css.PropertyValuePart} The next part of the property value or null if there is no next
  3453. * part.
  3454. * @method peek
  3455. */
  3456. PropertyValueIterator.prototype.peek = function(count){
  3457. return this.hasNext() ? this._parts[this._i + (count || 0)] : null;
  3458. };
  3459. /**
  3460. * Returns the next part of the property value or null if there is no next
  3461. * part.
  3462. * @return {parserlib.css.PropertyValuePart} The next part of the property value or null if there is no next
  3463. * part.
  3464. * @method next
  3465. */
  3466. PropertyValueIterator.prototype.next = function(){
  3467. return this.hasNext() ? this._parts[this._i++] : null;
  3468. };
  3469. /**
  3470. * Returns the previous part of the property value or null if there is no
  3471. * previous part.
  3472. * @return {parserlib.css.PropertyValuePart} The previous part of the
  3473. * property value or null if there is no next part.
  3474. * @method previous
  3475. */
  3476. PropertyValueIterator.prototype.previous = function(){
  3477. return this._i > 0 ? this._parts[--this._i] : null;
  3478. };
  3479. /**
  3480. * Restores the last saved bookmark.
  3481. * @return {void}
  3482. * @method restore
  3483. */
  3484. PropertyValueIterator.prototype.restore = function(){
  3485. if (this._marks.length){
  3486. this._i = this._marks.pop();
  3487. }
  3488. };
  3489. /*global SyntaxUnit, Parser, Colors*/
  3490. /**
  3491. * Represents a single part of a CSS property value, meaning that it represents
  3492. * just one part of the data between ":" and ";".
  3493. * @param {String} text The text representation of the unit.
  3494. * @param {int} line The line of text on which the unit resides.
  3495. * @param {int} col The column of text on which the unit resides.
  3496. * @namespace parserlib.css
  3497. * @class PropertyValuePart
  3498. * @extends parserlib.util.SyntaxUnit
  3499. * @constructor
  3500. */
  3501. function PropertyValuePart(text, line, col){
  3502. SyntaxUnit.call(this, text, line, col, Parser.PROPERTY_VALUE_PART_TYPE);
  3503. /**
  3504. * Indicates the type of value unit.
  3505. * @type String
  3506. * @property type
  3507. */
  3508. this.type = "unknown";
  3509. //figure out what type of data it is
  3510. var temp;
  3511. //it is a measurement?
  3512. if (/^([+\-]?[\d\.]+)([a-z]+)$/i.test(text)){ //dimension
  3513. this.type = "dimension";
  3514. this.value = +RegExp.$1;
  3515. this.units = RegExp.$2;
  3516. //try to narrow down
  3517. switch(this.units.toLowerCase()){
  3518. case "em":
  3519. case "rem":
  3520. case "ex":
  3521. case "px":
  3522. case "cm":
  3523. case "mm":
  3524. case "in":
  3525. case "pt":
  3526. case "pc":
  3527. case "ch":
  3528. this.type = "length";
  3529. break;
  3530. case "deg":
  3531. case "rad":
  3532. case "grad":
  3533. this.type = "angle";
  3534. break;
  3535. case "ms":
  3536. case "s":
  3537. this.type = "time";
  3538. break;
  3539. case "hz":
  3540. case "khz":
  3541. this.type = "frequency";
  3542. break;
  3543. case "dpi":
  3544. case "dpcm":
  3545. this.type = "resolution";
  3546. break;
  3547. //default
  3548. }
  3549. } else if (/^([+\-]?[\d\.]+)%$/i.test(text)){ //percentage
  3550. this.type = "percentage";
  3551. this.value = +RegExp.$1;
  3552. } else if (/^([+\-]?[\d\.]+)%$/i.test(text)){ //percentage
  3553. this.type = "percentage";
  3554. this.value = +RegExp.$1;
  3555. } else if (/^([+\-]?\d+)$/i.test(text)){ //integer
  3556. this.type = "integer";
  3557. this.value = +RegExp.$1;
  3558. } else if (/^([+\-]?[\d\.]+)$/i.test(text)){ //number
  3559. this.type = "number";
  3560. this.value = +RegExp.$1;
  3561. } else if (/^#([a-f0-9]{3,6})/i.test(text)){ //hexcolor
  3562. this.type = "color";
  3563. temp = RegExp.$1;
  3564. if (temp.length == 3){
  3565. this.red = parseInt(temp.charAt(0)+temp.charAt(0),16);
  3566. this.green = parseInt(temp.charAt(1)+temp.charAt(1),16);
  3567. this.blue = parseInt(temp.charAt(2)+temp.charAt(2),16);
  3568. } else {
  3569. this.red = parseInt(temp.substring(0,2),16);
  3570. this.green = parseInt(temp.substring(2,4),16);
  3571. this.blue = parseInt(temp.substring(4,6),16);
  3572. }
  3573. } else if (/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i.test(text)){ //rgb() color with absolute numbers
  3574. this.type = "color";
  3575. this.red = +RegExp.$1;
  3576. this.green = +RegExp.$2;
  3577. this.blue = +RegExp.$3;
  3578. } else if (/^rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/i.test(text)){ //rgb() color with percentages
  3579. this.type = "color";
  3580. this.red = +RegExp.$1 * 255 / 100;
  3581. this.green = +RegExp.$2 * 255 / 100;
  3582. this.blue = +RegExp.$3 * 255 / 100;
  3583. } else if (/^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d\.]+)\s*\)/i.test(text)){ //rgba() color with absolute numbers
  3584. this.type = "color";
  3585. this.red = +RegExp.$1;
  3586. this.green = +RegExp.$2;
  3587. this.blue = +RegExp.$3;
  3588. this.alpha = +RegExp.$4;
  3589. } else if (/^rgba\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([\d\.]+)\s*\)/i.test(text)){ //rgba() color with percentages
  3590. this.type = "color";
  3591. this.red = +RegExp.$1 * 255 / 100;
  3592. this.green = +RegExp.$2 * 255 / 100;
  3593. this.blue = +RegExp.$3 * 255 / 100;
  3594. this.alpha = +RegExp.$4;
  3595. } else if (/^hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/i.test(text)){ //hsl()
  3596. this.type = "color";
  3597. this.hue = +RegExp.$1;
  3598. this.saturation = +RegExp.$2 / 100;
  3599. this.lightness = +RegExp.$3 / 100;
  3600. } else if (/^hsla\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([\d\.]+)\s*\)/i.test(text)){ //hsla() color with percentages
  3601. this.type = "color";
  3602. this.hue = +RegExp.$1;
  3603. this.saturation = +RegExp.$2 / 100;
  3604. this.lightness = +RegExp.$3 / 100;
  3605. this.alpha = +RegExp.$4;
  3606. } else if (/^url\(["']?([^\)"']+)["']?\)/i.test(text)){ //URI
  3607. this.type = "uri";
  3608. this.uri = RegExp.$1;
  3609. } else if (/^([^\(]+)\(/i.test(text)){
  3610. this.type = "function";
  3611. this.name = RegExp.$1;
  3612. this.value = text;
  3613. } else if (/^["'][^"']*["']/.test(text)){ //string
  3614. this.type = "string";
  3615. this.value = eval(text);
  3616. } else if (Colors[text.toLowerCase()]){ //named color
  3617. this.type = "color";
  3618. temp = Colors[text.toLowerCase()].substring(1);
  3619. this.red = parseInt(temp.substring(0,2),16);
  3620. this.green = parseInt(temp.substring(2,4),16);
  3621. this.blue = parseInt(temp.substring(4,6),16);
  3622. } else if (/^[\,\/]$/.test(text)){
  3623. this.type = "operator";
  3624. this.value = text;
  3625. } else if (/^[a-z\-\u0080-\uFFFF][a-z0-9\-\u0080-\uFFFF]*$/i.test(text)){
  3626. this.type = "identifier";
  3627. this.value = text;
  3628. }
  3629. }
  3630. PropertyValuePart.prototype = new SyntaxUnit();
  3631. PropertyValuePart.prototype.constructor = PropertyValuePart;
  3632. /**
  3633. * Create a new syntax unit based solely on the given token.
  3634. * Convenience method for creating a new syntax unit when
  3635. * it represents a single token instead of multiple.
  3636. * @param {Object} token The token object to represent.
  3637. * @return {parserlib.css.PropertyValuePart} The object representing the token.
  3638. * @static
  3639. * @method fromToken
  3640. */
  3641. PropertyValuePart.fromToken = function(token){
  3642. return new PropertyValuePart(token.value, token.startLine, token.startCol);
  3643. };
  3644. var Pseudos = {
  3645. ":first-letter": 1,
  3646. ":first-line": 1,
  3647. ":before": 1,
  3648. ":after": 1
  3649. };
  3650. Pseudos.ELEMENT = 1;
  3651. Pseudos.CLASS = 2;
  3652. Pseudos.isElement = function(pseudo){
  3653. return pseudo.indexOf("::") === 0 || Pseudos[pseudo.toLowerCase()] == Pseudos.ELEMENT;
  3654. };
  3655. /*global SyntaxUnit, Parser, Specificity*/
  3656. /**
  3657. * Represents an entire single selector, including all parts but not
  3658. * including multiple selectors (those separated by commas).
  3659. * @namespace parserlib.css
  3660. * @class Selector
  3661. * @extends parserlib.util.SyntaxUnit
  3662. * @constructor
  3663. * @param {Array} parts Array of selectors parts making up this selector.
  3664. * @param {int} line The line of text on which the unit resides.
  3665. * @param {int} col The column of text on which the unit resides.
  3666. */
  3667. function Selector(parts, line, col){
  3668. SyntaxUnit.call(this, parts.join(" "), line, col, Parser.SELECTOR_TYPE);
  3669. /**
  3670. * The parts that make up the selector.
  3671. * @type Array
  3672. * @property parts
  3673. */
  3674. this.parts = parts;
  3675. /**
  3676. * The specificity of the selector.
  3677. * @type parserlib.css.Specificity
  3678. * @property specificity
  3679. */
  3680. this.specificity = Specificity.calculate(this);
  3681. }
  3682. Selector.prototype = new SyntaxUnit();
  3683. Selector.prototype.constructor = Selector;
  3684. /*global SyntaxUnit, Parser*/
  3685. /**
  3686. * Represents a single part of a selector string, meaning a single set of
  3687. * element name and modifiers. This does not include combinators such as
  3688. * spaces, +, >, etc.
  3689. * @namespace parserlib.css
  3690. * @class SelectorPart
  3691. * @extends parserlib.util.SyntaxUnit
  3692. * @constructor
  3693. * @param {String} elementName The element name in the selector or null
  3694. * if there is no element name.
  3695. * @param {Array} modifiers Array of individual modifiers for the element.
  3696. * May be empty if there are none.
  3697. * @param {String} text The text representation of the unit.
  3698. * @param {int} line The line of text on which the unit resides.
  3699. * @param {int} col The column of text on which the unit resides.
  3700. */
  3701. function SelectorPart(elementName, modifiers, text, line, col){
  3702. SyntaxUnit.call(this, text, line, col, Parser.SELECTOR_PART_TYPE);
  3703. /**
  3704. * The tag name of the element to which this part
  3705. * of the selector affects.
  3706. * @type String
  3707. * @property elementName
  3708. */
  3709. this.elementName = elementName;
  3710. /**
  3711. * The parts that come after the element name, such as class names, IDs,
  3712. * pseudo classes/elements, etc.
  3713. * @type Array
  3714. * @property modifiers
  3715. */
  3716. this.modifiers = modifiers;
  3717. }
  3718. SelectorPart.prototype = new SyntaxUnit();
  3719. SelectorPart.prototype.constructor = SelectorPart;
  3720. /*global SyntaxUnit, Parser*/
  3721. /**
  3722. * Represents a selector modifier string, meaning a class name, element name,
  3723. * element ID, pseudo rule, etc.
  3724. * @namespace parserlib.css
  3725. * @class SelectorSubPart
  3726. * @extends parserlib.util.SyntaxUnit
  3727. * @constructor
  3728. * @param {String} text The text representation of the unit.
  3729. * @param {String} type The type of selector modifier.
  3730. * @param {int} line The line of text on which the unit resides.
  3731. * @param {int} col The column of text on which the unit resides.
  3732. */
  3733. function SelectorSubPart(text, type, line, col){
  3734. SyntaxUnit.call(this, text, line, col, Parser.SELECTOR_SUB_PART_TYPE);
  3735. /**
  3736. * The type of modifier.
  3737. * @type String
  3738. * @property type
  3739. */
  3740. this.type = type;
  3741. /**
  3742. * Some subparts have arguments, this represents them.
  3743. * @type Array
  3744. * @property args
  3745. */
  3746. this.args = [];
  3747. }
  3748. SelectorSubPart.prototype = new SyntaxUnit();
  3749. SelectorSubPart.prototype.constructor = SelectorSubPart;
  3750. /*global Pseudos, SelectorPart*/
  3751. /**
  3752. * Represents a selector's specificity.
  3753. * @namespace parserlib.css
  3754. * @class Specificity
  3755. * @constructor
  3756. * @param {int} a Should be 1 for inline styles, zero for stylesheet styles
  3757. * @param {int} b Number of ID selectors
  3758. * @param {int} c Number of classes and pseudo classes
  3759. * @param {int} d Number of element names and pseudo elements
  3760. */
  3761. function Specificity(a, b, c, d){
  3762. this.a = a;
  3763. this.b = b;
  3764. this.c = c;
  3765. this.d = d;
  3766. }
  3767. Specificity.prototype = {
  3768. constructor: Specificity,
  3769. /**
  3770. * Compare this specificity to another.
  3771. * @param {Specificity} other The other specificity to compare to.
  3772. * @return {int} -1 if the other specificity is larger, 1 if smaller, 0 if equal.
  3773. * @method compare
  3774. */
  3775. compare: function(other){
  3776. var comps = ["a", "b", "c", "d"],
  3777. i, len;
  3778. for (i=0, len=comps.length; i < len; i++){
  3779. if (this[comps[i]] < other[comps[i]]){
  3780. return -1;
  3781. } else if (this[comps[i]] > other[comps[i]]){
  3782. return 1;
  3783. }
  3784. }
  3785. return 0;
  3786. },
  3787. /**
  3788. * Creates a numeric value for the specificity.
  3789. * @return {int} The numeric value for the specificity.
  3790. * @method valueOf
  3791. */
  3792. valueOf: function(){
  3793. return (this.a * 1000) + (this.b * 100) + (this.c * 10) + this.d;
  3794. },
  3795. /**
  3796. * Returns a string representation for specificity.
  3797. * @return {String} The string representation of specificity.
  3798. * @method toString
  3799. */
  3800. toString: function(){
  3801. return this.a + "," + this.b + "," + this.c + "," + this.d;
  3802. }
  3803. };
  3804. /**
  3805. * Calculates the specificity of the given selector.
  3806. * @param {parserlib.css.Selector} The selector to calculate specificity for.
  3807. * @return {parserlib.css.Specificity} The specificity of the selector.
  3808. * @static
  3809. * @method calculate
  3810. */
  3811. Specificity.calculate = function(selector){
  3812. var i, len,
  3813. part,
  3814. b=0, c=0, d=0;
  3815. function updateValues(part){
  3816. var i, j, len, num,
  3817. elementName = part.elementName ? part.elementName.text : "",
  3818. modifier;
  3819. if (elementName && elementName.charAt(elementName.length-1) != "*") {
  3820. d++;
  3821. }
  3822. for (i=0, len=part.modifiers.length; i < len; i++){
  3823. modifier = part.modifiers[i];
  3824. switch(modifier.type){
  3825. case "class":
  3826. case "attribute":
  3827. c++;
  3828. break;
  3829. case "id":
  3830. b++;
  3831. break;
  3832. case "pseudo":
  3833. if (Pseudos.isElement(modifier.text)){
  3834. d++;
  3835. } else {
  3836. c++;
  3837. }
  3838. break;
  3839. case "not":
  3840. for (j=0, num=modifier.args.length; j < num; j++){
  3841. updateValues(modifier.args[j]);
  3842. }
  3843. }
  3844. }
  3845. }
  3846. for (i=0, len=selector.parts.length; i < len; i++){
  3847. part = selector.parts[i];
  3848. if (part instanceof SelectorPart){
  3849. updateValues(part);
  3850. }
  3851. }
  3852. return new Specificity(0, b, c, d);
  3853. };
  3854. /*global Tokens, TokenStreamBase*/
  3855. var h = /^[0-9a-fA-F]$/,
  3856. nonascii = /^[\u0080-\uFFFF]$/,
  3857. nl = /\n|\r\n|\r|\f/;
  3858. //-----------------------------------------------------------------------------
  3859. // Helper functions
  3860. //-----------------------------------------------------------------------------
  3861. function isHexDigit(c){
  3862. return c !== null && h.test(c);
  3863. }
  3864. function isDigit(c){
  3865. return c !== null && /\d/.test(c);
  3866. }
  3867. function isWhitespace(c){
  3868. return c !== null && /\s/.test(c);
  3869. }
  3870. function isNewLine(c){
  3871. return c !== null && nl.test(c);
  3872. }
  3873. function isNameStart(c){
  3874. return c !== null && (/[a-z_\u0080-\uFFFF\\]/i.test(c));
  3875. }
  3876. function isNameChar(c){
  3877. return c !== null && (isNameStart(c) || /[0-9\-\\]/.test(c));
  3878. }
  3879. function isIdentStart(c){
  3880. return c !== null && (isNameStart(c) || /\-\\/.test(c));
  3881. }
  3882. function mix(receiver, supplier){
  3883. for (var prop in supplier){
  3884. if (supplier.hasOwnProperty(prop)){
  3885. receiver[prop] = supplier[prop];
  3886. }
  3887. }
  3888. return receiver;
  3889. }
  3890. //-----------------------------------------------------------------------------
  3891. // CSS Token Stream
  3892. //-----------------------------------------------------------------------------
  3893. /**
  3894. * A token stream that produces CSS tokens.
  3895. * @param {String|Reader} input The source of text to tokenize.
  3896. * @constructor
  3897. * @class TokenStream
  3898. * @namespace parserlib.css
  3899. */
  3900. function TokenStream(input){
  3901. TokenStreamBase.call(this, input, Tokens);
  3902. }
  3903. TokenStream.prototype = mix(new TokenStreamBase(), {
  3904. /**
  3905. * Overrides the TokenStreamBase method of the same name
  3906. * to produce CSS tokens.
  3907. * @param {variant} channel The name of the channel to use
  3908. * for the next token.
  3909. * @return {Object} A token object representing the next token.
  3910. * @method _getToken
  3911. * @private
  3912. */
  3913. _getToken: function(channel){
  3914. var c,
  3915. reader = this._reader,
  3916. token = null,
  3917. startLine = reader.getLine(),
  3918. startCol = reader.getCol();
  3919. c = reader.read();
  3920. while(c){
  3921. switch(c){
  3922. /*
  3923. * Potential tokens:
  3924. * - COMMENT
  3925. * - SLASH
  3926. * - CHAR
  3927. */
  3928. case "/":
  3929. if(reader.peek() == "*"){
  3930. token = this.commentToken(c, startLine, startCol);
  3931. } else {
  3932. token = this.charToken(c, startLine, startCol);
  3933. }
  3934. break;
  3935. /*
  3936. * Potential tokens:
  3937. * - DASHMATCH
  3938. * - INCLUDES
  3939. * - PREFIXMATCH
  3940. * - SUFFIXMATCH
  3941. * - SUBSTRINGMATCH
  3942. * - CHAR
  3943. */
  3944. case "|":
  3945. case "~":
  3946. case "^":
  3947. case "$":
  3948. case "*":
  3949. if(reader.peek() == "="){
  3950. token = this.comparisonToken(c, startLine, startCol);
  3951. } else {
  3952. token = this.charToken(c, startLine, startCol);
  3953. }
  3954. break;
  3955. /*
  3956. * Potential tokens:
  3957. * - STRING
  3958. * - INVALID
  3959. */
  3960. case "\"":
  3961. case "'":
  3962. token = this.stringToken(c, startLine, startCol);
  3963. break;
  3964. /*
  3965. * Potential tokens:
  3966. * - HASH
  3967. * - CHAR
  3968. */
  3969. case "#":
  3970. if (isNameChar(reader.peek())){
  3971. token = this.hashToken(c, startLine, startCol);
  3972. } else {
  3973. token = this.charToken(c, startLine, startCol);
  3974. }
  3975. break;
  3976. /*
  3977. * Potential tokens:
  3978. * - DOT
  3979. * - NUMBER
  3980. * - DIMENSION
  3981. * - PERCENTAGE
  3982. */
  3983. case ".":
  3984. if (isDigit(reader.peek())){
  3985. token = this.numberToken(c, startLine, startCol);
  3986. } else {
  3987. token = this.charToken(c, startLine, startCol);
  3988. }
  3989. break;
  3990. /*
  3991. * Potential tokens:
  3992. * - CDC
  3993. * - MINUS
  3994. * - NUMBER
  3995. * - DIMENSION
  3996. * - PERCENTAGE
  3997. */
  3998. case "-":
  3999. if (reader.peek() == "-"){ //could be closing HTML-style comment
  4000. token = this.htmlCommentEndToken(c, startLine, startCol);
  4001. } else if (isNameStart(reader.peek())){
  4002. token = this.identOrFunctionToken(c, startLine, startCol);
  4003. } else {
  4004. token = this.charToken(c, startLine, startCol);
  4005. }
  4006. break;
  4007. /*
  4008. * Potential tokens:
  4009. * - IMPORTANT_SYM
  4010. * - CHAR
  4011. */
  4012. case "!":
  4013. token = this.importantToken(c, startLine, startCol);
  4014. break;
  4015. /*
  4016. * Any at-keyword or CHAR
  4017. */
  4018. case "@":
  4019. token = this.atRuleToken(c, startLine, startCol);
  4020. break;
  4021. /*
  4022. * Potential tokens:
  4023. * - NOT
  4024. * - CHAR
  4025. */
  4026. case ":":
  4027. token = this.notToken(c, startLine, startCol);
  4028. break;
  4029. /*
  4030. * Potential tokens:
  4031. * - CDO
  4032. * - CHAR
  4033. */
  4034. case "<":
  4035. token = this.htmlCommentStartToken(c, startLine, startCol);
  4036. break;
  4037. /*
  4038. * Potential tokens:
  4039. * - UNICODE_RANGE
  4040. * - URL
  4041. * - CHAR
  4042. */
  4043. case "U":
  4044. case "u":
  4045. if (reader.peek() == "+"){
  4046. token = this.unicodeRangeToken(c, startLine, startCol);
  4047. break;
  4048. }
  4049. /* falls through */
  4050. default:
  4051. /*
  4052. * Potential tokens:
  4053. * - NUMBER
  4054. * - DIMENSION
  4055. * - LENGTH
  4056. * - FREQ
  4057. * - TIME
  4058. * - EMS
  4059. * - EXS
  4060. * - ANGLE
  4061. */
  4062. if (isDigit(c)){
  4063. token = this.numberToken(c, startLine, startCol);
  4064. } else
  4065. /*
  4066. * Potential tokens:
  4067. * - S
  4068. */
  4069. if (isWhitespace(c)){
  4070. token = this.whitespaceToken(c, startLine, startCol);
  4071. } else
  4072. /*
  4073. * Potential tokens:
  4074. * - IDENT
  4075. */
  4076. if (isIdentStart(c)){
  4077. token = this.identOrFunctionToken(c, startLine, startCol);
  4078. } else
  4079. /*
  4080. * Potential tokens:
  4081. * - CHAR
  4082. * - PLUS
  4083. */
  4084. {
  4085. token = this.charToken(c, startLine, startCol);
  4086. }
  4087. }
  4088. //make sure this token is wanted
  4089. //TODO: check channel
  4090. break;
  4091. }
  4092. if (!token && c === null){
  4093. token = this.createToken(Tokens.EOF,null,startLine,startCol);
  4094. }
  4095. return token;
  4096. },
  4097. //-------------------------------------------------------------------------
  4098. // Methods to create tokens
  4099. //-------------------------------------------------------------------------
  4100. /**
  4101. * Produces a token based on available data and the current
  4102. * reader position information. This method is called by other
  4103. * private methods to create tokens and is never called directly.
  4104. * @param {int} tt The token type.
  4105. * @param {String} value The text value of the token.
  4106. * @param {int} startLine The beginning line for the character.
  4107. * @param {int} startCol The beginning column for the character.
  4108. * @param {Object} options (Optional) Specifies a channel property
  4109. * to indicate that a different channel should be scanned
  4110. * and/or a hide property indicating that the token should
  4111. * be hidden.
  4112. * @return {Object} A token object.
  4113. * @method createToken
  4114. */
  4115. createToken: function(tt, value, startLine, startCol, options){
  4116. var reader = this._reader;
  4117. options = options || {};
  4118. return {
  4119. value: value,
  4120. type: tt,
  4121. channel: options.channel,
  4122. hide: options.hide || false,
  4123. startLine: startLine,
  4124. startCol: startCol,
  4125. endLine: reader.getLine(),
  4126. endCol: reader.getCol()
  4127. };
  4128. },
  4129. //-------------------------------------------------------------------------
  4130. // Methods to create specific tokens
  4131. //-------------------------------------------------------------------------
  4132. /**
  4133. * Produces a token for any at-rule. If the at-rule is unknown, then
  4134. * the token is for a single "@" character.
  4135. * @param {String} first The first character for the token.
  4136. * @param {int} startLine The beginning line for the character.
  4137. * @param {int} startCol The beginning column for the character.
  4138. * @return {Object} A token object.
  4139. * @method atRuleToken
  4140. */
  4141. atRuleToken: function(first, startLine, startCol){
  4142. var rule = first,
  4143. reader = this._reader,
  4144. tt = Tokens.CHAR,
  4145. valid = false,
  4146. ident,
  4147. c;
  4148. /*
  4149. * First, mark where we are. There are only four @ rules,
  4150. * so anything else is really just an invalid token.
  4151. * Basically, if this doesn't match one of the known @
  4152. * rules, just return '@' as an unknown token and allow
  4153. * parsing to continue after that point.
  4154. */
  4155. reader.mark();
  4156. //try to find the at-keyword
  4157. ident = this.readName();
  4158. rule = first + ident;
  4159. tt = Tokens.type(rule.toLowerCase());
  4160. //if it's not valid, use the first character only and reset the reader
  4161. if (tt == Tokens.CHAR || tt == Tokens.UNKNOWN){
  4162. if (rule.length > 1){
  4163. tt = Tokens.UNKNOWN_SYM;
  4164. } else {
  4165. tt = Tokens.CHAR;
  4166. rule = first;
  4167. reader.reset();
  4168. }
  4169. }
  4170. return this.createToken(tt, rule, startLine, startCol);
  4171. },
  4172. /**
  4173. * Produces a character token based on the given character
  4174. * and location in the stream. If there's a special (non-standard)
  4175. * token name, this is used; otherwise CHAR is used.
  4176. * @param {String} c The character for the token.
  4177. * @param {int} startLine The beginning line for the character.
  4178. * @param {int} startCol The beginning column for the character.
  4179. * @return {Object} A token object.
  4180. * @method charToken
  4181. */
  4182. charToken: function(c, startLine, startCol){
  4183. var tt = Tokens.type(c);
  4184. if (tt == -1){
  4185. tt = Tokens.CHAR;
  4186. }
  4187. return this.createToken(tt, c, startLine, startCol);
  4188. },
  4189. /**
  4190. * Produces a character token based on the given character
  4191. * and location in the stream. If there's a special (non-standard)
  4192. * token name, this is used; otherwise CHAR is used.
  4193. * @param {String} first The first character for the token.
  4194. * @param {int} startLine The beginning line for the character.
  4195. * @param {int} startCol The beginning column for the character.
  4196. * @return {Object} A token object.
  4197. * @method commentToken
  4198. */
  4199. commentToken: function(first, startLine, startCol){
  4200. var reader = this._reader,
  4201. comment = this.readComment(first);
  4202. return this.createToken(Tokens.COMMENT, comment, startLine, startCol);
  4203. },
  4204. /**
  4205. * Produces a comparison token based on the given character
  4206. * and location in the stream. The next character must be
  4207. * read and is already known to be an equals sign.
  4208. * @param {String} c The character for the token.
  4209. * @param {int} startLine The beginning line for the character.
  4210. * @param {int} startCol The beginning column for the character.
  4211. * @return {Object} A token object.
  4212. * @method comparisonToken
  4213. */
  4214. comparisonToken: function(c, startLine, startCol){
  4215. var reader = this._reader,
  4216. comparison = c + reader.read(),
  4217. tt = Tokens.type(comparison) || Tokens.CHAR;
  4218. return this.createToken(tt, comparison, startLine, startCol);
  4219. },
  4220. /**
  4221. * Produces a hash token based on the specified information. The
  4222. * first character provided is the pound sign (#) and then this
  4223. * method reads a name afterward.
  4224. * @param {String} first The first character (#) in the hash name.
  4225. * @param {int} startLine The beginning line for the character.
  4226. * @param {int} startCol The beginning column for the character.
  4227. * @return {Object} A token object.
  4228. * @method hashToken
  4229. */
  4230. hashToken: function(first, startLine, startCol){
  4231. var reader = this._reader,
  4232. name = this.readName(first);
  4233. return this.createToken(Tokens.HASH, name, startLine, startCol);
  4234. },
  4235. /**
  4236. * Produces a CDO or CHAR token based on the specified information. The
  4237. * first character is provided and the rest is read by the function to determine
  4238. * the correct token to create.
  4239. * @param {String} first The first character in the token.
  4240. * @param {int} startLine The beginning line for the character.
  4241. * @param {int} startCol The beginning column for the character.
  4242. * @return {Object} A token object.
  4243. * @method htmlCommentStartToken
  4244. */
  4245. htmlCommentStartToken: function(first, startLine, startCol){
  4246. var reader = this._reader,
  4247. text = first;
  4248. reader.mark();
  4249. text += reader.readCount(3);
  4250. if (text == "<!--"){
  4251. return this.createToken(Tokens.CDO, text, startLine, startCol);
  4252. } else {
  4253. reader.reset();
  4254. return this.charToken(first, startLine, startCol);
  4255. }
  4256. },
  4257. /**
  4258. * Produces a CDC or CHAR token based on the specified information. The
  4259. * first character is provided and the rest is read by the function to determine
  4260. * the correct token to create.
  4261. * @param {String} first The first character in the token.
  4262. * @param {int} startLine The beginning line for the character.
  4263. * @param {int} startCol The beginning column for the character.
  4264. * @return {Object} A token object.
  4265. * @method htmlCommentEndToken
  4266. */
  4267. htmlCommentEndToken: function(first, startLine, startCol){
  4268. var reader = this._reader,
  4269. text = first;
  4270. reader.mark();
  4271. text += reader.readCount(2);
  4272. if (text == "-->"){
  4273. return this.createToken(Tokens.CDC, text, startLine, startCol);
  4274. } else {
  4275. reader.reset();
  4276. return this.charToken(first, startLine, startCol);
  4277. }
  4278. },
  4279. /**
  4280. * Produces an IDENT or FUNCTION token based on the specified information. The
  4281. * first character is provided and the rest is read by the function to determine
  4282. * the correct token to create.
  4283. * @param {String} first The first character in the identifier.
  4284. * @param {int} startLine The beginning line for the character.
  4285. * @param {int} startCol The beginning column for the character.
  4286. * @return {Object} A token object.
  4287. * @method identOrFunctionToken
  4288. */
  4289. identOrFunctionToken: function(first, startLine, startCol){
  4290. var reader = this._reader,
  4291. ident = this.readName(first),
  4292. tt = Tokens.IDENT;
  4293. //if there's a left paren immediately after, it's a URI or function
  4294. if (reader.peek() == "("){
  4295. ident += reader.read();
  4296. if (ident.toLowerCase() == "url("){
  4297. tt = Tokens.URI;
  4298. ident = this.readURI(ident);
  4299. //didn't find a valid URL or there's no closing paren
  4300. if (ident.toLowerCase() == "url("){
  4301. tt = Tokens.FUNCTION;
  4302. }
  4303. } else {
  4304. tt = Tokens.FUNCTION;
  4305. }
  4306. } else if (reader.peek() == ":"){ //might be an IE function
  4307. //IE-specific functions always being with progid:
  4308. if (ident.toLowerCase() == "progid"){
  4309. ident += reader.readTo("(");
  4310. tt = Tokens.IE_FUNCTION;
  4311. }
  4312. }
  4313. return this.createToken(tt, ident, startLine, startCol);
  4314. },
  4315. /**
  4316. * Produces an IMPORTANT_SYM or CHAR token based on the specified information. The
  4317. * first character is provided and the rest is read by the function to determine
  4318. * the correct token to create.
  4319. * @param {String} first The first character in the token.
  4320. * @param {int} startLine The beginning line for the character.
  4321. * @param {int} startCol The beginning column for the character.
  4322. * @return {Object} A token object.
  4323. * @method importantToken
  4324. */
  4325. importantToken: function(first, startLine, startCol){
  4326. var reader = this._reader,
  4327. important = first,
  4328. tt = Tokens.CHAR,
  4329. temp,
  4330. c;
  4331. reader.mark();
  4332. c = reader.read();
  4333. while(c){
  4334. //there can be a comment in here
  4335. if (c == "/"){
  4336. //if the next character isn't a star, then this isn't a valid !important token
  4337. if (reader.peek() != "*"){
  4338. break;
  4339. } else {
  4340. temp = this.readComment(c);
  4341. if (temp === ""){ //broken!
  4342. break;
  4343. }
  4344. }
  4345. } else if (isWhitespace(c)){
  4346. important += c + this.readWhitespace();
  4347. } else if (/i/i.test(c)){
  4348. temp = reader.readCount(8);
  4349. if (/mportant/i.test(temp)){
  4350. important += c + temp;
  4351. tt = Tokens.IMPORTANT_SYM;
  4352. }
  4353. break; //we're done
  4354. } else {
  4355. break;
  4356. }
  4357. c = reader.read();
  4358. }
  4359. if (tt == Tokens.CHAR){
  4360. reader.reset();
  4361. return this.charToken(first, startLine, startCol);
  4362. } else {
  4363. return this.createToken(tt, important, startLine, startCol);
  4364. }
  4365. },
  4366. /**
  4367. * Produces a NOT or CHAR token based on the specified information. The
  4368. * first character is provided and the rest is read by the function to determine
  4369. * the correct token to create.
  4370. * @param {String} first The first character in the token.
  4371. * @param {int} startLine The beginning line for the character.
  4372. * @param {int} startCol The beginning column for the character.
  4373. * @return {Object} A token object.
  4374. * @method notToken
  4375. */
  4376. notToken: function(first, startLine, startCol){
  4377. var reader = this._reader,
  4378. text = first;
  4379. reader.mark();
  4380. text += reader.readCount(4);
  4381. if (text.toLowerCase() == ":not("){
  4382. return this.createToken(Tokens.NOT, text, startLine, startCol);
  4383. } else {
  4384. reader.reset();
  4385. return this.charToken(first, startLine, startCol);
  4386. }
  4387. },
  4388. /**
  4389. * Produces a number token based on the given character
  4390. * and location in the stream. This may return a token of
  4391. * NUMBER, EMS, EXS, LENGTH, ANGLE, TIME, FREQ, DIMENSION,
  4392. * or PERCENTAGE.
  4393. * @param {String} first The first character for the token.
  4394. * @param {int} startLine The beginning line for the character.
  4395. * @param {int} startCol The beginning column for the character.
  4396. * @return {Object} A token object.
  4397. * @method numberToken
  4398. */
  4399. numberToken: function(first, startLine, startCol){
  4400. var reader = this._reader,
  4401. value = this.readNumber(first),
  4402. ident,
  4403. tt = Tokens.NUMBER,
  4404. c = reader.peek();
  4405. if (isIdentStart(c)){
  4406. ident = this.readName(reader.read());
  4407. value += ident;
  4408. if (/^em$|^ex$|^px$|^gd$|^rem$|^vw$|^vh$|^vm$|^ch$|^cm$|^mm$|^in$|^pt$|^pc$/i.test(ident)){
  4409. tt = Tokens.LENGTH;
  4410. } else if (/^deg|^rad$|^grad$/i.test(ident)){
  4411. tt = Tokens.ANGLE;
  4412. } else if (/^ms$|^s$/i.test(ident)){
  4413. tt = Tokens.TIME;
  4414. } else if (/^hz$|^khz$/i.test(ident)){
  4415. tt = Tokens.FREQ;
  4416. } else if (/^dpi$|^dpcm$/i.test(ident)){
  4417. tt = Tokens.RESOLUTION;
  4418. } else {
  4419. tt = Tokens.DIMENSION;
  4420. }
  4421. } else if (c == "%"){
  4422. value += reader.read();
  4423. tt = Tokens.PERCENTAGE;
  4424. }
  4425. return this.createToken(tt, value, startLine, startCol);
  4426. },
  4427. /**
  4428. * Produces a string token based on the given character
  4429. * and location in the stream. Since strings may be indicated
  4430. * by single or double quotes, a failure to match starting
  4431. * and ending quotes results in an INVALID token being generated.
  4432. * The first character in the string is passed in and then
  4433. * the rest are read up to and including the final quotation mark.
  4434. * @param {String} first The first character in the string.
  4435. * @param {int} startLine The beginning line for the character.
  4436. * @param {int} startCol The beginning column for the character.
  4437. * @return {Object} A token object.
  4438. * @method stringToken
  4439. */
  4440. stringToken: function(first, startLine, startCol){
  4441. var delim = first,
  4442. string = first,
  4443. reader = this._reader,
  4444. prev = first,
  4445. tt = Tokens.STRING,
  4446. c = reader.read();
  4447. while(c){
  4448. string += c;
  4449. //if the delimiter is found with an escapement, we're done.
  4450. if (c == delim && prev != "\\"){
  4451. break;
  4452. }
  4453. //if there's a newline without an escapement, it's an invalid string
  4454. if (isNewLine(reader.peek()) && c != "\\"){
  4455. tt = Tokens.INVALID;
  4456. break;
  4457. }
  4458. //save previous and get next
  4459. prev = c;
  4460. c = reader.read();
  4461. }
  4462. //if c is null, that means we're out of input and the string was never closed
  4463. if (c === null){
  4464. tt = Tokens.INVALID;
  4465. }
  4466. return this.createToken(tt, string, startLine, startCol);
  4467. },
  4468. unicodeRangeToken: function(first, startLine, startCol){
  4469. var reader = this._reader,
  4470. value = first,
  4471. temp,
  4472. tt = Tokens.CHAR;
  4473. //then it should be a unicode range
  4474. if (reader.peek() == "+"){
  4475. reader.mark();
  4476. value += reader.read();
  4477. value += this.readUnicodeRangePart(true);
  4478. //ensure there's an actual unicode range here
  4479. if (value.length == 2){
  4480. reader.reset();
  4481. } else {
  4482. tt = Tokens.UNICODE_RANGE;
  4483. //if there's a ? in the first part, there can't be a second part
  4484. if (value.indexOf("?") == -1){
  4485. if (reader.peek() == "-"){
  4486. reader.mark();
  4487. temp = reader.read();
  4488. temp += this.readUnicodeRangePart(false);
  4489. //if there's not another value, back up and just take the first
  4490. if (temp.length == 1){
  4491. reader.reset();
  4492. } else {
  4493. value += temp;
  4494. }
  4495. }
  4496. }
  4497. }
  4498. }
  4499. return this.createToken(tt, value, startLine, startCol);
  4500. },
  4501. /**
  4502. * Produces a S token based on the specified information. Since whitespace
  4503. * may have multiple characters, this consumes all whitespace characters
  4504. * into a single token.
  4505. * @param {String} first The first character in the token.
  4506. * @param {int} startLine The beginning line for the character.
  4507. * @param {int} startCol The beginning column for the character.
  4508. * @return {Object} A token object.
  4509. * @method whitespaceToken
  4510. */
  4511. whitespaceToken: function(first, startLine, startCol){
  4512. var reader = this._reader,
  4513. value = first + this.readWhitespace();
  4514. return this.createToken(Tokens.S, value, startLine, startCol);
  4515. },
  4516. //-------------------------------------------------------------------------
  4517. // Methods to read values from the string stream
  4518. //-------------------------------------------------------------------------
  4519. readUnicodeRangePart: function(allowQuestionMark){
  4520. var reader = this._reader,
  4521. part = "",
  4522. c = reader.peek();
  4523. //first read hex digits
  4524. while(isHexDigit(c) && part.length < 6){
  4525. reader.read();
  4526. part += c;
  4527. c = reader.peek();
  4528. }
  4529. //then read question marks if allowed
  4530. if (allowQuestionMark){
  4531. while(c == "?" && part.length < 6){
  4532. reader.read();
  4533. part += c;
  4534. c = reader.peek();
  4535. }
  4536. }
  4537. //there can't be any other characters after this point
  4538. return part;
  4539. },
  4540. readWhitespace: function(){
  4541. var reader = this._reader,
  4542. whitespace = "",
  4543. c = reader.peek();
  4544. while(isWhitespace(c)){
  4545. reader.read();
  4546. whitespace += c;
  4547. c = reader.peek();
  4548. }
  4549. return whitespace;
  4550. },
  4551. readNumber: function(first){
  4552. var reader = this._reader,
  4553. number = first,
  4554. hasDot = (first == "."),
  4555. c = reader.peek();
  4556. while(c){
  4557. if (isDigit(c)){
  4558. number += reader.read();
  4559. } else if (c == "."){
  4560. if (hasDot){
  4561. break;
  4562. } else {
  4563. hasDot = true;
  4564. number += reader.read();
  4565. }
  4566. } else {
  4567. break;
  4568. }
  4569. c = reader.peek();
  4570. }
  4571. return number;
  4572. },
  4573. readString: function(){
  4574. var reader = this._reader,
  4575. delim = reader.read(),
  4576. string = delim,
  4577. prev = delim,
  4578. c = reader.peek();
  4579. while(c){
  4580. c = reader.read();
  4581. string += c;
  4582. //if the delimiter is found with an escapement, we're done.
  4583. if (c == delim && prev != "\\"){
  4584. break;
  4585. }
  4586. //if there's a newline without an escapement, it's an invalid string
  4587. if (isNewLine(reader.peek()) && c != "\\"){
  4588. string = "";
  4589. break;
  4590. }
  4591. //save previous and get next
  4592. prev = c;
  4593. c = reader.peek();
  4594. }
  4595. //if c is null, that means we're out of input and the string was never closed
  4596. if (c === null){
  4597. string = "";
  4598. }
  4599. return string;
  4600. },
  4601. readURI: function(first){
  4602. var reader = this._reader,
  4603. uri = first,
  4604. inner = "",
  4605. c = reader.peek();
  4606. reader.mark();
  4607. //skip whitespace before
  4608. while(c && isWhitespace(c)){
  4609. reader.read();
  4610. c = reader.peek();
  4611. }
  4612. //it's a string
  4613. if (c == "'" || c == "\""){
  4614. inner = this.readString();
  4615. } else {
  4616. inner = this.readURL();
  4617. }
  4618. c = reader.peek();
  4619. //skip whitespace after
  4620. while(c && isWhitespace(c)){
  4621. reader.read();
  4622. c = reader.peek();
  4623. }
  4624. //if there was no inner value or the next character isn't closing paren, it's not a URI
  4625. if (inner === "" || c != ")"){
  4626. uri = first;
  4627. reader.reset();
  4628. } else {
  4629. uri += inner + reader.read();
  4630. }
  4631. return uri;
  4632. },
  4633. readURL: function(){
  4634. var reader = this._reader,
  4635. url = "",
  4636. c = reader.peek();
  4637. //TODO: Check for escape and nonascii
  4638. while (/^[!#$%&\\*-~]$/.test(c)){
  4639. url += reader.read();
  4640. c = reader.peek();
  4641. }
  4642. return url;
  4643. },
  4644. readName: function(first){
  4645. var reader = this._reader,
  4646. ident = first || "",
  4647. c = reader.peek();
  4648. while(true){
  4649. if (c == "\\"){
  4650. ident += this.readEscape(reader.read());
  4651. c = reader.peek();
  4652. } else if(c && isNameChar(c)){
  4653. ident += reader.read();
  4654. c = reader.peek();
  4655. } else {
  4656. break;
  4657. }
  4658. }
  4659. return ident;
  4660. },
  4661. readEscape: function(first){
  4662. var reader = this._reader,
  4663. cssEscape = first || "",
  4664. i = 0,
  4665. c = reader.peek();
  4666. if (isHexDigit(c)){
  4667. do {
  4668. cssEscape += reader.read();
  4669. c = reader.peek();
  4670. } while(c && isHexDigit(c) && ++i < 6);
  4671. }
  4672. if (cssEscape.length == 3 && /\s/.test(c) ||
  4673. cssEscape.length == 7 || cssEscape.length == 1){
  4674. reader.read();
  4675. } else {
  4676. c = "";
  4677. }
  4678. return cssEscape + c;
  4679. },
  4680. readComment: function(first){
  4681. var reader = this._reader,
  4682. comment = first || "",
  4683. c = reader.read();
  4684. if (c == "*"){
  4685. while(c){
  4686. comment += c;
  4687. //look for end of comment
  4688. if (comment.length > 2 && c == "*" && reader.peek() == "/"){
  4689. comment += reader.read();
  4690. break;
  4691. }
  4692. c = reader.read();
  4693. }
  4694. return comment;
  4695. } else {
  4696. return "";
  4697. }
  4698. }
  4699. });
  4700. var Tokens = [
  4701. /*
  4702. * The following token names are defined in CSS3 Grammar: http://www.w3.org/TR/css3-syntax/#lexical
  4703. */
  4704. //HTML-style comments
  4705. { name: "CDO"},
  4706. { name: "CDC"},
  4707. //ignorables
  4708. { name: "S", whitespace: true/*, channel: "ws"*/},
  4709. { name: "COMMENT", comment: true, hide: true, channel: "comment" },
  4710. //attribute equality
  4711. { name: "INCLUDES", text: "~="},
  4712. { name: "DASHMATCH", text: "|="},
  4713. { name: "PREFIXMATCH", text: "^="},
  4714. { name: "SUFFIXMATCH", text: "$="},
  4715. { name: "SUBSTRINGMATCH", text: "*="},
  4716. //identifier types
  4717. { name: "STRING"},
  4718. { name: "IDENT"},
  4719. { name: "HASH"},
  4720. //at-keywords
  4721. { name: "IMPORT_SYM", text: "@import"},
  4722. { name: "PAGE_SYM", text: "@page"},
  4723. { name: "MEDIA_SYM", text: "@media"},
  4724. { name: "FONT_FACE_SYM", text: "@font-face"},
  4725. { name: "CHARSET_SYM", text: "@charset"},
  4726. { name: "NAMESPACE_SYM", text: "@namespace"},
  4727. { name: "UNKNOWN_SYM" },
  4728. //{ name: "ATKEYWORD"},
  4729. //CSS3 animations
  4730. { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes", "@-o-keyframes" ] },
  4731. //important symbol
  4732. { name: "IMPORTANT_SYM"},
  4733. //measurements
  4734. { name: "LENGTH"},
  4735. { name: "ANGLE"},
  4736. { name: "TIME"},
  4737. { name: "FREQ"},
  4738. { name: "DIMENSION"},
  4739. { name: "PERCENTAGE"},
  4740. { name: "NUMBER"},
  4741. //functions
  4742. { name: "URI"},
  4743. { name: "FUNCTION"},
  4744. //Unicode ranges
  4745. { name: "UNICODE_RANGE"},
  4746. /*
  4747. * The following token names are defined in CSS3 Selectors: http://www.w3.org/TR/css3-selectors/#selector-syntax
  4748. */
  4749. //invalid string
  4750. { name: "INVALID"},
  4751. //combinators
  4752. { name: "PLUS", text: "+" },
  4753. { name: "GREATER", text: ">"},
  4754. { name: "COMMA", text: ","},
  4755. { name: "TILDE", text: "~"},
  4756. //modifier
  4757. { name: "NOT"},
  4758. /*
  4759. * Defined in CSS3 Paged Media
  4760. */
  4761. { name: "TOPLEFTCORNER_SYM", text: "@top-left-corner"},
  4762. { name: "TOPLEFT_SYM", text: "@top-left"},
  4763. { name: "TOPCENTER_SYM", text: "@top-center"},
  4764. { name: "TOPRIGHT_SYM", text: "@top-right"},
  4765. { name: "TOPRIGHTCORNER_SYM", text: "@top-right-corner"},
  4766. { name: "BOTTOMLEFTCORNER_SYM", text: "@bottom-left-corner"},
  4767. { name: "BOTTOMLEFT_SYM", text: "@bottom-left"},
  4768. { name: "BOTTOMCENTER_SYM", text: "@bottom-center"},
  4769. { name: "BOTTOMRIGHT_SYM", text: "@bottom-right"},
  4770. { name: "BOTTOMRIGHTCORNER_SYM", text: "@bottom-right-corner"},
  4771. { name: "LEFTTOP_SYM", text: "@left-top"},
  4772. { name: "LEFTMIDDLE_SYM", text: "@left-middle"},
  4773. { name: "LEFTBOTTOM_SYM", text: "@left-bottom"},
  4774. { name: "RIGHTTOP_SYM", text: "@right-top"},
  4775. { name: "RIGHTMIDDLE_SYM", text: "@right-middle"},
  4776. { name: "RIGHTBOTTOM_SYM", text: "@right-bottom"},
  4777. /*
  4778. * The following token names are defined in CSS3 Media Queries: http://www.w3.org/TR/css3-mediaqueries/#syntax
  4779. */
  4780. /*{ name: "MEDIA_ONLY", state: "media"},
  4781. { name: "MEDIA_NOT", state: "media"},
  4782. { name: "MEDIA_AND", state: "media"},*/
  4783. { name: "RESOLUTION", state: "media"},
  4784. /*
  4785. * The following token names are not defined in any CSS specification but are used by the lexer.
  4786. */
  4787. //not a real token, but useful for stupid IE filters
  4788. { name: "IE_FUNCTION" },
  4789. //part of CSS3 grammar but not the Flex code
  4790. { name: "CHAR" },
  4791. //TODO: Needed?
  4792. //Not defined as tokens, but might as well be
  4793. {
  4794. name: "PIPE",
  4795. text: "|"
  4796. },
  4797. {
  4798. name: "SLASH",
  4799. text: "/"
  4800. },
  4801. {
  4802. name: "MINUS",
  4803. text: "-"
  4804. },
  4805. {
  4806. name: "STAR",
  4807. text: "*"
  4808. },
  4809. {
  4810. name: "LBRACE",
  4811. text: "{"
  4812. },
  4813. {
  4814. name: "RBRACE",
  4815. text: "}"
  4816. },
  4817. {
  4818. name: "LBRACKET",
  4819. text: "["
  4820. },
  4821. {
  4822. name: "RBRACKET",
  4823. text: "]"
  4824. },
  4825. {
  4826. name: "EQUALS",
  4827. text: "="
  4828. },
  4829. {
  4830. name: "COLON",
  4831. text: ":"
  4832. },
  4833. {
  4834. name: "SEMICOLON",
  4835. text: ";"
  4836. },
  4837. {
  4838. name: "LPAREN",
  4839. text: "("
  4840. },
  4841. {
  4842. name: "RPAREN",
  4843. text: ")"
  4844. },
  4845. {
  4846. name: "DOT",
  4847. text: "."
  4848. }
  4849. ];
  4850. (function(){
  4851. var nameMap = [],
  4852. typeMap = {};
  4853. Tokens.UNKNOWN = -1;
  4854. Tokens.unshift({name:"EOF"});
  4855. for (var i=0, len = Tokens.length; i < len; i++){
  4856. nameMap.push(Tokens[i].name);
  4857. Tokens[Tokens[i].name] = i;
  4858. if (Tokens[i].text){
  4859. if (Tokens[i].text instanceof Array){
  4860. for (var j=0; j < Tokens[i].text.length; j++){
  4861. typeMap[Tokens[i].text[j]] = i;
  4862. }
  4863. } else {
  4864. typeMap[Tokens[i].text] = i;
  4865. }
  4866. }
  4867. }
  4868. Tokens.name = function(tt){
  4869. return nameMap[tt];
  4870. };
  4871. Tokens.type = function(c){
  4872. return typeMap[c] || -1;
  4873. };
  4874. })();
  4875. //This file will likely change a lot! Very experimental!
  4876. /*global Properties, ValidationTypes, ValidationError, PropertyValueIterator */
  4877. var Validation = {
  4878. validate: function(property, value){
  4879. //normalize name
  4880. var name = property.toString().toLowerCase(),
  4881. parts = value.parts,
  4882. expression = new PropertyValueIterator(value),
  4883. spec = Properties[name],
  4884. part,
  4885. valid,
  4886. j, count,
  4887. msg,
  4888. types,
  4889. last,
  4890. literals,
  4891. max, multi, group;
  4892. if (!spec) {
  4893. if (name.indexOf("-") !== 0){ //vendor prefixed are ok
  4894. throw new ValidationError("Unknown property '" + property + "'.", property.line, property.col);
  4895. }
  4896. } else if (typeof spec != "number"){
  4897. //initialization
  4898. if (typeof spec == "string"){
  4899. if (spec.indexOf("||") > -1) {
  4900. this.groupProperty(spec, expression);
  4901. } else {
  4902. this.singleProperty(spec, expression, 1);
  4903. }
  4904. } else if (spec.multi) {
  4905. this.multiProperty(spec.multi, expression, spec.comma, spec.max || Infinity);
  4906. } else if (typeof spec == "function") {
  4907. spec(expression);
  4908. }
  4909. }
  4910. },
  4911. singleProperty: function(types, expression, max, partial) {
  4912. var result = false,
  4913. value = expression.value,
  4914. count = 0,
  4915. part;
  4916. while (expression.hasNext() && count < max) {
  4917. result = ValidationTypes.isAny(expression, types);
  4918. if (!result) {
  4919. break;
  4920. }
  4921. count++;
  4922. }
  4923. if (!result) {
  4924. if (expression.hasNext() && !expression.isFirst()) {
  4925. part = expression.peek();
  4926. throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
  4927. } else {
  4928. throw new ValidationError("Expected (" + types + ") but found '" + value + "'.", value.line, value.col);
  4929. }
  4930. } else if (expression.hasNext()) {
  4931. part = expression.next();
  4932. throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
  4933. }
  4934. },
  4935. multiProperty: function (types, expression, comma, max) {
  4936. var result = false,
  4937. value = expression.value,
  4938. count = 0,
  4939. sep = false,
  4940. part;
  4941. while(expression.hasNext() && !result && count < max) {
  4942. if (ValidationTypes.isAny(expression, types)) {
  4943. count++;
  4944. if (!expression.hasNext()) {
  4945. result = true;
  4946. } else if (comma) {
  4947. if (expression.peek() == ",") {
  4948. part = expression.next();
  4949. } else {
  4950. break;
  4951. }
  4952. }
  4953. } else {
  4954. break;
  4955. }
  4956. }
  4957. if (!result) {
  4958. if (expression.hasNext() && !expression.isFirst()) {
  4959. part = expression.peek();
  4960. throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
  4961. } else {
  4962. part = expression.previous();
  4963. if (comma && part == ",") {
  4964. throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
  4965. } else {
  4966. throw new ValidationError("Expected (" + types + ") but found '" + value + "'.", value.line, value.col);
  4967. }
  4968. }
  4969. } else if (expression.hasNext()) {
  4970. part = expression.next();
  4971. throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
  4972. }
  4973. },
  4974. groupProperty: function (types, expression, comma) {
  4975. var result = false,
  4976. value = expression.value,
  4977. typeCount = types.split("||").length,
  4978. groups = { count: 0 },
  4979. partial = false,
  4980. name,
  4981. part;
  4982. while(expression.hasNext() && !result) {
  4983. name = ValidationTypes.isAnyOfGroup(expression, types);
  4984. if (name) {
  4985. //no dupes
  4986. if (groups[name]) {
  4987. break;
  4988. } else {
  4989. groups[name] = 1;
  4990. groups.count++;
  4991. partial = true;
  4992. if (groups.count == typeCount || !expression.hasNext()) {
  4993. result = true;
  4994. }
  4995. }
  4996. } else {
  4997. break;
  4998. }
  4999. }
  5000. if (!result) {
  5001. if (partial && expression.hasNext()) {
  5002. part = expression.peek();
  5003. throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
  5004. } else {
  5005. throw new ValidationError("Expected (" + types + ") but found '" + value + "'.", value.line, value.col);
  5006. }
  5007. } else if (expression.hasNext()) {
  5008. part = expression.next();
  5009. throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
  5010. }
  5011. }
  5012. };
  5013. /**
  5014. * Type to use when a validation error occurs.
  5015. * @class ValidationError
  5016. * @namespace parserlib.util
  5017. * @constructor
  5018. * @param {String} message The error message.
  5019. * @param {int} line The line at which the error occurred.
  5020. * @param {int} col The column at which the error occurred.
  5021. */
  5022. function ValidationError(message, line, col){
  5023. /**
  5024. * The column at which the error occurred.
  5025. * @type int
  5026. * @property col
  5027. */
  5028. this.col = col;
  5029. /**
  5030. * The line at which the error occurred.
  5031. * @type int
  5032. * @property line
  5033. */
  5034. this.line = line;
  5035. /**
  5036. * The text representation of the unit.
  5037. * @type String
  5038. * @property text
  5039. */
  5040. this.message = message;
  5041. }
  5042. //inherit from Error
  5043. ValidationError.prototype = new Error();
  5044. //This file will likely change a lot! Very experimental!
  5045. /*global Properties, Validation, ValidationError, PropertyValueIterator, console*/
  5046. var ValidationTypes = {
  5047. isLiteral: function (part, literals) {
  5048. var text = part.text.toString().toLowerCase(),
  5049. args = literals.split(" | "),
  5050. i, len, found = false;
  5051. for (i=0,len=args.length; i < len && !found; i++){
  5052. if (text == args[i].toLowerCase()){
  5053. found = true;
  5054. }
  5055. }
  5056. return found;
  5057. },
  5058. isSimple: function(type) {
  5059. return !!this.simple[type];
  5060. },
  5061. isComplex: function(type) {
  5062. return !!this.complex[type];
  5063. },
  5064. /**
  5065. * Determines if the next part(s) of the given expression
  5066. * are any of the given types.
  5067. */
  5068. isAny: function (expression, types) {
  5069. var args = types.split(" | "),
  5070. i, len, found = false;
  5071. for (i=0,len=args.length; i < len && !found && expression.hasNext(); i++){
  5072. found = this.isType(expression, args[i]);
  5073. }
  5074. return found;
  5075. },
  5076. /**
  5077. * Determines if the next part(s) of the given expression
  5078. * are one of a group.
  5079. */
  5080. isAnyOfGroup: function(expression, types) {
  5081. var args = types.split(" || "),
  5082. i, len, found = false;
  5083. for (i=0,len=args.length; i < len && !found; i++){
  5084. found = this.isType(expression, args[i]);
  5085. }
  5086. return found ? args[i-1] : false;
  5087. },
  5088. /**
  5089. * Determines if the next part(s) of the given expression
  5090. * are of a given type.
  5091. */
  5092. isType: function (expression, type) {
  5093. var part = expression.peek(),
  5094. result = false;
  5095. if (type.charAt(0) != "<") {
  5096. result = this.isLiteral(part, type);
  5097. if (result) {
  5098. expression.next();
  5099. }
  5100. } else if (this.simple[type]) {
  5101. result = this.simple[type](part);
  5102. if (result) {
  5103. expression.next();
  5104. }
  5105. } else {
  5106. result = this.complex[type](expression);
  5107. }
  5108. return result;
  5109. },
  5110. simple: {
  5111. "<absolute-size>": function(part){
  5112. return ValidationTypes.isLiteral(part, "xx-small | x-small | small | medium | large | x-large | xx-large");
  5113. },
  5114. "<attachment>": function(part){
  5115. return ValidationTypes.isLiteral(part, "scroll | fixed | local");
  5116. },
  5117. "<attr>": function(part){
  5118. return part.type == "function" && part.name == "attr";
  5119. },
  5120. "<bg-image>": function(part){
  5121. return this["<image>"](part) || this["<gradient>"](part) || part == "none";
  5122. },
  5123. "<gradient>": function(part) {
  5124. return part.type == "function" && /^(?:\-(?:ms|moz|o|webkit)\-)?(?:repeating\-)?(?:radial\-|linear\-)?gradient/i.test(part);
  5125. },
  5126. "<box>": function(part){
  5127. return ValidationTypes.isLiteral(part, "padding-box | border-box | content-box");
  5128. },
  5129. "<content>": function(part){
  5130. return part.type == "function" && part.name == "content";
  5131. },
  5132. "<relative-size>": function(part){
  5133. return ValidationTypes.isLiteral(part, "smaller | larger");
  5134. },
  5135. //any identifier
  5136. "<ident>": function(part){
  5137. return part.type == "identifier";
  5138. },
  5139. "<length>": function(part){
  5140. if (part.type == "function" && /^(?:\-(?:ms|moz|o|webkit)\-)?calc/i.test(part)){
  5141. return true;
  5142. }else{
  5143. return part.type == "length" || part.type == "number" || part.type == "integer" || part == "0";
  5144. }
  5145. },
  5146. "<color>": function(part){
  5147. return part.type == "color" || part == "transparent";
  5148. },
  5149. "<number>": function(part){
  5150. return part.type == "number" || this["<integer>"](part);
  5151. },
  5152. "<integer>": function(part){
  5153. return part.type == "integer";
  5154. },
  5155. "<line>": function(part){
  5156. return part.type == "integer";
  5157. },
  5158. "<angle>": function(part){
  5159. return part.type == "angle";
  5160. },
  5161. "<uri>": function(part){
  5162. return part.type == "uri";
  5163. },
  5164. "<image>": function(part){
  5165. return this["<uri>"](part);
  5166. },
  5167. "<percentage>": function(part){
  5168. return part.type == "percentage" || part == "0";
  5169. },
  5170. "<border-width>": function(part){
  5171. return this["<length>"](part) || ValidationTypes.isLiteral(part, "thin | medium | thick");
  5172. },
  5173. "<border-style>": function(part){
  5174. return ValidationTypes.isLiteral(part, "none | hidden | dotted | dashed | solid | double | groove | ridge | inset | outset");
  5175. },
  5176. "<margin-width>": function(part){
  5177. return this["<length>"](part) || this["<percentage>"](part) || ValidationTypes.isLiteral(part, "auto");
  5178. },
  5179. "<padding-width>": function(part){
  5180. return this["<length>"](part) || this["<percentage>"](part);
  5181. },
  5182. "<shape>": function(part){
  5183. return part.type == "function" && (part.name == "rect" || part.name == "inset-rect");
  5184. },
  5185. "<time>": function(part) {
  5186. return part.type == "time";
  5187. }
  5188. },
  5189. complex: {
  5190. "<bg-position>": function(expression){
  5191. var types = this,
  5192. result = false,
  5193. numeric = "<percentage> | <length>",
  5194. xDir = "left | right",
  5195. yDir = "top | bottom",
  5196. count = 0,
  5197. hasNext = function() {
  5198. return expression.hasNext() && expression.peek() != ",";
  5199. };
  5200. while (expression.peek(count) && expression.peek(count) != ",") {
  5201. count++;
  5202. }
  5203. /*
  5204. <position> = [
  5205. [ left | center | right | top | bottom | <percentage> | <length> ]
  5206. |
  5207. [ left | center | right | <percentage> | <length> ]
  5208. [ top | center | bottom | <percentage> | <length> ]
  5209. |
  5210. [ center | [ left | right ] [ <percentage> | <length> ]? ] &&
  5211. [ center | [ top | bottom ] [ <percentage> | <length> ]? ]
  5212. ]
  5213. */
  5214. if (count < 3) {
  5215. if (ValidationTypes.isAny(expression, xDir + " | center | " + numeric)) {
  5216. result = true;
  5217. ValidationTypes.isAny(expression, yDir + " | center | " + numeric);
  5218. } else if (ValidationTypes.isAny(expression, yDir)) {
  5219. result = true;
  5220. ValidationTypes.isAny(expression, xDir + " | center");
  5221. }
  5222. } else {
  5223. if (ValidationTypes.isAny(expression, xDir)) {
  5224. if (ValidationTypes.isAny(expression, yDir)) {
  5225. result = true;
  5226. ValidationTypes.isAny(expression, numeric);
  5227. } else if (ValidationTypes.isAny(expression, numeric)) {
  5228. if (ValidationTypes.isAny(expression, yDir)) {
  5229. result = true;
  5230. ValidationTypes.isAny(expression, numeric);
  5231. } else if (ValidationTypes.isAny(expression, "center")) {
  5232. result = true;
  5233. }
  5234. }
  5235. } else if (ValidationTypes.isAny(expression, yDir)) {
  5236. if (ValidationTypes.isAny(expression, xDir)) {
  5237. result = true;
  5238. ValidationTypes.isAny(expression, numeric);
  5239. } else if (ValidationTypes.isAny(expression, numeric)) {
  5240. if (ValidationTypes.isAny(expression, xDir)) {
  5241. result = true;
  5242. ValidationTypes.isAny(expression, numeric);
  5243. } else if (ValidationTypes.isAny(expression, "center")) {
  5244. result = true;
  5245. }
  5246. }
  5247. } else if (ValidationTypes.isAny(expression, "center")) {
  5248. if (ValidationTypes.isAny(expression, xDir + " | " + yDir)) {
  5249. result = true;
  5250. ValidationTypes.isAny(expression, numeric);
  5251. }
  5252. }
  5253. }
  5254. return result;
  5255. },
  5256. "<bg-size>": function(expression){
  5257. //<bg-size> = [ <length> | <percentage> | auto ]{1,2} | cover | contain
  5258. var types = this,
  5259. result = false,
  5260. numeric = "<percentage> | <length> | auto",
  5261. part,
  5262. i, len;
  5263. if (ValidationTypes.isAny(expression, "cover | contain")) {
  5264. result = true;
  5265. } else if (ValidationTypes.isAny(expression, numeric)) {
  5266. result = true;
  5267. ValidationTypes.isAny(expression, numeric);
  5268. }
  5269. return result;
  5270. },
  5271. "<repeat-style>": function(expression){
  5272. //repeat-x | repeat-y | [repeat | space | round | no-repeat]{1,2}
  5273. var result = false,
  5274. values = "repeat | space | round | no-repeat",
  5275. part;
  5276. if (expression.hasNext()){
  5277. part = expression.next();
  5278. if (ValidationTypes.isLiteral(part, "repeat-x | repeat-y")) {
  5279. result = true;
  5280. } else if (ValidationTypes.isLiteral(part, values)) {
  5281. result = true;
  5282. if (expression.hasNext() && ValidationTypes.isLiteral(expression.peek(), values)) {
  5283. expression.next();
  5284. }
  5285. }
  5286. }
  5287. return result;
  5288. },
  5289. "<shadow>": function(expression) {
  5290. //inset? && [ <length>{2,4} && <color>? ]
  5291. var result = false,
  5292. count = 0,
  5293. inset = false,
  5294. color = false,
  5295. part;
  5296. if (expression.hasNext()) {
  5297. if (ValidationTypes.isAny(expression, "inset")){
  5298. inset = true;
  5299. }
  5300. if (ValidationTypes.isAny(expression, "<color>")) {
  5301. color = true;
  5302. }
  5303. while (ValidationTypes.isAny(expression, "<length>") && count < 4) {
  5304. count++;
  5305. }
  5306. if (expression.hasNext()) {
  5307. if (!color) {
  5308. ValidationTypes.isAny(expression, "<color>");
  5309. }
  5310. if (!inset) {
  5311. ValidationTypes.isAny(expression, "inset");
  5312. }
  5313. }
  5314. result = (count >= 2 && count <= 4);
  5315. }
  5316. return result;
  5317. },
  5318. "<x-one-radius>": function(expression) {
  5319. //[ <length> | <percentage> ] [ <length> | <percentage> ]?
  5320. var result = false,
  5321. count = 0,
  5322. numeric = "<length> | <percentage>",
  5323. part;
  5324. if (ValidationTypes.isAny(expression, numeric)){
  5325. result = true;
  5326. ValidationTypes.isAny(expression, numeric);
  5327. }
  5328. return result;
  5329. }
  5330. }
  5331. };
  5332. parserlib.css = {
  5333. Colors :Colors,
  5334. Combinator :Combinator,
  5335. Parser :Parser,
  5336. PropertyName :PropertyName,
  5337. PropertyValue :PropertyValue,
  5338. PropertyValuePart :PropertyValuePart,
  5339. MediaFeature :MediaFeature,
  5340. MediaQuery :MediaQuery,
  5341. Selector :Selector,
  5342. SelectorPart :SelectorPart,
  5343. SelectorSubPart :SelectorSubPart,
  5344. Specificity :Specificity,
  5345. TokenStream :TokenStream,
  5346. Tokens :Tokens,
  5347. ValidationError :ValidationError
  5348. };
  5349. })();
  5350. /**
  5351. * Main CSSLint object.
  5352. * @class CSSLint
  5353. * @static
  5354. * @extends parserlib.util.EventTarget
  5355. */
  5356. /*global parserlib, Reporter*/
  5357. var CSSLint = (function(){
  5358. var rules = [],
  5359. formatters = [],
  5360. embeddedRuleset = /\/\*csslint([^\*]*)\*\//,
  5361. api = new parserlib.util.EventTarget();
  5362. api.version = "0.9.10";
  5363. //-------------------------------------------------------------------------
  5364. // Rule Management
  5365. //-------------------------------------------------------------------------
  5366. /**
  5367. * Adds a new rule to the engine.
  5368. * @param {Object} rule The rule to add.
  5369. * @method addRule
  5370. */
  5371. api.addRule = function(rule){
  5372. rules.push(rule);
  5373. rules[rule.id] = rule;
  5374. };
  5375. /**
  5376. * Clears all rule from the engine.
  5377. * @method clearRules
  5378. */
  5379. api.clearRules = function(){
  5380. rules = [];
  5381. };
  5382. /**
  5383. * Returns the rule objects.
  5384. * @return An array of rule objects.
  5385. * @method getRules
  5386. */
  5387. api.getRules = function(){
  5388. return [].concat(rules).sort(function(a,b){
  5389. return a.id > b.id ? 1 : 0;
  5390. });
  5391. };
  5392. /**
  5393. * Returns a ruleset configuration object with all current rules.
  5394. * @return A ruleset object.
  5395. * @method getRuleset
  5396. */
  5397. api.getRuleset = function() {
  5398. var ruleset = {},
  5399. i = 0,
  5400. len = rules.length;
  5401. while (i < len){
  5402. ruleset[rules[i++].id] = 1; //by default, everything is a warning
  5403. }
  5404. return ruleset;
  5405. };
  5406. /**
  5407. * Returns a ruleset object based on embedded rules.
  5408. * @param {String} text A string of css containing embedded rules.
  5409. * @param {Object} ruleset A ruleset object to modify.
  5410. * @return {Object} A ruleset object.
  5411. * @method getEmbeddedRuleset
  5412. */
  5413. function applyEmbeddedRuleset(text, ruleset){
  5414. var valueMap,
  5415. embedded = text && text.match(embeddedRuleset),
  5416. rules = embedded && embedded[1];
  5417. if (rules) {
  5418. valueMap = {
  5419. "true": 2, // true is error
  5420. "": 1, // blank is warning
  5421. "false": 0, // false is ignore
  5422. "2": 2, // explicit error
  5423. "1": 1, // explicit warning
  5424. "0": 0 // explicit ignore
  5425. };
  5426. rules.toLowerCase().split(",").forEach(function(rule){
  5427. var pair = rule.split(":"),
  5428. property = pair[0] || "",
  5429. value = pair[1] || "";
  5430. ruleset[property.trim()] = valueMap[value.trim()];
  5431. });
  5432. }
  5433. return ruleset;
  5434. }
  5435. //-------------------------------------------------------------------------
  5436. // Formatters
  5437. //-------------------------------------------------------------------------
  5438. /**
  5439. * Adds a new formatter to the engine.
  5440. * @param {Object} formatter The formatter to add.
  5441. * @method addFormatter
  5442. */
  5443. api.addFormatter = function(formatter) {
  5444. // formatters.push(formatter);
  5445. formatters[formatter.id] = formatter;
  5446. };
  5447. /**
  5448. * Retrieves a formatter for use.
  5449. * @param {String} formatId The name of the format to retrieve.
  5450. * @return {Object} The formatter or undefined.
  5451. * @method getFormatter
  5452. */
  5453. api.getFormatter = function(formatId){
  5454. return formatters[formatId];
  5455. };
  5456. /**
  5457. * Formats the results in a particular format for a single file.
  5458. * @param {Object} result The results returned from CSSLint.verify().
  5459. * @param {String} filename The filename for which the results apply.
  5460. * @param {String} formatId The name of the formatter to use.
  5461. * @param {Object} options (Optional) for special output handling.
  5462. * @return {String} A formatted string for the results.
  5463. * @method format
  5464. */
  5465. api.format = function(results, filename, formatId, options) {
  5466. var formatter = this.getFormatter(formatId),
  5467. result = null;
  5468. if (formatter){
  5469. result = formatter.startFormat();
  5470. result += formatter.formatResults(results, filename, options || {});
  5471. result += formatter.endFormat();
  5472. }
  5473. return result;
  5474. };
  5475. /**
  5476. * Indicates if the given format is supported.
  5477. * @param {String} formatId The ID of the format to check.
  5478. * @return {Boolean} True if the format exists, false if not.
  5479. * @method hasFormat
  5480. */
  5481. api.hasFormat = function(formatId){
  5482. return formatters.hasOwnProperty(formatId);
  5483. };
  5484. //-------------------------------------------------------------------------
  5485. // Verification
  5486. //-------------------------------------------------------------------------
  5487. /**
  5488. * Starts the verification process for the given CSS text.
  5489. * @param {String} text The CSS text to verify.
  5490. * @param {Object} ruleset (Optional) List of rules to apply. If null, then
  5491. * all rules are used. If a rule has a value of 1 then it's a warning,
  5492. * a value of 2 means it's an error.
  5493. * @return {Object} Results of the verification.
  5494. * @method verify
  5495. */
  5496. api.verify = function(text, ruleset){
  5497. var i = 0,
  5498. len = rules.length,
  5499. reporter,
  5500. lines,
  5501. report,
  5502. parser = new parserlib.css.Parser({ starHack: true, ieFilters: true,
  5503. underscoreHack: true, strict: false });
  5504. // normalize line endings
  5505. lines = text.replace(/\n\r?/g, "$split$").split('$split$');
  5506. if (!ruleset){
  5507. ruleset = this.getRuleset();
  5508. }
  5509. if (embeddedRuleset.test(text)){
  5510. ruleset = applyEmbeddedRuleset(text, ruleset);
  5511. }
  5512. reporter = new Reporter(lines, ruleset);
  5513. ruleset.errors = 2; //always report parsing errors as errors
  5514. for (i in ruleset){
  5515. if(ruleset.hasOwnProperty(i) && ruleset[i]){
  5516. if (rules[i]){
  5517. rules[i].init(parser, reporter);
  5518. }
  5519. }
  5520. }
  5521. //capture most horrible error type
  5522. try {
  5523. parser.parse(text);
  5524. } catch (ex) {
  5525. reporter.error("Fatal error, cannot continue: " + ex.message, ex.line, ex.col, {});
  5526. }
  5527. report = {
  5528. messages : reporter.messages,
  5529. stats : reporter.stats,
  5530. ruleset : reporter.ruleset
  5531. };
  5532. //sort by line numbers, rollups at the bottom
  5533. report.messages.sort(function (a, b){
  5534. if (a.rollup && !b.rollup){
  5535. return 1;
  5536. } else if (!a.rollup && b.rollup){
  5537. return -1;
  5538. } else {
  5539. return a.line - b.line;
  5540. }
  5541. });
  5542. return report;
  5543. };
  5544. //-------------------------------------------------------------------------
  5545. // Publish the API
  5546. //-------------------------------------------------------------------------
  5547. return api;
  5548. })();
  5549. /*global CSSLint*/
  5550. /**
  5551. * An instance of Report is used to report results of the
  5552. * verification back to the main API.
  5553. * @class Reporter
  5554. * @constructor
  5555. * @param {String[]} lines The text lines of the source.
  5556. * @param {Object} ruleset The set of rules to work with, including if
  5557. * they are errors or warnings.
  5558. */
  5559. function Reporter(lines, ruleset){
  5560. /**
  5561. * List of messages being reported.
  5562. * @property messages
  5563. * @type String[]
  5564. */
  5565. this.messages = [];
  5566. /**
  5567. * List of statistics being reported.
  5568. * @property stats
  5569. * @type String[]
  5570. */
  5571. this.stats = [];
  5572. /**
  5573. * Lines of code being reported on. Used to provide contextual information
  5574. * for messages.
  5575. * @property lines
  5576. * @type String[]
  5577. */
  5578. this.lines = lines;
  5579. /**
  5580. * Information about the rules. Used to determine whether an issue is an
  5581. * error or warning.
  5582. * @property ruleset
  5583. * @type Object
  5584. */
  5585. this.ruleset = ruleset;
  5586. }
  5587. Reporter.prototype = {
  5588. //restore constructor
  5589. constructor: Reporter,
  5590. /**
  5591. * Report an error.
  5592. * @param {String} message The message to store.
  5593. * @param {int} line The line number.
  5594. * @param {int} col The column number.
  5595. * @param {Object} rule The rule this message relates to.
  5596. * @method error
  5597. */
  5598. error: function(message, line, col, rule){
  5599. this.messages.push({
  5600. type : "error",
  5601. line : line,
  5602. col : col,
  5603. message : message,
  5604. evidence: this.lines[line-1],
  5605. rule : rule || {}
  5606. });
  5607. },
  5608. /**
  5609. * Report an warning.
  5610. * @param {String} message The message to store.
  5611. * @param {int} line The line number.
  5612. * @param {int} col The column number.
  5613. * @param {Object} rule The rule this message relates to.
  5614. * @method warn
  5615. * @deprecated Use report instead.
  5616. */
  5617. warn: function(message, line, col, rule){
  5618. this.report(message, line, col, rule);
  5619. },
  5620. /**
  5621. * Report an issue.
  5622. * @param {String} message The message to store.
  5623. * @param {int} line The line number.
  5624. * @param {int} col The column number.
  5625. * @param {Object} rule The rule this message relates to.
  5626. * @method report
  5627. */
  5628. report: function(message, line, col, rule){
  5629. this.messages.push({
  5630. type : this.ruleset[rule.id] == 2 ? "error" : "warning",
  5631. line : line,
  5632. col : col,
  5633. message : message,
  5634. evidence: this.lines[line-1],
  5635. rule : rule
  5636. });
  5637. },
  5638. /**
  5639. * Report some informational text.
  5640. * @param {String} message The message to store.
  5641. * @param {int} line The line number.
  5642. * @param {int} col The column number.
  5643. * @param {Object} rule The rule this message relates to.
  5644. * @method info
  5645. */
  5646. info: function(message, line, col, rule){
  5647. this.messages.push({
  5648. type : "info",
  5649. line : line,
  5650. col : col,
  5651. message : message,
  5652. evidence: this.lines[line-1],
  5653. rule : rule
  5654. });
  5655. },
  5656. /**
  5657. * Report some rollup error information.
  5658. * @param {String} message The message to store.
  5659. * @param {Object} rule The rule this message relates to.
  5660. * @method rollupError
  5661. */
  5662. rollupError: function(message, rule){
  5663. this.messages.push({
  5664. type : "error",
  5665. rollup : true,
  5666. message : message,
  5667. rule : rule
  5668. });
  5669. },
  5670. /**
  5671. * Report some rollup warning information.
  5672. * @param {String} message The message to store.
  5673. * @param {Object} rule The rule this message relates to.
  5674. * @method rollupWarn
  5675. */
  5676. rollupWarn: function(message, rule){
  5677. this.messages.push({
  5678. type : "warning",
  5679. rollup : true,
  5680. message : message,
  5681. rule : rule
  5682. });
  5683. },
  5684. /**
  5685. * Report a statistic.
  5686. * @param {String} name The name of the stat to store.
  5687. * @param {Variant} value The value of the stat.
  5688. * @method stat
  5689. */
  5690. stat: function(name, value){
  5691. this.stats[name] = value;
  5692. }
  5693. };
  5694. //expose for testing purposes
  5695. CSSLint._Reporter = Reporter;
  5696. /*global CSSLint*/
  5697. /*
  5698. * Utility functions that make life easier.
  5699. */
  5700. CSSLint.Util = {
  5701. /*
  5702. * Adds all properties from supplier onto receiver,
  5703. * overwriting if the same name already exists on
  5704. * reciever.
  5705. * @param {Object} The object to receive the properties.
  5706. * @param {Object} The object to provide the properties.
  5707. * @return {Object} The receiver
  5708. */
  5709. mix: function(receiver, supplier){
  5710. var prop;
  5711. for (prop in supplier){
  5712. if (supplier.hasOwnProperty(prop)){
  5713. receiver[prop] = supplier[prop];
  5714. }
  5715. }
  5716. return prop;
  5717. },
  5718. /*
  5719. * Polyfill for array indexOf() method.
  5720. * @param {Array} values The array to search.
  5721. * @param {Variant} value The value to search for.
  5722. * @return {int} The index of the value if found, -1 if not.
  5723. */
  5724. indexOf: function(values, value){
  5725. if (values.indexOf){
  5726. return values.indexOf(value);
  5727. } else {
  5728. for (var i=0, len=values.length; i < len; i++){
  5729. if (values[i] === value){
  5730. return i;
  5731. }
  5732. }
  5733. return -1;
  5734. }
  5735. },
  5736. /*
  5737. * Polyfill for array forEach() method.
  5738. * @param {Array} values The array to operate on.
  5739. * @param {Function} func The function to call on each item.
  5740. * @return {void}
  5741. */
  5742. forEach: function(values, func) {
  5743. if (values.forEach){
  5744. return values.forEach(func);
  5745. } else {
  5746. for (var i=0, len=values.length; i < len; i++){
  5747. func(values[i], i, values);
  5748. }
  5749. }
  5750. }
  5751. };
  5752. /*global CSSLint*/
  5753. /*
  5754. * Rule: Don't use adjoining classes (.foo.bar).
  5755. */
  5756. CSSLint.addRule({
  5757. //rule information
  5758. id: "adjoining-classes",
  5759. name: "Disallow adjoining classes",
  5760. desc: "Don't use adjoining classes.",
  5761. browsers: "IE6",
  5762. //initialization
  5763. init: function(parser, reporter){
  5764. var rule = this;
  5765. parser.addListener("startrule", function(event){
  5766. var selectors = event.selectors,
  5767. selector,
  5768. part,
  5769. modifier,
  5770. classCount,
  5771. i, j, k;
  5772. for (i=0; i < selectors.length; i++){
  5773. selector = selectors[i];
  5774. for (j=0; j < selector.parts.length; j++){
  5775. part = selector.parts[j];
  5776. if (part.type == parser.SELECTOR_PART_TYPE){
  5777. classCount = 0;
  5778. for (k=0; k < part.modifiers.length; k++){
  5779. modifier = part.modifiers[k];
  5780. if (modifier.type == "class"){
  5781. classCount++;
  5782. }
  5783. if (classCount > 1){
  5784. reporter.report("Don't use adjoining classes.", part.line, part.col, rule);
  5785. }
  5786. }
  5787. }
  5788. }
  5789. }
  5790. });
  5791. }
  5792. });
  5793. /*global CSSLint*/
  5794. /*
  5795. * Rule: Don't use width or height when using padding or border.
  5796. */
  5797. CSSLint.addRule({
  5798. //rule information
  5799. id: "box-model",
  5800. name: "Beware of broken box size",
  5801. desc: "Don't use width or height when using padding or border.",
  5802. browsers: "All",
  5803. //initialization
  5804. init: function(parser, reporter){
  5805. var rule = this,
  5806. widthProperties = {
  5807. border: 1,
  5808. "border-left": 1,
  5809. "border-right": 1,
  5810. padding: 1,
  5811. "padding-left": 1,
  5812. "padding-right": 1
  5813. },
  5814. heightProperties = {
  5815. border: 1,
  5816. "border-bottom": 1,
  5817. "border-top": 1,
  5818. padding: 1,
  5819. "padding-bottom": 1,
  5820. "padding-top": 1
  5821. },
  5822. properties,
  5823. boxSizing = false;
  5824. function startRule(){
  5825. properties = {};
  5826. boxSizing = false;
  5827. }
  5828. function endRule(){
  5829. var prop, value;
  5830. if (!boxSizing) {
  5831. if (properties.height){
  5832. for (prop in heightProperties){
  5833. if (heightProperties.hasOwnProperty(prop) && properties[prop]){
  5834. value = properties[prop].value;
  5835. //special case for padding
  5836. if (!(prop == "padding" && value.parts.length === 2 && value.parts[0].value === 0)){
  5837. reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule);
  5838. }
  5839. }
  5840. }
  5841. }
  5842. if (properties.width){
  5843. for (prop in widthProperties){
  5844. if (widthProperties.hasOwnProperty(prop) && properties[prop]){
  5845. value = properties[prop].value;
  5846. if (!(prop == "padding" && value.parts.length === 2 && value.parts[1].value === 0)){
  5847. reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule);
  5848. }
  5849. }
  5850. }
  5851. }
  5852. }
  5853. }
  5854. parser.addListener("startrule", startRule);
  5855. parser.addListener("startfontface", startRule);
  5856. parser.addListener("startpage", startRule);
  5857. parser.addListener("startpagemargin", startRule);
  5858. parser.addListener("startkeyframerule", startRule);
  5859. parser.addListener("property", function(event){
  5860. var name = event.property.text.toLowerCase();
  5861. if (heightProperties[name] || widthProperties[name]){
  5862. if (!/^0\S*$/.test(event.value) && !(name == "border" && event.value == "none")){
  5863. properties[name] = { line: event.property.line, col: event.property.col, value: event.value };
  5864. }
  5865. } else {
  5866. if (/^(width|height)/i.test(name) && /^(length|percentage)/.test(event.value.parts[0].type)){
  5867. properties[name] = 1;
  5868. } else if (name == "box-sizing") {
  5869. boxSizing = true;
  5870. }
  5871. }
  5872. });
  5873. parser.addListener("endrule", endRule);
  5874. parser.addListener("endfontface", endRule);
  5875. parser.addListener("endpage", endRule);
  5876. parser.addListener("endpagemargin", endRule);
  5877. parser.addListener("endkeyframerule", endRule);
  5878. }
  5879. });
  5880. /*global CSSLint*/
  5881. /*
  5882. * Rule: box-sizing doesn't work in IE6 and IE7.
  5883. */
  5884. CSSLint.addRule({
  5885. //rule information
  5886. id: "box-sizing",
  5887. name: "Disallow use of box-sizing",
  5888. desc: "The box-sizing properties isn't supported in IE6 and IE7.",
  5889. browsers: "IE6, IE7",
  5890. tags: ["Compatibility"],
  5891. //initialization
  5892. init: function(parser, reporter){
  5893. var rule = this;
  5894. parser.addListener("property", function(event){
  5895. var name = event.property.text.toLowerCase();
  5896. if (name == "box-sizing"){
  5897. reporter.report("The box-sizing property isn't supported in IE6 and IE7.", event.line, event.col, rule);
  5898. }
  5899. });
  5900. }
  5901. });
  5902. /*
  5903. * Rule: Use the bulletproof @font-face syntax to avoid 404's in old IE
  5904. * (http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax)
  5905. */
  5906. /*global CSSLint*/
  5907. CSSLint.addRule({
  5908. //rule information
  5909. id: "bulletproof-font-face",
  5910. name: "Use the bulletproof @font-face syntax",
  5911. desc: "Use the bulletproof @font-face syntax to avoid 404's in old IE (http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax).",
  5912. browsers: "All",
  5913. //initialization
  5914. init: function(parser, reporter){
  5915. var rule = this,
  5916. count = 0,
  5917. fontFaceRule = false,
  5918. firstSrc = true,
  5919. ruleFailed = false,
  5920. line, col;
  5921. // Mark the start of a @font-face declaration so we only test properties inside it
  5922. parser.addListener("startfontface", function(event){
  5923. fontFaceRule = true;
  5924. });
  5925. parser.addListener("property", function(event){
  5926. // If we aren't inside an @font-face declaration then just return
  5927. if (!fontFaceRule) {
  5928. return;
  5929. }
  5930. var propertyName = event.property.toString().toLowerCase(),
  5931. value = event.value.toString();
  5932. // Set the line and col numbers for use in the endfontface listener
  5933. line = event.line;
  5934. col = event.col;
  5935. // This is the property that we care about, we can ignore the rest
  5936. if (propertyName === 'src') {
  5937. var regex = /^\s?url\(['"].+\.eot\?.*['"]\)\s*format\(['"]embedded-opentype['"]\).*$/i;
  5938. // We need to handle the advanced syntax with two src properties
  5939. if (!value.match(regex) && firstSrc) {
  5940. ruleFailed = true;
  5941. firstSrc = false;
  5942. } else if (value.match(regex) && !firstSrc) {
  5943. ruleFailed = false;
  5944. }
  5945. }
  5946. });
  5947. // Back to normal rules that we don't need to test
  5948. parser.addListener("endfontface", function(event){
  5949. fontFaceRule = false;
  5950. if (ruleFailed) {
  5951. reporter.report("@font-face declaration doesn't follow the fontspring bulletproof syntax.", line, col, rule);
  5952. }
  5953. });
  5954. }
  5955. });
  5956. /*
  5957. * Rule: Include all compatible vendor prefixes to reach a wider
  5958. * range of users.
  5959. */
  5960. /*global CSSLint*/
  5961. CSSLint.addRule({
  5962. //rule information
  5963. id: "compatible-vendor-prefixes",
  5964. name: "Require compatible vendor prefixes",
  5965. desc: "Include all compatible vendor prefixes to reach a wider range of users.",
  5966. browsers: "All",
  5967. //initialization
  5968. init: function (parser, reporter) {
  5969. var rule = this,
  5970. compatiblePrefixes,
  5971. properties,
  5972. prop,
  5973. variations,
  5974. prefixed,
  5975. i,
  5976. len,
  5977. inKeyFrame = false,
  5978. arrayPush = Array.prototype.push,
  5979. applyTo = [];
  5980. // See http://peter.sh/experiments/vendor-prefixed-css-property-overview/ for details
  5981. compatiblePrefixes = {
  5982. "animation" : "webkit moz",
  5983. "animation-delay" : "webkit moz",
  5984. "animation-direction" : "webkit moz",
  5985. "animation-duration" : "webkit moz",
  5986. "animation-fill-mode" : "webkit moz",
  5987. "animation-iteration-count" : "webkit moz",
  5988. "animation-name" : "webkit moz",
  5989. "animation-play-state" : "webkit moz",
  5990. "animation-timing-function" : "webkit moz",
  5991. "appearance" : "webkit moz",
  5992. "border-end" : "webkit moz",
  5993. "border-end-color" : "webkit moz",
  5994. "border-end-style" : "webkit moz",
  5995. "border-end-width" : "webkit moz",
  5996. "border-image" : "webkit moz o",
  5997. "border-radius" : "webkit",
  5998. "border-start" : "webkit moz",
  5999. "border-start-color" : "webkit moz",
  6000. "border-start-style" : "webkit moz",
  6001. "border-start-width" : "webkit moz",
  6002. "box-align" : "webkit moz ms",
  6003. "box-direction" : "webkit moz ms",
  6004. "box-flex" : "webkit moz ms",
  6005. "box-lines" : "webkit ms",
  6006. "box-ordinal-group" : "webkit moz ms",
  6007. "box-orient" : "webkit moz ms",
  6008. "box-pack" : "webkit moz ms",
  6009. "box-sizing" : "webkit moz",
  6010. "box-shadow" : "webkit moz",
  6011. "column-count" : "webkit moz ms",
  6012. "column-gap" : "webkit moz ms",
  6013. "column-rule" : "webkit moz ms",
  6014. "column-rule-color" : "webkit moz ms",
  6015. "column-rule-style" : "webkit moz ms",
  6016. "column-rule-width" : "webkit moz ms",
  6017. "column-width" : "webkit moz ms",
  6018. "hyphens" : "epub moz",
  6019. "line-break" : "webkit ms",
  6020. "margin-end" : "webkit moz",
  6021. "margin-start" : "webkit moz",
  6022. "marquee-speed" : "webkit wap",
  6023. "marquee-style" : "webkit wap",
  6024. "padding-end" : "webkit moz",
  6025. "padding-start" : "webkit moz",
  6026. "tab-size" : "moz o",
  6027. "text-size-adjust" : "webkit ms",
  6028. "transform" : "webkit moz ms o",
  6029. "transform-origin" : "webkit moz ms o",
  6030. "transition" : "webkit moz o",
  6031. "transition-delay" : "webkit moz o",
  6032. "transition-duration" : "webkit moz o",
  6033. "transition-property" : "webkit moz o",
  6034. "transition-timing-function" : "webkit moz o",
  6035. "user-modify" : "webkit moz",
  6036. "user-select" : "webkit moz ms",
  6037. "word-break" : "epub ms",
  6038. "writing-mode" : "epub ms"
  6039. };
  6040. for (prop in compatiblePrefixes) {
  6041. if (compatiblePrefixes.hasOwnProperty(prop)) {
  6042. variations = [];
  6043. prefixed = compatiblePrefixes[prop].split(' ');
  6044. for (i = 0, len = prefixed.length; i < len; i++) {
  6045. variations.push('-' + prefixed[i] + '-' + prop);
  6046. }
  6047. compatiblePrefixes[prop] = variations;
  6048. arrayPush.apply(applyTo, variations);
  6049. }
  6050. }
  6051. parser.addListener("startrule", function () {
  6052. properties = [];
  6053. });
  6054. parser.addListener("startkeyframes", function (event) {
  6055. inKeyFrame = event.prefix || true;
  6056. });
  6057. parser.addListener("endkeyframes", function (event) {
  6058. inKeyFrame = false;
  6059. });
  6060. parser.addListener("property", function (event) {
  6061. var name = event.property;
  6062. if (CSSLint.Util.indexOf(applyTo, name.text) > -1) {
  6063. // e.g., -moz-transform is okay to be alone in @-moz-keyframes
  6064. if (!inKeyFrame || typeof inKeyFrame != "string" ||
  6065. name.text.indexOf("-" + inKeyFrame + "-") !== 0) {
  6066. properties.push(name);
  6067. }
  6068. }
  6069. });
  6070. parser.addListener("endrule", function (event) {
  6071. if (!properties.length) {
  6072. return;
  6073. }
  6074. var propertyGroups = {},
  6075. i,
  6076. len,
  6077. name,
  6078. prop,
  6079. variations,
  6080. value,
  6081. full,
  6082. actual,
  6083. item,
  6084. propertiesSpecified;
  6085. for (i = 0, len = properties.length; i < len; i++) {
  6086. name = properties[i];
  6087. for (prop in compatiblePrefixes) {
  6088. if (compatiblePrefixes.hasOwnProperty(prop)) {
  6089. variations = compatiblePrefixes[prop];
  6090. if (CSSLint.Util.indexOf(variations, name.text) > -1) {
  6091. if (!propertyGroups[prop]) {
  6092. propertyGroups[prop] = {
  6093. full : variations.slice(0),
  6094. actual : [],
  6095. actualNodes: []
  6096. };
  6097. }
  6098. if (CSSLint.Util.indexOf(propertyGroups[prop].actual, name.text) === -1) {
  6099. propertyGroups[prop].actual.push(name.text);
  6100. propertyGroups[prop].actualNodes.push(name);
  6101. }
  6102. }
  6103. }
  6104. }
  6105. }
  6106. for (prop in propertyGroups) {
  6107. if (propertyGroups.hasOwnProperty(prop)) {
  6108. value = propertyGroups[prop];
  6109. full = value.full;
  6110. actual = value.actual;
  6111. if (full.length > actual.length) {
  6112. for (i = 0, len = full.length; i < len; i++) {
  6113. item = full[i];
  6114. if (CSSLint.Util.indexOf(actual, item) === -1) {
  6115. propertiesSpecified = (actual.length === 1) ? actual[0] : (actual.length == 2) ? actual.join(" and ") : actual.join(", ");
  6116. reporter.report("The property " + item + " is compatible with " + propertiesSpecified + " and should be included as well.", value.actualNodes[0].line, value.actualNodes[0].col, rule);
  6117. }
  6118. }
  6119. }
  6120. }
  6121. }
  6122. });
  6123. }
  6124. });
  6125. /*
  6126. * Rule: Certain properties don't play well with certain display values.
  6127. * - float should not be used with inline-block
  6128. * - height, width, margin-top, margin-bottom, float should not be used with inline
  6129. * - vertical-align should not be used with block
  6130. * - margin, float should not be used with table-*
  6131. */
  6132. /*global CSSLint*/
  6133. CSSLint.addRule({
  6134. //rule information
  6135. id: "display-property-grouping",
  6136. name: "Require properties appropriate for display",
  6137. desc: "Certain properties shouldn't be used with certain display property values.",
  6138. browsers: "All",
  6139. //initialization
  6140. init: function(parser, reporter){
  6141. var rule = this;
  6142. var propertiesToCheck = {
  6143. display: 1,
  6144. "float": "none",
  6145. height: 1,
  6146. width: 1,
  6147. margin: 1,
  6148. "margin-left": 1,
  6149. "margin-right": 1,
  6150. "margin-bottom": 1,
  6151. "margin-top": 1,
  6152. padding: 1,
  6153. "padding-left": 1,
  6154. "padding-right": 1,
  6155. "padding-bottom": 1,
  6156. "padding-top": 1,
  6157. "vertical-align": 1
  6158. },
  6159. properties;
  6160. function reportProperty(name, display, msg){
  6161. if (properties[name]){
  6162. if (typeof propertiesToCheck[name] != "string" || properties[name].value.toLowerCase() != propertiesToCheck[name]){
  6163. reporter.report(msg || name + " can't be used with display: " + display + ".", properties[name].line, properties[name].col, rule);
  6164. }
  6165. }
  6166. }
  6167. function startRule(){
  6168. properties = {};
  6169. }
  6170. function endRule(){
  6171. var display = properties.display ? properties.display.value : null;
  6172. if (display){
  6173. switch(display){
  6174. case "inline":
  6175. //height, width, margin-top, margin-bottom, float should not be used with inline
  6176. reportProperty("height", display);
  6177. reportProperty("width", display);
  6178. reportProperty("margin", display);
  6179. reportProperty("margin-top", display);
  6180. reportProperty("margin-bottom", display);
  6181. reportProperty("float", display, "display:inline has no effect on floated elements (but may be used to fix the IE6 double-margin bug).");
  6182. break;
  6183. case "block":
  6184. //vertical-align should not be used with block
  6185. reportProperty("vertical-align", display);
  6186. break;
  6187. case "inline-block":
  6188. //float should not be used with inline-block
  6189. reportProperty("float", display);
  6190. break;
  6191. default:
  6192. //margin, float should not be used with table
  6193. if (display.indexOf("table-") === 0){
  6194. reportProperty("margin", display);
  6195. reportProperty("margin-left", display);
  6196. reportProperty("margin-right", display);
  6197. reportProperty("margin-top", display);
  6198. reportProperty("margin-bottom", display);
  6199. reportProperty("float", display);
  6200. }
  6201. //otherwise do nothing
  6202. }
  6203. }
  6204. }
  6205. parser.addListener("startrule", startRule);
  6206. parser.addListener("startfontface", startRule);
  6207. parser.addListener("startkeyframerule", startRule);
  6208. parser.addListener("startpagemargin", startRule);
  6209. parser.addListener("startpage", startRule);
  6210. parser.addListener("property", function(event){
  6211. var name = event.property.text.toLowerCase();
  6212. if (propertiesToCheck[name]){
  6213. properties[name] = { value: event.value.text, line: event.property.line, col: event.property.col };
  6214. }
  6215. });
  6216. parser.addListener("endrule", endRule);
  6217. parser.addListener("endfontface", endRule);
  6218. parser.addListener("endkeyframerule", endRule);
  6219. parser.addListener("endpagemargin", endRule);
  6220. parser.addListener("endpage", endRule);
  6221. }
  6222. });
  6223. /*
  6224. * Rule: Disallow duplicate background-images (using url).
  6225. */
  6226. /*global CSSLint*/
  6227. CSSLint.addRule({
  6228. //rule information
  6229. id: "duplicate-background-images",
  6230. name: "Disallow duplicate background images",
  6231. desc: "Every background-image should be unique. Use a common class for e.g. sprites.",
  6232. browsers: "All",
  6233. //initialization
  6234. init: function(parser, reporter){
  6235. var rule = this,
  6236. stack = {};
  6237. parser.addListener("property", function(event){
  6238. var name = event.property.text,
  6239. value = event.value,
  6240. i, len;
  6241. if (name.match(/background/i)) {
  6242. for (i=0, len=value.parts.length; i < len; i++) {
  6243. if (value.parts[i].type == 'uri') {
  6244. if (typeof stack[value.parts[i].uri] === 'undefined') {
  6245. stack[value.parts[i].uri] = event;
  6246. }
  6247. else {
  6248. reporter.report("Background image '" + value.parts[i].uri + "' was used multiple times, first declared at line " + stack[value.parts[i].uri].line + ", col " + stack[value.parts[i].uri].col + ".", event.line, event.col, rule);
  6249. }
  6250. }
  6251. }
  6252. }
  6253. });
  6254. }
  6255. });
  6256. /*
  6257. * Rule: Duplicate properties must appear one after the other. If an already-defined
  6258. * property appears somewhere else in the rule, then it's likely an error.
  6259. */
  6260. /*global CSSLint*/
  6261. CSSLint.addRule({
  6262. //rule information
  6263. id: "duplicate-properties",
  6264. name: "Disallow duplicate properties",
  6265. desc: "Duplicate properties must appear one after the other.",
  6266. browsers: "All",
  6267. //initialization
  6268. init: function(parser, reporter){
  6269. var rule = this,
  6270. properties,
  6271. lastProperty;
  6272. function startRule(event){
  6273. properties = {};
  6274. }
  6275. parser.addListener("startrule", startRule);
  6276. parser.addListener("startfontface", startRule);
  6277. parser.addListener("startpage", startRule);
  6278. parser.addListener("startpagemargin", startRule);
  6279. parser.addListener("startkeyframerule", startRule);
  6280. parser.addListener("property", function(event){
  6281. var property = event.property,
  6282. name = property.text.toLowerCase();
  6283. if (properties[name] && (lastProperty != name || properties[name] == event.value.text)){
  6284. reporter.report("Duplicate property '" + event.property + "' found.", event.line, event.col, rule);
  6285. }
  6286. properties[name] = event.value.text;
  6287. lastProperty = name;
  6288. });
  6289. }
  6290. });
  6291. /*
  6292. * Rule: Style rules without any properties defined should be removed.
  6293. */
  6294. /*global CSSLint*/
  6295. CSSLint.addRule({
  6296. //rule information
  6297. id: "empty-rules",
  6298. name: "Disallow empty rules",
  6299. desc: "Rules without any properties specified should be removed.",
  6300. browsers: "All",
  6301. //initialization
  6302. init: function(parser, reporter){
  6303. var rule = this,
  6304. count = 0;
  6305. parser.addListener("startrule", function(){
  6306. count=0;
  6307. });
  6308. parser.addListener("property", function(){
  6309. count++;
  6310. });
  6311. parser.addListener("endrule", function(event){
  6312. var selectors = event.selectors;
  6313. if (count === 0){
  6314. reporter.report("Rule is empty.", selectors[0].line, selectors[0].col, rule);
  6315. }
  6316. });
  6317. }
  6318. });
  6319. /*
  6320. * Rule: There should be no syntax errors. (Duh.)
  6321. */
  6322. /*global CSSLint*/
  6323. CSSLint.addRule({
  6324. //rule information
  6325. id: "errors",
  6326. name: "Parsing Errors",
  6327. desc: "This rule looks for recoverable syntax errors.",
  6328. browsers: "All",
  6329. //initialization
  6330. init: function(parser, reporter){
  6331. var rule = this;
  6332. parser.addListener("error", function(event){
  6333. reporter.error(event.message, event.line, event.col, rule);
  6334. });
  6335. }
  6336. });
  6337. /*global CSSLint*/
  6338. CSSLint.addRule({
  6339. //rule information
  6340. id: "fallback-colors",
  6341. name: "Require fallback colors",
  6342. desc: "For older browsers that don't support RGBA, HSL, or HSLA, provide a fallback color.",
  6343. browsers: "IE6,IE7,IE8",
  6344. //initialization
  6345. init: function(parser, reporter){
  6346. var rule = this,
  6347. lastProperty,
  6348. propertiesToCheck = {
  6349. color: 1,
  6350. background: 1,
  6351. "border-color": 1,
  6352. "border-top-color": 1,
  6353. "border-right-color": 1,
  6354. "border-bottom-color": 1,
  6355. "border-left-color": 1,
  6356. border: 1,
  6357. "border-top": 1,
  6358. "border-right": 1,
  6359. "border-bottom": 1,
  6360. "border-left": 1,
  6361. "background-color": 1
  6362. },
  6363. properties;
  6364. function startRule(event){
  6365. properties = {};
  6366. lastProperty = null;
  6367. }
  6368. parser.addListener("startrule", startRule);
  6369. parser.addListener("startfontface", startRule);
  6370. parser.addListener("startpage", startRule);
  6371. parser.addListener("startpagemargin", startRule);
  6372. parser.addListener("startkeyframerule", startRule);
  6373. parser.addListener("property", function(event){
  6374. var property = event.property,
  6375. name = property.text.toLowerCase(),
  6376. parts = event.value.parts,
  6377. i = 0,
  6378. colorType = "",
  6379. len = parts.length;
  6380. if(propertiesToCheck[name]){
  6381. while(i < len){
  6382. if (parts[i].type == "color"){
  6383. if ("alpha" in parts[i] || "hue" in parts[i]){
  6384. if (/([^\)]+)\(/.test(parts[i])){
  6385. colorType = RegExp.$1.toUpperCase();
  6386. }
  6387. if (!lastProperty || (lastProperty.property.text.toLowerCase() != name || lastProperty.colorType != "compat")){
  6388. reporter.report("Fallback " + name + " (hex or RGB) should precede " + colorType + " " + name + ".", event.line, event.col, rule);
  6389. }
  6390. } else {
  6391. event.colorType = "compat";
  6392. }
  6393. }
  6394. i++;
  6395. }
  6396. }
  6397. lastProperty = event;
  6398. });
  6399. }
  6400. });
  6401. /*
  6402. * Rule: You shouldn't use more than 10 floats. If you do, there's probably
  6403. * room for some abstraction.
  6404. */
  6405. /*global CSSLint*/
  6406. CSSLint.addRule({
  6407. //rule information
  6408. id: "floats",
  6409. name: "Disallow too many floats",
  6410. desc: "This rule tests if the float property is used too many times",
  6411. browsers: "All",
  6412. //initialization
  6413. init: function(parser, reporter){
  6414. var rule = this;
  6415. var count = 0;
  6416. //count how many times "float" is used
  6417. parser.addListener("property", function(event){
  6418. if (event.property.text.toLowerCase() == "float" &&
  6419. event.value.text.toLowerCase() != "none"){
  6420. count++;
  6421. }
  6422. });
  6423. //report the results
  6424. parser.addListener("endstylesheet", function(){
  6425. reporter.stat("floats", count);
  6426. if (count >= 10){
  6427. reporter.rollupWarn("Too many floats (" + count + "), you're probably using them for layout. Consider using a grid system instead.", rule);
  6428. }
  6429. });
  6430. }
  6431. });
  6432. /*
  6433. * Rule: Avoid too many @font-face declarations in the same stylesheet.
  6434. */
  6435. /*global CSSLint*/
  6436. CSSLint.addRule({
  6437. //rule information
  6438. id: "font-faces",
  6439. name: "Don't use too many web fonts",
  6440. desc: "Too many different web fonts in the same stylesheet.",
  6441. browsers: "All",
  6442. //initialization
  6443. init: function(parser, reporter){
  6444. var rule = this,
  6445. count = 0;
  6446. parser.addListener("startfontface", function(){
  6447. count++;
  6448. });
  6449. parser.addListener("endstylesheet", function(){
  6450. if (count > 5){
  6451. reporter.rollupWarn("Too many @font-face declarations (" + count + ").", rule);
  6452. }
  6453. });
  6454. }
  6455. });
  6456. /*
  6457. * Rule: You shouldn't need more than 9 font-size declarations.
  6458. */
  6459. /*global CSSLint*/
  6460. CSSLint.addRule({
  6461. //rule information
  6462. id: "font-sizes",
  6463. name: "Disallow too many font sizes",
  6464. desc: "Checks the number of font-size declarations.",
  6465. browsers: "All",
  6466. //initialization
  6467. init: function(parser, reporter){
  6468. var rule = this,
  6469. count = 0;
  6470. //check for use of "font-size"
  6471. parser.addListener("property", function(event){
  6472. if (event.property == "font-size"){
  6473. count++;
  6474. }
  6475. });
  6476. //report the results
  6477. parser.addListener("endstylesheet", function(){
  6478. reporter.stat("font-sizes", count);
  6479. if (count >= 10){
  6480. reporter.rollupWarn("Too many font-size declarations (" + count + "), abstraction needed.", rule);
  6481. }
  6482. });
  6483. }
  6484. });
  6485. /*
  6486. * Rule: When using a vendor-prefixed gradient, make sure to use them all.
  6487. */
  6488. /*global CSSLint*/
  6489. CSSLint.addRule({
  6490. //rule information
  6491. id: "gradients",
  6492. name: "Require all gradient definitions",
  6493. desc: "When using a vendor-prefixed gradient, make sure to use them all.",
  6494. browsers: "All",
  6495. //initialization
  6496. init: function(parser, reporter){
  6497. var rule = this,
  6498. gradients;
  6499. parser.addListener("startrule", function(){
  6500. gradients = {
  6501. moz: 0,
  6502. webkit: 0,
  6503. oldWebkit: 0,
  6504. o: 0
  6505. };
  6506. });
  6507. parser.addListener("property", function(event){
  6508. if (/\-(moz|o|webkit)(?:\-(?:linear|radial))\-gradient/i.test(event.value)){
  6509. gradients[RegExp.$1] = 1;
  6510. } else if (/\-webkit\-gradient/i.test(event.value)){
  6511. gradients.oldWebkit = 1;
  6512. }
  6513. });
  6514. parser.addListener("endrule", function(event){
  6515. var missing = [];
  6516. if (!gradients.moz){
  6517. missing.push("Firefox 3.6+");
  6518. }
  6519. if (!gradients.webkit){
  6520. missing.push("Webkit (Safari 5+, Chrome)");
  6521. }
  6522. if (!gradients.oldWebkit){
  6523. missing.push("Old Webkit (Safari 4+, Chrome)");
  6524. }
  6525. if (!gradients.o){
  6526. missing.push("Opera 11.1+");
  6527. }
  6528. if (missing.length && missing.length < 4){
  6529. reporter.report("Missing vendor-prefixed CSS gradients for " + missing.join(", ") + ".", event.selectors[0].line, event.selectors[0].col, rule);
  6530. }
  6531. });
  6532. }
  6533. });
  6534. /*
  6535. * Rule: Don't use IDs for selectors.
  6536. */
  6537. /*global CSSLint*/
  6538. CSSLint.addRule({
  6539. //rule information
  6540. id: "ids",
  6541. name: "Disallow IDs in selectors",
  6542. desc: "Selectors should not contain IDs.",
  6543. browsers: "All",
  6544. //initialization
  6545. init: function(parser, reporter){
  6546. var rule = this;
  6547. parser.addListener("startrule", function(event){
  6548. var selectors = event.selectors,
  6549. selector,
  6550. part,
  6551. modifier,
  6552. idCount,
  6553. i, j, k;
  6554. for (i=0; i < selectors.length; i++){
  6555. selector = selectors[i];
  6556. idCount = 0;
  6557. for (j=0; j < selector.parts.length; j++){
  6558. part = selector.parts[j];
  6559. if (part.type == parser.SELECTOR_PART_TYPE){
  6560. for (k=0; k < part.modifiers.length; k++){
  6561. modifier = part.modifiers[k];
  6562. if (modifier.type == "id"){
  6563. idCount++;
  6564. }
  6565. }
  6566. }
  6567. }
  6568. if (idCount == 1){
  6569. reporter.report("Don't use IDs in selectors.", selector.line, selector.col, rule);
  6570. } else if (idCount > 1){
  6571. reporter.report(idCount + " IDs in the selector, really?", selector.line, selector.col, rule);
  6572. }
  6573. }
  6574. });
  6575. }
  6576. });
  6577. /*
  6578. * Rule: Don't use @import, use <link> instead.
  6579. */
  6580. /*global CSSLint*/
  6581. CSSLint.addRule({
  6582. //rule information
  6583. id: "import",
  6584. name: "Disallow @import",
  6585. desc: "Don't use @import, use <link> instead.",
  6586. browsers: "All",
  6587. //initialization
  6588. init: function(parser, reporter){
  6589. var rule = this;
  6590. parser.addListener("import", function(event){
  6591. reporter.report("@import prevents parallel downloads, use <link> instead.", event.line, event.col, rule);
  6592. });
  6593. }
  6594. });
  6595. /*
  6596. * Rule: Make sure !important is not overused, this could lead to specificity
  6597. * war. Display a warning on !important declarations, an error if it's
  6598. * used more at least 10 times.
  6599. */
  6600. /*global CSSLint*/
  6601. CSSLint.addRule({
  6602. //rule information
  6603. id: "important",
  6604. name: "Disallow !important",
  6605. desc: "Be careful when using !important declaration",
  6606. browsers: "All",
  6607. //initialization
  6608. init: function(parser, reporter){
  6609. var rule = this,
  6610. count = 0;
  6611. //warn that important is used and increment the declaration counter
  6612. parser.addListener("property", function(event){
  6613. if (event.important === true){
  6614. count++;
  6615. reporter.report("Use of !important", event.line, event.col, rule);
  6616. }
  6617. });
  6618. //if there are more than 10, show an error
  6619. parser.addListener("endstylesheet", function(){
  6620. reporter.stat("important", count);
  6621. if (count >= 10){
  6622. reporter.rollupWarn("Too many !important declarations (" + count + "), try to use less than 10 to avoid specificity issues.", rule);
  6623. }
  6624. });
  6625. }
  6626. });
  6627. /*
  6628. * Rule: Properties should be known (listed in CSS3 specification) or
  6629. * be a vendor-prefixed property.
  6630. */
  6631. /*global CSSLint*/
  6632. CSSLint.addRule({
  6633. //rule information
  6634. id: "known-properties",
  6635. name: "Require use of known properties",
  6636. desc: "Properties should be known (listed in CSS3 specification) or be a vendor-prefixed property.",
  6637. browsers: "All",
  6638. //initialization
  6639. init: function(parser, reporter){
  6640. var rule = this;
  6641. parser.addListener("property", function(event){
  6642. var name = event.property.text.toLowerCase();
  6643. // the check is handled entirely by the parser-lib (https://github.com/nzakas/parser-lib)
  6644. if (event.invalid) {
  6645. reporter.report(event.invalid.message, event.line, event.col, rule);
  6646. }
  6647. });
  6648. }
  6649. });
  6650. /*
  6651. * Rule: outline: none or outline: 0 should only be used in a :focus rule
  6652. * and only if there are other properties in the same rule.
  6653. */
  6654. /*global CSSLint*/
  6655. CSSLint.addRule({
  6656. //rule information
  6657. id: "outline-none",
  6658. name: "Disallow outline: none",
  6659. desc: "Use of outline: none or outline: 0 should be limited to :focus rules.",
  6660. browsers: "All",
  6661. tags: ["Accessibility"],
  6662. //initialization
  6663. init: function(parser, reporter){
  6664. var rule = this,
  6665. lastRule;
  6666. function startRule(event){
  6667. if (event.selectors){
  6668. lastRule = {
  6669. line: event.line,
  6670. col: event.col,
  6671. selectors: event.selectors,
  6672. propCount: 0,
  6673. outline: false
  6674. };
  6675. } else {
  6676. lastRule = null;
  6677. }
  6678. }
  6679. function endRule(event){
  6680. if (lastRule){
  6681. if (lastRule.outline){
  6682. if (lastRule.selectors.toString().toLowerCase().indexOf(":focus") == -1){
  6683. reporter.report("Outlines should only be modified using :focus.", lastRule.line, lastRule.col, rule);
  6684. } else if (lastRule.propCount == 1) {
  6685. reporter.report("Outlines shouldn't be hidden unless other visual changes are made.", lastRule.line, lastRule.col, rule);
  6686. }
  6687. }
  6688. }
  6689. }
  6690. parser.addListener("startrule", startRule);
  6691. parser.addListener("startfontface", startRule);
  6692. parser.addListener("startpage", startRule);
  6693. parser.addListener("startpagemargin", startRule);
  6694. parser.addListener("startkeyframerule", startRule);
  6695. parser.addListener("property", function(event){
  6696. var name = event.property.text.toLowerCase(),
  6697. value = event.value;
  6698. if (lastRule){
  6699. lastRule.propCount++;
  6700. if (name == "outline" && (value == "none" || value == "0")){
  6701. lastRule.outline = true;
  6702. }
  6703. }
  6704. });
  6705. parser.addListener("endrule", endRule);
  6706. parser.addListener("endfontface", endRule);
  6707. parser.addListener("endpage", endRule);
  6708. parser.addListener("endpagemargin", endRule);
  6709. parser.addListener("endkeyframerule", endRule);
  6710. }
  6711. });
  6712. /*
  6713. * Rule: Don't use classes or IDs with elements (a.foo or a#foo).
  6714. */
  6715. /*global CSSLint*/
  6716. CSSLint.addRule({
  6717. //rule information
  6718. id: "overqualified-elements",
  6719. name: "Disallow overqualified elements",
  6720. desc: "Don't use classes or IDs with elements (a.foo or a#foo).",
  6721. browsers: "All",
  6722. //initialization
  6723. init: function(parser, reporter){
  6724. var rule = this,
  6725. classes = {};
  6726. parser.addListener("startrule", function(event){
  6727. var selectors = event.selectors,
  6728. selector,
  6729. part,
  6730. modifier,
  6731. i, j, k;
  6732. for (i=0; i < selectors.length; i++){
  6733. selector = selectors[i];
  6734. for (j=0; j < selector.parts.length; j++){
  6735. part = selector.parts[j];
  6736. if (part.type == parser.SELECTOR_PART_TYPE){
  6737. for (k=0; k < part.modifiers.length; k++){
  6738. modifier = part.modifiers[k];
  6739. if (part.elementName && modifier.type == "id"){
  6740. reporter.report("Element (" + part + ") is overqualified, just use " + modifier + " without element name.", part.line, part.col, rule);
  6741. } else if (modifier.type == "class"){
  6742. if (!classes[modifier]){
  6743. classes[modifier] = [];
  6744. }
  6745. classes[modifier].push({ modifier: modifier, part: part });
  6746. }
  6747. }
  6748. }
  6749. }
  6750. }
  6751. });
  6752. parser.addListener("endstylesheet", function(){
  6753. var prop;
  6754. for (prop in classes){
  6755. if (classes.hasOwnProperty(prop)){
  6756. //one use means that this is overqualified
  6757. if (classes[prop].length == 1 && classes[prop][0].part.elementName){
  6758. reporter.report("Element (" + classes[prop][0].part + ") is overqualified, just use " + classes[prop][0].modifier + " without element name.", classes[prop][0].part.line, classes[prop][0].part.col, rule);
  6759. }
  6760. }
  6761. }
  6762. });
  6763. }
  6764. });
  6765. /*
  6766. * Rule: Headings (h1-h6) should not be qualified (namespaced).
  6767. */
  6768. /*global CSSLint*/
  6769. CSSLint.addRule({
  6770. //rule information
  6771. id: "qualified-headings",
  6772. name: "Disallow qualified headings",
  6773. desc: "Headings should not be qualified (namespaced).",
  6774. browsers: "All",
  6775. //initialization
  6776. init: function(parser, reporter){
  6777. var rule = this;
  6778. parser.addListener("startrule", function(event){
  6779. var selectors = event.selectors,
  6780. selector,
  6781. part,
  6782. i, j;
  6783. for (i=0; i < selectors.length; i++){
  6784. selector = selectors[i];
  6785. for (j=0; j < selector.parts.length; j++){
  6786. part = selector.parts[j];
  6787. if (part.type == parser.SELECTOR_PART_TYPE){
  6788. if (part.elementName && /h[1-6]/.test(part.elementName.toString()) && j > 0){
  6789. reporter.report("Heading (" + part.elementName + ") should not be qualified.", part.line, part.col, rule);
  6790. }
  6791. }
  6792. }
  6793. }
  6794. });
  6795. }
  6796. });
  6797. /*
  6798. * Rule: Selectors that look like regular expressions are slow and should be avoided.
  6799. */
  6800. /*global CSSLint*/
  6801. CSSLint.addRule({
  6802. //rule information
  6803. id: "regex-selectors",
  6804. name: "Disallow selectors that look like regexs",
  6805. desc: "Selectors that look like regular expressions are slow and should be avoided.",
  6806. browsers: "All",
  6807. //initialization
  6808. init: function(parser, reporter){
  6809. var rule = this;
  6810. parser.addListener("startrule", function(event){
  6811. var selectors = event.selectors,
  6812. selector,
  6813. part,
  6814. modifier,
  6815. i, j, k;
  6816. for (i=0; i < selectors.length; i++){
  6817. selector = selectors[i];
  6818. for (j=0; j < selector.parts.length; j++){
  6819. part = selector.parts[j];
  6820. if (part.type == parser.SELECTOR_PART_TYPE){
  6821. for (k=0; k < part.modifiers.length; k++){
  6822. modifier = part.modifiers[k];
  6823. if (modifier.type == "attribute"){
  6824. if (/([\~\|\^\$\*]=)/.test(modifier)){
  6825. reporter.report("Attribute selectors with " + RegExp.$1 + " are slow!", modifier.line, modifier.col, rule);
  6826. }
  6827. }
  6828. }
  6829. }
  6830. }
  6831. }
  6832. });
  6833. }
  6834. });
  6835. /*
  6836. * Rule: Total number of rules should not exceed x.
  6837. */
  6838. /*global CSSLint*/
  6839. CSSLint.addRule({
  6840. //rule information
  6841. id: "rules-count",
  6842. name: "Rules Count",
  6843. desc: "Track how many rules there are.",
  6844. browsers: "All",
  6845. //initialization
  6846. init: function(parser, reporter){
  6847. var rule = this,
  6848. count = 0;
  6849. //count each rule
  6850. parser.addListener("startrule", function(){
  6851. count++;
  6852. });
  6853. parser.addListener("endstylesheet", function(){
  6854. reporter.stat("rule-count", count);
  6855. });
  6856. }
  6857. });
  6858. /*
  6859. * Rule: Warn people with approaching the IE 4095 limit
  6860. */
  6861. /*global CSSLint*/
  6862. CSSLint.addRule({
  6863. //rule information
  6864. id: "selector-max-approaching",
  6865. name: "Warn when approaching the 4095 selector limit for IE",
  6866. desc: "Will warn when selector count is >= 3800 selectors.",
  6867. browsers: "IE",
  6868. //initialization
  6869. init: function(parser, reporter) {
  6870. var rule = this, count = 0;
  6871. parser.addListener('startrule', function(event) {
  6872. count += event.selectors.length;
  6873. });
  6874. parser.addListener("endstylesheet", function() {
  6875. if (count >= 3800) {
  6876. reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.",0,0,rule);
  6877. }
  6878. });
  6879. }
  6880. });
  6881. /*
  6882. * Rule: Warn people past the IE 4095 limit
  6883. */
  6884. /*global CSSLint*/
  6885. CSSLint.addRule({
  6886. //rule information
  6887. id: "selector-max",
  6888. name: "Error when past the 4095 selector limit for IE",
  6889. desc: "Will error when selector count is > 4095.",
  6890. browsers: "IE",
  6891. //initialization
  6892. init: function(parser, reporter){
  6893. var rule = this, count = 0;
  6894. parser.addListener('startrule',function(event) {
  6895. count += event.selectors.length;
  6896. });
  6897. parser.addListener("endstylesheet", function() {
  6898. if (count > 4095) {
  6899. reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.",0,0,rule);
  6900. }
  6901. });
  6902. }
  6903. });
  6904. /*
  6905. * Rule: Use shorthand properties where possible.
  6906. *
  6907. */
  6908. /*global CSSLint*/
  6909. CSSLint.addRule({
  6910. //rule information
  6911. id: "shorthand",
  6912. name: "Require shorthand properties",
  6913. desc: "Use shorthand properties where possible.",
  6914. browsers: "All",
  6915. //initialization
  6916. init: function(parser, reporter){
  6917. var rule = this,
  6918. prop, i, len,
  6919. propertiesToCheck = {},
  6920. properties,
  6921. mapping = {
  6922. "margin": [
  6923. "margin-top",
  6924. "margin-bottom",
  6925. "margin-left",
  6926. "margin-right"
  6927. ],
  6928. "padding": [
  6929. "padding-top",
  6930. "padding-bottom",
  6931. "padding-left",
  6932. "padding-right"
  6933. ]
  6934. };
  6935. //initialize propertiesToCheck
  6936. for (prop in mapping){
  6937. if (mapping.hasOwnProperty(prop)){
  6938. for (i=0, len=mapping[prop].length; i < len; i++){
  6939. propertiesToCheck[mapping[prop][i]] = prop;
  6940. }
  6941. }
  6942. }
  6943. function startRule(event){
  6944. properties = {};
  6945. }
  6946. //event handler for end of rules
  6947. function endRule(event){
  6948. var prop, i, len, total;
  6949. //check which properties this rule has
  6950. for (prop in mapping){
  6951. if (mapping.hasOwnProperty(prop)){
  6952. total=0;
  6953. for (i=0, len=mapping[prop].length; i < len; i++){
  6954. total += properties[mapping[prop][i]] ? 1 : 0;
  6955. }
  6956. if (total == mapping[prop].length){
  6957. reporter.report("The properties " + mapping[prop].join(", ") + " can be replaced by " + prop + ".", event.line, event.col, rule);
  6958. }
  6959. }
  6960. }
  6961. }
  6962. parser.addListener("startrule", startRule);
  6963. parser.addListener("startfontface", startRule);
  6964. //check for use of "font-size"
  6965. parser.addListener("property", function(event){
  6966. var name = event.property.toString().toLowerCase(),
  6967. value = event.value.parts[0].value;
  6968. if (propertiesToCheck[name]){
  6969. properties[name] = 1;
  6970. }
  6971. });
  6972. parser.addListener("endrule", endRule);
  6973. parser.addListener("endfontface", endRule);
  6974. }
  6975. });
  6976. /*
  6977. * Rule: Don't use properties with a star prefix.
  6978. *
  6979. */
  6980. /*global CSSLint*/
  6981. CSSLint.addRule({
  6982. //rule information
  6983. id: "star-property-hack",
  6984. name: "Disallow properties with a star prefix",
  6985. desc: "Checks for the star property hack (targets IE6/7)",
  6986. browsers: "All",
  6987. //initialization
  6988. init: function(parser, reporter){
  6989. var rule = this;
  6990. //check if property name starts with "*"
  6991. parser.addListener("property", function(event){
  6992. var property = event.property;
  6993. if (property.hack == "*") {
  6994. reporter.report("Property with star prefix found.", event.property.line, event.property.col, rule);
  6995. }
  6996. });
  6997. }
  6998. });
  6999. /*
  7000. * Rule: Don't use text-indent for image replacement if you need to support rtl.
  7001. *
  7002. */
  7003. /*global CSSLint*/
  7004. CSSLint.addRule({
  7005. //rule information
  7006. id: "text-indent",
  7007. name: "Disallow negative text-indent",
  7008. desc: "Checks for text indent less than -99px",
  7009. browsers: "All",
  7010. //initialization
  7011. init: function(parser, reporter){
  7012. var rule = this,
  7013. textIndent,
  7014. direction;
  7015. function startRule(event){
  7016. textIndent = false;
  7017. direction = "inherit";
  7018. }
  7019. //event handler for end of rules
  7020. function endRule(event){
  7021. if (textIndent && direction != "ltr"){
  7022. reporter.report("Negative text-indent doesn't work well with RTL. If you use text-indent for image replacement explicitly set direction for that item to ltr.", textIndent.line, textIndent.col, rule);
  7023. }
  7024. }
  7025. parser.addListener("startrule", startRule);
  7026. parser.addListener("startfontface", startRule);
  7027. //check for use of "font-size"
  7028. parser.addListener("property", function(event){
  7029. var name = event.property.toString().toLowerCase(),
  7030. value = event.value;
  7031. if (name == "text-indent" && value.parts[0].value < -99){
  7032. textIndent = event.property;
  7033. } else if (name == "direction" && value == "ltr"){
  7034. direction = "ltr";
  7035. }
  7036. });
  7037. parser.addListener("endrule", endRule);
  7038. parser.addListener("endfontface", endRule);
  7039. }
  7040. });
  7041. /*
  7042. * Rule: Don't use properties with a underscore prefix.
  7043. *
  7044. */
  7045. /*global CSSLint*/
  7046. CSSLint.addRule({
  7047. //rule information
  7048. id: "underscore-property-hack",
  7049. name: "Disallow properties with an underscore prefix",
  7050. desc: "Checks for the underscore property hack (targets IE6)",
  7051. browsers: "All",
  7052. //initialization
  7053. init: function(parser, reporter){
  7054. var rule = this;
  7055. //check if property name starts with "_"
  7056. parser.addListener("property", function(event){
  7057. var property = event.property;
  7058. if (property.hack == "_") {
  7059. reporter.report("Property with underscore prefix found.", event.property.line, event.property.col, rule);
  7060. }
  7061. });
  7062. }
  7063. });
  7064. /*
  7065. * Rule: Headings (h1-h6) should be defined only once.
  7066. */
  7067. /*global CSSLint*/
  7068. CSSLint.addRule({
  7069. //rule information
  7070. id: "unique-headings",
  7071. name: "Headings should only be defined once",
  7072. desc: "Headings should be defined only once.",
  7073. browsers: "All",
  7074. //initialization
  7075. init: function(parser, reporter){
  7076. var rule = this;
  7077. var headings = {
  7078. h1: 0,
  7079. h2: 0,
  7080. h3: 0,
  7081. h4: 0,
  7082. h5: 0,
  7083. h6: 0
  7084. };
  7085. parser.addListener("startrule", function(event){
  7086. var selectors = event.selectors,
  7087. selector,
  7088. part,
  7089. pseudo,
  7090. i, j;
  7091. for (i=0; i < selectors.length; i++){
  7092. selector = selectors[i];
  7093. part = selector.parts[selector.parts.length-1];
  7094. if (part.elementName && /(h[1-6])/i.test(part.elementName.toString())){
  7095. for (j=0; j < part.modifiers.length; j++){
  7096. if (part.modifiers[j].type == "pseudo"){
  7097. pseudo = true;
  7098. break;
  7099. }
  7100. }
  7101. if (!pseudo){
  7102. headings[RegExp.$1]++;
  7103. if (headings[RegExp.$1] > 1) {
  7104. reporter.report("Heading (" + part.elementName + ") has already been defined.", part.line, part.col, rule);
  7105. }
  7106. }
  7107. }
  7108. }
  7109. });
  7110. parser.addListener("endstylesheet", function(event){
  7111. var prop,
  7112. messages = [];
  7113. for (prop in headings){
  7114. if (headings.hasOwnProperty(prop)){
  7115. if (headings[prop] > 1){
  7116. messages.push(headings[prop] + " " + prop + "s");
  7117. }
  7118. }
  7119. }
  7120. if (messages.length){
  7121. reporter.rollupWarn("You have " + messages.join(", ") + " defined in this stylesheet.", rule);
  7122. }
  7123. });
  7124. }
  7125. });
  7126. /*
  7127. * Rule: Don't use universal selector because it's slow.
  7128. */
  7129. /*global CSSLint*/
  7130. CSSLint.addRule({
  7131. //rule information
  7132. id: "universal-selector",
  7133. name: "Disallow universal selector",
  7134. desc: "The universal selector (*) is known to be slow.",
  7135. browsers: "All",
  7136. //initialization
  7137. init: function(parser, reporter){
  7138. var rule = this;
  7139. parser.addListener("startrule", function(event){
  7140. var selectors = event.selectors,
  7141. selector,
  7142. part,
  7143. modifier,
  7144. i, j, k;
  7145. for (i=0; i < selectors.length; i++){
  7146. selector = selectors[i];
  7147. part = selector.parts[selector.parts.length-1];
  7148. if (part.elementName == "*"){
  7149. reporter.report(rule.desc, part.line, part.col, rule);
  7150. }
  7151. }
  7152. });
  7153. }
  7154. });
  7155. /*
  7156. * Rule: Don't use unqualified attribute selectors because they're just like universal selectors.
  7157. */
  7158. /*global CSSLint*/
  7159. CSSLint.addRule({
  7160. //rule information
  7161. id: "unqualified-attributes",
  7162. name: "Disallow unqualified attribute selectors",
  7163. desc: "Unqualified attribute selectors are known to be slow.",
  7164. browsers: "All",
  7165. //initialization
  7166. init: function(parser, reporter){
  7167. var rule = this;
  7168. parser.addListener("startrule", function(event){
  7169. var selectors = event.selectors,
  7170. selector,
  7171. part,
  7172. modifier,
  7173. i, j, k;
  7174. for (i=0; i < selectors.length; i++){
  7175. selector = selectors[i];
  7176. part = selector.parts[selector.parts.length-1];
  7177. if (part.type == parser.SELECTOR_PART_TYPE){
  7178. for (k=0; k < part.modifiers.length; k++){
  7179. modifier = part.modifiers[k];
  7180. if (modifier.type == "attribute" && (!part.elementName || part.elementName == "*")){
  7181. reporter.report(rule.desc, part.line, part.col, rule);
  7182. }
  7183. }
  7184. }
  7185. }
  7186. });
  7187. }
  7188. });
  7189. /*
  7190. * Rule: When using a vendor-prefixed property, make sure to
  7191. * include the standard one.
  7192. */
  7193. /*global CSSLint*/
  7194. CSSLint.addRule({
  7195. //rule information
  7196. id: "vendor-prefix",
  7197. name: "Require standard property with vendor prefix",
  7198. desc: "When using a vendor-prefixed property, make sure to include the standard one.",
  7199. browsers: "All",
  7200. //initialization
  7201. init: function(parser, reporter){
  7202. var rule = this,
  7203. properties,
  7204. num,
  7205. propertiesToCheck = {
  7206. "-webkit-border-radius": "border-radius",
  7207. "-webkit-border-top-left-radius": "border-top-left-radius",
  7208. "-webkit-border-top-right-radius": "border-top-right-radius",
  7209. "-webkit-border-bottom-left-radius": "border-bottom-left-radius",
  7210. "-webkit-border-bottom-right-radius": "border-bottom-right-radius",
  7211. "-o-border-radius": "border-radius",
  7212. "-o-border-top-left-radius": "border-top-left-radius",
  7213. "-o-border-top-right-radius": "border-top-right-radius",
  7214. "-o-border-bottom-left-radius": "border-bottom-left-radius",
  7215. "-o-border-bottom-right-radius": "border-bottom-right-radius",
  7216. "-moz-border-radius": "border-radius",
  7217. "-moz-border-radius-topleft": "border-top-left-radius",
  7218. "-moz-border-radius-topright": "border-top-right-radius",
  7219. "-moz-border-radius-bottomleft": "border-bottom-left-radius",
  7220. "-moz-border-radius-bottomright": "border-bottom-right-radius",
  7221. "-moz-column-count": "column-count",
  7222. "-webkit-column-count": "column-count",
  7223. "-moz-column-gap": "column-gap",
  7224. "-webkit-column-gap": "column-gap",
  7225. "-moz-column-rule": "column-rule",
  7226. "-webkit-column-rule": "column-rule",
  7227. "-moz-column-rule-style": "column-rule-style",
  7228. "-webkit-column-rule-style": "column-rule-style",
  7229. "-moz-column-rule-color": "column-rule-color",
  7230. "-webkit-column-rule-color": "column-rule-color",
  7231. "-moz-column-rule-width": "column-rule-width",
  7232. "-webkit-column-rule-width": "column-rule-width",
  7233. "-moz-column-width": "column-width",
  7234. "-webkit-column-width": "column-width",
  7235. "-webkit-column-span": "column-span",
  7236. "-webkit-columns": "columns",
  7237. "-moz-box-shadow": "box-shadow",
  7238. "-webkit-box-shadow": "box-shadow",
  7239. "-moz-transform" : "transform",
  7240. "-webkit-transform" : "transform",
  7241. "-o-transform" : "transform",
  7242. "-ms-transform" : "transform",
  7243. "-moz-transform-origin" : "transform-origin",
  7244. "-webkit-transform-origin" : "transform-origin",
  7245. "-o-transform-origin" : "transform-origin",
  7246. "-ms-transform-origin" : "transform-origin",
  7247. "-moz-box-sizing" : "box-sizing",
  7248. "-webkit-box-sizing" : "box-sizing",
  7249. "-moz-user-select" : "user-select",
  7250. "-khtml-user-select" : "user-select",
  7251. "-webkit-user-select" : "user-select"
  7252. };
  7253. //event handler for beginning of rules
  7254. function startRule(){
  7255. properties = {};
  7256. num=1;
  7257. }
  7258. //event handler for end of rules
  7259. function endRule(event){
  7260. var prop,
  7261. i, len,
  7262. standard,
  7263. needed,
  7264. actual,
  7265. needsStandard = [];
  7266. for (prop in properties){
  7267. if (propertiesToCheck[prop]){
  7268. needsStandard.push({ actual: prop, needed: propertiesToCheck[prop]});
  7269. }
  7270. }
  7271. for (i=0, len=needsStandard.length; i < len; i++){
  7272. needed = needsStandard[i].needed;
  7273. actual = needsStandard[i].actual;
  7274. if (!properties[needed]){
  7275. reporter.report("Missing standard property '" + needed + "' to go along with '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule);
  7276. } else {
  7277. //make sure standard property is last
  7278. if (properties[needed][0].pos < properties[actual][0].pos){
  7279. reporter.report("Standard property '" + needed + "' should come after vendor-prefixed property '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule);
  7280. }
  7281. }
  7282. }
  7283. }
  7284. parser.addListener("startrule", startRule);
  7285. parser.addListener("startfontface", startRule);
  7286. parser.addListener("startpage", startRule);
  7287. parser.addListener("startpagemargin", startRule);
  7288. parser.addListener("startkeyframerule", startRule);
  7289. parser.addListener("property", function(event){
  7290. var name = event.property.text.toLowerCase();
  7291. if (!properties[name]){
  7292. properties[name] = [];
  7293. }
  7294. properties[name].push({ name: event.property, value : event.value, pos:num++ });
  7295. });
  7296. parser.addListener("endrule", endRule);
  7297. parser.addListener("endfontface", endRule);
  7298. parser.addListener("endpage", endRule);
  7299. parser.addListener("endpagemargin", endRule);
  7300. parser.addListener("endkeyframerule", endRule);
  7301. }
  7302. });
  7303. /*
  7304. * Rule: You don't need to specify units when a value is 0.
  7305. */
  7306. /*global CSSLint*/
  7307. CSSLint.addRule({
  7308. //rule information
  7309. id: "zero-units",
  7310. name: "Disallow units for 0 values",
  7311. desc: "You don't need to specify units when a value is 0.",
  7312. browsers: "All",
  7313. //initialization
  7314. init: function(parser, reporter){
  7315. var rule = this;
  7316. //count how many times "float" is used
  7317. parser.addListener("property", function(event){
  7318. var parts = event.value.parts,
  7319. i = 0,
  7320. len = parts.length;
  7321. while(i < len){
  7322. if ((parts[i].units || parts[i].type == "percentage") && parts[i].value === 0 && parts[i].type != "time"){
  7323. reporter.report("Values of 0 shouldn't have units specified.", parts[i].line, parts[i].col, rule);
  7324. }
  7325. i++;
  7326. }
  7327. });
  7328. }
  7329. });
  7330. /*global CSSLint*/
  7331. (function() {
  7332. /**
  7333. * Replace special characters before write to output.
  7334. *
  7335. * Rules:
  7336. * - single quotes is the escape sequence for double-quotes
  7337. * - &amp; is the escape sequence for &
  7338. * - &lt; is the escape sequence for <
  7339. * - &gt; is the escape sequence for >
  7340. *
  7341. * @param {String} message to escape
  7342. * @return escaped message as {String}
  7343. */
  7344. var xmlEscape = function(str) {
  7345. if (!str || str.constructor !== String) {
  7346. return "";
  7347. }
  7348. return str.replace(/[\"&><]/g, function(match) {
  7349. switch (match) {
  7350. case "\"":
  7351. return "&quot;";
  7352. case "&":
  7353. return "&amp;";
  7354. case "<":
  7355. return "&lt;";
  7356. case ">":
  7357. return "&gt;";
  7358. }
  7359. });
  7360. };
  7361. CSSLint.addFormatter({
  7362. //format information
  7363. id: "checkstyle-xml",
  7364. name: "Checkstyle XML format",
  7365. /**
  7366. * Return opening root XML tag.
  7367. * @return {String} to prepend before all results
  7368. */
  7369. startFormat: function(){
  7370. return "<?xml version=\"1.0\" encoding=\"utf-8\"?><checkstyle>";
  7371. },
  7372. /**
  7373. * Return closing root XML tag.
  7374. * @return {String} to append after all results
  7375. */
  7376. endFormat: function(){
  7377. return "</checkstyle>";
  7378. },
  7379. /**
  7380. * Returns message when there is a file read error.
  7381. * @param {String} filename The name of the file that caused the error.
  7382. * @param {String} message The error message
  7383. * @return {String} The error message.
  7384. */
  7385. readError: function(filename, message) {
  7386. return "<file name=\"" + xmlEscape(filename) + "\"><error line=\"0\" column=\"0\" severty=\"error\" message=\"" + xmlEscape(message) + "\"></error></file>";
  7387. },
  7388. /**
  7389. * Given CSS Lint results for a file, return output for this format.
  7390. * @param results {Object} with error and warning messages
  7391. * @param filename {String} relative file path
  7392. * @param options {Object} (UNUSED for now) specifies special handling of output
  7393. * @return {String} output for results
  7394. */
  7395. formatResults: function(results, filename, options) {
  7396. var messages = results.messages,
  7397. output = [];
  7398. /**
  7399. * Generate a source string for a rule.
  7400. * Checkstyle source strings usually resemble Java class names e.g
  7401. * net.csslint.SomeRuleName
  7402. * @param {Object} rule
  7403. * @return rule source as {String}
  7404. */
  7405. var generateSource = function(rule) {
  7406. if (!rule || !('name' in rule)) {
  7407. return "";
  7408. }
  7409. return 'net.csslint.' + rule.name.replace(/\s/g,'');
  7410. };
  7411. if (messages.length > 0) {
  7412. output.push("<file name=\""+filename+"\">");
  7413. CSSLint.Util.forEach(messages, function (message, i) {
  7414. //ignore rollups for now
  7415. if (!message.rollup) {
  7416. output.push("<error line=\"" + message.line + "\" column=\"" + message.col + "\" severity=\"" + message.type + "\"" +
  7417. " message=\"" + xmlEscape(message.message) + "\" source=\"" + generateSource(message.rule) +"\"/>");
  7418. }
  7419. });
  7420. output.push("</file>");
  7421. }
  7422. return output.join("");
  7423. }
  7424. });
  7425. }());
  7426. /*global CSSLint*/
  7427. CSSLint.addFormatter({
  7428. //format information
  7429. id: "compact",
  7430. name: "Compact, 'porcelain' format",
  7431. /**
  7432. * Return content to be printed before all file results.
  7433. * @return {String} to prepend before all results
  7434. */
  7435. startFormat: function() {
  7436. return "";
  7437. },
  7438. /**
  7439. * Return content to be printed after all file results.
  7440. * @return {String} to append after all results
  7441. */
  7442. endFormat: function() {
  7443. return "";
  7444. },
  7445. /**
  7446. * Given CSS Lint results for a file, return output for this format.
  7447. * @param results {Object} with error and warning messages
  7448. * @param filename {String} relative file path
  7449. * @param options {Object} (Optional) specifies special handling of output
  7450. * @return {String} output for results
  7451. */
  7452. formatResults: function(results, filename, options) {
  7453. var messages = results.messages,
  7454. output = "";
  7455. options = options || {};
  7456. /**
  7457. * Capitalize and return given string.
  7458. * @param str {String} to capitalize
  7459. * @return {String} capitalized
  7460. */
  7461. var capitalize = function(str) {
  7462. return str.charAt(0).toUpperCase() + str.slice(1);
  7463. };
  7464. if (messages.length === 0) {
  7465. return options.quiet ? "" : filename + ": Lint Free!";
  7466. }
  7467. CSSLint.Util.forEach(messages, function(message, i) {
  7468. if (message.rollup) {
  7469. output += filename + ": " + capitalize(message.type) + " - " + message.message + "\n";
  7470. } else {
  7471. output += filename + ": " + "line " + message.line +
  7472. ", col " + message.col + ", " + capitalize(message.type) + " - " + message.message + "\n";
  7473. }
  7474. });
  7475. return output;
  7476. }
  7477. });
  7478. /*global CSSLint*/
  7479. CSSLint.addFormatter({
  7480. //format information
  7481. id: "csslint-xml",
  7482. name: "CSSLint XML format",
  7483. /**
  7484. * Return opening root XML tag.
  7485. * @return {String} to prepend before all results
  7486. */
  7487. startFormat: function(){
  7488. return "<?xml version=\"1.0\" encoding=\"utf-8\"?><csslint>";
  7489. },
  7490. /**
  7491. * Return closing root XML tag.
  7492. * @return {String} to append after all results
  7493. */
  7494. endFormat: function(){
  7495. return "</csslint>";
  7496. },
  7497. /**
  7498. * Given CSS Lint results for a file, return output for this format.
  7499. * @param results {Object} with error and warning messages
  7500. * @param filename {String} relative file path
  7501. * @param options {Object} (UNUSED for now) specifies special handling of output
  7502. * @return {String} output for results
  7503. */
  7504. formatResults: function(results, filename, options) {
  7505. var messages = results.messages,
  7506. output = [];
  7507. /**
  7508. * Replace special characters before write to output.
  7509. *
  7510. * Rules:
  7511. * - single quotes is the escape sequence for double-quotes
  7512. * - &amp; is the escape sequence for &
  7513. * - &lt; is the escape sequence for <
  7514. * - &gt; is the escape sequence for >
  7515. *
  7516. * @param {String} message to escape
  7517. * @return escaped message as {String}
  7518. */
  7519. var escapeSpecialCharacters = function(str) {
  7520. if (!str || str.constructor !== String) {
  7521. return "";
  7522. }
  7523. return str.replace(/\"/g, "'").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
  7524. };
  7525. if (messages.length > 0) {
  7526. output.push("<file name=\""+filename+"\">");
  7527. CSSLint.Util.forEach(messages, function (message, i) {
  7528. if (message.rollup) {
  7529. output.push("<issue severity=\"" + message.type + "\" reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
  7530. } else {
  7531. output.push("<issue line=\"" + message.line + "\" char=\"" + message.col + "\" severity=\"" + message.type + "\"" +
  7532. " reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
  7533. }
  7534. });
  7535. output.push("</file>");
  7536. }
  7537. return output.join("");
  7538. }
  7539. });
  7540. /*global CSSLint*/
  7541. CSSLint.addFormatter({
  7542. //format information
  7543. id: "junit-xml",
  7544. name: "JUNIT XML format",
  7545. /**
  7546. * Return opening root XML tag.
  7547. * @return {String} to prepend before all results
  7548. */
  7549. startFormat: function(){
  7550. return "<?xml version=\"1.0\" encoding=\"utf-8\"?><testsuites>";
  7551. },
  7552. /**
  7553. * Return closing root XML tag.
  7554. * @return {String} to append after all results
  7555. */
  7556. endFormat: function() {
  7557. return "</testsuites>";
  7558. },
  7559. /**
  7560. * Given CSS Lint results for a file, return output for this format.
  7561. * @param results {Object} with error and warning messages
  7562. * @param filename {String} relative file path
  7563. * @param options {Object} (UNUSED for now) specifies special handling of output
  7564. * @return {String} output for results
  7565. */
  7566. formatResults: function(results, filename, options) {
  7567. var messages = results.messages,
  7568. output = [],
  7569. tests = {
  7570. 'error': 0,
  7571. 'failure': 0
  7572. };
  7573. /**
  7574. * Generate a source string for a rule.
  7575. * JUNIT source strings usually resemble Java class names e.g
  7576. * net.csslint.SomeRuleName
  7577. * @param {Object} rule
  7578. * @return rule source as {String}
  7579. */
  7580. var generateSource = function(rule) {
  7581. if (!rule || !('name' in rule)) {
  7582. return "";
  7583. }
  7584. return 'net.csslint.' + rule.name.replace(/\s/g,'');
  7585. };
  7586. /**
  7587. * Replace special characters before write to output.
  7588. *
  7589. * Rules:
  7590. * - single quotes is the escape sequence for double-quotes
  7591. * - &lt; is the escape sequence for <
  7592. * - &gt; is the escape sequence for >
  7593. *
  7594. * @param {String} message to escape
  7595. * @return escaped message as {String}
  7596. */
  7597. var escapeSpecialCharacters = function(str) {
  7598. if (!str || str.constructor !== String) {
  7599. return "";
  7600. }
  7601. return str.replace(/\"/g, "'").replace(/</g, "&lt;").replace(/>/g, "&gt;");
  7602. };
  7603. if (messages.length > 0) {
  7604. messages.forEach(function (message, i) {
  7605. // since junit has no warning class
  7606. // all issues as errors
  7607. var type = message.type === 'warning' ? 'error' : message.type;
  7608. //ignore rollups for now
  7609. if (!message.rollup) {
  7610. // build the test case seperately, once joined
  7611. // we'll add it to a custom array filtered by type
  7612. output.push("<testcase time=\"0\" name=\"" + generateSource(message.rule) + "\">");
  7613. output.push("<" + type + " message=\"" + escapeSpecialCharacters(message.message) + "\"><![CDATA[" + message.line + ':' + message.col + ':' + escapeSpecialCharacters(message.evidence) + "]]></" + type + ">");
  7614. output.push("</testcase>");
  7615. tests[type] += 1;
  7616. }
  7617. });
  7618. output.unshift("<testsuite time=\"0\" tests=\"" + messages.length + "\" skipped=\"0\" errors=\"" + tests.error + "\" failures=\"" + tests.failure + "\" package=\"net.csslint\" name=\"" + filename + "\">");
  7619. output.push("</testsuite>");
  7620. }
  7621. return output.join("");
  7622. }
  7623. });
  7624. /*global CSSLint*/
  7625. CSSLint.addFormatter({
  7626. //format information
  7627. id: "lint-xml",
  7628. name: "Lint XML format",
  7629. /**
  7630. * Return opening root XML tag.
  7631. * @return {String} to prepend before all results
  7632. */
  7633. startFormat: function(){
  7634. return "<?xml version=\"1.0\" encoding=\"utf-8\"?><lint>";
  7635. },
  7636. /**
  7637. * Return closing root XML tag.
  7638. * @return {String} to append after all results
  7639. */
  7640. endFormat: function(){
  7641. return "</lint>";
  7642. },
  7643. /**
  7644. * Given CSS Lint results for a file, return output for this format.
  7645. * @param results {Object} with error and warning messages
  7646. * @param filename {String} relative file path
  7647. * @param options {Object} (UNUSED for now) specifies special handling of output
  7648. * @return {String} output for results
  7649. */
  7650. formatResults: function(results, filename, options) {
  7651. var messages = results.messages,
  7652. output = [];
  7653. /**
  7654. * Replace special characters before write to output.
  7655. *
  7656. * Rules:
  7657. * - single quotes is the escape sequence for double-quotes
  7658. * - &amp; is the escape sequence for &
  7659. * - &lt; is the escape sequence for <
  7660. * - &gt; is the escape sequence for >
  7661. *
  7662. * @param {String} message to escape
  7663. * @return escaped message as {String}
  7664. */
  7665. var escapeSpecialCharacters = function(str) {
  7666. if (!str || str.constructor !== String) {
  7667. return "";
  7668. }
  7669. return str.replace(/\"/g, "'").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
  7670. };
  7671. if (messages.length > 0) {
  7672. output.push("<file name=\""+filename+"\">");
  7673. CSSLint.Util.forEach(messages, function (message, i) {
  7674. if (message.rollup) {
  7675. output.push("<issue severity=\"" + message.type + "\" reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
  7676. } else {
  7677. output.push("<issue line=\"" + message.line + "\" char=\"" + message.col + "\" severity=\"" + message.type + "\"" +
  7678. " reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
  7679. }
  7680. });
  7681. output.push("</file>");
  7682. }
  7683. return output.join("");
  7684. }
  7685. });
  7686. /*global CSSLint*/
  7687. CSSLint.addFormatter({
  7688. //format information
  7689. id: "text",
  7690. name: "Plain Text",
  7691. /**
  7692. * Return content to be printed before all file results.
  7693. * @return {String} to prepend before all results
  7694. */
  7695. startFormat: function() {
  7696. return "";
  7697. },
  7698. /**
  7699. * Return content to be printed after all file results.
  7700. * @return {String} to append after all results
  7701. */
  7702. endFormat: function() {
  7703. return "";
  7704. },
  7705. /**
  7706. * Given CSS Lint results for a file, return output for this format.
  7707. * @param results {Object} with error and warning messages
  7708. * @param filename {String} relative file path
  7709. * @param options {Object} (Optional) specifies special handling of output
  7710. * @return {String} output for results
  7711. */
  7712. formatResults: function(results, filename, options) {
  7713. var messages = results.messages,
  7714. output = "";
  7715. options = options || {};
  7716. if (messages.length === 0) {
  7717. return options.quiet ? "" : "\n\ncsslint: No errors in " + filename + ".";
  7718. }
  7719. output = "\n\ncsslint: There are " + messages.length + " problems in " + filename + ".";
  7720. var pos = filename.lastIndexOf("/"),
  7721. shortFilename = filename;
  7722. if (pos === -1){
  7723. pos = filename.lastIndexOf("\\");
  7724. }
  7725. if (pos > -1){
  7726. shortFilename = filename.substring(pos+1);
  7727. }
  7728. CSSLint.Util.forEach(messages, function (message, i) {
  7729. output = output + "\n\n" + shortFilename;
  7730. if (message.rollup) {
  7731. output += "\n" + (i+1) + ": " + message.type;
  7732. output += "\n" + message.message;
  7733. } else {
  7734. output += "\n" + (i+1) + ": " + message.type + " at line " + message.line + ", col " + message.col;
  7735. output += "\n" + message.message;
  7736. output += "\n" + message.evidence;
  7737. }
  7738. });
  7739. return output;
  7740. }
  7741. });
  7742. exports.CSSLint = CSSLint;
  7743. });