xref: /plugin/condition/syntax.php (revision 09b43617b7cf414344497ee7ebf8cd1b31efdab4)
1*09b43617SGerry Weißbach<?php
2*09b43617SGerry Weißbach/**
3*09b43617SGerry Weißbach * Condition Plugin: render a block if a condition if fullfilled
4*09b43617SGerry Weißbach *
5*09b43617SGerry Weißbach * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6*09b43617SGerry Weißbach * @author     Etienne Meleard <etienne.meleard@free.fr>
7*09b43617SGerry Weißbach *
8*09b43617SGerry Weißbach * 2009/06/08 : Creation
9*09b43617SGerry Weißbach * 2009/06/09 : Drop of the multi-value tests / creation of tester class system
10*09b43617SGerry Weißbach * 2009/06/10 : Added tester class override to allow user to define custom tests
11*09b43617SGerry Weißbach * 2010/06/09 : Changed $tester visibility to ensure compatibility with PHP4
12*09b43617SGerry Weißbach */
13*09b43617SGerry Weißbach
14*09b43617SGerry Weißbachif(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
15*09b43617SGerry Weißbachif(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
16*09b43617SGerry Weißbachrequire_once(DOKU_PLUGIN.'syntax.php');
17*09b43617SGerry Weißbach
18*09b43617SGerry Weißbach/**
19*09b43617SGerry Weißbach * All DokuWiki plugins to extend the parser/rendering mechanism
20*09b43617SGerry Weißbach * need to inherit from this class
21*09b43617SGerry Weißbach */
22*09b43617SGerry Weißbachclass syntax_plugin_condition extends DokuWiki_Syntax_Plugin {
23*09b43617SGerry Weißbach	// To be used by _processblocks to mix the test results together
24*09b43617SGerry Weißbach	var $allowedoperators = array('\&\&', '\|\|', '\^', 'and', 'or', 'xor'); // plus '!' specific operator
25*09b43617SGerry Weißbach
26*09b43617SGerry Weißbach	// Allowed test operators, their behavior is defined in the tester class, they are just defined here for recognition during parsing
27*09b43617SGerry Weißbach	var $allowedtests = array();
28*09b43617SGerry Weißbach
29*09b43617SGerry Weißbach	// Allowed test keys
30*09b43617SGerry Weißbach	var $allowedkeys = array();
31*09b43617SGerry Weißbach
32*09b43617SGerry Weißbach	// To store the tester object
33*09b43617SGerry Weißbach	var $tester = null;
34*09b43617SGerry Weißbach
35*09b43617SGerry Weißbach	/*function accepts($mode) { return true; }
36*09b43617SGerry Weißbach	function getAllowedTypes() {
37*09b43617SGerry Weißbach		return array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs'); // quick hack
38*09b43617SGerry Weißbach		}*/
39*09b43617SGerry Weißbach
40*09b43617SGerry Weißbach	function getType() { return 'container';}
41*09b43617SGerry Weißbach	function getPType() { return 'normal';}
42*09b43617SGerry Weißbach	function getSort() { return 5; } // condition is top priority
43*09b43617SGerry Weißbach
44*09b43617SGerry Weißbach	// Connect pattern to lexer
45*09b43617SGerry Weißbach	function connectTo($mode){
46*09b43617SGerry Weißbach		$this->Lexer->addEntryPattern('<if(?=.*?</if>)', $mode, 'plugin_condition');
47*09b43617SGerry Weißbach	}
48*09b43617SGerry Weißbach
49*09b43617SGerry Weißbach	function postConnect() {
50*09b43617SGerry Weißbach		$this->Lexer->addExitPattern('</if>', 'plugin_condition');
51*09b43617SGerry Weißbach	}
52*09b43617SGerry Weißbach
53*09b43617SGerry Weißbach	// Handle the match
54*09b43617SGerry Weißbach	function handle($match, $state, $pos, &$handler) {
55*09b43617SGerry Weißbach		if($state != DOKU_LEXER_UNMATCHED) return false;
56*09b43617SGerry Weißbach
57*09b43617SGerry Weißbach		// Get allowed test operators
58*09b43617SGerry Weißbach		$this->_loadtester();
59*09b43617SGerry Weißbach		if(!$this->tester) return array(array(), '');
60*09b43617SGerry Weißbach		$this->allowedtests = $this->tester->getops();
61*09b43617SGerry Weißbach		$this->allowedkeys = $this->tester->getkeys();
62*09b43617SGerry Weißbach
63*09b43617SGerry Weißbach		$blocks = array();
64*09b43617SGerry Weißbach		$content = '';
65*09b43617SGerry Weißbach		$this->_parse($match, $blocks, $content);
66*09b43617SGerry Weißbach
67*09b43617SGerry Weißbach		return array($blocks, $content);
68*09b43617SGerry Weißbach	}
69*09b43617SGerry Weißbach
70*09b43617SGerry Weißbach	// extracts condition / content
71*09b43617SGerry Weißbach	function _parse(&$match, &$b, &$ctn) {
72*09b43617SGerry Weißbach		$match = preg_replace('`^\s+`', '', $match); // trim heading whitespaces
73*09b43617SGerry Weißbach		$b = $this->_fetch_block($match, 0);
74*09b43617SGerry Weißbach		if($match != '') $ctn = preg_replace('`\n+$`', '', preg_replace('`^\n+`', '', preg_replace('`^>`', '', $match)));
75*09b43617SGerry Weißbach		return true;
76*09b43617SGerry Weißbach	}
77*09b43617SGerry Weißbach
78*09b43617SGerry Weißbach	// fetch a condition block from buffer
79*09b43617SGerry Weißbach	function _fetch_block(&$match, $lvl=0) {
80*09b43617SGerry Weißbach		$match = preg_replace('`^\s+`', '', $match); // trim heading whitespaces
81*09b43617SGerry Weißbach		$instrs = array();
82*09b43617SGerry Weißbach		$continue = true;
83*09b43617SGerry Weißbach
84*09b43617SGerry Weißbach		while(($match{0} != '>') && ($match != '') && (($lvl == 0) || ($match{0} != ')')) && $continue) {
85*09b43617SGerry Weißbach			$i = array('type' => null, 'key' => '', 'test' => '', 'value' => '', 'next' => '');
86*09b43617SGerry Weißbach			if($this->_fetch_op($match, true)) { // ! heading equals block descending for first token
87*09b43617SGerry Weißbach				$i['type'] = 'nblock';
88*09b43617SGerry Weißbach				$match = substr($match, 1); // remove heading !
89*09b43617SGerry Weißbach				$i['value'] = $this->_fetch_block($match, $lvl+1);
90*09b43617SGerry Weißbach			}else if($this->_is_block($match)) {
91*09b43617SGerry Weißbach				$i['type'] = 'block';
92*09b43617SGerry Weißbach				$match = substr($match, 1); // remove heading (
93*09b43617SGerry Weißbach				$i['value'] = $this->_fetch_block($match, $lvl+1);
94*09b43617SGerry Weißbach			}else if($this->_is_key($match, $key)) {
95*09b43617SGerry Weißbach				$i['type'] = 'test';
96*09b43617SGerry Weißbach				$i['key'] = $key;
97*09b43617SGerry Weißbach				$match = substr($match, strlen($key)); // remove heading key
98*09b43617SGerry Weißbach				if($this->_is_test($match, $test)) {
99*09b43617SGerry Weißbach					$i['test'] = $test;
100*09b43617SGerry Weißbach					$match = substr($match, strlen($test)); // remove heading test
101*09b43617SGerry Weißbach					if(($v = $this->_fetch_value($match)) !== null) $i['value'] = $v;
102*09b43617SGerry Weißbach				}
103*09b43617SGerry Weißbach			}else $match = preg_replace('`^[^>\s\(]+`', '', $match); // here dummy stuff remains
104*09b43617SGerry Weißbach			if($i['type']) {
105*09b43617SGerry Weißbach				if(($op = $this->_fetch_op($match, false)) !== null) {
106*09b43617SGerry Weißbach					$match = substr($match, strlen($op)); // remove heading op
107*09b43617SGerry Weißbach					$i['next'] = $op;
108*09b43617SGerry Weißbach				}else $continue = false;
109*09b43617SGerry Weißbach				$instrs[] = $i;
110*09b43617SGerry Weißbach			}
111*09b43617SGerry Weißbach		}
112*09b43617SGerry Weißbach		return $instrs;
113*09b43617SGerry Weißbach	}
114*09b43617SGerry Weißbach
115*09b43617SGerry Weißbach	// test if buffer starts with new sub-block
116*09b43617SGerry Weißbach	function _is_block(&$match) {
117*09b43617SGerry Weißbach		$match = preg_replace('`^\s+`', '', $match); // trim heading whitespaces
118*09b43617SGerry Weißbach		return preg_match('`^\(`', $match);
119*09b43617SGerry Weißbach	}
120*09b43617SGerry Weißbach
121*09b43617SGerry Weißbach	// test if buffer starts with a key ref
122*09b43617SGerry Weißbach	function _is_key(&$match, &$key) {
123*09b43617SGerry Weißbach		$match = preg_replace('`^\s+`', '', $match); // trim heading whitespaces
124*09b43617SGerry Weißbach		if(preg_match('`^([a-zA-Z0-9_-]+)`', $match, $r)) {
125*09b43617SGerry Weißbach			if(preg_match('`^'.$this->_preg_build_alternative($this->allowedkeys).'$`', $r[1])) {
126*09b43617SGerry Weißbach				$key = $r[1];
127*09b43617SGerry Weißbach				return true;
128*09b43617SGerry Weißbach			}
129*09b43617SGerry Weißbach		}
130*09b43617SGerry Weißbach		return false;
131*09b43617SGerry Weißbach	}
132*09b43617SGerry Weißbach
133*09b43617SGerry Weißbach	// build a pcre alternative escaped test from array
134*09b43617SGerry Weißbach	function _preg_build_alternative($choices) {
135*09b43617SGerry Weißbach		//$choices = array_map(create_function('$e', 'return preg_replace(\'`([^a-zA-Z0-9])`\', \'\\\\\\\\$1\', $e);'), $choices);
136*09b43617SGerry Weißbach		return '('.implode('|', $choices).')';
137*09b43617SGerry Weißbach	}
138*09b43617SGerry Weißbach
139*09b43617SGerry Weißbach	// tells if buffer starts with a test operator
140*09b43617SGerry Weißbach	function _is_test(&$match, &$test) {
141*09b43617SGerry Weißbach		$match = preg_replace('`^\s+`', '', $match); // trim heading whitespaces
142*09b43617SGerry Weißbach		if(preg_match('`^'.$this->_preg_build_alternative($this->allowedtests).'`', $match, $r)) { $test = $r[1]; return true; }
143*09b43617SGerry Weißbach		return false;
144*09b43617SGerry Weißbach	}
145*09b43617SGerry Weißbach
146*09b43617SGerry Weißbach	// fetch value from buffer, handles value quoting
147*09b43617SGerry Weißbach	function _fetch_value(&$match) {
148*09b43617SGerry Weißbach		$match = preg_replace('`^\s+`', '', $match); // trim heading whitespaces
149*09b43617SGerry Weißbach		if($match{0} == '"') {
150*09b43617SGerry Weißbach			$match = substr($match, 1);
151*09b43617SGerry Weißbach			$value = substr($match, 0, strpos($match, '"'));
152*09b43617SGerry Weißbach			$match = substr($match, strlen($value) + 1);
153*09b43617SGerry Weißbach		}else{
154*09b43617SGerry Weißbach			$psp = strpos($match, ')');
155*09b43617SGerry Weißbach			$wsp = strpos($match, ' ');
156*09b43617SGerry Weißbach			$esp = strpos($match, '>');
157*09b43617SGerry Weißbach			$sp = 0;
158*09b43617SGerry Weißbach			$bug = false;
159*09b43617SGerry Weißbach			if(($wsp === false) && ($esp === false) && ($psp === false)) {
160*09b43617SGerry Weißbach				return null; // BUG
161*09b43617SGerry Weißbach			}else if(($wsp === false) && ($esp === false)) {
162*09b43617SGerry Weißbach				$sp = $psp;
163*09b43617SGerry Weißbach			}else if(($wsp === false) && ($psp === false)) {
164*09b43617SGerry Weißbach				$sp = $esp;
165*09b43617SGerry Weißbach			}else if(($psp === false) && ($esp === false)) {
166*09b43617SGerry Weißbach				$sp = $wsp;
167*09b43617SGerry Weißbach			}else if($wsp === false) {
168*09b43617SGerry Weißbach				$sp = min($esp, $psp);
169*09b43617SGerry Weißbach			}else if($esp === false) {
170*09b43617SGerry Weißbach				$sp = min($wsp, $psp);
171*09b43617SGerry Weißbach			}else if($psp === false) {
172*09b43617SGerry Weißbach				$sp = min($esp, $wsp);
173*09b43617SGerry Weißbach			}else $sp = min($wsp, $esp, $psp);
174*09b43617SGerry Weißbach
175*09b43617SGerry Weißbach			$value = substr($match, 0, $sp);
176*09b43617SGerry Weißbach			$match = substr($match, strlen($value));
177*09b43617SGerry Weißbach		}
178*09b43617SGerry Weißbach		return $value;
179*09b43617SGerry Weißbach	}
180*09b43617SGerry Weißbach
181*09b43617SGerry Weißbach	// fetch a logic operator from buffer
182*09b43617SGerry Weißbach	function _fetch_op(&$match, $head=false) {
183*09b43617SGerry Weißbach		$match = preg_replace('`^\s+`', '', $match); // trim heading whitespaces
184*09b43617SGerry Weißbach		$ops = $this->allowedoperators;
185*09b43617SGerry Weißbach		if($head) $ops = array('!');
186*09b43617SGerry Weißbach		if(preg_match('`^'.$this->_preg_build_alternative($ops).'`', $match, $r)) return $r[1];
187*09b43617SGerry Weißbach		return null;
188*09b43617SGerry Weißbach	}
189*09b43617SGerry Weißbach
190*09b43617SGerry Weißbach	/**
191*09b43617SGerry Weißbach	 * Create output
192*09b43617SGerry Weißbach	 */
193*09b43617SGerry Weißbach	function render($mode, &$renderer, $data) {
194*09b43617SGerry Weißbach		global $INFO;
195*09b43617SGerry Weißbach		if(count($data) != 2) return false;
196*09b43617SGerry Weißbach		if($mode == 'xhtml') {
197*09b43617SGerry Weißbach			global $ID;
198*09b43617SGerry Weißbach			// prevent caching to ensure good user data detection for tests
199*09b43617SGerry Weißbach			$renderer->info['cache'] = false;
200*09b43617SGerry Weißbach
201*09b43617SGerry Weißbach			$blocks = $data[0];
202*09b43617SGerry Weißbach			$content = $data[1];
203*09b43617SGerry Weißbach
204*09b43617SGerry Weißbach			// parsing content for a <else> statement
205*09b43617SGerry Weißbach			$else = '';
206*09b43617SGerry Weißbach			if(strpos($content, '<else>') !== false) {
207*09b43617SGerry Weißbach				$i = explode('<else>', $content);
208*09b43617SGerry Weißbach				$content = $i[0];
209*09b43617SGerry Weißbach				$else = implode('', array_slice($i, 1));
210*09b43617SGerry Weißbach			}
211*09b43617SGerry Weißbach
212*09b43617SGerry Weißbach			// Process condition blocks
213*09b43617SGerry Weißbach			$bug = false;
214*09b43617SGerry Weißbach			$this->_loadtester();
215*09b43617SGerry Weißbach			$ok = $this->_processblocks($blocks, $bug);
216*09b43617SGerry Weißbach
217*09b43617SGerry Weißbach			// Render content if all went well
218*09b43617SGerry Weißbach			$toc = $renderer->toc;
219*09b43617SGerry Weißbach			if(!$bug) {
220*09b43617SGerry Weißbach			  $instr = p_get_instructions($ok ? $content : $else);
221*09b43617SGerry Weißbach			  foreach($instr as $instruction) {
222*09b43617SGerry Weißbach			  	if ( in_array($instruction[0], array('document_start', 'document_end') ) ) continue;
223*09b43617SGerry Weißbach			    call_user_func_array(array(&$renderer, $instruction[0]), $instruction[1]);
224*09b43617SGerry Weißbach			  }
225*09b43617SGerry Weißbach			}
226*09b43617SGerry Weißbach			$renderer->toc = array_merge($toc, $renderer->toc);
227*09b43617SGerry Weißbach
228*09b43617SGerry Weißbach			return true;
229*09b43617SGerry Weißbach		}
230*09b43617SGerry Weißbach		if($mode == 'metadata') {
231*09b43617SGerry Weißbach			global $ID;
232*09b43617SGerry Weißbach			// prevent caching to ensure good user data detection for tests
233*09b43617SGerry Weißbach			$renderer->info['cache'] = false;
234*09b43617SGerry Weißbach
235*09b43617SGerry Weißbach			$blocks = $data[0];
236*09b43617SGerry Weißbach			$content = $data[1];
237*09b43617SGerry Weißbach
238*09b43617SGerry Weißbach			// parsing content for a <else> statement
239*09b43617SGerry Weißbach			$else = '';
240*09b43617SGerry Weißbach			if(strpos($content, '<else>') !== false) {
241*09b43617SGerry Weißbach				$i = explode('<else>', $content);
242*09b43617SGerry Weißbach				$content = $i[0];
243*09b43617SGerry Weißbach				$else = implode('', array_slice($i, 1));
244*09b43617SGerry Weißbach			}
245*09b43617SGerry Weißbach
246*09b43617SGerry Weißbach			// Process condition blocks
247*09b43617SGerry Weißbach			$bug = false;
248*09b43617SGerry Weißbach			$this->_loadtester();
249*09b43617SGerry Weißbach			$ok = $this->_processblocks($blocks, $bug);
250*09b43617SGerry Weißbach			// Render content if all went well
251*09b43617SGerry Weißbach			$metatoc = $renderer->meta['description']['tableofcontents'];
252*09b43617SGerry Weißbach			if(!$bug) {
253*09b43617SGerry Weißbach			  $instr = p_get_instructions($ok ? $content : $else);
254*09b43617SGerry Weißbach			  foreach($instr as $instruction) {
255*09b43617SGerry Weißbach			  	if ( in_array($instruction[0], array('document_start', 'document_end') ) ) continue;
256*09b43617SGerry Weißbach			    call_user_func_array(array(&$renderer, $instruction[0]), $instruction[1]);
257*09b43617SGerry Weißbach			  }
258*09b43617SGerry Weißbach			}
259*09b43617SGerry Weißbach
260*09b43617SGerry Weißbach			if ( !is_array($renderer->meta['description']['tableofcontents']) ) {
261*09b43617SGerry Weißbach				$renderer->meta['description']['tableofcontents'] = array();
262*09b43617SGerry Weißbach			}
263*09b43617SGerry Weißbach
264*09b43617SGerry Weißbach			$renderer->meta['description']['tableofcontents'] = array_merge($metatoc, $renderer->meta['description']['tableofcontents']);
265*09b43617SGerry Weißbach
266*09b43617SGerry Weißbach			return true;
267*09b43617SGerry Weißbach		}
268*09b43617SGerry Weißbach		return false;
269*09b43617SGerry Weißbach	}
270*09b43617SGerry Weißbach
271*09b43617SGerry Weißbach	// Strips the heading <p> and trailing </p> added by p_render xhtml to acheive inline behavior
272*09b43617SGerry Weißbach	function _stripp($data) {
273*09b43617SGerry Weißbach		$data = preg_replace('`^\s*<p[^>]*>\s*`', '', $data);
274*09b43617SGerry Weißbach		$data = preg_replace('`\s*</p[^>]*>\s*$`', '', $data);
275*09b43617SGerry Weißbach		return $data;
276*09b43617SGerry Weißbach	}
277*09b43617SGerry Weißbach
278*09b43617SGerry Weißbach	// evaluates the logical result from a set of blocks
279*09b43617SGerry Weißbach	function _processblocks($b, &$bug) {
280*09b43617SGerry Weißbach		for($i=0; $i<count($b); $i++) {
281*09b43617SGerry Weißbach			if(($b[$i]['type'] == 'block') || ($b[$i]['type'] == 'nblock')) {
282*09b43617SGerry Weißbach				$b[$i]['r'] = $this->_processblocks($b[$i]['value'], $bug);
283*09b43617SGerry Weißbach				if($b[$i]['type'] == 'nblock') $b[$i]['r'] = !$b[$i]['r'];
284*09b43617SGerry Weißbach			}else{
285*09b43617SGerry Weißbach				$b[$i]['r'] = $this->_evaluate($b[$i], $bug);
286*09b43617SGerry Weißbach			}
287*09b43617SGerry Weißbach		}
288*09b43617SGerry Weißbach		if(!count($b)) $bug = true; // no condition in block
289*09b43617SGerry Weißbach		if($bug) return false;
290*09b43617SGerry Weißbach
291*09b43617SGerry Weißbach		// assemble conditions
292*09b43617SGerry Weißbach		/* CUSTOMISATION :
293*09b43617SGerry Weißbach		 * You can add custom mixing operators here, don't forget to add them to
294*09b43617SGerry Weißbach		 * the "allowedoperators" list at the top of this file
295*09b43617SGerry Weißbach		 */
296*09b43617SGerry Weißbach		$r = $b[0]['r'];
297*09b43617SGerry Weißbach		for($i=1; $i<count($b); $i++) {
298*09b43617SGerry Weißbach			if($b[$i-1]['next'] == '') {
299*09b43617SGerry Weißbach				$bug = true;
300*09b43617SGerry Weißbach				return false;
301*09b43617SGerry Weißbach			}
302*09b43617SGerry Weißbach			switch($b[$i-1]['next']) {
303*09b43617SGerry Weißbach				case '&&' :
304*09b43617SGerry Weißbach				case 'and' :
305*09b43617SGerry Weißbach					$r &= $b[$i]['r'];
306*09b43617SGerry Weißbach					break;
307*09b43617SGerry Weißbach				case '||' :
308*09b43617SGerry Weißbach				case 'or' :
309*09b43617SGerry Weißbach					$r |= $b[$i]['r'];
310*09b43617SGerry Weißbach					break;
311*09b43617SGerry Weißbach				case '^' :
312*09b43617SGerry Weißbach				case 'xor' :
313*09b43617SGerry Weißbach					$r ^= $b[$i]['r'];
314*09b43617SGerry Weißbach					break;
315*09b43617SGerry Weißbach			}
316*09b43617SGerry Weißbach		}
317*09b43617SGerry Weißbach		return $r;
318*09b43617SGerry Weißbach	}
319*09b43617SGerry Weißbach
320*09b43617SGerry Weißbach	// evaluates a single test, loads custom tests if class exists, default test set otherwise
321*09b43617SGerry Weißbach	function _evaluate($b, &$bug) {
322*09b43617SGerry Weißbach		if(!$this->tester) {
323*09b43617SGerry Weißbach			$bug = true;
324*09b43617SGerry Weißbach			return false;
325*09b43617SGerry Weißbach		}
326*09b43617SGerry Weißbach		return $this->tester->run($b, $bug);
327*09b43617SGerry Weißbach	}
328*09b43617SGerry Weißbach
329*09b43617SGerry Weißbach	// tries to load user defined tester, then base tester if previous failed
330*09b43617SGerry Weißbach	function _loadtester() {
331*09b43617SGerry Weißbach		global $conf;
332*09b43617SGerry Weißbach		$this->tester = null;
333*09b43617SGerry Weißbach		include_once(DOKU_PLUGIN.'condition/base_tester.php');
334*09b43617SGerry Weißbach		if(@file_exists(DOKU_INC.'lib/tpl/'.$conf['template'].'/condition_plugin_custom_tester.php')) {
335*09b43617SGerry Weißbach			include_once(DOKU_INC.'lib/tpl/'.$conf['template'].'/condition_plugin_custom_tester.php');
336*09b43617SGerry Weißbach			if(class_exists('condition_plugin_custom_tester')) {
337*09b43617SGerry Weißbach				$this->tester = new condition_plugin_custom_tester();
338*09b43617SGerry Weißbach			}
339*09b43617SGerry Weißbach		}
340*09b43617SGerry Weißbach		if(!$this->tester) {
341*09b43617SGerry Weißbach			if(class_exists('condition_plugin_base_tester')) {
342*09b43617SGerry Weißbach				$this->tester = new condition_plugin_base_tester();
343*09b43617SGerry Weißbach			}
344*09b43617SGerry Weißbach		}
345*09b43617SGerry Weißbach	}
346*09b43617SGerry Weißbach} //class
347*09b43617SGerry Weißbach?>
348