phpQuery.php 166 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086
  1. <?php
  2. /**
  3. * phpQuery is a server-side, chainable, CSS3 selector driven
  4. * Document Object Model (DOM) API based on jQuery JavaScript Library.
  5. *
  6. * @version 1.0.0
  7. * @link http://code.google.com/p/phpquery/
  8. * @link http://phpquery-library.blogspot.com/
  9. * @link http://jquery.com/
  10. * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
  11. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  12. * @package phpQuery
  13. */
  14. // class names for instanceof
  15. // TODO move them as class constants into phpQuery
  16. define('DOMDOCUMENT', 'DOMDocument');
  17. define('DOMELEMENT', 'DOMElement');
  18. define('DOMNODELIST', 'DOMNodeList');
  19. define('DOMNODE', 'DOMNode');
  20. /**
  21. * DOMEvent class.
  22. *
  23. * Based on
  24. * @link http://developer.mozilla.org/En/DOM:event
  25. * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
  26. * @package phpQuery
  27. * @todo implement ArrayAccess ?
  28. */
  29. class DOMEvent
  30. {
  31. /**
  32. * Returns a boolean indicating whether the event bubbles up through the DOM or not.
  33. *
  34. * @var unknown_type
  35. */
  36. public $bubbles = true;
  37. /**
  38. * Returns a boolean indicating whether the event is cancelable.
  39. *
  40. * @var unknown_type
  41. */
  42. public $cancelable = true;
  43. /**
  44. * Returns a reference to the currently registered target for the event.
  45. *
  46. * @var unknown_type
  47. */
  48. public $currentTarget;
  49. /**
  50. * Returns detail about the event, depending on the type of event.
  51. *
  52. * @var unknown_type
  53. * @link http://developer.mozilla.org/en/DOM/event.detail
  54. */
  55. public $detail; // ???
  56. /**
  57. * Used to indicate which phase of the event flow is currently being evaluated.
  58. *
  59. * NOT IMPLEMENTED
  60. *
  61. * @var unknown_type
  62. * @link http://developer.mozilla.org/en/DOM/event.eventPhase
  63. */
  64. public $eventPhase; // ???
  65. /**
  66. * The explicit original target of the event (Mozilla-specific).
  67. *
  68. * NOT IMPLEMENTED
  69. *
  70. * @var unknown_type
  71. */
  72. public $explicitOriginalTarget; // moz only
  73. /**
  74. * The original target of the event, before any retargetings (Mozilla-specific).
  75. *
  76. * NOT IMPLEMENTED
  77. *
  78. * @var unknown_type
  79. */
  80. public $originalTarget; // moz only
  81. /**
  82. * Identifies a secondary target for the event.
  83. *
  84. * @var unknown_type
  85. */
  86. public $relatedTarget;
  87. /**
  88. * Returns a reference to the target to which the event was originally dispatched.
  89. *
  90. * @var unknown_type
  91. */
  92. public $target;
  93. /**
  94. * Returns the time that the event was created.
  95. *
  96. * @var unknown_type
  97. */
  98. public $timeStamp;
  99. /**
  100. * Returns the name of the event (case-insensitive).
  101. */
  102. public $type;
  103. public $runDefault = true;
  104. public $data = null;
  105. public function __construct($data)
  106. {
  107. foreach ($data as $k => $v) {
  108. $this->$k = $v;
  109. }
  110. if (!$this->timeStamp)
  111. $this->timeStamp = time();
  112. }
  113. /**
  114. * Cancels the event (if it is cancelable).
  115. *
  116. */
  117. public function preventDefault()
  118. {
  119. $this->runDefault = false;
  120. }
  121. /**
  122. * Stops the propagation of events further along in the DOM.
  123. *
  124. */
  125. public function stopPropagation()
  126. {
  127. $this->bubbles = false;
  128. }
  129. }
  130. /**
  131. * DOMDocumentWrapper class simplifies work with DOMDocument.
  132. *
  133. * Know bug:
  134. * - in XHTML fragments, <br /> changes to <br clear="none" />
  135. *
  136. * @todo check XML catalogs compatibility
  137. * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
  138. * @package phpQuery
  139. */
  140. class DOMDocumentWrapper
  141. {
  142. /**
  143. * @var DOMDocument
  144. */
  145. public $document;
  146. public $id;
  147. /**
  148. * @todo Rewrite as method and quess if null.
  149. * @var unknown_type
  150. */
  151. public $contentType = '';
  152. public $xpath;
  153. public $uuid = 0;
  154. public $data = array();
  155. public $dataNodes = array();
  156. public $events = array();
  157. public $eventsNodes = array();
  158. public $eventsGlobal = array();
  159. /**
  160. * @TODO iframes support http://code.google.com/p/phpquery/issues/detail?id=28
  161. * @var unknown_type
  162. */
  163. public $frames = array();
  164. /**
  165. * Document root, by default equals to document itself.
  166. * Used by documentFragments.
  167. *
  168. * @var DOMNode
  169. */
  170. public $root;
  171. public $isDocumentFragment;
  172. public $isXML = false;
  173. public $isXHTML = false;
  174. public $isHTML = false;
  175. public $charset;
  176. public function __construct($markup = null, $contentType = null, $newDocumentID = null)
  177. {
  178. if (isset($markup))
  179. $this->load($markup, $contentType, $newDocumentID);
  180. $this->id = $newDocumentID
  181. ? $newDocumentID
  182. : md5(microtime());
  183. }
  184. public function load($markup, $contentType = null, $newDocumentID = null)
  185. {
  186. // phpQuery::$documents[$id] = $this;
  187. $this->contentType = strtolower($contentType);
  188. if ($markup instanceof DOMDOCUMENT) {
  189. $this->document = $markup;
  190. $this->root = $this->document;
  191. $this->charset = $this->document->encoding;
  192. // TODO isDocumentFragment
  193. $loaded = true;
  194. } else {
  195. $loaded = $this->loadMarkup($markup);
  196. }
  197. if ($loaded) {
  198. // $this->document->formatOutput = true;
  199. $this->document->preserveWhiteSpace = true;
  200. $this->xpath = new DOMXPath($this->document);
  201. $this->afterMarkupLoad();
  202. return true;
  203. // remember last loaded document
  204. // return phpQuery::selectDocument($id);
  205. }
  206. return false;
  207. }
  208. protected function afterMarkupLoad()
  209. {
  210. if ($this->isXHTML) {
  211. $this->xpath->registerNamespace("html", "http://www.w3.org/1999/xhtml");
  212. }
  213. }
  214. protected function loadMarkup($markup)
  215. {
  216. $loaded = false;
  217. if ($this->contentType) {
  218. self::debug("Load markup for content type {$this->contentType}");
  219. // content determined by contentType
  220. list($contentType, $charset) = $this->contentTypeToArray($this->contentType);
  221. switch ($contentType) {
  222. case 'text/html':
  223. phpQuery::debug("Loading HTML, content type '{$this->contentType}'");
  224. $loaded = $this->loadMarkupHTML($markup, $charset);
  225. break;
  226. case 'text/xml':
  227. case 'application/xhtml+xml':
  228. phpQuery::debug("Loading XML, content type '{$this->contentType}'");
  229. $loaded = $this->loadMarkupXML($markup, $charset);
  230. break;
  231. default:
  232. // for feeds or anything that sometimes doesn't use text/xml
  233. if (strpos('xml', $this->contentType) !== false) {
  234. phpQuery::debug("Loading XML, content type '{$this->contentType}'");
  235. $loaded = $this->loadMarkupXML($markup, $charset);
  236. } else
  237. phpQuery::debug("Could not determine document type from content type '{$this->contentType}'");
  238. }
  239. } else {
  240. // content type autodetection
  241. if ($this->isXML($markup)) {
  242. phpQuery::debug("Loading XML, isXML() == true");
  243. $loaded = $this->loadMarkupXML($markup);
  244. if (!$loaded && $this->isXHTML) {
  245. phpQuery::debug('Loading as XML failed, trying to load as HTML, isXHTML == true');
  246. $loaded = $this->loadMarkupHTML($markup);
  247. }
  248. } else {
  249. phpQuery::debug("Loading HTML, isXML() == false");
  250. $loaded = $this->loadMarkupHTML($markup);
  251. }
  252. }
  253. return $loaded;
  254. }
  255. protected function loadMarkupReset()
  256. {
  257. $this->isXML = $this->isXHTML = $this->isHTML = false;
  258. }
  259. protected function documentCreate($charset, $version = '1.0')
  260. {
  261. if (!$version)
  262. $version = '1.0';
  263. $this->document = new DOMDocument($version, $charset);
  264. $this->charset = $this->document->encoding;
  265. // $this->document->encoding = $charset;
  266. $this->document->formatOutput = true;
  267. $this->document->preserveWhiteSpace = true;
  268. }
  269. protected function loadMarkupHTML($markup, $requestedCharset = null)
  270. {
  271. if (phpQuery::$debug)
  272. phpQuery::debug('Full markup load (HTML): ' . substr($markup, 0, 250));
  273. $this->loadMarkupReset();
  274. $this->isHTML = true;
  275. if (!isset($this->isDocumentFragment))
  276. $this->isDocumentFragment = self::isDocumentFragmentHTML($markup);
  277. $charset = null;
  278. $documentCharset = $this->charsetFromHTML($markup);
  279. $addDocumentCharset = false;
  280. if ($documentCharset) {
  281. $charset = $documentCharset;
  282. $markup = $this->charsetFixHTML($markup);
  283. } else if ($requestedCharset) {
  284. $charset = $requestedCharset;
  285. }
  286. if (!$charset)
  287. $charset = phpQuery::$defaultCharset;
  288. // HTTP 1.1 says that the default charset is ISO-8859-1
  289. // @see http://www.w3.org/International/O-HTTP-charset
  290. if (!$documentCharset) {
  291. $documentCharset = 'ISO-8859-1';
  292. $addDocumentCharset = true;
  293. }
  294. // Should be careful here, still need 'magic encoding detection' since lots of pages have other 'default encoding'
  295. // Worse, some pages can have mixed encodings... we'll try not to worry about that
  296. $requestedCharset = $requestedCharset ? strtoupper($requestedCharset) : "";
  297. $documentCharset = strtoupper($documentCharset);
  298. phpQuery::debug("DOC: $documentCharset REQ: $requestedCharset");
  299. if ($requestedCharset && $documentCharset && $requestedCharset !== $documentCharset) {
  300. phpQuery::debug("CHARSET CONVERT");
  301. // Document Encoding Conversion
  302. // http://code.google.com/p/phpquery/issues/detail?id=86
  303. if (function_exists('mb_detect_encoding')) {
  304. $possibleCharsets = array($documentCharset, $requestedCharset, 'AUTO');
  305. $docEncoding = mb_detect_encoding($markup, implode(', ', $possibleCharsets));
  306. if (!$docEncoding)
  307. $docEncoding = $documentCharset; // ok trust the document
  308. phpQuery::debug("DETECTED '$docEncoding'");
  309. // Detected does not match what document says...
  310. if ($docEncoding !== $documentCharset) {
  311. // Tricky..
  312. }
  313. if ($docEncoding !== $requestedCharset) {
  314. phpQuery::debug("CONVERT $docEncoding => $requestedCharset");
  315. $markup = mb_convert_encoding($markup, $requestedCharset, $docEncoding);
  316. $markup = $this->charsetAppendToHTML($markup, $requestedCharset);
  317. $charset = $requestedCharset;
  318. }
  319. } else {
  320. phpQuery::debug("TODO: charset conversion without mbstring...");
  321. }
  322. }
  323. $return = false;
  324. if ($this->isDocumentFragment) {
  325. phpQuery::debug("Full markup load (HTML), DocumentFragment detected, using charset '$charset'");
  326. $return = $this->documentFragmentLoadMarkup($this, $charset, $markup);
  327. } else {
  328. if ($addDocumentCharset) {
  329. phpQuery::debug("Full markup load (HTML), appending charset: '$charset'");
  330. $markup = $this->charsetAppendToHTML($markup, $charset);
  331. }
  332. phpQuery::debug("Full markup load (HTML), documentCreate('$charset')");
  333. $this->documentCreate($charset);
  334. $return = phpQuery::$debug === 2
  335. ? $this->document->loadHTML($markup)
  336. : @$this->document->loadHTML($markup);
  337. if ($return)
  338. $this->root = $this->document;
  339. }
  340. if ($return && !$this->contentType)
  341. $this->contentType = 'text/html';
  342. return $return;
  343. }
  344. protected function loadMarkupXML($markup, $requestedCharset = null)
  345. {
  346. if (phpQuery::$debug)
  347. phpQuery::debug('Full markup load (XML): ' . substr($markup, 0, 250));
  348. $this->loadMarkupReset();
  349. $this->isXML = true;
  350. // check agains XHTML in contentType or markup
  351. $isContentTypeXHTML = $this->isXHTML();
  352. $isMarkupXHTML = $this->isXHTML($markup);
  353. if ($isContentTypeXHTML || $isMarkupXHTML) {
  354. self::debug('Full markup load (XML), XHTML detected');
  355. $this->isXHTML = true;
  356. }
  357. // determine document fragment
  358. if (!isset($this->isDocumentFragment))
  359. $this->isDocumentFragment = $this->isXHTML
  360. ? self::isDocumentFragmentXHTML($markup)
  361. : self::isDocumentFragmentXML($markup);
  362. // this charset will be used
  363. $charset = null;
  364. // charset from XML declaration @var string
  365. $documentCharset = $this->charsetFromXML($markup);
  366. if (!$documentCharset) {
  367. if ($this->isXHTML) {
  368. // this is XHTML, try to get charset from content-type meta header
  369. $documentCharset = $this->charsetFromHTML($markup);
  370. if ($documentCharset) {
  371. phpQuery::debug("Full markup load (XML), appending XHTML charset '$documentCharset'");
  372. $this->charsetAppendToXML($markup, $documentCharset);
  373. $charset = $documentCharset;
  374. }
  375. }
  376. if (!$documentCharset) {
  377. // if still no document charset...
  378. $charset = $requestedCharset;
  379. }
  380. } else if ($requestedCharset) {
  381. $charset = $requestedCharset;
  382. }
  383. if (!$charset) {
  384. $charset = phpQuery::$defaultCharset;
  385. }
  386. if ($requestedCharset && $documentCharset && $requestedCharset != $documentCharset) {
  387. // TODO place for charset conversion
  388. // $charset = $requestedCharset;
  389. }
  390. $return = false;
  391. if ($this->isDocumentFragment) {
  392. phpQuery::debug("Full markup load (XML), DocumentFragment detected, using charset '$charset'");
  393. $return = $this->documentFragmentLoadMarkup($this, $charset, $markup);
  394. } else {
  395. // FIXME ???
  396. if ($isContentTypeXHTML && !$isMarkupXHTML)
  397. if (!$documentCharset) {
  398. phpQuery::debug("Full markup load (XML), appending charset '$charset'");
  399. $markup = $this->charsetAppendToXML($markup, $charset);
  400. }
  401. // see http://pl2.php.net/manual/en/book.dom.php#78929
  402. // LIBXML_DTDLOAD (>= PHP 5.1)
  403. // does XML ctalogues works with LIBXML_NONET
  404. // $this->document->resolveExternals = true;
  405. // TODO test LIBXML_COMPACT for performance improvement
  406. // create document
  407. $this->documentCreate($charset);
  408. if (phpversion() < 5.1) {
  409. $this->document->resolveExternals = true;
  410. $return = phpQuery::$debug === 2
  411. ? $this->document->loadXML($markup)
  412. : @$this->document->loadXML($markup);
  413. } else {
  414. /** @link http://pl2.php.net/manual/en/libxml.constants.php */
  415. $libxmlStatic = phpQuery::$debug === 2
  416. ? LIBXML_DTDLOAD | LIBXML_DTDATTR | LIBXML_NONET
  417. : LIBXML_DTDLOAD | LIBXML_DTDATTR | LIBXML_NONET | LIBXML_NOWARNING | LIBXML_NOERROR;
  418. $return = $this->document->loadXML($markup, $libxmlStatic);
  419. // if (! $return)
  420. // $return = $this->document->loadHTML($markup);
  421. }
  422. if ($return)
  423. $this->root = $this->document;
  424. }
  425. if ($return) {
  426. if (!$this->contentType) {
  427. if ($this->isXHTML)
  428. $this->contentType = 'application/xhtml+xml';
  429. else
  430. $this->contentType = 'text/xml';
  431. }
  432. return $return;
  433. } else {
  434. throw new Exception("Error loading XML markup");
  435. }
  436. }
  437. protected function isXHTML($markup = null)
  438. {
  439. if (!isset($markup)) {
  440. return strpos($this->contentType, 'xhtml') !== false;
  441. }
  442. // XXX ok ?
  443. return strpos($markup, "<!DOCTYPE html") !== false;
  444. // return stripos($doctype, 'xhtml') !== false;
  445. // $doctype = isset($dom->doctype) && is_object($dom->doctype)
  446. // ? $dom->doctype->publicId
  447. // : self::$defaultDoctype;
  448. }
  449. protected function isXML($markup)
  450. {
  451. // return strpos($markup, '<?xml') !== false && stripos($markup, 'xhtml') === false;
  452. return strpos(substr($markup, 0, 100), '<' . '?xml') !== false;
  453. }
  454. protected function contentTypeToArray($contentType)
  455. {
  456. $matches = explode(';', trim(strtolower($contentType)));
  457. if (isset($matches[1])) {
  458. $matches[1] = explode('=', $matches[1]);
  459. // strip 'charset='
  460. $matches[1] = isset($matches[1][1]) && trim($matches[1][1])
  461. ? $matches[1][1]
  462. : $matches[1][0];
  463. } else
  464. $matches[1] = null;
  465. return $matches;
  466. }
  467. /**
  468. *
  469. * @param $markup
  470. * @return array contentType, charset
  471. */
  472. protected function contentTypeFromHTML($markup)
  473. {
  474. $matches = array();
  475. // find meta tag
  476. preg_match(
  477. '@<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i',
  478. $markup,
  479. $matches
  480. );
  481. if (!isset($matches[0]))
  482. return array(null, null);
  483. // get attr 'content'
  484. preg_match('@content\\s*=\\s*(["|\'])(.+?)\\1@', $matches[0], $matches);
  485. if (!isset($matches[0]))
  486. return array(null, null);
  487. return $this->contentTypeToArray($matches[2]);
  488. }
  489. protected function charsetFromHTML($markup)
  490. {
  491. $contentType = $this->contentTypeFromHTML($markup);
  492. return $contentType[1];
  493. }
  494. protected function charsetFromXML($markup)
  495. {
  496. $matches;
  497. // find declaration
  498. preg_match(
  499. '@<' . '?xml[^>]+encoding\\s*=\\s*(["|\'])(.*?)\\1@i',
  500. $markup,
  501. $matches
  502. );
  503. return isset($matches[2])
  504. ? strtolower($matches[2])
  505. : null;
  506. }
  507. /**
  508. * Repositions meta[type=charset] at the start of head. Bypasses DOMDocument bug.
  509. *
  510. * @link http://code.google.com/p/phpquery/issues/detail?id=80
  511. * @param $html
  512. */
  513. protected function charsetFixHTML($markup)
  514. {
  515. $matches = array();
  516. // find meta tag
  517. preg_match(
  518. '@\s*<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i',
  519. $markup,
  520. $matches,
  521. PREG_OFFSET_CAPTURE
  522. );
  523. if (!isset($matches[0]))
  524. return;
  525. $metaContentType = $matches[0][0];
  526. $markup = substr($markup, 0, $matches[0][1])
  527. . substr($markup, $matches[0][1] + strlen($metaContentType));
  528. $headStart = stripos($markup, '<head>');
  529. $markup = substr($markup, 0, $headStart + 6) . $metaContentType
  530. . substr($markup, $headStart + 6);
  531. return $markup;
  532. }
  533. protected function charsetAppendToHTML($html, $charset, $xhtml = false)
  534. {
  535. // remove existing meta[type=content-type]
  536. $html = preg_replace('@\s*<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', '', $html);
  537. $meta = '<meta http-equiv="Content-Type" content="text/html;charset='
  538. . $charset . '" '
  539. . ($xhtml ? '/' : '')
  540. . '>';
  541. if (strpos($html, '<head') === false) {
  542. if (strpos($html, '<html') === false) {
  543. return $meta . $html;
  544. } else {
  545. return preg_replace(
  546. '@<html(.*?)(?(?<!\?)>)@s',
  547. "<html\\1><head>{$meta}</head>",
  548. $html
  549. );
  550. }
  551. } else {
  552. return preg_replace(
  553. '@<head(.*?)(?(?<!\?)>)@s',
  554. '<head\\1>' . $meta,
  555. $html
  556. );
  557. }
  558. }
  559. protected function charsetAppendToXML($markup, $charset)
  560. {
  561. $declaration = '<' . '?xml version="1.0" encoding="' . $charset . '"?' . '>';
  562. return $declaration . $markup;
  563. }
  564. public static function isDocumentFragmentHTML($markup)
  565. {
  566. return stripos($markup, '<html') === false && stripos($markup, '<!doctype') === false;
  567. }
  568. public static function isDocumentFragmentXML($markup)
  569. {
  570. return stripos($markup, '<' . '?xml') === false;
  571. }
  572. public static function isDocumentFragmentXHTML($markup)
  573. {
  574. return self::isDocumentFragmentHTML($markup);
  575. }
  576. public function importAttr($value)
  577. {
  578. // TODO
  579. }
  580. /**
  581. *
  582. * @param $source
  583. * @param $target
  584. * @param $sourceCharset
  585. * @return array Array of imported nodes.
  586. */
  587. public function import($source, $sourceCharset = null)
  588. {
  589. // TODO charset conversions
  590. $return = array();
  591. if ($source instanceof DOMNODE && !($source instanceof DOMNODELIST))
  592. $source = array($source);
  593. // if (is_array($source)) {
  594. // foreach($source as $node) {
  595. // if (is_string($node)) {
  596. // // string markup
  597. // $fake = $this->documentFragmentCreate($node, $sourceCharset);
  598. // if ($fake === false)
  599. // throw new Exception("Error loading documentFragment markup");
  600. // else
  601. // $return = array_merge($return,
  602. // $this->import($fake->root->childNodes)
  603. // );
  604. // } else {
  605. // $return[] = $this->document->importNode($node, true);
  606. // }
  607. // }
  608. // return $return;
  609. // } else {
  610. // // string markup
  611. // $fake = $this->documentFragmentCreate($source, $sourceCharset);
  612. // if ($fake === false)
  613. // throw new Exception("Error loading documentFragment markup");
  614. // else
  615. // return $this->import($fake->root->childNodes);
  616. // }
  617. if (is_array($source) || $source instanceof DOMNODELIST) {
  618. // dom nodes
  619. self::debug('Importing nodes to document');
  620. foreach ($source as $node)
  621. $return[] = $this->document->importNode($node, true);
  622. } else {
  623. // string markup
  624. $fake = $this->documentFragmentCreate($source, $sourceCharset);
  625. if ($fake === false)
  626. throw new Exception("Error loading documentFragment markup");
  627. else
  628. return $this->import($fake->root->childNodes);
  629. }
  630. return $return;
  631. }
  632. /**
  633. * Creates new document fragment.
  634. *
  635. * @param $source
  636. * @return DOMDocumentWrapper
  637. */
  638. protected function documentFragmentCreate($source, $charset = null)
  639. {
  640. $fake = new DOMDocumentWrapper();
  641. $fake->contentType = $this->contentType;
  642. $fake->isXML = $this->isXML;
  643. $fake->isHTML = $this->isHTML;
  644. $fake->isXHTML = $this->isXHTML;
  645. $fake->root = $fake->document;
  646. if (!$charset)
  647. $charset = $this->charset;
  648. // $fake->documentCreate($this->charset);
  649. if ($source instanceof DOMNODE && !($source instanceof DOMNODELIST))
  650. $source = array($source);
  651. if (is_array($source) || $source instanceof DOMNODELIST) {
  652. // dom nodes
  653. // load fake document
  654. if (!$this->documentFragmentLoadMarkup($fake, $charset))
  655. return false;
  656. $nodes = $fake->import($source);
  657. foreach ($nodes as $node)
  658. $fake->root->appendChild($node);
  659. } else {
  660. // string markup
  661. $this->documentFragmentLoadMarkup($fake, $charset, $source);
  662. }
  663. return $fake;
  664. }
  665. /**
  666. *
  667. * @param $document DOMDocumentWrapper
  668. * @param $markup
  669. * @return $document
  670. */
  671. private function documentFragmentLoadMarkup($fragment, $charset, $markup = null)
  672. {
  673. // TODO error handling
  674. // TODO copy doctype
  675. // tempolary turn off
  676. $fragment->isDocumentFragment = false;
  677. if ($fragment->isXML) {
  678. if ($fragment->isXHTML) {
  679. // add FAKE element to set default namespace
  680. $fragment->loadMarkupXML('<?xml version="1.0" encoding="' . $charset . '"?>'
  681. . '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" '
  682. . '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
  683. . '<fake xmlns="http://www.w3.org/1999/xhtml">' . $markup . '</fake>');
  684. $fragment->root = $fragment->document->firstChild->nextSibling;
  685. } else {
  686. $fragment->loadMarkupXML('<?xml version="1.0" encoding="' . $charset . '"?><fake>' . $markup . '</fake>');
  687. $fragment->root = $fragment->document->firstChild;
  688. }
  689. } else {
  690. $markup2 = phpQuery::$defaultDoctype . '<html><head><meta http-equiv="Content-Type" content="text/html;charset='
  691. . $charset . '"></head>';
  692. if ($markup == null) {
  693. $markup = "";
  694. }
  695. $noBody = strpos($markup, '<body') === false;
  696. if ($noBody)
  697. $markup2 .= '<body>';
  698. $markup2 .= $markup;
  699. if ($noBody)
  700. $markup2 .= '</body>';
  701. $markup2 .= '</html>';
  702. $fragment->loadMarkupHTML($markup2);
  703. // TODO resolv body tag merging issue
  704. $fragment->root = $noBody
  705. ? $fragment->document->firstChild->nextSibling->firstChild->nextSibling
  706. : $fragment->document->firstChild->nextSibling->firstChild->nextSibling;
  707. }
  708. if (!$fragment->root)
  709. return false;
  710. $fragment->isDocumentFragment = true;
  711. return true;
  712. }
  713. protected function documentFragmentToMarkup($fragment)
  714. {
  715. phpQuery::debug('documentFragmentToMarkup');
  716. $tmp = $fragment->isDocumentFragment;
  717. $fragment->isDocumentFragment = false;
  718. $markup = $fragment->markup();
  719. if ($fragment->isXML) {
  720. $markup = substr($markup, 0, strrpos($markup, '</fake>'));
  721. if ($fragment->isXHTML) {
  722. $markup = substr($markup, strpos($markup, '<fake') + 43);
  723. } else {
  724. $markup = substr($markup, strpos($markup, '<fake>') + 6);
  725. }
  726. } else {
  727. $markup = substr($markup, strpos($markup, '<body>') + 6);
  728. $markup = substr($markup, 0, strrpos($markup, '</body>'));
  729. }
  730. $fragment->isDocumentFragment = $tmp;
  731. if (phpQuery::$debug)
  732. phpQuery::debug('documentFragmentToMarkup: ' . substr($markup, 0, 150));
  733. return $markup;
  734. }
  735. /**
  736. * Return document markup, starting with optional $nodes as root.
  737. *
  738. * @param $nodes DOMNode|DOMNodeList
  739. * @return string
  740. */
  741. public function markup($nodes = null, $innerMarkup = false)
  742. {
  743. if (isset($nodes) && count($nodes) == 1 && $nodes[0] instanceof DOMDOCUMENT)
  744. $nodes = null;
  745. if (isset($nodes)) {
  746. $markup = '';
  747. if (!is_array($nodes) && !($nodes instanceof DOMNODELIST))
  748. $nodes = array($nodes);
  749. if ($this->isDocumentFragment && !$innerMarkup)
  750. foreach ($nodes as $i => $node)
  751. if ($node->isSameNode($this->root)) {
  752. // var_dump($node);
  753. $nodes = array_slice($nodes, 0, $i)
  754. + phpQuery::DOMNodeListToArray($node->childNodes)
  755. + array_slice($nodes, $i + 1);
  756. }
  757. if ($this->isXML && !$innerMarkup) {
  758. self::debug("Getting outerXML with charset '{$this->charset}'");
  759. // we need outerXML, so we can benefit from
  760. // $node param support in saveXML()
  761. foreach ($nodes as $node)
  762. $markup .= $this->document->saveXML($node);
  763. } else {
  764. $loop = array();
  765. if ($innerMarkup)
  766. foreach ($nodes as $node) {
  767. if ($node->childNodes)
  768. foreach ($node->childNodes as $child)
  769. $loop[] = $child;
  770. else
  771. $loop[] = $node;
  772. }
  773. else
  774. $loop = $nodes;
  775. self::debug("Getting markup, moving selected nodes (" . count($loop) . ") to new DocumentFragment");
  776. $fake = $this->documentFragmentCreate($loop);
  777. $markup = $this->documentFragmentToMarkup($fake);
  778. }
  779. if ($this->isXHTML) {
  780. self::debug("Fixing XHTML");
  781. $markup = self::markupFixXHTML($markup);
  782. }
  783. self::debug("Markup: " . substr($markup, 0, 250));
  784. return $markup;
  785. } else {
  786. if ($this->isDocumentFragment) {
  787. // documentFragment, html only...
  788. self::debug("Getting markup, DocumentFragment detected");
  789. // return $this->markup(
  790. //// $this->document->getElementsByTagName('body')->item(0)
  791. // $this->document->root, true
  792. // );
  793. $markup = $this->documentFragmentToMarkup($this);
  794. // no need for markupFixXHTML, as it's done thought markup($nodes) method
  795. return $markup;
  796. } else {
  797. self::debug("Getting markup (" . ($this->isXML ? 'XML' : 'HTML') . "), final with charset '{$this->charset}'");
  798. $markup = $this->isXML
  799. ? $this->document->saveXML()
  800. : $this->document->saveHTML();
  801. if ($this->isXHTML) {
  802. self::debug("Fixing XHTML");
  803. $markup = self::markupFixXHTML($markup);
  804. }
  805. self::debug("Markup: " . substr($markup, 0, 250));
  806. return $markup;
  807. }
  808. }
  809. }
  810. protected static function markupFixXHTML($markup)
  811. {
  812. $markup = self::expandEmptyTag('script', $markup);
  813. $markup = self::expandEmptyTag('select', $markup);
  814. $markup = self::expandEmptyTag('textarea', $markup);
  815. return $markup;
  816. }
  817. public static function debug($text)
  818. {
  819. phpQuery::debug($text);
  820. }
  821. /**
  822. * expandEmptyTag
  823. *
  824. * @param $tag
  825. * @param $xml
  826. * @return unknown_type
  827. * @author mjaque at ilkebenson dot com
  828. * @link http://php.net/manual/en/domdocument.savehtml.php#81256
  829. */
  830. public static function expandEmptyTag($tag, $xml)
  831. {
  832. $indice = 0;
  833. while ($indice < strlen($xml)) {
  834. $pos = strpos($xml, "<$tag ", $indice);
  835. if ($pos) {
  836. $posCierre = strpos($xml, ">", $pos);
  837. if ($xml[$posCierre - 1] == "/") {
  838. $xml = substr_replace($xml, "></$tag>", $posCierre - 1, 2);
  839. }
  840. $indice = $posCierre;
  841. } else break;
  842. }
  843. return $xml;
  844. }
  845. }
  846. /**
  847. * Event handling class.
  848. *
  849. * @author Tobiasz Cudnik
  850. * @package phpQuery
  851. * @static
  852. */
  853. abstract class phpQueryEvents
  854. {
  855. /**
  856. * Trigger a type of event on every matched element.
  857. *
  858. * @param DOMNode|phpQueryObject|string $document
  859. * @param unknown_type $type
  860. * @param unknown_type $data
  861. *
  862. * @TODO exclusive events (with !)
  863. * @TODO global events (test)
  864. * @TODO support more than event in $type (space-separated)
  865. */
  866. public static function trigger($document, $type, $data = array(), $node = null)
  867. {
  868. // trigger: function(type, data, elem, donative, extra) {
  869. $documentID = phpQuery::getDocumentID($document);
  870. $namespace = null;
  871. if (strpos($type, '.') !== false)
  872. list($name, $namespace) = explode('.', $type);
  873. else
  874. $name = $type;
  875. if (!$node) {
  876. if (self::issetGlobal($documentID, $type)) {
  877. $pq = phpQuery::getDocument($documentID);
  878. // TODO check add($pq->document)
  879. $pq->find('*')->add($pq->document)
  880. ->trigger($type, $data);
  881. }
  882. } else {
  883. if (isset($data[0]) && $data[0] instanceof DOMEvent) {
  884. $event = $data[0];
  885. $event->relatedTarget = $event->target;
  886. $event->target = $node;
  887. $data = array_slice($data, 1);
  888. } else {
  889. $event = new DOMEvent(array(
  890. 'type' => $type,
  891. 'target' => $node,
  892. 'timeStamp' => time(),
  893. ));
  894. }
  895. $i = 0;
  896. while ($node) {
  897. // TODO whois
  898. phpQuery::debug("Triggering " . ($i ? "bubbled " : '') . "event '{$type}' on "
  899. . "node \n"); //.phpQueryObject::whois($node)."\n");
  900. $event->currentTarget = $node;
  901. $eventNode = self::getNode($documentID, $node);
  902. if (isset($eventNode->eventHandlers)) {
  903. foreach ($eventNode->eventHandlers as $eventType => $handlers) {
  904. $eventNamespace = null;
  905. if (strpos($type, '.') !== false)
  906. list($eventName, $eventNamespace) = explode('.', $eventType);
  907. else
  908. $eventName = $eventType;
  909. if ($name != $eventName)
  910. continue;
  911. if ($namespace && $eventNamespace && $namespace != $eventNamespace)
  912. continue;
  913. foreach ($handlers as $handler) {
  914. phpQuery::debug("Calling event handler\n");
  915. $event->data = $handler['data']
  916. ? $handler['data']
  917. : null;
  918. $params = array_merge(array($event), $data);
  919. $return = phpQuery::callbackRun($handler['callback'], $params);
  920. if ($return === false) {
  921. $event->bubbles = false;
  922. }
  923. }
  924. }
  925. }
  926. // to bubble or not to bubble...
  927. if (!$event->bubbles)
  928. break;
  929. $node = $node->parentNode;
  930. $i++;
  931. }
  932. }
  933. }
  934. /**
  935. * Binds a handler to one or more events (like click) for each matched element.
  936. * Can also bind custom events.
  937. *
  938. * @param DOMNode|phpQueryObject|string $document
  939. * @param unknown_type $type
  940. * @param unknown_type $data Optional
  941. * @param unknown_type $callback
  942. *
  943. * @TODO support '!' (exclusive) events
  944. * @TODO support more than event in $type (space-separated)
  945. * @TODO support binding to global events
  946. */
  947. public static function add($document, $node, $type, $data, $callback = null)
  948. {
  949. phpQuery::debug("Binding '$type' event");
  950. $documentID = phpQuery::getDocumentID($document);
  951. // if (is_null($callback) && is_callable($data)) {
  952. // $callback = $data;
  953. // $data = null;
  954. // }
  955. $eventNode = self::getNode($documentID, $node);
  956. if (!$eventNode)
  957. $eventNode = self::setNode($documentID, $node);
  958. if (!isset($eventNode->eventHandlers[$type]))
  959. $eventNode->eventHandlers[$type] = array();
  960. $eventNode->eventHandlers[$type][] = array(
  961. 'callback' => $callback,
  962. 'data' => $data,
  963. );
  964. }
  965. /**
  966. * Enter description here...
  967. *
  968. * @param DOMNode|phpQueryObject|string $document
  969. * @param unknown_type $type
  970. * @param unknown_type $callback
  971. *
  972. * @TODO namespace events
  973. * @TODO support more than event in $type (space-separated)
  974. */
  975. public static function remove($document, $node, $type = null, $callback = null)
  976. {
  977. $documentID = phpQuery::getDocumentID($document);
  978. $eventNode = self::getNode($documentID, $node);
  979. if (is_object($eventNode) && isset($eventNode->eventHandlers[$type])) {
  980. if ($callback) {
  981. foreach ($eventNode->eventHandlers[$type] as $k => $handler)
  982. if ($handler['callback'] == $callback)
  983. unset($eventNode->eventHandlers[$type][$k]);
  984. } else {
  985. unset($eventNode->eventHandlers[$type]);
  986. }
  987. }
  988. }
  989. protected static function getNode($documentID, $node)
  990. {
  991. foreach (phpQuery::$documents[$documentID]->eventsNodes as $eventNode) {
  992. if ($node->isSameNode($eventNode))
  993. return $eventNode;
  994. }
  995. }
  996. protected static function setNode($documentID, $node)
  997. {
  998. phpQuery::$documents[$documentID]->eventsNodes[] = $node;
  999. return phpQuery::$documents[$documentID]->eventsNodes[count(phpQuery::$documents[$documentID]->eventsNodes) - 1];
  1000. }
  1001. protected static function issetGlobal($documentID, $type)
  1002. {
  1003. return isset(phpQuery::$documents[$documentID])
  1004. ? in_array($type, phpQuery::$documents[$documentID]->eventsGlobal)
  1005. : false;
  1006. }
  1007. }
  1008. interface ICallbackNamed
  1009. {
  1010. function hasName();
  1011. function getName();
  1012. }
  1013. /**
  1014. * Callback class introduces currying-like pattern.
  1015. *
  1016. * Example:
  1017. * function foo($param1, $param2, $param3) {
  1018. * var_dump($param1, $param2, $param3);
  1019. * }
  1020. * $fooCurried = new Callback('foo',
  1021. * 'param1 is now statically set',
  1022. * new CallbackParam, new CallbackParam
  1023. * );
  1024. * phpQuery::callbackRun($fooCurried,
  1025. * array('param2 value', 'param3 value'
  1026. * );
  1027. *
  1028. * Callback class is supported in all phpQuery methods which accepts callbacks.
  1029. *
  1030. * @link http://code.google.com/p/phpquery/wiki/Callbacks#Param_Structures
  1031. * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
  1032. *
  1033. * @TODO??? return fake forwarding function created via create_function
  1034. * @TODO honor paramStructure
  1035. */
  1036. class Callback
  1037. implements ICallbackNamed
  1038. {
  1039. public $callback = null;
  1040. public $params = null;
  1041. protected $name;
  1042. public function __construct(
  1043. $callback,
  1044. $param1 = null,
  1045. $param2 = null,
  1046. $param3 = null
  1047. ) {
  1048. $params = func_get_args();
  1049. $params = array_slice($params, 1);
  1050. if ($callback instanceof Callback) {
  1051. // TODO implement recurention
  1052. } else {
  1053. $this->callback = $callback;
  1054. $this->params = $params;
  1055. }
  1056. }
  1057. public function getName()
  1058. {
  1059. return 'Callback: ' . $this->name;
  1060. }
  1061. public function hasName()
  1062. {
  1063. return isset($this->name) && $this->name;
  1064. }
  1065. public function setName($name)
  1066. {
  1067. $this->name = $name;
  1068. return $this;
  1069. }
  1070. // TODO test me
  1071. // public function addParams() {
  1072. // $params = func_get_args();
  1073. // return new Callback($this->callback, $this->params+$params);
  1074. // }
  1075. }
  1076. /**
  1077. * Shorthand for new Callback(create_function(...), ...);
  1078. *
  1079. * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
  1080. */
  1081. class CallbackBody extends Callback
  1082. {
  1083. public function __construct(
  1084. $paramList,
  1085. $code,
  1086. $param1 = null,
  1087. $param2 = null,
  1088. $param3 = null
  1089. ) {
  1090. $params = func_get_args();
  1091. $params = array_slice($params, 2);
  1092. $this->callback = create_function($paramList, $code);
  1093. $this->params = $params;
  1094. }
  1095. }
  1096. /**
  1097. * Callback type which on execution returns reference passed during creation.
  1098. *
  1099. * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
  1100. */
  1101. class CallbackReturnReference extends Callback
  1102. implements ICallbackNamed
  1103. {
  1104. protected $reference;
  1105. public function __construct(&$reference, $name = null)
  1106. {
  1107. $this->reference = &$reference;
  1108. $this->callback = array($this, 'callback');
  1109. }
  1110. public function callback()
  1111. {
  1112. return $this->reference;
  1113. }
  1114. public function getName()
  1115. {
  1116. return 'Callback: ' . $this->name;
  1117. }
  1118. public function hasName()
  1119. {
  1120. return isset($this->name) && $this->name;
  1121. }
  1122. }
  1123. /**
  1124. * Callback type which on execution returns value passed during creation.
  1125. *
  1126. * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
  1127. */
  1128. class CallbackReturnValue extends Callback
  1129. implements ICallbackNamed
  1130. {
  1131. protected $value;
  1132. protected $name;
  1133. public function __construct($value, $name = null)
  1134. {
  1135. $this->value = &$value;
  1136. $this->name = $name;
  1137. $this->callback = array($this, 'callback');
  1138. }
  1139. public function callback()
  1140. {
  1141. return $this->value;
  1142. }
  1143. public function __toString()
  1144. {
  1145. return $this->getName();
  1146. }
  1147. public function getName()
  1148. {
  1149. return 'Callback: ' . $this->name;
  1150. }
  1151. public function hasName()
  1152. {
  1153. return isset($this->name) && $this->name;
  1154. }
  1155. }
  1156. /**
  1157. * CallbackParameterToReference can be used when we don't really want a callback,
  1158. * only parameter passed to it. CallbackParameterToReference takes first
  1159. * parameter's value and passes it to reference.
  1160. *
  1161. * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
  1162. */
  1163. class CallbackParameterToReference extends Callback
  1164. {
  1165. /**
  1166. * @param $reference
  1167. * @TODO implement $paramIndex;
  1168. * param index choose which callback param will be passed to reference
  1169. */
  1170. public function __construct(&$reference)
  1171. {
  1172. $this->callback = &$reference;
  1173. }
  1174. }
  1175. //class CallbackReference extends Callback {
  1176. // /**
  1177. // *
  1178. // * @param $reference
  1179. // * @param $paramIndex
  1180. // * @todo implement $paramIndex; param index choose which callback param will be passed to reference
  1181. // */
  1182. // public function __construct(&$reference, $name = null){
  1183. // $this->callback =& $reference;
  1184. // }
  1185. //}
  1186. class CallbackParam
  1187. {
  1188. }
  1189. /**
  1190. * Class representing phpQuery objects.
  1191. *
  1192. * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
  1193. * @package phpQuery
  1194. * @method phpQueryObject clone() clone()
  1195. * @method phpQueryObject empty() empty()
  1196. * @method phpQueryObject next() next($selector = null)
  1197. * @method phpQueryObject prev() prev($selector = null)
  1198. * @property Int $length
  1199. */
  1200. class phpQueryObject
  1201. implements Iterator, Countable, ArrayAccess
  1202. {
  1203. public $documentID = null;
  1204. /**
  1205. * DOMDocument class.
  1206. *
  1207. * @var DOMDocument
  1208. */
  1209. public $document = null;
  1210. public $charset = null;
  1211. /**
  1212. *
  1213. * @var DOMDocumentWrapper
  1214. */
  1215. public $documentWrapper = null;
  1216. /**
  1217. * XPath interface.
  1218. *
  1219. * @var DOMXPath
  1220. */
  1221. public $xpath = null;
  1222. /**
  1223. * Stack of selected elements.
  1224. * @TODO refactor to ->nodes
  1225. * @var array
  1226. */
  1227. public $elements = array();
  1228. /**
  1229. * @access private
  1230. */
  1231. protected $elementsBackup = array();
  1232. /**
  1233. * @access private
  1234. */
  1235. protected $previous = null;
  1236. /**
  1237. * @access private
  1238. * @TODO deprecate
  1239. */
  1240. protected $root = array();
  1241. /**
  1242. * Indicated if doument is just a fragment (no <html> tag).
  1243. *
  1244. * Every document is realy a full document, so even documentFragments can
  1245. * be queried against <html>, but getDocument(id)->htmlOuter() will return
  1246. * only contents of <body>.
  1247. *
  1248. * @var bool
  1249. */
  1250. public $documentFragment = true;
  1251. /**
  1252. * Iterator interface helper
  1253. * @access private
  1254. */
  1255. protected $elementsInterator = array();
  1256. /**
  1257. * Iterator interface helper
  1258. * @access private
  1259. */
  1260. protected $valid = false;
  1261. /**
  1262. * Iterator interface helper
  1263. * @access private
  1264. */
  1265. protected $current = null;
  1266. /**
  1267. * Enter description here...
  1268. *
  1269. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  1270. */
  1271. public function __construct($documentID)
  1272. {
  1273. // if ($documentID instanceof self)
  1274. // var_dump($documentID->getDocumentID());
  1275. $id = $documentID instanceof self
  1276. ? $documentID->getDocumentID()
  1277. : $documentID;
  1278. // var_dump($id);
  1279. if (!isset(phpQuery::$documents[$id])) {
  1280. // var_dump(phpQuery::$documents);
  1281. throw new Exception("Document with ID '{$id}' isn't loaded. Use phpQuery::newDocument(\$html) or phpQuery::newDocumentFile(\$file) first.");
  1282. }
  1283. $this->documentID = $id;
  1284. $this->documentWrapper = &phpQuery::$documents[$id];
  1285. $this->document = &$this->documentWrapper->document;
  1286. $this->xpath = &$this->documentWrapper->xpath;
  1287. $this->charset = &$this->documentWrapper->charset;
  1288. $this->documentFragment = &$this->documentWrapper->isDocumentFragment;
  1289. // TODO check $this->DOM->documentElement;
  1290. // $this->root = $this->document->documentElement;
  1291. $this->root = &$this->documentWrapper->root;
  1292. // $this->toRoot();
  1293. $this->elements = array($this->root);
  1294. }
  1295. /**
  1296. *
  1297. * @access private
  1298. * @param $attr
  1299. * @return unknown_type
  1300. */
  1301. public function __get($attr)
  1302. {
  1303. switch ($attr) {
  1304. // FIXME doesnt work at all ?
  1305. case 'length':
  1306. return $this->size();
  1307. break;
  1308. default:
  1309. return $this->$attr;
  1310. }
  1311. }
  1312. /**
  1313. * Saves actual object to $var by reference.
  1314. * Useful when need to break chain.
  1315. * @param phpQueryObject $var
  1316. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  1317. */
  1318. public function toReference(&$var)
  1319. {
  1320. return $var = $this;
  1321. }
  1322. public function documentFragment($state = null)
  1323. {
  1324. if ($state) {
  1325. phpQuery::$documents[$this->getDocumentID()]['documentFragment'] = $state;
  1326. return $this;
  1327. }
  1328. return $this->documentFragment;
  1329. }
  1330. /**
  1331. * @access private
  1332. * @TODO documentWrapper
  1333. */
  1334. protected function isRoot($node)
  1335. {
  1336. // return $node instanceof DOMDOCUMENT || $node->tagName == 'html';
  1337. return $node instanceof DOMDOCUMENT
  1338. || ($node instanceof DOMELEMENT && $node->tagName == 'html')
  1339. || $this->root->isSameNode($node);
  1340. }
  1341. /**
  1342. * @access private
  1343. */
  1344. protected function stackIsRoot()
  1345. {
  1346. return $this->size() == 1 && $this->isRoot($this->elements[0]);
  1347. }
  1348. /**
  1349. * Enter description here...
  1350. * NON JQUERY METHOD
  1351. *
  1352. * Watch out, it doesn't creates new instance, can be reverted with end().
  1353. *
  1354. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  1355. */
  1356. public function toRoot()
  1357. {
  1358. $this->elements = array($this->root);
  1359. return $this;
  1360. // return $this->newInstance(array($this->root));
  1361. }
  1362. /**
  1363. * Saves object's DocumentID to $var by reference.
  1364. * <code>
  1365. * $myDocumentId;
  1366. * phpQuery::newDocument('<div/>')
  1367. * ->getDocumentIDRef($myDocumentId)
  1368. * ->find('div')->...
  1369. * </code>
  1370. *
  1371. * @param unknown_type $domId
  1372. * @see phpQuery::newDocument
  1373. * @see phpQuery::newDocumentFile
  1374. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  1375. */
  1376. public function getDocumentIDRef(&$documentID)
  1377. {
  1378. $documentID = $this->getDocumentID();
  1379. return $this;
  1380. }
  1381. /**
  1382. * Returns object with stack set to document root.
  1383. *
  1384. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  1385. */
  1386. public function getDocument()
  1387. {
  1388. return phpQuery::getDocument($this->getDocumentID());
  1389. }
  1390. /**
  1391. *
  1392. * @return DOMDocument
  1393. */
  1394. public function getDOMDocument()
  1395. {
  1396. return $this->document;
  1397. }
  1398. /**
  1399. * Get object's Document ID.
  1400. *
  1401. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  1402. */
  1403. public function getDocumentID()
  1404. {
  1405. return $this->documentID;
  1406. }
  1407. /**
  1408. * Unloads whole document from memory.
  1409. * CAUTION! None further operations will be possible on this document.
  1410. * All objects refering to it will be useless.
  1411. *
  1412. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  1413. */
  1414. public function unloadDocument()
  1415. {
  1416. phpQuery::unloadDocuments($this->getDocumentID());
  1417. }
  1418. public function isHTML()
  1419. {
  1420. return $this->documentWrapper->isHTML;
  1421. }
  1422. public function isXHTML()
  1423. {
  1424. return $this->documentWrapper->isXHTML;
  1425. }
  1426. public function isXML()
  1427. {
  1428. return $this->documentWrapper->isXML;
  1429. }
  1430. /**
  1431. * Enter description here...
  1432. *
  1433. * @link http://docs.jquery.com/Ajax/serialize
  1434. * @return string
  1435. */
  1436. public function serialize()
  1437. {
  1438. return phpQuery::param($this->serializeArray());
  1439. }
  1440. /**
  1441. * Enter description here...
  1442. *
  1443. * @link http://docs.jquery.com/Ajax/serializeArray
  1444. * @return array
  1445. */
  1446. public function serializeArray($submit = null)
  1447. {
  1448. $source = $this->filter('form, input, select, textarea')
  1449. ->find('input, select, textarea')
  1450. ->andSelf()
  1451. ->not('form');
  1452. $return = array();
  1453. // $source->dumpDie();
  1454. foreach ($source as $input) {
  1455. $input = phpQuery::pq($input);
  1456. if ($input->is('[disabled]'))
  1457. continue;
  1458. if (!$input->is('[name]'))
  1459. continue;
  1460. if ($input->is('[type=checkbox]') && !$input->is('[checked]'))
  1461. continue;
  1462. // jquery diff
  1463. if ($submit && $input->is('[type=submit]')) {
  1464. if ($submit instanceof DOMELEMENT && !$input->elements[0]->isSameNode($submit))
  1465. continue;
  1466. else if (is_string($submit) && $input->attr('name') != $submit)
  1467. continue;
  1468. }
  1469. $return[] = array(
  1470. 'name' => $input->attr('name'),
  1471. 'value' => $input->val(),
  1472. );
  1473. }
  1474. return $return;
  1475. }
  1476. /**
  1477. * @access private
  1478. */
  1479. protected function debug($in)
  1480. {
  1481. if (!phpQuery::$debug)
  1482. return;
  1483. print('<pre>');
  1484. print_r($in);
  1485. // file debug
  1486. // file_put_contents(dirname(__FILE__).'/phpQuery.log', print_r($in, true)."\n", FILE_APPEND);
  1487. // quite handy debug trace
  1488. // if ( is_array($in))
  1489. // print_r(array_slice(debug_backtrace(), 3));
  1490. print("</pre>\n");
  1491. }
  1492. /**
  1493. * @access private
  1494. */
  1495. protected function isRegexp($pattern)
  1496. {
  1497. return in_array(
  1498. $pattern[mb_strlen($pattern) - 1],
  1499. array('^', '*', '$')
  1500. );
  1501. }
  1502. /**
  1503. * Determines if $char is really a char.
  1504. *
  1505. * @param string $char
  1506. * @return bool
  1507. * @todo rewrite me to charcode range ! ;)
  1508. * @access private
  1509. */
  1510. protected function isChar($char)
  1511. {
  1512. return extension_loaded('mbstring') && phpQuery::$mbstringSupport
  1513. ? mb_eregi('\w', $char)
  1514. : preg_match('@\w@', $char);
  1515. }
  1516. /**
  1517. * @access private
  1518. */
  1519. protected function parseSelector($query)
  1520. {
  1521. // clean spaces
  1522. // TODO include this inside parsing ?
  1523. $query = trim(
  1524. preg_replace(
  1525. '@\s+@',
  1526. ' ',
  1527. preg_replace('@\s*(>|\\+|~)\s*@', '\\1', $query)
  1528. )
  1529. );
  1530. $queries = array(array());
  1531. if (!$query)
  1532. return $queries;
  1533. $return = &$queries[0];
  1534. $specialChars = array('>', ' ');
  1535. // $specialCharsMapping = array('/' => '>');
  1536. $specialCharsMapping = array();
  1537. $strlen = mb_strlen($query);
  1538. $classChars = array('.', '-');
  1539. $pseudoChars = array('-');
  1540. $tagChars = array('*', '|', '-');
  1541. // split multibyte string
  1542. // http://code.google.com/p/phpquery/issues/detail?id=76
  1543. $_query = array();
  1544. for ($i = 0; $i < $strlen; $i++)
  1545. $_query[] = mb_substr($query, $i, 1);
  1546. $query = $_query;
  1547. // it works, but i dont like it...
  1548. $i = 0;
  1549. while ($i < $strlen) {
  1550. $c = $query[$i];
  1551. $tmp = '';
  1552. // TAG
  1553. if ($this->isChar($c) || in_array($c, $tagChars)) {
  1554. while (
  1555. isset($query[$i])
  1556. && ($this->isChar($query[$i]) || in_array($query[$i], $tagChars))
  1557. ) {
  1558. $tmp .= $query[$i];
  1559. $i++;
  1560. }
  1561. $return[] = $tmp;
  1562. // IDs
  1563. } else if ($c == '#') {
  1564. $i++;
  1565. while (isset($query[$i]) && ($this->isChar($query[$i]) || $query[$i] == '-')) {
  1566. $tmp .= $query[$i];
  1567. $i++;
  1568. }
  1569. $return[] = '#' . $tmp;
  1570. // SPECIAL CHARS
  1571. } else if (in_array($c, $specialChars)) {
  1572. $return[] = $c;
  1573. $i++;
  1574. // MAPPED SPECIAL MULTICHARS
  1575. // } else if ( $c.$query[$i+1] == '//') {
  1576. // $return[] = ' ';
  1577. // $i = $i+2;
  1578. // MAPPED SPECIAL CHARS
  1579. } else if (isset($specialCharsMapping[$c])) {
  1580. $return[] = $specialCharsMapping[$c];
  1581. $i++;
  1582. // COMMA
  1583. } else if ($c == ',') {
  1584. $queries[] = array();
  1585. $return = &$queries[count($queries) - 1];
  1586. $i++;
  1587. while (isset($query[$i]) && $query[$i] == ' ')
  1588. $i++;
  1589. // CLASSES
  1590. } else if ($c == '.') {
  1591. while (isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $classChars))) {
  1592. $tmp .= $query[$i];
  1593. $i++;
  1594. }
  1595. $return[] = $tmp;
  1596. // ~ General Sibling Selector
  1597. } else if ($c == '~') {
  1598. $spaceAllowed = true;
  1599. $tmp .= $query[$i++];
  1600. while (
  1601. isset($query[$i])
  1602. && ($this->isChar($query[$i])
  1603. || in_array($query[$i], $classChars)
  1604. || $query[$i] == '*'
  1605. || ($query[$i] == ' ' && $spaceAllowed)
  1606. )
  1607. ) {
  1608. if ($query[$i] != ' ')
  1609. $spaceAllowed = false;
  1610. $tmp .= $query[$i];
  1611. $i++;
  1612. }
  1613. $return[] = $tmp;
  1614. // + Adjacent sibling selectors
  1615. } else if ($c == '+') {
  1616. $spaceAllowed = true;
  1617. $tmp .= $query[$i++];
  1618. while (
  1619. isset($query[$i])
  1620. && ($this->isChar($query[$i])
  1621. || in_array($query[$i], $classChars)
  1622. || $query[$i] == '*'
  1623. || ($spaceAllowed && $query[$i] == ' ')
  1624. )
  1625. ) {
  1626. if ($query[$i] != ' ')
  1627. $spaceAllowed = false;
  1628. $tmp .= $query[$i];
  1629. $i++;
  1630. }
  1631. $return[] = $tmp;
  1632. // ATTRS
  1633. } else if ($c == '[') {
  1634. $stack = 1;
  1635. $tmp .= $c;
  1636. while (isset($query[++$i])) {
  1637. $tmp .= $query[$i];
  1638. if ($query[$i] == '[') {
  1639. $stack++;
  1640. } else if ($query[$i] == ']') {
  1641. $stack--;
  1642. if (!$stack)
  1643. break;
  1644. }
  1645. }
  1646. $return[] = $tmp;
  1647. $i++;
  1648. // PSEUDO CLASSES
  1649. } else if ($c == ':') {
  1650. $stack = 1;
  1651. $tmp .= $query[$i++];
  1652. while (isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $pseudoChars))) {
  1653. $tmp .= $query[$i];
  1654. $i++;
  1655. }
  1656. // with arguments ?
  1657. if (isset($query[$i]) && $query[$i] == '(') {
  1658. $tmp .= $query[$i];
  1659. $stack = 1;
  1660. while (isset($query[++$i])) {
  1661. $tmp .= $query[$i];
  1662. if ($query[$i] == '(') {
  1663. $stack++;
  1664. } else if ($query[$i] == ')') {
  1665. $stack--;
  1666. if (!$stack)
  1667. break;
  1668. }
  1669. }
  1670. $return[] = $tmp;
  1671. $i++;
  1672. } else {
  1673. $return[] = $tmp;
  1674. }
  1675. } else {
  1676. $i++;
  1677. }
  1678. }
  1679. foreach ($queries as $k => $q) {
  1680. if (isset($q[0])) {
  1681. if (isset($q[0][0]) && $q[0][0] == ':')
  1682. array_unshift($queries[$k], '*');
  1683. if ($q[0] != '>')
  1684. array_unshift($queries[$k], ' ');
  1685. }
  1686. }
  1687. return $queries;
  1688. }
  1689. /**
  1690. * Return matched DOM nodes.
  1691. *
  1692. * @param int $index
  1693. * @return array|DOMElement Single DOMElement or array of DOMElement.
  1694. */
  1695. public function get($index = null, $callback1 = null, $callback2 = null, $callback3 = null)
  1696. {
  1697. $return = isset($index)
  1698. ? (isset($this->elements[$index]) ? $this->elements[$index] : null)
  1699. : $this->elements;
  1700. // pass thou callbacks
  1701. $args = func_get_args();
  1702. $args = array_slice($args, 1);
  1703. foreach ($args as $callback) {
  1704. if (is_array($return))
  1705. foreach ($return as $k => $v)
  1706. $return[$k] = phpQuery::callbackRun($callback, array($v));
  1707. else
  1708. $return = phpQuery::callbackRun($callback, array($return));
  1709. }
  1710. return $return;
  1711. }
  1712. /**
  1713. * Return matched DOM nodes.
  1714. * jQuery difference.
  1715. *
  1716. * @param int $index
  1717. * @return array|string Returns string if $index != null
  1718. * @todo implement callbacks
  1719. * @todo return only arrays ?
  1720. * @todo maybe other name...
  1721. */
  1722. public function getString($index = null, $callback1 = null, $callback2 = null, $callback3 = null)
  1723. {
  1724. if (!is_null($index) && is_int($index))
  1725. $return = $this->eq($index)->text();
  1726. else {
  1727. $return = array();
  1728. for ($i = 0; $i < $this->size(); $i++) {
  1729. $return[] = $this->eq($i)->text();
  1730. }
  1731. }
  1732. // pass thou callbacks
  1733. $args = func_get_args();
  1734. $args = array_slice($args, 1);
  1735. foreach ($args as $callback) {
  1736. $return = phpQuery::callbackRun($callback, array($return));
  1737. }
  1738. return $return;
  1739. }
  1740. /**
  1741. * Return matched DOM nodes.
  1742. * jQuery difference.
  1743. *
  1744. * @param int $index
  1745. * @return array|string Returns string if $index != null
  1746. * @todo implement callbacks
  1747. * @todo return only arrays ?
  1748. * @todo maybe other name...
  1749. */
  1750. public function getStrings($index = null, $callback1 = null, $callback2 = null, $callback3 = null)
  1751. {
  1752. if (!is_null($index) && is_int($index))
  1753. $return = $this->eq($index)->text();
  1754. else {
  1755. $return = array();
  1756. for ($i = 0; $i < $this->size(); $i++) {
  1757. $return[] = $this->eq($i)->text();
  1758. }
  1759. // pass thou callbacks
  1760. $args = func_get_args();
  1761. $args = array_slice($args, 1);
  1762. }
  1763. foreach ($args as $callback) {
  1764. if (is_array($return))
  1765. foreach ($return as $k => $v)
  1766. $return[$k] = phpQuery::callbackRun($callback, array($v));
  1767. else
  1768. $return = phpQuery::callbackRun($callback, array($return));
  1769. }
  1770. return $return;
  1771. }
  1772. /**
  1773. * Returns new instance of actual class.
  1774. *
  1775. * @param array $newStack Optional. Will replace old stack with new and move old one to history.c
  1776. */
  1777. public function newInstance($newStack = null)
  1778. {
  1779. $class = get_class($this);
  1780. // support inheritance by passing old object to overloaded constructor
  1781. $new = $class != 'phpQuery'
  1782. ? new $class($this, $this->getDocumentID())
  1783. : new phpQueryObject($this->getDocumentID());
  1784. $new->previous = $this;
  1785. if (is_null($newStack)) {
  1786. $new->elements = $this->elements;
  1787. if ($this->elementsBackup)
  1788. $this->elements = $this->elementsBackup;
  1789. } else if (is_string($newStack)) {
  1790. $new->elements = phpQuery::pq($newStack, $this->getDocumentID())->stack();
  1791. } else {
  1792. $new->elements = $newStack;
  1793. }
  1794. return $new;
  1795. }
  1796. /**
  1797. * Enter description here...
  1798. *
  1799. * In the future, when PHP will support XLS 2.0, then we would do that this way:
  1800. * contains(tokenize(@class, '\s'), "something")
  1801. * @param unknown_type $class
  1802. * @param unknown_type $node
  1803. * @return boolean
  1804. * @access private
  1805. */
  1806. protected function matchClasses($class, $node)
  1807. {
  1808. // multi-class
  1809. if (mb_strpos($class, '.', 1)) {
  1810. $classes = explode('.', substr($class, 1));
  1811. $classesCount = count($classes);
  1812. $nodeClasses = explode(' ', $node->getAttribute('class'));
  1813. $nodeClassesCount = count($nodeClasses);
  1814. if ($classesCount > $nodeClassesCount)
  1815. return false;
  1816. $diff = count(
  1817. array_diff(
  1818. $classes,
  1819. $nodeClasses
  1820. )
  1821. );
  1822. if (!$diff)
  1823. return true;
  1824. // single-class
  1825. } else {
  1826. return in_array(
  1827. // strip leading dot from class name
  1828. substr($class, 1),
  1829. // get classes for element as array
  1830. explode(' ', $node->getAttribute('class'))
  1831. );
  1832. }
  1833. }
  1834. /**
  1835. * @access private
  1836. */
  1837. protected function runQuery($XQuery, $selector = null, $compare = null)
  1838. {
  1839. if ($compare && !method_exists($this, $compare))
  1840. return false;
  1841. $stack = array();
  1842. if (!$this->elements)
  1843. $this->debug('Stack empty, skipping...');
  1844. // var_dump($this->elements[0]->nodeType);
  1845. // element, document
  1846. foreach ($this->stack(array(1, 9, 13)) as $k => $stackNode) {
  1847. $detachAfter = false;
  1848. // to work on detached nodes we need temporary place them somewhere
  1849. // thats because context xpath queries sucks ;]
  1850. $testNode = $stackNode;
  1851. while ($testNode) {
  1852. if (!$testNode->parentNode && !$this->isRoot($testNode)) {
  1853. $this->root->appendChild($testNode);
  1854. $detachAfter = $testNode;
  1855. break;
  1856. }
  1857. $testNode = isset($testNode->parentNode)
  1858. ? $testNode->parentNode
  1859. : null;
  1860. }
  1861. // XXX tmp ?
  1862. $xpath = $this->documentWrapper->isXHTML
  1863. ? $this->getNodeXpath($stackNode, 'html')
  1864. : $this->getNodeXpath($stackNode);
  1865. // FIXME pseudoclasses-only query, support XML
  1866. $query = $XQuery == '//' && $xpath == '/html[1]'
  1867. ? '//*'
  1868. : $xpath . $XQuery;
  1869. $this->debug("XPATH: {$query}");
  1870. // run query, get elements
  1871. $nodes = $this->xpath->query($query);
  1872. $this->debug("QUERY FETCHED");
  1873. if (!$nodes->length)
  1874. $this->debug('Nothing found');
  1875. $debug = array();
  1876. foreach ($nodes as $node) {
  1877. $matched = false;
  1878. if ($compare) {
  1879. phpQuery::$debug ?
  1880. $this->debug("Found: " . $this->whois($node) . ", comparing with {$compare}()")
  1881. : null;
  1882. $phpQueryDebug = phpQuery::$debug;
  1883. phpQuery::$debug = false;
  1884. // TODO ??? use phpQuery::callbackRun()
  1885. if (call_user_func_array(array($this, $compare), array($selector, $node)))
  1886. $matched = true;
  1887. phpQuery::$debug = $phpQueryDebug;
  1888. } else {
  1889. $matched = true;
  1890. }
  1891. if ($matched) {
  1892. if (phpQuery::$debug)
  1893. $debug[] = $this->whois($node);
  1894. $stack[] = $node;
  1895. }
  1896. }
  1897. if (phpQuery::$debug) {
  1898. $this->debug("Matched " . count($debug) . ": " . implode(', ', $debug));
  1899. }
  1900. if ($detachAfter)
  1901. $this->root->removeChild($detachAfter);
  1902. }
  1903. $this->elements = $stack;
  1904. }
  1905. /**
  1906. * Enter description here...
  1907. *
  1908. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  1909. */
  1910. public function find($selectors, $context = null, $noHistory = false)
  1911. {
  1912. if (!$noHistory)
  1913. // backup last stack /for end()/
  1914. $this->elementsBackup = $this->elements;
  1915. // allow to define context
  1916. // TODO combine code below with phpQuery::pq() context guessing code
  1917. // as generic function
  1918. if ($context) {
  1919. if (!is_array($context) && $context instanceof DOMELEMENT)
  1920. $this->elements = array($context);
  1921. else if (is_array($context)) {
  1922. $this->elements = array();
  1923. foreach ($context as $c)
  1924. if ($c instanceof DOMELEMENT)
  1925. $this->elements[] = $c;
  1926. } else if ($context instanceof self)
  1927. $this->elements = $context->elements;
  1928. }
  1929. $queries = $this->parseSelector($selectors);
  1930. $this->debug(array('FIND', $selectors, $queries));
  1931. $XQuery = '';
  1932. // remember stack state because of multi-queries
  1933. $oldStack = $this->elements;
  1934. // here we will be keeping found elements
  1935. $stack = array();
  1936. foreach ($queries as $selector) {
  1937. $this->elements = $oldStack;
  1938. $delimiterBefore = false;
  1939. foreach ($selector as $s) {
  1940. // TAG
  1941. $isTag = extension_loaded('mbstring') && phpQuery::$mbstringSupport
  1942. ? mb_ereg_match('^[\w|\||-]+$', $s) || $s == '*'
  1943. : preg_match('@^[\w|\||-]+$@', $s) || $s == '*';
  1944. if ($isTag) {
  1945. if ($this->isXML()) {
  1946. // namespace support
  1947. if (mb_strpos($s, '|') !== false) {
  1948. $ns = $tag = null;
  1949. list($ns, $tag) = explode('|', $s);
  1950. $XQuery .= "$ns:$tag";
  1951. } else if ($s == '*') {
  1952. $XQuery .= "*";
  1953. } else {
  1954. $XQuery .= "*[local-name()='$s']";
  1955. }
  1956. } else {
  1957. $XQuery .= $s;
  1958. }
  1959. // ID
  1960. } else if ($s[0] == '#') {
  1961. if ($delimiterBefore)
  1962. $XQuery .= '*';
  1963. $XQuery .= "[@id='" . substr($s, 1) . "']";
  1964. // ATTRIBUTES
  1965. } else if ($s[0] == '[') {
  1966. if ($delimiterBefore)
  1967. $XQuery .= '*';
  1968. // strip side brackets
  1969. $attr = trim($s, '][');
  1970. $execute = false;
  1971. // attr with specifed value
  1972. if (mb_strpos($s, '=')) {
  1973. $value = null;
  1974. list($attr, $value) = explode('=', $attr);
  1975. $value = trim($value, "'\"");
  1976. if ($this->isRegexp($attr)) {
  1977. // cut regexp character
  1978. $attr = substr($attr, 0, -1);
  1979. $execute = true;
  1980. $XQuery .= "[@{$attr}]";
  1981. } else {
  1982. $XQuery .= "[@{$attr}='{$value}']";
  1983. }
  1984. // attr without specified value
  1985. } else {
  1986. $XQuery .= "[@{$attr}]";
  1987. }
  1988. if ($execute) {
  1989. $this->runQuery($XQuery, $s, 'is');
  1990. $XQuery = '';
  1991. if (!$this->length())
  1992. break;
  1993. }
  1994. // CLASSES
  1995. } else if ($s[0] == '.') {
  1996. // TODO use return $this->find("./self::*[contains(concat(\" \",@class,\" \"), \" $class \")]");
  1997. // thx wizDom ;)
  1998. if ($delimiterBefore)
  1999. $XQuery .= '*';
  2000. $XQuery .= '[@class]';
  2001. $this->runQuery($XQuery, $s, 'matchClasses');
  2002. $XQuery = '';
  2003. if (!$this->length())
  2004. break;
  2005. // ~ General Sibling Selector
  2006. } else if ($s[0] == '~') {
  2007. $this->runQuery($XQuery);
  2008. $XQuery = '';
  2009. $this->elements = $this
  2010. ->siblings(
  2011. substr($s, 1)
  2012. )->elements;
  2013. if (!$this->length())
  2014. break;
  2015. // + Adjacent sibling selectors
  2016. } else if ($s[0] == '+') {
  2017. // TODO /following-sibling::
  2018. $this->runQuery($XQuery);
  2019. $XQuery = '';
  2020. $subSelector = substr($s, 1);
  2021. $subElements = $this->elements;
  2022. $this->elements = array();
  2023. foreach ($subElements as $node) {
  2024. // search first DOMElement sibling
  2025. $test = $node->nextSibling;
  2026. while ($test && !($test instanceof DOMELEMENT))
  2027. $test = $test->nextSibling;
  2028. if ($test && $this->is($subSelector, $test))
  2029. $this->elements[] = $test;
  2030. }
  2031. if (!$this->length())
  2032. break;
  2033. // PSEUDO CLASSES
  2034. } else if ($s[0] == ':') {
  2035. // TODO optimization for :first :last
  2036. if ($XQuery) {
  2037. $this->runQuery($XQuery);
  2038. $XQuery = '';
  2039. }
  2040. if (!$this->length())
  2041. break;
  2042. $this->pseudoClasses($s);
  2043. if (!$this->length())
  2044. break;
  2045. // DIRECT DESCENDANDS
  2046. } else if ($s == '>') {
  2047. $XQuery .= '/';
  2048. $delimiterBefore = 2;
  2049. // ALL DESCENDANDS
  2050. } else if ($s == ' ') {
  2051. $XQuery .= '//';
  2052. $delimiterBefore = 2;
  2053. // ERRORS
  2054. } else {
  2055. phpQuery::debug("Unrecognized token '$s'");
  2056. }
  2057. $delimiterBefore = $delimiterBefore === 2;
  2058. }
  2059. // run query if any
  2060. if ($XQuery && $XQuery != '//') {
  2061. $this->runQuery($XQuery);
  2062. $XQuery = '';
  2063. }
  2064. foreach ($this->elements as $node)
  2065. if (!$this->elementsContainsNode($node, $stack))
  2066. $stack[] = $node;
  2067. }
  2068. $this->elements = $stack;
  2069. return $this->newInstance();
  2070. }
  2071. /**
  2072. * @todo create API for classes with pseudoselectors
  2073. * @access private
  2074. */
  2075. protected function pseudoClasses($class)
  2076. {
  2077. // TODO clean args parsing ?
  2078. $class = ltrim($class, ':');
  2079. $haveArgs = mb_strpos($class, '(');
  2080. if ($haveArgs !== false) {
  2081. $args = substr($class, $haveArgs + 1, -1);
  2082. $class = substr($class, 0, $haveArgs);
  2083. }
  2084. switch ($class) {
  2085. case 'even':
  2086. case 'odd':
  2087. $stack = array();
  2088. foreach ($this->elements as $i => $node) {
  2089. if ($class == 'even' && ($i % 2) == 0)
  2090. $stack[] = $node;
  2091. else if ($class == 'odd' && $i % 2)
  2092. $stack[] = $node;
  2093. }
  2094. $this->elements = $stack;
  2095. break;
  2096. case 'eq':
  2097. $k = intval($args);
  2098. if ($k < 0) {
  2099. $this->elements = array($this->elements[count($this->elements) + $k]);
  2100. } else {
  2101. $this->elements = isset($this->elements[$k])
  2102. ? array($this->elements[$k])
  2103. : array();
  2104. }
  2105. break;
  2106. case 'gt':
  2107. $this->elements = array_slice($this->elements, $args + 1);
  2108. break;
  2109. case 'lt':
  2110. $this->elements = array_slice($this->elements, 0, $args + 1);
  2111. break;
  2112. case 'first':
  2113. if (isset($this->elements[0]))
  2114. $this->elements = array($this->elements[0]);
  2115. break;
  2116. case 'last':
  2117. if ($this->elements)
  2118. $this->elements = array($this->elements[count($this->elements) - 1]);
  2119. break;
  2120. /*case 'parent':
  2121. $stack = array();
  2122. foreach($this->elements as $node) {
  2123. if ( $node->childNodes->length )
  2124. $stack[] = $node;
  2125. }
  2126. $this->elements = $stack;
  2127. break;*/
  2128. case 'contains':
  2129. $text = trim($args, "\"'");
  2130. $stack = array();
  2131. foreach ($this->elements as $node) {
  2132. if (mb_stripos($node->textContent, $text) === false)
  2133. continue;
  2134. $stack[] = $node;
  2135. }
  2136. $this->elements = $stack;
  2137. break;
  2138. case 'not':
  2139. $selector = self::unQuote($args);
  2140. $this->elements = $this->not($selector)->stack();
  2141. break;
  2142. case 'slice':
  2143. // TODO jQuery difference ?
  2144. $args = explode(
  2145. ',',
  2146. str_replace(', ', ',', trim($args, "\"'"))
  2147. );
  2148. $start = $args[0];
  2149. $end = isset($args[1])
  2150. ? $args[1]
  2151. : null;
  2152. if ($end > 0)
  2153. $end = $end - $start;
  2154. $this->elements = array_slice($this->elements, $start, $end);
  2155. break;
  2156. case 'has':
  2157. $selector = trim($args, "\"'");
  2158. $stack = array();
  2159. foreach ($this->stack(1) as $el) {
  2160. if ($this->find($selector, $el, true)->length)
  2161. $stack[] = $el;
  2162. }
  2163. $this->elements = $stack;
  2164. break;
  2165. case 'submit':
  2166. case 'reset':
  2167. $this->elements = phpQuery::merge(
  2168. $this->map(
  2169. array($this, 'is'),
  2170. "input[type=$class]",
  2171. new CallbackParam()
  2172. ),
  2173. $this->map(
  2174. array($this, 'is'),
  2175. "button[type=$class]",
  2176. new CallbackParam()
  2177. )
  2178. );
  2179. break;
  2180. // $stack = array();
  2181. // foreach($this->elements as $node)
  2182. // if ($node->is('input[type=submit]') || $node->is('button[type=submit]'))
  2183. // $stack[] = $el;
  2184. // $this->elements = $stack;
  2185. case 'input':
  2186. $this->elements = $this->map(
  2187. array($this, 'is'),
  2188. 'input',
  2189. new CallbackParam()
  2190. )->elements;
  2191. break;
  2192. case 'password':
  2193. case 'checkbox':
  2194. case 'radio':
  2195. case 'hidden':
  2196. case 'image':
  2197. case 'file':
  2198. $this->elements = $this->map(
  2199. array($this, 'is'),
  2200. "input[type=$class]",
  2201. new CallbackParam()
  2202. )->elements;
  2203. break;
  2204. case 'parent':
  2205. $this->elements = $this->map(
  2206. function ($node) {
  2207. return $node instanceof DOMELEMENT && $node->childNodes->length
  2208. ? $node : null;
  2209. }
  2210. )->elements;
  2211. break;
  2212. case 'empty':
  2213. $this->elements = $this->map(
  2214. function ($node) {
  2215. return $node instanceof DOMELEMENT && $node->childNodes->length
  2216. ? null : $node;
  2217. }
  2218. )->elements;
  2219. break;
  2220. case 'disabled':
  2221. case 'selected':
  2222. case 'checked':
  2223. $this->elements = $this->map(
  2224. array($this, 'is'),
  2225. "[$class]",
  2226. new CallbackParam()
  2227. )->elements;
  2228. break;
  2229. case 'enabled':
  2230. $this->elements = $this->map(
  2231. function ($node) {
  2232. return pq($node)->not(":disabled") ? $node : null;
  2233. }
  2234. )->elements;
  2235. break;
  2236. case 'header':
  2237. $this->elements = $this->map(
  2238. function ($node) {
  2239. $isHeader = isset($node->tagName) && in_array($node->tagName, array(
  2240. "h1", "h2", "h3", "h4", "h5", "h6", "h7"
  2241. ));
  2242. return $isHeader
  2243. ? $node
  2244. : null;
  2245. }
  2246. )->elements;
  2247. // $this->elements = $this->map(
  2248. // create_function('$node', '$node = pq($node);
  2249. // return $node->is("h1")
  2250. // || $node->is("h2")
  2251. // || $node->is("h3")
  2252. // || $node->is("h4")
  2253. // || $node->is("h5")
  2254. // || $node->is("h6")
  2255. // || $node->is("h7")
  2256. // ? $node
  2257. // : null;')
  2258. // )->elements;
  2259. break;
  2260. case 'only-child':
  2261. $this->elements = $this->map(
  2262. function ($node) {
  2263. return pq($node)->siblings()->size() == 0 ? $node : null;
  2264. }
  2265. )->elements;
  2266. break;
  2267. case 'first-child':
  2268. $this->elements = $this->map(
  2269. function ($node) {
  2270. return pq($node)->prevAll()->size() == 0 ? $node : null;
  2271. }
  2272. )->elements;
  2273. break;
  2274. case 'last-child':
  2275. $this->elements = $this->map(
  2276. function ($node) {
  2277. return pq($node)->nextAll()->size() == 0 ? $node : null;
  2278. }
  2279. )->elements;
  2280. break;
  2281. case 'nth-child':
  2282. $param = trim($args, "\"'");
  2283. if (!$param)
  2284. break;
  2285. // nth-child(n+b) to nth-child(1n+b)
  2286. if ($param[0] == 'n')
  2287. $param = '1' . $param;
  2288. // :nth-child(index/even/odd/equation)
  2289. if ($param == 'even' || $param == 'odd')
  2290. $mapped = $this->map(
  2291. function ($node, $param) {
  2292. $index = pq($node)->prevAll()->size() + 1;
  2293. if ($param == "even" && ($index % 2) == 0)
  2294. return $node;
  2295. else if ($param == "odd" && $index % 2 == 1)
  2296. return $node;
  2297. else
  2298. return null;
  2299. },
  2300. new CallbackParam(),
  2301. $param
  2302. );
  2303. else if (mb_strlen($param) > 1 && preg_match('/^(\d*)n([-+]?)(\d*)/', $param) === 1)
  2304. // an+b
  2305. $mapped = $this->map(
  2306. function ($node, $param) {
  2307. $prevs = pq($node)->prevAll()->size();
  2308. $index = 1 + $prevs;
  2309. preg_match("/^(\d*)n([-+]?)(\d*)/", $param, $matches);
  2310. $a = intval($matches[1]);
  2311. $b = intval($matches[3]);
  2312. if ($matches[2] === "-") {
  2313. $b = -$b;
  2314. }
  2315. if ($a > 0) {
  2316. return ($index - $b) % $a == 0
  2317. ? $node
  2318. : null;
  2319. phpQuery::debug($a . "*" . floor($index / $a) . "+$b-1 == " . ($a * floor($index / $a) + $b - 1) . " ?= $prevs");
  2320. return $a * floor($index / $a) + $b - 1 == $prevs
  2321. ? $node
  2322. : null;
  2323. } else if ($a == 0)
  2324. return $index == $b
  2325. ? $node
  2326. : null;
  2327. else
  2328. // negative value
  2329. return $index <= $b
  2330. ? $node
  2331. : null;
  2332. // if (! $b)
  2333. // return $index%$a == 0
  2334. // ? $node
  2335. // : null;
  2336. // else
  2337. // return ($index-$b)%$a == 0
  2338. // ? $node
  2339. // : null;
  2340. },
  2341. new CallbackParam(),
  2342. $param
  2343. );
  2344. else
  2345. // index
  2346. $mapped = $this->map(
  2347. function ($node, $index) {
  2348. $prevs = pq($node)->prevAll()->size();
  2349. if ($prevs && $prevs == $index - 1)
  2350. return $node;
  2351. else if (!$prevs && $index == 1)
  2352. return $node;
  2353. else
  2354. return null;
  2355. },
  2356. new CallbackParam(),
  2357. $param
  2358. );
  2359. $this->elements = $mapped->elements;
  2360. break;
  2361. default:
  2362. $this->debug("Unknown pseudoclass '{$class}', skipping...");
  2363. }
  2364. }
  2365. /**
  2366. * @access private
  2367. */
  2368. protected function __pseudoClassParam($paramsString)
  2369. {
  2370. // TODO;
  2371. }
  2372. /**
  2373. * Enter description here...
  2374. *
  2375. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2376. */
  2377. public function is($selector, $nodes = null)
  2378. {
  2379. phpQuery::debug(array("Is:", $selector));
  2380. if (!$selector)
  2381. return false;
  2382. $oldStack = $this->elements;
  2383. $returnArray = false;
  2384. if ($nodes && is_array($nodes)) {
  2385. $this->elements = $nodes;
  2386. } else if ($nodes)
  2387. $this->elements = array($nodes);
  2388. $this->filter($selector, true);
  2389. $stack = $this->elements;
  2390. $this->elements = $oldStack;
  2391. if ($nodes)
  2392. return $stack ? $stack : null;
  2393. return (bool)count($stack);
  2394. }
  2395. /**
  2396. * Enter description here...
  2397. * jQuery difference.
  2398. *
  2399. * Callback:
  2400. * - $index int
  2401. * - $node DOMNode
  2402. *
  2403. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2404. * @link http://docs.jquery.com/Traversing/filter
  2405. */
  2406. public function filterCallback($callback, $_skipHistory = false)
  2407. {
  2408. if (!$_skipHistory) {
  2409. $this->elementsBackup = $this->elements;
  2410. $this->debug("Filtering by callback");
  2411. }
  2412. $newStack = array();
  2413. foreach ($this->elements as $index => $node) {
  2414. $result = phpQuery::callbackRun($callback, array($index, $node));
  2415. if (is_null($result) || (!is_null($result) && $result))
  2416. $newStack[] = $node;
  2417. }
  2418. $this->elements = $newStack;
  2419. return $_skipHistory
  2420. ? $this
  2421. : $this->newInstance();
  2422. }
  2423. /**
  2424. * Enter description here...
  2425. *
  2426. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2427. * @link http://docs.jquery.com/Traversing/filter
  2428. */
  2429. public function filter($selectors, $_skipHistory = false)
  2430. {
  2431. if ($selectors instanceof Callback or $selectors instanceof Closure)
  2432. return $this->filterCallback($selectors, $_skipHistory);
  2433. if (!$_skipHistory)
  2434. $this->elementsBackup = $this->elements;
  2435. $notSimpleSelector = array(' ', '>', '~', '+', '/');
  2436. if (!is_array($selectors))
  2437. $selectors = $this->parseSelector($selectors);
  2438. if (!$_skipHistory)
  2439. $this->debug(array("Filtering:", $selectors));
  2440. $finalStack = array();
  2441. foreach ($selectors as $selector) {
  2442. $stack = array();
  2443. if (!$selector)
  2444. break;
  2445. // avoid first space or /
  2446. if (in_array($selector[0], $notSimpleSelector))
  2447. $selector = array_slice($selector, 1);
  2448. // PER NODE selector chunks
  2449. foreach ($this->stack() as $node) {
  2450. $break = false;
  2451. foreach ($selector as $s) {
  2452. if (!($node instanceof DOMELEMENT)) {
  2453. // all besides DOMElement
  2454. if ($s[0] == '[') {
  2455. $attr = trim($s, '[]');
  2456. if (mb_strpos($attr, '=')) {
  2457. list($attr, $val) = explode('=', $attr);
  2458. if ($attr == 'nodeType' && $node->nodeType != $val)
  2459. $break = true;
  2460. }
  2461. } else
  2462. $break = true;
  2463. } else {
  2464. // DOMElement only
  2465. // ID
  2466. if ($s[0] == '#') {
  2467. if ($node->getAttribute('id') != substr($s, 1))
  2468. $break = true;
  2469. // CLASSES
  2470. } else if ($s[0] == '.') {
  2471. if (!$this->matchClasses($s, $node))
  2472. $break = true;
  2473. // ATTRS
  2474. } else if ($s[0] == '[') {
  2475. // strip side brackets
  2476. $attr = trim($s, '[]');
  2477. if (mb_strpos($attr, '=')) {
  2478. list($attr, $val) = explode('=', $attr);
  2479. $val = self::unQuote($val);
  2480. if ($attr == 'nodeType') {
  2481. if ($val != $node->nodeType)
  2482. $break = true;
  2483. } else if ($this->isRegexp($attr)) {
  2484. $val = extension_loaded('mbstring') && phpQuery::$mbstringSupport
  2485. ? quotemeta(trim($val, '"\''))
  2486. : preg_quote(trim($val, '"\''), '@');
  2487. // switch last character
  2488. switch (substr($attr, -1)) {
  2489. // quotemeta used insted of preg_quote
  2490. // http://code.google.com/p/phpquery/issues/detail?id=76
  2491. case '^':
  2492. $pattern = '^' . $val;
  2493. break;
  2494. case '*':
  2495. $pattern = '.*' . $val . '.*';
  2496. break;
  2497. case '$':
  2498. $pattern = '.*' . $val . '$';
  2499. break;
  2500. }
  2501. // cut last character
  2502. $attr = substr($attr, 0, -1);
  2503. $isMatch = extension_loaded('mbstring') && phpQuery::$mbstringSupport
  2504. ? mb_ereg_match($pattern, $node->getAttribute($attr))
  2505. : preg_match("@{$pattern}@", $node->getAttribute($attr));
  2506. if (!$isMatch)
  2507. $break = true;
  2508. } else if ($node->getAttribute($attr) != $val)
  2509. $break = true;
  2510. } else if (!$node->hasAttribute($attr))
  2511. $break = true;
  2512. // PSEUDO CLASSES
  2513. } else if ($s[0] == ':') {
  2514. // skip
  2515. // TAG
  2516. } else if (trim($s)) {
  2517. if ($s != '*') {
  2518. // TODO namespaces
  2519. if (isset($node->tagName)) {
  2520. if ($node->tagName != $s)
  2521. $break = true;
  2522. } else if ($s == 'html' && !$this->isRoot($node))
  2523. $break = true;
  2524. }
  2525. // AVOID NON-SIMPLE SELECTORS
  2526. } else if (in_array($s, $notSimpleSelector)) {
  2527. $break = true;
  2528. $this->debug(array('Skipping non simple selector', $selector));
  2529. }
  2530. }
  2531. if ($break)
  2532. break;
  2533. }
  2534. // if element passed all chunks of selector - add it to new stack
  2535. if (!$break)
  2536. $stack[] = $node;
  2537. }
  2538. $tmpStack = $this->elements;
  2539. $this->elements = $stack;
  2540. // PER ALL NODES selector chunks
  2541. foreach ($selector as $s)
  2542. // PSEUDO CLASSES
  2543. if ($s[0] == ':')
  2544. $this->pseudoClasses($s);
  2545. foreach ($this->elements as $node)
  2546. // XXX it should be merged without duplicates
  2547. // but jQuery doesnt do that
  2548. $finalStack[] = $node;
  2549. $this->elements = $tmpStack;
  2550. }
  2551. $this->elements = $finalStack;
  2552. if ($_skipHistory) {
  2553. return $this;
  2554. } else {
  2555. $this->debug("Stack length after filter(): " . count($finalStack));
  2556. return $this->newInstance();
  2557. }
  2558. }
  2559. /**
  2560. *
  2561. * @param $value
  2562. * @return unknown_type
  2563. * @TODO implement in all methods using passed parameters
  2564. */
  2565. protected static function unQuote($value)
  2566. {
  2567. return $value[0] == '\'' || $value[0] == '"'
  2568. ? substr($value, 1, -1)
  2569. : $value;
  2570. }
  2571. /**
  2572. * Enter description here...
  2573. *
  2574. * @link http://docs.jquery.com/Ajax/load
  2575. * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2576. * @todo Support $selector
  2577. */
  2578. public function load($url, $data = null, $callback = null)
  2579. {
  2580. if ($data && !is_array($data)) {
  2581. $callback = $data;
  2582. $data = null;
  2583. }
  2584. if (mb_strpos($url, ' ') !== false) {
  2585. $matches = null;
  2586. if (extension_loaded('mbstring') && phpQuery::$mbstringSupport)
  2587. mb_ereg('^([^ ]+) (.*)$', $url, $matches);
  2588. else
  2589. preg_match('^([^ ]+) (.*)$', $url, $matches);
  2590. $url = $matches[1];
  2591. $selector = $matches[2];
  2592. // FIXME this sucks, pass as callback param
  2593. $this->_loadSelector = $selector;
  2594. }
  2595. $ajax = array(
  2596. 'url' => $url,
  2597. 'type' => $data ? 'POST' : 'GET',
  2598. 'data' => $data,
  2599. 'complete' => $callback,
  2600. 'success' => array($this, '__loadSuccess')
  2601. );
  2602. phpQuery::ajax($ajax);
  2603. return $this;
  2604. }
  2605. /**
  2606. * @access private
  2607. * @param $html
  2608. * @return unknown_type
  2609. */
  2610. public function __loadSuccess($html)
  2611. {
  2612. if ($this->_loadSelector) {
  2613. $html = phpQuery::newDocument($html)->find($this->_loadSelector);
  2614. unset($this->_loadSelector);
  2615. }
  2616. foreach ($this->stack(1) as $node) {
  2617. phpQuery::pq($node, $this->getDocumentID())
  2618. ->markup($html);
  2619. }
  2620. }
  2621. /**
  2622. * Enter description here...
  2623. *
  2624. * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2625. * @todo
  2626. */
  2627. public function css()
  2628. {
  2629. // TODO
  2630. return $this;
  2631. }
  2632. /**
  2633. * @todo
  2634. *
  2635. */
  2636. public function show()
  2637. {
  2638. // TODO
  2639. return $this;
  2640. }
  2641. /**
  2642. * @todo
  2643. *
  2644. */
  2645. public function hide()
  2646. {
  2647. // TODO
  2648. return $this;
  2649. }
  2650. /**
  2651. * Trigger a type of event on every matched element.
  2652. *
  2653. * @param unknown_type $type
  2654. * @param unknown_type $data
  2655. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2656. * @TODO support more than event in $type (space-separated)
  2657. */
  2658. public function trigger($type, $data = array())
  2659. {
  2660. foreach ($this->elements as $node)
  2661. phpQueryEvents::trigger($this->getDocumentID(), $type, $data, $node);
  2662. return $this;
  2663. }
  2664. /**
  2665. * This particular method triggers all bound event handlers on an element (for a specific event type) WITHOUT executing the browsers default actions.
  2666. *
  2667. * @param unknown_type $type
  2668. * @param unknown_type $data
  2669. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2670. * @TODO
  2671. */
  2672. public function triggerHandler($type, $data = array())
  2673. {
  2674. // TODO;
  2675. }
  2676. /**
  2677. * Binds a handler to one or more events (like click) for each matched element.
  2678. * Can also bind custom events.
  2679. *
  2680. * @param unknown_type $type
  2681. * @param unknown_type $data Optional
  2682. * @param unknown_type $callback
  2683. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2684. * @TODO support '!' (exclusive) events
  2685. * @TODO support more than event in $type (space-separated)
  2686. */
  2687. public function bind($type, $data, $callback = null)
  2688. {
  2689. // TODO check if $data is callable, not using is_callable
  2690. if (!isset($callback)) {
  2691. $callback = $data;
  2692. $data = null;
  2693. }
  2694. foreach ($this->elements as $node)
  2695. phpQueryEvents::add($this->getDocumentID(), $node, $type, $data, $callback);
  2696. return $this;
  2697. }
  2698. /**
  2699. * Enter description here...
  2700. *
  2701. * @param unknown_type $type
  2702. * @param unknown_type $callback
  2703. * @return unknown
  2704. * @TODO namespace events
  2705. * @TODO support more than event in $type (space-separated)
  2706. */
  2707. public function unbind($type = null, $callback = null)
  2708. {
  2709. foreach ($this->elements as $node)
  2710. phpQueryEvents::remove($this->getDocumentID(), $node, $type, $callback);
  2711. return $this;
  2712. }
  2713. /**
  2714. * Enter description here...
  2715. *
  2716. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2717. */
  2718. public function change($callback = null)
  2719. {
  2720. if ($callback)
  2721. return $this->bind('change', $callback);
  2722. return $this->trigger('change');
  2723. }
  2724. /**
  2725. * Enter description here...
  2726. *
  2727. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2728. */
  2729. public function submit($callback = null)
  2730. {
  2731. if ($callback)
  2732. return $this->bind('submit', $callback);
  2733. return $this->trigger('submit');
  2734. }
  2735. /**
  2736. * Enter description here...
  2737. *
  2738. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2739. */
  2740. public function click($callback = null)
  2741. {
  2742. if ($callback)
  2743. return $this->bind('click', $callback);
  2744. return $this->trigger('click');
  2745. }
  2746. /**
  2747. * Enter description here...
  2748. *
  2749. * @param String|phpQuery
  2750. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2751. */
  2752. public function wrapAllOld($wrapper)
  2753. {
  2754. $wrapper = pq($wrapper)->_clone();
  2755. if (!$wrapper->length() || !$this->length())
  2756. return $this;
  2757. $wrapper->insertBefore($this->elements[0]);
  2758. $deepest = $wrapper->elements[0];
  2759. while ($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT)
  2760. $deepest = $deepest->firstChild;
  2761. pq($deepest)->append($this);
  2762. return $this;
  2763. }
  2764. /**
  2765. * Enter description here...
  2766. *
  2767. * TODO testme...
  2768. * @param String|phpQuery
  2769. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2770. */
  2771. public function wrapAll($wrapper)
  2772. {
  2773. if (!$this->length())
  2774. return $this;
  2775. return phpQuery::pq($wrapper, $this->getDocumentID())
  2776. ->clone()
  2777. ->insertBefore($this->get(0))
  2778. ->map(array($this, '___wrapAllCallback'))
  2779. ->append($this);
  2780. }
  2781. /**
  2782. *
  2783. * @param $node
  2784. * @return unknown_type
  2785. * @access private
  2786. */
  2787. public function ___wrapAllCallback($node)
  2788. {
  2789. $deepest = $node;
  2790. while ($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT)
  2791. $deepest = $deepest->firstChild;
  2792. return $deepest;
  2793. }
  2794. /**
  2795. * Enter description here...
  2796. * NON JQUERY METHOD
  2797. *
  2798. * @param String|phpQuery
  2799. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2800. */
  2801. public function wrapAllPHP($codeBefore, $codeAfter)
  2802. {
  2803. return $this
  2804. ->slice(0, 1)
  2805. ->beforePHP($codeBefore)
  2806. ->end()
  2807. ->slice(-1)
  2808. ->afterPHP($codeAfter)
  2809. ->end();
  2810. }
  2811. /**
  2812. * Enter description here...
  2813. *
  2814. * @param String|phpQuery
  2815. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2816. */
  2817. public function wrap($wrapper)
  2818. {
  2819. foreach ($this->stack() as $node)
  2820. phpQuery::pq($node, $this->getDocumentID())->wrapAll($wrapper);
  2821. return $this;
  2822. }
  2823. /**
  2824. * Enter description here...
  2825. *
  2826. * @param String|phpQuery
  2827. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2828. */
  2829. public function wrapPHP($codeBefore, $codeAfter)
  2830. {
  2831. foreach ($this->stack() as $node)
  2832. phpQuery::pq($node, $this->getDocumentID())->wrapAllPHP($codeBefore, $codeAfter);
  2833. return $this;
  2834. }
  2835. /**
  2836. * Enter description here...
  2837. *
  2838. * @param String|phpQuery
  2839. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2840. */
  2841. public function wrapInner($wrapper)
  2842. {
  2843. foreach ($this->stack() as $node)
  2844. phpQuery::pq($node, $this->getDocumentID())->contents()->wrapAll($wrapper);
  2845. return $this;
  2846. }
  2847. /**
  2848. * Enter description here...
  2849. *
  2850. * @param String|phpQuery
  2851. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2852. */
  2853. public function wrapInnerPHP($codeBefore, $codeAfter)
  2854. {
  2855. foreach ($this->stack(1) as $node)
  2856. phpQuery::pq($node, $this->getDocumentID())->contents()
  2857. ->wrapAllPHP($codeBefore, $codeAfter);
  2858. return $this;
  2859. }
  2860. /**
  2861. * Enter description here...
  2862. *
  2863. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2864. * @testme Support for text nodes
  2865. */
  2866. public function contents()
  2867. {
  2868. $stack = array();
  2869. foreach ($this->stack(1) as $el) {
  2870. // FIXME (fixed) http://code.google.com/p/phpquery/issues/detail?id=56
  2871. // if (! isset($el->childNodes))
  2872. // continue;
  2873. foreach ($el->childNodes as $node) {
  2874. $stack[] = $node;
  2875. }
  2876. }
  2877. return $this->newInstance($stack);
  2878. }
  2879. /**
  2880. * Enter description here...
  2881. *
  2882. * jQuery difference.
  2883. *
  2884. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2885. */
  2886. public function contentsUnwrap()
  2887. {
  2888. foreach ($this->stack(1) as $node) {
  2889. if (!$node->parentNode)
  2890. continue;
  2891. $childNodes = array();
  2892. // any modification in DOM tree breaks childNodes iteration, so cache them first
  2893. foreach ($node->childNodes as $chNode)
  2894. $childNodes[] = $chNode;
  2895. foreach ($childNodes as $chNode)
  2896. // $node->parentNode->appendChild($chNode);
  2897. $node->parentNode->insertBefore($chNode, $node);
  2898. $node->parentNode->removeChild($node);
  2899. }
  2900. return $this;
  2901. }
  2902. /**
  2903. * Enter description here...
  2904. *
  2905. * jQuery difference.
  2906. */
  2907. public function switchWith($markup)
  2908. {
  2909. $markup = pq($markup, $this->getDocumentID());
  2910. $content = null;
  2911. foreach ($this->stack(1) as $node) {
  2912. pq($node)
  2913. ->contents()->toReference($content)->end()
  2914. ->replaceWith($markup->clone()->append($content));
  2915. }
  2916. return $this;
  2917. }
  2918. /**
  2919. * Enter description here...
  2920. *
  2921. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2922. */
  2923. public function eq($num)
  2924. {
  2925. $oldStack = $this->elements;
  2926. $this->elementsBackup = $this->elements;
  2927. $this->elements = array();
  2928. if (isset($oldStack[$num]))
  2929. $this->elements[] = $oldStack[$num];
  2930. return $this->newInstance();
  2931. }
  2932. /**
  2933. * Enter description here...
  2934. *
  2935. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2936. */
  2937. public function size()
  2938. {
  2939. return count($this->elements);
  2940. }
  2941. /**
  2942. * Enter description here...
  2943. *
  2944. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2945. * @deprecated Use length as attribute
  2946. */
  2947. public function length()
  2948. {
  2949. return $this->size();
  2950. }
  2951. #[\ReturnTypeWillChange]
  2952. public function count()
  2953. {
  2954. return $this->size();
  2955. }
  2956. /**
  2957. * Enter description here...
  2958. *
  2959. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2960. * @todo $level
  2961. */
  2962. public function end($level = 1)
  2963. {
  2964. // $this->elements = array_pop( $this->history );
  2965. // return $this;
  2966. // $this->previous->DOM = $this->DOM;
  2967. // $this->previous->XPath = $this->XPath;
  2968. return $this->previous
  2969. ? $this->previous
  2970. : $this;
  2971. }
  2972. /**
  2973. * Enter description here...
  2974. * Normal use ->clone() .
  2975. *
  2976. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2977. * @access private
  2978. */
  2979. public function _clone()
  2980. {
  2981. $newStack = array();
  2982. //pr(array('copy... ', $this->whois()));
  2983. //$this->dumpHistory('copy');
  2984. $this->elementsBackup = $this->elements;
  2985. foreach ($this->elements as $node) {
  2986. $newStack[] = $node->cloneNode(true);
  2987. }
  2988. $this->elements = $newStack;
  2989. return $this->newInstance();
  2990. }
  2991. /**
  2992. * Enter description here...
  2993. *
  2994. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  2995. */
  2996. public function replaceWithPHP($code)
  2997. {
  2998. return $this->replaceWith(phpQuery::php($code));
  2999. }
  3000. /**
  3001. * Enter description here...
  3002. *
  3003. * @param String|phpQuery $content
  3004. * @link http://docs.jquery.com/Manipulation/replaceWith#content
  3005. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3006. */
  3007. public function replaceWith($content)
  3008. {
  3009. return $this->after($content)->remove();
  3010. }
  3011. /**
  3012. * Enter description here...
  3013. *
  3014. * @param String $selector
  3015. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3016. * @todo this works ?
  3017. */
  3018. public function replaceAll($selector)
  3019. {
  3020. foreach (phpQuery::pq($selector, $this->getDocumentID()) as $node)
  3021. phpQuery::pq($node, $this->getDocumentID())
  3022. ->after($this->_clone())
  3023. ->remove();
  3024. return $this;
  3025. }
  3026. /**
  3027. * Enter description here...
  3028. *
  3029. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3030. */
  3031. public function remove($selector = null)
  3032. {
  3033. $loop = $selector
  3034. ? $this->filter($selector)->elements
  3035. : $this->elements;
  3036. foreach ($loop as $node) {
  3037. if (!$node->parentNode)
  3038. continue;
  3039. if (isset($node->tagName))
  3040. $this->debug("Removing '{$node->tagName}'");
  3041. $node->parentNode->removeChild($node);
  3042. // Mutation event
  3043. $event = new DOMEvent(array(
  3044. 'target' => $node,
  3045. 'type' => 'DOMNodeRemoved'
  3046. ));
  3047. phpQueryEvents::trigger(
  3048. $this->getDocumentID(),
  3049. $event->type,
  3050. array($event),
  3051. $node
  3052. );
  3053. }
  3054. return $this;
  3055. }
  3056. protected function markupEvents($newMarkup, $oldMarkup, $node)
  3057. {
  3058. if ($node->tagName == 'textarea' && $newMarkup != $oldMarkup) {
  3059. $event = new DOMEvent(array(
  3060. 'target' => $node,
  3061. 'type' => 'change'
  3062. ));
  3063. phpQueryEvents::trigger(
  3064. $this->getDocumentID(),
  3065. $event->type,
  3066. array($event),
  3067. $node
  3068. );
  3069. }
  3070. }
  3071. /**
  3072. * jQuey difference
  3073. *
  3074. * @param $markup
  3075. * @return unknown_type
  3076. * @TODO trigger change event for textarea
  3077. */
  3078. public function markup($markup = null, $callback1 = null, $callback2 = null, $callback3 = null)
  3079. {
  3080. $args = func_get_args();
  3081. if ($this->documentWrapper->isXML)
  3082. return call_user_func_array(array($this, 'xml'), $args);
  3083. else
  3084. return call_user_func_array(array($this, 'html'), $args);
  3085. }
  3086. /**
  3087. * jQuey difference
  3088. *
  3089. * @param $markup
  3090. * @return unknown_type
  3091. */
  3092. public function markupOuter($callback1 = null, $callback2 = null, $callback3 = null)
  3093. {
  3094. $args = func_get_args();
  3095. if ($this->documentWrapper->isXML)
  3096. return call_user_func_array(array($this, 'xmlOuter'), $args);
  3097. else
  3098. return call_user_func_array(array($this, 'htmlOuter'), $args);
  3099. }
  3100. /**
  3101. * Enter description here...
  3102. *
  3103. * @param unknown_type $html
  3104. * @return string|phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3105. * @TODO force html result
  3106. */
  3107. public function html($html = null, $callback1 = null, $callback2 = null, $callback3 = null)
  3108. {
  3109. if (isset($html)) {
  3110. // INSERT
  3111. $nodes = $this->documentWrapper->import($html);
  3112. $this->empty();
  3113. foreach ($this->stack(1) as $alreadyAdded => $node) {
  3114. // for now, limit events for textarea
  3115. if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea')
  3116. $oldHtml = pq($node, $this->getDocumentID())->markup();
  3117. foreach ($nodes as $newNode) {
  3118. $node->appendChild(
  3119. $alreadyAdded
  3120. ? $newNode->cloneNode(true)
  3121. : $newNode
  3122. );
  3123. }
  3124. // for now, limit events for textarea
  3125. if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea')
  3126. $this->markupEvents($html, $oldHtml, $node);
  3127. }
  3128. return $this;
  3129. } else {
  3130. // FETCH
  3131. $return = $this->documentWrapper->markup($this->elements, true);
  3132. $args = func_get_args();
  3133. foreach (array_slice($args, 1) as $callback) {
  3134. $return = phpQuery::callbackRun($callback, array($return));
  3135. }
  3136. return $return;
  3137. }
  3138. }
  3139. /**
  3140. * @TODO force xml result
  3141. */
  3142. public function xml($xml = null, $callback1 = null, $callback2 = null, $callback3 = null)
  3143. {
  3144. $args = func_get_args();
  3145. return call_user_func_array(array($this, 'html'), $args);
  3146. }
  3147. /**
  3148. * Enter description here...
  3149. * @TODO force html result
  3150. *
  3151. * @return String
  3152. */
  3153. public function htmlOuter($callback1 = null, $callback2 = null, $callback3 = null)
  3154. {
  3155. $markup = $this->documentWrapper->markup($this->elements);
  3156. // pass thou callbacks
  3157. $args = func_get_args();
  3158. foreach ($args as $callback) {
  3159. $markup = phpQuery::callbackRun($callback, array($markup));
  3160. }
  3161. return $markup;
  3162. }
  3163. /**
  3164. * @TODO force xml result
  3165. */
  3166. public function xmlOuter($callback1 = null, $callback2 = null, $callback3 = null)
  3167. {
  3168. $args = func_get_args();
  3169. return call_user_func_array(array($this, 'htmlOuter'), $args);
  3170. }
  3171. public function __toString()
  3172. {
  3173. return $this->markupOuter();
  3174. }
  3175. /**
  3176. * Just like html(), but returns markup with VALID (dangerous) PHP tags.
  3177. *
  3178. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3179. * @todo support returning markup with PHP tags when called without param
  3180. */
  3181. public function php($code = null)
  3182. {
  3183. return $this->markupPHP($code);
  3184. }
  3185. /**
  3186. * Enter description here...
  3187. *
  3188. * @param $code
  3189. * @return unknown_type
  3190. */
  3191. public function markupPHP($code = null)
  3192. {
  3193. return isset($code)
  3194. ? $this->markup(phpQuery::php($code))
  3195. : phpQuery::markupToPHP($this->markup());
  3196. }
  3197. /**
  3198. * Enter description here...
  3199. *
  3200. * @param $code
  3201. * @return unknown_type
  3202. */
  3203. public function markupOuterPHP()
  3204. {
  3205. return phpQuery::markupToPHP($this->markupOuter());
  3206. }
  3207. /**
  3208. * Enter description here...
  3209. *
  3210. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3211. */
  3212. public function children($selector = null)
  3213. {
  3214. $stack = array();
  3215. foreach ($this->stack(1) as $node) {
  3216. // foreach($node->getElementsByTagName('*') as $newNode) {
  3217. foreach ($node->childNodes as $newNode) {
  3218. if ($newNode->nodeType != 1)
  3219. continue;
  3220. if ($selector && !$this->is($selector, $newNode))
  3221. continue;
  3222. if ($this->elementsContainsNode($newNode, $stack))
  3223. continue;
  3224. $stack[] = $newNode;
  3225. }
  3226. }
  3227. $this->elementsBackup = $this->elements;
  3228. $this->elements = $stack;
  3229. return $this->newInstance();
  3230. }
  3231. /**
  3232. * Enter description here...
  3233. *
  3234. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3235. */
  3236. public function ancestors($selector = null)
  3237. {
  3238. return $this->children($selector);
  3239. }
  3240. /**
  3241. * Enter description here...
  3242. *
  3243. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3244. */
  3245. public function append($content)
  3246. {
  3247. return $this->insert($content, __FUNCTION__);
  3248. }
  3249. /**
  3250. * Enter description here...
  3251. *
  3252. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3253. */
  3254. public function appendPHP($content)
  3255. {
  3256. return $this->insert("<php><!-- {$content} --></php>", 'append');
  3257. }
  3258. /**
  3259. * Enter description here...
  3260. *
  3261. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3262. */
  3263. public function appendTo($seletor)
  3264. {
  3265. return $this->insert($seletor, __FUNCTION__);
  3266. }
  3267. /**
  3268. * Enter description here...
  3269. *
  3270. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3271. */
  3272. public function prepend($content)
  3273. {
  3274. return $this->insert($content, __FUNCTION__);
  3275. }
  3276. /**
  3277. * Enter description here...
  3278. *
  3279. * @todo accept many arguments, which are joined, arrays maybe also
  3280. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3281. */
  3282. public function prependPHP($content)
  3283. {
  3284. return $this->insert("<php><!-- {$content} --></php>", 'prepend');
  3285. }
  3286. /**
  3287. * Enter description here...
  3288. *
  3289. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3290. */
  3291. public function prependTo($seletor)
  3292. {
  3293. return $this->insert($seletor, __FUNCTION__);
  3294. }
  3295. /**
  3296. * Enter description here...
  3297. *
  3298. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3299. */
  3300. public function before($content)
  3301. {
  3302. return $this->insert($content, __FUNCTION__);
  3303. }
  3304. /**
  3305. * Enter description here...
  3306. *
  3307. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3308. */
  3309. public function beforePHP($content)
  3310. {
  3311. return $this->insert("<php><!-- {$content} --></php>", 'before');
  3312. }
  3313. /**
  3314. * Enter description here...
  3315. *
  3316. * @param String|phpQuery
  3317. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3318. */
  3319. public function insertBefore($seletor)
  3320. {
  3321. return $this->insert($seletor, __FUNCTION__);
  3322. }
  3323. /**
  3324. * Enter description here...
  3325. *
  3326. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3327. */
  3328. public function after($content)
  3329. {
  3330. return $this->insert($content, __FUNCTION__);
  3331. }
  3332. /**
  3333. * Enter description here...
  3334. *
  3335. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3336. */
  3337. public function afterPHP($content)
  3338. {
  3339. return $this->insert("<php><!-- {$content} --></php>", 'after');
  3340. }
  3341. /**
  3342. * Enter description here...
  3343. *
  3344. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3345. */
  3346. public function insertAfter($seletor)
  3347. {
  3348. return $this->insert($seletor, __FUNCTION__);
  3349. }
  3350. /**
  3351. * Internal insert method. Don't use it.
  3352. *
  3353. * @param unknown_type $target
  3354. * @param unknown_type $type
  3355. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3356. * @access private
  3357. */
  3358. public function insert($target, $type)
  3359. {
  3360. $this->debug("Inserting data with '{$type}'");
  3361. $to = false;
  3362. switch ($type) {
  3363. case 'appendTo':
  3364. case 'prependTo':
  3365. case 'insertBefore':
  3366. case 'insertAfter':
  3367. $to = true;
  3368. }
  3369. switch (gettype($target)) {
  3370. case 'string':
  3371. $insertFrom = $insertTo = array();
  3372. if ($to) {
  3373. // INSERT TO
  3374. $insertFrom = $this->elements;
  3375. if (phpQuery::isMarkup($target)) {
  3376. // $target is new markup, import it
  3377. $insertTo = $this->documentWrapper->import($target);
  3378. // insert into selected element
  3379. } else {
  3380. // $tagret is a selector
  3381. $thisStack = $this->elements;
  3382. $this->toRoot();
  3383. $insertTo = $this->find($target)->elements;
  3384. $this->elements = $thisStack;
  3385. }
  3386. } else {
  3387. // INSERT FROM
  3388. $insertTo = $this->elements;
  3389. $insertFrom = $this->documentWrapper->import($target);
  3390. }
  3391. break;
  3392. case 'object':
  3393. $insertFrom = $insertTo = array();
  3394. // phpQuery
  3395. if ($target instanceof self) {
  3396. if ($to) {
  3397. $insertTo = $target->elements;
  3398. if ($this->documentFragment && $this->stackIsRoot())
  3399. // get all body children
  3400. // $loop = $this->find('body > *')->elements;
  3401. // TODO test it, test it hard...
  3402. // $loop = $this->newInstance($this->root)->find('> *')->elements;
  3403. $loop = $this->root->childNodes;
  3404. else
  3405. $loop = $this->elements;
  3406. // import nodes if needed
  3407. $insertFrom = $this->getDocumentID() == $target->getDocumentID()
  3408. ? $loop
  3409. : $target->documentWrapper->import($loop);
  3410. } else {
  3411. $insertTo = $this->elements;
  3412. if ($target->documentFragment && $target->stackIsRoot())
  3413. // get all body children
  3414. // $loop = $target->find('body > *')->elements;
  3415. $loop = $target->root->childNodes;
  3416. else
  3417. $loop = $target->elements;
  3418. // import nodes if needed
  3419. $insertFrom = $this->getDocumentID() == $target->getDocumentID()
  3420. ? $loop
  3421. : $this->documentWrapper->import($loop);
  3422. }
  3423. // DOMNODE
  3424. } elseif ($target instanceof DOMNODE) {
  3425. // import node if needed
  3426. // if ( $target->ownerDocument != $this->DOM )
  3427. // $target = $this->DOM->importNode($target, true);
  3428. if ($to) {
  3429. $insertTo = array($target);
  3430. if ($this->documentFragment && $this->stackIsRoot())
  3431. // get all body children
  3432. $loop = $this->root->childNodes;
  3433. // $loop = $this->find('body > *')->elements;
  3434. else
  3435. $loop = $this->elements;
  3436. foreach ($loop as $fromNode)
  3437. // import nodes if needed
  3438. $insertFrom[] = !$fromNode->ownerDocument->isSameNode($target->ownerDocument)
  3439. ? $target->ownerDocument->importNode($fromNode, true)
  3440. : $fromNode;
  3441. } else {
  3442. // import node if needed
  3443. if (!$target->ownerDocument->isSameNode($this->document))
  3444. $target = $this->document->importNode($target, true);
  3445. $insertTo = $this->elements;
  3446. $insertFrom[] = $target;
  3447. }
  3448. }
  3449. break;
  3450. }
  3451. phpQuery::debug("From " . count($insertFrom) . "; To " . count($insertTo) . " nodes");
  3452. foreach ($insertTo as $insertNumber => $toNode) {
  3453. // we need static relative elements in some cases
  3454. switch ($type) {
  3455. case 'prependTo':
  3456. case 'prepend':
  3457. $firstChild = $toNode->firstChild;
  3458. break;
  3459. case 'insertAfter':
  3460. case 'after':
  3461. $nextSibling = $toNode->nextSibling;
  3462. break;
  3463. }
  3464. foreach ($insertFrom as $fromNode) {
  3465. // clone if inserted already before
  3466. $insert = $insertNumber
  3467. ? $fromNode->cloneNode(true)
  3468. : $fromNode;
  3469. switch ($type) {
  3470. case 'appendTo':
  3471. case 'append':
  3472. // $toNode->insertBefore(
  3473. // $fromNode,
  3474. // $toNode->lastChild->nextSibling
  3475. // );
  3476. $toNode->appendChild($insert);
  3477. $eventTarget = $insert;
  3478. break;
  3479. case 'prependTo':
  3480. case 'prepend':
  3481. $toNode->insertBefore(
  3482. $insert,
  3483. $firstChild
  3484. );
  3485. break;
  3486. case 'insertBefore':
  3487. case 'before':
  3488. if (!$toNode->parentNode)
  3489. throw new Exception("No parentNode, can't do {$type}()");
  3490. else
  3491. $toNode->parentNode->insertBefore(
  3492. $insert,
  3493. $toNode
  3494. );
  3495. break;
  3496. case 'insertAfter':
  3497. case 'after':
  3498. if (!$toNode->parentNode)
  3499. throw new Exception("No parentNode, can't do {$type}()");
  3500. else
  3501. $toNode->parentNode->insertBefore(
  3502. $insert,
  3503. $nextSibling
  3504. );
  3505. break;
  3506. }
  3507. // Mutation event
  3508. $event = new DOMEvent(array(
  3509. 'target' => $insert,
  3510. 'type' => 'DOMNodeInserted'
  3511. ));
  3512. phpQueryEvents::trigger(
  3513. $this->getDocumentID(),
  3514. $event->type,
  3515. array($event),
  3516. $insert
  3517. );
  3518. }
  3519. }
  3520. return $this;
  3521. }
  3522. /**
  3523. * Enter description here...
  3524. *
  3525. * @return Int
  3526. */
  3527. public function index($subject)
  3528. {
  3529. $index = -1;
  3530. $subject = $subject instanceof phpQueryObject
  3531. ? $subject->elements[0]
  3532. : $subject;
  3533. foreach ($this->newInstance() as $k => $node) {
  3534. if ($node->isSameNode($subject))
  3535. $index = $k;
  3536. }
  3537. return $index;
  3538. }
  3539. /**
  3540. * Enter description here...
  3541. *
  3542. * @param unknown_type $start
  3543. * @param unknown_type $end
  3544. *
  3545. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3546. * @testme
  3547. */
  3548. public function slice($start, $end = null)
  3549. {
  3550. // $last = count($this->elements)-1;
  3551. // $end = $end
  3552. // ? min($end, $last)
  3553. // : $last;
  3554. // if ($start < 0)
  3555. // $start = $last+$start;
  3556. // if ($start > $last)
  3557. // return array();
  3558. if ($end > 0)
  3559. $end = $end - $start;
  3560. return $this->newInstance(
  3561. array_slice($this->elements, $start, $end)
  3562. );
  3563. }
  3564. /**
  3565. * Enter description here...
  3566. *
  3567. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3568. */
  3569. public function reverse()
  3570. {
  3571. $this->elementsBackup = $this->elements;
  3572. $this->elements = array_reverse($this->elements);
  3573. return $this->newInstance();
  3574. }
  3575. /**
  3576. * Return joined text content.
  3577. * @return String
  3578. */
  3579. public function text($text = null, $callback1 = null, $callback2 = null, $callback3 = null)
  3580. {
  3581. if (isset($text))
  3582. return $this->html(htmlspecialchars($text));
  3583. $args = func_get_args();
  3584. $args = array_slice($args, 1);
  3585. $return = '';
  3586. foreach ($this->elements as $node) {
  3587. $text = $node->textContent;
  3588. if (count($this->elements) > 1 && $text)
  3589. $text .= "\n";
  3590. foreach ($args as $callback) {
  3591. $text = phpQuery::callbackRun($callback, array($text));
  3592. }
  3593. $return .= $text;
  3594. }
  3595. return $return;
  3596. }
  3597. /**
  3598. * @return The text content of each matching element, like
  3599. * text() but returns an array with one entry per matched element.
  3600. * Read only.
  3601. */
  3602. public function texts($attr = null)
  3603. {
  3604. $results = array();
  3605. foreach ($this->elements as $node) {
  3606. $results[] = $node->textContent;
  3607. }
  3608. return $results;
  3609. }
  3610. /**
  3611. * Enter description here...
  3612. *
  3613. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3614. */
  3615. public function plugin($class, $file = null)
  3616. {
  3617. phpQuery::plugin($class, $file);
  3618. return $this;
  3619. }
  3620. /**
  3621. * Deprecated, use $pq->plugin() instead.
  3622. *
  3623. * @deprecated
  3624. * @param $class
  3625. * @param $file
  3626. * @return unknown_type
  3627. */
  3628. public static function extend($class, $file = null)
  3629. {
  3630. return $this->plugin($class, $file);
  3631. }
  3632. /**
  3633. *
  3634. * @access private
  3635. * @param $method
  3636. * @param $args
  3637. * @return unknown_type
  3638. */
  3639. public function __call($method, $args)
  3640. {
  3641. $aliasMethods = array('clone', 'empty');
  3642. if (isset(phpQuery::$extendMethods[$method])) {
  3643. array_unshift($args, $this);
  3644. return phpQuery::callbackRun(
  3645. phpQuery::$extendMethods[$method],
  3646. $args
  3647. );
  3648. } else if (isset(phpQuery::$pluginsMethods[$method])) {
  3649. array_unshift($args, $this);
  3650. $class = phpQuery::$pluginsMethods[$method];
  3651. $realClass = "phpQueryObjectPlugin_$class";
  3652. $return = call_user_func_array(
  3653. array($realClass, $method),
  3654. $args
  3655. );
  3656. // XXX deprecate ?
  3657. return is_null($return)
  3658. ? $this
  3659. : $return;
  3660. } else if (in_array($method, $aliasMethods)) {
  3661. return call_user_func_array(array($this, '_' . $method), $args);
  3662. } else
  3663. throw new Exception("Method '{$method}' doesnt exist");
  3664. }
  3665. /**
  3666. * Safe rename of next().
  3667. *
  3668. * Use it ONLY when need to call next() on an iterated object (in same time).
  3669. * Normaly there is no need to do such thing ;)
  3670. *
  3671. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3672. * @access private
  3673. */
  3674. public function _next($selector = null)
  3675. {
  3676. return $this->newInstance(
  3677. $this->getElementSiblings('nextSibling', $selector, true)
  3678. );
  3679. }
  3680. /**
  3681. * Use prev() and next().
  3682. *
  3683. * @deprecated
  3684. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3685. * @access private
  3686. */
  3687. public function _prev($selector = null)
  3688. {
  3689. return $this->prev($selector);
  3690. }
  3691. /**
  3692. * Enter description here...
  3693. *
  3694. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3695. */
  3696. public function prev($selector = null)
  3697. {
  3698. return $this->newInstance(
  3699. $this->getElementSiblings('previousSibling', $selector, true)
  3700. );
  3701. }
  3702. /**
  3703. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3704. * @todo
  3705. */
  3706. public function prevAll($selector = null)
  3707. {
  3708. return $this->newInstance(
  3709. $this->getElementSiblings('previousSibling', $selector)
  3710. );
  3711. }
  3712. /**
  3713. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3714. * @todo FIXME: returns source elements insted of next siblings
  3715. */
  3716. public function nextAll($selector = null)
  3717. {
  3718. return $this->newInstance(
  3719. $this->getElementSiblings('nextSibling', $selector)
  3720. );
  3721. }
  3722. /**
  3723. * @access private
  3724. */
  3725. protected function getElementSiblings($direction, $selector = null, $limitToOne = false)
  3726. {
  3727. $stack = array();
  3728. $count = 0;
  3729. foreach ($this->stack() as $node) {
  3730. $test = $node;
  3731. while (isset($test->{$direction}) && $test->{$direction}) {
  3732. $test = $test->{$direction};
  3733. if (!$test instanceof DOMELEMENT)
  3734. continue;
  3735. $stack[] = $test;
  3736. if ($limitToOne)
  3737. break;
  3738. }
  3739. }
  3740. if ($selector) {
  3741. $stackOld = $this->elements;
  3742. $this->elements = $stack;
  3743. $stack = $this->filter($selector, true)->stack();
  3744. $this->elements = $stackOld;
  3745. }
  3746. return $stack;
  3747. }
  3748. /**
  3749. * Enter description here...
  3750. *
  3751. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3752. */
  3753. public function siblings($selector = null)
  3754. {
  3755. $stack = array();
  3756. $siblings = array_merge(
  3757. $this->getElementSiblings('previousSibling', $selector),
  3758. $this->getElementSiblings('nextSibling', $selector)
  3759. );
  3760. foreach ($siblings as $node) {
  3761. if (!$this->elementsContainsNode($node, $stack))
  3762. $stack[] = $node;
  3763. }
  3764. return $this->newInstance($stack);
  3765. }
  3766. /**
  3767. * Enter description here...
  3768. *
  3769. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3770. */
  3771. public function not($selector = null)
  3772. {
  3773. if (is_string($selector))
  3774. phpQuery::debug(array('not', $selector));
  3775. else
  3776. phpQuery::debug('not');
  3777. $stack = array();
  3778. if ($selector instanceof self || $selector instanceof DOMNODE) {
  3779. foreach ($this->stack() as $node) {
  3780. if ($selector instanceof self) {
  3781. $matchFound = false;
  3782. foreach ($selector->stack() as $notNode) {
  3783. if ($notNode->isSameNode($node))
  3784. $matchFound = true;
  3785. }
  3786. if (!$matchFound)
  3787. $stack[] = $node;
  3788. } else if ($selector instanceof DOMNODE) {
  3789. if (!$selector->isSameNode($node))
  3790. $stack[] = $node;
  3791. } else {
  3792. if (!$this->is($selector))
  3793. $stack[] = $node;
  3794. }
  3795. }
  3796. } else {
  3797. $orgStack = $this->stack();
  3798. $matched = $this->filter($selector, true)->stack();
  3799. // $matched = array();
  3800. // // simulate OR in filter() instead of AND 5y
  3801. // foreach($this->parseSelector($selector) as $s) {
  3802. // $matched = array_merge($matched,
  3803. // $this->filter(array($s))->stack()
  3804. // );
  3805. // }
  3806. foreach ($orgStack as $node)
  3807. if (!$this->elementsContainsNode($node, $matched))
  3808. $stack[] = $node;
  3809. }
  3810. return $this->newInstance($stack);
  3811. }
  3812. /**
  3813. * Enter description here...
  3814. *
  3815. * @param string|phpQueryObject
  3816. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3817. */
  3818. public function add($selector = null)
  3819. {
  3820. if (!$selector)
  3821. return $this;
  3822. $stack = array();
  3823. $this->elementsBackup = $this->elements;
  3824. $found = phpQuery::pq($selector, $this->getDocumentID());
  3825. $this->merge($found->elements);
  3826. return $this->newInstance();
  3827. }
  3828. /**
  3829. * @access private
  3830. */
  3831. protected function merge()
  3832. {
  3833. foreach (func_get_args() as $nodes)
  3834. foreach ($nodes as $newNode)
  3835. if (!$this->elementsContainsNode($newNode))
  3836. $this->elements[] = $newNode;
  3837. }
  3838. /**
  3839. * @access private
  3840. * TODO refactor to stackContainsNode
  3841. */
  3842. protected function elementsContainsNode($nodeToCheck, $elementsStack = null)
  3843. {
  3844. $loop = !is_null($elementsStack)
  3845. ? $elementsStack
  3846. : $this->elements;
  3847. foreach ($loop as $node) {
  3848. if ($node->isSameNode($nodeToCheck))
  3849. return true;
  3850. }
  3851. return false;
  3852. }
  3853. /**
  3854. * Enter description here...
  3855. *
  3856. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3857. */
  3858. public function parent($selector = null)
  3859. {
  3860. $stack = array();
  3861. foreach ($this->elements as $node)
  3862. if ($node->parentNode && !$this->elementsContainsNode($node->parentNode, $stack))
  3863. $stack[] = $node->parentNode;
  3864. $this->elementsBackup = $this->elements;
  3865. $this->elements = $stack;
  3866. if ($selector)
  3867. $this->filter($selector, true);
  3868. return $this->newInstance();
  3869. }
  3870. /**
  3871. * Enter description here...
  3872. *
  3873. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  3874. */
  3875. public function parents($selector = null)
  3876. {
  3877. $stack = array();
  3878. if (!$this->elements)
  3879. $this->debug('parents() - stack empty');
  3880. foreach ($this->elements as $node) {
  3881. $test = $node;
  3882. while ($test->parentNode) {
  3883. $test = $test->parentNode;
  3884. if ($this->isRoot($test))
  3885. break;
  3886. if (!$this->elementsContainsNode($test, $stack)) {
  3887. $stack[] = $test;
  3888. continue;
  3889. }
  3890. }
  3891. }
  3892. $this->elementsBackup = $this->elements;
  3893. $this->elements = $stack;
  3894. if ($selector)
  3895. $this->filter($selector, true);
  3896. return $this->newInstance();
  3897. }
  3898. /**
  3899. * Internal stack iterator.
  3900. *
  3901. * @access private
  3902. */
  3903. public function stack($nodeTypes = null)
  3904. {
  3905. if (!isset($nodeTypes))
  3906. return $this->elements;
  3907. if (!is_array($nodeTypes))
  3908. $nodeTypes = array($nodeTypes);
  3909. $return = array();
  3910. foreach ($this->elements as $node) {
  3911. if (in_array($node->nodeType, $nodeTypes))
  3912. $return[] = $node;
  3913. }
  3914. return $return;
  3915. }
  3916. // TODO phpdoc; $oldAttr is result of hasAttribute, before any changes
  3917. protected function attrEvents($attr, $oldAttr, $oldValue, $node)
  3918. {
  3919. // skip events for XML documents
  3920. if (!$this->isXHTML() && !$this->isHTML())
  3921. return;
  3922. $event = null;
  3923. // identify
  3924. $isInputValue = $node->tagName == 'input'
  3925. && (in_array(
  3926. $node->getAttribute('type'),
  3927. array('text', 'password', 'hidden')
  3928. )
  3929. || !$node->getAttribute('type')
  3930. );
  3931. $isRadio = $node->tagName == 'input'
  3932. && $node->getAttribute('type') == 'radio';
  3933. $isCheckbox = $node->tagName == 'input'
  3934. && $node->getAttribute('type') == 'checkbox';
  3935. $isOption = $node->tagName == 'option';
  3936. if ($isInputValue && $attr == 'value' && $oldValue != $node->getAttribute($attr)) {
  3937. $event = new DOMEvent(array(
  3938. 'target' => $node,
  3939. 'type' => 'change'
  3940. ));
  3941. } else if (($isRadio || $isCheckbox) && $attr == 'checked' && (
  3942. // check
  3943. (!$oldAttr && $node->hasAttribute($attr))
  3944. // un-check
  3945. || (!$node->hasAttribute($attr) && $oldAttr)
  3946. )) {
  3947. $event = new DOMEvent(array(
  3948. 'target' => $node,
  3949. 'type' => 'change'
  3950. ));
  3951. } else if ($isOption && $node->parentNode && $attr == 'selected' && (
  3952. // select
  3953. (!$oldAttr && $node->hasAttribute($attr))
  3954. // un-select
  3955. || (!$node->hasAttribute($attr) && $oldAttr)
  3956. )) {
  3957. $event = new DOMEvent(array(
  3958. 'target' => $node->parentNode,
  3959. 'type' => 'change'
  3960. ));
  3961. }
  3962. if ($event) {
  3963. phpQueryEvents::trigger(
  3964. $this->getDocumentID(),
  3965. $event->type,
  3966. array($event),
  3967. $node
  3968. );
  3969. }
  3970. }
  3971. public function attr($attr = null, $value = null)
  3972. {
  3973. foreach ($this->stack(1) as $node) {
  3974. if (!is_null($value)) {
  3975. $loop = $attr == '*'
  3976. ? $this->getNodeAttrs($node)
  3977. : array($attr);
  3978. foreach ($loop as $a) {
  3979. $oldValue = $node->getAttribute($a);
  3980. $oldAttr = $node->hasAttribute($a);
  3981. // TODO raises an error when charset other than UTF-8
  3982. // while document's charset is also not UTF-8
  3983. @$node->setAttribute($a, $value);
  3984. $this->attrEvents($a, $oldAttr, $oldValue, $node);
  3985. }
  3986. } else if ($attr == '*') {
  3987. // jQuery difference
  3988. $return = array();
  3989. foreach ($node->attributes as $n => $v)
  3990. $return[$n] = $v->value;
  3991. return $return;
  3992. } else
  3993. return $node->hasAttribute($attr)
  3994. ? $node->getAttribute($attr)
  3995. : null;
  3996. }
  3997. return is_null($value)
  3998. ? '' : $this;
  3999. }
  4000. /**
  4001. * @return The same attribute of each matching element, like
  4002. * attr() but returns an array with one entry per matched element.
  4003. * Read only.
  4004. */
  4005. public function attrs($attr = null)
  4006. {
  4007. $results = array();
  4008. foreach ($this->stack(1) as $node) {
  4009. $results[] = $node->hasAttribute($attr)
  4010. ? $node->getAttribute($attr)
  4011. : null;
  4012. }
  4013. return $results;
  4014. }
  4015. /**
  4016. * @access private
  4017. */
  4018. protected function getNodeAttrs($node)
  4019. {
  4020. $return = array();
  4021. foreach ($node->attributes as $n => $o)
  4022. $return[] = $n;
  4023. return $return;
  4024. }
  4025. /**
  4026. * Enter description here...
  4027. *
  4028. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  4029. * @todo check CDATA ???
  4030. */
  4031. public function attrPHP($attr, $code)
  4032. {
  4033. if (!is_null($code)) {
  4034. $value = '<' . '?php ' . $code . ' ?' . '>';
  4035. // TODO tempolary solution
  4036. // http://code.google.com/p/phpquery/issues/detail?id=17
  4037. // if (function_exists('mb_detect_encoding') && mb_detect_encoding($value) == 'ASCII')
  4038. // $value = mb_convert_encoding($value, 'UTF-8', 'HTML-ENTITIES');
  4039. }
  4040. foreach ($this->stack(1) as $node) {
  4041. if (!is_null($code)) {
  4042. // $attrNode = $this->DOM->createAttribute($attr);
  4043. $node->setAttribute($attr, $value);
  4044. // $attrNode->value = $value;
  4045. // $node->appendChild($attrNode);
  4046. } else if ($attr == '*') {
  4047. // jQuery diff
  4048. $return = array();
  4049. foreach ($node->attributes as $n => $v)
  4050. $return[$n] = $v->value;
  4051. return $return;
  4052. } else
  4053. return $node->getAttribute($attr);
  4054. }
  4055. return $this;
  4056. }
  4057. /**
  4058. * Enter description here...
  4059. *
  4060. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  4061. */
  4062. public function removeAttr($attr)
  4063. {
  4064. foreach ($this->stack(1) as $node) {
  4065. $loop = $attr == '*'
  4066. ? $this->getNodeAttrs($node)
  4067. : array($attr);
  4068. foreach ($loop as $a) {
  4069. $oldValue = $node->getAttribute($a);
  4070. $node->removeAttribute($a);
  4071. $this->attrEvents($a, $oldValue, null, $node);
  4072. }
  4073. }
  4074. return $this;
  4075. }
  4076. /**
  4077. * Return form element value.
  4078. *
  4079. * @return String Fields value.
  4080. */
  4081. public function val($val = null)
  4082. {
  4083. if (!isset($val)) {
  4084. if ($this->eq(0)->is('select')) {
  4085. $selected = $this->eq(0)->find('option[selected=selected]');
  4086. if ($selected->is('[value]'))
  4087. return $selected->attr('value');
  4088. else
  4089. return $selected->text();
  4090. } else if ($this->eq(0)->is('textarea'))
  4091. return $this->eq(0)->markup();
  4092. else
  4093. return $this->eq(0)->attr('value');
  4094. } else {
  4095. $_val = null;
  4096. foreach ($this->stack(1) as $node) {
  4097. $node = pq($node, $this->getDocumentID());
  4098. if (is_array($val) && in_array($node->attr('type'), array('checkbox', 'radio'))) {
  4099. $isChecked = in_array($node->attr('value'), $val)
  4100. || in_array($node->attr('name'), $val);
  4101. if ($isChecked)
  4102. $node->attr('checked', 'checked');
  4103. else
  4104. $node->removeAttr('checked');
  4105. } else if ($node->get(0)->tagName == 'select') {
  4106. if (!isset($_val)) {
  4107. $_val = array();
  4108. if (!is_array($val))
  4109. $_val = array((string)$val);
  4110. else
  4111. foreach ($val as $v)
  4112. $_val[] = $v;
  4113. }
  4114. foreach ($node['option']->stack(1) as $option) {
  4115. $option = pq($option, $this->getDocumentID());
  4116. $selected = false;
  4117. // XXX: workaround for string comparsion, see issue #96
  4118. // http://code.google.com/p/phpquery/issues/detail?id=96
  4119. $selected = is_null($option->attr('value'))
  4120. ? in_array($option->markup(), $_val)
  4121. : in_array($option->attr('value'), $_val);
  4122. // $optionValue = $option->attr('value');
  4123. // $optionText = $option->text();
  4124. // $optionTextLenght = mb_strlen($optionText);
  4125. // foreach($_val as $v)
  4126. // if ($optionValue == $v)
  4127. // $selected = true;
  4128. // else if ($optionText == $v && $optionTextLenght == mb_strlen($v))
  4129. // $selected = true;
  4130. if ($selected)
  4131. $option->attr('selected', 'selected');
  4132. else
  4133. $option->removeAttr('selected');
  4134. }
  4135. } else if ($node->get(0)->tagName == 'textarea')
  4136. $node->markup($val);
  4137. else
  4138. $node->attr('value', $val);
  4139. }
  4140. }
  4141. return $this;
  4142. }
  4143. /**
  4144. * Enter description here...
  4145. *
  4146. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  4147. */
  4148. public function andSelf()
  4149. {
  4150. if ($this->previous)
  4151. $this->elements = array_merge($this->elements, $this->previous->elements);
  4152. return $this;
  4153. }
  4154. /**
  4155. * Enter description here...
  4156. *
  4157. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  4158. */
  4159. public function addClass($className)
  4160. {
  4161. if (!$className)
  4162. return $this;
  4163. foreach ($this->stack(1) as $node) {
  4164. if (!$this->is(".$className", $node))
  4165. $node->setAttribute(
  4166. 'class',
  4167. trim($node->getAttribute('class') . ' ' . $className)
  4168. );
  4169. }
  4170. return $this;
  4171. }
  4172. /**
  4173. * Enter description here...
  4174. *
  4175. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  4176. */
  4177. public function addClassPHP($className)
  4178. {
  4179. foreach ($this->stack(1) as $node) {
  4180. $classes = $node->getAttribute('class');
  4181. $newValue = $classes
  4182. ? $classes . ' <' . '?php ' . $className . ' ?' . '>'
  4183. : '<' . '?php ' . $className . ' ?' . '>';
  4184. $node->setAttribute('class', $newValue);
  4185. }
  4186. return $this;
  4187. }
  4188. /**
  4189. * Enter description here...
  4190. *
  4191. * @param string $className
  4192. * @return bool
  4193. */
  4194. public function hasClass($className)
  4195. {
  4196. foreach ($this->stack(1) as $node) {
  4197. if ($this->is(".$className", $node))
  4198. return true;
  4199. }
  4200. return false;
  4201. }
  4202. /**
  4203. * Enter description here...
  4204. *
  4205. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  4206. */
  4207. public function removeClass($className)
  4208. {
  4209. foreach ($this->stack(1) as $node) {
  4210. $classes = explode(' ', $node->getAttribute('class'));
  4211. if (in_array($className, $classes)) {
  4212. $classes = array_diff($classes, array($className));
  4213. if ($classes)
  4214. $node->setAttribute('class', implode(' ', $classes));
  4215. else
  4216. $node->removeAttribute('class');
  4217. }
  4218. }
  4219. return $this;
  4220. }
  4221. /**
  4222. * Enter description here...
  4223. *
  4224. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  4225. */
  4226. public function toggleClass($className)
  4227. {
  4228. foreach ($this->stack(1) as $node) {
  4229. if ($this->is($node, '.' . $className))
  4230. $this->removeClass($className);
  4231. else
  4232. $this->addClass($className);
  4233. }
  4234. return $this;
  4235. }
  4236. /**
  4237. * Proper name without underscore (just ->empty()) also works.
  4238. *
  4239. * Removes all child nodes from the set of matched elements.
  4240. *
  4241. * Example:
  4242. * pq("p")._empty()
  4243. *
  4244. * HTML:
  4245. * <p>Hello, <span>Person</span> <a href="#">and person</a></p>
  4246. *
  4247. * Result:
  4248. * [ <p></p> ]
  4249. *
  4250. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  4251. * @access private
  4252. */
  4253. public function _empty()
  4254. {
  4255. foreach ($this->stack(1) as $node) {
  4256. // thx to 'dave at dgx dot cz'
  4257. $node->nodeValue = '';
  4258. }
  4259. return $this;
  4260. }
  4261. /**
  4262. * Enter description here...
  4263. *
  4264. * @param array|string $callback Expects $node as first param, $index as second
  4265. * @param array $scope External variables passed to callback. Use compact('varName1', 'varName2'...) and extract($scope)
  4266. * @param array $arg1 Will ba passed as third and futher args to callback.
  4267. * @param array $arg2 Will ba passed as fourth and futher args to callback, and so on...
  4268. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  4269. */
  4270. public function each($callback, $param1 = null, $param2 = null, $param3 = null)
  4271. {
  4272. $paramStructure = null;
  4273. if (func_num_args() > 1) {
  4274. $paramStructure = func_get_args();
  4275. $paramStructure = array_slice($paramStructure, 1);
  4276. }
  4277. foreach ($this->elements as $v)
  4278. phpQuery::callbackRun($callback, array($v), $paramStructure);
  4279. return $this;
  4280. }
  4281. /**
  4282. * Run callback on actual object.
  4283. *
  4284. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  4285. */
  4286. public function callback($callback, $param1 = null, $param2 = null, $param3 = null)
  4287. {
  4288. $params = func_get_args();
  4289. $params[0] = $this;
  4290. phpQuery::callbackRun($callback, $params);
  4291. return $this;
  4292. }
  4293. /**
  4294. * Enter description here...
  4295. *
  4296. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  4297. * @todo add $scope and $args as in each() ???
  4298. */
  4299. public function map($callback, $param1 = null, $param2 = null, $param3 = null)
  4300. {
  4301. // $stack = array();
  4302. //// foreach($this->newInstance() as $node) {
  4303. // foreach($this->newInstance() as $node) {
  4304. // $result = call_user_func($callback, $node);
  4305. // if ($result)
  4306. // $stack[] = $result;
  4307. // }
  4308. $params = func_get_args();
  4309. array_unshift($params, $this->elements);
  4310. return $this->newInstance(
  4311. call_user_func_array(array('phpQuery', 'map'), $params)
  4312. // phpQuery::map($this->elements, $callback)
  4313. );
  4314. }
  4315. /**
  4316. * Enter description here...
  4317. *
  4318. * @param <type> $key
  4319. * @param <type> $value
  4320. */
  4321. public function data($key, $value = null)
  4322. {
  4323. if (!isset($value)) {
  4324. // TODO? implement specific jQuery behavior od returning parent values
  4325. // is child which we look up doesn't exist
  4326. return phpQuery::data($this->get(0), $key, $value, $this->getDocumentID());
  4327. } else {
  4328. foreach ($this as $node)
  4329. phpQuery::data($node, $key, $value, $this->getDocumentID());
  4330. return $this;
  4331. }
  4332. }
  4333. /**
  4334. * Enter description here...
  4335. *
  4336. * @param <type> $key
  4337. */
  4338. public function removeData($key)
  4339. {
  4340. foreach ($this as $node)
  4341. phpQuery::removeData($node, $key, $this->getDocumentID());
  4342. return $this;
  4343. }
  4344. // INTERFACE IMPLEMENTATIONS
  4345. // ITERATOR INTERFACE
  4346. /**
  4347. * @access private
  4348. */
  4349. #[\ReturnTypeWillChange]
  4350. public function rewind()
  4351. {
  4352. $this->debug('iterating foreach');
  4353. // phpQuery::selectDocument($this->getDocumentID());
  4354. $this->elementsBackup = $this->elements;
  4355. $this->elementsInterator = $this->elements;
  4356. $this->valid = isset($this->elements[0])
  4357. ? 1 : 0;
  4358. // $this->elements = $this->valid
  4359. // ? array($this->elements[0])
  4360. // : array();
  4361. $this->current = 0;
  4362. }
  4363. /**
  4364. * @access private
  4365. */
  4366. #[\ReturnTypeWillChange]
  4367. public function current()
  4368. {
  4369. return $this->elementsInterator[$this->current];
  4370. }
  4371. /**
  4372. * @access private
  4373. */
  4374. #[\ReturnTypeWillChange]
  4375. public function key()
  4376. {
  4377. return $this->current;
  4378. }
  4379. /**
  4380. * Double-function method.
  4381. *
  4382. * First: main iterator interface method.
  4383. * Second: Returning next sibling, alias for _next().
  4384. *
  4385. * Proper functionality is choosed automagicaly.
  4386. *
  4387. * @see phpQueryObject::_next()
  4388. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  4389. */
  4390. #[\ReturnTypeWillChange]
  4391. public function next($cssSelector = null)
  4392. {
  4393. // if ($cssSelector || $this->valid)
  4394. // return $this->_next($cssSelector);
  4395. $this->valid = isset($this->elementsInterator[$this->current + 1])
  4396. ? true
  4397. : false;
  4398. if (!$this->valid && $this->elementsInterator) {
  4399. $this->elementsInterator = null;
  4400. } else if ($this->valid) {
  4401. $this->current++;
  4402. } else {
  4403. return $this->_next($cssSelector);
  4404. }
  4405. }
  4406. /**
  4407. * @access private
  4408. */
  4409. #[\ReturnTypeWillChange]
  4410. public function valid()
  4411. {
  4412. return $this->valid;
  4413. }
  4414. // ITERATOR INTERFACE END
  4415. // ARRAYACCESS INTERFACE
  4416. /**
  4417. * @access private
  4418. */
  4419. #[\ReturnTypeWillChange]
  4420. public function offsetExists($offset)
  4421. {
  4422. return $this->find($offset)->size() > 0;
  4423. }
  4424. /**
  4425. * @access private
  4426. */
  4427. #[\ReturnTypeWillChange]
  4428. public function offsetGet($offset)
  4429. {
  4430. return $this->find($offset);
  4431. }
  4432. /**
  4433. * @access private
  4434. */
  4435. #[\ReturnTypeWillChange]
  4436. public function offsetSet($offset, $value)
  4437. {
  4438. // $this->find($offset)->replaceWith($value);
  4439. $this->find($offset)->html($value);
  4440. }
  4441. /**
  4442. * @access private
  4443. */
  4444. #[\ReturnTypeWillChange]
  4445. public function offsetUnset($offset)
  4446. {
  4447. // empty
  4448. throw new Exception("Can't do unset, use array interface only for calling queries and replacing HTML.");
  4449. }
  4450. // ARRAYACCESS INTERFACE END
  4451. /**
  4452. * Returns node's XPath.
  4453. *
  4454. * @param unknown_type $oneNode
  4455. * @return string
  4456. * @TODO use native getNodePath is avaible
  4457. * @access private
  4458. */
  4459. protected function getNodeXpath($oneNode = null, $namespace = null)
  4460. {
  4461. $return = array();
  4462. $loop = $oneNode
  4463. ? array($oneNode)
  4464. : $this->elements;
  4465. // if ($namespace)
  4466. // $namespace .= ':';
  4467. foreach ($loop as $node) {
  4468. if ($node instanceof DOMDOCUMENT) {
  4469. $return[] = '';
  4470. continue;
  4471. }
  4472. $xpath = array();
  4473. while (!($node instanceof DOMDOCUMENT)) {
  4474. $i = 1;
  4475. $sibling = $node;
  4476. while ($sibling->previousSibling) {
  4477. $sibling = $sibling->previousSibling;
  4478. $isElement = $sibling instanceof DOMELEMENT;
  4479. if ($isElement && $sibling->tagName == $node->tagName)
  4480. $i++;
  4481. }
  4482. $xpath[] = $this->isXML()
  4483. ? "*[local-name()='{$node->tagName}'][{$i}]"
  4484. : "{$node->tagName}[{$i}]";
  4485. $node = $node->parentNode;
  4486. }
  4487. $xpath = implode('/', array_reverse($xpath));
  4488. $return[] = '/' . $xpath;
  4489. }
  4490. return $oneNode
  4491. ? $return[0]
  4492. : $return;
  4493. }
  4494. // HELPERS
  4495. public function whois($oneNode = null)
  4496. {
  4497. $return = array();
  4498. $loop = $oneNode
  4499. ? array($oneNode)
  4500. : $this->elements;
  4501. foreach ($loop as $node) {
  4502. if (isset($node->tagName)) {
  4503. $tag = in_array($node->tagName, array('php', 'js'))
  4504. ? strtoupper($node->tagName)
  4505. : $node->tagName;
  4506. $return[] = $tag
  4507. . ($node->getAttribute('id')
  4508. ? '#' . $node->getAttribute('id') : '')
  4509. . ($node->getAttribute('class')
  4510. ? '.' . implode('.', explode(' ', $node->getAttribute('class'))) : '')
  4511. . ($node->getAttribute('name')
  4512. ? '[name="' . $node->getAttribute('name') . '"]' : '')
  4513. . ($node->getAttribute('value') && strpos($node->getAttribute('value'), '<' . '?php') === false
  4514. ? '[value="' . substr(str_replace("\n", '', $node->getAttribute('value')), 0, 15) . '"]' : '')
  4515. . ($node->getAttribute('value') && strpos($node->getAttribute('value'), '<' . '?php') !== false
  4516. ? '[value=PHP]' : '')
  4517. . ($node->getAttribute('selected')
  4518. ? '[selected]' : '')
  4519. . ($node->getAttribute('checked')
  4520. ? '[checked]' : '');
  4521. } else if ($node instanceof DOMTEXT) {
  4522. if (trim($node->textContent))
  4523. $return[] = 'Text:' . substr(str_replace("\n", ' ', $node->textContent), 0, 15);
  4524. } else {
  4525. }
  4526. }
  4527. return $oneNode && isset($return[0])
  4528. ? $return[0]
  4529. : $return;
  4530. }
  4531. /**
  4532. * Dump htmlOuter and preserve chain. Usefull for debugging.
  4533. *
  4534. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  4535. *
  4536. */
  4537. public function dump()
  4538. {
  4539. print 'DUMP #' . (phpQuery::$dumpCount++) . ' ';
  4540. $debug = phpQuery::$debug;
  4541. phpQuery::$debug = false;
  4542. // print __FILE__.':'.__LINE__."\n";
  4543. var_dump($this->htmlOuter());
  4544. return $this;
  4545. }
  4546. public function dumpWhois()
  4547. {
  4548. print 'DUMP #' . (phpQuery::$dumpCount++) . ' ';
  4549. $debug = phpQuery::$debug;
  4550. phpQuery::$debug = false;
  4551. // print __FILE__.':'.__LINE__."\n";
  4552. var_dump('whois', $this->whois());
  4553. phpQuery::$debug = $debug;
  4554. return $this;
  4555. }
  4556. public function dumpLength()
  4557. {
  4558. print 'DUMP #' . (phpQuery::$dumpCount++) . ' ';
  4559. $debug = phpQuery::$debug;
  4560. phpQuery::$debug = false;
  4561. // print __FILE__.':'.__LINE__."\n";
  4562. var_dump('length', $this->length());
  4563. phpQuery::$debug = $debug;
  4564. return $this;
  4565. }
  4566. public function dumpTree($html = true, $title = true)
  4567. {
  4568. $output = $title
  4569. ? 'DUMP #' . (phpQuery::$dumpCount++) . " \n" : '';
  4570. $debug = phpQuery::$debug;
  4571. phpQuery::$debug = false;
  4572. foreach ($this->stack() as $node)
  4573. $output .= $this->__dumpTree($node);
  4574. phpQuery::$debug = $debug;
  4575. print $html
  4576. ? nl2br(str_replace(' ', '&nbsp;', $output))
  4577. : $output;
  4578. return $this;
  4579. }
  4580. private function __dumpTree($node, $intend = 0)
  4581. {
  4582. $whois = $this->whois($node);
  4583. $return = '';
  4584. if ($whois)
  4585. $return .= str_repeat(' - ', $intend) . $whois . "\n";
  4586. if (isset($node->childNodes))
  4587. foreach ($node->childNodes as $chNode)
  4588. $return .= $this->__dumpTree($chNode, $intend + 1);
  4589. return $return;
  4590. }
  4591. /**
  4592. * Dump htmlOuter and stop script execution. Usefull for debugging.
  4593. *
  4594. */
  4595. public function dumpDie()
  4596. {
  4597. print __FILE__ . ':' . __LINE__;
  4598. var_dump($this->htmlOuter());
  4599. die();
  4600. }
  4601. }
  4602. // -- Multibyte Compatibility functions ---------------------------------------
  4603. // http://svn.iphonewebdev.com/lace/lib/mb_compat.php
  4604. /**
  4605. * mb_internal_encoding()
  4606. *
  4607. * Included for mbstring pseudo-compatability.
  4608. */
  4609. if (!function_exists('mb_internal_encoding')) {
  4610. function mb_internal_encoding($enc)
  4611. {
  4612. return true;
  4613. }
  4614. }
  4615. /**
  4616. * mb_regex_encoding()
  4617. *
  4618. * Included for mbstring pseudo-compatability.
  4619. */
  4620. if (!function_exists('mb_regex_encoding')) {
  4621. function mb_regex_encoding($enc)
  4622. {
  4623. return true;
  4624. }
  4625. }
  4626. /**
  4627. * mb_strlen()
  4628. *
  4629. * Included for mbstring pseudo-compatability.
  4630. */
  4631. if (!function_exists('mb_strlen')) {
  4632. function mb_strlen($str)
  4633. {
  4634. return strlen($str);
  4635. }
  4636. }
  4637. /**
  4638. * mb_strpos()
  4639. *
  4640. * Included for mbstring pseudo-compatability.
  4641. */
  4642. if (!function_exists('mb_strpos')) {
  4643. function mb_strpos($haystack, $needle, $offset = 0)
  4644. {
  4645. return strpos($haystack, $needle, $offset);
  4646. }
  4647. }
  4648. /**
  4649. * mb_stripos()
  4650. *
  4651. * Included for mbstring pseudo-compatability.
  4652. */
  4653. if (!function_exists('mb_stripos')) {
  4654. function mb_stripos($haystack, $needle, $offset = 0)
  4655. {
  4656. return stripos($haystack, $needle, $offset);
  4657. }
  4658. }
  4659. /**
  4660. * mb_substr()
  4661. *
  4662. * Included for mbstring pseudo-compatability.
  4663. */
  4664. if (!function_exists('mb_substr')) {
  4665. function mb_substr($str, $start, $length = 0)
  4666. {
  4667. return substr($str, $start, $length);
  4668. }
  4669. }
  4670. /**
  4671. * mb_substr_count()
  4672. *
  4673. * Included for mbstring pseudo-compatability.
  4674. */
  4675. if (!function_exists('mb_substr_count')) {
  4676. function mb_substr_count($haystack, $needle)
  4677. {
  4678. return substr_count($haystack, $needle);
  4679. }
  4680. }
  4681. /**
  4682. * Static namespace for phpQuery functions.
  4683. *
  4684. * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
  4685. * @package phpQuery
  4686. */
  4687. abstract class phpQuery
  4688. {
  4689. /**
  4690. * XXX: Workaround for mbstring problems
  4691. *
  4692. * @var bool
  4693. */
  4694. public static $mbstringSupport = true;
  4695. public static $debug = false;
  4696. public static $documents = array();
  4697. public static $defaultDocumentID = null;
  4698. // public static $defaultDoctype = 'html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"';
  4699. /**
  4700. * Applies only to HTML.
  4701. *
  4702. * @var unknown_type
  4703. */
  4704. public static $defaultDoctype = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  4705. "http://www.w3.org/TR/html4/loose.dtd">';
  4706. public static $defaultCharset = 'UTF-8';
  4707. /**
  4708. * Static namespace for plugins.
  4709. *
  4710. * @var object
  4711. */
  4712. public static $plugins = array();
  4713. /**
  4714. * List of loaded plugins.
  4715. *
  4716. * @var unknown_type
  4717. */
  4718. public static $pluginsLoaded = array();
  4719. public static $pluginsMethods = array();
  4720. public static $pluginsStaticMethods = array();
  4721. public static $extendMethods = array();
  4722. /**
  4723. * @TODO implement
  4724. */
  4725. public static $extendStaticMethods = array();
  4726. /**
  4727. * Hosts allowed for AJAX connections.
  4728. * Dot '.' means $_SERVER['HTTP_HOST'] (if any).
  4729. *
  4730. * @var array
  4731. */
  4732. public static $ajaxAllowedHosts = array(
  4733. '.'
  4734. );
  4735. /**
  4736. * AJAX settings.
  4737. *
  4738. * @var array
  4739. * XXX should it be static or not ?
  4740. */
  4741. public static $ajaxSettings = array(
  4742. 'url' => '', //TODO
  4743. 'global' => true,
  4744. 'type' => "GET",
  4745. 'timeout' => null,
  4746. 'contentType' => "application/x-www-form-urlencoded",
  4747. 'processData' => true,
  4748. // 'async' => true,
  4749. 'data' => null,
  4750. 'username' => null,
  4751. 'password' => null,
  4752. 'accepts' => array(
  4753. 'xml' => "application/xml, text/xml",
  4754. 'html' => "text/html",
  4755. 'script' => "text/javascript, application/javascript",
  4756. 'json' => "application/json, text/javascript",
  4757. 'text' => "text/plain",
  4758. '_default' => "*/*"
  4759. )
  4760. );
  4761. public static $lastModified = null;
  4762. public static $active = 0;
  4763. public static $dumpCount = 0;
  4764. /**
  4765. * Multi-purpose function.
  4766. * Use pq() as shortcut.
  4767. *
  4768. * In below examples, $pq is any result of pq(); function.
  4769. *
  4770. * 1. Import markup into existing document (without any attaching):
  4771. * - Import into selected document:
  4772. * pq('<div/>') // DOESNT accept text nodes at beginning of input string !
  4773. * - Import into document with ID from $pq->getDocumentID():
  4774. * pq('<div/>', $pq->getDocumentID())
  4775. * - Import into same document as DOMNode belongs to:
  4776. * pq('<div/>', DOMNode)
  4777. * - Import into document from phpQuery object:
  4778. * pq('<div/>', $pq)
  4779. *
  4780. * 2. Run query:
  4781. * - Run query on last selected document:
  4782. * pq('div.myClass')
  4783. * - Run query on document with ID from $pq->getDocumentID():
  4784. * pq('div.myClass', $pq->getDocumentID())
  4785. * - Run query on same document as DOMNode belongs to and use node(s)as root for query:
  4786. * pq('div.myClass', DOMNode)
  4787. * - Run query on document from phpQuery object
  4788. * and use object's stack as root node(s) for query:
  4789. * pq('div.myClass', $pq)
  4790. *
  4791. * @param string|DOMNode|DOMNodeList|array $arg1 HTML markup, CSS Selector, DOMNode or array of DOMNodes
  4792. * @param string|phpQueryObject|DOMNode $context DOM ID from $pq->getDocumentID(), phpQuery object (determines also query root) or DOMNode (determines also query root)
  4793. *
  4794. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery|QueryTemplatesPhpQuery|false
  4795. * phpQuery object or false in case of error.
  4796. */
  4797. public static function pq($arg1, $context = null)
  4798. {
  4799. if ($arg1 instanceof DOMNODE && !isset($context)) {
  4800. foreach (phpQuery::$documents as $documentWrapper) {
  4801. $compare = $arg1 instanceof DOMDocument
  4802. ? $arg1 : $arg1->ownerDocument;
  4803. if ($documentWrapper->document->isSameNode($compare))
  4804. $context = $documentWrapper->id;
  4805. }
  4806. }
  4807. if (!$context) {
  4808. $domId = self::$defaultDocumentID;
  4809. if (!$domId)
  4810. throw new Exception("Can't use last created DOM, because there isn't any. Use phpQuery::newDocument() first.");
  4811. // } else if (is_object($context) && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject')))
  4812. } else if (is_object($context) && $context instanceof phpQueryObject)
  4813. $domId = $context->getDocumentID();
  4814. else if ($context instanceof DOMDOCUMENT) {
  4815. $domId = self::getDocumentID($context);
  4816. if (!$domId) {
  4817. //throw new Exception('Orphaned DOMDocument');
  4818. $domId = self::newDocument($context)->getDocumentID();
  4819. }
  4820. } else if ($context instanceof DOMNODE) {
  4821. $domId = self::getDocumentID($context);
  4822. if (!$domId) {
  4823. throw new Exception('Orphaned DOMNode');
  4824. // $domId = self::newDocument($context->ownerDocument);
  4825. }
  4826. } else
  4827. $domId = $context;
  4828. if ($arg1 instanceof phpQueryObject) {
  4829. // if (is_object($arg1) && (get_class($arg1) == 'phpQueryObject' || $arg1 instanceof PHPQUERY || is_subclass_of($arg1, 'phpQueryObject'))) {
  4830. /**
  4831. * Return $arg1 or import $arg1 stack if document differs:
  4832. * pq(pq('<div/>'))
  4833. */
  4834. if ($arg1->getDocumentID() == $domId)
  4835. return $arg1;
  4836. $class = get_class($arg1);
  4837. // support inheritance by passing old object to overloaded constructor
  4838. $phpQuery = $class != 'phpQuery'
  4839. ? new $class($arg1, $domId)
  4840. : new phpQueryObject($domId);
  4841. $phpQuery->elements = array();
  4842. foreach ($arg1->elements as $node)
  4843. $phpQuery->elements[] = $phpQuery->document->importNode($node, true);
  4844. return $phpQuery;
  4845. } else if ($arg1 instanceof DOMNODE || (is_array($arg1) && isset($arg1[0]) && $arg1[0] instanceof DOMNODE)) {
  4846. /*
  4847. * Wrap DOM nodes with phpQuery object, import into document when needed:
  4848. * pq(array($domNode1, $domNode2))
  4849. */
  4850. $phpQuery = new phpQueryObject($domId);
  4851. if (!($arg1 instanceof DOMNODELIST) && !is_array($arg1))
  4852. $arg1 = array($arg1);
  4853. $phpQuery->elements = array();
  4854. foreach ($arg1 as $node) {
  4855. $sameDocument = $node->ownerDocument instanceof DOMDOCUMENT
  4856. && !$node->ownerDocument->isSameNode($phpQuery->document);
  4857. $phpQuery->elements[] = $sameDocument
  4858. ? $phpQuery->document->importNode($node, true)
  4859. : $node;
  4860. }
  4861. return $phpQuery;
  4862. } else if (self::isMarkup($arg1)) {
  4863. /**
  4864. * Import HTML:
  4865. * pq('<div/>')
  4866. */
  4867. $phpQuery = new phpQueryObject($domId);
  4868. return $phpQuery->newInstance(
  4869. $phpQuery->documentWrapper->import($arg1)
  4870. );
  4871. } else {
  4872. /**
  4873. * Run CSS query:
  4874. * pq('div.myClass')
  4875. */
  4876. $phpQuery = new phpQueryObject($domId);
  4877. // if ($context && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject')))
  4878. if ($context && $context instanceof phpQueryObject)
  4879. $phpQuery->elements = $context->elements;
  4880. else if ($context && $context instanceof DOMNODELIST) {
  4881. $phpQuery->elements = array();
  4882. foreach ($context as $node)
  4883. $phpQuery->elements[] = $node;
  4884. } else if ($context && $context instanceof DOMNODE)
  4885. $phpQuery->elements = array($context);
  4886. return $phpQuery->find($arg1);
  4887. }
  4888. }
  4889. /**
  4890. * Sets default document to $id. Document has to be loaded prior
  4891. * to using this method.
  4892. * $id can be retrived via getDocumentID() or getDocumentIDRef().
  4893. *
  4894. * @param unknown_type $id
  4895. */
  4896. public static function selectDocument($id)
  4897. {
  4898. $id = self::getDocumentID($id);
  4899. self::debug("Selecting document '$id' as default one");
  4900. self::$defaultDocumentID = self::getDocumentID($id);
  4901. }
  4902. /**
  4903. * Returns document with id $id or last used as phpQueryObject.
  4904. * $id can be retrived via getDocumentID() or getDocumentIDRef().
  4905. * Chainable.
  4906. *
  4907. * @see phpQuery::selectDocument()
  4908. * @param unknown_type $id
  4909. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  4910. */
  4911. public static function getDocument($id = null)
  4912. {
  4913. if ($id)
  4914. phpQuery::selectDocument($id);
  4915. else
  4916. $id = phpQuery::$defaultDocumentID;
  4917. return new phpQueryObject($id);
  4918. }
  4919. /**
  4920. * Creates new document from markup.
  4921. * Chainable.
  4922. *
  4923. * @param unknown_type $markup
  4924. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  4925. */
  4926. public static function newDocument($markup = null, $contentType = null)
  4927. {
  4928. if (!$markup)
  4929. $markup = '';
  4930. $documentID = phpQuery::createDocumentWrapper($markup, $contentType);
  4931. return new phpQueryObject($documentID);
  4932. }
  4933. /**
  4934. * Creates new document from markup.
  4935. * Chainable.
  4936. *
  4937. * @param unknown_type $markup
  4938. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  4939. */
  4940. public static function newDocumentHTML($markup = null, $charset = null)
  4941. {
  4942. $contentType = $charset
  4943. ? ";charset=$charset"
  4944. : '';
  4945. return self::newDocument($markup, "text/html{$contentType}");
  4946. }
  4947. /**
  4948. * Creates new document from markup.
  4949. * Chainable.
  4950. *
  4951. * @param unknown_type $markup
  4952. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  4953. */
  4954. public static function newDocumentXML($markup = null, $charset = null)
  4955. {
  4956. $contentType = $charset
  4957. ? ";charset=$charset"
  4958. : '';
  4959. return self::newDocument($markup, "text/xml{$contentType}");
  4960. }
  4961. /**
  4962. * Creates new document from markup.
  4963. * Chainable.
  4964. *
  4965. * @param unknown_type $markup
  4966. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  4967. */
  4968. public static function newDocumentXHTML($markup = null, $charset = null)
  4969. {
  4970. $contentType = $charset
  4971. ? ";charset=$charset"
  4972. : '';
  4973. return self::newDocument($markup, "application/xhtml+xml{$contentType}");
  4974. }
  4975. /**
  4976. * Creates new document from markup.
  4977. * Chainable.
  4978. *
  4979. * @param unknown_type $markup
  4980. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  4981. */
  4982. public static function newDocumentPHP($markup = null, $contentType = "text/html")
  4983. {
  4984. // TODO pass charset to phpToMarkup if possible (use DOMDocumentWrapper function)
  4985. $markup = phpQuery::phpToMarkup($markup, self::$defaultCharset);
  4986. return self::newDocument($markup, $contentType);
  4987. }
  4988. public static function phpToMarkup($php, $charset = 'utf-8')
  4989. {
  4990. $regexes = array(
  4991. '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)<' . '?php?(.*?)(?:\\?>)([^\']*)\'@s',
  4992. '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)<' . '?php?(.*?)(?:\\?>)([^"]*)"@s',
  4993. );
  4994. foreach ($regexes as $regex)
  4995. while (preg_match($regex, $php, $matches)) {
  4996. $php = preg_replace_callback(
  4997. $regex,
  4998. // create_function('$m, $charset = "'.$charset.'"',
  4999. // 'return $m[1].$m[2]
  5000. // .htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset)
  5001. // .$m[5].$m[2];'
  5002. // ),
  5003. array('phpQuery', '_phpToMarkupCallback'),
  5004. $php
  5005. );
  5006. }
  5007. $regex = '@(^|>[^<]*)+?(<\?php(.*?)(\?>))@s';
  5008. //preg_match_all($regex, $php, $matches);
  5009. //var_dump($matches);
  5010. $php = preg_replace($regex, '\\1<php><!-- \\3 --></php>', $php);
  5011. return $php;
  5012. }
  5013. public static function _phpToMarkupCallback($php, $charset = 'utf-8')
  5014. {
  5015. return $m[1] . $m[2]
  5016. . htmlspecialchars("<" . "?php" . $m[4] . "?" . ">", ENT_QUOTES | ENT_NOQUOTES, $charset)
  5017. . $m[5] . $m[2];
  5018. }
  5019. public static function _markupToPHPCallback($m)
  5020. {
  5021. return "<" . "?php " . htmlspecialchars_decode($m[1]) . " ?" . ">";
  5022. }
  5023. /**
  5024. * Converts document markup containing PHP code generated by phpQuery::php()
  5025. * into valid (executable) PHP code syntax.
  5026. *
  5027. * @param string|phpQueryObject $content
  5028. * @return string PHP code.
  5029. */
  5030. public static function markupToPHP($content)
  5031. {
  5032. if ($content instanceof phpQueryObject)
  5033. $content = $content->markupOuter();
  5034. /* <php>...</php> to <?php...? > */
  5035. $content = preg_replace_callback(
  5036. '@<php>\s*<!--(.*?)-->\s*</php>@s',
  5037. // create_function('$m',
  5038. // 'return "<'.'?php ".htmlspecialchars_decode($m[1])." ?'.'>";'
  5039. // ),
  5040. array('phpQuery', '_markupToPHPCallback'),
  5041. $content
  5042. );
  5043. /* <node attr='< ?php ? >'> extra space added to save highlighters */
  5044. $regexes = array(
  5045. '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)(?:&lt;|%3C)\\?(?:php)?(.*?)(?:\\?(?:&gt;|%3E))([^\']*)\'@s',
  5046. '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)(?:&lt;|%3C)\\?(?:php)?(.*?)(?:\\?(?:&gt;|%3E))([^"]*)"@s',
  5047. );
  5048. foreach ($regexes as $regex)
  5049. while (preg_match($regex, $content))
  5050. $content = preg_replace_callback(
  5051. $regex,
  5052. function ($m) {
  5053. return $m[1] . $m[2] . $m[3] . "<?php "
  5054. . str_replace(
  5055. array("%20", "%3E", "%09", "&#10;", "&#9;", "%7B", "%24", "%7D", "%22", "%5B", "%5D"),
  5056. array(" ", ">", " ", "\n", " ", "{", "$", "}", '"', "[", "]"),
  5057. htmlspecialchars_decode($m[4])
  5058. )
  5059. . " ?>" . $m[5] . $m[2];
  5060. },
  5061. $content
  5062. );
  5063. return $content;
  5064. }
  5065. /**
  5066. * Creates new document from file $file.
  5067. * Chainable.
  5068. *
  5069. * @param string $file URLs allowed. See File wrapper page at php.net for more supported sources.
  5070. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  5071. */
  5072. public static function newDocumentFile($file, $contentType = null)
  5073. {
  5074. $documentID = self::createDocumentWrapper(
  5075. file_get_contents($file),
  5076. $contentType
  5077. );
  5078. return new phpQueryObject($documentID);
  5079. }
  5080. /**
  5081. * Creates new document from markup.
  5082. * Chainable.
  5083. *
  5084. * @param unknown_type $markup
  5085. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  5086. */
  5087. public static function newDocumentFileHTML($file, $charset = null)
  5088. {
  5089. $contentType = $charset
  5090. ? ";charset=$charset"
  5091. : '';
  5092. return self::newDocumentFile($file, "text/html{$contentType}");
  5093. }
  5094. /**
  5095. * Creates new document from markup.
  5096. * Chainable.
  5097. *
  5098. * @param unknown_type $markup
  5099. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  5100. */
  5101. public static function newDocumentFileXML($file, $charset = null)
  5102. {
  5103. $contentType = $charset
  5104. ? ";charset=$charset"
  5105. : '';
  5106. return self::newDocumentFile($file, "text/xml{$contentType}");
  5107. }
  5108. /**
  5109. * Creates new document from markup.
  5110. * Chainable.
  5111. *
  5112. * @param unknown_type $markup
  5113. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  5114. */
  5115. public static function newDocumentFileXHTML($file, $charset = null)
  5116. {
  5117. $contentType = $charset
  5118. ? ";charset=$charset"
  5119. : '';
  5120. return self::newDocumentFile($file, "application/xhtml+xml{$contentType}");
  5121. }
  5122. /**
  5123. * Creates new document from markup.
  5124. * Chainable.
  5125. *
  5126. * @param unknown_type $markup
  5127. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  5128. */
  5129. public static function newDocumentFilePHP($file, $contentType = null)
  5130. {
  5131. return self::newDocumentPHP(file_get_contents($file), $contentType);
  5132. }
  5133. /**
  5134. * Reuses existing DOMDocument object.
  5135. * Chainable.
  5136. *
  5137. * @param $document DOMDocument
  5138. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  5139. * @TODO support DOMDocument
  5140. */
  5141. public static function loadDocument($document)
  5142. {
  5143. // TODO
  5144. die('TODO loadDocument');
  5145. }
  5146. /**
  5147. * Enter description here...
  5148. *
  5149. * @param unknown_type $html
  5150. * @param unknown_type $domId
  5151. * @return unknown New DOM ID
  5152. * @todo support PHP tags in input
  5153. * @todo support passing DOMDocument object from self::loadDocument
  5154. */
  5155. protected static function createDocumentWrapper($html, $contentType = null, $documentID = null)
  5156. {
  5157. if (function_exists('domxml_open_mem'))
  5158. throw new Exception("Old PHP4 DOM XML extension detected. phpQuery won't work until this extension is enabled.");
  5159. // $id = $documentID
  5160. // ? $documentID
  5161. // : md5(microtime());
  5162. $document = null;
  5163. if ($html instanceof DOMDOCUMENT) {
  5164. if (self::getDocumentID($html)) {
  5165. // document already exists in phpQuery::$documents, make a copy
  5166. $document = clone $html;
  5167. } else {
  5168. // new document, add it to phpQuery::$documents
  5169. $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID);
  5170. }
  5171. } else {
  5172. $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID);
  5173. }
  5174. // $wrapper->id = $id;
  5175. // bind document
  5176. phpQuery::$documents[$wrapper->id] = $wrapper;
  5177. // remember last loaded document
  5178. phpQuery::selectDocument($wrapper->id);
  5179. return $wrapper->id;
  5180. }
  5181. /**
  5182. * Extend class namespace.
  5183. *
  5184. * @param string|array $target
  5185. * @param array $source
  5186. * @TODO support string $source
  5187. * @return unknown_type
  5188. */
  5189. public static function extend($target, $source)
  5190. {
  5191. switch ($target) {
  5192. case 'phpQueryObject':
  5193. $targetRef = &self::$extendMethods;
  5194. $targetRef2 = &self::$pluginsMethods;
  5195. break;
  5196. case 'phpQuery':
  5197. $targetRef = &self::$extendStaticMethods;
  5198. $targetRef2 = &self::$pluginsStaticMethods;
  5199. break;
  5200. default:
  5201. throw new Exception("Unsupported \$target type");
  5202. }
  5203. if (is_string($source))
  5204. $source = array($source => $source);
  5205. foreach ($source as $method => $callback) {
  5206. if (isset($targetRef[$method])) {
  5207. // throw new Exception
  5208. self::debug("Duplicate method '{$method}', can\'t extend '{$target}'");
  5209. continue;
  5210. }
  5211. if (isset($targetRef2[$method])) {
  5212. // throw new Exception
  5213. self::debug("Duplicate method '{$method}' from plugin '{$targetRef2[$method]}',"
  5214. . " can\'t extend '{$target}'");
  5215. continue;
  5216. }
  5217. $targetRef[$method] = $callback;
  5218. }
  5219. return true;
  5220. }
  5221. /**
  5222. * Extend phpQuery with $class from $file.
  5223. *
  5224. * @param string $class Extending class name. Real class name can be prepended phpQuery_.
  5225. * @param string $file Filename to include. Defaults to "{$class}.php".
  5226. */
  5227. public static function plugin($class, $file = null)
  5228. {
  5229. // TODO $class checked agains phpQuery_$class
  5230. // if (strpos($class, 'phpQuery') === 0)
  5231. // $class = substr($class, 8);
  5232. if (in_array($class, self::$pluginsLoaded))
  5233. return true;
  5234. if (!$file)
  5235. $file = $class . '.php';
  5236. $objectClassExists = class_exists('phpQueryObjectPlugin_' . $class);
  5237. $staticClassExists = class_exists('phpQueryPlugin_' . $class);
  5238. if (!$objectClassExists && !$staticClassExists)
  5239. require_once($file);
  5240. self::$pluginsLoaded[] = $class;
  5241. // static methods
  5242. if (class_exists('phpQueryPlugin_' . $class)) {
  5243. $realClass = 'phpQueryPlugin_' . $class;
  5244. $vars = get_class_vars($realClass);
  5245. $loop = isset($vars['phpQueryMethods'])
  5246. && !is_null($vars['phpQueryMethods'])
  5247. ? $vars['phpQueryMethods']
  5248. : get_class_methods($realClass);
  5249. foreach ($loop as $method) {
  5250. if ($method == '__initialize')
  5251. continue;
  5252. if (!is_callable(array($realClass, $method)))
  5253. continue;
  5254. if (isset(self::$pluginsStaticMethods[$method])) {
  5255. throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '" . self::$pluginsStaticMethods[$method] . "'");
  5256. return;
  5257. }
  5258. self::$pluginsStaticMethods[$method] = $class;
  5259. }
  5260. if (method_exists($realClass, '__initialize'))
  5261. call_user_func_array(array($realClass, '__initialize'), array());
  5262. }
  5263. // object methods
  5264. if (class_exists('phpQueryObjectPlugin_' . $class)) {
  5265. $realClass = 'phpQueryObjectPlugin_' . $class;
  5266. $vars = get_class_vars($realClass);
  5267. $loop = isset($vars['phpQueryMethods'])
  5268. && !is_null($vars['phpQueryMethods'])
  5269. ? $vars['phpQueryMethods']
  5270. : get_class_methods($realClass);
  5271. foreach ($loop as $method) {
  5272. if (!is_callable(array($realClass, $method)))
  5273. continue;
  5274. if (isset(self::$pluginsMethods[$method])) {
  5275. throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '" . self::$pluginsMethods[$method] . "'");
  5276. continue;
  5277. }
  5278. self::$pluginsMethods[$method] = $class;
  5279. }
  5280. }
  5281. return true;
  5282. }
  5283. /**
  5284. * Unloades all or specified document from memory.
  5285. *
  5286. * @param mixed $documentID @see phpQuery::getDocumentID() for supported types.
  5287. */
  5288. public static function unloadDocuments($id = null)
  5289. {
  5290. if (isset($id)) {
  5291. if ($id = self::getDocumentID($id))
  5292. unset(phpQuery::$documents[$id]);
  5293. } else {
  5294. foreach (phpQuery::$documents as $k => $v) {
  5295. unset(phpQuery::$documents[$k]);
  5296. }
  5297. }
  5298. }
  5299. /**
  5300. * Parses phpQuery object or HTML result against PHP tags and makes them active.
  5301. *
  5302. * @param phpQuery|string $content
  5303. * @deprecated
  5304. * @return string
  5305. */
  5306. public static function unsafePHPTags($content)
  5307. {
  5308. return self::markupToPHP($content);
  5309. }
  5310. public static function DOMNodeListToArray($DOMNodeList)
  5311. {
  5312. $array = array();
  5313. if (!$DOMNodeList)
  5314. return $array;
  5315. foreach ($DOMNodeList as $node)
  5316. $array[] = $node;
  5317. return $array;
  5318. }
  5319. /**
  5320. * Checks if $input is HTML string, which has to start with '<'.
  5321. *
  5322. * @deprecated
  5323. * @param String $input
  5324. * @return Bool
  5325. * @todo still used ?
  5326. */
  5327. public static function isMarkup($input)
  5328. {
  5329. return !is_array($input) && substr(trim($input), 0, 1) == '<';
  5330. }
  5331. public static function debug($text)
  5332. {
  5333. if (self::$debug)
  5334. print var_dump($text);
  5335. }
  5336. /**
  5337. * Make an AJAX request.
  5338. *
  5339. * @param array See $options http://docs.jquery.com/Ajax/jQuery.ajax#toptions
  5340. * Additional options are:
  5341. * 'document' - document for global events, @see phpQuery::getDocumentID()
  5342. * 'referer' - implemented
  5343. * 'requested_with' - TODO; not implemented (X-Requested-With)
  5344. * @return Zend_Http_Client
  5345. * @link http://docs.jquery.com/Ajax/jQuery.ajax
  5346. *
  5347. * @TODO $options['cache']
  5348. * @TODO $options['processData']
  5349. * @TODO $options['xhr']
  5350. * @TODO $options['data'] as string
  5351. * @TODO XHR interface
  5352. */
  5353. public static function ajax($options = array(), $xhr = null)
  5354. {
  5355. $options = array_merge(
  5356. self::$ajaxSettings,
  5357. $options
  5358. );
  5359. $documentID = isset($options['document'])
  5360. ? self::getDocumentID($options['document'])
  5361. : null;
  5362. if ($xhr) {
  5363. // reuse existing XHR object, but clean it up
  5364. $client = $xhr;
  5365. // $client->setParameterPost(null);
  5366. // $client->setParameterGet(null);
  5367. $client->setAuth(false);
  5368. $client->setHeaders("If-Modified-Since", null);
  5369. $client->setHeaders("Referer", null);
  5370. $client->resetParameters();
  5371. } else {
  5372. // create new XHR object
  5373. require_once('Zend/Http/Client.php');
  5374. $client = new Zend_Http_Client();
  5375. $client->setCookieJar();
  5376. }
  5377. if (isset($options['timeout']))
  5378. $client->setConfig(array(
  5379. 'timeout' => $options['timeout'],
  5380. ));
  5381. // 'maxredirects' => 0,
  5382. foreach (self::$ajaxAllowedHosts as $k => $host)
  5383. if ($host == '.' && isset($_SERVER['HTTP_HOST']))
  5384. self::$ajaxAllowedHosts[$k] = $_SERVER['HTTP_HOST'];
  5385. $host = parse_url($options['url'], PHP_URL_HOST);
  5386. if (!in_array($host, self::$ajaxAllowedHosts)) {
  5387. throw new Exception("Request not permitted, host '$host' not present in "
  5388. . "phpQuery::\$ajaxAllowedHosts");
  5389. }
  5390. // JSONP
  5391. $jsre = "/=\\?(&|$)/";
  5392. if (isset($options['dataType']) && $options['dataType'] == 'jsonp') {
  5393. $jsonpCallbackParam = $options['jsonp']
  5394. ? $options['jsonp'] : 'callback';
  5395. if (strtolower($options['type']) == 'get') {
  5396. if (!preg_match($jsre, $options['url'])) {
  5397. $sep = strpos($options['url'], '?')
  5398. ? '&' : '?';
  5399. $options['url'] .= "$sep$jsonpCallbackParam=?";
  5400. }
  5401. } else if ($options['data']) {
  5402. $jsonp = false;
  5403. foreach ($options['data'] as $n => $v) {
  5404. if ($v == '?')
  5405. $jsonp = true;
  5406. }
  5407. if (!$jsonp) {
  5408. $options['data'][$jsonpCallbackParam] = '?';
  5409. }
  5410. }
  5411. $options['dataType'] = 'json';
  5412. }
  5413. if (isset($options['dataType']) && $options['dataType'] == 'json') {
  5414. $jsonpCallback = 'json_' . md5(microtime());
  5415. $jsonpData = $jsonpUrl = false;
  5416. if ($options['data']) {
  5417. foreach ($options['data'] as $n => $v) {
  5418. if ($v == '?')
  5419. $jsonpData = $n;
  5420. }
  5421. }
  5422. if (preg_match($jsre, $options['url']))
  5423. $jsonpUrl = true;
  5424. if ($jsonpData !== false || $jsonpUrl) {
  5425. // remember callback name for httpData()
  5426. $options['_jsonp'] = $jsonpCallback;
  5427. if ($jsonpData !== false)
  5428. $options['data'][$jsonpData] = $jsonpCallback;
  5429. if ($jsonpUrl)
  5430. $options['url'] = preg_replace($jsre, "=$jsonpCallback\\1", $options['url']);
  5431. }
  5432. }
  5433. $client->setUri($options['url']);
  5434. $client->setMethod(strtoupper($options['type']));
  5435. if (isset($options['referer']) && $options['referer'])
  5436. $client->setHeaders('Referer', $options['referer']);
  5437. $client->setHeaders(array(
  5438. // 'content-type' => $options['contentType'],
  5439. 'User-Agent' => 'Mozilla/5.0 (X11; U; Linux x86; en-US; rv:1.9.0.5) Gecko'
  5440. . '/2008122010 Firefox/3.0.5',
  5441. // TODO custom charset
  5442. 'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
  5443. // 'Connection' => 'keep-alive',
  5444. // 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
  5445. 'Accept-Language' => 'en-us,en;q=0.5',
  5446. ));
  5447. if ($options['username'])
  5448. $client->setAuth($options['username'], $options['password']);
  5449. if (isset($options['ifModified']) && $options['ifModified'])
  5450. $client->setHeaders(
  5451. "If-Modified-Since",
  5452. self::$lastModified
  5453. ? self::$lastModified
  5454. : "Thu, 01 Jan 1970 00:00:00 GMT"
  5455. );
  5456. $client->setHeaders(
  5457. "Accept",
  5458. isset($options['dataType'])
  5459. && isset(self::$ajaxSettings['accepts'][$options['dataType']])
  5460. ? self::$ajaxSettings['accepts'][$options['dataType']] . ", */*"
  5461. : self::$ajaxSettings['accepts']['_default']
  5462. );
  5463. // TODO $options['processData']
  5464. if ($options['data'] instanceof phpQueryObject) {
  5465. $serialized = $options['data']->serializeArray($options['data']);
  5466. $options['data'] = array();
  5467. foreach ($serialized as $r)
  5468. $options['data'][$r['name']] = $r['value'];
  5469. }
  5470. if (strtolower($options['type']) == 'get') {
  5471. $client->setParameterGet($options['data']);
  5472. } else if (strtolower($options['type']) == 'post') {
  5473. $client->setEncType($options['contentType']);
  5474. $client->setParameterPost($options['data']);
  5475. }
  5476. if (self::$active == 0 && $options['global'])
  5477. phpQueryEvents::trigger($documentID, 'ajaxStart');
  5478. self::$active++;
  5479. // beforeSend callback
  5480. if (isset($options['beforeSend']) && $options['beforeSend'])
  5481. phpQuery::callbackRun($options['beforeSend'], array($client));
  5482. // ajaxSend event
  5483. if ($options['global'])
  5484. phpQueryEvents::trigger($documentID, 'ajaxSend', array($client, $options));
  5485. if (phpQuery::$debug) {
  5486. self::debug("{$options['type']}: {$options['url']}\n");
  5487. self::debug("Options: <pre>" . var_export($options, true) . "</pre>\n");
  5488. // if ($client->getCookieJar())
  5489. // self::debug("Cookies: <pre>".var_export($client->getCookieJar()->getMatchingCookies($options['url']), true)."</pre>\n");
  5490. }
  5491. // request
  5492. $response = $client->request();
  5493. if (phpQuery::$debug) {
  5494. self::debug('Status: ' . $response->getStatus() . ' / ' . $response->getMessage());
  5495. self::debug($client->getLastRequest());
  5496. self::debug($response->getHeaders());
  5497. }
  5498. if ($response->isSuccessful()) {
  5499. // XXX tempolary
  5500. self::$lastModified = $response->getHeader('Last-Modified');
  5501. $data = self::httpData($response->getBody(), $options['dataType'], $options);
  5502. if (isset($options['success']) && $options['success'])
  5503. phpQuery::callbackRun($options['success'], array($data, $response->getStatus(), $options));
  5504. if ($options['global'])
  5505. phpQueryEvents::trigger($documentID, 'ajaxSuccess', array($client, $options));
  5506. } else {
  5507. if (isset($options['error']) && $options['error'])
  5508. phpQuery::callbackRun($options['error'], array($client, $response->getStatus(), $response->getMessage()));
  5509. if ($options['global'])
  5510. phpQueryEvents::trigger($documentID, 'ajaxError', array($client, /*$response->getStatus(),*/ $response->getMessage(), $options));
  5511. }
  5512. if (isset($options['complete']) && $options['complete'])
  5513. phpQuery::callbackRun($options['complete'], array($client, $response->getStatus()));
  5514. if ($options['global'])
  5515. phpQueryEvents::trigger($documentID, 'ajaxComplete', array($client, $options));
  5516. if ($options['global'] && !--self::$active)
  5517. phpQueryEvents::trigger($documentID, 'ajaxStop');
  5518. return $client;
  5519. // if (is_null($domId))
  5520. // $domId = self::$defaultDocumentID ? self::$defaultDocumentID : false;
  5521. // return new phpQueryAjaxResponse($response, $domId);
  5522. }
  5523. protected static function httpData($data, $type, $options)
  5524. {
  5525. if (isset($options['dataFilter']) && $options['dataFilter'])
  5526. $data = self::callbackRun($options['dataFilter'], array($data, $type));
  5527. if (is_string($data)) {
  5528. if ($type == "json") {
  5529. if (isset($options['_jsonp']) && $options['_jsonp']) {
  5530. $data = preg_replace('/^\s*\w+\((.*)\)\s*$/s', '$1', $data);
  5531. }
  5532. $data = self::parseJSON($data);
  5533. }
  5534. }
  5535. return $data;
  5536. }
  5537. /**
  5538. * Enter description here...
  5539. *
  5540. * @param array|phpQuery $data
  5541. *
  5542. */
  5543. public static function param($data)
  5544. {
  5545. return http_build_query($data, null, '&');
  5546. }
  5547. public static function get($url, $data = null, $callback = null, $type = null)
  5548. {
  5549. if (!is_array($data)) {
  5550. $callback = $data;
  5551. $data = null;
  5552. }
  5553. // TODO some array_values on this shit
  5554. return phpQuery::ajax(array(
  5555. 'type' => 'GET',
  5556. 'url' => $url,
  5557. 'data' => $data,
  5558. 'success' => $callback,
  5559. 'dataType' => $type,
  5560. ));
  5561. }
  5562. public static function post($url, $data = null, $callback = null, $type = null)
  5563. {
  5564. if (!is_array($data)) {
  5565. $callback = $data;
  5566. $data = null;
  5567. }
  5568. return phpQuery::ajax(array(
  5569. 'type' => 'POST',
  5570. 'url' => $url,
  5571. 'data' => $data,
  5572. 'success' => $callback,
  5573. 'dataType' => $type,
  5574. ));
  5575. }
  5576. public static function getJSON($url, $data = null, $callback = null)
  5577. {
  5578. if (!is_array($data)) {
  5579. $callback = $data;
  5580. $data = null;
  5581. }
  5582. // TODO some array_values on this shit
  5583. return phpQuery::ajax(array(
  5584. 'type' => 'GET',
  5585. 'url' => $url,
  5586. 'data' => $data,
  5587. 'success' => $callback,
  5588. 'dataType' => 'json',
  5589. ));
  5590. }
  5591. public static function ajaxSetup($options)
  5592. {
  5593. self::$ajaxSettings = array_merge(
  5594. self::$ajaxSettings,
  5595. $options
  5596. );
  5597. }
  5598. public static function ajaxAllowHost($host1, $host2 = null, $host3 = null)
  5599. {
  5600. $loop = is_array($host1)
  5601. ? $host1
  5602. : func_get_args();
  5603. foreach ($loop as $host) {
  5604. if ($host && !in_array($host, phpQuery::$ajaxAllowedHosts)) {
  5605. phpQuery::$ajaxAllowedHosts[] = $host;
  5606. }
  5607. }
  5608. }
  5609. public static function ajaxAllowURL($url1, $url2 = null, $url3 = null)
  5610. {
  5611. $loop = is_array($url1)
  5612. ? $url1
  5613. : func_get_args();
  5614. foreach ($loop as $url)
  5615. phpQuery::ajaxAllowHost(parse_url($url, PHP_URL_HOST));
  5616. }
  5617. /**
  5618. * Returns JSON representation of $data.
  5619. *
  5620. * @static
  5621. * @param mixed $data
  5622. * @return string
  5623. */
  5624. public static function toJSON($data)
  5625. {
  5626. if (function_exists('json_encode'))
  5627. return json_encode($data);
  5628. require_once('Zend/Json/Encoder.php');
  5629. return Zend_Json_Encoder::encode($data);
  5630. }
  5631. /**
  5632. * Parses JSON into proper PHP type.
  5633. *
  5634. * @static
  5635. * @param string $json
  5636. * @return mixed
  5637. */
  5638. public static function parseJSON($json)
  5639. {
  5640. if (function_exists('json_decode')) {
  5641. $return = json_decode(trim($json), true);
  5642. // json_decode and UTF8 issues
  5643. if (isset($return))
  5644. return $return;
  5645. }
  5646. require_once('Zend/Json/Decoder.php');
  5647. return Zend_Json_Decoder::decode($json);
  5648. }
  5649. /**
  5650. * Returns source's document ID.
  5651. *
  5652. * @param $source DOMNode|phpQueryObject
  5653. * @return string
  5654. */
  5655. public static function getDocumentID($source)
  5656. {
  5657. if ($source instanceof DOMDOCUMENT) {
  5658. foreach (phpQuery::$documents as $id => $document) {
  5659. if ($source->isSameNode($document->document))
  5660. return $id;
  5661. }
  5662. } else if ($source instanceof DOMNODE) {
  5663. foreach (phpQuery::$documents as $id => $document) {
  5664. if ($source->ownerDocument->isSameNode($document->document))
  5665. return $id;
  5666. }
  5667. } else if ($source instanceof phpQueryObject)
  5668. return $source->getDocumentID();
  5669. else if (is_string($source) && isset(phpQuery::$documents[$source]))
  5670. return $source;
  5671. }
  5672. /**
  5673. * Get DOMDocument object related to $source.
  5674. * Returns null if such document doesn't exist.
  5675. *
  5676. * @param $source DOMNode|phpQueryObject|string
  5677. * @return string
  5678. */
  5679. public static function getDOMDocument($source)
  5680. {
  5681. if ($source instanceof DOMDOCUMENT)
  5682. return $source;
  5683. $source = self::getDocumentID($source);
  5684. return $source
  5685. ? self::$documents[$id]['document']
  5686. : null;
  5687. }
  5688. // UTILITIES
  5689. // http://docs.jquery.com/Utilities
  5690. /**
  5691. *
  5692. * @return unknown_type
  5693. * @link http://docs.jquery.com/Utilities/jQuery.makeArray
  5694. */
  5695. public static function makeArray($obj)
  5696. {
  5697. $array = array();
  5698. if (is_object($object) && $object instanceof DOMNODELIST) {
  5699. foreach ($object as $value)
  5700. $array[] = $value;
  5701. } else if (is_object($object) && !($object instanceof Iterator)) {
  5702. foreach (get_object_vars($object) as $name => $value)
  5703. $array[0][$name] = $value;
  5704. } else {
  5705. foreach ($object as $name => $value)
  5706. $array[0][$name] = $value;
  5707. }
  5708. return $array;
  5709. }
  5710. public static function inArray($value, $array)
  5711. {
  5712. return in_array($value, $array);
  5713. }
  5714. /**
  5715. *
  5716. * @param $object
  5717. * @param $callback
  5718. * @return unknown_type
  5719. * @link http://docs.jquery.com/Utilities/jQuery.each
  5720. */
  5721. public static function each($object, $callback, $param1 = null, $param2 = null, $param3 = null)
  5722. {
  5723. $paramStructure = null;
  5724. if (func_num_args() > 2) {
  5725. $paramStructure = func_get_args();
  5726. $paramStructure = array_slice($paramStructure, 2);
  5727. }
  5728. if (is_object($object) && !($object instanceof Iterator)) {
  5729. foreach (get_object_vars($object) as $name => $value)
  5730. phpQuery::callbackRun($callback, array($name, $value), $paramStructure);
  5731. } else {
  5732. foreach ($object as $name => $value)
  5733. phpQuery::callbackRun($callback, array($name, $value), $paramStructure);
  5734. }
  5735. }
  5736. /**
  5737. *
  5738. * @link http://docs.jquery.com/Utilities/jQuery.map
  5739. */
  5740. public static function map($array, $callback, $param1 = null, $param2 = null, $param3 = null)
  5741. {
  5742. $result = array();
  5743. $paramStructure = null;
  5744. if (func_num_args() > 2) {
  5745. $paramStructure = func_get_args();
  5746. $paramStructure = array_slice($paramStructure, 2);
  5747. }
  5748. foreach ($array as $v) {
  5749. $vv = phpQuery::callbackRun($callback, array($v), $paramStructure);
  5750. // $callbackArgs = $args;
  5751. // foreach($args as $i => $arg) {
  5752. // $callbackArgs[$i] = $arg instanceof CallbackParam
  5753. // ? $v
  5754. // : $arg;
  5755. // }
  5756. // $vv = call_user_func_array($callback, $callbackArgs);
  5757. if (is_array($vv)) {
  5758. foreach ($vv as $vvv)
  5759. $result[] = $vvv;
  5760. } else if ($vv !== null) {
  5761. $result[] = $vv;
  5762. }
  5763. }
  5764. return $result;
  5765. }
  5766. /**
  5767. *
  5768. * @param $callback Callback
  5769. * @param $params
  5770. * @param $paramStructure
  5771. * @return unknown_type
  5772. */
  5773. public static function callbackRun($callback, $params = array(), $paramStructure = null)
  5774. {
  5775. if (!$callback)
  5776. return;
  5777. if ($callback instanceof CallbackParameterToReference) {
  5778. // TODO support ParamStructure to select which $param push to reference
  5779. if (isset($params[0]))
  5780. $callback->callback = $params[0];
  5781. return true;
  5782. }
  5783. if ($callback instanceof Callback) {
  5784. $paramStructure = $callback->params;
  5785. $callback = $callback->callback;
  5786. }
  5787. if (!$paramStructure)
  5788. return call_user_func_array($callback, $params);
  5789. $p = 0;
  5790. foreach ($paramStructure as $i => $v) {
  5791. $paramStructure[$i] = $v instanceof CallbackParam
  5792. ? $params[$p++]
  5793. : $v;
  5794. }
  5795. return call_user_func_array($callback, $paramStructure);
  5796. }
  5797. /**
  5798. * Merge 2 phpQuery objects.
  5799. * @param array $one
  5800. * @param array $two
  5801. * @protected
  5802. * @todo node lists, phpQueryObject
  5803. */
  5804. public static function merge($one, $two)
  5805. {
  5806. $elements = $one->elements;
  5807. foreach ($two->elements as $node) {
  5808. $exists = false;
  5809. foreach ($elements as $node2) {
  5810. if ($node2->isSameNode($node))
  5811. $exists = true;
  5812. }
  5813. if (!$exists)
  5814. $elements[] = $node;
  5815. }
  5816. return $elements;
  5817. // $one = $one->newInstance();
  5818. // $one->elements = $elements;
  5819. // return $one;
  5820. }
  5821. /**
  5822. *
  5823. * @param $array
  5824. * @param $callback
  5825. * @param $invert
  5826. * @return unknown_type
  5827. * @link http://docs.jquery.com/Utilities/jQuery.grep
  5828. */
  5829. public static function grep($array, $callback, $invert = false)
  5830. {
  5831. $result = array();
  5832. foreach ($array as $k => $v) {
  5833. $r = call_user_func_array($callback, array($v, $k));
  5834. if ($r === !(bool)$invert)
  5835. $result[] = $v;
  5836. }
  5837. return $result;
  5838. }
  5839. public static function unique($array)
  5840. {
  5841. return array_unique($array);
  5842. }
  5843. /**
  5844. *
  5845. * @param $function
  5846. * @return unknown_type
  5847. * @TODO there are problems with non-static methods, second parameter pass it
  5848. * but doesnt verify is method is really callable
  5849. */
  5850. public static function isFunction($function)
  5851. {
  5852. return is_callable($function);
  5853. }
  5854. public static function trim($str)
  5855. {
  5856. return trim($str);
  5857. }
  5858. /* PLUGINS NAMESPACE */
  5859. /**
  5860. *
  5861. * @param $url
  5862. * @param $callback
  5863. * @param $param1
  5864. * @param $param2
  5865. * @param $param3
  5866. * @return phpQueryObject
  5867. */
  5868. public static function browserGet($url, $callback, $param1 = null, $param2 = null, $param3 = null)
  5869. {
  5870. if (self::plugin('WebBrowser')) {
  5871. $params = func_get_args();
  5872. return self::callbackRun(array(self::$plugins, 'browserGet'), $params);
  5873. } else {
  5874. self::debug('WebBrowser plugin not available...');
  5875. }
  5876. }
  5877. /**
  5878. *
  5879. * @param $url
  5880. * @param $data
  5881. * @param $callback
  5882. * @param $param1
  5883. * @param $param2
  5884. * @param $param3
  5885. * @return phpQueryObject
  5886. */
  5887. public static function browserPost($url, $data, $callback, $param1 = null, $param2 = null, $param3 = null)
  5888. {
  5889. if (self::plugin('WebBrowser')) {
  5890. $params = func_get_args();
  5891. return self::callbackRun(array(self::$plugins, 'browserPost'), $params);
  5892. } else {
  5893. self::debug('WebBrowser plugin not available...');
  5894. }
  5895. }
  5896. /**
  5897. *
  5898. * @param $ajaxSettings
  5899. * @param $callback
  5900. * @param $param1
  5901. * @param $param2
  5902. * @param $param3
  5903. * @return phpQueryObject
  5904. */
  5905. public static function browser($ajaxSettings, $callback, $param1 = null, $param2 = null, $param3 = null)
  5906. {
  5907. if (self::plugin('WebBrowser')) {
  5908. $params = func_get_args();
  5909. return self::callbackRun(array(self::$plugins, 'browser'), $params);
  5910. } else {
  5911. self::debug('WebBrowser plugin not available...');
  5912. }
  5913. }
  5914. /**
  5915. *
  5916. * @param $code
  5917. * @return string
  5918. */
  5919. public static function php($code)
  5920. {
  5921. return self::code('php', $code);
  5922. }
  5923. /**
  5924. *
  5925. * @param $type
  5926. * @param $code
  5927. * @return string
  5928. */
  5929. public static function code($type, $code)
  5930. {
  5931. return "<$type><!-- " . trim($code) . " --></$type>";
  5932. }
  5933. public static function __callStatic($method, $params)
  5934. {
  5935. return call_user_func_array(
  5936. array(phpQuery::$plugins, $method),
  5937. $params
  5938. );
  5939. }
  5940. protected static function dataSetupNode($node, $documentID)
  5941. {
  5942. // search are return if alredy exists
  5943. foreach (phpQuery::$documents[$documentID]->dataNodes as $dataNode) {
  5944. if ($node->isSameNode($dataNode))
  5945. return $dataNode;
  5946. }
  5947. // if doesn't, add it
  5948. phpQuery::$documents[$documentID]->dataNodes[] = $node;
  5949. return $node;
  5950. }
  5951. protected static function dataRemoveNode($node, $documentID)
  5952. {
  5953. // search are return if alredy exists
  5954. foreach (phpQuery::$documents[$documentID]->dataNodes as $k => $dataNode) {
  5955. if ($node->isSameNode($dataNode)) {
  5956. unset(self::$documents[$documentID]->dataNodes[$k]);
  5957. unset(self::$documents[$documentID]->data[$dataNode->dataID]);
  5958. }
  5959. }
  5960. }
  5961. public static function data($node, $name, $data, $documentID = null)
  5962. {
  5963. if (!$documentID)
  5964. // TODO check if this works
  5965. $documentID = self::getDocumentID($node);
  5966. $document = phpQuery::$documents[$documentID];
  5967. $node = self::dataSetupNode($node, $documentID);
  5968. if (!isset($node->dataID))
  5969. $node->dataID = ++phpQuery::$documents[$documentID]->uuid;
  5970. $id = $node->dataID;
  5971. if (!isset($document->data[$id]))
  5972. $document->data[$id] = array();
  5973. if (!is_null($data))
  5974. $document->data[$id][$name] = $data;
  5975. if ($name) {
  5976. if (isset($document->data[$id][$name]))
  5977. return $document->data[$id][$name];
  5978. } else
  5979. return $id;
  5980. }
  5981. public static function removeData($node, $name, $documentID)
  5982. {
  5983. if (!$documentID)
  5984. // TODO check if this works
  5985. $documentID = self::getDocumentID($node);
  5986. $document = phpQuery::$documents[$documentID];
  5987. $node = self::dataSetupNode($node, $documentID);
  5988. $id = $node->dataID;
  5989. if ($name) {
  5990. if (isset($document->data[$id][$name]))
  5991. unset($document->data[$id][$name]);
  5992. $name = null;
  5993. foreach ($document->data[$id] as $name)
  5994. break;
  5995. if (!$name)
  5996. self::removeData($node, $name, $documentID);
  5997. } else {
  5998. self::dataRemoveNode($node, $documentID);
  5999. }
  6000. }
  6001. }
  6002. /**
  6003. * Plugins static namespace class.
  6004. *
  6005. * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
  6006. * @package phpQuery
  6007. * @todo move plugin methods here (as statics)
  6008. */
  6009. class phpQueryPlugins
  6010. {
  6011. public function __call($method, $args)
  6012. {
  6013. if (isset(phpQuery::$extendStaticMethods[$method])) {
  6014. $return = call_user_func_array(
  6015. phpQuery::$extendStaticMethods[$method],
  6016. $args
  6017. );
  6018. } else if (isset(phpQuery::$pluginsStaticMethods[$method])) {
  6019. $class = phpQuery::$pluginsStaticMethods[$method];
  6020. $realClass = "phpQueryPlugin_$class";
  6021. $return = call_user_func_array(
  6022. array($realClass, $method),
  6023. $args
  6024. );
  6025. return isset($return)
  6026. ? $return
  6027. : $this;
  6028. } else
  6029. throw new Exception("Method '{$method}' doesnt exist");
  6030. }
  6031. }
  6032. /**
  6033. * Shortcut to phpQuery::pq($arg1, $context)
  6034. * Chainable.
  6035. *
  6036. * @see phpQuery::pq()
  6037. * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
  6038. * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
  6039. * @package phpQuery
  6040. */
  6041. function pq($arg1, $context = null)
  6042. {
  6043. $args = func_get_args();
  6044. return call_user_func_array(
  6045. array('phpQuery', 'pq'),
  6046. $args
  6047. );
  6048. }
  6049. // add plugins dir and Zend framework to include path
  6050. set_include_path(
  6051. get_include_path()
  6052. . PATH_SEPARATOR . dirname(__FILE__) . '/phpQuery/'
  6053. . PATH_SEPARATOR . dirname(__FILE__) . '/phpQuery/plugins/'
  6054. );
  6055. // why ? no __call nor __get for statics in php...
  6056. // XXX __callStatic will be available in PHP 5.3
  6057. phpQuery::$plugins = new phpQueryPlugins();
  6058. // include bootstrap file (personal library config)
  6059. if (file_exists(dirname(__FILE__) . '/phpQuery/bootstrap.php'))
  6060. require_once dirname(__FILE__) . '/phpQuery/bootstrap.php';