123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311 |
- <?php
- require_once( dirname(__FILE__).'/Cache.php');
- /**
- * Class for parsing and compiling less files into css
- *
- * @package Less
- * @subpackage parser
- *
- */
- class Less_Parser{
- /**
- * Default parser options
- */
- public static $default_options = array(
- 'compress' => false, // option - whether to compress
- 'strictUnits' => false, // whether units need to evaluate correctly
- 'strictMath' => false, // whether math has to be within parenthesis
- 'relativeUrls' => true, // option - whether to adjust URL's to be relative
- 'urlArgs' => array(), // whether to add args into url tokens
- 'numPrecision' => 8,
- 'import_dirs' => array(),
- 'import_callback' => null,
- 'cache_dir' => null,
- 'cache_method' => 'php', // false, 'serialize', 'php', 'var_export', 'callback';
- 'cache_callback_get' => null,
- 'cache_callback_set' => null,
- 'sourceMap' => false, // whether to output a source map
- 'sourceMapBasepath' => null,
- 'sourceMapWriteTo' => null,
- 'sourceMapURL' => null,
- 'plugins' => array(),
- );
- public static $options = array();
- private $input; // Less input string
- private $input_len; // input string length
- private $pos; // current index in `input`
- private $saveStack = array(); // holds state for backtracking
- private $furthest;
- /**
- * @var Less_Environment
- */
- private $env;
- private $rules = array();
- private static $imports = array();
- public static $has_extends = false;
- public static $next_id = 0;
- /**
- * Filename to contents of all parsed the files
- *
- * @var array
- */
- public static $contentsMap = array();
- /**
- * @param Less_Environment|array|null $env
- */
- public function __construct( $env = null ){
- // Top parser on an import tree must be sure there is one "env"
- // which will then be passed around by reference.
- if( $env instanceof Less_Environment ){
- $this->env = $env;
- }else{
- $this->SetOptions(Less_Parser::$default_options);
- $this->Reset( $env );
- }
- }
- /**
- * Reset the parser state completely
- *
- */
- public function Reset( $options = null ){
- $this->rules = array();
- self::$imports = array();
- self::$has_extends = false;
- self::$imports = array();
- self::$contentsMap = array();
- $this->env = new Less_Environment($options);
- $this->env->Init();
- //set new options
- if( is_array($options) ){
- $this->SetOptions(Less_Parser::$default_options);
- $this->SetOptions($options);
- }
- }
- /**
- * Set one or more compiler options
- * options: import_dirs, cache_dir, cache_method
- *
- */
- public function SetOptions( $options ){
- foreach($options as $option => $value){
- $this->SetOption($option,$value);
- }
- }
- /**
- * Set one compiler option
- *
- */
- public function SetOption($option,$value){
- switch($option){
- case 'import_dirs':
- $this->SetImportDirs($value);
- return;
- case 'cache_dir':
- if( is_string($value) ){
- Less_Cache::SetCacheDir($value);
- Less_Cache::CheckCacheDir();
- }
- return;
- }
- Less_Parser::$options[$option] = $value;
- }
- /**
- * Registers a new custom function
- *
- * @param string $name function name
- * @param callable $callback callback
- */
- public function registerFunction($name, $callback) {
- $this->env->functions[$name] = $callback;
- }
- /**
- * Removed an already registered function
- *
- * @param string $name function name
- */
- public function unregisterFunction($name) {
- if( isset($this->env->functions[$name]) )
- unset($this->env->functions[$name]);
- }
- /**
- * Get the current css buffer
- *
- * @return string
- */
- public function getCss(){
- $precision = ini_get('precision');
- @ini_set('precision',16);
- $locale = setlocale(LC_NUMERIC, 0);
- setlocale(LC_NUMERIC, "C");
- try {
- $root = new Less_Tree_Ruleset(array(), $this->rules );
- $root->root = true;
- $root->firstRoot = true;
- $this->PreVisitors($root);
- self::$has_extends = false;
- $evaldRoot = $root->compile($this->env);
- $this->PostVisitors($evaldRoot);
- if( Less_Parser::$options['sourceMap'] ){
- $generator = new Less_SourceMap_Generator($evaldRoot, Less_Parser::$contentsMap, Less_Parser::$options );
- // will also save file
- // FIXME: should happen somewhere else?
- $css = $generator->generateCSS();
- }else{
- $css = $evaldRoot->toCSS();
- }
- if( Less_Parser::$options['compress'] ){
- $css = preg_replace('/(^(\s)+)|((\s)+$)/', '', $css);
- }
- } catch (Exception $exc) {
- // Intentional fall-through so we can reset environment
- }
- //reset php settings
- @ini_set('precision',$precision);
- setlocale(LC_NUMERIC, $locale);
- // Rethrow exception after we handled resetting the environment
- if (!empty($exc)) {
- throw $exc;
- }
- return $css;
- }
- /**
- * Run pre-compile visitors
- *
- */
- private function PreVisitors($root){
- if( Less_Parser::$options['plugins'] ){
- foreach(Less_Parser::$options['plugins'] as $plugin){
- if( !empty($plugin->isPreEvalVisitor) ){
- $plugin->run($root);
- }
- }
- }
- }
- /**
- * Run post-compile visitors
- *
- */
- private function PostVisitors($evaldRoot){
- $visitors = array();
- $visitors[] = new Less_Visitor_joinSelector();
- if( self::$has_extends ){
- $visitors[] = new Less_Visitor_processExtends();
- }
- $visitors[] = new Less_Visitor_toCSS();
- if( Less_Parser::$options['plugins'] ){
- foreach(Less_Parser::$options['plugins'] as $plugin){
- if( property_exists($plugin,'isPreEvalVisitor') && $plugin->isPreEvalVisitor ){
- continue;
- }
- if( property_exists($plugin,'isPreVisitor') && $plugin->isPreVisitor ){
- array_unshift( $visitors, $plugin);
- }else{
- $visitors[] = $plugin;
- }
- }
- }
- for($i = 0; $i < count($visitors); $i++ ){
- $visitors[$i]->run($evaldRoot);
- }
- }
- /**
- * Parse a Less string into css
- *
- * @param string $str The string to convert
- * @param string $uri_root The url of the file
- * @return Less_Tree_Ruleset|Less_Parser
- */
- public function parse( $str, $file_uri = null ){
- if( !$file_uri ){
- $uri_root = '';
- $filename = 'anonymous-file-'.Less_Parser::$next_id++.'.less';
- }else{
- $file_uri = self::WinPath($file_uri);
- $filename = basename($file_uri);
- $uri_root = dirname($file_uri);
- }
- $previousFileInfo = $this->env->currentFileInfo;
- $uri_root = self::WinPath($uri_root);
- $this->SetFileInfo($filename, $uri_root);
- $this->input = $str;
- $this->_parse();
- if( $previousFileInfo ){
- $this->env->currentFileInfo = $previousFileInfo;
- }
- return $this;
- }
- /**
- * Parse a Less string from a given file
- *
- * @throws Less_Exception_Parser
- * @param string $filename The file to parse
- * @param string $uri_root The url of the file
- * @param bool $returnRoot Indicates whether the return value should be a css string a root node
- * @return Less_Tree_Ruleset|Less_Parser
- */
- public function parseFile( $filename, $uri_root = '', $returnRoot = false){
- if( !file_exists($filename) ){
- $this->Error(sprintf('File `%s` not found.', $filename));
- }
- // fix uri_root?
- // Instead of The mixture of file path for the first argument and directory path for the second argument has bee
- if( !$returnRoot && !empty($uri_root) && basename($uri_root) == basename($filename) ){
- $uri_root = dirname($uri_root);
- }
- $previousFileInfo = $this->env->currentFileInfo;
- if( $filename ){
- $filename = self::WinPath(realpath($filename));
- }
- $uri_root = self::WinPath($uri_root);
- $this->SetFileInfo($filename, $uri_root);
- self::AddParsedFile($filename);
- if( $returnRoot ){
- $rules = $this->GetRules( $filename );
- $return = new Less_Tree_Ruleset(array(), $rules );
- }else{
- $this->_parse( $filename );
- $return = $this;
- }
- if( $previousFileInfo ){
- $this->env->currentFileInfo = $previousFileInfo;
- }
- return $return;
- }
- /**
- * Allows a user to set variables values
- * @param array $vars
- * @return Less_Parser
- */
- public function ModifyVars( $vars ){
- $this->input = Less_Parser::serializeVars( $vars );
- $this->_parse();
- return $this;
- }
- /**
- * @param string $filename
- */
- public function SetFileInfo( $filename, $uri_root = ''){
- $filename = Less_Environment::normalizePath($filename);
- $dirname = preg_replace('/[^\/\\\\]*$/','',$filename);
- if( !empty($uri_root) ){
- $uri_root = rtrim($uri_root,'/').'/';
- }
- $currentFileInfo = array();
- //entry info
- if( isset($this->env->currentFileInfo) ){
- $currentFileInfo['entryPath'] = $this->env->currentFileInfo['entryPath'];
- $currentFileInfo['entryUri'] = $this->env->currentFileInfo['entryUri'];
- $currentFileInfo['rootpath'] = $this->env->currentFileInfo['rootpath'];
- }else{
- $currentFileInfo['entryPath'] = $dirname;
- $currentFileInfo['entryUri'] = $uri_root;
- $currentFileInfo['rootpath'] = $dirname;
- }
- $currentFileInfo['currentDirectory'] = $dirname;
- $currentFileInfo['currentUri'] = $uri_root.basename($filename);
- $currentFileInfo['filename'] = $filename;
- $currentFileInfo['uri_root'] = $uri_root;
- //inherit reference
- if( isset($this->env->currentFileInfo['reference']) && $this->env->currentFileInfo['reference'] ){
- $currentFileInfo['reference'] = true;
- }
- $this->env->currentFileInfo = $currentFileInfo;
- }
- /**
- * @deprecated 1.5.1.2
- *
- */
- public function SetCacheDir( $dir ){
- if( !file_exists($dir) ){
- if( mkdir($dir) ){
- return true;
- }
- throw new Less_Exception_Parser('Less.php cache directory couldn\'t be created: '.$dir);
- }elseif( !is_dir($dir) ){
- throw new Less_Exception_Parser('Less.php cache directory doesn\'t exist: '.$dir);
- }elseif( !is_writable($dir) ){
- throw new Less_Exception_Parser('Less.php cache directory isn\'t writable: '.$dir);
- }else{
- $dir = self::WinPath($dir);
- Less_Cache::$cache_dir = rtrim($dir,'/').'/';
- return true;
- }
- }
- /**
- * Set a list of directories or callbacks the parser should use for determining import paths
- *
- * @param array $dirs
- */
- public function SetImportDirs( $dirs ){
- Less_Parser::$options['import_dirs'] = array();
- foreach($dirs as $path => $uri_root){
- $path = self::WinPath($path);
- if( !empty($path) ){
- $path = rtrim($path,'/').'/';
- }
- if ( !is_callable($uri_root) ){
- $uri_root = self::WinPath($uri_root);
- if( !empty($uri_root) ){
- $uri_root = rtrim($uri_root,'/').'/';
- }
- }
- Less_Parser::$options['import_dirs'][$path] = $uri_root;
- }
- }
- /**
- * @param string $file_path
- */
- private function _parse( $file_path = null ){
- if (ini_get("mbstring.func_overload")) {
- $mb_internal_encoding = ini_get("mbstring.internal_encoding");
- @ini_set("mbstring.internal_encoding", "ascii");
- }
- $this->rules = array_merge($this->rules, $this->GetRules( $file_path ));
- //reset php settings
- if (isset($mb_internal_encoding)) {
- @ini_set("mbstring.internal_encoding", $mb_internal_encoding);
- }
- }
- /**
- * Return the results of parsePrimary for $file_path
- * Use cache and save cached results if possible
- *
- * @param string|null $file_path
- */
- private function GetRules( $file_path ){
- $this->SetInput($file_path);
- $cache_file = $this->CacheFile( $file_path );
- if( $cache_file ){
- if( Less_Parser::$options['cache_method'] == 'callback' ){
- if( is_callable(Less_Parser::$options['cache_callback_get']) ){
- $cache = call_user_func_array(
- Less_Parser::$options['cache_callback_get'],
- array($this, $file_path, $cache_file)
- );
- if( $cache ){
- $this->UnsetInput();
- return $cache;
- }
- }
- }elseif( file_exists($cache_file) ){
- switch(Less_Parser::$options['cache_method']){
- // Using serialize
- // Faster but uses more memory
- case 'serialize':
- $cache = unserialize(file_get_contents($cache_file));
- if( $cache ){
- touch($cache_file);
- $this->UnsetInput();
- return $cache;
- }
- break;
- // Using generated php code
- case 'var_export':
- case 'php':
- $this->UnsetInput();
- return include($cache_file);
- }
- }
- }
- $rules = $this->parsePrimary();
- if( $this->pos < $this->input_len ){
- throw new Less_Exception_Chunk($this->input, null, $this->furthest, $this->env->currentFileInfo);
- }
- $this->UnsetInput();
- //save the cache
- if( $cache_file ){
- if( Less_Parser::$options['cache_method'] == 'callback' ){
- if( is_callable(Less_Parser::$options['cache_callback_set']) ){
- call_user_func_array(
- Less_Parser::$options['cache_callback_set'],
- array($this, $file_path, $cache_file, $rules)
- );
- }
- }else{
- //msg('write cache file');
- switch(Less_Parser::$options['cache_method']){
- case 'serialize':
- file_put_contents( $cache_file, serialize($rules) );
- break;
- case 'php':
- file_put_contents( $cache_file, '<?php return '.self::ArgString($rules).'; ?>' );
- break;
- case 'var_export':
- //Requires __set_state()
- file_put_contents( $cache_file, '<?php return '.var_export($rules,true).'; ?>' );
- break;
- }
- Less_Cache::CleanCache();
- }
- }
- return $rules;
- }
- /**
- * Set up the input buffer
- *
- */
- public function SetInput( $file_path ){
- if( $file_path ){
- $this->input = file_get_contents( $file_path );
- }
- $this->pos = $this->furthest = 0;
- // Remove potential UTF Byte Order Mark
- $this->input = preg_replace('/\\G\xEF\xBB\xBF/', '', $this->input);
- $this->input_len = strlen($this->input);
- if( Less_Parser::$options['sourceMap'] && $this->env->currentFileInfo ){
- $uri = $this->env->currentFileInfo['currentUri'];
- Less_Parser::$contentsMap[$uri] = $this->input;
- }
- }
- /**
- * Free up some memory
- *
- */
- public function UnsetInput(){
- unset($this->input, $this->pos, $this->input_len, $this->furthest);
- $this->saveStack = array();
- }
- public function CacheFile( $file_path ){
- if( $file_path && $this->CacheEnabled() ){
- $env = get_object_vars($this->env);
- unset($env['frames']);
- $parts = array();
- $parts[] = $file_path;
- $parts[] = filesize( $file_path );
- $parts[] = filemtime( $file_path );
- $parts[] = $env;
- $parts[] = Less_Version::cache_version;
- $parts[] = Less_Parser::$options['cache_method'];
- return Less_Cache::$cache_dir . Less_Cache::$prefix . base_convert( sha1(json_encode($parts) ), 16, 36) . '.lesscache';
- }
- }
- static function AddParsedFile($file){
- self::$imports[] = $file;
- }
- static function AllParsedFiles(){
- return self::$imports;
- }
- /**
- * @param string $file
- */
- static function FileParsed($file){
- return in_array($file,self::$imports);
- }
- function save() {
- $this->saveStack[] = $this->pos;
- }
- private function restore() {
- $this->pos = array_pop($this->saveStack);
- }
- private function forget(){
- array_pop($this->saveStack);
- }
- private function isWhitespace($offset = 0) {
- return preg_match('/\s/',$this->input[ $this->pos + $offset]);
- }
- /**
- * Parse from a token, regexp or string, and move forward if match
- *
- * @param array $toks
- * @return array
- */
- private function match($toks){
- // The match is confirmed, add the match length to `this::pos`,
- // and consume any extra white-space characters (' ' || '\n')
- // which come after that. The reason for this is that LeSS's
- // grammar is mostly white-space insensitive.
- //
- foreach($toks as $tok){
- $char = $tok[0];
- if( $char === '/' ){
- $match = $this->MatchReg($tok);
- if( $match ){
- return count($match) === 1 ? $match[0] : $match;
- }
- }elseif( $char === '#' ){
- $match = $this->MatchChar($tok[1]);
- }else{
- // Non-terminal, match using a function call
- $match = $this->$tok();
- }
- if( $match ){
- return $match;
- }
- }
- }
- /**
- * @param string[] $toks
- *
- * @return string
- */
- private function MatchFuncs($toks){
- if( $this->pos < $this->input_len ){
- foreach($toks as $tok){
- $match = $this->$tok();
- if( $match ){
- return $match;
- }
- }
- }
- }
- // Match a single character in the input,
- private function MatchChar($tok){
- if( ($this->pos < $this->input_len) && ($this->input[$this->pos] === $tok) ){
- $this->skipWhitespace(1);
- return $tok;
- }
- }
- // Match a regexp from the current start point
- private function MatchReg($tok){
- if( preg_match($tok, $this->input, $match, 0, $this->pos) ){
- $this->skipWhitespace(strlen($match[0]));
- return $match;
- }
- }
- /**
- * Same as match(), but don't change the state of the parser,
- * just return the match.
- *
- * @param string $tok
- * @return integer
- */
- public function PeekReg($tok){
- return preg_match($tok, $this->input, $match, 0, $this->pos);
- }
- /**
- * @param string $tok
- */
- public function PeekChar($tok){
- //return ($this->input[$this->pos] === $tok );
- return ($this->pos < $this->input_len) && ($this->input[$this->pos] === $tok );
- }
- /**
- * @param integer $length
- */
- public function skipWhitespace($length){
- $this->pos += $length;
- for(; $this->pos < $this->input_len; $this->pos++ ){
- $c = $this->input[$this->pos];
- if( ($c !== "\n") && ($c !== "\r") && ($c !== "\t") && ($c !== ' ') ){
- break;
- }
- }
- }
- /**
- * @param string $tok
- * @param string|null $msg
- */
- public function expect($tok, $msg = NULL) {
- $result = $this->match( array($tok) );
- if (!$result) {
- $this->Error( $msg ? "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'" : $msg );
- } else {
- return $result;
- }
- }
- /**
- * @param string $tok
- */
- public function expectChar($tok, $msg = null ){
- $result = $this->MatchChar($tok);
- if( !$result ){
- $this->Error( $msg ? "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'" : $msg );
- }else{
- return $result;
- }
- }
- //
- // Here in, the parsing rules/functions
- //
- // The basic structure of the syntax tree generated is as follows:
- //
- // Ruleset -> Rule -> Value -> Expression -> Entity
- //
- // Here's some LESS code:
- //
- // .class {
- // color: #fff;
- // border: 1px solid #000;
- // width: @w + 4px;
- // > .child {...}
- // }
- //
- // And here's what the parse tree might look like:
- //
- // Ruleset (Selector '.class', [
- // Rule ("color", Value ([Expression [Color #fff]]))
- // Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
- // Rule ("width", Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]]))
- // Ruleset (Selector [Element '>', '.child'], [...])
- // ])
- //
- // In general, most rules will try to parse a token with the `$()` function, and if the return
- // value is truly, will return a new node, of the relevant type. Sometimes, we need to check
- // first, before parsing, that's when we use `peek()`.
- //
- //
- // The `primary` rule is the *entry* and *exit* point of the parser.
- // The rules here can appear at any level of the parse tree.
- //
- // The recursive nature of the grammar is an interplay between the `block`
- // rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule,
- // as represented by this simplified grammar:
- //
- // primary → (ruleset | rule)+
- // ruleset → selector+ block
- // block → '{' primary '}'
- //
- // Only at one point is the primary rule not called from the
- // block rule: at the root level.
- //
- private function parsePrimary(){
- $root = array();
- while( true ){
- if( $this->pos >= $this->input_len ){
- break;
- }
- $node = $this->parseExtend(true);
- if( $node ){
- $root = array_merge($root,$node);
- continue;
- }
- //$node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseDirective'));
- $node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseNameValue', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseRulesetCall', 'parseDirective'));
- if( $node ){
- $root[] = $node;
- }elseif( !$this->MatchReg('/\\G[\s\n;]+/') ){
- break;
- }
- if( $this->PeekChar('}') ){
- break;
- }
- }
- return $root;
- }
- // We create a Comment node for CSS comments `/* */`,
- // but keep the LeSS comments `//` silent, by just skipping
- // over them.
- private function parseComment(){
- if( $this->input[$this->pos] !== '/' ){
- return;
- }
- if( $this->input[$this->pos+1] === '/' ){
- $match = $this->MatchReg('/\\G\/\/.*/');
- return $this->NewObj4('Less_Tree_Comment',array($match[0], true, $this->pos, $this->env->currentFileInfo));
- }
- //$comment = $this->MatchReg('/\\G\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/');
- $comment = $this->MatchReg('/\\G\/\*(?s).*?\*+\/\n?/');//not the same as less.js to prevent fatal errors
- if( $comment ){
- return $this->NewObj4('Less_Tree_Comment',array($comment[0], false, $this->pos, $this->env->currentFileInfo));
- }
- }
- private function parseComments(){
- $comments = array();
- while( $this->pos < $this->input_len ){
- $comment = $this->parseComment();
- if( !$comment ){
- break;
- }
- $comments[] = $comment;
- }
- return $comments;
- }
- //
- // A string, which supports escaping " and '
- //
- // "milky way" 'he\'s the one!'
- //
- private function parseEntitiesQuoted() {
- $j = $this->pos;
- $e = false;
- $index = $this->pos;
- if( $this->input[$this->pos] === '~' ){
- $j++;
- $e = true; // Escaped strings
- }
- if( $this->input[$j] != '"' && $this->input[$j] !== "'" ){
- return;
- }
- if ($e) {
- $this->MatchChar('~');
- }
- // Fix for #124: match escaped newlines
- //$str = $this->MatchReg('/\\G"((?:[^"\\\\\r\n]|\\\\.)*)"|\'((?:[^\'\\\\\r\n]|\\\\.)*)\'/');
- $str = $this->MatchReg('/\\G"((?:[^"\\\\\r\n]|\\\\.|\\\\\r\n|\\\\[\n\r\f])*)"|\'((?:[^\'\\\\\r\n]|\\\\.|\\\\\r\n|\\\\[\n\r\f])*)\'/');
- if( $str ){
- $result = $str[0][0] == '"' ? $str[1] : $str[2];
- return $this->NewObj5('Less_Tree_Quoted',array($str[0], $result, $e, $index, $this->env->currentFileInfo) );
- }
- return;
- }
- //
- // A catch-all word, such as:
- //
- // black border-collapse
- //
- private function parseEntitiesKeyword(){
- //$k = $this->MatchReg('/\\G[_A-Za-z-][_A-Za-z0-9-]*/');
- $k = $this->MatchReg('/\\G%|\\G[_A-Za-z-][_A-Za-z0-9-]*/');
- if( $k ){
- $k = $k[0];
- $color = $this->fromKeyword($k);
- if( $color ){
- return $color;
- }
- return $this->NewObj1('Less_Tree_Keyword',$k);
- }
- }
- // duplicate of Less_Tree_Color::FromKeyword
- private function FromKeyword( $keyword ){
- $keyword = strtolower($keyword);
- if( Less_Colors::hasOwnProperty($keyword) ){
- // detect named color
- return $this->NewObj1('Less_Tree_Color',substr(Less_Colors::color($keyword), 1));
- }
- if( $keyword === 'transparent' ){
- return $this->NewObj3('Less_Tree_Color', array( array(0, 0, 0), 0, true));
- }
- }
- //
- // A function call
- //
- // rgb(255, 0, 255)
- //
- // We also try to catch IE's `alpha()`, but let the `alpha` parser
- // deal with the details.
- //
- // The arguments are parsed with the `entities.arguments` parser.
- //
- private function parseEntitiesCall(){
- $index = $this->pos;
- if( !preg_match('/\\G([\w-]+|%|progid:[\w\.]+)\(/', $this->input, $name,0,$this->pos) ){
- return;
- }
- $name = $name[1];
- $nameLC = strtolower($name);
- if ($nameLC === 'url') {
- return null;
- }
- $this->pos += strlen($name);
- if( $nameLC === 'alpha' ){
- $alpha_ret = $this->parseAlpha();
- if( $alpha_ret ){
- return $alpha_ret;
- }
- }
- $this->MatchChar('('); // Parse the '(' and consume whitespace.
- $args = $this->parseEntitiesArguments();
- if( !$this->MatchChar(')') ){
- return;
- }
- if ($name) {
- return $this->NewObj4('Less_Tree_Call',array($name, $args, $index, $this->env->currentFileInfo) );
- }
- }
- /**
- * Parse a list of arguments
- *
- * @return array
- */
- private function parseEntitiesArguments(){
- $args = array();
- while( true ){
- $arg = $this->MatchFuncs( array('parseEntitiesAssignment','parseExpression') );
- if( !$arg ){
- break;
- }
- $args[] = $arg;
- if( !$this->MatchChar(',') ){
- break;
- }
- }
- return $args;
- }
- private function parseEntitiesLiteral(){
- return $this->MatchFuncs( array('parseEntitiesDimension','parseEntitiesColor','parseEntitiesQuoted','parseUnicodeDescriptor') );
- }
- // Assignments are argument entities for calls.
- // They are present in ie filter properties as shown below.
- //
- // filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* )
- //
- private function parseEntitiesAssignment() {
- $key = $this->MatchReg('/\\G\w+(?=\s?=)/');
- if( !$key ){
- return;
- }
- if( !$this->MatchChar('=') ){
- return;
- }
- $value = $this->parseEntity();
- if( $value ){
- return $this->NewObj2('Less_Tree_Assignment',array($key[0], $value));
- }
- }
- //
- // Parse url() tokens
- //
- // We use a specific rule for urls, because they don't really behave like
- // standard function calls. The difference is that the argument doesn't have
- // to be enclosed within a string, so it can't be parsed as an Expression.
- //
- private function parseEntitiesUrl(){
- if( $this->input[$this->pos] !== 'u' || !$this->matchReg('/\\Gurl\(/') ){
- return;
- }
- $value = $this->match( array('parseEntitiesQuoted','parseEntitiesVariable','/\\Gdata\:.*?[^\)]+/','/\\G(?:(?:\\\\[\(\)\'"])|[^\(\)\'"])+/') );
- if( !$value ){
- $value = '';
- }
- $this->expectChar(')');
- if( isset($value->value) || $value instanceof Less_Tree_Variable ){
- return $this->NewObj2('Less_Tree_Url',array($value, $this->env->currentFileInfo));
- }
- return $this->NewObj2('Less_Tree_Url', array( $this->NewObj1('Less_Tree_Anonymous',$value), $this->env->currentFileInfo) );
- }
- //
- // A Variable entity, such as `@fink`, in
- //
- // width: @fink + 2px
- //
- // We use a different parser for variable definitions,
- // see `parsers.variable`.
- //
- private function parseEntitiesVariable(){
- $index = $this->pos;
- if ($this->PeekChar('@') && ($name = $this->MatchReg('/\\G@@?[\w-]+/'))) {
- return $this->NewObj3('Less_Tree_Variable', array( $name[0], $index, $this->env->currentFileInfo));
- }
- }
- // A variable entity useing the protective {} e.g. @{var}
- private function parseEntitiesVariableCurly() {
- $index = $this->pos;
- if( $this->input_len > ($this->pos+1) && $this->input[$this->pos] === '@' && ($curly = $this->MatchReg('/\\G@\{([\w-]+)\}/')) ){
- return $this->NewObj3('Less_Tree_Variable',array('@'.$curly[1], $index, $this->env->currentFileInfo));
- }
- }
- //
- // A Hexadecimal color
- //
- // #4F3C2F
- //
- // `rgb` and `hsl` colors are parsed through the `entities.call` parser.
- //
- private function parseEntitiesColor(){
- if ($this->PeekChar('#') && ($rgb = $this->MatchReg('/\\G#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/'))) {
- return $this->NewObj1('Less_Tree_Color',$rgb[1]);
- }
- }
- //
- // A Dimension, that is, a number and a unit
- //
- // 0.5em 95%
- //
- private function parseEntitiesDimension(){
- $c = @ord($this->input[$this->pos]);
- //Is the first char of the dimension 0-9, '.', '+' or '-'
- if (($c > 57 || $c < 43) || $c === 47 || $c == 44){
- return;
- }
- $value = $this->MatchReg('/\\G([+-]?\d*\.?\d+)(%|[a-z]+)?/');
- if( $value ){
- if( isset($value[2]) ){
- return $this->NewObj2('Less_Tree_Dimension', array($value[1],$value[2]));
- }
- return $this->NewObj1('Less_Tree_Dimension',$value[1]);
- }
- }
- //
- // A unicode descriptor, as is used in unicode-range
- //
- // U+0?? or U+00A1-00A9
- //
- function parseUnicodeDescriptor() {
- $ud = $this->MatchReg('/\\G(U\+[0-9a-fA-F?]+)(\-[0-9a-fA-F?]+)?/');
- if( $ud ){
- return $this->NewObj1('Less_Tree_UnicodeDescriptor', $ud[0]);
- }
- }
- //
- // JavaScript code to be evaluated
- //
- // `window.location.href`
- //
- private function parseEntitiesJavascript(){
- $e = false;
- $j = $this->pos;
- if( $this->input[$j] === '~' ){
- $j++;
- $e = true;
- }
- if( $this->input[$j] !== '`' ){
- return;
- }
- if( $e ){
- $this->MatchChar('~');
- }
- $str = $this->MatchReg('/\\G`([^`]*)`/');
- if( $str ){
- return $this->NewObj3('Less_Tree_Javascript', array($str[1], $this->pos, $e));
- }
- }
- //
- // The variable part of a variable definition. Used in the `rule` parser
- //
- // @fink:
- //
- private function parseVariable(){
- if ($this->PeekChar('@') && ($name = $this->MatchReg('/\\G(@[\w-]+)\s*:/'))) {
- return $name[1];
- }
- }
- //
- // The variable part of a variable definition. Used in the `rule` parser
- //
- // @fink();
- //
- private function parseRulesetCall(){
- if( $this->input[$this->pos] === '@' && ($name = $this->MatchReg('/\\G(@[\w-]+)\s*\(\s*\)\s*;/')) ){
- return $this->NewObj1('Less_Tree_RulesetCall', $name[1] );
- }
- }
- //
- // extend syntax - used to extend selectors
- //
- function parseExtend($isRule = false){
- $index = $this->pos;
- $extendList = array();
- if( !$this->MatchReg( $isRule ? '/\\G&:extend\(/' : '/\\G:extend\(/' ) ){ return; }
- do{
- $option = null;
- $elements = array();
- while( true ){
- $option = $this->MatchReg('/\\G(all)(?=\s*(\)|,))/');
- if( $option ){ break; }
- $e = $this->parseElement();
- if( !$e ){ break; }
- $elements[] = $e;
- }
- if( $option ){
- $option = $option[1];
- }
- $extendList[] = $this->NewObj3('Less_Tree_Extend', array( $this->NewObj1('Less_Tree_Selector',$elements), $option, $index ));
- }while( $this->MatchChar(",") );
- $this->expect('/\\G\)/');
- if( $isRule ){
- $this->expect('/\\G;/');
- }
- return $extendList;
- }
- //
- // A Mixin call, with an optional argument list
- //
- // #mixins > .square(#fff);
- // .rounded(4px, black);
- // .button;
- //
- // The `while` loop is there because mixins can be
- // namespaced, but we only support the child and descendant
- // selector for now.
- //
- private function parseMixinCall(){
- $char = $this->input[$this->pos];
- if( $char !== '.' && $char !== '#' ){
- return;
- }
- $index = $this->pos;
- $this->save(); // stop us absorbing part of an invalid selector
- $elements = $this->parseMixinCallElements();
- if( $elements ){
- if( $this->MatchChar('(') ){
- $returned = $this->parseMixinArgs(true);
- $args = $returned['args'];
- $this->expectChar(')');
- }else{
- $args = array();
- }
- $important = $this->parseImportant();
- if( $this->parseEnd() ){
- $this->forget();
- return $this->NewObj5('Less_Tree_Mixin_Call', array( $elements, $args, $index, $this->env->currentFileInfo, $important));
- }
- }
- $this->restore();
- }
- private function parseMixinCallElements(){
- $elements = array();
- $c = null;
- while( true ){
- $elemIndex = $this->pos;
- $e = $this->MatchReg('/\\G[#.](?:[\w-]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/');
- if( !$e ){
- break;
- }
- $elements[] = $this->NewObj4('Less_Tree_Element', array($c, $e[0], $elemIndex, $this->env->currentFileInfo));
- $c = $this->MatchChar('>');
- }
- return $elements;
- }
- /**
- * @param boolean $isCall
- */
- private function parseMixinArgs( $isCall ){
- $expressions = array();
- $argsSemiColon = array();
- $isSemiColonSeperated = null;
- $argsComma = array();
- $expressionContainsNamed = null;
- $name = null;
- $returner = array('args'=>array(), 'variadic'=> false);
- $this->save();
- while( true ){
- if( $isCall ){
- $arg = $this->MatchFuncs( array( 'parseDetachedRuleset','parseExpression' ) );
- } else {
- $this->parseComments();
- if( $this->input[ $this->pos ] === '.' && $this->MatchReg('/\\G\.{3}/') ){
- $returner['variadic'] = true;
- if( $this->MatchChar(";") && !$isSemiColonSeperated ){
- $isSemiColonSeperated = true;
- }
- if( $isSemiColonSeperated ){
- $argsSemiColon[] = array('variadic'=>true);
- }else{
- $argsComma[] = array('variadic'=>true);
- }
- break;
- }
- $arg = $this->MatchFuncs( array('parseEntitiesVariable','parseEntitiesLiteral','parseEntitiesKeyword') );
- }
- if( !$arg ){
- break;
- }
- $nameLoop = null;
- if( $arg instanceof Less_Tree_Expression ){
- $arg->throwAwayComments();
- }
- $value = $arg;
- $val = null;
- if( $isCall ){
- // Variable
- if( property_exists($arg,'value') && count($arg->value) == 1 ){
- $val = $arg->value[0];
- }
- } else {
- $val = $arg;
- }
- if( $val instanceof Less_Tree_Variable ){
- if( $this->MatchChar(':') ){
- if( $expressions ){
- if( $isSemiColonSeperated ){
- $this->Error('Cannot mix ; and , as delimiter types');
- }
- $expressionContainsNamed = true;
- }
- // we do not support setting a ruleset as a default variable - it doesn't make sense
- // However if we do want to add it, there is nothing blocking it, just don't error
- // and remove isCall dependency below
- $value = null;
- if( $isCall ){
- $value = $this->parseDetachedRuleset();
- }
- if( !$value ){
- $value = $this->parseExpression();
- }
- if( !$value ){
- if( $isCall ){
- $this->Error('could not understand value for named argument');
- } else {
- $this->restore();
- $returner['args'] = array();
- return $returner;
- }
- }
- $nameLoop = ($name = $val->name);
- }elseif( !$isCall && $this->MatchReg('/\\G\.{3}/') ){
- $returner['variadic'] = true;
- if( $this->MatchChar(";") && !$isSemiColonSeperated ){
- $isSemiColonSeperated = true;
- }
- if( $isSemiColonSeperated ){
- $argsSemiColon[] = array('name'=> $arg->name, 'variadic' => true);
- }else{
- $argsComma[] = array('name'=> $arg->name, 'variadic' => true);
- }
- break;
- }elseif( !$isCall ){
- $name = $nameLoop = $val->name;
- $value = null;
- }
- }
- if( $value ){
- $expressions[] = $value;
- }
- $argsComma[] = array('name'=>$nameLoop, 'value'=>$value );
- if( $this->MatchChar(',') ){
- continue;
- }
- if( $this->MatchChar(';') || $isSemiColonSeperated ){
- if( $expressionContainsNamed ){
- $this->Error('Cannot mix ; and , as delimiter types');
- }
- $isSemiColonSeperated = true;
- if( count($expressions) > 1 ){
- $value = $this->NewObj1('Less_Tree_Value', $expressions);
- }
- $argsSemiColon[] = array('name'=>$name, 'value'=>$value );
- $name = null;
- $expressions = array();
- $expressionContainsNamed = false;
- }
- }
- $this->forget();
- $returner['args'] = ($isSemiColonSeperated ? $argsSemiColon : $argsComma);
- return $returner;
- }
- //
- // A Mixin definition, with a list of parameters
- //
- // .rounded (@radius: 2px, @color) {
- // ...
- // }
- //
- // Until we have a finer grained state-machine, we have to
- // do a look-ahead, to make sure we don't have a mixin call.
- // See the `rule` function for more information.
- //
- // We start by matching `.rounded (`, and then proceed on to
- // the argument list, which has optional default values.
- // We store the parameters in `params`, with a `value` key,
- // if there is a value, such as in the case of `@radius`.
- //
- // Once we've got our params list, and a closing `)`, we parse
- // the `{...}` block.
- //
- private function parseMixinDefinition(){
- $cond = null;
- $char = $this->input[$this->pos];
- if( ($char !== '.' && $char !== '#') || ($char === '{' && $this->PeekReg('/\\G[^{]*\}/')) ){
- return;
- }
- $this->save();
- $match = $this->MatchReg('/\\G([#.](?:[\w-]|\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/');
- if( $match ){
- $name = $match[1];
- $argInfo = $this->parseMixinArgs( false );
- $params = $argInfo['args'];
- $variadic = $argInfo['variadic'];
- // .mixincall("@{a}");
- // looks a bit like a mixin definition..
- // also
- // .mixincall(@a: {rule: set;});
- // so we have to be nice and restore
- if( !$this->MatchChar(')') ){
- $this->furthest = $this->pos;
- $this->restore();
- return;
- }
- $this->parseComments();
- if ($this->MatchReg('/\\Gwhen/')) { // Guard
- $cond = $this->expect('parseConditions', 'Expected conditions');
- }
- $ruleset = $this->parseBlock();
- if( is_array($ruleset) ){
- $this->forget();
- return $this->NewObj5('Less_Tree_Mixin_Definition', array( $name, $params, $ruleset, $cond, $variadic));
- }
- $this->restore();
- }else{
- $this->forget();
- }
- }
- //
- // Entities are the smallest recognized token,
- // and can be found inside a rule's value.
- //
- private function parseEntity(){
- return $this->MatchFuncs( array('parseEntitiesLiteral','parseEntitiesVariable','parseEntitiesUrl','parseEntitiesCall','parseEntitiesKeyword','parseEntitiesJavascript','parseComment') );
- }
- //
- // A Rule terminator. Note that we use `peek()` to check for '}',
- // because the `block` rule will be expecting it, but we still need to make sure
- // it's there, if ';' was ommitted.
- //
- private function parseEnd(){
- return $this->MatchChar(';') || $this->PeekChar('}');
- }
- //
- // IE's alpha function
- //
- // alpha(opacity=88)
- //
- private function parseAlpha(){
- if ( ! $this->MatchReg('/\\G\(opacity=/i')) {
- return;
- }
- $value = $this->MatchReg('/\\G[0-9]+/');
- if( $value ){
- $value = $value[0];
- }else{
- $value = $this->parseEntitiesVariable();
- if( !$value ){
- return;
- }
- }
- $this->expectChar(')');
- return $this->NewObj1('Less_Tree_Alpha',$value);
- }
- //
- // A Selector Element
- //
- // div
- // + h1
- // #socks
- // input[type="text"]
- //
- // Elements are the building blocks for Selectors,
- // they are made out of a `Combinator` (see combinator rule),
- // and an element name, such as a tag a class, or `*`.
- //
- private function parseElement(){
- $c = $this->parseCombinator();
- $index = $this->pos;
- $e = $this->match( array('/\\G(?:\d+\.\d+|\d+)%/', '/\\G(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/',
- '#*', '#&', 'parseAttribute', '/\\G\([^()@]+\)/', '/\\G[\.#](?=@)/', 'parseEntitiesVariableCurly') );
- if( is_null($e) ){
- $this->save();
- if( $this->MatchChar('(') ){
- if( ($v = $this->parseSelector()) && $this->MatchChar(')') ){
- $e = $this->NewObj1('Less_Tree_Paren',$v);
- $this->forget();
- }else{
- $this->restore();
- }
- }else{
- $this->forget();
- }
- }
- if( !is_null($e) ){
- return $this->NewObj4('Less_Tree_Element',array( $c, $e, $index, $this->env->currentFileInfo));
- }
- }
- //
- // Combinators combine elements together, in a Selector.
- //
- // Because our parser isn't white-space sensitive, special care
- // has to be taken, when parsing the descendant combinator, ` `,
- // as it's an empty space. We have to check the previous character
- // in the input, to see if it's a ` ` character.
- //
- private function parseCombinator(){
- if( $this->pos < $this->input_len ){
- $c = $this->input[$this->pos];
- if ($c === '>' || $c === '+' || $c === '~' || $c === '|' || $c === '^' ){
- $this->pos++;
- if( $this->input[$this->pos] === '^' ){
- $c = '^^';
- $this->pos++;
- }
- $this->skipWhitespace(0);
- return $c;
- }
- if( $this->pos > 0 && $this->isWhitespace(-1) ){
- return ' ';
- }
- }
- }
- //
- // A CSS selector (see selector below)
- // with less extensions e.g. the ability to extend and guard
- //
- private function parseLessSelector(){
- return $this->parseSelector(true);
- }
- //
- // A CSS Selector
- //
- // .class > div + h1
- // li a:hover
- //
- // Selectors are made out of one or more Elements, see above.
- //
- private function parseSelector( $isLess = false ){
- $elements = array();
- $extendList = array();
- $condition = null;
- $when = false;
- $extend = false;
- $e = null;
- $c = null;
- $index = $this->pos;
- while( ($isLess && ($extend = $this->parseExtend())) || ($isLess && ($when = $this->MatchReg('/\\Gwhen/') )) || ($e = $this->parseElement()) ){
- if( $when ){
- $condition = $this->expect('parseConditions', 'expected condition');
- }elseif( $condition ){
- //error("CSS guard can only be used at the end of selector");
- }elseif( $extend ){
- $extendList = array_merge($extendList,$extend);
- }else{
- //if( count($extendList) ){
- //error("Extend can only be used at the end of selector");
- //}
- if( $this->pos < $this->input_len ){
- $c = $this->input[ $this->pos ];
- }
- $elements[] = $e;
- $e = null;
- }
- if( $c === '{' || $c === '}' || $c === ';' || $c === ',' || $c === ')') { break; }
- }
- if( $elements ){
- return $this->NewObj5('Less_Tree_Selector',array($elements, $extendList, $condition, $index, $this->env->currentFileInfo));
- }
- if( $extendList ) {
- $this->Error('Extend must be used to extend a selector, it cannot be used on its own');
- }
- }
- private function parseTag(){
- return ( $tag = $this->MatchReg('/\\G[A-Za-z][A-Za-z-]*[0-9]?/') ) ? $tag : $this->MatchChar('*');
- }
- private function parseAttribute(){
- $val = null;
- if( !$this->MatchChar('[') ){
- return;
- }
- $key = $this->parseEntitiesVariableCurly();
- if( !$key ){
- $key = $this->expect('/\\G(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\\\.)+/');
- }
- $op = $this->MatchReg('/\\G[|~*$^]?=/');
- if( $op ){
- $val = $this->match( array('parseEntitiesQuoted','/\\G[0-9]+%/','/\\G[\w-]+/','parseEntitiesVariableCurly') );
- }
- $this->expectChar(']');
- return $this->NewObj3('Less_Tree_Attribute',array( $key, $op[0], $val));
- }
- //
- // The `block` rule is used by `ruleset` and `mixin.definition`.
- // It's a wrapper around the `primary` rule, with added `{}`.
- //
- private function parseBlock(){
- if( $this->MatchChar('{') ){
- $content = $this->parsePrimary();
- if( $this->MatchChar('}') ){
- return $content;
- }
- }
- }
- private function parseBlockRuleset(){
- $block = $this->parseBlock();
- if( $block ){
- $block = $this->NewObj2('Less_Tree_Ruleset',array( null, $block));
- }
- return $block;
- }
- private function parseDetachedRuleset(){
- $blockRuleset = $this->parseBlockRuleset();
- if( $blockRuleset ){
- return $this->NewObj1('Less_Tree_DetachedRuleset',$blockRuleset);
- }
- }
- //
- // div, .class, body > p {...}
- //
- private function parseRuleset(){
- $selectors = array();
- $this->save();
- while( true ){
- $s = $this->parseLessSelector();
- if( !$s ){
- break;
- }
- $selectors[] = $s;
- $this->parseComments();
- if( $s->condition && count($selectors) > 1 ){
- $this->Error('Guards are only currently allowed on a single selector.');
- }
- if( !$this->MatchChar(',') ){
- break;
- }
- if( $s->condition ){
- $this->Error('Guards are only currently allowed on a single selector.');
- }
- $this->parseComments();
- }
- if( $selectors ){
- $rules = $this->parseBlock();
- if( is_array($rules) ){
- $this->forget();
- return $this->NewObj2('Less_Tree_Ruleset',array( $selectors, $rules)); //Less_Environment::$strictImports
- }
- }
- // Backtrack
- $this->furthest = $this->pos;
- $this->restore();
- }
- /**
- * Custom less.php parse function for finding simple name-value css pairs
- * ex: width:100px;
- *
- */
- private function parseNameValue(){
- $index = $this->pos;
- $this->save();
- //$match = $this->MatchReg('/\\G([a-zA-Z\-]+)\s*:\s*((?:\'")?[a-zA-Z0-9\-% \.,!]+?(?:\'")?)\s*([;}])/');
- $match = $this->MatchReg('/\\G([a-zA-Z\-]+)\s*:\s*([\'"]?[#a-zA-Z0-9\-%\.,]+?[\'"]?) *(! *important)?\s*([;}])/');
- if( $match ){
- if( $match[4] == '}' ){
- $this->pos = $index + strlen($match[0])-1;
- }
- if( $match[3] ){
- $match[2] .= ' !important';
- }
- return $this->NewObj4('Less_Tree_NameValue',array( $match[1], $match[2], $index, $this->env->currentFileInfo));
- }
- $this->restore();
- }
- private function parseRule( $tryAnonymous = null ){
- $merge = false;
- $startOfRule = $this->pos;
- $c = $this->input[$this->pos];
- if( $c === '.' || $c === '#' || $c === '&' ){
- return;
- }
- $this->save();
- $name = $this->MatchFuncs( array('parseVariable','parseRuleProperty'));
- if( $name ){
- $isVariable = is_string($name);
- $value = null;
- if( $isVariable ){
- $value = $this->parseDetachedRuleset();
- }
- $important = null;
- if( !$value ){
- // prefer to try to parse first if its a variable or we are compressing
- // but always fallback on the other one
- //if( !$tryAnonymous && is_string($name) && $name[0] === '@' ){
- if( !$tryAnonymous && (Less_Parser::$options['compress'] || $isVariable) ){
- $value = $this->MatchFuncs( array('parseValue','parseAnonymousValue'));
- }else{
- $value = $this->MatchFuncs( array('parseAnonymousValue','parseValue'));
- }
- $important = $this->parseImportant();
- // a name returned by this.ruleProperty() is always an array of the form:
- // [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
- // where each item is a tree.Keyword or tree.Variable
- if( !$isVariable && is_array($name) ){
- $nm = array_pop($name);
- if( $nm->value ){
- $merge = $nm->value;
- }
- }
- }
- if( $value && $this->parseEnd() ){
- $this->forget();
- return $this->NewObj6('Less_Tree_Rule',array( $name, $value, $important, $merge, $startOfRule, $this->env->currentFileInfo));
- }else{
- $this->furthest = $this->pos;
- $this->restore();
- if( $value && !$tryAnonymous ){
- return $this->parseRule(true);
- }
- }
- }else{
- $this->forget();
- }
- }
- function parseAnonymousValue(){
- if( preg_match('/\\G([^@+\/\'"*`(;{}-]*);/',$this->input, $match, 0, $this->pos) ){
- $this->pos += strlen($match[1]);
- return $this->NewObj1('Less_Tree_Anonymous',$match[1]);
- }
- }
- //
- // An @import directive
- //
- // @import "lib";
- //
- // Depending on our environment, importing is done differently:
- // In the browser, it's an XHR request, in Node, it would be a
- // file-system operation. The function used for importing is
- // stored in `import`, which we pass to the Import constructor.
- //
- private function parseImport(){
- $this->save();
- $dir = $this->MatchReg('/\\G@import?\s+/');
- if( $dir ){
- $options = $this->parseImportOptions();
- $path = $this->MatchFuncs( array('parseEntitiesQuoted','parseEntitiesUrl'));
- if( $path ){
- $features = $this->parseMediaFeatures();
- if( $this->MatchChar(';') ){
- if( $features ){
- $features = $this->NewObj1('Less_Tree_Value',$features);
- }
- $this->forget();
- return $this->NewObj5('Less_Tree_Import',array( $path, $features, $options, $this->pos, $this->env->currentFileInfo));
- }
- }
- }
- $this->restore();
- }
- private function parseImportOptions(){
- $options = array();
- // list of options, surrounded by parens
- if( !$this->MatchChar('(') ){
- return $options;
- }
- do{
- $optionName = $this->parseImportOption();
- if( $optionName ){
- $value = true;
- switch( $optionName ){
- case "css":
- $optionName = "less";
- $value = false;
- break;
- case "once":
- $optionName = "multiple";
- $value = false;
- break;
- }
- $options[$optionName] = $value;
- if( !$this->MatchChar(',') ){ break; }
- }
- }while( $optionName );
- $this->expectChar(')');
- return $options;
- }
- private function parseImportOption(){
- $opt = $this->MatchReg('/\\G(less|css|multiple|once|inline|reference)/');
- if( $opt ){
- return $opt[1];
- }
- }
- private function parseMediaFeature() {
- $nodes = array();
- do{
- $e = $this->MatchFuncs(array('parseEntitiesKeyword','parseEntitiesVariable'));
- if( $e ){
- $nodes[] = $e;
- } elseif ($this->MatchChar('(')) {
- $p = $this->parseProperty();
- $e = $this->parseValue();
- if ($this->MatchChar(')')) {
- if ($p && $e) {
- $r = $this->NewObj7('Less_Tree_Rule', array( $p, $e, null, null, $this->pos, $this->env->currentFileInfo, true));
- $nodes[] = $this->NewObj1('Less_Tree_Paren',$r);
- } elseif ($e) {
- $nodes[] = $this->NewObj1('Less_Tree_Paren',$e);
- } else {
- return null;
- }
- } else
- return null;
- }
- } while ($e);
- if ($nodes) {
- return $this->NewObj1('Less_Tree_Expression',$nodes);
- }
- }
- private function parseMediaFeatures() {
- $features = array();
- do{
- $e = $this->parseMediaFeature();
- if( $e ){
- $features[] = $e;
- if (!$this->MatchChar(',')) break;
- }else{
- $e = $this->parseEntitiesVariable();
- if( $e ){
- $features[] = $e;
- if (!$this->MatchChar(',')) break;
- }
- }
- } while ($e);
- return $features ? $features : null;
- }
- private function parseMedia() {
- if( $this->MatchReg('/\\G@media/') ){
- $features = $this->parseMediaFeatures();
- $rules = $this->parseBlock();
- if( is_array($rules) ){
- return $this->NewObj4('Less_Tree_Media',array( $rules, $features, $this->pos, $this->env->currentFileInfo));
- }
- }
- }
- //
- // A CSS Directive
- //
- // @charset "utf-8";
- //
- private function parseDirective(){
- if( !$this->PeekChar('@') ){
- return;
- }
- $rules = null;
- $index = $this->pos;
- $hasBlock = true;
- $hasIdentifier = false;
- $hasExpression = false;
- $hasUnknown = false;
- $value = $this->MatchFuncs(array('parseImport','parseMedia'));
- if( $value ){
- return $value;
- }
- $this->save();
- $name = $this->MatchReg('/\\G@[a-z-]+/');
- if( !$name ) return;
- $name = $name[0];
- $nonVendorSpecificName = $name;
- $pos = strpos($name,'-', 2);
- if( $name[1] == '-' && $pos > 0 ){
- $nonVendorSpecificName = "@" . substr($name, $pos + 1);
- }
- switch( $nonVendorSpecificName ){
- /*
- case "@font-face":
- case "@viewport":
- case "@top-left":
- case "@top-left-corner":
- case "@top-center":
- case "@top-right":
- case "@top-right-corner":
- case "@bottom-left":
- case "@bottom-left-corner":
- case "@bottom-center":
- case "@bottom-right":
- case "@bottom-right-corner":
- case "@left-top":
- case "@left-middle":
- case "@left-bottom":
- case "@right-top":
- case "@right-middle":
- case "@right-bottom":
- hasBlock = true;
- break;
- */
- case "@charset":
- $hasIdentifier = true;
- $hasBlock = false;
- break;
- case "@namespace":
- $hasExpression = true;
- $hasBlock = false;
- break;
- case "@keyframes":
- $hasIdentifier = true;
- break;
- case "@host":
- case "@page":
- case "@document":
- case "@supports":
- $hasUnknown = true;
- break;
- }
- if( $hasIdentifier ){
- $value = $this->parseEntity();
- if( !$value ){
- $this->error("expected " . $name . " identifier");
- }
- } else if( $hasExpression ){
- $value = $this->parseExpression();
- if( !$value ){
- $this->error("expected " . $name. " expression");
- }
- } else if ($hasUnknown) {
- $value = $this->MatchReg('/\\G[^{;]+/');
- if( $value ){
- $value = $this->NewObj1('Less_Tree_Anonymous',trim($value[0]));
- }
- }
- if( $hasBlock ){
- $rules = $this->parseBlockRuleset();
- }
- if( $rules || (!$hasBlock && $value && $this->MatchChar(';'))) {
- $this->forget();
- return $this->NewObj5('Less_Tree_Directive',array($name, $value, $rules, $index, $this->env->currentFileInfo));
- }
- $this->restore();
- }
- //
- // A Value is a comma-delimited list of Expressions
- //
- // font-family: Baskerville, Georgia, serif;
- //
- // In a Rule, a Value represents everything after the `:`,
- // and before the `;`.
- //
- private function parseValue(){
- $expressions = array();
- do{
- $e = $this->parseExpression();
- if( $e ){
- $expressions[] = $e;
- if (! $this->MatchChar(',')) {
- break;
- }
- }
- }while($e);
- if( $expressions ){
- return $this->NewObj1('Less_Tree_Value',$expressions);
- }
- }
- private function parseImportant (){
- if( $this->PeekChar('!') && $this->MatchReg('/\\G! *important/') ){
- return ' !important';
- }
- }
- private function parseSub (){
- if( $this->MatchChar('(') ){
- $a = $this->parseAddition();
- if( $a ){
- $this->expectChar(')');
- return $this->NewObj2('Less_Tree_Expression',array( array($a), true) ); //instead of $e->parens = true so the value is cached
- }
- }
- }
- /**
- * Parses multiplication operation
- *
- * @return Less_Tree_Operation|null
- */
- function parseMultiplication(){
- $return = $m = $this->parseOperand();
- if( $return ){
- while( true ){
- $isSpaced = $this->isWhitespace( -1 );
- if( $this->PeekReg('/\\G\/[*\/]/') ){
- break;
- }
- $op = $this->MatchChar('/');
- if( !$op ){
- $op = $this->MatchChar('*');
- if( !$op ){
- break;
- }
- }
- $a = $this->parseOperand();
- if(!$a) { break; }
- $m->parensInOp = true;
- $a->parensInOp = true;
- $return = $this->NewObj3('Less_Tree_Operation',array( $op, array( $return, $a ), $isSpaced) );
- }
- }
- return $return;
- }
- /**
- * Parses an addition operation
- *
- * @return Less_Tree_Operation|null
- */
- private function parseAddition (){
- $return = $m = $this->parseMultiplication();
- if( $return ){
- while( true ){
- $isSpaced = $this->isWhitespace( -1 );
- $op = $this->MatchReg('/\\G[-+]\s+/');
- if( $op ){
- $op = $op[0];
- }else{
- if( !$isSpaced ){
- $op = $this->match(array('#+','#-'));
- }
- if( !$op ){
- break;
- }
- }
- $a = $this->parseMultiplication();
- if( !$a ){
- break;
- }
- $m->parensInOp = true;
- $a->parensInOp = true;
- $return = $this->NewObj3('Less_Tree_Operation',array($op, array($return, $a), $isSpaced));
- }
- }
- return $return;
- }
- /**
- * Parses the conditions
- *
- * @return Less_Tree_Condition|null
- */
- private function parseConditions() {
- $index = $this->pos;
- $return = $a = $this->parseCondition();
- if( $a ){
- while( true ){
- if( !$this->PeekReg('/\\G,\s*(not\s*)?\(/') || !$this->MatchChar(',') ){
- break;
- }
- $b = $this->parseCondition();
- if( !$b ){
- break;
- }
- $return = $this->NewObj4('Less_Tree_Condition',array('or', $return, $b, $index));
- }
- return $return;
- }
- }
- private function parseCondition() {
- $index = $this->pos;
- $negate = false;
- $c = null;
- if ($this->MatchReg('/\\Gnot/')) $negate = true;
- $this->expectChar('(');
- $a = $this->MatchFuncs(array('parseAddition','parseEntitiesKeyword','parseEntitiesQuoted'));
- if( $a ){
- $op = $this->MatchReg('/\\G(?:>=|<=|=<|[<=>])/');
- if( $op ){
- $b = $this->MatchFuncs(array('parseAddition','parseEntitiesKeyword','parseEntitiesQuoted'));
- if( $b ){
- $c = $this->NewObj5('Less_Tree_Condition',array($op[0], $a, $b, $index, $negate));
- } else {
- $this->Error('Unexpected expression');
- }
- } else {
- $k = $this->NewObj1('Less_Tree_Keyword','true');
- $c = $this->NewObj5('Less_Tree_Condition',array('=', $a, $k, $index, $negate));
- }
- $this->expectChar(')');
- return $this->MatchReg('/\\Gand/') ? $this->NewObj3('Less_Tree_Condition',array('and', $c, $this->parseCondition())) : $c;
- }
- }
- /**
- * An operand is anything that can be part of an operation,
- * such as a Color, or a Variable
- *
- */
- private function parseOperand (){
- $negate = false;
- $offset = $this->pos+1;
- if( $offset >= $this->input_len ){
- return;
- }
- $char = $this->input[$offset];
- if( $char === '@' || $char === '(' ){
- $negate = $this->MatchChar('-');
- }
- $o = $this->MatchFuncs(array('parseSub','parseEntitiesDimension','parseEntitiesColor','parseEntitiesVariable','parseEntitiesCall'));
- if( $negate ){
- $o->parensInOp = true;
- $o = $this->NewObj1('Less_Tree_Negative',$o);
- }
- return $o;
- }
- /**
- * Expressions either represent mathematical operations,
- * or white-space delimited Entities.
- *
- * 1px solid black
- * @var * 2
- *
- * @return Less_Tree_Expression|null
- */
- private function parseExpression (){
- $entities = array();
- do{
- $e = $this->MatchFuncs(array('parseAddition','parseEntity'));
- if( $e ){
- $entities[] = $e;
- // operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here
- if( !$this->PeekReg('/\\G\/[\/*]/') ){
- $delim = $this->MatchChar('/');
- if( $delim ){
- $entities[] = $this->NewObj1('Less_Tree_Anonymous',$delim);
- }
- }
- }
- }while($e);
- if( $entities ){
- return $this->NewObj1('Less_Tree_Expression',$entities);
- }
- }
- /**
- * Parse a property
- * eg: 'min-width', 'orientation', etc
- *
- * @return string
- */
- private function parseProperty (){
- $name = $this->MatchReg('/\\G(\*?-?[_a-zA-Z0-9-]+)\s*:/');
- if( $name ){
- return $name[1];
- }
- }
- /**
- * Parse a rule property
- * eg: 'color', 'width', 'height', etc
- *
- * @return string
- */
- private function parseRuleProperty(){
- $offset = $this->pos;
- $name = array();
- $index = array();
- $length = 0;
- $this->rulePropertyMatch('/\\G(\*?)/', $offset, $length, $index, $name );
- while( $this->rulePropertyMatch('/\\G((?:[\w-]+)|(?:@\{[\w-]+\}))/', $offset, $length, $index, $name )); // !
- if( (count($name) > 1) && $this->rulePropertyMatch('/\\G\s*((?:\+_|\+)?)\s*:/', $offset, $length, $index, $name) ){
- // at last, we have the complete match now. move forward,
- // convert name particles to tree objects and return:
- $this->skipWhitespace($length);
- if( $name[0] === '' ){
- array_shift($name);
- array_shift($index);
- }
- foreach($name as $k => $s ){
- if( !$s || $s[0] !== '@' ){
- $name[$k] = $this->NewObj1('Less_Tree_Keyword',$s);
- }else{
- $name[$k] = $this->NewObj3('Less_Tree_Variable',array('@' . substr($s,2,-1), $index[$k], $this->env->currentFileInfo));
- }
- }
- return $name;
- }
- }
- private function rulePropertyMatch( $re, &$offset, &$length, &$index, &$name ){
- preg_match($re, $this->input, $a, 0, $offset);
- if( $a ){
- $index[] = $this->pos + $length;
- $length += strlen($a[0]);
- $offset += strlen($a[0]);
- $name[] = $a[1];
- return true;
- }
- }
- public static function serializeVars( $vars ){
- $s = '';
- foreach($vars as $name => $value){
- $s .= (($name[0] === '@') ? '' : '@') . $name .': '. $value . ((substr($value,-1) === ';') ? '' : ';');
- }
- return $s;
- }
- /**
- * Some versions of php have trouble with method_exists($a,$b) if $a is not an object
- *
- * @param string $b
- */
- public static function is_method($a,$b){
- return is_object($a) && method_exists($a,$b);
- }
- /**
- * Round numbers similarly to javascript
- * eg: 1.499999 to 1 instead of 2
- *
- */
- public static function round($i, $precision = 0){
- $precision = pow(10,$precision);
- $i = $i*$precision;
- $ceil = ceil($i);
- $floor = floor($i);
- if( ($ceil - $i) <= ($i - $floor) ){
- return $ceil/$precision;
- }else{
- return $floor/$precision;
- }
- }
- /**
- * Create Less_Tree_* objects and optionally generate a cache string
- *
- * @return mixed
- */
- public function NewObj0($class){
- $obj = new $class();
- if( $this->CacheEnabled() ){
- $obj->cache_string = ' new '.$class.'()';
- }
- return $obj;
- }
- public function NewObj1($class, $arg){
- $obj = new $class( $arg );
- if( $this->CacheEnabled() ){
- $obj->cache_string = ' new '.$class.'('.Less_Parser::ArgString($arg).')';
- }
- return $obj;
- }
- public function NewObj2($class, $args){
- $obj = new $class( $args[0], $args[1] );
- if( $this->CacheEnabled() ){
- $this->ObjCache( $obj, $class, $args);
- }
- return $obj;
- }
- public function NewObj3($class, $args){
- $obj = new $class( $args[0], $args[1], $args[2] );
- if( $this->CacheEnabled() ){
- $this->ObjCache( $obj, $class, $args);
- }
- return $obj;
- }
- public function NewObj4($class, $args){
- $obj = new $class( $args[0], $args[1], $args[2], $args[3] );
- if( $this->CacheEnabled() ){
- $this->ObjCache( $obj, $class, $args);
- }
- return $obj;
- }
- public function NewObj5($class, $args){
- $obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4] );
- if( $this->CacheEnabled() ){
- $this->ObjCache( $obj, $class, $args);
- }
- return $obj;
- }
- public function NewObj6($class, $args){
- $obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5] );
- if( $this->CacheEnabled() ){
- $this->ObjCache( $obj, $class, $args);
- }
- return $obj;
- }
- public function NewObj7($class, $args){
- $obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6] );
- if( $this->CacheEnabled() ){
- $this->ObjCache( $obj, $class, $args);
- }
- return $obj;
- }
- //caching
- public function ObjCache($obj, $class, $args=array()){
- $obj->cache_string = ' new '.$class.'('. self::ArgCache($args).')';
- }
- public function ArgCache($args){
- return implode(',',array_map( array('Less_Parser','ArgString'),$args));
- }
- /**
- * Convert an argument to a string for use in the parser cache
- *
- * @return string
- */
- public static function ArgString($arg){
- $type = gettype($arg);
- if( $type === 'object'){
- $string = $arg->cache_string;
- unset($arg->cache_string);
- return $string;
- }elseif( $type === 'array' ){
- $string = ' Array(';
- foreach($arg as $k => $a){
- $string .= var_export($k,true).' => '.self::ArgString($a).',';
- }
- return $string . ')';
- }
- return var_export($arg,true);
- }
- public function Error($msg){
- throw new Less_Exception_Parser($msg, null, $this->furthest, $this->env->currentFileInfo);
- }
- public static function WinPath($path){
- return str_replace('\\', '/', $path);
- }
- public function CacheEnabled(){
- return (Less_Parser::$options['cache_method'] && (Less_Cache::$cache_dir || (Less_Parser::$options['cache_method'] == 'callback')));
- }
- }
-
- /**
- * Utility for css colors
- *
- * @package Less
- * @subpackage color
- */
- class Less_Colors {
- public static $colors = array(
- 'aliceblue'=>'#f0f8ff',
- 'antiquewhite'=>'#faebd7',
- 'aqua'=>'#00ffff',
- 'aquamarine'=>'#7fffd4',
- 'azure'=>'#f0ffff',
- 'beige'=>'#f5f5dc',
- 'bisque'=>'#ffe4c4',
- 'black'=>'#000000',
- 'blanchedalmond'=>'#ffebcd',
- 'blue'=>'#0000ff',
- 'blueviolet'=>'#8a2be2',
- 'brown'=>'#a52a2a',
- 'burlywood'=>'#deb887',
- 'cadetblue'=>'#5f9ea0',
- 'chartreuse'=>'#7fff00',
- 'chocolate'=>'#d2691e',
- 'coral'=>'#ff7f50',
- 'cornflowerblue'=>'#6495ed',
- 'cornsilk'=>'#fff8dc',
- 'crimson'=>'#dc143c',
- 'cyan'=>'#00ffff',
- 'darkblue'=>'#00008b',
- 'darkcyan'=>'#008b8b',
- 'darkgoldenrod'=>'#b8860b',
- 'darkgray'=>'#a9a9a9',
- 'darkgrey'=>'#a9a9a9',
- 'darkgreen'=>'#006400',
- 'darkkhaki'=>'#bdb76b',
- 'darkmagenta'=>'#8b008b',
- 'darkolivegreen'=>'#556b2f',
- 'darkorange'=>'#ff8c00',
- 'darkorchid'=>'#9932cc',
- 'darkred'=>'#8b0000',
- 'darksalmon'=>'#e9967a',
- 'darkseagreen'=>'#8fbc8f',
- 'darkslateblue'=>'#483d8b',
- 'darkslategray'=>'#2f4f4f',
- 'darkslategrey'=>'#2f4f4f',
- 'darkturquoise'=>'#00ced1',
- 'darkviolet'=>'#9400d3',
- 'deeppink'=>'#ff1493',
- 'deepskyblue'=>'#00bfff',
- 'dimgray'=>'#696969',
- 'dimgrey'=>'#696969',
- 'dodgerblue'=>'#1e90ff',
- 'firebrick'=>'#b22222',
- 'floralwhite'=>'#fffaf0',
- 'forestgreen'=>'#228b22',
- 'fuchsia'=>'#ff00ff',
- 'gainsboro'=>'#dcdcdc',
- 'ghostwhite'=>'#f8f8ff',
- 'gold'=>'#ffd700',
- 'goldenrod'=>'#daa520',
- 'gray'=>'#808080',
- 'grey'=>'#808080',
- 'green'=>'#008000',
- 'greenyellow'=>'#adff2f',
- 'honeydew'=>'#f0fff0',
- 'hotpink'=>'#ff69b4',
- 'indianred'=>'#cd5c5c',
- 'indigo'=>'#4b0082',
- 'ivory'=>'#fffff0',
- 'khaki'=>'#f0e68c',
- 'lavender'=>'#e6e6fa',
- 'lavenderblush'=>'#fff0f5',
- 'lawngreen'=>'#7cfc00',
- 'lemonchiffon'=>'#fffacd',
- 'lightblue'=>'#add8e6',
- 'lightcoral'=>'#f08080',
- 'lightcyan'=>'#e0ffff',
- 'lightgoldenrodyellow'=>'#fafad2',
- 'lightgray'=>'#d3d3d3',
- 'lightgrey'=>'#d3d3d3',
- 'lightgreen'=>'#90ee90',
- 'lightpink'=>'#ffb6c1',
- 'lightsalmon'=>'#ffa07a',
- 'lightseagreen'=>'#20b2aa',
- 'lightskyblue'=>'#87cefa',
- 'lightslategray'=>'#778899',
- 'lightslategrey'=>'#778899',
- 'lightsteelblue'=>'#b0c4de',
- 'lightyellow'=>'#ffffe0',
- 'lime'=>'#00ff00',
- 'limegreen'=>'#32cd32',
- 'linen'=>'#faf0e6',
- 'magenta'=>'#ff00ff',
- 'maroon'=>'#800000',
- 'mediumaquamarine'=>'#66cdaa',
- 'mediumblue'=>'#0000cd',
- 'mediumorchid'=>'#ba55d3',
- 'mediumpurple'=>'#9370d8',
- 'mediumseagreen'=>'#3cb371',
- 'mediumslateblue'=>'#7b68ee',
- 'mediumspringgreen'=>'#00fa9a',
- 'mediumturquoise'=>'#48d1cc',
- 'mediumvioletred'=>'#c71585',
- 'midnightblue'=>'#191970',
- 'mintcream'=>'#f5fffa',
- 'mistyrose'=>'#ffe4e1',
- 'moccasin'=>'#ffe4b5',
- 'navajowhite'=>'#ffdead',
- 'navy'=>'#000080',
- 'oldlace'=>'#fdf5e6',
- 'olive'=>'#808000',
- 'olivedrab'=>'#6b8e23',
- 'orange'=>'#ffa500',
- 'orangered'=>'#ff4500',
- 'orchid'=>'#da70d6',
- 'palegoldenrod'=>'#eee8aa',
- 'palegreen'=>'#98fb98',
- 'paleturquoise'=>'#afeeee',
- 'palevioletred'=>'#d87093',
- 'papayawhip'=>'#ffefd5',
- 'peachpuff'=>'#ffdab9',
- 'peru'=>'#cd853f',
- 'pink'=>'#ffc0cb',
- 'plum'=>'#dda0dd',
- 'powderblue'=>'#b0e0e6',
- 'purple'=>'#800080',
- 'red'=>'#ff0000',
- 'rosybrown'=>'#bc8f8f',
- 'royalblue'=>'#4169e1',
- 'saddlebrown'=>'#8b4513',
- 'salmon'=>'#fa8072',
- 'sandybrown'=>'#f4a460',
- 'seagreen'=>'#2e8b57',
- 'seashell'=>'#fff5ee',
- 'sienna'=>'#a0522d',
- 'silver'=>'#c0c0c0',
- 'skyblue'=>'#87ceeb',
- 'slateblue'=>'#6a5acd',
- 'slategray'=>'#708090',
- 'slategrey'=>'#708090',
- 'snow'=>'#fffafa',
- 'springgreen'=>'#00ff7f',
- 'steelblue'=>'#4682b4',
- 'tan'=>'#d2b48c',
- 'teal'=>'#008080',
- 'thistle'=>'#d8bfd8',
- 'tomato'=>'#ff6347',
- 'turquoise'=>'#40e0d0',
- 'violet'=>'#ee82ee',
- 'wheat'=>'#f5deb3',
- 'white'=>'#ffffff',
- 'whitesmoke'=>'#f5f5f5',
- 'yellow'=>'#ffff00',
- 'yellowgreen'=>'#9acd32'
- );
- public static function hasOwnProperty($color) {
- return isset(self::$colors[$color]);
- }
- public static function color($color) {
- return self::$colors[$color];
- }
- }
-
- /**
- * Environment
- *
- * @package Less
- * @subpackage environment
- */
- class Less_Environment{
- //public $paths = array(); // option - unmodified - paths to search for imports on
- //public static $files = array(); // list of files that have been imported, used for import-once
- //public $rootpath; // option - rootpath to append to URL's
- //public static $strictImports = null; // option -
- //public $insecure; // option - whether to allow imports from insecure ssl hosts
- //public $processImports; // option - whether to process imports. if false then imports will not be imported
- //public $javascriptEnabled; // option - whether JavaScript is enabled. if undefined, defaults to true
- //public $useFileCache; // browser only - whether to use the per file session cache
- public $currentFileInfo; // information about the current file - for error reporting and importing and making urls relative etc.
- public $importMultiple = false; // whether we are currently importing multiple copies
- /**
- * @var array
- */
- public $frames = array();
- /**
- * @var array
- */
- public $mediaBlocks = array();
- /**
- * @var array
- */
- public $mediaPath = array();
- public static $parensStack = 0;
- public static $tabLevel = 0;
- public static $lastRule = false;
- public static $_outputMap;
- public static $mixin_stack = 0;
- /**
- * @var array
- */
- public $functions = array();
- public function Init(){
- self::$parensStack = 0;
- self::$tabLevel = 0;
- self::$lastRule = false;
- self::$mixin_stack = 0;
- if( Less_Parser::$options['compress'] ){
- Less_Environment::$_outputMap = array(
- ',' => ',',
- ': ' => ':',
- '' => '',
- ' ' => ' ',
- ':' => ' :',
- '+' => '+',
- '~' => '~',
- '>' => '>',
- '|' => '|',
- '^' => '^',
- '^^' => '^^'
- );
- }else{
- Less_Environment::$_outputMap = array(
- ',' => ', ',
- ': ' => ': ',
- '' => '',
- ' ' => ' ',
- ':' => ' :',
- '+' => ' + ',
- '~' => ' ~ ',
- '>' => ' > ',
- '|' => '|',
- '^' => ' ^ ',
- '^^' => ' ^^ '
- );
- }
- }
- public function copyEvalEnv($frames = array() ){
- $new_env = new Less_Environment();
- $new_env->frames = $frames;
- return $new_env;
- }
- public static function isMathOn(){
- return !Less_Parser::$options['strictMath'] || Less_Environment::$parensStack;
- }
- public static function isPathRelative($path){
- return !preg_match('/^(?:[a-z-]+:|\/)/',$path);
- }
- /**
- * Canonicalize a path by resolving references to '/./', '/../'
- * Does not remove leading "../"
- * @param string path or url
- * @return string Canonicalized path
- *
- */
- public static function normalizePath($path){
- $segments = explode('/',$path);
- $segments = array_reverse($segments);
- $path = array();
- $path_len = 0;
- while( $segments ){
- $segment = array_pop($segments);
- switch( $segment ) {
- case '.':
- break;
- case '..':
- if( !$path_len || ( $path[$path_len-1] === '..') ){
- $path[] = $segment;
- $path_len++;
- }else{
- array_pop($path);
- $path_len--;
- }
- break;
- default:
- $path[] = $segment;
- $path_len++;
- break;
- }
- }
- return implode('/',$path);
- }
- public function unshiftFrame($frame){
- array_unshift($this->frames, $frame);
- }
- public function shiftFrame(){
- return array_shift($this->frames);
- }
- }
-
- /**
- * Builtin functions
- *
- * @package Less
- * @subpackage function
- * @see http://lesscss.org/functions/
- */
- class Less_Functions{
- public $env;
- public $currentFileInfo;
- function __construct($env, $currentFileInfo = null ){
- $this->env = $env;
- $this->currentFileInfo = $currentFileInfo;
- }
- /**
- * @param string $op
- */
- public static function operate( $op, $a, $b ){
- switch ($op) {
- case '+': return $a + $b;
- case '-': return $a - $b;
- case '*': return $a * $b;
- case '/': return $a / $b;
- }
- }
- public static function clamp($val, $max = 1){
- return min( max($val, 0), $max);
- }
- public static function fround( $value ){
- if( $value === 0 ){
- return $value;
- }
- if( Less_Parser::$options['numPrecision'] ){
- $p = pow(10, Less_Parser::$options['numPrecision']);
- return round( $value * $p) / $p;
- }
- return $value;
- }
- public static function number($n){
- if ($n instanceof Less_Tree_Dimension) {
- return floatval( $n->unit->is('%') ? $n->value / 100 : $n->value);
- } else if (is_numeric($n)) {
- return $n;
- } else {
- throw new Less_Exception_Compiler("color functions take numbers as parameters");
- }
- }
- public static function scaled($n, $size = 255 ){
- if( $n instanceof Less_Tree_Dimension && $n->unit->is('%') ){
- return (float)$n->value * $size / 100;
- } else {
- return Less_Functions::number($n);
- }
- }
- public function rgb ($r, $g, $b){
- return $this->rgba($r, $g, $b, 1.0);
- }
- public function rgba($r, $g, $b, $a){
- $rgb = array($r, $g, $b);
- $rgb = array_map(array('Less_Functions','scaled'),$rgb);
- $a = self::number($a);
- return new Less_Tree_Color($rgb, $a);
- }
- public function hsl($h, $s, $l){
- return $this->hsla($h, $s, $l, 1.0);
- }
- public function hsla($h, $s, $l, $a){
- $h = fmod(self::number($h), 360) / 360; // Classic % operator will change float to int
- $s = self::clamp(self::number($s));
- $l = self::clamp(self::number($l));
- $a = self::clamp(self::number($a));
- $m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s;
- $m1 = $l * 2 - $m2;
- return $this->rgba( self::hsla_hue($h + 1/3, $m1, $m2) * 255,
- self::hsla_hue($h, $m1, $m2) * 255,
- self::hsla_hue($h - 1/3, $m1, $m2) * 255,
- $a);
- }
- /**
- * @param double $h
- */
- public function hsla_hue($h, $m1, $m2){
- $h = $h < 0 ? $h + 1 : ($h > 1 ? $h - 1 : $h);
- if ($h * 6 < 1) return $m1 + ($m2 - $m1) * $h * 6;
- else if ($h * 2 < 1) return $m2;
- else if ($h * 3 < 2) return $m1 + ($m2 - $m1) * (2/3 - $h) * 6;
- else return $m1;
- }
- public function hsv($h, $s, $v) {
- return $this->hsva($h, $s, $v, 1.0);
- }
- /**
- * @param double $a
- */
- public function hsva($h, $s, $v, $a) {
- $h = ((Less_Functions::number($h) % 360) / 360 ) * 360;
- $s = Less_Functions::number($s);
- $v = Less_Functions::number($v);
- $a = Less_Functions::number($a);
- $i = floor(($h / 60) % 6);
- $f = ($h / 60) - $i;
- $vs = array( $v,
- $v * (1 - $s),
- $v * (1 - $f * $s),
- $v * (1 - (1 - $f) * $s));
- $perm = array(array(0, 3, 1),
- array(2, 0, 1),
- array(1, 0, 3),
- array(1, 2, 0),
- array(3, 1, 0),
- array(0, 1, 2));
- return $this->rgba($vs[$perm[$i][0]] * 255,
- $vs[$perm[$i][1]] * 255,
- $vs[$perm[$i][2]] * 255,
- $a);
- }
- public function hue($color){
- $c = $color->toHSL();
- return new Less_Tree_Dimension(Less_Parser::round($c['h']));
- }
- public function saturation($color){
- $c = $color->toHSL();
- return new Less_Tree_Dimension(Less_Parser::round($c['s'] * 100), '%');
- }
- public function lightness($color){
- $c = $color->toHSL();
- return new Less_Tree_Dimension(Less_Parser::round($c['l'] * 100), '%');
- }
- public function hsvhue( $color ){
- $hsv = $color->toHSV();
- return new Less_Tree_Dimension( Less_Parser::round($hsv['h']) );
- }
- public function hsvsaturation( $color ){
- $hsv = $color->toHSV();
- return new Less_Tree_Dimension( Less_Parser::round($hsv['s'] * 100), '%' );
- }
- public function hsvvalue( $color ){
- $hsv = $color->toHSV();
- return new Less_Tree_Dimension( Less_Parser::round($hsv['v'] * 100), '%' );
- }
- public function red($color) {
- return new Less_Tree_Dimension( $color->rgb[0] );
- }
- public function green($color) {
- return new Less_Tree_Dimension( $color->rgb[1] );
- }
- public function blue($color) {
- return new Less_Tree_Dimension( $color->rgb[2] );
- }
- public function alpha($color){
- $c = $color->toHSL();
- return new Less_Tree_Dimension($c['a']);
- }
- public function luma ($color) {
- return new Less_Tree_Dimension(Less_Parser::round( $color->luma() * $color->alpha * 100), '%');
- }
- public function luminance( $color ){
- $luminance =
- (0.2126 * $color->rgb[0] / 255)
- + (0.7152 * $color->rgb[1] / 255)
- + (0.0722 * $color->rgb[2] / 255);
- return new Less_Tree_Dimension(Less_Parser::round( $luminance * $color->alpha * 100), '%');
- }
- public function saturate($color, $amount = null){
- // filter: saturate(3.2);
- // should be kept as is, so check for color
- if( !property_exists($color,'rgb') ){
- return null;
- }
- $hsl = $color->toHSL();
- $hsl['s'] += $amount->value / 100;
- $hsl['s'] = self::clamp($hsl['s']);
- return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
- }
- /**
- * @param Less_Tree_Dimension $amount
- */
- public function desaturate($color, $amount){
- $hsl = $color->toHSL();
- $hsl['s'] -= $amount->value / 100;
- $hsl['s'] = self::clamp($hsl['s']);
- return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
- }
- public function lighten($color, $amount){
- $hsl = $color->toHSL();
- $hsl['l'] += $amount->value / 100;
- $hsl['l'] = self::clamp($hsl['l']);
- return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
- }
- public function darken($color, $amount){
- if( $color instanceof Less_Tree_Color ){
- $hsl = $color->toHSL();
- $hsl['l'] -= $amount->value / 100;
- $hsl['l'] = self::clamp($hsl['l']);
- return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
- }
- Less_Functions::Expected('color',$color);
- }
- public function fadein($color, $amount){
- $hsl = $color->toHSL();
- $hsl['a'] += $amount->value / 100;
- $hsl['a'] = self::clamp($hsl['a']);
- return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
- }
- public function fadeout($color, $amount){
- $hsl = $color->toHSL();
- $hsl['a'] -= $amount->value / 100;
- $hsl['a'] = self::clamp($hsl['a']);
- return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
- }
- public function fade($color, $amount){
- $hsl = $color->toHSL();
- $hsl['a'] = $amount->value / 100;
- $hsl['a'] = self::clamp($hsl['a']);
- return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
- }
- public function spin($color, $amount){
- $hsl = $color->toHSL();
- $hue = fmod($hsl['h'] + $amount->value, 360);
- $hsl['h'] = $hue < 0 ? 360 + $hue : $hue;
- return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
- }
- //
- // Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
- // http://sass-lang.com
- //
- /**
- * @param Less_Tree_Color $color1
- */
- public function mix($color1, $color2, $weight = null){
- if (!$weight) {
- $weight = new Less_Tree_Dimension('50', '%');
- }
- $p = $weight->value / 100.0;
- $w = $p * 2 - 1;
- $hsl1 = $color1->toHSL();
- $hsl2 = $color2->toHSL();
- $a = $hsl1['a'] - $hsl2['a'];
- $w1 = (((($w * $a) == -1) ? $w : ($w + $a) / (1 + $w * $a)) + 1) / 2;
- $w2 = 1 - $w1;
- $rgb = array($color1->rgb[0] * $w1 + $color2->rgb[0] * $w2,
- $color1->rgb[1] * $w1 + $color2->rgb[1] * $w2,
- $color1->rgb[2] * $w1 + $color2->rgb[2] * $w2);
- $alpha = $color1->alpha * $p + $color2->alpha * (1 - $p);
- return new Less_Tree_Color($rgb, $alpha);
- }
- public function greyscale($color){
- return $this->desaturate($color, new Less_Tree_Dimension(100));
- }
- public function contrast( $color, $dark = null, $light = null, $threshold = null){
- // filter: contrast(3.2);
- // should be kept as is, so check for color
- if( !property_exists($color,'rgb') ){
- return null;
- }
- if( !$light ){
- $light = $this->rgba(255, 255, 255, 1.0);
- }
- if( !$dark ){
- $dark = $this->rgba(0, 0, 0, 1.0);
- }
- //Figure out which is actually light and dark!
- if( $dark->luma() > $light->luma() ){
- $t = $light;
- $light = $dark;
- $dark = $t;
- }
- if( !$threshold ){
- $threshold = 0.43;
- } else {
- $threshold = Less_Functions::number($threshold);
- }
- if( $color->luma() < $threshold ){
- return $light;
- } else {
- return $dark;
- }
- }
- public function e ($str){
- if( is_string($str) ){
- return new Less_Tree_Anonymous($str);
- }
- return new Less_Tree_Anonymous($str instanceof Less_Tree_JavaScript ? $str->expression : $str->value);
- }
- public function escape ($str){
- $revert = array('%21'=>'!', '%2A'=>'*', '%27'=>"'",'%3F'=>'?','%26'=>'&','%2C'=>',','%2F'=>'/','%40'=>'@','%2B'=>'+','%24'=>'$');
- return new Less_Tree_Anonymous(strtr(rawurlencode($str->value), $revert));
- }
- /**
- * todo: This function will need some additional work to make it work the same as less.js
- *
- */
- public function replace( $string, $pattern, $replacement, $flags = null ){
- $result = $string->value;
- $expr = '/'.str_replace('/','\\/',$pattern->value).'/';
- if( $flags && $flags->value){
- $expr .= self::replace_flags($flags->value);
- }
- $result = preg_replace($expr,$replacement->value,$result);
- if( property_exists($string,'quote') ){
- return new Less_Tree_Quoted( $string->quote, $result, $string->escaped);
- }
- return new Less_Tree_Quoted( '', $result );
- }
- public static function replace_flags($flags){
- $flags = str_split($flags,1);
- $new_flags = '';
- foreach($flags as $flag){
- switch($flag){
- case 'e':
- case 'g':
- break;
- default:
- $new_flags .= $flag;
- break;
- }
- }
- return $new_flags;
- }
- public function _percent(){
- $string = func_get_arg(0);
- $args = func_get_args();
- array_shift($args);
- $result = $string->value;
- foreach($args as $arg){
- if( preg_match('/%[sda]/i',$result, $token) ){
- $token = $token[0];
- $value = stristr($token, 's') ? $arg->value : $arg->toCSS();
- $value = preg_match('/[A-Z]$/', $token) ? urlencode($value) : $value;
- $result = preg_replace('/%[sda]/i',$value, $result, 1);
- }
- }
- $result = str_replace('%%', '%', $result);
- return new Less_Tree_Quoted( $string->quote , $result, $string->escaped);
- }
- public function unit( $val, $unit = null) {
- if( !($val instanceof Less_Tree_Dimension) ){
- throw new Less_Exception_Compiler('The first argument to unit must be a number' . ($val instanceof Less_Tree_Operation ? '. Have you forgotten parenthesis?' : '.') );
- }
- if( $unit ){
- if( $unit instanceof Less_Tree_Keyword ){
- $unit = $unit->value;
- } else {
- $unit = $unit->toCSS();
- }
- } else {
- $unit = "";
- }
- return new Less_Tree_Dimension($val->value, $unit );
- }
- public function convert($val, $unit){
- return $val->convertTo($unit->value);
- }
- public function round($n, $f = false) {
- $fraction = 0;
- if( $f !== false ){
- $fraction = $f->value;
- }
- return $this->_math('Less_Parser::round',null, $n, $fraction);
- }
- public function pi(){
- return new Less_Tree_Dimension(M_PI);
- }
- public function mod($a, $b) {
- return new Less_Tree_Dimension( $a->value % $b->value, $a->unit);
- }
- public function pow($x, $y) {
- if( is_numeric($x) && is_numeric($y) ){
- $x = new Less_Tree_Dimension($x);
- $y = new Less_Tree_Dimension($y);
- }elseif( !($x instanceof Less_Tree_Dimension) || !($y instanceof Less_Tree_Dimension) ){
- throw new Less_Exception_Compiler('Arguments must be numbers');
- }
- return new Less_Tree_Dimension( pow($x->value, $y->value), $x->unit );
- }
- // var mathFunctions = [{name:"ce ...
- public function ceil( $n ){ return $this->_math('ceil', null, $n); }
- public function floor( $n ){ return $this->_math('floor', null, $n); }
- public function sqrt( $n ){ return $this->_math('sqrt', null, $n); }
- public function abs( $n ){ return $this->_math('abs', null, $n); }
- public function tan( $n ){ return $this->_math('tan', '', $n); }
- public function sin( $n ){ return $this->_math('sin', '', $n); }
- public function cos( $n ){ return $this->_math('cos', '', $n); }
- public function atan( $n ){ return $this->_math('atan', 'rad', $n); }
- public function asin( $n ){ return $this->_math('asin', 'rad', $n); }
- public function acos( $n ){ return $this->_math('acos', 'rad', $n); }
- private function _math() {
- $args = func_get_args();
- $fn = array_shift($args);
- $unit = array_shift($args);
- if ($args[0] instanceof Less_Tree_Dimension) {
- if( $unit === null ){
- $unit = $args[0]->unit;
- }else{
- $args[0] = $args[0]->unify();
- }
- $args[0] = (float)$args[0]->value;
- return new Less_Tree_Dimension( call_user_func_array($fn, $args), $unit);
- } else if (is_numeric($args[0])) {
- return call_user_func_array($fn,$args);
- } else {
- throw new Less_Exception_Compiler("math functions take numbers as parameters");
- }
- }
- /**
- * @param boolean $isMin
- */
- private function _minmax( $isMin, $args ){
- $arg_count = count($args);
- if( $arg_count < 1 ){
- throw new Less_Exception_Compiler( 'one or more arguments required');
- }
- $j = null;
- $unitClone = null;
- $unitStatic = null;
- $order = array(); // elems only contains original argument values.
- $values = array(); // key is the unit.toString() for unified tree.Dimension values,
- // value is the index into the order array.
- for( $i = 0; $i < $arg_count; $i++ ){
- $current = $args[$i];
- if( !($current instanceof Less_Tree_Dimension) ){
- if( is_array($args[$i]->value) ){
- $args[] = $args[$i]->value;
- }
- continue;
- }
- if( $current->unit->toString() === '' && !$unitClone ){
- $temp = new Less_Tree_Dimension($current->value, $unitClone);
- $currentUnified = $temp->unify();
- }else{
- $currentUnified = $current->unify();
- }
- if( $currentUnified->unit->toString() === "" && !$unitStatic ){
- $unit = $unitStatic;
- }else{
- $unit = $currentUnified->unit->toString();
- }
- if( $unit !== '' && !$unitStatic || $unit !== '' && $order[0]->unify()->unit->toString() === "" ){
- $unitStatic = $unit;
- }
- if( $unit != '' && !$unitClone ){
- $unitClone = $current->unit->toString();
- }
- if( isset($values['']) && $unit !== '' && $unit === $unitStatic ){
- $j = $values[''];
- }elseif( isset($values[$unit]) ){
- $j = $values[$unit];
- }else{
- if( $unitStatic && $unit !== $unitStatic ){
- throw new Less_Exception_Compiler( 'incompatible types');
- }
- $values[$unit] = count($order);
- $order[] = $current;
- continue;
- }
- if( $order[$j]->unit->toString() === "" && $unitClone ){
- $temp = new Less_Tree_Dimension( $order[$j]->value, $unitClone);
- $referenceUnified = $temp->unifiy();
- }else{
- $referenceUnified = $order[$j]->unify();
- }
- if( ($isMin && $currentUnified->value < $referenceUnified->value) || (!$isMin && $currentUnified->value > $referenceUnified->value) ){
- $order[$j] = $current;
- }
- }
- if( count($order) == 1 ){
- return $order[0];
- }
- $args = array();
- foreach($order as $a){
- $args[] = $a->toCSS($this->env);
- }
- return new Less_Tree_Anonymous( ($isMin?'min(':'max(') . implode(Less_Environment::$_outputMap[','],$args).')');
- }
- public function min(){
- $args = func_get_args();
- return $this->_minmax( true, $args );
- }
- public function max(){
- $args = func_get_args();
- return $this->_minmax( false, $args );
- }
- public function getunit($n){
- return new Less_Tree_Anonymous($n->unit);
- }
- public function argb($color) {
- return new Less_Tree_Anonymous($color->toARGB());
- }
- public function percentage($n) {
- return new Less_Tree_Dimension($n->value * 100, '%');
- }
- public function color($n) {
- if( $n instanceof Less_Tree_Quoted ){
- $colorCandidate = $n->value;
- $returnColor = Less_Tree_Color::fromKeyword($colorCandidate);
- if( $returnColor ){
- return $returnColor;
- }
- if( preg_match('/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/',$colorCandidate) ){
- return new Less_Tree_Color(substr($colorCandidate, 1));
- }
- throw new Less_Exception_Compiler("argument must be a color keyword or 3/6 digit hex e.g. #FFF");
- } else {
- throw new Less_Exception_Compiler("argument must be a string");
- }
- }
- public function iscolor($n) {
- return $this->_isa($n, 'Less_Tree_Color');
- }
- public function isnumber($n) {
- return $this->_isa($n, 'Less_Tree_Dimension');
- }
- public function isstring($n) {
- return $this->_isa($n, 'Less_Tree_Quoted');
- }
- public function iskeyword($n) {
- return $this->_isa($n, 'Less_Tree_Keyword');
- }
- public function isurl($n) {
- return $this->_isa($n, 'Less_Tree_Url');
- }
- public function ispixel($n) {
- return $this->isunit($n, 'px');
- }
- public function ispercentage($n) {
- return $this->isunit($n, '%');
- }
- public function isem($n) {
- return $this->isunit($n, 'em');
- }
- /**
- * @param string $unit
- */
- public function isunit( $n, $unit ){
- return ($n instanceof Less_Tree_Dimension) && $n->unit->is( ( property_exists($unit,'value') ? $unit->value : $unit) ) ? new Less_Tree_Keyword('true') : new Less_Tree_Keyword('false');
- }
- /**
- * @param string $type
- */
- private function _isa($n, $type) {
- return is_a($n, $type) ? new Less_Tree_Keyword('true') : new Less_Tree_Keyword('false');
- }
- public function tint($color, $amount) {
- return $this->mix( $this->rgb(255,255,255), $color, $amount);
- }
- public function shade($color, $amount) {
- return $this->mix($this->rgb(0, 0, 0), $color, $amount);
- }
- public function extract($values, $index ){
- $index = (int)$index->value - 1; // (1-based index)
- // handle non-array values as an array of length 1
- // return 'undefined' if index is invalid
- if( property_exists($values,'value') && is_array($values->value) ){
- if( isset($values->value[$index]) ){
- return $values->value[$index];
- }
- return null;
- }elseif( (int)$index === 0 ){
- return $values;
- }
- return null;
- }
- public function length($values){
- $n = (property_exists($values,'value') && is_array($values->value)) ? count($values->value) : 1;
- return new Less_Tree_Dimension($n);
- }
- public function datauri($mimetypeNode, $filePathNode = null ) {
- $filePath = ( $filePathNode ? $filePathNode->value : null );
- $mimetype = $mimetypeNode->value;
- $args = 2;
- if( !$filePath ){
- $filePath = $mimetype;
- $args = 1;
- }
- $filePath = str_replace('\\','/',$filePath);
- if( Less_Environment::isPathRelative($filePath) ){
- if( Less_Parser::$options['relativeUrls'] ){
- $temp = $this->currentFileInfo['currentDirectory'];
- } else {
- $temp = $this->currentFileInfo['entryPath'];
- }
- if( !empty($temp) ){
- $filePath = Less_Environment::normalizePath(rtrim($temp,'/').'/'.$filePath);
- }
- }
- // detect the mimetype if not given
- if( $args < 2 ){
- /* incomplete
- $mime = require('mime');
- mimetype = mime.lookup(path);
- // use base 64 unless it's an ASCII or UTF-8 format
- var charset = mime.charsets.lookup(mimetype);
- useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0;
- if (useBase64) mimetype += ';base64';
- */
- $mimetype = Less_Mime::lookup($filePath);
- $charset = Less_Mime::charsets_lookup($mimetype);
- $useBase64 = !in_array($charset,array('US-ASCII', 'UTF-8'));
- if( $useBase64 ){ $mimetype .= ';base64'; }
- }else{
- $useBase64 = preg_match('/;base64$/',$mimetype);
- }
- if( file_exists($filePath) ){
- $buf = @file_get_contents($filePath);
- }else{
- $buf = false;
- }
- // IE8 cannot handle a data-uri larger than 32KB. If this is exceeded
- // and the --ieCompat flag is enabled, return a normal url() instead.
- $DATA_URI_MAX_KB = 32;
- $fileSizeInKB = round( strlen($buf) / 1024 );
- if( $fileSizeInKB >= $DATA_URI_MAX_KB ){
- $url = new Less_Tree_Url( ($filePathNode ? $filePathNode : $mimetypeNode), $this->currentFileInfo);
- return $url->compile($this);
- }
- if( $buf ){
- $buf = $useBase64 ? base64_encode($buf) : rawurlencode($buf);
- $filePath = '"data:' . $mimetype . ',' . $buf . '"';
- }
- return new Less_Tree_Url( new Less_Tree_Anonymous($filePath) );
- }
- //svg-gradient
- public function svggradient( $direction ){
- $throw_message = 'svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]';
- $arguments = func_get_args();
- if( count($arguments) < 3 ){
- throw new Less_Exception_Compiler( $throw_message );
- }
- $stops = array_slice($arguments,1);
- $gradientType = 'linear';
- $rectangleDimension = 'x="0" y="0" width="1" height="1"';
- $useBase64 = true;
- $directionValue = $direction->toCSS();
- switch( $directionValue ){
- case "to bottom":
- $gradientDirectionSvg = 'x1="0%" y1="0%" x2="0%" y2="100%"';
- break;
- case "to right":
- $gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="0%"';
- break;
- case "to bottom right":
- $gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="100%"';
- break;
- case "to top right":
- $gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"';
- break;
- case "ellipse":
- case "ellipse at center":
- $gradientType = "radial";
- $gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"';
- $rectangleDimension = 'x="-50" y="-50" width="101" height="101"';
- break;
- default:
- throw new Less_Exception_Compiler( "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" );
- }
- $returner = '<?xml version="1.0" ?>' .
- '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">' .
- '<' . $gradientType . 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' . $gradientDirectionSvg . '>';
- for( $i = 0; $i < count($stops); $i++ ){
- if( is_object($stops[$i]) && property_exists($stops[$i],'value') ){
- $color = $stops[$i]->value[0];
- $position = $stops[$i]->value[1];
- }else{
- $color = $stops[$i];
- $position = null;
- }
- if( !($color instanceof Less_Tree_Color) || (!(($i === 0 || $i+1 === count($stops)) && $position === null) && !($position instanceof Less_Tree_Dimension)) ){
- throw new Less_Exception_Compiler( $throw_message );
- }
- if( $position ){
- $positionValue = $position->toCSS();
- }elseif( $i === 0 ){
- $positionValue = '0%';
- }else{
- $positionValue = '100%';
- }
- $alpha = $color->alpha;
- $returner .= '<stop offset="' . $positionValue . '" stop-color="' . $color->toRGB() . '"' . ($alpha < 1 ? ' stop-opacity="' . $alpha . '"' : '') . '/>';
- }
- $returner .= '</' . $gradientType . 'Gradient><rect ' . $rectangleDimension . ' fill="url(#gradient)" /></svg>';
- if( $useBase64 ){
- $returner = "'data:image/svg+xml;base64,".base64_encode($returner)."'";
- }else{
- $returner = "'data:image/svg+xml,".$returner."'";
- }
- return new Less_Tree_URL( new Less_Tree_Anonymous( $returner ) );
- }
- /**
- * @param string $type
- */
- private static function Expected( $type, $arg ){
- $debug = debug_backtrace();
- array_shift($debug);
- $last = array_shift($debug);
- $last = array_intersect_key($last,array('function'=>'','class'=>'','line'=>''));
- $message = 'Object of type '.get_class($arg).' passed to darken function. Expecting `'.$type.'`. '.$arg->toCSS().'. '.print_r($last,true);
- throw new Less_Exception_Compiler($message);
- }
- /**
- * Php version of javascript's `encodeURIComponent` function
- *
- * @param string $string The string to encode
- * @return string The encoded string
- */
- public static function encodeURIComponent($string){
- $revert = array('%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')');
- return strtr(rawurlencode($string), $revert);
- }
- // Color Blending
- // ref: http://www.w3.org/TR/compositing-1
- public function colorBlend( $mode, $color1, $color2 ){
- $ab = $color1->alpha; // backdrop
- $as = $color2->alpha; // source
- $r = array(); // result
- $ar = $as + $ab * (1 - $as);
- for( $i = 0; $i < 3; $i++ ){
- $cb = $color1->rgb[$i] / 255;
- $cs = $color2->rgb[$i] / 255;
- $cr = call_user_func( $mode, $cb, $cs );
- if( $ar ){
- $cr = ($as * $cs + $ab * ($cb - $as * ($cb + $cs - $cr))) / $ar;
- }
- $r[$i] = $cr * 255;
- }
- return new Less_Tree_Color($r, $ar);
- }
- public function multiply($color1, $color2 ){
- return $this->colorBlend( array($this,'colorBlendMultiply'), $color1, $color2 );
- }
- private function colorBlendMultiply($cb, $cs){
- return $cb * $cs;
- }
- public function screen($color1, $color2 ){
- return $this->colorBlend( array($this,'colorBlendScreen'), $color1, $color2 );
- }
- private function colorBlendScreen( $cb, $cs){
- return $cb + $cs - $cb * $cs;
- }
- public function overlay($color1, $color2){
- return $this->colorBlend( array($this,'colorBlendOverlay'), $color1, $color2 );
- }
- private function colorBlendOverlay($cb, $cs ){
- $cb *= 2;
- return ($cb <= 1)
- ? $this->colorBlendMultiply($cb, $cs)
- : $this->colorBlendScreen($cb - 1, $cs);
- }
- public function softlight($color1, $color2){
- return $this->colorBlend( array($this,'colorBlendSoftlight'), $color1, $color2 );
- }
- private function colorBlendSoftlight($cb, $cs ){
- $d = 1;
- $e = $cb;
- if( $cs > 0.5 ){
- $e = 1;
- $d = ($cb > 0.25) ? sqrt($cb)
- : ((16 * $cb - 12) * $cb + 4) * $cb;
- }
- return $cb - (1 - 2 * $cs) * $e * ($d - $cb);
- }
- public function hardlight($color1, $color2){
- return $this->colorBlend( array($this,'colorBlendHardlight'), $color1, $color2 );
- }
- private function colorBlendHardlight( $cb, $cs ){
- return $this->colorBlendOverlay($cs, $cb);
- }
- public function difference($color1, $color2) {
- return $this->colorBlend( array($this,'colorBlendDifference'), $color1, $color2 );
- }
- private function colorBlendDifference( $cb, $cs ){
- return abs($cb - $cs);
- }
- public function exclusion( $color1, $color2 ){
- return $this->colorBlend( array($this,'colorBlendExclusion'), $color1, $color2 );
- }
- private function colorBlendExclusion( $cb, $cs ){
- return $cb + $cs - 2 * $cb * $cs;
- }
- public function average($color1, $color2){
- return $this->colorBlend( array($this,'colorBlendAverage'), $color1, $color2 );
- }
- // non-w3c functions:
- public function colorBlendAverage($cb, $cs ){
- return ($cb + $cs) / 2;
- }
- public function negation($color1, $color2 ){
- return $this->colorBlend( array($this,'colorBlendNegation'), $color1, $color2 );
- }
- public function colorBlendNegation($cb, $cs){
- return 1 - abs($cb + $cs - 1);
- }
- // ~ End of Color Blending
- }
-
- /**
- * Mime lookup
- *
- * @package Less
- * @subpackage node
- */
- class Less_Mime{
- // this map is intentionally incomplete
- // if you want more, install 'mime' dep
- static $_types = array(
- '.htm' => 'text/html',
- '.html'=> 'text/html',
- '.gif' => 'image/gif',
- '.jpg' => 'image/jpeg',
- '.jpeg'=> 'image/jpeg',
- '.png' => 'image/png',
- '.ttf' => 'application/x-font-ttf',
- '.otf' => 'application/x-font-otf',
- '.eot' => 'application/vnd.ms-fontobject',
- '.woff' => 'application/x-font-woff',
- '.svg' => 'image/svg+xml',
- );
- public static function lookup( $filepath ){
- $parts = explode('.',$filepath);
- $ext = '.'.strtolower(array_pop($parts));
- if( !isset(self::$_types[$ext]) ){
- return null;
- }
- return self::$_types[$ext];
- }
- public static function charsets_lookup( $type = null ){
- // assumes all text types are UTF-8
- return $type && preg_match('/^text\//',$type) ? 'UTF-8' : '';
- }
- }
-
- /**
- * Tree
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree{
- public $cache_string;
- public function toCSS(){
- $output = new Less_Output();
- $this->genCSS($output);
- return $output->toString();
- }
- /**
- * Generate CSS by adding it to the output object
- *
- * @param Less_Output $output The output
- * @return void
- */
- public function genCSS($output){}
- /**
- * @param Less_Tree_Ruleset[] $rules
- */
- public static function outputRuleset( $output, $rules ){
- $ruleCnt = count($rules);
- Less_Environment::$tabLevel++;
- // Compressed
- if( Less_Parser::$options['compress'] ){
- $output->add('{');
- for( $i = 0; $i < $ruleCnt; $i++ ){
- $rules[$i]->genCSS( $output );
- }
- $output->add( '}' );
- Less_Environment::$tabLevel--;
- return;
- }
- // Non-compressed
- $tabSetStr = "\n".str_repeat( ' ' , Less_Environment::$tabLevel-1 );
- $tabRuleStr = $tabSetStr.' ';
- $output->add( " {" );
- for($i = 0; $i < $ruleCnt; $i++ ){
- $output->add( $tabRuleStr );
- $rules[$i]->genCSS( $output );
- }
- Less_Environment::$tabLevel--;
- $output->add( $tabSetStr.'}' );
- }
- public function accept($visitor){}
- public static function ReferencedArray($rules){
- foreach($rules as $rule){
- if( method_exists($rule, 'markReferenced') ){
- $rule->markReferenced();
- }
- }
- }
- /**
- * Requires php 5.3+
- */
- public static function __set_state($args){
- $class = get_called_class();
- $obj = new $class(null,null,null,null);
- foreach($args as $key => $val){
- $obj->$key = $val;
- }
- return $obj;
- }
- }
- /**
- * Parser output
- *
- * @package Less
- * @subpackage output
- */
- class Less_Output{
- /**
- * Output holder
- *
- * @var string
- */
- protected $strs = array();
- /**
- * Adds a chunk to the stack
- *
- * @param string $chunk The chunk to output
- * @param Less_FileInfo $fileInfo The file information
- * @param integer $index The index
- * @param mixed $mapLines
- */
- public function add($chunk, $fileInfo = null, $index = 0, $mapLines = null){
- $this->strs[] = $chunk;
- }
- /**
- * Is the output empty?
- *
- * @return boolean
- */
- public function isEmpty(){
- return count($this->strs) === 0;
- }
- /**
- * Converts the output to string
- *
- * @return string
- */
- public function toString(){
- return implode('',$this->strs);
- }
- }
- /**
- * Visitor
- *
- * @package Less
- * @subpackage visitor
- */
- class Less_Visitor{
- protected $methods = array();
- protected $_visitFnCache = array();
- public function __construct(){
- $this->_visitFnCache = get_class_methods(get_class($this));
- $this->_visitFnCache = array_flip($this->_visitFnCache);
- }
- public function visitObj( $node ){
- $funcName = 'visit'.$node->type;
- if( isset($this->_visitFnCache[$funcName]) ){
- $visitDeeper = true;
- $this->$funcName( $node, $visitDeeper );
- if( $visitDeeper ){
- $node->accept($this);
- }
- $funcName = $funcName . "Out";
- if( isset($this->_visitFnCache[$funcName]) ){
- $this->$funcName( $node );
- }
- }else{
- $node->accept($this);
- }
- return $node;
- }
- public function visitArray( $nodes ){
- array_map( array($this,'visitObj'), $nodes);
- return $nodes;
- }
- }
-
- /**
- * Replacing Visitor
- *
- * @package Less
- * @subpackage visitor
- */
- class Less_VisitorReplacing extends Less_Visitor{
- public function visitObj( $node ){
- $funcName = 'visit'.$node->type;
- if( isset($this->_visitFnCache[$funcName]) ){
- $visitDeeper = true;
- $node = $this->$funcName( $node, $visitDeeper );
- if( $node ){
- if( $visitDeeper && is_object($node) ){
- $node->accept($this);
- }
- $funcName = $funcName . "Out";
- if( isset($this->_visitFnCache[$funcName]) ){
- $this->$funcName( $node );
- }
- }
- }else{
- $node->accept($this);
- }
- return $node;
- }
- public function visitArray( $nodes ){
- $newNodes = array();
- foreach($nodes as $node){
- $evald = $this->visitObj($node);
- if( $evald ){
- if( is_array($evald) ){
- self::flatten($evald,$newNodes);
- }else{
- $newNodes[] = $evald;
- }
- }
- }
- return $newNodes;
- }
- public function flatten( $arr, &$out ){
- foreach($arr as $item){
- if( !is_array($item) ){
- $out[] = $item;
- continue;
- }
- foreach($item as $nestedItem){
- if( is_array($nestedItem) ){
- self::flatten( $nestedItem, $out);
- }else{
- $out[] = $nestedItem;
- }
- }
- }
- return $out;
- }
- }
-
- /**
- * Configurable
- *
- * @package Less
- * @subpackage Core
- */
- abstract class Less_Configurable {
- /**
- * Array of options
- *
- * @var array
- */
- protected $options = array();
- /**
- * Array of default options
- *
- * @var array
- */
- protected $defaultOptions = array();
- /**
- * Set options
- *
- * If $options is an object it will be converted into an array by called
- * it's toArray method.
- *
- * @throws Exception
- * @param array|object $options
- *
- */
- public function setOptions($options){
- $options = array_intersect_key($options,$this->defaultOptions);
- $this->options = array_merge($this->defaultOptions, $this->options, $options);
- }
- /**
- * Get an option value by name
- *
- * If the option is empty or not set a NULL value will be returned.
- *
- * @param string $name
- * @param mixed $default Default value if confiuration of $name is not present
- * @return mixed
- */
- public function getOption($name, $default = null){
- if(isset($this->options[$name])){
- return $this->options[$name];
- }
- return $default;
- }
- /**
- * Set an option
- *
- * @param string $name
- * @param mixed $value
- */
- public function setOption($name, $value){
- $this->options[$name] = $value;
- }
- }
- /**
- * Alpha
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Alpha extends Less_Tree{
- public $value;
- public $type = 'Alpha';
- public function __construct($val){
- $this->value = $val;
- }
- //function accept( $visitor ){
- // $this->value = $visitor->visit( $this->value );
- //}
- public function compile($env){
- if( is_object($this->value) ){
- $this->value = $this->value->compile($env);
- }
- return $this;
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- $output->add( "alpha(opacity=" );
- if( is_string($this->value) ){
- $output->add( $this->value );
- }else{
- $this->value->genCSS( $output);
- }
- $output->add( ')' );
- }
- public function toCSS(){
- return "alpha(opacity=" . (is_string($this->value) ? $this->value : $this->value->toCSS()) . ")";
- }
- }
- /**
- * Anonymous
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Anonymous extends Less_Tree{
- public $value;
- public $quote;
- public $index;
- public $mapLines;
- public $currentFileInfo;
- public $type = 'Anonymous';
- /**
- * @param integer $index
- * @param boolean $mapLines
- */
- public function __construct($value, $index = null, $currentFileInfo = null, $mapLines = null ){
- $this->value = $value;
- $this->index = $index;
- $this->mapLines = $mapLines;
- $this->currentFileInfo = $currentFileInfo;
- }
- public function compile(){
- return new Less_Tree_Anonymous($this->value, $this->index, $this->currentFileInfo, $this->mapLines);
- }
- public function compare($x){
- if( !is_object($x) ){
- return -1;
- }
- $left = $this->toCSS();
- $right = $x->toCSS();
- if( $left === $right ){
- return 0;
- }
- return $left < $right ? -1 : 1;
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- $output->add( $this->value, $this->currentFileInfo, $this->index, $this->mapLines );
- }
- public function toCSS(){
- return $this->value;
- }
- }
-
- /**
- * Assignment
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Assignment extends Less_Tree{
- public $key;
- public $value;
- public $type = 'Assignment';
- public function __construct($key, $val) {
- $this->key = $key;
- $this->value = $val;
- }
- public function accept( $visitor ){
- $this->value = $visitor->visitObj( $this->value );
- }
- public function compile($env) {
- return new Less_Tree_Assignment( $this->key, $this->value->compile($env));
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- $output->add( $this->key . '=' );
- $this->value->genCSS( $output );
- }
- public function toCss(){
- return $this->key . '=' . $this->value->toCSS();
- }
- }
-
- /**
- * Attribute
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Attribute extends Less_Tree{
- public $key;
- public $op;
- public $value;
- public $type = 'Attribute';
- public function __construct($key, $op, $value){
- $this->key = $key;
- $this->op = $op;
- $this->value = $value;
- }
- public function compile($env){
- $key_obj = is_object($this->key);
- $val_obj = is_object($this->value);
- if( !$key_obj && !$val_obj ){
- return $this;
- }
- return new Less_Tree_Attribute(
- $key_obj ? $this->key->compile($env) : $this->key ,
- $this->op,
- $val_obj ? $this->value->compile($env) : $this->value);
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- $output->add( $this->toCSS() );
- }
- public function toCSS(){
- $value = $this->key;
- if( $this->op ){
- $value .= $this->op;
- $value .= (is_object($this->value) ? $this->value->toCSS() : $this->value);
- }
- return '[' . $value . ']';
- }
- }
- /**
- * Call
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Call extends Less_Tree{
- public $value;
- protected $name;
- protected $args;
- protected $index;
- protected $currentFileInfo;
- public $type = 'Call';
- public function __construct($name, $args, $index, $currentFileInfo = null ){
- $this->name = $name;
- $this->args = $args;
- $this->index = $index;
- $this->currentFileInfo = $currentFileInfo;
- }
- public function accept( $visitor ){
- $this->args = $visitor->visitArray( $this->args );
- }
- //
- // When evaluating a function call,
- // we either find the function in `tree.functions` [1],
- // in which case we call it, passing the evaluated arguments,
- // or we simply print it out as it appeared originally [2].
- //
- // The *functions.js* file contains the built-in functions.
- //
- // The reason why we evaluate the arguments, is in the case where
- // we try to pass a variable to a function, like: `saturate(@color)`.
- // The function should receive the value, not the variable.
- //
- public function compile($env=null){
- $args = array();
- foreach($this->args as $a){
- $args[] = $a->compile($env);
- }
- $nameLC = strtolower($this->name);
- switch($nameLC){
- case '%':
- $nameLC = '_percent';
- break;
- case 'get-unit':
- $nameLC = 'getunit';
- break;
- case 'data-uri':
- $nameLC = 'datauri';
- break;
- case 'svg-gradient':
- $nameLC = 'svggradient';
- break;
- }
- $result = null;
- if( $nameLC === 'default' ){
- $result = Less_Tree_DefaultFunc::compile();
- }else{
- if( method_exists('Less_Functions',$nameLC) ){ // 1.
- try {
- $func = new Less_Functions($env, $this->currentFileInfo);
- $result = call_user_func_array( array($func,$nameLC),$args);
- } catch (Exception $e) {
- throw new Less_Exception_Compiler('error evaluating function `' . $this->name . '` '.$e->getMessage().' index: '. $this->index);
- }
- } elseif( isset( $env->functions[$nameLC] ) && is_callable( $env->functions[$nameLC] ) ) {
- try {
- $result = call_user_func_array( $env->functions[$nameLC], $args );
- } catch (Exception $e) {
- throw new Less_Exception_Compiler('error evaluating function `' . $this->name . '` '.$e->getMessage().' index: '. $this->index);
- }
- }
- }
- if( $result !== null ){
- return $result;
- }
- return new Less_Tree_Call( $this->name, $args, $this->index, $this->currentFileInfo );
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- $output->add( $this->name . '(', $this->currentFileInfo, $this->index );
- $args_len = count($this->args);
- for($i = 0; $i < $args_len; $i++ ){
- $this->args[$i]->genCSS( $output );
- if( $i + 1 < $args_len ){
- $output->add( ', ' );
- }
- }
- $output->add( ')' );
- }
- //public function toCSS(){
- // return $this->compile()->toCSS();
- //}
- }
-
- /**
- * Color
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Color extends Less_Tree{
- public $rgb;
- public $alpha;
- public $isTransparentKeyword;
- public $type = 'Color';
- public function __construct($rgb, $a = 1, $isTransparentKeyword = null ){
- if( $isTransparentKeyword ){
- $this->rgb = $rgb;
- $this->alpha = $a;
- $this->isTransparentKeyword = true;
- return;
- }
- $this->rgb = array();
- if( is_array($rgb) ){
- $this->rgb = $rgb;
- }else if( strlen($rgb) == 6 ){
- foreach(str_split($rgb, 2) as $c){
- $this->rgb[] = hexdec($c);
- }
- }else{
- foreach(str_split($rgb, 1) as $c){
- $this->rgb[] = hexdec($c.$c);
- }
- }
- $this->alpha = is_numeric($a) ? $a : 1;
- }
- public function compile(){
- return $this;
- }
- public function luma(){
- $r = $this->rgb[0] / 255;
- $g = $this->rgb[1] / 255;
- $b = $this->rgb[2] / 255;
- $r = ($r <= 0.03928) ? $r / 12.92 : pow((($r + 0.055) / 1.055), 2.4);
- $g = ($g <= 0.03928) ? $g / 12.92 : pow((($g + 0.055) / 1.055), 2.4);
- $b = ($b <= 0.03928) ? $b / 12.92 : pow((($b + 0.055) / 1.055), 2.4);
- return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- $output->add( $this->toCSS() );
- }
- public function toCSS( $doNotCompress = false ){
- $compress = Less_Parser::$options['compress'] && !$doNotCompress;
- $alpha = Less_Functions::fround( $this->alpha );
- //
- // If we have some transparency, the only way to represent it
- // is via `rgba`. Otherwise, we use the hex representation,
- // which has better compatibility with older browsers.
- // Values are capped between `0` and `255`, rounded and zero-padded.
- //
- if( $alpha < 1 ){
- if( ( $alpha === 0 || $alpha === 0.0 ) && isset($this->isTransparentKeyword) && $this->isTransparentKeyword ){
- return 'transparent';
- }
- $values = array();
- foreach($this->rgb as $c){
- $values[] = Less_Functions::clamp( round($c), 255);
- }
- $values[] = $alpha;
- $glue = ($compress ? ',' : ', ');
- return "rgba(" . implode($glue, $values) . ")";
- }else{
- $color = $this->toRGB();
- if( $compress ){
- // Convert color to short format
- if( $color[1] === $color[2] && $color[3] === $color[4] && $color[5] === $color[6]) {
- $color = '#'.$color[1] . $color[3] . $color[5];
- }
- }
- return $color;
- }
- }
- //
- // Operations have to be done per-channel, if not,
- // channels will spill onto each other. Once we have
- // our result, in the form of an integer triplet,
- // we create a new Color node to hold the result.
- //
- /**
- * @param string $op
- */
- public function operate( $op, $other) {
- $rgb = array();
- $alpha = $this->alpha * (1 - $other->alpha) + $other->alpha;
- for ($c = 0; $c < 3; $c++) {
- $rgb[$c] = Less_Functions::operate( $op, $this->rgb[$c], $other->rgb[$c]);
- }
- return new Less_Tree_Color($rgb, $alpha);
- }
- public function toRGB(){
- return $this->toHex($this->rgb);
- }
- public function toHSL(){
- $r = $this->rgb[0] / 255;
- $g = $this->rgb[1] / 255;
- $b = $this->rgb[2] / 255;
- $a = $this->alpha;
- $max = max($r, $g, $b);
- $min = min($r, $g, $b);
- $l = ($max + $min) / 2;
- $d = $max - $min;
- $h = $s = 0;
- if( $max !== $min ){
- $s = $l > 0.5 ? $d / (2 - $max - $min) : $d / ($max + $min);
- switch ($max) {
- case $r: $h = ($g - $b) / $d + ($g < $b ? 6 : 0); break;
- case $g: $h = ($b - $r) / $d + 2; break;
- case $b: $h = ($r - $g) / $d + 4; break;
- }
- $h /= 6;
- }
- return array('h' => $h * 360, 's' => $s, 'l' => $l, 'a' => $a );
- }
- //Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
- public function toHSV() {
- $r = $this->rgb[0] / 255;
- $g = $this->rgb[1] / 255;
- $b = $this->rgb[2] / 255;
- $a = $this->alpha;
- $max = max($r, $g, $b);
- $min = min($r, $g, $b);
- $v = $max;
- $d = $max - $min;
- if ($max === 0) {
- $s = 0;
- } else {
- $s = $d / $max;
- }
- $h = 0;
- if( $max !== $min ){
- switch($max){
- case $r: $h = ($g - $b) / $d + ($g < $b ? 6 : 0); break;
- case $g: $h = ($b - $r) / $d + 2; break;
- case $b: $h = ($r - $g) / $d + 4; break;
- }
- $h /= 6;
- }
- return array('h'=> $h * 360, 's'=> $s, 'v'=> $v, 'a' => $a );
- }
- public function toARGB(){
- $argb = array_merge( (array) Less_Parser::round($this->alpha * 255), $this->rgb);
- return $this->toHex( $argb );
- }
- public function compare($x){
- if( !property_exists( $x, 'rgb' ) ){
- return -1;
- }
- return ($x->rgb[0] === $this->rgb[0] &&
- $x->rgb[1] === $this->rgb[1] &&
- $x->rgb[2] === $this->rgb[2] &&
- $x->alpha === $this->alpha) ? 0 : -1;
- }
- public function toHex( $v ){
- $ret = '#';
- foreach($v as $c){
- $c = Less_Functions::clamp( Less_Parser::round($c), 255);
- if( $c < 16 ){
- $ret .= '0';
- }
- $ret .= dechex($c);
- }
- return $ret;
- }
- /**
- * @param string $keyword
- */
- public static function fromKeyword( $keyword ){
- $keyword = strtolower($keyword);
- if( Less_Colors::hasOwnProperty($keyword) ){
- // detect named color
- return new Less_Tree_Color(substr(Less_Colors::color($keyword), 1));
- }
- if( $keyword === 'transparent' ){
- return new Less_Tree_Color( array(0, 0, 0), 0, true);
- }
- }
- }
-
- /**
- * Comment
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Comment extends Less_Tree{
- public $value;
- public $silent;
- public $isReferenced;
- public $currentFileInfo;
- public $type = 'Comment';
- public function __construct($value, $silent, $index = null, $currentFileInfo = null ){
- $this->value = $value;
- $this->silent = !! $silent;
- $this->currentFileInfo = $currentFileInfo;
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- //if( $this->debugInfo ){
- //$output->add( tree.debugInfo($env, $this), $this->currentFileInfo, $this->index);
- //}
- $output->add( trim($this->value) );//TODO shouldn't need to trim, we shouldn't grab the \n
- }
- public function toCSS(){
- return Less_Parser::$options['compress'] ? '' : $this->value;
- }
- public function isSilent(){
- $isReference = ($this->currentFileInfo && isset($this->currentFileInfo['reference']) && (!isset($this->isReferenced) || !$this->isReferenced) );
- $isCompressed = Less_Parser::$options['compress'] && !preg_match('/^\/\*!/', $this->value);
- return $this->silent || $isReference || $isCompressed;
- }
- public function compile(){
- return $this;
- }
- public function markReferenced(){
- $this->isReferenced = true;
- }
- }
-
- /**
- * Condition
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Condition extends Less_Tree{
- public $op;
- public $lvalue;
- public $rvalue;
- public $index;
- public $negate;
- public $type = 'Condition';
- public function __construct($op, $l, $r, $i = 0, $negate = false) {
- $this->op = trim($op);
- $this->lvalue = $l;
- $this->rvalue = $r;
- $this->index = $i;
- $this->negate = $negate;
- }
- public function accept($visitor){
- $this->lvalue = $visitor->visitObj( $this->lvalue );
- $this->rvalue = $visitor->visitObj( $this->rvalue );
- }
- public function compile($env) {
- $a = $this->lvalue->compile($env);
- $b = $this->rvalue->compile($env);
- switch( $this->op ){
- case 'and':
- $result = $a && $b;
- break;
- case 'or':
- $result = $a || $b;
- break;
- default:
- if( Less_Parser::is_method($a, 'compare') ){
- $result = $a->compare($b);
- }elseif( Less_Parser::is_method($b, 'compare') ){
- $result = $b->compare($a);
- }else{
- throw new Less_Exception_Compiler('Unable to perform comparison', null, $this->index);
- }
- switch ($result) {
- case -1:
- $result = $this->op === '<' || $this->op === '=<' || $this->op === '<=';
- break;
- case 0:
- $result = $this->op === '=' || $this->op === '>=' || $this->op === '=<' || $this->op === '<=';
- break;
- case 1:
- $result = $this->op === '>' || $this->op === '>=';
- break;
- }
- break;
- }
- return $this->negate ? !$result : $result;
- }
- }
-
- /**
- * DefaultFunc
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_DefaultFunc{
- static $error_;
- static $value_;
- public static function compile(){
- if( self::$error_ ){
- throw new Exception(self::$error_);
- }
- if( self::$value_ !== null ){
- return self::$value_ ? new Less_Tree_Keyword('true') : new Less_Tree_Keyword('false');
- }
- }
- public static function value( $v ){
- self::$value_ = $v;
- }
- public static function error( $e ){
- self::$error_ = $e;
- }
- public static function reset(){
- self::$value_ = self::$error_ = null;
- }
- }
- /**
- * DetachedRuleset
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_DetachedRuleset extends Less_Tree{
- public $ruleset;
- public $frames;
- public $type = 'DetachedRuleset';
- public function __construct( $ruleset, $frames = null ){
- $this->ruleset = $ruleset;
- $this->frames = $frames;
- }
- public function accept($visitor) {
- $this->ruleset = $visitor->visitObj($this->ruleset);
- }
- public function compile($env){
- if( $this->frames ){
- $frames = $this->frames;
- }else{
- $frames = $env->frames;
- }
- return new Less_Tree_DetachedRuleset($this->ruleset, $frames);
- }
- public function callEval($env) {
- if( $this->frames ){
- return $this->ruleset->compile( $env->copyEvalEnv( array_merge($this->frames,$env->frames) ) );
- }
- return $this->ruleset->compile( $env );
- }
- }
-
- /**
- * Dimension
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Dimension extends Less_Tree{
- public $value;
- public $unit;
- public $type = 'Dimension';
- public function __construct($value, $unit = null){
- $this->value = floatval($value);
- if( $unit && ($unit instanceof Less_Tree_Unit) ){
- $this->unit = $unit;
- }elseif( $unit ){
- $this->unit = new Less_Tree_Unit( array($unit) );
- }else{
- $this->unit = new Less_Tree_Unit( );
- }
- }
- public function accept( $visitor ){
- $this->unit = $visitor->visitObj( $this->unit );
- }
- public function compile(){
- return $this;
- }
- public function toColor() {
- return new Less_Tree_Color(array($this->value, $this->value, $this->value));
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- if( Less_Parser::$options['strictUnits'] && !$this->unit->isSingular() ){
- throw new Less_Exception_Compiler("Multiple units in dimension. Correct the units or use the unit function. Bad unit: ".$this->unit->toString());
- }
- $value = Less_Functions::fround( $this->value );
- $strValue = (string)$value;
- if( $value !== 0 && $value < 0.000001 && $value > -0.000001 ){
- // would be output 1e-6 etc.
- $strValue = number_format($strValue,10);
- $strValue = preg_replace('/\.?0+$/','', $strValue);
- }
- if( Less_Parser::$options['compress'] ){
- // Zero values doesn't need a unit
- if( $value === 0 && $this->unit->isLength() ){
- $output->add( $strValue );
- return $strValue;
- }
- // Float values doesn't need a leading zero
- if( $value > 0 && $value < 1 && $strValue[0] === '0' ){
- $strValue = substr($strValue,1);
- }
- }
- $output->add( $strValue );
- $this->unit->genCSS( $output );
- }
- public function __toString(){
- return $this->toCSS();
- }
- // In an operation between two Dimensions,
- // we default to the first Dimension's unit,
- // so `1px + 2em` will yield `3px`.
- /**
- * @param string $op
- */
- public function operate( $op, $other){
- $value = Less_Functions::operate( $op, $this->value, $other->value);
- $unit = clone $this->unit;
- if( $op === '+' || $op === '-' ){
- if( !$unit->numerator && !$unit->denominator ){
- $unit->numerator = $other->unit->numerator;
- $unit->denominator = $other->unit->denominator;
- }elseif( !$other->unit->numerator && !$other->unit->denominator ){
- // do nothing
- }else{
- $other = $other->convertTo( $this->unit->usedUnits());
- if( Less_Parser::$options['strictUnits'] && $other->unit->toString() !== $unit->toCSS() ){
- throw new Less_Exception_Compiler("Incompatible units. Change the units or use the unit function. Bad units: '".$unit->toString() . "' and ".$other->unit->toString()+"'.");
- }
- $value = Less_Functions::operate( $op, $this->value, $other->value);
- }
- }elseif( $op === '*' ){
- $unit->numerator = array_merge($unit->numerator, $other->unit->numerator);
- $unit->denominator = array_merge($unit->denominator, $other->unit->denominator);
- sort($unit->numerator);
- sort($unit->denominator);
- $unit->cancel();
- }elseif( $op === '/' ){
- $unit->numerator = array_merge($unit->numerator, $other->unit->denominator);
- $unit->denominator = array_merge($unit->denominator, $other->unit->numerator);
- sort($unit->numerator);
- sort($unit->denominator);
- $unit->cancel();
- }
- return new Less_Tree_Dimension( $value, $unit);
- }
- public function compare($other) {
- if ($other instanceof Less_Tree_Dimension) {
- if( $this->unit->isEmpty() || $other->unit->isEmpty() ){
- $a = $this;
- $b = $other;
- } else {
- $a = $this->unify();
- $b = $other->unify();
- if( $a->unit->compare($b->unit) !== 0 ){
- return -1;
- }
- }
- $aValue = $a->value;
- $bValue = $b->value;
- if ($bValue > $aValue) {
- return -1;
- } elseif ($bValue < $aValue) {
- return 1;
- } else {
- return 0;
- }
- } else {
- return -1;
- }
- }
- public function unify() {
- return $this->convertTo(array('length'=> 'px', 'duration'=> 's', 'angle' => 'rad' ));
- }
- public function convertTo($conversions) {
- $value = $this->value;
- $unit = clone $this->unit;
- if( is_string($conversions) ){
- $derivedConversions = array();
- foreach( Less_Tree_UnitConversions::$groups as $i ){
- if( isset(Less_Tree_UnitConversions::${$i}[$conversions]) ){
- $derivedConversions = array( $i => $conversions);
- }
- }
- $conversions = $derivedConversions;
- }
- foreach($conversions as $groupName => $targetUnit){
- $group = Less_Tree_UnitConversions::${$groupName};
- //numerator
- foreach($unit->numerator as $i => $atomicUnit){
- $atomicUnit = $unit->numerator[$i];
- if( !isset($group[$atomicUnit]) ){
- continue;
- }
- $value = $value * ($group[$atomicUnit] / $group[$targetUnit]);
- $unit->numerator[$i] = $targetUnit;
- }
- //denominator
- foreach($unit->denominator as $i => $atomicUnit){
- $atomicUnit = $unit->denominator[$i];
- if( !isset($group[$atomicUnit]) ){
- continue;
- }
- $value = $value / ($group[$atomicUnit] / $group[$targetUnit]);
- $unit->denominator[$i] = $targetUnit;
- }
- }
- $unit->cancel();
- return new Less_Tree_Dimension( $value, $unit);
- }
- }
-
- /**
- * Directive
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Directive extends Less_Tree{
- public $name;
- public $value;
- public $rules;
- public $index;
- public $isReferenced;
- public $currentFileInfo;
- public $debugInfo;
- public $type = 'Directive';
- public function __construct($name, $value = null, $rules, $index = null, $currentFileInfo = null, $debugInfo = null ){
- $this->name = $name;
- $this->value = $value;
- if( $rules ){
- $this->rules = $rules;
- $this->rules->allowImports = true;
- }
- $this->index = $index;
- $this->currentFileInfo = $currentFileInfo;
- $this->debugInfo = $debugInfo;
- }
- public function accept( $visitor ){
- if( $this->rules ){
- $this->rules = $visitor->visitObj( $this->rules );
- }
- if( $this->value ){
- $this->value = $visitor->visitObj( $this->value );
- }
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- $value = $this->value;
- $rules = $this->rules;
- $output->add( $this->name, $this->currentFileInfo, $this->index );
- if( $this->value ){
- $output->add(' ');
- $this->value->genCSS($output);
- }
- if( $this->rules ){
- Less_Tree::outputRuleset( $output, array($this->rules));
- } else {
- $output->add(';');
- }
- }
- public function compile($env){
- $value = $this->value;
- $rules = $this->rules;
- if( $value ){
- $value = $value->compile($env);
- }
- if( $rules ){
- $rules = $rules->compile($env);
- $rules->root = true;
- }
- return new Less_Tree_Directive( $this->name, $value, $rules, $this->index, $this->currentFileInfo, $this->debugInfo );
- }
- public function variable($name){
- if( $this->rules ){
- return $this->rules->variable($name);
- }
- }
- public function find($selector){
- if( $this->rules ){
- return $this->rules->find($selector, $this);
- }
- }
- //rulesets: function () { if (this.rules) return tree.Ruleset.prototype.rulesets.apply(this.rules); },
- public function markReferenced(){
- $this->isReferenced = true;
- if( $this->rules ){
- Less_Tree::ReferencedArray($this->rules->rules);
- }
- }
- }
-
- /**
- * Element
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Element extends Less_Tree{
- public $combinator = '';
- public $value = '';
- public $index;
- public $currentFileInfo;
- public $type = 'Element';
- public $value_is_object = false;
- public function __construct($combinator, $value, $index = null, $currentFileInfo = null ){
- $this->value = $value;
- $this->value_is_object = is_object($value);
- if( $combinator ){
- $this->combinator = $combinator;
- }
- $this->index = $index;
- $this->currentFileInfo = $currentFileInfo;
- }
- public function accept( $visitor ){
- if( $this->value_is_object ){ //object or string
- $this->value = $visitor->visitObj( $this->value );
- }
- }
- public function compile($env){
- if( Less_Environment::$mixin_stack ){
- return new Less_Tree_Element($this->combinator, ($this->value_is_object ? $this->value->compile($env) : $this->value), $this->index, $this->currentFileInfo );
- }
- if( $this->value_is_object ){
- $this->value = $this->value->compile($env);
- }
- return $this;
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- $output->add( $this->toCSS(), $this->currentFileInfo, $this->index );
- }
- public function toCSS(){
- if( $this->value_is_object ){
- $value = $this->value->toCSS();
- }else{
- $value = $this->value;
- }
- if( $value === '' && $this->combinator && $this->combinator === '&' ){
- return '';
- }
- return Less_Environment::$_outputMap[$this->combinator] . $value;
- }
- }
-
- /**
- * Expression
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Expression extends Less_Tree{
- public $value = array();
- public $parens = false;
- public $parensInOp = false;
- public $type = 'Expression';
- public function __construct( $value, $parens = null ){
- $this->value = $value;
- $this->parens = $parens;
- }
- public function accept( $visitor ){
- $this->value = $visitor->visitArray( $this->value );
- }
- public function compile($env) {
- $doubleParen = false;
- if( $this->parens && !$this->parensInOp ){
- Less_Environment::$parensStack++;
- }
- $returnValue = null;
- if( $this->value ){
- $count = count($this->value);
- if( $count > 1 ){
- $ret = array();
- foreach($this->value as $e){
- $ret[] = $e->compile($env);
- }
- $returnValue = new Less_Tree_Expression($ret);
- }else{
- if( ($this->value[0] instanceof Less_Tree_Expression) && $this->value[0]->parens && !$this->value[0]->parensInOp ){
- $doubleParen = true;
- }
- $returnValue = $this->value[0]->compile($env);
- }
- } else {
- $returnValue = $this;
- }
- if( $this->parens ){
- if( !$this->parensInOp ){
- Less_Environment::$parensStack--;
- }elseif( !Less_Environment::isMathOn() && !$doubleParen ){
- $returnValue = new Less_Tree_Paren($returnValue);
- }
- }
- return $returnValue;
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- $val_len = count($this->value);
- for( $i = 0; $i < $val_len; $i++ ){
- $this->value[$i]->genCSS( $output );
- if( $i + 1 < $val_len ){
- $output->add( ' ' );
- }
- }
- }
- public function throwAwayComments() {
- if( is_array($this->value) ){
- $new_value = array();
- foreach($this->value as $v){
- if( $v instanceof Less_Tree_Comment ){
- continue;
- }
- $new_value[] = $v;
- }
- $this->value = $new_value;
- }
- }
- }
-
- /**
- * Extend
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Extend extends Less_Tree{
- public $selector;
- public $option;
- public $index;
- public $selfSelectors = array();
- public $allowBefore;
- public $allowAfter;
- public $firstExtendOnThisSelectorPath;
- public $type = 'Extend';
- public $ruleset;
- public $object_id;
- public $parent_ids = array();
- /**
- * @param integer $index
- */
- public function __construct($selector, $option, $index){
- static $i = 0;
- $this->selector = $selector;
- $this->option = $option;
- $this->index = $index;
- switch($option){
- case "all":
- $this->allowBefore = true;
- $this->allowAfter = true;
- break;
- default:
- $this->allowBefore = false;
- $this->allowAfter = false;
- break;
- }
- $this->object_id = $i++;
- $this->parent_ids = array($this->object_id);
- }
- public function accept( $visitor ){
- $this->selector = $visitor->visitObj( $this->selector );
- }
- public function compile( $env ){
- Less_Parser::$has_extends = true;
- $this->selector = $this->selector->compile($env);
- return $this;
- //return new Less_Tree_Extend( $this->selector->compile($env), $this->option, $this->index);
- }
- public function findSelfSelectors( $selectors ){
- $selfElements = array();
- for( $i = 0, $selectors_len = count($selectors); $i < $selectors_len; $i++ ){
- $selectorElements = $selectors[$i]->elements;
- // duplicate the logic in genCSS function inside the selector node.
- // future TODO - move both logics into the selector joiner visitor
- if( $i && $selectorElements && $selectorElements[0]->combinator === "") {
- $selectorElements[0]->combinator = ' ';
- }
- $selfElements = array_merge( $selfElements, $selectors[$i]->elements );
- }
- $this->selfSelectors = array(new Less_Tree_Selector($selfElements));
- }
- }
- /**
- * CSS @import node
- *
- * The general strategy here is that we don't want to wait
- * for the parsing to be completed, before we start importing
- * the file. That's because in the context of a browser,
- * most of the time will be spent waiting for the server to respond.
- *
- * On creation, we push the import path to our import queue, though
- * `import,push`, we also pass it a callback, which it'll call once
- * the file has been fetched, and parsed.
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Import extends Less_Tree{
- public $options;
- public $index;
- public $path;
- public $features;
- public $currentFileInfo;
- public $css;
- public $skip;
- public $root;
- public $type = 'Import';
- public function __construct($path, $features, $options, $index, $currentFileInfo = null ){
- $this->options = $options;
- $this->index = $index;
- $this->path = $path;
- $this->features = $features;
- $this->currentFileInfo = $currentFileInfo;
- if( is_array($options) ){
- $this->options += array('inline'=>false);
- if( isset($this->options['less']) || $this->options['inline'] ){
- $this->css = !isset($this->options['less']) || !$this->options['less'] || $this->options['inline'];
- } else {
- $pathValue = $this->getPath();
- if( $pathValue && preg_match('/css([\?;].*)?$/',$pathValue) ){
- $this->css = true;
- }
- }
- }
- }
- //
- // The actual import node doesn't return anything, when converted to CSS.
- // The reason is that it's used at the evaluation stage, so that the rules
- // it imports can be treated like any other rules.
- //
- // In `eval`, we make sure all Import nodes get evaluated, recursively, so
- // we end up with a flat structure, which can easily be imported in the parent
- // ruleset.
- //
- public function accept($visitor){
- if( $this->features ){
- $this->features = $visitor->visitObj($this->features);
- }
- $this->path = $visitor->visitObj($this->path);
- if( !$this->options['inline'] && $this->root ){
- $this->root = $visitor->visit($this->root);
- }
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- if( $this->css ){
- $output->add( '@import ', $this->currentFileInfo, $this->index );
- $this->path->genCSS( $output );
- if( $this->features ){
- $output->add( ' ' );
- $this->features->genCSS( $output );
- }
- $output->add( ';' );
- }
- }
- public function toCSS(){
- $features = $this->features ? ' ' . $this->features->toCSS() : '';
- if ($this->css) {
- return "@import " . $this->path->toCSS() . $features . ";\n";
- } else {
- return "";
- }
- }
- /**
- * @return string
- */
- public function getPath(){
- if ($this->path instanceof Less_Tree_Quoted) {
- $path = $this->path->value;
- $path = ( isset($this->css) || preg_match('/(\.[a-z]*$)|([\?;].*)$/',$path)) ? $path : $path . '.less';
- } else if ($this->path instanceof Less_Tree_URL) {
- $path = $this->path->value->value;
- }else{
- return null;
- }
- //remove query string and fragment
- return preg_replace('/[\?#][^\?]*$/','',$path);
- }
- public function compileForImport( $env ){
- return new Less_Tree_Import( $this->path->compile($env), $this->features, $this->options, $this->index, $this->currentFileInfo);
- }
- public function compilePath($env) {
- $path = $this->path->compile($env);
- $rootpath = '';
- if( $this->currentFileInfo && $this->currentFileInfo['rootpath'] ){
- $rootpath = $this->currentFileInfo['rootpath'];
- }
- if( !($path instanceof Less_Tree_URL) ){
- if( $rootpath ){
- $pathValue = $path->value;
- // Add the base path if the import is relative
- if( $pathValue && Less_Environment::isPathRelative($pathValue) ){
- $path->value = $this->currentFileInfo['uri_root'].$pathValue;
- }
- }
- $path->value = Less_Environment::normalizePath($path->value);
- }
- return $path;
- }
- public function compile( $env ){
- $evald = $this->compileForImport($env);
- //get path & uri
- $path_and_uri = null;
- if( is_callable(Less_Parser::$options['import_callback']) ){
- $path_and_uri = call_user_func(Less_Parser::$options['import_callback'],$evald);
- }
- if( !$path_and_uri ){
- $path_and_uri = $evald->PathAndUri();
- }
- if( $path_and_uri ){
- list($full_path, $uri) = $path_and_uri;
- }else{
- $full_path = $uri = $evald->getPath();
- }
- //import once
- if( $evald->skip( $full_path, $env) ){
- return array();
- }
- if( $this->options['inline'] ){
- //todo needs to reference css file not import
- //$contents = new Less_Tree_Anonymous($this->root, 0, array('filename'=>$this->importedFilename), true );
- Less_Parser::AddParsedFile($full_path);
- $contents = new Less_Tree_Anonymous( file_get_contents($full_path), 0, array(), true );
- if( $this->features ){
- return new Less_Tree_Media( array($contents), $this->features->value );
- }
- return array( $contents );
- }
- // css ?
- if( $evald->css ){
- $features = ( $evald->features ? $evald->features->compile($env) : null );
- return new Less_Tree_Import( $this->compilePath( $env), $features, $this->options, $this->index);
- }
- return $this->ParseImport( $full_path, $uri, $env );
- }
- /**
- * Using the import directories, get the full absolute path and uri of the import
- *
- * @param Less_Tree_Import $evald
- */
- public function PathAndUri(){
- $evald_path = $this->getPath();
- if( $evald_path ){
- $import_dirs = array();
- if( Less_Environment::isPathRelative($evald_path) ){
- //if the path is relative, the file should be in the current directory
- $import_dirs[ $this->currentFileInfo['currentDirectory'] ] = $this->currentFileInfo['uri_root'];
- }else{
- //otherwise, the file should be relative to the server root
- $import_dirs[ $this->currentFileInfo['entryPath'] ] = $this->currentFileInfo['entryUri'];
- //if the user supplied entryPath isn't the actual root
- $import_dirs[ $_SERVER['DOCUMENT_ROOT'] ] = '';
- }
- // always look in user supplied import directories
- $import_dirs = array_merge( $import_dirs, Less_Parser::$options['import_dirs'] );
- foreach( $import_dirs as $rootpath => $rooturi){
- if( is_callable($rooturi) ){
- list($path, $uri) = call_user_func($rooturi, $evald_path);
- if( is_string($path) ){
- $full_path = $path;
- return array( $full_path, $uri );
- }
- }elseif( !empty($rootpath) ){
- $path = rtrim($rootpath,'/\\').'/'.ltrim($evald_path,'/\\');
- if( file_exists($path) ){
- $full_path = Less_Environment::normalizePath($path);
- $uri = Less_Environment::normalizePath(dirname($rooturi.$evald_path));
- return array( $full_path, $uri );
- } elseif( file_exists($path.'.less') ){
- $full_path = Less_Environment::normalizePath($path.'.less');
- $uri = Less_Environment::normalizePath(dirname($rooturi.$evald_path.'.less'));
- return array( $full_path, $uri );
- }
- }
- }
- }
- }
- /**
- * Parse the import url and return the rules
- *
- * @return Less_Tree_Media|array
- */
- public function ParseImport( $full_path, $uri, $env ){
- $import_env = clone $env;
- if( (isset($this->options['reference']) && $this->options['reference']) || isset($this->currentFileInfo['reference']) ){
- $import_env->currentFileInfo['reference'] = true;
- }
- if( (isset($this->options['multiple']) && $this->options['multiple']) ){
- $import_env->importMultiple = true;
- }
- $parser = new Less_Parser($import_env);
- $root = $parser->parseFile($full_path, $uri, true);
- $ruleset = new Less_Tree_Ruleset(array(), $root->rules );
- $ruleset->evalImports($import_env);
- return $this->features ? new Less_Tree_Media($ruleset->rules, $this->features->value) : $ruleset->rules;
- }
- /**
- * Should the import be skipped?
- *
- * @return boolean|null
- */
- private function Skip($path, $env){
- $path = Less_Parser::winPath(realpath($path));
- if( $path && Less_Parser::FileParsed($path) ){
- if( isset($this->currentFileInfo['reference']) ){
- return true;
- }
- return !isset($this->options['multiple']) && !$env->importMultiple;
- }
- }
- }
-
- /**
- * Javascript
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Javascript extends Less_Tree{
- public $type = 'Javascript';
- public $escaped;
- public $expression;
- public $index;
- /**
- * @param boolean $index
- * @param boolean $escaped
- */
- public function __construct($string, $index, $escaped){
- $this->escaped = $escaped;
- $this->expression = $string;
- $this->index = $index;
- }
- public function compile(){
- return new Less_Tree_Anonymous('/* Sorry, can not do JavaScript evaluation in PHP... :( */');
- }
- }
-
- /**
- * Keyword
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Keyword extends Less_Tree{
- public $value;
- public $type = 'Keyword';
- /**
- * @param string $value
- */
- public function __construct($value){
- $this->value = $value;
- }
- public function compile(){
- return $this;
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- if( $this->value === '%') {
- throw new Less_Exception_Compiler("Invalid % without number");
- }
- $output->add( $this->value );
- }
- public function compare($other) {
- if ($other instanceof Less_Tree_Keyword) {
- return $other->value === $this->value ? 0 : 1;
- } else {
- return -1;
- }
- }
- }
-
- /**
- * Media
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Media extends Less_Tree{
- public $features;
- public $rules;
- public $index;
- public $currentFileInfo;
- public $isReferenced;
- public $type = 'Media';
- public function __construct($value = array(), $features = array(), $index = null, $currentFileInfo = null ){
- $this->index = $index;
- $this->currentFileInfo = $currentFileInfo;
- $selectors = $this->emptySelectors();
- $this->features = new Less_Tree_Value($features);
- $this->rules = array(new Less_Tree_Ruleset($selectors, $value));
- $this->rules[0]->allowImports = true;
- }
- public function accept( $visitor ){
- $this->features = $visitor->visitObj($this->features);
- $this->rules = $visitor->visitArray($this->rules);
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- $output->add( '@media ', $this->currentFileInfo, $this->index );
- $this->features->genCSS( $output );
- Less_Tree::outputRuleset( $output, $this->rules);
- }
- public function compile($env) {
- $media = new Less_Tree_Media(array(), array(), $this->index, $this->currentFileInfo );
- $strictMathBypass = false;
- if( Less_Parser::$options['strictMath'] === false) {
- $strictMathBypass = true;
- Less_Parser::$options['strictMath'] = true;
- }
- $media->features = $this->features->compile($env);
- if( $strictMathBypass ){
- Less_Parser::$options['strictMath'] = false;
- }
- $env->mediaPath[] = $media;
- $env->mediaBlocks[] = $media;
- array_unshift($env->frames, $this->rules[0]);
- $media->rules = array($this->rules[0]->compile($env));
- array_shift($env->frames);
- array_pop($env->mediaPath);
- return !$env->mediaPath ? $media->compileTop($env) : $media->compileNested($env);
- }
- public function variable($name) {
- return $this->rules[0]->variable($name);
- }
- public function find($selector) {
- return $this->rules[0]->find($selector, $this);
- }
- public function emptySelectors(){
- $el = new Less_Tree_Element('','&', $this->index, $this->currentFileInfo );
- $sels = array( new Less_Tree_Selector(array($el), array(), null, $this->index, $this->currentFileInfo) );
- $sels[0]->mediaEmpty = true;
- return $sels;
- }
- public function markReferenced(){
- $this->rules[0]->markReferenced();
- $this->isReferenced = true;
- Less_Tree::ReferencedArray($this->rules[0]->rules);
- }
- // evaltop
- public function compileTop($env) {
- $result = $this;
- if (count($env->mediaBlocks) > 1) {
- $selectors = $this->emptySelectors();
- $result = new Less_Tree_Ruleset($selectors, $env->mediaBlocks);
- $result->multiMedia = true;
- }
- $env->mediaBlocks = array();
- $env->mediaPath = array();
- return $result;
- }
- public function compileNested($env) {
- $path = array_merge($env->mediaPath, array($this));
- // Extract the media-query conditions separated with `,` (OR).
- foreach ($path as $key => $p) {
- $value = $p->features instanceof Less_Tree_Value ? $p->features->value : $p->features;
- $path[$key] = is_array($value) ? $value : array($value);
- }
- // Trace all permutations to generate the resulting media-query.
- //
- // (a, b and c) with nested (d, e) ->
- // a and d
- // a and e
- // b and c and d
- // b and c and e
- $permuted = $this->permute($path);
- $expressions = array();
- foreach($permuted as $path){
- for( $i=0, $len=count($path); $i < $len; $i++){
- $path[$i] = Less_Parser::is_method($path[$i], 'toCSS') ? $path[$i] : new Less_Tree_Anonymous($path[$i]);
- }
- for( $i = count($path) - 1; $i > 0; $i-- ){
- array_splice($path, $i, 0, array(new Less_Tree_Anonymous('and')));
- }
- $expressions[] = new Less_Tree_Expression($path);
- }
- $this->features = new Less_Tree_Value($expressions);
- // Fake a tree-node that doesn't output anything.
- return new Less_Tree_Ruleset(array(), array());
- }
- public function permute($arr) {
- if (!$arr)
- return array();
- if (count($arr) == 1)
- return $arr[0];
- $result = array();
- $rest = $this->permute(array_slice($arr, 1));
- foreach ($rest as $r) {
- foreach ($arr[0] as $a) {
- $result[] = array_merge(
- is_array($a) ? $a : array($a),
- is_array($r) ? $r : array($r)
- );
- }
- }
- return $result;
- }
- public function bubbleSelectors($selectors) {
- if( !$selectors) return;
- $this->rules = array(new Less_Tree_Ruleset( $selectors, array($this->rules[0])));
- }
- }
-
- /**
- * A simple css name-value pair
- * ex: width:100px;
- *
- * In bootstrap, there are about 600-1,000 simple name-value pairs (depending on how forgiving the match is) -vs- 6,020 dynamic rules (Less_Tree_Rule)
- * Using the name-value object can speed up bootstrap compilation slightly, but it breaks color keyword interpretation: color:red -> color:#FF0000;
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_NameValue extends Less_Tree{
- public $name;
- public $value;
- public $index;
- public $currentFileInfo;
- public $type = 'NameValue';
- public function __construct($name, $value = null, $index = null, $currentFileInfo = null ){
- $this->name = $name;
- $this->value = $value;
- $this->index = $index;
- $this->currentFileInfo = $currentFileInfo;
- }
- public function genCSS( $output ){
- $output->add(
- $this->name
- . Less_Environment::$_outputMap[': ']
- . $this->value
- . (((Less_Environment::$lastRule && Less_Parser::$options['compress'])) ? "" : ";")
- , $this->currentFileInfo, $this->index);
- }
- public function compile ($env){
- return $this;
- }
- }
-
- /**
- * Negative
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Negative extends Less_Tree{
- public $value;
- public $type = 'Negative';
- public function __construct($node){
- $this->value = $node;
- }
- //function accept($visitor) {
- // $this->value = $visitor->visit($this->value);
- //}
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- $output->add( '-' );
- $this->value->genCSS( $output );
- }
- public function compile($env) {
- if( Less_Environment::isMathOn() ){
- $ret = new Less_Tree_Operation('*', array( new Less_Tree_Dimension(-1), $this->value ) );
- return $ret->compile($env);
- }
- return new Less_Tree_Negative( $this->value->compile($env) );
- }
- }
- /**
- * Operation
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Operation extends Less_Tree{
- public $op;
- public $operands;
- public $isSpaced;
- public $type = 'Operation';
- /**
- * @param string $op
- */
- public function __construct($op, $operands, $isSpaced = false){
- $this->op = trim($op);
- $this->operands = $operands;
- $this->isSpaced = $isSpaced;
- }
- public function accept($visitor) {
- $this->operands = $visitor->visitArray($this->operands);
- }
- public function compile($env){
- $a = $this->operands[0]->compile($env);
- $b = $this->operands[1]->compile($env);
- if( Less_Environment::isMathOn() ){
- if( $a instanceof Less_Tree_Dimension && $b instanceof Less_Tree_Color ){
- $a = $a->toColor();
- }elseif( $b instanceof Less_Tree_Dimension && $a instanceof Less_Tree_Color ){
- $b = $b->toColor();
- }
- if( !method_exists($a,'operate') ){
- throw new Less_Exception_Compiler("Operation on an invalid type");
- }
- return $a->operate( $this->op, $b);
- }
- return new Less_Tree_Operation($this->op, array($a, $b), $this->isSpaced );
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- $this->operands[0]->genCSS( $output );
- if( $this->isSpaced ){
- $output->add( " " );
- }
- $output->add( $this->op );
- if( $this->isSpaced ){
- $output->add( ' ' );
- }
- $this->operands[1]->genCSS( $output );
- }
- }
-
- /**
- * Paren
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Paren extends Less_Tree{
- public $value;
- public $type = 'Paren';
- public function __construct($value) {
- $this->value = $value;
- }
- public function accept($visitor){
- $this->value = $visitor->visitObj($this->value);
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- $output->add( '(' );
- $this->value->genCSS( $output );
- $output->add( ')' );
- }
- public function compile($env) {
- return new Less_Tree_Paren($this->value->compile($env));
- }
- }
-
- /**
- * Quoted
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Quoted extends Less_Tree{
- public $escaped;
- public $value;
- public $quote;
- public $index;
- public $currentFileInfo;
- public $type = 'Quoted';
- /**
- * @param string $str
- */
- public function __construct($str, $content = '', $escaped = false, $index = false, $currentFileInfo = null ){
- $this->escaped = $escaped;
- $this->value = $content;
- if( $str ){
- $this->quote = $str[0];
- }
- $this->index = $index;
- $this->currentFileInfo = $currentFileInfo;
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- if( !$this->escaped ){
- $output->add( $this->quote, $this->currentFileInfo, $this->index );
- }
- $output->add( $this->value );
- if( !$this->escaped ){
- $output->add( $this->quote );
- }
- }
- public function compile($env){
- $value = $this->value;
- if( preg_match_all('/`([^`]+)`/', $this->value, $matches) ){
- foreach($matches as $i => $match){
- $js = new Less_Tree_JavaScript($matches[1], $this->index, true);
- $js = $js->compile()->value;
- $value = str_replace($matches[0][$i], $js, $value);
- }
- }
- if( preg_match_all('/@\{([\w-]+)\}/',$value,$matches) ){
- foreach($matches[1] as $i => $match){
- $v = new Less_Tree_Variable('@' . $match, $this->index, $this->currentFileInfo );
- $v = $v->compile($env);
- $v = ($v instanceof Less_Tree_Quoted) ? $v->value : $v->toCSS();
- $value = str_replace($matches[0][$i], $v, $value);
- }
- }
- return new Less_Tree_Quoted($this->quote . $value . $this->quote, $value, $this->escaped, $this->index, $this->currentFileInfo);
- }
- public function compare($x) {
- if( !Less_Parser::is_method($x, 'toCSS') ){
- return -1;
- }
- $left = $this->toCSS();
- $right = $x->toCSS();
- if ($left === $right) {
- return 0;
- }
- return $left < $right ? -1 : 1;
- }
- }
-
- /**
- * Rule
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Rule extends Less_Tree{
- public $name;
- public $value;
- public $important;
- public $merge;
- public $index;
- public $inline;
- public $variable;
- public $currentFileInfo;
- public $type = 'Rule';
- /**
- * @param string $important
- */
- public function __construct($name, $value = null, $important = null, $merge = null, $index = null, $currentFileInfo = null, $inline = false){
- $this->name = $name;
- $this->value = ($value instanceof Less_Tree_Value || $value instanceof Less_Tree_Ruleset) ? $value : new Less_Tree_Value(array($value));
- $this->important = $important ? ' ' . trim($important) : '';
- $this->merge = $merge;
- $this->index = $index;
- $this->currentFileInfo = $currentFileInfo;
- $this->inline = $inline;
- $this->variable = ( is_string($name) && $name[0] === '@');
- }
- public function accept($visitor) {
- $this->value = $visitor->visitObj( $this->value );
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- $output->add( $this->name . Less_Environment::$_outputMap[': '], $this->currentFileInfo, $this->index);
- try{
- $this->value->genCSS( $output);
- }catch( Less_Exception_Parser $e ){
- $e->index = $this->index;
- $e->currentFile = $this->currentFileInfo;
- throw $e;
- }
- $output->add( $this->important . (($this->inline || (Less_Environment::$lastRule && Less_Parser::$options['compress'])) ? "" : ";"), $this->currentFileInfo, $this->index);
- }
- public function compile ($env){
- $name = $this->name;
- if( is_array($name) ){
- // expand 'primitive' name directly to get
- // things faster (~10% for benchmark.less):
- if( count($name) === 1 && $name[0] instanceof Less_Tree_Keyword ){
- $name = $name[0]->value;
- }else{
- $name = $this->CompileName($env,$name);
- }
- }
- $strictMathBypass = Less_Parser::$options['strictMath'];
- if( $name === "font" && !Less_Parser::$options['strictMath'] ){
- Less_Parser::$options['strictMath'] = true;
- }
- try {
- $evaldValue = $this->value->compile($env);
- if( !$this->variable && $evaldValue->type === "DetachedRuleset") {
- throw new Less_Exception_Compiler("Rulesets cannot be evaluated on a property.", null, $this->index, $this->currentFileInfo);
- }
- if( Less_Environment::$mixin_stack ){
- $return = new Less_Tree_Rule($name, $evaldValue, $this->important, $this->merge, $this->index, $this->currentFileInfo, $this->inline);
- }else{
- $this->name = $name;
- $this->value = $evaldValue;
- $return = $this;
- }
- }catch( Less_Exception_Parser $e ){
- if( !is_numeric($e->index) ){
- $e->index = $this->index;
- $e->currentFile = $this->currentFileInfo;
- }
- throw $e;
- }
- Less_Parser::$options['strictMath'] = $strictMathBypass;
- return $return;
- }
- public function CompileName( $env, $name ){
- $output = new Less_Output();
- foreach($name as $n){
- $n->compile($env)->genCSS($output);
- }
- return $output->toString();
- }
- public function makeImportant(){
- return new Less_Tree_Rule($this->name, $this->value, '!important', $this->merge, $this->index, $this->currentFileInfo, $this->inline);
- }
- }
-
- /**
- * Ruleset
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Ruleset extends Less_Tree{
- protected $lookups;
- public $_variables;
- public $_rulesets;
- public $strictImports;
- public $selectors;
- public $rules;
- public $root;
- public $allowImports;
- public $paths;
- public $firstRoot;
- public $type = 'Ruleset';
- public $multiMedia;
- public $allExtends;
- public $ruleset_id;
- public $originalRuleset;
- public $first_oelements;
- public function SetRulesetIndex(){
- $this->ruleset_id = Less_Parser::$next_id++;
- $this->originalRuleset = $this->ruleset_id;
- if( $this->selectors ){
- foreach($this->selectors as $sel){
- if( $sel->_oelements ){
- $this->first_oelements[$sel->_oelements[0]] = true;
- }
- }
- }
- }
- public function __construct($selectors, $rules, $strictImports = null){
- $this->selectors = $selectors;
- $this->rules = $rules;
- $this->lookups = array();
- $this->strictImports = $strictImports;
- $this->SetRulesetIndex();
- }
- public function accept( $visitor ){
- if( $this->paths ){
- $paths_len = count($this->paths);
- for($i = 0,$paths_len; $i < $paths_len; $i++ ){
- $this->paths[$i] = $visitor->visitArray($this->paths[$i]);
- }
- }elseif( $this->selectors ){
- $this->selectors = $visitor->visitArray($this->selectors);
- }
- if( $this->rules ){
- $this->rules = $visitor->visitArray($this->rules);
- }
- }
- public function compile($env){
- $ruleset = $this->PrepareRuleset($env);
- // Store the frames around mixin definitions,
- // so they can be evaluated like closures when the time comes.
- $rsRuleCnt = count($ruleset->rules);
- for( $i = 0; $i < $rsRuleCnt; $i++ ){
- if( $ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset ){
- $ruleset->rules[$i] = $ruleset->rules[$i]->compile($env);
- }
- }
- $mediaBlockCount = 0;
- if( $env instanceof Less_Environment ){
- $mediaBlockCount = count($env->mediaBlocks);
- }
- // Evaluate mixin calls.
- $this->EvalMixinCalls( $ruleset, $env, $rsRuleCnt );
- // Evaluate everything else
- for( $i=0; $i<$rsRuleCnt; $i++ ){
- if(! ($ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset) ){
- $ruleset->rules[$i] = $ruleset->rules[$i]->compile($env);
- }
- }
- // Evaluate everything else
- for( $i=0; $i<$rsRuleCnt; $i++ ){
- $rule = $ruleset->rules[$i];
- // for rulesets, check if it is a css guard and can be removed
- if( $rule instanceof Less_Tree_Ruleset && $rule->selectors && count($rule->selectors) === 1 ){
- // check if it can be folded in (e.g. & where)
- if( $rule->selectors[0]->isJustParentSelector() ){
- array_splice($ruleset->rules,$i--,1);
- $rsRuleCnt--;
- for($j = 0; $j < count($rule->rules); $j++ ){
- $subRule = $rule->rules[$j];
- if( !($subRule instanceof Less_Tree_Rule) || !$subRule->variable ){
- array_splice($ruleset->rules, ++$i, 0, array($subRule));
- $rsRuleCnt++;
- }
- }
- }
- }
- }
- // Pop the stack
- $env->shiftFrame();
- if ($mediaBlockCount) {
- $len = count($env->mediaBlocks);
- for($i = $mediaBlockCount; $i < $len; $i++ ){
- $env->mediaBlocks[$i]->bubbleSelectors($ruleset->selectors);
- }
- }
- return $ruleset;
- }
- /**
- * Compile Less_Tree_Mixin_Call objects
- *
- * @param Less_Tree_Ruleset $ruleset
- * @param integer $rsRuleCnt
- */
- private function EvalMixinCalls( $ruleset, $env, &$rsRuleCnt ){
- for($i=0; $i < $rsRuleCnt; $i++){
- $rule = $ruleset->rules[$i];
- if( $rule instanceof Less_Tree_Mixin_Call ){
- $rule = $rule->compile($env);
- $temp = array();
- foreach($rule as $r){
- if( ($r instanceof Less_Tree_Rule) && $r->variable ){
- // do not pollute the scope if the variable is
- // already there. consider returning false here
- // but we need a way to "return" variable from mixins
- if( !$ruleset->variable($r->name) ){
- $temp[] = $r;
- }
- }else{
- $temp[] = $r;
- }
- }
- $temp_count = count($temp)-1;
- array_splice($ruleset->rules, $i, 1, $temp);
- $rsRuleCnt += $temp_count;
- $i += $temp_count;
- $ruleset->resetCache();
- }elseif( $rule instanceof Less_Tree_RulesetCall ){
- $rule = $rule->compile($env);
- $rules = array();
- foreach($rule->rules as $r){
- if( ($r instanceof Less_Tree_Rule) && $r->variable ){
- continue;
- }
- $rules[] = $r;
- }
- array_splice($ruleset->rules, $i, 1, $rules);
- $temp_count = count($rules);
- $rsRuleCnt += $temp_count - 1;
- $i += $temp_count-1;
- $ruleset->resetCache();
- }
- }
- }
- /**
- * Compile the selectors and create a new ruleset object for the compile() method
- *
- */
- private function PrepareRuleset($env){
- $hasOnePassingSelector = false;
- $selectors = array();
- if( $this->selectors ){
- Less_Tree_DefaultFunc::error("it is currently only allowed in parametric mixin guards,");
- foreach($this->selectors as $s){
- $selector = $s->compile($env);
- $selectors[] = $selector;
- if( $selector->evaldCondition ){
- $hasOnePassingSelector = true;
- }
- }
- Less_Tree_DefaultFunc::reset();
- } else {
- $hasOnePassingSelector = true;
- }
- if( $this->rules && $hasOnePassingSelector ){
- $rules = $this->rules;
- }else{
- $rules = array();
- }
- $ruleset = new Less_Tree_Ruleset($selectors, $rules, $this->strictImports);
- $ruleset->originalRuleset = $this->ruleset_id;
- $ruleset->root = $this->root;
- $ruleset->firstRoot = $this->firstRoot;
- $ruleset->allowImports = $this->allowImports;
- // push the current ruleset to the frames stack
- $env->unshiftFrame($ruleset);
- // Evaluate imports
- if( $ruleset->root || $ruleset->allowImports || !$ruleset->strictImports ){
- $ruleset->evalImports($env);
- }
- return $ruleset;
- }
- function evalImports($env) {
- $rules_len = count($this->rules);
- for($i=0; $i < $rules_len; $i++){
- $rule = $this->rules[$i];
- if( $rule instanceof Less_Tree_Import ){
- $rules = $rule->compile($env);
- if( is_array($rules) ){
- array_splice($this->rules, $i, 1, $rules);
- $temp_count = count($rules)-1;
- $i += $temp_count;
- $rules_len += $temp_count;
- }else{
- array_splice($this->rules, $i, 1, array($rules));
- }
- $this->resetCache();
- }
- }
- }
- function makeImportant(){
- $important_rules = array();
- foreach($this->rules as $rule){
- if( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_Ruleset ){
- $important_rules[] = $rule->makeImportant();
- }else{
- $important_rules[] = $rule;
- }
- }
- return new Less_Tree_Ruleset($this->selectors, $important_rules, $this->strictImports );
- }
- public function matchArgs($args){
- return !$args;
- }
- // lets you call a css selector with a guard
- public function matchCondition( $args, $env ){
- $lastSelector = end($this->selectors);
- if( !$lastSelector->evaldCondition ){
- return false;
- }
- if( $lastSelector->condition && !$lastSelector->condition->compile( $env->copyEvalEnv( $env->frames ) ) ){
- return false;
- }
- return true;
- }
- function resetCache(){
- $this->_rulesets = null;
- $this->_variables = null;
- $this->lookups = array();
- }
- public function variables(){
- $this->_variables = array();
- foreach( $this->rules as $r){
- if ($r instanceof Less_Tree_Rule && $r->variable === true) {
- $this->_variables[$r->name] = $r;
- }
- }
- }
- public function variable($name){
- if( is_null($this->_variables) ){
- $this->variables();
- }
- return isset($this->_variables[$name]) ? $this->_variables[$name] : null;
- }
- public function find( $selector, $self = null ){
- $key = implode(' ',$selector->_oelements);
- if( !isset($this->lookups[$key]) ){
- if( !$self ){
- $self = $this->ruleset_id;
- }
- $this->lookups[$key] = array();
- $first_oelement = $selector->_oelements[0];
- foreach($this->rules as $rule){
- if( $rule instanceof Less_Tree_Ruleset && $rule->ruleset_id != $self ){
- if( isset($rule->first_oelements[$first_oelement]) ){
- foreach( $rule->selectors as $ruleSelector ){
- $match = $selector->match($ruleSelector);
- if( $match ){
- if( $selector->elements_len > $match ){
- $this->lookups[$key] = array_merge($this->lookups[$key], $rule->find( new Less_Tree_Selector(array_slice($selector->elements, $match)), $self));
- } else {
- $this->lookups[$key][] = $rule;
- }
- break;
- }
- }
- }
- }
- }
- }
- return $this->lookups[$key];
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- if( !$this->root ){
- Less_Environment::$tabLevel++;
- }
- $tabRuleStr = $tabSetStr = '';
- if( !Less_Parser::$options['compress'] ){
- if( Less_Environment::$tabLevel ){
- $tabRuleStr = "\n".str_repeat( ' ' , Less_Environment::$tabLevel );
- $tabSetStr = "\n".str_repeat( ' ' , Less_Environment::$tabLevel-1 );
- }else{
- $tabSetStr = $tabRuleStr = "\n";
- }
- }
- $ruleNodes = array();
- $rulesetNodes = array();
- foreach($this->rules as $rule){
- $class = get_class($rule);
- if( ($class === 'Less_Tree_Media') || ($class === 'Less_Tree_Directive') || ($this->root && $class === 'Less_Tree_Comment') || ($class === 'Less_Tree_Ruleset' && $rule->rules) ){
- $rulesetNodes[] = $rule;
- }else{
- $ruleNodes[] = $rule;
- }
- }
- // If this is the root node, we don't render
- // a selector, or {}.
- if( !$this->root ){
- /*
- debugInfo = tree.debugInfo(env, this, tabSetStr);
- if (debugInfo) {
- output.add(debugInfo);
- output.add(tabSetStr);
- }
- */
- $paths_len = count($this->paths);
- for( $i = 0; $i < $paths_len; $i++ ){
- $path = $this->paths[$i];
- $firstSelector = true;
- foreach($path as $p){
- $p->genCSS( $output, $firstSelector );
- $firstSelector = false;
- }
- if( $i + 1 < $paths_len ){
- $output->add( ',' . $tabSetStr );
- }
- }
- $output->add( (Less_Parser::$options['compress'] ? '{' : " {") . $tabRuleStr );
- }
- // Compile rules and rulesets
- $ruleNodes_len = count($ruleNodes);
- $rulesetNodes_len = count($rulesetNodes);
- for( $i = 0; $i < $ruleNodes_len; $i++ ){
- $rule = $ruleNodes[$i];
- // @page{ directive ends up with root elements inside it, a mix of rules and rulesets
- // In this instance we do not know whether it is the last property
- if( $i + 1 === $ruleNodes_len && (!$this->root || $rulesetNodes_len === 0 || $this->firstRoot ) ){
- Less_Environment::$lastRule = true;
- }
- $rule->genCSS( $output );
- if( !Less_Environment::$lastRule ){
- $output->add( $tabRuleStr );
- }else{
- Less_Environment::$lastRule = false;
- }
- }
- if( !$this->root ){
- $output->add( $tabSetStr . '}' );
- Less_Environment::$tabLevel--;
- }
- $firstRuleset = true;
- $space = ($this->root ? $tabRuleStr : $tabSetStr);
- for( $i = 0; $i < $rulesetNodes_len; $i++ ){
- if( $ruleNodes_len && $firstRuleset ){
- $output->add( $space );
- }elseif( !$firstRuleset ){
- $output->add( $space );
- }
- $firstRuleset = false;
- $rulesetNodes[$i]->genCSS( $output);
- }
- if( !Less_Parser::$options['compress'] && $this->firstRoot ){
- $output->add( "\n" );
- }
- }
- function markReferenced(){
- if( !$this->selectors ){
- return;
- }
- foreach($this->selectors as $selector){
- $selector->markReferenced();
- }
- }
- public function joinSelectors( $context, $selectors ){
- $paths = array();
- if( is_array($selectors) ){
- foreach($selectors as $selector) {
- $this->joinSelector( $paths, $context, $selector);
- }
- }
- return $paths;
- }
- public function joinSelector( &$paths, $context, $selector){
- $hasParentSelector = false;
- foreach($selector->elements as $el) {
- if( $el->value === '&') {
- $hasParentSelector = true;
- }
- }
- if( !$hasParentSelector ){
- if( $context ){
- foreach($context as $context_el){
- $paths[] = array_merge($context_el, array($selector) );
- }
- }else {
- $paths[] = array($selector);
- }
- return;
- }
- // The paths are [[Selector]]
- // The first list is a list of comma seperated selectors
- // The inner list is a list of inheritance seperated selectors
- // e.g.
- // .a, .b {
- // .c {
- // }
- // }
- // == [[.a] [.c]] [[.b] [.c]]
- //
- // the elements from the current selector so far
- $currentElements = array();
- // the current list of new selectors to add to the path.
- // We will build it up. We initiate it with one empty selector as we "multiply" the new selectors
- // by the parents
- $newSelectors = array(array());
- foreach( $selector->elements as $el){
- // non parent reference elements just get added
- if( $el->value !== '&' ){
- $currentElements[] = $el;
- } else {
- // the new list of selectors to add
- $selectorsMultiplied = array();
- // merge the current list of non parent selector elements
- // on to the current list of selectors to add
- if( $currentElements ){
- $this->mergeElementsOnToSelectors( $currentElements, $newSelectors);
- }
- // loop through our current selectors
- foreach($newSelectors as $sel){
- // if we don't have any parent paths, the & might be in a mixin so that it can be used
- // whether there are parents or not
- if( !$context ){
- // the combinator used on el should now be applied to the next element instead so that
- // it is not lost
- if( $sel ){
- $sel[0]->elements = array_slice($sel[0]->elements,0);
- $sel[0]->elements[] = new Less_Tree_Element($el->combinator, '', $el->index, $el->currentFileInfo );
- }
- $selectorsMultiplied[] = $sel;
- }else {
- // and the parent selectors
- foreach($context as $parentSel){
- // We need to put the current selectors
- // then join the last selector's elements on to the parents selectors
- // our new selector path
- $newSelectorPath = array();
- // selectors from the parent after the join
- $afterParentJoin = array();
- $newJoinedSelectorEmpty = true;
- //construct the joined selector - if & is the first thing this will be empty,
- // if not newJoinedSelector will be the last set of elements in the selector
- if( $sel ){
- $newSelectorPath = $sel;
- $lastSelector = array_pop($newSelectorPath);
- $newJoinedSelector = $selector->createDerived( array_slice($lastSelector->elements,0) );
- $newJoinedSelectorEmpty = false;
- }
- else {
- $newJoinedSelector = $selector->createDerived(array());
- }
- //put together the parent selectors after the join
- if ( count($parentSel) > 1) {
- $afterParentJoin = array_merge($afterParentJoin, array_slice($parentSel,1) );
- }
- if ( $parentSel ){
- $newJoinedSelectorEmpty = false;
- // join the elements so far with the first part of the parent
- $newJoinedSelector->elements[] = new Less_Tree_Element( $el->combinator, $parentSel[0]->elements[0]->value, $el->index, $el->currentFileInfo);
- $newJoinedSelector->elements = array_merge( $newJoinedSelector->elements, array_slice($parentSel[0]->elements, 1) );
- }
- if (!$newJoinedSelectorEmpty) {
- // now add the joined selector
- $newSelectorPath[] = $newJoinedSelector;
- }
- // and the rest of the parent
- $newSelectorPath = array_merge($newSelectorPath, $afterParentJoin);
- // add that to our new set of selectors
- $selectorsMultiplied[] = $newSelectorPath;
- }
- }
- }
- // our new selectors has been multiplied, so reset the state
- $newSelectors = $selectorsMultiplied;
- $currentElements = array();
- }
- }
- // if we have any elements left over (e.g. .a& .b == .b)
- // add them on to all the current selectors
- if( $currentElements ){
- $this->mergeElementsOnToSelectors($currentElements, $newSelectors);
- }
- foreach( $newSelectors as $new_sel){
- if( $new_sel ){
- $paths[] = $new_sel;
- }
- }
- }
- function mergeElementsOnToSelectors( $elements, &$selectors){
- if( !$selectors ){
- $selectors[] = array( new Less_Tree_Selector($elements) );
- return;
- }
- foreach( $selectors as &$sel){
- // if the previous thing in sel is a parent this needs to join on to it
- if( $sel ){
- $last = count($sel)-1;
- $sel[$last] = $sel[$last]->createDerived( array_merge($sel[$last]->elements, $elements) );
- }else{
- $sel[] = new Less_Tree_Selector( $elements );
- }
- }
- }
- }
-
- /**
- * RulesetCall
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_RulesetCall extends Less_Tree{
- public $variable;
- public $type = "RulesetCall";
- public function __construct($variable){
- $this->variable = $variable;
- }
- public function accept($visitor) {}
- public function compile( $env ){
- $variable = new Less_Tree_Variable($this->variable);
- $detachedRuleset = $variable->compile($env);
- return $detachedRuleset->callEval($env);
- }
- }
-
- /**
- * Selector
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Selector extends Less_Tree{
- public $elements;
- public $condition;
- public $extendList = array();
- public $_css;
- public $index;
- public $evaldCondition = false;
- public $type = 'Selector';
- public $currentFileInfo = array();
- public $isReferenced;
- public $mediaEmpty;
- public $elements_len = 0;
- public $_oelements;
- public $_oelements_len;
- public $cacheable = true;
- /**
- * @param boolean $isReferenced
- */
- public function __construct( $elements, $extendList = array() , $condition = null, $index=null, $currentFileInfo=null, $isReferenced=null ){
- $this->elements = $elements;
- $this->elements_len = count($elements);
- $this->extendList = $extendList;
- $this->condition = $condition;
- if( $currentFileInfo ){
- $this->currentFileInfo = $currentFileInfo;
- }
- $this->isReferenced = $isReferenced;
- if( !$condition ){
- $this->evaldCondition = true;
- }
- $this->CacheElements();
- }
- public function accept($visitor) {
- $this->elements = $visitor->visitArray($this->elements);
- $this->extendList = $visitor->visitArray($this->extendList);
- if( $this->condition ){
- $this->condition = $visitor->visitObj($this->condition);
- }
- if( $visitor instanceof Less_Visitor_extendFinder ){
- $this->CacheElements();
- }
- }
- public function createDerived( $elements, $extendList = null, $evaldCondition = null ){
- $newSelector = new Less_Tree_Selector( $elements, ($extendList ? $extendList : $this->extendList), null, $this->index, $this->currentFileInfo, $this->isReferenced);
- $newSelector->evaldCondition = $evaldCondition ? $evaldCondition : $this->evaldCondition;
- return $newSelector;
- }
- public function match( $other ){
- if( !$other->_oelements || ($this->elements_len < $other->_oelements_len) ){
- return 0;
- }
- for( $i = 0; $i < $other->_oelements_len; $i++ ){
- if( $this->elements[$i]->value !== $other->_oelements[$i]) {
- return 0;
- }
- }
- return $other->_oelements_len; // return number of matched elements
- }
- public function CacheElements(){
- $this->_oelements = array();
- $css = '';
- foreach($this->elements as $v){
- $css .= $v->combinator;
- if( !$v->value_is_object ){
- $css .= $v->value;
- continue;
- }
- if( !property_exists($v->value,'value') || !is_string($v->value->value) ){
- $this->cacheable = false;
- return;
- }
- $css .= $v->value->value;
- }
- $this->_oelements_len = preg_match_all('/[,&#\.\w-](?:[\w-]|(?:\\\\.))*/', $css, $matches);
- if( $this->_oelements_len ){
- $this->_oelements = $matches[0];
- if( $this->_oelements[0] === '&' ){
- array_shift($this->_oelements);
- $this->_oelements_len--;
- }
- }
- }
- public function isJustParentSelector(){
- return !$this->mediaEmpty &&
- count($this->elements) === 1 &&
- $this->elements[0]->value === '&' &&
- ($this->elements[0]->combinator === ' ' || $this->elements[0]->combinator === '');
- }
- public function compile($env) {
- $elements = array();
- foreach($this->elements as $el){
- $elements[] = $el->compile($env);
- }
- $extendList = array();
- foreach($this->extendList as $el){
- $extendList[] = $el->compile($el);
- }
- $evaldCondition = false;
- if( $this->condition ){
- $evaldCondition = $this->condition->compile($env);
- }
- return $this->createDerived( $elements, $extendList, $evaldCondition );
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output, $firstSelector = true ){
- if( !$firstSelector && $this->elements[0]->combinator === "" ){
- $output->add(' ', $this->currentFileInfo, $this->index);
- }
- foreach($this->elements as $element){
- $element->genCSS( $output );
- }
- }
- public function markReferenced(){
- $this->isReferenced = true;
- }
- public function getIsReferenced(){
- return !isset($this->currentFileInfo['reference']) || !$this->currentFileInfo['reference'] || $this->isReferenced;
- }
- public function getIsOutput(){
- return $this->evaldCondition;
- }
- }
-
- /**
- * UnicodeDescriptor
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_UnicodeDescriptor extends Less_Tree{
- public $value;
- public $type = 'UnicodeDescriptor';
- public function __construct($value){
- $this->value = $value;
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- $output->add( $this->value );
- }
- public function compile(){
- return $this;
- }
- }
-
- /**
- * Unit
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Unit extends Less_Tree{
- var $numerator = array();
- var $denominator = array();
- public $backupUnit;
- public $type = 'Unit';
- public function __construct($numerator = array(), $denominator = array(), $backupUnit = null ){
- $this->numerator = $numerator;
- $this->denominator = $denominator;
- $this->backupUnit = $backupUnit;
- }
- public function __clone(){
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- if( $this->numerator ){
- $output->add( $this->numerator[0] );
- }elseif( $this->denominator ){
- $output->add( $this->denominator[0] );
- }elseif( !Less_Parser::$options['strictUnits'] && $this->backupUnit ){
- $output->add( $this->backupUnit );
- return ;
- }
- }
- public function toString(){
- $returnStr = implode('*',$this->numerator);
- foreach($this->denominator as $d){
- $returnStr .= '/'.$d;
- }
- return $returnStr;
- }
- public function __toString(){
- return $this->toString();
- }
- /**
- * @param Less_Tree_Unit $other
- */
- public function compare($other) {
- return $this->is( $other->toString() ) ? 0 : -1;
- }
- public function is($unitString){
- return $this->toString() === $unitString;
- }
- public function isLength(){
- $css = $this->toCSS();
- return !!preg_match('/px|em|%|in|cm|mm|pc|pt|ex/',$css);
- }
- public function isAngle() {
- return isset( Less_Tree_UnitConversions::$angle[$this->toCSS()] );
- }
- public function isEmpty(){
- return !$this->numerator && !$this->denominator;
- }
- public function isSingular() {
- return count($this->numerator) <= 1 && !$this->denominator;
- }
- public function usedUnits(){
- $result = array();
- foreach(Less_Tree_UnitConversions::$groups as $groupName){
- $group = Less_Tree_UnitConversions::${$groupName};
- foreach($this->numerator as $atomicUnit){
- if( isset($group[$atomicUnit]) && !isset($result[$groupName]) ){
- $result[$groupName] = $atomicUnit;
- }
- }
- foreach($this->denominator as $atomicUnit){
- if( isset($group[$atomicUnit]) && !isset($result[$groupName]) ){
- $result[$groupName] = $atomicUnit;
- }
- }
- }
- return $result;
- }
- public function cancel(){
- $counter = array();
- $backup = null;
- foreach($this->numerator as $atomicUnit){
- if( !$backup ){
- $backup = $atomicUnit;
- }
- $counter[$atomicUnit] = ( isset($counter[$atomicUnit]) ? $counter[$atomicUnit] : 0) + 1;
- }
- foreach($this->denominator as $atomicUnit){
- if( !$backup ){
- $backup = $atomicUnit;
- }
- $counter[$atomicUnit] = ( isset($counter[$atomicUnit]) ? $counter[$atomicUnit] : 0) - 1;
- }
- $this->numerator = array();
- $this->denominator = array();
- foreach($counter as $atomicUnit => $count){
- if( $count > 0 ){
- for( $i = 0; $i < $count; $i++ ){
- $this->numerator[] = $atomicUnit;
- }
- }elseif( $count < 0 ){
- for( $i = 0; $i < -$count; $i++ ){
- $this->denominator[] = $atomicUnit;
- }
- }
- }
- if( !$this->numerator && !$this->denominator && $backup ){
- $this->backupUnit = $backup;
- }
- sort($this->numerator);
- sort($this->denominator);
- }
- }
-
- /**
- * UnitConversions
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_UnitConversions{
- public static $groups = array('length','duration','angle');
- public static $length = array(
- 'm'=> 1,
- 'cm'=> 0.01,
- 'mm'=> 0.001,
- 'in'=> 0.0254,
- 'px'=> 0.000264583, // 0.0254 / 96,
- 'pt'=> 0.000352778, // 0.0254 / 72,
- 'pc'=> 0.004233333, // 0.0254 / 72 * 12
- );
- public static $duration = array(
- 's'=> 1,
- 'ms'=> 0.001
- );
- public static $angle = array(
- 'rad' => 0.1591549430919, // 1/(2*M_PI),
- 'deg' => 0.002777778, // 1/360,
- 'grad'=> 0.0025, // 1/400,
- 'turn'=> 1
- );
- }
- /**
- * Url
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Url extends Less_Tree{
- public $attrs;
- public $value;
- public $currentFileInfo;
- public $isEvald;
- public $type = 'Url';
- public function __construct($value, $currentFileInfo = null, $isEvald = null){
- $this->value = $value;
- $this->currentFileInfo = $currentFileInfo;
- $this->isEvald = $isEvald;
- }
- public function accept( $visitor ){
- $this->value = $visitor->visitObj($this->value);
- }
- /**
- * @see Less_Tree::genCSS
- */
- public function genCSS( $output ){
- $output->add( 'url(' );
- $this->value->genCSS( $output );
- $output->add( ')' );
- }
- /**
- * @param Less_Functions $ctx
- */
- public function compile($ctx){
- $val = $this->value->compile($ctx);
- if( !$this->isEvald ){
- // Add the base path if the URL is relative
- if( Less_Parser::$options['relativeUrls']
- && $this->currentFileInfo
- && is_string($val->value)
- && Less_Environment::isPathRelative($val->value)
- ){
- $rootpath = $this->currentFileInfo['uri_root'];
- if ( !$val->quote ){
- $rootpath = preg_replace('/[\(\)\'"\s]/', '\\$1', $rootpath );
- }
- $val->value = $rootpath . $val->value;
- }
- $val->value = Less_Environment::normalizePath( $val->value);
- }
- // Add cache buster if enabled
- if( Less_Parser::$options['urlArgs'] ){
- if( !preg_match('/^\s*data:/',$val->value) ){
- $delimiter = strpos($val->value,'?') === false ? '?' : '&';
- $urlArgs = $delimiter . Less_Parser::$options['urlArgs'];
- $hash_pos = strpos($val->value,'#');
- if( $hash_pos !== false ){
- $val->value = substr_replace($val->value,$urlArgs, $hash_pos, 0);
- } else {
- $val->value .= $urlArgs;
- }
- }
- }
- return new Less_Tree_URL($val, $this->currentFileInfo, true);
- }
- }
-
- /**
- * Value
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Value extends Less_Tree{
- public $type = 'Value';
- public $value;
- public function __construct($value){
- $this->value = $value;
- }
- public function accept($visitor) {
- $this->value = $visitor->visitArray($this->value);
- }
- public function compile($env){
- $ret = array();
- $i = 0;
- foreach($this->value as $i => $v){
- $ret[] = $v->compile($env);
- }
- if( $i > 0 ){
- return new Less_Tree_Value($ret);
- }
- return $ret[0];
- }
- /**
- * @see Less_Tree::genCSS
- */
- function genCSS( $output ){
- $len = count($this->value);
- for($i = 0; $i < $len; $i++ ){
- $this->value[$i]->genCSS( $output );
- if( $i+1 < $len ){
- $output->add( Less_Environment::$_outputMap[','] );
- }
- }
- }
- }
-
- /**
- * Variable
- *
- * @package Less
- * @subpackage tree
- */
- class Less_Tree_Variable extends Less_Tree{
- public $name;
- public $index;
- public $currentFileInfo;
- public $evaluating = false;
- public $type = 'Variable';
- /**
- * @param string $name
- */
- public function __construct($name, $index = null, $currentFileInfo = null) {
- $this->name = $name;
- $this->index = $index;
- $this->currentFileInfo = $currentFileInfo;
- }
- public function compile($env) {
- if( $this->name[1] === '@' ){
- $v = new Less_Tree_Variable(substr($this->name, 1), $this->index + 1, $this->currentFileInfo);
- $name = '@' . $v->compile($env)->value;
- }else{
- $name = $this->name;
- }
- if ($this->evaluating) {
- throw new Less_Exception_Compiler("Recursive variable definition for " . $name, null, $this->index, $this->currentFileInfo);
- }
- $this->evaluating = true;
- foreach($env->frames as $frame){
- if( $v = $frame->variable($name) ){
- $r = $v->value->compile($env);
- $this->evaluating = false;
- return $r;
- }
- }
- throw new Less_Exception_Compiler("variable " . $name . " is undefined in file ".$this->currentFileInfo["filename"], null, $this->index, $this->currentFileInfo);
- }
- }
-
- class Less_Tree_Mixin_Call extends Less_Tree{
- public $selector;
- public $arguments;
- public $index;
- public $currentFileInfo;
- public $important;
- public $type = 'MixinCall';
- /**
- * less.js: tree.mixin.Call
- *
- */
- public function __construct($elements, $args, $index, $currentFileInfo, $important = false){
- $this->selector = new Less_Tree_Selector($elements);
- $this->arguments = $args;
- $this->index = $index;
- $this->currentFileInfo = $currentFileInfo;
- $this->important = $important;
- }
- //function accept($visitor){
- // $this->selector = $visitor->visit($this->selector);
- // $this->arguments = $visitor->visit($this->arguments);
- //}
- public function compile($env){
- $rules = array();
- $match = false;
- $isOneFound = false;
- $candidates = array();
- $defaultUsed = false;
- $conditionResult = array();
- $args = array();
- foreach($this->arguments as $a){
- $args[] = array('name'=> $a['name'], 'value' => $a['value']->compile($env) );
- }
- foreach($env->frames as $frame){
- $mixins = $frame->find($this->selector);
- if( !$mixins ){
- continue;
- }
- $isOneFound = true;
- $defNone = 0;
- $defTrue = 1;
- $defFalse = 2;
- // To make `default()` function independent of definition order we have two "subpasses" here.
- // At first we evaluate each guard *twice* (with `default() == true` and `default() == false`),
- // and build candidate list with corresponding flags. Then, when we know all possible matches,
- // we make a final decision.
- $mixins_len = count($mixins);
- for( $m = 0; $m < $mixins_len; $m++ ){
- $mixin = $mixins[$m];
- if( $this->IsRecursive( $env, $mixin ) ){
- continue;
- }
- if( $mixin->matchArgs($args, $env) ){
- $candidate = array('mixin' => $mixin, 'group' => $defNone);
- if( $mixin instanceof Less_Tree_Ruleset ){
- for( $f = 0; $f < 2; $f++ ){
- Less_Tree_DefaultFunc::value($f);
- $conditionResult[$f] = $mixin->matchCondition( $args, $env);
- }
- if( $conditionResult[0] || $conditionResult[1] ){
- if( $conditionResult[0] != $conditionResult[1] ){
- $candidate['group'] = $conditionResult[1] ? $defTrue : $defFalse;
- }
- $candidates[] = $candidate;
- }
- }else{
- $candidates[] = $candidate;
- }
- $match = true;
- }
- }
- Less_Tree_DefaultFunc::reset();
- $count = array(0, 0, 0);
- for( $m = 0; $m < count($candidates); $m++ ){
- $count[ $candidates[$m]['group'] ]++;
- }
- if( $count[$defNone] > 0 ){
- $defaultResult = $defFalse;
- } else {
- $defaultResult = $defTrue;
- if( ($count[$defTrue] + $count[$defFalse]) > 1 ){
- throw new Exception( 'Ambiguous use of `default()` found when matching for `'. $this->format($args) + '`' );
- }
- }
- $candidates_length = count($candidates);
- $length_1 = ($candidates_length == 1);
- for( $m = 0; $m < $candidates_length; $m++){
- $candidate = $candidates[$m]['group'];
- if( ($candidate === $defNone) || ($candidate === $defaultResult) ){
- try{
- $mixin = $candidates[$m]['mixin'];
- if( !($mixin instanceof Less_Tree_Mixin_Definition) ){
- $mixin = new Less_Tree_Mixin_Definition('', array(), $mixin->rules, null, false);
- $mixin->originalRuleset = $mixins[$m]->originalRuleset;
- }
- $rules = array_merge($rules, $mixin->evalCall($env, $args, $this->important)->rules);
- } catch (Exception $e) {
- //throw new Less_Exception_Compiler($e->getMessage(), $e->index, null, $this->currentFileInfo['filename']);
- throw new Less_Exception_Compiler($e->getMessage(), null, null, $this->currentFileInfo);
- }
- }
- }
- if( $match ){
- if( !$this->currentFileInfo || !isset($this->currentFileInfo['reference']) || !$this->currentFileInfo['reference'] ){
- Less_Tree::ReferencedArray($rules);
- }
- return $rules;
- }
- }
- if( $isOneFound ){
- throw new Less_Exception_Compiler('No matching definition was found for `'.$this->Format( $args ).'`', null, $this->index, $this->currentFileInfo);
- }else{
- throw new Less_Exception_Compiler(trim($this->selector->toCSS()) . " is undefined in ".$this->currentFileInfo['filename'], null, $this->index);
- }
- }
- /**
- * Format the args for use in exception messages
- *
- */
- private function Format($args){
- $message = array();
- if( $args ){
- foreach($args as $a){
- $argValue = '';
- if( $a['name'] ){
- $argValue += $a['name']+':';
- }
- if( is_object($a['value']) ){
- $argValue += $a['value']->toCSS();
- }else{
- $argValue += '???';
- }
- $message[] = $argValue;
- }
- }
- return implode(', ',$message);
- }
- /**
- * Are we in a recursive mixin call?
- *
- * @return bool
- */
- private function IsRecursive( $env, $mixin ){
- foreach($env->frames as $recur_frame){
- if( !($mixin instanceof Less_Tree_Mixin_Definition) ){
- if( $mixin === $recur_frame ){
- return true;
- }
- if( isset($recur_frame->originalRuleset) && $mixin->ruleset_id === $recur_frame->originalRuleset ){
- return true;
- }
- }
- }
- return false;
- }
- }
-
- class Less_Tree_Mixin_Definition extends Less_Tree_Ruleset{
- public $name;
- public $selectors;
- public $params;
- public $arity = 0;
- public $rules;
- public $lookups = array();
- public $required = 0;
- public $frames = array();
- public $condition;
- public $variadic;
- public $type = 'MixinDefinition';
- // less.js : /lib/less/tree/mixin.js : tree.mixin.Definition
- public function __construct($name, $params, $rules, $condition, $variadic = false, $frames = array() ){
- $this->name = $name;
- $this->selectors = array(new Less_Tree_Selector(array( new Less_Tree_Element(null, $name))));
- $this->params = $params;
- $this->condition = $condition;
- $this->variadic = $variadic;
- $this->rules = $rules;
- if( $params ){
- $this->arity = count($params);
- foreach( $params as $p ){
- if (! isset($p['name']) || ($p['name'] && !isset($p['value']))) {
- $this->required++;
- }
- }
- }
- $this->frames = $frames;
- $this->SetRulesetIndex();
- }
- //function accept( $visitor ){
- // $this->params = $visitor->visit($this->params);
- // $this->rules = $visitor->visit($this->rules);
- // $this->condition = $visitor->visit($this->condition);
- //}
- public function toCSS(){
- return '';
- }
- // less.js : /lib/less/tree/mixin.js : tree.mixin.Definition.evalParams
- public function compileParams($env, $mixinFrames, $args = array() , &$evaldArguments = array() ){
- $frame = new Less_Tree_Ruleset(null, array());
- $params = $this->params;
- $mixinEnv = null;
- $argsLength = 0;
- if( $args ){
- $argsLength = count($args);
- for($i = 0; $i < $argsLength; $i++ ){
- $arg = $args[$i];
- if( $arg && $arg['name'] ){
- $isNamedFound = false;
- foreach($params as $j => $param){
- if( !isset($evaldArguments[$j]) && $arg['name'] === $params[$j]['name']) {
- $evaldArguments[$j] = $arg['value']->compile($env);
- array_unshift($frame->rules, new Less_Tree_Rule( $arg['name'], $arg['value']->compile($env) ) );
- $isNamedFound = true;
- break;
- }
- }
- if ($isNamedFound) {
- array_splice($args, $i, 1);
- $i--;
- $argsLength--;
- continue;
- } else {
- throw new Less_Exception_Compiler("Named argument for " . $this->name .' '.$args[$i]['name'] . ' not found');
- }
- }
- }
- }
- $argIndex = 0;
- foreach($params as $i => $param){
- if ( isset($evaldArguments[$i]) ){ continue; }
- $arg = null;
- if( isset($args[$argIndex]) ){
- $arg = $args[$argIndex];
- }
- if (isset($param['name']) && $param['name']) {
- if( isset($param['variadic']) ){
- $varargs = array();
- for ($j = $argIndex; $j < $argsLength; $j++) {
- $varargs[] = $args[$j]['value']->compile($env);
- }
- $expression = new Less_Tree_Expression($varargs);
- array_unshift($frame->rules, new Less_Tree_Rule($param['name'], $expression->compile($env)));
- }else{
- $val = ($arg && $arg['value']) ? $arg['value'] : false;
- if ($val) {
- $val = $val->compile($env);
- } else if ( isset($param['value']) ) {
- if( !$mixinEnv ){
- $mixinEnv = new Less_Environment();
- $mixinEnv->frames = array_merge( array($frame), $mixinFrames);
- }
- $val = $param['value']->compile($mixinEnv);
- $frame->resetCache();
- } else {
- throw new Less_Exception_Compiler("Wrong number of arguments for " . $this->name . " (" . $argsLength . ' for ' . $this->arity . ")");
- }
- array_unshift($frame->rules, new Less_Tree_Rule($param['name'], $val));
- $evaldArguments[$i] = $val;
- }
- }
- if ( isset($param['variadic']) && $args) {
- for ($j = $argIndex; $j < $argsLength; $j++) {
- $evaldArguments[$j] = $args[$j]['value']->compile($env);
- }
- }
- $argIndex++;
- }
- ksort($evaldArguments);
- $evaldArguments = array_values($evaldArguments);
- return $frame;
- }
- public function compile($env) {
- if( $this->frames ){
- return new Less_Tree_Mixin_Definition($this->name, $this->params, $this->rules, $this->condition, $this->variadic, $this->frames );
- }
- return new Less_Tree_Mixin_Definition($this->name, $this->params, $this->rules, $this->condition, $this->variadic, $env->frames );
- }
- public function evalCall($env, $args = NULL, $important = NULL) {
- Less_Environment::$mixin_stack++;
- $_arguments = array();
- if( $this->frames ){
- $mixinFrames = array_merge($this->frames, $env->frames);
- }else{
- $mixinFrames = $env->frames;
- }
- $frame = $this->compileParams($env, $mixinFrames, $args, $_arguments);
- $ex = new Less_Tree_Expression($_arguments);
- array_unshift($frame->rules, new Less_Tree_Rule('@arguments', $ex->compile($env)));
- $ruleset = new Less_Tree_Ruleset(null, $this->rules);
- $ruleset->originalRuleset = $this->ruleset_id;
- $ruleSetEnv = new Less_Environment();
- $ruleSetEnv->frames = array_merge( array($this, $frame), $mixinFrames );
- $ruleset = $ruleset->compile( $ruleSetEnv );
- if( $important ){
- $ruleset = $ruleset->makeImportant();
- }
- Less_Environment::$mixin_stack--;
- return $ruleset;
- }
- public function matchCondition($args, $env) {
- if( !$this->condition ){
- return true;
- }
- // set array to prevent error on array_merge
- if(!is_array($this->frames)) {
- $this->frames = array();
- }
- $frame = $this->compileParams($env, array_merge($this->frames,$env->frames), $args );
- $compile_env = new Less_Environment();
- $compile_env->frames = array_merge(
- array($frame) // the parameter variables
- , $this->frames // the parent namespace/mixin frames
- , $env->frames // the current environment frames
- );
- $compile_env->functions = $env->functions;
- return (bool)$this->condition->compile($compile_env);
- }
- public function matchArgs($args, $env = NULL){
- $argsLength = count($args);
- if( !$this->variadic ){
- if( $argsLength < $this->required ){
- return false;
- }
- if( $argsLength > count($this->params) ){
- return false;
- }
- }else{
- if( $argsLength < ($this->required - 1)){
- return false;
- }
- }
- $len = min($argsLength, $this->arity);
- for( $i = 0; $i < $len; $i++ ){
- if( !isset($this->params[$i]['name']) && !isset($this->params[$i]['variadic']) ){
- if( $args[$i]['value']->compile($env)->toCSS() != $this->params[$i]['value']->compile($env)->toCSS() ){
- return false;
- }
- }
- }
- return true;
- }
- }
-
- /**
- * Extend Finder Visitor
- *
- * @package Less
- * @subpackage visitor
- */
- class Less_Visitor_extendFinder extends Less_Visitor{
- public $contexts = array();
- public $allExtendsStack;
- public $foundExtends;
- public function __construct(){
- $this->contexts = array();
- $this->allExtendsStack = array(array());
- parent::__construct();
- }
- /**
- * @param Less_Tree_Ruleset $root
- */
- public function run($root){
- $root = $this->visitObj($root);
- $root->allExtends =& $this->allExtendsStack[0];
- return $root;
- }
- public function visitRule($ruleNode, &$visitDeeper ){
- $visitDeeper = false;
- }
- public function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ){
- $visitDeeper = false;
- }
- public function visitRuleset($rulesetNode){
- if( $rulesetNode->root ){
- return;
- }
- $allSelectorsExtendList = array();
- // get &:extend(.a); rules which apply to all selectors in this ruleset
- if( $rulesetNode->rules ){
- foreach($rulesetNode->rules as $rule){
- if( $rule instanceof Less_Tree_Extend ){
- $allSelectorsExtendList[] = $rule;
- $rulesetNode->extendOnEveryPath = true;
- }
- }
- }
- // now find every selector and apply the extends that apply to all extends
- // and the ones which apply to an individual extend
- foreach($rulesetNode->paths as $selectorPath){
- $selector = end($selectorPath); //$selectorPath[ count($selectorPath)-1];
- $j = 0;
- foreach($selector->extendList as $extend){
- $this->allExtendsStackPush($rulesetNode, $selectorPath, $extend, $j);
- }
- foreach($allSelectorsExtendList as $extend){
- $this->allExtendsStackPush($rulesetNode, $selectorPath, $extend, $j);
- }
- }
- $this->contexts[] = $rulesetNode->selectors;
- }
- public function allExtendsStackPush($rulesetNode, $selectorPath, $extend, &$j){
- $this->foundExtends = true;
- $extend = clone $extend;
- $extend->findSelfSelectors( $selectorPath );
- $extend->ruleset = $rulesetNode;
- if( $j === 0 ){
- $extend->firstExtendOnThisSelectorPath = true;
- }
- $end_key = count($this->allExtendsStack)-1;
- $this->allExtendsStack[$end_key][] = $extend;
- $j++;
- }
- public function visitRulesetOut( $rulesetNode ){
- if( !is_object($rulesetNode) || !$rulesetNode->root ){
- array_pop($this->contexts);
- }
- }
- public function visitMedia( $mediaNode ){
- $mediaNode->allExtends = array();
- $this->allExtendsStack[] =& $mediaNode->allExtends;
- }
- public function visitMediaOut(){
- array_pop($this->allExtendsStack);
- }
- public function visitDirective( $directiveNode ){
- $directiveNode->allExtends = array();
- $this->allExtendsStack[] =& $directiveNode->allExtends;
- }
- public function visitDirectiveOut(){
- array_pop($this->allExtendsStack);
- }
- }
-
- /*
- class Less_Visitor_import extends Less_VisitorReplacing{
- public $_visitor;
- public $_importer;
- public $importCount;
- function __construct( $evalEnv ){
- $this->env = $evalEnv;
- $this->importCount = 0;
- parent::__construct();
- }
- function run( $root ){
- $root = $this->visitObj($root);
- $this->isFinished = true;
- //if( $this->importCount === 0) {
- // $this->_finish();
- //}
- }
- function visitImport($importNode, &$visitDeeper ){
- $importVisitor = $this;
- $inlineCSS = $importNode->options['inline'];
- if( !$importNode->css || $inlineCSS ){
- $evaldImportNode = $importNode->compileForImport($this->env);
- if( $evaldImportNode && (!$evaldImportNode->css || $inlineCSS) ){
- $importNode = $evaldImportNode;
- $this->importCount++;
- $env = clone $this->env;
- if( (isset($importNode->options['multiple']) && $importNode->options['multiple']) ){
- $env->importMultiple = true;
- }
- //get path & uri
- $path_and_uri = null;
- if( is_callable(Less_Parser::$options['import_callback']) ){
- $path_and_uri = call_user_func(Less_Parser::$options['import_callback'],$importNode);
- }
- if( !$path_and_uri ){
- $path_and_uri = $importNode->PathAndUri();
- }
- if( $path_and_uri ){
- list($full_path, $uri) = $path_and_uri;
- }else{
- $full_path = $uri = $importNode->getPath();
- }
- //import once
- if( $importNode->skip( $full_path, $env) ){
- return array();
- }
- if( $importNode->options['inline'] ){
- //todo needs to reference css file not import
- //$contents = new Less_Tree_Anonymous($importNode->root, 0, array('filename'=>$importNode->importedFilename), true );
- Less_Parser::AddParsedFile($full_path);
- $contents = new Less_Tree_Anonymous( file_get_contents($full_path), 0, array(), true );
- if( $importNode->features ){
- return new Less_Tree_Media( array($contents), $importNode->features->value );
- }
- return array( $contents );
- }
- // css ?
- if( $importNode->css ){
- $features = ( $importNode->features ? $importNode->features->compile($env) : null );
- return new Less_Tree_Import( $importNode->compilePath( $env), $features, $importNode->options, $this->index);
- }
- return $importNode->ParseImport( $full_path, $uri, $env );
- }
- }
- $visitDeeper = false;
- return $importNode;
- }
- function visitRule( $ruleNode, &$visitDeeper ){
- $visitDeeper = false;
- return $ruleNode;
- }
- function visitDirective($directiveNode, $visitArgs){
- array_unshift($this->env->frames,$directiveNode);
- return $directiveNode;
- }
- function visitDirectiveOut($directiveNode) {
- array_shift($this->env->frames);
- }
- function visitMixinDefinition($mixinDefinitionNode, $visitArgs) {
- array_unshift($this->env->frames,$mixinDefinitionNode);
- return $mixinDefinitionNode;
- }
- function visitMixinDefinitionOut($mixinDefinitionNode) {
- array_shift($this->env->frames);
- }
- function visitRuleset($rulesetNode, $visitArgs) {
- array_unshift($this->env->frames,$rulesetNode);
- return $rulesetNode;
- }
- function visitRulesetOut($rulesetNode) {
- array_shift($this->env->frames);
- }
- function visitMedia($mediaNode, $visitArgs) {
- array_unshift($this->env->frames, $mediaNode->ruleset);
- return $mediaNode;
- }
- function visitMediaOut($mediaNode) {
- array_shift($this->env->frames);
- }
- }
- */
-
- /**
- * Join Selector Visitor
- *
- * @package Less
- * @subpackage visitor
- */
- class Less_Visitor_joinSelector extends Less_Visitor{
- public $contexts = array( array() );
- /**
- * @param Less_Tree_Ruleset $root
- */
- public function run( $root ){
- return $this->visitObj($root);
- }
- public function visitRule( $ruleNode, &$visitDeeper ){
- $visitDeeper = false;
- }
- public function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ){
- $visitDeeper = false;
- }
- public function visitRuleset( $rulesetNode ){
- $paths = array();
- if( !$rulesetNode->root ){
- $selectors = array();
- if( $rulesetNode->selectors && $rulesetNode->selectors ){
- foreach($rulesetNode->selectors as $selector){
- if( $selector->getIsOutput() ){
- $selectors[] = $selector;
- }
- }
- }
- if( !$selectors ){
- $rulesetNode->selectors = null;
- $rulesetNode->rules = null;
- }else{
- $context = end($this->contexts); //$context = $this->contexts[ count($this->contexts) - 1];
- $paths = $rulesetNode->joinSelectors( $context, $selectors);
- }
- $rulesetNode->paths = $paths;
- }
- $this->contexts[] = $paths; //different from less.js. Placed after joinSelectors() so that $this->contexts will get correct $paths
- }
- public function visitRulesetOut(){
- array_pop($this->contexts);
- }
- public function visitMedia($mediaNode) {
- $context = end($this->contexts); //$context = $this->contexts[ count($this->contexts) - 1];
- if( !count($context) || (is_object($context[0]) && $context[0]->multiMedia) ){
- $mediaNode->rules[0]->root = true;
- }
- }
- }
-
- /**
- * Process Extends Visitor
- *
- * @package Less
- * @subpackage visitor
- */
- class Less_Visitor_processExtends extends Less_Visitor{
- public $allExtendsStack;
- /**
- * @param Less_Tree_Ruleset $root
- */
- public function run( $root ){
- $extendFinder = new Less_Visitor_extendFinder();
- $extendFinder->run( $root );
- if( !$extendFinder->foundExtends){
- return $root;
- }
- $root->allExtends = $this->doExtendChaining( $root->allExtends, $root->allExtends);
- $this->allExtendsStack = array();
- $this->allExtendsStack[] = &$root->allExtends;
- return $this->visitObj( $root );
- }
- private function doExtendChaining( $extendsList, $extendsListTarget, $iterationCount = 0){
- //
- // chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting
- // the selector we would do normally, but we are also adding an extend with the same target selector
- // this means this new extend can then go and alter other extends
- //
- // this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors
- // this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if
- // we look at each selector at a time, as is done in visitRuleset
- $extendsToAdd = array();
- //loop through comparing every extend with every target extend.
- // a target extend is the one on the ruleset we are looking at copy/edit/pasting in place
- // e.g. .a:extend(.b) {} and .b:extend(.c) {} then the first extend extends the second one
- // and the second is the target.
- // the seperation into two lists allows us to process a subset of chains with a bigger set, as is the
- // case when processing media queries
- for( $extendIndex = 0, $extendsList_len = count($extendsList); $extendIndex < $extendsList_len; $extendIndex++ ){
- for( $targetExtendIndex = 0; $targetExtendIndex < count($extendsListTarget); $targetExtendIndex++ ){
- $extend = $extendsList[$extendIndex];
- $targetExtend = $extendsListTarget[$targetExtendIndex];
- // look for circular references
- if( in_array($targetExtend->object_id, $extend->parent_ids,true) ){
- continue;
- }
- // find a match in the target extends self selector (the bit before :extend)
- $selectorPath = array( $targetExtend->selfSelectors[0] );
- $matches = $this->findMatch( $extend, $selectorPath);
- if( $matches ){
- // we found a match, so for each self selector..
- foreach($extend->selfSelectors as $selfSelector ){
- // process the extend as usual
- $newSelector = $this->extendSelector( $matches, $selectorPath, $selfSelector);
- // but now we create a new extend from it
- $newExtend = new Less_Tree_Extend( $targetExtend->selector, $targetExtend->option, 0);
- $newExtend->selfSelectors = $newSelector;
- // add the extend onto the list of extends for that selector
- end($newSelector)->extendList = array($newExtend);
- //$newSelector[ count($newSelector)-1]->extendList = array($newExtend);
- // record that we need to add it.
- $extendsToAdd[] = $newExtend;
- $newExtend->ruleset = $targetExtend->ruleset;
- //remember its parents for circular references
- $newExtend->parent_ids = array_merge($newExtend->parent_ids,$targetExtend->parent_ids,$extend->parent_ids);
- // only process the selector once.. if we have :extend(.a,.b) then multiple
- // extends will look at the same selector path, so when extending
- // we know that any others will be duplicates in terms of what is added to the css
- if( $targetExtend->firstExtendOnThisSelectorPath ){
- $newExtend->firstExtendOnThisSelectorPath = true;
- $targetExtend->ruleset->paths[] = $newSelector;
- }
- }
- }
- }
- }
- if( $extendsToAdd ){
- // try to detect circular references to stop a stack overflow.
- // may no longer be needed. $this->extendChainCount++;
- if( $iterationCount > 100) {
- try{
- $selectorOne = $extendsToAdd[0]->selfSelectors[0]->toCSS();
- $selectorTwo = $extendsToAdd[0]->selector->toCSS();
- }catch(Exception $e){
- $selectorOne = "{unable to calculate}";
- $selectorTwo = "{unable to calculate}";
- }
- throw new Less_Exception_Parser("extend circular reference detected. One of the circular extends is currently:"+$selectorOne+":extend(" + $selectorTwo+")");
- }
- // now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e...
- $extendsToAdd = $this->doExtendChaining( $extendsToAdd, $extendsListTarget, $iterationCount+1);
- }
- return array_merge($extendsList, $extendsToAdd);
- }
- protected function visitRule( $ruleNode, &$visitDeeper ){
- $visitDeeper = false;
- }
- protected function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ){
- $visitDeeper = false;
- }
- protected function visitSelector( $selectorNode, &$visitDeeper ){
- $visitDeeper = false;
- }
- protected function visitRuleset($rulesetNode){
- if( $rulesetNode->root ){
- return;
- }
- $allExtends = end($this->allExtendsStack);
- $paths_len = count($rulesetNode->paths);
- // look at each selector path in the ruleset, find any extend matches and then copy, find and replace
- foreach($allExtends as $allExtend){
- for($pathIndex = 0; $pathIndex < $paths_len; $pathIndex++ ){
- // extending extends happens initially, before the main pass
- if( isset($rulesetNode->extendOnEveryPath) && $rulesetNode->extendOnEveryPath ){
- continue;
- }
- $selectorPath = $rulesetNode->paths[$pathIndex];
- if( end($selectorPath)->extendList ){
- continue;
- }
- $this->ExtendMatch( $rulesetNode, $allExtend, $selectorPath);
- }
- }
- }
- private function ExtendMatch( $rulesetNode, $extend, $selectorPath ){
- $matches = $this->findMatch($extend, $selectorPath);
- if( $matches ){
- foreach($extend->selfSelectors as $selfSelector ){
- $rulesetNode->paths[] = $this->extendSelector($matches, $selectorPath, $selfSelector);
- }
- }
- }
- private function findMatch($extend, $haystackSelectorPath ){
- if( !$this->HasMatches($extend, $haystackSelectorPath) ){
- return false;
- }
- //
- // look through the haystack selector path to try and find the needle - extend.selector
- // returns an array of selector matches that can then be replaced
- //
- $needleElements = $extend->selector->elements;
- $potentialMatches = array();
- $potentialMatches_len = 0;
- $potentialMatch = null;
- $matches = array();
- // loop through the haystack elements
- $haystack_path_len = count($haystackSelectorPath);
- for($haystackSelectorIndex = 0; $haystackSelectorIndex < $haystack_path_len; $haystackSelectorIndex++ ){
- $hackstackSelector = $haystackSelectorPath[$haystackSelectorIndex];
- $haystack_elements_len = count($hackstackSelector->elements);
- for($hackstackElementIndex = 0; $hackstackElementIndex < $haystack_elements_len; $hackstackElementIndex++ ){
- $haystackElement = $hackstackSelector->elements[$hackstackElementIndex];
- // if we allow elements before our match we can add a potential match every time. otherwise only at the first element.
- if( $extend->allowBefore || ($haystackSelectorIndex === 0 && $hackstackElementIndex === 0) ){
- $potentialMatches[] = array('pathIndex'=> $haystackSelectorIndex, 'index'=> $hackstackElementIndex, 'matched'=> 0, 'initialCombinator'=> $haystackElement->combinator);
- $potentialMatches_len++;
- }
- for($i = 0; $i < $potentialMatches_len; $i++ ){
- $potentialMatch = &$potentialMatches[$i];
- $potentialMatch = $this->PotentialMatch( $potentialMatch, $needleElements, $haystackElement, $hackstackElementIndex );
- // if we are still valid and have finished, test whether we have elements after and whether these are allowed
- if( $potentialMatch && $potentialMatch['matched'] === $extend->selector->elements_len ){
- $potentialMatch['finished'] = true;
- if( !$extend->allowAfter && ($hackstackElementIndex+1 < $haystack_elements_len || $haystackSelectorIndex+1 < $haystack_path_len) ){
- $potentialMatch = null;
- }
- }
- // if null we remove, if not, we are still valid, so either push as a valid match or continue
- if( $potentialMatch ){
- if( $potentialMatch['finished'] ){
- $potentialMatch['length'] = $extend->selector->elements_len;
- $potentialMatch['endPathIndex'] = $haystackSelectorIndex;
- $potentialMatch['endPathElementIndex'] = $hackstackElementIndex + 1; // index after end of match
- $potentialMatches = array(); // we don't allow matches to overlap, so start matching again
- $potentialMatches_len = 0;
- $matches[] = $potentialMatch;
- }
- continue;
- }
- array_splice($potentialMatches, $i, 1);
- $potentialMatches_len--;
- $i--;
- }
- }
- }
- return $matches;
- }
- // Before going through all the nested loops, lets check to see if a match is possible
- // Reduces Bootstrap 3.1 compile time from ~6.5s to ~5.6s
- private function HasMatches($extend, $haystackSelectorPath){
- if( !$extend->selector->cacheable ){
- return true;
- }
- $first_el = $extend->selector->_oelements[0];
- foreach($haystackSelectorPath as $hackstackSelector){
- if( !$hackstackSelector->cacheable ){
- return true;
- }
- if( in_array($first_el, $hackstackSelector->_oelements) ){
- return true;
- }
- }
- return false;
- }
- /**
- * @param integer $hackstackElementIndex
- */
- private function PotentialMatch( $potentialMatch, $needleElements, $haystackElement, $hackstackElementIndex ){
- if( $potentialMatch['matched'] > 0 ){
- // selectors add " " onto the first element. When we use & it joins the selectors together, but if we don't
- // then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to work out
- // what the resulting combinator will be
- $targetCombinator = $haystackElement->combinator;
- if( $targetCombinator === '' && $hackstackElementIndex === 0 ){
- $targetCombinator = ' ';
- }
- if( $needleElements[ $potentialMatch['matched'] ]->combinator !== $targetCombinator ){
- return null;
- }
- }
- // if we don't match, null our match to indicate failure
- if( !$this->isElementValuesEqual( $needleElements[$potentialMatch['matched'] ]->value, $haystackElement->value) ){
- return null;
- }
- $potentialMatch['finished'] = false;
- $potentialMatch['matched']++;
- return $potentialMatch;
- }
- private function isElementValuesEqual( $elementValue1, $elementValue2 ){
- if( $elementValue1 === $elementValue2 ){
- return true;
- }
- if( is_string($elementValue1) || is_string($elementValue2) ) {
- return false;
- }
- if( $elementValue1 instanceof Less_Tree_Attribute ){
- return $this->isAttributeValuesEqual( $elementValue1, $elementValue2 );
- }
- $elementValue1 = $elementValue1->value;
- if( $elementValue1 instanceof Less_Tree_Selector ){
- return $this->isSelectorValuesEqual( $elementValue1, $elementValue2 );
- }
- return false;
- }
- /**
- * @param Less_Tree_Selector $elementValue1
- */
- private function isSelectorValuesEqual( $elementValue1, $elementValue2 ){
- $elementValue2 = $elementValue2->value;
- if( !($elementValue2 instanceof Less_Tree_Selector) || $elementValue1->elements_len !== $elementValue2->elements_len ){
- return false;
- }
- for( $i = 0; $i < $elementValue1->elements_len; $i++ ){
- if( $elementValue1->elements[$i]->combinator !== $elementValue2->elements[$i]->combinator ){
- if( $i !== 0 || ($elementValue1->elements[$i]->combinator || ' ') !== ($elementValue2->elements[$i]->combinator || ' ') ){
- return false;
- }
- }
- if( !$this->isElementValuesEqual($elementValue1->elements[$i]->value, $elementValue2->elements[$i]->value) ){
- return false;
- }
- }
- return true;
- }
- /**
- * @param Less_Tree_Attribute $elementValue1
- */
- private function isAttributeValuesEqual( $elementValue1, $elementValue2 ){
- if( $elementValue1->op !== $elementValue2->op || $elementValue1->key !== $elementValue2->key ){
- return false;
- }
- if( !$elementValue1->value || !$elementValue2->value ){
- if( $elementValue1->value || $elementValue2->value ) {
- return false;
- }
- return true;
- }
- $elementValue1 = ($elementValue1->value->value ? $elementValue1->value->value : $elementValue1->value );
- $elementValue2 = ($elementValue2->value->value ? $elementValue2->value->value : $elementValue2->value );
- return $elementValue1 === $elementValue2;
- }
- private function extendSelector($matches, $selectorPath, $replacementSelector){
- //for a set of matches, replace each match with the replacement selector
- $currentSelectorPathIndex = 0;
- $currentSelectorPathElementIndex = 0;
- $path = array();
- $selectorPath_len = count($selectorPath);
- for($matchIndex = 0, $matches_len = count($matches); $matchIndex < $matches_len; $matchIndex++ ){
- $match = $matches[$matchIndex];
- $selector = $selectorPath[ $match['pathIndex'] ];
- $firstElement = new Less_Tree_Element(
- $match['initialCombinator'],
- $replacementSelector->elements[0]->value,
- $replacementSelector->elements[0]->index,
- $replacementSelector->elements[0]->currentFileInfo
- );
- if( $match['pathIndex'] > $currentSelectorPathIndex && $currentSelectorPathElementIndex > 0 ){
- $last_path = end($path);
- $last_path->elements = array_merge( $last_path->elements, array_slice( $selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex));
- $currentSelectorPathElementIndex = 0;
- $currentSelectorPathIndex++;
- }
- $newElements = array_merge(
- array_slice($selector->elements, $currentSelectorPathElementIndex, ($match['index'] - $currentSelectorPathElementIndex) ) // last parameter of array_slice is different than the last parameter of javascript's slice
- , array($firstElement)
- , array_slice($replacementSelector->elements,1)
- );
- if( $currentSelectorPathIndex === $match['pathIndex'] && $matchIndex > 0 ){
- $last_key = count($path)-1;
- $path[$last_key]->elements = array_merge($path[$last_key]->elements,$newElements);
- }else{
- $path = array_merge( $path, array_slice( $selectorPath, $currentSelectorPathIndex, $match['pathIndex'] ));
- $path[] = new Less_Tree_Selector( $newElements );
- }
- $currentSelectorPathIndex = $match['endPathIndex'];
- $currentSelectorPathElementIndex = $match['endPathElementIndex'];
- if( $currentSelectorPathElementIndex >= count($selectorPath[$currentSelectorPathIndex]->elements) ){
- $currentSelectorPathElementIndex = 0;
- $currentSelectorPathIndex++;
- }
- }
- if( $currentSelectorPathIndex < $selectorPath_len && $currentSelectorPathElementIndex > 0 ){
- $last_path = end($path);
- $last_path->elements = array_merge( $last_path->elements, array_slice($selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex));
- $currentSelectorPathIndex++;
- }
- $slice_len = $selectorPath_len - $currentSelectorPathIndex;
- $path = array_merge($path, array_slice($selectorPath, $currentSelectorPathIndex, $slice_len));
- return $path;
- }
- protected function visitMedia( $mediaNode ){
- $newAllExtends = array_merge( $mediaNode->allExtends, end($this->allExtendsStack) );
- $this->allExtendsStack[] = $this->doExtendChaining($newAllExtends, $mediaNode->allExtends);
- }
- protected function visitMediaOut(){
- array_pop( $this->allExtendsStack );
- }
- protected function visitDirective( $directiveNode ){
- $newAllExtends = array_merge( $directiveNode->allExtends, end($this->allExtendsStack) );
- $this->allExtendsStack[] = $this->doExtendChaining($newAllExtends, $directiveNode->allExtends);
- }
- protected function visitDirectiveOut(){
- array_pop($this->allExtendsStack);
- }
- }
- /**
- * toCSS Visitor
- *
- * @package Less
- * @subpackage visitor
- */
- class Less_Visitor_toCSS extends Less_VisitorReplacing{
- private $charset;
- public function __construct(){
- parent::__construct();
- }
- /**
- * @param Less_Tree_Ruleset $root
- */
- public function run( $root ){
- return $this->visitObj($root);
- }
- public function visitRule( $ruleNode ){
- if( $ruleNode->variable ){
- return array();
- }
- return $ruleNode;
- }
- public function visitMixinDefinition($mixinNode){
- // mixin definitions do not get eval'd - this means they keep state
- // so we have to clear that state here so it isn't used if toCSS is called twice
- $mixinNode->frames = array();
- return array();
- }
- public function visitExtend(){
- return array();
- }
- public function visitComment( $commentNode ){
- if( $commentNode->isSilent() ){
- return array();
- }
- return $commentNode;
- }
- public function visitMedia( $mediaNode, &$visitDeeper ){
- $mediaNode->accept($this);
- $visitDeeper = false;
- if( !$mediaNode->rules ){
- return array();
- }
- return $mediaNode;
- }
- public function visitDirective( $directiveNode ){
- if( isset($directiveNode->currentFileInfo['reference']) && (!property_exists($directiveNode,'isReferenced') || !$directiveNode->isReferenced) ){
- return array();
- }
- if( $directiveNode->name === '@charset' ){
- // Only output the debug info together with subsequent @charset definitions
- // a comment (or @media statement) before the actual @charset directive would
- // be considered illegal css as it has to be on the first line
- if( isset($this->charset) && $this->charset ){
- //if( $directiveNode->debugInfo ){
- // $comment = new Less_Tree_Comment('/* ' . str_replace("\n",'',$directiveNode->toCSS())." */\n");
- // $comment->debugInfo = $directiveNode->debugInfo;
- // return $this->visit($comment);
- //}
- return array();
- }
- $this->charset = true;
- }
- return $directiveNode;
- }
- public function checkPropertiesInRoot( $rulesetNode ){
- if( !$rulesetNode->firstRoot ){
- return;
- }
- foreach($rulesetNode->rules as $ruleNode){
- if( $ruleNode instanceof Less_Tree_Rule && !$ruleNode->variable ){
- $msg = "properties must be inside selector blocks, they cannot be in the root. Index ".$ruleNode->index.($ruleNode->currentFileInfo ? (' Filename: '.$ruleNode->currentFileInfo['filename']) : null);
- throw new Less_Exception_Compiler($msg);
- }
- }
- }
- public function visitRuleset( $rulesetNode, &$visitDeeper ){
- $visitDeeper = false;
- $this->checkPropertiesInRoot( $rulesetNode );
- if( $rulesetNode->root ){
- return $this->visitRulesetRoot( $rulesetNode );
- }
- $rulesets = array();
- $rulesetNode->paths = $this->visitRulesetPaths($rulesetNode);
- // Compile rules and rulesets
- $nodeRuleCnt = count($rulesetNode->rules);
- for( $i = 0; $i < $nodeRuleCnt; ){
- $rule = $rulesetNode->rules[$i];
- if( property_exists($rule,'rules') ){
- // visit because we are moving them out from being a child
- $rulesets[] = $this->visitObj($rule);
- array_splice($rulesetNode->rules,$i,1);
- $nodeRuleCnt--;
- continue;
- }
- $i++;
- }
- // accept the visitor to remove rules and refactor itself
- // then we can decide now whether we want it or not
- if( $nodeRuleCnt > 0 ){
- $rulesetNode->accept($this);
- if( $rulesetNode->rules ){
- if( count($rulesetNode->rules) > 1 ){
- $this->_mergeRules( $rulesetNode->rules );
- $this->_removeDuplicateRules( $rulesetNode->rules );
- }
- // now decide whether we keep the ruleset
- if( $rulesetNode->paths ){
- //array_unshift($rulesets, $rulesetNode);
- array_splice($rulesets,0,0,array($rulesetNode));
- }
- }
- }
- if( count($rulesets) === 1 ){
- return $rulesets[0];
- }
- return $rulesets;
- }
- /**
- * Helper function for visitiRuleset
- *
- * return array|Less_Tree_Ruleset
- */
- private function visitRulesetRoot( $rulesetNode ){
- $rulesetNode->accept( $this );
- if( $rulesetNode->firstRoot || $rulesetNode->rules ){
- return $rulesetNode;
- }
- return array();
- }
- /**
- * Helper function for visitRuleset()
- *
- * @return array
- */
- private function visitRulesetPaths($rulesetNode){
- $paths = array();
- foreach($rulesetNode->paths as $p){
- if( $p[0]->elements[0]->combinator === ' ' ){
- $p[0]->elements[0]->combinator = '';
- }
- foreach($p as $pi){
- if( $pi->getIsReferenced() && $pi->getIsOutput() ){
- $paths[] = $p;
- break;
- }
- }
- }
- return $paths;
- }
- protected function _removeDuplicateRules( &$rules ){
- // remove duplicates
- $ruleCache = array();
- for( $i = count($rules)-1; $i >= 0 ; $i-- ){
- $rule = $rules[$i];
- if( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_NameValue ){
- if( !isset($ruleCache[$rule->name]) ){
- $ruleCache[$rule->name] = $rule;
- }else{
- $ruleList =& $ruleCache[$rule->name];
- if( $ruleList instanceof Less_Tree_Rule || $ruleList instanceof Less_Tree_NameValue ){
- $ruleList = $ruleCache[$rule->name] = array( $ruleCache[$rule->name]->toCSS() );
- }
- $ruleCSS = $rule->toCSS();
- if( array_search($ruleCSS,$ruleList) !== false ){
- array_splice($rules,$i,1);
- }else{
- $ruleList[] = $ruleCSS;
- }
- }
- }
- }
- }
- protected function _mergeRules( &$rules ){
- $groups = array();
- //obj($rules);
- $rules_len = count($rules);
- for( $i = 0; $i < $rules_len; $i++ ){
- $rule = $rules[$i];
- if( ($rule instanceof Less_Tree_Rule) && $rule->merge ){
- $key = $rule->name;
- if( $rule->important ){
- $key .= ',!';
- }
- if( !isset($groups[$key]) ){
- $groups[$key] = array();
- }else{
- array_splice($rules, $i--, 1);
- $rules_len--;
- }
- $groups[$key][] = $rule;
- }
- }
- foreach($groups as $parts){
- if( count($parts) > 1 ){
- $rule = $parts[0];
- $spacedGroups = array();
- $lastSpacedGroup = array();
- $parts_mapped = array();
- foreach($parts as $p){
- if( $p->merge === '+' ){
- if( $lastSpacedGroup ){
- $spacedGroups[] = self::toExpression($lastSpacedGroup);
- }
- $lastSpacedGroup = array();
- }
- $lastSpacedGroup[] = $p;
- }
- $spacedGroups[] = self::toExpression($lastSpacedGroup);
- $rule->value = self::toValue($spacedGroups);
- }
- }
- }
- public static function toExpression($values){
- $mapped = array();
- foreach($values as $p){
- $mapped[] = $p->value;
- }
- return new Less_Tree_Expression( $mapped );
- }
- public static function toValue($values){
- //return new Less_Tree_Value($values); ??
- $mapped = array();
- foreach($values as $p){
- $mapped[] = $p;
- }
- return new Less_Tree_Value($mapped);
- }
- }
-
- /**
- * Parser Exception
- *
- * @package Less
- * @subpackage exception
- */
- class Less_Exception_Parser extends Exception{
- /**
- * The current file
- *
- * @var Less_ImportedFile
- */
- public $currentFile;
- /**
- * The current parser index
- *
- * @var integer
- */
- public $index;
- protected $input;
- protected $details = array();
- /**
- * Constructor
- *
- * @param string $message
- * @param Exception $previous Previous exception
- * @param integer $index The current parser index
- * @param Less_FileInfo|string $currentFile The file
- * @param integer $code The exception code
- */
- public function __construct($message = null, Exception $previous = null, $index = null, $currentFile = null, $code = 0){
- if (PHP_VERSION_ID < 50300) {
- $this->previous = $previous;
- parent::__construct($message, $code);
- } else {
- parent::__construct($message, $code, $previous);
- }
- $this->currentFile = $currentFile;
- $this->index = $index;
- $this->genMessage();
- }
- protected function getInput(){
- if( !$this->input && $this->currentFile && $this->currentFile['filename'] ){
- $this->input = file_get_contents( $this->currentFile['filename'] );
- }
- }
- /**
- * Converts the exception to string
- *
- * @return string
- */
- public function genMessage(){
- if( $this->currentFile && $this->currentFile['filename'] ){
- $this->message .= ' in '.basename($this->currentFile['filename']);
- }
- if( $this->index !== null ){
- $this->getInput();
- if( $this->input ){
- $line = self::getLineNumber();
- $this->message .= ' on line '.$line.', column '.self::getColumn();
- $lines = explode("\n",$this->input);
- $count = count($lines);
- $start_line = max(0, $line-3);
- $last_line = min($count, $start_line+6);
- $num_len = strlen($last_line);
- for( $i = $start_line; $i < $last_line; $i++ ){
- $this->message .= "\n".str_pad($i+1,$num_len,'0',STR_PAD_LEFT).'| '.$lines[$i];
- }
- }
- }
- }
- /**
- * Returns the line number the error was encountered
- *
- * @return integer
- */
- public function getLineNumber(){
- if( $this->index ){
- // https://bugs.php.net/bug.php?id=49790
- if (ini_get("mbstring.func_overload")) {
- return substr_count(substr($this->input, 0, $this->index), "\n") + 1;
- } else {
- return substr_count($this->input, "\n", 0, $this->index) + 1;
- }
- }
- return 1;
- }
- /**
- * Returns the column the error was encountered
- *
- * @return integer
- */
- public function getColumn(){
- $part = substr($this->input, 0, $this->index);
- $pos = strrpos($part,"\n");
- return $this->index - $pos;
- }
- }
-
- /**
- * Chunk Exception
- *
- * @package Less
- * @subpackage exception
- */
- class Less_Exception_Chunk extends Less_Exception_Parser{
- protected $parserCurrentIndex = 0;
- protected $emitFrom = 0;
- protected $input_len;
- /**
- * Constructor
- *
- * @param string $input
- * @param Exception $previous Previous exception
- * @param integer $index The current parser index
- * @param Less_FileInfo|string $currentFile The file
- * @param integer $code The exception code
- */
- public function __construct($input, Exception $previous = null, $index = null, $currentFile = null, $code = 0){
- $this->message = 'ParseError: Unexpected input'; //default message
- $this->index = $index;
- $this->currentFile = $currentFile;
- $this->input = $input;
- $this->input_len = strlen($input);
- $this->Chunks();
- $this->genMessage();
- }
- /**
- * See less.js chunks()
- * We don't actually need the chunks
- *
- */
- protected function Chunks(){
- $level = 0;
- $parenLevel = 0;
- $lastMultiCommentEndBrace = null;
- $lastOpening = null;
- $lastMultiComment = null;
- $lastParen = null;
- for( $this->parserCurrentIndex = 0; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++ ){
- $cc = $this->CharCode($this->parserCurrentIndex);
- if ((($cc >= 97) && ($cc <= 122)) || ($cc < 34)) {
- // a-z or whitespace
- continue;
- }
- switch ($cc) {
- // (
- case 40:
- $parenLevel++;
- $lastParen = $this->parserCurrentIndex;
- continue;
- // )
- case 41:
- $parenLevel--;
- if( $parenLevel < 0 ){
- return $this->fail("missing opening `(`");
- }
- continue;
- // ;
- case 59:
- //if (!$parenLevel) { $this->emitChunk(); }
- continue;
- // {
- case 123:
- $level++;
- $lastOpening = $this->parserCurrentIndex;
- continue;
- // }
- case 125:
- $level--;
- if( $level < 0 ){
- return $this->fail("missing opening `{`");
- }
- //if (!$level && !$parenLevel) { $this->emitChunk(); }
- continue;
- // \
- case 92:
- if ($this->parserCurrentIndex < $this->input_len - 1) { $this->parserCurrentIndex++; continue; }
- return $this->fail("unescaped `\\`");
- // ", ' and `
- case 34:
- case 39:
- case 96:
- $matched = 0;
- $currentChunkStartIndex = $this->parserCurrentIndex;
- for ($this->parserCurrentIndex = $this->parserCurrentIndex + 1; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++) {
- $cc2 = $this->CharCode($this->parserCurrentIndex);
- if ($cc2 > 96) { continue; }
- if ($cc2 == $cc) { $matched = 1; break; }
- if ($cc2 == 92) { // \
- if ($this->parserCurrentIndex == $this->input_len - 1) {
- return $this->fail("unescaped `\\`");
- }
- $this->parserCurrentIndex++;
- }
- }
- if ($matched) { continue; }
- return $this->fail("unmatched `" + chr($cc) + "`", $currentChunkStartIndex);
- // /, check for comment
- case 47:
- if ($parenLevel || ($this->parserCurrentIndex == $this->input_len - 1)) { continue; }
- $cc2 = $this->CharCode($this->parserCurrentIndex+1);
- if ($cc2 == 47) {
- // //, find lnfeed
- for ($this->parserCurrentIndex = $this->parserCurrentIndex + 2; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++) {
- $cc2 = $this->CharCode($this->parserCurrentIndex);
- if (($cc2 <= 13) && (($cc2 == 10) || ($cc2 == 13))) { break; }
- }
- } else if ($cc2 == 42) {
- // /*, find */
- $lastMultiComment = $currentChunkStartIndex = $this->parserCurrentIndex;
- for ($this->parserCurrentIndex = $this->parserCurrentIndex + 2; $this->parserCurrentIndex < $this->input_len - 1; $this->parserCurrentIndex++) {
- $cc2 = $this->CharCode($this->parserCurrentIndex);
- if ($cc2 == 125) { $lastMultiCommentEndBrace = $this->parserCurrentIndex; }
- if ($cc2 != 42) { continue; }
- if ($this->CharCode($this->parserCurrentIndex+1) == 47) { break; }
- }
- if ($this->parserCurrentIndex == $this->input_len - 1) {
- return $this->fail("missing closing `*/`", $currentChunkStartIndex);
- }
- }
- continue;
- // *, check for unmatched */
- case 42:
- if (($this->parserCurrentIndex < $this->input_len - 1) && ($this->CharCode($this->parserCurrentIndex+1) == 47)) {
- return $this->fail("unmatched `/*`");
- }
- continue;
- }
- }
- if( $level !== 0 ){
- if( ($lastMultiComment > $lastOpening) && ($lastMultiCommentEndBrace > $lastMultiComment) ){
- return $this->fail("missing closing `}` or `*/`", $lastOpening);
- } else {
- return $this->fail("missing closing `}`", $lastOpening);
- }
- } else if ( $parenLevel !== 0 ){
- return $this->fail("missing closing `)`", $lastParen);
- }
- //chunk didn't fail
- //$this->emitChunk(true);
- }
- public function CharCode($pos){
- return ord($this->input[$pos]);
- }
- public function fail( $msg, $index = null ){
- if( !$index ){
- $this->index = $this->parserCurrentIndex;
- }else{
- $this->index = $index;
- }
- $this->message = 'ParseError: '.$msg;
- }
- /*
- function emitChunk( $force = false ){
- $len = $this->parserCurrentIndex - $this->emitFrom;
- if ((($len < 512) && !$force) || !$len) {
- return;
- }
- $chunks[] = substr($this->input, $this->emitFrom, $this->parserCurrentIndex + 1 - $this->emitFrom );
- $this->emitFrom = $this->parserCurrentIndex + 1;
- }
- */
- }
-
- /**
- * Compiler Exception
- *
- * @package Less
- * @subpackage exception
- */
- class Less_Exception_Compiler extends Less_Exception_Parser{
- }
- /**
- * Parser output with source map
- *
- * @package Less
- * @subpackage Output
- */
- class Less_Output_Mapped extends Less_Output {
- /**
- * The source map generator
- *
- * @var Less_SourceMap_Generator
- */
- protected $generator;
- /**
- * Current line
- *
- * @var integer
- */
- protected $lineNumber = 0;
- /**
- * Current column
- *
- * @var integer
- */
- protected $column = 0;
- /**
- * Array of contents map (file and its content)
- *
- * @var array
- */
- protected $contentsMap = array();
- /**
- * Constructor
- *
- * @param array $contentsMap Array of filename to contents map
- * @param Less_SourceMap_Generator $generator
- */
- public function __construct(array $contentsMap, $generator){
- $this->contentsMap = $contentsMap;
- $this->generator = $generator;
- }
- /**
- * Adds a chunk to the stack
- * The $index for less.php may be different from less.js since less.php does not chunkify inputs
- *
- * @param string $chunk
- * @param string $fileInfo
- * @param integer $index
- * @param mixed $mapLines
- */
- public function add($chunk, $fileInfo = null, $index = 0, $mapLines = null){
- //ignore adding empty strings
- if( $chunk === '' ){
- return;
- }
- $sourceLines = array();
- $sourceColumns = ' ';
- if( $fileInfo ){
- $url = $fileInfo['currentUri'];
- if( isset($this->contentsMap[$url]) ){
- $inputSource = substr($this->contentsMap[$url], 0, $index);
- $sourceLines = explode("\n", $inputSource);
- $sourceColumns = end($sourceLines);
- }else{
- throw new Exception('Filename '.$url.' not in contentsMap');
- }
- }
- $lines = explode("\n", $chunk);
- $columns = end($lines);
- if($fileInfo){
- if(!$mapLines){
- $this->generator->addMapping(
- $this->lineNumber + 1, // generated_line
- $this->column, // generated_column
- count($sourceLines), // original_line
- strlen($sourceColumns), // original_column
- $fileInfo
- );
- }else{
- for($i = 0, $count = count($lines); $i < $count; $i++){
- $this->generator->addMapping(
- $this->lineNumber + $i + 1, // generated_line
- $i === 0 ? $this->column : 0, // generated_column
- count($sourceLines) + $i, // original_line
- $i === 0 ? strlen($sourceColumns) : 0, // original_column
- $fileInfo
- );
- }
- }
- }
- if(count($lines) === 1){
- $this->column += strlen($columns);
- }else{
- $this->lineNumber += count($lines) - 1;
- $this->column = strlen($columns);
- }
- // add only chunk
- parent::add($chunk);
- }
- }
- /**
- * Encode / Decode Base64 VLQ.
- *
- * @package Less
- * @subpackage SourceMap
- */
- class Less_SourceMap_Base64VLQ {
- /**
- * Shift
- *
- * @var integer
- */
- private $shift = 5;
- /**
- * Mask
- *
- * @var integer
- */
- private $mask = 0x1F; // == (1 << shift) == 0b00011111
- /**
- * Continuation bit
- *
- * @var integer
- */
- private $continuationBit = 0x20; // == (mask - 1 ) == 0b00100000
- /**
- * Char to integer map
- *
- * @var array
- */
- private $charToIntMap = array(
- 'A' => 0, 'B' => 1, 'C' => 2, 'D' => 3, 'E' => 4, 'F' => 5, 'G' => 6,
- 'H' => 7,'I' => 8, 'J' => 9, 'K' => 10, 'L' => 11, 'M' => 12, 'N' => 13,
- 'O' => 14, 'P' => 15, 'Q' => 16, 'R' => 17, 'S' => 18, 'T' => 19, 'U' => 20,
- 'V' => 21, 'W' => 22, 'X' => 23, 'Y' => 24, 'Z' => 25, 'a' => 26, 'b' => 27,
- 'c' => 28, 'd' => 29, 'e' => 30, 'f' => 31, 'g' => 32, 'h' => 33, 'i' => 34,
- 'j' => 35, 'k' => 36, 'l' => 37, 'm' => 38, 'n' => 39, 'o' => 40, 'p' => 41,
- 'q' => 42, 'r' => 43, 's' => 44, 't' => 45, 'u' => 46, 'v' => 47, 'w' => 48,
- 'x' => 49, 'y' => 50, 'z' => 51, 0 => 52, 1 => 53, 2 => 54, 3 => 55, 4 => 56,
- 5 => 57, 6 => 58, 7 => 59, 8 => 60, 9 => 61, '+' => 62, '/' => 63,
- );
- /**
- * Integer to char map
- *
- * @var array
- */
- private $intToCharMap = array(
- 0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D', 4 => 'E', 5 => 'F', 6 => 'G',
- 7 => 'H', 8 => 'I', 9 => 'J', 10 => 'K', 11 => 'L', 12 => 'M', 13 => 'N',
- 14 => 'O', 15 => 'P', 16 => 'Q', 17 => 'R', 18 => 'S', 19 => 'T', 20 => 'U',
- 21 => 'V', 22 => 'W', 23 => 'X', 24 => 'Y', 25 => 'Z', 26 => 'a', 27 => 'b',
- 28 => 'c', 29 => 'd', 30 => 'e', 31 => 'f', 32 => 'g', 33 => 'h', 34 => 'i',
- 35 => 'j', 36 => 'k', 37 => 'l', 38 => 'm', 39 => 'n', 40 => 'o', 41 => 'p',
- 42 => 'q', 43 => 'r', 44 => 's', 45 => 't', 46 => 'u', 47 => 'v', 48 => 'w',
- 49 => 'x', 50 => 'y', 51 => 'z', 52 => '0', 53 => '1', 54 => '2', 55 => '3',
- 56 => '4', 57 => '5', 58 => '6', 59 => '7', 60 => '8', 61 => '9', 62 => '+',
- 63 => '/',
- );
- /**
- * Constructor
- */
- public function __construct(){
- // I leave it here for future reference
- // foreach(str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/') as $i => $char)
- // {
- // $this->charToIntMap[$char] = $i;
- // $this->intToCharMap[$i] = $char;
- // }
- }
- /**
- * Convert from a two-complement value to a value where the sign bit is
- * is placed in the least significant bit. For example, as decimals:
- * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
- * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
- * We generate the value for 32 bit machines, hence -2147483648 becomes 1, not 4294967297,
- * even on a 64 bit machine.
- * @param string $aValue
- */
- public function toVLQSigned($aValue){
- return 0xffffffff & ($aValue < 0 ? ((-$aValue) << 1) + 1 : ($aValue << 1) + 0);
- }
- /**
- * Convert to a two-complement value from a value where the sign bit is
- * is placed in the least significant bit. For example, as decimals:
- * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
- * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
- * We assume that the value was generated with a 32 bit machine in mind.
- * Hence
- * 1 becomes -2147483648
- * even on a 64 bit machine.
- * @param integer $aValue
- */
- public function fromVLQSigned($aValue){
- return $aValue & 1 ? $this->zeroFill(~$aValue + 2, 1) | (-1 - 0x7fffffff) : $this->zeroFill($aValue, 1);
- }
- /**
- * Return the base 64 VLQ encoded value.
- *
- * @param string $aValue The value to encode
- * @return string The encoded value
- */
- public function encode($aValue){
- $encoded = '';
- $vlq = $this->toVLQSigned($aValue);
- do
- {
- $digit = $vlq & $this->mask;
- $vlq = $this->zeroFill($vlq, $this->shift);
- if($vlq > 0){
- $digit |= $this->continuationBit;
- }
- $encoded .= $this->base64Encode($digit);
- } while($vlq > 0);
- return $encoded;
- }
- /**
- * Return the value decoded from base 64 VLQ.
- *
- * @param string $encoded The encoded value to decode
- * @return integer The decoded value
- */
- public function decode($encoded){
- $vlq = 0;
- $i = 0;
- do
- {
- $digit = $this->base64Decode($encoded[$i]);
- $vlq |= ($digit & $this->mask) << ($i * $this->shift);
- $i++;
- } while($digit & $this->continuationBit);
- return $this->fromVLQSigned($vlq);
- }
- /**
- * Right shift with zero fill.
- *
- * @param integer $a number to shift
- * @param integer $b number of bits to shift
- * @return integer
- */
- public function zeroFill($a, $b){
- return ($a >= 0) ? ($a >> $b) : ($a >> $b) & (PHP_INT_MAX >> ($b - 1));
- }
- /**
- * Encode single 6-bit digit as base64.
- *
- * @param integer $number
- * @return string
- * @throws Exception If the number is invalid
- */
- public function base64Encode($number){
- if($number < 0 || $number > 63){
- throw new Exception(sprintf('Invalid number "%s" given. Must be between 0 and 63.', $number));
- }
- return $this->intToCharMap[$number];
- }
- /**
- * Decode single 6-bit digit from base64
- *
- * @param string $char
- * @return number
- * @throws Exception If the number is invalid
- */
- public function base64Decode($char){
- if(!array_key_exists($char, $this->charToIntMap)){
- throw new Exception(sprintf('Invalid base 64 digit "%s" given.', $char));
- }
- return $this->charToIntMap[$char];
- }
- }
-
- /**
- * Source map generator
- *
- * @package Less
- * @subpackage Output
- */
- class Less_SourceMap_Generator extends Less_Configurable {
- /**
- * What version of source map does the generator generate?
- */
- const VERSION = 3;
- /**
- * Array of default options
- *
- * @var array
- */
- protected $defaultOptions = array(
- // an optional source root, useful for relocating source files
- // on a server or removing repeated values in the 'sources' entry.
- // This value is prepended to the individual entries in the 'source' field.
- 'sourceRoot' => '',
- // an optional name of the generated code that this source map is associated with.
- 'sourceMapFilename' => null,
- // url of the map
- 'sourceMapURL' => null,
- // absolute path to a file to write the map to
- 'sourceMapWriteTo' => null,
- // output source contents?
- 'outputSourceFiles' => false,
- // base path for filename normalization
- 'sourceMapRootpath' => '',
- // base path for filename normalization
- 'sourceMapBasepath' => ''
- );
- /**
- * The base64 VLQ encoder
- *
- * @var Less_SourceMap_Base64VLQ
- */
- protected $encoder;
- /**
- * Array of mappings
- *
- * @var array
- */
- protected $mappings = array();
- /**
- * The root node
- *
- * @var Less_Tree_Ruleset
- */
- protected $root;
- /**
- * Array of contents map
- *
- * @var array
- */
- protected $contentsMap = array();
- /**
- * File to content map
- *
- * @var array
- */
- protected $sources = array();
- protected $source_keys = array();
- /**
- * Constructor
- *
- * @param Less_Tree_Ruleset $root The root node
- * @param array $options Array of options
- */
- public function __construct(Less_Tree_Ruleset $root, $contentsMap, $options = array()){
- $this->root = $root;
- $this->contentsMap = $contentsMap;
- $this->encoder = new Less_SourceMap_Base64VLQ();
- $this->SetOptions($options);
- // fix windows paths
- if( !empty($this->options['sourceMapRootpath']) ){
- $this->options['sourceMapRootpath'] = str_replace('\\', '/', $this->options['sourceMapRootpath']);
- $this->options['sourceMapRootpath'] = rtrim($this->options['sourceMapRootpath'],'/').'/';
- }
- }
- /**
- * Generates the CSS
- *
- * @return string
- */
- public function generateCSS(){
- $output = new Less_Output_Mapped($this->contentsMap, $this);
- // catch the output
- $this->root->genCSS($output);
- $sourceMapUrl = $this->getOption('sourceMapURL');
- $sourceMapFilename = $this->getOption('sourceMapFilename');
- $sourceMapContent = $this->generateJson();
- $sourceMapWriteTo = $this->getOption('sourceMapWriteTo');
- if( !$sourceMapUrl && $sourceMapFilename ){
- $sourceMapUrl = $this->normalizeFilename($sourceMapFilename);
- }
- // write map to a file
- if( $sourceMapWriteTo ){
- $this->saveMap($sourceMapWriteTo, $sourceMapContent);
- }
- // inline the map
- if( !$sourceMapUrl ){
- $sourceMapUrl = sprintf('data:application/json,%s', Less_Functions::encodeURIComponent($sourceMapContent));
- }
- if( $sourceMapUrl ){
- $output->add( sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl) );
- }
- return $output->toString();
- }
- /**
- * Saves the source map to a file
- *
- * @param string $file The absolute path to a file
- * @param string $content The content to write
- * @throws Exception If the file could not be saved
- */
- protected function saveMap($file, $content){
- $dir = dirname($file);
- // directory does not exist
- if( !is_dir($dir) ){
- // FIXME: create the dir automatically?
- throw new Exception(sprintf('The directory "%s" does not exist. Cannot save the source map.', $dir));
- }
- // FIXME: proper saving, with dir write check!
- if(file_put_contents($file, $content) === false){
- throw new Exception(sprintf('Cannot save the source map to "%s"', $file));
- }
- return true;
- }
- /**
- * Normalizes the filename
- *
- * @param string $filename
- * @return string
- */
- protected function normalizeFilename($filename){
- $filename = str_replace('\\', '/', $filename);
- $rootpath = $this->getOption('sourceMapRootpath');
- $basePath = $this->getOption('sourceMapBasepath');
- // "Trim" the 'sourceMapBasepath' from the output filename.
- if (strpos($filename, $basePath) === 0) {
- $filename = substr($filename, strlen($basePath));
- }
- // Remove extra leading path separators.
- if(strpos($filename, '\\') === 0 || strpos($filename, '/') === 0){
- $filename = substr($filename, 1);
- }
- return $rootpath . $filename;
- }
- /**
- * Adds a mapping
- *
- * @param integer $generatedLine The line number in generated file
- * @param integer $generatedColumn The column number in generated file
- * @param integer $originalLine The line number in original file
- * @param integer $originalColumn The column number in original file
- * @param string $sourceFile The original source file
- */
- public function addMapping($generatedLine, $generatedColumn, $originalLine, $originalColumn, $fileInfo ){
- $this->mappings[] = array(
- 'generated_line' => $generatedLine,
- 'generated_column' => $generatedColumn,
- 'original_line' => $originalLine,
- 'original_column' => $originalColumn,
- 'source_file' => $fileInfo['currentUri']
- );
- $this->sources[$fileInfo['currentUri']] = $fileInfo['filename'];
- }
- /**
- * Generates the JSON source map
- *
- * @return string
- * @see https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#
- */
- protected function generateJson(){
- $sourceMap = array();
- $mappings = $this->generateMappings();
- // File version (always the first entry in the object) and must be a positive integer.
- $sourceMap['version'] = self::VERSION;
- // An optional name of the generated code that this source map is associated with.
- $file = $this->getOption('sourceMapFilename');
- if( $file ){
- $sourceMap['file'] = $file;
- }
- // An optional source root, useful for relocating source files on a server or removing repeated values in the 'sources' entry. This value is prepended to the individual entries in the 'source' field.
- $root = $this->getOption('sourceRoot');
- if( $root ){
- $sourceMap['sourceRoot'] = $root;
- }
- // A list of original sources used by the 'mappings' entry.
- $sourceMap['sources'] = array();
- foreach($this->sources as $source_uri => $source_filename){
- $sourceMap['sources'][] = $this->normalizeFilename($source_filename);
- }
- // A list of symbol names used by the 'mappings' entry.
- $sourceMap['names'] = array();
- // A string with the encoded mapping data.
- $sourceMap['mappings'] = $mappings;
- if( $this->getOption('outputSourceFiles') ){
- // An optional list of source content, useful when the 'source' can't be hosted.
- // The contents are listed in the same order as the sources above.
- // 'null' may be used if some original sources should be retrieved by name.
- $sourceMap['sourcesContent'] = $this->getSourcesContent();
- }
- // less.js compat fixes
- if( count($sourceMap['sources']) && empty($sourceMap['sourceRoot']) ){
- unset($sourceMap['sourceRoot']);
- }
- return json_encode($sourceMap);
- }
- /**
- * Returns the sources contents
- *
- * @return array|null
- */
- protected function getSourcesContent(){
- if(empty($this->sources)){
- return;
- }
- $content = array();
- foreach($this->sources as $sourceFile){
- $content[] = file_get_contents($sourceFile);
- }
- return $content;
- }
- /**
- * Generates the mappings string
- *
- * @return string
- */
- public function generateMappings(){
- if( !count($this->mappings) ){
- return '';
- }
- $this->source_keys = array_flip(array_keys($this->sources));
- // group mappings by generated line number.
- $groupedMap = $groupedMapEncoded = array();
- foreach($this->mappings as $m){
- $groupedMap[$m['generated_line']][] = $m;
- }
- ksort($groupedMap);
- $lastGeneratedLine = $lastOriginalIndex = $lastOriginalLine = $lastOriginalColumn = 0;
- foreach($groupedMap as $lineNumber => $line_map){
- while(++$lastGeneratedLine < $lineNumber){
- $groupedMapEncoded[] = ';';
- }
- $lineMapEncoded = array();
- $lastGeneratedColumn = 0;
- foreach($line_map as $m){
- $mapEncoded = $this->encoder->encode($m['generated_column'] - $lastGeneratedColumn);
- $lastGeneratedColumn = $m['generated_column'];
- // find the index
- if( $m['source_file'] ){
- $index = $this->findFileIndex($m['source_file']);
- if( $index !== false ){
- $mapEncoded .= $this->encoder->encode($index - $lastOriginalIndex);
- $lastOriginalIndex = $index;
- // lines are stored 0-based in SourceMap spec version 3
- $mapEncoded .= $this->encoder->encode($m['original_line'] - 1 - $lastOriginalLine);
- $lastOriginalLine = $m['original_line'] - 1;
- $mapEncoded .= $this->encoder->encode($m['original_column'] - $lastOriginalColumn);
- $lastOriginalColumn = $m['original_column'];
- }
- }
- $lineMapEncoded[] = $mapEncoded;
- }
- $groupedMapEncoded[] = implode(',', $lineMapEncoded) . ';';
- }
- return rtrim(implode($groupedMapEncoded), ';');
- }
- /**
- * Finds the index for the filename
- *
- * @param string $filename
- * @return integer|false
- */
- protected function findFileIndex($filename){
- return $this->source_keys[$filename];
- }
- }
|