02_FTUISRV.pm 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219
  1. ################################################################
  2. #
  3. #
  4. # 02_FTUISRV.pm
  5. #
  6. # written by Johannes Viegener
  7. # based on 02_HTTPSRV written by Dr. Boris Neubert 2012-08-27
  8. #
  9. # This file is part of Fhem.
  10. #
  11. # Fhem is free software: you can redistribute it and/or modify
  12. # it under the terms of the GNU General Public License as published by
  13. # the Free Software Foundation, either version 2 of the License, or
  14. # (at your option) any later version.
  15. #
  16. # Fhem is distributed in the hope that it will be useful,
  17. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. # GNU General Public License for more details.
  20. #
  21. # You should have received a copy of the GNU General Public License
  22. # along with Fhem. If not, see <http://www.gnu.org/licenses/>.
  23. #
  24. ##############################################################################
  25. ################################################################
  26. #
  27. # FTUISRV https://github.com/viegener/Telegram-fhem/ftuisrv
  28. #
  29. # This module provides a mini HTTP server plugin for FHEMWEB for the specific use with FTUI or new FHEM tablet UI
  30. #
  31. # It serves files from a given directory and parses them according to specific rules.
  32. # The goal is to be able to create reusable elements of multiple widgets and
  33. # surrounding tags on multiple pages and even with different devices or other
  34. # modifications. Therefore changes to the design have to be done only at one place
  35. # and not at every occurence of the template (called parts in this doc).
  36. #
  37. # Discussed in FHEM Forum: https://forum.fhem.de/index.php/topic,43110.0.html
  38. #
  39. # $Id: 02_FTUISRV.pm 13365 2017-02-08 21:13:27Z viegener $
  40. #
  41. ##############################################################################
  42. # 0.0 Initial version FTUIHTTPSRV
  43. # enable include und key value replacement
  44. # also recursive operation
  45. # show missing key definitions
  46. # 0.1 - First working version FTUISRV
  47. #
  48. # check and warn for remaining keys
  49. # added header for includes also for defining default values
  50. # changed key replacement to run through all content instead of list of keys
  51. # removed all callback elements
  52. # allow device content readings (and perl commands) in header
  53. # add validateFiles / validateResult as attributes for HTML validation
  54. # validate for HTML and part files
  55. # validate a specific file only once (if unchanged)
  56. # validate* 1 means only errors/warnings / 2 means also opening and closing being logged
  57. # documentation for validate* added
  58. # 0.2 - Extended by validation of html, device data and default values (header)
  59. #
  60. # add documentation for device readings (set logic)
  61. # allow reading values also in inc tag
  62. # 0.3 - 2016-04-25 - Version for publication in SVN
  63. #
  64. # Allow replacements setp by step in headerline --> ?> must be escaped to ?\>
  65. # added if else endif for segments ftui-if=( <expr> )
  66. # simplified keyvalue parsing
  67. # simplified include in separate sub
  68. # add loopinc for looping include multiple times loopinc="<path>" <key>=( <expr> ) <keyvalues>
  69. # summary for commandref
  70. # added if and loopinc to commandref
  71. # add new attribute for defining special template urls templateFiles
  72. # allow spaces around = and after <? for more tolerance
  73. # do not require space at end of tag before ?> for more tolerance
  74. # more tolerance on spaces around =
  75. #
  76. #
  77. ################################################################
  78. #TODO:
  79. #
  80. #
  81. # deepcopy only if new keys found
  82. #
  83. ##############################################
  84. #
  85. # ATTENTION: filenames need to have .ftui. before extension to be parsed
  86. #
  87. #
  88. ################################################################
  89. package main;
  90. use strict;
  91. use warnings;
  92. use vars qw(%data);
  93. use File::Basename;
  94. #use HttpUtils;
  95. my $FTUISRV_matchlink = "^\/?(([^\/]*(\/[^\/]+)*)\/?)\$";
  96. my $FTUISRV_matchtemplatefile = "^.*\.ftui\.[^\.]+\$";
  97. my $FTUISRV_ftuimatch_header = '<\?\s*ftui-header\s*=\s*"([^"\?]*)"\s+(.*?)\?>';
  98. my $FTUISRV_ftuimatch_keysegment = '^\s*([^=\s]+)(\s*=\s*"([^"]*)")?\s*';
  99. my $FTUISRV_ftuimatch_keygeneric = '<\?\s*ftui-key\s*=\s*([^\s\?]+)\s*\?>';
  100. my $FTUISRV_ftuimatch_if_het = '^(.*?)<\?\s*ftui-if\s*=\s*\((.*?)\)\s*\?>(.*)$';
  101. my $FTUISRV_ftuimatch_else_ht = '^(.*?)<\?\s*ftui-else\s*\?>(.*)$';
  102. my $FTUISRV_ftuimatch_endif_ht = '^(.*?)<\?\s*ftui-endif\s*\?>(.*)$';
  103. my $FTUISRV_ftuimatch_inc_hfvt = '^(.*?)<\?\s*ftui-inc\s*=\s*"([^"\?]+)"\s+([^\?]*)\?>(.*?)$';
  104. my $FTUISRV_ftuimatch_loopinc_hfkevt = '^(.*?)<\?\s*ftui-loopinc\s*=\s*"([^"\?]+)"\s+([^=\s]+)\s*=\s*\((.+?)\)\s+([^\?]*)\?>(.*?)$';
  105. #########################
  106. # FORWARD DECLARATIONS
  107. sub FTUISRV_handletemplatefile( $$$$ );
  108. sub FTUISRV_validateHtml( $$$$ );
  109. sub FTUISRV_handleIf( $$$ );
  110. #########################
  111. sub
  112. FTUISRV_addExtension($$$$) {
  113. my ($name,$func,$link,$friendlyname)= @_;
  114. # do some cleanup on link/url
  115. # link should really show the link as expected to be called (might include trailing / but no leading /)
  116. # url should only contain the directory piece with a leading / but no trailing /
  117. # $1 is complete link without potentially leading /
  118. # $2 is complete link without potentially leading / and trailing /
  119. $link =~ /$FTUISRV_matchlink/;
  120. my $url = "/".$2;
  121. my $modlink = $1;
  122. Log3 $name, 3, "Registering FTUISRV $name for URL $url and assigned link $modlink ...";
  123. $data{FWEXT}{$url}{deviceName}= $name;
  124. $data{FWEXT}{$url}{FUNC} = $func;
  125. $data{FWEXT}{$url}{LINK} = $modlink;
  126. $data{FWEXT}{$url}{NAME} = $friendlyname;
  127. }
  128. sub
  129. FTUISRV_removeExtension($) {
  130. my ($link)= @_;
  131. # do some cleanup on link/url
  132. # link should really show the link as expected to be called (might include trailing / but no leading /)
  133. # url should only contain the directory piece with a leading / but no trailing /
  134. # $1 is complete link without potentially leading /
  135. # $2 is complete link without potentially leading / and trailing /
  136. $link =~ /$FTUISRV_matchlink/;
  137. my $url = "/".$2;
  138. my $name= $data{FWEXT}{$url}{deviceName};
  139. Log3 $name, 3, "Unregistering FTUISRV $name for URL $url...";
  140. delete $data{FWEXT}{$url};
  141. }
  142. ##################
  143. sub
  144. FTUISRV_Initialize($) {
  145. my ($hash) = @_;
  146. $hash->{DefFn} = "FTUISRV_Define";
  147. $hash->{UndefFn} = "FTUISRV_Undef";
  148. $hash->{AttrList} = "directoryindex templateFiles ".
  149. "readings validateFiles:0,1,2 validateResult:0,1,2 ";
  150. $hash->{AttrFn} = "FTUISRV_Attr";
  151. #$hash->{SetFn} = "FTUISRV_Set";
  152. return undef;
  153. }
  154. ##################
  155. sub
  156. FTUISRV_Define($$) {
  157. my ($hash, $def) = @_;
  158. my @a = split("[ \t]+", $def, 6);
  159. return "Usage: define <name> FTUISRV <infix> <directory> <friendlyname>" if(( int(@a) < 5) );
  160. my $name= $a[0];
  161. my $infix= $a[2];
  162. my $directory= $a[3];
  163. my $friendlyname;
  164. $friendlyname = $a[4].(( int(@a) == 6 )?" ".$a[5]:"");
  165. $hash->{fhem}{infix}= $infix;
  166. $hash->{fhem}{directory}= $directory;
  167. $hash->{fhem}{friendlyname}= $friendlyname;
  168. Log3 $name, 3, "$name: new ext defined infix:$infix: dir:$directory:";
  169. FTUISRV_addExtension($name, "FTUISRV_CGI", $infix, $friendlyname);
  170. $hash->{STATE} = $name;
  171. return undef;
  172. }
  173. ##################
  174. sub
  175. FTUISRV_Undef($$) {
  176. my ($hash, $name) = @_;
  177. FTUISRV_removeExtension($hash->{fhem}{infix});
  178. return undef;
  179. }
  180. ##################
  181. sub
  182. FTUISRV_Attr(@)
  183. {
  184. my ($cmd,$name,$aName,$aVal) = @_;
  185. if ($cmd eq "set") {
  186. if ($aName =~ "readings") {
  187. if ($aVal !~ /^[A-Z_a-z0-9\,]+$/) {
  188. Log3 $name, 2, "$name: Invalid reading list in attr $name $aName $aVal (only A-Z, a-z, 0-9, _ and , allowed)";
  189. return "Invalid reading name $aVal (only A-Z, a-z, 0-9, _ and , allowed)";
  190. }
  191. addToDevAttrList($name, $aName);
  192. } elsif ($aName =~ "validateFiles") {
  193. $attr{$name}{'validateFiles'} = (($aVal eq "2")? "2": (($aVal eq "1")? "1": "0"));
  194. } elsif ($aName =~ "validateResult") {
  195. $attr{$name}{'validateResult'} = (($aVal eq "2")? "2": (($aVal eq "1")? "1": "0"));
  196. }
  197. }
  198. return undef;
  199. }
  200. ##################
  201. #
  202. # here we answer any request to http://host:port/fhem/$infix and below
  203. sub FTUISRV_CGI() {
  204. my ($request) = @_; # /$infix/filename
  205. # Debug "request= $request";
  206. Log3 undef, 4, "FTUISRV: Request to FTUISRV :$request:";
  207. # Match request first without trailing / in the link part
  208. if($request =~ m,^(/[^/]+)(/([^\?]*)?)?(\?([^#]*))?$,) {
  209. my $link= $1;
  210. my $filename= $3;
  211. my $qparams= $5;
  212. my $name;
  213. # If FWEXT not found for this make a second try with a trailing slash in the link part
  214. if(! $data{FWEXT}{$link}) {
  215. $link = $link."/";
  216. return("text/plain; charset=utf-8", "Illegal request: $request") if(! $data{FWEXT}{$link});
  217. }
  218. # get device name
  219. $name= $data{FWEXT}{$link}{deviceName};
  220. # check system / device loglevel
  221. my $logLevel = $attr{global}{verbose};
  222. $logLevel = $attr{$name}{verbose} if ( defined( $attr{$name}{verbose} ) );
  223. # Log3 undef, 3, "FTUISRV: Request to FTUISRV :$request:";
  224. if ( 4 <= $logLevel ) {
  225. if ( ( $request =~ /index.html/ ) || ( $request =~ /\/$/ ) ) {
  226. Log3 $name, 4, "FTUISRV: request to FTUISRV :$request: Header :".join("\n",@FW_httpheader).":";
  227. }
  228. }
  229. # Debug "link= ".((defined($link))?$link:"<undef>");
  230. # Debug "filename= ".((defined($filename))?$filename:"<undef>");
  231. # Debug "qparams= ".((defined($qparams))?$qparams:"<undef>");
  232. # Debug "name= $name";
  233. if ( ! $name ) {
  234. Log3 undef, 1, "FTUISRV: Request to FTUISRV but no link found !! :$request:";
  235. }
  236. # return error if no such device
  237. return("text/plain; charset=utf-8", "No FTUISRV device for $link") unless($name);
  238. my $fullName = $filename;
  239. foreach my $reading (split (/,/, AttrVal($name, "readings", ""))) {
  240. my $value = "";
  241. if ($fullName =~ /^([^\?]+)\?(.*)($reading)=([^;&]*)([&;].*)?$/) {
  242. $filename = $1;
  243. $value = $4;
  244. Log3 $name, 5, "$name: set Reading $reading = $value";
  245. readingsSingleUpdate($defs{$name}, $reading, $value, 1);
  246. }
  247. };
  248. Log3 $name, 5, "$name: Request to :$request:";
  249. $filename= AttrVal($name,"directoryindex","index.html") unless($filename);
  250. my $MIMEtype= filename2MIMEType($filename);
  251. my $directory= $defs{$name}{fhem}{directory};
  252. $filename= "$directory/$filename";
  253. #Debug "read filename= $filename";
  254. return("text/plain; charset=utf-8", "File not found: $filename") if(! -e $filename );
  255. my $parhash = {};
  256. my $validatehash = {};
  257. my ($err, $validated, $content) = FTUISRV_handletemplatefile( $name, $filename, $parhash, $validatehash );
  258. # Validate HTML Result after parsing
  259. my $validate = AttrVal($name,'validateResult',0);
  260. if ( ( $validate ) && ( ( $filename =~ /\.html?$/i ) || ( $filename =~ /\.part?$/i ) ) && ( ! $validated ) ) {
  261. FTUISRV_validateHtml( $name, $content, $validate, $filename );
  262. }
  263. return("text/plain; charset=utf-8", "Error in filehandling: $err") if ( defined($err) );
  264. return("$MIMEtype; charset=utf-8", $content);
  265. } else {
  266. return("text/plain; charset=utf-8", "Illegal request: $request");
  267. }
  268. }
  269. ##############################################
  270. ##############################################
  271. ##
  272. ## validate HTML
  273. ##
  274. ##############################################
  275. ##############################################
  276. ##################
  277. #
  278. # validate HTML according to basic criteria
  279. # should be best build with HTML::Parser (cpan) --> allows also to parse processing instructions
  280. # example: 23_KOSTALPIKO.pm
  281. # comments correctly closed
  282. # build tag dictionary / array
  283. # optional: check FTUI
  284. sub FTUISRV_validateHtml( $$$$ ) {
  285. my ($name, $content, $validateLevel, $filename ) = @_;
  286. # state: 0: normal / 1: in tag / 2: in comment / 3: in quotes / 4: in dquotes / 5: in ptag
  287. #
  288. # tags contains
  289. # <li if tag has been found, but is not yet finished
  290. # li if li tag is found and completed
  291. # " in quotes
  292. # ' in double quotes
  293. # # in comment
  294. # <? in processing tag (unfinished)
  295. # handle /> as end of tag
  296. # handle no close tags ==> meta, img
  297. # handle doctype with <! ==> as in processing tag no end
  298. # pushtag / poptag add prefix FTUISRV_
  299. Log3 $name, (( $validateLevel > 1 )?1:4), "$name: validate parsed HTML for request :$filename:";
  300. $content .= " ";
  301. my $state = 0;
  302. my $line = 1;
  303. my $pos = 0;
  304. my $slen = length( $content );
  305. my @tags = ();
  306. my @tagline= ();
  307. my $ctag = "";
  308. while ( $pos < $slen ) {
  309. my $ch = substr( $content, $pos, 1 );
  310. $pos++;
  311. # Processing tag
  312. if ( $state == 5 ) {
  313. if ( $ch eq "\\" ) {
  314. $pos++;
  315. } elsif ( $ch eq "\"" ) {
  316. pushTag( \@tags, \@tagline, "<?", $line );
  317. $state = 4;
  318. } elsif ( $ch eq "\'" ) {
  319. $state = 3;
  320. pushTag( \@tags, \@tagline, "<?", $line );
  321. } elsif ( ( $ch eq "?" ) && ( substr( $content, $pos, 1 ) eq ">" ) ) {
  322. Log3( $name, 1, "<< Leave Processing Tag: #$line" ) if ( $validateLevel > 1 );
  323. $pos++;
  324. ( $state, $ctag ) = popTag( \@tags, \@tagline );
  325. }
  326. # quote tags
  327. } elsif ( $state >= 3 ) {
  328. if ( $ch eq "\\" ) {
  329. $pos++;
  330. } elsif ( ( $ch eq "\"" ) && ( $state == 4 ) ){
  331. ( $state, $ctag ) = popTag( \@tags, \@tagline );
  332. # Debug "New state $state #$line";
  333. } elsif ( $ch eq "\"" ) {
  334. pushTag( \@tags, \@tagline, "\'", $line );
  335. $state = 4;
  336. } elsif ( ( $ch eq "\'" ) && ( $state == 3 ) ){
  337. ( $state, $ctag ) = popTag( \@tags, \@tagline );
  338. } elsif ( $ch eq "\'" ) {
  339. $state = 3;
  340. pushTag( \@tags, \@tagline, "\"", $line );
  341. }
  342. # comment tag
  343. } elsif ( $state == 2 ) {
  344. if ( ( $ch eq "-" ) && ( substr( $content, $pos, 2 ) eq "->" ) ) {
  345. $pos+=2;
  346. Log3( $name, 1, "<< Leave Comment: #$line" ) if ( $validateLevel > 1 );
  347. ( $state, $ctag ) = popTag( \@tags, \@tagline );
  348. }
  349. # in tag
  350. } elsif ( $state == 1 ) {
  351. if ( $ch eq "\"" ) {
  352. pushTag( \@tags, \@tagline, $ctag, $line );
  353. # Debug "Go to state 4 #$line";
  354. $state = 4;
  355. } elsif ( $ch eq "\'" ) {
  356. pushTag( \@tags, \@tagline, $ctag, $line );
  357. $state = 3;
  358. } elsif ( ( $ch eq "<" ) && ( substr( $content, $pos, 1 ) eq "?" ) ) {
  359. pushTag( \@tags, \@tagline, $ctag, $line );
  360. $pos++;
  361. $state = 5;
  362. } elsif ( ( $ch eq "<" ) && ( substr( $content, $pos, 3 ) eq "!--" ) ) {
  363. pushTag( \@tags, \@tagline, $ctag, $line );
  364. $pos+=2;
  365. $state = 2;
  366. } elsif ( $ch eq "<" ) {
  367. Log3( $name, 1, "FTUISRV_validate: Warning Spurious < in $filename (line $line)" );
  368. } elsif ( ( $ch eq "/" ) && ( substr( $content, $pos, 1 ) eq ">" ) ) {
  369. my $dl = $tagline[$#tagline];
  370. ( $state, $ctag ) = popTag( \@tags, \@tagline );
  371. Log3( $name, 1, "<< end tag directly :$ctag: #$line" ) if ( $validateLevel > 1 );
  372. # correct state (outside tag)
  373. $state = 0;
  374. } elsif ( $ch eq ">" ) {
  375. my $dl = $tagline[$#tagline];
  376. ( $state, $ctag ) = popTag( \@tags, \@tagline );
  377. Log3( $name, 1, "-- start tag complete :$ctag: #$line" ) if ( $validateLevel > 1 );
  378. # restore old tag start line
  379. pushTag( \@tags, \@tagline, substr($ctag,1), $dl );
  380. # correct state (outside tag)
  381. $state = 0;
  382. }
  383. # out of everything
  384. } else {
  385. if ( ( $ch eq "<" ) && ( substr( $content, $pos, 1 ) eq "?" ) ) {
  386. pushTag( \@tags, \@tagline, "", $line );
  387. $pos++;
  388. $state = 5;
  389. Log3( $name, 1, ">> Enter Processing Tag #$line" ) if ( $validateLevel > 1 );
  390. } elsif ( ( $ch eq "<" ) && ( substr( $content, $pos, 3 ) eq "!--" ) ) {
  391. pushTag( \@tags, \@tagline, "", $line );
  392. $pos+=2;
  393. $state = 2;
  394. Log3( $name, 1, ">> Enter Comment #$line" ) if ( $validateLevel > 1 );
  395. } elsif ( ( $ch eq "<" ) && ( substr( $content, $pos, 1 ) eq "/" ) ) {
  396. $pos++;
  397. my $tag = "";
  398. while ( $pos < $slen ) {
  399. my $ch2 = substr( $content, $pos, 1 );
  400. $pos++;
  401. if ( $ch2 eq ">" ) {
  402. last;
  403. } elsif (( $ch2 eq "\n" ) || ( $ch2 eq " " ) || ( $ch2 eq "\t" ) ) {
  404. $pos = $slen;
  405. } else {
  406. $tag .= $ch2;
  407. }
  408. }
  409. if ( $pos >= $slen ) {
  410. Log3( $name, 1, "FTUISRV_validate: Error incomplete tag :".(defined($tag)?$tag:"<undef>").": not finished with > in $filename (line $line)" );
  411. @tags = 0;
  412. } else {
  413. Log3( $name, 1, "<< end tag $tag: #$line" ) if ( $validateLevel > 1 );
  414. while ( scalar(@tags) > 0 ) {
  415. my $ptag = pop( @tags );
  416. my $pline = pop( @tagline );
  417. if ( $ptag eq $tag ) {
  418. Log3( $name, 1, "FTUISRV_validate: Warning void tag :".(defined($tag)?$tag:"<undef>").": unnecessarily closed $filename (opened in line $pline)" ) if ( FTUISRV_isVoidTag( $tag ) );
  419. last;
  420. } elsif ( scalar(@tags) == 0 ) {
  421. Log3( $name, 1, "FTUISRV_validate: Error tag :".(defined($tag)?$tag:"<undef>").": closed but not open $filename (line $line)" );
  422. $pos = $slen;
  423. } else {
  424. Log3( $name, 1, "FTUISRV_validate: Warning tag :".(defined($ptag)?$ptag:"<undef>").": not closed $filename (opened in line $pline)" )
  425. if ( ! FTUISRV_isVoidTag( $ptag ) );
  426. }
  427. }
  428. }
  429. } elsif ( $ch eq "<" ) {
  430. # identify tag
  431. my $tag = "<";
  432. while ( $pos < $slen ) {
  433. my $ch2 = substr( $content, $pos, 1 );
  434. $pos++;
  435. if ( $ch2 eq ">" ) {
  436. $pos--;
  437. last;
  438. } elsif (( $ch2 eq "\n" ) || ( $ch2 eq " " ) || ( $ch2 eq "\t" ) ) {
  439. $pos--;
  440. last;
  441. } else {
  442. $tag .= $ch2;
  443. }
  444. }
  445. if ( $pos >= $slen ) {
  446. Log3( $name, 1, "FTUISRV_validate: Warning start tag :".(defined($tag)?$tag:"<undef>").": not finished in $filename (line $line)" );
  447. } else {
  448. Log3( $name, 1, "<< start tag $tag: #$line" ) if ( $validateLevel > 1 );
  449. $ctag = $tag;
  450. $state = 1;
  451. pushTag( \@tags, \@tagline, $ctag, $line );
  452. }
  453. }
  454. }
  455. $line++ if ( $ch eq "\n" );
  456. # ???
  457. # $pos = $slen if ( $line > 50 );
  458. }
  459. # remaining tags report
  460. while ( scalar(@tags) > 0 ) {
  461. my $ptag = pop( @tags );
  462. my $pline = pop( @tagline );
  463. Log3( $name, 1, "FTUISRV_validate: Warning tag :".(defined($ptag)?$ptag:"<undef>").": not closed $filename (opened in line $pline)" )
  464. if ( ! FTUISRV_isVoidTag( $ptag ) );
  465. }
  466. }
  467. ##################
  468. # Check if tag does not require an explicit end
  469. sub FTUISRV_isVoidTag( $ ) {
  470. my ($tag) = @_;
  471. return ( index( " area base br col command embed hr img input link meta param source !DOCTYPE ", " ".$tag." " ) != -1 );
  472. }
  473. ##############################################
  474. sub pushTag( $$$$ ) {
  475. my ( $ptags, $ptagline, $ch, $line ) = @_ ;
  476. push( @{ $ptags }, $ch );
  477. push( @{ $ptagline }, $line );
  478. }
  479. ##############################################
  480. sub popTag( $$ ) {
  481. my ( $ptags, $ptagline ) = @_;
  482. return (0, "") if ( scalar($ptags) == 0 );
  483. my $ch = pop( @{ $ptags } );
  484. my $line = pop( @{ $ptagline } );
  485. my $state = 0;
  486. # state: 0: normal / 1: in tag / 2: in comment / 3: in quotes / 4: in dquotes / 5: in ptag
  487. if ( $ch eq "" ) {
  488. $state = 0;
  489. } elsif ( $ch eq "<!--" ) {
  490. $state = 2;
  491. } elsif ( $ch eq "<?" ) {
  492. $state = 5;
  493. } elsif ( $ch eq "\'" ) {
  494. $state = 3;
  495. } elsif ( $ch eq "\"" ) {
  496. $state = 4;
  497. } elsif ( substr($ch,0,1) eq "<" ) {
  498. $state = 1;
  499. } else {
  500. # nothing else must be tag
  501. $state = 0;
  502. }
  503. return ( $state, $ch );
  504. }
  505. ##############################################
  506. ##############################################
  507. ##
  508. ## Template handling
  509. ##
  510. ##############################################
  511. ##############################################
  512. ##################
  513. #
  514. # handle a ftui template string
  515. # name of the current ftui device
  516. # filename full fledged filename to be handled
  517. # string with content to be replaced
  518. # parhash reference to a hash with the current key-values
  519. # returns
  520. # contents
  521. sub FTUISRV_replaceKeys( $$$$ ) {
  522. my ($name, $filename, $content, $parhash) = @_;
  523. # make replacements of keys from hash
  524. while ( $content =~ /<\?ftui-key=([^\s]+)\s*\?>/g ) {
  525. my $key = $1;
  526. my $value = $parhash->{$key};
  527. if ( ! defined( $value ) ) {
  528. Log3 $name, 4, "$name: unmatched key in file :$filename: key :$key:";
  529. $value = "";
  530. }
  531. Log3 $name, 4, "$name: replace key in file :$filename: key :$key: with :$value:";
  532. $content =~ s/<\?ftui-key=$key\s*\?>/$value/sg;
  533. }
  534. # while ( $content =~ /$FTUISRV_ftuimatch_keygeneric/s ) {
  535. while ( $content =~ /<\?ftui-key=([^\s]+)\s*\?>/g ) {
  536. Log3 $name, 4, "$name: unmatched key in file :$filename: key :$1:";
  537. }
  538. return $content;
  539. }
  540. ##################
  541. #
  542. # handle a ftui template for ifs
  543. # name of the current ftui device
  544. # filename full fledged filename to be handled
  545. # string with content to be replaced
  546. # returns
  547. # contents
  548. sub FTUISRV_handleIf( $$$ ) {
  549. my ($name, $filename, $content) = @_;
  550. # Look for if expression
  551. my $done = "";
  552. return $content if ( $content !~ /$FTUISRV_ftuimatch_if_het/s );
  553. $done .= $1;
  554. my $expr = $2;
  555. my $rest = $3;
  556. # handle rest to check recursively for further ifs
  557. $rest = FTUISRV_handleIf( $name, $filename, $rest );
  558. # identify then and else parts
  559. my $then;
  560. my $else = "";
  561. if ( $rest =~ /$FTUISRV_ftuimatch_else_ht/s ) {
  562. $then = $1;
  563. $rest = $2;
  564. }
  565. if ( $rest =~ /$FTUISRV_ftuimatch_endif_ht/s ) {
  566. $else = $1;
  567. $rest = $2;
  568. if ( ! defined($then) ) {
  569. $then = $else;
  570. $else = "";
  571. }
  572. }
  573. # check expression
  574. my %dummy;
  575. my ($err, @a) = ReplaceSetMagic(\%dummy, 0, ( $expr ) );
  576. if ( $err ) {
  577. Log3 $name, 1, "$name: FTUISRV_handleIf failed on ReplaceSetmagic with :$err: on header :$expr:";
  578. } else {
  579. Log3 $name, 4, "$name: FTUISRV_handleIf expr before setmagic :".$expr.":";
  580. $expr = join(" ", @a);
  581. # need to trim result of replacesetmagic -> multiple elements some space
  582. $expr =~ s/^\s+|\s+$//g;
  583. Log3 $name, 4, "$name: FTUISRV_handleIf expr elements :".scalar(@a).":";
  584. Log3 $name, 4, "$name: FTUISRV_handleIf expr after setmagic :".$expr.":";
  585. }
  586. # put then/else depending on expr
  587. $done .= ( ( $expr ) ? $then : $else );
  588. $done .= $rest;
  589. return $done;
  590. }
  591. ##################
  592. #
  593. # handle a ftui template for incs and then this as the template
  594. # name of the current ftui device
  595. # filename full fledged filename to be handled
  596. # curdir current directory for filename
  597. # string with content to be replaced
  598. # parhash reference to a hash with the current key-values
  599. # validated is ref to hash with filenames
  600. # returns
  601. # err (might be undef)
  602. # contents
  603. sub FTUISRV_handleInc( $$$$$$ ) {
  604. my ($name, $filename, $curdir, $content, $parhash, $validatehash) = @_;
  605. # Look for if expression
  606. my $done = "";
  607. my $rest = $content;
  608. while ( $rest =~ /$FTUISRV_ftuimatch_inc_hfvt/s ) {
  609. $done .= $1;
  610. my $incfile = $2;
  611. my $values = $3;
  612. $rest = $4;
  613. Log3 $name, 4, "$name: include found :$filename: inc :$incfile: vals :$values:";
  614. return ("$name: Empty file name in include :$filename:", $content) if ( length($incfile) == 0 );
  615. # replace [device:reading] or even perl expressions with replaceSetMagic
  616. my %dummy;
  617. Log3 $name, 4, "$name: FTUISRV_handleInc ReplaceSetmagic INC before :$values:";
  618. my ($err, @a) = ReplaceSetMagic(\%dummy, 0, ( $values ) );
  619. if ( $err ) {
  620. Log3 $name, 1, "$name: FTUISRV_handleInc ($filename) failed on ReplaceSetmagic with :$err: on INC :$values:";
  621. } else {
  622. $values = join(" ", @a);
  623. Log3 $name, 4, "$name: FTUISRV_handleInc ($filename) ReplaceSetmagic INC after :".$values.":";
  624. }
  625. # deepcopy parhash here
  626. my $incparhash = deepcopy( $parhash );
  627. # parse $values + add keys to inchash
  628. while ( $values =~ s/$FTUISRV_ftuimatch_keysegment//s ) {
  629. my $skey = $1;
  630. my $sval = $3;
  631. $sval="" if ( ! defined($sval) );
  632. Log3 $name, 4, "$name: a key :$skey: = :$sval: ";
  633. $incparhash->{$skey} = $sval;
  634. }
  635. # build new filename (if not absolute already)
  636. $incfile = $curdir.$incfile if ( substr($incfile,0,1) ne "/" );
  637. Log3 $name, 4, "$name: start handling include (rec) :$incfile:";
  638. my $inccontent;
  639. my $dummy;
  640. ($err, $dummy, $inccontent) = FTUISRV_handletemplatefile( $name, $incfile, $incparhash, $validatehash );
  641. Log3 $name, 4, "$name: done handling include (rec) :$incfile: ".(defined($err)?"Err: ".$err:"ok");
  642. # error will always result in stopping recursion
  643. return ($err." (included)", $content) if ( defined($err) );
  644. $done .= $inccontent;
  645. # Log3 $name, 3, "$name: done handling include new content:----------------\n$content\n--------------------";
  646. last if ( length($rest) == 0 );
  647. }
  648. $done .= $rest;
  649. return ( undef, $done );
  650. }
  651. ##################
  652. #
  653. # handle a ftui template for loopInc and then the file as a template for all expression results
  654. # name of the current ftui device
  655. # filename full fledged filename to be handled
  656. # curdir current directory for filename
  657. # string with content to be replaced
  658. # parhash reference to a hash with the current key-values
  659. # validated is ref to hash with filenames
  660. # returns
  661. # err (might be undef)
  662. # contents
  663. sub FTUISRV_handleLoopInc( $$$$$$ ) {
  664. my ($name, $filename, $curdir, $content, $parhash, $validatehash) = @_;
  665. # Look for if expression
  666. my $done = "";
  667. my $rest = $content;
  668. while ( $rest =~ /$FTUISRV_ftuimatch_loopinc_hfkevt/s ) {
  669. $done .= $1;
  670. my $incfile = $2;
  671. my $key = $3;
  672. my $expr = $4;
  673. my $values = $5;
  674. $rest = $6;
  675. Log3 $name, 4, "$name: include loop found :$filename: key :$key: expr:$expr:\n inc :$incfile: vals :$values:";
  676. return ("$name: Empty file name in loopinc :$filename:", $content) if ( length($incfile) == 0 );
  677. # Evaluate expression as command to get list of entries for loop ???
  678. my $result = AnalyzeCommand(undef, $expr);
  679. # Identify split char ???
  680. # split at splitchar (default \n) into array ???
  681. my @aResults = split( /\n/, $result );
  682. # replace [device:reading] or even perl expressions with replaceSetMagic
  683. my %dummy;
  684. Log3 $name, 4, "$name: FTUISRV_handleLoopInc ReplaceSetmagic INC before :$values:";
  685. my ($err, @a) = ReplaceSetMagic(\%dummy, 0, ( $values ) );
  686. if ( $err ) {
  687. Log3 $name, 1, "$name: FTUISRV_handleLoopInc ($filename) failed on ReplaceSetmagic with :$err: on INC :$values:";
  688. } else {
  689. $values = join(" ", @a);
  690. Log3 $name, 4, "$name: FTUISRV_handleLoopInc ($filename) ReplaceSetmagic INC after :".$values.":";
  691. }
  692. # deepcopy parhash here
  693. my $incparhash = deepcopy( $parhash );
  694. # parse $values + add keys to inchash
  695. while ( $values =~ s/$FTUISRV_ftuimatch_keysegment//s ) {
  696. my $skey = $1;
  697. my $sval = $3;
  698. $sval="" if ( ! defined($sval) );
  699. Log3 $name, 4, "$name: a key :$skey: = :$sval: ";
  700. $incparhash->{$skey} = $sval;
  701. }
  702. # build new filename (if not absolute already)
  703. $incfile = $curdir.$incfile if ( substr($incfile,0,1) ne "/" );
  704. # Loop over list of values
  705. foreach my $loopvariable ( @aResults ) {
  706. # deepcopy parhash here
  707. my $loopincparhash = deepcopy( $incparhash );
  708. # add loopvariable with current value
  709. $loopincparhash->{$key} = $loopvariable;
  710. Log3 $name, 4, "$name: start handling include (rec) :$incfile: with value $key = :$loopvariable:";
  711. my $inccontent;
  712. my $dummy;
  713. ($err, $dummy, $inccontent) = FTUISRV_handletemplatefile( $name, $incfile, $loopincparhash, $validatehash );
  714. Log3 $name, 4, "$name: done handling include (rec) :$incfile: ".(defined($err)?"Err: ".$err:"ok");
  715. # error will always result in stopping recursion
  716. return ($err." (included)", $content) if ( defined($err) );
  717. $done .= $inccontent;
  718. # Log3 $name, 3, "$name: done handling include new content:----------------\n$content\n--------------------";
  719. }
  720. last if ( length($rest) == 0 );
  721. }
  722. $done .= $rest;
  723. return ( undef, $done );
  724. }
  725. ##################
  726. #
  727. # handle a ftui template file
  728. # name of the current ftui device
  729. # filename full fledged filename to be handled
  730. # parhash reference to a hash with the current key-values
  731. # validated is ref to hash with filenames
  732. # returns
  733. # err
  734. # validated if the file handed over has been validated in unmodified form (only if not parsed, then this will be reseit)
  735. # contents
  736. sub FTUISRV_handletemplatefile( $$$$ ) {
  737. my ($name, $filename, $parhash, $validatehash) = @_;
  738. my $content;
  739. my $err;
  740. my $validated = 0;
  741. Log3 $name, 5, "$name: handletemplatefile :$filename:";
  742. $content = FTUISRV_BinaryFileRead( $filename );
  743. return ("$name: File not existing or empty :$filename:", $validated, $content) if ( length($content) == 0 );
  744. # Validate HTML Result after parsing
  745. my $validate = AttrVal($name,'validateFiles',0);
  746. if ( ( $validate ) && ( ! defined($validatehash->{$filename} ) ) && ( ( $filename =~ /\.html?$/i ) || ( $filename =~ /\.part?$/i ) ) ) {
  747. $validated = 1;
  748. $validatehash->{$filename} = 1 if ( ! defined($validatehash->{$filename} ) );
  749. FTUISRV_validateHtml( $name, $content, $validate, $filename );
  750. }
  751. if ( ( $filename =~ /$FTUISRV_matchtemplatefile/ ) || ( index( ":".AttrVal($name,"templateFiles","").":", ":".$filename.":" ) != -1 ) ) {
  752. Log3 $name, 4, "$name: is real template :$filename:";
  753. $validated = 0;
  754. my ($dum, $curdir) = fileparse( $filename );
  755. # Get file header with keys / default values (optional)
  756. if ( $content =~ /$FTUISRV_ftuimatch_header/s ) {
  757. my $hvalues = $2;
  758. Log3 $name, 4, "$name: found header with hvalues :$hvalues: ";
  759. # replace [device:reading] or even perl expressions with replaceSetMagic
  760. my %dummy;
  761. Log3 $name, 4, "$name: FTUISRV_handletemplatefile ReplaceSetmagic HEADER before :$hvalues:";
  762. # grab keys for default values from header
  763. while ( $hvalues =~ /$FTUISRV_ftuimatch_keysegment/s ) {
  764. my $skey = $1;
  765. my $sval = $3;
  766. if ( defined($sval) ) {
  767. Log3 $name, 4, "$name: default value for key :$skey: = :$sval: ";
  768. # replace escaped > (i.e. \>) by > for key replacement
  769. $sval =~ s/\?\\>/\?>/g;
  770. $sval = FTUISRV_replaceKeys( $name, $filename." header", $sval, $parhash );
  771. Log3 $name, 4, "$name: FTUISRV_handletemplatefile default value for key :$skey: after replace :".$sval.":";
  772. my ($err, @a) = ReplaceSetMagic(\%dummy, 0, ( $sval ) );
  773. if ( $err ) {
  774. Log3 $name, 1, "$name: FTUISRV_handletemplatefile failed on ReplaceSetmagic with :$err: on header :$sval:";
  775. } else {
  776. $sval = join(" ", @a);
  777. Log3 $name, 4, "$name: FTUISRV_handletemplatefile default value for key :$skey: after setmagic :".$sval.":";
  778. }
  779. $parhash->{$skey} = $sval if ( ! defined($parhash->{$skey} ) )
  780. }
  781. $hvalues =~ s/$FTUISRV_ftuimatch_keysegment//s;
  782. }
  783. # remove header from output
  784. $content =~ s/$FTUISRV_ftuimatch_header//s
  785. }
  786. # replace the keys first before loop/if
  787. $content = FTUISRV_replaceKeys( $name, $filename, $content, $parhash );
  788. # eval if else endif expressions
  789. $content = FTUISRV_handleIf( $name, $filename, $content );
  790. # Handle includes
  791. Log3 $name, 4, "$name: look for includes :$filename:";
  792. ( $err, $content ) = FTUISRV_handleInc( $name, $filename, $curdir, $content, $parhash, $validatehash );
  793. # error will always result in stopping recursion
  794. return ($err, $validated, $content) if ( defined($err) );
  795. ( $err, $content ) = FTUISRV_handleLoopInc( $name, $filename, $curdir, $content, $parhash, $validatehash );
  796. # error will always result in stopping recursion
  797. return ($err, $validated, $content) if ( defined($err) );
  798. }
  799. return ($err,$validated,$content);
  800. }
  801. ##################
  802. # from http://www.volkerschatz.com/perl/snippets/dup.html
  803. # Duplicate a nested data structure of hash and array references.
  804. # -> List of scalars, possibly array or hash references
  805. # <- List of deep copies of arguments. References that are not hash or array
  806. # refs are copied as-is.
  807. sub deepcopy
  808. {
  809. my @result;
  810. for (@_) {
  811. my $reftype= ref $_;
  812. if( $reftype eq "ARRAY" ) {
  813. push @result, [ deepcopy(@$_) ];
  814. }
  815. elsif( $reftype eq "HASH" ) {
  816. my %h;
  817. @h{keys %$_}= deepcopy(values %$_);
  818. push @result, \%h;
  819. }
  820. else {
  821. push @result, $_;
  822. }
  823. }
  824. return @_ == 1 ? $result[0] : @result;
  825. }
  826. ##################
  827. #
  828. # Callback for FTUI handling
  829. sub FTUISRV_returnFileContent($$) {
  830. my ($name, $request) = @_; # name of extension and request (url)
  831. # split request
  832. $request =~ m,^(/[^/]+)(/([^\?]*)?)?(\?([^#]*))?$,;
  833. my $link= $1;
  834. my $filename= $3;
  835. my $qparams= $5;
  836. Debug "link= ".((defined($link))?$link:"<undef>");
  837. Debug "filename= ".((defined($filename))?$filename:"<undef>");
  838. Debug "qparams= ".((defined($qparams))?$qparams:"<undef>");
  839. $filename= AttrVal($name,"directoryindex","index.html") unless($filename);
  840. my $MIMEtype= filename2MIMEType($filename);
  841. my $directory= $defs{$name}{fhem}{directory};
  842. $filename= "$directory/$filename";
  843. #Debug "read filename= $filename";
  844. my @contents;
  845. if(open(INPUTFILE, $filename)) {
  846. binmode(INPUTFILE);
  847. @contents= <INPUTFILE>;
  848. close(INPUTFILE);
  849. return("$MIMEtype; charset=utf-8", join("", @contents));
  850. } else {
  851. return("text/plain; charset=utf-8", "File not found: $filename");
  852. }
  853. }
  854. ######################################
  855. # read binary file for Phototransfer - returns undef or empty string on error
  856. #
  857. sub FTUISRV_BinaryFileRead($) {
  858. my ($fileName) = @_;
  859. return '' if ( ! (-e $fileName) );
  860. my $fileData = '';
  861. open FHS_BINFILE, '<'.$fileName;
  862. binmode FHS_BINFILE;
  863. while (<FHS_BINFILE>){
  864. $fileData .= $_;
  865. }
  866. close FHS_BINFILE;
  867. return $fileData;
  868. }
  869. ##############################################
  870. ##############################################
  871. ##############################################
  872. ##############################################
  873. ##############################################
  874. ####
  875. 1;
  876. =pod
  877. =item summary HTTP Server for tablet UI with server side includes, loops, ifs
  878. =item summary_DE HTTP-Server für das tablet UI mit server-seitigen includes, loop, if
  879. =begin html
  880. <a name="FTUISRV"></a>
  881. <h3>FTUISRV</h3>
  882. <ul>
  883. Provides a mini HTTP server plugin for FHEMWEB for the specific use with FTUI.
  884. It serves files from a given directory and parses them according to specific rules.
  885. The goal is to be able to create reusable elements of multiple widgets and surrounding tags on multiple pages and even with different devices or other modifications. Therefore changes to the design have to be done only at one place and not at every occurence of the template (called parts in this doc).
  886. FTUISRV is an extension to <a href="FTUISRV">FHEMWEB</a> and code is based on HTTPSRV. You must install FHEMWEB to use FTUISRV.</p>
  887. FTUISRV is able to handled includes and replacements in files before sending the result back to the client (Browser).
  888. Special handling of files is ONLY done if the filenames include the specific pattern ".ftui." in the filename.
  889. For example a file named "test.ftui.html" would be handled specifically in FTUISRV.
  890. <br><br>
  891. FTUI files can contain the following elements
  892. <ul><br>
  893. <li><code>&lt;?ftui-inc="name" varname1="content1" ... varnameN="contentN" ?&gt;</code> <br>
  894. INCLUDE statement: Including other files that will be embedded in the result at the place of the include statement.
  895. Additionally in the embedded files the variables listed as varnamex will be replaced by the content
  896. enclosed in double quotes (").
  897. <br>
  898. The quotation marks and the spaces between the variable replacements and before the final ? are significant and can not be ommitted.
  899. <br>Example: <code>&lt;?ftui-inc="temphum-inline.ftui.part" thdev="sensorWZ" thformat="top-space-2x" thtemp="measured-temp" ?&gt;</code>
  900. </li><br>
  901. <li><code>&lt;?ftui-if=( expression ) ?&gt; ... [ &lt;?ftui-else ?&gt; ... ] &lt;?ftui-endif ?&gt; </code> <br>
  902. IF statement: Allow the inclusion of a block depending on an expression that might again include also variables and expressions in fhem. The else block is optional and can contain a block that is included if the expression is not evaluated to true.
  903. <br>
  904. Example: <code>&lt;?ftui-if=( [tempdevice:batteryok] ) ?&gt; ... &lt;?ftui-else ?&gt; ... &lt;?ftui-endif ?&gt; </code>
  905. </li><br>
  906. <li><code>&lt;?ftui-loopinc="name" loopvariable=( loop-expression ) varname1="content1" ... varnameN="contentN" ?&gt;</code> <br>
  907. LOOP-INCLUDE statement: Including other files that will be embedded in the result at the place of the include statement. The include will be executed once for every entry (line) that is returned when evaluating the loop-expression as an fhem command. So the loop expression could be a list command returning multiple devices
  908. <br>
  909. The quotation marks and the spaces between the variable replacements and before the final ? are significant and can not be ommitted.
  910. <br>Example: <code>&lt;?ftui-loopinc="temphum-inline.ftui.part" thdev=( list TYPE=CUL_TX ) thformat="top-space-2x" thtemp="measured-temp" ?&gt;</code>
  911. </li><br>
  912. <li><code>&lt;?ftui-key=varname ?&gt;</code> <br>
  913. VARIABLE specification: Replacement of variables with given parameters in the include statement (or the include header).
  914. The text specified for the corresponding variable will be inserted at the place of the FTUI-Statement in parentheses.
  915. There will be no space or other padding added before or after the replacement,
  916. the replacement will be done exactly as specified in the definition in the include
  917. <br>Example: <code>&lt;?ftui-key=measured-temp ?&gt;</code>
  918. </li><br>
  919. <li><code>&lt;?ftui-header="include name" varname1[="defaultcontent1"] .. varnameN[="defaultcontentN"] ?&gt;</code> <br>
  920. HEADER definition: Optional header for included files that can be used also to specify which variables are used in the include
  921. file and optionally specify default content for the variables that will be used if no content is specified in the include statement.
  922. the header is removed from the output given by FTUISRV.
  923. Headers are only required if default values should be specified and are helpful in showing the necessary variable names easy for users.
  924. (The name for the include does not need to be matching the file name)
  925. <br>Example: <code>&lt;?ftui-header="TempHum inline" thdev thformat thtemp="temperature" ?&gt;</code>
  926. Headers can also use device readings in for setting default values in the form of <code>[device:reading]</code>(according to the syntax and logic used in the set command)
  927. <br>Example: <code>&lt;?ftui-header="TempHum inline" thdev thformat thbattery=[temphm:batteryok] thtemp="temperature" ?&gt;</code>
  928. <br>
  929. In the special case, where also variable content shall be used in the header part a special escaping for the closing tags for the ftui-key needs to be used. That means for the example above:
  930. Example: <code>&lt;?ftui-header="TempHum inline" thdev thformat thbattery=[<ftui-key=thdev ?\>:batteryok] thtemp="temperature" ?&gt;</code>
  931. </li><br>
  932. </ul>
  933. <br><br>
  934. <a name="FTUISRVdefine"></a>
  935. <b>Define</b>
  936. <ul>
  937. <code>define &lt;name&gt; &lt;infix&gt; &lt;directory&gt; &lt;friendlyname&gt;</code><br><br>
  938. Defines the HTTP server. <code>&lt;infix&gt;</code> is the portion behind the FHEMWEB base URL (usually
  939. <code>http://hostname:8083/fhem</code>), <code>&lt;directory&gt;</code> is the absolute path the
  940. files are served from, and <code>&lt;friendlyname&gt;</code> is the name displayed in the side menu of FHEMWEB.<p><p>
  941. <br>
  942. </ul>
  943. <a name="FTUISRVset"></a>
  944. <b>Set</b>
  945. <ul>
  946. n/a
  947. </ul>
  948. <br><br>
  949. <a name="FTUISRVattr"></a>
  950. <b>Attributes</b>
  951. <br><br>
  952. <ul>
  953. <li><code>validateFiles &lt;0,1,2&gt;</code><br>
  954. Allows basic validation of HTML/Part files on correct opening/closing tags etc.
  955. Here the original files from disk are validated (setting to 1 means validation is done / 2 means also the full parsing is logged (Attention very verbose !)
  956. </li>
  957. <li><code>validateResult &lt;0,1,2&gt;</code><br>
  958. Allows basic validation of HTML content on correct opening/closing tags etc. Here the resulting content provided to the browser
  959. (after parsing) are validated (setting to 1 means validation is done / 2 means also the full parsing is logged (Attention very verbose !)
  960. </li>
  961. <li><code>templateFiles &lt;relative paths separated by :&gt;</code><br>
  962. specify specific files / urls to be handled as templates even if not containing the ftui in the filename. Multiple files can be separated by colon.
  963. </li>
  964. </ul>
  965. <br><br>
  966. </ul>
  967. =end html
  968. =cut