Xataface HTML Reports Module 0.2
HTML Reports Module for Xataface
|
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 }