Less.php 243 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311
  1. <?php
  2. require_once( dirname(__FILE__).'/Cache.php');
  3. /**
  4. * Class for parsing and compiling less files into css
  5. *
  6. * @package Less
  7. * @subpackage parser
  8. *
  9. */
  10. class Less_Parser{
  11. /**
  12. * Default parser options
  13. */
  14. public static $default_options = array(
  15. 'compress' => false, // option - whether to compress
  16. 'strictUnits' => false, // whether units need to evaluate correctly
  17. 'strictMath' => false, // whether math has to be within parenthesis
  18. 'relativeUrls' => true, // option - whether to adjust URL's to be relative
  19. 'urlArgs' => array(), // whether to add args into url tokens
  20. 'numPrecision' => 8,
  21. 'import_dirs' => array(),
  22. 'import_callback' => null,
  23. 'cache_dir' => null,
  24. 'cache_method' => 'php', // false, 'serialize', 'php', 'var_export', 'callback';
  25. 'cache_callback_get' => null,
  26. 'cache_callback_set' => null,
  27. 'sourceMap' => false, // whether to output a source map
  28. 'sourceMapBasepath' => null,
  29. 'sourceMapWriteTo' => null,
  30. 'sourceMapURL' => null,
  31. 'plugins' => array(),
  32. );
  33. public static $options = array();
  34. private $input; // Less input string
  35. private $input_len; // input string length
  36. private $pos; // current index in `input`
  37. private $saveStack = array(); // holds state for backtracking
  38. private $furthest;
  39. /**
  40. * @var Less_Environment
  41. */
  42. private $env;
  43. private $rules = array();
  44. private static $imports = array();
  45. public static $has_extends = false;
  46. public static $next_id = 0;
  47. /**
  48. * Filename to contents of all parsed the files
  49. *
  50. * @var array
  51. */
  52. public static $contentsMap = array();
  53. /**
  54. * @param Less_Environment|array|null $env
  55. */
  56. public function __construct( $env = null ){
  57. // Top parser on an import tree must be sure there is one "env"
  58. // which will then be passed around by reference.
  59. if( $env instanceof Less_Environment ){
  60. $this->env = $env;
  61. }else{
  62. $this->SetOptions(Less_Parser::$default_options);
  63. $this->Reset( $env );
  64. }
  65. }
  66. /**
  67. * Reset the parser state completely
  68. *
  69. */
  70. public function Reset( $options = null ){
  71. $this->rules = array();
  72. self::$imports = array();
  73. self::$has_extends = false;
  74. self::$imports = array();
  75. self::$contentsMap = array();
  76. $this->env = new Less_Environment($options);
  77. $this->env->Init();
  78. //set new options
  79. if( is_array($options) ){
  80. $this->SetOptions(Less_Parser::$default_options);
  81. $this->SetOptions($options);
  82. }
  83. }
  84. /**
  85. * Set one or more compiler options
  86. * options: import_dirs, cache_dir, cache_method
  87. *
  88. */
  89. public function SetOptions( $options ){
  90. foreach($options as $option => $value){
  91. $this->SetOption($option,$value);
  92. }
  93. }
  94. /**
  95. * Set one compiler option
  96. *
  97. */
  98. public function SetOption($option,$value){
  99. switch($option){
  100. case 'import_dirs':
  101. $this->SetImportDirs($value);
  102. return;
  103. case 'cache_dir':
  104. if( is_string($value) ){
  105. Less_Cache::SetCacheDir($value);
  106. Less_Cache::CheckCacheDir();
  107. }
  108. return;
  109. }
  110. Less_Parser::$options[$option] = $value;
  111. }
  112. /**
  113. * Registers a new custom function
  114. *
  115. * @param string $name function name
  116. * @param callable $callback callback
  117. */
  118. public function registerFunction($name, $callback) {
  119. $this->env->functions[$name] = $callback;
  120. }
  121. /**
  122. * Removed an already registered function
  123. *
  124. * @param string $name function name
  125. */
  126. public function unregisterFunction($name) {
  127. if( isset($this->env->functions[$name]) )
  128. unset($this->env->functions[$name]);
  129. }
  130. /**
  131. * Get the current css buffer
  132. *
  133. * @return string
  134. */
  135. public function getCss(){
  136. $precision = ini_get('precision');
  137. @ini_set('precision',16);
  138. $locale = setlocale(LC_NUMERIC, 0);
  139. setlocale(LC_NUMERIC, "C");
  140. try {
  141. $root = new Less_Tree_Ruleset(array(), $this->rules );
  142. $root->root = true;
  143. $root->firstRoot = true;
  144. $this->PreVisitors($root);
  145. self::$has_extends = false;
  146. $evaldRoot = $root->compile($this->env);
  147. $this->PostVisitors($evaldRoot);
  148. if( Less_Parser::$options['sourceMap'] ){
  149. $generator = new Less_SourceMap_Generator($evaldRoot, Less_Parser::$contentsMap, Less_Parser::$options );
  150. // will also save file
  151. // FIXME: should happen somewhere else?
  152. $css = $generator->generateCSS();
  153. }else{
  154. $css = $evaldRoot->toCSS();
  155. }
  156. if( Less_Parser::$options['compress'] ){
  157. $css = preg_replace('/(^(\s)+)|((\s)+$)/', '', $css);
  158. }
  159. } catch (Exception $exc) {
  160. // Intentional fall-through so we can reset environment
  161. }
  162. //reset php settings
  163. @ini_set('precision',$precision);
  164. setlocale(LC_NUMERIC, $locale);
  165. // Rethrow exception after we handled resetting the environment
  166. if (!empty($exc)) {
  167. throw $exc;
  168. }
  169. return $css;
  170. }
  171. /**
  172. * Run pre-compile visitors
  173. *
  174. */
  175. private function PreVisitors($root){
  176. if( Less_Parser::$options['plugins'] ){
  177. foreach(Less_Parser::$options['plugins'] as $plugin){
  178. if( !empty($plugin->isPreEvalVisitor) ){
  179. $plugin->run($root);
  180. }
  181. }
  182. }
  183. }
  184. /**
  185. * Run post-compile visitors
  186. *
  187. */
  188. private function PostVisitors($evaldRoot){
  189. $visitors = array();
  190. $visitors[] = new Less_Visitor_joinSelector();
  191. if( self::$has_extends ){
  192. $visitors[] = new Less_Visitor_processExtends();
  193. }
  194. $visitors[] = new Less_Visitor_toCSS();
  195. if( Less_Parser::$options['plugins'] ){
  196. foreach(Less_Parser::$options['plugins'] as $plugin){
  197. if( property_exists($plugin,'isPreEvalVisitor') && $plugin->isPreEvalVisitor ){
  198. continue;
  199. }
  200. if( property_exists($plugin,'isPreVisitor') && $plugin->isPreVisitor ){
  201. array_unshift( $visitors, $plugin);
  202. }else{
  203. $visitors[] = $plugin;
  204. }
  205. }
  206. }
  207. for($i = 0; $i < count($visitors); $i++ ){
  208. $visitors[$i]->run($evaldRoot);
  209. }
  210. }
  211. /**
  212. * Parse a Less string into css
  213. *
  214. * @param string $str The string to convert
  215. * @param string $uri_root The url of the file
  216. * @return Less_Tree_Ruleset|Less_Parser
  217. */
  218. public function parse( $str, $file_uri = null ){
  219. if( !$file_uri ){
  220. $uri_root = '';
  221. $filename = 'anonymous-file-'.Less_Parser::$next_id++.'.less';
  222. }else{
  223. $file_uri = self::WinPath($file_uri);
  224. $filename = basename($file_uri);
  225. $uri_root = dirname($file_uri);
  226. }
  227. $previousFileInfo = $this->env->currentFileInfo;
  228. $uri_root = self::WinPath($uri_root);
  229. $this->SetFileInfo($filename, $uri_root);
  230. $this->input = $str;
  231. $this->_parse();
  232. if( $previousFileInfo ){
  233. $this->env->currentFileInfo = $previousFileInfo;
  234. }
  235. return $this;
  236. }
  237. /**
  238. * Parse a Less string from a given file
  239. *
  240. * @throws Less_Exception_Parser
  241. * @param string $filename The file to parse
  242. * @param string $uri_root The url of the file
  243. * @param bool $returnRoot Indicates whether the return value should be a css string a root node
  244. * @return Less_Tree_Ruleset|Less_Parser
  245. */
  246. public function parseFile( $filename, $uri_root = '', $returnRoot = false){
  247. if( !file_exists($filename) ){
  248. $this->Error(sprintf('File `%s` not found.', $filename));
  249. }
  250. // fix uri_root?
  251. // Instead of The mixture of file path for the first argument and directory path for the second argument has bee
  252. if( !$returnRoot && !empty($uri_root) && basename($uri_root) == basename($filename) ){
  253. $uri_root = dirname($uri_root);
  254. }
  255. $previousFileInfo = $this->env->currentFileInfo;
  256. if( $filename ){
  257. $filename = self::WinPath(realpath($filename));
  258. }
  259. $uri_root = self::WinPath($uri_root);
  260. $this->SetFileInfo($filename, $uri_root);
  261. self::AddParsedFile($filename);
  262. if( $returnRoot ){
  263. $rules = $this->GetRules( $filename );
  264. $return = new Less_Tree_Ruleset(array(), $rules );
  265. }else{
  266. $this->_parse( $filename );
  267. $return = $this;
  268. }
  269. if( $previousFileInfo ){
  270. $this->env->currentFileInfo = $previousFileInfo;
  271. }
  272. return $return;
  273. }
  274. /**
  275. * Allows a user to set variables values
  276. * @param array $vars
  277. * @return Less_Parser
  278. */
  279. public function ModifyVars( $vars ){
  280. $this->input = Less_Parser::serializeVars( $vars );
  281. $this->_parse();
  282. return $this;
  283. }
  284. /**
  285. * @param string $filename
  286. */
  287. public function SetFileInfo( $filename, $uri_root = ''){
  288. $filename = Less_Environment::normalizePath($filename);
  289. $dirname = preg_replace('/[^\/\\\\]*$/','',$filename);
  290. if( !empty($uri_root) ){
  291. $uri_root = rtrim($uri_root,'/').'/';
  292. }
  293. $currentFileInfo = array();
  294. //entry info
  295. if( isset($this->env->currentFileInfo) ){
  296. $currentFileInfo['entryPath'] = $this->env->currentFileInfo['entryPath'];
  297. $currentFileInfo['entryUri'] = $this->env->currentFileInfo['entryUri'];
  298. $currentFileInfo['rootpath'] = $this->env->currentFileInfo['rootpath'];
  299. }else{
  300. $currentFileInfo['entryPath'] = $dirname;
  301. $currentFileInfo['entryUri'] = $uri_root;
  302. $currentFileInfo['rootpath'] = $dirname;
  303. }
  304. $currentFileInfo['currentDirectory'] = $dirname;
  305. $currentFileInfo['currentUri'] = $uri_root.basename($filename);
  306. $currentFileInfo['filename'] = $filename;
  307. $currentFileInfo['uri_root'] = $uri_root;
  308. //inherit reference
  309. if( isset($this->env->currentFileInfo['reference']) && $this->env->currentFileInfo['reference'] ){
  310. $currentFileInfo['reference'] = true;
  311. }
  312. $this->env->currentFileInfo = $currentFileInfo;
  313. }
  314. /**
  315. * @deprecated 1.5.1.2
  316. *
  317. */
  318. public function SetCacheDir( $dir ){
  319. if( !file_exists($dir) ){
  320. if( mkdir($dir) ){
  321. return true;
  322. }
  323. throw new Less_Exception_Parser('Less.php cache directory couldn\'t be created: '.$dir);
  324. }elseif( !is_dir($dir) ){
  325. throw new Less_Exception_Parser('Less.php cache directory doesn\'t exist: '.$dir);
  326. }elseif( !is_writable($dir) ){
  327. throw new Less_Exception_Parser('Less.php cache directory isn\'t writable: '.$dir);
  328. }else{
  329. $dir = self::WinPath($dir);
  330. Less_Cache::$cache_dir = rtrim($dir,'/').'/';
  331. return true;
  332. }
  333. }
  334. /**
  335. * Set a list of directories or callbacks the parser should use for determining import paths
  336. *
  337. * @param array $dirs
  338. */
  339. public function SetImportDirs( $dirs ){
  340. Less_Parser::$options['import_dirs'] = array();
  341. foreach($dirs as $path => $uri_root){
  342. $path = self::WinPath($path);
  343. if( !empty($path) ){
  344. $path = rtrim($path,'/').'/';
  345. }
  346. if ( !is_callable($uri_root) ){
  347. $uri_root = self::WinPath($uri_root);
  348. if( !empty($uri_root) ){
  349. $uri_root = rtrim($uri_root,'/').'/';
  350. }
  351. }
  352. Less_Parser::$options['import_dirs'][$path] = $uri_root;
  353. }
  354. }
  355. /**
  356. * @param string $file_path
  357. */
  358. private function _parse( $file_path = null ){
  359. if (ini_get("mbstring.func_overload")) {
  360. $mb_internal_encoding = ini_get("mbstring.internal_encoding");
  361. @ini_set("mbstring.internal_encoding", "ascii");
  362. }
  363. $this->rules = array_merge($this->rules, $this->GetRules( $file_path ));
  364. //reset php settings
  365. if (isset($mb_internal_encoding)) {
  366. @ini_set("mbstring.internal_encoding", $mb_internal_encoding);
  367. }
  368. }
  369. /**
  370. * Return the results of parsePrimary for $file_path
  371. * Use cache and save cached results if possible
  372. *
  373. * @param string|null $file_path
  374. */
  375. private function GetRules( $file_path ){
  376. $this->SetInput($file_path);
  377. $cache_file = $this->CacheFile( $file_path );
  378. if( $cache_file ){
  379. if( Less_Parser::$options['cache_method'] == 'callback' ){
  380. if( is_callable(Less_Parser::$options['cache_callback_get']) ){
  381. $cache = call_user_func_array(
  382. Less_Parser::$options['cache_callback_get'],
  383. array($this, $file_path, $cache_file)
  384. );
  385. if( $cache ){
  386. $this->UnsetInput();
  387. return $cache;
  388. }
  389. }
  390. }elseif( file_exists($cache_file) ){
  391. switch(Less_Parser::$options['cache_method']){
  392. // Using serialize
  393. // Faster but uses more memory
  394. case 'serialize':
  395. $cache = unserialize(file_get_contents($cache_file));
  396. if( $cache ){
  397. touch($cache_file);
  398. $this->UnsetInput();
  399. return $cache;
  400. }
  401. break;
  402. // Using generated php code
  403. case 'var_export':
  404. case 'php':
  405. $this->UnsetInput();
  406. return include($cache_file);
  407. }
  408. }
  409. }
  410. $rules = $this->parsePrimary();
  411. if( $this->pos < $this->input_len ){
  412. throw new Less_Exception_Chunk($this->input, null, $this->furthest, $this->env->currentFileInfo);
  413. }
  414. $this->UnsetInput();
  415. //save the cache
  416. if( $cache_file ){
  417. if( Less_Parser::$options['cache_method'] == 'callback' ){
  418. if( is_callable(Less_Parser::$options['cache_callback_set']) ){
  419. call_user_func_array(
  420. Less_Parser::$options['cache_callback_set'],
  421. array($this, $file_path, $cache_file, $rules)
  422. );
  423. }
  424. }else{
  425. //msg('write cache file');
  426. switch(Less_Parser::$options['cache_method']){
  427. case 'serialize':
  428. file_put_contents( $cache_file, serialize($rules) );
  429. break;
  430. case 'php':
  431. file_put_contents( $cache_file, '<?php return '.self::ArgString($rules).'; ?>' );
  432. break;
  433. case 'var_export':
  434. //Requires __set_state()
  435. file_put_contents( $cache_file, '<?php return '.var_export($rules,true).'; ?>' );
  436. break;
  437. }
  438. Less_Cache::CleanCache();
  439. }
  440. }
  441. return $rules;
  442. }
  443. /**
  444. * Set up the input buffer
  445. *
  446. */
  447. public function SetInput( $file_path ){
  448. if( $file_path ){
  449. $this->input = file_get_contents( $file_path );
  450. }
  451. $this->pos = $this->furthest = 0;
  452. // Remove potential UTF Byte Order Mark
  453. $this->input = preg_replace('/\\G\xEF\xBB\xBF/', '', $this->input);
  454. $this->input_len = strlen($this->input);
  455. if( Less_Parser::$options['sourceMap'] && $this->env->currentFileInfo ){
  456. $uri = $this->env->currentFileInfo['currentUri'];
  457. Less_Parser::$contentsMap[$uri] = $this->input;
  458. }
  459. }
  460. /**
  461. * Free up some memory
  462. *
  463. */
  464. public function UnsetInput(){
  465. unset($this->input, $this->pos, $this->input_len, $this->furthest);
  466. $this->saveStack = array();
  467. }
  468. public function CacheFile( $file_path ){
  469. if( $file_path && $this->CacheEnabled() ){
  470. $env = get_object_vars($this->env);
  471. unset($env['frames']);
  472. $parts = array();
  473. $parts[] = $file_path;
  474. $parts[] = filesize( $file_path );
  475. $parts[] = filemtime( $file_path );
  476. $parts[] = $env;
  477. $parts[] = Less_Version::cache_version;
  478. $parts[] = Less_Parser::$options['cache_method'];
  479. return Less_Cache::$cache_dir . Less_Cache::$prefix . base_convert( sha1(json_encode($parts) ), 16, 36) . '.lesscache';
  480. }
  481. }
  482. static function AddParsedFile($file){
  483. self::$imports[] = $file;
  484. }
  485. static function AllParsedFiles(){
  486. return self::$imports;
  487. }
  488. /**
  489. * @param string $file
  490. */
  491. static function FileParsed($file){
  492. return in_array($file,self::$imports);
  493. }
  494. function save() {
  495. $this->saveStack[] = $this->pos;
  496. }
  497. private function restore() {
  498. $this->pos = array_pop($this->saveStack);
  499. }
  500. private function forget(){
  501. array_pop($this->saveStack);
  502. }
  503. private function isWhitespace($offset = 0) {
  504. return preg_match('/\s/',$this->input[ $this->pos + $offset]);
  505. }
  506. /**
  507. * Parse from a token, regexp or string, and move forward if match
  508. *
  509. * @param array $toks
  510. * @return array
  511. */
  512. private function match($toks){
  513. // The match is confirmed, add the match length to `this::pos`,
  514. // and consume any extra white-space characters (' ' || '\n')
  515. // which come after that. The reason for this is that LeSS's
  516. // grammar is mostly white-space insensitive.
  517. //
  518. foreach($toks as $tok){
  519. $char = $tok[0];
  520. if( $char === '/' ){
  521. $match = $this->MatchReg($tok);
  522. if( $match ){
  523. return count($match) === 1 ? $match[0] : $match;
  524. }
  525. }elseif( $char === '#' ){
  526. $match = $this->MatchChar($tok[1]);
  527. }else{
  528. // Non-terminal, match using a function call
  529. $match = $this->$tok();
  530. }
  531. if( $match ){
  532. return $match;
  533. }
  534. }
  535. }
  536. /**
  537. * @param string[] $toks
  538. *
  539. * @return string
  540. */
  541. private function MatchFuncs($toks){
  542. if( $this->pos < $this->input_len ){
  543. foreach($toks as $tok){
  544. $match = $this->$tok();
  545. if( $match ){
  546. return $match;
  547. }
  548. }
  549. }
  550. }
  551. // Match a single character in the input,
  552. private function MatchChar($tok){
  553. if( ($this->pos < $this->input_len) && ($this->input[$this->pos] === $tok) ){
  554. $this->skipWhitespace(1);
  555. return $tok;
  556. }
  557. }
  558. // Match a regexp from the current start point
  559. private function MatchReg($tok){
  560. if( preg_match($tok, $this->input, $match, 0, $this->pos) ){
  561. $this->skipWhitespace(strlen($match[0]));
  562. return $match;
  563. }
  564. }
  565. /**
  566. * Same as match(), but don't change the state of the parser,
  567. * just return the match.
  568. *
  569. * @param string $tok
  570. * @return integer
  571. */
  572. public function PeekReg($tok){
  573. return preg_match($tok, $this->input, $match, 0, $this->pos);
  574. }
  575. /**
  576. * @param string $tok
  577. */
  578. public function PeekChar($tok){
  579. //return ($this->input[$this->pos] === $tok );
  580. return ($this->pos < $this->input_len) && ($this->input[$this->pos] === $tok );
  581. }
  582. /**
  583. * @param integer $length
  584. */
  585. public function skipWhitespace($length){
  586. $this->pos += $length;
  587. for(; $this->pos < $this->input_len; $this->pos++ ){
  588. $c = $this->input[$this->pos];
  589. if( ($c !== "\n") && ($c !== "\r") && ($c !== "\t") && ($c !== ' ') ){
  590. break;
  591. }
  592. }
  593. }
  594. /**
  595. * @param string $tok
  596. * @param string|null $msg
  597. */
  598. public function expect($tok, $msg = NULL) {
  599. $result = $this->match( array($tok) );
  600. if (!$result) {
  601. $this->Error( $msg ? "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'" : $msg );
  602. } else {
  603. return $result;
  604. }
  605. }
  606. /**
  607. * @param string $tok
  608. */
  609. public function expectChar($tok, $msg = null ){
  610. $result = $this->MatchChar($tok);
  611. if( !$result ){
  612. $this->Error( $msg ? "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'" : $msg );
  613. }else{
  614. return $result;
  615. }
  616. }
  617. //
  618. // Here in, the parsing rules/functions
  619. //
  620. // The basic structure of the syntax tree generated is as follows:
  621. //
  622. // Ruleset -> Rule -> Value -> Expression -> Entity
  623. //
  624. // Here's some LESS code:
  625. //
  626. // .class {
  627. // color: #fff;
  628. // border: 1px solid #000;
  629. // width: @w + 4px;
  630. // > .child {...}
  631. // }
  632. //
  633. // And here's what the parse tree might look like:
  634. //
  635. // Ruleset (Selector '.class', [
  636. // Rule ("color", Value ([Expression [Color #fff]]))
  637. // Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
  638. // Rule ("width", Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]]))
  639. // Ruleset (Selector [Element '>', '.child'], [...])
  640. // ])
  641. //
  642. // In general, most rules will try to parse a token with the `$()` function, and if the return
  643. // value is truly, will return a new node, of the relevant type. Sometimes, we need to check
  644. // first, before parsing, that's when we use `peek()`.
  645. //
  646. //
  647. // The `primary` rule is the *entry* and *exit* point of the parser.
  648. // The rules here can appear at any level of the parse tree.
  649. //
  650. // The recursive nature of the grammar is an interplay between the `block`
  651. // rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule,
  652. // as represented by this simplified grammar:
  653. //
  654. // primary → (ruleset | rule)+
  655. // ruleset → selector+ block
  656. // block → '{' primary '}'
  657. //
  658. // Only at one point is the primary rule not called from the
  659. // block rule: at the root level.
  660. //
  661. private function parsePrimary(){
  662. $root = array();
  663. while( true ){
  664. if( $this->pos >= $this->input_len ){
  665. break;
  666. }
  667. $node = $this->parseExtend(true);
  668. if( $node ){
  669. $root = array_merge($root,$node);
  670. continue;
  671. }
  672. //$node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseDirective'));
  673. $node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseNameValue', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseRulesetCall', 'parseDirective'));
  674. if( $node ){
  675. $root[] = $node;
  676. }elseif( !$this->MatchReg('/\\G[\s\n;]+/') ){
  677. break;
  678. }
  679. if( $this->PeekChar('}') ){
  680. break;
  681. }
  682. }
  683. return $root;
  684. }
  685. // We create a Comment node for CSS comments `/* */`,
  686. // but keep the LeSS comments `//` silent, by just skipping
  687. // over them.
  688. private function parseComment(){
  689. if( $this->input[$this->pos] !== '/' ){
  690. return;
  691. }
  692. if( $this->input[$this->pos+1] === '/' ){
  693. $match = $this->MatchReg('/\\G\/\/.*/');
  694. return $this->NewObj4('Less_Tree_Comment',array($match[0], true, $this->pos, $this->env->currentFileInfo));
  695. }
  696. //$comment = $this->MatchReg('/\\G\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/');
  697. $comment = $this->MatchReg('/\\G\/\*(?s).*?\*+\/\n?/');//not the same as less.js to prevent fatal errors
  698. if( $comment ){
  699. return $this->NewObj4('Less_Tree_Comment',array($comment[0], false, $this->pos, $this->env->currentFileInfo));
  700. }
  701. }
  702. private function parseComments(){
  703. $comments = array();
  704. while( $this->pos < $this->input_len ){
  705. $comment = $this->parseComment();
  706. if( !$comment ){
  707. break;
  708. }
  709. $comments[] = $comment;
  710. }
  711. return $comments;
  712. }
  713. //
  714. // A string, which supports escaping " and '
  715. //
  716. // "milky way" 'he\'s the one!'
  717. //
  718. private function parseEntitiesQuoted() {
  719. $j = $this->pos;
  720. $e = false;
  721. $index = $this->pos;
  722. if( $this->input[$this->pos] === '~' ){
  723. $j++;
  724. $e = true; // Escaped strings
  725. }
  726. if( $this->input[$j] != '"' && $this->input[$j] !== "'" ){
  727. return;
  728. }
  729. if ($e) {
  730. $this->MatchChar('~');
  731. }
  732. // Fix for #124: match escaped newlines
  733. //$str = $this->MatchReg('/\\G"((?:[^"\\\\\r\n]|\\\\.)*)"|\'((?:[^\'\\\\\r\n]|\\\\.)*)\'/');
  734. $str = $this->MatchReg('/\\G"((?:[^"\\\\\r\n]|\\\\.|\\\\\r\n|\\\\[\n\r\f])*)"|\'((?:[^\'\\\\\r\n]|\\\\.|\\\\\r\n|\\\\[\n\r\f])*)\'/');
  735. if( $str ){
  736. $result = $str[0][0] == '"' ? $str[1] : $str[2];
  737. return $this->NewObj5('Less_Tree_Quoted',array($str[0], $result, $e, $index, $this->env->currentFileInfo) );
  738. }
  739. return;
  740. }
  741. //
  742. // A catch-all word, such as:
  743. //
  744. // black border-collapse
  745. //
  746. private function parseEntitiesKeyword(){
  747. //$k = $this->MatchReg('/\\G[_A-Za-z-][_A-Za-z0-9-]*/');
  748. $k = $this->MatchReg('/\\G%|\\G[_A-Za-z-][_A-Za-z0-9-]*/');
  749. if( $k ){
  750. $k = $k[0];
  751. $color = $this->fromKeyword($k);
  752. if( $color ){
  753. return $color;
  754. }
  755. return $this->NewObj1('Less_Tree_Keyword',$k);
  756. }
  757. }
  758. // duplicate of Less_Tree_Color::FromKeyword
  759. private function FromKeyword( $keyword ){
  760. $keyword = strtolower($keyword);
  761. if( Less_Colors::hasOwnProperty($keyword) ){
  762. // detect named color
  763. return $this->NewObj1('Less_Tree_Color',substr(Less_Colors::color($keyword), 1));
  764. }
  765. if( $keyword === 'transparent' ){
  766. return $this->NewObj3('Less_Tree_Color', array( array(0, 0, 0), 0, true));
  767. }
  768. }
  769. //
  770. // A function call
  771. //
  772. // rgb(255, 0, 255)
  773. //
  774. // We also try to catch IE's `alpha()`, but let the `alpha` parser
  775. // deal with the details.
  776. //
  777. // The arguments are parsed with the `entities.arguments` parser.
  778. //
  779. private function parseEntitiesCall(){
  780. $index = $this->pos;
  781. if( !preg_match('/\\G([\w-]+|%|progid:[\w\.]+)\(/', $this->input, $name,0,$this->pos) ){
  782. return;
  783. }
  784. $name = $name[1];
  785. $nameLC = strtolower($name);
  786. if ($nameLC === 'url') {
  787. return null;
  788. }
  789. $this->pos += strlen($name);
  790. if( $nameLC === 'alpha' ){
  791. $alpha_ret = $this->parseAlpha();
  792. if( $alpha_ret ){
  793. return $alpha_ret;
  794. }
  795. }
  796. $this->MatchChar('('); // Parse the '(' and consume whitespace.
  797. $args = $this->parseEntitiesArguments();
  798. if( !$this->MatchChar(')') ){
  799. return;
  800. }
  801. if ($name) {
  802. return $this->NewObj4('Less_Tree_Call',array($name, $args, $index, $this->env->currentFileInfo) );
  803. }
  804. }
  805. /**
  806. * Parse a list of arguments
  807. *
  808. * @return array
  809. */
  810. private function parseEntitiesArguments(){
  811. $args = array();
  812. while( true ){
  813. $arg = $this->MatchFuncs( array('parseEntitiesAssignment','parseExpression') );
  814. if( !$arg ){
  815. break;
  816. }
  817. $args[] = $arg;
  818. if( !$this->MatchChar(',') ){
  819. break;
  820. }
  821. }
  822. return $args;
  823. }
  824. private function parseEntitiesLiteral(){
  825. return $this->MatchFuncs( array('parseEntitiesDimension','parseEntitiesColor','parseEntitiesQuoted','parseUnicodeDescriptor') );
  826. }
  827. // Assignments are argument entities for calls.
  828. // They are present in ie filter properties as shown below.
  829. //
  830. // filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* )
  831. //
  832. private function parseEntitiesAssignment() {
  833. $key = $this->MatchReg('/\\G\w+(?=\s?=)/');
  834. if( !$key ){
  835. return;
  836. }
  837. if( !$this->MatchChar('=') ){
  838. return;
  839. }
  840. $value = $this->parseEntity();
  841. if( $value ){
  842. return $this->NewObj2('Less_Tree_Assignment',array($key[0], $value));
  843. }
  844. }
  845. //
  846. // Parse url() tokens
  847. //
  848. // We use a specific rule for urls, because they don't really behave like
  849. // standard function calls. The difference is that the argument doesn't have
  850. // to be enclosed within a string, so it can't be parsed as an Expression.
  851. //
  852. private function parseEntitiesUrl(){
  853. if( $this->input[$this->pos] !== 'u' || !$this->matchReg('/\\Gurl\(/') ){
  854. return;
  855. }
  856. $value = $this->match( array('parseEntitiesQuoted','parseEntitiesVariable','/\\Gdata\:.*?[^\)]+/','/\\G(?:(?:\\\\[\(\)\'"])|[^\(\)\'"])+/') );
  857. if( !$value ){
  858. $value = '';
  859. }
  860. $this->expectChar(')');
  861. if( isset($value->value) || $value instanceof Less_Tree_Variable ){
  862. return $this->NewObj2('Less_Tree_Url',array($value, $this->env->currentFileInfo));
  863. }
  864. return $this->NewObj2('Less_Tree_Url', array( $this->NewObj1('Less_Tree_Anonymous',$value), $this->env->currentFileInfo) );
  865. }
  866. //
  867. // A Variable entity, such as `@fink`, in
  868. //
  869. // width: @fink + 2px
  870. //
  871. // We use a different parser for variable definitions,
  872. // see `parsers.variable`.
  873. //
  874. private function parseEntitiesVariable(){
  875. $index = $this->pos;
  876. if ($this->PeekChar('@') && ($name = $this->MatchReg('/\\G@@?[\w-]+/'))) {
  877. return $this->NewObj3('Less_Tree_Variable', array( $name[0], $index, $this->env->currentFileInfo));
  878. }
  879. }
  880. // A variable entity useing the protective {} e.g. @{var}
  881. private function parseEntitiesVariableCurly() {
  882. $index = $this->pos;
  883. if( $this->input_len > ($this->pos+1) && $this->input[$this->pos] === '@' && ($curly = $this->MatchReg('/\\G@\{([\w-]+)\}/')) ){
  884. return $this->NewObj3('Less_Tree_Variable',array('@'.$curly[1], $index, $this->env->currentFileInfo));
  885. }
  886. }
  887. //
  888. // A Hexadecimal color
  889. //
  890. // #4F3C2F
  891. //
  892. // `rgb` and `hsl` colors are parsed through the `entities.call` parser.
  893. //
  894. private function parseEntitiesColor(){
  895. if ($this->PeekChar('#') && ($rgb = $this->MatchReg('/\\G#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/'))) {
  896. return $this->NewObj1('Less_Tree_Color',$rgb[1]);
  897. }
  898. }
  899. //
  900. // A Dimension, that is, a number and a unit
  901. //
  902. // 0.5em 95%
  903. //
  904. private function parseEntitiesDimension(){
  905. $c = @ord($this->input[$this->pos]);
  906. //Is the first char of the dimension 0-9, '.', '+' or '-'
  907. if (($c > 57 || $c < 43) || $c === 47 || $c == 44){
  908. return;
  909. }
  910. $value = $this->MatchReg('/\\G([+-]?\d*\.?\d+)(%|[a-z]+)?/');
  911. if( $value ){
  912. if( isset($value[2]) ){
  913. return $this->NewObj2('Less_Tree_Dimension', array($value[1],$value[2]));
  914. }
  915. return $this->NewObj1('Less_Tree_Dimension',$value[1]);
  916. }
  917. }
  918. //
  919. // A unicode descriptor, as is used in unicode-range
  920. //
  921. // U+0?? or U+00A1-00A9
  922. //
  923. function parseUnicodeDescriptor() {
  924. $ud = $this->MatchReg('/\\G(U\+[0-9a-fA-F?]+)(\-[0-9a-fA-F?]+)?/');
  925. if( $ud ){
  926. return $this->NewObj1('Less_Tree_UnicodeDescriptor', $ud[0]);
  927. }
  928. }
  929. //
  930. // JavaScript code to be evaluated
  931. //
  932. // `window.location.href`
  933. //
  934. private function parseEntitiesJavascript(){
  935. $e = false;
  936. $j = $this->pos;
  937. if( $this->input[$j] === '~' ){
  938. $j++;
  939. $e = true;
  940. }
  941. if( $this->input[$j] !== '`' ){
  942. return;
  943. }
  944. if( $e ){
  945. $this->MatchChar('~');
  946. }
  947. $str = $this->MatchReg('/\\G`([^`]*)`/');
  948. if( $str ){
  949. return $this->NewObj3('Less_Tree_Javascript', array($str[1], $this->pos, $e));
  950. }
  951. }
  952. //
  953. // The variable part of a variable definition. Used in the `rule` parser
  954. //
  955. // @fink:
  956. //
  957. private function parseVariable(){
  958. if ($this->PeekChar('@') && ($name = $this->MatchReg('/\\G(@[\w-]+)\s*:/'))) {
  959. return $name[1];
  960. }
  961. }
  962. //
  963. // The variable part of a variable definition. Used in the `rule` parser
  964. //
  965. // @fink();
  966. //
  967. private function parseRulesetCall(){
  968. if( $this->input[$this->pos] === '@' && ($name = $this->MatchReg('/\\G(@[\w-]+)\s*\(\s*\)\s*;/')) ){
  969. return $this->NewObj1('Less_Tree_RulesetCall', $name[1] );
  970. }
  971. }
  972. //
  973. // extend syntax - used to extend selectors
  974. //
  975. function parseExtend($isRule = false){
  976. $index = $this->pos;
  977. $extendList = array();
  978. if( !$this->MatchReg( $isRule ? '/\\G&:extend\(/' : '/\\G:extend\(/' ) ){ return; }
  979. do{
  980. $option = null;
  981. $elements = array();
  982. while( true ){
  983. $option = $this->MatchReg('/\\G(all)(?=\s*(\)|,))/');
  984. if( $option ){ break; }
  985. $e = $this->parseElement();
  986. if( !$e ){ break; }
  987. $elements[] = $e;
  988. }
  989. if( $option ){
  990. $option = $option[1];
  991. }
  992. $extendList[] = $this->NewObj3('Less_Tree_Extend', array( $this->NewObj1('Less_Tree_Selector',$elements), $option, $index ));
  993. }while( $this->MatchChar(",") );
  994. $this->expect('/\\G\)/');
  995. if( $isRule ){
  996. $this->expect('/\\G;/');
  997. }
  998. return $extendList;
  999. }
  1000. //
  1001. // A Mixin call, with an optional argument list
  1002. //
  1003. // #mixins > .square(#fff);
  1004. // .rounded(4px, black);
  1005. // .button;
  1006. //
  1007. // The `while` loop is there because mixins can be
  1008. // namespaced, but we only support the child and descendant
  1009. // selector for now.
  1010. //
  1011. private function parseMixinCall(){
  1012. $char = $this->input[$this->pos];
  1013. if( $char !== '.' && $char !== '#' ){
  1014. return;
  1015. }
  1016. $index = $this->pos;
  1017. $this->save(); // stop us absorbing part of an invalid selector
  1018. $elements = $this->parseMixinCallElements();
  1019. if( $elements ){
  1020. if( $this->MatchChar('(') ){
  1021. $returned = $this->parseMixinArgs(true);
  1022. $args = $returned['args'];
  1023. $this->expectChar(')');
  1024. }else{
  1025. $args = array();
  1026. }
  1027. $important = $this->parseImportant();
  1028. if( $this->parseEnd() ){
  1029. $this->forget();
  1030. return $this->NewObj5('Less_Tree_Mixin_Call', array( $elements, $args, $index, $this->env->currentFileInfo, $important));
  1031. }
  1032. }
  1033. $this->restore();
  1034. }
  1035. private function parseMixinCallElements(){
  1036. $elements = array();
  1037. $c = null;
  1038. while( true ){
  1039. $elemIndex = $this->pos;
  1040. $e = $this->MatchReg('/\\G[#.](?:[\w-]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/');
  1041. if( !$e ){
  1042. break;
  1043. }
  1044. $elements[] = $this->NewObj4('Less_Tree_Element', array($c, $e[0], $elemIndex, $this->env->currentFileInfo));
  1045. $c = $this->MatchChar('>');
  1046. }
  1047. return $elements;
  1048. }
  1049. /**
  1050. * @param boolean $isCall
  1051. */
  1052. private function parseMixinArgs( $isCall ){
  1053. $expressions = array();
  1054. $argsSemiColon = array();
  1055. $isSemiColonSeperated = null;
  1056. $argsComma = array();
  1057. $expressionContainsNamed = null;
  1058. $name = null;
  1059. $returner = array('args'=>array(), 'variadic'=> false);
  1060. $this->save();
  1061. while( true ){
  1062. if( $isCall ){
  1063. $arg = $this->MatchFuncs( array( 'parseDetachedRuleset','parseExpression' ) );
  1064. } else {
  1065. $this->parseComments();
  1066. if( $this->input[ $this->pos ] === '.' && $this->MatchReg('/\\G\.{3}/') ){
  1067. $returner['variadic'] = true;
  1068. if( $this->MatchChar(";") && !$isSemiColonSeperated ){
  1069. $isSemiColonSeperated = true;
  1070. }
  1071. if( $isSemiColonSeperated ){
  1072. $argsSemiColon[] = array('variadic'=>true);
  1073. }else{
  1074. $argsComma[] = array('variadic'=>true);
  1075. }
  1076. break;
  1077. }
  1078. $arg = $this->MatchFuncs( array('parseEntitiesVariable','parseEntitiesLiteral','parseEntitiesKeyword') );
  1079. }
  1080. if( !$arg ){
  1081. break;
  1082. }
  1083. $nameLoop = null;
  1084. if( $arg instanceof Less_Tree_Expression ){
  1085. $arg->throwAwayComments();
  1086. }
  1087. $value = $arg;
  1088. $val = null;
  1089. if( $isCall ){
  1090. // Variable
  1091. if( property_exists($arg,'value') && count($arg->value) == 1 ){
  1092. $val = $arg->value[0];
  1093. }
  1094. } else {
  1095. $val = $arg;
  1096. }
  1097. if( $val instanceof Less_Tree_Variable ){
  1098. if( $this->MatchChar(':') ){
  1099. if( $expressions ){
  1100. if( $isSemiColonSeperated ){
  1101. $this->Error('Cannot mix ; and , as delimiter types');
  1102. }
  1103. $expressionContainsNamed = true;
  1104. }
  1105. // we do not support setting a ruleset as a default variable - it doesn't make sense
  1106. // However if we do want to add it, there is nothing blocking it, just don't error
  1107. // and remove isCall dependency below
  1108. $value = null;
  1109. if( $isCall ){
  1110. $value = $this->parseDetachedRuleset();
  1111. }
  1112. if( !$value ){
  1113. $value = $this->parseExpression();
  1114. }
  1115. if( !$value ){
  1116. if( $isCall ){
  1117. $this->Error('could not understand value for named argument');
  1118. } else {
  1119. $this->restore();
  1120. $returner['args'] = array();
  1121. return $returner;
  1122. }
  1123. }
  1124. $nameLoop = ($name = $val->name);
  1125. }elseif( !$isCall && $this->MatchReg('/\\G\.{3}/') ){
  1126. $returner['variadic'] = true;
  1127. if( $this->MatchChar(";") && !$isSemiColonSeperated ){
  1128. $isSemiColonSeperated = true;
  1129. }
  1130. if( $isSemiColonSeperated ){
  1131. $argsSemiColon[] = array('name'=> $arg->name, 'variadic' => true);
  1132. }else{
  1133. $argsComma[] = array('name'=> $arg->name, 'variadic' => true);
  1134. }
  1135. break;
  1136. }elseif( !$isCall ){
  1137. $name = $nameLoop = $val->name;
  1138. $value = null;
  1139. }
  1140. }
  1141. if( $value ){
  1142. $expressions[] = $value;
  1143. }
  1144. $argsComma[] = array('name'=>$nameLoop, 'value'=>$value );
  1145. if( $this->MatchChar(',') ){
  1146. continue;
  1147. }
  1148. if( $this->MatchChar(';') || $isSemiColonSeperated ){
  1149. if( $expressionContainsNamed ){
  1150. $this->Error('Cannot mix ; and , as delimiter types');
  1151. }
  1152. $isSemiColonSeperated = true;
  1153. if( count($expressions) > 1 ){
  1154. $value = $this->NewObj1('Less_Tree_Value', $expressions);
  1155. }
  1156. $argsSemiColon[] = array('name'=>$name, 'value'=>$value );
  1157. $name = null;
  1158. $expressions = array();
  1159. $expressionContainsNamed = false;
  1160. }
  1161. }
  1162. $this->forget();
  1163. $returner['args'] = ($isSemiColonSeperated ? $argsSemiColon : $argsComma);
  1164. return $returner;
  1165. }
  1166. //
  1167. // A Mixin definition, with a list of parameters
  1168. //
  1169. // .rounded (@radius: 2px, @color) {
  1170. // ...
  1171. // }
  1172. //
  1173. // Until we have a finer grained state-machine, we have to
  1174. // do a look-ahead, to make sure we don't have a mixin call.
  1175. // See the `rule` function for more information.
  1176. //
  1177. // We start by matching `.rounded (`, and then proceed on to
  1178. // the argument list, which has optional default values.
  1179. // We store the parameters in `params`, with a `value` key,
  1180. // if there is a value, such as in the case of `@radius`.
  1181. //
  1182. // Once we've got our params list, and a closing `)`, we parse
  1183. // the `{...}` block.
  1184. //
  1185. private function parseMixinDefinition(){
  1186. $cond = null;
  1187. $char = $this->input[$this->pos];
  1188. if( ($char !== '.' && $char !== '#') || ($char === '{' && $this->PeekReg('/\\G[^{]*\}/')) ){
  1189. return;
  1190. }
  1191. $this->save();
  1192. $match = $this->MatchReg('/\\G([#.](?:[\w-]|\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/');
  1193. if( $match ){
  1194. $name = $match[1];
  1195. $argInfo = $this->parseMixinArgs( false );
  1196. $params = $argInfo['args'];
  1197. $variadic = $argInfo['variadic'];
  1198. // .mixincall("@{a}");
  1199. // looks a bit like a mixin definition..
  1200. // also
  1201. // .mixincall(@a: {rule: set;});
  1202. // so we have to be nice and restore
  1203. if( !$this->MatchChar(')') ){
  1204. $this->furthest = $this->pos;
  1205. $this->restore();
  1206. return;
  1207. }
  1208. $this->parseComments();
  1209. if ($this->MatchReg('/\\Gwhen/')) { // Guard
  1210. $cond = $this->expect('parseConditions', 'Expected conditions');
  1211. }
  1212. $ruleset = $this->parseBlock();
  1213. if( is_array($ruleset) ){
  1214. $this->forget();
  1215. return $this->NewObj5('Less_Tree_Mixin_Definition', array( $name, $params, $ruleset, $cond, $variadic));
  1216. }
  1217. $this->restore();
  1218. }else{
  1219. $this->forget();
  1220. }
  1221. }
  1222. //
  1223. // Entities are the smallest recognized token,
  1224. // and can be found inside a rule's value.
  1225. //
  1226. private function parseEntity(){
  1227. return $this->MatchFuncs( array('parseEntitiesLiteral','parseEntitiesVariable','parseEntitiesUrl','parseEntitiesCall','parseEntitiesKeyword','parseEntitiesJavascript','parseComment') );
  1228. }
  1229. //
  1230. // A Rule terminator. Note that we use `peek()` to check for '}',
  1231. // because the `block` rule will be expecting it, but we still need to make sure
  1232. // it's there, if ';' was ommitted.
  1233. //
  1234. private function parseEnd(){
  1235. return $this->MatchChar(';') || $this->PeekChar('}');
  1236. }
  1237. //
  1238. // IE's alpha function
  1239. //
  1240. // alpha(opacity=88)
  1241. //
  1242. private function parseAlpha(){
  1243. if ( ! $this->MatchReg('/\\G\(opacity=/i')) {
  1244. return;
  1245. }
  1246. $value = $this->MatchReg('/\\G[0-9]+/');
  1247. if( $value ){
  1248. $value = $value[0];
  1249. }else{
  1250. $value = $this->parseEntitiesVariable();
  1251. if( !$value ){
  1252. return;
  1253. }
  1254. }
  1255. $this->expectChar(')');
  1256. return $this->NewObj1('Less_Tree_Alpha',$value);
  1257. }
  1258. //
  1259. // A Selector Element
  1260. //
  1261. // div
  1262. // + h1
  1263. // #socks
  1264. // input[type="text"]
  1265. //
  1266. // Elements are the building blocks for Selectors,
  1267. // they are made out of a `Combinator` (see combinator rule),
  1268. // and an element name, such as a tag a class, or `*`.
  1269. //
  1270. private function parseElement(){
  1271. $c = $this->parseCombinator();
  1272. $index = $this->pos;
  1273. $e = $this->match( array('/\\G(?:\d+\.\d+|\d+)%/', '/\\G(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/',
  1274. '#*', '#&', 'parseAttribute', '/\\G\([^()@]+\)/', '/\\G[\.#](?=@)/', 'parseEntitiesVariableCurly') );
  1275. if( is_null($e) ){
  1276. $this->save();
  1277. if( $this->MatchChar('(') ){
  1278. if( ($v = $this->parseSelector()) && $this->MatchChar(')') ){
  1279. $e = $this->NewObj1('Less_Tree_Paren',$v);
  1280. $this->forget();
  1281. }else{
  1282. $this->restore();
  1283. }
  1284. }else{
  1285. $this->forget();
  1286. }
  1287. }
  1288. if( !is_null($e) ){
  1289. return $this->NewObj4('Less_Tree_Element',array( $c, $e, $index, $this->env->currentFileInfo));
  1290. }
  1291. }
  1292. //
  1293. // Combinators combine elements together, in a Selector.
  1294. //
  1295. // Because our parser isn't white-space sensitive, special care
  1296. // has to be taken, when parsing the descendant combinator, ` `,
  1297. // as it's an empty space. We have to check the previous character
  1298. // in the input, to see if it's a ` ` character.
  1299. //
  1300. private function parseCombinator(){
  1301. if( $this->pos < $this->input_len ){
  1302. $c = $this->input[$this->pos];
  1303. if ($c === '>' || $c === '+' || $c === '~' || $c === '|' || $c === '^' ){
  1304. $this->pos++;
  1305. if( $this->input[$this->pos] === '^' ){
  1306. $c = '^^';
  1307. $this->pos++;
  1308. }
  1309. $this->skipWhitespace(0);
  1310. return $c;
  1311. }
  1312. if( $this->pos > 0 && $this->isWhitespace(-1) ){
  1313. return ' ';
  1314. }
  1315. }
  1316. }
  1317. //
  1318. // A CSS selector (see selector below)
  1319. // with less extensions e.g. the ability to extend and guard
  1320. //
  1321. private function parseLessSelector(){
  1322. return $this->parseSelector(true);
  1323. }
  1324. //
  1325. // A CSS Selector
  1326. //
  1327. // .class > div + h1
  1328. // li a:hover
  1329. //
  1330. // Selectors are made out of one or more Elements, see above.
  1331. //
  1332. private function parseSelector( $isLess = false ){
  1333. $elements = array();
  1334. $extendList = array();
  1335. $condition = null;
  1336. $when = false;
  1337. $extend = false;
  1338. $e = null;
  1339. $c = null;
  1340. $index = $this->pos;
  1341. while( ($isLess && ($extend = $this->parseExtend())) || ($isLess && ($when = $this->MatchReg('/\\Gwhen/') )) || ($e = $this->parseElement()) ){
  1342. if( $when ){
  1343. $condition = $this->expect('parseConditions', 'expected condition');
  1344. }elseif( $condition ){
  1345. //error("CSS guard can only be used at the end of selector");
  1346. }elseif( $extend ){
  1347. $extendList = array_merge($extendList,$extend);
  1348. }else{
  1349. //if( count($extendList) ){
  1350. //error("Extend can only be used at the end of selector");
  1351. //}
  1352. if( $this->pos < $this->input_len ){
  1353. $c = $this->input[ $this->pos ];
  1354. }
  1355. $elements[] = $e;
  1356. $e = null;
  1357. }
  1358. if( $c === '{' || $c === '}' || $c === ';' || $c === ',' || $c === ')') { break; }
  1359. }
  1360. if( $elements ){
  1361. return $this->NewObj5('Less_Tree_Selector',array($elements, $extendList, $condition, $index, $this->env->currentFileInfo));
  1362. }
  1363. if( $extendList ) {
  1364. $this->Error('Extend must be used to extend a selector, it cannot be used on its own');
  1365. }
  1366. }
  1367. private function parseTag(){
  1368. return ( $tag = $this->MatchReg('/\\G[A-Za-z][A-Za-z-]*[0-9]?/') ) ? $tag : $this->MatchChar('*');
  1369. }
  1370. private function parseAttribute(){
  1371. $val = null;
  1372. if( !$this->MatchChar('[') ){
  1373. return;
  1374. }
  1375. $key = $this->parseEntitiesVariableCurly();
  1376. if( !$key ){
  1377. $key = $this->expect('/\\G(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\\\.)+/');
  1378. }
  1379. $op = $this->MatchReg('/\\G[|~*$^]?=/');
  1380. if( $op ){
  1381. $val = $this->match( array('parseEntitiesQuoted','/\\G[0-9]+%/','/\\G[\w-]+/','parseEntitiesVariableCurly') );
  1382. }
  1383. $this->expectChar(']');
  1384. return $this->NewObj3('Less_Tree_Attribute',array( $key, $op[0], $val));
  1385. }
  1386. //
  1387. // The `block` rule is used by `ruleset` and `mixin.definition`.
  1388. // It's a wrapper around the `primary` rule, with added `{}`.
  1389. //
  1390. private function parseBlock(){
  1391. if( $this->MatchChar('{') ){
  1392. $content = $this->parsePrimary();
  1393. if( $this->MatchChar('}') ){
  1394. return $content;
  1395. }
  1396. }
  1397. }
  1398. private function parseBlockRuleset(){
  1399. $block = $this->parseBlock();
  1400. if( $block ){
  1401. $block = $this->NewObj2('Less_Tree_Ruleset',array( null, $block));
  1402. }
  1403. return $block;
  1404. }
  1405. private function parseDetachedRuleset(){
  1406. $blockRuleset = $this->parseBlockRuleset();
  1407. if( $blockRuleset ){
  1408. return $this->NewObj1('Less_Tree_DetachedRuleset',$blockRuleset);
  1409. }
  1410. }
  1411. //
  1412. // div, .class, body > p {...}
  1413. //
  1414. private function parseRuleset(){
  1415. $selectors = array();
  1416. $this->save();
  1417. while( true ){
  1418. $s = $this->parseLessSelector();
  1419. if( !$s ){
  1420. break;
  1421. }
  1422. $selectors[] = $s;
  1423. $this->parseComments();
  1424. if( $s->condition && count($selectors) > 1 ){
  1425. $this->Error('Guards are only currently allowed on a single selector.');
  1426. }
  1427. if( !$this->MatchChar(',') ){
  1428. break;
  1429. }
  1430. if( $s->condition ){
  1431. $this->Error('Guards are only currently allowed on a single selector.');
  1432. }
  1433. $this->parseComments();
  1434. }
  1435. if( $selectors ){
  1436. $rules = $this->parseBlock();
  1437. if( is_array($rules) ){
  1438. $this->forget();
  1439. return $this->NewObj2('Less_Tree_Ruleset',array( $selectors, $rules)); //Less_Environment::$strictImports
  1440. }
  1441. }
  1442. // Backtrack
  1443. $this->furthest = $this->pos;
  1444. $this->restore();
  1445. }
  1446. /**
  1447. * Custom less.php parse function for finding simple name-value css pairs
  1448. * ex: width:100px;
  1449. *
  1450. */
  1451. private function parseNameValue(){
  1452. $index = $this->pos;
  1453. $this->save();
  1454. //$match = $this->MatchReg('/\\G([a-zA-Z\-]+)\s*:\s*((?:\'")?[a-zA-Z0-9\-% \.,!]+?(?:\'")?)\s*([;}])/');
  1455. $match = $this->MatchReg('/\\G([a-zA-Z\-]+)\s*:\s*([\'"]?[#a-zA-Z0-9\-%\.,]+?[\'"]?) *(! *important)?\s*([;}])/');
  1456. if( $match ){
  1457. if( $match[4] == '}' ){
  1458. $this->pos = $index + strlen($match[0])-1;
  1459. }
  1460. if( $match[3] ){
  1461. $match[2] .= ' !important';
  1462. }
  1463. return $this->NewObj4('Less_Tree_NameValue',array( $match[1], $match[2], $index, $this->env->currentFileInfo));
  1464. }
  1465. $this->restore();
  1466. }
  1467. private function parseRule( $tryAnonymous = null ){
  1468. $merge = false;
  1469. $startOfRule = $this->pos;
  1470. $c = $this->input[$this->pos];
  1471. if( $c === '.' || $c === '#' || $c === '&' ){
  1472. return;
  1473. }
  1474. $this->save();
  1475. $name = $this->MatchFuncs( array('parseVariable','parseRuleProperty'));
  1476. if( $name ){
  1477. $isVariable = is_string($name);
  1478. $value = null;
  1479. if( $isVariable ){
  1480. $value = $this->parseDetachedRuleset();
  1481. }
  1482. $important = null;
  1483. if( !$value ){
  1484. // prefer to try to parse first if its a variable or we are compressing
  1485. // but always fallback on the other one
  1486. //if( !$tryAnonymous && is_string($name) && $name[0] === '@' ){
  1487. if( !$tryAnonymous && (Less_Parser::$options['compress'] || $isVariable) ){
  1488. $value = $this->MatchFuncs( array('parseValue','parseAnonymousValue'));
  1489. }else{
  1490. $value = $this->MatchFuncs( array('parseAnonymousValue','parseValue'));
  1491. }
  1492. $important = $this->parseImportant();
  1493. // a name returned by this.ruleProperty() is always an array of the form:
  1494. // [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
  1495. // where each item is a tree.Keyword or tree.Variable
  1496. if( !$isVariable && is_array($name) ){
  1497. $nm = array_pop($name);
  1498. if( $nm->value ){
  1499. $merge = $nm->value;
  1500. }
  1501. }
  1502. }
  1503. if( $value && $this->parseEnd() ){
  1504. $this->forget();
  1505. return $this->NewObj6('Less_Tree_Rule',array( $name, $value, $important, $merge, $startOfRule, $this->env->currentFileInfo));
  1506. }else{
  1507. $this->furthest = $this->pos;
  1508. $this->restore();
  1509. if( $value && !$tryAnonymous ){
  1510. return $this->parseRule(true);
  1511. }
  1512. }
  1513. }else{
  1514. $this->forget();
  1515. }
  1516. }
  1517. function parseAnonymousValue(){
  1518. if( preg_match('/\\G([^@+\/\'"*`(;{}-]*);/',$this->input, $match, 0, $this->pos) ){
  1519. $this->pos += strlen($match[1]);
  1520. return $this->NewObj1('Less_Tree_Anonymous',$match[1]);
  1521. }
  1522. }
  1523. //
  1524. // An @import directive
  1525. //
  1526. // @import "lib";
  1527. //
  1528. // Depending on our environment, importing is done differently:
  1529. // In the browser, it's an XHR request, in Node, it would be a
  1530. // file-system operation. The function used for importing is
  1531. // stored in `import`, which we pass to the Import constructor.
  1532. //
  1533. private function parseImport(){
  1534. $this->save();
  1535. $dir = $this->MatchReg('/\\G@import?\s+/');
  1536. if( $dir ){
  1537. $options = $this->parseImportOptions();
  1538. $path = $this->MatchFuncs( array('parseEntitiesQuoted','parseEntitiesUrl'));
  1539. if( $path ){
  1540. $features = $this->parseMediaFeatures();
  1541. if( $this->MatchChar(';') ){
  1542. if( $features ){
  1543. $features = $this->NewObj1('Less_Tree_Value',$features);
  1544. }
  1545. $this->forget();
  1546. return $this->NewObj5('Less_Tree_Import',array( $path, $features, $options, $this->pos, $this->env->currentFileInfo));
  1547. }
  1548. }
  1549. }
  1550. $this->restore();
  1551. }
  1552. private function parseImportOptions(){
  1553. $options = array();
  1554. // list of options, surrounded by parens
  1555. if( !$this->MatchChar('(') ){
  1556. return $options;
  1557. }
  1558. do{
  1559. $optionName = $this->parseImportOption();
  1560. if( $optionName ){
  1561. $value = true;
  1562. switch( $optionName ){
  1563. case "css":
  1564. $optionName = "less";
  1565. $value = false;
  1566. break;
  1567. case "once":
  1568. $optionName = "multiple";
  1569. $value = false;
  1570. break;
  1571. }
  1572. $options[$optionName] = $value;
  1573. if( !$this->MatchChar(',') ){ break; }
  1574. }
  1575. }while( $optionName );
  1576. $this->expectChar(')');
  1577. return $options;
  1578. }
  1579. private function parseImportOption(){
  1580. $opt = $this->MatchReg('/\\G(less|css|multiple|once|inline|reference)/');
  1581. if( $opt ){
  1582. return $opt[1];
  1583. }
  1584. }
  1585. private function parseMediaFeature() {
  1586. $nodes = array();
  1587. do{
  1588. $e = $this->MatchFuncs(array('parseEntitiesKeyword','parseEntitiesVariable'));
  1589. if( $e ){
  1590. $nodes[] = $e;
  1591. } elseif ($this->MatchChar('(')) {
  1592. $p = $this->parseProperty();
  1593. $e = $this->parseValue();
  1594. if ($this->MatchChar(')')) {
  1595. if ($p && $e) {
  1596. $r = $this->NewObj7('Less_Tree_Rule', array( $p, $e, null, null, $this->pos, $this->env->currentFileInfo, true));
  1597. $nodes[] = $this->NewObj1('Less_Tree_Paren',$r);
  1598. } elseif ($e) {
  1599. $nodes[] = $this->NewObj1('Less_Tree_Paren',$e);
  1600. } else {
  1601. return null;
  1602. }
  1603. } else
  1604. return null;
  1605. }
  1606. } while ($e);
  1607. if ($nodes) {
  1608. return $this->NewObj1('Less_Tree_Expression',$nodes);
  1609. }
  1610. }
  1611. private function parseMediaFeatures() {
  1612. $features = array();
  1613. do{
  1614. $e = $this->parseMediaFeature();
  1615. if( $e ){
  1616. $features[] = $e;
  1617. if (!$this->MatchChar(',')) break;
  1618. }else{
  1619. $e = $this->parseEntitiesVariable();
  1620. if( $e ){
  1621. $features[] = $e;
  1622. if (!$this->MatchChar(',')) break;
  1623. }
  1624. }
  1625. } while ($e);
  1626. return $features ? $features : null;
  1627. }
  1628. private function parseMedia() {
  1629. if( $this->MatchReg('/\\G@media/') ){
  1630. $features = $this->parseMediaFeatures();
  1631. $rules = $this->parseBlock();
  1632. if( is_array($rules) ){
  1633. return $this->NewObj4('Less_Tree_Media',array( $rules, $features, $this->pos, $this->env->currentFileInfo));
  1634. }
  1635. }
  1636. }
  1637. //
  1638. // A CSS Directive
  1639. //
  1640. // @charset "utf-8";
  1641. //
  1642. private function parseDirective(){
  1643. if( !$this->PeekChar('@') ){
  1644. return;
  1645. }
  1646. $rules = null;
  1647. $index = $this->pos;
  1648. $hasBlock = true;
  1649. $hasIdentifier = false;
  1650. $hasExpression = false;
  1651. $hasUnknown = false;
  1652. $value = $this->MatchFuncs(array('parseImport','parseMedia'));
  1653. if( $value ){
  1654. return $value;
  1655. }
  1656. $this->save();
  1657. $name = $this->MatchReg('/\\G@[a-z-]+/');
  1658. if( !$name ) return;
  1659. $name = $name[0];
  1660. $nonVendorSpecificName = $name;
  1661. $pos = strpos($name,'-', 2);
  1662. if( $name[1] == '-' && $pos > 0 ){
  1663. $nonVendorSpecificName = "@" . substr($name, $pos + 1);
  1664. }
  1665. switch( $nonVendorSpecificName ){
  1666. /*
  1667. case "@font-face":
  1668. case "@viewport":
  1669. case "@top-left":
  1670. case "@top-left-corner":
  1671. case "@top-center":
  1672. case "@top-right":
  1673. case "@top-right-corner":
  1674. case "@bottom-left":
  1675. case "@bottom-left-corner":
  1676. case "@bottom-center":
  1677. case "@bottom-right":
  1678. case "@bottom-right-corner":
  1679. case "@left-top":
  1680. case "@left-middle":
  1681. case "@left-bottom":
  1682. case "@right-top":
  1683. case "@right-middle":
  1684. case "@right-bottom":
  1685. hasBlock = true;
  1686. break;
  1687. */
  1688. case "@charset":
  1689. $hasIdentifier = true;
  1690. $hasBlock = false;
  1691. break;
  1692. case "@namespace":
  1693. $hasExpression = true;
  1694. $hasBlock = false;
  1695. break;
  1696. case "@keyframes":
  1697. $hasIdentifier = true;
  1698. break;
  1699. case "@host":
  1700. case "@page":
  1701. case "@document":
  1702. case "@supports":
  1703. $hasUnknown = true;
  1704. break;
  1705. }
  1706. if( $hasIdentifier ){
  1707. $value = $this->parseEntity();
  1708. if( !$value ){
  1709. $this->error("expected " . $name . " identifier");
  1710. }
  1711. } else if( $hasExpression ){
  1712. $value = $this->parseExpression();
  1713. if( !$value ){
  1714. $this->error("expected " . $name. " expression");
  1715. }
  1716. } else if ($hasUnknown) {
  1717. $value = $this->MatchReg('/\\G[^{;]+/');
  1718. if( $value ){
  1719. $value = $this->NewObj1('Less_Tree_Anonymous',trim($value[0]));
  1720. }
  1721. }
  1722. if( $hasBlock ){
  1723. $rules = $this->parseBlockRuleset();
  1724. }
  1725. if( $rules || (!$hasBlock && $value && $this->MatchChar(';'))) {
  1726. $this->forget();
  1727. return $this->NewObj5('Less_Tree_Directive',array($name, $value, $rules, $index, $this->env->currentFileInfo));
  1728. }
  1729. $this->restore();
  1730. }
  1731. //
  1732. // A Value is a comma-delimited list of Expressions
  1733. //
  1734. // font-family: Baskerville, Georgia, serif;
  1735. //
  1736. // In a Rule, a Value represents everything after the `:`,
  1737. // and before the `;`.
  1738. //
  1739. private function parseValue(){
  1740. $expressions = array();
  1741. do{
  1742. $e = $this->parseExpression();
  1743. if( $e ){
  1744. $expressions[] = $e;
  1745. if (! $this->MatchChar(',')) {
  1746. break;
  1747. }
  1748. }
  1749. }while($e);
  1750. if( $expressions ){
  1751. return $this->NewObj1('Less_Tree_Value',$expressions);
  1752. }
  1753. }
  1754. private function parseImportant (){
  1755. if( $this->PeekChar('!') && $this->MatchReg('/\\G! *important/') ){
  1756. return ' !important';
  1757. }
  1758. }
  1759. private function parseSub (){
  1760. if( $this->MatchChar('(') ){
  1761. $a = $this->parseAddition();
  1762. if( $a ){
  1763. $this->expectChar(')');
  1764. return $this->NewObj2('Less_Tree_Expression',array( array($a), true) ); //instead of $e->parens = true so the value is cached
  1765. }
  1766. }
  1767. }
  1768. /**
  1769. * Parses multiplication operation
  1770. *
  1771. * @return Less_Tree_Operation|null
  1772. */
  1773. function parseMultiplication(){
  1774. $return = $m = $this->parseOperand();
  1775. if( $return ){
  1776. while( true ){
  1777. $isSpaced = $this->isWhitespace( -1 );
  1778. if( $this->PeekReg('/\\G\/[*\/]/') ){
  1779. break;
  1780. }
  1781. $op = $this->MatchChar('/');
  1782. if( !$op ){
  1783. $op = $this->MatchChar('*');
  1784. if( !$op ){
  1785. break;
  1786. }
  1787. }
  1788. $a = $this->parseOperand();
  1789. if(!$a) { break; }
  1790. $m->parensInOp = true;
  1791. $a->parensInOp = true;
  1792. $return = $this->NewObj3('Less_Tree_Operation',array( $op, array( $return, $a ), $isSpaced) );
  1793. }
  1794. }
  1795. return $return;
  1796. }
  1797. /**
  1798. * Parses an addition operation
  1799. *
  1800. * @return Less_Tree_Operation|null
  1801. */
  1802. private function parseAddition (){
  1803. $return = $m = $this->parseMultiplication();
  1804. if( $return ){
  1805. while( true ){
  1806. $isSpaced = $this->isWhitespace( -1 );
  1807. $op = $this->MatchReg('/\\G[-+]\s+/');
  1808. if( $op ){
  1809. $op = $op[0];
  1810. }else{
  1811. if( !$isSpaced ){
  1812. $op = $this->match(array('#+','#-'));
  1813. }
  1814. if( !$op ){
  1815. break;
  1816. }
  1817. }
  1818. $a = $this->parseMultiplication();
  1819. if( !$a ){
  1820. break;
  1821. }
  1822. $m->parensInOp = true;
  1823. $a->parensInOp = true;
  1824. $return = $this->NewObj3('Less_Tree_Operation',array($op, array($return, $a), $isSpaced));
  1825. }
  1826. }
  1827. return $return;
  1828. }
  1829. /**
  1830. * Parses the conditions
  1831. *
  1832. * @return Less_Tree_Condition|null
  1833. */
  1834. private function parseConditions() {
  1835. $index = $this->pos;
  1836. $return = $a = $this->parseCondition();
  1837. if( $a ){
  1838. while( true ){
  1839. if( !$this->PeekReg('/\\G,\s*(not\s*)?\(/') || !$this->MatchChar(',') ){
  1840. break;
  1841. }
  1842. $b = $this->parseCondition();
  1843. if( !$b ){
  1844. break;
  1845. }
  1846. $return = $this->NewObj4('Less_Tree_Condition',array('or', $return, $b, $index));
  1847. }
  1848. return $return;
  1849. }
  1850. }
  1851. private function parseCondition() {
  1852. $index = $this->pos;
  1853. $negate = false;
  1854. $c = null;
  1855. if ($this->MatchReg('/\\Gnot/')) $negate = true;
  1856. $this->expectChar('(');
  1857. $a = $this->MatchFuncs(array('parseAddition','parseEntitiesKeyword','parseEntitiesQuoted'));
  1858. if( $a ){
  1859. $op = $this->MatchReg('/\\G(?:>=|<=|=<|[<=>])/');
  1860. if( $op ){
  1861. $b = $this->MatchFuncs(array('parseAddition','parseEntitiesKeyword','parseEntitiesQuoted'));
  1862. if( $b ){
  1863. $c = $this->NewObj5('Less_Tree_Condition',array($op[0], $a, $b, $index, $negate));
  1864. } else {
  1865. $this->Error('Unexpected expression');
  1866. }
  1867. } else {
  1868. $k = $this->NewObj1('Less_Tree_Keyword','true');
  1869. $c = $this->NewObj5('Less_Tree_Condition',array('=', $a, $k, $index, $negate));
  1870. }
  1871. $this->expectChar(')');
  1872. return $this->MatchReg('/\\Gand/') ? $this->NewObj3('Less_Tree_Condition',array('and', $c, $this->parseCondition())) : $c;
  1873. }
  1874. }
  1875. /**
  1876. * An operand is anything that can be part of an operation,
  1877. * such as a Color, or a Variable
  1878. *
  1879. */
  1880. private function parseOperand (){
  1881. $negate = false;
  1882. $offset = $this->pos+1;
  1883. if( $offset >= $this->input_len ){
  1884. return;
  1885. }
  1886. $char = $this->input[$offset];
  1887. if( $char === '@' || $char === '(' ){
  1888. $negate = $this->MatchChar('-');
  1889. }
  1890. $o = $this->MatchFuncs(array('parseSub','parseEntitiesDimension','parseEntitiesColor','parseEntitiesVariable','parseEntitiesCall'));
  1891. if( $negate ){
  1892. $o->parensInOp = true;
  1893. $o = $this->NewObj1('Less_Tree_Negative',$o);
  1894. }
  1895. return $o;
  1896. }
  1897. /**
  1898. * Expressions either represent mathematical operations,
  1899. * or white-space delimited Entities.
  1900. *
  1901. * 1px solid black
  1902. * @var * 2
  1903. *
  1904. * @return Less_Tree_Expression|null
  1905. */
  1906. private function parseExpression (){
  1907. $entities = array();
  1908. do{
  1909. $e = $this->MatchFuncs(array('parseAddition','parseEntity'));
  1910. if( $e ){
  1911. $entities[] = $e;
  1912. // operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here
  1913. if( !$this->PeekReg('/\\G\/[\/*]/') ){
  1914. $delim = $this->MatchChar('/');
  1915. if( $delim ){
  1916. $entities[] = $this->NewObj1('Less_Tree_Anonymous',$delim);
  1917. }
  1918. }
  1919. }
  1920. }while($e);
  1921. if( $entities ){
  1922. return $this->NewObj1('Less_Tree_Expression',$entities);
  1923. }
  1924. }
  1925. /**
  1926. * Parse a property
  1927. * eg: 'min-width', 'orientation', etc
  1928. *
  1929. * @return string
  1930. */
  1931. private function parseProperty (){
  1932. $name = $this->MatchReg('/\\G(\*?-?[_a-zA-Z0-9-]+)\s*:/');
  1933. if( $name ){
  1934. return $name[1];
  1935. }
  1936. }
  1937. /**
  1938. * Parse a rule property
  1939. * eg: 'color', 'width', 'height', etc
  1940. *
  1941. * @return string
  1942. */
  1943. private function parseRuleProperty(){
  1944. $offset = $this->pos;
  1945. $name = array();
  1946. $index = array();
  1947. $length = 0;
  1948. $this->rulePropertyMatch('/\\G(\*?)/', $offset, $length, $index, $name );
  1949. while( $this->rulePropertyMatch('/\\G((?:[\w-]+)|(?:@\{[\w-]+\}))/', $offset, $length, $index, $name )); // !
  1950. if( (count($name) > 1) && $this->rulePropertyMatch('/\\G\s*((?:\+_|\+)?)\s*:/', $offset, $length, $index, $name) ){
  1951. // at last, we have the complete match now. move forward,
  1952. // convert name particles to tree objects and return:
  1953. $this->skipWhitespace($length);
  1954. if( $name[0] === '' ){
  1955. array_shift($name);
  1956. array_shift($index);
  1957. }
  1958. foreach($name as $k => $s ){
  1959. if( !$s || $s[0] !== '@' ){
  1960. $name[$k] = $this->NewObj1('Less_Tree_Keyword',$s);
  1961. }else{
  1962. $name[$k] = $this->NewObj3('Less_Tree_Variable',array('@' . substr($s,2,-1), $index[$k], $this->env->currentFileInfo));
  1963. }
  1964. }
  1965. return $name;
  1966. }
  1967. }
  1968. private function rulePropertyMatch( $re, &$offset, &$length, &$index, &$name ){
  1969. preg_match($re, $this->input, $a, 0, $offset);
  1970. if( $a ){
  1971. $index[] = $this->pos + $length;
  1972. $length += strlen($a[0]);
  1973. $offset += strlen($a[0]);
  1974. $name[] = $a[1];
  1975. return true;
  1976. }
  1977. }
  1978. public static function serializeVars( $vars ){
  1979. $s = '';
  1980. foreach($vars as $name => $value){
  1981. $s .= (($name[0] === '@') ? '' : '@') . $name .': '. $value . ((substr($value,-1) === ';') ? '' : ';');
  1982. }
  1983. return $s;
  1984. }
  1985. /**
  1986. * Some versions of php have trouble with method_exists($a,$b) if $a is not an object
  1987. *
  1988. * @param string $b
  1989. */
  1990. public static function is_method($a,$b){
  1991. return is_object($a) && method_exists($a,$b);
  1992. }
  1993. /**
  1994. * Round numbers similarly to javascript
  1995. * eg: 1.499999 to 1 instead of 2
  1996. *
  1997. */
  1998. public static function round($i, $precision = 0){
  1999. $precision = pow(10,$precision);
  2000. $i = $i*$precision;
  2001. $ceil = ceil($i);
  2002. $floor = floor($i);
  2003. if( ($ceil - $i) <= ($i - $floor) ){
  2004. return $ceil/$precision;
  2005. }else{
  2006. return $floor/$precision;
  2007. }
  2008. }
  2009. /**
  2010. * Create Less_Tree_* objects and optionally generate a cache string
  2011. *
  2012. * @return mixed
  2013. */
  2014. public function NewObj0($class){
  2015. $obj = new $class();
  2016. if( $this->CacheEnabled() ){
  2017. $obj->cache_string = ' new '.$class.'()';
  2018. }
  2019. return $obj;
  2020. }
  2021. public function NewObj1($class, $arg){
  2022. $obj = new $class( $arg );
  2023. if( $this->CacheEnabled() ){
  2024. $obj->cache_string = ' new '.$class.'('.Less_Parser::ArgString($arg).')';
  2025. }
  2026. return $obj;
  2027. }
  2028. public function NewObj2($class, $args){
  2029. $obj = new $class( $args[0], $args[1] );
  2030. if( $this->CacheEnabled() ){
  2031. $this->ObjCache( $obj, $class, $args);
  2032. }
  2033. return $obj;
  2034. }
  2035. public function NewObj3($class, $args){
  2036. $obj = new $class( $args[0], $args[1], $args[2] );
  2037. if( $this->CacheEnabled() ){
  2038. $this->ObjCache( $obj, $class, $args);
  2039. }
  2040. return $obj;
  2041. }
  2042. public function NewObj4($class, $args){
  2043. $obj = new $class( $args[0], $args[1], $args[2], $args[3] );
  2044. if( $this->CacheEnabled() ){
  2045. $this->ObjCache( $obj, $class, $args);
  2046. }
  2047. return $obj;
  2048. }
  2049. public function NewObj5($class, $args){
  2050. $obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4] );
  2051. if( $this->CacheEnabled() ){
  2052. $this->ObjCache( $obj, $class, $args);
  2053. }
  2054. return $obj;
  2055. }
  2056. public function NewObj6($class, $args){
  2057. $obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5] );
  2058. if( $this->CacheEnabled() ){
  2059. $this->ObjCache( $obj, $class, $args);
  2060. }
  2061. return $obj;
  2062. }
  2063. public function NewObj7($class, $args){
  2064. $obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6] );
  2065. if( $this->CacheEnabled() ){
  2066. $this->ObjCache( $obj, $class, $args);
  2067. }
  2068. return $obj;
  2069. }
  2070. //caching
  2071. public function ObjCache($obj, $class, $args=array()){
  2072. $obj->cache_string = ' new '.$class.'('. self::ArgCache($args).')';
  2073. }
  2074. public function ArgCache($args){
  2075. return implode(',',array_map( array('Less_Parser','ArgString'),$args));
  2076. }
  2077. /**
  2078. * Convert an argument to a string for use in the parser cache
  2079. *
  2080. * @return string
  2081. */
  2082. public static function ArgString($arg){
  2083. $type = gettype($arg);
  2084. if( $type === 'object'){
  2085. $string = $arg->cache_string;
  2086. unset($arg->cache_string);
  2087. return $string;
  2088. }elseif( $type === 'array' ){
  2089. $string = ' Array(';
  2090. foreach($arg as $k => $a){
  2091. $string .= var_export($k,true).' => '.self::ArgString($a).',';
  2092. }
  2093. return $string . ')';
  2094. }
  2095. return var_export($arg,true);
  2096. }
  2097. public function Error($msg){
  2098. throw new Less_Exception_Parser($msg, null, $this->furthest, $this->env->currentFileInfo);
  2099. }
  2100. public static function WinPath($path){
  2101. return str_replace('\\', '/', $path);
  2102. }
  2103. public function CacheEnabled(){
  2104. return (Less_Parser::$options['cache_method'] && (Less_Cache::$cache_dir || (Less_Parser::$options['cache_method'] == 'callback')));
  2105. }
  2106. }
  2107. /**
  2108. * Utility for css colors
  2109. *
  2110. * @package Less
  2111. * @subpackage color
  2112. */
  2113. class Less_Colors {
  2114. public static $colors = array(
  2115. 'aliceblue'=>'#f0f8ff',
  2116. 'antiquewhite'=>'#faebd7',
  2117. 'aqua'=>'#00ffff',
  2118. 'aquamarine'=>'#7fffd4',
  2119. 'azure'=>'#f0ffff',
  2120. 'beige'=>'#f5f5dc',
  2121. 'bisque'=>'#ffe4c4',
  2122. 'black'=>'#000000',
  2123. 'blanchedalmond'=>'#ffebcd',
  2124. 'blue'=>'#0000ff',
  2125. 'blueviolet'=>'#8a2be2',
  2126. 'brown'=>'#a52a2a',
  2127. 'burlywood'=>'#deb887',
  2128. 'cadetblue'=>'#5f9ea0',
  2129. 'chartreuse'=>'#7fff00',
  2130. 'chocolate'=>'#d2691e',
  2131. 'coral'=>'#ff7f50',
  2132. 'cornflowerblue'=>'#6495ed',
  2133. 'cornsilk'=>'#fff8dc',
  2134. 'crimson'=>'#dc143c',
  2135. 'cyan'=>'#00ffff',
  2136. 'darkblue'=>'#00008b',
  2137. 'darkcyan'=>'#008b8b',
  2138. 'darkgoldenrod'=>'#b8860b',
  2139. 'darkgray'=>'#a9a9a9',
  2140. 'darkgrey'=>'#a9a9a9',
  2141. 'darkgreen'=>'#006400',
  2142. 'darkkhaki'=>'#bdb76b',
  2143. 'darkmagenta'=>'#8b008b',
  2144. 'darkolivegreen'=>'#556b2f',
  2145. 'darkorange'=>'#ff8c00',
  2146. 'darkorchid'=>'#9932cc',
  2147. 'darkred'=>'#8b0000',
  2148. 'darksalmon'=>'#e9967a',
  2149. 'darkseagreen'=>'#8fbc8f',
  2150. 'darkslateblue'=>'#483d8b',
  2151. 'darkslategray'=>'#2f4f4f',
  2152. 'darkslategrey'=>'#2f4f4f',
  2153. 'darkturquoise'=>'#00ced1',
  2154. 'darkviolet'=>'#9400d3',
  2155. 'deeppink'=>'#ff1493',
  2156. 'deepskyblue'=>'#00bfff',
  2157. 'dimgray'=>'#696969',
  2158. 'dimgrey'=>'#696969',
  2159. 'dodgerblue'=>'#1e90ff',
  2160. 'firebrick'=>'#b22222',
  2161. 'floralwhite'=>'#fffaf0',
  2162. 'forestgreen'=>'#228b22',
  2163. 'fuchsia'=>'#ff00ff',
  2164. 'gainsboro'=>'#dcdcdc',
  2165. 'ghostwhite'=>'#f8f8ff',
  2166. 'gold'=>'#ffd700',
  2167. 'goldenrod'=>'#daa520',
  2168. 'gray'=>'#808080',
  2169. 'grey'=>'#808080',
  2170. 'green'=>'#008000',
  2171. 'greenyellow'=>'#adff2f',
  2172. 'honeydew'=>'#f0fff0',
  2173. 'hotpink'=>'#ff69b4',
  2174. 'indianred'=>'#cd5c5c',
  2175. 'indigo'=>'#4b0082',
  2176. 'ivory'=>'#fffff0',
  2177. 'khaki'=>'#f0e68c',
  2178. 'lavender'=>'#e6e6fa',
  2179. 'lavenderblush'=>'#fff0f5',
  2180. 'lawngreen'=>'#7cfc00',
  2181. 'lemonchiffon'=>'#fffacd',
  2182. 'lightblue'=>'#add8e6',
  2183. 'lightcoral'=>'#f08080',
  2184. 'lightcyan'=>'#e0ffff',
  2185. 'lightgoldenrodyellow'=>'#fafad2',
  2186. 'lightgray'=>'#d3d3d3',
  2187. 'lightgrey'=>'#d3d3d3',
  2188. 'lightgreen'=>'#90ee90',
  2189. 'lightpink'=>'#ffb6c1',
  2190. 'lightsalmon'=>'#ffa07a',
  2191. 'lightseagreen'=>'#20b2aa',
  2192. 'lightskyblue'=>'#87cefa',
  2193. 'lightslategray'=>'#778899',
  2194. 'lightslategrey'=>'#778899',
  2195. 'lightsteelblue'=>'#b0c4de',
  2196. 'lightyellow'=>'#ffffe0',
  2197. 'lime'=>'#00ff00',
  2198. 'limegreen'=>'#32cd32',
  2199. 'linen'=>'#faf0e6',
  2200. 'magenta'=>'#ff00ff',
  2201. 'maroon'=>'#800000',
  2202. 'mediumaquamarine'=>'#66cdaa',
  2203. 'mediumblue'=>'#0000cd',
  2204. 'mediumorchid'=>'#ba55d3',
  2205. 'mediumpurple'=>'#9370d8',
  2206. 'mediumseagreen'=>'#3cb371',
  2207. 'mediumslateblue'=>'#7b68ee',
  2208. 'mediumspringgreen'=>'#00fa9a',
  2209. 'mediumturquoise'=>'#48d1cc',
  2210. 'mediumvioletred'=>'#c71585',
  2211. 'midnightblue'=>'#191970',
  2212. 'mintcream'=>'#f5fffa',
  2213. 'mistyrose'=>'#ffe4e1',
  2214. 'moccasin'=>'#ffe4b5',
  2215. 'navajowhite'=>'#ffdead',
  2216. 'navy'=>'#000080',
  2217. 'oldlace'=>'#fdf5e6',
  2218. 'olive'=>'#808000',
  2219. 'olivedrab'=>'#6b8e23',
  2220. 'orange'=>'#ffa500',
  2221. 'orangered'=>'#ff4500',
  2222. 'orchid'=>'#da70d6',
  2223. 'palegoldenrod'=>'#eee8aa',
  2224. 'palegreen'=>'#98fb98',
  2225. 'paleturquoise'=>'#afeeee',
  2226. 'palevioletred'=>'#d87093',
  2227. 'papayawhip'=>'#ffefd5',
  2228. 'peachpuff'=>'#ffdab9',
  2229. 'peru'=>'#cd853f',
  2230. 'pink'=>'#ffc0cb',
  2231. 'plum'=>'#dda0dd',
  2232. 'powderblue'=>'#b0e0e6',
  2233. 'purple'=>'#800080',
  2234. 'red'=>'#ff0000',
  2235. 'rosybrown'=>'#bc8f8f',
  2236. 'royalblue'=>'#4169e1',
  2237. 'saddlebrown'=>'#8b4513',
  2238. 'salmon'=>'#fa8072',
  2239. 'sandybrown'=>'#f4a460',
  2240. 'seagreen'=>'#2e8b57',
  2241. 'seashell'=>'#fff5ee',
  2242. 'sienna'=>'#a0522d',
  2243. 'silver'=>'#c0c0c0',
  2244. 'skyblue'=>'#87ceeb',
  2245. 'slateblue'=>'#6a5acd',
  2246. 'slategray'=>'#708090',
  2247. 'slategrey'=>'#708090',
  2248. 'snow'=>'#fffafa',
  2249. 'springgreen'=>'#00ff7f',
  2250. 'steelblue'=>'#4682b4',
  2251. 'tan'=>'#d2b48c',
  2252. 'teal'=>'#008080',
  2253. 'thistle'=>'#d8bfd8',
  2254. 'tomato'=>'#ff6347',
  2255. 'turquoise'=>'#40e0d0',
  2256. 'violet'=>'#ee82ee',
  2257. 'wheat'=>'#f5deb3',
  2258. 'white'=>'#ffffff',
  2259. 'whitesmoke'=>'#f5f5f5',
  2260. 'yellow'=>'#ffff00',
  2261. 'yellowgreen'=>'#9acd32'
  2262. );
  2263. public static function hasOwnProperty($color) {
  2264. return isset(self::$colors[$color]);
  2265. }
  2266. public static function color($color) {
  2267. return self::$colors[$color];
  2268. }
  2269. }
  2270. /**
  2271. * Environment
  2272. *
  2273. * @package Less
  2274. * @subpackage environment
  2275. */
  2276. class Less_Environment{
  2277. //public $paths = array(); // option - unmodified - paths to search for imports on
  2278. //public static $files = array(); // list of files that have been imported, used for import-once
  2279. //public $rootpath; // option - rootpath to append to URL's
  2280. //public static $strictImports = null; // option -
  2281. //public $insecure; // option - whether to allow imports from insecure ssl hosts
  2282. //public $processImports; // option - whether to process imports. if false then imports will not be imported
  2283. //public $javascriptEnabled; // option - whether JavaScript is enabled. if undefined, defaults to true
  2284. //public $useFileCache; // browser only - whether to use the per file session cache
  2285. public $currentFileInfo; // information about the current file - for error reporting and importing and making urls relative etc.
  2286. public $importMultiple = false; // whether we are currently importing multiple copies
  2287. /**
  2288. * @var array
  2289. */
  2290. public $frames = array();
  2291. /**
  2292. * @var array
  2293. */
  2294. public $mediaBlocks = array();
  2295. /**
  2296. * @var array
  2297. */
  2298. public $mediaPath = array();
  2299. public static $parensStack = 0;
  2300. public static $tabLevel = 0;
  2301. public static $lastRule = false;
  2302. public static $_outputMap;
  2303. public static $mixin_stack = 0;
  2304. /**
  2305. * @var array
  2306. */
  2307. public $functions = array();
  2308. public function Init(){
  2309. self::$parensStack = 0;
  2310. self::$tabLevel = 0;
  2311. self::$lastRule = false;
  2312. self::$mixin_stack = 0;
  2313. if( Less_Parser::$options['compress'] ){
  2314. Less_Environment::$_outputMap = array(
  2315. ',' => ',',
  2316. ': ' => ':',
  2317. '' => '',
  2318. ' ' => ' ',
  2319. ':' => ' :',
  2320. '+' => '+',
  2321. '~' => '~',
  2322. '>' => '>',
  2323. '|' => '|',
  2324. '^' => '^',
  2325. '^^' => '^^'
  2326. );
  2327. }else{
  2328. Less_Environment::$_outputMap = array(
  2329. ',' => ', ',
  2330. ': ' => ': ',
  2331. '' => '',
  2332. ' ' => ' ',
  2333. ':' => ' :',
  2334. '+' => ' + ',
  2335. '~' => ' ~ ',
  2336. '>' => ' > ',
  2337. '|' => '|',
  2338. '^' => ' ^ ',
  2339. '^^' => ' ^^ '
  2340. );
  2341. }
  2342. }
  2343. public function copyEvalEnv($frames = array() ){
  2344. $new_env = new Less_Environment();
  2345. $new_env->frames = $frames;
  2346. return $new_env;
  2347. }
  2348. public static function isMathOn(){
  2349. return !Less_Parser::$options['strictMath'] || Less_Environment::$parensStack;
  2350. }
  2351. public static function isPathRelative($path){
  2352. return !preg_match('/^(?:[a-z-]+:|\/)/',$path);
  2353. }
  2354. /**
  2355. * Canonicalize a path by resolving references to '/./', '/../'
  2356. * Does not remove leading "../"
  2357. * @param string path or url
  2358. * @return string Canonicalized path
  2359. *
  2360. */
  2361. public static function normalizePath($path){
  2362. $segments = explode('/',$path);
  2363. $segments = array_reverse($segments);
  2364. $path = array();
  2365. $path_len = 0;
  2366. while( $segments ){
  2367. $segment = array_pop($segments);
  2368. switch( $segment ) {
  2369. case '.':
  2370. break;
  2371. case '..':
  2372. if( !$path_len || ( $path[$path_len-1] === '..') ){
  2373. $path[] = $segment;
  2374. $path_len++;
  2375. }else{
  2376. array_pop($path);
  2377. $path_len--;
  2378. }
  2379. break;
  2380. default:
  2381. $path[] = $segment;
  2382. $path_len++;
  2383. break;
  2384. }
  2385. }
  2386. return implode('/',$path);
  2387. }
  2388. public function unshiftFrame($frame){
  2389. array_unshift($this->frames, $frame);
  2390. }
  2391. public function shiftFrame(){
  2392. return array_shift($this->frames);
  2393. }
  2394. }
  2395. /**
  2396. * Builtin functions
  2397. *
  2398. * @package Less
  2399. * @subpackage function
  2400. * @see http://lesscss.org/functions/
  2401. */
  2402. class Less_Functions{
  2403. public $env;
  2404. public $currentFileInfo;
  2405. function __construct($env, $currentFileInfo = null ){
  2406. $this->env = $env;
  2407. $this->currentFileInfo = $currentFileInfo;
  2408. }
  2409. /**
  2410. * @param string $op
  2411. */
  2412. public static function operate( $op, $a, $b ){
  2413. switch ($op) {
  2414. case '+': return $a + $b;
  2415. case '-': return $a - $b;
  2416. case '*': return $a * $b;
  2417. case '/': return $a / $b;
  2418. }
  2419. }
  2420. public static function clamp($val, $max = 1){
  2421. return min( max($val, 0), $max);
  2422. }
  2423. public static function fround( $value ){
  2424. if( $value === 0 ){
  2425. return $value;
  2426. }
  2427. if( Less_Parser::$options['numPrecision'] ){
  2428. $p = pow(10, Less_Parser::$options['numPrecision']);
  2429. return round( $value * $p) / $p;
  2430. }
  2431. return $value;
  2432. }
  2433. public static function number($n){
  2434. if ($n instanceof Less_Tree_Dimension) {
  2435. return floatval( $n->unit->is('%') ? $n->value / 100 : $n->value);
  2436. } else if (is_numeric($n)) {
  2437. return $n;
  2438. } else {
  2439. throw new Less_Exception_Compiler("color functions take numbers as parameters");
  2440. }
  2441. }
  2442. public static function scaled($n, $size = 255 ){
  2443. if( $n instanceof Less_Tree_Dimension && $n->unit->is('%') ){
  2444. return (float)$n->value * $size / 100;
  2445. } else {
  2446. return Less_Functions::number($n);
  2447. }
  2448. }
  2449. public function rgb ($r, $g, $b){
  2450. return $this->rgba($r, $g, $b, 1.0);
  2451. }
  2452. public function rgba($r, $g, $b, $a){
  2453. $rgb = array($r, $g, $b);
  2454. $rgb = array_map(array('Less_Functions','scaled'),$rgb);
  2455. $a = self::number($a);
  2456. return new Less_Tree_Color($rgb, $a);
  2457. }
  2458. public function hsl($h, $s, $l){
  2459. return $this->hsla($h, $s, $l, 1.0);
  2460. }
  2461. public function hsla($h, $s, $l, $a){
  2462. $h = fmod(self::number($h), 360) / 360; // Classic % operator will change float to int
  2463. $s = self::clamp(self::number($s));
  2464. $l = self::clamp(self::number($l));
  2465. $a = self::clamp(self::number($a));
  2466. $m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s;
  2467. $m1 = $l * 2 - $m2;
  2468. return $this->rgba( self::hsla_hue($h + 1/3, $m1, $m2) * 255,
  2469. self::hsla_hue($h, $m1, $m2) * 255,
  2470. self::hsla_hue($h - 1/3, $m1, $m2) * 255,
  2471. $a);
  2472. }
  2473. /**
  2474. * @param double $h
  2475. */
  2476. public function hsla_hue($h, $m1, $m2){
  2477. $h = $h < 0 ? $h + 1 : ($h > 1 ? $h - 1 : $h);
  2478. if ($h * 6 < 1) return $m1 + ($m2 - $m1) * $h * 6;
  2479. else if ($h * 2 < 1) return $m2;
  2480. else if ($h * 3 < 2) return $m1 + ($m2 - $m1) * (2/3 - $h) * 6;
  2481. else return $m1;
  2482. }
  2483. public function hsv($h, $s, $v) {
  2484. return $this->hsva($h, $s, $v, 1.0);
  2485. }
  2486. /**
  2487. * @param double $a
  2488. */
  2489. public function hsva($h, $s, $v, $a) {
  2490. $h = ((Less_Functions::number($h) % 360) / 360 ) * 360;
  2491. $s = Less_Functions::number($s);
  2492. $v = Less_Functions::number($v);
  2493. $a = Less_Functions::number($a);
  2494. $i = floor(($h / 60) % 6);
  2495. $f = ($h / 60) - $i;
  2496. $vs = array( $v,
  2497. $v * (1 - $s),
  2498. $v * (1 - $f * $s),
  2499. $v * (1 - (1 - $f) * $s));
  2500. $perm = array(array(0, 3, 1),
  2501. array(2, 0, 1),
  2502. array(1, 0, 3),
  2503. array(1, 2, 0),
  2504. array(3, 1, 0),
  2505. array(0, 1, 2));
  2506. return $this->rgba($vs[$perm[$i][0]] * 255,
  2507. $vs[$perm[$i][1]] * 255,
  2508. $vs[$perm[$i][2]] * 255,
  2509. $a);
  2510. }
  2511. public function hue($color){
  2512. $c = $color->toHSL();
  2513. return new Less_Tree_Dimension(Less_Parser::round($c['h']));
  2514. }
  2515. public function saturation($color){
  2516. $c = $color->toHSL();
  2517. return new Less_Tree_Dimension(Less_Parser::round($c['s'] * 100), '%');
  2518. }
  2519. public function lightness($color){
  2520. $c = $color->toHSL();
  2521. return new Less_Tree_Dimension(Less_Parser::round($c['l'] * 100), '%');
  2522. }
  2523. public function hsvhue( $color ){
  2524. $hsv = $color->toHSV();
  2525. return new Less_Tree_Dimension( Less_Parser::round($hsv['h']) );
  2526. }
  2527. public function hsvsaturation( $color ){
  2528. $hsv = $color->toHSV();
  2529. return new Less_Tree_Dimension( Less_Parser::round($hsv['s'] * 100), '%' );
  2530. }
  2531. public function hsvvalue( $color ){
  2532. $hsv = $color->toHSV();
  2533. return new Less_Tree_Dimension( Less_Parser::round($hsv['v'] * 100), '%' );
  2534. }
  2535. public function red($color) {
  2536. return new Less_Tree_Dimension( $color->rgb[0] );
  2537. }
  2538. public function green($color) {
  2539. return new Less_Tree_Dimension( $color->rgb[1] );
  2540. }
  2541. public function blue($color) {
  2542. return new Less_Tree_Dimension( $color->rgb[2] );
  2543. }
  2544. public function alpha($color){
  2545. $c = $color->toHSL();
  2546. return new Less_Tree_Dimension($c['a']);
  2547. }
  2548. public function luma ($color) {
  2549. return new Less_Tree_Dimension(Less_Parser::round( $color->luma() * $color->alpha * 100), '%');
  2550. }
  2551. public function luminance( $color ){
  2552. $luminance =
  2553. (0.2126 * $color->rgb[0] / 255)
  2554. + (0.7152 * $color->rgb[1] / 255)
  2555. + (0.0722 * $color->rgb[2] / 255);
  2556. return new Less_Tree_Dimension(Less_Parser::round( $luminance * $color->alpha * 100), '%');
  2557. }
  2558. public function saturate($color, $amount = null){
  2559. // filter: saturate(3.2);
  2560. // should be kept as is, so check for color
  2561. if( !property_exists($color,'rgb') ){
  2562. return null;
  2563. }
  2564. $hsl = $color->toHSL();
  2565. $hsl['s'] += $amount->value / 100;
  2566. $hsl['s'] = self::clamp($hsl['s']);
  2567. return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
  2568. }
  2569. /**
  2570. * @param Less_Tree_Dimension $amount
  2571. */
  2572. public function desaturate($color, $amount){
  2573. $hsl = $color->toHSL();
  2574. $hsl['s'] -= $amount->value / 100;
  2575. $hsl['s'] = self::clamp($hsl['s']);
  2576. return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
  2577. }
  2578. public function lighten($color, $amount){
  2579. $hsl = $color->toHSL();
  2580. $hsl['l'] += $amount->value / 100;
  2581. $hsl['l'] = self::clamp($hsl['l']);
  2582. return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
  2583. }
  2584. public function darken($color, $amount){
  2585. if( $color instanceof Less_Tree_Color ){
  2586. $hsl = $color->toHSL();
  2587. $hsl['l'] -= $amount->value / 100;
  2588. $hsl['l'] = self::clamp($hsl['l']);
  2589. return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
  2590. }
  2591. Less_Functions::Expected('color',$color);
  2592. }
  2593. public function fadein($color, $amount){
  2594. $hsl = $color->toHSL();
  2595. $hsl['a'] += $amount->value / 100;
  2596. $hsl['a'] = self::clamp($hsl['a']);
  2597. return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
  2598. }
  2599. public function fadeout($color, $amount){
  2600. $hsl = $color->toHSL();
  2601. $hsl['a'] -= $amount->value / 100;
  2602. $hsl['a'] = self::clamp($hsl['a']);
  2603. return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
  2604. }
  2605. public function fade($color, $amount){
  2606. $hsl = $color->toHSL();
  2607. $hsl['a'] = $amount->value / 100;
  2608. $hsl['a'] = self::clamp($hsl['a']);
  2609. return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
  2610. }
  2611. public function spin($color, $amount){
  2612. $hsl = $color->toHSL();
  2613. $hue = fmod($hsl['h'] + $amount->value, 360);
  2614. $hsl['h'] = $hue < 0 ? 360 + $hue : $hue;
  2615. return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
  2616. }
  2617. //
  2618. // Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
  2619. // http://sass-lang.com
  2620. //
  2621. /**
  2622. * @param Less_Tree_Color $color1
  2623. */
  2624. public function mix($color1, $color2, $weight = null){
  2625. if (!$weight) {
  2626. $weight = new Less_Tree_Dimension('50', '%');
  2627. }
  2628. $p = $weight->value / 100.0;
  2629. $w = $p * 2 - 1;
  2630. $hsl1 = $color1->toHSL();
  2631. $hsl2 = $color2->toHSL();
  2632. $a = $hsl1['a'] - $hsl2['a'];
  2633. $w1 = (((($w * $a) == -1) ? $w : ($w + $a) / (1 + $w * $a)) + 1) / 2;
  2634. $w2 = 1 - $w1;
  2635. $rgb = array($color1->rgb[0] * $w1 + $color2->rgb[0] * $w2,
  2636. $color1->rgb[1] * $w1 + $color2->rgb[1] * $w2,
  2637. $color1->rgb[2] * $w1 + $color2->rgb[2] * $w2);
  2638. $alpha = $color1->alpha * $p + $color2->alpha * (1 - $p);
  2639. return new Less_Tree_Color($rgb, $alpha);
  2640. }
  2641. public function greyscale($color){
  2642. return $this->desaturate($color, new Less_Tree_Dimension(100));
  2643. }
  2644. public function contrast( $color, $dark = null, $light = null, $threshold = null){
  2645. // filter: contrast(3.2);
  2646. // should be kept as is, so check for color
  2647. if( !property_exists($color,'rgb') ){
  2648. return null;
  2649. }
  2650. if( !$light ){
  2651. $light = $this->rgba(255, 255, 255, 1.0);
  2652. }
  2653. if( !$dark ){
  2654. $dark = $this->rgba(0, 0, 0, 1.0);
  2655. }
  2656. //Figure out which is actually light and dark!
  2657. if( $dark->luma() > $light->luma() ){
  2658. $t = $light;
  2659. $light = $dark;
  2660. $dark = $t;
  2661. }
  2662. if( !$threshold ){
  2663. $threshold = 0.43;
  2664. } else {
  2665. $threshold = Less_Functions::number($threshold);
  2666. }
  2667. if( $color->luma() < $threshold ){
  2668. return $light;
  2669. } else {
  2670. return $dark;
  2671. }
  2672. }
  2673. public function e ($str){
  2674. if( is_string($str) ){
  2675. return new Less_Tree_Anonymous($str);
  2676. }
  2677. return new Less_Tree_Anonymous($str instanceof Less_Tree_JavaScript ? $str->expression : $str->value);
  2678. }
  2679. public function escape ($str){
  2680. $revert = array('%21'=>'!', '%2A'=>'*', '%27'=>"'",'%3F'=>'?','%26'=>'&','%2C'=>',','%2F'=>'/','%40'=>'@','%2B'=>'+','%24'=>'$');
  2681. return new Less_Tree_Anonymous(strtr(rawurlencode($str->value), $revert));
  2682. }
  2683. /**
  2684. * todo: This function will need some additional work to make it work the same as less.js
  2685. *
  2686. */
  2687. public function replace( $string, $pattern, $replacement, $flags = null ){
  2688. $result = $string->value;
  2689. $expr = '/'.str_replace('/','\\/',$pattern->value).'/';
  2690. if( $flags && $flags->value){
  2691. $expr .= self::replace_flags($flags->value);
  2692. }
  2693. $result = preg_replace($expr,$replacement->value,$result);
  2694. if( property_exists($string,'quote') ){
  2695. return new Less_Tree_Quoted( $string->quote, $result, $string->escaped);
  2696. }
  2697. return new Less_Tree_Quoted( '', $result );
  2698. }
  2699. public static function replace_flags($flags){
  2700. $flags = str_split($flags,1);
  2701. $new_flags = '';
  2702. foreach($flags as $flag){
  2703. switch($flag){
  2704. case 'e':
  2705. case 'g':
  2706. break;
  2707. default:
  2708. $new_flags .= $flag;
  2709. break;
  2710. }
  2711. }
  2712. return $new_flags;
  2713. }
  2714. public function _percent(){
  2715. $string = func_get_arg(0);
  2716. $args = func_get_args();
  2717. array_shift($args);
  2718. $result = $string->value;
  2719. foreach($args as $arg){
  2720. if( preg_match('/%[sda]/i',$result, $token) ){
  2721. $token = $token[0];
  2722. $value = stristr($token, 's') ? $arg->value : $arg->toCSS();
  2723. $value = preg_match('/[A-Z]$/', $token) ? urlencode($value) : $value;
  2724. $result = preg_replace('/%[sda]/i',$value, $result, 1);
  2725. }
  2726. }
  2727. $result = str_replace('%%', '%', $result);
  2728. return new Less_Tree_Quoted( $string->quote , $result, $string->escaped);
  2729. }
  2730. public function unit( $val, $unit = null) {
  2731. if( !($val instanceof Less_Tree_Dimension) ){
  2732. throw new Less_Exception_Compiler('The first argument to unit must be a number' . ($val instanceof Less_Tree_Operation ? '. Have you forgotten parenthesis?' : '.') );
  2733. }
  2734. if( $unit ){
  2735. if( $unit instanceof Less_Tree_Keyword ){
  2736. $unit = $unit->value;
  2737. } else {
  2738. $unit = $unit->toCSS();
  2739. }
  2740. } else {
  2741. $unit = "";
  2742. }
  2743. return new Less_Tree_Dimension($val->value, $unit );
  2744. }
  2745. public function convert($val, $unit){
  2746. return $val->convertTo($unit->value);
  2747. }
  2748. public function round($n, $f = false) {
  2749. $fraction = 0;
  2750. if( $f !== false ){
  2751. $fraction = $f->value;
  2752. }
  2753. return $this->_math('Less_Parser::round',null, $n, $fraction);
  2754. }
  2755. public function pi(){
  2756. return new Less_Tree_Dimension(M_PI);
  2757. }
  2758. public function mod($a, $b) {
  2759. return new Less_Tree_Dimension( $a->value % $b->value, $a->unit);
  2760. }
  2761. public function pow($x, $y) {
  2762. if( is_numeric($x) && is_numeric($y) ){
  2763. $x = new Less_Tree_Dimension($x);
  2764. $y = new Less_Tree_Dimension($y);
  2765. }elseif( !($x instanceof Less_Tree_Dimension) || !($y instanceof Less_Tree_Dimension) ){
  2766. throw new Less_Exception_Compiler('Arguments must be numbers');
  2767. }
  2768. return new Less_Tree_Dimension( pow($x->value, $y->value), $x->unit );
  2769. }
  2770. // var mathFunctions = [{name:"ce ...
  2771. public function ceil( $n ){ return $this->_math('ceil', null, $n); }
  2772. public function floor( $n ){ return $this->_math('floor', null, $n); }
  2773. public function sqrt( $n ){ return $this->_math('sqrt', null, $n); }
  2774. public function abs( $n ){ return $this->_math('abs', null, $n); }
  2775. public function tan( $n ){ return $this->_math('tan', '', $n); }
  2776. public function sin( $n ){ return $this->_math('sin', '', $n); }
  2777. public function cos( $n ){ return $this->_math('cos', '', $n); }
  2778. public function atan( $n ){ return $this->_math('atan', 'rad', $n); }
  2779. public function asin( $n ){ return $this->_math('asin', 'rad', $n); }
  2780. public function acos( $n ){ return $this->_math('acos', 'rad', $n); }
  2781. private function _math() {
  2782. $args = func_get_args();
  2783. $fn = array_shift($args);
  2784. $unit = array_shift($args);
  2785. if ($args[0] instanceof Less_Tree_Dimension) {
  2786. if( $unit === null ){
  2787. $unit = $args[0]->unit;
  2788. }else{
  2789. $args[0] = $args[0]->unify();
  2790. }
  2791. $args[0] = (float)$args[0]->value;
  2792. return new Less_Tree_Dimension( call_user_func_array($fn, $args), $unit);
  2793. } else if (is_numeric($args[0])) {
  2794. return call_user_func_array($fn,$args);
  2795. } else {
  2796. throw new Less_Exception_Compiler("math functions take numbers as parameters");
  2797. }
  2798. }
  2799. /**
  2800. * @param boolean $isMin
  2801. */
  2802. private function _minmax( $isMin, $args ){
  2803. $arg_count = count($args);
  2804. if( $arg_count < 1 ){
  2805. throw new Less_Exception_Compiler( 'one or more arguments required');
  2806. }
  2807. $j = null;
  2808. $unitClone = null;
  2809. $unitStatic = null;
  2810. $order = array(); // elems only contains original argument values.
  2811. $values = array(); // key is the unit.toString() for unified tree.Dimension values,
  2812. // value is the index into the order array.
  2813. for( $i = 0; $i < $arg_count; $i++ ){
  2814. $current = $args[$i];
  2815. if( !($current instanceof Less_Tree_Dimension) ){
  2816. if( is_array($args[$i]->value) ){
  2817. $args[] = $args[$i]->value;
  2818. }
  2819. continue;
  2820. }
  2821. if( $current->unit->toString() === '' && !$unitClone ){
  2822. $temp = new Less_Tree_Dimension($current->value, $unitClone);
  2823. $currentUnified = $temp->unify();
  2824. }else{
  2825. $currentUnified = $current->unify();
  2826. }
  2827. if( $currentUnified->unit->toString() === "" && !$unitStatic ){
  2828. $unit = $unitStatic;
  2829. }else{
  2830. $unit = $currentUnified->unit->toString();
  2831. }
  2832. if( $unit !== '' && !$unitStatic || $unit !== '' && $order[0]->unify()->unit->toString() === "" ){
  2833. $unitStatic = $unit;
  2834. }
  2835. if( $unit != '' && !$unitClone ){
  2836. $unitClone = $current->unit->toString();
  2837. }
  2838. if( isset($values['']) && $unit !== '' && $unit === $unitStatic ){
  2839. $j = $values[''];
  2840. }elseif( isset($values[$unit]) ){
  2841. $j = $values[$unit];
  2842. }else{
  2843. if( $unitStatic && $unit !== $unitStatic ){
  2844. throw new Less_Exception_Compiler( 'incompatible types');
  2845. }
  2846. $values[$unit] = count($order);
  2847. $order[] = $current;
  2848. continue;
  2849. }
  2850. if( $order[$j]->unit->toString() === "" && $unitClone ){
  2851. $temp = new Less_Tree_Dimension( $order[$j]->value, $unitClone);
  2852. $referenceUnified = $temp->unifiy();
  2853. }else{
  2854. $referenceUnified = $order[$j]->unify();
  2855. }
  2856. if( ($isMin && $currentUnified->value < $referenceUnified->value) || (!$isMin && $currentUnified->value > $referenceUnified->value) ){
  2857. $order[$j] = $current;
  2858. }
  2859. }
  2860. if( count($order) == 1 ){
  2861. return $order[0];
  2862. }
  2863. $args = array();
  2864. foreach($order as $a){
  2865. $args[] = $a->toCSS($this->env);
  2866. }
  2867. return new Less_Tree_Anonymous( ($isMin?'min(':'max(') . implode(Less_Environment::$_outputMap[','],$args).')');
  2868. }
  2869. public function min(){
  2870. $args = func_get_args();
  2871. return $this->_minmax( true, $args );
  2872. }
  2873. public function max(){
  2874. $args = func_get_args();
  2875. return $this->_minmax( false, $args );
  2876. }
  2877. public function getunit($n){
  2878. return new Less_Tree_Anonymous($n->unit);
  2879. }
  2880. public function argb($color) {
  2881. return new Less_Tree_Anonymous($color->toARGB());
  2882. }
  2883. public function percentage($n) {
  2884. return new Less_Tree_Dimension($n->value * 100, '%');
  2885. }
  2886. public function color($n) {
  2887. if( $n instanceof Less_Tree_Quoted ){
  2888. $colorCandidate = $n->value;
  2889. $returnColor = Less_Tree_Color::fromKeyword($colorCandidate);
  2890. if( $returnColor ){
  2891. return $returnColor;
  2892. }
  2893. if( preg_match('/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/',$colorCandidate) ){
  2894. return new Less_Tree_Color(substr($colorCandidate, 1));
  2895. }
  2896. throw new Less_Exception_Compiler("argument must be a color keyword or 3/6 digit hex e.g. #FFF");
  2897. } else {
  2898. throw new Less_Exception_Compiler("argument must be a string");
  2899. }
  2900. }
  2901. public function iscolor($n) {
  2902. return $this->_isa($n, 'Less_Tree_Color');
  2903. }
  2904. public function isnumber($n) {
  2905. return $this->_isa($n, 'Less_Tree_Dimension');
  2906. }
  2907. public function isstring($n) {
  2908. return $this->_isa($n, 'Less_Tree_Quoted');
  2909. }
  2910. public function iskeyword($n) {
  2911. return $this->_isa($n, 'Less_Tree_Keyword');
  2912. }
  2913. public function isurl($n) {
  2914. return $this->_isa($n, 'Less_Tree_Url');
  2915. }
  2916. public function ispixel($n) {
  2917. return $this->isunit($n, 'px');
  2918. }
  2919. public function ispercentage($n) {
  2920. return $this->isunit($n, '%');
  2921. }
  2922. public function isem($n) {
  2923. return $this->isunit($n, 'em');
  2924. }
  2925. /**
  2926. * @param string $unit
  2927. */
  2928. public function isunit( $n, $unit ){
  2929. 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');
  2930. }
  2931. /**
  2932. * @param string $type
  2933. */
  2934. private function _isa($n, $type) {
  2935. return is_a($n, $type) ? new Less_Tree_Keyword('true') : new Less_Tree_Keyword('false');
  2936. }
  2937. public function tint($color, $amount) {
  2938. return $this->mix( $this->rgb(255,255,255), $color, $amount);
  2939. }
  2940. public function shade($color, $amount) {
  2941. return $this->mix($this->rgb(0, 0, 0), $color, $amount);
  2942. }
  2943. public function extract($values, $index ){
  2944. $index = (int)$index->value - 1; // (1-based index)
  2945. // handle non-array values as an array of length 1
  2946. // return 'undefined' if index is invalid
  2947. if( property_exists($values,'value') && is_array($values->value) ){
  2948. if( isset($values->value[$index]) ){
  2949. return $values->value[$index];
  2950. }
  2951. return null;
  2952. }elseif( (int)$index === 0 ){
  2953. return $values;
  2954. }
  2955. return null;
  2956. }
  2957. public function length($values){
  2958. $n = (property_exists($values,'value') && is_array($values->value)) ? count($values->value) : 1;
  2959. return new Less_Tree_Dimension($n);
  2960. }
  2961. public function datauri($mimetypeNode, $filePathNode = null ) {
  2962. $filePath = ( $filePathNode ? $filePathNode->value : null );
  2963. $mimetype = $mimetypeNode->value;
  2964. $args = 2;
  2965. if( !$filePath ){
  2966. $filePath = $mimetype;
  2967. $args = 1;
  2968. }
  2969. $filePath = str_replace('\\','/',$filePath);
  2970. if( Less_Environment::isPathRelative($filePath) ){
  2971. if( Less_Parser::$options['relativeUrls'] ){
  2972. $temp = $this->currentFileInfo['currentDirectory'];
  2973. } else {
  2974. $temp = $this->currentFileInfo['entryPath'];
  2975. }
  2976. if( !empty($temp) ){
  2977. $filePath = Less_Environment::normalizePath(rtrim($temp,'/').'/'.$filePath);
  2978. }
  2979. }
  2980. // detect the mimetype if not given
  2981. if( $args < 2 ){
  2982. /* incomplete
  2983. $mime = require('mime');
  2984. mimetype = mime.lookup(path);
  2985. // use base 64 unless it's an ASCII or UTF-8 format
  2986. var charset = mime.charsets.lookup(mimetype);
  2987. useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0;
  2988. if (useBase64) mimetype += ';base64';
  2989. */
  2990. $mimetype = Less_Mime::lookup($filePath);
  2991. $charset = Less_Mime::charsets_lookup($mimetype);
  2992. $useBase64 = !in_array($charset,array('US-ASCII', 'UTF-8'));
  2993. if( $useBase64 ){ $mimetype .= ';base64'; }
  2994. }else{
  2995. $useBase64 = preg_match('/;base64$/',$mimetype);
  2996. }
  2997. if( file_exists($filePath) ){
  2998. $buf = @file_get_contents($filePath);
  2999. }else{
  3000. $buf = false;
  3001. }
  3002. // IE8 cannot handle a data-uri larger than 32KB. If this is exceeded
  3003. // and the --ieCompat flag is enabled, return a normal url() instead.
  3004. $DATA_URI_MAX_KB = 32;
  3005. $fileSizeInKB = round( strlen($buf) / 1024 );
  3006. if( $fileSizeInKB >= $DATA_URI_MAX_KB ){
  3007. $url = new Less_Tree_Url( ($filePathNode ? $filePathNode : $mimetypeNode), $this->currentFileInfo);
  3008. return $url->compile($this);
  3009. }
  3010. if( $buf ){
  3011. $buf = $useBase64 ? base64_encode($buf) : rawurlencode($buf);
  3012. $filePath = '"data:' . $mimetype . ',' . $buf . '"';
  3013. }
  3014. return new Less_Tree_Url( new Less_Tree_Anonymous($filePath) );
  3015. }
  3016. //svg-gradient
  3017. public function svggradient( $direction ){
  3018. $throw_message = 'svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]';
  3019. $arguments = func_get_args();
  3020. if( count($arguments) < 3 ){
  3021. throw new Less_Exception_Compiler( $throw_message );
  3022. }
  3023. $stops = array_slice($arguments,1);
  3024. $gradientType = 'linear';
  3025. $rectangleDimension = 'x="0" y="0" width="1" height="1"';
  3026. $useBase64 = true;
  3027. $directionValue = $direction->toCSS();
  3028. switch( $directionValue ){
  3029. case "to bottom":
  3030. $gradientDirectionSvg = 'x1="0%" y1="0%" x2="0%" y2="100%"';
  3031. break;
  3032. case "to right":
  3033. $gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="0%"';
  3034. break;
  3035. case "to bottom right":
  3036. $gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="100%"';
  3037. break;
  3038. case "to top right":
  3039. $gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"';
  3040. break;
  3041. case "ellipse":
  3042. case "ellipse at center":
  3043. $gradientType = "radial";
  3044. $gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"';
  3045. $rectangleDimension = 'x="-50" y="-50" width="101" height="101"';
  3046. break;
  3047. default:
  3048. throw new Less_Exception_Compiler( "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" );
  3049. }
  3050. $returner = '<?xml version="1.0" ?>' .
  3051. '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">' .
  3052. '<' . $gradientType . 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' . $gradientDirectionSvg . '>';
  3053. for( $i = 0; $i < count($stops); $i++ ){
  3054. if( is_object($stops[$i]) && property_exists($stops[$i],'value') ){
  3055. $color = $stops[$i]->value[0];
  3056. $position = $stops[$i]->value[1];
  3057. }else{
  3058. $color = $stops[$i];
  3059. $position = null;
  3060. }
  3061. if( !($color instanceof Less_Tree_Color) || (!(($i === 0 || $i+1 === count($stops)) && $position === null) && !($position instanceof Less_Tree_Dimension)) ){
  3062. throw new Less_Exception_Compiler( $throw_message );
  3063. }
  3064. if( $position ){
  3065. $positionValue = $position->toCSS();
  3066. }elseif( $i === 0 ){
  3067. $positionValue = '0%';
  3068. }else{
  3069. $positionValue = '100%';
  3070. }
  3071. $alpha = $color->alpha;
  3072. $returner .= '<stop offset="' . $positionValue . '" stop-color="' . $color->toRGB() . '"' . ($alpha < 1 ? ' stop-opacity="' . $alpha . '"' : '') . '/>';
  3073. }
  3074. $returner .= '</' . $gradientType . 'Gradient><rect ' . $rectangleDimension . ' fill="url(#gradient)" /></svg>';
  3075. if( $useBase64 ){
  3076. $returner = "'data:image/svg+xml;base64,".base64_encode($returner)."'";
  3077. }else{
  3078. $returner = "'data:image/svg+xml,".$returner."'";
  3079. }
  3080. return new Less_Tree_URL( new Less_Tree_Anonymous( $returner ) );
  3081. }
  3082. /**
  3083. * @param string $type
  3084. */
  3085. private static function Expected( $type, $arg ){
  3086. $debug = debug_backtrace();
  3087. array_shift($debug);
  3088. $last = array_shift($debug);
  3089. $last = array_intersect_key($last,array('function'=>'','class'=>'','line'=>''));
  3090. $message = 'Object of type '.get_class($arg).' passed to darken function. Expecting `'.$type.'`. '.$arg->toCSS().'. '.print_r($last,true);
  3091. throw new Less_Exception_Compiler($message);
  3092. }
  3093. /**
  3094. * Php version of javascript's `encodeURIComponent` function
  3095. *
  3096. * @param string $string The string to encode
  3097. * @return string The encoded string
  3098. */
  3099. public static function encodeURIComponent($string){
  3100. $revert = array('%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')');
  3101. return strtr(rawurlencode($string), $revert);
  3102. }
  3103. // Color Blending
  3104. // ref: http://www.w3.org/TR/compositing-1
  3105. public function colorBlend( $mode, $color1, $color2 ){
  3106. $ab = $color1->alpha; // backdrop
  3107. $as = $color2->alpha; // source
  3108. $r = array(); // result
  3109. $ar = $as + $ab * (1 - $as);
  3110. for( $i = 0; $i < 3; $i++ ){
  3111. $cb = $color1->rgb[$i] / 255;
  3112. $cs = $color2->rgb[$i] / 255;
  3113. $cr = call_user_func( $mode, $cb, $cs );
  3114. if( $ar ){
  3115. $cr = ($as * $cs + $ab * ($cb - $as * ($cb + $cs - $cr))) / $ar;
  3116. }
  3117. $r[$i] = $cr * 255;
  3118. }
  3119. return new Less_Tree_Color($r, $ar);
  3120. }
  3121. public function multiply($color1, $color2 ){
  3122. return $this->colorBlend( array($this,'colorBlendMultiply'), $color1, $color2 );
  3123. }
  3124. private function colorBlendMultiply($cb, $cs){
  3125. return $cb * $cs;
  3126. }
  3127. public function screen($color1, $color2 ){
  3128. return $this->colorBlend( array($this,'colorBlendScreen'), $color1, $color2 );
  3129. }
  3130. private function colorBlendScreen( $cb, $cs){
  3131. return $cb + $cs - $cb * $cs;
  3132. }
  3133. public function overlay($color1, $color2){
  3134. return $this->colorBlend( array($this,'colorBlendOverlay'), $color1, $color2 );
  3135. }
  3136. private function colorBlendOverlay($cb, $cs ){
  3137. $cb *= 2;
  3138. return ($cb <= 1)
  3139. ? $this->colorBlendMultiply($cb, $cs)
  3140. : $this->colorBlendScreen($cb - 1, $cs);
  3141. }
  3142. public function softlight($color1, $color2){
  3143. return $this->colorBlend( array($this,'colorBlendSoftlight'), $color1, $color2 );
  3144. }
  3145. private function colorBlendSoftlight($cb, $cs ){
  3146. $d = 1;
  3147. $e = $cb;
  3148. if( $cs > 0.5 ){
  3149. $e = 1;
  3150. $d = ($cb > 0.25) ? sqrt($cb)
  3151. : ((16 * $cb - 12) * $cb + 4) * $cb;
  3152. }
  3153. return $cb - (1 - 2 * $cs) * $e * ($d - $cb);
  3154. }
  3155. public function hardlight($color1, $color2){
  3156. return $this->colorBlend( array($this,'colorBlendHardlight'), $color1, $color2 );
  3157. }
  3158. private function colorBlendHardlight( $cb, $cs ){
  3159. return $this->colorBlendOverlay($cs, $cb);
  3160. }
  3161. public function difference($color1, $color2) {
  3162. return $this->colorBlend( array($this,'colorBlendDifference'), $color1, $color2 );
  3163. }
  3164. private function colorBlendDifference( $cb, $cs ){
  3165. return abs($cb - $cs);
  3166. }
  3167. public function exclusion( $color1, $color2 ){
  3168. return $this->colorBlend( array($this,'colorBlendExclusion'), $color1, $color2 );
  3169. }
  3170. private function colorBlendExclusion( $cb, $cs ){
  3171. return $cb + $cs - 2 * $cb * $cs;
  3172. }
  3173. public function average($color1, $color2){
  3174. return $this->colorBlend( array($this,'colorBlendAverage'), $color1, $color2 );
  3175. }
  3176. // non-w3c functions:
  3177. public function colorBlendAverage($cb, $cs ){
  3178. return ($cb + $cs) / 2;
  3179. }
  3180. public function negation($color1, $color2 ){
  3181. return $this->colorBlend( array($this,'colorBlendNegation'), $color1, $color2 );
  3182. }
  3183. public function colorBlendNegation($cb, $cs){
  3184. return 1 - abs($cb + $cs - 1);
  3185. }
  3186. // ~ End of Color Blending
  3187. }
  3188. /**
  3189. * Mime lookup
  3190. *
  3191. * @package Less
  3192. * @subpackage node
  3193. */
  3194. class Less_Mime{
  3195. // this map is intentionally incomplete
  3196. // if you want more, install 'mime' dep
  3197. static $_types = array(
  3198. '.htm' => 'text/html',
  3199. '.html'=> 'text/html',
  3200. '.gif' => 'image/gif',
  3201. '.jpg' => 'image/jpeg',
  3202. '.jpeg'=> 'image/jpeg',
  3203. '.png' => 'image/png',
  3204. '.ttf' => 'application/x-font-ttf',
  3205. '.otf' => 'application/x-font-otf',
  3206. '.eot' => 'application/vnd.ms-fontobject',
  3207. '.woff' => 'application/x-font-woff',
  3208. '.svg' => 'image/svg+xml',
  3209. );
  3210. public static function lookup( $filepath ){
  3211. $parts = explode('.',$filepath);
  3212. $ext = '.'.strtolower(array_pop($parts));
  3213. if( !isset(self::$_types[$ext]) ){
  3214. return null;
  3215. }
  3216. return self::$_types[$ext];
  3217. }
  3218. public static function charsets_lookup( $type = null ){
  3219. // assumes all text types are UTF-8
  3220. return $type && preg_match('/^text\//',$type) ? 'UTF-8' : '';
  3221. }
  3222. }
  3223. /**
  3224. * Tree
  3225. *
  3226. * @package Less
  3227. * @subpackage tree
  3228. */
  3229. class Less_Tree{
  3230. public $cache_string;
  3231. public function toCSS(){
  3232. $output = new Less_Output();
  3233. $this->genCSS($output);
  3234. return $output->toString();
  3235. }
  3236. /**
  3237. * Generate CSS by adding it to the output object
  3238. *
  3239. * @param Less_Output $output The output
  3240. * @return void
  3241. */
  3242. public function genCSS($output){}
  3243. /**
  3244. * @param Less_Tree_Ruleset[] $rules
  3245. */
  3246. public static function outputRuleset( $output, $rules ){
  3247. $ruleCnt = count($rules);
  3248. Less_Environment::$tabLevel++;
  3249. // Compressed
  3250. if( Less_Parser::$options['compress'] ){
  3251. $output->add('{');
  3252. for( $i = 0; $i < $ruleCnt; $i++ ){
  3253. $rules[$i]->genCSS( $output );
  3254. }
  3255. $output->add( '}' );
  3256. Less_Environment::$tabLevel--;
  3257. return;
  3258. }
  3259. // Non-compressed
  3260. $tabSetStr = "\n".str_repeat( ' ' , Less_Environment::$tabLevel-1 );
  3261. $tabRuleStr = $tabSetStr.' ';
  3262. $output->add( " {" );
  3263. for($i = 0; $i < $ruleCnt; $i++ ){
  3264. $output->add( $tabRuleStr );
  3265. $rules[$i]->genCSS( $output );
  3266. }
  3267. Less_Environment::$tabLevel--;
  3268. $output->add( $tabSetStr.'}' );
  3269. }
  3270. public function accept($visitor){}
  3271. public static function ReferencedArray($rules){
  3272. foreach($rules as $rule){
  3273. if( method_exists($rule, 'markReferenced') ){
  3274. $rule->markReferenced();
  3275. }
  3276. }
  3277. }
  3278. /**
  3279. * Requires php 5.3+
  3280. */
  3281. public static function __set_state($args){
  3282. $class = get_called_class();
  3283. $obj = new $class(null,null,null,null);
  3284. foreach($args as $key => $val){
  3285. $obj->$key = $val;
  3286. }
  3287. return $obj;
  3288. }
  3289. }
  3290. /**
  3291. * Parser output
  3292. *
  3293. * @package Less
  3294. * @subpackage output
  3295. */
  3296. class Less_Output{
  3297. /**
  3298. * Output holder
  3299. *
  3300. * @var string
  3301. */
  3302. protected $strs = array();
  3303. /**
  3304. * Adds a chunk to the stack
  3305. *
  3306. * @param string $chunk The chunk to output
  3307. * @param Less_FileInfo $fileInfo The file information
  3308. * @param integer $index The index
  3309. * @param mixed $mapLines
  3310. */
  3311. public function add($chunk, $fileInfo = null, $index = 0, $mapLines = null){
  3312. $this->strs[] = $chunk;
  3313. }
  3314. /**
  3315. * Is the output empty?
  3316. *
  3317. * @return boolean
  3318. */
  3319. public function isEmpty(){
  3320. return count($this->strs) === 0;
  3321. }
  3322. /**
  3323. * Converts the output to string
  3324. *
  3325. * @return string
  3326. */
  3327. public function toString(){
  3328. return implode('',$this->strs);
  3329. }
  3330. }
  3331. /**
  3332. * Visitor
  3333. *
  3334. * @package Less
  3335. * @subpackage visitor
  3336. */
  3337. class Less_Visitor{
  3338. protected $methods = array();
  3339. protected $_visitFnCache = array();
  3340. public function __construct(){
  3341. $this->_visitFnCache = get_class_methods(get_class($this));
  3342. $this->_visitFnCache = array_flip($this->_visitFnCache);
  3343. }
  3344. public function visitObj( $node ){
  3345. $funcName = 'visit'.$node->type;
  3346. if( isset($this->_visitFnCache[$funcName]) ){
  3347. $visitDeeper = true;
  3348. $this->$funcName( $node, $visitDeeper );
  3349. if( $visitDeeper ){
  3350. $node->accept($this);
  3351. }
  3352. $funcName = $funcName . "Out";
  3353. if( isset($this->_visitFnCache[$funcName]) ){
  3354. $this->$funcName( $node );
  3355. }
  3356. }else{
  3357. $node->accept($this);
  3358. }
  3359. return $node;
  3360. }
  3361. public function visitArray( $nodes ){
  3362. array_map( array($this,'visitObj'), $nodes);
  3363. return $nodes;
  3364. }
  3365. }
  3366. /**
  3367. * Replacing Visitor
  3368. *
  3369. * @package Less
  3370. * @subpackage visitor
  3371. */
  3372. class Less_VisitorReplacing extends Less_Visitor{
  3373. public function visitObj( $node ){
  3374. $funcName = 'visit'.$node->type;
  3375. if( isset($this->_visitFnCache[$funcName]) ){
  3376. $visitDeeper = true;
  3377. $node = $this->$funcName( $node, $visitDeeper );
  3378. if( $node ){
  3379. if( $visitDeeper && is_object($node) ){
  3380. $node->accept($this);
  3381. }
  3382. $funcName = $funcName . "Out";
  3383. if( isset($this->_visitFnCache[$funcName]) ){
  3384. $this->$funcName( $node );
  3385. }
  3386. }
  3387. }else{
  3388. $node->accept($this);
  3389. }
  3390. return $node;
  3391. }
  3392. public function visitArray( $nodes ){
  3393. $newNodes = array();
  3394. foreach($nodes as $node){
  3395. $evald = $this->visitObj($node);
  3396. if( $evald ){
  3397. if( is_array($evald) ){
  3398. self::flatten($evald,$newNodes);
  3399. }else{
  3400. $newNodes[] = $evald;
  3401. }
  3402. }
  3403. }
  3404. return $newNodes;
  3405. }
  3406. public function flatten( $arr, &$out ){
  3407. foreach($arr as $item){
  3408. if( !is_array($item) ){
  3409. $out[] = $item;
  3410. continue;
  3411. }
  3412. foreach($item as $nestedItem){
  3413. if( is_array($nestedItem) ){
  3414. self::flatten( $nestedItem, $out);
  3415. }else{
  3416. $out[] = $nestedItem;
  3417. }
  3418. }
  3419. }
  3420. return $out;
  3421. }
  3422. }
  3423. /**
  3424. * Configurable
  3425. *
  3426. * @package Less
  3427. * @subpackage Core
  3428. */
  3429. abstract class Less_Configurable {
  3430. /**
  3431. * Array of options
  3432. *
  3433. * @var array
  3434. */
  3435. protected $options = array();
  3436. /**
  3437. * Array of default options
  3438. *
  3439. * @var array
  3440. */
  3441. protected $defaultOptions = array();
  3442. /**
  3443. * Set options
  3444. *
  3445. * If $options is an object it will be converted into an array by called
  3446. * it's toArray method.
  3447. *
  3448. * @throws Exception
  3449. * @param array|object $options
  3450. *
  3451. */
  3452. public function setOptions($options){
  3453. $options = array_intersect_key($options,$this->defaultOptions);
  3454. $this->options = array_merge($this->defaultOptions, $this->options, $options);
  3455. }
  3456. /**
  3457. * Get an option value by name
  3458. *
  3459. * If the option is empty or not set a NULL value will be returned.
  3460. *
  3461. * @param string $name
  3462. * @param mixed $default Default value if confiuration of $name is not present
  3463. * @return mixed
  3464. */
  3465. public function getOption($name, $default = null){
  3466. if(isset($this->options[$name])){
  3467. return $this->options[$name];
  3468. }
  3469. return $default;
  3470. }
  3471. /**
  3472. * Set an option
  3473. *
  3474. * @param string $name
  3475. * @param mixed $value
  3476. */
  3477. public function setOption($name, $value){
  3478. $this->options[$name] = $value;
  3479. }
  3480. }
  3481. /**
  3482. * Alpha
  3483. *
  3484. * @package Less
  3485. * @subpackage tree
  3486. */
  3487. class Less_Tree_Alpha extends Less_Tree{
  3488. public $value;
  3489. public $type = 'Alpha';
  3490. public function __construct($val){
  3491. $this->value = $val;
  3492. }
  3493. //function accept( $visitor ){
  3494. // $this->value = $visitor->visit( $this->value );
  3495. //}
  3496. public function compile($env){
  3497. if( is_object($this->value) ){
  3498. $this->value = $this->value->compile($env);
  3499. }
  3500. return $this;
  3501. }
  3502. /**
  3503. * @see Less_Tree::genCSS
  3504. */
  3505. public function genCSS( $output ){
  3506. $output->add( "alpha(opacity=" );
  3507. if( is_string($this->value) ){
  3508. $output->add( $this->value );
  3509. }else{
  3510. $this->value->genCSS( $output);
  3511. }
  3512. $output->add( ')' );
  3513. }
  3514. public function toCSS(){
  3515. return "alpha(opacity=" . (is_string($this->value) ? $this->value : $this->value->toCSS()) . ")";
  3516. }
  3517. }
  3518. /**
  3519. * Anonymous
  3520. *
  3521. * @package Less
  3522. * @subpackage tree
  3523. */
  3524. class Less_Tree_Anonymous extends Less_Tree{
  3525. public $value;
  3526. public $quote;
  3527. public $index;
  3528. public $mapLines;
  3529. public $currentFileInfo;
  3530. public $type = 'Anonymous';
  3531. /**
  3532. * @param integer $index
  3533. * @param boolean $mapLines
  3534. */
  3535. public function __construct($value, $index = null, $currentFileInfo = null, $mapLines = null ){
  3536. $this->value = $value;
  3537. $this->index = $index;
  3538. $this->mapLines = $mapLines;
  3539. $this->currentFileInfo = $currentFileInfo;
  3540. }
  3541. public function compile(){
  3542. return new Less_Tree_Anonymous($this->value, $this->index, $this->currentFileInfo, $this->mapLines);
  3543. }
  3544. public function compare($x){
  3545. if( !is_object($x) ){
  3546. return -1;
  3547. }
  3548. $left = $this->toCSS();
  3549. $right = $x->toCSS();
  3550. if( $left === $right ){
  3551. return 0;
  3552. }
  3553. return $left < $right ? -1 : 1;
  3554. }
  3555. /**
  3556. * @see Less_Tree::genCSS
  3557. */
  3558. public function genCSS( $output ){
  3559. $output->add( $this->value, $this->currentFileInfo, $this->index, $this->mapLines );
  3560. }
  3561. public function toCSS(){
  3562. return $this->value;
  3563. }
  3564. }
  3565. /**
  3566. * Assignment
  3567. *
  3568. * @package Less
  3569. * @subpackage tree
  3570. */
  3571. class Less_Tree_Assignment extends Less_Tree{
  3572. public $key;
  3573. public $value;
  3574. public $type = 'Assignment';
  3575. public function __construct($key, $val) {
  3576. $this->key = $key;
  3577. $this->value = $val;
  3578. }
  3579. public function accept( $visitor ){
  3580. $this->value = $visitor->visitObj( $this->value );
  3581. }
  3582. public function compile($env) {
  3583. return new Less_Tree_Assignment( $this->key, $this->value->compile($env));
  3584. }
  3585. /**
  3586. * @see Less_Tree::genCSS
  3587. */
  3588. public function genCSS( $output ){
  3589. $output->add( $this->key . '=' );
  3590. $this->value->genCSS( $output );
  3591. }
  3592. public function toCss(){
  3593. return $this->key . '=' . $this->value->toCSS();
  3594. }
  3595. }
  3596. /**
  3597. * Attribute
  3598. *
  3599. * @package Less
  3600. * @subpackage tree
  3601. */
  3602. class Less_Tree_Attribute extends Less_Tree{
  3603. public $key;
  3604. public $op;
  3605. public $value;
  3606. public $type = 'Attribute';
  3607. public function __construct($key, $op, $value){
  3608. $this->key = $key;
  3609. $this->op = $op;
  3610. $this->value = $value;
  3611. }
  3612. public function compile($env){
  3613. $key_obj = is_object($this->key);
  3614. $val_obj = is_object($this->value);
  3615. if( !$key_obj && !$val_obj ){
  3616. return $this;
  3617. }
  3618. return new Less_Tree_Attribute(
  3619. $key_obj ? $this->key->compile($env) : $this->key ,
  3620. $this->op,
  3621. $val_obj ? $this->value->compile($env) : $this->value);
  3622. }
  3623. /**
  3624. * @see Less_Tree::genCSS
  3625. */
  3626. public function genCSS( $output ){
  3627. $output->add( $this->toCSS() );
  3628. }
  3629. public function toCSS(){
  3630. $value = $this->key;
  3631. if( $this->op ){
  3632. $value .= $this->op;
  3633. $value .= (is_object($this->value) ? $this->value->toCSS() : $this->value);
  3634. }
  3635. return '[' . $value . ']';
  3636. }
  3637. }
  3638. /**
  3639. * Call
  3640. *
  3641. * @package Less
  3642. * @subpackage tree
  3643. */
  3644. class Less_Tree_Call extends Less_Tree{
  3645. public $value;
  3646. protected $name;
  3647. protected $args;
  3648. protected $index;
  3649. protected $currentFileInfo;
  3650. public $type = 'Call';
  3651. public function __construct($name, $args, $index, $currentFileInfo = null ){
  3652. $this->name = $name;
  3653. $this->args = $args;
  3654. $this->index = $index;
  3655. $this->currentFileInfo = $currentFileInfo;
  3656. }
  3657. public function accept( $visitor ){
  3658. $this->args = $visitor->visitArray( $this->args );
  3659. }
  3660. //
  3661. // When evaluating a function call,
  3662. // we either find the function in `tree.functions` [1],
  3663. // in which case we call it, passing the evaluated arguments,
  3664. // or we simply print it out as it appeared originally [2].
  3665. //
  3666. // The *functions.js* file contains the built-in functions.
  3667. //
  3668. // The reason why we evaluate the arguments, is in the case where
  3669. // we try to pass a variable to a function, like: `saturate(@color)`.
  3670. // The function should receive the value, not the variable.
  3671. //
  3672. public function compile($env=null){
  3673. $args = array();
  3674. foreach($this->args as $a){
  3675. $args[] = $a->compile($env);
  3676. }
  3677. $nameLC = strtolower($this->name);
  3678. switch($nameLC){
  3679. case '%':
  3680. $nameLC = '_percent';
  3681. break;
  3682. case 'get-unit':
  3683. $nameLC = 'getunit';
  3684. break;
  3685. case 'data-uri':
  3686. $nameLC = 'datauri';
  3687. break;
  3688. case 'svg-gradient':
  3689. $nameLC = 'svggradient';
  3690. break;
  3691. }
  3692. $result = null;
  3693. if( $nameLC === 'default' ){
  3694. $result = Less_Tree_DefaultFunc::compile();
  3695. }else{
  3696. if( method_exists('Less_Functions',$nameLC) ){ // 1.
  3697. try {
  3698. $func = new Less_Functions($env, $this->currentFileInfo);
  3699. $result = call_user_func_array( array($func,$nameLC),$args);
  3700. } catch (Exception $e) {
  3701. throw new Less_Exception_Compiler('error evaluating function `' . $this->name . '` '.$e->getMessage().' index: '. $this->index);
  3702. }
  3703. } elseif( isset( $env->functions[$nameLC] ) && is_callable( $env->functions[$nameLC] ) ) {
  3704. try {
  3705. $result = call_user_func_array( $env->functions[$nameLC], $args );
  3706. } catch (Exception $e) {
  3707. throw new Less_Exception_Compiler('error evaluating function `' . $this->name . '` '.$e->getMessage().' index: '. $this->index);
  3708. }
  3709. }
  3710. }
  3711. if( $result !== null ){
  3712. return $result;
  3713. }
  3714. return new Less_Tree_Call( $this->name, $args, $this->index, $this->currentFileInfo );
  3715. }
  3716. /**
  3717. * @see Less_Tree::genCSS
  3718. */
  3719. public function genCSS( $output ){
  3720. $output->add( $this->name . '(', $this->currentFileInfo, $this->index );
  3721. $args_len = count($this->args);
  3722. for($i = 0; $i < $args_len; $i++ ){
  3723. $this->args[$i]->genCSS( $output );
  3724. if( $i + 1 < $args_len ){
  3725. $output->add( ', ' );
  3726. }
  3727. }
  3728. $output->add( ')' );
  3729. }
  3730. //public function toCSS(){
  3731. // return $this->compile()->toCSS();
  3732. //}
  3733. }
  3734. /**
  3735. * Color
  3736. *
  3737. * @package Less
  3738. * @subpackage tree
  3739. */
  3740. class Less_Tree_Color extends Less_Tree{
  3741. public $rgb;
  3742. public $alpha;
  3743. public $isTransparentKeyword;
  3744. public $type = 'Color';
  3745. public function __construct($rgb, $a = 1, $isTransparentKeyword = null ){
  3746. if( $isTransparentKeyword ){
  3747. $this->rgb = $rgb;
  3748. $this->alpha = $a;
  3749. $this->isTransparentKeyword = true;
  3750. return;
  3751. }
  3752. $this->rgb = array();
  3753. if( is_array($rgb) ){
  3754. $this->rgb = $rgb;
  3755. }else if( strlen($rgb) == 6 ){
  3756. foreach(str_split($rgb, 2) as $c){
  3757. $this->rgb[] = hexdec($c);
  3758. }
  3759. }else{
  3760. foreach(str_split($rgb, 1) as $c){
  3761. $this->rgb[] = hexdec($c.$c);
  3762. }
  3763. }
  3764. $this->alpha = is_numeric($a) ? $a : 1;
  3765. }
  3766. public function compile(){
  3767. return $this;
  3768. }
  3769. public function luma(){
  3770. $r = $this->rgb[0] / 255;
  3771. $g = $this->rgb[1] / 255;
  3772. $b = $this->rgb[2] / 255;
  3773. $r = ($r <= 0.03928) ? $r / 12.92 : pow((($r + 0.055) / 1.055), 2.4);
  3774. $g = ($g <= 0.03928) ? $g / 12.92 : pow((($g + 0.055) / 1.055), 2.4);
  3775. $b = ($b <= 0.03928) ? $b / 12.92 : pow((($b + 0.055) / 1.055), 2.4);
  3776. return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
  3777. }
  3778. /**
  3779. * @see Less_Tree::genCSS
  3780. */
  3781. public function genCSS( $output ){
  3782. $output->add( $this->toCSS() );
  3783. }
  3784. public function toCSS( $doNotCompress = false ){
  3785. $compress = Less_Parser::$options['compress'] && !$doNotCompress;
  3786. $alpha = Less_Functions::fround( $this->alpha );
  3787. //
  3788. // If we have some transparency, the only way to represent it
  3789. // is via `rgba`. Otherwise, we use the hex representation,
  3790. // which has better compatibility with older browsers.
  3791. // Values are capped between `0` and `255`, rounded and zero-padded.
  3792. //
  3793. if( $alpha < 1 ){
  3794. if( ( $alpha === 0 || $alpha === 0.0 ) && isset($this->isTransparentKeyword) && $this->isTransparentKeyword ){
  3795. return 'transparent';
  3796. }
  3797. $values = array();
  3798. foreach($this->rgb as $c){
  3799. $values[] = Less_Functions::clamp( round($c), 255);
  3800. }
  3801. $values[] = $alpha;
  3802. $glue = ($compress ? ',' : ', ');
  3803. return "rgba(" . implode($glue, $values) . ")";
  3804. }else{
  3805. $color = $this->toRGB();
  3806. if( $compress ){
  3807. // Convert color to short format
  3808. if( $color[1] === $color[2] && $color[3] === $color[4] && $color[5] === $color[6]) {
  3809. $color = '#'.$color[1] . $color[3] . $color[5];
  3810. }
  3811. }
  3812. return $color;
  3813. }
  3814. }
  3815. //
  3816. // Operations have to be done per-channel, if not,
  3817. // channels will spill onto each other. Once we have
  3818. // our result, in the form of an integer triplet,
  3819. // we create a new Color node to hold the result.
  3820. //
  3821. /**
  3822. * @param string $op
  3823. */
  3824. public function operate( $op, $other) {
  3825. $rgb = array();
  3826. $alpha = $this->alpha * (1 - $other->alpha) + $other->alpha;
  3827. for ($c = 0; $c < 3; $c++) {
  3828. $rgb[$c] = Less_Functions::operate( $op, $this->rgb[$c], $other->rgb[$c]);
  3829. }
  3830. return new Less_Tree_Color($rgb, $alpha);
  3831. }
  3832. public function toRGB(){
  3833. return $this->toHex($this->rgb);
  3834. }
  3835. public function toHSL(){
  3836. $r = $this->rgb[0] / 255;
  3837. $g = $this->rgb[1] / 255;
  3838. $b = $this->rgb[2] / 255;
  3839. $a = $this->alpha;
  3840. $max = max($r, $g, $b);
  3841. $min = min($r, $g, $b);
  3842. $l = ($max + $min) / 2;
  3843. $d = $max - $min;
  3844. $h = $s = 0;
  3845. if( $max !== $min ){
  3846. $s = $l > 0.5 ? $d / (2 - $max - $min) : $d / ($max + $min);
  3847. switch ($max) {
  3848. case $r: $h = ($g - $b) / $d + ($g < $b ? 6 : 0); break;
  3849. case $g: $h = ($b - $r) / $d + 2; break;
  3850. case $b: $h = ($r - $g) / $d + 4; break;
  3851. }
  3852. $h /= 6;
  3853. }
  3854. return array('h' => $h * 360, 's' => $s, 'l' => $l, 'a' => $a );
  3855. }
  3856. //Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
  3857. public function toHSV() {
  3858. $r = $this->rgb[0] / 255;
  3859. $g = $this->rgb[1] / 255;
  3860. $b = $this->rgb[2] / 255;
  3861. $a = $this->alpha;
  3862. $max = max($r, $g, $b);
  3863. $min = min($r, $g, $b);
  3864. $v = $max;
  3865. $d = $max - $min;
  3866. if ($max === 0) {
  3867. $s = 0;
  3868. } else {
  3869. $s = $d / $max;
  3870. }
  3871. $h = 0;
  3872. if( $max !== $min ){
  3873. switch($max){
  3874. case $r: $h = ($g - $b) / $d + ($g < $b ? 6 : 0); break;
  3875. case $g: $h = ($b - $r) / $d + 2; break;
  3876. case $b: $h = ($r - $g) / $d + 4; break;
  3877. }
  3878. $h /= 6;
  3879. }
  3880. return array('h'=> $h * 360, 's'=> $s, 'v'=> $v, 'a' => $a );
  3881. }
  3882. public function toARGB(){
  3883. $argb = array_merge( (array) Less_Parser::round($this->alpha * 255), $this->rgb);
  3884. return $this->toHex( $argb );
  3885. }
  3886. public function compare($x){
  3887. if( !property_exists( $x, 'rgb' ) ){
  3888. return -1;
  3889. }
  3890. return ($x->rgb[0] === $this->rgb[0] &&
  3891. $x->rgb[1] === $this->rgb[1] &&
  3892. $x->rgb[2] === $this->rgb[2] &&
  3893. $x->alpha === $this->alpha) ? 0 : -1;
  3894. }
  3895. public function toHex( $v ){
  3896. $ret = '#';
  3897. foreach($v as $c){
  3898. $c = Less_Functions::clamp( Less_Parser::round($c), 255);
  3899. if( $c < 16 ){
  3900. $ret .= '0';
  3901. }
  3902. $ret .= dechex($c);
  3903. }
  3904. return $ret;
  3905. }
  3906. /**
  3907. * @param string $keyword
  3908. */
  3909. public static function fromKeyword( $keyword ){
  3910. $keyword = strtolower($keyword);
  3911. if( Less_Colors::hasOwnProperty($keyword) ){
  3912. // detect named color
  3913. return new Less_Tree_Color(substr(Less_Colors::color($keyword), 1));
  3914. }
  3915. if( $keyword === 'transparent' ){
  3916. return new Less_Tree_Color( array(0, 0, 0), 0, true);
  3917. }
  3918. }
  3919. }
  3920. /**
  3921. * Comment
  3922. *
  3923. * @package Less
  3924. * @subpackage tree
  3925. */
  3926. class Less_Tree_Comment extends Less_Tree{
  3927. public $value;
  3928. public $silent;
  3929. public $isReferenced;
  3930. public $currentFileInfo;
  3931. public $type = 'Comment';
  3932. public function __construct($value, $silent, $index = null, $currentFileInfo = null ){
  3933. $this->value = $value;
  3934. $this->silent = !! $silent;
  3935. $this->currentFileInfo = $currentFileInfo;
  3936. }
  3937. /**
  3938. * @see Less_Tree::genCSS
  3939. */
  3940. public function genCSS( $output ){
  3941. //if( $this->debugInfo ){
  3942. //$output->add( tree.debugInfo($env, $this), $this->currentFileInfo, $this->index);
  3943. //}
  3944. $output->add( trim($this->value) );//TODO shouldn't need to trim, we shouldn't grab the \n
  3945. }
  3946. public function toCSS(){
  3947. return Less_Parser::$options['compress'] ? '' : $this->value;
  3948. }
  3949. public function isSilent(){
  3950. $isReference = ($this->currentFileInfo && isset($this->currentFileInfo['reference']) && (!isset($this->isReferenced) || !$this->isReferenced) );
  3951. $isCompressed = Less_Parser::$options['compress'] && !preg_match('/^\/\*!/', $this->value);
  3952. return $this->silent || $isReference || $isCompressed;
  3953. }
  3954. public function compile(){
  3955. return $this;
  3956. }
  3957. public function markReferenced(){
  3958. $this->isReferenced = true;
  3959. }
  3960. }
  3961. /**
  3962. * Condition
  3963. *
  3964. * @package Less
  3965. * @subpackage tree
  3966. */
  3967. class Less_Tree_Condition extends Less_Tree{
  3968. public $op;
  3969. public $lvalue;
  3970. public $rvalue;
  3971. public $index;
  3972. public $negate;
  3973. public $type = 'Condition';
  3974. public function __construct($op, $l, $r, $i = 0, $negate = false) {
  3975. $this->op = trim($op);
  3976. $this->lvalue = $l;
  3977. $this->rvalue = $r;
  3978. $this->index = $i;
  3979. $this->negate = $negate;
  3980. }
  3981. public function accept($visitor){
  3982. $this->lvalue = $visitor->visitObj( $this->lvalue );
  3983. $this->rvalue = $visitor->visitObj( $this->rvalue );
  3984. }
  3985. public function compile($env) {
  3986. $a = $this->lvalue->compile($env);
  3987. $b = $this->rvalue->compile($env);
  3988. switch( $this->op ){
  3989. case 'and':
  3990. $result = $a && $b;
  3991. break;
  3992. case 'or':
  3993. $result = $a || $b;
  3994. break;
  3995. default:
  3996. if( Less_Parser::is_method($a, 'compare') ){
  3997. $result = $a->compare($b);
  3998. }elseif( Less_Parser::is_method($b, 'compare') ){
  3999. $result = $b->compare($a);
  4000. }else{
  4001. throw new Less_Exception_Compiler('Unable to perform comparison', null, $this->index);
  4002. }
  4003. switch ($result) {
  4004. case -1:
  4005. $result = $this->op === '<' || $this->op === '=<' || $this->op === '<=';
  4006. break;
  4007. case 0:
  4008. $result = $this->op === '=' || $this->op === '>=' || $this->op === '=<' || $this->op === '<=';
  4009. break;
  4010. case 1:
  4011. $result = $this->op === '>' || $this->op === '>=';
  4012. break;
  4013. }
  4014. break;
  4015. }
  4016. return $this->negate ? !$result : $result;
  4017. }
  4018. }
  4019. /**
  4020. * DefaultFunc
  4021. *
  4022. * @package Less
  4023. * @subpackage tree
  4024. */
  4025. class Less_Tree_DefaultFunc{
  4026. static $error_;
  4027. static $value_;
  4028. public static function compile(){
  4029. if( self::$error_ ){
  4030. throw new Exception(self::$error_);
  4031. }
  4032. if( self::$value_ !== null ){
  4033. return self::$value_ ? new Less_Tree_Keyword('true') : new Less_Tree_Keyword('false');
  4034. }
  4035. }
  4036. public static function value( $v ){
  4037. self::$value_ = $v;
  4038. }
  4039. public static function error( $e ){
  4040. self::$error_ = $e;
  4041. }
  4042. public static function reset(){
  4043. self::$value_ = self::$error_ = null;
  4044. }
  4045. }
  4046. /**
  4047. * DetachedRuleset
  4048. *
  4049. * @package Less
  4050. * @subpackage tree
  4051. */
  4052. class Less_Tree_DetachedRuleset extends Less_Tree{
  4053. public $ruleset;
  4054. public $frames;
  4055. public $type = 'DetachedRuleset';
  4056. public function __construct( $ruleset, $frames = null ){
  4057. $this->ruleset = $ruleset;
  4058. $this->frames = $frames;
  4059. }
  4060. public function accept($visitor) {
  4061. $this->ruleset = $visitor->visitObj($this->ruleset);
  4062. }
  4063. public function compile($env){
  4064. if( $this->frames ){
  4065. $frames = $this->frames;
  4066. }else{
  4067. $frames = $env->frames;
  4068. }
  4069. return new Less_Tree_DetachedRuleset($this->ruleset, $frames);
  4070. }
  4071. public function callEval($env) {
  4072. if( $this->frames ){
  4073. return $this->ruleset->compile( $env->copyEvalEnv( array_merge($this->frames,$env->frames) ) );
  4074. }
  4075. return $this->ruleset->compile( $env );
  4076. }
  4077. }
  4078. /**
  4079. * Dimension
  4080. *
  4081. * @package Less
  4082. * @subpackage tree
  4083. */
  4084. class Less_Tree_Dimension extends Less_Tree{
  4085. public $value;
  4086. public $unit;
  4087. public $type = 'Dimension';
  4088. public function __construct($value, $unit = null){
  4089. $this->value = floatval($value);
  4090. if( $unit && ($unit instanceof Less_Tree_Unit) ){
  4091. $this->unit = $unit;
  4092. }elseif( $unit ){
  4093. $this->unit = new Less_Tree_Unit( array($unit) );
  4094. }else{
  4095. $this->unit = new Less_Tree_Unit( );
  4096. }
  4097. }
  4098. public function accept( $visitor ){
  4099. $this->unit = $visitor->visitObj( $this->unit );
  4100. }
  4101. public function compile(){
  4102. return $this;
  4103. }
  4104. public function toColor() {
  4105. return new Less_Tree_Color(array($this->value, $this->value, $this->value));
  4106. }
  4107. /**
  4108. * @see Less_Tree::genCSS
  4109. */
  4110. public function genCSS( $output ){
  4111. if( Less_Parser::$options['strictUnits'] && !$this->unit->isSingular() ){
  4112. throw new Less_Exception_Compiler("Multiple units in dimension. Correct the units or use the unit function. Bad unit: ".$this->unit->toString());
  4113. }
  4114. $value = Less_Functions::fround( $this->value );
  4115. $strValue = (string)$value;
  4116. if( $value !== 0 && $value < 0.000001 && $value > -0.000001 ){
  4117. // would be output 1e-6 etc.
  4118. $strValue = number_format($strValue,10);
  4119. $strValue = preg_replace('/\.?0+$/','', $strValue);
  4120. }
  4121. if( Less_Parser::$options['compress'] ){
  4122. // Zero values doesn't need a unit
  4123. if( $value === 0 && $this->unit->isLength() ){
  4124. $output->add( $strValue );
  4125. return $strValue;
  4126. }
  4127. // Float values doesn't need a leading zero
  4128. if( $value > 0 && $value < 1 && $strValue[0] === '0' ){
  4129. $strValue = substr($strValue,1);
  4130. }
  4131. }
  4132. $output->add( $strValue );
  4133. $this->unit->genCSS( $output );
  4134. }
  4135. public function __toString(){
  4136. return $this->toCSS();
  4137. }
  4138. // In an operation between two Dimensions,
  4139. // we default to the first Dimension's unit,
  4140. // so `1px + 2em` will yield `3px`.
  4141. /**
  4142. * @param string $op
  4143. */
  4144. public function operate( $op, $other){
  4145. $value = Less_Functions::operate( $op, $this->value, $other->value);
  4146. $unit = clone $this->unit;
  4147. if( $op === '+' || $op === '-' ){
  4148. if( !$unit->numerator && !$unit->denominator ){
  4149. $unit->numerator = $other->unit->numerator;
  4150. $unit->denominator = $other->unit->denominator;
  4151. }elseif( !$other->unit->numerator && !$other->unit->denominator ){
  4152. // do nothing
  4153. }else{
  4154. $other = $other->convertTo( $this->unit->usedUnits());
  4155. if( Less_Parser::$options['strictUnits'] && $other->unit->toString() !== $unit->toCSS() ){
  4156. throw new Less_Exception_Compiler("Incompatible units. Change the units or use the unit function. Bad units: '".$unit->toString() . "' and ".$other->unit->toString()+"'.");
  4157. }
  4158. $value = Less_Functions::operate( $op, $this->value, $other->value);
  4159. }
  4160. }elseif( $op === '*' ){
  4161. $unit->numerator = array_merge($unit->numerator, $other->unit->numerator);
  4162. $unit->denominator = array_merge($unit->denominator, $other->unit->denominator);
  4163. sort($unit->numerator);
  4164. sort($unit->denominator);
  4165. $unit->cancel();
  4166. }elseif( $op === '/' ){
  4167. $unit->numerator = array_merge($unit->numerator, $other->unit->denominator);
  4168. $unit->denominator = array_merge($unit->denominator, $other->unit->numerator);
  4169. sort($unit->numerator);
  4170. sort($unit->denominator);
  4171. $unit->cancel();
  4172. }
  4173. return new Less_Tree_Dimension( $value, $unit);
  4174. }
  4175. public function compare($other) {
  4176. if ($other instanceof Less_Tree_Dimension) {
  4177. if( $this->unit->isEmpty() || $other->unit->isEmpty() ){
  4178. $a = $this;
  4179. $b = $other;
  4180. } else {
  4181. $a = $this->unify();
  4182. $b = $other->unify();
  4183. if( $a->unit->compare($b->unit) !== 0 ){
  4184. return -1;
  4185. }
  4186. }
  4187. $aValue = $a->value;
  4188. $bValue = $b->value;
  4189. if ($bValue > $aValue) {
  4190. return -1;
  4191. } elseif ($bValue < $aValue) {
  4192. return 1;
  4193. } else {
  4194. return 0;
  4195. }
  4196. } else {
  4197. return -1;
  4198. }
  4199. }
  4200. public function unify() {
  4201. return $this->convertTo(array('length'=> 'px', 'duration'=> 's', 'angle' => 'rad' ));
  4202. }
  4203. public function convertTo($conversions) {
  4204. $value = $this->value;
  4205. $unit = clone $this->unit;
  4206. if( is_string($conversions) ){
  4207. $derivedConversions = array();
  4208. foreach( Less_Tree_UnitConversions::$groups as $i ){
  4209. if( isset(Less_Tree_UnitConversions::${$i}[$conversions]) ){
  4210. $derivedConversions = array( $i => $conversions);
  4211. }
  4212. }
  4213. $conversions = $derivedConversions;
  4214. }
  4215. foreach($conversions as $groupName => $targetUnit){
  4216. $group = Less_Tree_UnitConversions::${$groupName};
  4217. //numerator
  4218. foreach($unit->numerator as $i => $atomicUnit){
  4219. $atomicUnit = $unit->numerator[$i];
  4220. if( !isset($group[$atomicUnit]) ){
  4221. continue;
  4222. }
  4223. $value = $value * ($group[$atomicUnit] / $group[$targetUnit]);
  4224. $unit->numerator[$i] = $targetUnit;
  4225. }
  4226. //denominator
  4227. foreach($unit->denominator as $i => $atomicUnit){
  4228. $atomicUnit = $unit->denominator[$i];
  4229. if( !isset($group[$atomicUnit]) ){
  4230. continue;
  4231. }
  4232. $value = $value / ($group[$atomicUnit] / $group[$targetUnit]);
  4233. $unit->denominator[$i] = $targetUnit;
  4234. }
  4235. }
  4236. $unit->cancel();
  4237. return new Less_Tree_Dimension( $value, $unit);
  4238. }
  4239. }
  4240. /**
  4241. * Directive
  4242. *
  4243. * @package Less
  4244. * @subpackage tree
  4245. */
  4246. class Less_Tree_Directive extends Less_Tree{
  4247. public $name;
  4248. public $value;
  4249. public $rules;
  4250. public $index;
  4251. public $isReferenced;
  4252. public $currentFileInfo;
  4253. public $debugInfo;
  4254. public $type = 'Directive';
  4255. public function __construct($name, $value = null, $rules, $index = null, $currentFileInfo = null, $debugInfo = null ){
  4256. $this->name = $name;
  4257. $this->value = $value;
  4258. if( $rules ){
  4259. $this->rules = $rules;
  4260. $this->rules->allowImports = true;
  4261. }
  4262. $this->index = $index;
  4263. $this->currentFileInfo = $currentFileInfo;
  4264. $this->debugInfo = $debugInfo;
  4265. }
  4266. public function accept( $visitor ){
  4267. if( $this->rules ){
  4268. $this->rules = $visitor->visitObj( $this->rules );
  4269. }
  4270. if( $this->value ){
  4271. $this->value = $visitor->visitObj( $this->value );
  4272. }
  4273. }
  4274. /**
  4275. * @see Less_Tree::genCSS
  4276. */
  4277. public function genCSS( $output ){
  4278. $value = $this->value;
  4279. $rules = $this->rules;
  4280. $output->add( $this->name, $this->currentFileInfo, $this->index );
  4281. if( $this->value ){
  4282. $output->add(' ');
  4283. $this->value->genCSS($output);
  4284. }
  4285. if( $this->rules ){
  4286. Less_Tree::outputRuleset( $output, array($this->rules));
  4287. } else {
  4288. $output->add(';');
  4289. }
  4290. }
  4291. public function compile($env){
  4292. $value = $this->value;
  4293. $rules = $this->rules;
  4294. if( $value ){
  4295. $value = $value->compile($env);
  4296. }
  4297. if( $rules ){
  4298. $rules = $rules->compile($env);
  4299. $rules->root = true;
  4300. }
  4301. return new Less_Tree_Directive( $this->name, $value, $rules, $this->index, $this->currentFileInfo, $this->debugInfo );
  4302. }
  4303. public function variable($name){
  4304. if( $this->rules ){
  4305. return $this->rules->variable($name);
  4306. }
  4307. }
  4308. public function find($selector){
  4309. if( $this->rules ){
  4310. return $this->rules->find($selector, $this);
  4311. }
  4312. }
  4313. //rulesets: function () { if (this.rules) return tree.Ruleset.prototype.rulesets.apply(this.rules); },
  4314. public function markReferenced(){
  4315. $this->isReferenced = true;
  4316. if( $this->rules ){
  4317. Less_Tree::ReferencedArray($this->rules->rules);
  4318. }
  4319. }
  4320. }
  4321. /**
  4322. * Element
  4323. *
  4324. * @package Less
  4325. * @subpackage tree
  4326. */
  4327. class Less_Tree_Element extends Less_Tree{
  4328. public $combinator = '';
  4329. public $value = '';
  4330. public $index;
  4331. public $currentFileInfo;
  4332. public $type = 'Element';
  4333. public $value_is_object = false;
  4334. public function __construct($combinator, $value, $index = null, $currentFileInfo = null ){
  4335. $this->value = $value;
  4336. $this->value_is_object = is_object($value);
  4337. if( $combinator ){
  4338. $this->combinator = $combinator;
  4339. }
  4340. $this->index = $index;
  4341. $this->currentFileInfo = $currentFileInfo;
  4342. }
  4343. public function accept( $visitor ){
  4344. if( $this->value_is_object ){ //object or string
  4345. $this->value = $visitor->visitObj( $this->value );
  4346. }
  4347. }
  4348. public function compile($env){
  4349. if( Less_Environment::$mixin_stack ){
  4350. return new Less_Tree_Element($this->combinator, ($this->value_is_object ? $this->value->compile($env) : $this->value), $this->index, $this->currentFileInfo );
  4351. }
  4352. if( $this->value_is_object ){
  4353. $this->value = $this->value->compile($env);
  4354. }
  4355. return $this;
  4356. }
  4357. /**
  4358. * @see Less_Tree::genCSS
  4359. */
  4360. public function genCSS( $output ){
  4361. $output->add( $this->toCSS(), $this->currentFileInfo, $this->index );
  4362. }
  4363. public function toCSS(){
  4364. if( $this->value_is_object ){
  4365. $value = $this->value->toCSS();
  4366. }else{
  4367. $value = $this->value;
  4368. }
  4369. if( $value === '' && $this->combinator && $this->combinator === '&' ){
  4370. return '';
  4371. }
  4372. return Less_Environment::$_outputMap[$this->combinator] . $value;
  4373. }
  4374. }
  4375. /**
  4376. * Expression
  4377. *
  4378. * @package Less
  4379. * @subpackage tree
  4380. */
  4381. class Less_Tree_Expression extends Less_Tree{
  4382. public $value = array();
  4383. public $parens = false;
  4384. public $parensInOp = false;
  4385. public $type = 'Expression';
  4386. public function __construct( $value, $parens = null ){
  4387. $this->value = $value;
  4388. $this->parens = $parens;
  4389. }
  4390. public function accept( $visitor ){
  4391. $this->value = $visitor->visitArray( $this->value );
  4392. }
  4393. public function compile($env) {
  4394. $doubleParen = false;
  4395. if( $this->parens && !$this->parensInOp ){
  4396. Less_Environment::$parensStack++;
  4397. }
  4398. $returnValue = null;
  4399. if( $this->value ){
  4400. $count = count($this->value);
  4401. if( $count > 1 ){
  4402. $ret = array();
  4403. foreach($this->value as $e){
  4404. $ret[] = $e->compile($env);
  4405. }
  4406. $returnValue = new Less_Tree_Expression($ret);
  4407. }else{
  4408. if( ($this->value[0] instanceof Less_Tree_Expression) && $this->value[0]->parens && !$this->value[0]->parensInOp ){
  4409. $doubleParen = true;
  4410. }
  4411. $returnValue = $this->value[0]->compile($env);
  4412. }
  4413. } else {
  4414. $returnValue = $this;
  4415. }
  4416. if( $this->parens ){
  4417. if( !$this->parensInOp ){
  4418. Less_Environment::$parensStack--;
  4419. }elseif( !Less_Environment::isMathOn() && !$doubleParen ){
  4420. $returnValue = new Less_Tree_Paren($returnValue);
  4421. }
  4422. }
  4423. return $returnValue;
  4424. }
  4425. /**
  4426. * @see Less_Tree::genCSS
  4427. */
  4428. public function genCSS( $output ){
  4429. $val_len = count($this->value);
  4430. for( $i = 0; $i < $val_len; $i++ ){
  4431. $this->value[$i]->genCSS( $output );
  4432. if( $i + 1 < $val_len ){
  4433. $output->add( ' ' );
  4434. }
  4435. }
  4436. }
  4437. public function throwAwayComments() {
  4438. if( is_array($this->value) ){
  4439. $new_value = array();
  4440. foreach($this->value as $v){
  4441. if( $v instanceof Less_Tree_Comment ){
  4442. continue;
  4443. }
  4444. $new_value[] = $v;
  4445. }
  4446. $this->value = $new_value;
  4447. }
  4448. }
  4449. }
  4450. /**
  4451. * Extend
  4452. *
  4453. * @package Less
  4454. * @subpackage tree
  4455. */
  4456. class Less_Tree_Extend extends Less_Tree{
  4457. public $selector;
  4458. public $option;
  4459. public $index;
  4460. public $selfSelectors = array();
  4461. public $allowBefore;
  4462. public $allowAfter;
  4463. public $firstExtendOnThisSelectorPath;
  4464. public $type = 'Extend';
  4465. public $ruleset;
  4466. public $object_id;
  4467. public $parent_ids = array();
  4468. /**
  4469. * @param integer $index
  4470. */
  4471. public function __construct($selector, $option, $index){
  4472. static $i = 0;
  4473. $this->selector = $selector;
  4474. $this->option = $option;
  4475. $this->index = $index;
  4476. switch($option){
  4477. case "all":
  4478. $this->allowBefore = true;
  4479. $this->allowAfter = true;
  4480. break;
  4481. default:
  4482. $this->allowBefore = false;
  4483. $this->allowAfter = false;
  4484. break;
  4485. }
  4486. $this->object_id = $i++;
  4487. $this->parent_ids = array($this->object_id);
  4488. }
  4489. public function accept( $visitor ){
  4490. $this->selector = $visitor->visitObj( $this->selector );
  4491. }
  4492. public function compile( $env ){
  4493. Less_Parser::$has_extends = true;
  4494. $this->selector = $this->selector->compile($env);
  4495. return $this;
  4496. //return new Less_Tree_Extend( $this->selector->compile($env), $this->option, $this->index);
  4497. }
  4498. public function findSelfSelectors( $selectors ){
  4499. $selfElements = array();
  4500. for( $i = 0, $selectors_len = count($selectors); $i < $selectors_len; $i++ ){
  4501. $selectorElements = $selectors[$i]->elements;
  4502. // duplicate the logic in genCSS function inside the selector node.
  4503. // future TODO - move both logics into the selector joiner visitor
  4504. if( $i && $selectorElements && $selectorElements[0]->combinator === "") {
  4505. $selectorElements[0]->combinator = ' ';
  4506. }
  4507. $selfElements = array_merge( $selfElements, $selectors[$i]->elements );
  4508. }
  4509. $this->selfSelectors = array(new Less_Tree_Selector($selfElements));
  4510. }
  4511. }
  4512. /**
  4513. * CSS @import node
  4514. *
  4515. * The general strategy here is that we don't want to wait
  4516. * for the parsing to be completed, before we start importing
  4517. * the file. That's because in the context of a browser,
  4518. * most of the time will be spent waiting for the server to respond.
  4519. *
  4520. * On creation, we push the import path to our import queue, though
  4521. * `import,push`, we also pass it a callback, which it'll call once
  4522. * the file has been fetched, and parsed.
  4523. *
  4524. * @package Less
  4525. * @subpackage tree
  4526. */
  4527. class Less_Tree_Import extends Less_Tree{
  4528. public $options;
  4529. public $index;
  4530. public $path;
  4531. public $features;
  4532. public $currentFileInfo;
  4533. public $css;
  4534. public $skip;
  4535. public $root;
  4536. public $type = 'Import';
  4537. public function __construct($path, $features, $options, $index, $currentFileInfo = null ){
  4538. $this->options = $options;
  4539. $this->index = $index;
  4540. $this->path = $path;
  4541. $this->features = $features;
  4542. $this->currentFileInfo = $currentFileInfo;
  4543. if( is_array($options) ){
  4544. $this->options += array('inline'=>false);
  4545. if( isset($this->options['less']) || $this->options['inline'] ){
  4546. $this->css = !isset($this->options['less']) || !$this->options['less'] || $this->options['inline'];
  4547. } else {
  4548. $pathValue = $this->getPath();
  4549. if( $pathValue && preg_match('/css([\?;].*)?$/',$pathValue) ){
  4550. $this->css = true;
  4551. }
  4552. }
  4553. }
  4554. }
  4555. //
  4556. // The actual import node doesn't return anything, when converted to CSS.
  4557. // The reason is that it's used at the evaluation stage, so that the rules
  4558. // it imports can be treated like any other rules.
  4559. //
  4560. // In `eval`, we make sure all Import nodes get evaluated, recursively, so
  4561. // we end up with a flat structure, which can easily be imported in the parent
  4562. // ruleset.
  4563. //
  4564. public function accept($visitor){
  4565. if( $this->features ){
  4566. $this->features = $visitor->visitObj($this->features);
  4567. }
  4568. $this->path = $visitor->visitObj($this->path);
  4569. if( !$this->options['inline'] && $this->root ){
  4570. $this->root = $visitor->visit($this->root);
  4571. }
  4572. }
  4573. /**
  4574. * @see Less_Tree::genCSS
  4575. */
  4576. public function genCSS( $output ){
  4577. if( $this->css ){
  4578. $output->add( '@import ', $this->currentFileInfo, $this->index );
  4579. $this->path->genCSS( $output );
  4580. if( $this->features ){
  4581. $output->add( ' ' );
  4582. $this->features->genCSS( $output );
  4583. }
  4584. $output->add( ';' );
  4585. }
  4586. }
  4587. public function toCSS(){
  4588. $features = $this->features ? ' ' . $this->features->toCSS() : '';
  4589. if ($this->css) {
  4590. return "@import " . $this->path->toCSS() . $features . ";\n";
  4591. } else {
  4592. return "";
  4593. }
  4594. }
  4595. /**
  4596. * @return string
  4597. */
  4598. public function getPath(){
  4599. if ($this->path instanceof Less_Tree_Quoted) {
  4600. $path = $this->path->value;
  4601. $path = ( isset($this->css) || preg_match('/(\.[a-z]*$)|([\?;].*)$/',$path)) ? $path : $path . '.less';
  4602. } else if ($this->path instanceof Less_Tree_URL) {
  4603. $path = $this->path->value->value;
  4604. }else{
  4605. return null;
  4606. }
  4607. //remove query string and fragment
  4608. return preg_replace('/[\?#][^\?]*$/','',$path);
  4609. }
  4610. public function compileForImport( $env ){
  4611. return new Less_Tree_Import( $this->path->compile($env), $this->features, $this->options, $this->index, $this->currentFileInfo);
  4612. }
  4613. public function compilePath($env) {
  4614. $path = $this->path->compile($env);
  4615. $rootpath = '';
  4616. if( $this->currentFileInfo && $this->currentFileInfo['rootpath'] ){
  4617. $rootpath = $this->currentFileInfo['rootpath'];
  4618. }
  4619. if( !($path instanceof Less_Tree_URL) ){
  4620. if( $rootpath ){
  4621. $pathValue = $path->value;
  4622. // Add the base path if the import is relative
  4623. if( $pathValue && Less_Environment::isPathRelative($pathValue) ){
  4624. $path->value = $this->currentFileInfo['uri_root'].$pathValue;
  4625. }
  4626. }
  4627. $path->value = Less_Environment::normalizePath($path->value);
  4628. }
  4629. return $path;
  4630. }
  4631. public function compile( $env ){
  4632. $evald = $this->compileForImport($env);
  4633. //get path & uri
  4634. $path_and_uri = null;
  4635. if( is_callable(Less_Parser::$options['import_callback']) ){
  4636. $path_and_uri = call_user_func(Less_Parser::$options['import_callback'],$evald);
  4637. }
  4638. if( !$path_and_uri ){
  4639. $path_and_uri = $evald->PathAndUri();
  4640. }
  4641. if( $path_and_uri ){
  4642. list($full_path, $uri) = $path_and_uri;
  4643. }else{
  4644. $full_path = $uri = $evald->getPath();
  4645. }
  4646. //import once
  4647. if( $evald->skip( $full_path, $env) ){
  4648. return array();
  4649. }
  4650. if( $this->options['inline'] ){
  4651. //todo needs to reference css file not import
  4652. //$contents = new Less_Tree_Anonymous($this->root, 0, array('filename'=>$this->importedFilename), true );
  4653. Less_Parser::AddParsedFile($full_path);
  4654. $contents = new Less_Tree_Anonymous( file_get_contents($full_path), 0, array(), true );
  4655. if( $this->features ){
  4656. return new Less_Tree_Media( array($contents), $this->features->value );
  4657. }
  4658. return array( $contents );
  4659. }
  4660. // css ?
  4661. if( $evald->css ){
  4662. $features = ( $evald->features ? $evald->features->compile($env) : null );
  4663. return new Less_Tree_Import( $this->compilePath( $env), $features, $this->options, $this->index);
  4664. }
  4665. return $this->ParseImport( $full_path, $uri, $env );
  4666. }
  4667. /**
  4668. * Using the import directories, get the full absolute path and uri of the import
  4669. *
  4670. * @param Less_Tree_Import $evald
  4671. */
  4672. public function PathAndUri(){
  4673. $evald_path = $this->getPath();
  4674. if( $evald_path ){
  4675. $import_dirs = array();
  4676. if( Less_Environment::isPathRelative($evald_path) ){
  4677. //if the path is relative, the file should be in the current directory
  4678. $import_dirs[ $this->currentFileInfo['currentDirectory'] ] = $this->currentFileInfo['uri_root'];
  4679. }else{
  4680. //otherwise, the file should be relative to the server root
  4681. $import_dirs[ $this->currentFileInfo['entryPath'] ] = $this->currentFileInfo['entryUri'];
  4682. //if the user supplied entryPath isn't the actual root
  4683. $import_dirs[ $_SERVER['DOCUMENT_ROOT'] ] = '';
  4684. }
  4685. // always look in user supplied import directories
  4686. $import_dirs = array_merge( $import_dirs, Less_Parser::$options['import_dirs'] );
  4687. foreach( $import_dirs as $rootpath => $rooturi){
  4688. if( is_callable($rooturi) ){
  4689. list($path, $uri) = call_user_func($rooturi, $evald_path);
  4690. if( is_string($path) ){
  4691. $full_path = $path;
  4692. return array( $full_path, $uri );
  4693. }
  4694. }elseif( !empty($rootpath) ){
  4695. $path = rtrim($rootpath,'/\\').'/'.ltrim($evald_path,'/\\');
  4696. if( file_exists($path) ){
  4697. $full_path = Less_Environment::normalizePath($path);
  4698. $uri = Less_Environment::normalizePath(dirname($rooturi.$evald_path));
  4699. return array( $full_path, $uri );
  4700. } elseif( file_exists($path.'.less') ){
  4701. $full_path = Less_Environment::normalizePath($path.'.less');
  4702. $uri = Less_Environment::normalizePath(dirname($rooturi.$evald_path.'.less'));
  4703. return array( $full_path, $uri );
  4704. }
  4705. }
  4706. }
  4707. }
  4708. }
  4709. /**
  4710. * Parse the import url and return the rules
  4711. *
  4712. * @return Less_Tree_Media|array
  4713. */
  4714. public function ParseImport( $full_path, $uri, $env ){
  4715. $import_env = clone $env;
  4716. if( (isset($this->options['reference']) && $this->options['reference']) || isset($this->currentFileInfo['reference']) ){
  4717. $import_env->currentFileInfo['reference'] = true;
  4718. }
  4719. if( (isset($this->options['multiple']) && $this->options['multiple']) ){
  4720. $import_env->importMultiple = true;
  4721. }
  4722. $parser = new Less_Parser($import_env);
  4723. $root = $parser->parseFile($full_path, $uri, true);
  4724. $ruleset = new Less_Tree_Ruleset(array(), $root->rules );
  4725. $ruleset->evalImports($import_env);
  4726. return $this->features ? new Less_Tree_Media($ruleset->rules, $this->features->value) : $ruleset->rules;
  4727. }
  4728. /**
  4729. * Should the import be skipped?
  4730. *
  4731. * @return boolean|null
  4732. */
  4733. private function Skip($path, $env){
  4734. $path = Less_Parser::winPath(realpath($path));
  4735. if( $path && Less_Parser::FileParsed($path) ){
  4736. if( isset($this->currentFileInfo['reference']) ){
  4737. return true;
  4738. }
  4739. return !isset($this->options['multiple']) && !$env->importMultiple;
  4740. }
  4741. }
  4742. }
  4743. /**
  4744. * Javascript
  4745. *
  4746. * @package Less
  4747. * @subpackage tree
  4748. */
  4749. class Less_Tree_Javascript extends Less_Tree{
  4750. public $type = 'Javascript';
  4751. public $escaped;
  4752. public $expression;
  4753. public $index;
  4754. /**
  4755. * @param boolean $index
  4756. * @param boolean $escaped
  4757. */
  4758. public function __construct($string, $index, $escaped){
  4759. $this->escaped = $escaped;
  4760. $this->expression = $string;
  4761. $this->index = $index;
  4762. }
  4763. public function compile(){
  4764. return new Less_Tree_Anonymous('/* Sorry, can not do JavaScript evaluation in PHP... :( */');
  4765. }
  4766. }
  4767. /**
  4768. * Keyword
  4769. *
  4770. * @package Less
  4771. * @subpackage tree
  4772. */
  4773. class Less_Tree_Keyword extends Less_Tree{
  4774. public $value;
  4775. public $type = 'Keyword';
  4776. /**
  4777. * @param string $value
  4778. */
  4779. public function __construct($value){
  4780. $this->value = $value;
  4781. }
  4782. public function compile(){
  4783. return $this;
  4784. }
  4785. /**
  4786. * @see Less_Tree::genCSS
  4787. */
  4788. public function genCSS( $output ){
  4789. if( $this->value === '%') {
  4790. throw new Less_Exception_Compiler("Invalid % without number");
  4791. }
  4792. $output->add( $this->value );
  4793. }
  4794. public function compare($other) {
  4795. if ($other instanceof Less_Tree_Keyword) {
  4796. return $other->value === $this->value ? 0 : 1;
  4797. } else {
  4798. return -1;
  4799. }
  4800. }
  4801. }
  4802. /**
  4803. * Media
  4804. *
  4805. * @package Less
  4806. * @subpackage tree
  4807. */
  4808. class Less_Tree_Media extends Less_Tree{
  4809. public $features;
  4810. public $rules;
  4811. public $index;
  4812. public $currentFileInfo;
  4813. public $isReferenced;
  4814. public $type = 'Media';
  4815. public function __construct($value = array(), $features = array(), $index = null, $currentFileInfo = null ){
  4816. $this->index = $index;
  4817. $this->currentFileInfo = $currentFileInfo;
  4818. $selectors = $this->emptySelectors();
  4819. $this->features = new Less_Tree_Value($features);
  4820. $this->rules = array(new Less_Tree_Ruleset($selectors, $value));
  4821. $this->rules[0]->allowImports = true;
  4822. }
  4823. public function accept( $visitor ){
  4824. $this->features = $visitor->visitObj($this->features);
  4825. $this->rules = $visitor->visitArray($this->rules);
  4826. }
  4827. /**
  4828. * @see Less_Tree::genCSS
  4829. */
  4830. public function genCSS( $output ){
  4831. $output->add( '@media ', $this->currentFileInfo, $this->index );
  4832. $this->features->genCSS( $output );
  4833. Less_Tree::outputRuleset( $output, $this->rules);
  4834. }
  4835. public function compile($env) {
  4836. $media = new Less_Tree_Media(array(), array(), $this->index, $this->currentFileInfo );
  4837. $strictMathBypass = false;
  4838. if( Less_Parser::$options['strictMath'] === false) {
  4839. $strictMathBypass = true;
  4840. Less_Parser::$options['strictMath'] = true;
  4841. }
  4842. $media->features = $this->features->compile($env);
  4843. if( $strictMathBypass ){
  4844. Less_Parser::$options['strictMath'] = false;
  4845. }
  4846. $env->mediaPath[] = $media;
  4847. $env->mediaBlocks[] = $media;
  4848. array_unshift($env->frames, $this->rules[0]);
  4849. $media->rules = array($this->rules[0]->compile($env));
  4850. array_shift($env->frames);
  4851. array_pop($env->mediaPath);
  4852. return !$env->mediaPath ? $media->compileTop($env) : $media->compileNested($env);
  4853. }
  4854. public function variable($name) {
  4855. return $this->rules[0]->variable($name);
  4856. }
  4857. public function find($selector) {
  4858. return $this->rules[0]->find($selector, $this);
  4859. }
  4860. public function emptySelectors(){
  4861. $el = new Less_Tree_Element('','&', $this->index, $this->currentFileInfo );
  4862. $sels = array( new Less_Tree_Selector(array($el), array(), null, $this->index, $this->currentFileInfo) );
  4863. $sels[0]->mediaEmpty = true;
  4864. return $sels;
  4865. }
  4866. public function markReferenced(){
  4867. $this->rules[0]->markReferenced();
  4868. $this->isReferenced = true;
  4869. Less_Tree::ReferencedArray($this->rules[0]->rules);
  4870. }
  4871. // evaltop
  4872. public function compileTop($env) {
  4873. $result = $this;
  4874. if (count($env->mediaBlocks) > 1) {
  4875. $selectors = $this->emptySelectors();
  4876. $result = new Less_Tree_Ruleset($selectors, $env->mediaBlocks);
  4877. $result->multiMedia = true;
  4878. }
  4879. $env->mediaBlocks = array();
  4880. $env->mediaPath = array();
  4881. return $result;
  4882. }
  4883. public function compileNested($env) {
  4884. $path = array_merge($env->mediaPath, array($this));
  4885. // Extract the media-query conditions separated with `,` (OR).
  4886. foreach ($path as $key => $p) {
  4887. $value = $p->features instanceof Less_Tree_Value ? $p->features->value : $p->features;
  4888. $path[$key] = is_array($value) ? $value : array($value);
  4889. }
  4890. // Trace all permutations to generate the resulting media-query.
  4891. //
  4892. // (a, b and c) with nested (d, e) ->
  4893. // a and d
  4894. // a and e
  4895. // b and c and d
  4896. // b and c and e
  4897. $permuted = $this->permute($path);
  4898. $expressions = array();
  4899. foreach($permuted as $path){
  4900. for( $i=0, $len=count($path); $i < $len; $i++){
  4901. $path[$i] = Less_Parser::is_method($path[$i], 'toCSS') ? $path[$i] : new Less_Tree_Anonymous($path[$i]);
  4902. }
  4903. for( $i = count($path) - 1; $i > 0; $i-- ){
  4904. array_splice($path, $i, 0, array(new Less_Tree_Anonymous('and')));
  4905. }
  4906. $expressions[] = new Less_Tree_Expression($path);
  4907. }
  4908. $this->features = new Less_Tree_Value($expressions);
  4909. // Fake a tree-node that doesn't output anything.
  4910. return new Less_Tree_Ruleset(array(), array());
  4911. }
  4912. public function permute($arr) {
  4913. if (!$arr)
  4914. return array();
  4915. if (count($arr) == 1)
  4916. return $arr[0];
  4917. $result = array();
  4918. $rest = $this->permute(array_slice($arr, 1));
  4919. foreach ($rest as $r) {
  4920. foreach ($arr[0] as $a) {
  4921. $result[] = array_merge(
  4922. is_array($a) ? $a : array($a),
  4923. is_array($r) ? $r : array($r)
  4924. );
  4925. }
  4926. }
  4927. return $result;
  4928. }
  4929. public function bubbleSelectors($selectors) {
  4930. if( !$selectors) return;
  4931. $this->rules = array(new Less_Tree_Ruleset( $selectors, array($this->rules[0])));
  4932. }
  4933. }
  4934. /**
  4935. * A simple css name-value pair
  4936. * ex: width:100px;
  4937. *
  4938. * 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)
  4939. * Using the name-value object can speed up bootstrap compilation slightly, but it breaks color keyword interpretation: color:red -> color:#FF0000;
  4940. *
  4941. * @package Less
  4942. * @subpackage tree
  4943. */
  4944. class Less_Tree_NameValue extends Less_Tree{
  4945. public $name;
  4946. public $value;
  4947. public $index;
  4948. public $currentFileInfo;
  4949. public $type = 'NameValue';
  4950. public function __construct($name, $value = null, $index = null, $currentFileInfo = null ){
  4951. $this->name = $name;
  4952. $this->value = $value;
  4953. $this->index = $index;
  4954. $this->currentFileInfo = $currentFileInfo;
  4955. }
  4956. public function genCSS( $output ){
  4957. $output->add(
  4958. $this->name
  4959. . Less_Environment::$_outputMap[': ']
  4960. . $this->value
  4961. . (((Less_Environment::$lastRule && Less_Parser::$options['compress'])) ? "" : ";")
  4962. , $this->currentFileInfo, $this->index);
  4963. }
  4964. public function compile ($env){
  4965. return $this;
  4966. }
  4967. }
  4968. /**
  4969. * Negative
  4970. *
  4971. * @package Less
  4972. * @subpackage tree
  4973. */
  4974. class Less_Tree_Negative extends Less_Tree{
  4975. public $value;
  4976. public $type = 'Negative';
  4977. public function __construct($node){
  4978. $this->value = $node;
  4979. }
  4980. //function accept($visitor) {
  4981. // $this->value = $visitor->visit($this->value);
  4982. //}
  4983. /**
  4984. * @see Less_Tree::genCSS
  4985. */
  4986. public function genCSS( $output ){
  4987. $output->add( '-' );
  4988. $this->value->genCSS( $output );
  4989. }
  4990. public function compile($env) {
  4991. if( Less_Environment::isMathOn() ){
  4992. $ret = new Less_Tree_Operation('*', array( new Less_Tree_Dimension(-1), $this->value ) );
  4993. return $ret->compile($env);
  4994. }
  4995. return new Less_Tree_Negative( $this->value->compile($env) );
  4996. }
  4997. }
  4998. /**
  4999. * Operation
  5000. *
  5001. * @package Less
  5002. * @subpackage tree
  5003. */
  5004. class Less_Tree_Operation extends Less_Tree{
  5005. public $op;
  5006. public $operands;
  5007. public $isSpaced;
  5008. public $type = 'Operation';
  5009. /**
  5010. * @param string $op
  5011. */
  5012. public function __construct($op, $operands, $isSpaced = false){
  5013. $this->op = trim($op);
  5014. $this->operands = $operands;
  5015. $this->isSpaced = $isSpaced;
  5016. }
  5017. public function accept($visitor) {
  5018. $this->operands = $visitor->visitArray($this->operands);
  5019. }
  5020. public function compile($env){
  5021. $a = $this->operands[0]->compile($env);
  5022. $b = $this->operands[1]->compile($env);
  5023. if( Less_Environment::isMathOn() ){
  5024. if( $a instanceof Less_Tree_Dimension && $b instanceof Less_Tree_Color ){
  5025. $a = $a->toColor();
  5026. }elseif( $b instanceof Less_Tree_Dimension && $a instanceof Less_Tree_Color ){
  5027. $b = $b->toColor();
  5028. }
  5029. if( !method_exists($a,'operate') ){
  5030. throw new Less_Exception_Compiler("Operation on an invalid type");
  5031. }
  5032. return $a->operate( $this->op, $b);
  5033. }
  5034. return new Less_Tree_Operation($this->op, array($a, $b), $this->isSpaced );
  5035. }
  5036. /**
  5037. * @see Less_Tree::genCSS
  5038. */
  5039. public function genCSS( $output ){
  5040. $this->operands[0]->genCSS( $output );
  5041. if( $this->isSpaced ){
  5042. $output->add( " " );
  5043. }
  5044. $output->add( $this->op );
  5045. if( $this->isSpaced ){
  5046. $output->add( ' ' );
  5047. }
  5048. $this->operands[1]->genCSS( $output );
  5049. }
  5050. }
  5051. /**
  5052. * Paren
  5053. *
  5054. * @package Less
  5055. * @subpackage tree
  5056. */
  5057. class Less_Tree_Paren extends Less_Tree{
  5058. public $value;
  5059. public $type = 'Paren';
  5060. public function __construct($value) {
  5061. $this->value = $value;
  5062. }
  5063. public function accept($visitor){
  5064. $this->value = $visitor->visitObj($this->value);
  5065. }
  5066. /**
  5067. * @see Less_Tree::genCSS
  5068. */
  5069. public function genCSS( $output ){
  5070. $output->add( '(' );
  5071. $this->value->genCSS( $output );
  5072. $output->add( ')' );
  5073. }
  5074. public function compile($env) {
  5075. return new Less_Tree_Paren($this->value->compile($env));
  5076. }
  5077. }
  5078. /**
  5079. * Quoted
  5080. *
  5081. * @package Less
  5082. * @subpackage tree
  5083. */
  5084. class Less_Tree_Quoted extends Less_Tree{
  5085. public $escaped;
  5086. public $value;
  5087. public $quote;
  5088. public $index;
  5089. public $currentFileInfo;
  5090. public $type = 'Quoted';
  5091. /**
  5092. * @param string $str
  5093. */
  5094. public function __construct($str, $content = '', $escaped = false, $index = false, $currentFileInfo = null ){
  5095. $this->escaped = $escaped;
  5096. $this->value = $content;
  5097. if( $str ){
  5098. $this->quote = $str[0];
  5099. }
  5100. $this->index = $index;
  5101. $this->currentFileInfo = $currentFileInfo;
  5102. }
  5103. /**
  5104. * @see Less_Tree::genCSS
  5105. */
  5106. public function genCSS( $output ){
  5107. if( !$this->escaped ){
  5108. $output->add( $this->quote, $this->currentFileInfo, $this->index );
  5109. }
  5110. $output->add( $this->value );
  5111. if( !$this->escaped ){
  5112. $output->add( $this->quote );
  5113. }
  5114. }
  5115. public function compile($env){
  5116. $value = $this->value;
  5117. if( preg_match_all('/`([^`]+)`/', $this->value, $matches) ){
  5118. foreach($matches as $i => $match){
  5119. $js = new Less_Tree_JavaScript($matches[1], $this->index, true);
  5120. $js = $js->compile()->value;
  5121. $value = str_replace($matches[0][$i], $js, $value);
  5122. }
  5123. }
  5124. if( preg_match_all('/@\{([\w-]+)\}/',$value,$matches) ){
  5125. foreach($matches[1] as $i => $match){
  5126. $v = new Less_Tree_Variable('@' . $match, $this->index, $this->currentFileInfo );
  5127. $v = $v->compile($env);
  5128. $v = ($v instanceof Less_Tree_Quoted) ? $v->value : $v->toCSS();
  5129. $value = str_replace($matches[0][$i], $v, $value);
  5130. }
  5131. }
  5132. return new Less_Tree_Quoted($this->quote . $value . $this->quote, $value, $this->escaped, $this->index, $this->currentFileInfo);
  5133. }
  5134. public function compare($x) {
  5135. if( !Less_Parser::is_method($x, 'toCSS') ){
  5136. return -1;
  5137. }
  5138. $left = $this->toCSS();
  5139. $right = $x->toCSS();
  5140. if ($left === $right) {
  5141. return 0;
  5142. }
  5143. return $left < $right ? -1 : 1;
  5144. }
  5145. }
  5146. /**
  5147. * Rule
  5148. *
  5149. * @package Less
  5150. * @subpackage tree
  5151. */
  5152. class Less_Tree_Rule extends Less_Tree{
  5153. public $name;
  5154. public $value;
  5155. public $important;
  5156. public $merge;
  5157. public $index;
  5158. public $inline;
  5159. public $variable;
  5160. public $currentFileInfo;
  5161. public $type = 'Rule';
  5162. /**
  5163. * @param string $important
  5164. */
  5165. public function __construct($name, $value = null, $important = null, $merge = null, $index = null, $currentFileInfo = null, $inline = false){
  5166. $this->name = $name;
  5167. $this->value = ($value instanceof Less_Tree_Value || $value instanceof Less_Tree_Ruleset) ? $value : new Less_Tree_Value(array($value));
  5168. $this->important = $important ? ' ' . trim($important) : '';
  5169. $this->merge = $merge;
  5170. $this->index = $index;
  5171. $this->currentFileInfo = $currentFileInfo;
  5172. $this->inline = $inline;
  5173. $this->variable = ( is_string($name) && $name[0] === '@');
  5174. }
  5175. public function accept($visitor) {
  5176. $this->value = $visitor->visitObj( $this->value );
  5177. }
  5178. /**
  5179. * @see Less_Tree::genCSS
  5180. */
  5181. public function genCSS( $output ){
  5182. $output->add( $this->name . Less_Environment::$_outputMap[': '], $this->currentFileInfo, $this->index);
  5183. try{
  5184. $this->value->genCSS( $output);
  5185. }catch( Less_Exception_Parser $e ){
  5186. $e->index = $this->index;
  5187. $e->currentFile = $this->currentFileInfo;
  5188. throw $e;
  5189. }
  5190. $output->add( $this->important . (($this->inline || (Less_Environment::$lastRule && Less_Parser::$options['compress'])) ? "" : ";"), $this->currentFileInfo, $this->index);
  5191. }
  5192. public function compile ($env){
  5193. $name = $this->name;
  5194. if( is_array($name) ){
  5195. // expand 'primitive' name directly to get
  5196. // things faster (~10% for benchmark.less):
  5197. if( count($name) === 1 && $name[0] instanceof Less_Tree_Keyword ){
  5198. $name = $name[0]->value;
  5199. }else{
  5200. $name = $this->CompileName($env,$name);
  5201. }
  5202. }
  5203. $strictMathBypass = Less_Parser::$options['strictMath'];
  5204. if( $name === "font" && !Less_Parser::$options['strictMath'] ){
  5205. Less_Parser::$options['strictMath'] = true;
  5206. }
  5207. try {
  5208. $evaldValue = $this->value->compile($env);
  5209. if( !$this->variable && $evaldValue->type === "DetachedRuleset") {
  5210. throw new Less_Exception_Compiler("Rulesets cannot be evaluated on a property.", null, $this->index, $this->currentFileInfo);
  5211. }
  5212. if( Less_Environment::$mixin_stack ){
  5213. $return = new Less_Tree_Rule($name, $evaldValue, $this->important, $this->merge, $this->index, $this->currentFileInfo, $this->inline);
  5214. }else{
  5215. $this->name = $name;
  5216. $this->value = $evaldValue;
  5217. $return = $this;
  5218. }
  5219. }catch( Less_Exception_Parser $e ){
  5220. if( !is_numeric($e->index) ){
  5221. $e->index = $this->index;
  5222. $e->currentFile = $this->currentFileInfo;
  5223. }
  5224. throw $e;
  5225. }
  5226. Less_Parser::$options['strictMath'] = $strictMathBypass;
  5227. return $return;
  5228. }
  5229. public function CompileName( $env, $name ){
  5230. $output = new Less_Output();
  5231. foreach($name as $n){
  5232. $n->compile($env)->genCSS($output);
  5233. }
  5234. return $output->toString();
  5235. }
  5236. public function makeImportant(){
  5237. return new Less_Tree_Rule($this->name, $this->value, '!important', $this->merge, $this->index, $this->currentFileInfo, $this->inline);
  5238. }
  5239. }
  5240. /**
  5241. * Ruleset
  5242. *
  5243. * @package Less
  5244. * @subpackage tree
  5245. */
  5246. class Less_Tree_Ruleset extends Less_Tree{
  5247. protected $lookups;
  5248. public $_variables;
  5249. public $_rulesets;
  5250. public $strictImports;
  5251. public $selectors;
  5252. public $rules;
  5253. public $root;
  5254. public $allowImports;
  5255. public $paths;
  5256. public $firstRoot;
  5257. public $type = 'Ruleset';
  5258. public $multiMedia;
  5259. public $allExtends;
  5260. public $ruleset_id;
  5261. public $originalRuleset;
  5262. public $first_oelements;
  5263. public function SetRulesetIndex(){
  5264. $this->ruleset_id = Less_Parser::$next_id++;
  5265. $this->originalRuleset = $this->ruleset_id;
  5266. if( $this->selectors ){
  5267. foreach($this->selectors as $sel){
  5268. if( $sel->_oelements ){
  5269. $this->first_oelements[$sel->_oelements[0]] = true;
  5270. }
  5271. }
  5272. }
  5273. }
  5274. public function __construct($selectors, $rules, $strictImports = null){
  5275. $this->selectors = $selectors;
  5276. $this->rules = $rules;
  5277. $this->lookups = array();
  5278. $this->strictImports = $strictImports;
  5279. $this->SetRulesetIndex();
  5280. }
  5281. public function accept( $visitor ){
  5282. if( $this->paths ){
  5283. $paths_len = count($this->paths);
  5284. for($i = 0,$paths_len; $i < $paths_len; $i++ ){
  5285. $this->paths[$i] = $visitor->visitArray($this->paths[$i]);
  5286. }
  5287. }elseif( $this->selectors ){
  5288. $this->selectors = $visitor->visitArray($this->selectors);
  5289. }
  5290. if( $this->rules ){
  5291. $this->rules = $visitor->visitArray($this->rules);
  5292. }
  5293. }
  5294. public function compile($env){
  5295. $ruleset = $this->PrepareRuleset($env);
  5296. // Store the frames around mixin definitions,
  5297. // so they can be evaluated like closures when the time comes.
  5298. $rsRuleCnt = count($ruleset->rules);
  5299. for( $i = 0; $i < $rsRuleCnt; $i++ ){
  5300. if( $ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset ){
  5301. $ruleset->rules[$i] = $ruleset->rules[$i]->compile($env);
  5302. }
  5303. }
  5304. $mediaBlockCount = 0;
  5305. if( $env instanceof Less_Environment ){
  5306. $mediaBlockCount = count($env->mediaBlocks);
  5307. }
  5308. // Evaluate mixin calls.
  5309. $this->EvalMixinCalls( $ruleset, $env, $rsRuleCnt );
  5310. // Evaluate everything else
  5311. for( $i=0; $i<$rsRuleCnt; $i++ ){
  5312. if(! ($ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset) ){
  5313. $ruleset->rules[$i] = $ruleset->rules[$i]->compile($env);
  5314. }
  5315. }
  5316. // Evaluate everything else
  5317. for( $i=0; $i<$rsRuleCnt; $i++ ){
  5318. $rule = $ruleset->rules[$i];
  5319. // for rulesets, check if it is a css guard and can be removed
  5320. if( $rule instanceof Less_Tree_Ruleset && $rule->selectors && count($rule->selectors) === 1 ){
  5321. // check if it can be folded in (e.g. & where)
  5322. if( $rule->selectors[0]->isJustParentSelector() ){
  5323. array_splice($ruleset->rules,$i--,1);
  5324. $rsRuleCnt--;
  5325. for($j = 0; $j < count($rule->rules); $j++ ){
  5326. $subRule = $rule->rules[$j];
  5327. if( !($subRule instanceof Less_Tree_Rule) || !$subRule->variable ){
  5328. array_splice($ruleset->rules, ++$i, 0, array($subRule));
  5329. $rsRuleCnt++;
  5330. }
  5331. }
  5332. }
  5333. }
  5334. }
  5335. // Pop the stack
  5336. $env->shiftFrame();
  5337. if ($mediaBlockCount) {
  5338. $len = count($env->mediaBlocks);
  5339. for($i = $mediaBlockCount; $i < $len; $i++ ){
  5340. $env->mediaBlocks[$i]->bubbleSelectors($ruleset->selectors);
  5341. }
  5342. }
  5343. return $ruleset;
  5344. }
  5345. /**
  5346. * Compile Less_Tree_Mixin_Call objects
  5347. *
  5348. * @param Less_Tree_Ruleset $ruleset
  5349. * @param integer $rsRuleCnt
  5350. */
  5351. private function EvalMixinCalls( $ruleset, $env, &$rsRuleCnt ){
  5352. for($i=0; $i < $rsRuleCnt; $i++){
  5353. $rule = $ruleset->rules[$i];
  5354. if( $rule instanceof Less_Tree_Mixin_Call ){
  5355. $rule = $rule->compile($env);
  5356. $temp = array();
  5357. foreach($rule as $r){
  5358. if( ($r instanceof Less_Tree_Rule) && $r->variable ){
  5359. // do not pollute the scope if the variable is
  5360. // already there. consider returning false here
  5361. // but we need a way to "return" variable from mixins
  5362. if( !$ruleset->variable($r->name) ){
  5363. $temp[] = $r;
  5364. }
  5365. }else{
  5366. $temp[] = $r;
  5367. }
  5368. }
  5369. $temp_count = count($temp)-1;
  5370. array_splice($ruleset->rules, $i, 1, $temp);
  5371. $rsRuleCnt += $temp_count;
  5372. $i += $temp_count;
  5373. $ruleset->resetCache();
  5374. }elseif( $rule instanceof Less_Tree_RulesetCall ){
  5375. $rule = $rule->compile($env);
  5376. $rules = array();
  5377. foreach($rule->rules as $r){
  5378. if( ($r instanceof Less_Tree_Rule) && $r->variable ){
  5379. continue;
  5380. }
  5381. $rules[] = $r;
  5382. }
  5383. array_splice($ruleset->rules, $i, 1, $rules);
  5384. $temp_count = count($rules);
  5385. $rsRuleCnt += $temp_count - 1;
  5386. $i += $temp_count-1;
  5387. $ruleset->resetCache();
  5388. }
  5389. }
  5390. }
  5391. /**
  5392. * Compile the selectors and create a new ruleset object for the compile() method
  5393. *
  5394. */
  5395. private function PrepareRuleset($env){
  5396. $hasOnePassingSelector = false;
  5397. $selectors = array();
  5398. if( $this->selectors ){
  5399. Less_Tree_DefaultFunc::error("it is currently only allowed in parametric mixin guards,");
  5400. foreach($this->selectors as $s){
  5401. $selector = $s->compile($env);
  5402. $selectors[] = $selector;
  5403. if( $selector->evaldCondition ){
  5404. $hasOnePassingSelector = true;
  5405. }
  5406. }
  5407. Less_Tree_DefaultFunc::reset();
  5408. } else {
  5409. $hasOnePassingSelector = true;
  5410. }
  5411. if( $this->rules && $hasOnePassingSelector ){
  5412. $rules = $this->rules;
  5413. }else{
  5414. $rules = array();
  5415. }
  5416. $ruleset = new Less_Tree_Ruleset($selectors, $rules, $this->strictImports);
  5417. $ruleset->originalRuleset = $this->ruleset_id;
  5418. $ruleset->root = $this->root;
  5419. $ruleset->firstRoot = $this->firstRoot;
  5420. $ruleset->allowImports = $this->allowImports;
  5421. // push the current ruleset to the frames stack
  5422. $env->unshiftFrame($ruleset);
  5423. // Evaluate imports
  5424. if( $ruleset->root || $ruleset->allowImports || !$ruleset->strictImports ){
  5425. $ruleset->evalImports($env);
  5426. }
  5427. return $ruleset;
  5428. }
  5429. function evalImports($env) {
  5430. $rules_len = count($this->rules);
  5431. for($i=0; $i < $rules_len; $i++){
  5432. $rule = $this->rules[$i];
  5433. if( $rule instanceof Less_Tree_Import ){
  5434. $rules = $rule->compile($env);
  5435. if( is_array($rules) ){
  5436. array_splice($this->rules, $i, 1, $rules);
  5437. $temp_count = count($rules)-1;
  5438. $i += $temp_count;
  5439. $rules_len += $temp_count;
  5440. }else{
  5441. array_splice($this->rules, $i, 1, array($rules));
  5442. }
  5443. $this->resetCache();
  5444. }
  5445. }
  5446. }
  5447. function makeImportant(){
  5448. $important_rules = array();
  5449. foreach($this->rules as $rule){
  5450. if( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_Ruleset ){
  5451. $important_rules[] = $rule->makeImportant();
  5452. }else{
  5453. $important_rules[] = $rule;
  5454. }
  5455. }
  5456. return new Less_Tree_Ruleset($this->selectors, $important_rules, $this->strictImports );
  5457. }
  5458. public function matchArgs($args){
  5459. return !$args;
  5460. }
  5461. // lets you call a css selector with a guard
  5462. public function matchCondition( $args, $env ){
  5463. $lastSelector = end($this->selectors);
  5464. if( !$lastSelector->evaldCondition ){
  5465. return false;
  5466. }
  5467. if( $lastSelector->condition && !$lastSelector->condition->compile( $env->copyEvalEnv( $env->frames ) ) ){
  5468. return false;
  5469. }
  5470. return true;
  5471. }
  5472. function resetCache(){
  5473. $this->_rulesets = null;
  5474. $this->_variables = null;
  5475. $this->lookups = array();
  5476. }
  5477. public function variables(){
  5478. $this->_variables = array();
  5479. foreach( $this->rules as $r){
  5480. if ($r instanceof Less_Tree_Rule && $r->variable === true) {
  5481. $this->_variables[$r->name] = $r;
  5482. }
  5483. }
  5484. }
  5485. public function variable($name){
  5486. if( is_null($this->_variables) ){
  5487. $this->variables();
  5488. }
  5489. return isset($this->_variables[$name]) ? $this->_variables[$name] : null;
  5490. }
  5491. public function find( $selector, $self = null ){
  5492. $key = implode(' ',$selector->_oelements);
  5493. if( !isset($this->lookups[$key]) ){
  5494. if( !$self ){
  5495. $self = $this->ruleset_id;
  5496. }
  5497. $this->lookups[$key] = array();
  5498. $first_oelement = $selector->_oelements[0];
  5499. foreach($this->rules as $rule){
  5500. if( $rule instanceof Less_Tree_Ruleset && $rule->ruleset_id != $self ){
  5501. if( isset($rule->first_oelements[$first_oelement]) ){
  5502. foreach( $rule->selectors as $ruleSelector ){
  5503. $match = $selector->match($ruleSelector);
  5504. if( $match ){
  5505. if( $selector->elements_len > $match ){
  5506. $this->lookups[$key] = array_merge($this->lookups[$key], $rule->find( new Less_Tree_Selector(array_slice($selector->elements, $match)), $self));
  5507. } else {
  5508. $this->lookups[$key][] = $rule;
  5509. }
  5510. break;
  5511. }
  5512. }
  5513. }
  5514. }
  5515. }
  5516. }
  5517. return $this->lookups[$key];
  5518. }
  5519. /**
  5520. * @see Less_Tree::genCSS
  5521. */
  5522. public function genCSS( $output ){
  5523. if( !$this->root ){
  5524. Less_Environment::$tabLevel++;
  5525. }
  5526. $tabRuleStr = $tabSetStr = '';
  5527. if( !Less_Parser::$options['compress'] ){
  5528. if( Less_Environment::$tabLevel ){
  5529. $tabRuleStr = "\n".str_repeat( ' ' , Less_Environment::$tabLevel );
  5530. $tabSetStr = "\n".str_repeat( ' ' , Less_Environment::$tabLevel-1 );
  5531. }else{
  5532. $tabSetStr = $tabRuleStr = "\n";
  5533. }
  5534. }
  5535. $ruleNodes = array();
  5536. $rulesetNodes = array();
  5537. foreach($this->rules as $rule){
  5538. $class = get_class($rule);
  5539. if( ($class === 'Less_Tree_Media') || ($class === 'Less_Tree_Directive') || ($this->root && $class === 'Less_Tree_Comment') || ($class === 'Less_Tree_Ruleset' && $rule->rules) ){
  5540. $rulesetNodes[] = $rule;
  5541. }else{
  5542. $ruleNodes[] = $rule;
  5543. }
  5544. }
  5545. // If this is the root node, we don't render
  5546. // a selector, or {}.
  5547. if( !$this->root ){
  5548. /*
  5549. debugInfo = tree.debugInfo(env, this, tabSetStr);
  5550. if (debugInfo) {
  5551. output.add(debugInfo);
  5552. output.add(tabSetStr);
  5553. }
  5554. */
  5555. $paths_len = count($this->paths);
  5556. for( $i = 0; $i < $paths_len; $i++ ){
  5557. $path = $this->paths[$i];
  5558. $firstSelector = true;
  5559. foreach($path as $p){
  5560. $p->genCSS( $output, $firstSelector );
  5561. $firstSelector = false;
  5562. }
  5563. if( $i + 1 < $paths_len ){
  5564. $output->add( ',' . $tabSetStr );
  5565. }
  5566. }
  5567. $output->add( (Less_Parser::$options['compress'] ? '{' : " {") . $tabRuleStr );
  5568. }
  5569. // Compile rules and rulesets
  5570. $ruleNodes_len = count($ruleNodes);
  5571. $rulesetNodes_len = count($rulesetNodes);
  5572. for( $i = 0; $i < $ruleNodes_len; $i++ ){
  5573. $rule = $ruleNodes[$i];
  5574. // @page{ directive ends up with root elements inside it, a mix of rules and rulesets
  5575. // In this instance we do not know whether it is the last property
  5576. if( $i + 1 === $ruleNodes_len && (!$this->root || $rulesetNodes_len === 0 || $this->firstRoot ) ){
  5577. Less_Environment::$lastRule = true;
  5578. }
  5579. $rule->genCSS( $output );
  5580. if( !Less_Environment::$lastRule ){
  5581. $output->add( $tabRuleStr );
  5582. }else{
  5583. Less_Environment::$lastRule = false;
  5584. }
  5585. }
  5586. if( !$this->root ){
  5587. $output->add( $tabSetStr . '}' );
  5588. Less_Environment::$tabLevel--;
  5589. }
  5590. $firstRuleset = true;
  5591. $space = ($this->root ? $tabRuleStr : $tabSetStr);
  5592. for( $i = 0; $i < $rulesetNodes_len; $i++ ){
  5593. if( $ruleNodes_len && $firstRuleset ){
  5594. $output->add( $space );
  5595. }elseif( !$firstRuleset ){
  5596. $output->add( $space );
  5597. }
  5598. $firstRuleset = false;
  5599. $rulesetNodes[$i]->genCSS( $output);
  5600. }
  5601. if( !Less_Parser::$options['compress'] && $this->firstRoot ){
  5602. $output->add( "\n" );
  5603. }
  5604. }
  5605. function markReferenced(){
  5606. if( !$this->selectors ){
  5607. return;
  5608. }
  5609. foreach($this->selectors as $selector){
  5610. $selector->markReferenced();
  5611. }
  5612. }
  5613. public function joinSelectors( $context, $selectors ){
  5614. $paths = array();
  5615. if( is_array($selectors) ){
  5616. foreach($selectors as $selector) {
  5617. $this->joinSelector( $paths, $context, $selector);
  5618. }
  5619. }
  5620. return $paths;
  5621. }
  5622. public function joinSelector( &$paths, $context, $selector){
  5623. $hasParentSelector = false;
  5624. foreach($selector->elements as $el) {
  5625. if( $el->value === '&') {
  5626. $hasParentSelector = true;
  5627. }
  5628. }
  5629. if( !$hasParentSelector ){
  5630. if( $context ){
  5631. foreach($context as $context_el){
  5632. $paths[] = array_merge($context_el, array($selector) );
  5633. }
  5634. }else {
  5635. $paths[] = array($selector);
  5636. }
  5637. return;
  5638. }
  5639. // The paths are [[Selector]]
  5640. // The first list is a list of comma seperated selectors
  5641. // The inner list is a list of inheritance seperated selectors
  5642. // e.g.
  5643. // .a, .b {
  5644. // .c {
  5645. // }
  5646. // }
  5647. // == [[.a] [.c]] [[.b] [.c]]
  5648. //
  5649. // the elements from the current selector so far
  5650. $currentElements = array();
  5651. // the current list of new selectors to add to the path.
  5652. // We will build it up. We initiate it with one empty selector as we "multiply" the new selectors
  5653. // by the parents
  5654. $newSelectors = array(array());
  5655. foreach( $selector->elements as $el){
  5656. // non parent reference elements just get added
  5657. if( $el->value !== '&' ){
  5658. $currentElements[] = $el;
  5659. } else {
  5660. // the new list of selectors to add
  5661. $selectorsMultiplied = array();
  5662. // merge the current list of non parent selector elements
  5663. // on to the current list of selectors to add
  5664. if( $currentElements ){
  5665. $this->mergeElementsOnToSelectors( $currentElements, $newSelectors);
  5666. }
  5667. // loop through our current selectors
  5668. foreach($newSelectors as $sel){
  5669. // if we don't have any parent paths, the & might be in a mixin so that it can be used
  5670. // whether there are parents or not
  5671. if( !$context ){
  5672. // the combinator used on el should now be applied to the next element instead so that
  5673. // it is not lost
  5674. if( $sel ){
  5675. $sel[0]->elements = array_slice($sel[0]->elements,0);
  5676. $sel[0]->elements[] = new Less_Tree_Element($el->combinator, '', $el->index, $el->currentFileInfo );
  5677. }
  5678. $selectorsMultiplied[] = $sel;
  5679. }else {
  5680. // and the parent selectors
  5681. foreach($context as $parentSel){
  5682. // We need to put the current selectors
  5683. // then join the last selector's elements on to the parents selectors
  5684. // our new selector path
  5685. $newSelectorPath = array();
  5686. // selectors from the parent after the join
  5687. $afterParentJoin = array();
  5688. $newJoinedSelectorEmpty = true;
  5689. //construct the joined selector - if & is the first thing this will be empty,
  5690. // if not newJoinedSelector will be the last set of elements in the selector
  5691. if( $sel ){
  5692. $newSelectorPath = $sel;
  5693. $lastSelector = array_pop($newSelectorPath);
  5694. $newJoinedSelector = $selector->createDerived( array_slice($lastSelector->elements,0) );
  5695. $newJoinedSelectorEmpty = false;
  5696. }
  5697. else {
  5698. $newJoinedSelector = $selector->createDerived(array());
  5699. }
  5700. //put together the parent selectors after the join
  5701. if ( count($parentSel) > 1) {
  5702. $afterParentJoin = array_merge($afterParentJoin, array_slice($parentSel,1) );
  5703. }
  5704. if ( $parentSel ){
  5705. $newJoinedSelectorEmpty = false;
  5706. // join the elements so far with the first part of the parent
  5707. $newJoinedSelector->elements[] = new Less_Tree_Element( $el->combinator, $parentSel[0]->elements[0]->value, $el->index, $el->currentFileInfo);
  5708. $newJoinedSelector->elements = array_merge( $newJoinedSelector->elements, array_slice($parentSel[0]->elements, 1) );
  5709. }
  5710. if (!$newJoinedSelectorEmpty) {
  5711. // now add the joined selector
  5712. $newSelectorPath[] = $newJoinedSelector;
  5713. }
  5714. // and the rest of the parent
  5715. $newSelectorPath = array_merge($newSelectorPath, $afterParentJoin);
  5716. // add that to our new set of selectors
  5717. $selectorsMultiplied[] = $newSelectorPath;
  5718. }
  5719. }
  5720. }
  5721. // our new selectors has been multiplied, so reset the state
  5722. $newSelectors = $selectorsMultiplied;
  5723. $currentElements = array();
  5724. }
  5725. }
  5726. // if we have any elements left over (e.g. .a& .b == .b)
  5727. // add them on to all the current selectors
  5728. if( $currentElements ){
  5729. $this->mergeElementsOnToSelectors($currentElements, $newSelectors);
  5730. }
  5731. foreach( $newSelectors as $new_sel){
  5732. if( $new_sel ){
  5733. $paths[] = $new_sel;
  5734. }
  5735. }
  5736. }
  5737. function mergeElementsOnToSelectors( $elements, &$selectors){
  5738. if( !$selectors ){
  5739. $selectors[] = array( new Less_Tree_Selector($elements) );
  5740. return;
  5741. }
  5742. foreach( $selectors as &$sel){
  5743. // if the previous thing in sel is a parent this needs to join on to it
  5744. if( $sel ){
  5745. $last = count($sel)-1;
  5746. $sel[$last] = $sel[$last]->createDerived( array_merge($sel[$last]->elements, $elements) );
  5747. }else{
  5748. $sel[] = new Less_Tree_Selector( $elements );
  5749. }
  5750. }
  5751. }
  5752. }
  5753. /**
  5754. * RulesetCall
  5755. *
  5756. * @package Less
  5757. * @subpackage tree
  5758. */
  5759. class Less_Tree_RulesetCall extends Less_Tree{
  5760. public $variable;
  5761. public $type = "RulesetCall";
  5762. public function __construct($variable){
  5763. $this->variable = $variable;
  5764. }
  5765. public function accept($visitor) {}
  5766. public function compile( $env ){
  5767. $variable = new Less_Tree_Variable($this->variable);
  5768. $detachedRuleset = $variable->compile($env);
  5769. return $detachedRuleset->callEval($env);
  5770. }
  5771. }
  5772. /**
  5773. * Selector
  5774. *
  5775. * @package Less
  5776. * @subpackage tree
  5777. */
  5778. class Less_Tree_Selector extends Less_Tree{
  5779. public $elements;
  5780. public $condition;
  5781. public $extendList = array();
  5782. public $_css;
  5783. public $index;
  5784. public $evaldCondition = false;
  5785. public $type = 'Selector';
  5786. public $currentFileInfo = array();
  5787. public $isReferenced;
  5788. public $mediaEmpty;
  5789. public $elements_len = 0;
  5790. public $_oelements;
  5791. public $_oelements_len;
  5792. public $cacheable = true;
  5793. /**
  5794. * @param boolean $isReferenced
  5795. */
  5796. public function __construct( $elements, $extendList = array() , $condition = null, $index=null, $currentFileInfo=null, $isReferenced=null ){
  5797. $this->elements = $elements;
  5798. $this->elements_len = count($elements);
  5799. $this->extendList = $extendList;
  5800. $this->condition = $condition;
  5801. if( $currentFileInfo ){
  5802. $this->currentFileInfo = $currentFileInfo;
  5803. }
  5804. $this->isReferenced = $isReferenced;
  5805. if( !$condition ){
  5806. $this->evaldCondition = true;
  5807. }
  5808. $this->CacheElements();
  5809. }
  5810. public function accept($visitor) {
  5811. $this->elements = $visitor->visitArray($this->elements);
  5812. $this->extendList = $visitor->visitArray($this->extendList);
  5813. if( $this->condition ){
  5814. $this->condition = $visitor->visitObj($this->condition);
  5815. }
  5816. if( $visitor instanceof Less_Visitor_extendFinder ){
  5817. $this->CacheElements();
  5818. }
  5819. }
  5820. public function createDerived( $elements, $extendList = null, $evaldCondition = null ){
  5821. $newSelector = new Less_Tree_Selector( $elements, ($extendList ? $extendList : $this->extendList), null, $this->index, $this->currentFileInfo, $this->isReferenced);
  5822. $newSelector->evaldCondition = $evaldCondition ? $evaldCondition : $this->evaldCondition;
  5823. return $newSelector;
  5824. }
  5825. public function match( $other ){
  5826. if( !$other->_oelements || ($this->elements_len < $other->_oelements_len) ){
  5827. return 0;
  5828. }
  5829. for( $i = 0; $i < $other->_oelements_len; $i++ ){
  5830. if( $this->elements[$i]->value !== $other->_oelements[$i]) {
  5831. return 0;
  5832. }
  5833. }
  5834. return $other->_oelements_len; // return number of matched elements
  5835. }
  5836. public function CacheElements(){
  5837. $this->_oelements = array();
  5838. $css = '';
  5839. foreach($this->elements as $v){
  5840. $css .= $v->combinator;
  5841. if( !$v->value_is_object ){
  5842. $css .= $v->value;
  5843. continue;
  5844. }
  5845. if( !property_exists($v->value,'value') || !is_string($v->value->value) ){
  5846. $this->cacheable = false;
  5847. return;
  5848. }
  5849. $css .= $v->value->value;
  5850. }
  5851. $this->_oelements_len = preg_match_all('/[,&#\.\w-](?:[\w-]|(?:\\\\.))*/', $css, $matches);
  5852. if( $this->_oelements_len ){
  5853. $this->_oelements = $matches[0];
  5854. if( $this->_oelements[0] === '&' ){
  5855. array_shift($this->_oelements);
  5856. $this->_oelements_len--;
  5857. }
  5858. }
  5859. }
  5860. public function isJustParentSelector(){
  5861. return !$this->mediaEmpty &&
  5862. count($this->elements) === 1 &&
  5863. $this->elements[0]->value === '&' &&
  5864. ($this->elements[0]->combinator === ' ' || $this->elements[0]->combinator === '');
  5865. }
  5866. public function compile($env) {
  5867. $elements = array();
  5868. foreach($this->elements as $el){
  5869. $elements[] = $el->compile($env);
  5870. }
  5871. $extendList = array();
  5872. foreach($this->extendList as $el){
  5873. $extendList[] = $el->compile($el);
  5874. }
  5875. $evaldCondition = false;
  5876. if( $this->condition ){
  5877. $evaldCondition = $this->condition->compile($env);
  5878. }
  5879. return $this->createDerived( $elements, $extendList, $evaldCondition );
  5880. }
  5881. /**
  5882. * @see Less_Tree::genCSS
  5883. */
  5884. public function genCSS( $output, $firstSelector = true ){
  5885. if( !$firstSelector && $this->elements[0]->combinator === "" ){
  5886. $output->add(' ', $this->currentFileInfo, $this->index);
  5887. }
  5888. foreach($this->elements as $element){
  5889. $element->genCSS( $output );
  5890. }
  5891. }
  5892. public function markReferenced(){
  5893. $this->isReferenced = true;
  5894. }
  5895. public function getIsReferenced(){
  5896. return !isset($this->currentFileInfo['reference']) || !$this->currentFileInfo['reference'] || $this->isReferenced;
  5897. }
  5898. public function getIsOutput(){
  5899. return $this->evaldCondition;
  5900. }
  5901. }
  5902. /**
  5903. * UnicodeDescriptor
  5904. *
  5905. * @package Less
  5906. * @subpackage tree
  5907. */
  5908. class Less_Tree_UnicodeDescriptor extends Less_Tree{
  5909. public $value;
  5910. public $type = 'UnicodeDescriptor';
  5911. public function __construct($value){
  5912. $this->value = $value;
  5913. }
  5914. /**
  5915. * @see Less_Tree::genCSS
  5916. */
  5917. public function genCSS( $output ){
  5918. $output->add( $this->value );
  5919. }
  5920. public function compile(){
  5921. return $this;
  5922. }
  5923. }
  5924. /**
  5925. * Unit
  5926. *
  5927. * @package Less
  5928. * @subpackage tree
  5929. */
  5930. class Less_Tree_Unit extends Less_Tree{
  5931. var $numerator = array();
  5932. var $denominator = array();
  5933. public $backupUnit;
  5934. public $type = 'Unit';
  5935. public function __construct($numerator = array(), $denominator = array(), $backupUnit = null ){
  5936. $this->numerator = $numerator;
  5937. $this->denominator = $denominator;
  5938. $this->backupUnit = $backupUnit;
  5939. }
  5940. public function __clone(){
  5941. }
  5942. /**
  5943. * @see Less_Tree::genCSS
  5944. */
  5945. public function genCSS( $output ){
  5946. if( $this->numerator ){
  5947. $output->add( $this->numerator[0] );
  5948. }elseif( $this->denominator ){
  5949. $output->add( $this->denominator[0] );
  5950. }elseif( !Less_Parser::$options['strictUnits'] && $this->backupUnit ){
  5951. $output->add( $this->backupUnit );
  5952. return ;
  5953. }
  5954. }
  5955. public function toString(){
  5956. $returnStr = implode('*',$this->numerator);
  5957. foreach($this->denominator as $d){
  5958. $returnStr .= '/'.$d;
  5959. }
  5960. return $returnStr;
  5961. }
  5962. public function __toString(){
  5963. return $this->toString();
  5964. }
  5965. /**
  5966. * @param Less_Tree_Unit $other
  5967. */
  5968. public function compare($other) {
  5969. return $this->is( $other->toString() ) ? 0 : -1;
  5970. }
  5971. public function is($unitString){
  5972. return $this->toString() === $unitString;
  5973. }
  5974. public function isLength(){
  5975. $css = $this->toCSS();
  5976. return !!preg_match('/px|em|%|in|cm|mm|pc|pt|ex/',$css);
  5977. }
  5978. public function isAngle() {
  5979. return isset( Less_Tree_UnitConversions::$angle[$this->toCSS()] );
  5980. }
  5981. public function isEmpty(){
  5982. return !$this->numerator && !$this->denominator;
  5983. }
  5984. public function isSingular() {
  5985. return count($this->numerator) <= 1 && !$this->denominator;
  5986. }
  5987. public function usedUnits(){
  5988. $result = array();
  5989. foreach(Less_Tree_UnitConversions::$groups as $groupName){
  5990. $group = Less_Tree_UnitConversions::${$groupName};
  5991. foreach($this->numerator as $atomicUnit){
  5992. if( isset($group[$atomicUnit]) && !isset($result[$groupName]) ){
  5993. $result[$groupName] = $atomicUnit;
  5994. }
  5995. }
  5996. foreach($this->denominator as $atomicUnit){
  5997. if( isset($group[$atomicUnit]) && !isset($result[$groupName]) ){
  5998. $result[$groupName] = $atomicUnit;
  5999. }
  6000. }
  6001. }
  6002. return $result;
  6003. }
  6004. public function cancel(){
  6005. $counter = array();
  6006. $backup = null;
  6007. foreach($this->numerator as $atomicUnit){
  6008. if( !$backup ){
  6009. $backup = $atomicUnit;
  6010. }
  6011. $counter[$atomicUnit] = ( isset($counter[$atomicUnit]) ? $counter[$atomicUnit] : 0) + 1;
  6012. }
  6013. foreach($this->denominator as $atomicUnit){
  6014. if( !$backup ){
  6015. $backup = $atomicUnit;
  6016. }
  6017. $counter[$atomicUnit] = ( isset($counter[$atomicUnit]) ? $counter[$atomicUnit] : 0) - 1;
  6018. }
  6019. $this->numerator = array();
  6020. $this->denominator = array();
  6021. foreach($counter as $atomicUnit => $count){
  6022. if( $count > 0 ){
  6023. for( $i = 0; $i < $count; $i++ ){
  6024. $this->numerator[] = $atomicUnit;
  6025. }
  6026. }elseif( $count < 0 ){
  6027. for( $i = 0; $i < -$count; $i++ ){
  6028. $this->denominator[] = $atomicUnit;
  6029. }
  6030. }
  6031. }
  6032. if( !$this->numerator && !$this->denominator && $backup ){
  6033. $this->backupUnit = $backup;
  6034. }
  6035. sort($this->numerator);
  6036. sort($this->denominator);
  6037. }
  6038. }
  6039. /**
  6040. * UnitConversions
  6041. *
  6042. * @package Less
  6043. * @subpackage tree
  6044. */
  6045. class Less_Tree_UnitConversions{
  6046. public static $groups = array('length','duration','angle');
  6047. public static $length = array(
  6048. 'm'=> 1,
  6049. 'cm'=> 0.01,
  6050. 'mm'=> 0.001,
  6051. 'in'=> 0.0254,
  6052. 'px'=> 0.000264583, // 0.0254 / 96,
  6053. 'pt'=> 0.000352778, // 0.0254 / 72,
  6054. 'pc'=> 0.004233333, // 0.0254 / 72 * 12
  6055. );
  6056. public static $duration = array(
  6057. 's'=> 1,
  6058. 'ms'=> 0.001
  6059. );
  6060. public static $angle = array(
  6061. 'rad' => 0.1591549430919, // 1/(2*M_PI),
  6062. 'deg' => 0.002777778, // 1/360,
  6063. 'grad'=> 0.0025, // 1/400,
  6064. 'turn'=> 1
  6065. );
  6066. }
  6067. /**
  6068. * Url
  6069. *
  6070. * @package Less
  6071. * @subpackage tree
  6072. */
  6073. class Less_Tree_Url extends Less_Tree{
  6074. public $attrs;
  6075. public $value;
  6076. public $currentFileInfo;
  6077. public $isEvald;
  6078. public $type = 'Url';
  6079. public function __construct($value, $currentFileInfo = null, $isEvald = null){
  6080. $this->value = $value;
  6081. $this->currentFileInfo = $currentFileInfo;
  6082. $this->isEvald = $isEvald;
  6083. }
  6084. public function accept( $visitor ){
  6085. $this->value = $visitor->visitObj($this->value);
  6086. }
  6087. /**
  6088. * @see Less_Tree::genCSS
  6089. */
  6090. public function genCSS( $output ){
  6091. $output->add( 'url(' );
  6092. $this->value->genCSS( $output );
  6093. $output->add( ')' );
  6094. }
  6095. /**
  6096. * @param Less_Functions $ctx
  6097. */
  6098. public function compile($ctx){
  6099. $val = $this->value->compile($ctx);
  6100. if( !$this->isEvald ){
  6101. // Add the base path if the URL is relative
  6102. if( Less_Parser::$options['relativeUrls']
  6103. && $this->currentFileInfo
  6104. && is_string($val->value)
  6105. && Less_Environment::isPathRelative($val->value)
  6106. ){
  6107. $rootpath = $this->currentFileInfo['uri_root'];
  6108. if ( !$val->quote ){
  6109. $rootpath = preg_replace('/[\(\)\'"\s]/', '\\$1', $rootpath );
  6110. }
  6111. $val->value = $rootpath . $val->value;
  6112. }
  6113. $val->value = Less_Environment::normalizePath( $val->value);
  6114. }
  6115. // Add cache buster if enabled
  6116. if( Less_Parser::$options['urlArgs'] ){
  6117. if( !preg_match('/^\s*data:/',$val->value) ){
  6118. $delimiter = strpos($val->value,'?') === false ? '?' : '&';
  6119. $urlArgs = $delimiter . Less_Parser::$options['urlArgs'];
  6120. $hash_pos = strpos($val->value,'#');
  6121. if( $hash_pos !== false ){
  6122. $val->value = substr_replace($val->value,$urlArgs, $hash_pos, 0);
  6123. } else {
  6124. $val->value .= $urlArgs;
  6125. }
  6126. }
  6127. }
  6128. return new Less_Tree_URL($val, $this->currentFileInfo, true);
  6129. }
  6130. }
  6131. /**
  6132. * Value
  6133. *
  6134. * @package Less
  6135. * @subpackage tree
  6136. */
  6137. class Less_Tree_Value extends Less_Tree{
  6138. public $type = 'Value';
  6139. public $value;
  6140. public function __construct($value){
  6141. $this->value = $value;
  6142. }
  6143. public function accept($visitor) {
  6144. $this->value = $visitor->visitArray($this->value);
  6145. }
  6146. public function compile($env){
  6147. $ret = array();
  6148. $i = 0;
  6149. foreach($this->value as $i => $v){
  6150. $ret[] = $v->compile($env);
  6151. }
  6152. if( $i > 0 ){
  6153. return new Less_Tree_Value($ret);
  6154. }
  6155. return $ret[0];
  6156. }
  6157. /**
  6158. * @see Less_Tree::genCSS
  6159. */
  6160. function genCSS( $output ){
  6161. $len = count($this->value);
  6162. for($i = 0; $i < $len; $i++ ){
  6163. $this->value[$i]->genCSS( $output );
  6164. if( $i+1 < $len ){
  6165. $output->add( Less_Environment::$_outputMap[','] );
  6166. }
  6167. }
  6168. }
  6169. }
  6170. /**
  6171. * Variable
  6172. *
  6173. * @package Less
  6174. * @subpackage tree
  6175. */
  6176. class Less_Tree_Variable extends Less_Tree{
  6177. public $name;
  6178. public $index;
  6179. public $currentFileInfo;
  6180. public $evaluating = false;
  6181. public $type = 'Variable';
  6182. /**
  6183. * @param string $name
  6184. */
  6185. public function __construct($name, $index = null, $currentFileInfo = null) {
  6186. $this->name = $name;
  6187. $this->index = $index;
  6188. $this->currentFileInfo = $currentFileInfo;
  6189. }
  6190. public function compile($env) {
  6191. if( $this->name[1] === '@' ){
  6192. $v = new Less_Tree_Variable(substr($this->name, 1), $this->index + 1, $this->currentFileInfo);
  6193. $name = '@' . $v->compile($env)->value;
  6194. }else{
  6195. $name = $this->name;
  6196. }
  6197. if ($this->evaluating) {
  6198. throw new Less_Exception_Compiler("Recursive variable definition for " . $name, null, $this->index, $this->currentFileInfo);
  6199. }
  6200. $this->evaluating = true;
  6201. foreach($env->frames as $frame){
  6202. if( $v = $frame->variable($name) ){
  6203. $r = $v->value->compile($env);
  6204. $this->evaluating = false;
  6205. return $r;
  6206. }
  6207. }
  6208. throw new Less_Exception_Compiler("variable " . $name . " is undefined in file ".$this->currentFileInfo["filename"], null, $this->index, $this->currentFileInfo);
  6209. }
  6210. }
  6211. class Less_Tree_Mixin_Call extends Less_Tree{
  6212. public $selector;
  6213. public $arguments;
  6214. public $index;
  6215. public $currentFileInfo;
  6216. public $important;
  6217. public $type = 'MixinCall';
  6218. /**
  6219. * less.js: tree.mixin.Call
  6220. *
  6221. */
  6222. public function __construct($elements, $args, $index, $currentFileInfo, $important = false){
  6223. $this->selector = new Less_Tree_Selector($elements);
  6224. $this->arguments = $args;
  6225. $this->index = $index;
  6226. $this->currentFileInfo = $currentFileInfo;
  6227. $this->important = $important;
  6228. }
  6229. //function accept($visitor){
  6230. // $this->selector = $visitor->visit($this->selector);
  6231. // $this->arguments = $visitor->visit($this->arguments);
  6232. //}
  6233. public function compile($env){
  6234. $rules = array();
  6235. $match = false;
  6236. $isOneFound = false;
  6237. $candidates = array();
  6238. $defaultUsed = false;
  6239. $conditionResult = array();
  6240. $args = array();
  6241. foreach($this->arguments as $a){
  6242. $args[] = array('name'=> $a['name'], 'value' => $a['value']->compile($env) );
  6243. }
  6244. foreach($env->frames as $frame){
  6245. $mixins = $frame->find($this->selector);
  6246. if( !$mixins ){
  6247. continue;
  6248. }
  6249. $isOneFound = true;
  6250. $defNone = 0;
  6251. $defTrue = 1;
  6252. $defFalse = 2;
  6253. // To make `default()` function independent of definition order we have two "subpasses" here.
  6254. // At first we evaluate each guard *twice* (with `default() == true` and `default() == false`),
  6255. // and build candidate list with corresponding flags. Then, when we know all possible matches,
  6256. // we make a final decision.
  6257. $mixins_len = count($mixins);
  6258. for( $m = 0; $m < $mixins_len; $m++ ){
  6259. $mixin = $mixins[$m];
  6260. if( $this->IsRecursive( $env, $mixin ) ){
  6261. continue;
  6262. }
  6263. if( $mixin->matchArgs($args, $env) ){
  6264. $candidate = array('mixin' => $mixin, 'group' => $defNone);
  6265. if( $mixin instanceof Less_Tree_Ruleset ){
  6266. for( $f = 0; $f < 2; $f++ ){
  6267. Less_Tree_DefaultFunc::value($f);
  6268. $conditionResult[$f] = $mixin->matchCondition( $args, $env);
  6269. }
  6270. if( $conditionResult[0] || $conditionResult[1] ){
  6271. if( $conditionResult[0] != $conditionResult[1] ){
  6272. $candidate['group'] = $conditionResult[1] ? $defTrue : $defFalse;
  6273. }
  6274. $candidates[] = $candidate;
  6275. }
  6276. }else{
  6277. $candidates[] = $candidate;
  6278. }
  6279. $match = true;
  6280. }
  6281. }
  6282. Less_Tree_DefaultFunc::reset();
  6283. $count = array(0, 0, 0);
  6284. for( $m = 0; $m < count($candidates); $m++ ){
  6285. $count[ $candidates[$m]['group'] ]++;
  6286. }
  6287. if( $count[$defNone] > 0 ){
  6288. $defaultResult = $defFalse;
  6289. } else {
  6290. $defaultResult = $defTrue;
  6291. if( ($count[$defTrue] + $count[$defFalse]) > 1 ){
  6292. throw new Exception( 'Ambiguous use of `default()` found when matching for `'. $this->format($args) + '`' );
  6293. }
  6294. }
  6295. $candidates_length = count($candidates);
  6296. $length_1 = ($candidates_length == 1);
  6297. for( $m = 0; $m < $candidates_length; $m++){
  6298. $candidate = $candidates[$m]['group'];
  6299. if( ($candidate === $defNone) || ($candidate === $defaultResult) ){
  6300. try{
  6301. $mixin = $candidates[$m]['mixin'];
  6302. if( !($mixin instanceof Less_Tree_Mixin_Definition) ){
  6303. $mixin = new Less_Tree_Mixin_Definition('', array(), $mixin->rules, null, false);
  6304. $mixin->originalRuleset = $mixins[$m]->originalRuleset;
  6305. }
  6306. $rules = array_merge($rules, $mixin->evalCall($env, $args, $this->important)->rules);
  6307. } catch (Exception $e) {
  6308. //throw new Less_Exception_Compiler($e->getMessage(), $e->index, null, $this->currentFileInfo['filename']);
  6309. throw new Less_Exception_Compiler($e->getMessage(), null, null, $this->currentFileInfo);
  6310. }
  6311. }
  6312. }
  6313. if( $match ){
  6314. if( !$this->currentFileInfo || !isset($this->currentFileInfo['reference']) || !$this->currentFileInfo['reference'] ){
  6315. Less_Tree::ReferencedArray($rules);
  6316. }
  6317. return $rules;
  6318. }
  6319. }
  6320. if( $isOneFound ){
  6321. throw new Less_Exception_Compiler('No matching definition was found for `'.$this->Format( $args ).'`', null, $this->index, $this->currentFileInfo);
  6322. }else{
  6323. throw new Less_Exception_Compiler(trim($this->selector->toCSS()) . " is undefined in ".$this->currentFileInfo['filename'], null, $this->index);
  6324. }
  6325. }
  6326. /**
  6327. * Format the args for use in exception messages
  6328. *
  6329. */
  6330. private function Format($args){
  6331. $message = array();
  6332. if( $args ){
  6333. foreach($args as $a){
  6334. $argValue = '';
  6335. if( $a['name'] ){
  6336. $argValue += $a['name']+':';
  6337. }
  6338. if( is_object($a['value']) ){
  6339. $argValue += $a['value']->toCSS();
  6340. }else{
  6341. $argValue += '???';
  6342. }
  6343. $message[] = $argValue;
  6344. }
  6345. }
  6346. return implode(', ',$message);
  6347. }
  6348. /**
  6349. * Are we in a recursive mixin call?
  6350. *
  6351. * @return bool
  6352. */
  6353. private function IsRecursive( $env, $mixin ){
  6354. foreach($env->frames as $recur_frame){
  6355. if( !($mixin instanceof Less_Tree_Mixin_Definition) ){
  6356. if( $mixin === $recur_frame ){
  6357. return true;
  6358. }
  6359. if( isset($recur_frame->originalRuleset) && $mixin->ruleset_id === $recur_frame->originalRuleset ){
  6360. return true;
  6361. }
  6362. }
  6363. }
  6364. return false;
  6365. }
  6366. }
  6367. class Less_Tree_Mixin_Definition extends Less_Tree_Ruleset{
  6368. public $name;
  6369. public $selectors;
  6370. public $params;
  6371. public $arity = 0;
  6372. public $rules;
  6373. public $lookups = array();
  6374. public $required = 0;
  6375. public $frames = array();
  6376. public $condition;
  6377. public $variadic;
  6378. public $type = 'MixinDefinition';
  6379. // less.js : /lib/less/tree/mixin.js : tree.mixin.Definition
  6380. public function __construct($name, $params, $rules, $condition, $variadic = false, $frames = array() ){
  6381. $this->name = $name;
  6382. $this->selectors = array(new Less_Tree_Selector(array( new Less_Tree_Element(null, $name))));
  6383. $this->params = $params;
  6384. $this->condition = $condition;
  6385. $this->variadic = $variadic;
  6386. $this->rules = $rules;
  6387. if( $params ){
  6388. $this->arity = count($params);
  6389. foreach( $params as $p ){
  6390. if (! isset($p['name']) || ($p['name'] && !isset($p['value']))) {
  6391. $this->required++;
  6392. }
  6393. }
  6394. }
  6395. $this->frames = $frames;
  6396. $this->SetRulesetIndex();
  6397. }
  6398. //function accept( $visitor ){
  6399. // $this->params = $visitor->visit($this->params);
  6400. // $this->rules = $visitor->visit($this->rules);
  6401. // $this->condition = $visitor->visit($this->condition);
  6402. //}
  6403. public function toCSS(){
  6404. return '';
  6405. }
  6406. // less.js : /lib/less/tree/mixin.js : tree.mixin.Definition.evalParams
  6407. public function compileParams($env, $mixinFrames, $args = array() , &$evaldArguments = array() ){
  6408. $frame = new Less_Tree_Ruleset(null, array());
  6409. $params = $this->params;
  6410. $mixinEnv = null;
  6411. $argsLength = 0;
  6412. if( $args ){
  6413. $argsLength = count($args);
  6414. for($i = 0; $i < $argsLength; $i++ ){
  6415. $arg = $args[$i];
  6416. if( $arg && $arg['name'] ){
  6417. $isNamedFound = false;
  6418. foreach($params as $j => $param){
  6419. if( !isset($evaldArguments[$j]) && $arg['name'] === $params[$j]['name']) {
  6420. $evaldArguments[$j] = $arg['value']->compile($env);
  6421. array_unshift($frame->rules, new Less_Tree_Rule( $arg['name'], $arg['value']->compile($env) ) );
  6422. $isNamedFound = true;
  6423. break;
  6424. }
  6425. }
  6426. if ($isNamedFound) {
  6427. array_splice($args, $i, 1);
  6428. $i--;
  6429. $argsLength--;
  6430. continue;
  6431. } else {
  6432. throw new Less_Exception_Compiler("Named argument for " . $this->name .' '.$args[$i]['name'] . ' not found');
  6433. }
  6434. }
  6435. }
  6436. }
  6437. $argIndex = 0;
  6438. foreach($params as $i => $param){
  6439. if ( isset($evaldArguments[$i]) ){ continue; }
  6440. $arg = null;
  6441. if( isset($args[$argIndex]) ){
  6442. $arg = $args[$argIndex];
  6443. }
  6444. if (isset($param['name']) && $param['name']) {
  6445. if( isset($param['variadic']) ){
  6446. $varargs = array();
  6447. for ($j = $argIndex; $j < $argsLength; $j++) {
  6448. $varargs[] = $args[$j]['value']->compile($env);
  6449. }
  6450. $expression = new Less_Tree_Expression($varargs);
  6451. array_unshift($frame->rules, new Less_Tree_Rule($param['name'], $expression->compile($env)));
  6452. }else{
  6453. $val = ($arg && $arg['value']) ? $arg['value'] : false;
  6454. if ($val) {
  6455. $val = $val->compile($env);
  6456. } else if ( isset($param['value']) ) {
  6457. if( !$mixinEnv ){
  6458. $mixinEnv = new Less_Environment();
  6459. $mixinEnv->frames = array_merge( array($frame), $mixinFrames);
  6460. }
  6461. $val = $param['value']->compile($mixinEnv);
  6462. $frame->resetCache();
  6463. } else {
  6464. throw new Less_Exception_Compiler("Wrong number of arguments for " . $this->name . " (" . $argsLength . ' for ' . $this->arity . ")");
  6465. }
  6466. array_unshift($frame->rules, new Less_Tree_Rule($param['name'], $val));
  6467. $evaldArguments[$i] = $val;
  6468. }
  6469. }
  6470. if ( isset($param['variadic']) && $args) {
  6471. for ($j = $argIndex; $j < $argsLength; $j++) {
  6472. $evaldArguments[$j] = $args[$j]['value']->compile($env);
  6473. }
  6474. }
  6475. $argIndex++;
  6476. }
  6477. ksort($evaldArguments);
  6478. $evaldArguments = array_values($evaldArguments);
  6479. return $frame;
  6480. }
  6481. public function compile($env) {
  6482. if( $this->frames ){
  6483. return new Less_Tree_Mixin_Definition($this->name, $this->params, $this->rules, $this->condition, $this->variadic, $this->frames );
  6484. }
  6485. return new Less_Tree_Mixin_Definition($this->name, $this->params, $this->rules, $this->condition, $this->variadic, $env->frames );
  6486. }
  6487. public function evalCall($env, $args = NULL, $important = NULL) {
  6488. Less_Environment::$mixin_stack++;
  6489. $_arguments = array();
  6490. if( $this->frames ){
  6491. $mixinFrames = array_merge($this->frames, $env->frames);
  6492. }else{
  6493. $mixinFrames = $env->frames;
  6494. }
  6495. $frame = $this->compileParams($env, $mixinFrames, $args, $_arguments);
  6496. $ex = new Less_Tree_Expression($_arguments);
  6497. array_unshift($frame->rules, new Less_Tree_Rule('@arguments', $ex->compile($env)));
  6498. $ruleset = new Less_Tree_Ruleset(null, $this->rules);
  6499. $ruleset->originalRuleset = $this->ruleset_id;
  6500. $ruleSetEnv = new Less_Environment();
  6501. $ruleSetEnv->frames = array_merge( array($this, $frame), $mixinFrames );
  6502. $ruleset = $ruleset->compile( $ruleSetEnv );
  6503. if( $important ){
  6504. $ruleset = $ruleset->makeImportant();
  6505. }
  6506. Less_Environment::$mixin_stack--;
  6507. return $ruleset;
  6508. }
  6509. public function matchCondition($args, $env) {
  6510. if( !$this->condition ){
  6511. return true;
  6512. }
  6513. // set array to prevent error on array_merge
  6514. if(!is_array($this->frames)) {
  6515. $this->frames = array();
  6516. }
  6517. $frame = $this->compileParams($env, array_merge($this->frames,$env->frames), $args );
  6518. $compile_env = new Less_Environment();
  6519. $compile_env->frames = array_merge(
  6520. array($frame) // the parameter variables
  6521. , $this->frames // the parent namespace/mixin frames
  6522. , $env->frames // the current environment frames
  6523. );
  6524. $compile_env->functions = $env->functions;
  6525. return (bool)$this->condition->compile($compile_env);
  6526. }
  6527. public function matchArgs($args, $env = NULL){
  6528. $argsLength = count($args);
  6529. if( !$this->variadic ){
  6530. if( $argsLength < $this->required ){
  6531. return false;
  6532. }
  6533. if( $argsLength > count($this->params) ){
  6534. return false;
  6535. }
  6536. }else{
  6537. if( $argsLength < ($this->required - 1)){
  6538. return false;
  6539. }
  6540. }
  6541. $len = min($argsLength, $this->arity);
  6542. for( $i = 0; $i < $len; $i++ ){
  6543. if( !isset($this->params[$i]['name']) && !isset($this->params[$i]['variadic']) ){
  6544. if( $args[$i]['value']->compile($env)->toCSS() != $this->params[$i]['value']->compile($env)->toCSS() ){
  6545. return false;
  6546. }
  6547. }
  6548. }
  6549. return true;
  6550. }
  6551. }
  6552. /**
  6553. * Extend Finder Visitor
  6554. *
  6555. * @package Less
  6556. * @subpackage visitor
  6557. */
  6558. class Less_Visitor_extendFinder extends Less_Visitor{
  6559. public $contexts = array();
  6560. public $allExtendsStack;
  6561. public $foundExtends;
  6562. public function __construct(){
  6563. $this->contexts = array();
  6564. $this->allExtendsStack = array(array());
  6565. parent::__construct();
  6566. }
  6567. /**
  6568. * @param Less_Tree_Ruleset $root
  6569. */
  6570. public function run($root){
  6571. $root = $this->visitObj($root);
  6572. $root->allExtends =& $this->allExtendsStack[0];
  6573. return $root;
  6574. }
  6575. public function visitRule($ruleNode, &$visitDeeper ){
  6576. $visitDeeper = false;
  6577. }
  6578. public function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ){
  6579. $visitDeeper = false;
  6580. }
  6581. public function visitRuleset($rulesetNode){
  6582. if( $rulesetNode->root ){
  6583. return;
  6584. }
  6585. $allSelectorsExtendList = array();
  6586. // get &:extend(.a); rules which apply to all selectors in this ruleset
  6587. if( $rulesetNode->rules ){
  6588. foreach($rulesetNode->rules as $rule){
  6589. if( $rule instanceof Less_Tree_Extend ){
  6590. $allSelectorsExtendList[] = $rule;
  6591. $rulesetNode->extendOnEveryPath = true;
  6592. }
  6593. }
  6594. }
  6595. // now find every selector and apply the extends that apply to all extends
  6596. // and the ones which apply to an individual extend
  6597. foreach($rulesetNode->paths as $selectorPath){
  6598. $selector = end($selectorPath); //$selectorPath[ count($selectorPath)-1];
  6599. $j = 0;
  6600. foreach($selector->extendList as $extend){
  6601. $this->allExtendsStackPush($rulesetNode, $selectorPath, $extend, $j);
  6602. }
  6603. foreach($allSelectorsExtendList as $extend){
  6604. $this->allExtendsStackPush($rulesetNode, $selectorPath, $extend, $j);
  6605. }
  6606. }
  6607. $this->contexts[] = $rulesetNode->selectors;
  6608. }
  6609. public function allExtendsStackPush($rulesetNode, $selectorPath, $extend, &$j){
  6610. $this->foundExtends = true;
  6611. $extend = clone $extend;
  6612. $extend->findSelfSelectors( $selectorPath );
  6613. $extend->ruleset = $rulesetNode;
  6614. if( $j === 0 ){
  6615. $extend->firstExtendOnThisSelectorPath = true;
  6616. }
  6617. $end_key = count($this->allExtendsStack)-1;
  6618. $this->allExtendsStack[$end_key][] = $extend;
  6619. $j++;
  6620. }
  6621. public function visitRulesetOut( $rulesetNode ){
  6622. if( !is_object($rulesetNode) || !$rulesetNode->root ){
  6623. array_pop($this->contexts);
  6624. }
  6625. }
  6626. public function visitMedia( $mediaNode ){
  6627. $mediaNode->allExtends = array();
  6628. $this->allExtendsStack[] =& $mediaNode->allExtends;
  6629. }
  6630. public function visitMediaOut(){
  6631. array_pop($this->allExtendsStack);
  6632. }
  6633. public function visitDirective( $directiveNode ){
  6634. $directiveNode->allExtends = array();
  6635. $this->allExtendsStack[] =& $directiveNode->allExtends;
  6636. }
  6637. public function visitDirectiveOut(){
  6638. array_pop($this->allExtendsStack);
  6639. }
  6640. }
  6641. /*
  6642. class Less_Visitor_import extends Less_VisitorReplacing{
  6643. public $_visitor;
  6644. public $_importer;
  6645. public $importCount;
  6646. function __construct( $evalEnv ){
  6647. $this->env = $evalEnv;
  6648. $this->importCount = 0;
  6649. parent::__construct();
  6650. }
  6651. function run( $root ){
  6652. $root = $this->visitObj($root);
  6653. $this->isFinished = true;
  6654. //if( $this->importCount === 0) {
  6655. // $this->_finish();
  6656. //}
  6657. }
  6658. function visitImport($importNode, &$visitDeeper ){
  6659. $importVisitor = $this;
  6660. $inlineCSS = $importNode->options['inline'];
  6661. if( !$importNode->css || $inlineCSS ){
  6662. $evaldImportNode = $importNode->compileForImport($this->env);
  6663. if( $evaldImportNode && (!$evaldImportNode->css || $inlineCSS) ){
  6664. $importNode = $evaldImportNode;
  6665. $this->importCount++;
  6666. $env = clone $this->env;
  6667. if( (isset($importNode->options['multiple']) && $importNode->options['multiple']) ){
  6668. $env->importMultiple = true;
  6669. }
  6670. //get path & uri
  6671. $path_and_uri = null;
  6672. if( is_callable(Less_Parser::$options['import_callback']) ){
  6673. $path_and_uri = call_user_func(Less_Parser::$options['import_callback'],$importNode);
  6674. }
  6675. if( !$path_and_uri ){
  6676. $path_and_uri = $importNode->PathAndUri();
  6677. }
  6678. if( $path_and_uri ){
  6679. list($full_path, $uri) = $path_and_uri;
  6680. }else{
  6681. $full_path = $uri = $importNode->getPath();
  6682. }
  6683. //import once
  6684. if( $importNode->skip( $full_path, $env) ){
  6685. return array();
  6686. }
  6687. if( $importNode->options['inline'] ){
  6688. //todo needs to reference css file not import
  6689. //$contents = new Less_Tree_Anonymous($importNode->root, 0, array('filename'=>$importNode->importedFilename), true );
  6690. Less_Parser::AddParsedFile($full_path);
  6691. $contents = new Less_Tree_Anonymous( file_get_contents($full_path), 0, array(), true );
  6692. if( $importNode->features ){
  6693. return new Less_Tree_Media( array($contents), $importNode->features->value );
  6694. }
  6695. return array( $contents );
  6696. }
  6697. // css ?
  6698. if( $importNode->css ){
  6699. $features = ( $importNode->features ? $importNode->features->compile($env) : null );
  6700. return new Less_Tree_Import( $importNode->compilePath( $env), $features, $importNode->options, $this->index);
  6701. }
  6702. return $importNode->ParseImport( $full_path, $uri, $env );
  6703. }
  6704. }
  6705. $visitDeeper = false;
  6706. return $importNode;
  6707. }
  6708. function visitRule( $ruleNode, &$visitDeeper ){
  6709. $visitDeeper = false;
  6710. return $ruleNode;
  6711. }
  6712. function visitDirective($directiveNode, $visitArgs){
  6713. array_unshift($this->env->frames,$directiveNode);
  6714. return $directiveNode;
  6715. }
  6716. function visitDirectiveOut($directiveNode) {
  6717. array_shift($this->env->frames);
  6718. }
  6719. function visitMixinDefinition($mixinDefinitionNode, $visitArgs) {
  6720. array_unshift($this->env->frames,$mixinDefinitionNode);
  6721. return $mixinDefinitionNode;
  6722. }
  6723. function visitMixinDefinitionOut($mixinDefinitionNode) {
  6724. array_shift($this->env->frames);
  6725. }
  6726. function visitRuleset($rulesetNode, $visitArgs) {
  6727. array_unshift($this->env->frames,$rulesetNode);
  6728. return $rulesetNode;
  6729. }
  6730. function visitRulesetOut($rulesetNode) {
  6731. array_shift($this->env->frames);
  6732. }
  6733. function visitMedia($mediaNode, $visitArgs) {
  6734. array_unshift($this->env->frames, $mediaNode->ruleset);
  6735. return $mediaNode;
  6736. }
  6737. function visitMediaOut($mediaNode) {
  6738. array_shift($this->env->frames);
  6739. }
  6740. }
  6741. */
  6742. /**
  6743. * Join Selector Visitor
  6744. *
  6745. * @package Less
  6746. * @subpackage visitor
  6747. */
  6748. class Less_Visitor_joinSelector extends Less_Visitor{
  6749. public $contexts = array( array() );
  6750. /**
  6751. * @param Less_Tree_Ruleset $root
  6752. */
  6753. public function run( $root ){
  6754. return $this->visitObj($root);
  6755. }
  6756. public function visitRule( $ruleNode, &$visitDeeper ){
  6757. $visitDeeper = false;
  6758. }
  6759. public function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ){
  6760. $visitDeeper = false;
  6761. }
  6762. public function visitRuleset( $rulesetNode ){
  6763. $paths = array();
  6764. if( !$rulesetNode->root ){
  6765. $selectors = array();
  6766. if( $rulesetNode->selectors && $rulesetNode->selectors ){
  6767. foreach($rulesetNode->selectors as $selector){
  6768. if( $selector->getIsOutput() ){
  6769. $selectors[] = $selector;
  6770. }
  6771. }
  6772. }
  6773. if( !$selectors ){
  6774. $rulesetNode->selectors = null;
  6775. $rulesetNode->rules = null;
  6776. }else{
  6777. $context = end($this->contexts); //$context = $this->contexts[ count($this->contexts) - 1];
  6778. $paths = $rulesetNode->joinSelectors( $context, $selectors);
  6779. }
  6780. $rulesetNode->paths = $paths;
  6781. }
  6782. $this->contexts[] = $paths; //different from less.js. Placed after joinSelectors() so that $this->contexts will get correct $paths
  6783. }
  6784. public function visitRulesetOut(){
  6785. array_pop($this->contexts);
  6786. }
  6787. public function visitMedia($mediaNode) {
  6788. $context = end($this->contexts); //$context = $this->contexts[ count($this->contexts) - 1];
  6789. if( !count($context) || (is_object($context[0]) && $context[0]->multiMedia) ){
  6790. $mediaNode->rules[0]->root = true;
  6791. }
  6792. }
  6793. }
  6794. /**
  6795. * Process Extends Visitor
  6796. *
  6797. * @package Less
  6798. * @subpackage visitor
  6799. */
  6800. class Less_Visitor_processExtends extends Less_Visitor{
  6801. public $allExtendsStack;
  6802. /**
  6803. * @param Less_Tree_Ruleset $root
  6804. */
  6805. public function run( $root ){
  6806. $extendFinder = new Less_Visitor_extendFinder();
  6807. $extendFinder->run( $root );
  6808. if( !$extendFinder->foundExtends){
  6809. return $root;
  6810. }
  6811. $root->allExtends = $this->doExtendChaining( $root->allExtends, $root->allExtends);
  6812. $this->allExtendsStack = array();
  6813. $this->allExtendsStack[] = &$root->allExtends;
  6814. return $this->visitObj( $root );
  6815. }
  6816. private function doExtendChaining( $extendsList, $extendsListTarget, $iterationCount = 0){
  6817. //
  6818. // chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting
  6819. // the selector we would do normally, but we are also adding an extend with the same target selector
  6820. // this means this new extend can then go and alter other extends
  6821. //
  6822. // this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors
  6823. // this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if
  6824. // we look at each selector at a time, as is done in visitRuleset
  6825. $extendsToAdd = array();
  6826. //loop through comparing every extend with every target extend.
  6827. // a target extend is the one on the ruleset we are looking at copy/edit/pasting in place
  6828. // e.g. .a:extend(.b) {} and .b:extend(.c) {} then the first extend extends the second one
  6829. // and the second is the target.
  6830. // the seperation into two lists allows us to process a subset of chains with a bigger set, as is the
  6831. // case when processing media queries
  6832. for( $extendIndex = 0, $extendsList_len = count($extendsList); $extendIndex < $extendsList_len; $extendIndex++ ){
  6833. for( $targetExtendIndex = 0; $targetExtendIndex < count($extendsListTarget); $targetExtendIndex++ ){
  6834. $extend = $extendsList[$extendIndex];
  6835. $targetExtend = $extendsListTarget[$targetExtendIndex];
  6836. // look for circular references
  6837. if( in_array($targetExtend->object_id, $extend->parent_ids,true) ){
  6838. continue;
  6839. }
  6840. // find a match in the target extends self selector (the bit before :extend)
  6841. $selectorPath = array( $targetExtend->selfSelectors[0] );
  6842. $matches = $this->findMatch( $extend, $selectorPath);
  6843. if( $matches ){
  6844. // we found a match, so for each self selector..
  6845. foreach($extend->selfSelectors as $selfSelector ){
  6846. // process the extend as usual
  6847. $newSelector = $this->extendSelector( $matches, $selectorPath, $selfSelector);
  6848. // but now we create a new extend from it
  6849. $newExtend = new Less_Tree_Extend( $targetExtend->selector, $targetExtend->option, 0);
  6850. $newExtend->selfSelectors = $newSelector;
  6851. // add the extend onto the list of extends for that selector
  6852. end($newSelector)->extendList = array($newExtend);
  6853. //$newSelector[ count($newSelector)-1]->extendList = array($newExtend);
  6854. // record that we need to add it.
  6855. $extendsToAdd[] = $newExtend;
  6856. $newExtend->ruleset = $targetExtend->ruleset;
  6857. //remember its parents for circular references
  6858. $newExtend->parent_ids = array_merge($newExtend->parent_ids,$targetExtend->parent_ids,$extend->parent_ids);
  6859. // only process the selector once.. if we have :extend(.a,.b) then multiple
  6860. // extends will look at the same selector path, so when extending
  6861. // we know that any others will be duplicates in terms of what is added to the css
  6862. if( $targetExtend->firstExtendOnThisSelectorPath ){
  6863. $newExtend->firstExtendOnThisSelectorPath = true;
  6864. $targetExtend->ruleset->paths[] = $newSelector;
  6865. }
  6866. }
  6867. }
  6868. }
  6869. }
  6870. if( $extendsToAdd ){
  6871. // try to detect circular references to stop a stack overflow.
  6872. // may no longer be needed. $this->extendChainCount++;
  6873. if( $iterationCount > 100) {
  6874. try{
  6875. $selectorOne = $extendsToAdd[0]->selfSelectors[0]->toCSS();
  6876. $selectorTwo = $extendsToAdd[0]->selector->toCSS();
  6877. }catch(Exception $e){
  6878. $selectorOne = "{unable to calculate}";
  6879. $selectorTwo = "{unable to calculate}";
  6880. }
  6881. throw new Less_Exception_Parser("extend circular reference detected. One of the circular extends is currently:"+$selectorOne+":extend(" + $selectorTwo+")");
  6882. }
  6883. // now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e...
  6884. $extendsToAdd = $this->doExtendChaining( $extendsToAdd, $extendsListTarget, $iterationCount+1);
  6885. }
  6886. return array_merge($extendsList, $extendsToAdd);
  6887. }
  6888. protected function visitRule( $ruleNode, &$visitDeeper ){
  6889. $visitDeeper = false;
  6890. }
  6891. protected function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ){
  6892. $visitDeeper = false;
  6893. }
  6894. protected function visitSelector( $selectorNode, &$visitDeeper ){
  6895. $visitDeeper = false;
  6896. }
  6897. protected function visitRuleset($rulesetNode){
  6898. if( $rulesetNode->root ){
  6899. return;
  6900. }
  6901. $allExtends = end($this->allExtendsStack);
  6902. $paths_len = count($rulesetNode->paths);
  6903. // look at each selector path in the ruleset, find any extend matches and then copy, find and replace
  6904. foreach($allExtends as $allExtend){
  6905. for($pathIndex = 0; $pathIndex < $paths_len; $pathIndex++ ){
  6906. // extending extends happens initially, before the main pass
  6907. if( isset($rulesetNode->extendOnEveryPath) && $rulesetNode->extendOnEveryPath ){
  6908. continue;
  6909. }
  6910. $selectorPath = $rulesetNode->paths[$pathIndex];
  6911. if( end($selectorPath)->extendList ){
  6912. continue;
  6913. }
  6914. $this->ExtendMatch( $rulesetNode, $allExtend, $selectorPath);
  6915. }
  6916. }
  6917. }
  6918. private function ExtendMatch( $rulesetNode, $extend, $selectorPath ){
  6919. $matches = $this->findMatch($extend, $selectorPath);
  6920. if( $matches ){
  6921. foreach($extend->selfSelectors as $selfSelector ){
  6922. $rulesetNode->paths[] = $this->extendSelector($matches, $selectorPath, $selfSelector);
  6923. }
  6924. }
  6925. }
  6926. private function findMatch($extend, $haystackSelectorPath ){
  6927. if( !$this->HasMatches($extend, $haystackSelectorPath) ){
  6928. return false;
  6929. }
  6930. //
  6931. // look through the haystack selector path to try and find the needle - extend.selector
  6932. // returns an array of selector matches that can then be replaced
  6933. //
  6934. $needleElements = $extend->selector->elements;
  6935. $potentialMatches = array();
  6936. $potentialMatches_len = 0;
  6937. $potentialMatch = null;
  6938. $matches = array();
  6939. // loop through the haystack elements
  6940. $haystack_path_len = count($haystackSelectorPath);
  6941. for($haystackSelectorIndex = 0; $haystackSelectorIndex < $haystack_path_len; $haystackSelectorIndex++ ){
  6942. $hackstackSelector = $haystackSelectorPath[$haystackSelectorIndex];
  6943. $haystack_elements_len = count($hackstackSelector->elements);
  6944. for($hackstackElementIndex = 0; $hackstackElementIndex < $haystack_elements_len; $hackstackElementIndex++ ){
  6945. $haystackElement = $hackstackSelector->elements[$hackstackElementIndex];
  6946. // if we allow elements before our match we can add a potential match every time. otherwise only at the first element.
  6947. if( $extend->allowBefore || ($haystackSelectorIndex === 0 && $hackstackElementIndex === 0) ){
  6948. $potentialMatches[] = array('pathIndex'=> $haystackSelectorIndex, 'index'=> $hackstackElementIndex, 'matched'=> 0, 'initialCombinator'=> $haystackElement->combinator);
  6949. $potentialMatches_len++;
  6950. }
  6951. for($i = 0; $i < $potentialMatches_len; $i++ ){
  6952. $potentialMatch = &$potentialMatches[$i];
  6953. $potentialMatch = $this->PotentialMatch( $potentialMatch, $needleElements, $haystackElement, $hackstackElementIndex );
  6954. // if we are still valid and have finished, test whether we have elements after and whether these are allowed
  6955. if( $potentialMatch && $potentialMatch['matched'] === $extend->selector->elements_len ){
  6956. $potentialMatch['finished'] = true;
  6957. if( !$extend->allowAfter && ($hackstackElementIndex+1 < $haystack_elements_len || $haystackSelectorIndex+1 < $haystack_path_len) ){
  6958. $potentialMatch = null;
  6959. }
  6960. }
  6961. // if null we remove, if not, we are still valid, so either push as a valid match or continue
  6962. if( $potentialMatch ){
  6963. if( $potentialMatch['finished'] ){
  6964. $potentialMatch['length'] = $extend->selector->elements_len;
  6965. $potentialMatch['endPathIndex'] = $haystackSelectorIndex;
  6966. $potentialMatch['endPathElementIndex'] = $hackstackElementIndex + 1; // index after end of match
  6967. $potentialMatches = array(); // we don't allow matches to overlap, so start matching again
  6968. $potentialMatches_len = 0;
  6969. $matches[] = $potentialMatch;
  6970. }
  6971. continue;
  6972. }
  6973. array_splice($potentialMatches, $i, 1);
  6974. $potentialMatches_len--;
  6975. $i--;
  6976. }
  6977. }
  6978. }
  6979. return $matches;
  6980. }
  6981. // Before going through all the nested loops, lets check to see if a match is possible
  6982. // Reduces Bootstrap 3.1 compile time from ~6.5s to ~5.6s
  6983. private function HasMatches($extend, $haystackSelectorPath){
  6984. if( !$extend->selector->cacheable ){
  6985. return true;
  6986. }
  6987. $first_el = $extend->selector->_oelements[0];
  6988. foreach($haystackSelectorPath as $hackstackSelector){
  6989. if( !$hackstackSelector->cacheable ){
  6990. return true;
  6991. }
  6992. if( in_array($first_el, $hackstackSelector->_oelements) ){
  6993. return true;
  6994. }
  6995. }
  6996. return false;
  6997. }
  6998. /**
  6999. * @param integer $hackstackElementIndex
  7000. */
  7001. private function PotentialMatch( $potentialMatch, $needleElements, $haystackElement, $hackstackElementIndex ){
  7002. if( $potentialMatch['matched'] > 0 ){
  7003. // selectors add " " onto the first element. When we use & it joins the selectors together, but if we don't
  7004. // then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to work out
  7005. // what the resulting combinator will be
  7006. $targetCombinator = $haystackElement->combinator;
  7007. if( $targetCombinator === '' && $hackstackElementIndex === 0 ){
  7008. $targetCombinator = ' ';
  7009. }
  7010. if( $needleElements[ $potentialMatch['matched'] ]->combinator !== $targetCombinator ){
  7011. return null;
  7012. }
  7013. }
  7014. // if we don't match, null our match to indicate failure
  7015. if( !$this->isElementValuesEqual( $needleElements[$potentialMatch['matched'] ]->value, $haystackElement->value) ){
  7016. return null;
  7017. }
  7018. $potentialMatch['finished'] = false;
  7019. $potentialMatch['matched']++;
  7020. return $potentialMatch;
  7021. }
  7022. private function isElementValuesEqual( $elementValue1, $elementValue2 ){
  7023. if( $elementValue1 === $elementValue2 ){
  7024. return true;
  7025. }
  7026. if( is_string($elementValue1) || is_string($elementValue2) ) {
  7027. return false;
  7028. }
  7029. if( $elementValue1 instanceof Less_Tree_Attribute ){
  7030. return $this->isAttributeValuesEqual( $elementValue1, $elementValue2 );
  7031. }
  7032. $elementValue1 = $elementValue1->value;
  7033. if( $elementValue1 instanceof Less_Tree_Selector ){
  7034. return $this->isSelectorValuesEqual( $elementValue1, $elementValue2 );
  7035. }
  7036. return false;
  7037. }
  7038. /**
  7039. * @param Less_Tree_Selector $elementValue1
  7040. */
  7041. private function isSelectorValuesEqual( $elementValue1, $elementValue2 ){
  7042. $elementValue2 = $elementValue2->value;
  7043. if( !($elementValue2 instanceof Less_Tree_Selector) || $elementValue1->elements_len !== $elementValue2->elements_len ){
  7044. return false;
  7045. }
  7046. for( $i = 0; $i < $elementValue1->elements_len; $i++ ){
  7047. if( $elementValue1->elements[$i]->combinator !== $elementValue2->elements[$i]->combinator ){
  7048. if( $i !== 0 || ($elementValue1->elements[$i]->combinator || ' ') !== ($elementValue2->elements[$i]->combinator || ' ') ){
  7049. return false;
  7050. }
  7051. }
  7052. if( !$this->isElementValuesEqual($elementValue1->elements[$i]->value, $elementValue2->elements[$i]->value) ){
  7053. return false;
  7054. }
  7055. }
  7056. return true;
  7057. }
  7058. /**
  7059. * @param Less_Tree_Attribute $elementValue1
  7060. */
  7061. private function isAttributeValuesEqual( $elementValue1, $elementValue2 ){
  7062. if( $elementValue1->op !== $elementValue2->op || $elementValue1->key !== $elementValue2->key ){
  7063. return false;
  7064. }
  7065. if( !$elementValue1->value || !$elementValue2->value ){
  7066. if( $elementValue1->value || $elementValue2->value ) {
  7067. return false;
  7068. }
  7069. return true;
  7070. }
  7071. $elementValue1 = ($elementValue1->value->value ? $elementValue1->value->value : $elementValue1->value );
  7072. $elementValue2 = ($elementValue2->value->value ? $elementValue2->value->value : $elementValue2->value );
  7073. return $elementValue1 === $elementValue2;
  7074. }
  7075. private function extendSelector($matches, $selectorPath, $replacementSelector){
  7076. //for a set of matches, replace each match with the replacement selector
  7077. $currentSelectorPathIndex = 0;
  7078. $currentSelectorPathElementIndex = 0;
  7079. $path = array();
  7080. $selectorPath_len = count($selectorPath);
  7081. for($matchIndex = 0, $matches_len = count($matches); $matchIndex < $matches_len; $matchIndex++ ){
  7082. $match = $matches[$matchIndex];
  7083. $selector = $selectorPath[ $match['pathIndex'] ];
  7084. $firstElement = new Less_Tree_Element(
  7085. $match['initialCombinator'],
  7086. $replacementSelector->elements[0]->value,
  7087. $replacementSelector->elements[0]->index,
  7088. $replacementSelector->elements[0]->currentFileInfo
  7089. );
  7090. if( $match['pathIndex'] > $currentSelectorPathIndex && $currentSelectorPathElementIndex > 0 ){
  7091. $last_path = end($path);
  7092. $last_path->elements = array_merge( $last_path->elements, array_slice( $selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex));
  7093. $currentSelectorPathElementIndex = 0;
  7094. $currentSelectorPathIndex++;
  7095. }
  7096. $newElements = array_merge(
  7097. array_slice($selector->elements, $currentSelectorPathElementIndex, ($match['index'] - $currentSelectorPathElementIndex) ) // last parameter of array_slice is different than the last parameter of javascript's slice
  7098. , array($firstElement)
  7099. , array_slice($replacementSelector->elements,1)
  7100. );
  7101. if( $currentSelectorPathIndex === $match['pathIndex'] && $matchIndex > 0 ){
  7102. $last_key = count($path)-1;
  7103. $path[$last_key]->elements = array_merge($path[$last_key]->elements,$newElements);
  7104. }else{
  7105. $path = array_merge( $path, array_slice( $selectorPath, $currentSelectorPathIndex, $match['pathIndex'] ));
  7106. $path[] = new Less_Tree_Selector( $newElements );
  7107. }
  7108. $currentSelectorPathIndex = $match['endPathIndex'];
  7109. $currentSelectorPathElementIndex = $match['endPathElementIndex'];
  7110. if( $currentSelectorPathElementIndex >= count($selectorPath[$currentSelectorPathIndex]->elements) ){
  7111. $currentSelectorPathElementIndex = 0;
  7112. $currentSelectorPathIndex++;
  7113. }
  7114. }
  7115. if( $currentSelectorPathIndex < $selectorPath_len && $currentSelectorPathElementIndex > 0 ){
  7116. $last_path = end($path);
  7117. $last_path->elements = array_merge( $last_path->elements, array_slice($selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex));
  7118. $currentSelectorPathIndex++;
  7119. }
  7120. $slice_len = $selectorPath_len - $currentSelectorPathIndex;
  7121. $path = array_merge($path, array_slice($selectorPath, $currentSelectorPathIndex, $slice_len));
  7122. return $path;
  7123. }
  7124. protected function visitMedia( $mediaNode ){
  7125. $newAllExtends = array_merge( $mediaNode->allExtends, end($this->allExtendsStack) );
  7126. $this->allExtendsStack[] = $this->doExtendChaining($newAllExtends, $mediaNode->allExtends);
  7127. }
  7128. protected function visitMediaOut(){
  7129. array_pop( $this->allExtendsStack );
  7130. }
  7131. protected function visitDirective( $directiveNode ){
  7132. $newAllExtends = array_merge( $directiveNode->allExtends, end($this->allExtendsStack) );
  7133. $this->allExtendsStack[] = $this->doExtendChaining($newAllExtends, $directiveNode->allExtends);
  7134. }
  7135. protected function visitDirectiveOut(){
  7136. array_pop($this->allExtendsStack);
  7137. }
  7138. }
  7139. /**
  7140. * toCSS Visitor
  7141. *
  7142. * @package Less
  7143. * @subpackage visitor
  7144. */
  7145. class Less_Visitor_toCSS extends Less_VisitorReplacing{
  7146. private $charset;
  7147. public function __construct(){
  7148. parent::__construct();
  7149. }
  7150. /**
  7151. * @param Less_Tree_Ruleset $root
  7152. */
  7153. public function run( $root ){
  7154. return $this->visitObj($root);
  7155. }
  7156. public function visitRule( $ruleNode ){
  7157. if( $ruleNode->variable ){
  7158. return array();
  7159. }
  7160. return $ruleNode;
  7161. }
  7162. public function visitMixinDefinition($mixinNode){
  7163. // mixin definitions do not get eval'd - this means they keep state
  7164. // so we have to clear that state here so it isn't used if toCSS is called twice
  7165. $mixinNode->frames = array();
  7166. return array();
  7167. }
  7168. public function visitExtend(){
  7169. return array();
  7170. }
  7171. public function visitComment( $commentNode ){
  7172. if( $commentNode->isSilent() ){
  7173. return array();
  7174. }
  7175. return $commentNode;
  7176. }
  7177. public function visitMedia( $mediaNode, &$visitDeeper ){
  7178. $mediaNode->accept($this);
  7179. $visitDeeper = false;
  7180. if( !$mediaNode->rules ){
  7181. return array();
  7182. }
  7183. return $mediaNode;
  7184. }
  7185. public function visitDirective( $directiveNode ){
  7186. if( isset($directiveNode->currentFileInfo['reference']) && (!property_exists($directiveNode,'isReferenced') || !$directiveNode->isReferenced) ){
  7187. return array();
  7188. }
  7189. if( $directiveNode->name === '@charset' ){
  7190. // Only output the debug info together with subsequent @charset definitions
  7191. // a comment (or @media statement) before the actual @charset directive would
  7192. // be considered illegal css as it has to be on the first line
  7193. if( isset($this->charset) && $this->charset ){
  7194. //if( $directiveNode->debugInfo ){
  7195. // $comment = new Less_Tree_Comment('/* ' . str_replace("\n",'',$directiveNode->toCSS())." */\n");
  7196. // $comment->debugInfo = $directiveNode->debugInfo;
  7197. // return $this->visit($comment);
  7198. //}
  7199. return array();
  7200. }
  7201. $this->charset = true;
  7202. }
  7203. return $directiveNode;
  7204. }
  7205. public function checkPropertiesInRoot( $rulesetNode ){
  7206. if( !$rulesetNode->firstRoot ){
  7207. return;
  7208. }
  7209. foreach($rulesetNode->rules as $ruleNode){
  7210. if( $ruleNode instanceof Less_Tree_Rule && !$ruleNode->variable ){
  7211. $msg = "properties must be inside selector blocks, they cannot be in the root. Index ".$ruleNode->index.($ruleNode->currentFileInfo ? (' Filename: '.$ruleNode->currentFileInfo['filename']) : null);
  7212. throw new Less_Exception_Compiler($msg);
  7213. }
  7214. }
  7215. }
  7216. public function visitRuleset( $rulesetNode, &$visitDeeper ){
  7217. $visitDeeper = false;
  7218. $this->checkPropertiesInRoot( $rulesetNode );
  7219. if( $rulesetNode->root ){
  7220. return $this->visitRulesetRoot( $rulesetNode );
  7221. }
  7222. $rulesets = array();
  7223. $rulesetNode->paths = $this->visitRulesetPaths($rulesetNode);
  7224. // Compile rules and rulesets
  7225. $nodeRuleCnt = count($rulesetNode->rules);
  7226. for( $i = 0; $i < $nodeRuleCnt; ){
  7227. $rule = $rulesetNode->rules[$i];
  7228. if( property_exists($rule,'rules') ){
  7229. // visit because we are moving them out from being a child
  7230. $rulesets[] = $this->visitObj($rule);
  7231. array_splice($rulesetNode->rules,$i,1);
  7232. $nodeRuleCnt--;
  7233. continue;
  7234. }
  7235. $i++;
  7236. }
  7237. // accept the visitor to remove rules and refactor itself
  7238. // then we can decide now whether we want it or not
  7239. if( $nodeRuleCnt > 0 ){
  7240. $rulesetNode->accept($this);
  7241. if( $rulesetNode->rules ){
  7242. if( count($rulesetNode->rules) > 1 ){
  7243. $this->_mergeRules( $rulesetNode->rules );
  7244. $this->_removeDuplicateRules( $rulesetNode->rules );
  7245. }
  7246. // now decide whether we keep the ruleset
  7247. if( $rulesetNode->paths ){
  7248. //array_unshift($rulesets, $rulesetNode);
  7249. array_splice($rulesets,0,0,array($rulesetNode));
  7250. }
  7251. }
  7252. }
  7253. if( count($rulesets) === 1 ){
  7254. return $rulesets[0];
  7255. }
  7256. return $rulesets;
  7257. }
  7258. /**
  7259. * Helper function for visitiRuleset
  7260. *
  7261. * return array|Less_Tree_Ruleset
  7262. */
  7263. private function visitRulesetRoot( $rulesetNode ){
  7264. $rulesetNode->accept( $this );
  7265. if( $rulesetNode->firstRoot || $rulesetNode->rules ){
  7266. return $rulesetNode;
  7267. }
  7268. return array();
  7269. }
  7270. /**
  7271. * Helper function for visitRuleset()
  7272. *
  7273. * @return array
  7274. */
  7275. private function visitRulesetPaths($rulesetNode){
  7276. $paths = array();
  7277. foreach($rulesetNode->paths as $p){
  7278. if( $p[0]->elements[0]->combinator === ' ' ){
  7279. $p[0]->elements[0]->combinator = '';
  7280. }
  7281. foreach($p as $pi){
  7282. if( $pi->getIsReferenced() && $pi->getIsOutput() ){
  7283. $paths[] = $p;
  7284. break;
  7285. }
  7286. }
  7287. }
  7288. return $paths;
  7289. }
  7290. protected function _removeDuplicateRules( &$rules ){
  7291. // remove duplicates
  7292. $ruleCache = array();
  7293. for( $i = count($rules)-1; $i >= 0 ; $i-- ){
  7294. $rule = $rules[$i];
  7295. if( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_NameValue ){
  7296. if( !isset($ruleCache[$rule->name]) ){
  7297. $ruleCache[$rule->name] = $rule;
  7298. }else{
  7299. $ruleList =& $ruleCache[$rule->name];
  7300. if( $ruleList instanceof Less_Tree_Rule || $ruleList instanceof Less_Tree_NameValue ){
  7301. $ruleList = $ruleCache[$rule->name] = array( $ruleCache[$rule->name]->toCSS() );
  7302. }
  7303. $ruleCSS = $rule->toCSS();
  7304. if( array_search($ruleCSS,$ruleList) !== false ){
  7305. array_splice($rules,$i,1);
  7306. }else{
  7307. $ruleList[] = $ruleCSS;
  7308. }
  7309. }
  7310. }
  7311. }
  7312. }
  7313. protected function _mergeRules( &$rules ){
  7314. $groups = array();
  7315. //obj($rules);
  7316. $rules_len = count($rules);
  7317. for( $i = 0; $i < $rules_len; $i++ ){
  7318. $rule = $rules[$i];
  7319. if( ($rule instanceof Less_Tree_Rule) && $rule->merge ){
  7320. $key = $rule->name;
  7321. if( $rule->important ){
  7322. $key .= ',!';
  7323. }
  7324. if( !isset($groups[$key]) ){
  7325. $groups[$key] = array();
  7326. }else{
  7327. array_splice($rules, $i--, 1);
  7328. $rules_len--;
  7329. }
  7330. $groups[$key][] = $rule;
  7331. }
  7332. }
  7333. foreach($groups as $parts){
  7334. if( count($parts) > 1 ){
  7335. $rule = $parts[0];
  7336. $spacedGroups = array();
  7337. $lastSpacedGroup = array();
  7338. $parts_mapped = array();
  7339. foreach($parts as $p){
  7340. if( $p->merge === '+' ){
  7341. if( $lastSpacedGroup ){
  7342. $spacedGroups[] = self::toExpression($lastSpacedGroup);
  7343. }
  7344. $lastSpacedGroup = array();
  7345. }
  7346. $lastSpacedGroup[] = $p;
  7347. }
  7348. $spacedGroups[] = self::toExpression($lastSpacedGroup);
  7349. $rule->value = self::toValue($spacedGroups);
  7350. }
  7351. }
  7352. }
  7353. public static function toExpression($values){
  7354. $mapped = array();
  7355. foreach($values as $p){
  7356. $mapped[] = $p->value;
  7357. }
  7358. return new Less_Tree_Expression( $mapped );
  7359. }
  7360. public static function toValue($values){
  7361. //return new Less_Tree_Value($values); ??
  7362. $mapped = array();
  7363. foreach($values as $p){
  7364. $mapped[] = $p;
  7365. }
  7366. return new Less_Tree_Value($mapped);
  7367. }
  7368. }
  7369. /**
  7370. * Parser Exception
  7371. *
  7372. * @package Less
  7373. * @subpackage exception
  7374. */
  7375. class Less_Exception_Parser extends Exception{
  7376. /**
  7377. * The current file
  7378. *
  7379. * @var Less_ImportedFile
  7380. */
  7381. public $currentFile;
  7382. /**
  7383. * The current parser index
  7384. *
  7385. * @var integer
  7386. */
  7387. public $index;
  7388. protected $input;
  7389. protected $details = array();
  7390. /**
  7391. * Constructor
  7392. *
  7393. * @param string $message
  7394. * @param Exception $previous Previous exception
  7395. * @param integer $index The current parser index
  7396. * @param Less_FileInfo|string $currentFile The file
  7397. * @param integer $code The exception code
  7398. */
  7399. public function __construct($message = null, Exception $previous = null, $index = null, $currentFile = null, $code = 0){
  7400. if (PHP_VERSION_ID < 50300) {
  7401. $this->previous = $previous;
  7402. parent::__construct($message, $code);
  7403. } else {
  7404. parent::__construct($message, $code, $previous);
  7405. }
  7406. $this->currentFile = $currentFile;
  7407. $this->index = $index;
  7408. $this->genMessage();
  7409. }
  7410. protected function getInput(){
  7411. if( !$this->input && $this->currentFile && $this->currentFile['filename'] ){
  7412. $this->input = file_get_contents( $this->currentFile['filename'] );
  7413. }
  7414. }
  7415. /**
  7416. * Converts the exception to string
  7417. *
  7418. * @return string
  7419. */
  7420. public function genMessage(){
  7421. if( $this->currentFile && $this->currentFile['filename'] ){
  7422. $this->message .= ' in '.basename($this->currentFile['filename']);
  7423. }
  7424. if( $this->index !== null ){
  7425. $this->getInput();
  7426. if( $this->input ){
  7427. $line = self::getLineNumber();
  7428. $this->message .= ' on line '.$line.', column '.self::getColumn();
  7429. $lines = explode("\n",$this->input);
  7430. $count = count($lines);
  7431. $start_line = max(0, $line-3);
  7432. $last_line = min($count, $start_line+6);
  7433. $num_len = strlen($last_line);
  7434. for( $i = $start_line; $i < $last_line; $i++ ){
  7435. $this->message .= "\n".str_pad($i+1,$num_len,'0',STR_PAD_LEFT).'| '.$lines[$i];
  7436. }
  7437. }
  7438. }
  7439. }
  7440. /**
  7441. * Returns the line number the error was encountered
  7442. *
  7443. * @return integer
  7444. */
  7445. public function getLineNumber(){
  7446. if( $this->index ){
  7447. // https://bugs.php.net/bug.php?id=49790
  7448. if (ini_get("mbstring.func_overload")) {
  7449. return substr_count(substr($this->input, 0, $this->index), "\n") + 1;
  7450. } else {
  7451. return substr_count($this->input, "\n", 0, $this->index) + 1;
  7452. }
  7453. }
  7454. return 1;
  7455. }
  7456. /**
  7457. * Returns the column the error was encountered
  7458. *
  7459. * @return integer
  7460. */
  7461. public function getColumn(){
  7462. $part = substr($this->input, 0, $this->index);
  7463. $pos = strrpos($part,"\n");
  7464. return $this->index - $pos;
  7465. }
  7466. }
  7467. /**
  7468. * Chunk Exception
  7469. *
  7470. * @package Less
  7471. * @subpackage exception
  7472. */
  7473. class Less_Exception_Chunk extends Less_Exception_Parser{
  7474. protected $parserCurrentIndex = 0;
  7475. protected $emitFrom = 0;
  7476. protected $input_len;
  7477. /**
  7478. * Constructor
  7479. *
  7480. * @param string $input
  7481. * @param Exception $previous Previous exception
  7482. * @param integer $index The current parser index
  7483. * @param Less_FileInfo|string $currentFile The file
  7484. * @param integer $code The exception code
  7485. */
  7486. public function __construct($input, Exception $previous = null, $index = null, $currentFile = null, $code = 0){
  7487. $this->message = 'ParseError: Unexpected input'; //default message
  7488. $this->index = $index;
  7489. $this->currentFile = $currentFile;
  7490. $this->input = $input;
  7491. $this->input_len = strlen($input);
  7492. $this->Chunks();
  7493. $this->genMessage();
  7494. }
  7495. /**
  7496. * See less.js chunks()
  7497. * We don't actually need the chunks
  7498. *
  7499. */
  7500. protected function Chunks(){
  7501. $level = 0;
  7502. $parenLevel = 0;
  7503. $lastMultiCommentEndBrace = null;
  7504. $lastOpening = null;
  7505. $lastMultiComment = null;
  7506. $lastParen = null;
  7507. for( $this->parserCurrentIndex = 0; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++ ){
  7508. $cc = $this->CharCode($this->parserCurrentIndex);
  7509. if ((($cc >= 97) && ($cc <= 122)) || ($cc < 34)) {
  7510. // a-z or whitespace
  7511. continue;
  7512. }
  7513. switch ($cc) {
  7514. // (
  7515. case 40:
  7516. $parenLevel++;
  7517. $lastParen = $this->parserCurrentIndex;
  7518. continue;
  7519. // )
  7520. case 41:
  7521. $parenLevel--;
  7522. if( $parenLevel < 0 ){
  7523. return $this->fail("missing opening `(`");
  7524. }
  7525. continue;
  7526. // ;
  7527. case 59:
  7528. //if (!$parenLevel) { $this->emitChunk(); }
  7529. continue;
  7530. // {
  7531. case 123:
  7532. $level++;
  7533. $lastOpening = $this->parserCurrentIndex;
  7534. continue;
  7535. // }
  7536. case 125:
  7537. $level--;
  7538. if( $level < 0 ){
  7539. return $this->fail("missing opening `{`");
  7540. }
  7541. //if (!$level && !$parenLevel) { $this->emitChunk(); }
  7542. continue;
  7543. // \
  7544. case 92:
  7545. if ($this->parserCurrentIndex < $this->input_len - 1) { $this->parserCurrentIndex++; continue; }
  7546. return $this->fail("unescaped `\\`");
  7547. // ", ' and `
  7548. case 34:
  7549. case 39:
  7550. case 96:
  7551. $matched = 0;
  7552. $currentChunkStartIndex = $this->parserCurrentIndex;
  7553. for ($this->parserCurrentIndex = $this->parserCurrentIndex + 1; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++) {
  7554. $cc2 = $this->CharCode($this->parserCurrentIndex);
  7555. if ($cc2 > 96) { continue; }
  7556. if ($cc2 == $cc) { $matched = 1; break; }
  7557. if ($cc2 == 92) { // \
  7558. if ($this->parserCurrentIndex == $this->input_len - 1) {
  7559. return $this->fail("unescaped `\\`");
  7560. }
  7561. $this->parserCurrentIndex++;
  7562. }
  7563. }
  7564. if ($matched) { continue; }
  7565. return $this->fail("unmatched `" + chr($cc) + "`", $currentChunkStartIndex);
  7566. // /, check for comment
  7567. case 47:
  7568. if ($parenLevel || ($this->parserCurrentIndex == $this->input_len - 1)) { continue; }
  7569. $cc2 = $this->CharCode($this->parserCurrentIndex+1);
  7570. if ($cc2 == 47) {
  7571. // //, find lnfeed
  7572. for ($this->parserCurrentIndex = $this->parserCurrentIndex + 2; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++) {
  7573. $cc2 = $this->CharCode($this->parserCurrentIndex);
  7574. if (($cc2 <= 13) && (($cc2 == 10) || ($cc2 == 13))) { break; }
  7575. }
  7576. } else if ($cc2 == 42) {
  7577. // /*, find */
  7578. $lastMultiComment = $currentChunkStartIndex = $this->parserCurrentIndex;
  7579. for ($this->parserCurrentIndex = $this->parserCurrentIndex + 2; $this->parserCurrentIndex < $this->input_len - 1; $this->parserCurrentIndex++) {
  7580. $cc2 = $this->CharCode($this->parserCurrentIndex);
  7581. if ($cc2 == 125) { $lastMultiCommentEndBrace = $this->parserCurrentIndex; }
  7582. if ($cc2 != 42) { continue; }
  7583. if ($this->CharCode($this->parserCurrentIndex+1) == 47) { break; }
  7584. }
  7585. if ($this->parserCurrentIndex == $this->input_len - 1) {
  7586. return $this->fail("missing closing `*/`", $currentChunkStartIndex);
  7587. }
  7588. }
  7589. continue;
  7590. // *, check for unmatched */
  7591. case 42:
  7592. if (($this->parserCurrentIndex < $this->input_len - 1) && ($this->CharCode($this->parserCurrentIndex+1) == 47)) {
  7593. return $this->fail("unmatched `/*`");
  7594. }
  7595. continue;
  7596. }
  7597. }
  7598. if( $level !== 0 ){
  7599. if( ($lastMultiComment > $lastOpening) && ($lastMultiCommentEndBrace > $lastMultiComment) ){
  7600. return $this->fail("missing closing `}` or `*/`", $lastOpening);
  7601. } else {
  7602. return $this->fail("missing closing `}`", $lastOpening);
  7603. }
  7604. } else if ( $parenLevel !== 0 ){
  7605. return $this->fail("missing closing `)`", $lastParen);
  7606. }
  7607. //chunk didn't fail
  7608. //$this->emitChunk(true);
  7609. }
  7610. public function CharCode($pos){
  7611. return ord($this->input[$pos]);
  7612. }
  7613. public function fail( $msg, $index = null ){
  7614. if( !$index ){
  7615. $this->index = $this->parserCurrentIndex;
  7616. }else{
  7617. $this->index = $index;
  7618. }
  7619. $this->message = 'ParseError: '.$msg;
  7620. }
  7621. /*
  7622. function emitChunk( $force = false ){
  7623. $len = $this->parserCurrentIndex - $this->emitFrom;
  7624. if ((($len < 512) && !$force) || !$len) {
  7625. return;
  7626. }
  7627. $chunks[] = substr($this->input, $this->emitFrom, $this->parserCurrentIndex + 1 - $this->emitFrom );
  7628. $this->emitFrom = $this->parserCurrentIndex + 1;
  7629. }
  7630. */
  7631. }
  7632. /**
  7633. * Compiler Exception
  7634. *
  7635. * @package Less
  7636. * @subpackage exception
  7637. */
  7638. class Less_Exception_Compiler extends Less_Exception_Parser{
  7639. }
  7640. /**
  7641. * Parser output with source map
  7642. *
  7643. * @package Less
  7644. * @subpackage Output
  7645. */
  7646. class Less_Output_Mapped extends Less_Output {
  7647. /**
  7648. * The source map generator
  7649. *
  7650. * @var Less_SourceMap_Generator
  7651. */
  7652. protected $generator;
  7653. /**
  7654. * Current line
  7655. *
  7656. * @var integer
  7657. */
  7658. protected $lineNumber = 0;
  7659. /**
  7660. * Current column
  7661. *
  7662. * @var integer
  7663. */
  7664. protected $column = 0;
  7665. /**
  7666. * Array of contents map (file and its content)
  7667. *
  7668. * @var array
  7669. */
  7670. protected $contentsMap = array();
  7671. /**
  7672. * Constructor
  7673. *
  7674. * @param array $contentsMap Array of filename to contents map
  7675. * @param Less_SourceMap_Generator $generator
  7676. */
  7677. public function __construct(array $contentsMap, $generator){
  7678. $this->contentsMap = $contentsMap;
  7679. $this->generator = $generator;
  7680. }
  7681. /**
  7682. * Adds a chunk to the stack
  7683. * The $index for less.php may be different from less.js since less.php does not chunkify inputs
  7684. *
  7685. * @param string $chunk
  7686. * @param string $fileInfo
  7687. * @param integer $index
  7688. * @param mixed $mapLines
  7689. */
  7690. public function add($chunk, $fileInfo = null, $index = 0, $mapLines = null){
  7691. //ignore adding empty strings
  7692. if( $chunk === '' ){
  7693. return;
  7694. }
  7695. $sourceLines = array();
  7696. $sourceColumns = ' ';
  7697. if( $fileInfo ){
  7698. $url = $fileInfo['currentUri'];
  7699. if( isset($this->contentsMap[$url]) ){
  7700. $inputSource = substr($this->contentsMap[$url], 0, $index);
  7701. $sourceLines = explode("\n", $inputSource);
  7702. $sourceColumns = end($sourceLines);
  7703. }else{
  7704. throw new Exception('Filename '.$url.' not in contentsMap');
  7705. }
  7706. }
  7707. $lines = explode("\n", $chunk);
  7708. $columns = end($lines);
  7709. if($fileInfo){
  7710. if(!$mapLines){
  7711. $this->generator->addMapping(
  7712. $this->lineNumber + 1, // generated_line
  7713. $this->column, // generated_column
  7714. count($sourceLines), // original_line
  7715. strlen($sourceColumns), // original_column
  7716. $fileInfo
  7717. );
  7718. }else{
  7719. for($i = 0, $count = count($lines); $i < $count; $i++){
  7720. $this->generator->addMapping(
  7721. $this->lineNumber + $i + 1, // generated_line
  7722. $i === 0 ? $this->column : 0, // generated_column
  7723. count($sourceLines) + $i, // original_line
  7724. $i === 0 ? strlen($sourceColumns) : 0, // original_column
  7725. $fileInfo
  7726. );
  7727. }
  7728. }
  7729. }
  7730. if(count($lines) === 1){
  7731. $this->column += strlen($columns);
  7732. }else{
  7733. $this->lineNumber += count($lines) - 1;
  7734. $this->column = strlen($columns);
  7735. }
  7736. // add only chunk
  7737. parent::add($chunk);
  7738. }
  7739. }
  7740. /**
  7741. * Encode / Decode Base64 VLQ.
  7742. *
  7743. * @package Less
  7744. * @subpackage SourceMap
  7745. */
  7746. class Less_SourceMap_Base64VLQ {
  7747. /**
  7748. * Shift
  7749. *
  7750. * @var integer
  7751. */
  7752. private $shift = 5;
  7753. /**
  7754. * Mask
  7755. *
  7756. * @var integer
  7757. */
  7758. private $mask = 0x1F; // == (1 << shift) == 0b00011111
  7759. /**
  7760. * Continuation bit
  7761. *
  7762. * @var integer
  7763. */
  7764. private $continuationBit = 0x20; // == (mask - 1 ) == 0b00100000
  7765. /**
  7766. * Char to integer map
  7767. *
  7768. * @var array
  7769. */
  7770. private $charToIntMap = array(
  7771. 'A' => 0, 'B' => 1, 'C' => 2, 'D' => 3, 'E' => 4, 'F' => 5, 'G' => 6,
  7772. 'H' => 7,'I' => 8, 'J' => 9, 'K' => 10, 'L' => 11, 'M' => 12, 'N' => 13,
  7773. 'O' => 14, 'P' => 15, 'Q' => 16, 'R' => 17, 'S' => 18, 'T' => 19, 'U' => 20,
  7774. 'V' => 21, 'W' => 22, 'X' => 23, 'Y' => 24, 'Z' => 25, 'a' => 26, 'b' => 27,
  7775. 'c' => 28, 'd' => 29, 'e' => 30, 'f' => 31, 'g' => 32, 'h' => 33, 'i' => 34,
  7776. 'j' => 35, 'k' => 36, 'l' => 37, 'm' => 38, 'n' => 39, 'o' => 40, 'p' => 41,
  7777. 'q' => 42, 'r' => 43, 's' => 44, 't' => 45, 'u' => 46, 'v' => 47, 'w' => 48,
  7778. 'x' => 49, 'y' => 50, 'z' => 51, 0 => 52, 1 => 53, 2 => 54, 3 => 55, 4 => 56,
  7779. 5 => 57, 6 => 58, 7 => 59, 8 => 60, 9 => 61, '+' => 62, '/' => 63,
  7780. );
  7781. /**
  7782. * Integer to char map
  7783. *
  7784. * @var array
  7785. */
  7786. private $intToCharMap = array(
  7787. 0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D', 4 => 'E', 5 => 'F', 6 => 'G',
  7788. 7 => 'H', 8 => 'I', 9 => 'J', 10 => 'K', 11 => 'L', 12 => 'M', 13 => 'N',
  7789. 14 => 'O', 15 => 'P', 16 => 'Q', 17 => 'R', 18 => 'S', 19 => 'T', 20 => 'U',
  7790. 21 => 'V', 22 => 'W', 23 => 'X', 24 => 'Y', 25 => 'Z', 26 => 'a', 27 => 'b',
  7791. 28 => 'c', 29 => 'd', 30 => 'e', 31 => 'f', 32 => 'g', 33 => 'h', 34 => 'i',
  7792. 35 => 'j', 36 => 'k', 37 => 'l', 38 => 'm', 39 => 'n', 40 => 'o', 41 => 'p',
  7793. 42 => 'q', 43 => 'r', 44 => 's', 45 => 't', 46 => 'u', 47 => 'v', 48 => 'w',
  7794. 49 => 'x', 50 => 'y', 51 => 'z', 52 => '0', 53 => '1', 54 => '2', 55 => '3',
  7795. 56 => '4', 57 => '5', 58 => '6', 59 => '7', 60 => '8', 61 => '9', 62 => '+',
  7796. 63 => '/',
  7797. );
  7798. /**
  7799. * Constructor
  7800. */
  7801. public function __construct(){
  7802. // I leave it here for future reference
  7803. // foreach(str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/') as $i => $char)
  7804. // {
  7805. // $this->charToIntMap[$char] = $i;
  7806. // $this->intToCharMap[$i] = $char;
  7807. // }
  7808. }
  7809. /**
  7810. * Convert from a two-complement value to a value where the sign bit is
  7811. * is placed in the least significant bit. For example, as decimals:
  7812. * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
  7813. * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
  7814. * We generate the value for 32 bit machines, hence -2147483648 becomes 1, not 4294967297,
  7815. * even on a 64 bit machine.
  7816. * @param string $aValue
  7817. */
  7818. public function toVLQSigned($aValue){
  7819. return 0xffffffff & ($aValue < 0 ? ((-$aValue) << 1) + 1 : ($aValue << 1) + 0);
  7820. }
  7821. /**
  7822. * Convert to a two-complement value from a value where the sign bit is
  7823. * is placed in the least significant bit. For example, as decimals:
  7824. * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
  7825. * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
  7826. * We assume that the value was generated with a 32 bit machine in mind.
  7827. * Hence
  7828. * 1 becomes -2147483648
  7829. * even on a 64 bit machine.
  7830. * @param integer $aValue
  7831. */
  7832. public function fromVLQSigned($aValue){
  7833. return $aValue & 1 ? $this->zeroFill(~$aValue + 2, 1) | (-1 - 0x7fffffff) : $this->zeroFill($aValue, 1);
  7834. }
  7835. /**
  7836. * Return the base 64 VLQ encoded value.
  7837. *
  7838. * @param string $aValue The value to encode
  7839. * @return string The encoded value
  7840. */
  7841. public function encode($aValue){
  7842. $encoded = '';
  7843. $vlq = $this->toVLQSigned($aValue);
  7844. do
  7845. {
  7846. $digit = $vlq & $this->mask;
  7847. $vlq = $this->zeroFill($vlq, $this->shift);
  7848. if($vlq > 0){
  7849. $digit |= $this->continuationBit;
  7850. }
  7851. $encoded .= $this->base64Encode($digit);
  7852. } while($vlq > 0);
  7853. return $encoded;
  7854. }
  7855. /**
  7856. * Return the value decoded from base 64 VLQ.
  7857. *
  7858. * @param string $encoded The encoded value to decode
  7859. * @return integer The decoded value
  7860. */
  7861. public function decode($encoded){
  7862. $vlq = 0;
  7863. $i = 0;
  7864. do
  7865. {
  7866. $digit = $this->base64Decode($encoded[$i]);
  7867. $vlq |= ($digit & $this->mask) << ($i * $this->shift);
  7868. $i++;
  7869. } while($digit & $this->continuationBit);
  7870. return $this->fromVLQSigned($vlq);
  7871. }
  7872. /**
  7873. * Right shift with zero fill.
  7874. *
  7875. * @param integer $a number to shift
  7876. * @param integer $b number of bits to shift
  7877. * @return integer
  7878. */
  7879. public function zeroFill($a, $b){
  7880. return ($a >= 0) ? ($a >> $b) : ($a >> $b) & (PHP_INT_MAX >> ($b - 1));
  7881. }
  7882. /**
  7883. * Encode single 6-bit digit as base64.
  7884. *
  7885. * @param integer $number
  7886. * @return string
  7887. * @throws Exception If the number is invalid
  7888. */
  7889. public function base64Encode($number){
  7890. if($number < 0 || $number > 63){
  7891. throw new Exception(sprintf('Invalid number "%s" given. Must be between 0 and 63.', $number));
  7892. }
  7893. return $this->intToCharMap[$number];
  7894. }
  7895. /**
  7896. * Decode single 6-bit digit from base64
  7897. *
  7898. * @param string $char
  7899. * @return number
  7900. * @throws Exception If the number is invalid
  7901. */
  7902. public function base64Decode($char){
  7903. if(!array_key_exists($char, $this->charToIntMap)){
  7904. throw new Exception(sprintf('Invalid base 64 digit "%s" given.', $char));
  7905. }
  7906. return $this->charToIntMap[$char];
  7907. }
  7908. }
  7909. /**
  7910. * Source map generator
  7911. *
  7912. * @package Less
  7913. * @subpackage Output
  7914. */
  7915. class Less_SourceMap_Generator extends Less_Configurable {
  7916. /**
  7917. * What version of source map does the generator generate?
  7918. */
  7919. const VERSION = 3;
  7920. /**
  7921. * Array of default options
  7922. *
  7923. * @var array
  7924. */
  7925. protected $defaultOptions = array(
  7926. // an optional source root, useful for relocating source files
  7927. // on a server or removing repeated values in the 'sources' entry.
  7928. // This value is prepended to the individual entries in the 'source' field.
  7929. 'sourceRoot' => '',
  7930. // an optional name of the generated code that this source map is associated with.
  7931. 'sourceMapFilename' => null,
  7932. // url of the map
  7933. 'sourceMapURL' => null,
  7934. // absolute path to a file to write the map to
  7935. 'sourceMapWriteTo' => null,
  7936. // output source contents?
  7937. 'outputSourceFiles' => false,
  7938. // base path for filename normalization
  7939. 'sourceMapRootpath' => '',
  7940. // base path for filename normalization
  7941. 'sourceMapBasepath' => ''
  7942. );
  7943. /**
  7944. * The base64 VLQ encoder
  7945. *
  7946. * @var Less_SourceMap_Base64VLQ
  7947. */
  7948. protected $encoder;
  7949. /**
  7950. * Array of mappings
  7951. *
  7952. * @var array
  7953. */
  7954. protected $mappings = array();
  7955. /**
  7956. * The root node
  7957. *
  7958. * @var Less_Tree_Ruleset
  7959. */
  7960. protected $root;
  7961. /**
  7962. * Array of contents map
  7963. *
  7964. * @var array
  7965. */
  7966. protected $contentsMap = array();
  7967. /**
  7968. * File to content map
  7969. *
  7970. * @var array
  7971. */
  7972. protected $sources = array();
  7973. protected $source_keys = array();
  7974. /**
  7975. * Constructor
  7976. *
  7977. * @param Less_Tree_Ruleset $root The root node
  7978. * @param array $options Array of options
  7979. */
  7980. public function __construct(Less_Tree_Ruleset $root, $contentsMap, $options = array()){
  7981. $this->root = $root;
  7982. $this->contentsMap = $contentsMap;
  7983. $this->encoder = new Less_SourceMap_Base64VLQ();
  7984. $this->SetOptions($options);
  7985. // fix windows paths
  7986. if( !empty($this->options['sourceMapRootpath']) ){
  7987. $this->options['sourceMapRootpath'] = str_replace('\\', '/', $this->options['sourceMapRootpath']);
  7988. $this->options['sourceMapRootpath'] = rtrim($this->options['sourceMapRootpath'],'/').'/';
  7989. }
  7990. }
  7991. /**
  7992. * Generates the CSS
  7993. *
  7994. * @return string
  7995. */
  7996. public function generateCSS(){
  7997. $output = new Less_Output_Mapped($this->contentsMap, $this);
  7998. // catch the output
  7999. $this->root->genCSS($output);
  8000. $sourceMapUrl = $this->getOption('sourceMapURL');
  8001. $sourceMapFilename = $this->getOption('sourceMapFilename');
  8002. $sourceMapContent = $this->generateJson();
  8003. $sourceMapWriteTo = $this->getOption('sourceMapWriteTo');
  8004. if( !$sourceMapUrl && $sourceMapFilename ){
  8005. $sourceMapUrl = $this->normalizeFilename($sourceMapFilename);
  8006. }
  8007. // write map to a file
  8008. if( $sourceMapWriteTo ){
  8009. $this->saveMap($sourceMapWriteTo, $sourceMapContent);
  8010. }
  8011. // inline the map
  8012. if( !$sourceMapUrl ){
  8013. $sourceMapUrl = sprintf('data:application/json,%s', Less_Functions::encodeURIComponent($sourceMapContent));
  8014. }
  8015. if( $sourceMapUrl ){
  8016. $output->add( sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl) );
  8017. }
  8018. return $output->toString();
  8019. }
  8020. /**
  8021. * Saves the source map to a file
  8022. *
  8023. * @param string $file The absolute path to a file
  8024. * @param string $content The content to write
  8025. * @throws Exception If the file could not be saved
  8026. */
  8027. protected function saveMap($file, $content){
  8028. $dir = dirname($file);
  8029. // directory does not exist
  8030. if( !is_dir($dir) ){
  8031. // FIXME: create the dir automatically?
  8032. throw new Exception(sprintf('The directory "%s" does not exist. Cannot save the source map.', $dir));
  8033. }
  8034. // FIXME: proper saving, with dir write check!
  8035. if(file_put_contents($file, $content) === false){
  8036. throw new Exception(sprintf('Cannot save the source map to "%s"', $file));
  8037. }
  8038. return true;
  8039. }
  8040. /**
  8041. * Normalizes the filename
  8042. *
  8043. * @param string $filename
  8044. * @return string
  8045. */
  8046. protected function normalizeFilename($filename){
  8047. $filename = str_replace('\\', '/', $filename);
  8048. $rootpath = $this->getOption('sourceMapRootpath');
  8049. $basePath = $this->getOption('sourceMapBasepath');
  8050. // "Trim" the 'sourceMapBasepath' from the output filename.
  8051. if (strpos($filename, $basePath) === 0) {
  8052. $filename = substr($filename, strlen($basePath));
  8053. }
  8054. // Remove extra leading path separators.
  8055. if(strpos($filename, '\\') === 0 || strpos($filename, '/') === 0){
  8056. $filename = substr($filename, 1);
  8057. }
  8058. return $rootpath . $filename;
  8059. }
  8060. /**
  8061. * Adds a mapping
  8062. *
  8063. * @param integer $generatedLine The line number in generated file
  8064. * @param integer $generatedColumn The column number in generated file
  8065. * @param integer $originalLine The line number in original file
  8066. * @param integer $originalColumn The column number in original file
  8067. * @param string $sourceFile The original source file
  8068. */
  8069. public function addMapping($generatedLine, $generatedColumn, $originalLine, $originalColumn, $fileInfo ){
  8070. $this->mappings[] = array(
  8071. 'generated_line' => $generatedLine,
  8072. 'generated_column' => $generatedColumn,
  8073. 'original_line' => $originalLine,
  8074. 'original_column' => $originalColumn,
  8075. 'source_file' => $fileInfo['currentUri']
  8076. );
  8077. $this->sources[$fileInfo['currentUri']] = $fileInfo['filename'];
  8078. }
  8079. /**
  8080. * Generates the JSON source map
  8081. *
  8082. * @return string
  8083. * @see https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#
  8084. */
  8085. protected function generateJson(){
  8086. $sourceMap = array();
  8087. $mappings = $this->generateMappings();
  8088. // File version (always the first entry in the object) and must be a positive integer.
  8089. $sourceMap['version'] = self::VERSION;
  8090. // An optional name of the generated code that this source map is associated with.
  8091. $file = $this->getOption('sourceMapFilename');
  8092. if( $file ){
  8093. $sourceMap['file'] = $file;
  8094. }
  8095. // 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.
  8096. $root = $this->getOption('sourceRoot');
  8097. if( $root ){
  8098. $sourceMap['sourceRoot'] = $root;
  8099. }
  8100. // A list of original sources used by the 'mappings' entry.
  8101. $sourceMap['sources'] = array();
  8102. foreach($this->sources as $source_uri => $source_filename){
  8103. $sourceMap['sources'][] = $this->normalizeFilename($source_filename);
  8104. }
  8105. // A list of symbol names used by the 'mappings' entry.
  8106. $sourceMap['names'] = array();
  8107. // A string with the encoded mapping data.
  8108. $sourceMap['mappings'] = $mappings;
  8109. if( $this->getOption('outputSourceFiles') ){
  8110. // An optional list of source content, useful when the 'source' can't be hosted.
  8111. // The contents are listed in the same order as the sources above.
  8112. // 'null' may be used if some original sources should be retrieved by name.
  8113. $sourceMap['sourcesContent'] = $this->getSourcesContent();
  8114. }
  8115. // less.js compat fixes
  8116. if( count($sourceMap['sources']) && empty($sourceMap['sourceRoot']) ){
  8117. unset($sourceMap['sourceRoot']);
  8118. }
  8119. return json_encode($sourceMap);
  8120. }
  8121. /**
  8122. * Returns the sources contents
  8123. *
  8124. * @return array|null
  8125. */
  8126. protected function getSourcesContent(){
  8127. if(empty($this->sources)){
  8128. return;
  8129. }
  8130. $content = array();
  8131. foreach($this->sources as $sourceFile){
  8132. $content[] = file_get_contents($sourceFile);
  8133. }
  8134. return $content;
  8135. }
  8136. /**
  8137. * Generates the mappings string
  8138. *
  8139. * @return string
  8140. */
  8141. public function generateMappings(){
  8142. if( !count($this->mappings) ){
  8143. return '';
  8144. }
  8145. $this->source_keys = array_flip(array_keys($this->sources));
  8146. // group mappings by generated line number.
  8147. $groupedMap = $groupedMapEncoded = array();
  8148. foreach($this->mappings as $m){
  8149. $groupedMap[$m['generated_line']][] = $m;
  8150. }
  8151. ksort($groupedMap);
  8152. $lastGeneratedLine = $lastOriginalIndex = $lastOriginalLine = $lastOriginalColumn = 0;
  8153. foreach($groupedMap as $lineNumber => $line_map){
  8154. while(++$lastGeneratedLine < $lineNumber){
  8155. $groupedMapEncoded[] = ';';
  8156. }
  8157. $lineMapEncoded = array();
  8158. $lastGeneratedColumn = 0;
  8159. foreach($line_map as $m){
  8160. $mapEncoded = $this->encoder->encode($m['generated_column'] - $lastGeneratedColumn);
  8161. $lastGeneratedColumn = $m['generated_column'];
  8162. // find the index
  8163. if( $m['source_file'] ){
  8164. $index = $this->findFileIndex($m['source_file']);
  8165. if( $index !== false ){
  8166. $mapEncoded .= $this->encoder->encode($index - $lastOriginalIndex);
  8167. $lastOriginalIndex = $index;
  8168. // lines are stored 0-based in SourceMap spec version 3
  8169. $mapEncoded .= $this->encoder->encode($m['original_line'] - 1 - $lastOriginalLine);
  8170. $lastOriginalLine = $m['original_line'] - 1;
  8171. $mapEncoded .= $this->encoder->encode($m['original_column'] - $lastOriginalColumn);
  8172. $lastOriginalColumn = $m['original_column'];
  8173. }
  8174. }
  8175. $lineMapEncoded[] = $mapEncoded;
  8176. }
  8177. $groupedMapEncoded[] = implode(',', $lineMapEncoded) . ';';
  8178. }
  8179. return rtrim(implode($groupedMapEncoded), ';');
  8180. }
  8181. /**
  8182. * Finds the index for the filename
  8183. *
  8184. * @param string $filename
  8185. * @return integer|false
  8186. */
  8187. protected function findFileIndex($filename){
  8188. return $this->source_keys[$filename];
  8189. }
  8190. }