1
0

Less.php 255 KB

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