|
1 /* |
|
2 * This file is part of the XSL implementation. |
|
3 * |
|
4 * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple, Inc. All rights reserved. |
|
5 * Copyright (C) 2005, 2006 Alexey Proskuryakov <ap@webkit.org> |
|
6 * |
|
7 * This library is free software; you can redistribute it and/or |
|
8 * modify it under the terms of the GNU Library General Public |
|
9 * License as published by the Free Software Foundation; either |
|
10 * version 2 of the License, or (at your option) any later version. |
|
11 * |
|
12 * This library is distributed in the hope that it will be useful, |
|
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
15 * Library General Public License for more details. |
|
16 * |
|
17 * You should have received a copy of the GNU Library General Public License |
|
18 * along with this library; see the file COPYING.LIB. If not, write to |
|
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
|
20 * Boston, MA 02110-1301, USA. |
|
21 */ |
|
22 |
|
23 #include "config.h" |
|
24 |
|
25 #if ENABLE(XSLT) |
|
26 |
|
27 #include "XSLTProcessor.h" |
|
28 |
|
29 #include "Console.h" |
|
30 #include "DOMWindow.h" |
|
31 #include "DocLoader.h" |
|
32 #include "Frame.h" |
|
33 #include "ResourceError.h" |
|
34 #include "ResourceHandle.h" |
|
35 #include "ResourceRequest.h" |
|
36 #include "ResourceResponse.h" |
|
37 #include "TransformSource.h" |
|
38 #include "XMLDocumentParser.h" |
|
39 #include "XSLStyleSheet.h" |
|
40 #include "XSLTExtensions.h" |
|
41 #include "XSLTUnicodeSort.h" |
|
42 #include "loader.h" |
|
43 #include "markup.h" |
|
44 #include <libxslt/imports.h> |
|
45 #include <libxslt/variables.h> |
|
46 #include <libxslt/xsltutils.h> |
|
47 #include <wtf/Assertions.h> |
|
48 #include <wtf/text/CString.h> |
|
49 #include <wtf/Vector.h> |
|
50 |
|
51 #if PLATFORM(MAC) |
|
52 #include "SoftLinking.h" |
|
53 |
|
54 SOFT_LINK_LIBRARY(libxslt); |
|
55 SOFT_LINK(libxslt, xsltFreeStylesheet, void, (xsltStylesheetPtr sheet), (sheet)) |
|
56 SOFT_LINK(libxslt, xsltFreeTransformContext, void, (xsltTransformContextPtr ctxt), (ctxt)) |
|
57 SOFT_LINK(libxslt, xsltNewTransformContext, xsltTransformContextPtr, (xsltStylesheetPtr style, xmlDocPtr doc), (style, doc)) |
|
58 SOFT_LINK(libxslt, xsltApplyStylesheetUser, xmlDocPtr, (xsltStylesheetPtr style, xmlDocPtr doc, const char** params, const char* output, FILE* profile, xsltTransformContextPtr userCtxt), (style, doc, params, output, profile, userCtxt)) |
|
59 SOFT_LINK(libxslt, xsltQuoteUserParams, int, (xsltTransformContextPtr ctxt, const char** params), (ctxt, params)) |
|
60 SOFT_LINK(libxslt, xsltSetCtxtSortFunc, void, (xsltTransformContextPtr ctxt, xsltSortFunc handler), (ctxt, handler)) |
|
61 SOFT_LINK(libxslt, xsltSetLoaderFunc, void, (xsltDocLoaderFunc f), (f)) |
|
62 SOFT_LINK(libxslt, xsltSaveResultTo, int, (xmlOutputBufferPtr buf, xmlDocPtr result, xsltStylesheetPtr style), (buf, result, style)) |
|
63 SOFT_LINK(libxslt, xsltNextImport, xsltStylesheetPtr, (xsltStylesheetPtr style), (style)) |
|
64 #endif |
|
65 |
|
66 namespace WebCore { |
|
67 |
|
68 void XSLTProcessor::genericErrorFunc(void*, const char*, ...) |
|
69 { |
|
70 // It would be nice to do something with this error message. |
|
71 } |
|
72 |
|
73 void XSLTProcessor::parseErrorFunc(void* userData, xmlError* error) |
|
74 { |
|
75 Console* console = static_cast<Console*>(userData); |
|
76 if (!console) |
|
77 return; |
|
78 |
|
79 MessageLevel level; |
|
80 switch (error->level) { |
|
81 case XML_ERR_NONE: |
|
82 level = TipMessageLevel; |
|
83 break; |
|
84 case XML_ERR_WARNING: |
|
85 level = WarningMessageLevel; |
|
86 break; |
|
87 case XML_ERR_ERROR: |
|
88 case XML_ERR_FATAL: |
|
89 default: |
|
90 level = ErrorMessageLevel; |
|
91 break; |
|
92 } |
|
93 |
|
94 console->addMessage(XMLMessageSource, LogMessageType, level, error->message, error->line, error->file); |
|
95 } |
|
96 |
|
97 // FIXME: There seems to be no way to control the ctxt pointer for loading here, thus we have globals. |
|
98 static XSLTProcessor* globalProcessor = 0; |
|
99 static DocLoader* globalDocLoader = 0; |
|
100 static xmlDocPtr docLoaderFunc(const xmlChar* uri, |
|
101 xmlDictPtr, |
|
102 int options, |
|
103 void* ctxt, |
|
104 xsltLoadType type) |
|
105 { |
|
106 if (!globalProcessor) |
|
107 return 0; |
|
108 |
|
109 switch (type) { |
|
110 case XSLT_LOAD_DOCUMENT: { |
|
111 xsltTransformContextPtr context = (xsltTransformContextPtr)ctxt; |
|
112 xmlChar* base = xmlNodeGetBase(context->document->doc, context->node); |
|
113 KURL url(KURL(ParsedURLString, reinterpret_cast<const char*>(base)), reinterpret_cast<const char*>(uri)); |
|
114 xmlFree(base); |
|
115 ResourceError error; |
|
116 ResourceResponse response; |
|
117 |
|
118 Vector<char> data; |
|
119 |
|
120 bool requestAllowed = globalDocLoader->frame() && globalDocLoader->doc()->securityOrigin()->canRequest(url); |
|
121 if (requestAllowed) { |
|
122 globalDocLoader->frame()->loader()->loadResourceSynchronously(url, AllowStoredCredentials, error, response, data); |
|
123 requestAllowed = globalDocLoader->doc()->securityOrigin()->canRequest(response.url()); |
|
124 } |
|
125 if (!requestAllowed) { |
|
126 data.clear(); |
|
127 globalDocLoader->printAccessDeniedMessage(url); |
|
128 } |
|
129 |
|
130 Console* console = 0; |
|
131 if (Frame* frame = globalProcessor->xslStylesheet()->ownerDocument()->frame()) |
|
132 console = frame->domWindow()->console(); |
|
133 xmlSetStructuredErrorFunc(console, XSLTProcessor::parseErrorFunc); |
|
134 xmlSetGenericErrorFunc(console, XSLTProcessor::genericErrorFunc); |
|
135 |
|
136 // We don't specify an encoding here. Neither Gecko nor WinIE respects |
|
137 // the encoding specified in the HTTP headers. |
|
138 xmlDocPtr doc = xmlReadMemory(data.data(), data.size(), (const char*)uri, 0, options); |
|
139 |
|
140 xmlSetStructuredErrorFunc(0, 0); |
|
141 xmlSetGenericErrorFunc(0, 0); |
|
142 |
|
143 return doc; |
|
144 } |
|
145 case XSLT_LOAD_STYLESHEET: |
|
146 return globalProcessor->xslStylesheet()->locateStylesheetSubResource(((xsltStylesheetPtr)ctxt)->doc, uri); |
|
147 default: |
|
148 break; |
|
149 } |
|
150 |
|
151 return 0; |
|
152 } |
|
153 |
|
154 static inline void setXSLTLoadCallBack(xsltDocLoaderFunc func, XSLTProcessor* processor, DocLoader* loader) |
|
155 { |
|
156 xsltSetLoaderFunc(func); |
|
157 globalProcessor = processor; |
|
158 globalDocLoader = loader; |
|
159 } |
|
160 |
|
161 static int writeToVector(void* context, const char* buffer, int len) |
|
162 { |
|
163 Vector<UChar>& resultOutput = *static_cast<Vector<UChar>*>(context); |
|
164 String decodedChunk = String::fromUTF8(buffer, len); |
|
165 resultOutput.append(decodedChunk.characters(), decodedChunk.length()); |
|
166 return len; |
|
167 } |
|
168 |
|
169 static bool saveResultToString(xmlDocPtr resultDoc, xsltStylesheetPtr sheet, String& resultString) |
|
170 { |
|
171 xmlOutputBufferPtr outputBuf = xmlAllocOutputBuffer(0); |
|
172 if (!outputBuf) |
|
173 return false; |
|
174 |
|
175 Vector<UChar> resultVector; |
|
176 outputBuf->context = &resultVector; |
|
177 outputBuf->writecallback = writeToVector; |
|
178 |
|
179 int retval = xsltSaveResultTo(outputBuf, resultDoc, sheet); |
|
180 xmlOutputBufferClose(outputBuf); |
|
181 if (retval < 0) |
|
182 return false; |
|
183 |
|
184 // Workaround for <http://bugzilla.gnome.org/show_bug.cgi?id=495668>: libxslt appends an extra line feed to the result. |
|
185 if (resultVector.size() > 0 && resultVector[resultVector.size() - 1] == '\n') |
|
186 resultVector.removeLast(); |
|
187 |
|
188 resultString = String::adopt(resultVector); |
|
189 |
|
190 return true; |
|
191 } |
|
192 |
|
193 static const char** xsltParamArrayFromParameterMap(XSLTProcessor::ParameterMap& parameters) |
|
194 { |
|
195 if (parameters.isEmpty()) |
|
196 return 0; |
|
197 |
|
198 const char** parameterArray = (const char**)fastMalloc(((parameters.size() * 2) + 1) * sizeof(char*)); |
|
199 |
|
200 XSLTProcessor::ParameterMap::iterator end = parameters.end(); |
|
201 unsigned index = 0; |
|
202 for (XSLTProcessor::ParameterMap::iterator it = parameters.begin(); it != end; ++it) { |
|
203 parameterArray[index++] = fastStrDup(it->first.utf8().data()); |
|
204 parameterArray[index++] = fastStrDup(it->second.utf8().data()); |
|
205 } |
|
206 parameterArray[index] = 0; |
|
207 |
|
208 return parameterArray; |
|
209 } |
|
210 |
|
211 static void freeXsltParamArray(const char** params) |
|
212 { |
|
213 const char** temp = params; |
|
214 if (!params) |
|
215 return; |
|
216 |
|
217 while (*temp) { |
|
218 fastFree((void*)*(temp++)); |
|
219 fastFree((void*)*(temp++)); |
|
220 } |
|
221 fastFree(params); |
|
222 } |
|
223 |
|
224 static xsltStylesheetPtr xsltStylesheetPointer(RefPtr<XSLStyleSheet>& cachedStylesheet, Node* stylesheetRootNode) |
|
225 { |
|
226 if (!cachedStylesheet && stylesheetRootNode) { |
|
227 cachedStylesheet = XSLStyleSheet::create(stylesheetRootNode->parent() ? stylesheetRootNode->parent() : stylesheetRootNode, |
|
228 stylesheetRootNode->document()->url().string(), |
|
229 stylesheetRootNode->document()->url()); // FIXME: Should we use baseURL here? |
|
230 cachedStylesheet->parseString(createMarkup(stylesheetRootNode)); |
|
231 } |
|
232 |
|
233 if (!cachedStylesheet || !cachedStylesheet->document()) |
|
234 return 0; |
|
235 |
|
236 return cachedStylesheet->compileStyleSheet(); |
|
237 } |
|
238 |
|
239 static inline xmlDocPtr xmlDocPtrFromNode(Node* sourceNode, bool& shouldDelete) |
|
240 { |
|
241 RefPtr<Document> ownerDocument = sourceNode->document(); |
|
242 bool sourceIsDocument = (sourceNode == ownerDocument.get()); |
|
243 |
|
244 xmlDocPtr sourceDoc = 0; |
|
245 if (sourceIsDocument && ownerDocument->transformSource()) |
|
246 sourceDoc = (xmlDocPtr)ownerDocument->transformSource()->platformSource(); |
|
247 if (!sourceDoc) { |
|
248 sourceDoc = (xmlDocPtr)xmlDocPtrForString(ownerDocument->docLoader(), createMarkup(sourceNode), |
|
249 sourceIsDocument ? ownerDocument->url().string() : String()); |
|
250 shouldDelete = sourceDoc; |
|
251 } |
|
252 return sourceDoc; |
|
253 } |
|
254 |
|
255 static inline String resultMIMEType(xmlDocPtr resultDoc, xsltStylesheetPtr sheet) |
|
256 { |
|
257 // There are three types of output we need to be able to deal with: |
|
258 // HTML (create an HTML document), XML (create an XML document), |
|
259 // and text (wrap in a <pre> and create an XML document). |
|
260 |
|
261 const xmlChar* resultType = 0; |
|
262 XSLT_GET_IMPORT_PTR(resultType, sheet, method); |
|
263 if (!resultType && resultDoc->type == XML_HTML_DOCUMENT_NODE) |
|
264 resultType = (const xmlChar*)"html"; |
|
265 |
|
266 if (xmlStrEqual(resultType, (const xmlChar*)"html")) |
|
267 return "text/html"; |
|
268 if (xmlStrEqual(resultType, (const xmlChar*)"text")) |
|
269 return "text/plain"; |
|
270 |
|
271 return "application/xml"; |
|
272 } |
|
273 |
|
274 bool XSLTProcessor::transformToString(Node* sourceNode, String& mimeType, String& resultString, String& resultEncoding) |
|
275 { |
|
276 RefPtr<Document> ownerDocument = sourceNode->document(); |
|
277 |
|
278 setXSLTLoadCallBack(docLoaderFunc, this, ownerDocument->docLoader()); |
|
279 xsltStylesheetPtr sheet = xsltStylesheetPointer(m_stylesheet, m_stylesheetRootNode.get()); |
|
280 if (!sheet) { |
|
281 setXSLTLoadCallBack(0, 0, 0); |
|
282 return false; |
|
283 } |
|
284 m_stylesheet->clearDocuments(); |
|
285 |
|
286 xmlChar* origMethod = sheet->method; |
|
287 if (!origMethod && mimeType == "text/html") |
|
288 sheet->method = (xmlChar*)"html"; |
|
289 |
|
290 bool success = false; |
|
291 bool shouldFreeSourceDoc = false; |
|
292 if (xmlDocPtr sourceDoc = xmlDocPtrFromNode(sourceNode, shouldFreeSourceDoc)) { |
|
293 // The XML declaration would prevent parsing the result as a fragment, and it's not needed even for documents, |
|
294 // as the result of this function is always immediately parsed. |
|
295 sheet->omitXmlDeclaration = true; |
|
296 |
|
297 xsltTransformContextPtr transformContext = xsltNewTransformContext(sheet, sourceDoc); |
|
298 registerXSLTExtensions(transformContext); |
|
299 |
|
300 // <http://bugs.webkit.org/show_bug.cgi?id=16077>: XSLT processor <xsl:sort> algorithm only compares by code point. |
|
301 xsltSetCtxtSortFunc(transformContext, xsltUnicodeSortFunction); |
|
302 |
|
303 // This is a workaround for a bug in libxslt. |
|
304 // The bug has been fixed in version 1.1.13, so once we ship that this can be removed. |
|
305 if (!transformContext->globalVars) |
|
306 transformContext->globalVars = xmlHashCreate(20); |
|
307 |
|
308 const char** params = xsltParamArrayFromParameterMap(m_parameters); |
|
309 xsltQuoteUserParams(transformContext, params); |
|
310 xmlDocPtr resultDoc = xsltApplyStylesheetUser(sheet, sourceDoc, 0, 0, 0, transformContext); |
|
311 |
|
312 xsltFreeTransformContext(transformContext); |
|
313 freeXsltParamArray(params); |
|
314 |
|
315 if (shouldFreeSourceDoc) |
|
316 xmlFreeDoc(sourceDoc); |
|
317 |
|
318 if ((success = saveResultToString(resultDoc, sheet, resultString))) { |
|
319 mimeType = resultMIMEType(resultDoc, sheet); |
|
320 resultEncoding = (char*)resultDoc->encoding; |
|
321 } |
|
322 xmlFreeDoc(resultDoc); |
|
323 } |
|
324 |
|
325 sheet->method = origMethod; |
|
326 setXSLTLoadCallBack(0, 0, 0); |
|
327 xsltFreeStylesheet(sheet); |
|
328 m_stylesheet = 0; |
|
329 |
|
330 return success; |
|
331 } |
|
332 |
|
333 } // namespace WebCore |
|
334 |
|
335 #endif // ENABLE(XSLT) |