Xataface HTML Reports Module 0.2
HTML Reports Module for Xataface
classes/XfHtmlReportBuilder.class.php
Go to the documentation of this file.
00001 <?php
00002 /*
00003  * Xataface HTML Reports Module
00004  * Copyright (C) 2011  Steve Hannah <steve@weblite.ca>
00005  * 
00006  * This library is free software; you can redistribute it and/or
00007  * modify it under the terms of the GNU Library General Public
00008  * License as published by the Free Software Foundation; either
00009  * version 2 of the License, or (at your option) any later version.
00010  * 
00011  * This library is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014  * Library General Public License for more details.
00015  * 
00016  * You should have received a copy of the GNU Library General Public
00017  * License along with this library; if not, write to the
00018  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
00019  * Boston, MA  02110-1301, USA.
00020  *
00021  */
00022 require_once dirname(__FILE__).DIRECTORY_SEPARATOR.'simple_html_dom.php';
00023 
00028 class XfHtmlReportBuilder {
00029         
00030         const COMPILE_ERROR = 501;
00031         public static $SUMMARY_FUNCTIONS = array('sum','min','max','count');
00032         public static $ALLOWED_EXPRESSION_FUNCTIONS = array(
00033                 'floor',
00034                 'ceil',
00035                 'round',
00036                 'max',
00037                 'min',
00038                 'abs',
00039                 'acos',
00040                 'acosh',
00041                 'asinh',
00042                 'asin',
00043                 'atan',
00044                 'atan2',
00045                 'atanh',
00046                 'base_convert',
00047                 'bindec',
00048                 'cos',
00049                 'cosh',
00050                 'decbin',
00051                 'dechex',
00052                 'decoct',
00053                 'deg2rad',
00054                 'exp',
00055                 'expm11',
00056                 'fmod',
00057                 'getrandmax',
00058                 'hexdec',
00059                 'hypot',
00060                 'lcd_value',
00061                 'log',
00062                 'log10',
00063                 'log1p',
00064                 'mt_getrandmax',
00065                 'mt_rand',
00066                 'mt_srand',
00067                 'octdec',
00068                 'pi',
00069                 'pow',
00070                 'deg2rad',
00071                 'rand',
00072                 'sin',
00073                 'sinh',
00074                 'sqrt',
00075                 'srand',
00076                 'tan',
00077                 'tanh',
00078                 'M_E',
00079                 'E_EULER',
00080                 'M_LNPI',
00081                 'M_LN2',
00082                 'M_LN10',
00083                 'M_LOG2E',
00084                 'M_LOG10E',
00085                 'M_PI',
00086                 'M_PI2',
00087                 'M_PI4',
00088                 'M_1_PI',
00089                 'M_2_PI',
00090                 'M_SQRT_PI',
00091                 'M_2SQRT_PI',
00092                 'M_SQRT1_2',
00093                 'M_SQRT2',
00094                 'M_SQRT3'
00095         );
00096         
00097         private $templateContent = null;
00098         private $records = null;
00099         private $output = '';
00100         
00101         private $context = null;
00102         
00107         protected $strict = false;
00108         
00109         
00115         public static function getFields($el){
00116                 $out = array();
00117                 if ( preg_match_all('/\{\$([^\}]+)\}/', $el->outertext, $matches, PREG_SET_ORDER) ){
00118                         foreach ($matches as $match){
00119                                 $out[] = trim($match[1]);
00120                         }
00121                 }
00122                 $out = array_unique($out);
00123                 return $out;
00124         }
00125         
00131         public static function getSummaryFields($el){
00132                 $out = array();
00133                 if ( preg_match_all('/\{@([a-zA-Z]+\([^\}]+\))\}/', $el->outertext, $matches, PREG_SET_ORDER) ){
00134                         foreach ($matches as $match){
00135                                 $out[] = trim($match[1]);
00136                         }
00137                 }
00138                 $out = array_unique($out);
00139                 return $out;
00140         
00141         }
00142         
00143         public static function display($record, $key, $displayMethod='htmlValue'){
00144                 if ( isset($record->pouch['__reports_display'][$key]) ){
00145                         if ( $displayMethod == 'strval' ){
00146                                 $out = $record->pouch['__reports_strval'][$key];
00147                         } else {
00148                                 $out = $record->pouch['__reports_display'][$key];
00149                         }
00150                 } else {
00151                         //echo "{About to display ".(2/3)." with $displayMethod for column of ".$record->table()->getType($key)." ".$record->$displayMethod($key)." / ".setlocale(LC_NUMERIC,"0")."}";
00152                         $out = $record->$displayMethod($key);
00153                         
00154                         
00155                         
00156                 }
00157                 if ( $displayMethod == 'strval'){
00158                         
00159                         $locale_data = localeconv();
00160                         $decimal = $locale_data['decimal_point'];
00161                         $out = str_replace($decimal, '.', (string)$out);
00162                         //echo "[Changed decimal place: $out]";
00163                 }
00164                 return $out;
00165         }
00166         
00167         public static function set($record, $key, $value, $strval=null){
00168                 if ( !isset($strval) ) $strval = $value;
00169                 if ( strpos($key, '.') === false ){
00170                         $record->setValue($key, $value);
00171                 } else {
00172                         $record->pouch['__reports_display'][$key] = $value;
00173                         $record->pouch['__reports_strval'][$key] = $strval;
00174                 }
00175         }
00176         
00177         
00188         public static function printGroupData(&$groupData, $groupFields, $summaryFields, $groupHeaders, $groupFooters, $template, $asTable){
00189                 $header = $groupHeaders[$groupData['level']];
00190                 $footer = $groupFooters[$groupData['level']];
00191                 $gfields = $groupFields[$groupData['level']];
00192                 $sfields = $summaryFields[$groupData['level']];
00193                 if ( !@$groupData['summaryRecord'] ){
00194                         throw new Exception("No summary record provided.", 2000);
00195                 }
00196                 $headerText = '';
00197                 if ( $header ){
00198                         
00199                         $headerText = self::fillReportSingle($groupData['summaryRecord'], $header->innertext);
00200                 }
00201                 $bodyText = '';
00202                 if ( isset($groupData['sections']) ){
00203                         foreach ($groupData['sections'] as $key=>$section){
00204                                 $bodyText .= self::printGroupData($groupData['sections'][$key], $groupFields, $summaryFields, $groupHeaders, $groupFooters, $template, $asTable);
00205                                 
00206                         }
00207                 } else if ( isset($groupData['records']) ){
00208                         $bodyText = self::fillReportMultiple($groupData['records'], $template, false /* no headers */, $asTable);
00209                 }
00210                 $footerText = '';
00211                 if ( $footer ){
00212                         $footerText = self::fillReportSingle($groupData['summaryRecord'], $footer->innertext);
00213                 }
00214                 return $headerText.$bodyText.$footerText;
00215         }
00216         
00225         public static function getGroupKey(Dataface_Record $record, $fields){
00226                 $out = array();
00227                 foreach ($fields as $f){
00228                         $out[] = urlencode($f).'='.urlencode($record->val($f));
00229                 }
00230                 return implode('&', $out);
00231         }
00232         
00233         
00244         public static function fillReportTable($records, $template, $headersAndFooters=true){
00245 
00246                 return self::fillReportMultiple($records, $template, $headersAndFooters, true);
00247         }
00248         
00249         
00258         public static function fillReportMultiple($records, $template, $headersAndFooters=true, $asTable=false){
00259                 if ( !is_array($records) ){
00260                         throw new Exception("Records must be an array.", 2000);
00261                 }
00262                 if ( is_string($template) ){
00263                         $el = str_get_html($template);
00264                 } else {
00265                         $el = $template;
00266                 }
00267                 $groupFields = array();
00268                 $summaryFields = array();
00269                 
00270                 $sectionHeaderEls = $el->find('div.xf-htmlreports-section-header');
00271                 $sectionFooterEls = $el->find('div.xf-htmlreports-section-footer');
00272                 $numLevels = max(count($sectionHeaderEls), count($sectionFooterEls));
00273 
00274                 if ( $numLevels > 0 ){
00275                         if ( !$headersAndFooters ){
00276                                 // Caller doesn't want headers and footers.  We clear them out
00277                                 // then process the remainder of the template.
00278                                 foreach ($sectionHeaderEls as $myel){
00279                                         $myel->outertext = '';
00280                                 }
00281                                 foreach ($sectionFooterEls as $myel){
00282                                         $myel->outertext = '';
00283                                 }
00284                                 return self::fillReportMultiple($records, $el->outertext, false, $asTable);
00285                         }
00286                         $groupFooters = array();
00287                         $groupHeaders = array();
00288                         
00289                         
00290                         for ($i=0; $i<$numLevels; $i++){
00291                                 $groupFields[$i] =  $summaryFields[$i] = array();
00292                                 $groupHeaders[$i] = $groupFooters[$i] = null;
00293                         }
00294                         
00295                         for ($i=0; $i<$numLevels; $i++){
00296                         
00297                                 $j = $numLevels-$i-1;
00298                                 
00299                                 if ( isset($sectionHeaderEls[$i]) ){
00300                                         $groupFields[$i] = array_merge($groupFields[$i], self::getFields($sectionHeaderEls[$i]));
00301                                         $summaryFields[$i] = array_merge($summaryFields[$i], self::getSummaryFields($sectionHeaderEls[$i]));
00302                                         $groupHeaders[$i] = $sectionHeaderEls[$i];
00303                                 } 
00304                                 
00305                                 
00306                                 if ( isset($sectionFooterEls[$j]) ){
00307                                         $groupFields[$i] = array_merge($groupFields[$i], self::getFields($sectionFooterEls[$j]));
00308                                         $summaryFields[$i] = array_merge($summaryFields[$i], self::getSummaryFields($sectionFooterEls[$j]));
00309                                         $groupFooters[$i] = $sectionFooterEls[$j];
00310                                 }
00311                                 
00312                         }
00313                         
00314                         for ( $i=0; $i<$numLevels; $i++){
00315                                 $groupFields[$i] = array_unique($groupFields[$i]);
00316                                 $summaryFields[$i] = array_unique($summaryFields[$i]);
00317                                 
00318                         }
00319                         
00320                         
00321                         // Now that we have our groupings and summaries we can proceed to
00322                         // group the records and create summary rows.
00323                         
00324         
00325                         foreach ($records as $record){
00326                                 $node =& $tree;
00327                                 for ( $i=0; $i<$numLevels; $i++){       
00328                                         $groupKey = self::getGroupKey($record, $groupFields[$i]);
00329                                         if ( !isset($node[$groupKey]) ){
00330                                                 $node[$groupKey] = array(
00331                                                         'summaryRecord'=> null,
00332                                                         'level'=>$i
00333                                                 );
00334                                                 if ( $i<$numLevels-1 ){
00335                                                         $node[$groupKey]['sections'] = array();
00336                                                 } else {
00337                                                         $node[$groupKey]['records'] = array();
00338                                                 }
00339                                         }
00340                                         
00341                                         $temp =& $node[$groupKey];
00342                                         unset($node);
00343                                         if ( isset($temp['sections']) ){
00344                                                 $node =& $temp['sections'];
00345                                         } else {
00346                                                 $node =& $temp['records'];
00347                                         }
00348                                         unset($temp);
00349                                         
00350                                 }
00351                                 $node[] = $record;
00352                                 unset($node);
00353                         }
00354                         $out = '';
00355                         foreach (array_keys($tree) as $groupKey){
00356                                 $groupData =& $tree[$groupKey];
00357                                 self::compileGroupData($groupData, $groupFields, $summaryFields);
00358                                 $out .= self::printGroupData($groupData, $groupFields, $summaryFields, $groupHeaders, $groupFooters, $template, $asTable);
00359                                 
00360                                 
00361                         }
00362                 
00363                 } else {
00364                 
00365                         $out = '';
00366                         
00367                                 
00368                         
00369                         if ( !is_array($records) ){
00370                                 throw new Exception("No array of records provided.", 2000);
00371                         }
00372                         
00373                         
00374                         if ( $asTable ){
00375                                 $tableTags = $el->find('table');
00376                                 
00377                                 if ( count($tableTags) == 1 ){
00378                                         $thead = $tableTags[0]->find('thead');
00379                                         $tbody = $tableTags[0]->find('tbody');
00380                                         if ( count($thead) == 1 and count($tbody) == 1){
00381                                                 $bodyout = array();
00382                                                 $rowTemplates = $tableTags[0]->find('tbody tr');
00383                                                 foreach ($records as $rec){
00384                                                         foreach ($rowTemplates as $rowTemplate){
00385                                                                 $bodyout[] = self::fillReportSingle($rec, $rowTemplate->outertext);
00386                                                         }
00387                                                 }
00388                                                 
00389                                                 $tbody[0]->innertext = implode('', $bodyout);
00390                                                 return $el->outertext;
00391                                         }
00392                                         
00393                                         
00394                                 }
00395                                 $fields = self::getFields($el);
00396                                 $rowtemplate = '<tr>';
00397                                 foreach ($fields as $field){
00398                                         $fielddef = $records[0]->table()->getField($field);
00399                                         if ( PEAR::isError($fielddef) ) continue;
00400                                         $rowtemplate .= '<td class="xf-htmlreports-field xf-htmlreports-field-'.str_replace('.','_', $field).'">{$'.$field.'}</td>';
00401                                 } 
00402                                 $rowtemplate .= '</tr>';
00403                                 $template = $rowtemplate;
00404                                 
00405                                 $cols = array();
00406                                 foreach ($fields as $fieldname){
00407                                         $field = $records[0]->table()->getField($fieldname);
00408                                         if ( PEAR::isError($field) ) continue;
00409                                         $label = $field['widget']['label'];
00410                                         if ( @$field['column'] and @$field['column']['label'] ) $label = $field['column']['label'];
00411                                         $cols[] = $label;
00412                                         
00413                                 }
00414                                 
00415                                 $colTemplate = '<tr><th>'.implode('</th><th>', array_map('htmlspecialchars', $cols)).'</th></tr>';
00416                                 $out = '<table><thead>'.$colTemplate.'</thead><tbody>';
00417                         }
00418                         
00419                         foreach ($records as $rec){
00420                                 $out .= self::fillReportSingle($rec, $template);
00421                         }
00422                         if ( $asTable ){
00423                                 $out .= '</tbody></table>';
00424                         }
00425                 }
00426                 return $out;
00427         
00428         }
00429         
00430         
00431         public static function isDelegateField(Dataface_Table $table, $fieldname){
00432                 if ( strpos($fieldname, '.') !== false ){
00433                         list($rel, $fld) = explode('.', $fieldname);
00434                         $relObj = $table->getRelationship($rel);
00435                         if ( PEAR::isError($relObj) ) throw new Exception($relObj->getMessage(), $relObj->getCode());
00436                         $rtable = $relObj->getTable($fld);
00437                         if ( PEAR::isError($rtable) ) throw new Exception($rtable->getMessage(), $rtable->getCode());
00438                         return self::isDelegateField($rtable, $fld);
00439                 } else {
00440                         $flds =& $table->delegateFields(true);
00441                         return isset($flds[$fieldname]);
00442                 }
00443         }
00444         
00451         public static function extractRecordsFromGroupData(&$groupData){
00452         
00453                 $out = array();
00454                 if ( isset($groupData['records']) ){
00455                         return $groupData['records'];
00456                 } else if ( isset($groupData['sections']) ){
00457                         
00458                         foreach ($groupData['sections'] as $key=>$section){
00459                                 $secRecs = self::extractRecordsFromGroupData($groupData['sections'][$key]);
00460                                 foreach ($secRecs as $rec){
00461                                         $out[] = $rec;
00462                                 }
00463                         }
00464                 }
00465                 return $out;
00466         
00467         }
00468         
00469         
00485         public static function compileGroupData(&$groupData, $groupFields, $summaryFields){
00486                 $gfields = $groupFields[$groupData['level']];
00487                 $sfields = $summaryFields[$groupData['level']];
00488                 
00489                 $records = self::extractRecordsFromGroupData($groupData);
00490                 if ( !$records ) return;
00491                 $summaryRecord = new Dataface_Record($records[0]->table()->tablename, array());
00492                 foreach ($gfields as $gf ){
00493                         if ( strpos($gf, '.') === false ){
00494                                 $summaryRecord->setValue($gf, $records[0]->val($gf));
00495                         } else {
00496                                 self::set($summaryRecord, $gf, $records[0]->htmlValue($gf), $records[0]->strval($gf) );
00497                         }
00498                 }
00499                 
00500                 
00501                 foreach ($sfields as $opt){
00502                         $summaryRecord->pouch['summaries'][$opt] = self::evaluateAggregateExpression($records, $opt);
00503                         
00504                 }
00505                 $groupData['summaryRecord'] = $summaryRecord;
00506                 if ( isset($groupData['sections']) ){
00507                         foreach (array_keys($groupData['sections']) as $secid){
00508                                 $secGroupData =& $groupData['sections'][$secid];
00509                                 self::compileGroupData($secGroupData, $groupFields, $summaryFields);
00510                                 unset($secGroupData);
00511                         }
00512                 }
00513                 
00514                 
00515                 
00516         }
00517         
00527         public static function evaluateAggregateExpression(array $records, $expression){
00528         
00529                 $parsed = self::parseExpression($expression);
00530                 switch ($parsed['opt']){
00531                 
00532                         case 'sum':
00533                                 $total = 0;
00534                                 if ( !$records ) return $total;
00535                                 if ( $records[0]->table()->isInt($parsed['field']) ){
00536                                         foreach ($records as $rec){
00537                                                 $total += intval($rec->val($parsed['field']));
00538                                         }
00539                                 } else {
00540                                         foreach ($records as $rec){
00541                                                 $total += doubleval($rec->val($parsed['field']));
00542                                         }
00543                                 }
00544                                 return $total;
00545                                 
00546                         case 'count':
00547                                 return count($records);
00548                                 
00549                         case 'max':
00550                                 $max = 0;
00551                                 if ( !$records ) return $max;
00552                                 if ( $records[0]->table()->isInt($parsed['field']) ){
00553                                         foreach ($records as $rec){
00554                                                 $max = max($max, intval($rec->val($parsed['field'])));
00555                                         }
00556                                         return $max;
00557                                 } else if ( $records[0]->table()->isFloat($parsed['field']) ){
00558                                         foreach ($records as $rec){
00559                                                 $max = max($max, doubleval($rec->val($parsed['field'])));
00560                                         }
00561                                         return $max;
00562                                         
00563                                 } else if ( $records[0]->table()->isDate($parsed['field'])){
00564                                         foreach ($records as $rec){
00565                                                 $max = max($max, strtotime($record->strval($parsed['field'])));
00566                                         }
00567                                         return date('Y-m-d H:i:s', $max);
00568                                 } else {
00569                                         $max = null;
00570                                         foreach ($records as $rec){
00571                                                 if ( !isset($max) ){
00572                                                         $max = $rec->val($parsed['field']);
00573                                                 } else if ( ($currtest = $rec->val($parsed['field'])) > $max ){
00574                                                         $max = $currtest;
00575                                                 }
00576                                         }
00577                                         return $max;
00578                                 }
00579                                 
00580                                 
00581                         case 'min':
00582                                 $min = null;
00583                                 if ( !$records ) return $min;
00584                                 if ( $records[0]->table()->isInt($parsed['field']) ){
00585                                         foreach ($records as $rec){
00586                                                 if ( !isset($min)  ){
00587                                                         $min = intval($rec->val($parsed['field']));
00588                                                 } else {
00589                                                         $min = min($min, intval($rec->val($parsed['field'])));
00590                                                 }
00591                                         }
00592                                         return $min;
00593                                 } else if ( $records[0]->table()->isFloat($parsed['field']) ){
00594                                         foreach ($records as $rec){
00595                                                 if ( !isset($min) ){
00596                                                         $min = doubleval($rec->val($parsed['field']));
00597                                                 } else {
00598                                                         $min = min($min, doubleval($rec->val($parsed['field'])));
00599                                                 }
00600                                         }
00601                                         return $min;
00602                                         
00603                                 } else if ( $records[0]->table()->isDate($parsed['field'])){
00604                                         foreach ($records as $rec){
00605                                                 if ( !isset($min) ){
00606                                                         $min = strtotime($record->strval($parsed['field']));
00607                                                 } else {
00608                                         
00609                                                         $min = min($min, strtotime($record->strval($parsed['field'])));
00610                                                 }
00611                                         }
00612                                         return date('Y-m-d H:i:s', $min);
00613                                 } else {
00614                                         $min = null;
00615                                         foreach ($records as $rec){
00616                                                 if ( !isset($min) ){
00617                                                         $min = $rec->val($parsed['field']);
00618                                                 } else if ( ($currtest = $rec->val($parsed['field'])) > $min ){
00619                                                         $min = $currtest;
00620                                                 }
00621                                         }
00622                                         return $min;
00623                                 }
00624                         default:
00625                                 throw new Exception("Unrecognized aggregate operator: ".$parsed['opt'], self::COMPILE_ERROR);
00626                         
00627                         
00628                 }
00629         }
00630         
00631         
00643         public static function parseExpression($expression){
00644                 $expression = trim($expression);
00645                 if ( preg_match('/^([a-zA-Z]+)\(([^\)]+)\)$/', $expression, $matches)){
00646                         return array(
00647                                 'opt'=>trim(strtolower($matches[1])),
00648                                 'field'=>trim($matches[2])
00649                         );
00650                 } else {
00651                         throw new Exception("Failed to parse expression '".$expression."'.  It does not match the required pattern.");
00652                         
00653                 }
00654         }
00655         
00664         public static function fillReportSingle(Dataface_Record $record, $template, $strict = false){
00665                 if ( !$record ) throw new Exception("Null record provided.", 2000);
00666                 if ( is_string($template) ){
00667                         $el = str_get_html($template);
00668                 } else {
00669                         $el = $template;
00670                 }
00671                 $builder = new XfHtmlReportBuilder();
00672                 if ( $strict ){
00673                         $builder->strict = true;
00674                 }
00675                 return $builder->fillReport($record, $record, $el, '');
00676         }
00677         
00678         
00688         public static function validateTemplate(Dataface_Table $table, $template){
00689                 $rec = new Dataface_Record($table->tablename, array());
00690                 self::fillReportSingle($rec, $template, true);
00691                 return true;
00692         }
00693         
00694         
00703         public function fillReport($root, $record, $el, $basePath='', $contextParams=null){
00704                 $oldContext = $this->context;
00705                 $this->context = new stdClass;
00706                 $this->context->root = $root;
00707                 $this->context->record = $record;
00708                 $this->context->el = $el;
00709                 $this->context->basePath = $basePath;
00710                 $this->context->rrec = null;
00711                 if ( is_array($contextParams)){
00712                         foreach ($contextParams as $k=>$v){
00713                                 $this->context->{$k} = $v;
00714                         }
00715                 }
00716                 if ( !isset($this->context->formatValues) ) $this->context->formatValues = true;
00717                 if ( !isset($this->context->displayMethod) ) $this->context->displayMethod = 'htmlValue';
00718                 
00719                 //$dom = str_get_html($template);
00720                 $rrec = null;
00721                 if ( is_a($record, 'Dataface_RelatedRecord') ){
00722                         $rrec = $record;
00723                         $record = $rrec->toRecord();
00724                         $this->context->rrec = $rrec;
00725                         $this->context->record = $record;
00726                 }
00727                 $uls = $el->find('ul[relationship]');
00728                 foreach ($uls as $ul){
00729                         $rel = $ul->relationship;
00730                         
00731                         if ( $this->strict ){
00732                         
00733                                 $relationship = $root->table()->getRelationship($rel);
00734                                 if ( PEAR::isError($relationship) ){
00735                                         throw new Exception(sprintf(
00736                                                         'Relationship "%s" does not exist in table "%s" in the ul tag: %s',
00737                                                         $rel,
00738                                                         $root->table()->tablename,
00739                                                         str_replace($ul->innertext,'',$ul->outertext)
00740                                                 ),
00741                                                 self::COMPILE_ERROR
00742                                         );
00743                                 }
00744                         }
00745                         
00746                         $relatedRecords = $root->getRelatedRecordObjects($rel);
00747                         $lis = $ul->find('li');
00748                         
00749                         $newlis = array();
00750                         foreach ($relatedRecords as $rrec){
00751                                 foreach ($lis as $li){
00752                                         $newlis[] = $this->fillReport($root, $rrec, $li, $rel.'.' );
00753                                 }
00754                         }
00755                         
00756                         $ul->innertext = implode("\n", $newlis);
00757                         
00758                 }
00759                 
00760                 $ols = $el->find('ol[relationship]');
00761                 foreach ($ols as $ol){
00762                         $rel = $ol->relationship;
00763                         
00764                         
00765                         if ( $this->strict ){
00766                         
00767                                 $relationship = $root->table()->getRelationship($rel);
00768                                 if ( PEAR::isError($relationship) ){
00769                                         throw new Exception(sprintf(
00770                                                         'Relationship "%s" does not exist in table "%s" in the ol tag: %s',
00771                                                         $rel,
00772                                                         $root->table()->tablename,
00773                                                         str_replace($ol->innertext,'',$ol->outertext)
00774                                                 ),
00775                                                 self::COMPILE_ERROR
00776                                         );
00777                                 }
00778                         }
00779                         
00780                         $relatedRecords = $root->getRelatedRecordObjects($rel);
00781                         $lis = $ol->find('li');
00782                         
00783                         $newlis = array();
00784                         foreach ($relatedRecords as $rrec){
00785                                 foreach ($lis as $li){
00786                                         $newlis[] = $this->fillReport($root, $rrec, $li, $rel.'.' );
00787                                 }
00788                         }
00789                         
00790                         $ol->innertext = implode("\n", $newlis);
00791                         
00792                 }
00793                 
00794                 $tables = $el->find('table[relationship]');
00795                 foreach ($tables as $table){
00796                         $rel = $table->relationship;
00797                         
00798                         if ( $this->strict ){
00799                         
00800                                 $relationship = $root->table()->getRelationship($rel);
00801                                 if ( PEAR::isError($relationship) ){
00802                                         throw new Exception(sprintf(
00803                                                         'Relationship "%s" does not exist in table "%s" in the table tag: %s',
00804                                                         $rel,
00805                                                         $root->table()->tablename,
00806                                                         str_replace($table->innertext,'',$table->outertext)
00807                                                 ),
00808                                                 self::COMPILE_ERROR
00809                                         );
00810                                 }
00811                         }
00812                         
00813                         $tbody = $table->find('tbody');
00814                         $relatedRecords = $root->getRelatedRecordObjects($rel);
00815                         $trs = $tbody[0]->find('tr');
00816                         
00817                         $newtrs = array();
00818                         foreach ($relatedRecords as $rrec){
00819                                 foreach ($trs as $tr){
00820                                         $newtrs[] = $this->fillReport($root, $rrec, $tr, $rel.'.' );
00821                                 }
00822                         }
00823                         
00824                         $tbody[0]->innertext = implode("\n", $newtrs);
00825                         
00826                 }
00827                 
00828                 
00829                 $content = $el->outertext;
00830                 
00831                 $prevContent = $content;
00832                 while ( preg_match('#\{%[\s\S]+?%\}#', $content) ){
00833                         $content = preg_replace_callback('#\{%([\s\S]+?)%\}#', array($this, '_replace_expressions'), $content);
00834                         if ( $content == $prevContent ) break;  // no change made this round... prevent infinite loop
00835                         $prevContent = $content;
00836                 }
00837                 
00838                 $content = preg_replace_callback('#\{\$([a-zA-Z0-9_\.]+)\}#', array($this, '_replace_fields'), $content);
00839                 
00840                 $content = preg_replace_callback('#\{@([a-zA-Z]+\([a-zA-Z0-9_\.]+\))\}#', array($this, '_replace_summary_fields'), $content);
00841                 
00842                 
00843                 
00844                 
00845                 $this->context = $oldContext;
00846                 return $content;
00847                 
00848                 
00849         
00850         }
00851         
00852         function _replace_expressions($matches){
00853                 //$oldLocale = setlocale(LC_NUMERIC, "0");
00854                 //setlocale(LC_NUMERIC, "en_US");
00855                 $matches[1] = str_replace(' ', '', $matches[1]);
00856                 $el = str_get_html('<html><body>'.$matches[1].'</body></html>');
00857                 
00858                 $out = $this->fillReport($this->context->root, $this->context->record, $el, $this->context->basePath, array(
00859                         'displayMethod' => 'strval',
00860                         'formatValues' => false
00861                 ));
00862                 
00863                 if ( preg_match('/^<html><body>([\s\S]+)<\/body><\/html>$/', (string)$out, $omatches)){
00864                         $out = preg_replace('/&[a-zA-Z]{2,6};/', '', $omatches[1]);
00865                 } else {
00866                         throw new Exception("Error parsing after filling report:". htmlspecialchars($out));
00867                 }
00868                 
00869                 $out = strip_tags($out);
00870                 
00871                 //echo '['.htmlspecialchars($out).']';
00872                 // make sure that we only have arithmetic
00873                 $pattern = array();
00874                 foreach (self::$ALLOWED_EXPRESSION_FUNCTIONS as $f){
00875                         $pattern[] = '/\b'.preg_quote($f, '/').'\b/i';
00876                 }
00877                 $stripped = preg_replace($pattern, '', $out);
00878                 
00879                 
00880                 if ( preg_match('#[^0-9\.\,\+\-\*/\(\) ]#', $stripped ) ){
00881                         throw new Exception("Illegal Expression: ".htmlspecialchars($out)." produced by ".htmlspecialchars($matches[1]));
00882                 }
00883                 eval('$out='.$out.';');
00884                 //setlocale(LC_NUMERIC, $oldLocale);
00885                 return $out;
00886         }       
00887         
00888         
00894         function _replace_fields($matches){
00895                 //print_r($matches);
00896                 if ( $this->strict ){
00897                         
00898                         if ( !$this->context->root->table()->hasField($matches[1]) ){
00899                                 throw new Exception(sprintf(
00900                                                 'Field "%s" does not exist in table "%s" so the macro "%s" cannot be resolved.',
00901                                                 $matches[1],
00902                                                 $this->context->root->table()->tablename,
00903                                                 $matches[0]
00904                                         ),
00905                                         self::COMPILE_ERROR
00906                                 );
00907                         }
00908                 }
00909                 //echo "{Context display method is ".$this->context->displayMethod."}";
00910                 $displayMethod = $this->context->displayMethod or 'htmlValue';
00911                 $parts = explode('.', $matches[1]);
00912                 if ( count($parts)>1 and $this->context->rrec ){
00913                         $rel = $parts[0];
00914                         $fld = $parts[1];
00915                         
00916                         if ( isset($this->context->rrec) and $this->context->rrec->_relationshipName == $rel ){
00917                                 $out =  $this->context->rrec->$displayMethod($fld);
00918                                 
00919                                 if ( $displayMethod == 'strval' ){
00920                                         $locale_data = localeconv();
00921                                         $decimal = $locale_data['decimal_point'];
00922                                         $out = str_replace($decimal, '.', $out);
00923                                 }
00924                                 return $out;
00925                         } else {
00926                                 //echo "Related record: ".$this->context->rrec->_relationshipName;
00927                         }
00928                 } else {
00929                         //echo "Getting field ".$parts[0].".";
00930                         if ( !$this->context->root ){
00931                                 throw new Exception("NO root record to replace fields from.", 2000);
00932                         }
00933                         //return $this->context->root->htmlValue($matches[1]);
00934                         return self::display($this->context->root, $matches[1], $displayMethod);
00935                 }
00936                 
00937                 return $matches[0];
00938         }
00939         
00940         
00944         function _replace_summary_fields($matches){
00945                 //print_r($matches);
00946                 
00947                 //echo "Getting field ".$parts[0].".";
00948                 if ( !$this->context->root ){
00949                         throw new Exception("NO root record to replace fields from.", 2000);
00950                 }
00951                 if ( isset($this->context->root->pouch['summaries'][$matches[1]]) ){
00952                         $val = $this->context->root->pouch['summaries'][$matches[1]];
00953                         $expr = self::parseExpression($matches[1]);
00954                         $fld = $expr['field'];
00955                         
00956                         if ( $this->strict ){
00957                         
00958                                 if ( !$this->context->root->table()->hasField($fld) ){
00959                                         throw new Exception(sprintf(
00960                                                         'Field "%s" does not exist in table "%s" so the summary macro "%s" cannot be resolved.',
00961                                                         $fld,
00962                                                         $this->context->root->table()->tablename,
00963                                                         $matches[0]
00964                                                 ),
00965                                                 self::COMPILE_ERROR
00966                                         );
00967                                 }
00968                                 
00969                                 if ( !in_array($expr['opt'] , self::$SUMMARY_FUNCTIONS) ){
00970                                         throw new Exception(sprintf(
00971                                                         'Summary function "%s" is not supported.  Currently only %s are supported.  (Found in macro "%s").',
00972                                                         $expr['opt'],
00973                                                         '"'.implode('", "', self::$SUMMARY_FUNCTIONS).'"',
00974                                                         $matches[0]
00975                                                 ),
00976                                                 self::COMPILE_ERROR
00977                                         );
00978                                         
00979                                 }
00980                         }
00981                         //if ( self::isDelegateField($this->context->root->table(), $fld) ) return $val;
00982                         
00983                         //$old = $this->context->root->val($fld);
00984                         //self::set($this->context->root,$fld, $val);
00985                         //$out = self::display($this->context->root,$fld);
00986                         //self::set($this->context->root, $fld, $old);
00987                         if ( @$this->context->formatValues ){
00988                                 $out = $this->context->root->table()->format($fld, $val);
00989                         } else {
00990                                 $locale_data = localeconv();
00991                                 $decimal = $locale_data['decimal_point'];
00992                                 $out = str_replace($decimal, '.', (string)$val);
00993                                 //echo "[Changed decimal place: $out]";
00994                         
00995                                 
00996                                 
00997                         }
00998                         return $out;
00999                 }
01000                 //return $this->context->root->htmlValue($parts[0]);
01001         
01002         
01003                 return $matches[0];
01004         }
01005         
01006 }
 All Data Structures Files Functions Variables Enumerations