gendoc.php 90 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846
  1. #!/usr/bin/php
  2. <?php
  3. /*
  4. * gendoc.php
  5. *
  6. * Copyright (C) 2022 bzt (bztsrc@gitlab)
  7. *
  8. * This program is free software; you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation; either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, write to the Free Software
  20. * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  21. *
  22. * @brief Small script to generate documentation into a single, self-contained static HTML file
  23. *
  24. */
  25. setlocale(LC_ALL, 'en_US.UTF8');
  26. mb_internal_encoding('utf-8');
  27. if($_SERVER['argc'] < 3) die("./gendoc.php <output file> <input file> [input file] [input file...]\r\n");
  28. class gendoc {
  29. public static $version = "1.0.0";
  30. /*** default translations and variables if not set with <doc> ***/
  31. public static $lang = [
  32. "lang" => "en",
  33. "titleimg" => "",
  34. "title" => "",
  35. "url" => "#",
  36. "version" => "stable",
  37. "theme" => "",
  38. "rslt" => "Search Results",
  39. "home" => "Home",
  40. "link" => "Permalink to this headline",
  41. "info" => "Important",
  42. "hint" => "Hint",
  43. "note" => "Note",
  44. "also" => "See Also",
  45. "todo" => "To Do",
  46. "warn" => "Warning",
  47. "args" => "Arguments",
  48. "rval" => "Return Value",
  49. "prev" => "Previous",
  50. "next" => "Next",
  51. "copy" => "unknown"
  52. ];
  53. public static $rules = []; /* syntax highlight rules lang => [ comments, pseudo, op, num, str, types, keywords ] */
  54. public static $writer = null; /* the output writer plugin */
  55. public static $fn = ""; /* file name, for error reporting */
  56. public static $l = 0; /* line number, for error reporting */
  57. public static $out = ""; /* output string */
  58. public static $toc = []; /* table of contents, each entry id => [ level, name ] */
  59. public static $cap = ""; /* last caption */
  60. public static $err = 0; /* number of errors */
  61. public static $H = 0; /* set if there was a hello tag */
  62. public static $h = 0; /* set if we are in hello tag right now */
  63. public static $fwd = []; /* forward references */
  64. public static $vld = []; /* validations */
  65. private static $n = 0; /* number of captions */
  66. private static $first = ""; /* for linking pages */
  67. private static $last = "";
  68. private static $prev = "";
  69. private static $lsec = "";
  70. /**
  71. * Convert a string into an URL and id attribute-safe string.
  72. * Sometimes it sucks that internet was designed by English only speakers. gendoc class only method.
  73. * @param string input
  74. * @return string the converted string
  75. */
  76. public static function safeid($str)
  77. {
  78. return preg_replace("/[\ ]+/", '_', trim(preg_replace("/[^a-z0-9]/", ' ', strtr(strtolower(html_entity_decode(trim($str))),
  79. ["\r" => "", "\"" => "", "\'" => "", "#" => "", "?" => "", "/" => "", "&" => "", ";" => "",
  80. "À"=>"a","à"=>"a","Á"=>"a","á"=>"a","Â"=>"a","â"=>"a","Ã"=>"a","ã"=>"a","Ä"=>"a","ä"=>"a","Å"=>"a","å"=>"a","Æ"=>"ae","æ"=>"ae",
  81. "Ç"=>"c","ç"=>"c","È"=>"e","è"=>"e","É"=>"e","é"=>"e","Ê"=>"e","ê"=>"e","Ë"=>"e","ë"=>"e","Ì"=>"i","ì"=>"i","Í"=>"i","í"=>"i",
  82. "Î"=>"i","î"=>"i","Ï"=>"i","ï"=>"i","Ð"=>"d","ð"=>"d","Ñ"=>"n","ñ"=>"n","Ò"=>"o","ò"=>"o","Ó"=>"o","ó"=>"o","Ô"=>"o","ô"=>"o",
  83. "Õ"=>"o","õ"=>"o","Ö"=>"o","ö"=>"o","Ø"=>"o","ø"=>"o","Ù"=>"u","ù"=>"u","Ú"=>"u","ú"=>"u","Û"=>"u","û"=>"u","Ü"=>"u","ü"=>"u",
  84. "Ý"=>"y","ý"=>"y","Þ"=>"p","þ"=>"p","Ā"=>"a","ā"=>"a","Ă"=>"a","ă"=>"a","Ą"=>"a","ą"=>"a","Ć"=>"c","ć"=>"c","Ĉ"=>"c","ĉ"=>"c",
  85. "Ċ"=>"c","ċ"=>"c","Č"=>"c","č"=>"c","Ď"=>"d","ď"=>"d","Đ"=>"d","đ"=>"d","Ē"=>"e","ē"=>"e","Ĕ"=>"e","ĕ"=>"e","Ė"=>"e","ė"=>"e",
  86. "Ę"=>"e","ę"=>"e","Ě"=>"e","ě"=>"e","Ĝ"=>"g","ĝ"=>"g","Ğ"=>"g","ğ"=>"g","Ġ"=>"g","ġ"=>"g","Ģ"=>"g","ģ"=>"g","Ĥ"=>"h","ĥ"=>"h",
  87. "Ħ"=>"h","ħ"=>"h","Ĩ"=>"i","ĩ"=>"i","Ī"=>"i","ī"=>"i","Ĭ"=>"i","ĭ"=>"i","Į"=>"i","į"=>"i","İ"=>"i","i"=>"i","IJ"=>"ij","ij"=>"ij",
  88. "Ĵ"=>"j","ĵ"=>"j","Ķ"=>"k","ķ"=>"k","Ĺ"=>"l","ĺ"=>"l","Ļ"=>"l","ļ"=>"l","Ľ"=>"l","ľ"=>"l","Ŀ"=>"l","ŀ"=>"l","Ł"=>"l","ł"=>"l",
  89. "Ń"=>"n","ń"=>"n","Ņ"=>"n","ņ"=>"n","Ň"=>"n","ň"=>"n","Ŋ"=>"n","ŋ"=>"n","Ō"=>"o","ō"=>"o","Ŏ"=>"o","ŏ"=>"o","Ő"=>"o","ő"=>"o",
  90. "Œ"=>"ce","œ"=>"ce","Ŕ"=>"r","ŕ"=>"r","Ŗ"=>"r","ŗ"=>"r","Ř"=>"r","ř"=>"r","Ś"=>"s","ś"=>"s","Ŝ"=>"s","ŝ"=>"s","Ş"=>"s","ş"=>"s",
  91. "Š"=>"s","š"=>"s","Ţ"=>"t","ţ"=>"t","Ť"=>"t","ť"=>"t","Ŧ"=>"t","ŧ"=>"t","Ũ"=>"u","ũ"=>"u","Ū"=>"u","ū"=>"u","Ŭ"=>"u","ŭ"=>"u",
  92. "Ů"=>"u","ů"=>"u","Ű"=>"u","ű"=>"u","Ų"=>"u","ų"=>"u","Ŵ"=>"w","ŵ"=>"w","Ŷ"=>"y","ŷ"=>"y","Ÿ"=>"y","ÿ"=>"y","Ź"=>"z","ź"=>"z",
  93. "Ż"=>"z","ż"=>"z","Ž"=>"z","ž"=>"z","Ɓ"=>"b","ɓ"=>"b","Ƃ"=>"b","ƃ"=>"b","Ƅ"=>"b","ƅ"=>"b","Ɔ"=>"c","ɔ"=>"c","Ƈ"=>"c","ƈ"=>"c",
  94. "Ɖ"=>"ɖ","ɖ"=>"d","Ɗ"=>"d","ɗ"=>"d","Ƌ"=>"d","ƌ"=>"d","Ǝ"=>"e","ǝ"=>"e","Ə"=>"e","ə"=>"e","Ɛ"=>"e","ɛ"=>"e","Ƒ"=>"f","ƒ"=>"f",
  95. "Ɠ"=>"g","ɠ"=>"g","Ɣ"=>"y","ɣ"=>"y","Ɩ"=>"l","ɩ"=>"l","Ɨ"=>"i","ɨ"=>"i","Ƙ"=>"k","ƙ"=>"k","Ɯ"=>"w","ɯ"=>"w","Ɲ"=>"n","ɲ"=>"n",
  96. "Ɵ"=>"o","ɵ"=>"o","Ơ"=>"o","ơ"=>"o","Ƣ"=>"oj","ƣ"=>"oj","Ƥ"=>"p","ƥ"=>"p","Ʀ"=>"r","ʀ"=>"r","Ƨ"=>"s","ƨ"=>"s","Ʃ"=>"s","ʃ"=>"s",
  97. "Ƭ"=>"t","ƭ"=>"t","Ʈ"=>"t","ʈ"=>"t","Ư"=>"u","ư"=>"u","Ʊ"=>"u","ʊ"=>"u","Ʋ"=>"u","ʋ"=>"u","Ƴ"=>"y","ƴ"=>"y","Ƶ"=>"z","ƶ"=>"z",
  98. "Ʒ"=>"z","ʒ"=>"z","Ƹ"=>"z","ƹ"=>"z","Ƽ"=>"z","ƽ"=>"z","DŽ"=>"dz","dž"=>"dz","Dž"=>"dz","dž"=>"dz","LJ"=>"lj","lj"=>"lj","Lj"=>"lj",
  99. "lj"=>"lj","NJ"=>"nj","nj"=>"nj","Nj"=>"nj","nj"=>"nj","Ǎ"=>"a","ǎ"=>"a","Ǐ"=>"i","ǐ"=>"i","Ǒ"=>"o","ǒ"=>"o","Ǔ"=>"u","ǔ"=>"u",
  100. "Ǖ"=>"u","ǖ"=>"u","Ǘ"=>"u","ǘ"=>"u","Ǚ"=>"u","ǚ"=>"u","Ǜ"=>"u","ǜ"=>"u","Ǟ"=>"a","ǟ"=>"a","Ǡ"=>"a","ǡ"=>"a","Ǣ"=>"ae","ǣ"=>"ae",
  101. "Ǥ"=>"g","ǥ"=>"g","Ǧ"=>"g","ǧ"=>"g","Ǩ"=>"k","ǩ"=>"k","Ǫ"=>"o","ǫ"=>"o","Ǭ"=>"o","ǭ"=>"o","Ǯ"=>"z","ǯ"=>"z","DZ"=>"dz","dz"=>"dz",
  102. "Dz"=>"dz","dz"=>"dz","Ǵ"=>"g","ǵ"=>"g","Ƕ"=>"hj","ƕ"=>"hj","Ƿ"=>"p","ƿ"=>"p","Ǹ"=>"n","ǹ"=>"n","Ǻ"=>"a","ǻ"=>"a","Ǽ"=>"ae","ǽ"=>"ae",
  103. "Ǿ"=>"o","ǿ"=>"ǿ","Ȁ"=>"a","ȁ"=>"a","Ȃ"=>"a","ȃ"=>"a","Ȅ"=>"e","ȅ"=>"e","Ȇ"=>"e","ȇ"=>"e","Ȉ"=>"i","ȉ"=>"i","Ȋ"=>"i","ȋ"=>"i",
  104. "Ȍ"=>"o","ȍ"=>"o","Ȏ"=>"o","ȏ"=>"o","Ȑ"=>"r","ȑ"=>"r","Ȓ"=>"r","ȓ"=>"r","Ȕ"=>"u","ȕ"=>"u","Ȗ"=>"u","ȗ"=>"u","Ș"=>"s","ș"=>"s",
  105. "Ț"=>"t","ț"=>"t","Ȝ"=>"z","ȝ"=>"z","Ȟ"=>"h","ȟ"=>"h","Ƞ"=>"n","ƞ"=>"n","Ȣ"=>"o","ȣ"=>"o","Ȥ"=>"z","ȥ"=>"z","Ȧ"=>"a","ȧ"=>"a",
  106. "Ȩ"=>"e","ȩ"=>"e","Ȫ"=>"o","ȫ"=>"o","Ȭ"=>"o","ȭ"=>"o","Ȯ"=>"o","ȯ"=>"o","Ȱ"=>"o","ȱ"=>"o","Ȳ"=>"y","ȳ"=>"y","Ⱥ"=>"a","ⱥ"=>"a",
  107. "Ȼ"=>"c","ȼ"=>"c","Ƚ"=>"l","ƚ"=>"l","Ⱦ"=>"t","ⱦ"=>"t","Ƀ"=>"b","ƀ"=>"b","Ʉ"=>"u","ʉ"=>"u","Ɇ"=>"e","ɇ"=>"e","Ɉ"=>"j","ɉ"=>"j",
  108. "Ɋ"=>"q","ɋ"=>"q","Ɍ"=>"r","ɍ"=>"r","Ɏ"=>"y","ɏ"=>"y"]))));
  109. }
  110. /**
  111. * Report an error. gendoc class only method.
  112. * @param string error message
  113. */
  114. public static function report_error($msg)
  115. {
  116. echo("gendoc error: ".self::$fn.":".self::$l.": ".$msg."\r\n");
  117. self::$err++;
  118. }
  119. /**
  120. * Generate API documentation. gendoc class only method.
  121. * @param string language
  122. * @param string file name
  123. */
  124. public static function api($c, $fn)
  125. {
  126. if(!empty(self::$fn)) $fn = dirname(self::$fn)."/".$fn;
  127. $s = @file_get_contents($fn);
  128. if(empty($s)) { self::report_error("unable to read source '".$fn."'"); return; }
  129. $ext = "api_".$c;
  130. $plugin = dirname(__FILE__)."/plugins/".$ext.".php";
  131. $ext = "gendoc_".$ext;
  132. if(file_exists($plugin))
  133. require_once $plugin;
  134. else $ext="";
  135. if(!empty($ext) && function_exists($ext))
  136. $ext($s);
  137. else {
  138. /* in lack of a plugin, a simple API doc generator */
  139. if(preg_match_all("/\/\*\*[^\*](.*?)\*\/[\n](.*?)$/ims", $s, $M, PREG_SET_ORDER)) {
  140. foreach($M as $m) {
  141. self::data_list_open();
  142. self::data_topic_open();
  143. self::source_code(trim($m[2]), $c);
  144. self::data_topic_close();
  145. self::data_description_open();
  146. $t = 0;
  147. foreach(explode("\n", $m[1]) as $dsc) {
  148. $dsc = htmlspecialchars(trim(preg_replace("/^[\ \t]*\*[\ \t]*/", "", $dsc)));
  149. if(!empty($dsc)) {
  150. if(substr($dsc, 0, 7) == "@param ") {
  151. if(!$t) {
  152. $t = 1;
  153. self::table_open();
  154. self::table_row_open();
  155. self::table_header_open();
  156. self::text(self::$lang["args"]);
  157. self::table_header_close();
  158. self::table_row_close();
  159. }
  160. self::table_row_open();
  161. self::table_cell_open();
  162. self::text(trim(substr($dsc, 7)));
  163. self::table_cell_close();
  164. self::table_row_close();
  165. } else
  166. if(substr($dsc, 0, 8) == "@return ") {
  167. if(!$t) {
  168. $t = 1;
  169. self::table_open();
  170. }
  171. self::table_row_open();
  172. self::table_header_open();
  173. self::text(self::$lang["rval"]);
  174. self::table_header_close();
  175. self::table_row_close();
  176. self::table_row_open();
  177. self::table_cell_open();
  178. self::text(trim(substr($dsc, 8)));
  179. self::table_cell_close();
  180. self::table_row_close();
  181. } else
  182. if(!$t)
  183. self::text($dsc." ");
  184. }
  185. }
  186. if($t)
  187. self::table_close();
  188. self::data_description_close();
  189. self::data_list_close();
  190. self::line_break();
  191. }
  192. }
  193. }
  194. }
  195. /**
  196. * Include another source document. gendoc class only method.
  197. * @param string file name
  198. */
  199. public static function include($fn) {
  200. if(!empty(self::$fn)) $fn = dirname(self::$fn)."/".$fn;
  201. $s = @file_get_contents($fn);
  202. if(empty($s)) { echo("gendoc error: ".$fn.":0: unable to read\r\n"); self::$err++; return; }
  203. $oldfn = self::$fn; $oldl = self::$l;
  204. self::$fn = $fn;
  205. self::$l = 1;
  206. $l = strrpos($fn, '.');
  207. if($l !== false) {
  208. $ext = "_".substr($fn, $l + 1);
  209. $plugin = dirname(__FILE__)."/plugins/fmt".$ext.".php";
  210. $ext = "gendoc".$ext;
  211. if(file_exists($plugin))
  212. require_once $plugin;
  213. else {
  214. if($ext != "gendoc_xml")
  215. self::report_error("there is no ".$ext." plugin to read this format.");
  216. $ext="";
  217. }
  218. }
  219. if(!empty($ext) && class_exists($ext)) {
  220. if(method_exists($ext, "parse"))
  221. $s = $ext::parse($s);
  222. else
  223. self::report_error("the ".$ext." plugin does not support reading files.");
  224. }
  225. if(!empty($s)) self::parse($s);
  226. self::$fn = $oldfn; self::$l = $oldl;
  227. }
  228. /**
  229. * Parse <doc> tag. gendoc class only method.
  230. * @param string with <doc> sub-tags
  231. */
  232. public static function doc($str)
  233. {
  234. if(preg_match_all("/<([^\/][^>]+)>([^<]+)*/ms", $str, $m, PREG_SET_ORDER))
  235. foreach($m as $M) {
  236. self::$lang[$M[1]] =
  237. $M[1] == "theme" && !empty($M[2]) ? dirname(self::$fn)."/".$M[2] : (
  238. $M[1] == "titleimg" && !empty($M[2]) ?
  239. dirname(self::$fn)."/".substr($M[2], 0, strpos($M[2], " ")).substr($M[2], strpos($M[2], " ")) : $M[2]);
  240. }
  241. }
  242. /**
  243. * Start <hello> tag. gendoc class only method.
  244. */
  245. public static function hello_open()
  246. {
  247. self::$h = self::$H = 1;
  248. self::$last = self::$first = "_";
  249. }
  250. /**
  251. * End <hello> tag. gendoc class only method.
  252. */
  253. public static function hello_close()
  254. {
  255. self::$h = 0;
  256. }
  257. /**
  258. * Add a caption to TOC. gendoc class only method.
  259. * @param string caption's name
  260. */
  261. public static function caption($name)
  262. {
  263. if(!empty(self::$vld)) {
  264. foreach(self::$vld as $k => $v)
  265. if($v != 0)
  266. self::report_error("unclosed ".$k." in section ".(empty(self::$lsec) ? "unknown" : self::$lsec));
  267. self::$vld = [];
  268. }
  269. self::$lsec = $name;
  270. self::$toc["!".self::$n++] = [ "0", $name ];
  271. }
  272. /**
  273. * Add a heading. Has to be implemented by writer plugins.
  274. * @param string "1" to "6"
  275. * @param string heading user-readable name
  276. * @param string heading label id
  277. */
  278. public static function heading($level, $name, $id = "", $alias = "")
  279. {
  280. if(intval($level) < 1 || intval($level) > 6) {
  281. self::report_error("invalid heading level");
  282. return;
  283. }
  284. $level .= "";
  285. $name = trim($name);
  286. if(!empty(self::$vld)) {
  287. foreach(self::$vld as $k => $v)
  288. if($v != 0)
  289. self::report_error("unclosed ".$k." in section ".(empty(self::$lsec) ? "unknown" : self::$lsec));
  290. self::$vld = [];
  291. }
  292. self::$lsec = $name;
  293. if(empty($name)) {
  294. self::report_error("empty heading name");
  295. return;
  296. }
  297. if(self::$h) {
  298. if(!empty(self::$writer)) self::$writer->heading($level, "", $name);
  299. else {
  300. if($level == "1")
  301. self::$out .= "<div class=\"page\" rel=\"_\">";
  302. self::$out .= "\n<h".$level.">".$name."</h".$level.">";
  303. }
  304. } else {
  305. $id = self::safeid(!empty($id) ? $id : $name); $alias = self::safeid($alias);
  306. self::$out = trim(self::$out);
  307. if($level == "1" && !empty(self::$out)) {
  308. self::prev_link();
  309. self::next_link($id, $name);
  310. self::$prev = self::$last;
  311. self::$last = $id;
  312. }
  313. if(empty($id)) {
  314. self::report_error("no id for heading (".$name.")");
  315. } else
  316. if(!empty(self::$toc[$id])) {
  317. self::report_error("id for heading isn't unique (".$id.")");
  318. $id = "";
  319. } else
  320. self::$toc[$id] = [ $level, $name ];
  321. if(!empty(self::$fwd[self::safeid($name)])) {
  322. self::resolve_link(self::safeid($name), $id);
  323. }
  324. if(!empty(self::$writer)) self::$writer->heading($level, $id, $name);
  325. else {
  326. if($level == "1") {
  327. if(empty(self::$first)) self::$first = $id;
  328. self::$out .= "<div class=\"page\"".(!empty($id) ? " rel=\"".$id."\"" : "")."><div><ul class=\"breadcrumbs\"><li><label class=\"home\" for=\"_".
  329. (self::$first=='_'?"":self::$first)."\" ".
  330. "title=\"".htmlspecialchars(self::$lang["home"])."\"></label>&nbsp;»</li>".
  331. (!empty(self::$cap) ? "<li>&nbsp;".self::$cap."&nbsp;»</li>" : "")."<li>&nbsp;".$name."</li></ul><hr></div>";
  332. }
  333. self::$out .= "\n".(!empty($alias) ? "<span id=\"".$alias."\"></span>" : "")."<h".$level.(!empty($id) ? " id=\"".$id."\"" : "").">";
  334. self::$out .= $name.(!empty($id) ? "<a href=\"#".$id."\"></a>" : "")."</h".$level.">";
  335. }
  336. }
  337. }
  338. /**
  339. * Start a paragraph. Has to be implemented by writer plugins.
  340. */
  341. public static function paragraph_open()
  342. {
  343. @self::$vld["p"]++;
  344. if(!empty(self::$writer)) self::$writer->paragraph_open();
  345. else self::$out .= "<p>";
  346. }
  347. /**
  348. * End a paragraph. Has to be implemented by writer plugins.
  349. */
  350. public static function paragraph_close()
  351. {
  352. if(empty(self::$vld["p"]))
  353. self::report_error("cannot close, paragraph is not open");
  354. else {
  355. if(!empty(self::$writer)) self::$writer->paragraph_close();
  356. else self::$out .= "</p>";
  357. self::$vld["p"]--;
  358. }
  359. }
  360. /**
  361. * Start bold text. Has to be implemented by writer plugins.
  362. */
  363. public static function bold_open()
  364. {
  365. if(!empty(self::$vld["b"]))
  366. self::report_error("bold is already open");
  367. else {
  368. if(!empty(self::$writer)) self::$writer->bold_open();
  369. else self::$out .= "<b>";
  370. self::$vld["b"] = 1;
  371. }
  372. }
  373. /**
  374. * Stop bold text. Has to be implemented by writer plugins.
  375. */
  376. public static function bold_close()
  377. {
  378. if(empty(self::$vld["b"]))
  379. self::report_error("cannot close, bold is not open");
  380. else {
  381. if(!empty(self::$writer)) self::$writer->bold_close();
  382. else self::$out .= "</b>";
  383. unset(self::$vld["b"]);
  384. }
  385. }
  386. /**
  387. * Start italic text. Has to be implemented by writer plugins.
  388. */
  389. public static function italic_open()
  390. {
  391. if(!empty(self::$vld["i"]))
  392. self::report_error("italic is already open");
  393. else {
  394. if(!empty(self::$writer)) self::$writer->italic_open();
  395. else self::$out .= "<i>";
  396. self::$vld["i"] = 1;
  397. }
  398. }
  399. /**
  400. * Stop italic text. Has to be implemented by writer plugins.
  401. */
  402. public static function italic_close()
  403. {
  404. if(empty(self::$vld["i"]))
  405. self::report_error("cannot close, italic is not open");
  406. else {
  407. if(!empty(self::$writer)) self::$writer->italic_close();
  408. else self::$out .= "</i>";
  409. unset(self::$vld["i"]);
  410. }
  411. }
  412. /**
  413. * Start underlined text. Has to be implemented by writer plugins.
  414. */
  415. public static function underline_open()
  416. {
  417. if(!empty(self::$vld["u"]))
  418. self::report_error("underline is already open");
  419. else {
  420. if(!empty(self::$writer)) self::$writer->underline_open();
  421. else self::$out .= "<u>";
  422. self::$vld["u"] = 1;
  423. }
  424. }
  425. /**
  426. * Stop underlined text. Has to be implemented by writer plugins.
  427. */
  428. public static function underline_close()
  429. {
  430. if(empty(self::$vld["u"]))
  431. self::report_error("cannot close, underline is not open");
  432. else {
  433. if(!empty(self::$writer)) self::$writer->underline_close();
  434. else self::$out .= "</u>";
  435. unset(self::$vld["u"]);
  436. }
  437. }
  438. /**
  439. * Start striked-through text. Has to be implemented by writer plugins.
  440. */
  441. public static function strike_open()
  442. {
  443. if(!empty(self::$vld["s"]))
  444. self::report_error("strike-through is already open");
  445. else {
  446. if(!empty(self::$writer)) self::$writer->strike_open();
  447. else self::$out .= "<s>";
  448. self::$vld["s"] = 1;
  449. }
  450. }
  451. /**
  452. * Stop striked-through text. Has to be implemented by writer plugins.
  453. */
  454. public static function strike_close()
  455. {
  456. if(empty(self::$vld["s"]))
  457. self::report_error("cannot close, strike-through is not open");
  458. else {
  459. if(!empty(self::$writer)) self::$writer->strike_close();
  460. else self::$out .= "</s>";
  461. unset(self::$vld["s"]);
  462. }
  463. }
  464. /**
  465. * Start superscript text. Has to be implemented by writer plugins.
  466. */
  467. public static function superscript_open()
  468. {
  469. @self::$vld["sup"]++;
  470. if(!empty(self::$writer)) self::$writer->superscript_open();
  471. else self::$out .= "<sup>";
  472. }
  473. /**
  474. * Stop superscript text. Has to be implemented by writer plugins.
  475. */
  476. public static function superscript_close()
  477. {
  478. if(empty(self::$vld["sup"]))
  479. self::report_error("cannot close, superscript is not open");
  480. else {
  481. if(!empty(self::$writer)) self::$writer->superscript_close();
  482. else self::$out .= "</sup>";
  483. self::$vld["sup"]--;
  484. }
  485. }
  486. /**
  487. * Start subscript text. Has to be implemented by writer plugins.
  488. */
  489. public static function subscript_open()
  490. {
  491. @self::$vld["sub"]++;
  492. if(!empty(self::$writer)) self::$writer->subscript_open();
  493. else self::$out .= "<sub>";
  494. }
  495. /**
  496. * Stop subscript text. Has to be implemented by writer plugins.
  497. */
  498. public static function subscript_close()
  499. {
  500. if(empty(self::$vld["sub"]))
  501. self::report_error("cannot close, subscript is not open");
  502. else {
  503. if(!empty(self::$writer)) self::$writer->subscript_close();
  504. else self::$out .= "</sub>";
  505. self::$vld["sub"]--;
  506. }
  507. }
  508. /**
  509. * Start quote text. Has to be implemented by writer plugins.
  510. */
  511. public static function quote_open()
  512. {
  513. if(!empty(self::$vld["quote"]))
  514. self::report_error("cannot open, quote is already open");
  515. else {
  516. @self::$vld["quote"]++;
  517. if(!empty(self::$writer)) self::$writer->quote_open();
  518. else self::$out .= "<blockquote class=\"pre\"><span></span>";
  519. }
  520. }
  521. /**
  522. * Stop quote text. Has to be implemented by writer plugins.
  523. */
  524. public static function quote_close()
  525. {
  526. if(empty(self::$vld["quote"]))
  527. self::report_error("cannot close, quote is not open");
  528. else {
  529. if(!empty(self::$writer)) self::$writer->quote_close();
  530. else self::$out .= "</blockquote>";
  531. self::$vld["quote"]--;
  532. }
  533. }
  534. /**
  535. * Add line break. Has to be implemented by writer plugins.
  536. */
  537. public static function line_break()
  538. {
  539. if(!empty(self::$writer)) self::$writer->line_break();
  540. else self::$out .= "<br>";
  541. }
  542. /**
  543. * Add a horizontal ruler. Has to be implemented by writer plugins.
  544. */
  545. public static function horizontal_ruler()
  546. {
  547. if(!empty(self::$writer)) self::$writer->horizontal_ruler();
  548. else self::$out .= "<hr>";
  549. }
  550. /**
  551. * Start ordered list. Has to be implemented by writer plugins.
  552. */
  553. public static function ordered_list_open()
  554. {
  555. @self::$vld["ol"]++;
  556. if(!empty(self::$writer)) self::$writer->ordered_list_open();
  557. else self::$out .= "<ol>";
  558. }
  559. /**
  560. * End ordered list. Has to be implemented by writer plugins.
  561. */
  562. public static function ordered_list_close()
  563. {
  564. if(empty(self::$vld["ol"]))
  565. self::report_error("cannot close, ordered list is not open");
  566. else {
  567. if(@self::$vld["li"] >= self::$vld["ol"] + @self::$vld["ul"])
  568. self::report_error("list item is still open");
  569. if(!empty(self::$writer)) self::$writer->ordered_list_close();
  570. else self::$out .= "</ol>";
  571. self::$vld["ol"]--;
  572. }
  573. }
  574. /**
  575. * Start unordered list. Has to be implemented by writer plugins.
  576. */
  577. public static function unordered_list_open()
  578. {
  579. @self::$vld["ul"]++;
  580. if(!empty(self::$writer)) self::$writer->unordered_list_open();
  581. else self::$out .= "<ul>";
  582. }
  583. /**
  584. * End unordered list. Has to be implemented by writer plugins.
  585. */
  586. public static function unordered_list_close()
  587. {
  588. if(empty(self::$vld["ul"]))
  589. self::report_error("cannot close, unordered list is not open");
  590. else {
  591. if(@self::$vld["li"] >= self::$vld["ul"] + @self::$vld["ol"])
  592. self::report_error("list item is still open");
  593. if(!empty(self::$writer)) self::$writer->unordered_list_close();
  594. else self::$out .= "</ul>";
  595. self::$vld["ul"]--;
  596. }
  597. }
  598. /**
  599. * Start list item. Has to be implemented by writer plugins.
  600. */
  601. public static function list_item_open()
  602. {
  603. if(empty(self::$vld["ol"]) && empty(self::$vld["ul"]))
  604. self::report_error("cannot add list item, no list is open");
  605. else {
  606. @self::$vld["li"]++;
  607. if(!empty(self::$writer)) self::$writer->list_item_open();
  608. else self::$out .= "<li>";
  609. }
  610. }
  611. /**
  612. * End list item. Has to be implemented by writer plugins.
  613. */
  614. public static function list_item_close()
  615. {
  616. if(@self::$vld["li"] < @self::$vld["ol"] + @self::$vld["ul"])
  617. self::report_error("cannot close, list item is not open");
  618. else {
  619. if(!empty(self::$writer)) self::$writer->list_item_close();
  620. else self::$out .= "</li>";
  621. self::$vld["li"]--;
  622. }
  623. }
  624. /**
  625. * Start data list. Has to be implemented by writer plugins.
  626. */
  627. public static function data_list_open()
  628. {
  629. @self::$vld["dl"]++;
  630. if(!empty(self::$writer)) self::$writer->data_list_open();
  631. else self::$out .= "<dl>";
  632. }
  633. /**
  634. * End data list. Has to be implemented by writer plugins.
  635. */
  636. public static function data_list_close()
  637. {
  638. if(empty(self::$vld["dl"]))
  639. self::report_error("cannot close, data list is not open");
  640. else {
  641. if(@self::$vld["dt"] > self::$vld["dl"] || @self::$vld["dd"] > self::$vld["dl"])
  642. self::report_error("data list item is still open");
  643. if(!empty(self::$writer)) self::$writer->data_list_close();
  644. else self::$out .= "</dl>";
  645. self::$vld["dl"]--;
  646. }
  647. }
  648. /**
  649. * Start data topic. Has to be implemented by writer plugins.
  650. */
  651. public static function data_topic_open()
  652. {
  653. if(empty(self::$vld["dl"]))
  654. self::report_error("cannot add data topic, data list is not open");
  655. else {
  656. @self::$vld["dt"]++;
  657. if(!empty(self::$writer)) self::$writer->data_topic_open();
  658. else self::$out .= "<dt>";
  659. }
  660. }
  661. /**
  662. * End data topic. Has to be implemented by writer plugins.
  663. */
  664. public static function data_topic_close()
  665. {
  666. if(@self::$vld["dt"] < @self::$vld["dl"])
  667. self::report_error("cannot close, data list is not open");
  668. else {
  669. if(!empty(self::$writer)) self::$writer->data_topic_close();
  670. else self::$out .= "</dt>";
  671. self::$vld["dt"]--;
  672. }
  673. }
  674. /**
  675. * Start data description. Has to be implemented by writer plugins.
  676. */
  677. public static function data_description_open()
  678. {
  679. if(empty(self::$vld["dl"]))
  680. self::report_error("cannot add data description, data list is not open");
  681. else {
  682. @self::$vld["dd"]++;
  683. if(!empty(self::$writer)) self::$writer->data_description_open();
  684. else self::$out .= "<dd>";
  685. }
  686. }
  687. /**
  688. * End data description. Has to be implemented by writer plugins.
  689. */
  690. public static function data_description_close()
  691. {
  692. if(@self::$vld["dd"] < @self::$vld["dl"])
  693. self::report_error("cannot close, data description is not open");
  694. else {
  695. if(!empty(self::$writer)) self::$writer->data_description_close();
  696. else self::$out .= "</dd>";
  697. self::$vld["dd"]--;
  698. }
  699. }
  700. /**
  701. * Start grid. Has to be implemented by writer plugins.
  702. */
  703. public static function grid_open()
  704. {
  705. @self::$vld["grid"]++;
  706. if(!empty(self::$writer)) self::$writer->grid_open();
  707. else self::$out .= "<table class=\"grid\">";
  708. }
  709. /**
  710. * Start grid row. Has to be implemented by writer plugins.
  711. */
  712. public static function grid_row_open()
  713. {
  714. if(empty(self::$vld["grid"]))
  715. self::report_error("cannot add row, grid is not open");
  716. else {
  717. @self::$vld["gr"]++;
  718. if(!empty(self::$writer)) self::$writer->grid_row_open();
  719. else self::$out .= "<tr>";
  720. }
  721. }
  722. /**
  723. * Start grid cell. Has to be implemented by writer plugins.
  724. */
  725. public static function grid_cell_open()
  726. {
  727. if(empty(self::$vld["gr"]) || self::$vld["gr"] < @self::$vld["grid"])
  728. self::report_error("cannot add grid cell, grid row is not open");
  729. else {
  730. @self::$vld["gd"]++;
  731. if(!empty(self::$writer)) self::$writer->grid_cell_open();
  732. else self::$out .= "<td>";
  733. }
  734. }
  735. /**
  736. * Start wide grid cell. Has to be implemented by writer plugins.
  737. */
  738. public static function grid_cell_wide_open()
  739. {
  740. if(empty(self::$vld["gr"]) || self::$vld["gr"] < @self::$vld["grid"])
  741. self::report_error("cannot add grid cell, grid row is not open");
  742. else {
  743. @self::$vld["gd"]++;
  744. if(!empty(self::$writer)) self::$writer->grid_cell_wide_open();
  745. else self::$out .= "<td class=\"wide\">";
  746. }
  747. }
  748. /**
  749. * End grid cell. Has to be implemented by writer plugins.
  750. */
  751. public static function grid_cell_close()
  752. {
  753. if(empty(self::$vld["gd"]) || self::$vld["gd"] < @self::$vld["gr"])
  754. self::report_error("cannot close, grid cell is not open");
  755. else {
  756. if(!empty(self::$writer)) self::$writer->grid_cell_close();
  757. else self::$out .= "</td>";
  758. self::$vld["gd"]--;
  759. }
  760. }
  761. /**
  762. * End grid row. Has to be implemented by writer plugins.
  763. */
  764. public static function grid_row_close()
  765. {
  766. if(empty(self::$vld["gr"]) || self::$vld["gr"] < @self::$vld["grid"])
  767. self::report_error("cannot close, grid row is not open");
  768. else {
  769. if(@self::$vld["gd"] > self::$vld["gr"])
  770. self::report_error("grid cell is still open");
  771. if(!empty(self::$writer)) self::$writer->grid_row_close();
  772. else self::$out .= "</tr>";
  773. self::$vld["gr"]--;
  774. }
  775. }
  776. /**
  777. * End grid. Has to be implemented by writer plugins.
  778. */
  779. public static function grid_close()
  780. {
  781. if(empty(self::$vld["grid"]))
  782. self::report_error("cannot close, grid is not open");
  783. else {
  784. if(@self::$vld["gr"] > self::$vld["grid"])
  785. self::report_error("grid row is still open");
  786. if(!empty(self::$writer)) self::$writer->grid_close();
  787. else self::$out .= "</table>";
  788. self::$vld["grid"]--;
  789. }
  790. }
  791. /**
  792. * Start table. Has to be implemented by writer plugins.
  793. */
  794. public static function table_open()
  795. {
  796. @self::$vld["table"]++;
  797. if(!empty(self::$writer)) self::$writer->table_open();
  798. else self::$out .= "<div class=\"table\"><table>";
  799. }
  800. /**
  801. * Start table row. Has to be implemented by writer plugins.
  802. */
  803. public static function table_row_open()
  804. {
  805. if(empty(self::$vld["table"]))
  806. self::report_error("cannot add row, table is not open");
  807. else {
  808. @self::$vld["tr"]++;
  809. if(!empty(self::$writer)) self::$writer->table_row_open();
  810. else self::$out .= "<tr>";
  811. }
  812. }
  813. /**
  814. * Start table header. Has to be implemented by writer plugins.
  815. */
  816. public static function table_header_open()
  817. {
  818. if(empty(self::$vld["tr"]) || self::$vld["tr"] < @self::$vld["table"])
  819. self::report_error("cannot add header, table row is not open");
  820. else {
  821. @self::$vld["th"]++;
  822. if(!empty(self::$writer)) self::$writer->table_header_open();
  823. else self::$out .= "<th>";
  824. }
  825. }
  826. /**
  827. * Start wide table header. Has to be implemented by writer plugins.
  828. */
  829. public static function table_header_wide_open()
  830. {
  831. if(empty(self::$vld["tr"]) || self::$vld["tr"] < @self::$vld["table"])
  832. self::report_error("cannot add header, table row is not open");
  833. else {
  834. @self::$vld["th"]++;
  835. if(!empty(self::$writer)) self::$writer->table_header_wide_open();
  836. else self::$out .= "<th class=\"wide\">";
  837. }
  838. }
  839. /**
  840. * End table header. Has to be implemented by writer plugins.
  841. */
  842. public static function table_header_close()
  843. {
  844. if(empty(self::$vld["th"]) || self::$vld["th"] < @self::$vld["tr"])
  845. self::report_error("cannot close, table header is not open");
  846. else {
  847. if(!empty(self::$writer)) self::$writer->table_header_close();
  848. else self::$out .= "</th>";
  849. self::$vld["th"]--;
  850. }
  851. }
  852. /**
  853. * Start table cell. Has to be implemented by writer plugins.
  854. */
  855. public static function table_cell_open()
  856. {
  857. if(empty(self::$vld["tr"]) || self::$vld["tr"] < @self::$vld["table"])
  858. self::report_error("cannot add cell, table row is not open");
  859. else {
  860. @self::$vld["td"]++;
  861. if(!empty(self::$writer)) self::$writer->table_cell_open();
  862. else self::$out .= "<td>";
  863. }
  864. }
  865. /**
  866. * Start wide table cell. Has to be implemented by writer plugins.
  867. */
  868. public static function table_cell_wide_open()
  869. {
  870. if(empty(self::$vld["tr"]) || self::$vld["tr"] < @self::$vld["table"])
  871. self::report_error("cannot add cell, table row is not open");
  872. else {
  873. @self::$vld["td"]++;
  874. if(!empty(self::$writer)) self::$writer->table_cell_wide_open();
  875. else self::$out .= "<td class=\"wide\">";
  876. }
  877. }
  878. /**
  879. * Start numeric table cell. Has to be implemented by writer plugins.
  880. */
  881. public static function table_number_open()
  882. {
  883. if(empty(self::$vld["tr"]) || self::$vld["tr"] < @self::$vld["table"])
  884. self::report_error("cannot add cell, table row is not open");
  885. else {
  886. @self::$vld["td"]++;
  887. if(!empty(self::$writer)) self::$writer->table_number_open();
  888. else self::$out .= "<td class=\"right\">";
  889. }
  890. }
  891. /**
  892. * Start wide numeric table cell. Has to be implemented by writer plugins.
  893. */
  894. public static function table_number_wide_open()
  895. {
  896. if(empty(self::$vld["tr"]) || self::$vld["tr"] < @self::$vld["table"])
  897. self::report_error("cannot add cell, table row is not open");
  898. else {
  899. @self::$vld["td"]++;
  900. if(!empty(self::$writer)) self::$writer->table_number_wide_open();
  901. else self::$out .= "<td class=\"right wide\">";
  902. }
  903. }
  904. /**
  905. * End table cell. Has to be implemented by writer plugins.
  906. */
  907. public static function table_cell_close()
  908. {
  909. if(empty(self::$vld["td"]) || self::$vld["td"] < @self::$vld["tr"])
  910. self::report_error("cannot close, table header is not open");
  911. else {
  912. if(!empty(self::$writer)) self::$writer->table_cell_close();
  913. else self::$out .= "</td>";
  914. self::$vld["td"]--;
  915. }
  916. }
  917. /**
  918. * End table row. Has to be implemented by writer plugins.
  919. */
  920. public static function table_row_close()
  921. {
  922. if(empty(self::$vld["tr"]) || self::$vld["tr"] < @self::$vld["table"])
  923. self::report_error("cannot close, table row is not open");
  924. else {
  925. if(@self::$vld["td"] > self::$vld["tr"])
  926. self::report_error("table cell is still open");
  927. if(!empty(self::$writer)) self::$writer->table_row_close();
  928. else self::$out .= "</tr>";
  929. self::$vld["tr"]--;
  930. }
  931. }
  932. /**
  933. * End table. Has to be implemented by writer plugins.
  934. */
  935. public static function table_close()
  936. {
  937. if(empty(self::$vld["table"]))
  938. self::report_error("cannot close, table is not open");
  939. else {
  940. if(@self::$vld["tr"] > self::$vld["table"])
  941. self::report_error("table row is still open");
  942. if(!empty(self::$writer)) self::$writer->table_close();
  943. else self::$out .= "</table></div>";
  944. self::$vld["table"]--;
  945. }
  946. }
  947. /**
  948. * Add teletype (monospace) text. Has to be implemented by writer plugins.
  949. * @param string the text
  950. */
  951. public static function teletype($str)
  952. {
  953. if(!empty(self::$writer)) self::$writer->teletype($str);
  954. else self::$out .= "<samp>".htmlspecialchars($str)."</samp>";
  955. }
  956. /**
  957. * Add preformatted (multiline monospace) text. Has to be implemented by writer plugins.
  958. * @param string the text
  959. */
  960. public static function preformatted($str)
  961. {
  962. if(!empty(self::$writer)) self::$writer->preformatted($str);
  963. else self::$out .= "<div class=\"pre\"><pre>".strtr(htmlspecialchars($str), [ "&lt;hl&gt;" => "<span class=\"hl_h\">",
  964. "&lt;/hl&gt;" => "</span>", "&lt;hm&gt;" => "<span class=\"hl_h hl_b\">", "&lt;/hm&gt;\r\n" => "</span>",
  965. "&lt;/hm&gt;\n" => "</span>", "&lt;/hm&gt;" => "</span>" ])."</pre></div>";
  966. }
  967. /**
  968. * Add source code. Has to be implemented by writer plugins.
  969. * @param string the source code as string
  970. * @param string the language type (or empty string)
  971. * @param array tokenized source code (for syntax highlighting)
  972. */
  973. public static function source_code($str, $type = "", $tokens = [])
  974. {
  975. /* load highlight rules */
  976. $r=[];
  977. if(!empty($type)) {
  978. if(!empty(self::$rules[$type])) $r = self::$rules[$type];
  979. else echo("gendoc warning: ".self::$fn.":".self::$l.": no highlight rules for '".$type."' using generics\r\n");
  980. }
  981. /* use a generic scheme */
  982. if(empty($r)) $r = [ [ "\/\/.*?$", "\/\*.*?\*\/", "#.*?$" ], [ ], [ "[:=\<\>\+\-\*\/%&\^\|!][:=]?" ],
  983. [ "[0-9][0-9bx]?[0-9\.a-f\+\-]*?" ], [ "\"", "\'", "`" ], [ "[", "]", "{", "}", ")", ",", ";" ],
  984. [ "char", "int", "float", "true", "false", "nil", "null", "nullptr", "none",
  985. "public", "static", "struct", "from", "with", "new", "delete" ],
  986. [ "import", "def", "if", "then", "else", "endif", "elif", "switch", "case", "loop", "until", "for", "foreach", "as",
  987. "is", "in", "or", "and", "while", "do", "break", "continue", "function", "return", "enum", "try", "catch",
  988. "volatile", "class", "typedef" ]
  989. ];
  990. $t = [];
  991. /* tokenize string */
  992. for($k = 0; $k < strlen($str); ) {
  993. if(!$k && substr($str, 0, 2) == "#!") {
  994. while($k < strlen($str) && $str[$k] != '\r' && $str[$k] != '\n') $k++;
  995. $t[] = [ "c", substr($str, 0, $k) ];
  996. }
  997. if($str[$k] == "(") { $t[] = [ "", "(" ]; $k++; }
  998. if(preg_match("/<[\/]?h[lm]>/Aims", $str, $m, 0, $k)) {
  999. if(empty($t) || $t[count($t) - 1][0] != "") $t[] = [ "", "" ];
  1000. $t[count($t) - 1][1] .= $m[0]; $k += strlen($m[0]); continue; }
  1001. if(in_array($str[$k], [ ')', ' ', "\t", "\r", "\n" ]) || in_array($str[$k], $r[5])) {
  1002. if(empty($t) || $t[count($t) - 1][0] != "") $t[] = [ "", "" ];
  1003. $t[count($t) - 1][1] .= $str[$k++]; continue;
  1004. }
  1005. foreach([ "c", "p", "o", "n" ] as $o => $O) if(!($O == "n" && !empty($t) && $t[count($t) - 1][0] == "v")) {
  1006. foreach($r[$o] as $n) if(preg_match("/".$n."/Aims", $str, $m, 0, $k)) {
  1007. if(empty($t) || $t[count($t) - 1][0] != $O) $t[] = [ $O, "" ];
  1008. $t[count($t) - 1][1] .= $m[0]; $k += strlen($m[0]); continue 3; }
  1009. }
  1010. foreach($r[4] as $n) {
  1011. if(substr($str, $k, strlen($n)) == $n) {
  1012. $d = $n[strlen($n) - 1];
  1013. for($n = $k + strlen($n); $n < strlen($str); $n++) {
  1014. if($str[$n] == "\\") $n++; else
  1015. if($str[$n] == $d) { if(@$str[$n + 1] != $d) break; else $n++; }
  1016. }
  1017. $t[] = [ "s", substr($str, $k, $n - $k + 1) ]; $k = $n + 1; continue 2;
  1018. }
  1019. }
  1020. if(empty($t) || $t[count($t) - 1][0] != "v") $t[] = [ "v", "" ];
  1021. $t[count($t) - 1][1] .= $str[$k++];
  1022. }
  1023. foreach($t as $k => $T) {
  1024. if($T[0] == "v") {
  1025. if(in_array(strtolower($T[1]), $r[6])) $t[$k][0] = "t"; else
  1026. if(in_array(strtolower($T[1]), $r[7])) $t[$k][0] = "k"; else
  1027. if((!empty($t[$k + 1]) && $t[$k + 1][1][0] == "(") || (!empty($t[$k + 2]) && $t[$k + 1][0] == "" && $t[$k + 2][1][0] == "("))
  1028. $t[$k][0] = "f";
  1029. }
  1030. if($T[0] == "n" && @$t[$k - 1][0] == "o" && ($t[$k - 1][1] == "-" || $t[$k - 1][1] == ".")) {
  1031. $t[$k][1] = $t[$k - 1][1].$t[$k][1];
  1032. unset($t[$k - 1]);
  1033. }
  1034. }
  1035. if(!empty(self::$writer)) self::$writer->source_code($str, $type, $t);
  1036. else {
  1037. $L = substr_count(rtrim($str), "\n") + 1;
  1038. self::$out .= "<div class=\"pre\"><pre class=\"lineno\">";
  1039. for($k = 0; $k < $L; $k++) self::$out .= ($k + 1)."<br>";
  1040. self::$out .= "</pre><code>";
  1041. $c = "";
  1042. /* concatenate tokens into a string with highlight spans */
  1043. foreach($t as $k => $T) {
  1044. if($T[0] == "") $c .= htmlspecialchars($T[1]);
  1045. else $c .= "<span class=\"hl_".$T[0]."\">".htmlspecialchars($T[1])."</span>";
  1046. }
  1047. self::$out .= strtr($c, [ "&lt;hl&gt;" => "<span class=\"hl_h\">", "&lt;/hl&gt;" => "</span>",
  1048. "&lt;hm&gt;" => "<span class=\"hl_h hl_b\">", "&lt;/hm&gt;\r\n" => "</span>", "&lt;/hm&gt;\n" => "</span>",
  1049. "&lt;/hm&gt;" => "</span>" ])."</code></div>";
  1050. }
  1051. }
  1052. /**
  1053. * Add link to previous page. Could be implemented by writer plugins.
  1054. */
  1055. public static function prev_link()
  1056. {
  1057. if(!empty(self::$prev)) {
  1058. if(!empty(self::$writer)) self::$writer->prev_link();
  1059. else self::$out .= "<br style=\"clear:both;\"><label class=\"btn prev\" accesskey=\"p\" for=\"_".(self::$prev!="_"?self::$prev:"")."\"".(self::$prev!="_"?" title=\"".htmlspecialchars(self::$toc[self::$prev][1])."\"":"").">".self::$lang["prev"]."</label>";
  1060. }
  1061. }
  1062. /**
  1063. * Add link to next page. Could be implemented by writer plugins.
  1064. * @param string heading label id
  1065. * @param string heading user-readable name
  1066. */
  1067. public static function next_link($id, $name)
  1068. {
  1069. if(!empty(self::$writer)) self::$writer->next_link($id, $name);
  1070. else {
  1071. self::$out .= (!empty($id) ? (empty(self::$prev) ? "<br style=\"clear:both;\">" : "")."<label class=\"btn next\" accesskey=\"n\" for=\"_".$id."\" title=\"".htmlspecialchars($name)."\">".self::$lang["next"]."</label>" : "");
  1072. self::$out .= "</div>\n";
  1073. }
  1074. }
  1075. /**
  1076. * Add an internal link to any heading. Could be implemented by writer plugins.
  1077. * @param string heading user-readable name
  1078. * @param string where to link to, could be a substitute string for forward references
  1079. */
  1080. public static function internal_link($name, $lnk = "")
  1081. {
  1082. if(empty($lnk)) $lnk = self::safeid($name);
  1083. if(!empty($lnk)) {
  1084. if(empty(self::$toc[$lnk])) {
  1085. if(empty(self::$fwd[$lnk]))
  1086. self::$fwd[$lnk] = self::$fn.":".self::$l.": unresolved link: ".$name;
  1087. $lnk = "@GENDOC:".$lnk."@";
  1088. }
  1089. if(!empty(self::$writer)) self::$writer->internal_link($name, $lnk);
  1090. /* we use onclick too because some browsers do not reload the page if only the anchor changes */
  1091. else self::$out .= "<a href=\"#".$lnk."\" onclick=\"c('".$lnk."')\">".$name."</a>";
  1092. }
  1093. }
  1094. /**
  1095. * Resolve a forward internal link. Could be implemented by writer plugins.
  1096. * @param string the substitution string that was used as id
  1097. * @param string the real id
  1098. */
  1099. public static function resolve_link($subst, $id)
  1100. {
  1101. unset(self::$fwd[$subst]);
  1102. if(!empty(self::$writer)) self::$writer->resolve_link("@GENDOC:".$subst."@", $id);
  1103. else self::$out = str_replace("@GENDOC:".$subst."@", $id, self::$out);
  1104. }
  1105. /**
  1106. * Start an external link. Could be implemented by writer plugins.
  1107. * @param string link's URL
  1108. */
  1109. public static function external_link_open($url)
  1110. {
  1111. /* if url starts with '#', then insert it as an internal link, but without checks */
  1112. if(!empty(self::$writer)) self::$writer->external_link_open($url);
  1113. else self::$out .= "<a href=\"".$url."\" ".($url[0] == '#' ? "onclick=\"c('".substr($url,1)."')\"" : "target=\"new\"").">";
  1114. }
  1115. /**
  1116. * End an external link. Could be implemented by writer plugins.
  1117. */
  1118. public static function external_link_close()
  1119. {
  1120. if(!empty(self::$writer)) self::$writer->external_link_close();
  1121. else self::$out .= "</a>";
  1122. }
  1123. /**
  1124. * Start a user interface element. Has to be implemented by writer plugins.
  1125. * @param string type, "1" to "6"
  1126. */
  1127. public static function user_interface_open($type)
  1128. {
  1129. if(!empty(self::$vld["ui"]))
  1130. self::report_error("user interface element is already open");
  1131. else {
  1132. if(!empty(self::$writer)) self::$writer->user_interface_open($type);
  1133. else self::$out .= "<span class=\"ui".$type."\">";
  1134. @self::$vld["ui"]++;
  1135. }
  1136. }
  1137. /**
  1138. * End a user interface element. Has to be implemented by writer plugins.
  1139. */
  1140. public static function user_interface_close()
  1141. {
  1142. if(empty(self::$vld["ui"]))
  1143. self::report_error("cannot close, user interface element is not open");
  1144. else {
  1145. if(!empty(self::$writer)) self::$writer->user_interface_close();
  1146. else self::$out .= "</span>";
  1147. self::$vld["ui"]--;
  1148. }
  1149. }
  1150. /**
  1151. * Add a keyboard key. Has to be implemented by writer plugins.
  1152. * @param string key's name
  1153. */
  1154. public static function keyboard($key)
  1155. {
  1156. if(!empty(self::$writer)) self::$writer->keyboard($key);
  1157. else self::$out .= "<kbd>".htmlspecialchars($key)."</kbd>";
  1158. }
  1159. /**
  1160. * Add a mouse button image. Has to be implemented by writer plugins.
  1161. * @param string either "l" (left button), "r" (right button) or "w" (wheel)
  1162. */
  1163. public static function mouse_button($type)
  1164. {
  1165. if($type != "r" && $type != "w") $type = "l";
  1166. if(!empty(self::$writer)) self::$writer->mouse_button($type);
  1167. else self::$out .= "<span class=\"mouse".($type == "r" ? "right" : ($type == "w" ? "wheel" : "left"))."\"></span>";
  1168. }
  1169. /**
  1170. * Add an image. Has to be implemented by writer plugins.
  1171. * @param string either "t" (inlined as text), "l" (left), "r" (right), "c" (centered) or "w" (wide, centered)
  1172. * @param string image's file name
  1173. * @param array index 0 width, 1 height, 'mime' mime-type
  1174. * @param string image data
  1175. */
  1176. public static function image($align, $fn, $a = [], $img = "")
  1177. {
  1178. if($align != "l" && $align != "r" && $align != "c" && $align != "w") $align = "t";
  1179. $l = strlen(self::$lang["url"]);
  1180. $imfn = dirname(self::$fn)."/".(substr($fn,0,$l) == self::$lang["url"] ? substr($fn, $l) : $fn);
  1181. $img = @file_get_contents($imfn);
  1182. $a = @getimagesizefromstring($img);
  1183. if($align == "t" && function_exists("imagecreatefromstring") && ($i = @imagecreatefromstring($img))) {
  1184. $X = imagesx($i); $Y = imagesy($i);
  1185. if($Y > 22) {
  1186. $a[0] = floor(22*$X/$Y); $a[1] = 22;
  1187. $N = imagecreatetruecolor($a[0], 22);
  1188. imagealphablending($N, 0);imagesavealpha($N, 1);imagefill($N, 0, 0, imagecolorallocatealpha($N, 0, 0, 0, 0));
  1189. imagecopyresampled($N, $i, 0, 0, 0, 0, $a[0], $a[1], $X, $Y);
  1190. ob_start();imagepng($N);$d=ob_get_contents();ob_end_clean();
  1191. imagedestroy($N);
  1192. if(strlen($d) < strlen($img)) { $img = $d; $a['mime'] = "image/png"; }
  1193. }
  1194. imagedestroy($i);
  1195. }
  1196. if(empty($img) || @$a[0] < 1 || @$a[1] < 1 || empty($a['mime'])) {
  1197. self::report_error("unable to read image '".$imfn."'");
  1198. } else {
  1199. if(!empty(self::$writer)) self::$writer->image($align, $fn, $a, $img);
  1200. else self::$out .= ($align == "c" ? "<div class=\"imgc\">" : "").
  1201. "<img class=\"img".$align."\"".($align != "w" ? " width=\"".$a[0]."\" height=\"".$a[1]."\"" : "").
  1202. " alt=\"".htmlspecialchars(basename($fn))."\" src=\"data:".$a['mime'].";base64,".base64_encode($img)."\">".
  1203. ($align == "c" ? "</div>" : "");
  1204. }
  1205. }
  1206. /**
  1207. * Add an image caption. Has to be implemented by writer plugins.
  1208. * @param string figure description
  1209. */
  1210. public static function figure($name)
  1211. {
  1212. if(!empty(self::$writer)) self::$writer->figure($name);
  1213. else self::$out .= "<div class=\"fig\">".$name."</div>";
  1214. }
  1215. /**
  1216. * Start an alert box. Has to be implemented by writer plugins.
  1217. * @param string either "info", "hint", "note", "also", "todo" or "warn"
  1218. */
  1219. public static function alert_box_open($type)
  1220. {
  1221. if(!in_array($type, [ "hint", "note", "also", "todo", "warn" ])) $type = "info";
  1222. if(!empty(self::$vld["alert"]))
  1223. self::report_error("cannot open, alert box is already open");
  1224. else {
  1225. if(!empty(self::$writer)) self::$writer->alert_box_open($type);
  1226. else self::$out .= "<div class=\"".($type == "hint" ? "hint" : ($type == "todo" || $type == "warn" ? "warn" : "info"))."\">".
  1227. "<p><span>".self::$lang[$type]."</span></p><p>";
  1228. @self::$vld["alert"]++;
  1229. }
  1230. }
  1231. /**
  1232. * End an alert box. Has to be implemented by writer plugins.
  1233. */
  1234. public static function alert_box_close()
  1235. {
  1236. if(empty(self::$vld["alert"]))
  1237. self::report_error("cannot close, alert box is not open");
  1238. else {
  1239. if(!empty(self::$writer)) self::$writer->alert_box_close();
  1240. self::$out .= "</p></div>";
  1241. self::$vld["alert"]--;
  1242. }
  1243. }
  1244. /**
  1245. * Add any arbitrary text to output. If a tag is open, then to that. Has to be implemented by writer plugins.
  1246. * @param string the text
  1247. */
  1248. public static function text($str)
  1249. {
  1250. if(!empty(self::$writer)) self::$writer->text($str);
  1251. else self::$out .= $str;
  1252. }
  1253. /**
  1254. * Parse a source document. Has to be implemented by file format reader plugins.
  1255. * @param string source document's content
  1256. * @param gendoc::\$fn name of the source file
  1257. * @return gendoc::\$l current line number, must be incremented by the reader plugin
  1258. */
  1259. public static function parse($s)
  1260. {
  1261. for($i = 0; $i < strlen($s); ) {
  1262. /* skip comments */
  1263. if(substr($s, $i, 4) == "<!--") {
  1264. for($i += 4; $i < strlen($s) - 3 && substr($s, $i, 3) != "-->"; $i++) {
  1265. if($s[$i] == "\n") self::$l++;
  1266. }
  1267. $i += 3; continue;
  1268. }
  1269. /* copy everything between tags */
  1270. if($s[$i] == "\n") self::$l++;
  1271. if($s[$i] != '<') {
  1272. if($s[$i] == ' ' || $s[$i] == "\t") {
  1273. if($i == 0 || ($s[$i - 1] != ' ' && $s[$i - 1] != "\t" && $s[$i - 1] != "\n")) self::text(" ");
  1274. $i++; continue;
  1275. }
  1276. if($s[$i] == "\r" || ($s[$i] == "\n" && !empty(self::$out) && self::$out[strlen(self::$out) - 1] == "\n")) { $i++; continue; }
  1277. self::text($s[$i++]); continue;
  1278. }
  1279. /* our <doc> tag, defining some variables. Skip that from output */
  1280. if(substr($s, $i, 5) == "<doc>") {
  1281. $j = $i + 5; $i = strpos($s, "</doc>", $i) + 6;
  1282. self::$l += substr_count(substr($s, $j, $i - $j), "\n");
  1283. self::doc(substr($s, $j, $i - $j));
  1284. } else
  1285. /* hello page markers */
  1286. if(substr($s, $i, 7) == "<hello>") { $i += 7; self::hello_open(); } else
  1287. if(substr($s, $i, 8) == "</hello>") { $i += 8; self::hello_close(); } else
  1288. /* parse headings and collect Table of Contents info */
  1289. if(substr($s, $i, 2) == "<h" && ord($s[$i + 2]) >= 0x31 && ord($s[$i + 2]) <= 0x36) {
  1290. $j = $i + 3; $i = strpos($s, "</h", $i + 2); $L = $s[$j - 1];
  1291. $a = explode(">", substr($s, $j, $i - $j));
  1292. if(!empty($a[0])) {
  1293. $b = explode(" ", trim($a[0]));
  1294. $id = !empty($b[1]) ? $b[1] : $b[0];
  1295. $alias = !empty($b[1]) ? $b[0] : "";
  1296. } else {
  1297. $id = $a[1];
  1298. $alias = "";
  1299. }
  1300. self::heading($L, $a[1], $id, $alias);
  1301. self::$l += substr_count($a[1], "\n");
  1302. $i += 5;
  1303. } else
  1304. /* Table of Contents caption, just adds to the toc, but does not generate output */
  1305. if(substr($s, $i, 5) == "<cap>") {
  1306. $j = $i + 5; $i = strpos($s, "</cap>", $i);
  1307. self::$cap = substr($s, $j, $i - $j);
  1308. self::caption(self::$cap);
  1309. self::$l += substr_count(self::$cap, "\n");
  1310. $i += 6;
  1311. } else
  1312. /* styling text */
  1313. if(substr($s, $i, 3) == "<b>") { $i += 3; self::bold_open(); } else
  1314. if(substr($s, $i, 4) == "</b>") { $i += 4; self::bold_close(); } else
  1315. if(substr($s, $i, 3) == "<i>") { $i += 3; self::italic_open(); } else
  1316. if(substr($s, $i, 4) == "</i>") { $i += 4; self::italic_close(); } else
  1317. if(substr($s, $i, 3) == "<u>") { $i += 3; self::underline_open(); } else
  1318. if(substr($s, $i, 4) == "</u>") { $i += 4; self::underline_close(); } else
  1319. if(substr($s, $i, 3) == "<s>") { $i += 3; self::strike_open(); } else
  1320. if(substr($s, $i, 4) == "</s>") { $i += 4; self::strike_close(); } else
  1321. if(substr($s, $i, 5) == "<sup>") { $i += 5; self::superscript_open(); } else
  1322. if(substr($s, $i, 6) == "</sup>") { $i += 6; self::superscript_close(); } else
  1323. if(substr($s, $i, 5) == "<sub>") { $i += 5; self::subscript_open(); } else
  1324. if(substr($s, $i, 6) == "</sub>") { $i += 6; self::subscript_close(); } else
  1325. if(substr($s, $i, 7) == "<quote>") { $i += 7; self::quote_open(); } else
  1326. if(substr($s, $i, 8) == "</quote>") { $i += 8; self::quote_close(); } else
  1327. /* structuring text */
  1328. if(substr($s, $i, 3) == "<p>") { $i += 3; self::paragraph_open(); } else
  1329. if(substr($s, $i, 4) == "</p>") { $i += 4; self::paragraph_close(); } else
  1330. /* line breaks */
  1331. if(substr($s, $i, 4) == "<br>") { $i += 4; self::line_break(); } else
  1332. if(substr($s, $i, 4) == "<hr>") { $i += 4; self::horizontal_ruler(); } else
  1333. /* lists */
  1334. if(substr($s, $i, 4) == "<ol>") { $i += 4; self::ordered_list_open(); } else
  1335. if(substr($s, $i, 5) == "</ol>") { $i += 5; self::ordered_list_close(); } else
  1336. if(substr($s, $i, 4) == "<ul>") { $i += 4; self::unordered_list_open(); } else
  1337. if(substr($s, $i, 5) == "</ul>") { $i += 5; self::unordered_list_close(); } else
  1338. if(substr($s, $i, 4) == "<li>") { $i += 4; self::list_item_open(); } else
  1339. if(substr($s, $i, 5) == "</li>") { $i += 5; self::list_item_close(); } else
  1340. /* data fields */
  1341. if(substr($s, $i, 4) == "<dl>") { $i += 4; self::data_list_open(); } else
  1342. if(substr($s, $i, 5) == "</dl>") { $i += 5; self::data_list_close(); } else
  1343. if(substr($s, $i, 4) == "<dt>") { $i += 4; self::data_topic_open(); } else
  1344. if(substr($s, $i, 5) == "</dt>") { $i += 5; self::data_topic_close(); } else
  1345. if(substr($s, $i, 4) == "<dd>") { $i += 4; self::data_description_open(); } else
  1346. if(substr($s, $i, 5) == "</dd>") { $i += 5; self::data_description_close(); } else
  1347. /* grid, invisible table */
  1348. if(substr($s, $i, 6) == "<grid>") { $i += 6; self::grid_open(); } else
  1349. if(substr($s, $i, 7) == "</grid>") { $i += 7; self::grid_close(); } else
  1350. if(substr($s, $i, 4) == "<gr>") { $i += 4; self::grid_row_open(); } else
  1351. if(substr($s, $i, 5) == "</gr>") { $i += 5; self::grid_row_close(); } else
  1352. if(substr($s, $i, 4) == "<gd>") { $i += 4; self::grid_cell_open(); } else
  1353. if(substr($s, $i, 4) == "<gD>") { $i += 4; self::grid_cell_wide_open(); } else
  1354. if(substr($s, $i, 5) == "</gd>") { $i += 5; self::grid_cell_close(); } else
  1355. /* table, needs a surrounding div for scrollbars */
  1356. if(substr($s, $i, 7) == "<table>") { $i += 7; self::table_open(); } else
  1357. if(substr($s, $i, 8) == "</table>") { $i += 8; self::table_close(); } else
  1358. if(substr($s, $i, 4) == "<tr>") { $i += 4; self::table_row_open(); } else
  1359. if(substr($s, $i, 5) == "</tr>") { $i += 5; self::table_row_close(); } else
  1360. /* table cell header */
  1361. if(substr($s, $i, 4) == "<th>") { $i += 4; self::table_header_open(); } else
  1362. if(substr($s, $i, 4) == "<tH>") { $i += 4; self::table_header_wide_open(); } else
  1363. if(substr($s, $i, 5) == "</th>") { $i += 5; self::table_header_close(); } else
  1364. /* table cell data */
  1365. if(substr($s, $i, 4) == "<td>") { $i += 4; self::table_cell_open(); } else
  1366. if(substr($s, $i, 4) == "<tD>") { $i += 4; self::table_cell_wide_open(); } else
  1367. if(substr($s, $i, 5) == "</td>") { $i += 5; self::table_cell_close(); } else
  1368. /* table cell aligned right (number) */
  1369. if(substr($s, $i, 4) == "<tn>") { $i += 4; self::table_number_open(); } else
  1370. if(substr($s, $i, 4) == "<tN>") { $i += 4; self::table_number_wide_open(); } else
  1371. if(substr($s, $i, 5) == "</tn>") { $i += 5; self::table_cell_close(); } else
  1372. /* internal link */
  1373. if(substr($s, $i, 3) == "<a>") {
  1374. $j = $i + 3; $i = strpos($s, "</a>", $i);
  1375. self::internal_link(substr($s, $j, $i - $j));
  1376. self::$l += substr_count(substr($s, $j, $i - $j), "\n");
  1377. $i += 4;
  1378. } else
  1379. /* external link, open in a new tab */
  1380. if(substr($s, $i, 3) == "<a ") {
  1381. $i += 3; $j = $i; while($s[$i] != '>') $i++;
  1382. self::external_link_open(substr($s, $j, $i - $j));
  1383. $i++;
  1384. } else
  1385. if(substr($s, $i, 4) == "</a>") {
  1386. self::external_link_close();
  1387. $i += 4;
  1388. } else
  1389. /* add teletype tags to output without parsing */
  1390. if(substr($s, $i, 4) == "<tt>") {
  1391. $j = $i + 4; $i = strpos($s, "</tt>", $i);
  1392. self::teletype(substr($s, $j, $i - $j));
  1393. self::$l += substr_count(substr($s, $j, $i - $j), "\n");
  1394. $i += 5;
  1395. } else
  1396. /* pre is pretty much the same, but adds an optional div around for scrollbars */
  1397. if(substr($s, $i, 5) == "<pre>") {
  1398. $j = $i + 5; $i = strpos($s, "</pre>", $i);
  1399. self::preformatted(substr($s, $j, $i - $j));
  1400. self::$l += substr_count(substr($s, $j, $i - $j), "\n");
  1401. $i += 6;
  1402. } else
  1403. /* code is similar to pre, but needs line numbers and a syntax highlighter */
  1404. if(substr($s, $i, 5) == "<code") {
  1405. $i += 5; $j = $i; while($s[$j] == ' ') $j++;
  1406. while($s[$i - 1] != '>') $i++;
  1407. $c = $s[$j] != '>' ? substr($s, $j, $i - $j - 1) : "";
  1408. while($s[$i] == "\n" || $s[$i] == "\r") { if($s[$i] == "\n") self::$l++; $i++; }
  1409. $j = $i; $i = strpos($s, "</code>", $i);
  1410. self::source_code(rtrim(substr($s, $j, $i - $j)), $c);
  1411. self::$l += substr_count(substr($s, $j, $i - $j), "\n");
  1412. $i += 7;
  1413. } else
  1414. /* user interface elements */
  1415. if(substr($s, $i, 3) == "<ui" && $s[$i + 3] >= '1' && $s[$i + 3] <= '6' && $s[$i + 4] == '>') {
  1416. self::user_interface_open($s[$i + 3]);
  1417. $i += 5;
  1418. } else
  1419. if(substr($s, $i, 4) == "</ui" && $s[$i + 4] >= '1' && $s[$i + 4] <= '6' && $s[$i + 5] == '>') {
  1420. self::user_interface_close();
  1421. $i += 6;
  1422. } else
  1423. /* keyboard keys */
  1424. if(substr($s, $i, 5) == "<kbd>") {
  1425. $j = $i + 5; $i = strpos($s, "</kbd>", $i);
  1426. self::keyboard(substr($s, $j, $i - $j));
  1427. self::$l += substr_count(substr($s, $j, $i - $j), "\n");
  1428. $i += 6;
  1429. } else
  1430. /* mouse button left, right and wheel "icons" */
  1431. if(substr($s, $i, 5) == "<mbl>") { $i += 5; self::mouse_button("l"); } else
  1432. if(substr($s, $i, 5) == "<mbr>") { $i += 5; self::mouse_button("r"); } else
  1433. if(substr($s, $i, 5) == "<mbw>") { $i += 5; self::mouse_button("w"); } else
  1434. /* parse our image tags (<imgt>, <imgl>, <imgr>, <imgc>, <imgw>), but not the original HTML <img> tags */
  1435. if(substr($s, $i, 4) == "<img" && $s[$i + 4] != ' ' && $s[$i + 4] != '/' && $s[$i + 4] != '>') {
  1436. $i += 6; $j = $i; while($s[$i] != '>') $i++;
  1437. self::image($s[$j - 2], explode("\n",trim(substr($s, $j, $i - $j)))[0]);
  1438. $i++;
  1439. } else
  1440. /* image caption */
  1441. if(substr($s, $i, 5) == "<fig>") {
  1442. $j = $i + 5; $i = strpos($s, "</fig>", $i);
  1443. self::figure(substr($s, $j, $i - $j));
  1444. self::$l += substr_count(substr($s, $j, $i - $j), "\n");
  1445. $i += 6;
  1446. } else
  1447. /* alert boxes */
  1448. if(in_array(substr($s, $i, 6),[ "<info>", "<hint>", "<note>", "<also>", "<todo>", "<warn>" ])) {
  1449. self::alert_box_open(substr($s, $i + 1, 4));
  1450. $i += 6;
  1451. } else
  1452. if(in_array(substr($s, $i, 7),[ "</info>", "</hint>", "</note>", "</also>", "</todo>", "</warn>" ])) {
  1453. self::alert_box_close();
  1454. $i += 7;
  1455. } else
  1456. /* include another input file */
  1457. if(substr($s, $i, 9) == "<include ") {
  1458. $i += 9; $j = $i; while($s[$i] != '>') $i++;
  1459. self::include(substr($s, $j, $i - $j));
  1460. $i++;
  1461. } else
  1462. /* generate api documentation */
  1463. if(substr($s, $i, 5) == "<api ") {
  1464. $i += 5; $j = $i; while($s[$i] != ' ') $i++;
  1465. $c = substr($s, $j, $i - $j);
  1466. $j = $i + 1; while($s[$i] != '>') $i++;
  1467. self::api($c, substr($s, $j, $i - $j));
  1468. $i++;
  1469. } else {
  1470. /* copy all the other tags to the output untouched */
  1471. $j = $i; $i++; while($i < strlen($s) && $s[$i] != '<' && $s[$i - 1] != '>') $i++;
  1472. echo("gendoc warning: ".self::$fn.":".self::$l.": not gendoc compatible tag '".explode("\n",trim(substr($s, $j)))[0]."'\r\n");
  1473. self::text(substr($s, $j, $i - $j));
  1474. self::$l += substr_count(substr($s, $j, $i - $j), "\n");
  1475. }
  1476. }
  1477. }
  1478. /**
  1479. * Output documentation. Has to be implemented by writer plugins.
  1480. * @param string name of the file to write to
  1481. */
  1482. public static function output($fn)
  1483. {
  1484. if(empty(self::$toc)) { echo("gendoc error: no table of contents detected\r\n"); self::$err++; }
  1485. if(!empty(self::$fwd)) {
  1486. foreach(self::$fwd as $v)
  1487. echo("gendoc error: ".$v."\r\n");
  1488. self::$err++;
  1489. }
  1490. if(!empty(self::$vld)) {
  1491. foreach(self::$vld as $k => $v)
  1492. if($v != 0)
  1493. self::report_error("unclosed ".$k." in section ".(empty(self::$lsec) ? "unknown" : self::$lsec));
  1494. self::$vld = [];
  1495. }
  1496. self::$fn = $fn;
  1497. self::$l = 0;
  1498. if(!empty(self::$writer)) {
  1499. self::$writer->prev_link();
  1500. self::$writer->output($fn);
  1501. } else {
  1502. self::$out = trim(self::$out);
  1503. if(!empty(self::$out)) {
  1504. self::prev_link();
  1505. self::$out .= "</div>";
  1506. }
  1507. /* load user specified theme */
  1508. $theme = "hr,table,th,td{border-color:#e1e4e5;}\n".
  1509. "th{background:#d6d6d6;}\n".
  1510. "tr:nth-child(odd){background:#f3f6f6;}\n".
  1511. "a{text-decoration:none;color:#2980B9;}\n".
  1512. ".content{background:#fcfcfc;color:#404040;font-family:Lato,Helvetica,Neue,Arial,Deja Vu,sans-serif;}\n".
  1513. ".title,.home,h1>a,h2>a,h3>a,h4>a,h5>a,h6>a{background:#2980B9;color:#fcfcfc;}\n".
  1514. ".version{color:rgba(255,255,255,0.3);}\n".
  1515. ".search{border:1px solid #2472a4;background:#fcfcfc;}\n".
  1516. ".nav{background:#343131;color:#d9d9d9;}\n".
  1517. ".nav p{color:#55a5d9;}\n".
  1518. ".nav label:hover,.nav a:hover{background:#4e4a4a;}\n".
  1519. ".nav .current{background:#fcfcfc;color:#404040;}\n".
  1520. ".nav li>ul>li{background:#e3e3e3;}\n".
  1521. ".nav li>ul>li>a{color:#404040;}\n".
  1522. ".nav li>ul>li>a:hover{background:#d6d6d6;}\n".
  1523. ".pre {border:1px solid #e1e4e5;background:#f8f8f8;}\n".
  1524. ".info{background:#e7f2fa;}\n".
  1525. ".info>p:first-child{background:#6ab0de;color:#fff;}\n".
  1526. ".hint{background:#dbfaf4;}\n".
  1527. ".hint>p:first-child{background:#1abc9c;color:#fff;}\n".
  1528. ".warn{background:#ffedcc;}\n".
  1529. ".warn>p:first-child{background:#f0b37e;color:#fff;}\n".
  1530. ".btn{background:#f3f6f6;}\n".
  1531. ".btn:hover{background:#e5ebeb;}\n".
  1532. ".hl_h{background-color:#ccffcc;}\n".
  1533. ".hl_c{color:#808080;font-style:italic;}\n".
  1534. ".hl_p{color:#1f7199;}\n".
  1535. ".hl_o{color:#404040;}\n".
  1536. ".hl_n{color:#0164eb;}\n".
  1537. ".hl_s{color:#986801;}\n".
  1538. ".hl_t{color:#60A050;}\n".
  1539. ".hl_k{color:#a626a4;}\n".
  1540. ".hl_f{color:#2a9292;}\n".
  1541. ".hl_v{color:#e95649;}\n";
  1542. if(!empty(self::$lang["theme"])) {
  1543. $theme = @file_get_contents(self::$lang["theme"]);
  1544. if(empty($theme)) { echo("gendoc error: ".self::$lang["theme"].":0: unable to read theme css\r\n"); self::$err++; }
  1545. }
  1546. /* load title image */
  1547. $titimg = ""; $title = self::$lang["title"];
  1548. if(!empty(self::$lang["titleimg"])) {
  1549. $s = self::$lang["titleimg"]; for($i = 0; $i < strlen($s) && $s[$i] != ' '; $i++);
  1550. $title = trim(trim(substr($s, $i + 1)." ".self::$lang["title"]));
  1551. $imfn = substr($s, 0, $i);
  1552. $img = @file_get_contents($imfn);
  1553. $a = @getimagesizefromstring($img);
  1554. if(empty($img) || @$a[0] < 1 || @$a[1] < 1 || empty($a['mime'])) {
  1555. self::report_error("unable to read image '".$imfn."'");
  1556. } else
  1557. $titimg = "<img alt=\"".htmlspecialchars(substr($s, $i + 1))."\" src=\"data:".$a['mime'].";base64,".base64_encode($img)."\">";
  1558. }
  1559. if(empty($title)) $title = "No Name";
  1560. /*** output html ***/
  1561. $f = fopen($fn, "wb+");
  1562. if(!$f) die("gendoc error: ".$fn.":0: unable to write file\r\n");
  1563. fwrite($f, "<!DOCTYPE html>\n<html lang=\"".self::$lang["lang"]."\">\n<head>\n <meta charset=\"utf-8\">\n".
  1564. " <meta name=\"generator\" content=\"gendoc ".self::$version.": https://gitlab.com/bztsrc/gendoc\">\n".
  1565. " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n".
  1566. " <title>".$title."</title>\n".
  1567. /* embedded stylesheet licensed under CC-BY */
  1568. " <style rel=\"logic\">*{box-sizing:border-box;font-family:inherit;}".
  1569. "body {background:rgba(0,0,0,0.05);font-weight:400;font-size:16px;}".
  1570. "hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:26px 0;padding:0;border-top:1px solid;}".
  1571. "br:after,br:before{display:table;content:\"\"}br{clear:both;}".
  1572. "h1,h2,h3,h4,h5,h6{clear:both;margin:0px 0px 20px 0px;padding-top:4px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;}".
  1573. "p{margin:0 0 24px}a{cursor:pointer;}".
  1574. "h1{font-size:175%}h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}".
  1575. "pre,samp,code,var,kbd{font-family:Monaco,Consolas,Liberation Mono,Courier,monospace;font-variant-ligatures:none;}".
  1576. "pre,code{display:block;overflow:auto;white-space:pre;font-size:14px;line-height:16px!important;}pre{padding:12px;margin:0px;}".
  1577. "code{padding:0 0 12px 0;margin:12px 12px 0px 2px;background:url(data:type/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAgCAYAAADT5RIaAAAAFklEQVQI12NgYGDgZWJgYGCgDkFtAAAWnAAsyj4TxgAAAABJRU5ErkJggg==) 0 0 repeat;}".
  1578. ".lineno{display:block;padding:0px 4px 0px 4px;margin:12px 0px 0px 0px;opacity:.4;text-align:right;float:left;white-space:pre;font-size:12px;line-height:16px!important;}".
  1579. "pre .hl_b,samp .hl_b,code .hl_b{display:block;}".
  1580. "blockquote{margin:0px;padding:12px;}blockquote>span:first-child::before{content:url(data:type/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAgCAYAAABU1PscAAABRElEQVRYw+3WTytEURjH8c/4l1JTpKxsyEbYWFkpG+UVKOWFeAts7eytbJSysLLVxIKNFAslwhSlUQabUdM0xm0e967Ot+7inPN8z+13z73nXBKJRCIRoJSxbggrmMMInlHBIWoF+KEAQ9jAaJuxe2zhJUe/Iz0ZahZ/uTmMYT1nPxxg/I/xWQzn6IcD1IIha//wkEIBKhlq+nP0wwHOsYuvDjWvOfod6c1Yd9O4ZjDQZvwAbzn6oRVofpKbqLf0P+GxAD8cAO7w0NJ3WqAfDlBCuan9gaMC/XCA+cbJ+sMRqgX6oQCTWGtqX2O/QL8tfRlqyljGUlPgW2y3+SDz8Lv6mRvEQmPbm25ZqQvs/LHtRf3wCkxgtaWvij2cZJg36ocD/Pw91nGJY5zhM+O8UT/8Ck01TswrvHcxb9RPJBKJRDF8AyNbWk4WFTIzAAAAAElFTkSuQmCC);float:left;vertical-align:top;}".
  1581. ".ui1,.ui2,.ui3,.ui4,.ui5,.ui6{display:inline-block;height:24px!important;line-height:24px!important;padding:0px 4px;margin:-2px 0px -2px;}".
  1582. "kbd{display:inline-block;font-weight:700;border:1px solid #888;height:24px!important;padding:0px 4px;margin:-2px 0px -2px;border-radius:4px;background-image:linear-gradient(#ddd 0%,#eee 10%,#bbb 10%,#ccc 30%,#fff 85%,#eee 85%,#888 100%);}".
  1583. ".mouseleft,.mouseright,.mousewheel{display:inline-block;min-width:16px;height:24px!important;padding:0px;margin:-2px 0px 0px 0px;vertical-align:middle;}".
  1584. ".mouseleft::before{content:url(data:type/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAYCAMAAADEfo0+AAAAe1BMVEUAAACoqKj9/f2zs7O1tbWRkZGlpaWsrKybm5u2traNjY2Wlpanp6empqaYmJiPj4+3t7f5+fmjo6P19fXu7u6ZmZnx8fHi4uLm5ube3t7q6uqwsLC0tLS7u7u4uLi/v7/W1tbDw8PLy8vPz8/T09Pa2trHx8eIiIhERkShhqFGAAAAAXRSTlMAQObYZgAAAI5JREFUGNNV0EcCwjAMRFEB6R2DKSGQUBLp/idEjsG23m7+cgAMIsJWwR8Z235pumDTnkUbv+lg7CIfTqvSbTquxsGFfjWXLlwsdOFs+XC1EHAWOEwCh4/A4S1weAkcFoHDU0CI8zGQx1Cpe0BVAO0j0PIfiR4cnZjLdHP7abQ9VRVZnaZ1VvjfO42o7edfH3EoHZS6XE4AAAAASUVORK5CYII=);}".
  1585. ".mouseright::before{content:url(data:type/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAYCAMAAADEfo0+AAAAe1BMVEUAAACoqKj9/f2zs7O1tbWRkZGlpaWsrKybm5u2traNjY2Wlpanp6empqaYmJiPj4+3t7f5+fmjo6P19fXu7u6ZmZnx8fHi4uLm5ube3t7q6uqwsLC0tLS7u7u4uLi/v7/W1tbDw8PLy8vPz8/T09Pa2trHx8eIiIhERkShhqFGAAAAAXRSTlMAQObYZgAAAI9JREFUGNNV0NkWgjAMRdGozFOxWgdEcYLk/7/Q0EpL9tNd5/ECzLRCIoJF20zdlsinTbRn5Eu0O8zIl/Jk+dAPR4uWUo6d5QNenBDOTghXJ4RRQMCnwOErcPgIHN4Ch0ng8BIQ4nxYyWOo9H1FVwDqsaL4j8T0nknmy0xz+2uMO1UXWZ2mdVbo8LtBNK2dP/2+KB2shyfVAAAAAElFTkSuQmCC);}".
  1586. ".mousewheel::before{content:url(data:type/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAYCAMAAADEfo0+AAAAe1BMVEUAAACzs7OoqKisrKyRkZGlpaX9/f22trabm5uNjY2mpqanp6ePj4+1tbWYmJi3t7eWlpajo6OZmZmwsLD5+fnu7u7x8fHi4uLW1tbm5ube3t7T09Pq6ur19fW4uLi7u7u0tLTa2trPz8+/v7/Dw8PLy8vHx8eIiIhERkS4354xAAAAAXRSTlMAQObYZgAAAKdJREFUGNNV0NkagiAUhdHjPAECzWWlWdL7P2Fno/nJumHzXx4iMMI5YeivVVOX592k2vkfy/1CxvjL6L6KJAd9ZF+GVxP144Eh4B170kPHEPAOmtwFEPxw5E6A4AeHKyD4wWEABD84nAHBDw43QPCDwyvA4RPgMAU4vAOO0mLcKFJqzHPDNETisSH4HpntVzbDyazaLZSdj2qqsk6Suqw2d7fO2fnmP7kAJW9a/HbiAAAAAElFTkSuQmCC);}".
  1587. "footer{width:100%;padding:0 3.236em;}footer p{opacity:0.6;}footer small{opacity:0.5;}footer a{text-decoration:none;color:inherit;}footer a:hover{text-decoration:underline;}".
  1588. "dl{margin:0 0 24px 0;padding:0px;}dt{font-weight:700;margin-bottom:12px;}dd{margin:0 0 12px 24px;}".
  1589. ".table table{margin:0px;border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid;width:100%;}".
  1590. "th{font-weight:700;padding:8px 16px;overflow:visible;vertical-align:middle;white-space:nowrap;border:1px solid;}th.wide{width:100%;}".
  1591. "td{padding:8px 16px;overflow:visible;vertical-align:middle;font-size:90%;border:1px solid;}td.right{text-align:right;}".
  1592. "table.grid{margin:0px;padding:0px;border:none!important;background:none!important;border-spacing:0;border:0px!important;empty-cells:show;width:100%;}".
  1593. "table.grid tr, table.grid td{margin:0px;padding:0px;overflow:hidden;vertical-align:top;background:none!important;border:0px!important;font-size:90%;}".
  1594. "div.frame{position:absolute;width:100%;min-height:100%;margin:0px;padding:0px;max-width:1100px;top:0px;left:0px;}".
  1595. "#_m{margin-left:300px;min-height:100%;}".
  1596. "div.title{display:block;width:300px;padding-top:.809em;padding-bottom:.809em;margin-bottom:.809em;text-align:center;font-weight:700;}".
  1597. "div.title>a{padding:4px 6px;margin-bottom:.809em;font-size:150%;}div.title>a:hover{background:transparent;}".
  1598. "div.title>a>img{max-width:280px;border:0px;padding:0px;margin:0px;}".
  1599. "div.title input{display:none;width:270px;border-radius:50px;padding:6px 12px;font-size:80%;box-shadow:inset 0 1px 3px #ddd;transition:border .3s linear;}".
  1600. "div.title input:required:invalid{background:#fcfcfc url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAASFBMVEUAAAC6urq7u7y7vLy8vL3Dw8TLzM67urq8vL29vr69vr/AwMK5ubm5ubm4uLi5t7fAwMC/vr/FxMXBwsPExcW+u7vDw8S4uLiryZHFAAAAF3RSTlMA9vGttk4lfbWnpJQG0MpzYEIglFRCGzMa+EsAAAB0SURBVBjTbY5bCsMwDAQl2U6cR5s0fcz9b1oLpWBC52dhENqVRsmG5SIn60wwryEm0LQkbUacAveh5XGDh4uKfsQZlOqpJAkS5gHPUyzgYdeLjB6/H7lvGbzlssP2WDoRGGzxrVRD82sHRukZ/xhv7tns/QXrhgcaFdOKBwAAAABJRU5ErkJggg==) no-repeat 10px 50%;}".
  1601. "div.title input:focus{background:#fcfcfc!important;}".
  1602. "div.version{margin-top:.4045em;margin-bottom:.809em;font-size:90%;}".
  1603. "nav.side {display:block;position:fixed;top:0;bottom:0;left:0;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;font-weight:400;z-index:999;}".
  1604. "nav.mobile {display:none;font-weight:bold;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1;}".
  1605. "nav a{color:inherit;text-decoration:none;display:block;}".
  1606. "nav.side>div{position:relative;overflow-x:hidden;overflow-y:scroll;width:320px;height:100%;padding-bottom:64px;}".
  1607. "div.nav p{height:32px;line-height:32px;padding:0 1.618em;margin:12px 0px 0px 0px;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap;-webkit-font-smoothing:antialiased}".
  1608. "div.nav li>.current,div.nav li>ul{display:none;}".
  1609. "div.nav li>a,div.nav li>label{display:block;}".
  1610. "div.nav a,div.nav ul>li>label,div.nav ul>li>.current{width:300px;line-height:18px;padding:0.4045em 1.618em;}".
  1611. "div.nav a,div.nav ul>li>label{cursor:pointer;}".
  1612. "div.nav .current{font-weight:700;border-top:1px solid;border-bottom:1px solid #c9c9c9;}".
  1613. "div.nav ul>li>ul>li>a{border-right:solid 1px #c9c9c9;font-size:90%;}".
  1614. "div.nav ul>li>ul>li.h2>a{padding:0.4045em 2.427em;}".
  1615. "div.nav ul>li>ul>li.h3>a{padding:.4045em 1.618em .4045em 4.045em;}".
  1616. "div.nav ul>li>ul>li.h4>a{padding:.4045em 1.618em .4045em 5.663em;}".
  1617. "div.nav ul>li>ul>li.h5>a{padding:.4045em 1.618em .4045em 7.281em;}".
  1618. "div.nav ul>li>ul>li.h6>a{padding:.4045em 1.618em .4045em 8.899em;}".
  1619. "div.nav ul,div.nav li,.breadcrumbs{margin:0px!important;padding:0px;list-style:none;}".
  1620. "ul.breadcrumbs,.breadcrumbs li{display:inline-block;}".
  1621. ".menu{display:inline-block;position:absolute;top:12px;right:20px;cursor:pointer;width:1.5em;height:1.5em;vertical-align:middle;padding:16px 24px 16px 24px;border:solid 1px rgba(255, 255, 255, 0.5);border-radius:5px;background:no-repeat center center url(\"data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E\");}".
  1622. ".home{display:inline-block;max-width:16px;max-height:16px;line-height:16px;margin:0 5px 0 0;cursor:pointer;}".
  1623. ".home::before{content:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAS1BMVEUAAAD8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/PyxWsjVAAAAGHRSTlMAx6oLLRnYiXFEvPbey518NQbsj11QKhQ+J0ktAAAAbElEQVQY053ISQ7DMAwEwRYpavHurPz/SwMoMpD46L4MpvBTB0zTP+R9z55Ebh0yQL5DaDA+0WVR3h3Gikp9iPKF9MIkQhSDwUlGHCLQxhLGJkpLZcNYS/tdyorP/DQ7HgBqKRUgHBDcw2X4AFsJCSXB/5UVAAAAAElFTkSuQmCC);}".
  1624. "h1>a,h2>a,h3>a,h4>a,h5>a,h6>a{display:none;max-width:16px;max-height:24px;margin:-8px 0 0 5px;vertical-align:middle;}".
  1625. "h1:hover>a,h2:hover>a,h3:hover>a,h4:hover>a,h5:hover>a,h6:hover>a{display:inline-block;text-decoration:none!important;}".
  1626. "h1>a::before,h2>a::before,h3>a::before,h4>a::before,h5>a::before,h6>a::before{content:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAiBAMAAACkb0T0AAAAMFBMVEUAAAD8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/PzS+N+gAAAAD3RSTlMABab5cVY73o4k2cJTPb1Q83MyAAAAh0lEQVQY02P4DwWkM7YxsNiDGJ8EGBgmgxiKDDkCLAwgAQn7hQxARiGj8v8/IIYDi2a9GZChHiAnMLmBjeGTaIBUAMcFaYaNvA2sC1kD9RkSOCwZF7IW/WdgSP4+yYHlP5Bx/r+RgDiIIfW5kVEfyHBgPMYgBLLdkoEBKABkfHZgyIa7h04MAOVty/RC/PhoAAAAAElFTkSuQmCC);}".
  1627. "h1>a:hover::after,h2>a:hover::after,h3>a:hover::after,h4>a:hover::after,h5>a:hover::after,h6>a:hover::after{".
  1628. "content:\"".self::$lang["link"]."\";display:block;padding:12px;position:absolute;margin:-8px 8px;font-weight:400;font-size:14px;background:rgba(0,0,0,.8);color:#fff;border-radius:4px;}".
  1629. "input[type=radio]{display:none;}".
  1630. "input[type=radio]:checked ~ ul{display:block;}".
  1631. ".fig{margin-top:-12px;padding-bottom:12px;display:block;text-align:center;font-style:italic;}".
  1632. "div.page{width:100%;padding:1.618em 3.236em;margin:auto;line-height:24px;}".
  1633. "div.page ol{margin:0 0 24px 12px;padding-left:0px;}div.page ul{margin:0 0 24px 24px;list-style:disc outside;padding-left:0px;}".
  1634. "div.page ol{list-style-type:none;counter-reset:list;}div.page ol li:before{counter-increment:list;content:counters(list,\".\") \". \";}".
  1635. "div.pre{overflow-x:auto;margin:1px 0px 24px;}div.table{overflow-x:auto;margin:0px 0px 24px;}".
  1636. "div.info,div.hint,div.warn{padding:12px;line-height:24px;margin-bottom:24px;}".
  1637. "div.info>p,div.hint>p,div.warn>p{margin:0px;}".
  1638. "div.info>p:first-child,div.hint>p:first-child,div.warn>p:first-child{display:block;font-weight:700;padding:2px 8px 2px;margin:-12px -12px 8px -12px;vertical-align:middle;}".
  1639. "div.info>p:first-child>span,div.hint>p:first-child>span,div.warn>p:first-child>span{display:block;max-height:20px;margin:0px;vertical-align:middle;}".
  1640. "div.info>p:first-child>span::before,div.hint>p:first-child>span::before,div.warn>p:first-child>span::before{content:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAApUlEQVQ4y82TwQnDMAxFpVJyLPiSbbJETr13Fo+RZZxLRghkgnSEEni9ONS0sg1tDv3wwfjbX7Iki/wVAAd4IPBCiHuudrkHVvJYgb50eaOO7cMkpv0eeQGukYuRiUsNvBFpSvTJ0P2un0Sk+6Le3WEG58yBFrjt61r7Qqbij0gLIX3CaPgOqtqoaiMig6GPtTbOwCVyLraxMEj3yPIgHTLKv3ymJySzt16bW/sWAAAAAElFTkSuQmCC);}".
  1641. "p>div:last-child,dd>*:last-child,td>*:last-child,li>ol,li>ul{margin-bottom:0px!important;}".
  1642. "img{border:0px;}img.imgt{display:inline-block;max-height:22px!important;padding:0px;margin:-4px 0px 0px 0px;vertical-align:middle;}img.imgl{float:left;margin:0px 12px 12px 0px;}img.imgr{float:right;margin:0px 0px 12px 12px;}div.imgc{text-align:center;padding:0px;margin:0 12px 0 0;clear:both;}img.imgc{max-width:100%%;}img.imgw{width:100%;margin-bottom:12px;clear:both;}".
  1643. ".btn{border-radius:2px;line-height:normal;white-space:nowrap;color:inherit;text-align:center;cursor:pointer;font-size:100%;padding:4px 12px 8px;border:1px solid rgba(0,0,0,.1);text-decoration:none;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);vertical-align:middle;*zoom:1;user-select:none;transition:all .1s linear}".
  1644. ".prev{float:left;}.prev::before{content:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAANlBMVEUAAABAQEBAQEBAQEBAQEBAQEBAQEBBQUFAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAWW5SEAAAAEnRSTlMA/fC9r2kXAjMN34F3ZlUu6B40Y5wGAAAAVElEQVQY08XPSw6AIAwEUIbS8lFE739Zq6luGtbM8iXTTMOCZGECiCX/MhIQTyCNz+SRrUc1MWKVvR5KYCN6pUFDRtqq4Sql9IYJ+aI/70f4qdOHblOhAuUcC5KnAAAAAElFTkSuQmCC);}".
  1645. ".next{float:right;}.next::after{content:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASBAMAAACk4JNkAAAAJFBMVEUAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEC4lvDfAAAAC3RSTlMAx711ZjlFPh3zLASjkrYAAABDSURBVAjXY6AUsGhvcgAzOKR3797YAGIx7t5mvVsAxPLevZ159xYQS3v3zgLrTSDW7tTQBcy7ESyELEIHwhSEyQjbAAH1HsMY8tCHAAAAAElFTkSuQmCC);}".
  1646. "@media screen and (max-width:991.98px){nav.mobile{display:block;}nav.side{display:none;}#menuchk:checked ~ nav.side{display:block;}#_m{margin-left:0px;}}");
  1647. foreach(self::$toc as $k=>$v) if($v[0] == "1") fwrite($f,"#_".$k.":checked ~ nav div ul li[rel=".$k."]>.toc,");
  1648. fwrite($f, "div.page{display:none;}");
  1649. foreach(self::$toc as $k=>$v) if($v[0] == "1") fwrite($f,"#_".$k.":checked ~ nav div ul li[rel=".$k."]>ul,#_".$k.":checked ~ nav div ul li[rel=".$k."]>.current,#_".$k.":checked ~ div div[rel=".$k."],");
  1650. fwrite($f, "#_:checked ~ div div[rel=_]{display:block;}</style>\n <style rel=\"theme\">".trim(strtr($theme, [ "\r" => "", "\n" => "" ]))."</style>\n</head>\n<body>\n <div class=\"frame content\">\n ");
  1651. if(self::$H)fwrite($f, "<input type=\"radio\" name=\"page\" id=\"_\" checked>");
  1652. foreach(self::$toc as $k=>$v)
  1653. if($v[0] == "1") {
  1654. fwrite($f, "<input type=\"radio\" name=\"page\" id=\"_".$k."\"".(!self::$H ? " checked":"").">");
  1655. self::$H = 1;
  1656. }
  1657. fwrite($f, (self::$H ? "\n" : ""). " <input type=\"checkbox\" id=\"menuchk\" style=\"display:none;\"><nav class=\"side nav\"><div>\n <div class=\"title\"><a href=\"".self::$lang["url"]."\">".(!empty($titimg) ? $titimg.self::$lang["title"] : $title)."</a><div class=\"version\">".self::$lang["version"]."</div>".
  1658. "<input id=\"_q\" class=\"search\" type=\"text\" required=\"required\" onkeyup=\"s(this.value);\"></div>".
  1659. " <div id=\"_s\" class=\"nav\"></div>\n <div id=\"_t\" class=\"nav\">\n");
  1660. $H = 0;
  1661. foreach(self::$toc as $k=>$v) {
  1662. if($v[0] == "0") {
  1663. if($H > 1) fwrite($f, " </ul></li>\n");
  1664. if($H) fwrite($f, " </ul>\n");
  1665. fwrite($f, " <p>".$v[1]."</p>\n");
  1666. } else
  1667. if($v[0] == "1") {
  1668. if(!$H) fwrite($f, " <ul>\n");
  1669. else fwrite($f, " </ul></li>\n");
  1670. fwrite($f, " <li rel=\"".$k."\"><label class=\"toc\" for=\"_".$k."\">".$v[1]."</label><div class=\"current\">".$v[1]."</div><ul>\n");
  1671. } else
  1672. fwrite($f, " <li class=\"h".$v[0]."\"><a href=\"#".$k."\" onclick=\"m()\">".$v[1]."</a></li>\n");
  1673. $H = intval($v[0]);
  1674. }
  1675. if($H > 1) fwrite($f, " </ul></li>\n");
  1676. if($H) fwrite($f, " </ul>\n");
  1677. fwrite($f, " </div>\n".
  1678. " </div></nav>\n".
  1679. " <div id=\"_m\">\n".
  1680. " <nav class=\"mobile title\">".$title."<label for=\"menuchk\" class=\"menu\"></label></nav>\n".self::$out.
  1681. "\n <footer><hr><p>© Copyright ".self::$lang["copy"]."<br><small>Generated by <a href=\"https://gitlab.com/bztsrc/gendoc\">gendoc</a> v".self::$version."</small></p></footer>\n".
  1682. " </div>\n".
  1683. " </div>\n".
  1684. /* embedded JavaScript, optional, minimal, vanilla (jQuery-less) code. Licensed under CC-BY */
  1685. "<script>".
  1686. "function m(){document.getElementById(\"menuchk\").checked=false;}".
  1687. /* onclick handler that changes the url *and* switches pages too */
  1688. "function c(s){".
  1689. "var r=document.getElementById(s);".
  1690. "if(r!=undefined){".
  1691. "if(r.tagName==\"INPUT\")r.checked=true;".
  1692. "else document.getElementById(\"_\"+r.parentNode.getAttribute(\"rel\")).checked=true;".
  1693. "}m();}".
  1694. /* search function */
  1695. "function s(s){".
  1696. "var r=document.getElementById(\"_s\"),p=document.getElementById(\"_m\").getElementsByClassName(\"page\"),n,i,j,a,b,c,d;".
  1697. "if(s){".
  1698. "s=s.toLowerCase();document.getElementById(\"_t\").style.display=\"none\";r.style.display=\"block\";".
  1699. "while(r.firstChild)r.removeChild(r.firstChild);n=document.createElement(\"p\");n.appendChild(document.createTextNode(\"".self::$lang["rslt"]."\"));r.appendChild(n);".
  1700. "for(i=1;i<p.length;i++){".
  1701. "a=p[i].getAttribute(\"rel\");b=\"\";c=p[i].childNodes;d=p[i].getElementsByTagName(\"H1\")[0].innerText;".
  1702. "for(j=1;j<c.length && c[j].className!=\"btn prev\";j++){".
  1703. "if(c[j].id!=undefined&&c[j].id!=\"\"){".
  1704. "a=c[j].id;d=c[j].innerText;".
  1705. "}else if(a!=b&&c[j].innerText!=undefined&&c[j].innerText.toLowerCase().indexOf(s)!=-1){".
  1706. "b=a;n=document.createElement(\"a\");n.appendChild(document.createTextNode(d));n.setAttribute(\"href\",\"#\"+a);n.setAttribute(\"onclick\",\"c('\"+a+\"');\");r.appendChild(n);".
  1707. "}}}".
  1708. "}else{".
  1709. "document.getElementById(\"_t\").style.display=\"block\";r.style.display=\"none\";}}".
  1710. "document.addEventListener(\"DOMContentLoaded\",function(e){var i,r,n;document.getElementById(\"_q\").style.display=\"inline-block\";".
  1711. /* the query URL hack */
  1712. "if(document.location.href.indexOf(\"?\")!=-1)document.location.href=document.location.href.replace(\"?\",\"#\");else{".
  1713. /* replace labels with "a" tags that change the url too */
  1714. "r=document.querySelectorAll(\"LABEL:not(.menu)\");".
  1715. "while(r.length){".
  1716. "l=r[0].getAttribute(\"for\").substr(1);".
  1717. "n=document.createElement(\"a\");n.appendChild(document.createTextNode(r[0].innerText));".
  1718. "n.setAttribute(\"href\",\"#\"+l);n.setAttribute(\"onclick\",\"c('\"+(l!=\"\"?l:\"_\")+\"');\");".
  1719. "if(r[0].getAttribute(\"class\")!=undefined)n.setAttribute(\"class\",r[0].getAttribute(\"class\"));".
  1720. "if(r[0].getAttribute(\"title\")!=undefined&&l!=\"\")n.setAttribute(\"title\",r[0].getAttribute(\"title\"));".
  1721. "if(r[0].getAttribute(\"accesskey\")!=undefined)n.setAttribute(\"accesskey\",r[0].getAttribute(\"accesskey\"));".
  1722. "r[0].parentNode.replaceChild(n,r[0]);".
  1723. "r=document.querySelectorAll(\"LABEL:not(.menu)\");".
  1724. /* switch to a page which is detected from the url */
  1725. "}try{c(document.location.href.split(\"#\")[1]);}catch(e){}}});".
  1726. "</script>\n</body>\n</html>\n");
  1727. fclose($f);
  1728. }
  1729. }
  1730. }
  1731. /*** collect info ***/
  1732. $files = $_SERVER['argv'];
  1733. array_shift($files);
  1734. $outfn = $files[0];
  1735. $l = strrpos($outfn, '.');
  1736. if($l !== false) {
  1737. $plugin = dirname(__FILE__)."/plugins/fmt_".substr($outfn, $l + 1).".php";
  1738. if(file_exists($plugin)) {
  1739. gendoc::$writer = require_once $plugin;
  1740. if(empty(gendoc::$writer) || !method_exists(gendoc::$writer, "output")) {
  1741. gendoc::report_error("the gendoc_".substr($outfn, $l + 1)." plugin does not support writing files.");
  1742. die;
  1743. }
  1744. }
  1745. }
  1746. array_shift($files);
  1747. /* load all highlight rules */
  1748. $plugins = @glob(dirname(__FILE__)."/plugins/hl_*.json");
  1749. foreach($plugins as $fn) {
  1750. $v = basename($fn);
  1751. $type = substr($v, 3, strlen($v) - 8);
  1752. $v = @json_decode(str_replace("\\\"", "\"", str_replace("\\", "\\\\",preg_replace("/\/\*.*?\*\//ims","",@file_get_contents($fn)))),false);
  1753. if(is_array($v) && !empty($v)) gendoc::$rules[$type] = $v;
  1754. }
  1755. /* iterate on input files */
  1756. foreach($files as $fn)
  1757. gendoc::include($fn);
  1758. gendoc::output($outfn);
  1759. if(gendoc::$err)
  1760. echo("gendoc: there were errors during generation, your documentation is although saved, but probably is incomplete!\r\n");