02_FTUISRV.pm 42 KB

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