1<?php
2/**
3 * XML_GRDDL
4 *
5 * PHP version 5
6 *
7 * Copyright (c) 2008, Daniel O'Connor <daniel.oconnor@gmail.com>.
8 * All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 *
14 *   * Redistributions of source code must retain the above copyright
15 *     notice, this list of conditions and the following disclaimer.
16 *
17 *   * Redistributions in binary form must reproduce the above copyright
18 *     notice, this list of conditions and the following disclaimer in
19 *     the documentation and/or other materials provided with the
20 *     distribution.
21 *
22 *   * Neither the name of Daniel O'Connor nor the names of his
23 *     contributors may be used to endorse or promote products derived
24 *     from this software without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
29 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
30 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
31 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
32 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
33 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
34 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
36 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37 * POSSIBILITY OF SUCH DAMAGE.
38 *
39 * @category  Semantic_Web
40 * @package   XML_GRDDL
41 * @author    Daniel O'Connor <daniel.oconnor@gmail.com>
42 * @copyright 2008 Daniel O'Connor
43 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
44 * @version   SVN: $Id: Xsl.php 261528 2008-06-23 11:20:59Z clockwerx $
45 * @link      http://code.google.com/p/xmlgrddl/
46 */
47
48require_once 'XML/GRDDL/Driver.php';
49
50/**
51 * A driver for PHP 5's XSL extension.
52 *
53 * Requires PHP 5.2.6, XSL extension
54 *
55 * @category Semantic_Web
56 * @package  XML_GRDDL
57 * @author   Daniel O'Connor <daniel.oconnor@gmail.com>
58 * @license  http://www.opensource.org/licenses/bsd-license.php  BSD License
59 * @version  Release: @package_version@
60 * @link     http://code.google.com/p/xmlgrddl/
61 */
62class XML_GRDDL_Driver_Xsl extends XML_GRDDL_Driver
63{
64    /**
65     * Make a new instance of XML_GRRDL_Driver_XSL directly
66     *
67     * @param mixed[] $options An array of driver specific options
68     *
69     * @todo Document driver specific options!
70     *
71     * @see XML_GRDDL::factory()
72     *
73     * @return  void
74     */
75    public function __construct($options = array())
76    {
77        if (!extension_loaded('xsl')) {
78            throw new XML_GRDDL_Exception("Don't forget to enable the xsl
79                                            extension");
80        }
81
82        parent::__construct($options);
83    }
84
85    /**
86     * Transform the given XML with the provided XSLT.
87     *
88     * @param string $stylesheet URL or file location of an XSLT transformation
89     * @param string $xml        String of XML
90     *
91     * @bug fails http://www.w3.org/TR/grddl-tests/#grddlProfileBase1 because
92     *      when we DOMDocument::loadXML(), it's from a string, and worse, that string
93     *      is HTML. We can't work out the DOMDocument->documentElement->baseURI
94     *      correctly.
95     *
96     * @return  string  Transformed document contents.
97     */
98    public function transform($stylesheet, $xml)
99    {
100        if (empty($stylesheet) || empty($xml)) {
101            $this->logger->log("Given empty stylesheet or xml");
102            return $xml;
103        }
104
105        $oldCwd = getcwd();
106
107        $paths = array();
108
109        $paths[] = '@data_dir@/@package_name@/data/grddl-library/';
110        $paths[] = dirname(__FILE__) . '/../../../data/grddl-library/';
111
112        foreach ($paths as $path) {
113            if (file_exists($path)) {
114                chdir($path);
115                break;
116            }
117        }
118
119        if (getcwd() == $oldCwd) {
120            $this->logger->log("Could not access standard transform library");
121        }
122
123        try {
124            $this->logger->log("Attempting to transform with " . $stylesheet);
125
126            $dom = new DOMDocument('1.0');
127            $dom->loadXML($xml);
128
129
130            $xslt = $this->fetch($stylesheet, 'xsl');
131
132            $xsl = new DOMDocument();
133            $xsl->loadXML($xslt, LIBXML_NOCDATA | LIBXML_NOENT);
134
135            $this->checkStylesheetOutputType($xsl);
136
137            set_error_handler(array($this, 'handleTransformationErrorMessage'));
138
139            $proc = new XSLTProcessor();
140            $proc->importStyleSheet($xsl);
141
142            $result = $proc->transformToXML($dom);
143            restore_error_handler();
144
145            $this->logger->log("Transformed successfully with " . $stylesheet);
146
147            return $result;
148        } catch (XML_GRDDL_Exception $e) {
149            restore_error_handler();
150            chdir($oldCwd);
151
152            throw $e;
153        }
154    }
155
156    /**
157     * A driver specific method to check if a given stylesheet will output
158     * an understandable format.
159     *
160     * This driver only understands application/rdf+xml
161     *
162     * @param DOMDocument $stylesheet A given stylesheet to inspect
163     *
164     * @see http://code.google.com/p/xmlgrddl/issues/detail?id=24#makechanges
165     *
166     * @throws XML_GRDDL_Exception
167     *
168     * @return bool
169     */
170    protected function checkStylesheetOutputType(DOMDocument $stylesheet)
171    {
172        $this->logger->log("Checking stylesheet for odd output types");
173
174        $sxe = simplexml_import_dom($stylesheet);
175
176
177        $sxe->registerXPathNamespace('xsl', 'http://www.w3.org/1999/XSL/Transform');
178
179        $nodes = $sxe->xpath('//xsl:output[@media-type]');
180
181        if (empty($nodes)) {
182            $this->logger->log("No xsl:output media-type found / not a stylesheet,"
183                                . " assuming application/rdf+xml");
184            return true;
185        }
186
187        list($output) = $nodes;
188
189        $this->logger->log("This transformation produces " . $output['media-type']);
190
191        if ($output['media-type'] != 'application/rdf+xml') {
192            throw new XML_GRDDL_Exception("Cannot use this transform, I don't "
193                                            . "understand " . $output['media-type']);
194        }
195
196        return true;
197    }
198
199    /**
200     * Handle generated error messages
201     *
202     * @param string   $errno      Error number
203     * @param string   $errstr     Error message
204     * @param string   $errfile    Error file
205     * @param string   $errline    Error line
206     * @param string[] $errcontext Error context
207     *
208     * @throws  XML_GRDDL_Exception
209     * @return  void
210     */
211    public function handleTransformationErrorMessage($errno, $errstr, $errfile,
212                                                     $errline, $errcontext = array())
213    {
214        throw new XML_GRDDL_Exception($errstr);
215    }
216}
217