parser.c 161 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925
  1. // Copyright 2010 Google Inc. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. //
  15. // Author: [email protected] (Jonathan Tang)
  16. #include <assert.h>
  17. #include <ctype.h>
  18. #include <stdarg.h>
  19. #include <stdlib.h>
  20. #include <string.h>
  21. #include <strings.h>
  22. #include "attribute.h"
  23. #include "error.h"
  24. #include "gumbo.h"
  25. #include "insertion_mode.h"
  26. #include "parser.h"
  27. #include "tokenizer.h"
  28. #include "tokenizer_states.h"
  29. #include "utf8.h"
  30. #include "util.h"
  31. #include "vector.h"
  32. #define AVOID_UNUSED_VARIABLE_WARNING(i) (void)(i)
  33. #define GUMBO_STRING(literal) { literal, sizeof(literal) - 1 }
  34. #define TERMINATOR { "", 0 }
  35. static void* malloc_wrapper(void* unused, size_t size) {
  36. return malloc(size);
  37. }
  38. static void free_wrapper(void* unused, void* ptr) {
  39. free(ptr);
  40. }
  41. const GumboOptions kGumboDefaultOptions = {
  42. &malloc_wrapper,
  43. &free_wrapper,
  44. NULL,
  45. 8,
  46. false,
  47. -1,
  48. };
  49. static const GumboStringPiece kDoctypeHtml = GUMBO_STRING("html");
  50. static const GumboStringPiece kPublicIdHtml4_0 = GUMBO_STRING(
  51. "-//W3C//DTD HTML 4.0//EN");
  52. static const GumboStringPiece kPublicIdHtml4_01 = GUMBO_STRING(
  53. "-//W3C//DTD HTML 4.01//EN");
  54. static const GumboStringPiece kPublicIdXhtml1_0 = GUMBO_STRING(
  55. "-//W3C//DTD XHTML 1.0 Strict//EN");
  56. static const GumboStringPiece kPublicIdXhtml1_1 = GUMBO_STRING(
  57. "-//W3C//DTD XHTML 1.1//EN");
  58. static const GumboStringPiece kSystemIdRecHtml4_0 = GUMBO_STRING(
  59. "http://www.w3.org/TR/REC-html40/strict.dtd");
  60. static const GumboStringPiece kSystemIdHtml4 = GUMBO_STRING(
  61. "http://www.w3.org/TR/html4/strict.dtd");
  62. static const GumboStringPiece kSystemIdXhtmlStrict1_1 = GUMBO_STRING(
  63. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd");
  64. static const GumboStringPiece kSystemIdXhtml1_1 = GUMBO_STRING(
  65. "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd");
  66. static const GumboStringPiece kSystemIdLegacyCompat = GUMBO_STRING(
  67. "about:legacy-compat");
  68. // The doctype arrays have an explicit terminator because we want to pass them
  69. // to a helper function, and passing them as a pointer discards sizeof
  70. // information. The SVG arrays are used only by one-off functions, and so loops
  71. // over them use sizeof directly instead of a terminator.
  72. static const GumboStringPiece kQuirksModePublicIdPrefixes[] = {
  73. GUMBO_STRING("+//Silmaril//dtd html Pro v0r11 19970101//"),
  74. GUMBO_STRING("-//AdvaSoft Ltd//DTD HTML 3.0 asWedit + extensions//"),
  75. GUMBO_STRING("-//AS//DTD HTML 3.0 asWedit + extensions//"),
  76. GUMBO_STRING("-//IETF//DTD HTML 2.0 Level 1//"),
  77. GUMBO_STRING("-//IETF//DTD HTML 2.0 Level 2//"),
  78. GUMBO_STRING("-//IETF//DTD HTML 2.0 Strict Level 1//"),
  79. GUMBO_STRING("-//IETF//DTD HTML 2.0 Strict Level 2//"),
  80. GUMBO_STRING("-//IETF//DTD HTML 2.0 Strict//"),
  81. GUMBO_STRING("-//IETF//DTD HTML 2.0//"),
  82. GUMBO_STRING("-//IETF//DTD HTML 2.1E//"),
  83. GUMBO_STRING("-//IETF//DTD HTML 3.0//"),
  84. GUMBO_STRING("-//IETF//DTD HTML 3.2 Final//"),
  85. GUMBO_STRING("-//IETF//DTD HTML 3.2//"),
  86. GUMBO_STRING("-//IETF//DTD HTML 3//"),
  87. GUMBO_STRING("-//IETF//DTD HTML Level 0//"),
  88. GUMBO_STRING("-//IETF//DTD HTML Level 1//"),
  89. GUMBO_STRING("-//IETF//DTD HTML Level 2//"),
  90. GUMBO_STRING("-//IETF//DTD HTML Level 3//"),
  91. GUMBO_STRING("-//IETF//DTD HTML Strict Level 0//"),
  92. GUMBO_STRING("-//IETF//DTD HTML Strict Level 1//"),
  93. GUMBO_STRING("-//IETF//DTD HTML Strict Level 2//"),
  94. GUMBO_STRING("-//IETF//DTD HTML Strict Level 3//"),
  95. GUMBO_STRING("-//IETF//DTD HTML Strict//"),
  96. GUMBO_STRING("-//IETF//DTD HTML//"),
  97. GUMBO_STRING("-//Metrius//DTD Metrius Presentational//"),
  98. GUMBO_STRING("-//Microsoft//DTD Internet Explorer 2.0 HTML Strict//"),
  99. GUMBO_STRING("-//Microsoft//DTD Internet Explorer 2.0 HTML//"),
  100. GUMBO_STRING("-//Microsoft//DTD Internet Explorer 2.0 Tables//"),
  101. GUMBO_STRING("-//Microsoft//DTD Internet Explorer 3.0 HTML Strict//"),
  102. GUMBO_STRING("-//Microsoft//DTD Internet Explorer 3.0 HTML//"),
  103. GUMBO_STRING("-//Microsoft//DTD Internet Explorer 3.0 Tables//"),
  104. GUMBO_STRING("-//Netscape Comm. Corp.//DTD HTML//"),
  105. GUMBO_STRING("-//Netscape Comm. Corp.//DTD Strict HTML//"),
  106. GUMBO_STRING("-//O'Reilly and Associates//DTD HTML 2.0//"),
  107. GUMBO_STRING("-//O'Reilly and Associates//DTD HTML Extended 1.0//"),
  108. GUMBO_STRING("-//O'Reilly and Associates//DTD HTML Extended Relaxed 1.0//"),
  109. GUMBO_STRING("-//SoftQuad Software//DTD HoTMetaL PRO 6.0::19990601::)"
  110. "extensions to HTML 4.0//"),
  111. GUMBO_STRING("-//SoftQuad//DTD HoTMetaL PRO 4.0::19971010::"
  112. "extensions to HTML 4.0//"),
  113. GUMBO_STRING("-//Spyglass//DTD HTML 2.0 Extended//"),
  114. GUMBO_STRING("-//SQ//DTD HTML 2.0 HoTMetaL + extensions//"),
  115. GUMBO_STRING("-//Sun Microsystems Corp.//DTD HotJava HTML//"),
  116. GUMBO_STRING("-//Sun Microsystems Corp.//DTD HotJava Strict HTML//"),
  117. GUMBO_STRING("-//W3C//DTD HTML 3 1995-03-24//"),
  118. GUMBO_STRING("-//W3C//DTD HTML 3.2 Draft//"),
  119. GUMBO_STRING("-//W3C//DTD HTML 3.2 Final//"),
  120. GUMBO_STRING("-//W3C//DTD HTML 3.2//"),
  121. GUMBO_STRING("-//W3C//DTD HTML 3.2S Draft//"),
  122. GUMBO_STRING("-//W3C//DTD HTML 4.0 Frameset//"),
  123. GUMBO_STRING("-//W3C//DTD HTML 4.0 Transitional//"),
  124. GUMBO_STRING("-//W3C//DTD HTML Experimental 19960712//"),
  125. GUMBO_STRING("-//W3C//DTD HTML Experimental 970421//"),
  126. GUMBO_STRING("-//W3C//DTD W3 HTML//"),
  127. GUMBO_STRING("-//W3O//DTD W3 HTML 3.0//"),
  128. GUMBO_STRING("-//WebTechs//DTD Mozilla HTML 2.0//"),
  129. GUMBO_STRING("-//WebTechs//DTD Mozilla HTML//"),
  130. TERMINATOR
  131. };
  132. static const GumboStringPiece kQuirksModePublicIdExactMatches[] = {
  133. GUMBO_STRING("-//W3O//DTD W3 HTML Strict 3.0//EN//"),
  134. GUMBO_STRING("-/W3C/DTD HTML 4.0 Transitional/EN"),
  135. GUMBO_STRING("HTML"),
  136. TERMINATOR
  137. };
  138. static const GumboStringPiece kQuirksModeSystemIdExactMatches[] = {
  139. GUMBO_STRING("http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd"),
  140. TERMINATOR
  141. };
  142. static const GumboStringPiece kLimitedQuirksPublicIdPrefixes[] = {
  143. GUMBO_STRING("-//W3C//DTD XHTML 1.0 Frameset//"),
  144. GUMBO_STRING("-//W3C//DTD XHTML 1.0 Transitional//"),
  145. TERMINATOR
  146. };
  147. static const GumboStringPiece kLimitedQuirksRequiresSystemIdPublicIdPrefixes[] = {
  148. GUMBO_STRING("-//W3C//DTD HTML 4.01 Frameset//"),
  149. GUMBO_STRING("-//W3C//DTD HTML 4.01 Transitional//"),
  150. TERMINATOR
  151. };
  152. // Indexed by GumboNamespaceEnum; keep in sync with that.
  153. static const char* kLegalXmlns[] = {
  154. "http://www.w3.org/1999/xhtml",
  155. "http://www.w3.org/2000/svg",
  156. "http://www.w3.org/1998/Math/MathML"
  157. };
  158. typedef struct _ReplacementEntry {
  159. const GumboStringPiece from;
  160. const GumboStringPiece to;
  161. } ReplacementEntry;
  162. #define REPLACEMENT_ENTRY(from, to) \
  163. { GUMBO_STRING(from), GUMBO_STRING(to) }
  164. // Static data for SVG attribute replacements.
  165. // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#adjust-svg-attributes
  166. static const ReplacementEntry kSvgAttributeReplacements[] = {
  167. REPLACEMENT_ENTRY("attributename", "attributeName"),
  168. REPLACEMENT_ENTRY("attributetype", "attributeType"),
  169. REPLACEMENT_ENTRY("basefrequency", "baseFrequency"),
  170. REPLACEMENT_ENTRY("baseprofile", "baseProfile"),
  171. REPLACEMENT_ENTRY("calcmode", "calcMode"),
  172. REPLACEMENT_ENTRY("clippathunits", "clipPathUnits"),
  173. REPLACEMENT_ENTRY("contentscripttype", "contentScriptType"),
  174. REPLACEMENT_ENTRY("contentstyletype", "contentStyleType"),
  175. REPLACEMENT_ENTRY("diffuseconstant", "diffuseConstant"),
  176. REPLACEMENT_ENTRY("edgemode", "edgeMode"),
  177. REPLACEMENT_ENTRY("externalresourcesrequired", "externalResourcesRequired"),
  178. REPLACEMENT_ENTRY("filterres", "filterRes"),
  179. REPLACEMENT_ENTRY("filterunits", "filterUnits"),
  180. REPLACEMENT_ENTRY("glyphref", "glyphRef"),
  181. REPLACEMENT_ENTRY("gradienttransform", "gradientTransform"),
  182. REPLACEMENT_ENTRY("gradientunits", "gradientUnits"),
  183. REPLACEMENT_ENTRY("kernelmatrix", "kernelMatrix"),
  184. REPLACEMENT_ENTRY("kernelunitlength", "kernelUnitLength"),
  185. REPLACEMENT_ENTRY("keypoints", "keyPoints"),
  186. REPLACEMENT_ENTRY("keysplines", "keySplines"),
  187. REPLACEMENT_ENTRY("keytimes", "keyTimes"),
  188. REPLACEMENT_ENTRY("lengthadjust", "lengthAdjust"),
  189. REPLACEMENT_ENTRY("limitingconeangle", "limitingConeAngle"),
  190. REPLACEMENT_ENTRY("markerheight", "markerHeight"),
  191. REPLACEMENT_ENTRY("markerunits", "markerUnits"),
  192. REPLACEMENT_ENTRY("markerwidth", "markerWidth"),
  193. REPLACEMENT_ENTRY("maskcontentunits", "maskContentUnits"),
  194. REPLACEMENT_ENTRY("maskunits", "maskUnits"),
  195. REPLACEMENT_ENTRY("numoctaves", "numOctaves"),
  196. REPLACEMENT_ENTRY("pathlength", "pathLength"),
  197. REPLACEMENT_ENTRY("patterncontentunits", "patternContentUnits"),
  198. REPLACEMENT_ENTRY("patterntransform", "patternTransform"),
  199. REPLACEMENT_ENTRY("patternunits", "patternUnits"),
  200. REPLACEMENT_ENTRY("pointsatx", "pointsAtX"),
  201. REPLACEMENT_ENTRY("pointsaty", "pointsAtY"),
  202. REPLACEMENT_ENTRY("pointsatz", "pointsAtZ"),
  203. REPLACEMENT_ENTRY("preservealpha", "preserveAlpha"),
  204. REPLACEMENT_ENTRY("preserveaspectratio", "preserveAspectRatio"),
  205. REPLACEMENT_ENTRY("primitiveunits", "primitiveUnits"),
  206. REPLACEMENT_ENTRY("refx", "refX"),
  207. REPLACEMENT_ENTRY("refy", "refY"),
  208. REPLACEMENT_ENTRY("repeatcount", "repeatCount"),
  209. REPLACEMENT_ENTRY("repeatdur", "repeatDur"),
  210. REPLACEMENT_ENTRY("requiredextensions", "requiredExtensions"),
  211. REPLACEMENT_ENTRY("requiredfeatures", "requiredFeatures"),
  212. REPLACEMENT_ENTRY("specularconstant", "specularConstant"),
  213. REPLACEMENT_ENTRY("specularexponent", "specularExponent"),
  214. REPLACEMENT_ENTRY("spreadmethod", "spreadMethod"),
  215. REPLACEMENT_ENTRY("startoffset", "startOffset"),
  216. REPLACEMENT_ENTRY("stddeviation", "stdDeviation"),
  217. REPLACEMENT_ENTRY("stitchtiles", "stitchTiles"),
  218. REPLACEMENT_ENTRY("surfacescale", "surfaceScale"),
  219. REPLACEMENT_ENTRY("systemlanguage", "systemLanguage"),
  220. REPLACEMENT_ENTRY("tablevalues", "tableValues"),
  221. REPLACEMENT_ENTRY("targetx", "targetX"),
  222. REPLACEMENT_ENTRY("targety", "targetY"),
  223. REPLACEMENT_ENTRY("textlength", "textLength"),
  224. REPLACEMENT_ENTRY("viewbox", "viewBox"),
  225. REPLACEMENT_ENTRY("viewtarget", "viewTarget"),
  226. REPLACEMENT_ENTRY("xchannelselector", "xChannelSelector"),
  227. REPLACEMENT_ENTRY("ychannelselector", "yChannelSelector"),
  228. REPLACEMENT_ENTRY("zoomandpan", "zoomAndPan"),
  229. };
  230. static const ReplacementEntry kSvgTagReplacements[] = {
  231. REPLACEMENT_ENTRY("altglyph", "altGlyph"),
  232. REPLACEMENT_ENTRY("altglyphdef", "altGlyphDef"),
  233. REPLACEMENT_ENTRY("altglyphitem", "altGlyphItem"),
  234. REPLACEMENT_ENTRY("animatecolor", "animateColor"),
  235. REPLACEMENT_ENTRY("animatemotion", "animateMotion"),
  236. REPLACEMENT_ENTRY("animatetransform", "animateTransform"),
  237. REPLACEMENT_ENTRY("clippath", "clipPath"),
  238. REPLACEMENT_ENTRY("feblend", "feBlend"),
  239. REPLACEMENT_ENTRY("fecolormatrix", "feColorMatrix"),
  240. REPLACEMENT_ENTRY("fecomponenttransfer", "feComponentTransfer"),
  241. REPLACEMENT_ENTRY("fecomposite", "feComposite"),
  242. REPLACEMENT_ENTRY("feconvolvematrix", "feConvolveMatrix"),
  243. REPLACEMENT_ENTRY("fediffuselighting", "feDiffuseLighting"),
  244. REPLACEMENT_ENTRY("fedisplacementmap", "feDisplacementMap"),
  245. REPLACEMENT_ENTRY("fedistantlight", "feDistantLight"),
  246. REPLACEMENT_ENTRY("feflood", "feFlood"),
  247. REPLACEMENT_ENTRY("fefunca", "feFuncA"),
  248. REPLACEMENT_ENTRY("fefuncb", "feFuncB"),
  249. REPLACEMENT_ENTRY("fefuncg", "feFuncG"),
  250. REPLACEMENT_ENTRY("fefuncr", "feFuncR"),
  251. REPLACEMENT_ENTRY("fegaussianblur", "feGaussianBlur"),
  252. REPLACEMENT_ENTRY("feimage", "feImage"),
  253. REPLACEMENT_ENTRY("femerge", "feMerge"),
  254. REPLACEMENT_ENTRY("femergenode", "feMergeNode"),
  255. REPLACEMENT_ENTRY("femorphology", "feMorphology"),
  256. REPLACEMENT_ENTRY("feoffset", "feOffset"),
  257. REPLACEMENT_ENTRY("fepointlight", "fePointLight"),
  258. REPLACEMENT_ENTRY("fespecularlighting", "feSpecularLighting"),
  259. REPLACEMENT_ENTRY("fespotlight", "feSpotLight"),
  260. REPLACEMENT_ENTRY("fetile", "feTile"),
  261. REPLACEMENT_ENTRY("feturbulence", "feTurbulence"),
  262. REPLACEMENT_ENTRY("foreignobject", "foreignObject"),
  263. REPLACEMENT_ENTRY("glyphref", "glyphRef"),
  264. REPLACEMENT_ENTRY("lineargradient", "linearGradient"),
  265. REPLACEMENT_ENTRY("radialgradient", "radialGradient"),
  266. REPLACEMENT_ENTRY("textpath", "textPath"),
  267. };
  268. typedef struct _NamespacedAttributeReplacement {
  269. const char* from;
  270. const char* local_name;
  271. const GumboAttributeNamespaceEnum attr_namespace;
  272. } NamespacedAttributeReplacement;
  273. static const NamespacedAttributeReplacement kForeignAttributeReplacements[] = {
  274. { "xlink:actuate", "actuate", GUMBO_ATTR_NAMESPACE_XLINK },
  275. { "xlink:actuate", "actuate", GUMBO_ATTR_NAMESPACE_XLINK },
  276. { "xlink:href", "href", GUMBO_ATTR_NAMESPACE_XLINK },
  277. { "xlink:role", "role", GUMBO_ATTR_NAMESPACE_XLINK },
  278. { "xlink:show", "show", GUMBO_ATTR_NAMESPACE_XLINK },
  279. { "xlink:title", "title", GUMBO_ATTR_NAMESPACE_XLINK },
  280. { "xlink:type", "type", GUMBO_ATTR_NAMESPACE_XLINK },
  281. { "xml:base", "base", GUMBO_ATTR_NAMESPACE_XML },
  282. { "xml:lang", "lang", GUMBO_ATTR_NAMESPACE_XML },
  283. { "xml:space", "space", GUMBO_ATTR_NAMESPACE_XML },
  284. { "xmlns", "xmlns", GUMBO_ATTR_NAMESPACE_XMLNS },
  285. { "xmlns:xlink", "xlink", GUMBO_ATTR_NAMESPACE_XMLNS },
  286. };
  287. // The "scope marker" for the list of active formatting elements. We use a
  288. // pointer to this as a generic marker element, since the particular element
  289. // scope doesn't matter.
  290. static const GumboNode kActiveFormattingScopeMarker;
  291. // The tag_is and tag_in function use true & false to denote start & end tags,
  292. // but for readability, we define constants for them here.
  293. static const bool kStartTag = true;
  294. static const bool kEndTag = false;
  295. // Because GumboStringPieces are immutable, we can't insert a character directly
  296. // into a text node. Instead, we accumulate all pending characters here and
  297. // flush them out to a text node whenever a new element is inserted.
  298. //
  299. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#insert-a-character
  300. typedef struct _TextNodeBufferState {
  301. // The accumulated text to be inserted into the current text node.
  302. GumboStringBuffer _buffer;
  303. // A pointer to the original text represented by this text node. Note that
  304. // because of foster parenting and other strange DOM manipulations, this may
  305. // include other non-text HTML tags in it; it is defined as the span of
  306. // original text from the first character in this text node to the last
  307. // character in this text node.
  308. const char* _start_original_text;
  309. // The source position of the start of this text node.
  310. GumboSourcePosition _start_position;
  311. // The type of node that will be inserted (TEXT or WHITESPACE).
  312. GumboNodeType _type;
  313. } TextNodeBufferState;
  314. typedef struct GumboInternalParserState {
  315. // http://www.whatwg.org/specs/web-apps/current-work/complete/parsing.html#insertion-mode
  316. GumboInsertionMode _insertion_mode;
  317. // Used for run_generic_parsing_algorithm, which needs to switch back to the
  318. // original insertion mode at its conclusion.
  319. GumboInsertionMode _original_insertion_mode;
  320. // http://www.whatwg.org/specs/web-apps/current-work/complete/parsing.html#the-stack-of-open-elements
  321. GumboVector /*GumboNode*/ _open_elements;
  322. // http://www.whatwg.org/specs/web-apps/current-work/complete/parsing.html#the-list-of-active-formatting-elements
  323. GumboVector /*GumboNode*/ _active_formatting_elements;
  324. // The stack of template insertion modes.
  325. // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#the-insertion-mode
  326. GumboVector /*InsertionMode*/ _template_insertion_modes;
  327. // http://www.whatwg.org/specs/web-apps/current-work/complete/parsing.html#the-element-pointers
  328. GumboNode* _head_element;
  329. GumboNode* _form_element;
  330. // The flag for when the spec says "Reprocess the current token in..."
  331. bool _reprocess_current_token;
  332. // The flag for "acknowledge the token's self-closing flag".
  333. bool _self_closing_flag_acknowledged;
  334. // The "frameset-ok" flag from the spec.
  335. bool _frameset_ok;
  336. // The flag for "If the next token is a LINE FEED, ignore that token...".
  337. bool _ignore_next_linefeed;
  338. // The flag for "whenever a node would be inserted into the current node, it
  339. // must instead be foster parented". This is used for misnested table
  340. // content, which needs to be handled according to "in body" rules yet foster
  341. // parented outside of the table.
  342. // It would perhaps be more explicit to have this as a parameter to
  343. // handle_in_body and insert_element, but given how special-purpose this is
  344. // and the number of call-sites that would need to take the extra parameter,
  345. // it's easier just to have a state flag.
  346. bool _foster_parent_insertions;
  347. // The accumulated text node buffer state.
  348. TextNodeBufferState _text_node;
  349. // The current token.
  350. GumboToken* _current_token;
  351. // The way that the spec is written, the </body> and </html> tags are *always*
  352. // implicit, because encountering one of those tokens merely switches the
  353. // insertion mode out of "in body". So we have individual state flags for
  354. // those end tags that are then inspected by pop_current_node when the <body>
  355. // and <html> nodes are popped to set the GUMBO_INSERTION_IMPLICIT_END_TAG
  356. // flag appropriately.
  357. bool _closed_body_tag;
  358. bool _closed_html_tag;
  359. } GumboParserState;
  360. static bool token_has_attribute(const GumboToken* token, const char* name) {
  361. assert(token->type == GUMBO_TOKEN_START_TAG);
  362. return gumbo_get_attribute(&token->v.start_tag.attributes, name) != NULL;
  363. }
  364. // Checks if the value of the specified attribute is a case-insensitive match
  365. // for the specified string.
  366. static bool attribute_matches(
  367. const GumboVector* attributes, const char* name, const char* value) {
  368. const GumboAttribute* attr = gumbo_get_attribute(attributes, name);
  369. return attr ? strcasecmp(value, attr->value) == 0 : false;
  370. }
  371. // Checks if the value of the specified attribute is a case-sensitive match
  372. // for the specified string.
  373. static bool attribute_matches_case_sensitive(
  374. const GumboVector* attributes, const char* name, const char* value) {
  375. const GumboAttribute* attr = gumbo_get_attribute(attributes, name);
  376. return attr ? strcmp(value, attr->value) == 0 : false;
  377. }
  378. // Checks if the specified attribute vectors are identical.
  379. static bool all_attributes_match(
  380. const GumboVector* attr1, const GumboVector* attr2) {
  381. int num_unmatched_attr2_elements = attr2->length;
  382. for (int i = 0; i < attr1->length; ++i) {
  383. const GumboAttribute* attr = attr1->data[i];
  384. if (attribute_matches_case_sensitive(attr2, attr->name, attr->value)) {
  385. --num_unmatched_attr2_elements;
  386. } else {
  387. return false;
  388. }
  389. }
  390. return num_unmatched_attr2_elements == 0;
  391. }
  392. static void set_frameset_not_ok(GumboParser* parser) {
  393. gumbo_debug("Setting frameset_ok to false.\n");
  394. parser->_parser_state->_frameset_ok = false;
  395. }
  396. static GumboNode* create_node(GumboParser* parser, GumboNodeType type) {
  397. GumboNode* node = gumbo_parser_allocate(parser, sizeof(GumboNode));
  398. node->parent = NULL;
  399. node->index_within_parent = -1;
  400. node->type = type;
  401. node->parse_flags = GUMBO_INSERTION_NORMAL;
  402. return node;
  403. }
  404. static GumboNode* new_document_node(GumboParser* parser) {
  405. GumboNode* document_node = create_node(parser, GUMBO_NODE_DOCUMENT);
  406. document_node->parse_flags = GUMBO_INSERTION_BY_PARSER;
  407. gumbo_vector_init(
  408. parser, 1, &document_node->v.document.children);
  409. // Must be initialized explicitly, as there's no guarantee that we'll see a
  410. // doc type token.
  411. GumboDocument* document = &document_node->v.document;
  412. document->has_doctype = false;
  413. document->name = NULL;
  414. document->public_identifier = NULL;
  415. document->system_identifier = NULL;
  416. return document_node;
  417. }
  418. static void output_init(GumboParser* parser) {
  419. GumboOutput* output = gumbo_parser_allocate(parser, sizeof(GumboOutput));
  420. output->root = NULL;
  421. output->document = new_document_node(parser);
  422. parser->_output = output;
  423. gumbo_init_errors(parser);
  424. }
  425. static void parser_state_init(GumboParser* parser) {
  426. GumboParserState* parser_state =
  427. gumbo_parser_allocate(parser, sizeof(GumboParserState));
  428. parser_state->_insertion_mode = GUMBO_INSERTION_MODE_INITIAL;
  429. parser_state->_reprocess_current_token = false;
  430. parser_state->_frameset_ok = true;
  431. parser_state->_ignore_next_linefeed = false;
  432. parser_state->_foster_parent_insertions = false;
  433. parser_state->_text_node._type = GUMBO_NODE_WHITESPACE;
  434. gumbo_string_buffer_init(parser, &parser_state->_text_node._buffer);
  435. gumbo_vector_init(parser, 10, &parser_state->_open_elements);
  436. gumbo_vector_init(parser, 5, &parser_state->_active_formatting_elements);
  437. gumbo_vector_init(parser, 5, &parser_state->_template_insertion_modes);
  438. parser_state->_head_element = NULL;
  439. parser_state->_form_element = NULL;
  440. parser_state->_current_token = NULL;
  441. parser_state->_closed_body_tag = false;
  442. parser_state->_closed_html_tag = false;
  443. parser->_parser_state = parser_state;
  444. }
  445. static void parser_state_destroy(GumboParser* parser) {
  446. GumboParserState* state = parser->_parser_state;
  447. gumbo_vector_destroy(parser, &state->_active_formatting_elements);
  448. gumbo_vector_destroy(parser, &state->_open_elements);
  449. gumbo_vector_destroy(parser, &state->_template_insertion_modes);
  450. gumbo_string_buffer_destroy(parser, &state->_text_node._buffer);
  451. gumbo_parser_deallocate(parser, state);
  452. }
  453. static GumboNode* get_document_node(GumboParser* parser) {
  454. return parser->_output->document;
  455. }
  456. // Returns the node at the bottom of the stack of open elements, or NULL if no
  457. // elements have been added yet.
  458. static GumboNode* get_current_node(GumboParser* parser) {
  459. GumboVector* open_elements = &parser->_parser_state->_open_elements;
  460. if (open_elements->length == 0) {
  461. assert(!parser->_output->root);
  462. return NULL;
  463. }
  464. assert(open_elements->length > 0);
  465. assert(open_elements->data != NULL);
  466. return open_elements->data[open_elements->length - 1];
  467. }
  468. // Returns true if the given needle is in the given array of literal
  469. // GumboStringPieces. If exact_match is true, this requires that they match
  470. // exactly; otherwise, this performs a prefix match to check if any of the
  471. // elements in haystack start with needle. This always performs a
  472. // case-insensitive match.
  473. static bool is_in_static_list(
  474. const char* needle, const GumboStringPiece* haystack, bool exact_match) {
  475. for (int i = 0; haystack[i].length > 0; ++i) {
  476. if ((exact_match && !strcmp(needle, haystack[i].data)) ||
  477. (!exact_match && !strcasecmp(needle, haystack[i].data))) {
  478. return true;
  479. }
  480. }
  481. return false;
  482. }
  483. static void push_template_insertion_mode(
  484. GumboParser* parser, GumboInsertionMode mode) {
  485. gumbo_vector_add(
  486. parser, (void*) mode, &parser->_parser_state->_template_insertion_modes);
  487. }
  488. static void pop_template_insertion_mode(GumboParser* parser) {
  489. gumbo_vector_pop(parser, &parser->_parser_state->_template_insertion_modes);
  490. }
  491. static GumboInsertionMode get_current_template_insertion_mode(
  492. GumboParser* parser) {
  493. GumboVector* template_insertion_modes =
  494. &parser->_parser_state->_template_insertion_modes;
  495. assert(template_insertion_modes->length > 0);
  496. return (GumboInsertionMode) template_insertion_modes->data[
  497. template_insertion_modes->length - 1];
  498. }
  499. static void set_insertion_mode(GumboParser* parser, GumboInsertionMode mode) {
  500. parser->_parser_state->_insertion_mode = mode;
  501. }
  502. // http://www.whatwg.org/specs/web-apps/current-work/complete/parsing.html#reset-the-insertion-mode-appropriately
  503. // This is a helper function that returns the appropriate insertion mode instead
  504. // of setting it. Returns GUMBO_INSERTION_MODE_INITIAL as a sentinel value to
  505. // indicate that there is no appropriate insertion mode, and the loop should
  506. // continue.
  507. static GumboInsertionMode get_appropriate_insertion_mode(
  508. const GumboNode* node, bool is_last) {
  509. assert(node->type == GUMBO_NODE_ELEMENT);
  510. switch (node->v.element.tag) {
  511. case GUMBO_TAG_SELECT:
  512. return GUMBO_INSERTION_MODE_IN_SELECT;
  513. case GUMBO_TAG_TD:
  514. case GUMBO_TAG_TH:
  515. return is_last ?
  516. GUMBO_INSERTION_MODE_IN_BODY : GUMBO_INSERTION_MODE_IN_CELL;
  517. case GUMBO_TAG_TR:
  518. return GUMBO_INSERTION_MODE_IN_ROW;
  519. case GUMBO_TAG_TBODY:
  520. case GUMBO_TAG_THEAD:
  521. case GUMBO_TAG_TFOOT:
  522. return GUMBO_INSERTION_MODE_IN_TABLE_BODY;
  523. case GUMBO_TAG_CAPTION:
  524. return GUMBO_INSERTION_MODE_IN_CAPTION;
  525. case GUMBO_TAG_COLGROUP:
  526. return GUMBO_INSERTION_MODE_IN_COLUMN_GROUP;
  527. case GUMBO_TAG_TABLE:
  528. return GUMBO_INSERTION_MODE_IN_TABLE;
  529. case GUMBO_TAG_HEAD:
  530. case GUMBO_TAG_BODY:
  531. return GUMBO_INSERTION_MODE_IN_BODY;
  532. case GUMBO_TAG_FRAMESET:
  533. return GUMBO_INSERTION_MODE_IN_FRAMESET;
  534. case GUMBO_TAG_HTML:
  535. return GUMBO_INSERTION_MODE_BEFORE_HEAD;
  536. default:
  537. return is_last ?
  538. GUMBO_INSERTION_MODE_IN_BODY : GUMBO_INSERTION_MODE_INITIAL;
  539. }
  540. }
  541. // This performs the actual "reset the insertion mode" loop.
  542. static void reset_insertion_mode_appropriately(GumboParser* parser) {
  543. const GumboVector* open_elements = &parser->_parser_state->_open_elements;
  544. for (int i = open_elements->length; --i >= 0; ) {
  545. GumboInsertionMode mode =
  546. get_appropriate_insertion_mode(open_elements->data[i], i == 0);
  547. if (mode != GUMBO_INSERTION_MODE_INITIAL) {
  548. set_insertion_mode(parser, mode);
  549. return;
  550. }
  551. }
  552. // Should never get here, because is_last will be set on the last iteration
  553. // and will force GUMBO_INSERTION_MODE_IN_BODY.
  554. assert(0);
  555. }
  556. static GumboError* add_parse_error(GumboParser* parser, const GumboToken* token) {
  557. gumbo_debug("Adding parse error.\n");
  558. GumboError* error = gumbo_add_error(parser);
  559. if (!error) {
  560. return NULL;
  561. }
  562. error->type = GUMBO_ERR_PARSER;
  563. error->position = token->position;
  564. error->original_text = token->original_text.data;
  565. GumboParserError* extra_data = &error->v.parser;
  566. extra_data->input_type = token->type;
  567. extra_data->input_tag = GUMBO_TAG_UNKNOWN;
  568. if (token->type == GUMBO_TOKEN_START_TAG) {
  569. extra_data->input_tag = token->v.start_tag.tag;
  570. } else if (token->type == GUMBO_TOKEN_END_TAG) {
  571. extra_data->input_tag = token->v.end_tag;
  572. }
  573. GumboParserState* state = parser->_parser_state;
  574. extra_data->parser_state = state->_insertion_mode;
  575. gumbo_vector_init(parser, state->_open_elements.length,
  576. &extra_data->tag_stack);
  577. for (int i = 0; i < state->_open_elements.length; ++i) {
  578. const GumboNode* node = state->_open_elements.data[i];
  579. assert(node->type == GUMBO_NODE_ELEMENT);
  580. gumbo_vector_add(parser, (void*) node->v.element.tag,
  581. &extra_data->tag_stack);
  582. }
  583. return error;
  584. }
  585. // Returns true if the specified token is either a start or end tag (specified
  586. // by is_start) with one of the tag types in the varargs list. Terminate the
  587. // list with GUMBO_TAG_LAST; this functions as a sentinel since no portion of
  588. // the spec references tags that are not in the spec.
  589. // TODO(jdtang): A lot of the tag lists for this function are repeated in many
  590. // places in the code. This is how it's written in the spec (and it's done this
  591. // way so it's easy to verify the code against the spec), but it may be worth
  592. // coming up with a notion of a "tag set" that includes a list of tags, and
  593. // using that in many places. It'd probably also help performance, but I want
  594. // to profile before optimizing.
  595. static bool tag_in(const GumboToken* token, bool is_start, ...) {
  596. GumboTag token_tag;
  597. if (is_start && token->type == GUMBO_TOKEN_START_TAG) {
  598. token_tag = token->v.start_tag.tag;
  599. } else if (!is_start && token->type == GUMBO_TOKEN_END_TAG) {
  600. token_tag = token->v.end_tag;
  601. } else {
  602. return false;
  603. }
  604. va_list tags;
  605. va_start(tags, is_start);
  606. bool result = false;
  607. for (GumboTag tag = va_arg(tags, GumboTag); tag != GUMBO_TAG_LAST;
  608. tag = va_arg(tags, GumboTag)) {
  609. if (tag == token_tag) {
  610. result = true;
  611. break;
  612. }
  613. }
  614. va_end(tags);
  615. return result;
  616. }
  617. // Like tag_in, but for the single-tag case.
  618. static bool tag_is(const GumboToken* token, bool is_start, GumboTag tag) {
  619. if (is_start && token->type == GUMBO_TOKEN_START_TAG) {
  620. return token->v.start_tag.tag == tag;
  621. } else if (!is_start && token->type == GUMBO_TOKEN_END_TAG) {
  622. return token->v.end_tag == tag;
  623. } else {
  624. return false;
  625. }
  626. }
  627. // Like tag_in, but checks for the tag of a node, rather than a token.
  628. static bool node_tag_in(const GumboNode* node, ...) {
  629. assert(node != NULL);
  630. if (node->type != GUMBO_NODE_ELEMENT) {
  631. return false;
  632. }
  633. GumboTag node_tag = node->v.element.tag;
  634. va_list tags;
  635. va_start(tags, node);
  636. bool result = false;
  637. for (GumboTag tag = va_arg(tags, GumboTag); tag != GUMBO_TAG_LAST;
  638. tag = va_arg(tags, GumboTag)) {
  639. assert(tag <= GUMBO_TAG_LAST);
  640. if (tag == node_tag) {
  641. result = true;
  642. break;
  643. }
  644. }
  645. va_end(tags);
  646. return result;
  647. }
  648. // Like node_tag_in, but for the single-tag case.
  649. static bool node_tag_is(const GumboNode* node, GumboTag tag) {
  650. return node->type == GUMBO_NODE_ELEMENT && node->v.element.tag == tag;
  651. }
  652. // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#mathml-text-integration-point
  653. static bool is_mathml_integration_point(const GumboNode* node) {
  654. return node_tag_in(node, GUMBO_TAG_MI, GUMBO_TAG_MO, GUMBO_TAG_MN,
  655. GUMBO_TAG_MS, GUMBO_TAG_MTEXT, GUMBO_TAG_LAST) &&
  656. node->v.element.tag_namespace == GUMBO_NAMESPACE_MATHML;
  657. }
  658. // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#html-integration-point
  659. static bool is_html_integration_point(const GumboNode* node) {
  660. return (node_tag_in(node, GUMBO_TAG_FOREIGNOBJECT, GUMBO_TAG_DESC,
  661. GUMBO_TAG_TITLE, GUMBO_TAG_LAST) &&
  662. node->v.element.tag_namespace == GUMBO_NAMESPACE_SVG) ||
  663. (node_tag_is(node, GUMBO_TAG_ANNOTATION_XML) && (
  664. attribute_matches(&node->v.element.attributes,
  665. "encoding", "text/html") ||
  666. attribute_matches(&node->v.element.attributes,
  667. "encoding", "application/xhtml+xml")));
  668. }
  669. // Appends a node to the end of its parent, setting the "parent" and
  670. // "index_within_parent" fields appropriately.
  671. static void append_node(
  672. GumboParser* parser, GumboNode* parent, GumboNode* node) {
  673. assert(node->parent == NULL);
  674. assert(node->index_within_parent == -1);
  675. GumboVector* children;
  676. if (parent->type == GUMBO_NODE_ELEMENT) {
  677. children = &parent->v.element.children;
  678. } else {
  679. assert(parent->type == GUMBO_NODE_DOCUMENT);
  680. children = &parent->v.document.children;
  681. }
  682. node->parent = parent;
  683. node->index_within_parent = children->length;
  684. gumbo_vector_add(parser, (void*) node, children);
  685. assert(node->index_within_parent < children->length);
  686. }
  687. // Inserts a node at the specified index within its parent, updating the
  688. // "parent" and "index_within_parent" fields of it and all its siblings.
  689. static void insert_node(
  690. GumboParser* parser, GumboNode* parent, int index, GumboNode* node) {
  691. assert(node->parent == NULL);
  692. assert(node->index_within_parent == -1);
  693. assert(parent->type == GUMBO_NODE_ELEMENT);
  694. GumboVector* children = &parent->v.element.children;
  695. assert(index >= 0);
  696. assert(index < children->length);
  697. node->parent = parent;
  698. node->index_within_parent = index;
  699. gumbo_vector_insert_at(parser, (void*) node, index, children);
  700. assert(node->index_within_parent < children->length);
  701. for (int i = index + 1; i < children->length; ++i) {
  702. GumboNode* sibling = children->data[i];
  703. sibling->index_within_parent = i;
  704. assert(sibling->index_within_parent < children->length);
  705. }
  706. }
  707. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#foster-parenting
  708. static void foster_parent_element(GumboParser* parser, GumboNode* node) {
  709. GumboVector* open_elements = &parser->_parser_state->_open_elements;
  710. assert(open_elements->length > 2);
  711. node->parse_flags |= GUMBO_INSERTION_FOSTER_PARENTED;
  712. GumboNode* foster_parent_element = open_elements->data[0];
  713. assert(foster_parent_element->type == GUMBO_NODE_ELEMENT);
  714. assert(node_tag_is(foster_parent_element, GUMBO_TAG_HTML));
  715. for (int i = open_elements->length; --i > 1; ) {
  716. GumboNode* table_element = open_elements->data[i];
  717. if (node_tag_is(table_element, GUMBO_TAG_TABLE)) {
  718. foster_parent_element = table_element->parent;
  719. if (!foster_parent_element ||
  720. foster_parent_element->type != GUMBO_NODE_ELEMENT) {
  721. // Table has no parent; spec says it's possible if a script manipulated
  722. // the DOM, although I don't think we have to worry about this case.
  723. gumbo_debug("Table has no parent.\n");
  724. foster_parent_element = open_elements->data[i - 1];
  725. break;
  726. }
  727. assert(foster_parent_element->type == GUMBO_NODE_ELEMENT);
  728. gumbo_debug("Found enclosing table (%x) at %d; parent=%s, index=%d.\n",
  729. table_element, i, gumbo_normalized_tagname(
  730. foster_parent_element->v.element.tag),
  731. table_element->index_within_parent);
  732. assert(foster_parent_element->v.element.children.data[
  733. table_element->index_within_parent] == table_element);
  734. insert_node(parser, foster_parent_element,
  735. table_element->index_within_parent, node);
  736. return;
  737. }
  738. }
  739. if (node->type == GUMBO_NODE_ELEMENT) {
  740. gumbo_vector_add(parser, (void*) node, open_elements);
  741. }
  742. append_node(parser, foster_parent_element, node);
  743. }
  744. static void maybe_flush_text_node_buffer(GumboParser* parser) {
  745. GumboParserState* state = parser->_parser_state;
  746. TextNodeBufferState* buffer_state = &state->_text_node;
  747. if (buffer_state->_buffer.length == 0) {
  748. return;
  749. }
  750. assert(buffer_state->_type == GUMBO_NODE_WHITESPACE ||
  751. buffer_state->_type == GUMBO_NODE_TEXT);
  752. GumboNode* text_node = create_node(parser, buffer_state->_type);
  753. GumboText* text_node_data = &text_node->v.text;
  754. text_node_data->text = gumbo_string_buffer_to_string(
  755. parser, &buffer_state->_buffer);
  756. text_node_data->original_text.data = buffer_state->_start_original_text;
  757. text_node_data->original_text.length =
  758. state->_current_token->original_text.data -
  759. buffer_state->_start_original_text;
  760. text_node_data->start_pos = buffer_state->_start_position;
  761. if (state->_foster_parent_insertions && node_tag_in(
  762. get_current_node(parser), GUMBO_TAG_TABLE, GUMBO_TAG_TBODY,
  763. GUMBO_TAG_TFOOT, GUMBO_TAG_THEAD, GUMBO_TAG_TR, GUMBO_TAG_LAST)) {
  764. foster_parent_element(parser, text_node);
  765. } else {
  766. append_node(
  767. parser, parser->_output->root ?
  768. get_current_node(parser) : parser->_output->document, text_node);
  769. }
  770. gumbo_debug("Flushing text node buffer of %.*s.\n",
  771. (int) buffer_state->_buffer.length, buffer_state->_buffer.data);
  772. gumbo_string_buffer_destroy(parser, &buffer_state->_buffer);
  773. gumbo_string_buffer_init(parser, &buffer_state->_buffer);
  774. buffer_state->_type = GUMBO_NODE_WHITESPACE;
  775. assert(buffer_state->_buffer.length == 0);
  776. }
  777. static void record_end_of_element(
  778. GumboToken* current_token, GumboElement* element) {
  779. element->end_pos = current_token->position;
  780. element->original_end_tag =
  781. current_token->type == GUMBO_TOKEN_END_TAG ?
  782. current_token->original_text : kGumboEmptyString;
  783. }
  784. static GumboNode* pop_current_node(GumboParser* parser) {
  785. GumboParserState* state = parser->_parser_state;
  786. maybe_flush_text_node_buffer(parser);
  787. if (state->_open_elements.length > 0) {
  788. assert(node_tag_is(state->_open_elements.data[0], GUMBO_TAG_HTML));
  789. gumbo_debug(
  790. "Popping %s node.\n",
  791. gumbo_normalized_tagname(get_current_node(parser)->v.element.tag));
  792. }
  793. GumboNode* current_node = gumbo_vector_pop(parser, &state->_open_elements);
  794. if (!current_node) {
  795. assert(state->_open_elements.length == 0);
  796. return NULL;
  797. }
  798. assert(current_node->type == GUMBO_NODE_ELEMENT);
  799. bool is_closed_body_or_html_tag =
  800. (node_tag_is(current_node, GUMBO_TAG_BODY) && state->_closed_body_tag) ||
  801. (node_tag_is(current_node, GUMBO_TAG_HTML) && state->_closed_html_tag);
  802. if ((state->_current_token->type != GUMBO_TOKEN_END_TAG ||
  803. !node_tag_is(current_node, state->_current_token->v.end_tag)) &&
  804. !is_closed_body_or_html_tag) {
  805. current_node->parse_flags |= GUMBO_INSERTION_IMPLICIT_END_TAG;
  806. }
  807. if (!is_closed_body_or_html_tag) {
  808. record_end_of_element(state->_current_token, &current_node->v.element);
  809. }
  810. return current_node;
  811. }
  812. static void append_comment_node(
  813. GumboParser* parser, GumboNode* node, const GumboToken* token) {
  814. maybe_flush_text_node_buffer(parser);
  815. GumboNode* comment = create_node(parser, GUMBO_NODE_COMMENT);
  816. comment->type = GUMBO_NODE_COMMENT;
  817. comment->parse_flags = GUMBO_INSERTION_NORMAL;
  818. comment->v.text.text = token->v.text;
  819. comment->v.text.original_text = token->original_text;
  820. comment->v.text.start_pos = token->position;
  821. append_node(parser, node, comment);
  822. }
  823. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#clear-the-stack-back-to-a-table-row-context
  824. static void clear_stack_to_table_row_context(GumboParser* parser) {
  825. while (!node_tag_in(get_current_node(parser),
  826. GUMBO_TAG_HTML, GUMBO_TAG_TR, GUMBO_TAG_LAST)) {
  827. pop_current_node(parser);
  828. }
  829. }
  830. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#clear-the-stack-back-to-a-table-context
  831. static void clear_stack_to_table_context(GumboParser* parser) {
  832. while (!node_tag_in(get_current_node(parser),
  833. GUMBO_TAG_HTML, GUMBO_TAG_TABLE, GUMBO_TAG_LAST)) {
  834. pop_current_node(parser);
  835. }
  836. }
  837. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#clear-the-stack-back-to-a-table-body-context
  838. void clear_stack_to_table_body_context(GumboParser* parser) {
  839. while (!node_tag_in(get_current_node(parser), GUMBO_TAG_HTML,
  840. GUMBO_TAG_TBODY, GUMBO_TAG_TFOOT, GUMBO_TAG_THEAD,
  841. GUMBO_TAG_LAST)) {
  842. pop_current_node(parser);
  843. }
  844. }
  845. // Creates a parser-inserted element in the HTML namespace and returns it.
  846. static GumboNode* create_element(GumboParser* parser, GumboTag tag) {
  847. GumboNode* node = create_node(parser, GUMBO_NODE_ELEMENT);
  848. GumboElement* element = &node->v.element;
  849. gumbo_vector_init(parser, 1, &element->children);
  850. gumbo_vector_init(parser, 0, &element->attributes);
  851. element->tag = tag;
  852. element->tag_namespace = GUMBO_NAMESPACE_HTML;
  853. element->original_tag = kGumboEmptyString;
  854. element->original_end_tag = kGumboEmptyString;
  855. element->start_pos = parser->_parser_state->_current_token->position;
  856. element->end_pos = kGumboEmptySourcePosition;
  857. return node;
  858. }
  859. // Constructs an element from the given start tag token.
  860. static GumboNode* create_element_from_token(
  861. GumboParser* parser, GumboToken* token, GumboNamespaceEnum tag_namespace) {
  862. assert(token->type == GUMBO_TOKEN_START_TAG);
  863. GumboTokenStartTag* start_tag = &token->v.start_tag;
  864. GumboNode* node = create_node(parser, GUMBO_NODE_ELEMENT);
  865. GumboElement* element = &node->v.element;
  866. gumbo_vector_init(parser, 1, &element->children);
  867. element->attributes = start_tag->attributes;
  868. element->tag = start_tag->tag;
  869. element->tag_namespace = tag_namespace;
  870. assert(token->original_text.length >= 2);
  871. assert(token->original_text.data[0] == '<');
  872. assert(token->original_text.data[token->original_text.length - 1] == '>');
  873. element->original_tag = token->original_text;
  874. element->start_pos = token->position;
  875. element->original_end_tag = kGumboEmptyString;
  876. element->end_pos = kGumboEmptySourcePosition;
  877. // The element takes ownership of the attributes from the token, so any
  878. // allocated-memory fields should be nulled out.
  879. start_tag->attributes = kGumboEmptyVector;
  880. return node;
  881. }
  882. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#insert-an-html-element
  883. static void insert_element(GumboParser* parser, GumboNode* node,
  884. bool is_reconstructing_formatting_elements) {
  885. GumboParserState* state = parser->_parser_state;
  886. // NOTE(jdtang): The text node buffer must always be flushed before inserting
  887. // a node, otherwise we're handling nodes in a different order than the spec
  888. // mandated. However, one clause of the spec (character tokens in the body)
  889. // requires that we reconstruct the active formatting elements *before* adding
  890. // the character, and reconstructing the active formatting elements may itself
  891. // result in the insertion of new elements (which should be pushed onto the
  892. // stack of open elements before the buffer is flushed). We solve this (for
  893. // the time being, the spec has been rewritten for <template> and the new
  894. // version may be simpler here) with a boolean flag to this method.
  895. if (!is_reconstructing_formatting_elements) {
  896. maybe_flush_text_node_buffer(parser);
  897. }
  898. if (state->_foster_parent_insertions && node_tag_in(
  899. get_current_node(parser), GUMBO_TAG_TABLE, GUMBO_TAG_TBODY,
  900. GUMBO_TAG_TFOOT, GUMBO_TAG_THEAD, GUMBO_TAG_TR, GUMBO_TAG_LAST)) {
  901. foster_parent_element(parser, node);
  902. gumbo_vector_add(parser, (void*) node, &state->_open_elements);
  903. return;
  904. }
  905. // This is called to insert the root HTML element, but get_current_node
  906. // assumes the stack of open elements is non-empty, so we need special
  907. // handling for this case.
  908. append_node(
  909. parser, parser->_output->root ?
  910. get_current_node(parser) : parser->_output->document, node);
  911. gumbo_vector_add(parser, (void*) node, &state->_open_elements);
  912. }
  913. // Convenience method that combines create_element_from_token and
  914. // insert_element, inserting the generated element directly into the current
  915. // node. Returns the node inserted.
  916. static GumboNode* insert_element_from_token(
  917. GumboParser* parser, GumboToken* token) {
  918. GumboNode* element =
  919. create_element_from_token(parser, token, GUMBO_NAMESPACE_HTML);
  920. insert_element(parser, element, false);
  921. gumbo_debug("Inserting <%s> element (@%x) from token.\n",
  922. gumbo_normalized_tagname(element->v.element.tag), element);
  923. return element;
  924. }
  925. // Convenience method that combines create_element and insert_element, inserting
  926. // a parser-generated element of a specific tag type. Returns the node
  927. // inserted.
  928. static GumboNode* insert_element_of_tag_type(
  929. GumboParser* parser, GumboTag tag, GumboParseFlags reason) {
  930. GumboNode* element = create_element(parser, tag);
  931. element->parse_flags |= GUMBO_INSERTION_BY_PARSER | reason;
  932. insert_element(parser, element, false);
  933. gumbo_debug("Inserting %s element (@%x) from tag type.\n",
  934. gumbo_normalized_tagname(tag), element);
  935. return element;
  936. }
  937. // Convenience method for creating foreign namespaced element. Returns the node
  938. // inserted.
  939. static GumboNode* insert_foreign_element(
  940. GumboParser* parser, GumboToken* token, GumboNamespaceEnum tag_namespace) {
  941. assert(token->type == GUMBO_TOKEN_START_TAG);
  942. GumboNode* element = create_element_from_token(parser, token, tag_namespace);
  943. insert_element(parser, element, false);
  944. if (token_has_attribute(token, "xmlns") &&
  945. !attribute_matches_case_sensitive(
  946. &token->v.start_tag.attributes, "xmlns",
  947. kLegalXmlns[tag_namespace])) {
  948. // TODO(jdtang): Since there're multiple possible error codes here, we
  949. // eventually need reason codes to differentiate them.
  950. add_parse_error(parser, token);
  951. }
  952. if (token_has_attribute(token, "xmlns:xlink") &&
  953. !attribute_matches_case_sensitive(
  954. &token->v.start_tag.attributes,
  955. "xmlns:xlink", "http://www.w3.org/1999/xlink")) {
  956. add_parse_error(parser, token);
  957. }
  958. return element;
  959. }
  960. static void insert_text_token(GumboParser* parser, GumboToken* token) {
  961. assert(token->type == GUMBO_TOKEN_WHITESPACE ||
  962. token->type == GUMBO_TOKEN_CHARACTER);
  963. TextNodeBufferState* buffer_state = &parser->_parser_state->_text_node;
  964. if (buffer_state->_buffer.length == 0) {
  965. // Initialize position fields.
  966. buffer_state->_start_original_text = token->original_text.data;
  967. buffer_state->_start_position = token->position;
  968. }
  969. gumbo_string_buffer_append_codepoint(
  970. parser, token->v.character, &buffer_state->_buffer);
  971. if (token->type == GUMBO_TOKEN_CHARACTER) {
  972. buffer_state->_type = GUMBO_NODE_TEXT;
  973. }
  974. gumbo_debug("Inserting text token '%c'.\n", token->v.character);
  975. }
  976. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#generic-rcdata-element-parsing-algorithm
  977. static void run_generic_parsing_algorithm(
  978. GumboParser* parser, GumboToken* token, GumboTokenizerEnum lexer_state) {
  979. insert_element_from_token(parser, token);
  980. gumbo_tokenizer_set_state(parser, lexer_state);
  981. parser->_parser_state->_original_insertion_mode =
  982. parser->_parser_state->_insertion_mode;
  983. parser->_parser_state->_insertion_mode = GUMBO_INSERTION_MODE_TEXT;
  984. }
  985. static void acknowledge_self_closing_tag(GumboParser* parser) {
  986. parser->_parser_state->_self_closing_flag_acknowledged = true;
  987. }
  988. // Returns true if there's an anchor tag in the list of active formatting
  989. // elements, and fills in its index if so.
  990. static bool find_last_anchor_index(GumboParser* parser, int* anchor_index) {
  991. GumboVector* elements = &parser->_parser_state->_active_formatting_elements;
  992. for (int i = elements->length; --i >= 0; ) {
  993. GumboNode* node = elements->data[i];
  994. if (node == &kActiveFormattingScopeMarker) {
  995. return false;
  996. }
  997. if (node_tag_is(node, GUMBO_TAG_A)) {
  998. *anchor_index = i;
  999. return true;
  1000. }
  1001. }
  1002. return false;
  1003. }
  1004. // Counts the number of open formatting elements in the list of active
  1005. // formatting elements (after the last active scope marker) that have a specific
  1006. // tag. If this is > 0, then earliest_matching_index will be filled in with the
  1007. // index of the first such element.
  1008. static int count_formatting_elements_of_tag(
  1009. GumboParser* parser, const GumboNode* desired_node,
  1010. int* earliest_matching_index) {
  1011. const GumboElement* desired_element = &desired_node->v.element;
  1012. GumboVector* elements = &parser->_parser_state->_active_formatting_elements;
  1013. int num_identical_elements = 0;
  1014. for (int i = elements->length; --i >= 0; ) {
  1015. GumboNode* node = elements->data[i];
  1016. if (node == &kActiveFormattingScopeMarker) {
  1017. break;
  1018. }
  1019. assert(node->type == GUMBO_NODE_ELEMENT);
  1020. GumboElement* element = &node->v.element;
  1021. if (node_tag_is(node, desired_element->tag) &&
  1022. element->tag_namespace == desired_element->tag_namespace &&
  1023. all_attributes_match(&element->attributes,
  1024. &desired_element->attributes)) {
  1025. num_identical_elements++;
  1026. *earliest_matching_index = i;
  1027. }
  1028. }
  1029. return num_identical_elements;
  1030. }
  1031. // http://www.whatwg.org/specs/web-apps/current-work/complete/parsing.html#reconstruct-the-active-formatting-elements
  1032. static void add_formatting_element(GumboParser* parser, const GumboNode* node) {
  1033. assert(node == &kActiveFormattingScopeMarker ||
  1034. node->type == GUMBO_NODE_ELEMENT);
  1035. GumboVector* elements = &parser->_parser_state->_active_formatting_elements;
  1036. if (node == &kActiveFormattingScopeMarker) {
  1037. gumbo_debug("Adding a scope marker.\n");
  1038. } else {
  1039. gumbo_debug("Adding a formatting element.\n");
  1040. }
  1041. // Hunt for identical elements.
  1042. int earliest_identical_element = elements->length;
  1043. int num_identical_elements = count_formatting_elements_of_tag(
  1044. parser, node, &earliest_identical_element);
  1045. // Noah's Ark clause: if there're at least 3, remove the earliest.
  1046. if (num_identical_elements >= 3) {
  1047. gumbo_debug("Noah's ark clause: removing element at %d.\n",
  1048. earliest_identical_element);
  1049. gumbo_vector_remove_at(parser, earliest_identical_element, elements);
  1050. }
  1051. gumbo_vector_add(parser, (void*) node, elements);
  1052. }
  1053. static bool is_open_element(GumboParser* parser, const GumboNode* node) {
  1054. GumboVector* open_elements = &parser->_parser_state->_open_elements;
  1055. for (int i = 0; i < open_elements->length; ++i) {
  1056. if (open_elements->data[i] == node) {
  1057. return true;
  1058. }
  1059. }
  1060. return false;
  1061. }
  1062. // Clones attributes, tags, etc. of a node, but does not copy the content. The
  1063. // clone shares no structure with the original node: all owned strings and
  1064. // values are fresh copies.
  1065. GumboNode* clone_node(
  1066. GumboParser* parser, const GumboNode* node, GumboParseFlags reason) {
  1067. assert(node->type == GUMBO_NODE_ELEMENT);
  1068. GumboNode* new_node = gumbo_parser_allocate(parser, sizeof(GumboNode));
  1069. *new_node = *node;
  1070. new_node->parent = NULL;
  1071. new_node->index_within_parent = -1;
  1072. // Clear the GUMBO_INSERTION_IMPLICIT_END_TAG flag, as the cloned node may
  1073. // have a separate end tag.
  1074. new_node->parse_flags &= ~GUMBO_INSERTION_IMPLICIT_END_TAG;
  1075. new_node->parse_flags |= reason | GUMBO_INSERTION_BY_PARSER;
  1076. GumboElement* element = &new_node->v.element;
  1077. gumbo_vector_init(parser, 1, &element->children);
  1078. const GumboVector* old_attributes = &node->v.element.attributes;
  1079. gumbo_vector_init(parser, old_attributes->length, &element->attributes);
  1080. for (int i = 0; i < old_attributes->length; ++i) {
  1081. const GumboAttribute* old_attr = old_attributes->data[i];
  1082. GumboAttribute* attr =
  1083. gumbo_parser_allocate(parser, sizeof(GumboAttribute));
  1084. *attr = *old_attr;
  1085. attr->name = gumbo_copy_stringz(parser, old_attr->name);
  1086. attr->value = gumbo_copy_stringz(parser, old_attr->value);
  1087. gumbo_vector_add(parser, attr, &element->attributes);
  1088. }
  1089. return new_node;
  1090. }
  1091. // "Reconstruct active formatting elements" part of the spec.
  1092. // This implementation is based on the html5lib translation from the mess of
  1093. // GOTOs in the spec to reasonably structured programming.
  1094. // http://code.google.com/p/html5lib/source/browse/python/html5lib/treebuilders/_base.py
  1095. static void reconstruct_active_formatting_elements(GumboParser* parser) {
  1096. GumboVector* elements = &parser->_parser_state->_active_formatting_elements;
  1097. // Step 1
  1098. if (elements->length == 0) {
  1099. return;
  1100. }
  1101. // Step 2 & 3
  1102. int i = elements->length - 1;
  1103. const GumboNode* element = elements->data[i];
  1104. if (element == &kActiveFormattingScopeMarker ||
  1105. is_open_element(parser, element)) {
  1106. return;
  1107. }
  1108. // Step 6
  1109. do {
  1110. if (i == 0) {
  1111. // Step 4
  1112. i = -1; // Incremented to 0 below.
  1113. break;
  1114. }
  1115. // Step 5
  1116. element = elements->data[--i];
  1117. } while (element != &kActiveFormattingScopeMarker &&
  1118. !is_open_element(parser, element));
  1119. ++i;
  1120. gumbo_debug("Reconstructing elements from %d on %s parent.\n", i,
  1121. gumbo_normalized_tagname(
  1122. get_current_node(parser)->v.element.tag));
  1123. for(; i < elements->length; ++i) {
  1124. // Step 7 & 8.
  1125. assert(elements->length > 0);
  1126. assert(i < elements->length);
  1127. element = elements->data[i];
  1128. assert(element != &kActiveFormattingScopeMarker);
  1129. GumboNode* clone = clone_node(
  1130. parser, element, GUMBO_INSERTION_RECONSTRUCTED_FORMATTING_ELEMENT);
  1131. // Step 9.
  1132. insert_element(parser, clone, true);
  1133. // Step 10.
  1134. elements->data[i] = clone;
  1135. gumbo_debug("Reconstructed %s element at %d.\n",
  1136. gumbo_normalized_tagname(clone->v.element.tag), i);
  1137. }
  1138. }
  1139. static void clear_active_formatting_elements(GumboParser* parser) {
  1140. GumboVector* elements = &parser->_parser_state->_active_formatting_elements;
  1141. int num_elements_cleared = 0;
  1142. const GumboNode* node;
  1143. do {
  1144. node = gumbo_vector_pop(parser, elements);
  1145. ++num_elements_cleared;
  1146. } while(node && node != &kActiveFormattingScopeMarker);
  1147. gumbo_debug("Cleared %d elements from active formatting list.\n",
  1148. num_elements_cleared);
  1149. }
  1150. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#the-initial-insertion-mode
  1151. static GumboQuirksModeEnum compute_quirks_mode(
  1152. const GumboTokenDocType* doctype) {
  1153. if (doctype->force_quirks ||
  1154. strcmp(doctype->name, kDoctypeHtml.data) ||
  1155. is_in_static_list(doctype->public_identifier,
  1156. kQuirksModePublicIdPrefixes, false) ||
  1157. is_in_static_list(doctype->public_identifier,
  1158. kQuirksModePublicIdExactMatches, true) ||
  1159. is_in_static_list(doctype->system_identifier,
  1160. kQuirksModeSystemIdExactMatches, true) ||
  1161. (is_in_static_list(doctype->public_identifier,
  1162. kLimitedQuirksRequiresSystemIdPublicIdPrefixes, false)
  1163. && !doctype->has_system_identifier)) {
  1164. return GUMBO_DOCTYPE_QUIRKS;
  1165. } else if (
  1166. is_in_static_list(doctype->public_identifier,
  1167. kLimitedQuirksPublicIdPrefixes, false) ||
  1168. (is_in_static_list(doctype->public_identifier,
  1169. kLimitedQuirksRequiresSystemIdPublicIdPrefixes, false)
  1170. && doctype->has_system_identifier)) {
  1171. return GUMBO_DOCTYPE_LIMITED_QUIRKS;
  1172. }
  1173. return GUMBO_DOCTYPE_NO_QUIRKS;
  1174. }
  1175. // The following functions are all defined by the "has an element in __ scope"
  1176. // sections of the HTML5 spec:
  1177. // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#has-an-element-in-the-specific-scope
  1178. // The basic idea behind them is that they check for an element of the given tag
  1179. // name, contained within a scope formed by a set of other tag names. For
  1180. // example, "has an element in list scope" looks for an element of the given tag
  1181. // within the nearest enclosing <ol> or <ul>, along with a bunch of generic
  1182. // element types that serve to "firewall" their content from the rest of the
  1183. // document.
  1184. static bool has_an_element_in_specific_scope(
  1185. GumboParser* parser, GumboVector* /* GumboTag */ expected, bool negate, ...) {
  1186. GumboVector* open_elements = &parser->_parser_state->_open_elements;
  1187. va_list args;
  1188. va_start(args, negate);
  1189. // va_arg can only run through the list once, so we copy it to an GumboVector
  1190. // here. I wonder if it'd make more sense to make tags the GumboVector*
  1191. // parameter and 'expected' a vararg list, but that'd require changing a lot
  1192. // of code for unknown benefit. We may want to change the representation of
  1193. // these tag sets anyway, to something more efficient.
  1194. GumboVector tags;
  1195. gumbo_vector_init(parser, 10, &tags);
  1196. for (GumboTag tag = va_arg(args, GumboTag); tag != GUMBO_TAG_LAST;
  1197. tag = va_arg(args, GumboTag)) {
  1198. // We store the tags inline instead of storing pointers to them.
  1199. gumbo_vector_add(parser, (void*) tag, &tags);
  1200. }
  1201. va_end(args);
  1202. bool result = false;
  1203. for (int i = open_elements->length; --i >= 0; ) {
  1204. const GumboNode* node = open_elements->data[i];
  1205. if (node->type != GUMBO_NODE_ELEMENT) {
  1206. continue;
  1207. }
  1208. GumboTag node_tag = node->v.element.tag;
  1209. for (int j = 0; j < expected->length; ++j) {
  1210. GumboTag expected_tag = (GumboTag) expected->data[j];
  1211. if (node_tag == expected_tag) {
  1212. result = true;
  1213. goto cleanup;
  1214. }
  1215. }
  1216. bool found_tag = false;
  1217. for (int j = 0; j < tags.length; ++j) {
  1218. GumboTag tag = (GumboTag) tags.data[j];
  1219. if (tag == node_tag) {
  1220. found_tag = true;
  1221. break;
  1222. }
  1223. }
  1224. if (negate != found_tag) {
  1225. result = false;
  1226. goto cleanup;
  1227. }
  1228. }
  1229. cleanup:
  1230. gumbo_vector_destroy(parser, &tags);
  1231. return result;
  1232. }
  1233. // This is a bit of a hack to stack-allocate a one-element GumboVector name
  1234. // 'varname' containing the 'from_var' variable, since it's used in nearly all
  1235. // the subsequent helper functions. Note the use of void* and casts instead of
  1236. // GumboTag; this is so the alignment requirements are the same as GumboVector
  1237. // and the data inside it can be freely accessed as if it were a normal
  1238. // GumboVector.
  1239. #define DECLARE_ONE_ELEMENT_GUMBO_VECTOR(varname, from_var) \
  1240. void* varname ## _tmp_array[1] = { (void*) from_var }; \
  1241. GumboVector varname = { varname ## _tmp_array, 1, 1 }
  1242. // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#has-an-element-in-scope
  1243. static bool has_an_element_in_scope(GumboParser* parser, GumboTag tag) {
  1244. DECLARE_ONE_ELEMENT_GUMBO_VECTOR(tags, tag);
  1245. return has_an_element_in_specific_scope(
  1246. parser, &tags, false, GUMBO_TAG_APPLET, GUMBO_TAG_CAPTION, GUMBO_TAG_HTML,
  1247. GUMBO_TAG_TABLE, GUMBO_TAG_TD, GUMBO_TAG_TH, GUMBO_TAG_MARQUEE,
  1248. GUMBO_TAG_OBJECT, GUMBO_TAG_MI, GUMBO_TAG_MO, GUMBO_TAG_MN, GUMBO_TAG_MS,
  1249. GUMBO_TAG_MTEXT, GUMBO_TAG_ANNOTATION_XML, GUMBO_TAG_FOREIGNOBJECT,
  1250. GUMBO_TAG_DESC, GUMBO_TAG_TITLE, GUMBO_TAG_LAST);
  1251. }
  1252. // Like "has an element in scope", but for the specific case of looking for a
  1253. // unique target node, not for any node with a given tag name. This duplicates
  1254. // much of the algorithm from has_an_element_in_specific_scope because the
  1255. // predicate is different when checking for an exact node, and it's easier &
  1256. // faster just to duplicate the code for this one case than to try and
  1257. // parameterize it.
  1258. static bool has_node_in_scope(GumboParser* parser, const GumboNode* node) {
  1259. GumboVector* open_elements = &parser->_parser_state->_open_elements;
  1260. for (int i = open_elements->length; --i >= 0; ) {
  1261. const GumboNode* current = open_elements->data[i];
  1262. if (current == node) {
  1263. return true;
  1264. }
  1265. if (current->type != GUMBO_NODE_ELEMENT) {
  1266. continue;
  1267. }
  1268. if (node_tag_in(
  1269. current, GUMBO_TAG_APPLET, GUMBO_TAG_CAPTION, GUMBO_TAG_HTML,
  1270. GUMBO_TAG_TABLE, GUMBO_TAG_TD, GUMBO_TAG_TH, GUMBO_TAG_MARQUEE,
  1271. GUMBO_TAG_OBJECT, GUMBO_TAG_MI, GUMBO_TAG_MO, GUMBO_TAG_MN,
  1272. GUMBO_TAG_MS, GUMBO_TAG_MTEXT, GUMBO_TAG_ANNOTATION_XML,
  1273. GUMBO_TAG_FOREIGNOBJECT, GUMBO_TAG_DESC, GUMBO_TAG_TITLE,
  1274. GUMBO_TAG_LAST)) {
  1275. return false;
  1276. }
  1277. }
  1278. assert(false);
  1279. return false;
  1280. }
  1281. // Like has_an_element_in_scope, but restricts the expected tag to a range of
  1282. // possible tag names instead of just a single one.
  1283. static bool has_an_element_in_scope_with_tagname(GumboParser* parser, ...) {
  1284. GumboVector tags;
  1285. // 6 = arbitrary initial size for vector, chosen because the major use-case
  1286. // for this method is heading tags, of which there are 6.
  1287. gumbo_vector_init(parser, 6, &tags);
  1288. va_list args;
  1289. va_start(args, parser);
  1290. for (GumboTag tag = va_arg(args, GumboTag); tag != GUMBO_TAG_LAST;
  1291. tag = va_arg(args, GumboTag)) {
  1292. gumbo_vector_add(parser, (void*) tag, &tags);
  1293. }
  1294. bool found = has_an_element_in_specific_scope(
  1295. parser, &tags, false, GUMBO_TAG_APPLET, GUMBO_TAG_CAPTION, GUMBO_TAG_HTML,
  1296. GUMBO_TAG_TABLE, GUMBO_TAG_TD, GUMBO_TAG_TH, GUMBO_TAG_MARQUEE,
  1297. GUMBO_TAG_OBJECT, GUMBO_TAG_MI, GUMBO_TAG_MO, GUMBO_TAG_MN, GUMBO_TAG_MS,
  1298. GUMBO_TAG_MTEXT, GUMBO_TAG_ANNOTATION_XML, GUMBO_TAG_FOREIGNOBJECT,
  1299. GUMBO_TAG_DESC, GUMBO_TAG_TITLE, GUMBO_TAG_LAST);
  1300. gumbo_vector_destroy(parser, &tags);
  1301. va_end(args);
  1302. return found;
  1303. }
  1304. // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#has-an-element-in-list-item-scope
  1305. static bool has_an_element_in_list_scope(GumboParser* parser, GumboTag tag) {
  1306. DECLARE_ONE_ELEMENT_GUMBO_VECTOR(tags, tag);
  1307. return has_an_element_in_specific_scope(
  1308. parser, &tags, false, GUMBO_TAG_APPLET, GUMBO_TAG_CAPTION, GUMBO_TAG_HTML,
  1309. GUMBO_TAG_TABLE, GUMBO_TAG_TD, GUMBO_TAG_TH, GUMBO_TAG_MARQUEE,
  1310. GUMBO_TAG_OBJECT, GUMBO_TAG_MI, GUMBO_TAG_MO, GUMBO_TAG_MN, GUMBO_TAG_MS,
  1311. GUMBO_TAG_MTEXT, GUMBO_TAG_ANNOTATION_XML, GUMBO_TAG_FOREIGNOBJECT,
  1312. GUMBO_TAG_DESC, GUMBO_TAG_TITLE, GUMBO_TAG_OL, GUMBO_TAG_UL,
  1313. GUMBO_TAG_LAST);
  1314. }
  1315. // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#has-an-element-in-button-scope
  1316. static bool has_an_element_in_button_scope(GumboParser* parser, GumboTag tag) {
  1317. DECLARE_ONE_ELEMENT_GUMBO_VECTOR(tags, tag);
  1318. return has_an_element_in_specific_scope(
  1319. parser, &tags, false, GUMBO_TAG_APPLET, GUMBO_TAG_CAPTION, GUMBO_TAG_HTML,
  1320. GUMBO_TAG_TABLE, GUMBO_TAG_TD, GUMBO_TAG_TH, GUMBO_TAG_MARQUEE,
  1321. GUMBO_TAG_OBJECT, GUMBO_TAG_MI, GUMBO_TAG_MO, GUMBO_TAG_MN, GUMBO_TAG_MS,
  1322. GUMBO_TAG_MTEXT, GUMBO_TAG_ANNOTATION_XML, GUMBO_TAG_FOREIGNOBJECT,
  1323. GUMBO_TAG_DESC, GUMBO_TAG_TITLE, GUMBO_TAG_BUTTON, GUMBO_TAG_LAST);
  1324. }
  1325. // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#has-an-element-in-table-scope
  1326. static bool has_an_element_in_table_scope(GumboParser* parser, GumboTag tag) {
  1327. DECLARE_ONE_ELEMENT_GUMBO_VECTOR(tags, tag);
  1328. return has_an_element_in_specific_scope(
  1329. parser, &tags, false, GUMBO_TAG_HTML, GUMBO_TAG_TABLE, GUMBO_TAG_LAST);
  1330. }
  1331. // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#has-an-element-in-select-scope
  1332. static bool has_an_element_in_select_scope(GumboParser* parser, GumboTag tag) {
  1333. DECLARE_ONE_ELEMENT_GUMBO_VECTOR(tags, tag);
  1334. return has_an_element_in_specific_scope(
  1335. parser, &tags, true, GUMBO_TAG_OPTGROUP, GUMBO_TAG_OPTION,
  1336. GUMBO_TAG_LAST);
  1337. }
  1338. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#generate-implied-end-tags
  1339. // "exception" is the "element to exclude from the process" listed in the spec.
  1340. // Pass GUMBO_TAG_LAST to not exclude any of them.
  1341. static void generate_implied_end_tags(GumboParser* parser, GumboTag exception) {
  1342. for (;
  1343. node_tag_in(get_current_node(parser), GUMBO_TAG_DD, GUMBO_TAG_DT,
  1344. GUMBO_TAG_LI, GUMBO_TAG_OPTION, GUMBO_TAG_OPTGROUP,
  1345. GUMBO_TAG_P, GUMBO_TAG_RP, GUMBO_TAG_RT, GUMBO_TAG_LAST) &&
  1346. !node_tag_is(get_current_node(parser), exception);
  1347. pop_current_node(parser));
  1348. }
  1349. // This factors out the clauses relating to "act as if an end tag token with tag
  1350. // name "table" had been seen. Returns true if there's a table element in table
  1351. // scope which was successfully closed, false if not and the token should be
  1352. // ignored. Does not add parse errors; callers should handle that.
  1353. static bool close_table(GumboParser* parser) {
  1354. if (!has_an_element_in_table_scope(parser, GUMBO_TAG_TABLE)) {
  1355. return false;
  1356. }
  1357. GumboNode* node = pop_current_node(parser);
  1358. while (!node_tag_is(node, GUMBO_TAG_TABLE)) {
  1359. node = pop_current_node(parser);
  1360. }
  1361. reset_insertion_mode_appropriately(parser);
  1362. return true;
  1363. }
  1364. // This factors out the clauses relating to "act as if an end tag token with tag
  1365. // name `cell_tag` had been seen".
  1366. static bool close_table_cell(GumboParser* parser, const GumboToken* token,
  1367. GumboTag cell_tag) {
  1368. bool result = true;
  1369. generate_implied_end_tags(parser, GUMBO_TAG_LAST);
  1370. const GumboNode* node = get_current_node(parser);
  1371. if (!node_tag_is(node, cell_tag)) {
  1372. add_parse_error(parser, token);
  1373. result = false;
  1374. }
  1375. do {
  1376. node = pop_current_node(parser);
  1377. } while (!node_tag_is(node, cell_tag));
  1378. clear_active_formatting_elements(parser);
  1379. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_ROW);
  1380. return result;
  1381. }
  1382. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#close-the-cell
  1383. // This holds the logic to determine whether we should close a <td> or a <th>.
  1384. static bool close_current_cell(GumboParser* parser, const GumboToken* token) {
  1385. if (has_an_element_in_table_scope(parser, GUMBO_TAG_TD)) {
  1386. assert(!has_an_element_in_table_scope(parser, GUMBO_TAG_TH));
  1387. return close_table_cell(parser, token, GUMBO_TAG_TD);
  1388. } else {
  1389. assert(has_an_element_in_table_scope(parser, GUMBO_TAG_TH));
  1390. return close_table_cell(parser, token, GUMBO_TAG_TH);
  1391. }
  1392. }
  1393. // This factors out the "act as if an end tag of tag name 'select' had been
  1394. // seen" clause of the spec, since it's referenced in several places. It pops
  1395. // all nodes from the stack until the current <select> has been closed, then
  1396. // resets the insertion mode appropriately.
  1397. static void close_current_select(GumboParser* parser) {
  1398. GumboNode* node = pop_current_node(parser);
  1399. while (!node_tag_is(node, GUMBO_TAG_SELECT)) {
  1400. node = pop_current_node(parser);
  1401. }
  1402. reset_insertion_mode_appropriately(parser);
  1403. }
  1404. // The list of nodes in the "special" category:
  1405. // http://www.whatwg.org/specs/web-apps/current-work/complete/parsing.html#special
  1406. static bool is_special_node(const GumboNode* node) {
  1407. assert(node->type == GUMBO_NODE_ELEMENT);
  1408. switch (node->v.element.tag_namespace) {
  1409. case GUMBO_NAMESPACE_HTML:
  1410. return node_tag_in(node,
  1411. GUMBO_TAG_ADDRESS, GUMBO_TAG_APPLET, GUMBO_TAG_AREA,
  1412. GUMBO_TAG_ARTICLE, GUMBO_TAG_ASIDE, GUMBO_TAG_BASE,
  1413. GUMBO_TAG_BASEFONT, GUMBO_TAG_BGSOUND, GUMBO_TAG_BLOCKQUOTE,
  1414. GUMBO_TAG_BODY, GUMBO_TAG_BR, GUMBO_TAG_BUTTON, GUMBO_TAG_CAPTION,
  1415. GUMBO_TAG_CENTER, GUMBO_TAG_COL, GUMBO_TAG_COLGROUP,
  1416. GUMBO_TAG_MENUITEM, GUMBO_TAG_DD, GUMBO_TAG_DETAILS, GUMBO_TAG_DIR,
  1417. GUMBO_TAG_DIV, GUMBO_TAG_DL, GUMBO_TAG_DT, GUMBO_TAG_EMBED,
  1418. GUMBO_TAG_FIELDSET, GUMBO_TAG_FIGCAPTION, GUMBO_TAG_FIGURE,
  1419. GUMBO_TAG_FOOTER, GUMBO_TAG_FORM, GUMBO_TAG_FRAME,
  1420. GUMBO_TAG_FRAMESET, GUMBO_TAG_H1, GUMBO_TAG_H2, GUMBO_TAG_H3,
  1421. GUMBO_TAG_H4, GUMBO_TAG_H5, GUMBO_TAG_H6, GUMBO_TAG_HEAD,
  1422. GUMBO_TAG_HEADER, GUMBO_TAG_HGROUP, GUMBO_TAG_HR, GUMBO_TAG_HTML,
  1423. GUMBO_TAG_IFRAME, GUMBO_TAG_IMG, GUMBO_TAG_INPUT, GUMBO_TAG_ISINDEX,
  1424. GUMBO_TAG_LI, GUMBO_TAG_LINK, GUMBO_TAG_LISTING, GUMBO_TAG_MARQUEE,
  1425. GUMBO_TAG_MENU, GUMBO_TAG_META, GUMBO_TAG_NAV, GUMBO_TAG_NOEMBED,
  1426. GUMBO_TAG_NOFRAMES, GUMBO_TAG_NOSCRIPT, GUMBO_TAG_OBJECT,
  1427. GUMBO_TAG_OL, GUMBO_TAG_P, GUMBO_TAG_PARAM, GUMBO_TAG_PLAINTEXT,
  1428. GUMBO_TAG_PRE, GUMBO_TAG_SCRIPT, GUMBO_TAG_SECTION, GUMBO_TAG_SELECT,
  1429. GUMBO_TAG_STYLE, GUMBO_TAG_SUMMARY, GUMBO_TAG_TABLE, GUMBO_TAG_TBODY,
  1430. GUMBO_TAG_TD, GUMBO_TAG_TEXTAREA, GUMBO_TAG_TFOOT, GUMBO_TAG_TH,
  1431. GUMBO_TAG_THEAD, GUMBO_TAG_TITLE, GUMBO_TAG_TR, GUMBO_TAG_UL,
  1432. GUMBO_TAG_WBR, GUMBO_TAG_XMP, GUMBO_TAG_LAST);
  1433. case GUMBO_NAMESPACE_MATHML:
  1434. return node_tag_in(node,
  1435. GUMBO_TAG_MI, GUMBO_TAG_MO, GUMBO_TAG_MN, GUMBO_TAG_MS,
  1436. GUMBO_TAG_MTEXT, GUMBO_TAG_ANNOTATION_XML, GUMBO_TAG_LAST);
  1437. case GUMBO_NAMESPACE_SVG:
  1438. return node_tag_in(node,
  1439. GUMBO_TAG_FOREIGNOBJECT, GUMBO_TAG_DESC, GUMBO_TAG_LAST);
  1440. }
  1441. abort();
  1442. return false; // Pacify compiler.
  1443. }
  1444. // Implicitly closes currently open tags until it reaches an element with the
  1445. // specified tag name. If the elements closed are in the set handled by
  1446. // generate_implied_end_tags, this is normal operation and this function returns
  1447. // true. Otherwise, a parse error is recorded and this function returns false.
  1448. static bool implicitly_close_tags(
  1449. GumboParser* parser, GumboToken* token, GumboTag target) {
  1450. bool result = true;
  1451. generate_implied_end_tags(parser, target);
  1452. if (!node_tag_is(get_current_node(parser), target)) {
  1453. add_parse_error(parser, token);
  1454. while (!node_tag_is(get_current_node(parser), target)) {
  1455. pop_current_node(parser);
  1456. }
  1457. result = false;
  1458. }
  1459. assert(node_tag_is(get_current_node(parser), target));
  1460. pop_current_node(parser);
  1461. return result;
  1462. }
  1463. // If the stack of open elements has a <p> tag in button scope, this acts as if
  1464. // a </p> tag was encountered, implicitly closing tags. Returns false if a
  1465. // parse error occurs. This is a convenience function because this particular
  1466. // clause appears several times in the spec.
  1467. static bool maybe_implicitly_close_p_tag(GumboParser* parser, GumboToken* token) {
  1468. if (has_an_element_in_button_scope(parser, GUMBO_TAG_P)) {
  1469. return implicitly_close_tags(parser, token, GUMBO_TAG_P);
  1470. }
  1471. return true;
  1472. }
  1473. // Convenience function to encapsulate the logic for closing <li> or <dd>/<dt>
  1474. // tags. Pass true to is_li for handling <li> tags, false for <dd> and <dt>.
  1475. static void maybe_implicitly_close_list_tag(
  1476. GumboParser* parser, GumboToken* token, bool is_li) {
  1477. GumboParserState* state = parser->_parser_state;
  1478. state->_frameset_ok = false;
  1479. for (int i = state->_open_elements.length; --i >= 0; ) {
  1480. const GumboNode* node = state->_open_elements.data[i];
  1481. bool is_list_tag = is_li ?
  1482. node_tag_is(node, GUMBO_TAG_LI) :
  1483. node_tag_in(node, GUMBO_TAG_DD, GUMBO_TAG_DT, GUMBO_TAG_LAST);
  1484. if (is_list_tag) {
  1485. implicitly_close_tags(parser, token, node->v.element.tag);
  1486. return;
  1487. }
  1488. if (is_special_node(node) &&
  1489. !node_tag_in(node, GUMBO_TAG_ADDRESS, GUMBO_TAG_DIV, GUMBO_TAG_P,
  1490. GUMBO_TAG_LAST)) {
  1491. return;
  1492. }
  1493. }
  1494. }
  1495. static void merge_attributes(
  1496. GumboParser* parser, GumboToken* token, GumboNode* node) {
  1497. assert(token->type == GUMBO_TOKEN_START_TAG);
  1498. assert(node->type == GUMBO_NODE_ELEMENT);
  1499. const GumboVector* token_attr = &token->v.start_tag.attributes;
  1500. GumboVector* node_attr = &node->v.element.attributes;
  1501. for (int i = 0; i < token_attr->length; ++i) {
  1502. GumboAttribute* attr = token_attr->data[i];
  1503. if (!gumbo_get_attribute(node_attr, attr->name)) {
  1504. // Ownership of the attribute is transferred by this gumbo_vector_add,
  1505. // so it has to be nulled out of the original token so it doesn't get
  1506. // double-deleted.
  1507. gumbo_vector_add(parser, attr, node_attr);
  1508. token_attr->data[i] = NULL;
  1509. }
  1510. }
  1511. // When attributes are merged, it means the token has been ignored and merged
  1512. // with another token, so we need to free its memory. The attributes that are
  1513. // transferred need to be nulled-out in the vector above so that they aren't
  1514. // double-deleted.
  1515. gumbo_token_destroy(parser, token);
  1516. #ifndef NDEBUG
  1517. // Mark this sentinel so the assertion in the main loop knows it's been
  1518. // destroyed.
  1519. token->v.start_tag.attributes = kGumboEmptyVector;
  1520. #endif
  1521. }
  1522. const char* gumbo_normalize_svg_tagname(const GumboStringPiece* tag) {
  1523. for (int i = 0;
  1524. i < sizeof(kSvgTagReplacements) / sizeof(ReplacementEntry); ++i) {
  1525. const ReplacementEntry* entry = &kSvgTagReplacements[i];
  1526. if (gumbo_string_equals_ignore_case(tag, &entry->from)) {
  1527. return entry->to.data;
  1528. }
  1529. }
  1530. return NULL;
  1531. }
  1532. // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#adjust-foreign-attributes
  1533. // This destructively modifies any matching attributes on the token and sets the
  1534. // namespace appropriately.
  1535. static void adjust_foreign_attributes(GumboParser* parser, GumboToken* token) {
  1536. assert(token->type == GUMBO_TOKEN_START_TAG);
  1537. const GumboVector* attributes = &token->v.start_tag.attributes;
  1538. for (int i = 0;
  1539. i < sizeof(kForeignAttributeReplacements) /
  1540. sizeof(NamespacedAttributeReplacement); ++i) {
  1541. const NamespacedAttributeReplacement* entry =
  1542. &kForeignAttributeReplacements[i];
  1543. GumboAttribute* attr = gumbo_get_attribute(attributes, entry->from);
  1544. if (!attr) {
  1545. continue;
  1546. }
  1547. gumbo_parser_deallocate(parser, (void*) attr->name);
  1548. attr->attr_namespace = entry->attr_namespace;
  1549. attr->name = gumbo_copy_stringz(parser, entry->local_name);
  1550. }
  1551. }
  1552. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#adjust-svg-attributes
  1553. // This destructively modifies any matching attributes on the token.
  1554. static void adjust_svg_attributes(GumboParser* parser, GumboToken* token) {
  1555. assert(token->type == GUMBO_TOKEN_START_TAG);
  1556. const GumboVector* attributes = &token->v.start_tag.attributes;
  1557. for (int i = 0;
  1558. i < sizeof(kSvgAttributeReplacements) / sizeof(ReplacementEntry); ++i) {
  1559. const ReplacementEntry* entry = &kSvgAttributeReplacements[i];
  1560. GumboAttribute* attr = gumbo_get_attribute(attributes, entry->from.data);
  1561. if (!attr) {
  1562. continue;
  1563. }
  1564. gumbo_parser_deallocate(parser, (void*) attr->name);
  1565. attr->name = gumbo_copy_stringz(parser, entry->to.data);
  1566. }
  1567. }
  1568. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#adjust-mathml-attributes
  1569. // Note that this may destructively modify the token with the new attribute
  1570. // value.
  1571. static void adjust_mathml_attributes(GumboParser* parser, GumboToken* token) {
  1572. assert(token->type == GUMBO_TOKEN_START_TAG);
  1573. GumboAttribute* attr = gumbo_get_attribute(
  1574. &token->v.start_tag.attributes, "definitionurl");
  1575. if (!attr) {
  1576. return;
  1577. }
  1578. gumbo_parser_deallocate(parser, (void*) attr->name);
  1579. attr->name = gumbo_copy_stringz(parser, "definitionURL");
  1580. }
  1581. static bool doctype_matches(
  1582. const GumboTokenDocType* doctype,
  1583. const GumboStringPiece* public_id,
  1584. const GumboStringPiece* system_id,
  1585. bool allow_missing_system_id) {
  1586. return !strcmp(doctype->public_identifier, public_id->data) &&
  1587. (allow_missing_system_id || doctype->has_system_identifier) &&
  1588. !strcmp(doctype->system_identifier, system_id->data);
  1589. }
  1590. static bool maybe_add_doctype_error(
  1591. GumboParser* parser, const GumboToken* token) {
  1592. const GumboTokenDocType* doctype = &token->v.doc_type;
  1593. bool html_doctype = !strcmp(doctype->name, kDoctypeHtml.data);
  1594. if (!html_doctype ||
  1595. doctype->has_public_identifier ||
  1596. (doctype->has_system_identifier && !strcmp(
  1597. doctype->system_identifier, kSystemIdLegacyCompat.data)) ||
  1598. !(html_doctype && (
  1599. doctype_matches(doctype, &kPublicIdHtml4_0,
  1600. &kSystemIdRecHtml4_0, true) ||
  1601. doctype_matches(doctype, &kPublicIdHtml4_01, &kSystemIdHtml4, true) ||
  1602. doctype_matches(doctype, &kPublicIdXhtml1_0,
  1603. &kSystemIdXhtmlStrict1_1, false) ||
  1604. doctype_matches(doctype, &kPublicIdXhtml1_1,
  1605. &kSystemIdXhtml1_1, false)))) {
  1606. add_parse_error(parser, token);
  1607. return false;
  1608. }
  1609. return true;
  1610. }
  1611. static void remove_from_parent(GumboParser* parser, GumboNode* node) {
  1612. if (!node->parent) {
  1613. // The node may not have a parent if, for example, it is a newly-cloned copy
  1614. // of an active formatting element. DOM manipulations continue with the
  1615. // orphaned fragment of the DOM tree until it's appended/foster-parented to
  1616. // the common ancestor at the end of the adoption agency algorithm.
  1617. return;
  1618. }
  1619. assert(node->parent->type == GUMBO_NODE_ELEMENT);
  1620. GumboVector* children = &node->parent->v.element.children;
  1621. int index = gumbo_vector_index_of(children, node);
  1622. assert(index != -1);
  1623. gumbo_vector_remove_at(parser, index, children);
  1624. node->parent = NULL;
  1625. node->index_within_parent = -1;
  1626. for (int i = index; i < children->length; ++i) {
  1627. GumboNode* child = children->data[i];
  1628. child->index_within_parent = i;
  1629. }
  1630. }
  1631. // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#an-introduction-to-error-handling-and-strange-cases-in-the-parser
  1632. // Also described in the "in body" handling for end formatting tags.
  1633. static bool adoption_agency_algorithm(
  1634. GumboParser* parser, GumboToken* token, GumboTag closing_tag) {
  1635. GumboParserState* state = parser->_parser_state;
  1636. gumbo_debug("Entering adoption agency algorithm.\n");
  1637. // Steps 1-3 & 16:
  1638. for (int i = 0; i < 8; ++i) {
  1639. // Step 4.
  1640. GumboNode* formatting_node = NULL;
  1641. int formatting_node_in_open_elements = -1;
  1642. for (int j = state->_active_formatting_elements.length; --j >= 0; ) {
  1643. GumboNode* current_node = state->_active_formatting_elements.data[j];
  1644. if (current_node == &kActiveFormattingScopeMarker) {
  1645. gumbo_debug("Broke on scope marker; aborting.\n");
  1646. // Last scope marker; abort the algorithm.
  1647. return false;
  1648. }
  1649. if (node_tag_is(current_node, closing_tag)) {
  1650. // Found it.
  1651. formatting_node = current_node;
  1652. formatting_node_in_open_elements = gumbo_vector_index_of(
  1653. &state->_open_elements, formatting_node);
  1654. gumbo_debug("Formatting element of tag %s at %d.\n",
  1655. gumbo_normalized_tagname(closing_tag),
  1656. formatting_node_in_open_elements);
  1657. break;
  1658. }
  1659. }
  1660. if (!formatting_node) {
  1661. // No matching tag; not a parse error outright, but fall through to the
  1662. // "any other end tag" clause (which may potentially add a parse error,
  1663. // but not always).
  1664. gumbo_debug("No active formatting elements; aborting.\n");
  1665. return false;
  1666. }
  1667. if (formatting_node_in_open_elements == -1) {
  1668. gumbo_debug("Formatting node not on stack of open elements.\n");
  1669. gumbo_vector_remove(parser, formatting_node,
  1670. &state->_active_formatting_elements);
  1671. return false;
  1672. }
  1673. if (!has_an_element_in_scope(parser, formatting_node->v.element.tag)) {
  1674. add_parse_error(parser, token);
  1675. gumbo_debug("Element not in scope.\n");
  1676. return false;
  1677. }
  1678. if (formatting_node != get_current_node(parser)) {
  1679. add_parse_error(parser, token); // But continue onwards.
  1680. }
  1681. assert(formatting_node);
  1682. assert(!node_tag_is(formatting_node, GUMBO_TAG_HTML));
  1683. assert(!node_tag_is(formatting_node, GUMBO_TAG_BODY));
  1684. // Step 5 & 6.
  1685. GumboNode* furthest_block = NULL;
  1686. for (int j = formatting_node_in_open_elements;
  1687. j < state->_open_elements.length; ++j) {
  1688. assert(j > 0);
  1689. GumboNode* current = state->_open_elements.data[j];
  1690. if (is_special_node(current)) {
  1691. // Step 5.
  1692. furthest_block = current;
  1693. break;
  1694. }
  1695. }
  1696. if (!furthest_block) {
  1697. // Step 6.
  1698. while (get_current_node(parser) != formatting_node) {
  1699. pop_current_node(parser);
  1700. }
  1701. // And the formatting element itself.
  1702. pop_current_node(parser);
  1703. gumbo_vector_remove(parser, formatting_node,
  1704. &state->_active_formatting_elements);
  1705. return false;
  1706. }
  1707. assert(!node_tag_is(furthest_block, GUMBO_TAG_HTML));
  1708. assert(furthest_block);
  1709. // Step 7.
  1710. // Elements may be moved and reparented by this algorithm, so
  1711. // common_ancestor is not necessarily the same as formatting_node->parent.
  1712. GumboNode* common_ancestor =
  1713. state->_open_elements.data[gumbo_vector_index_of(
  1714. &state->_open_elements, formatting_node) - 1];
  1715. gumbo_debug("Common ancestor tag = %s, furthest block tag = %s.\n",
  1716. gumbo_normalized_tagname(common_ancestor->v.element.tag),
  1717. gumbo_normalized_tagname(furthest_block->v.element.tag));
  1718. // Step 8.
  1719. int bookmark = gumbo_vector_index_of(
  1720. &state->_active_formatting_elements, formatting_node);;
  1721. // Step 9.
  1722. GumboNode* node = furthest_block;
  1723. GumboNode* last_node = furthest_block;
  1724. // Must be stored explicitly, in case node is removed from the stack of open
  1725. // elements, to handle step 9.4.
  1726. int saved_node_index = gumbo_vector_index_of(&state->_open_elements, node);
  1727. assert(saved_node_index > 0);
  1728. // Step 9.1-9.3 & 9.11.
  1729. for (int j = 0; j < 3; ++j) {
  1730. // Step 9.4.
  1731. int node_index = gumbo_vector_index_of(&state->_open_elements, node);
  1732. gumbo_debug(
  1733. "Current index: %d, last index: %d.\n", node_index, saved_node_index);
  1734. if (node_index == -1) {
  1735. node_index = saved_node_index;
  1736. }
  1737. saved_node_index = --node_index;
  1738. assert(node_index > 0);
  1739. assert(node_index < state->_open_elements.capacity);
  1740. node = state->_open_elements.data[node_index];
  1741. assert(node->parent);
  1742. // Step 9.5.
  1743. if (gumbo_vector_index_of(
  1744. &state->_active_formatting_elements, node) == -1) {
  1745. gumbo_vector_remove_at(parser, node_index, &state->_open_elements);
  1746. continue;
  1747. } else if (node == formatting_node) {
  1748. // Step 9.6.
  1749. break;
  1750. }
  1751. // Step 9.7.
  1752. int formatting_index = gumbo_vector_index_of(
  1753. &state->_active_formatting_elements, node);
  1754. node = clone_node(parser, node, GUMBO_INSERTION_ADOPTION_AGENCY_CLONED);
  1755. state->_active_formatting_elements.data[formatting_index] = node;
  1756. state->_open_elements.data[node_index] = node;
  1757. // Step 9.8.
  1758. if (last_node == furthest_block) {
  1759. bookmark = formatting_index + 1;
  1760. assert(bookmark <= state->_active_formatting_elements.length);
  1761. }
  1762. // Step 9.9.
  1763. last_node->parse_flags |= GUMBO_INSERTION_ADOPTION_AGENCY_MOVED;
  1764. remove_from_parent(parser, last_node);
  1765. append_node(parser, node, last_node);
  1766. // Step 9.10.
  1767. last_node = node;
  1768. }
  1769. // Step 10.
  1770. gumbo_debug("Removing %s node from parent ",
  1771. gumbo_normalized_tagname(last_node->v.element.tag));
  1772. remove_from_parent(parser, last_node);
  1773. last_node->parse_flags |= GUMBO_INSERTION_ADOPTION_AGENCY_MOVED;
  1774. if (node_tag_in(common_ancestor, GUMBO_TAG_TABLE, GUMBO_TAG_TBODY,
  1775. GUMBO_TAG_TFOOT, GUMBO_TAG_THEAD, GUMBO_TAG_TR,
  1776. GUMBO_TAG_LAST)) {
  1777. gumbo_debug("and foster-parenting it.\n");
  1778. foster_parent_element(parser, last_node);
  1779. } else {
  1780. gumbo_debug("and inserting it into %s.\n",
  1781. gumbo_normalized_tagname(common_ancestor->v.element.tag));
  1782. append_node(parser, common_ancestor, last_node);
  1783. }
  1784. // Step 11.
  1785. GumboNode* new_formatting_node = clone_node(
  1786. parser, formatting_node, GUMBO_INSERTION_ADOPTION_AGENCY_CLONED);
  1787. formatting_node->parse_flags |= GUMBO_INSERTION_IMPLICIT_END_TAG;
  1788. // Step 12. Instead of appending nodes one-by-one, we swap the children
  1789. // vector of furthest_block with the empty children of new_formatting_node,
  1790. // reducing memory traffic and allocations. We still have to reset their
  1791. // parent pointers, though.
  1792. GumboVector temp = new_formatting_node->v.element.children;
  1793. new_formatting_node->v.element.children =
  1794. furthest_block->v.element.children;
  1795. furthest_block->v.element.children = temp;
  1796. temp = new_formatting_node->v.element.children;
  1797. for (int i = 0; i < temp.length; ++i) {
  1798. GumboNode* child = temp.data[i];
  1799. child->parent = new_formatting_node;
  1800. }
  1801. // Step 13.
  1802. append_node(parser, furthest_block, new_formatting_node);
  1803. // Step 14.
  1804. // If the formatting node was before the bookmark, it may shift over all
  1805. // indices after it, so we need to explicitly find the index and possibly
  1806. // adjust the bookmark.
  1807. int formatting_node_index = gumbo_vector_index_of(
  1808. &state->_active_formatting_elements, formatting_node);
  1809. assert(formatting_node_index != -1);
  1810. if (formatting_node_index < bookmark) {
  1811. --bookmark;
  1812. }
  1813. gumbo_vector_remove_at(
  1814. parser, formatting_node_index, &state->_active_formatting_elements);
  1815. assert(bookmark >= 0);
  1816. assert(bookmark <= state->_active_formatting_elements.length);
  1817. gumbo_vector_insert_at(parser, new_formatting_node, bookmark,
  1818. &state->_active_formatting_elements);
  1819. // Step 15.
  1820. gumbo_vector_remove(
  1821. parser, formatting_node, &state->_open_elements);
  1822. int insert_at = gumbo_vector_index_of(
  1823. &state->_open_elements, furthest_block) + 1;
  1824. assert(insert_at >= 0);
  1825. assert(insert_at <= state->_open_elements.length);
  1826. gumbo_vector_insert_at(
  1827. parser, new_formatting_node, insert_at, &state->_open_elements);
  1828. }
  1829. return true;
  1830. }
  1831. // This is here to clean up memory when the spec says "Ignore current token."
  1832. static void ignore_token(GumboParser* parser) {
  1833. GumboToken* token = parser->_parser_state->_current_token;
  1834. // Ownership of the token's internal buffers are normally transferred to the
  1835. // element, but if no element is emitted (as happens in non-verbatim-mode
  1836. // when a token is ignored), we need to free it here to prevent a memory
  1837. // leak.
  1838. gumbo_token_destroy(parser, token);
  1839. #ifndef NDEBUG
  1840. if (token->type == GUMBO_TOKEN_START_TAG) {
  1841. // Mark this sentinel so the assertion in the main loop knows it's been
  1842. // destroyed.
  1843. token->v.start_tag.attributes = kGumboEmptyVector;
  1844. }
  1845. #endif
  1846. }
  1847. // http://www.whatwg.org/specs/web-apps/current-work/complete/the-end.html
  1848. static void finish_parsing(GumboParser* parser) {
  1849. maybe_flush_text_node_buffer(parser);
  1850. GumboParserState* state = parser->_parser_state;
  1851. for (GumboNode* node = pop_current_node(parser); node;
  1852. node = pop_current_node(parser)) {
  1853. if ((node_tag_is(node, GUMBO_TAG_BODY) && state->_closed_body_tag) ||
  1854. (node_tag_is(node, GUMBO_TAG_HTML) && state->_closed_html_tag)) {
  1855. continue;
  1856. }
  1857. node->parse_flags |= GUMBO_INSERTION_IMPLICIT_END_TAG;
  1858. }
  1859. while (pop_current_node(parser)); // Pop them all.
  1860. }
  1861. static bool handle_initial(GumboParser* parser, GumboToken* token) {
  1862. GumboDocument* document = &get_document_node(parser)->v.document;
  1863. if (token->type == GUMBO_TOKEN_WHITESPACE) {
  1864. ignore_token(parser);
  1865. return true;
  1866. } else if (token->type == GUMBO_TOKEN_COMMENT) {
  1867. append_comment_node(parser, get_document_node(parser), token);
  1868. return true;
  1869. } else if (token->type == GUMBO_TOKEN_DOCTYPE) {
  1870. document->has_doctype = true;
  1871. document->name = token->v.doc_type.name;
  1872. document->public_identifier = token->v.doc_type.public_identifier;
  1873. document->system_identifier = token->v.doc_type.system_identifier;
  1874. document->doc_type_quirks_mode = compute_quirks_mode(&token->v.doc_type);
  1875. set_insertion_mode(parser, GUMBO_INSERTION_MODE_BEFORE_HTML);
  1876. return maybe_add_doctype_error(parser, token);
  1877. }
  1878. add_parse_error(parser, token);
  1879. document->doc_type_quirks_mode = GUMBO_DOCTYPE_QUIRKS;
  1880. set_insertion_mode(parser, GUMBO_INSERTION_MODE_BEFORE_HTML);
  1881. parser->_parser_state->_reprocess_current_token = true;
  1882. return true;
  1883. }
  1884. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#the-before-html-insertion-mode
  1885. static bool handle_before_html(GumboParser* parser, GumboToken* token) {
  1886. if (token->type == GUMBO_TOKEN_DOCTYPE) {
  1887. add_parse_error(parser, token);
  1888. ignore_token(parser);
  1889. return false;
  1890. } else if (token->type == GUMBO_TOKEN_COMMENT) {
  1891. append_comment_node(parser, get_document_node(parser), token);
  1892. return true;
  1893. } else if (token->type == GUMBO_TOKEN_WHITESPACE) {
  1894. ignore_token(parser);
  1895. return true;
  1896. } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
  1897. GumboNode* html_node = insert_element_from_token(parser, token);
  1898. parser->_output->root = html_node;
  1899. set_insertion_mode(parser, GUMBO_INSERTION_MODE_BEFORE_HEAD);
  1900. return true;
  1901. } else if (token->type == GUMBO_TOKEN_END_TAG && !tag_in(
  1902. token, false, GUMBO_TAG_HEAD, GUMBO_TAG_BODY, GUMBO_TAG_HTML,
  1903. GUMBO_TAG_BR, GUMBO_TAG_LAST)) {
  1904. add_parse_error(parser, token);
  1905. ignore_token(parser);
  1906. return false;
  1907. } else {
  1908. GumboNode* html_node = insert_element_of_tag_type(
  1909. parser, GUMBO_TAG_HTML, GUMBO_INSERTION_IMPLIED);
  1910. assert(html_node);
  1911. parser->_output->root = html_node;
  1912. set_insertion_mode(parser, GUMBO_INSERTION_MODE_BEFORE_HEAD);
  1913. parser->_parser_state->_reprocess_current_token = true;
  1914. return true;
  1915. }
  1916. }
  1917. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#the-before-head-insertion-mode
  1918. static bool handle_before_head(GumboParser* parser, GumboToken* token) {
  1919. if (token->type == GUMBO_TOKEN_DOCTYPE) {
  1920. add_parse_error(parser, token);
  1921. ignore_token(parser);
  1922. return false;
  1923. } else if (token->type == GUMBO_TOKEN_COMMENT) {
  1924. append_comment_node(parser, get_current_node(parser), token);
  1925. return true;
  1926. } else if (token->type == GUMBO_TOKEN_WHITESPACE) {
  1927. ignore_token(parser);
  1928. return true;
  1929. } else if (tag_is(token, kStartTag, GUMBO_TAG_HEAD)) {
  1930. GumboNode* node = insert_element_from_token(parser, token);
  1931. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_HEAD);
  1932. parser->_parser_state->_head_element = node;
  1933. return true;
  1934. } else if (token->type == GUMBO_TOKEN_END_TAG && !tag_in(
  1935. token, false, GUMBO_TAG_HEAD, GUMBO_TAG_BODY, GUMBO_TAG_HTML,
  1936. GUMBO_TAG_BR, GUMBO_TAG_LAST)) {
  1937. add_parse_error(parser, token);
  1938. ignore_token(parser);
  1939. return false;
  1940. } else {
  1941. GumboNode* node = insert_element_of_tag_type(
  1942. parser, GUMBO_TAG_HEAD, GUMBO_INSERTION_IMPLIED);
  1943. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_HEAD);
  1944. parser->_parser_state->_head_element = node;
  1945. parser->_parser_state->_reprocess_current_token = true;
  1946. return true;
  1947. }
  1948. }
  1949. // Forward declarations because of mutual dependencies.
  1950. static bool handle_token(GumboParser* parser, GumboToken* token);
  1951. static bool handle_in_body(GumboParser* parser, GumboToken* token);
  1952. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-inhead
  1953. static bool handle_in_head(GumboParser* parser, GumboToken* token) {
  1954. if (token->type == GUMBO_TOKEN_WHITESPACE) {
  1955. insert_text_token(parser, token);
  1956. return true;
  1957. } else if (token->type == GUMBO_TOKEN_DOCTYPE) {
  1958. add_parse_error(parser, token);
  1959. ignore_token(parser);
  1960. return false;
  1961. } else if (token->type == GUMBO_TOKEN_COMMENT) {
  1962. append_comment_node(parser, get_current_node(parser), token);
  1963. return true;
  1964. } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
  1965. return handle_in_body(parser, token);
  1966. } else if (tag_in(token, kStartTag, GUMBO_TAG_BASE, GUMBO_TAG_BASEFONT,
  1967. GUMBO_TAG_BGSOUND, GUMBO_TAG_MENUITEM, GUMBO_TAG_LINK,
  1968. GUMBO_TAG_LAST)) {
  1969. insert_element_from_token(parser, token);
  1970. pop_current_node(parser);
  1971. acknowledge_self_closing_tag(parser);
  1972. return true;
  1973. } else if (tag_is(token, kStartTag, GUMBO_TAG_META)) {
  1974. insert_element_from_token(parser, token);
  1975. pop_current_node(parser);
  1976. acknowledge_self_closing_tag(parser);
  1977. // NOTE(jdtang): Gumbo handles only UTF-8, so the encoding clause of the
  1978. // spec doesn't apply. If clients want to handle meta-tag re-encoding, they
  1979. // should specifically look for that string in the document and re-encode it
  1980. // before passing to Gumbo.
  1981. return true;
  1982. } else if (tag_is(token, kStartTag, GUMBO_TAG_TITLE)) {
  1983. run_generic_parsing_algorithm(parser, token, GUMBO_LEX_RCDATA);
  1984. return true;
  1985. } else if (tag_in(token, kStartTag, GUMBO_TAG_NOFRAMES, GUMBO_TAG_STYLE,
  1986. GUMBO_TAG_LAST)) {
  1987. run_generic_parsing_algorithm(parser, token, GUMBO_LEX_RAWTEXT);
  1988. return true;
  1989. } else if (tag_is(token, kStartTag, GUMBO_TAG_NOSCRIPT)) {
  1990. insert_element_from_token(parser, token);
  1991. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_HEAD_NOSCRIPT);
  1992. return true;
  1993. } else if (tag_is(token, kStartTag, GUMBO_TAG_SCRIPT)) {
  1994. run_generic_parsing_algorithm(parser, token, GUMBO_LEX_SCRIPT);
  1995. return true;
  1996. } else if (tag_is(token, kEndTag, GUMBO_TAG_HEAD)) {
  1997. GumboNode* head = pop_current_node(parser);
  1998. AVOID_UNUSED_VARIABLE_WARNING(head);
  1999. assert(node_tag_is(head, GUMBO_TAG_HEAD));
  2000. set_insertion_mode(parser, GUMBO_INSERTION_MODE_AFTER_HEAD);
  2001. return true;
  2002. } else if (tag_is(token, kStartTag, GUMBO_TAG_HEAD)) {
  2003. add_parse_error(parser, token);
  2004. ignore_token(parser);
  2005. return false;
  2006. } else if (tag_is(token, kStartTag, GUMBO_TAG_HEAD) ||
  2007. (token->type == GUMBO_TOKEN_END_TAG &&
  2008. !tag_in(token, kEndTag, GUMBO_TAG_BODY, GUMBO_TAG_HTML,
  2009. GUMBO_TAG_BR, GUMBO_TAG_LAST))) {
  2010. add_parse_error(parser, token);
  2011. return false;
  2012. } else {
  2013. const GumboNode* node = pop_current_node(parser);
  2014. assert(node_tag_is(node, GUMBO_TAG_HEAD));
  2015. AVOID_UNUSED_VARIABLE_WARNING(node);
  2016. set_insertion_mode(parser, GUMBO_INSERTION_MODE_AFTER_HEAD);
  2017. parser->_parser_state->_reprocess_current_token = true;
  2018. return true;
  2019. }
  2020. return true;
  2021. }
  2022. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-inheadnoscript
  2023. static bool handle_in_head_noscript(GumboParser* parser, GumboToken* token) {
  2024. if (token->type == GUMBO_TOKEN_DOCTYPE) {
  2025. add_parse_error(parser, token);
  2026. return false;
  2027. } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
  2028. return handle_in_body(parser, token);
  2029. } else if (tag_is(token, kEndTag, GUMBO_TAG_NOSCRIPT)) {
  2030. const GumboNode* node = pop_current_node(parser);
  2031. assert(node_tag_is(node, GUMBO_TAG_NOSCRIPT));
  2032. AVOID_UNUSED_VARIABLE_WARNING(node);
  2033. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_HEAD);
  2034. return true;
  2035. } else if (token->type == GUMBO_TOKEN_WHITESPACE ||
  2036. token->type == GUMBO_TOKEN_COMMENT ||
  2037. tag_in(token, kStartTag, GUMBO_TAG_BASEFONT, GUMBO_TAG_BGSOUND,
  2038. GUMBO_TAG_LINK, GUMBO_TAG_META, GUMBO_TAG_NOFRAMES,
  2039. GUMBO_TAG_STYLE, GUMBO_TAG_LAST)) {
  2040. return handle_in_head(parser, token);
  2041. } else if (tag_in(token, kStartTag, GUMBO_TAG_HEAD, GUMBO_TAG_NOSCRIPT,
  2042. GUMBO_TAG_LAST) ||
  2043. (token->type == GUMBO_TOKEN_END_TAG &&
  2044. !tag_is(token, kEndTag, GUMBO_TAG_BR))) {
  2045. add_parse_error(parser, token);
  2046. ignore_token(parser);
  2047. return false;
  2048. } else {
  2049. add_parse_error(parser, token);
  2050. const GumboNode* node = pop_current_node(parser);
  2051. assert(node_tag_is(node, GUMBO_TAG_NOSCRIPT));
  2052. AVOID_UNUSED_VARIABLE_WARNING(node);
  2053. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_HEAD);
  2054. parser->_parser_state->_reprocess_current_token = true;
  2055. return false;
  2056. }
  2057. }
  2058. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#the-after-head-insertion-mode
  2059. static bool handle_after_head(GumboParser* parser, GumboToken* token) {
  2060. GumboParserState* state = parser->_parser_state;
  2061. if (token->type == GUMBO_TOKEN_WHITESPACE) {
  2062. insert_text_token(parser, token);
  2063. return true;
  2064. } else if (token->type == GUMBO_TOKEN_DOCTYPE) {
  2065. add_parse_error(parser, token);
  2066. ignore_token(parser);
  2067. return false;
  2068. } else if (token->type == GUMBO_TOKEN_COMMENT) {
  2069. append_comment_node(parser, get_current_node(parser), token);
  2070. return true;
  2071. } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
  2072. return handle_in_body(parser, token);
  2073. } else if (tag_is(token, kStartTag, GUMBO_TAG_BODY)) {
  2074. insert_element_from_token(parser, token);
  2075. state->_frameset_ok = false;
  2076. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_BODY);
  2077. return true;
  2078. } else if (tag_is(token, kStartTag, GUMBO_TAG_FRAMESET)) {
  2079. insert_element_from_token(parser, token);
  2080. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_FRAMESET);
  2081. return true;
  2082. } else if (tag_in(token, kStartTag, GUMBO_TAG_BASE, GUMBO_TAG_BASEFONT,
  2083. GUMBO_TAG_BGSOUND, GUMBO_TAG_LINK, GUMBO_TAG_META,
  2084. GUMBO_TAG_NOFRAMES, GUMBO_TAG_SCRIPT, GUMBO_TAG_STYLE,
  2085. GUMBO_TAG_TITLE, GUMBO_TAG_LAST)) {
  2086. add_parse_error(parser, token);
  2087. assert(state->_head_element != NULL);
  2088. // This must be flushed before we push the head element on, as there may be
  2089. // pending character tokens that should be attached to the root.
  2090. maybe_flush_text_node_buffer(parser);
  2091. gumbo_vector_add(parser, state->_head_element, &state->_open_elements);
  2092. bool result = handle_in_head(parser, token);
  2093. gumbo_vector_remove(parser, state->_head_element, &state->_open_elements);
  2094. return result;
  2095. } else if (tag_is(token, kStartTag, GUMBO_TAG_HEAD) ||
  2096. (token->type == GUMBO_TOKEN_END_TAG &&
  2097. !tag_in(token, kEndTag, GUMBO_TAG_BODY, GUMBO_TAG_HTML,
  2098. GUMBO_TAG_BR, GUMBO_TAG_LAST))) {
  2099. add_parse_error(parser, token);
  2100. ignore_token(parser);
  2101. return false;
  2102. } else {
  2103. insert_element_of_tag_type(parser, GUMBO_TAG_BODY, GUMBO_INSERTION_IMPLIED);
  2104. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_BODY);
  2105. state->_reprocess_current_token = true;
  2106. return true;
  2107. }
  2108. }
  2109. static void destroy_node(GumboParser* parser, GumboNode* node) {
  2110. switch (node->type) {
  2111. case GUMBO_NODE_DOCUMENT:
  2112. {
  2113. GumboDocument* doc = &node->v.document;
  2114. for (int i = 0; i < doc->children.length; ++i) {
  2115. destroy_node(parser, doc->children.data[i]);
  2116. }
  2117. gumbo_parser_deallocate(parser, (void*) doc->children.data);
  2118. gumbo_parser_deallocate(parser, (void*) doc->name);
  2119. gumbo_parser_deallocate(parser, (void*) doc->public_identifier);
  2120. gumbo_parser_deallocate(parser, (void*) doc->system_identifier);
  2121. }
  2122. break;
  2123. case GUMBO_NODE_ELEMENT:
  2124. for (int i = 0; i < node->v.element.attributes.length; ++i) {
  2125. gumbo_destroy_attribute(parser, node->v.element.attributes.data[i]);
  2126. }
  2127. gumbo_parser_deallocate(parser, node->v.element.attributes.data);
  2128. for (int i = 0; i < node->v.element.children.length; ++i) {
  2129. destroy_node(parser, node->v.element.children.data[i]);
  2130. }
  2131. gumbo_parser_deallocate(parser, node->v.element.children.data);
  2132. break;
  2133. case GUMBO_NODE_TEXT:
  2134. case GUMBO_NODE_CDATA:
  2135. case GUMBO_NODE_COMMENT:
  2136. case GUMBO_NODE_WHITESPACE:
  2137. gumbo_parser_deallocate(parser, (void*) node->v.text.text);
  2138. break;
  2139. }
  2140. gumbo_parser_deallocate(parser, node);
  2141. }
  2142. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-inbody
  2143. static bool handle_in_body(GumboParser* parser, GumboToken* token) {
  2144. GumboParserState* state = parser->_parser_state;
  2145. assert(state->_open_elements.length > 0);
  2146. if (token->type == GUMBO_TOKEN_NULL) {
  2147. add_parse_error(parser, token);
  2148. ignore_token(parser);
  2149. return false;
  2150. } else if (token->type == GUMBO_TOKEN_WHITESPACE) {
  2151. reconstruct_active_formatting_elements(parser);
  2152. insert_text_token(parser, token);
  2153. return true;
  2154. } else if (token->type == GUMBO_TOKEN_CHARACTER) {
  2155. reconstruct_active_formatting_elements(parser);
  2156. insert_text_token(parser, token);
  2157. set_frameset_not_ok(parser);
  2158. return true;
  2159. } else if (token->type == GUMBO_TOKEN_COMMENT) {
  2160. append_comment_node(parser, get_current_node(parser), token);
  2161. return true;
  2162. } else if (token->type == GUMBO_TOKEN_DOCTYPE) {
  2163. add_parse_error(parser, token);
  2164. ignore_token(parser);
  2165. return false;
  2166. } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
  2167. assert(parser->_output->root != NULL);
  2168. assert(parser->_output->root->type == GUMBO_NODE_ELEMENT);
  2169. add_parse_error(parser, token);
  2170. merge_attributes(parser, token, parser->_output->root);
  2171. return false;
  2172. } else if (tag_in(token, kStartTag, GUMBO_TAG_BASE, GUMBO_TAG_BASEFONT,
  2173. GUMBO_TAG_BGSOUND, GUMBO_TAG_MENUITEM, GUMBO_TAG_LINK,
  2174. GUMBO_TAG_META, GUMBO_TAG_NOFRAMES, GUMBO_TAG_SCRIPT,
  2175. GUMBO_TAG_STYLE, GUMBO_TAG_TITLE, GUMBO_TAG_LAST)) {
  2176. return handle_in_head(parser, token);
  2177. } else if (tag_is(token, kStartTag, GUMBO_TAG_BODY)) {
  2178. add_parse_error(parser, token);
  2179. if (state->_open_elements.length < 2 ||
  2180. !node_tag_is(state->_open_elements.data[1], GUMBO_TAG_BODY)) {
  2181. ignore_token(parser);
  2182. return false;
  2183. }
  2184. state->_frameset_ok = false;
  2185. merge_attributes(parser, token, state->_open_elements.data[1]);
  2186. return false;
  2187. } else if (tag_is(token, kStartTag, GUMBO_TAG_FRAMESET)) {
  2188. add_parse_error(parser, token);
  2189. if (state->_open_elements.length < 2 ||
  2190. !node_tag_is(state->_open_elements.data[1], GUMBO_TAG_BODY) ||
  2191. !state->_frameset_ok) {
  2192. ignore_token(parser);
  2193. return false;
  2194. }
  2195. // Save the body node for later removal.
  2196. GumboNode* body_node = state->_open_elements.data[1];
  2197. // Pop all nodes except root HTML element.
  2198. GumboNode* node;
  2199. do {
  2200. node = pop_current_node(parser);
  2201. } while (node != state->_open_elements.data[1]);
  2202. // Remove the body node. We may want to factor this out into a generic
  2203. // helper, but right now this is the only code that needs to do this.
  2204. GumboVector* children = &parser->_output->root->v.element.children;
  2205. for (int i = 0; i < children->length; ++i) {
  2206. if (children->data[i] == body_node) {
  2207. gumbo_vector_remove_at(parser, i, children);
  2208. break;
  2209. }
  2210. }
  2211. destroy_node(parser, body_node);
  2212. // Insert the <frameset>, and switch the insertion mode.
  2213. insert_element_from_token(parser, token);
  2214. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_FRAMESET);
  2215. return true;
  2216. } else if (token->type == GUMBO_TOKEN_EOF) {
  2217. for (int i = 0; i < state->_open_elements.length; ++i) {
  2218. if (!node_tag_in(state->_open_elements.data[i], GUMBO_TAG_DD,
  2219. GUMBO_TAG_DT, GUMBO_TAG_LI, GUMBO_TAG_P, GUMBO_TAG_TBODY,
  2220. GUMBO_TAG_TD, GUMBO_TAG_TFOOT, GUMBO_TAG_TH,
  2221. GUMBO_TAG_THEAD, GUMBO_TAG_TR, GUMBO_TAG_BODY,
  2222. GUMBO_TAG_HTML, GUMBO_TAG_LAST)) {
  2223. add_parse_error(parser, token);
  2224. return false;
  2225. }
  2226. }
  2227. return true;
  2228. } else if (tag_in(token, kEndTag, GUMBO_TAG_BODY, GUMBO_TAG_HTML,
  2229. GUMBO_TAG_LAST)) {
  2230. if (!has_an_element_in_scope(parser, GUMBO_TAG_BODY)) {
  2231. add_parse_error(parser, token);
  2232. ignore_token(parser);
  2233. return false;
  2234. }
  2235. bool success = true;
  2236. for (int i = 0; i < state->_open_elements.length; ++i) {
  2237. if (!node_tag_in(state->_open_elements.data[i], GUMBO_TAG_DD,
  2238. GUMBO_TAG_DT, GUMBO_TAG_LI, GUMBO_TAG_OPTGROUP,
  2239. GUMBO_TAG_OPTION, GUMBO_TAG_P, GUMBO_TAG_RP,
  2240. GUMBO_TAG_RT, GUMBO_TAG_TBODY, GUMBO_TAG_TD,
  2241. GUMBO_TAG_TFOOT, GUMBO_TAG_TH, GUMBO_TAG_THEAD,
  2242. GUMBO_TAG_TR, GUMBO_TAG_BODY, GUMBO_TAG_HTML,
  2243. GUMBO_TAG_LAST)) {
  2244. add_parse_error(parser, token);
  2245. success = false;
  2246. break;
  2247. }
  2248. }
  2249. set_insertion_mode(parser, GUMBO_INSERTION_MODE_AFTER_BODY);
  2250. if (tag_is(token, kEndTag, GUMBO_TAG_HTML)) {
  2251. parser->_parser_state->_reprocess_current_token = true;
  2252. } else {
  2253. GumboNode* body = state->_open_elements.data[1];
  2254. assert(node_tag_is(body, GUMBO_TAG_BODY));
  2255. record_end_of_element(state->_current_token, &body->v.element);
  2256. }
  2257. return success;
  2258. } else if (tag_in(token, kStartTag, GUMBO_TAG_ADDRESS, GUMBO_TAG_ARTICLE,
  2259. GUMBO_TAG_ASIDE, GUMBO_TAG_BLOCKQUOTE, GUMBO_TAG_CENTER,
  2260. GUMBO_TAG_DETAILS, GUMBO_TAG_DIR, GUMBO_TAG_DIV,
  2261. GUMBO_TAG_DL, GUMBO_TAG_FIELDSET, GUMBO_TAG_FIGCAPTION,
  2262. GUMBO_TAG_FIGURE, GUMBO_TAG_FOOTER, GUMBO_TAG_HEADER,
  2263. GUMBO_TAG_HGROUP, GUMBO_TAG_MENU, GUMBO_TAG_NAV,
  2264. GUMBO_TAG_OL, GUMBO_TAG_P, GUMBO_TAG_SECTION,
  2265. GUMBO_TAG_SUMMARY, GUMBO_TAG_UL, GUMBO_TAG_LAST)) {
  2266. bool result = maybe_implicitly_close_p_tag(parser, token);
  2267. insert_element_from_token(parser, token);
  2268. return result;
  2269. } else if (tag_in(token, kStartTag, GUMBO_TAG_H1, GUMBO_TAG_H2, GUMBO_TAG_H3,
  2270. GUMBO_TAG_H4, GUMBO_TAG_H5, GUMBO_TAG_H6, GUMBO_TAG_LAST)) {
  2271. bool result = maybe_implicitly_close_p_tag(parser, token);
  2272. if (node_tag_in(get_current_node(parser), GUMBO_TAG_H1, GUMBO_TAG_H2,
  2273. GUMBO_TAG_H3, GUMBO_TAG_H4, GUMBO_TAG_H5, GUMBO_TAG_H6,
  2274. GUMBO_TAG_LAST)) {
  2275. add_parse_error(parser, token);
  2276. pop_current_node(parser);
  2277. result = false;
  2278. }
  2279. insert_element_from_token(parser, token);
  2280. return result;
  2281. } else if (tag_in(token, kStartTag, GUMBO_TAG_PRE, GUMBO_TAG_LISTING,
  2282. GUMBO_TAG_LAST)) {
  2283. bool result = maybe_implicitly_close_p_tag(parser, token);
  2284. insert_element_from_token(parser, token);
  2285. state->_ignore_next_linefeed = true;
  2286. state->_frameset_ok = false;
  2287. return result;
  2288. } else if (tag_is(token, kStartTag, GUMBO_TAG_FORM)) {
  2289. if (state->_form_element != NULL) {
  2290. gumbo_debug("Ignoring nested form.\n");
  2291. add_parse_error(parser, token);
  2292. ignore_token(parser);
  2293. return false;
  2294. }
  2295. bool result = maybe_implicitly_close_p_tag(parser, token);
  2296. state->_form_element =
  2297. insert_element_from_token(parser, token);
  2298. return result;
  2299. } else if (tag_is(token, kStartTag, GUMBO_TAG_LI)) {
  2300. maybe_implicitly_close_list_tag(parser, token, true);
  2301. bool result = maybe_implicitly_close_p_tag(parser, token);
  2302. insert_element_from_token(parser, token);
  2303. return result;
  2304. } else if (tag_in(token, kStartTag, GUMBO_TAG_DD, GUMBO_TAG_DT,
  2305. GUMBO_TAG_LAST)) {
  2306. maybe_implicitly_close_list_tag(parser, token, false);
  2307. bool result = maybe_implicitly_close_p_tag(parser, token);
  2308. insert_element_from_token(parser, token);
  2309. return result;
  2310. } else if (tag_is(token, kStartTag, GUMBO_TAG_PLAINTEXT)) {
  2311. bool result = maybe_implicitly_close_p_tag(parser, token);
  2312. insert_element_from_token(parser, token);
  2313. gumbo_tokenizer_set_state(parser, GUMBO_LEX_PLAINTEXT);
  2314. return result;
  2315. } else if (tag_is(token, kStartTag, GUMBO_TAG_BUTTON)) {
  2316. if (has_an_element_in_scope(parser, GUMBO_TAG_BUTTON)) {
  2317. add_parse_error(parser, token);
  2318. implicitly_close_tags(parser, token, GUMBO_TAG_BUTTON);
  2319. state->_reprocess_current_token = true;
  2320. return false;
  2321. }
  2322. reconstruct_active_formatting_elements(parser);
  2323. insert_element_from_token(parser, token);
  2324. state->_frameset_ok = false;
  2325. return true;
  2326. } else if (tag_in(token, kEndTag, GUMBO_TAG_ADDRESS, GUMBO_TAG_ARTICLE,
  2327. GUMBO_TAG_ASIDE, GUMBO_TAG_BLOCKQUOTE, GUMBO_TAG_BUTTON,
  2328. GUMBO_TAG_CENTER, GUMBO_TAG_DETAILS, GUMBO_TAG_DIR,
  2329. GUMBO_TAG_DIV, GUMBO_TAG_DL, GUMBO_TAG_FIELDSET,
  2330. GUMBO_TAG_FIGCAPTION, GUMBO_TAG_FIGURE, GUMBO_TAG_FOOTER,
  2331. GUMBO_TAG_HEADER, GUMBO_TAG_HGROUP, GUMBO_TAG_LISTING,
  2332. GUMBO_TAG_MENU, GUMBO_TAG_NAV, GUMBO_TAG_OL, GUMBO_TAG_PRE,
  2333. GUMBO_TAG_SECTION, GUMBO_TAG_SUMMARY, GUMBO_TAG_UL,
  2334. GUMBO_TAG_LAST)) {
  2335. GumboTag tag = token->v.end_tag;
  2336. if (!has_an_element_in_scope(parser, tag)) {
  2337. add_parse_error(parser, token);
  2338. ignore_token(parser);
  2339. return false;
  2340. }
  2341. implicitly_close_tags(parser, token, token->v.end_tag);
  2342. return true;
  2343. } else if (tag_is(token, kEndTag, GUMBO_TAG_FORM)) {
  2344. bool result = true;
  2345. const GumboNode* node = state->_form_element;
  2346. assert(!node || node->type == GUMBO_NODE_ELEMENT);
  2347. state->_form_element = NULL;
  2348. if (!node || !has_node_in_scope(parser, node)) {
  2349. gumbo_debug("Closing an unopened form.\n");
  2350. add_parse_error(parser, token);
  2351. ignore_token(parser);
  2352. return false;
  2353. }
  2354. // This differs from implicitly_close_tags because we remove *only* the
  2355. // <form> element; other nodes are left in scope.
  2356. generate_implied_end_tags(parser, GUMBO_TAG_LAST);
  2357. if (get_current_node(parser) != node) {
  2358. add_parse_error(parser, token);
  2359. result = false;
  2360. }
  2361. GumboVector* open_elements = &state->_open_elements;
  2362. int index = open_elements->length - 1;
  2363. for (; index >= 0 && open_elements->data[index] != node; --index);
  2364. assert(index >= 0);
  2365. gumbo_vector_remove_at(parser, index, open_elements);
  2366. return result;
  2367. } else if (tag_is(token, kEndTag, GUMBO_TAG_P)) {
  2368. if (!has_an_element_in_button_scope(parser, GUMBO_TAG_P)) {
  2369. add_parse_error(parser, token);
  2370. reconstruct_active_formatting_elements(parser);
  2371. insert_element_of_tag_type(
  2372. parser, GUMBO_TAG_P, GUMBO_INSERTION_CONVERTED_FROM_END_TAG);
  2373. state->_reprocess_current_token = true;
  2374. return false;
  2375. }
  2376. return implicitly_close_tags(parser, token, GUMBO_TAG_P);
  2377. } else if (tag_is(token, kEndTag, GUMBO_TAG_LI)) {
  2378. if (!has_an_element_in_list_scope(parser, GUMBO_TAG_LI)) {
  2379. add_parse_error(parser, token);
  2380. ignore_token(parser);
  2381. return false;
  2382. }
  2383. return implicitly_close_tags(parser, token, GUMBO_TAG_LI);
  2384. } else if (tag_in(token, kEndTag, GUMBO_TAG_DD, GUMBO_TAG_DT,
  2385. GUMBO_TAG_LAST)) {
  2386. assert(token->type == GUMBO_TOKEN_END_TAG);
  2387. GumboTag token_tag = token->v.end_tag;
  2388. if (!has_an_element_in_scope(parser, token_tag)) {
  2389. add_parse_error(parser, token);
  2390. ignore_token(parser);
  2391. return false;
  2392. }
  2393. return implicitly_close_tags(parser, token, token_tag);
  2394. } else if (tag_in(token, kEndTag, GUMBO_TAG_H1, GUMBO_TAG_H2, GUMBO_TAG_H3,
  2395. GUMBO_TAG_H4, GUMBO_TAG_H5, GUMBO_TAG_H6, GUMBO_TAG_LAST)) {
  2396. if (!has_an_element_in_scope_with_tagname(
  2397. parser, GUMBO_TAG_H1, GUMBO_TAG_H2, GUMBO_TAG_H3, GUMBO_TAG_H4,
  2398. GUMBO_TAG_H5, GUMBO_TAG_H6, GUMBO_TAG_LAST)) {
  2399. // No heading open; ignore the token entirely.
  2400. add_parse_error(parser, token);
  2401. ignore_token(parser);
  2402. return false;
  2403. } else {
  2404. generate_implied_end_tags(parser, GUMBO_TAG_LAST);
  2405. const GumboNode* current_node = get_current_node(parser);
  2406. bool success = node_tag_is(current_node, token->v.end_tag);
  2407. if (!success) {
  2408. // There're children of the heading currently open; close them below and
  2409. // record a parse error.
  2410. // TODO(jdtang): Add a way to distinguish this error case from the one
  2411. // above.
  2412. add_parse_error(parser, token);
  2413. }
  2414. do {
  2415. current_node = pop_current_node(parser);
  2416. } while (!node_tag_in(current_node, GUMBO_TAG_H1, GUMBO_TAG_H2,
  2417. GUMBO_TAG_H3, GUMBO_TAG_H4, GUMBO_TAG_H5,
  2418. GUMBO_TAG_H6, GUMBO_TAG_LAST));
  2419. return success;
  2420. }
  2421. } else if (tag_is(token, kStartTag, GUMBO_TAG_A)) {
  2422. bool success = true;
  2423. int last_a;
  2424. int has_matching_a = find_last_anchor_index(parser, &last_a);
  2425. if (has_matching_a) {
  2426. assert(has_matching_a == 1);
  2427. add_parse_error(parser, token);
  2428. adoption_agency_algorithm(parser, token, GUMBO_TAG_A);
  2429. // The adoption agency algorithm usually removes all instances of <a>
  2430. // from the list of active formatting elements, but in case it doesn't,
  2431. // we're supposed to do this. (The conditions where it might not are
  2432. // listed in the spec.)
  2433. if (find_last_anchor_index(parser, &last_a)) {
  2434. void* last_element = gumbo_vector_remove_at(
  2435. parser, last_a, &state->_active_formatting_elements);
  2436. gumbo_vector_remove(
  2437. parser, last_element, &state->_open_elements);
  2438. }
  2439. success = false;
  2440. }
  2441. reconstruct_active_formatting_elements(parser);
  2442. add_formatting_element(parser, insert_element_from_token(parser, token));
  2443. return success;
  2444. } else if (tag_in(token, kStartTag, GUMBO_TAG_B, GUMBO_TAG_BIG,
  2445. GUMBO_TAG_CODE, GUMBO_TAG_EM, GUMBO_TAG_FONT, GUMBO_TAG_I,
  2446. GUMBO_TAG_S, GUMBO_TAG_SMALL, GUMBO_TAG_STRIKE,
  2447. GUMBO_TAG_STRONG, GUMBO_TAG_TT, GUMBO_TAG_U,
  2448. GUMBO_TAG_LAST)) {
  2449. reconstruct_active_formatting_elements(parser);
  2450. add_formatting_element(parser, insert_element_from_token(parser, token));
  2451. return true;
  2452. } else if (tag_is(token, kStartTag, GUMBO_TAG_NOBR)) {
  2453. bool result = true;
  2454. reconstruct_active_formatting_elements(parser);
  2455. if (has_an_element_in_scope(parser, GUMBO_TAG_NOBR)) {
  2456. result = false;
  2457. add_parse_error(parser, token);
  2458. adoption_agency_algorithm(parser, token, GUMBO_TAG_NOBR);
  2459. reconstruct_active_formatting_elements(parser);
  2460. }
  2461. insert_element_from_token(parser, token);
  2462. add_formatting_element(parser, get_current_node(parser));
  2463. return result;
  2464. } else if (tag_in(token, kEndTag, GUMBO_TAG_A, GUMBO_TAG_B, GUMBO_TAG_BIG,
  2465. GUMBO_TAG_CODE, GUMBO_TAG_EM, GUMBO_TAG_FONT, GUMBO_TAG_I,
  2466. GUMBO_TAG_NOBR, GUMBO_TAG_S, GUMBO_TAG_SMALL,
  2467. GUMBO_TAG_STRIKE, GUMBO_TAG_STRONG, GUMBO_TAG_TT,
  2468. GUMBO_TAG_U, GUMBO_TAG_LAST)) {
  2469. return adoption_agency_algorithm(parser, token, token->v.end_tag);
  2470. } else if (tag_in(token, kStartTag, GUMBO_TAG_APPLET, GUMBO_TAG_MARQUEE,
  2471. GUMBO_TAG_OBJECT, GUMBO_TAG_LAST)) {
  2472. reconstruct_active_formatting_elements(parser);
  2473. insert_element_from_token(parser, token);
  2474. add_formatting_element(parser, &kActiveFormattingScopeMarker);
  2475. set_frameset_not_ok(parser);
  2476. return true;
  2477. } else if (tag_in(token, kEndTag, GUMBO_TAG_APPLET, GUMBO_TAG_MARQUEE,
  2478. GUMBO_TAG_OBJECT, GUMBO_TAG_LAST)) {
  2479. GumboTag token_tag = token->v.end_tag;
  2480. if (!has_an_element_in_table_scope(parser, token_tag)) {
  2481. add_parse_error(parser, token);
  2482. ignore_token(parser);
  2483. return false;
  2484. }
  2485. implicitly_close_tags(parser, token, token_tag);
  2486. clear_active_formatting_elements(parser);
  2487. return true;
  2488. } else if (tag_is(token, kStartTag, GUMBO_TAG_TABLE)) {
  2489. if (get_document_node(parser)->v.document.doc_type_quirks_mode !=
  2490. GUMBO_DOCTYPE_QUIRKS) {
  2491. maybe_implicitly_close_p_tag(parser, token);
  2492. }
  2493. insert_element_from_token(parser, token);
  2494. set_frameset_not_ok(parser);
  2495. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE);
  2496. return true;
  2497. } else if (tag_in(token, kStartTag, GUMBO_TAG_AREA, GUMBO_TAG_BR,
  2498. GUMBO_TAG_EMBED, GUMBO_TAG_IMG, GUMBO_TAG_IMAGE,
  2499. GUMBO_TAG_KEYGEN, GUMBO_TAG_WBR, GUMBO_TAG_LAST)) {
  2500. bool success = true;
  2501. if (tag_is(token, kStartTag, GUMBO_TAG_IMAGE)) {
  2502. success = false;
  2503. add_parse_error(parser, token);
  2504. token->v.start_tag.tag = GUMBO_TAG_IMG;
  2505. }
  2506. reconstruct_active_formatting_elements(parser);
  2507. GumboNode* node = insert_element_from_token(parser, token);
  2508. if (tag_is(token, kStartTag, GUMBO_TAG_IMAGE)) {
  2509. success = false;
  2510. add_parse_error(parser, token);
  2511. node->v.element.tag = GUMBO_TAG_IMG;
  2512. node->parse_flags |= GUMBO_INSERTION_FROM_IMAGE;
  2513. }
  2514. pop_current_node(parser);
  2515. acknowledge_self_closing_tag(parser);
  2516. set_frameset_not_ok(parser);
  2517. return success;
  2518. } else if (tag_is(token, kStartTag, GUMBO_TAG_INPUT)) {
  2519. if (!attribute_matches(&token->v.start_tag.attributes, "type", "hidden")) {
  2520. // Must be before the element is inserted, as that takes ownership of the
  2521. // token's attribute vector.
  2522. set_frameset_not_ok(parser);
  2523. }
  2524. reconstruct_active_formatting_elements(parser);
  2525. insert_element_from_token(parser, token);
  2526. pop_current_node(parser);
  2527. acknowledge_self_closing_tag(parser);
  2528. return true;
  2529. } else if (tag_in(token, kStartTag, GUMBO_TAG_PARAM, GUMBO_TAG_SOURCE,
  2530. GUMBO_TAG_TRACK, GUMBO_TAG_LAST)) {
  2531. insert_element_from_token(parser, token);
  2532. pop_current_node(parser);
  2533. acknowledge_self_closing_tag(parser);
  2534. return true;
  2535. } else if (tag_is(token, kStartTag, GUMBO_TAG_HR)) {
  2536. bool result = maybe_implicitly_close_p_tag(parser, token);
  2537. insert_element_from_token(parser, token);
  2538. pop_current_node(parser);
  2539. acknowledge_self_closing_tag(parser);
  2540. set_frameset_not_ok(parser);
  2541. return result;
  2542. } else if (tag_is(token, kStartTag, GUMBO_TAG_ISINDEX)) {
  2543. add_parse_error(parser, token);
  2544. if (parser->_parser_state->_form_element != NULL) {
  2545. ignore_token(parser);
  2546. return false;
  2547. }
  2548. acknowledge_self_closing_tag(parser);
  2549. maybe_implicitly_close_p_tag(parser, token);
  2550. set_frameset_not_ok(parser);
  2551. GumboVector* token_attrs = &token->v.start_tag.attributes;
  2552. GumboAttribute* prompt_attr = gumbo_get_attribute(token_attrs, "prompt");
  2553. GumboAttribute* action_attr = gumbo_get_attribute(token_attrs, "action");
  2554. GumboAttribute* name_attr = gumbo_get_attribute(token_attrs, "isindex");
  2555. GumboNode* form = insert_element_of_tag_type(
  2556. parser, GUMBO_TAG_FORM, GUMBO_INSERTION_FROM_ISINDEX);
  2557. if (action_attr) {
  2558. gumbo_vector_add(parser, action_attr, &form->v.element.attributes);
  2559. }
  2560. insert_element_of_tag_type(parser, GUMBO_TAG_HR,
  2561. GUMBO_INSERTION_FROM_ISINDEX);
  2562. pop_current_node(parser); // <hr>
  2563. insert_element_of_tag_type(parser, GUMBO_TAG_LABEL,
  2564. GUMBO_INSERTION_FROM_ISINDEX);
  2565. TextNodeBufferState* text_state = &parser->_parser_state->_text_node;
  2566. text_state->_start_original_text = token->original_text.data;
  2567. text_state->_start_position = token->position;
  2568. text_state->_type = GUMBO_NODE_TEXT;
  2569. if (prompt_attr) {
  2570. int prompt_attr_length = strlen(prompt_attr->value);
  2571. gumbo_string_buffer_destroy(parser, &text_state->_buffer);
  2572. text_state->_buffer.data = gumbo_copy_stringz(parser, prompt_attr->value);
  2573. text_state->_buffer.length = prompt_attr_length;
  2574. text_state->_buffer.capacity = prompt_attr_length + 1;
  2575. gumbo_destroy_attribute(parser, prompt_attr);
  2576. } else {
  2577. GumboStringPiece prompt_text = GUMBO_STRING(
  2578. "This is a searchable index. Enter search keywords: ");
  2579. gumbo_string_buffer_append_string(
  2580. parser, &prompt_text, &text_state->_buffer);
  2581. }
  2582. GumboNode* input = insert_element_of_tag_type(
  2583. parser, GUMBO_TAG_INPUT, GUMBO_INSERTION_FROM_ISINDEX);
  2584. for (int i = 0; i < token_attrs->length; ++i) {
  2585. GumboAttribute* attr = token_attrs->data[i];
  2586. if (attr != prompt_attr && attr != action_attr && attr != name_attr) {
  2587. gumbo_vector_add(parser, attr, &input->v.element.attributes);
  2588. }
  2589. token_attrs->data[i] = NULL;
  2590. }
  2591. // All attributes have been successfully transferred and nulled out at this
  2592. // point, so the call to ignore_token will free the memory for it without
  2593. // touching the attributes.
  2594. ignore_token(parser);
  2595. GumboAttribute* name =
  2596. gumbo_parser_allocate(parser, sizeof(GumboAttribute));
  2597. GumboStringPiece name_str = GUMBO_STRING("name");
  2598. GumboStringPiece isindex_str = GUMBO_STRING("isindex");
  2599. name->attr_namespace = GUMBO_ATTR_NAMESPACE_NONE;
  2600. name->name = gumbo_copy_stringz(parser, "name");
  2601. name->value = gumbo_copy_stringz(parser, "isindex");
  2602. name->original_name = name_str;
  2603. name->original_value = isindex_str;
  2604. name->name_start = kGumboEmptySourcePosition;
  2605. name->name_end = kGumboEmptySourcePosition;
  2606. name->value_start = kGumboEmptySourcePosition;
  2607. name->value_end = kGumboEmptySourcePosition;
  2608. gumbo_vector_add(parser, name, &input->v.element.attributes);
  2609. pop_current_node(parser); // <input>
  2610. pop_current_node(parser); // <label>
  2611. insert_element_of_tag_type(
  2612. parser, GUMBO_TAG_HR, GUMBO_INSERTION_FROM_ISINDEX);
  2613. pop_current_node(parser); // <hr>
  2614. pop_current_node(parser); // <form>
  2615. return false;
  2616. } else if (tag_is(token, kStartTag, GUMBO_TAG_TEXTAREA)) {
  2617. run_generic_parsing_algorithm(parser, token, GUMBO_LEX_RCDATA);
  2618. parser->_parser_state->_ignore_next_linefeed = true;
  2619. set_frameset_not_ok(parser);
  2620. return true;
  2621. } else if (tag_is(token, kStartTag, GUMBO_TAG_XMP)) {
  2622. bool result = maybe_implicitly_close_p_tag(parser, token);
  2623. reconstruct_active_formatting_elements(parser);
  2624. set_frameset_not_ok(parser);
  2625. run_generic_parsing_algorithm(parser, token, GUMBO_LEX_RAWTEXT);
  2626. return result;
  2627. } else if (tag_is(token, kStartTag, GUMBO_TAG_IFRAME)) {
  2628. set_frameset_not_ok(parser);
  2629. run_generic_parsing_algorithm(parser, token, GUMBO_LEX_RAWTEXT);
  2630. return true;
  2631. } else if (tag_is(token, kStartTag, GUMBO_TAG_NOEMBED)) {
  2632. run_generic_parsing_algorithm(parser, token, GUMBO_LEX_RAWTEXT);
  2633. return true;
  2634. } else if (tag_is(token, kStartTag, GUMBO_TAG_SELECT)) {
  2635. reconstruct_active_formatting_elements(parser);
  2636. insert_element_from_token(parser, token);
  2637. set_frameset_not_ok(parser);
  2638. GumboInsertionMode state = parser->_parser_state->_insertion_mode;
  2639. if (state == GUMBO_INSERTION_MODE_IN_TABLE ||
  2640. state == GUMBO_INSERTION_MODE_IN_CAPTION ||
  2641. state == GUMBO_INSERTION_MODE_IN_TABLE_BODY ||
  2642. state == GUMBO_INSERTION_MODE_IN_ROW ||
  2643. state == GUMBO_INSERTION_MODE_IN_CELL) {
  2644. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_SELECT_IN_TABLE);
  2645. } else {
  2646. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_SELECT);
  2647. }
  2648. return true;
  2649. } else if (tag_in(token, kStartTag, GUMBO_TAG_OPTION, GUMBO_TAG_OPTGROUP,
  2650. GUMBO_TAG_LAST)) {
  2651. if (node_tag_is(get_current_node(parser), GUMBO_TAG_OPTION)) {
  2652. pop_current_node(parser);
  2653. }
  2654. reconstruct_active_formatting_elements(parser);
  2655. insert_element_from_token(parser, token);
  2656. return true;
  2657. } else if (tag_in(token, kStartTag, GUMBO_TAG_RP, GUMBO_TAG_RT,
  2658. GUMBO_TAG_LAST)) {
  2659. bool success = true;
  2660. if (has_an_element_in_scope(parser, GUMBO_TAG_RUBY)) {
  2661. generate_implied_end_tags(parser, GUMBO_TAG_LAST);
  2662. }
  2663. if (!node_tag_is(get_current_node(parser), GUMBO_TAG_RUBY)) {
  2664. add_parse_error(parser, token);
  2665. success = false;
  2666. }
  2667. insert_element_from_token(parser, token);
  2668. return success;
  2669. } else if (tag_is(token, kEndTag, GUMBO_TAG_BR)) {
  2670. add_parse_error(parser, token);
  2671. reconstruct_active_formatting_elements(parser);
  2672. insert_element_of_tag_type(
  2673. parser, GUMBO_TAG_BR, GUMBO_INSERTION_CONVERTED_FROM_END_TAG);
  2674. pop_current_node(parser);
  2675. return false;
  2676. } else if (tag_is(token, kStartTag, GUMBO_TAG_MATH)) {
  2677. reconstruct_active_formatting_elements(parser);
  2678. adjust_mathml_attributes(parser, token);
  2679. adjust_foreign_attributes(parser, token);
  2680. insert_foreign_element(parser, token, GUMBO_NAMESPACE_MATHML);
  2681. if (token->v.start_tag.is_self_closing) {
  2682. pop_current_node(parser);
  2683. acknowledge_self_closing_tag(parser);
  2684. }
  2685. return true;
  2686. } else if (tag_is(token, kStartTag, GUMBO_TAG_SVG)) {
  2687. reconstruct_active_formatting_elements(parser);
  2688. adjust_svg_attributes(parser, token);
  2689. adjust_foreign_attributes(parser, token);
  2690. insert_foreign_element(parser, token, GUMBO_NAMESPACE_SVG);
  2691. if (token->v.start_tag.is_self_closing) {
  2692. pop_current_node(parser);
  2693. acknowledge_self_closing_tag(parser);
  2694. }
  2695. return true;
  2696. } else if (tag_in(token, kStartTag, GUMBO_TAG_CAPTION, GUMBO_TAG_COL,
  2697. GUMBO_TAG_COLGROUP, GUMBO_TAG_FRAME, GUMBO_TAG_HEAD,
  2698. GUMBO_TAG_TBODY, GUMBO_TAG_TD, GUMBO_TAG_TFOOT,
  2699. GUMBO_TAG_TH, GUMBO_TAG_THEAD, GUMBO_TAG_TR,
  2700. GUMBO_TAG_LAST)) {
  2701. add_parse_error(parser, token);
  2702. ignore_token(parser);
  2703. return false;
  2704. } else if (token->type == GUMBO_TOKEN_START_TAG) {
  2705. reconstruct_active_formatting_elements(parser);
  2706. insert_element_from_token(parser, token);
  2707. return true;
  2708. } else {
  2709. assert(token->type == GUMBO_TOKEN_END_TAG);
  2710. GumboTag end_tag = token->v.end_tag;
  2711. assert(state->_open_elements.length > 0);
  2712. assert(node_tag_is(state->_open_elements.data[0], GUMBO_TAG_HTML));
  2713. // Walk up the stack of open elements until we find one that either:
  2714. // a) Matches the tag name we saw
  2715. // b) Is in the "special" category.
  2716. // If we see a), implicitly close everything up to and including it. If we
  2717. // see b), then record a parse error, don't close anything (except the
  2718. // implied end tags) and ignore the end tag token.
  2719. for (int i = state->_open_elements.length; --i >= 0; ) {
  2720. const GumboNode* node = state->_open_elements.data[i];
  2721. if (node->v.element.tag_namespace == GUMBO_NAMESPACE_HTML &&
  2722. node_tag_is(node, end_tag)) {
  2723. generate_implied_end_tags(parser, end_tag);
  2724. // TODO(jdtang): Do I need to add a parse error here? The condition in
  2725. // the spec seems like it's the inverse of the loop condition above, and
  2726. // so would never fire.
  2727. while (node != pop_current_node(parser)); // Pop everything.
  2728. return true;
  2729. } else if (is_special_node(node)) {
  2730. add_parse_error(parser, token);
  2731. ignore_token(parser);
  2732. return false;
  2733. }
  2734. }
  2735. // <html> is in the special category, so we should never get here.
  2736. assert(0);
  2737. return false;
  2738. }
  2739. }
  2740. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-incdata
  2741. static bool handle_text(GumboParser* parser, GumboToken* token) {
  2742. if (token->type == GUMBO_TOKEN_CHARACTER || token->type == GUMBO_TOKEN_WHITESPACE) {
  2743. insert_text_token(parser, token);
  2744. } else {
  2745. // We provide only bare-bones script handling that doesn't involve any of
  2746. // the parser-pause/already-started/script-nesting flags or re-entrant
  2747. // invocations of the tokenizer. Because the intended usage of this library
  2748. // is mostly for templating, refactoring, and static-analysis libraries, we
  2749. // provide the script body as a text-node child of the <script> element.
  2750. // This behavior doesn't support document.write of partial HTML elements,
  2751. // but should be adequate for almost all other scripting support.
  2752. if (token->type == GUMBO_TOKEN_EOF) {
  2753. add_parse_error(parser, token);
  2754. parser->_parser_state->_reprocess_current_token = true;
  2755. }
  2756. pop_current_node(parser);
  2757. set_insertion_mode(parser, parser->_parser_state->_original_insertion_mode);
  2758. }
  2759. return true;
  2760. }
  2761. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-intable
  2762. static bool handle_in_table(GumboParser* parser, GumboToken* token) {
  2763. GumboParserState* state = parser->_parser_state;
  2764. if (token->type == GUMBO_TOKEN_CHARACTER ||
  2765. token->type == GUMBO_TOKEN_WHITESPACE) {
  2766. // The "pending table character tokens" list described in the spec is
  2767. // nothing more than the TextNodeBufferState. We accumulate text tokens as
  2768. // normal, except that when we go to flush them in the handle_in_table_text,
  2769. // we set _foster_parent_insertions if there're non-whitespace characters in
  2770. // the buffer.
  2771. assert(state->_text_node._buffer.length == 0);
  2772. state->_original_insertion_mode = state->_insertion_mode;
  2773. state->_reprocess_current_token = true;
  2774. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE_TEXT);
  2775. return true;
  2776. } else if (token->type == GUMBO_TOKEN_DOCTYPE) {
  2777. add_parse_error(parser, token);
  2778. ignore_token(parser);
  2779. return false;
  2780. } else if (token->type == GUMBO_TOKEN_COMMENT) {
  2781. append_comment_node(parser, get_current_node(parser), token);
  2782. return true;
  2783. } else if (tag_is(token, kStartTag, GUMBO_TAG_CAPTION)) {
  2784. clear_stack_to_table_context(parser);
  2785. add_formatting_element(parser, &kActiveFormattingScopeMarker);
  2786. insert_element_from_token(parser, token);
  2787. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_CAPTION);
  2788. return true;
  2789. } else if (tag_is(token, kStartTag, GUMBO_TAG_COLGROUP)) {
  2790. clear_stack_to_table_context(parser);
  2791. insert_element_from_token(parser, token);
  2792. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_COLUMN_GROUP);
  2793. return true;
  2794. } else if (tag_is(token, kStartTag, GUMBO_TAG_COL)) {
  2795. clear_stack_to_table_context(parser);
  2796. insert_element_of_tag_type(
  2797. parser, GUMBO_TAG_COLGROUP, GUMBO_INSERTION_IMPLIED);
  2798. parser->_parser_state->_reprocess_current_token = true;
  2799. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_COLUMN_GROUP);
  2800. return true;
  2801. } else if (tag_in(token, kStartTag, GUMBO_TAG_TBODY, GUMBO_TAG_TFOOT,
  2802. GUMBO_TAG_THEAD, GUMBO_TAG_TD, GUMBO_TAG_TH, GUMBO_TAG_TR,
  2803. GUMBO_TAG_LAST)) {
  2804. clear_stack_to_table_context(parser);
  2805. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE_BODY);
  2806. if (tag_in(token, kStartTag, GUMBO_TAG_TD, GUMBO_TAG_TH, GUMBO_TAG_TR,
  2807. GUMBO_TAG_LAST)) {
  2808. insert_element_of_tag_type(
  2809. parser, GUMBO_TAG_TBODY, GUMBO_INSERTION_IMPLIED);
  2810. state->_reprocess_current_token = true;
  2811. } else {
  2812. insert_element_from_token(parser, token);
  2813. }
  2814. return true;
  2815. } else if (tag_is(token, kStartTag, GUMBO_TAG_TABLE)) {
  2816. add_parse_error(parser, token);
  2817. if (close_table(parser)) {
  2818. parser->_parser_state->_reprocess_current_token = true;
  2819. } else {
  2820. ignore_token(parser);
  2821. }
  2822. return false;
  2823. } else if (tag_is(token, kEndTag, GUMBO_TAG_TABLE)) {
  2824. if (!close_table(parser)) {
  2825. add_parse_error(parser, token);
  2826. return false;
  2827. }
  2828. return true;
  2829. } else if (tag_in(token, kEndTag, GUMBO_TAG_BODY, GUMBO_TAG_CAPTION,
  2830. GUMBO_TAG_COL, GUMBO_TAG_COLGROUP, GUMBO_TAG_HTML,
  2831. GUMBO_TAG_TBODY, GUMBO_TAG_TD, GUMBO_TAG_TFOOT,
  2832. GUMBO_TAG_TH, GUMBO_TAG_THEAD, GUMBO_TAG_TR,
  2833. GUMBO_TAG_LAST)) {
  2834. add_parse_error(parser, token);
  2835. ignore_token(parser);
  2836. return false;
  2837. } else if (tag_in(token, kStartTag, GUMBO_TAG_STYLE, GUMBO_TAG_SCRIPT,
  2838. GUMBO_TAG_LAST)) {
  2839. return handle_in_head(parser, token);
  2840. } else if (tag_is(token, kStartTag, GUMBO_TAG_INPUT) &&
  2841. attribute_matches(&token->v.start_tag.attributes,
  2842. "type", "hidden")) {
  2843. add_parse_error(parser, token);
  2844. insert_element_from_token(parser, token);
  2845. pop_current_node(parser);
  2846. return false;
  2847. } else if (tag_is(token, kStartTag, GUMBO_TAG_FORM)) {
  2848. add_parse_error(parser, token);
  2849. if (state->_form_element) {
  2850. ignore_token(parser);
  2851. return false;
  2852. }
  2853. state->_form_element = insert_element_from_token(parser, token);
  2854. pop_current_node(parser);
  2855. return false;
  2856. } else if (token->type == GUMBO_TOKEN_EOF) {
  2857. if (!node_tag_is(get_current_node(parser), GUMBO_TAG_HTML)) {
  2858. add_parse_error(parser, token);
  2859. return false;
  2860. }
  2861. return true;
  2862. } else {
  2863. add_parse_error(parser, token);
  2864. state->_foster_parent_insertions = true;
  2865. bool result = handle_in_body(parser, token);
  2866. state->_foster_parent_insertions = false;
  2867. return result;
  2868. }
  2869. }
  2870. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-intabletext
  2871. static bool handle_in_table_text(GumboParser* parser, GumboToken* token) {
  2872. if (token->type == GUMBO_TOKEN_NULL) {
  2873. add_parse_error(parser, token);
  2874. ignore_token(parser);
  2875. return false;
  2876. } else if (token->type == GUMBO_TOKEN_CHARACTER ||
  2877. token->type == GUMBO_TOKEN_WHITESPACE) {
  2878. insert_text_token(parser, token);
  2879. return true;
  2880. } else {
  2881. GumboParserState* state = parser->_parser_state;
  2882. GumboStringBuffer* buffer = &state->_text_node._buffer;
  2883. // Can't use strspn for this because GumboStringBuffers are not
  2884. // null-terminated.
  2885. // Note that TextNodeBuffer may contain UTF-8 characters, but the presence
  2886. // of any one byte that is not whitespace means we flip the flag, so this
  2887. // loop is still valid.
  2888. for (int i = 0; i < buffer->length; ++i) {
  2889. if (!isspace(buffer->data[i]) || buffer->data[i] == '\v') {
  2890. state->_foster_parent_insertions = true;
  2891. reconstruct_active_formatting_elements(parser);
  2892. break;
  2893. }
  2894. }
  2895. maybe_flush_text_node_buffer(parser);
  2896. state->_foster_parent_insertions = false;
  2897. state->_reprocess_current_token = true;
  2898. state->_insertion_mode = state->_original_insertion_mode;
  2899. return true;
  2900. }
  2901. }
  2902. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-incaption
  2903. static bool handle_in_caption(GumboParser* parser, GumboToken* token) {
  2904. if (tag_in(token, kStartTag, GUMBO_TAG_CAPTION, GUMBO_TAG_COL,
  2905. GUMBO_TAG_COLGROUP, GUMBO_TAG_TBODY, GUMBO_TAG_TD,
  2906. GUMBO_TAG_TFOOT, GUMBO_TAG_TH, GUMBO_TAG_THEAD,
  2907. GUMBO_TAG_TR, GUMBO_TAG_LAST) ||
  2908. tag_in(token, kEndTag, GUMBO_TAG_CAPTION, GUMBO_TAG_TABLE,
  2909. GUMBO_TAG_LAST)) {
  2910. if (!has_an_element_in_table_scope(parser, GUMBO_TAG_CAPTION)) {
  2911. add_parse_error(parser, token);
  2912. ignore_token(parser);
  2913. return false;
  2914. }
  2915. if (!tag_is(token, kEndTag, GUMBO_TAG_CAPTION)) {
  2916. add_parse_error(parser, token);
  2917. parser->_parser_state->_reprocess_current_token = true;
  2918. }
  2919. generate_implied_end_tags(parser, GUMBO_TAG_LAST);
  2920. bool result = true;
  2921. if (!node_tag_is(get_current_node(parser), GUMBO_TAG_CAPTION)) {
  2922. add_parse_error(parser, token);
  2923. while (!node_tag_is(get_current_node(parser), GUMBO_TAG_CAPTION)) {
  2924. pop_current_node(parser);
  2925. }
  2926. result = false;
  2927. }
  2928. pop_current_node(parser); // The <caption> itself.
  2929. clear_active_formatting_elements(parser);
  2930. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE);
  2931. return result;
  2932. } else if (tag_in(token, kEndTag, GUMBO_TAG_BODY, GUMBO_TAG_COL,
  2933. GUMBO_TAG_COLGROUP, GUMBO_TAG_HTML, GUMBO_TAG_TBODY,
  2934. GUMBO_TAG_TD, GUMBO_TAG_TFOOT, GUMBO_TAG_TH,
  2935. GUMBO_TAG_THEAD, GUMBO_TAG_TR, GUMBO_TAG_LAST)) {
  2936. add_parse_error(parser, token);
  2937. ignore_token(parser);
  2938. return false;
  2939. } else {
  2940. return handle_in_body(parser, token);
  2941. }
  2942. }
  2943. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-incolgroup
  2944. static bool handle_in_column_group(GumboParser* parser, GumboToken* token) {
  2945. if (token->type == GUMBO_TOKEN_WHITESPACE) {
  2946. insert_text_token(parser, token);
  2947. return true;
  2948. } else if (token->type == GUMBO_TOKEN_DOCTYPE) {
  2949. add_parse_error(parser, token);
  2950. ignore_token(parser);
  2951. return false;
  2952. } else if (token->type == GUMBO_TOKEN_COMMENT) {
  2953. append_comment_node(parser, get_current_node(parser), token);
  2954. return true;
  2955. } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
  2956. return handle_in_body(parser, token);
  2957. } else if (tag_is(token, kStartTag, GUMBO_TAG_COL)) {
  2958. insert_element_from_token(parser, token);
  2959. pop_current_node(parser);
  2960. acknowledge_self_closing_tag(parser);
  2961. return true;
  2962. } else if (tag_is(token, kEndTag, GUMBO_TAG_COL)) {
  2963. add_parse_error(parser, token);
  2964. ignore_token(parser);
  2965. return false;
  2966. } else if (token->type == GUMBO_TOKEN_EOF &&
  2967. get_current_node(parser) == parser->_output->root) {
  2968. return true;
  2969. } else {
  2970. if (get_current_node(parser) == parser->_output->root) {
  2971. add_parse_error(parser, token);
  2972. return false;
  2973. }
  2974. assert(node_tag_is(get_current_node(parser), GUMBO_TAG_COLGROUP));
  2975. pop_current_node(parser);
  2976. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE);
  2977. if (!tag_is(token, kEndTag, GUMBO_TAG_COLGROUP)) {
  2978. parser->_parser_state->_reprocess_current_token = true;
  2979. }
  2980. return true;
  2981. }
  2982. }
  2983. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-intbody
  2984. static bool handle_in_table_body(GumboParser* parser, GumboToken* token) {
  2985. if (tag_is(token, kStartTag, GUMBO_TAG_TR)) {
  2986. clear_stack_to_table_body_context(parser);
  2987. insert_element_from_token(parser, token);
  2988. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_ROW);
  2989. return true;
  2990. } else if (tag_in(token, kStartTag, GUMBO_TAG_TD, GUMBO_TAG_TH,
  2991. GUMBO_TAG_LAST)) {
  2992. add_parse_error(parser, token);
  2993. clear_stack_to_table_body_context(parser);
  2994. insert_element_of_tag_type(parser, GUMBO_TAG_TR, GUMBO_INSERTION_IMPLIED);
  2995. parser->_parser_state->_reprocess_current_token = true;
  2996. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_ROW);
  2997. return false;
  2998. } else if (tag_in(token, kEndTag, GUMBO_TAG_TBODY, GUMBO_TAG_TFOOT,
  2999. GUMBO_TAG_THEAD, GUMBO_TAG_LAST)) {
  3000. if (!has_an_element_in_table_scope(parser, token->v.end_tag)) {
  3001. add_parse_error(parser, token);
  3002. ignore_token(parser);
  3003. return false;
  3004. }
  3005. clear_stack_to_table_body_context(parser);
  3006. pop_current_node(parser);
  3007. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE);
  3008. return true;
  3009. } else if (tag_in(token, kStartTag, GUMBO_TAG_CAPTION, GUMBO_TAG_COL,
  3010. GUMBO_TAG_COLGROUP, GUMBO_TAG_TBODY, GUMBO_TAG_TFOOT,
  3011. GUMBO_TAG_THEAD, GUMBO_TAG_LAST) ||
  3012. tag_is(token, kEndTag, GUMBO_TAG_TABLE)) {
  3013. if (!(has_an_element_in_table_scope(parser, GUMBO_TAG_TBODY) ||
  3014. has_an_element_in_table_scope(parser, GUMBO_TAG_THEAD) ||
  3015. has_an_element_in_table_scope(parser, GUMBO_TAG_TFOOT))) {
  3016. add_parse_error(parser, token);
  3017. ignore_token(parser);
  3018. return false;
  3019. }
  3020. clear_stack_to_table_body_context(parser);
  3021. pop_current_node(parser);
  3022. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE);
  3023. parser->_parser_state->_reprocess_current_token = true;
  3024. return true;
  3025. } else if (tag_in(token, kEndTag, GUMBO_TAG_BODY, GUMBO_TAG_CAPTION,
  3026. GUMBO_TAG_COL, GUMBO_TAG_TR, GUMBO_TAG_COLGROUP,
  3027. GUMBO_TAG_HTML, GUMBO_TAG_TD, GUMBO_TAG_TH, GUMBO_TAG_LAST))
  3028. {
  3029. add_parse_error(parser, token);
  3030. ignore_token(parser);
  3031. return false;
  3032. } else {
  3033. return handle_in_table(parser, token);
  3034. }
  3035. }
  3036. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-intr
  3037. static bool handle_in_row(GumboParser* parser, GumboToken* token) {
  3038. if (tag_in(token, kStartTag, GUMBO_TAG_TH, GUMBO_TAG_TD, GUMBO_TAG_LAST)) {
  3039. clear_stack_to_table_row_context(parser);
  3040. insert_element_from_token(parser, token);
  3041. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_CELL);
  3042. add_formatting_element(parser, &kActiveFormattingScopeMarker);
  3043. return true;
  3044. } else if (tag_in(token, kStartTag, GUMBO_TAG_CAPTION, GUMBO_TAG_COLGROUP,
  3045. GUMBO_TAG_TBODY, GUMBO_TAG_TFOOT, GUMBO_TAG_THEAD,
  3046. GUMBO_TAG_TR, GUMBO_TAG_LAST) ||
  3047. tag_in(token, kEndTag, GUMBO_TAG_TR, GUMBO_TAG_TABLE,
  3048. GUMBO_TAG_TBODY, GUMBO_TAG_TFOOT, GUMBO_TAG_THEAD,
  3049. GUMBO_TAG_LAST)) {
  3050. // This case covers 4 clauses of the spec, each of which say "Otherwise, act
  3051. // as if an end tag with the tag name "tr" had been seen." The differences
  3052. // are in error handling and whether the current token is reprocessed.
  3053. GumboTag desired_tag =
  3054. tag_in(token, kEndTag, GUMBO_TAG_TBODY, GUMBO_TAG_TFOOT,
  3055. GUMBO_TAG_THEAD, GUMBO_TAG_LAST)
  3056. ? token->v.end_tag : GUMBO_TAG_TR;
  3057. if (!has_an_element_in_table_scope(parser, desired_tag)) {
  3058. gumbo_debug("Bailing because there is no tag %s in table scope.\nOpen elements:",
  3059. gumbo_normalized_tagname(desired_tag));
  3060. for (int i = 0; i < parser->_parser_state->_open_elements.length; ++i) {
  3061. const GumboNode* node = parser->_parser_state->_open_elements.data[i];
  3062. gumbo_debug("%s\n", gumbo_normalized_tagname(node->v.element.tag));
  3063. }
  3064. add_parse_error(parser, token);
  3065. ignore_token(parser);
  3066. return false;
  3067. }
  3068. clear_stack_to_table_row_context(parser);
  3069. GumboNode* last_element = pop_current_node(parser);
  3070. assert(node_tag_is(last_element, GUMBO_TAG_TR));
  3071. AVOID_UNUSED_VARIABLE_WARNING(last_element);
  3072. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE_BODY);
  3073. if (!tag_is(token, kEndTag, GUMBO_TAG_TR)) {
  3074. parser->_parser_state->_reprocess_current_token = true;
  3075. }
  3076. return true;
  3077. } else if (tag_in(token, kEndTag, GUMBO_TAG_BODY, GUMBO_TAG_CAPTION,
  3078. GUMBO_TAG_COL, GUMBO_TAG_COLGROUP, GUMBO_TAG_HTML,
  3079. GUMBO_TAG_TD, GUMBO_TAG_TH, GUMBO_TAG_LAST)) {
  3080. add_parse_error(parser, token);
  3081. ignore_token(parser);
  3082. return false;
  3083. } else {
  3084. return handle_in_table(parser, token);
  3085. }
  3086. }
  3087. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-intd
  3088. static bool handle_in_cell(GumboParser* parser, GumboToken* token) {
  3089. if (tag_in(token, kEndTag, GUMBO_TAG_TD, GUMBO_TAG_TH, GUMBO_TAG_LAST)) {
  3090. GumboTag token_tag = token->v.end_tag;
  3091. if (!has_an_element_in_table_scope(parser, token_tag)) {
  3092. add_parse_error(parser, token);
  3093. return false;
  3094. }
  3095. return close_table_cell(parser, token, token_tag);
  3096. } else if (tag_in(token, kStartTag, GUMBO_TAG_CAPTION, GUMBO_TAG_COL,
  3097. GUMBO_TAG_COLGROUP, GUMBO_TAG_TBODY, GUMBO_TAG_TD,
  3098. GUMBO_TAG_TFOOT, GUMBO_TAG_TH, GUMBO_TAG_THEAD,
  3099. GUMBO_TAG_TR, GUMBO_TAG_LAST)) {
  3100. gumbo_debug("Handling <td> in cell.\n");
  3101. if (!has_an_element_in_table_scope(parser, GUMBO_TAG_TH) &&
  3102. !has_an_element_in_table_scope(parser, GUMBO_TAG_TD)) {
  3103. gumbo_debug("Bailing out because there's no <td> or <th> in scope.\n");
  3104. add_parse_error(parser, token);
  3105. ignore_token(parser);
  3106. return false;
  3107. }
  3108. parser->_parser_state->_reprocess_current_token = true;
  3109. return close_current_cell(parser, token);
  3110. } else if (tag_in(token, kEndTag, GUMBO_TAG_BODY, GUMBO_TAG_CAPTION,
  3111. GUMBO_TAG_COL, GUMBO_TAG_COLGROUP, GUMBO_TAG_HTML,
  3112. GUMBO_TAG_LAST)) {
  3113. add_parse_error(parser, token);
  3114. ignore_token(parser);
  3115. return false;
  3116. } else if (tag_in(token, kEndTag, GUMBO_TAG_TABLE, GUMBO_TAG_TBODY,
  3117. GUMBO_TAG_TFOOT, GUMBO_TAG_THEAD, GUMBO_TAG_TR,
  3118. GUMBO_TAG_LAST)) {
  3119. if (!has_an_element_in_table_scope(parser, token->v.end_tag)) {
  3120. add_parse_error(parser, token);
  3121. ignore_token(parser);
  3122. return false;
  3123. }
  3124. parser->_parser_state->_reprocess_current_token = true;
  3125. return close_current_cell(parser, token);
  3126. } else {
  3127. return handle_in_body(parser, token);
  3128. }
  3129. }
  3130. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-inselect
  3131. static bool handle_in_select(GumboParser* parser, GumboToken* token) {
  3132. if (token->type == GUMBO_TOKEN_NULL) {
  3133. add_parse_error(parser, token);
  3134. ignore_token(parser);
  3135. return false;
  3136. } else if (token->type == GUMBO_TOKEN_CHARACTER ||
  3137. token->type == GUMBO_TOKEN_WHITESPACE) {
  3138. insert_text_token(parser, token);
  3139. return true;
  3140. } else if (token->type == GUMBO_TOKEN_DOCTYPE) {
  3141. add_parse_error(parser, token);
  3142. ignore_token(parser);
  3143. return false;
  3144. } else if (token->type == GUMBO_TOKEN_COMMENT) {
  3145. append_comment_node(parser, get_current_node(parser), token);
  3146. return true;
  3147. } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
  3148. return handle_in_body(parser, token);
  3149. } else if (tag_is(token, kStartTag, GUMBO_TAG_OPTION)) {
  3150. if (node_tag_is(get_current_node(parser), GUMBO_TAG_OPTION)) {
  3151. pop_current_node(parser);
  3152. }
  3153. insert_element_from_token(parser, token);
  3154. return true;
  3155. } else if (tag_is(token, kStartTag, GUMBO_TAG_OPTGROUP)) {
  3156. if (node_tag_is(get_current_node(parser), GUMBO_TAG_OPTION)) {
  3157. pop_current_node(parser);
  3158. }
  3159. if (node_tag_is(get_current_node(parser), GUMBO_TAG_OPTGROUP)) {
  3160. pop_current_node(parser);
  3161. }
  3162. insert_element_from_token(parser, token);
  3163. return true;
  3164. } else if (tag_is(token, kEndTag, GUMBO_TAG_OPTGROUP)) {
  3165. GumboVector* open_elements = &parser->_parser_state->_open_elements;
  3166. if (node_tag_is(get_current_node(parser), GUMBO_TAG_OPTION) &&
  3167. node_tag_is(open_elements->data[open_elements->length - 2],
  3168. GUMBO_TAG_OPTGROUP)) {
  3169. pop_current_node(parser);
  3170. }
  3171. if (node_tag_is(get_current_node(parser), GUMBO_TAG_OPTGROUP)) {
  3172. pop_current_node(parser);
  3173. return true;
  3174. } else {
  3175. add_parse_error(parser, token);
  3176. ignore_token(parser);
  3177. return false;
  3178. }
  3179. } else if (tag_is(token, kEndTag, GUMBO_TAG_OPTION)) {
  3180. if (node_tag_is(get_current_node(parser), GUMBO_TAG_OPTION)) {
  3181. pop_current_node(parser);
  3182. return true;
  3183. } else {
  3184. add_parse_error(parser, token);
  3185. ignore_token(parser);
  3186. return false;
  3187. }
  3188. } else if (tag_is(token, kEndTag, GUMBO_TAG_SELECT)) {
  3189. if (!has_an_element_in_select_scope(parser, GUMBO_TAG_SELECT)) {
  3190. add_parse_error(parser, token);
  3191. ignore_token(parser);
  3192. return false;
  3193. }
  3194. close_current_select(parser);
  3195. return true;
  3196. } else if (tag_is(token, kStartTag, GUMBO_TAG_SELECT)) {
  3197. add_parse_error(parser, token);
  3198. ignore_token(parser);
  3199. close_current_select(parser);
  3200. return false;
  3201. } else if (tag_in(token, kStartTag, GUMBO_TAG_INPUT, GUMBO_TAG_KEYGEN,
  3202. GUMBO_TAG_TEXTAREA, GUMBO_TAG_LAST)) {
  3203. add_parse_error(parser, token);
  3204. if (!has_an_element_in_select_scope(parser, GUMBO_TAG_SELECT)) {
  3205. ignore_token(parser);
  3206. } else {
  3207. close_current_select(parser);
  3208. parser->_parser_state->_reprocess_current_token = true;
  3209. }
  3210. return false;
  3211. } else if (tag_is(token, kStartTag, GUMBO_TAG_SCRIPT)) {
  3212. return handle_in_head(parser, token);
  3213. } else if (token->type == GUMBO_TOKEN_EOF) {
  3214. if (get_current_node(parser) != parser->_output->root) {
  3215. add_parse_error(parser, token);
  3216. return false;
  3217. }
  3218. return true;
  3219. } else {
  3220. add_parse_error(parser, token);
  3221. ignore_token(parser);
  3222. return false;
  3223. }
  3224. }
  3225. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-inselectintable
  3226. static bool handle_in_select_in_table(GumboParser* parser, GumboToken* token) {
  3227. if (tag_in(token, kStartTag, GUMBO_TAG_CAPTION, GUMBO_TAG_TABLE,
  3228. GUMBO_TAG_TBODY, GUMBO_TAG_TFOOT, GUMBO_TAG_THEAD, GUMBO_TAG_TR,
  3229. GUMBO_TAG_TD, GUMBO_TAG_TH, GUMBO_TAG_LAST)) {
  3230. add_parse_error(parser, token);
  3231. close_current_select(parser);
  3232. parser->_parser_state->_reprocess_current_token = true;
  3233. return false;
  3234. } else if (tag_in(token, kEndTag, GUMBO_TAG_CAPTION, GUMBO_TAG_TABLE,
  3235. GUMBO_TAG_TBODY, GUMBO_TAG_TFOOT, GUMBO_TAG_THEAD,
  3236. GUMBO_TAG_TR, GUMBO_TAG_TD, GUMBO_TAG_TH, GUMBO_TAG_LAST)) {
  3237. add_parse_error(parser, token);
  3238. if (has_an_element_in_table_scope(parser, token->v.end_tag)) {
  3239. close_current_select(parser);
  3240. reset_insertion_mode_appropriately(parser);
  3241. parser->_parser_state->_reprocess_current_token = true;
  3242. } else {
  3243. ignore_token(parser);
  3244. }
  3245. return false;
  3246. } else {
  3247. return handle_in_select(parser, token);
  3248. }
  3249. }
  3250. // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#parsing-main-intemplate
  3251. static bool handle_in_template(GumboParser* parser, GumboToken* token) {
  3252. // TODO(jdtang): Implement this.
  3253. return true;
  3254. }
  3255. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-afterbody
  3256. static bool handle_after_body(GumboParser* parser, GumboToken* token) {
  3257. if (token->type == GUMBO_TOKEN_WHITESPACE ||
  3258. tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
  3259. return handle_in_body(parser, token);
  3260. } else if (token->type == GUMBO_TOKEN_COMMENT) {
  3261. GumboNode* html_node = parser->_output->root;
  3262. assert(html_node != NULL);
  3263. append_comment_node(parser, html_node, token);
  3264. return true;
  3265. } else if (token->type == GUMBO_TOKEN_DOCTYPE) {
  3266. add_parse_error(parser, token);
  3267. ignore_token(parser);
  3268. return false;
  3269. } else if (tag_is(token, kEndTag, GUMBO_TAG_HTML)) {
  3270. // TODO(jdtang): Handle fragment parsing algorithm case.
  3271. set_insertion_mode(parser, GUMBO_INSERTION_MODE_AFTER_AFTER_BODY);
  3272. GumboNode* html = parser->_parser_state->_open_elements.data[0];
  3273. assert(node_tag_is(html, GUMBO_TAG_HTML));
  3274. record_end_of_element(
  3275. parser->_parser_state->_current_token, &html->v.element);
  3276. return true;
  3277. } else if (token->type == GUMBO_TOKEN_EOF) {
  3278. return true;
  3279. } else {
  3280. add_parse_error(parser, token);
  3281. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_BODY);
  3282. parser->_parser_state->_reprocess_current_token = true;
  3283. return false;
  3284. }
  3285. }
  3286. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-inframeset
  3287. static bool handle_in_frameset(GumboParser* parser, GumboToken* token) {
  3288. if (token->type == GUMBO_TOKEN_WHITESPACE) {
  3289. insert_text_token(parser, token);
  3290. return true;
  3291. } else if (token->type == GUMBO_TOKEN_COMMENT) {
  3292. append_comment_node(parser, get_current_node(parser), token);
  3293. return true;
  3294. } else if (token->type == GUMBO_TOKEN_DOCTYPE) {
  3295. add_parse_error(parser, token);
  3296. ignore_token(parser);
  3297. return false;
  3298. } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
  3299. return handle_in_body(parser, token);
  3300. } else if (tag_is(token, kStartTag, GUMBO_TAG_FRAMESET)) {
  3301. insert_element_from_token(parser, token);
  3302. return true;
  3303. } else if (tag_is(token, kEndTag, GUMBO_TAG_FRAMESET)) {
  3304. if (node_tag_is(get_current_node(parser), GUMBO_TAG_HTML)) {
  3305. add_parse_error(parser, token);
  3306. ignore_token(parser);
  3307. return false;
  3308. }
  3309. pop_current_node(parser);
  3310. // TODO(jdtang): Add a condition to ignore this for the fragment parsing
  3311. // algorithm.
  3312. if (!node_tag_is(get_current_node(parser), GUMBO_TAG_FRAMESET)) {
  3313. set_insertion_mode(parser, GUMBO_INSERTION_MODE_AFTER_FRAMESET);
  3314. }
  3315. return true;
  3316. } else if (tag_is(token, kStartTag, GUMBO_TAG_FRAME)) {
  3317. insert_element_from_token(parser, token);
  3318. pop_current_node(parser);
  3319. acknowledge_self_closing_tag(parser);
  3320. return true;
  3321. } else if (tag_is(token, kStartTag, GUMBO_TAG_NOFRAMES)) {
  3322. return handle_in_head(parser, token);
  3323. } else if (token->type == GUMBO_TOKEN_EOF) {
  3324. if (!node_tag_is(get_current_node(parser), GUMBO_TAG_HTML)) {
  3325. add_parse_error(parser, token);
  3326. return false;
  3327. }
  3328. return true;
  3329. } else {
  3330. add_parse_error(parser, token);
  3331. ignore_token(parser);
  3332. return false;
  3333. }
  3334. }
  3335. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-afterframeset
  3336. static bool handle_after_frameset(GumboParser* parser, GumboToken* token) {
  3337. if (token->type == GUMBO_TOKEN_WHITESPACE) {
  3338. insert_text_token(parser, token);
  3339. return true;
  3340. } else if (token->type == GUMBO_TOKEN_COMMENT) {
  3341. append_comment_node(parser, get_current_node(parser), token);
  3342. return true;
  3343. } else if (token->type == GUMBO_TOKEN_DOCTYPE) {
  3344. add_parse_error(parser, token);
  3345. ignore_token(parser);
  3346. return false;
  3347. } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
  3348. return handle_in_body(parser, token);
  3349. } else if (tag_is(token, kEndTag, GUMBO_TAG_HTML)) {
  3350. set_insertion_mode(parser, GUMBO_INSERTION_MODE_AFTER_AFTER_FRAMESET);
  3351. return true;
  3352. } else if (tag_is(token, kStartTag, GUMBO_TAG_NOFRAMES)) {
  3353. return handle_in_head(parser, token);
  3354. } else if (token->type == GUMBO_TOKEN_EOF) {
  3355. return true;
  3356. } else {
  3357. add_parse_error(parser, token);
  3358. ignore_token(parser);
  3359. return false;
  3360. }
  3361. }
  3362. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#the-after-after-body-insertion-mode
  3363. static bool handle_after_after_body(GumboParser* parser, GumboToken* token) {
  3364. if (token->type == GUMBO_TOKEN_COMMENT) {
  3365. append_comment_node(parser, get_document_node(parser), token);
  3366. return true;
  3367. } else if (token->type == GUMBO_TOKEN_DOCTYPE ||
  3368. token->type == GUMBO_TOKEN_WHITESPACE ||
  3369. tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
  3370. return handle_in_body(parser, token);
  3371. } else if (token->type == GUMBO_TOKEN_EOF) {
  3372. return true;
  3373. } else {
  3374. add_parse_error(parser, token);
  3375. set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_BODY);
  3376. parser->_parser_state->_reprocess_current_token = true;
  3377. return false;
  3378. }
  3379. }
  3380. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#the-after-after-frameset-insertion-mode
  3381. static bool handle_after_after_frameset(
  3382. GumboParser* parser, GumboToken* token) {
  3383. if (token->type == GUMBO_TOKEN_COMMENT) {
  3384. append_comment_node(parser, get_document_node(parser), token);
  3385. return true;
  3386. } else if (token->type == GUMBO_TOKEN_DOCTYPE ||
  3387. token->type == GUMBO_TOKEN_WHITESPACE ||
  3388. tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
  3389. return handle_in_body(parser, token);
  3390. } else if (token->type == GUMBO_TOKEN_EOF) {
  3391. return true;
  3392. } else if (tag_is(token, kStartTag, GUMBO_TAG_NOFRAMES)) {
  3393. return handle_in_head(parser, token);
  3394. } else {
  3395. add_parse_error(parser, token);
  3396. ignore_token(parser);
  3397. return false;
  3398. }
  3399. }
  3400. // Function pointers for each insertion mode. Keep in sync with
  3401. // insertion_mode.h.
  3402. typedef bool (*TokenHandler)(GumboParser* parser, GumboToken* token);
  3403. static const TokenHandler kTokenHandlers[] = {
  3404. handle_initial,
  3405. handle_before_html,
  3406. handle_before_head,
  3407. handle_in_head,
  3408. handle_in_head_noscript,
  3409. handle_after_head,
  3410. handle_in_body,
  3411. handle_text,
  3412. handle_in_table,
  3413. handle_in_table_text,
  3414. handle_in_caption,
  3415. handle_in_column_group,
  3416. handle_in_table_body,
  3417. handle_in_row,
  3418. handle_in_cell,
  3419. handle_in_select,
  3420. handle_in_select_in_table,
  3421. handle_in_template,
  3422. handle_after_body,
  3423. handle_in_frameset,
  3424. handle_after_frameset,
  3425. handle_after_after_body,
  3426. handle_after_after_frameset
  3427. };
  3428. static bool handle_html_content(GumboParser* parser, GumboToken* token) {
  3429. return kTokenHandlers[(unsigned int) parser->_parser_state->_insertion_mode](
  3430. parser, token);
  3431. }
  3432. // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-inforeign
  3433. static bool handle_in_foreign_content(GumboParser* parser, GumboToken* token) {
  3434. switch (token->type) {
  3435. case GUMBO_TOKEN_NULL:
  3436. add_parse_error(parser, token);
  3437. token->type = GUMBO_TOKEN_CHARACTER;
  3438. token->v.character = kUtf8ReplacementChar;
  3439. insert_text_token(parser, token);
  3440. return false;
  3441. case GUMBO_TOKEN_WHITESPACE:
  3442. insert_text_token(parser, token);
  3443. return true;
  3444. case GUMBO_TOKEN_CHARACTER:
  3445. insert_text_token(parser, token);
  3446. set_frameset_not_ok(parser);
  3447. return true;
  3448. case GUMBO_TOKEN_COMMENT:
  3449. append_comment_node(parser, get_current_node(parser), token);
  3450. return true;
  3451. case GUMBO_TOKEN_DOCTYPE:
  3452. add_parse_error(parser, token);
  3453. ignore_token(parser);
  3454. return false;
  3455. default:
  3456. // Fall through to the if-statements below.
  3457. break;
  3458. }
  3459. // Order matters for these clauses.
  3460. if (tag_in(token, kStartTag, GUMBO_TAG_B, GUMBO_TAG_BIG,
  3461. GUMBO_TAG_BLOCKQUOTE, GUMBO_TAG_BODY, GUMBO_TAG_BR,
  3462. GUMBO_TAG_CENTER, GUMBO_TAG_CODE, GUMBO_TAG_DD, GUMBO_TAG_DIV,
  3463. GUMBO_TAG_DL, GUMBO_TAG_DT, GUMBO_TAG_EM, GUMBO_TAG_EMBED,
  3464. GUMBO_TAG_H1, GUMBO_TAG_H2, GUMBO_TAG_H3, GUMBO_TAG_H4,
  3465. GUMBO_TAG_H5, GUMBO_TAG_H6, GUMBO_TAG_HEAD, GUMBO_TAG_HR,
  3466. GUMBO_TAG_I, GUMBO_TAG_IMG, GUMBO_TAG_LI, GUMBO_TAG_LISTING,
  3467. GUMBO_TAG_MENU, GUMBO_TAG_META, GUMBO_TAG_NOBR, GUMBO_TAG_OL,
  3468. GUMBO_TAG_P, GUMBO_TAG_PRE, GUMBO_TAG_RUBY, GUMBO_TAG_S,
  3469. GUMBO_TAG_SMALL, GUMBO_TAG_SPAN, GUMBO_TAG_STRONG,
  3470. GUMBO_TAG_STRIKE, GUMBO_TAG_SUB, GUMBO_TAG_SUP,
  3471. GUMBO_TAG_TABLE, GUMBO_TAG_TT, GUMBO_TAG_U, GUMBO_TAG_UL,
  3472. GUMBO_TAG_VAR, GUMBO_TAG_LAST) ||
  3473. (tag_is(token, kStartTag, GUMBO_TAG_FONT) && (
  3474. token_has_attribute(token, "color") ||
  3475. token_has_attribute(token, "face") ||
  3476. token_has_attribute(token, "size")))) {
  3477. add_parse_error(parser, token);
  3478. do {
  3479. pop_current_node(parser);
  3480. } while(!(is_mathml_integration_point(get_current_node(parser)) ||
  3481. is_html_integration_point(get_current_node(parser)) ||
  3482. get_current_node(parser)->v.element.tag_namespace ==
  3483. GUMBO_NAMESPACE_HTML));
  3484. parser->_parser_state->_reprocess_current_token = true;
  3485. return false;
  3486. } else if (token->type == GUMBO_TOKEN_START_TAG) {
  3487. const GumboNamespaceEnum current_namespace =
  3488. get_current_node(parser)->v.element.tag_namespace;
  3489. if (current_namespace == GUMBO_NAMESPACE_MATHML) {
  3490. adjust_mathml_attributes(parser, token);
  3491. }
  3492. if (current_namespace == GUMBO_NAMESPACE_SVG) {
  3493. // Tag adjustment is left to the gumbo_normalize_svg_tagname helper
  3494. // function.
  3495. adjust_svg_attributes(parser, token);
  3496. }
  3497. adjust_foreign_attributes(parser, token);
  3498. insert_foreign_element(parser, token, current_namespace);
  3499. if (token->v.start_tag.is_self_closing) {
  3500. pop_current_node(parser);
  3501. acknowledge_self_closing_tag(parser);
  3502. }
  3503. return true;
  3504. // </script> tags are handled like any other end tag, putting the script's
  3505. // text into a text node child and closing the current node.
  3506. } else {
  3507. assert(token->type == GUMBO_TOKEN_END_TAG);
  3508. GumboNode* node = get_current_node(parser);
  3509. assert(node != NULL);
  3510. GumboStringPiece token_tagname = token->original_text;
  3511. GumboStringPiece node_tagname = node->v.element.original_tag;
  3512. gumbo_tag_from_original_text(&token_tagname);
  3513. gumbo_tag_from_original_text(&node_tagname);
  3514. bool is_success = true;
  3515. if (!gumbo_string_equals_ignore_case(&node_tagname, &token_tagname)) {
  3516. add_parse_error(parser, token);
  3517. is_success = false;
  3518. }
  3519. int i = parser->_parser_state->_open_elements.length;
  3520. for( --i; i > 0; ) {
  3521. // Here we move up the stack until we find an HTML element (in which
  3522. // case we do nothing) or we find the element that we're about to
  3523. // close (in which case we pop everything we've seen until that
  3524. // point.)
  3525. gumbo_debug("Foreign %.*s node at %d.\n", node_tagname.length,
  3526. node_tagname.data, i);
  3527. if (gumbo_string_equals_ignore_case(&node_tagname, &token_tagname)) {
  3528. gumbo_debug("Matches.\n");
  3529. while (pop_current_node(parser) != node) {
  3530. // Pop all the nodes below the current one. Node is guaranteed to
  3531. // be an element on the stack of open elements (set below), so
  3532. // this loop is guaranteed to terminate.
  3533. }
  3534. return is_success;
  3535. }
  3536. --i;
  3537. node = parser->_parser_state->_open_elements.data[i];
  3538. if (node->v.element.tag_namespace == GUMBO_NAMESPACE_HTML) {
  3539. // Must break before gumbo_tag_from_original_text to avoid passing
  3540. // parser-inserted nodes through.
  3541. break;
  3542. }
  3543. node_tagname = node->v.element.original_tag;
  3544. gumbo_tag_from_original_text(&node_tagname);
  3545. }
  3546. assert(node->v.element.tag_namespace == GUMBO_NAMESPACE_HTML);
  3547. // We can't call handle_token directly because the current node is still in
  3548. // the SVG namespace, so it would re-enter this and result in infinite
  3549. // recursion.
  3550. return handle_html_content(parser, token) && is_success;
  3551. }
  3552. }
  3553. // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#tree-construction
  3554. static bool handle_token(GumboParser* parser, GumboToken* token) {
  3555. if (parser->_parser_state->_ignore_next_linefeed &&
  3556. token->type == GUMBO_TOKEN_WHITESPACE && token->v.character == '\n') {
  3557. parser->_parser_state->_ignore_next_linefeed = false;
  3558. ignore_token(parser);
  3559. return true;
  3560. }
  3561. // This needs to be reset both here and in the conditional above to catch both
  3562. // the case where the next token is not whitespace (so we don't ignore
  3563. // whitespace in the middle of <pre> tags) and where there are multiple
  3564. // whitespace tokens (so we don't ignore the second one).
  3565. parser->_parser_state->_ignore_next_linefeed = false;
  3566. if (tag_is(token, kEndTag, GUMBO_TAG_BODY)) {
  3567. parser->_parser_state->_closed_body_tag = true;
  3568. }
  3569. if (tag_is(token, kEndTag, GUMBO_TAG_HTML)) {
  3570. parser->_parser_state->_closed_html_tag = true;
  3571. }
  3572. const GumboNode* current_node = get_current_node(parser);
  3573. assert(!current_node || current_node->type == GUMBO_NODE_ELEMENT);
  3574. if (current_node) {
  3575. gumbo_debug("Current node: <%s>.\n",
  3576. gumbo_normalized_tagname(current_node->v.element.tag));
  3577. }
  3578. if (!current_node ||
  3579. current_node->v.element.tag_namespace == GUMBO_NAMESPACE_HTML ||
  3580. (is_mathml_integration_point(current_node) &&
  3581. (token->type == GUMBO_TOKEN_CHARACTER ||
  3582. token->type == GUMBO_TOKEN_WHITESPACE ||
  3583. token->type == GUMBO_TOKEN_NULL ||
  3584. (token->type == GUMBO_TOKEN_START_TAG &&
  3585. !tag_in(token, kStartTag, GUMBO_TAG_MGLYPH, GUMBO_TAG_MALIGNMARK,
  3586. GUMBO_TAG_LAST)))) ||
  3587. (current_node->v.element.tag_namespace == GUMBO_NAMESPACE_MATHML &&
  3588. node_tag_is(current_node, GUMBO_TAG_ANNOTATION_XML) &&
  3589. tag_is(token, kStartTag, GUMBO_TAG_SVG)) ||
  3590. (is_html_integration_point(current_node) && (
  3591. token->type == GUMBO_TOKEN_START_TAG ||
  3592. token->type == GUMBO_TOKEN_CHARACTER ||
  3593. token->type == GUMBO_TOKEN_NULL ||
  3594. token->type == GUMBO_TOKEN_WHITESPACE)) ||
  3595. token->type == GUMBO_TOKEN_EOF) {
  3596. return handle_html_content(parser, token);
  3597. } else {
  3598. return handle_in_foreign_content(parser, token);
  3599. }
  3600. }
  3601. GumboOutput* gumbo_parse(const char* buffer) {
  3602. return gumbo_parse_with_options(
  3603. &kGumboDefaultOptions, buffer, strlen(buffer));
  3604. }
  3605. GumboOutput* gumbo_parse_with_options(
  3606. const GumboOptions* options, const char* buffer, size_t length) {
  3607. GumboParser parser;
  3608. parser._options = options;
  3609. output_init(&parser);
  3610. gumbo_tokenizer_state_init(&parser, buffer, length);
  3611. parser_state_init(&parser);
  3612. GumboParserState* state = parser._parser_state;
  3613. gumbo_debug("Parsing %.*s.\n", length, buffer);
  3614. // Sanity check so that infinite loops die with an assertion failure instead
  3615. // of hanging the process before we ever get an error.
  3616. int loop_count = 0;
  3617. GumboToken token;
  3618. bool has_error = false;
  3619. do {
  3620. if (state->_reprocess_current_token) {
  3621. state->_reprocess_current_token = false;
  3622. } else {
  3623. GumboNode* current_node = get_current_node(&parser);
  3624. gumbo_tokenizer_set_is_current_node_foreign(
  3625. &parser, current_node &&
  3626. current_node->v.element.tag_namespace != GUMBO_NAMESPACE_HTML);
  3627. has_error = !gumbo_lex(&parser, &token) || has_error;
  3628. }
  3629. const char* token_type = "text";
  3630. switch (token.type) {
  3631. case GUMBO_TOKEN_DOCTYPE:
  3632. token_type = "doctype";
  3633. break;
  3634. case GUMBO_TOKEN_START_TAG:
  3635. token_type = gumbo_normalized_tagname(token.v.start_tag.tag);
  3636. break;
  3637. case GUMBO_TOKEN_END_TAG:
  3638. token_type = gumbo_normalized_tagname(token.v.end_tag);
  3639. break;
  3640. case GUMBO_TOKEN_COMMENT:
  3641. token_type = "comment";
  3642. break;
  3643. default:
  3644. break;
  3645. }
  3646. gumbo_debug("Handling %s token @%d:%d in state %d.\n",
  3647. (char*) token_type, token.position.line, token.position.column,
  3648. state->_insertion_mode);
  3649. state->_current_token = &token;
  3650. state->_self_closing_flag_acknowledged =
  3651. !(token.type == GUMBO_TOKEN_START_TAG &&
  3652. token.v.start_tag.is_self_closing);
  3653. has_error = !handle_token(&parser, &token) || has_error;
  3654. // Check for memory leaks when ownership is transferred from start tag
  3655. // tokens to nodes.
  3656. assert(state->_reprocess_current_token ||
  3657. token.type != GUMBO_TOKEN_START_TAG ||
  3658. token.v.start_tag.attributes.data == NULL);
  3659. if (!state->_self_closing_flag_acknowledged) {
  3660. GumboError* error = add_parse_error(&parser, &token);
  3661. if (error) {
  3662. error->type = GUMBO_ERR_UNACKNOWLEDGED_SELF_CLOSING_TAG;
  3663. }
  3664. }
  3665. ++loop_count;
  3666. assert(loop_count < 1000000000);
  3667. } while ((token.type != GUMBO_TOKEN_EOF || state->_reprocess_current_token) &&
  3668. !(options->stop_on_first_error && has_error));
  3669. finish_parsing(&parser);
  3670. // For API uniformity reasons, if the doctype still has nulls, convert them to
  3671. // empty strings.
  3672. GumboDocument* doc_type = &parser._output->document->v.document;
  3673. if (doc_type->name == NULL) {
  3674. doc_type->name = gumbo_copy_stringz(&parser, "");
  3675. }
  3676. if (doc_type->public_identifier == NULL) {
  3677. doc_type->public_identifier = gumbo_copy_stringz(&parser, "");
  3678. }
  3679. if (doc_type->system_identifier == NULL) {
  3680. doc_type->system_identifier = gumbo_copy_stringz(&parser, "");
  3681. }
  3682. parser_state_destroy(&parser);
  3683. gumbo_tokenizer_state_destroy(&parser);
  3684. return parser._output;
  3685. }
  3686. void gumbo_destroy_node(GumboOptions* options, GumboNode* node) {
  3687. // Need a dummy GumboParser because the allocator comes along with the
  3688. // options object.
  3689. GumboParser parser;
  3690. parser._options = options;
  3691. destroy_node(&parser, node);
  3692. }
  3693. void gumbo_destroy_output(const GumboOptions* options, GumboOutput* output) {
  3694. // Need a dummy GumboParser because the allocator comes along with the
  3695. // options object.
  3696. GumboParser parser;
  3697. parser._options = options;
  3698. destroy_node(&parser, output->document);
  3699. for (int i = 0; i < output->errors.length; ++i) {
  3700. gumbo_error_destroy(&parser, output->errors.data[i]);
  3701. }
  3702. gumbo_vector_destroy(&parser, &output->errors);
  3703. gumbo_parser_deallocate(&parser, output);
  3704. }