1<?php
2require_once 'Zorba/zorba_api_wrapper.php';
3
4class XQueryCompilerException extends Exception{}
5class XQueryProcessorException extends Exception{}
6
7/**
8 * Iterate over a sequence of XQuery items.
9 * This class implements the SPL Iterator interface.
10 *
11 * The following code snippet iterates over a small sequence of items.
12 * <code>
13 * <?php
14 * require_once 'Zorba/XQueryProcessor.php';
15 *
16 * $xquery = new XQueryProcessor();
17 * $xquery->importQuery('(1, 2, 3)');
18 *
19 * $iterator = $xquery->getIterator();
20 * foreach($it as $key => $value) {
21 *   echo $value."\n";
22 * }
23 * ?>
24 * </code>
25 */
26class XQueryIterator implements Iterator {
27
28  private $xquery   = null;
29  private $iterator = null;
30  private $item     = null;
31  private $position = 0;
32  private $valid    = false;
33
34  /**
35   * XQueryIterator constructor.
36   * This constructor is used internally only.
37   *
38   * @param XQuery XQuery object.
39   *
40   * @see XQueryProcessor::getIterator()
41   */
42  public function __construct(XQuery $xquery)
43  {
44    $this->xquery = $xquery;
45    $this->item = Item::createEmptyItem();
46  }
47
48
49  public function __destruct()
50  {
51    $this->xquery->destroy();
52  }
53
54  /**
55   * Rewinds back to the first item of the Iterator.
56   */
57  public function rewind()
58  {
59    if ($this->iterator != null) {
60      $this->iterator->close();
61      $this->iterator->destroy();
62    }
63
64    $this->position = 0;
65    $this->iterator = $this->xquery->iterator();
66    $this->iterator->open();
67    $this->valid    = $this->iterator->next($this->item);
68  }
69
70  /**
71   * Return the current item serialized as string.
72   *
73   * @return string The current item serialized as string.
74   *
75   */
76  public function current()
77  {
78    return $this->item->serialize();
79  }
80
81  /**
82   * Return the position of current item.
83   *
84   * @return interger the position of the current item.
85   */
86  public function key()
87  {
88    return $this->position;
89  }
90
91  /**
92   * Move forward to the next item
93   *
94   */
95  public function next()
96  {
97    ++$this->position;
98    $this->valid = $this->iterator->next($this->item);
99  }
100
101  /**
102   * Checks if there are still items to process.
103   *
104   * @return boolean Returns true if the iterator has still items to process or false otherwise.
105   */
106  public function valid()
107  {
108    return $this->valid;
109  }
110}
111
112/**
113 * The XQueryProcessor class allows to invoke the
114 * <a href="http://www.zorba-xquery.com">Zorba XQuery Processor</a>.
115 *
116 * Instructions to install the extension can be found at <a href="http://www.zorba-xquery.com/site2/html/php.html">http://www.zorba-xquery.com/site2/html/php.html</a>.
117 *
118 * The following code snippet executes a small <em>Hello World</em> program:
119 * <code>
120 * <?php
121 * require_once 'ZorbaXQueryProcessor.php';
122 *
123 * $xquery = new XQueryProcessor();
124 *
125 * $query = <<<'XQ'
126 * declare variable $name external;
127 *
128 * <h1>Hello {$name}</h1>
129 * XQ;
130 *
131 * $xquery->importQuery($query);
132 *
133 * $xquery->setVariable('name', 'World');
134 *
135 * echo $xquery->execute();
136 * ?>
137 * </code>
138 */
139class XQueryProcessor implements IteratorAggregate {
140
141  private $store = null;
142  private $zorba = null;
143  private $query = null;
144  private $variables = array();
145
146  /**
147   * Creates an XQueryProcessor instance.
148   */
149  public function __construct(){
150    $this->store = InMemoryStore::getInstance();
151    $this->zorba = Zorba::getInstance($this->store);
152  }
153
154  /**
155   * Shuts down the XQueryProcessor instance.
156   */
157  public function __destruct() {
158    $this->zorba->shutdown();
159    InMemoryStore::shutdown($this->store);
160  }
161
162  /**
163   * Import a query to execute.
164   * For instance, the following code snippets imports and executes the query '1+1':
165   * <code>
166   * $xquery = new ZorbaXQueryProcessor();
167   *
168   * $xquery->importQuery('1+1');
169   *
170   * echo $xquery->execute() . '\n';
171   * </code>
172   * The following code snippets imports and executes an <em>Hello World</em> query:
173   * <code>
174   * <?php
175   * $xquery = new XQueryProcessor();
176   *
177   * $query = <<<'XQ'
178   * let $world := 'World'
179   * return <h1>Hello {$world}</h1>
180   * XQ;
181   *
182   * $xquery->importQuery($query);
183   *
184   * echo $xquery->execute() . '\n';
185   * ?>
186   * </code>
187   *
188   * @param $query Query to execute.
189   * @return ZorbaXQueryProcessor instance.
190   */
191  public function importQuery($query) {
192    if(!is_string($query)) {
193      throw new XQueryProcessorException('The query parameter must be a string. For instance: XQueryProcessor->importQuery("1+1")');
194    }
195    $this->query = $query;
196    return $this;
197  }
198
199  /**
200   * Import a query to execute from a file with the given name.
201   * For instance, the following code snippet imports the query file named <em>hello_word.xq</em>:
202   * <code>
203   * $xquery = new ZorbaXQueryProcessor();
204   *
205   * $xquery->importQueryFromURI('hello_world.xq');
206   *
207   * echo $xquery->execute() . '\n';
208   * </code>
209   *
210   * @param  $filename Filename containing the query to execute.
211   * @return ZorbaXQueryProcessor instance.
212   */
213  public function importQueryFromURI($filename) {
214    $ctx = null;
215    if(func_num_args() == 2) {
216      $ctx = func_get_arg(1);
217    }
218    $query = file_get_contents($filename, FILE_USE_INCLUDE_PATH, $ctx);
219    $this->importQuery($query);
220    return $this;
221  }
222
223  /**
224   * Set a value for an external variable.
225   *
226   * The following code snippet sets the value of the variable
227   * <em>$i</em> with <em>1</em> with type xs:integer.
228   * <code>
229   * $xquery = new ZorbaXQueryProcessor();
230   *
231   * $query = <<<'XQ'
232   * declare variable $i as xs:integer external;
233   *
234   * $i + 1
235   * 'XQ';
236   *
237   * $xquery->importQuery($query);
238   * $xquery->setVariable($i, 1);
239   *
240   * echo $xquery->execute() . '\n';
241   * </code>
242   *
243   * The following code snippet sets the value of the variable <em>$i</em> in
244   * the local namespace to the value <em>1</em>.
245   * <code>
246   * $xquery = new ZorbaXQueryProcessor();
247   *
248   * $query = <<<'XQ'
249   * declare variable $local:i as xs:integer external;
250   *
251   * $i + 1
252   * 'XQ';
253   *
254   * $xquery->importQuery($query);
255   * $xquery->setVariable("http://www.w3.org/2005/xquery-local-functions", $i, 1);
256   *
257   * echo $xquery->execute() . '\n';
258   * </code>
259   *
260   * PHP types are converted to the following XML types:
261   * - <b>DOMDocument</b> & <b>SimpleXMLElement</b>: document-node()
262   * - <b>string</b>: xs:string
263   * - <b>float</b>: xs:float
264   * - <b>long</b>: xs:long
265   * - <b>integer</b>: xs:integer
266   * - <b>boolean</b>: xs:boolean
267   * - <b>DOMDocument</b>: document-node()
268   *
269   * @param string $namespace optional Namespace URI of the external variable.
270   * @param string $name Local name of the external variable.
271   * @param mixed  $value of the external variable.
272   *
273   * return ZorbaXQueryProcessor instance.
274   */
275  public function setVariable($arg1, $arg2) {
276    $count = func_num_args();
277    if($count == 2) {
278      $name  = func_get_arg(0);
279      $value = func_get_arg(1);
280      $this->variables['_'][$name] = $value;
281    } else {
282      $ns  = func_get_arg(0);
283      $name  = func_get_arg(1);
284      $value = func_get_arg(2);
285      $this->variables[$ns][$name] = $value;
286    }
287    return $this;
288  }
289
290  /**
291   * Execute the Query.
292   *
293   * @return Query result.
294   */
295  public function execute() {
296    //Execute
297    $query = $this->compile();
298    $result = $query->execute();
299    $query->destroy();
300    return $result;
301  }
302
303  /**
304   * Provide an intance of the SPL iterator to iterator over the
305   * sequence of items produced by the query result.
306   *
307   * @return XQueryIterator
308   */
309  public function getIterator() {
310    return new XQueryIterator($this->compile());
311  }
312
313  /**
314   * Internal method that creates an instance of the
315   * XQuery class from the input parameters (importQuery and setVariable).
316   *
317   * @return XQuery compiled query.
318   */
319  private function compile()
320  {
321    //You need at least to import a query in order to compile it.
322    if(!is_string($this->query)) {
323      throw new XQueryCompilerException('No Query Imported. Use XQueryProcessor->importQuery($query).');
324    }
325
326    //Compile Query
327    $query = $this->zorba->compileQuery($this->query);
328
329    //Set Variables
330    $dctx = $query->getDynamicContext();
331    foreach($this->variables as $ns => $variables){
332      foreach($variables as $name => $value) {
333        if($ns == "_") $ns = "";
334        $param = $this->zorba->compileQuery(".");
335        $value = $this->getItem($value);
336        $param->getDynamicContext()->setContextItem($value);
337        $dctx->setVariable($ns, $name, $param->iterator());
338      }
339    }
340    //Returns an instance of the XQuery class
341    return $query;
342  }
343
344  /**
345   * Converts a PHP value to an XQuery Item.
346   * The mapping between PHP and XQuery types in {@link setVariable}.
347   *
348   * @see setVariable()
349   */
350  private function getItem($value) {
351    $itemFactory = $this->zorba->getItemFactory();
352
353    if($value instanceof DOMDocument or $value instanceof SimpleXMLElement) {
354      $value = $this->parseXML($value->saveXML());
355    } else if(is_string($value)) {
356      $value = $itemFactory->createString($value);
357    } else if(is_int($value)) {
358      $value = $itemFactory->createInteger($value);
359    } else if(is_bool($value)) {
360      $value = $itemFactory->createBoolean($value);
361    } else if(is_float($value)) {
362      $value = $itemFactory->createFloat($value);
363    } else if(is_long($value)) {
364      $value = $itemFactory->createLong($value);
365    } else {
366      throw new XQueryCompilerException("Unsupported variable type: ".gettype($value));
367    }
368
369    assert($value instanceof Item);
370
371    return $value;
372  }
373
374  /**
375   * Parse an XML string to an XQuery Item.
376   * This function is used internally only.
377   * @param $xml string XML string to parse.
378   *
379   * @return Item instance.
380   */
381  private function parseXML($xml)
382  {
383    $lDataManager = $this->zorba->getXmlDataManager();
384    $lDocMgr = $lDataManager->getDocumentManager();
385    $iter = $lDataManager->parseXML($xml);
386
387    $iter->open();
388    $doc = Item::createEmptyItem();
389
390    $iter->next($doc);
391    $iter->close();
392    $iter->destroy();
393
394    return $doc;
395  }
396}
397?>
398