PageRenderTime 209ms CodeModel.GetById 80ms app.highlight 55ms RepoModel.GetById 66ms app.codeStats 0ms

/test.php

http://github.com/digitalbazaar/php-json-ld
PHP | 765 lines | 540 code | 67 blank | 158 comment | 70 complexity | 956fc58928edd9a372c3b42387f9de5e MD5 | raw file
  1<?php
  2/**
  3 * PHP unit tests for JSON-LD.
  4 *
  5 * @author Dave Longley
  6 *
  7 * Copyright (c) 2013-2014 Digital Bazaar, Inc. All rights reserved.
  8 */
  9require_once('jsonld.php');
 10
 11class JsonLdTestCase extends PHPUnit_Framework_TestCase {
 12  /**
 13   * Runs this test case. Overridden to attach to EARL report w/o need for
 14   * an external XML configuration file.
 15   *
 16   * @param PHPUnit_Framework_TestResult $result the test result.
 17   */
 18  public function run(PHPUnit_Framework_TestResult $result = NULL) {
 19    global $EARL;
 20    $EARL->attach($result);
 21    $this->result = $result;
 22    parent::run($result);
 23  }
 24
 25  /**
 26   * Tests expansion.
 27   *
 28   * @param JsonLdTest $test the test to run.
 29   *
 30   * @group expand
 31   * @group json-ld.org
 32   * @dataProvider expandProvider
 33   */
 34  public function testExpand($test) {
 35    $this->test = $test;
 36    $input = $test->readUrl('input');
 37    $options = $test->createOptions();
 38    $test->run('jsonld_expand', array($input, $options));
 39  }
 40
 41  /**
 42   * Tests compaction.
 43   *
 44   * @param JsonLdTest $test the test to run.
 45   *
 46   * @group compact
 47   * @group json-ld.org
 48   * @dataProvider compactProvider
 49   */
 50  public function testCompact($test) {
 51    $this->test = $test;
 52    $input = $test->readUrl('input');
 53    $context = $test->readProperty('context');
 54    $options = $test->createOptions();
 55    $test->run('jsonld_compact', array($input, $context, $options));
 56  }
 57
 58  /**
 59   * Tests flatten.
 60   *
 61   * @param JsonLdTest $test the test to run.
 62   *
 63   * @group flatten
 64   * @group json-ld.org
 65   * @dataProvider flattenProvider
 66   */
 67  public function testFlatten($test) {
 68    $this->test = $test;
 69    $input = $test->readUrl('input');
 70    $context = $test->readProperty('context');
 71    $options = $test->createOptions();
 72    $test->run('jsonld_flatten', array($input, $context, $options));
 73  }
 74
 75  /**
 76   * Tests serialization to RDF.
 77   *
 78   * @param JsonLdTest $test the test to run.
 79   *
 80   * @group toRdf
 81   * @group json-ld.org
 82   * @dataProvider toRdfProvider
 83   */
 84  public function testToRdf($test) {
 85    $this->test = $test;
 86    $input = $test->readUrl('input');
 87    $options = $test->createOptions(array('format' => 'application/nquads'));
 88    $test->run('jsonld_to_rdf', array($input, $options));
 89  }
 90
 91  /**
 92   * Tests deserialization from RDF.
 93   *
 94   * @param JsonLdTest $test the test to run.
 95   *
 96   * @group fromRdf
 97   * @group json-ld.org
 98   * @dataProvider fromRdfProvider
 99   */
100  public function testFromRdf($test) {
101    $this->test = $test;
102    $input = $test->readProperty('input');
103    $options = $test->createOptions(array('format' => 'application/nquads'));
104    $test->run('jsonld_from_rdf', array($input, $options));
105  }
106
107  /**
108   * Tests framing.
109   *
110   * @param JsonLdTest $test the test to run.
111   *
112   * @group frame
113   * @group json-ld.org
114   * @dataProvider frameProvider
115   */
116  public function testFrame($test) {
117    $this->test = $test;
118    $input = $test->readUrl('input');
119    $frame = $test->readProperty('frame');
120    $options = $test->createOptions();
121    $test->run('jsonld_frame', array($input, $frame, $options));
122  }
123
124  /**
125   * Tests normalization.
126   *
127   * @param JsonLdTest $test the test to run.
128   *
129   * @group normalize
130   * @group json-ld.org
131   * @dataProvider normalizeProvider
132   */
133  public function testNormalize($test) {
134    $this->test = $test;
135    $input = $test->readUrl('input');
136    $options = $test->createOptions(array('format' => 'application/nquads'));
137    $test->run('jsonld_normalize', array($input, $options));
138  }
139
140  /**
141   * Tests URGNA2012 normalization.
142   *
143   * @param JsonLdTest $test the test to run.
144   *
145   * @group normalize
146   * @group normalization
147   * @dataProvider urgna2012Provider
148   */
149  public function testUrgna2012($test) {
150    $this->test = $test;
151    $input = $test->readProperty('action');
152    $options = $test->createOptions(array(
153      'algorithm' => 'URGNA2012',
154      'inputFormat' => 'application/nquads',
155      'format' => 'application/nquads'));
156    $test->run('jsonld_normalize', array($input, $options));
157  }
158
159  /**
160   * Tests URDNA2015 normalization.
161   *
162   * @param JsonLdTest $test the test to run.
163   *
164   * @group normalize
165   * @group normalization
166   * @dataProvider urdna2015Provider
167   */
168  public function testUrdna2015($test) {
169    $this->test = $test;
170    $input = $test->readProperty('action');
171    $options = $test->createOptions(array(
172      'algorithm' => 'URDNA2015',
173      'inputFormat' => 'application/nquads',
174      'format' => 'application/nquads'));
175    $test->run('jsonld_normalize', array($input, $options));
176  }
177
178  public function expandProvider() {
179    return new JsonLdTestIterator('jld:ExpandTest');
180  }
181
182  public function compactProvider() {
183    return new JsonLdTestIterator('jld:CompactTest');
184  }
185
186  public function flattenProvider() {
187    return new JsonLdTestIterator('jld:FlattenTest');
188  }
189
190  public function toRdfProvider() {
191    return new JsonLdTestIterator('jld:ToRDFTest');
192  }
193
194  public function fromRdfProvider() {
195    return new JsonLdTestIterator('jld:FromRDFTest');
196  }
197
198  public function normalizeProvider() {
199    return new JsonLdTestIterator('jld:NormalizeTest');
200  }
201
202  public function frameProvider() {
203    return new JsonLdTestIterator('jld:FrameTest');
204  }
205
206  public function urgna2012Provider() {
207    return new JsonLdTestIterator('rdfn:Urgna2012EvalTest');
208  }
209
210  public function urdna2015Provider() {
211    return new JsonLdTestIterator('rdfn:Urdna2015EvalTest');
212  }
213}
214
215class JsonLdManifest {
216  public function __construct($data, $filename) {
217    $this->data = $data;
218    $this->filename = $filename;
219    $this->dirname = dirname($filename);
220  }
221
222  public function load(&$tests) {
223    $entries = array_merge(
224      JsonLdProcessor::getValues($this->data, 'sequence'),
225      JsonLdProcessor::getValues($this->data, 'entries'));
226    $includes = JsonLdProcessor::getValues($this->data, 'include');
227    foreach($includes as $include) {
228      array_push($entries, $include . '.jsonld');
229    }
230    foreach($entries as $entry) {
231      if(is_string($entry)) {
232        $filename = join(
233          DIRECTORY_SEPARATOR, array($this->dirname, $entry));
234        $entry = Util::readJson($filename);
235      } else {
236        $filename = $this->filename;
237      }
238
239      if(JsonLdProcessor::hasValue($entry, '@type', 'mf:Manifest') ||
240        JsonLdProcessor::hasValue($entry, 'type', 'mf:Manifest')) {
241        // entry is another manifest
242        $manifest = new JsonLdManifest($entry, $filename);
243        $manifest->load($tests);
244      } else {
245        // assume entry is a test
246        $test = new JsonLdTest($this, $entry, $filename);
247        $types = array_merge(
248           JsonLdProcessor::getValues($test->data, '@type'),
249           JsonLdProcessor::getValues($test->data, 'type'));
250        foreach($types as $type) {
251          if(!isset($tests[$type])) {
252            $tests[$type] = array();
253          }
254          $tests[$type][] = $test;
255        }
256      }
257    }
258  }
259}
260
261class JsonLdTest {
262  public function __construct($manifest, $data, $filename) {
263    $this->manifest = $manifest;
264    $this->data = $data;
265    $this->filename = $filename;
266    $this->dirname = dirname($filename);
267    $this->isPositive =
268      JsonLdProcessor::hasValue(
269        $data, '@type', 'jld:PositiveEvaluationTest') ||
270      JsonLdProcessor::hasValue(
271        $data, 'type', 'jld:PositiveEvaluationTest');
272    $this->isNegative =
273      JsonLdProcessor::hasValue(
274        $data, '@type', 'jld:NegativeEvaluationTest') ||
275      JsonLdProcessor::hasValue(
276        $data, 'type', 'jld:NegativeEvaluationTest');
277
278    // generate test name
279    if(isset($manifest->data->name)) {
280      $manifestLabel = $manifest->data->name;
281    } else if(isset($manifest->data->label)) {
282      $manifestLabel = $manifest->data->label;
283    } else {
284      $manifestLabel = 'UNNAMED';
285    }
286    if(isset($this->data->id)) {
287      $testId = $this->data->id;
288    } else {
289      $testId = $this->data->{'@id'};
290    }
291    if(isset($this->data->name)) {
292      $testLabel = $this->data->name;
293    } else if(isset($this->data->label)) {
294      $testLabel = $this->data->label;
295    } else {
296      $testLabel = 'UNNAMED';
297    }
298    
299    $this->name = $manifestLabel . ' ' . $testId . ' - ' . $testLabel;
300
301    // expand @id and input base
302    if(isset($manifest->data->baseIri)) {
303      $data->{'@id'} = ($manifest->data->baseIri .
304        basename($manifest->filename) . $data->{'@id'});
305      $this->base = $manifest->data->baseIri . $data->input;
306    }
307  }
308
309  private function _getResultProperty() {
310    if(isset($this->data->expect)) {
311      return 'expect';
312    } else if(isset($this->data->result)) {
313      return 'result';
314    } else {
315      throw new Exception('No test result property found.');
316    }
317  }
318
319  public function run($fn, $params) {
320    // read expected data
321    if($this->isNegative) {
322      $this->expected = $this->data->expect;
323    } else {
324      $this->expected = $this->readProperty($this->_getResultProperty());
325    }
326
327    try {
328      $this->actual = call_user_func_array($fn, $params);
329      if($this->isNegative) {
330        throw new Exception('Expected an error; one was not raised.');
331      }
332      PHPUnit_Framework_TestCase::assertEquals($this->expected, $this->actual);
333    } catch(Exception $e) {
334     // assume positive test
335      if($this->isNegative) {
336        $this->actual = $this->getJsonLdErrorCode($e);
337        PHPUnit_Framework_TestCase::assertEquals(
338          $this->expected, $this->actual);
339      } else {
340        throw $e;
341      }
342    }
343  }
344
345  public function readUrl($property) {
346    if(!property_exists($this->data, $property)) {
347      return null;
348    }
349    return $this->manifest->data->baseIri . $this->data->{$property};
350  }
351
352  public function readProperty($property) {
353    $data = $this->data;
354    if(!property_exists($data, $property)) {
355      return null;
356    }
357    $filename = join(
358      DIRECTORY_SEPARATOR, array($this->dirname, $data->{$property}));
359    $extension = pathinfo($filename, PATHINFO_EXTENSION);
360    if($extension === 'jsonld') {
361      return Util::readJson($filename);
362    }
363    return Util::readFile($filename);
364  }
365
366  public function createOptions($opts=array()) {
367    $http_options = array(
368      'contentType', 'httpLink', 'httpStatus', 'redirectTo');
369    $test_options = (property_exists($this->data, 'option') ?
370      $this->data->option : array());
371    $options = array();
372    foreach($test_options as $k => $v) {
373      if(!in_array($k, $http_options)) {
374        $options[$k] = $v;
375      }
376    }
377    $options['documentLoader'] = $this->createDocumentLoader();
378    $options = array_merge($options, $opts);
379    if(isset($options['expandContext'])) {
380      $filename = join(
381        DIRECTORY_SEPARATOR, array($this->dirname, $options['expandContext']));
382      $options['expandContext'] = Util::readJson($filename);
383    }
384    return $options;
385  }
386
387  public function createDocumentLoader() {
388    $base = 'http://json-ld.org/test-suite';
389    $test = $this;
390
391    $load_locally = function($url) use ($test, $base) {
392      $doc = (object)array(
393        'contextUrl' => null, 'documentUrl' => $url, 'document' => null);
394      $options = (property_exists($test->data, 'option') ?
395        $test->data->option : null);
396      if($options and $url === $test->base) {
397        if(property_exists($options, 'redirectTo') &&
398          property_exists($options, 'httpStatus') &&
399          $options->httpStatus >= '300') {
400          $doc->documentUrl = ($test->manifest->data->baseIri .
401            $options->redirectTo);
402        } else if(property_exists($options, 'httpLink')) {
403          $content_type = (property_exists($options, 'contentType') ?
404            $options->contentType : null);
405          $extension = pathinfo($url, PATHINFO_EXTENSION);
406          if(!$content_type && $extension === 'jsonld') {
407            $content_type = 'application/ld+json';
408          }
409          $link_header = $options->httpLink;
410          if(is_array($link_header)) {
411            $link_header = join(',', $link_header);
412          }
413          $link_header = jsonld_parse_link_header($link_header);
414          if(isset($link_header['http://www.w3.org/ns/json-ld#context'])) {
415            $link_header = $link_header['http://www.w3.org/ns/json-ld#context'];
416          } else {
417            $link_header = null;
418          }
419          if($link_header && $content_type !== 'application/ld+json') {
420            if(is_array($link_header)) {
421              throw new Exception('multiple context link headers');
422            }
423            $doc->contextUrl = $link_header->target;
424          }
425        }
426      }
427      global $ROOT_MANIFEST_DIR;
428      if(strpos($doc->documentUrl, ':') === false) {
429        $filename = join(
430          DIRECTORY_SEPARATOR, array(
431            $ROOT_MANIFEST_DIR, $doc->documentUrl));
432        $doc->documentUrl = 'file://' . $filename;
433      } else {
434        $filename = join(
435          DIRECTORY_SEPARATOR, array(
436            $ROOT_MANIFEST_DIR, substr($doc->documentUrl, strlen($base))));
437      }
438      try {
439        $doc->document = Util::readJson($filename);
440      } catch(Exception $e) {
441        throw new Exception('loading document failed');
442      }
443      return $doc;
444    };
445
446    $local_loader = function($url) use ($test, $base, $load_locally) {
447      // always load remote-doc and non-base tests remotely
448      if((strpos($url, $base) !== 0 && strpos($url, ':') !== false) ||
449        $test->manifest->data->name === 'Remote document') {
450        return call_user_func('jsonld_default_document_loader', $url);
451      }
452
453      // attempt to load locally
454      return call_user_func($load_locally, $url);
455    };
456
457    return $local_loader;
458  }
459
460  public function getJsonLdErrorCode($err) {
461    if($err instanceof JsonLdException) {
462      if($err->getCode()) {
463        return $err->getCode();
464      }
465      if($err->cause) {
466        return $this->getJsonLdErrorCode($err->cause);
467      }
468    }
469    return $err->getMessage();
470  }
471}
472
473class JsonLdTestIterator implements Iterator {
474  /**
475   * The current test index.
476   */
477  protected $index = 0;
478
479  /**
480   * The total number of tests.
481   */
482  protected $count = 0;
483
484  /**
485   * Creates a TestIterator.
486   *
487   * @param string $type the type of tests to iterate over.
488   */
489  public function __construct($type) {
490    global $TESTS;
491    if(isset($TESTS[$type])) {
492      $this->tests = $TESTS[$type];
493    } else {
494      $this->tests = array();
495    }
496    $this->count = count($this->tests);
497  }
498
499  /**
500   * Gets the parameters for the next test.
501   *
502   * @return assoc the parameters for the next test.
503   */
504  public function current() {
505    return array('test' => $this->tests[$this->index]);
506  }
507
508  /**
509   * Gets the current test number.
510   *
511   * @return int the current test number.
512   */
513  public function key() {
514    return $this->index;
515  }
516
517  /**
518   * Proceeds to the next test.
519   */
520  public function next() {
521    $this->index += 1;
522  }
523
524  /**
525   * Rewinds to the first test.
526   */
527  public function rewind() {
528    $this->index = 0;
529  }
530
531  /**
532   * Returns true if there are more tests to be run.
533   *
534   * @return bool true if there are more tests to be run.
535   */
536  public function valid() {
537    return $this->index < $this->count;
538  }
539}
540
541class EarlReport extends PHPUnit_Util_Printer
542  implements PHPUnit_Framework_TestListener {
543  public function __construct() {
544    $this->filename = null;
545    $this->attached = false;
546    $this->report = (object)array(
547      '@context' => (object)array(
548        'doap' => 'http://usefulinc.com/ns/doap#',
549        'foaf' => 'http://xmlns.com/foaf/0.1/',
550        'dc' => 'http://purl.org/dc/terms/',
551        'earl' => 'http://www.w3.org/ns/earl#',
552        'xsd' => 'http://www.w3.org/2001/XMLSchema#',
553        'doap:homepage' => (object)array('@type' => '@id'),
554        'doap:license' => (object)array('@type' => '@id'),
555        'dc:creator' => (object)array('@type' => '@id'),
556        'foaf:homepage' => (object)array('@type' => '@id'),
557        'subjectOf' => (object)array('@reverse' => 'earl:subject'),
558        'earl:assertedBy' => (object)array('@type' => '@id'),
559        'earl:mode' => (object)array('@type' => '@id'),
560        'earl:test' => (object)array('@type' => '@id'),
561        'earl:outcome' => (object)array('@type' => '@id'),
562        'dc:date' => (object)array('@type' => 'xsd:date')
563      ),
564      '@id' => 'https://github.com/digitalbazaar/php-json-ld',
565      '@type' => array('doap:Project', 'earl:TestSubject', 'earl:Software'),
566      'doap:name' => 'php-json-ld',
567      'dc:title' => 'php-json-ld',
568      'doap:homepage' => 'https://github.com/digitalbazaar/php-json-ld',
569      'doap:license' => 'https://github.com/digitalbazaar/php-json-ld/blob/master/LICENSE',
570      'doap:description' => 'A JSON-LD processor for PHP',
571      'doap:programming-language' => 'PHP',
572      'dc:creator' => 'https://github.com/dlongley',
573      'doap:developer' => (object)array(
574        '@id' => 'https://github.com/dlongley',
575        '@type' => array('foaf:Person', 'earl:Assertor'),
576        'foaf:name' => 'Dave Longley',
577        'foaf:homepage' => 'https://github.com/dlongley'
578      ),
579      'dc:date' => array(
580        '@value' => gmdate('Y-m-d'),
581        '@type' => 'xsd:date'
582      ),
583      'subjectOf' => array()
584    );
585  }
586
587  /**
588   * Attaches to the given test result, if not yet attached.
589   *
590   * @param PHPUnit_Framework_Test $result the result to attach to.
591   */
592  public function attach(PHPUnit_Framework_TestResult $result) {
593    if(!$this->attached && $this->filename) {
594      $this->attached = true;
595      $result->addListener($this);
596    }
597  }
598
599  /**
600   * Adds an assertion to this EARL report.
601   *
602   * @param JsonLdTest $test the JsonLdTest for the assertion is for.
603   * @param bool $passed whether or not the test passed.
604   */
605  public function addAssertion($test, $passed) {
606    $this->report->{'subjectOf'}[] = (object)array(
607      '@type' => 'earl:Assertion',
608      'earl:assertedBy' => $this->report->{'doap:developer'}->{'@id'},
609      'earl:mode' => 'earl:automatic',
610      'earl:test' => $test->data->{'@id'},
611      'earl:result' => (object)array(
612        '@type' => 'earl:TestResult',
613        'dc:date' => gmdate(DateTime::ISO8601),
614        'earl:outcome' => $passed ? 'earl:passed' : 'earl:failed'
615      )
616    );
617    return $this;
618  }
619
620  /**
621   * Writes this EARL report to a file.
622   */
623  public function flush() {
624    if($this->filename) {
625      printf("\nWriting EARL report to: %s\n", $this->filename);
626      $fd = fopen($this->filename, 'w');
627      fwrite($fd, Util::jsonldEncode($this->report));
628      fclose($fd);
629    }
630  }
631
632  public function endTest(PHPUnit_Framework_Test $test, $time) {
633    $this->addAssertion($test->test, true);
634  }
635
636  public function addError(
637    PHPUnit_Framework_Test $test, Exception $e, $time) {
638    $this->addAssertion($test->test, false);
639  }
640
641  public function addFailure(
642    PHPUnit_Framework_Test $test,
643    PHPUnit_Framework_AssertionFailedError $e, $time) {
644    $this->addAssertion($test->test, false);
645    if($test->result->shouldStop()) {
646      if(isset($test->test->name)) {
647        $name = $test->test->name;
648      } else if(isset($test->test->label)) {
649        $name = $test->test->label;
650      } else {
651        $name = 'UNNAMED';
652      }
653      printf("\n\nFAILED\n");
654      printf("Test: %s\n", $name);
655      printf("Purpose: %s\n", $test->test->data->purpose);
656      printf("EXPECTED: %s\n", Util::jsonldEncode($test->test->expected));
657      printf("ACTUAL: %s\n", Util::jsonldEncode($test->test->actual));
658    }
659  }
660
661  public function addIncompleteTest(
662    PHPUnit_Framework_Test $test, Exception $e, $time) {
663    $this->addAssertion($test->test, false);
664  }
665
666  public function addRiskyTest(
667    PHPUnit_Framework_Test $test, Exception $e, $time) {}
668  public function addSkippedTest(
669    PHPUnit_Framework_Test $test, Exception $e, $time) {}
670  public function startTest(PHPUnit_Framework_Test $test) {}
671  public function startTestSuite(PHPUnit_Framework_TestSuite $suite) {}
672  public function endTestSuite(PHPUnit_Framework_TestSuite $suite) {}
673}
674
675class Util {
676  public static function readFile($filename) {
677    $rval = @file_get_contents($filename);
678    if($rval === false) {
679      throw new Exception('File read error: ' . $filename);
680    }
681    return $rval;
682  }
683
684  public static function readJson($filename) {
685    $rval = json_decode(self::readFile($filename));
686    if($rval === null) {
687      throw new Exception('JSON parse error');
688    }
689    return $rval;
690  }
691
692  public static function readNQuads($filename) {
693    return self::readFile($filename);
694  }
695
696  public static function jsonldEncode($input) {
697    // newer PHP has a flag to avoid escaped '/'
698    if(defined('JSON_UNESCAPED_SLASHES')) {
699      $options = JSON_UNESCAPED_SLASHES;
700      if(defined('JSON_PRETTY_PRINT')) {
701        $options |= JSON_PRETTY_PRINT;
702      }
703      $json = json_encode($input, $options);
704    } else {
705      // use a simple string replacement of '\/' to '/'.
706      $json = str_replace('\\/', '/', json_encode($input));
707    }
708    return $json;
709  }
710}
711
712// tests to skip
713$SKIP_TESTS = array();
714
715// root manifest directory
716$ROOT_MANIFEST_DIR;
717
718// parsed tests; keyed by type
719$TESTS = array();
720
721// parsed command line options
722$OPTIONS = array();
723
724// parse command line options
725global $argv;
726$args = $argv;
727$total = count($args);
728$start = false;
729for($i = 0; $i < $total; ++$i) {
730  $arg = $args[$i];
731  if(!$start) {
732    if(realpath($arg) === realpath(__FILE__)) {
733      $start = true;
734    }
735    continue;
736  }
737  if($arg[0] !== '-') {
738    break;
739  }
740  $i += 1;
741  $OPTIONS[$arg] = $args[$i];
742}
743if(!isset($OPTIONS['-d'])) {
744  $dvar = 'path to json-ld.org/test-suite';
745  $evar = 'file to write EARL report to';
746  echo "php-json-ld Tests\n";
747  echo "Usage: phpunit test.php -d <$dvar> [-e <$evar>]\n\n";
748  exit(0);
749}
750
751// EARL Report
752$EARL = new EarlReport();
753if(isset($OPTIONS['-e'])) {
754  $EARL->filename = $OPTIONS['-e'];
755}
756
757// load root manifest
758$ROOT_MANIFEST_DIR = realpath($OPTIONS['-d']);
759$filename = join(
760  DIRECTORY_SEPARATOR, array($ROOT_MANIFEST_DIR, 'manifest.jsonld'));
761$root_manifest = Util::readJson($filename);
762$manifest = new JsonLdManifest($root_manifest, $filename);
763$manifest->load($TESTS);
764
765/* end of file, omit ?> */