* @copyright 2002-11 by RalfBecker@outdoor-training.de * @version $Id$ */ if (!function_exists('var2xml')) { if (file_exists(EGW_API_INC.'class.xmltool.inc.php')) { include_once(EGW_API_INC.'class.xmltool.inc.php'); } else { include_once('class.xmltool.inc.php'); } } /** * XUL/XML Import & Export for eTemplates * * used only internaly */ class xul_io { /** * translate attr, common to all widgets * * @var array */ var $attr2xul = array( 'name' => 'id', 'help' => 'statustext', 'span' => 'span,class', 'type' => '', // this is the widget-name => dont write as attr 'disabled' => 'disabled=true', 'readonly' => 'readonly=true', 'size' => 'options' ); /** * translate widget-names and widget-spec. attr., not set ones are identical * * @var array */ var $widget2xul = array( 'label' => array( '.name' => 'description', 'label' => 'value', 'size' => 'font_style,href,activate_links,for,extra_link_target,extra_link_popup,extra_link_title', ), 'text' => array( '.name' => 'textbox', 'size' => 'size,maxlength,validator' ), 'textarea' => array( '.name' => 'textbox', '.set' => 'multiline=true', 'size' => 'rows,cols' ), 'integer' => array( '.name' => 'textbox', '.set' => 'type=integer', 'size' => 'min,max,size,precision,step' ), 'int' => array( '.name' => 'textbox', '.set' => 'type=integer', 'size' => 'min,max,size,precision,step' ), 'float' => array( '.name' => 'textbox', '.set' => 'type=float', 'size' => 'min,max,size,precision,step' ), 'select' => array( '.name' => 'menulist,menupopup', ), 'select-multi' => array( // multiselection, if size > 0 '.name' => 'listbox', 'size' => 'rows,options' ), 'template' => array( '.name' => 'template', 'size' => 'content' ), 'image' => array( '.name' => 'image', 'name' => 'src', 'size' => 'href,extra_link_target,imagemap,extra_link_popup,id', ), 'progres' => array( '.name' => 'progress', 'size' => 'href,extra_link_target,,extra_link_popup', ), 'tab' => array( '.name' => 'tabbox,tabs,tabpanels' ), 'button' => array( '.name' => 'button', 'size' => 'image,ro_image' ), 'htmlarea' => array( 'size' => 'mode,height,width,toolbar,base_href', ), 'nextmatch' => array( 'size' => 'template,hide_header,header_left,header_right', ), ); /** * translate xul-widget names to our internal ones, not set ones are identical * * @var array */ var $xul2widget = array( 'menulist' => 'select', 'listbox' => 'select', 'menupopup' => 'select', 'description' => 'label' ); /** * explicit whitelist for certain attributes and widget types */ var $attr_whitelist = array( 'rows' => array('textbox'), 'cols' => array('textbox'), ); /** * Keys of currently processed template on export, to resolve relative names * * @var array */ var $load_via; /** * sets an attribute in the xml object representing a widget * * @param object &$widget widget to set the attribute in * @param string $attr comma delimited attr = default-value pairs, eg. "type=int,min=0" * @param array $val array with values to set */ function set_attributes(&$widget,$attr,$val) { if ($attr != '' && !is_numeric($attr)) { $attrs = explode(',',$attr); if (count($attrs)) { $vals = count($attrs) > 1 ? explode(',',$val,count($attrs)) : array($val); foreach($attrs as $n => $attr) { if (($val = $vals[$n]) != '') { list($attr,$set) = explode('=',$attr); $widget->set_attribute($attr,$set != '' ? $set : $val); } } } } } /** * add a widget to a parent * * @param object &$parent parten to add the widget * @param array $cell widget to add * @param array &$embeded_too already embeded eTemplates * @return object reference (!) the the xml object representing the widget, so other children can be added */ function &add_widget(&$parent,$cell,&$embeded_too) { // sort attributes, to stop xet files from changing because of changed attribute order ksort($cell, SORT_STRING); $type = $cell['type']; if (is_array($type)) { list(,$type) = each($type); } if (!$type) $cell['type'] = $type = 'unknown'; if (substr($type,0,6) == 'select') { $type = $cell['size'] > 1 ? 'select-multi' : 'select'; } $widgetattr2xul = isset($this->widget2xul[$type]) ? $this->widget2xul[$type] : array(); $type = isset($widgetattr2xul['.name']) ? $widgetattr2xul['.name'] : $type; list($type,$child,$child2) = explode(',',$type); $widget = new xmlnode($type); $attr_widget = &$widget; if ($child) { $child = new xmlnode($child); if ($type != 'tabbox') $attr_widget = &$child; } if ($child2) { $child2 = new xmlnode($child2); } if (isset($widgetattr2xul['.set'])) // set default-attr for type { $attrs = explode(',',$widgetattr2xul['.set']); foreach($attrs as $attr) { list($attr,$val) = explode('=',$attr); $widget->set_attribute($attr,$val); } } switch ($type) { case 'nextmatch': $tpls = $cell['size'] = explode(',', $cell['size']); // template,hide_header,header_left,header_right unset($tpls[1]); // hide_header is no template foreach($tpls as $n => $tpl) { if (empty($tpl)) continue; $embeded = new boetemplate($tpl,$this->load_via); if ($embeded_too) { $this->add_etempl($embeded,$embeded_too); } $cell['size'][$n] = $embeded->name; unset($embeded); } $cell['size'] = implode(',', $cell['size']); break; case 'tabbox': $labels = explode('|',$cell['label']); unset($cell['label']); $helps = explode('|',$cell['help']); unset($cell['help']); if (strpos($tab_names=$cell['name'],'=') !== false) { list($cell['name'],$tab_names) = explode('=',$cell['name']); } $names = explode('|',$tab_names); for ($n = 0; $n < count($labels); ++$n) { $tab = new xmlnode('tab'); $tab->set_attribute('id',$names[$n]); $tab->set_attribute('label',$labels[$n]); if ($helps[$n]) $tab->set_attribute('statustext',$helps[$n]); $child->add_node($tab); $embeded = new boetemplate($names[$n],$this->load_via); if ($embeded_too) { $this->add_etempl($embeded,$embeded_too); } $template = new xmlnode('template'); $template->set_attribute('id',$embeded->name); $child2->add_node($template); unset($embeded); unset($template); } break; case 'menulist': // id,options belongs to the 'menupopup' child if ($cell['span']) { $this->set_attributes($widget,'span,class',$cell['span']); unset($cell['span']); } // fall-trought case 'listbox': if ($cell['type'] != 'select') // one of the sub-types { $attr_widget->set_attribute('type',$cell['type']); } break; case 'groupbox': if ($cell['label']) { $caption = new xmlnode('caption'); $caption->set_attribute('label',$cell['label']); $widget->add_node($caption); unset($cell['label']); } // fall-through case 'split': case 'vbox': case 'hbox': case 'box': case 'deck': list($anz,$orient,$options) = explode(',',$cell['size'],3); for ($n = 1; $n <= $anz; ++$n) { $this->add_widget($widget,$cell[$n],$embeded_too); unset($cell[$n]); } // no sure where the data key gets set, but it gives a warning in xml serialization (empty array) unset($cell['data']); if (!empty($orient)) $cell['orient'] = $orient; $cell['size'] = $options; break; case 'template': if ($cell['name'][0] != '@' && $embeded_too) { $templ = new boetemplate(); if ($templ->read(boetemplate::expand_name($cell['name'],0,0),'default','default',0,'',$this->load_via)) { $this->add_etempl($templ,$embeded_too); } unset($templ); } break; case 'grid': $this->add_grid($parent,$cell,$embeded_too); return; // grid is already added } foreach($cell as $attr => $val) { if (is_array($val)) // correct old buggy etemplates { list(,$val) = each($val); } if (isset($widgetattr2xul[$attr])) { $attr = $widgetattr2xul[$attr]; } elseif (isset($this->attr2xul[$attr])) { $attr = $this->attr2xul[$attr]; } // check if attribute has an explicit whitelist and widget type is in it if (isset($this->attr_whitelist[$attr]) && !in_array($type, $this->attr_whitelist[$attr])) { continue; } $this->set_attributes($attr_widget,$attr,$val); } if ($child) { $widget->add_node($child); } if ($child2) { $widget->add_node($child2); } $parent->add_node($widget); } /** * add a grid to $parent (xml object) * * @param object &$parent where to add the grid * @param array $grid grid to add * @param array &embeded_too array with already embeded eTemplates */ function add_grid(&$parent,$grid,&$embeded_too) { $xul_grid = new xmlnode('grid'); $this->set_attributes($xul_grid,'width,height,border,class,spacing,padding,overflow',$grid['size']); $this->set_attributes($xul_grid,'id',$grid['name']); $xul_columns = new xmlnode('columns'); $xul_rows = new xmlnode('rows'); reset($grid['data']); list(,$opts) = each ($grid['data']); // read over options-row while (list($r,$row) = each ($grid['data'])) { $xul_row = new xmlnode('row'); $this->set_attributes($xul_row,'class,valign',$opts["c$r"]); $this->set_attributes($xul_row,'height,disabled,part',$opts["h$r"]); $spanned = 0; foreach($row as $c => $cell) { if ($r == '1') // write columns only once in the first row { $xul_column = new xmlnode('column'); $this->set_attributes($xul_column,'width,disabled',$opts[$c]); $xul_columns->add_node($xul_column); } if ($spanned-- > 1) { continue; // spanned cells are not written } $this->add_widget($xul_row,$cell,$embeded_too); $spanned = $cell['span'] == 'all' ? 999 : $cell['span']; } $xul_rows->add_node($xul_row); } $xul_grid->add_node($xul_columns); $xul_grid->add_node($xul_rows); $parent->add_node($xul_grid); } /** * add / embed an eTemplate into the global $xul_overlay object (used by export) * * @param boetemplate &$etempl eTemplate to embed * @param array &embeded_too array with already embeded templates */ function add_etempl(boetemplate $etempl,&$embeded_too) { if (is_array($embeded_too)) { if (isset($embeded_too[$etempl->name])) { return; // allready embeded } } else { $embeded_too = array(); } $embeded_too[$etempl->name] = True; $template = new xmlnode('template'); $template->set_attribute('id',$etempl->name); $template->set_attribute('template',$etempl->template); $template->set_attribute('lang',$etempl->lang); $template->set_attribute('group',$etempl->group); $template->set_attribute('version',$etempl->version); $backup_load_via = $this->load_via; $this->load_via = $etempl->as_array(-1); foreach($etempl->children as $child) { $this->add_widget($template,$child,$embeded_too); } $this->load_via = $backup_load_via; if ($etempl->style != '') { $styles = new xmlnode('styles'); $styles->set_value(str_replace("\r",'',$etempl->style)); $template->add_node($styles); } $this->xul_overlay->add_node($template); } /** * create an XML representation of an eTemplate * * @param etemplate $etempl eTemplate object to export * @return string the XML */ function export($etempl) { if ($this->debug) { echo "
etempl->data = "; _debug_array($etempl->data); } $doc = new xmldoc(); $doc->add_comment('$'.'Id$'); $this->xul_overlay = new xmlnode('overlay'); // global for all add_etempl calls $embeded_too = True; $this->add_etempl($etempl,$embeded_too); $doc->add_root($this->xul_overlay); $xml = $doc->export_xml(); if ($this->debug) { echo "
\n" . htmlentities($xml) . "\n\n"; } return $xml; } /** * create an eTemplate from it's XML representation * * @param object &$etempl eTemplate object to set * @param string $data the XML * @return array/string array with names of imported templates or error-message */ function import(&$etempl,$data) { if ($this->debug) { echo "
\n" . htmlentities($data) . "\n
\n"; } $parser = xml_parser_create(); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1); $vals = $index = ''; $ok = xml_parse_into_struct($parser, $data, $vals, $index); if (!$ok || !is_array($vals)) { $err = 'Error Line '.xml_get_current_line_number($parser).', Column '.xml_get_current_column_number($parser). ': '.xml_error_string(xml_get_error_code($parser)); } xml_parser_free($parser); if ($err != '') { return $err; } $parents = array(); $parent = null; foreach($vals as $n => $node) { if ($this->debug) { echo "
".print_r($node,true).""; } $type = $node['type']; $tag = $node['tag']; $attr = is_array($node['attributes']) ? $node['attributes'] : array(); if ($attr['id']) { $attr['name'] = $attr['id']; unset($attr['id']); } if (isset($attr['options']) && $attr['options'] != '') { $attr['size'] = $attr['options']; unset($attr['options']); } if ($tag != 'textbox' && !isset($attr['type'])) { $attr['type'] = $this->xul2widget[$tag] ? $this->xul2widget[$tag] : $tag; } if ($this->debug) { echo "
$node[level]: $tag/$type: value='$node[value]' attr=\n"; _debug_array($attr);
}
switch ($tag)
{
case 'overlay':
break;
case 'template':
case 'grid':
if ($type != 'open' && is_array($tab_attr)) // templates/grids in a tabpanel
{
$tab_names[] = $attr['name'];
break;
}
if ($tag == 'template' && $type != 'complete' && $node['level'] > 2) // level 1 is the overlay
{
return "Can't import nested $tag's !!!";
}
switch ($type)
{
case 'close':
if (!count($parents)) // templ import complet => save it
{
unset($parent); $parents = array();
$etempl->fix_old_template_format(); // set the depricated compat vars
// save tmpl to the cache, as the file may contain more then one tmpl
$cname = ($etempl->template == '' ? 'default' : $etempl->template).'/'.$etempl->name.
($etempl->lang == '' ? '' : '.'.$etempl->lang);
boetemplate::store_in_cache($etempl);
if ($this->debug)
{
$etempl->echo_tmpl();
}
$imported[] = $etempl->name;
}
else
{
// poping the last used parent from the end of the parents array (array_pop does not work with references)
$parent = &$parents[count($parents)-1];
unset($parents[count($parents)-1]);
}
break;
case 'open':
if (($is_root = is_null($parent))) // starting a new templ
{
$etempl->init($attr);
$etempl->children = array(); // init adds one grid by default
$parent = &$etempl; // parent is the template-object itself!
}
if ($tag == 'grid')
{
$size = '';
foreach(array('overflow','padding','spacing','class','border','height','width') as $opt)
{
$size = $attr[$opt] . ($size != '' ? ",$size" : '');
}
$grid = array( // empty grid
'type' => 'grid',
'name' => $attr['name'],
'data' => array(),
'cols' => 0,
'rows' => 0,
'size' => $size,
);
soetemplate::add_child($parent,$grid);
$parents[count($parents)] = &$parent;
$parent = &$grid;
unset($grid);
}
break;
case 'complete': // reference to an other template
$attr['type'] = 'template'; // might be grid in old xet-files
soetemplate::add_child($parent,$attr);
unset($attr);
break;
}
break;
case 'columns':
case 'rows':
break;
case 'column':
if ($type != 'complete')
{
return 'place widgets in ".print_r($parent,true)."
";
echo "parents".print_r($parents,true)."
";
echo "children".print_r($etempl->children,true)."
";
}
}
return $imported;
}
/**
* re-assemble legacy options in "size" attribute
*
* @param string $tag
* @param array &$attr
*/
function set_legacy_options($tag, &$attr)
{
// re-assemble legacy options in "size" attribute
if (empty($attr['size']) && $this->widget2xul[$tag]['size'])
{
foreach(explode(',', $this->widget2xul[$tag]['size']) as $l_attr)
{
$attr['size'] .= ($attr['size'] ? ',' : '').$attr[$l_attr];
unset($attr[$l_attr]);
}
while(substr($attr['size'], -1) == ',')
{
$attr['size'] = substr($attr['size'], 0, -1);
}
}
}
}