1 | #include "xslt.h" |
2 | |
3 | #include <libxslt/xsltconfig.h> |
4 | #include <libxslt/xsltInternals.h> |
5 | #include <libxslt/transform.h> |
6 | #include <libxslt/xsltutils.h> |
7 | #include <libxml/xmlIO.h> |
8 | #include <libxml/parserInternals.h> |
9 | #include <libxml/catalog.h> |
10 | #include <QtCore/QDate> |
11 | #include <QtCore/QDir> |
12 | #include <QtCore/QRegExp> |
13 | #include <assert.h> |
14 | #include <QtCore/QTextCodec> |
15 | #include <stdlib.h> |
16 | #include <stdarg.h> |
17 | |
18 | #ifdef Q_OS_WIN |
19 | #include <config-kdoctools.h> |
20 | #include <QtCore/QCoreApplication> |
21 | #include <QtCore/QDebug> |
22 | #include <QtCore/QHash> |
23 | #endif |
24 | |
25 | #if !defined( SIMPLE_XSLT ) |
26 | extern HelpProtocol *slave; |
27 | #define INFO( x ) if (slave) slave->infoMessage(x); |
28 | #else |
29 | #define INFO( x ) |
30 | #endif |
31 | |
32 | int writeToQString(void * context, const char * buffer, int len) |
33 | { |
34 | QString *t = (QString*)context; |
35 | *t += QString::fromUtf8(buffer, len); |
36 | return len; |
37 | } |
38 | |
39 | int closeQString(void * context) { |
40 | QString *t = (QString*)context; |
41 | *t += '\n'; |
42 | return 0; |
43 | } |
44 | |
45 | #if defined (SIMPLE_XSLT) && defined(Q_WS_WIN) |
46 | |
47 | #define MAX_PATHS 64 |
48 | xmlExternalEntityLoader defaultEntityLoader = NULL; |
49 | static xmlChar *paths[MAX_PATHS + 1]; |
50 | static int nbpaths = 0; |
51 | static QHash<QString,QString> replaceURLList; |
52 | |
53 | /* |
54 | * Entity loading control and customization. |
55 | * taken from xsltproc.c |
56 | */ |
57 | static xmlParserInputPtr xsltprocExternalEntityLoader(const char *_URL, const char *ID,xmlParserCtxtPtr ctxt) |
58 | { |
59 | xmlParserInputPtr ret; |
60 | warningSAXFunc warning = NULL; |
61 | |
62 | // use local available dtd versions instead of fetching it everytime from the internet |
63 | QString url = QLatin1String(_URL); |
64 | QHash<QString, QString>::const_iterator i; |
65 | for(i = replaceURLList.constBegin(); i != replaceURLList.constEnd(); i++) |
66 | { |
67 | if (url.startsWith(i.key())) |
68 | { |
69 | url.replace(i.key(),i.value()); |
70 | qDebug() << "converted" << _URL << "to" << url; |
71 | } |
72 | } |
73 | char URL[1024]; |
74 | strcpy(URL,url.toLatin1().constData()); |
75 | |
76 | const char *lastsegment = URL; |
77 | const char *iter = URL; |
78 | |
79 | if (nbpaths > 0) { |
80 | while (*iter != 0) { |
81 | if (*iter == '/') |
82 | lastsegment = iter + 1; |
83 | iter++; |
84 | } |
85 | } |
86 | |
87 | if ((ctxt != NULL) && (ctxt->sax != NULL)) { |
88 | warning = ctxt->sax->warning; |
89 | ctxt->sax->warning = NULL; |
90 | } |
91 | |
92 | if (defaultEntityLoader != NULL) { |
93 | ret = defaultEntityLoader(URL, ID, ctxt); |
94 | if (ret != NULL) { |
95 | if (warning != NULL) |
96 | ctxt->sax->warning = warning; |
97 | qDebug() << "Loaded URL=\"" << URL << "\" ID=\"" << ID << "\"" ; |
98 | return(ret); |
99 | } |
100 | } |
101 | for (int i = 0;i < nbpaths;i++) { |
102 | xmlChar *newURL; |
103 | |
104 | newURL = xmlStrdup((const xmlChar *) paths[i]); |
105 | newURL = xmlStrcat(newURL, (const xmlChar *) "/" ); |
106 | newURL = xmlStrcat(newURL, (const xmlChar *) lastsegment); |
107 | if (newURL != NULL) { |
108 | ret = defaultEntityLoader((const char *)newURL, ID, ctxt); |
109 | if (ret != NULL) { |
110 | if (warning != NULL) |
111 | ctxt->sax->warning = warning; |
112 | qDebug() << "Loaded URL=\"" << newURL << "\" ID=\"" << ID << "\"" ; |
113 | xmlFree(newURL); |
114 | return(ret); |
115 | } |
116 | xmlFree(newURL); |
117 | } |
118 | } |
119 | if (warning != NULL) { |
120 | ctxt->sax->warning = warning; |
121 | if (URL != NULL) |
122 | warning(ctxt, "failed to load external entity \"%s\"\n" , URL); |
123 | else if (ID != NULL) |
124 | warning(ctxt, "failed to load external entity \"%s\"\n" , ID); |
125 | } |
126 | return(NULL); |
127 | } |
128 | #endif |
129 | |
130 | QString transform( const QString &pat, const QString& tss, |
131 | const QVector<const char *> ¶ms ) |
132 | { |
133 | QString parsed; |
134 | |
135 | INFO(i18n("Parsing stylesheet" )); |
136 | #if defined (SIMPLE_XSLT) && defined(Q_WS_WIN) |
137 | // prepare use of local available dtd versions instead of fetching everytime from the internet |
138 | // this approach is url based |
139 | if (!defaultEntityLoader) { |
140 | defaultEntityLoader = xmlGetExternalEntityLoader(); |
141 | xmlSetExternalEntityLoader(xsltprocExternalEntityLoader); |
142 | |
143 | replaceURLList[QLatin1String("http://www.oasis-open.org/docbook/xml/4.2" )] = QString("file:///%1" ).arg(DOCBOOK_XML_CURRDTD); |
144 | } |
145 | #endif |
146 | |
147 | xsltStylesheetPtr style_sheet = |
148 | xsltParseStylesheetFile((const xmlChar *)QFile::encodeName(tss).constData()); |
149 | |
150 | if ( !style_sheet ) { |
151 | return parsed; |
152 | } |
153 | if (style_sheet->indent == 1) |
154 | xmlIndentTreeOutput = 1; |
155 | else |
156 | xmlIndentTreeOutput = 0; |
157 | |
158 | INFO(i18n("Parsing document" )); |
159 | |
160 | xmlParserCtxtPtr pctxt; |
161 | |
162 | pctxt = xmlNewParserCtxt(); |
163 | if ( pctxt == NULL ) { |
164 | return parsed; |
165 | } |
166 | |
167 | xmlDocPtr doc = xmlCtxtReadFile(pctxt, QFile::encodeName(pat), NULL, |
168 | XML_PARSE_NOENT|XML_PARSE_DTDLOAD|XML_PARSE_NONET); |
169 | /* Check both the returned doc (for parsing errors) and the context |
170 | (for validation errors) */ |
171 | if (doc == NULL) { |
172 | return parsed; |
173 | } else { |
174 | if (pctxt->valid == 0) { |
175 | xmlFreeDoc(doc); |
176 | return parsed; |
177 | } |
178 | } |
179 | |
180 | xsltTransformContextPtr ctxt; |
181 | |
182 | ctxt = xsltNewTransformContext(style_sheet, doc); |
183 | if (ctxt == NULL) |
184 | return parsed; |
185 | |
186 | INFO(i18n("Applying stylesheet" )); |
187 | QVector<const char *> p = params; |
188 | p.append( NULL ); |
189 | xmlDocPtr res = xsltApplyStylesheet(style_sheet, doc, const_cast<const char **>(&p[0])); |
190 | xmlFreeDoc(doc); |
191 | if (res != NULL) { |
192 | xmlOutputBufferPtr outp = xmlOutputBufferCreateIO(writeToQString, (xmlOutputCloseCallback)closeQString, &parsed, 0); |
193 | outp->written = 0; |
194 | INFO(i18n("Writing document" )); |
195 | xsltSaveResultTo ( outp, res, style_sheet ); |
196 | xmlOutputBufferFlush(outp); |
197 | xmlFreeDoc(res); |
198 | } |
199 | xsltFreeStylesheet(style_sheet); |
200 | |
201 | if (parsed.isEmpty()) |
202 | parsed = ' '; // avoid error message |
203 | return parsed; |
204 | } |
205 | |
206 | /* |
207 | xmlParserInputPtr meinExternalEntityLoader(const char *URL, const char *ID, |
208 | xmlParserCtxtPtr ctxt) { |
209 | xmlParserInputPtr ret = NULL; |
210 | |
211 | // fprintf(stderr, "loading %s %s %s\n", URL, ID, ctxt->directory); |
212 | |
213 | if (URL == NULL) { |
214 | if ((ctxt->sax != NULL) && (ctxt->sax->warning != NULL)) |
215 | ctxt->sax->warning(ctxt, |
216 | "failed to load external entity \"%s\"\n", ID); |
217 | return(NULL); |
218 | } |
219 | if (!qstrcmp(ID, "-//OASIS//DTD DocBook XML V4.1.2//EN")) |
220 | URL = "docbook/xml-dtd-4.1.2/docbookx.dtd"; |
221 | if (!qstrcmp(ID, "-//OASIS//DTD XML DocBook V4.1.2//EN")) |
222 | URL = "docbook/xml-dtd-4.1.2/docbookx.dtd"; |
223 | |
224 | QString file; |
225 | if (KStandardDirs::exists( QDir::currentPath() + "/" + URL ) ) |
226 | file = QDir::currentPath() + "/" + URL; |
227 | else |
228 | file = locate("dtd", URL); |
229 | |
230 | ret = xmlNewInputFromFile(ctxt, file.toLatin1().constData()); |
231 | if (ret == NULL) { |
232 | if ((ctxt->sax != NULL) && (ctxt->sax->warning != NULL)) |
233 | ctxt->sax->warning(ctxt, |
234 | |
235 | "failed to load external entity \"%s\"\n", URL); |
236 | } |
237 | return(ret); |
238 | } |
239 | */ |
240 | |
241 | QString splitOut(const QString &parsed, int index) |
242 | { |
243 | int start_index = index + 1; |
244 | while (parsed.at(start_index - 1) != '>') start_index++; |
245 | |
246 | int inside = 0; |
247 | |
248 | QString filedata; |
249 | |
250 | while (true) { |
251 | int endindex = parsed.indexOf("</FILENAME>" , index); |
252 | int startindex = parsed.indexOf("<FILENAME " , index) + 1; |
253 | |
254 | // kDebug() << "FILENAME " << startindex << " " << endindex << " " << inside << " " << parsed.mid(startindex + 18, 15)<< " " << parsed.length(); |
255 | |
256 | if (startindex > 0) { |
257 | if (startindex < endindex) { |
258 | // kDebug() << "finding another"; |
259 | index = startindex + 8; |
260 | inside++; |
261 | } else { |
262 | index = endindex + 8; |
263 | inside--; |
264 | } |
265 | } else { |
266 | inside--; |
267 | index = endindex + 1; |
268 | } |
269 | |
270 | if (inside == 0) { |
271 | filedata = parsed.mid(start_index, endindex - start_index); |
272 | break; |
273 | } |
274 | |
275 | } |
276 | |
277 | index = filedata.indexOf("<FILENAME " ); |
278 | |
279 | if (index > 0) { |
280 | int endindex = filedata.lastIndexOf("</FILENAME>" ); |
281 | while (filedata.at(endindex) != '>') endindex++; |
282 | endindex++; |
283 | filedata = filedata.left(index) + filedata.mid(endindex); |
284 | } |
285 | |
286 | // filedata.replace(QRegExp(">"), "\n>"); |
287 | return filedata; |
288 | } |
289 | |
290 | QByteArray fromUnicode( const QString &data ) |
291 | { |
292 | #ifdef Q_WS_WIN |
293 | return data.toUtf8(); |
294 | #else |
295 | QTextCodec *locale = QTextCodec::codecForLocale(); |
296 | QByteArray result; |
297 | char buffer[30000]; |
298 | uint buffer_len = 0; |
299 | uint len = 0; |
300 | int offset = 0; |
301 | const int part_len = 5000; |
302 | |
303 | QString part; |
304 | |
305 | while ( offset < data.length() ) |
306 | { |
307 | part = data.mid( offset, part_len ); |
308 | QByteArray test = locale->fromUnicode( part ); |
309 | if ( locale->toUnicode( test ) == part ) { |
310 | result += test; |
311 | offset += part_len; |
312 | continue; |
313 | } |
314 | len = part.length(); |
315 | buffer_len = 0; |
316 | for ( uint i = 0; i < len; i++ ) { |
317 | QByteArray test = locale->fromUnicode( part.mid( i, 1 ) ); |
318 | if ( locale->toUnicode( test ) == part.mid( i, 1 ) ) { |
319 | if (buffer_len + test.length() + 1 > sizeof(buffer)) |
320 | break; |
321 | strcpy( buffer + buffer_len, test.data() ); |
322 | buffer_len += test.length(); |
323 | } else { |
324 | QString res; |
325 | res.sprintf( "&#%d;" , part.at( i ).unicode() ); |
326 | test = locale->fromUnicode( res ); |
327 | if (buffer_len + test.length() + 1 > sizeof(buffer)) |
328 | break; |
329 | strcpy( buffer + buffer_len, test.data() ); |
330 | buffer_len += test.length(); |
331 | } |
332 | } |
333 | result += QByteArray( buffer, buffer_len + 1); |
334 | offset += part_len; |
335 | } |
336 | return result; |
337 | #endif |
338 | } |
339 | |
340 | void ( QString &output ) |
341 | { |
342 | QString name; |
343 | #ifdef Q_WS_WIN |
344 | name = "utf-8" ; |
345 | // may be required for all xml output |
346 | if (output.contains("<table-of-contents>" )) |
347 | output.replace( QString( "<?xml version=\"1.0\"?>" ), |
348 | QString( "<?xml version=\"1.0\" encoding=\"%1\"?>" ).arg( name ) ); |
349 | #else |
350 | name = QTextCodec::codecForLocale()->name(); |
351 | name.replace( QString( "ISO " ), "iso-" ); |
352 | output.replace( QString( "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">" ), |
353 | QString( "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%1\">" ).arg( name ) ); |
354 | #endif |
355 | } |
356 | |