XSLStyleSheetLibxslt.cpp revision cad810f21b803229eb11403f9209855525a25d57
1/*
2 * This file is part of the XSL implementation.
3 *
4 * Copyright (C) 2004, 2005, 2006, 2008 Apple Inc. All rights reserved.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public License
17 * along with this library; see the file COPYING.LIB.  If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21
22#include "config.h"
23#include "XSLStyleSheet.h"
24
25#if ENABLE(XSLT)
26
27#include "Console.h"
28#include "DOMWindow.h"
29#include "CachedResourceLoader.h"
30#include "Document.h"
31#include "Frame.h"
32#include "Node.h"
33#include "TransformSource.h"
34#include "XMLDocumentParser.h"
35#include "XMLDocumentParserScope.h"
36#include "XSLImportRule.h"
37#include "XSLTProcessor.h"
38#include <wtf/text/CString.h>
39
40#include <libxml/uri.h>
41#include <libxslt/xsltutils.h>
42
43#if PLATFORM(MAC)
44#include "SoftLinking.h"
45#endif
46
47#if PLATFORM(MAC)
48SOFT_LINK_LIBRARY(libxslt)
49SOFT_LINK(libxslt, xsltIsBlank, int, (xmlChar *str), (str))
50SOFT_LINK(libxslt, xsltGetNsProp, xmlChar *, (xmlNodePtr node, const xmlChar *name, const xmlChar *nameSpace), (node, name, nameSpace))
51SOFT_LINK(libxslt, xsltParseStylesheetDoc, xsltStylesheetPtr, (xmlDocPtr doc), (doc))
52SOFT_LINK(libxslt, xsltLoadStylesheetPI, xsltStylesheetPtr, (xmlDocPtr doc), (doc))
53#endif
54
55namespace WebCore {
56
57XSLStyleSheet::XSLStyleSheet(XSLImportRule* parentRule, const String& originalURL, const KURL& finalURL)
58    : StyleSheet(parentRule, originalURL, finalURL)
59    , m_ownerDocument(0)
60    , m_embedded(false)
61    , m_processed(false) // Child sheets get marked as processed when the libxslt engine has finally seen them.
62    , m_stylesheetDoc(0)
63    , m_stylesheetDocTaken(false)
64    , m_parentStyleSheet(0)
65{
66}
67
68XSLStyleSheet::XSLStyleSheet(Node* parentNode, const String& originalURL, const KURL& finalURL,  bool embedded)
69    : StyleSheet(parentNode, originalURL, finalURL)
70    , m_ownerDocument(parentNode->document())
71    , m_embedded(embedded)
72    , m_processed(true) // The root sheet starts off processed.
73    , m_stylesheetDoc(0)
74    , m_stylesheetDocTaken(false)
75    , m_parentStyleSheet(0)
76{
77}
78
79XSLStyleSheet::~XSLStyleSheet()
80{
81    if (!m_stylesheetDocTaken)
82        xmlFreeDoc(m_stylesheetDoc);
83}
84
85bool XSLStyleSheet::isLoading()
86{
87    unsigned len = length();
88    for (unsigned i = 0; i < len; ++i) {
89        StyleBase* rule = item(i);
90        if (rule->isImportRule()) {
91            XSLImportRule* import = static_cast<XSLImportRule*>(rule);
92            if (import->isLoading())
93                return true;
94        }
95    }
96    return false;
97}
98
99void XSLStyleSheet::checkLoaded()
100{
101    if (isLoading())
102        return;
103    if (parent())
104        parent()->checkLoaded();
105    if (ownerNode())
106        ownerNode()->sheetLoaded();
107}
108
109xmlDocPtr XSLStyleSheet::document()
110{
111    if (m_embedded && ownerDocument() && ownerDocument()->transformSource())
112        return (xmlDocPtr)ownerDocument()->transformSource()->platformSource();
113    return m_stylesheetDoc;
114}
115
116void XSLStyleSheet::clearDocuments()
117{
118    m_stylesheetDoc = 0;
119    unsigned len = length();
120    for (unsigned i = 0; i < len; ++i) {
121        StyleBase* rule = item(i);
122        if (rule->isImportRule()) {
123            XSLImportRule* import = static_cast<XSLImportRule*>(rule);
124            if (import->styleSheet())
125                import->styleSheet()->clearDocuments();
126        }
127    }
128}
129
130CachedResourceLoader* XSLStyleSheet::cachedResourceLoader()
131{
132    if (!m_ownerDocument)
133        return 0;
134    return m_ownerDocument->cachedResourceLoader();
135}
136
137bool XSLStyleSheet::parseString(const String& string, bool)
138{
139    // Parse in a single chunk into an xmlDocPtr
140    const UChar BOM = 0xFEFF;
141    const unsigned char BOMHighByte = *reinterpret_cast<const unsigned char*>(&BOM);
142    if (!m_stylesheetDocTaken)
143        xmlFreeDoc(m_stylesheetDoc);
144    m_stylesheetDocTaken = false;
145
146    Console* console = 0;
147    if (Frame* frame = ownerDocument()->frame())
148        console = frame->domWindow()->console();
149
150    XMLDocumentParserScope scope(cachedResourceLoader(), XSLTProcessor::genericErrorFunc, XSLTProcessor::parseErrorFunc, console);
151
152    const char* buffer = reinterpret_cast<const char*>(string.characters());
153    int size = string.length() * sizeof(UChar);
154
155    xmlParserCtxtPtr ctxt = xmlCreateMemoryParserCtxt(buffer, size);
156    if (!ctxt)
157        return 0;
158
159    if (m_parentStyleSheet) {
160        // The XSL transform may leave the newly-transformed document
161        // with references to the symbol dictionaries of the style sheet
162        // and any of its children. XML document disposal can corrupt memory
163        // if a document uses more than one symbol dictionary, so we
164        // ensure that all child stylesheets use the same dictionaries as their
165        // parents.
166        xmlDictFree(ctxt->dict);
167        ctxt->dict = m_parentStyleSheet->m_stylesheetDoc->dict;
168        xmlDictReference(ctxt->dict);
169    }
170
171    m_stylesheetDoc = xmlCtxtReadMemory(ctxt, buffer, size,
172        finalURL().string().utf8().data(),
173        BOMHighByte == 0xFF ? "UTF-16LE" : "UTF-16BE",
174        XML_PARSE_NOENT | XML_PARSE_DTDATTR | XML_PARSE_NOWARNING | XML_PARSE_NOCDATA);
175    xmlFreeParserCtxt(ctxt);
176
177    loadChildSheets();
178
179    return m_stylesheetDoc;
180}
181
182void XSLStyleSheet::loadChildSheets()
183{
184    if (!document())
185        return;
186
187    xmlNodePtr stylesheetRoot = document()->children;
188
189    // Top level children may include other things such as DTD nodes, we ignore those.
190    while (stylesheetRoot && stylesheetRoot->type != XML_ELEMENT_NODE)
191        stylesheetRoot = stylesheetRoot->next;
192
193    if (m_embedded) {
194        // We have to locate (by ID) the appropriate embedded stylesheet element, so that we can walk the
195        // import/include list.
196        xmlAttrPtr idNode = xmlGetID(document(), (const xmlChar*)(finalURL().string().utf8().data()));
197        if (!idNode)
198            return;
199        stylesheetRoot = idNode->parent;
200    } else {
201        // FIXME: Need to handle an external URI with a # in it.  This is a pretty minor edge case, so we'll deal
202        // with it later.
203    }
204
205    if (stylesheetRoot) {
206        // Walk the children of the root element and look for import/include elements.
207        // Imports must occur first.
208        xmlNodePtr curr = stylesheetRoot->children;
209        while (curr) {
210            if (curr->type != XML_ELEMENT_NODE) {
211                curr = curr->next;
212                continue;
213            }
214            if (IS_XSLT_ELEM(curr) && IS_XSLT_NAME(curr, "import")) {
215                xmlChar* uriRef = xsltGetNsProp(curr, (const xmlChar*)"href", XSLT_NAMESPACE);
216                loadChildSheet(String::fromUTF8((const char*)uriRef));
217                xmlFree(uriRef);
218            } else
219                break;
220            curr = curr->next;
221        }
222
223        // Now handle includes.
224        while (curr) {
225            if (curr->type == XML_ELEMENT_NODE && IS_XSLT_ELEM(curr) && IS_XSLT_NAME(curr, "include")) {
226                xmlChar* uriRef = xsltGetNsProp(curr, (const xmlChar*)"href", XSLT_NAMESPACE);
227                loadChildSheet(String::fromUTF8((const char*)uriRef));
228                xmlFree(uriRef);
229            }
230            curr = curr->next;
231        }
232    }
233}
234
235void XSLStyleSheet::loadChildSheet(const String& href)
236{
237    RefPtr<XSLImportRule> childRule = XSLImportRule::create(this, href);
238    append(childRule);
239    childRule->loadSheet();
240}
241
242xsltStylesheetPtr XSLStyleSheet::compileStyleSheet()
243{
244    // FIXME: Hook up error reporting for the stylesheet compilation process.
245    if (m_embedded)
246        return xsltLoadStylesheetPI(document());
247
248    // xsltParseStylesheetDoc makes the document part of the stylesheet
249    // so we have to release our pointer to it.
250    ASSERT(!m_stylesheetDocTaken);
251    xsltStylesheetPtr result = xsltParseStylesheetDoc(m_stylesheetDoc);
252    if (result)
253        m_stylesheetDocTaken = true;
254    return result;
255}
256
257void XSLStyleSheet::setParentStyleSheet(XSLStyleSheet* parent)
258{
259    m_parentStyleSheet = parent;
260    if (parent)
261        m_ownerDocument = parent->ownerDocument();
262}
263
264xmlDocPtr XSLStyleSheet::locateStylesheetSubResource(xmlDocPtr parentDoc, const xmlChar* uri)
265{
266    bool matchedParent = (parentDoc == document());
267    unsigned len = length();
268    for (unsigned i = 0; i < len; ++i) {
269        StyleBase* rule = item(i);
270        if (rule->isImportRule()) {
271            XSLImportRule* import = static_cast<XSLImportRule*>(rule);
272            XSLStyleSheet* child = import->styleSheet();
273            if (!child)
274                continue;
275            if (matchedParent) {
276                if (child->processed())
277                    continue; // libxslt has been given this sheet already.
278
279                // Check the URI of the child stylesheet against the doc URI.
280                // In order to ensure that libxml canonicalized both URLs, we get the original href
281                // string from the import rule and canonicalize it using libxml before comparing it
282                // with the URI argument.
283                CString importHref = import->href().utf8();
284                xmlChar* base = xmlNodeGetBase(parentDoc, (xmlNodePtr)parentDoc);
285                xmlChar* childURI = xmlBuildURI((const xmlChar*)importHref.data(), base);
286                bool equalURIs = xmlStrEqual(uri, childURI);
287                xmlFree(base);
288                xmlFree(childURI);
289                if (equalURIs) {
290                    child->markAsProcessed();
291                    return child->document();
292                }
293            } else {
294                xmlDocPtr result = import->styleSheet()->locateStylesheetSubResource(parentDoc, uri);
295                if (result)
296                    return result;
297            }
298        }
299    }
300
301    return 0;
302}
303
304void XSLStyleSheet::markAsProcessed()
305{
306    ASSERT(!m_processed);
307    ASSERT(!m_stylesheetDocTaken);
308    m_processed = true;
309    m_stylesheetDocTaken = true;
310}
311
312} // namespace WebCore
313
314#endif // ENABLE(XSLT)
315