1 // SciTE - Scintilla based Text Editor
2 /** @file ExportHTML.cxx
3 ** Export the current document to HTML.
4 **/
5 // Copyright 1998-2006 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
7
8 #include <cstddef>
9 #include <cstdlib>
10 #include <cstdint>
11 #include <cstring>
12 #include <cstdio>
13 #include <ctime>
14
15 #include <string>
16 #include <string_view>
17 #include <vector>
18 #include <map>
19 #include <set>
20 #include <memory>
21 #include <chrono>
22 #include <sstream>
23 #include <atomic>
24 #include <mutex>
25
26 #include <fcntl.h>
27 #include <sys/stat.h>
28
29 #include "ILexer.h"
30
31 #include "ScintillaTypes.h"
32 #include "ScintillaCall.h"
33
34 #include "GUI.h"
35 #include "ScintillaWindow.h"
36
37 #include "StringList.h"
38 #include "StringHelpers.h"
39 #include "FilePath.h"
40 #include "StyleDefinition.h"
41 #include "PropSetFile.h"
42 #include "StyleWriter.h"
43 #include "Extender.h"
44 #include "SciTE.h"
45 #include "JobQueue.h"
46 #include "Cookie.h"
47 #include "Worker.h"
48 #include "MatchMarker.h"
49 #include "SciTEBase.h"
50
51 //---------- Save to HTML ----------
52
SaveToHTML(const FilePath & saveName)53 void SciTEBase::SaveToHTML(const FilePath &saveName) {
54 RemoveFindMarks();
55 wEditor.ColouriseAll();
56 int tabSize = props.GetInt("tabsize");
57 if (tabSize == 0)
58 tabSize = 4;
59 const int wysiwyg = props.GetInt("export.html.wysiwyg", 1);
60 const int tabs = props.GetInt("export.html.tabs", 0);
61 const int folding = props.GetInt("export.html.folding", 0);
62 const int onlyStylesUsed = props.GetInt("export.html.styleused", 0);
63 const int titleFullPath = props.GetInt("export.html.title.fullpath", 0);
64
65 const SA::Position lengthDoc = LengthDocument();
66 TextReader acc(wEditor);
67
68 constexpr int StyleLastPredefined = static_cast<int>(SA::StylesCommon::LastPredefined);
69
70 bool styleIsUsed[StyleMax + 1] = {};
71 if (onlyStylesUsed) {
72 // check the used styles
73 for (SA::Position i = 0; i < lengthDoc; i++) {
74 styleIsUsed[acc.StyleAt(i)] = true;
75 }
76 } else {
77 for (int i = 0; i <= StyleMax; i++) {
78 styleIsUsed[i] = true;
79 }
80 }
81 styleIsUsed[StyleDefault] = true;
82
83 FILE *fp = saveName.Open(GUI_TEXT("wt"));
84 bool failedWrite = fp == nullptr;
85 if (fp) {
86 fputs("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n", fp);
87 fputs("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n", fp);
88 fputs("<head>\n", fp);
89 if (titleFullPath)
90 fprintf(fp, "<title>%s</title>\n",
91 filePath.AsUTF8().c_str());
92 else
93 fprintf(fp, "<title>%s</title>\n",
94 filePath.Name().AsUTF8().c_str());
95 // Probably not used by robots, but making a little advertisement for those looking
96 // at the source code doesn't hurt...
97 fputs("<meta name=\"Generator\" content=\"SciTE - www.Scintilla.org\" />\n", fp);
98 if (codePage == SA::CpUtf8)
99 fputs("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n", fp);
100
101 if (folding) {
102 fputs("<script language=\"JavaScript\" type=\"text/javascript\">\n"
103 "<!--\n"
104 "function symbol(id, sym) {\n"
105 " if (id.textContent==undefined) {\n"
106 " id.innerText=sym; } else {\n"
107 " id.textContent=sym; }\n"
108 "}\n"
109 "function toggle(id) {\n"
110 "var thislayer=document.getElementById('ln'+id);\n"
111 "id-=1;\n"
112 "var togline=document.getElementById('hd'+id);\n"
113 "var togsym=document.getElementById('bt'+id);\n"
114 "if (thislayer.style.display == 'none') {\n"
115 " thislayer.style.display='';\n"
116 " togline.style.textDecoration='none';\n"
117 " symbol(togsym,'- ');\n"
118 "} else {\n"
119 " thislayer.style.display='none';\n"
120 " togline.style.textDecoration='underline';\n"
121 " symbol(togsym,'+ ');\n"
122 "}\n"
123 "}\n"
124 "//-->\n"
125 "</script>\n", fp);
126 }
127
128 fputs("<style type=\"text/css\">\n", fp);
129
130 std::string bgColour;
131
132 StyleDefinition sddef = StyleDefinitionFor(StyleDefault);
133
134 if (sddef.back.length()) {
135 bgColour = sddef.back;
136 }
137
138 std::string sval = props.GetExpandedString("font.monospace");
139 StyleDefinition sdmono(sval.c_str());
140
141 for (int istyle = 0; istyle <= StyleMax; istyle++) {
142 if ((istyle > StyleDefault) && (istyle <= StyleLastPredefined))
143 continue;
144 if (styleIsUsed[istyle]) {
145
146 StyleDefinition sd = StyleDefinitionFor(istyle);
147
148 if (CurrentBufferConst()->useMonoFont && sd.font.length() && sdmono.font.length()) {
149 sd.font = sdmono.font;
150 sd.size = sdmono.size;
151 sd.italics = sdmono.italics;
152 sd.weight = sdmono.weight;
153 }
154
155 if (sd.specified != StyleDefinition::sdNone) {
156 if (istyle == StyleDefault) {
157 fprintf(fp, "span {\n");
158 } else {
159 fprintf(fp, ".S%0d {\n", istyle);
160 }
161 if (sd.italics) {
162 fprintf(fp, "\tfont-style: italic;\n");
163 }
164 if (sd.IsBold()) {
165 fprintf(fp, "\tfont-weight: bold;\n");
166 }
167 if (wysiwyg && sd.font.length()) {
168 fprintf(fp, "\tfont-family: '%s';\n", sd.font.c_str());
169 }
170 if (sd.fore.length()) {
171 fprintf(fp, "\tcolor: %s;\n", sd.fore.c_str());
172 } else if (istyle == StyleDefault) {
173 fprintf(fp, "\tcolor: #000000;\n");
174 }
175 if ((sd.specified & StyleDefinition::sdBack) && sd.back.length()) {
176 if (istyle != StyleDefault && bgColour != sd.back) {
177 fprintf(fp, "\tbackground: %s;\n", sd.back.c_str());
178 fprintf(fp, "\ttext-decoration: inherit;\n");
179 }
180 }
181 if (wysiwyg && sd.size) {
182 fprintf(fp, "\tfont-size: %0dpt;\n", sd.size);
183 }
184 fprintf(fp, "}\n");
185 } else {
186 styleIsUsed[istyle] = false; // No definition, it uses default style (32)
187 }
188 }
189 }
190 fputs("</style>\n", fp);
191 fputs("</head>\n", fp);
192 if (bgColour.length() > 0)
193 fprintf(fp, "<body bgcolor=\"%s\">\n", bgColour.c_str());
194 else
195 fputs("<body>\n", fp);
196
197 SA::Line line = acc.GetLine(0);
198 int level = LevelNumber(acc.LevelAt(line)) - static_cast<int>(SA::FoldLevel::Base);
199 int styleCurrent = acc.StyleAt(0);
200 bool inStyleSpan = false;
201 bool inFoldSpan = false;
202 // Global span for default attributes
203 if (wysiwyg) {
204 fputs("<span>", fp);
205 } else {
206 fputs("<pre>", fp);
207 }
208
209 if (folding) {
210 const SA::FoldLevel lvl = acc.LevelAt(0);
211 level = LevelNumber(lvl) - static_cast<int>(SA::FoldLevel::Base);
212
213 if (LevelIsHeader(lvl)) {
214 const std::string sLine = std::to_string(line);
215 const std::string sLineNext = std::to_string(line+1);
216 fprintf(fp, "<span id=\"hd%s\" onclick=\"toggle('%s')\">", sLine.c_str(), sLineNext.c_str());
217 fprintf(fp, "<span id=\"bt%s\">- </span>", sLine.c_str());
218 inFoldSpan = true;
219 } else {
220 fputs(" ", fp);
221 }
222 }
223
224 if (styleIsUsed[styleCurrent]) {
225 fprintf(fp, "<span class=\"S%0d\">", styleCurrent);
226 inStyleSpan = true;
227 }
228 // Else, this style has no definition (beside default one):
229 // no span for it, except the global one
230
231 int column = 0;
232 for (SA::Position i = 0; i < lengthDoc; i++) {
233 const char ch = acc[i];
234 const int style = acc.StyleAt(i);
235
236 if (style != styleCurrent) {
237 if (inStyleSpan) {
238 fputs("</span>", fp);
239 inStyleSpan = false;
240 }
241 if (ch != '\r' && ch != '\n') { // No need of a span for the EOL
242 if (styleIsUsed[style]) {
243 fprintf(fp, "<span class=\"S%0d\">", style);
244 inStyleSpan = true;
245 }
246 styleCurrent = style;
247 }
248 }
249 if (ch == ' ') {
250 if (wysiwyg) {
251 char prevCh = '\0';
252 if (column == 0) { // At start of line, must put a because regular space will be collapsed
253 prevCh = ' ';
254 }
255 while (i < lengthDoc && acc[i] == ' ') {
256 if (prevCh != ' ') {
257 fputc(' ', fp);
258 } else {
259 fputs(" ", fp);
260 }
261 prevCh = acc[i];
262 i++;
263 column++;
264 }
265 i--; // the last incrementation will be done by the for loop
266 } else {
267 fputc(' ', fp);
268 column++;
269 }
270 } else if (ch == '\t') {
271 const int ts = tabSize - (column % tabSize);
272 if (wysiwyg) {
273 for (int itab = 0; itab < ts; itab++) {
274 if (itab % 2) {
275 fputc(' ', fp);
276 } else {
277 fputs(" ", fp);
278 }
279 }
280 column += ts;
281 } else {
282 if (tabs) {
283 fputc(ch, fp);
284 column++;
285 } else {
286 for (int itab = 0; itab < ts; itab++) {
287 fputc(' ', fp);
288 }
289 column += ts;
290 }
291 }
292 } else if (ch == '\r' || ch == '\n') {
293 if (inStyleSpan) {
294 fputs("</span>", fp);
295 inStyleSpan = false;
296 }
297 if (inFoldSpan) {
298 fputs("</span>", fp);
299 inFoldSpan = false;
300 }
301 if (ch == '\r' && acc[i + 1] == '\n') {
302 i++; // CR+LF line ending, skip the "extra" EOL char
303 }
304 column = 0;
305 if (wysiwyg) {
306 fputs("<br />", fp);
307 }
308
309 styleCurrent = acc.StyleAt(i + 1);
310 if (folding) {
311 line = acc.GetLine(i + 1);
312
313 const SA::FoldLevel lvl = acc.LevelAt(line);
314 const int newLevel = LevelNumber(lvl) - static_cast<int>(SA::FoldLevel::Base);
315
316 if (newLevel < level)
317 fprintf(fp, "</span>");
318 fputc('\n', fp); // here to get clean code
319 if (newLevel > level) {
320 const std::string sLine = std::to_string(line);
321 fprintf(fp, "<span id=\"ln%s\">", sLine.c_str());
322 }
323
324 if (LevelIsHeader(lvl)) {
325 const std::string sLine = std::to_string(line);
326 const std::string sLineNext = std::to_string(line + 1);
327 fprintf(fp, "<span id=\"hd%s\" onclick=\"toggle('%s')\">", sLine.c_str(), sLineNext.c_str());
328 fprintf(fp, "<span id=\"bt%s\">- </span>", sLine.c_str());
329 inFoldSpan = true;
330 } else
331 fputs(" ", fp);
332 level = newLevel;
333 } else {
334 fputc('\n', fp);
335 }
336
337 if (styleIsUsed[styleCurrent] && acc[i + 1] != '\r' && acc[i + 1] != '\n') {
338 // We know it's the correct next style,
339 // but no (empty) span for an empty line
340 fprintf(fp, "<span class=\"S%0d\">", styleCurrent);
341 inStyleSpan = true;
342 }
343 } else {
344 switch (ch) {
345 case '<':
346 fputs("<", fp);
347 break;
348 case '>':
349 fputs(">", fp);
350 break;
351 case '&':
352 fputs("&", fp);
353 break;
354 default:
355 fputc(ch, fp);
356 }
357 column++;
358 }
359 }
360
361 if (inStyleSpan) {
362 fputs("</span>", fp);
363 }
364
365 if (folding) {
366 while (level > 0) {
367 fprintf(fp, "</span>");
368 level--;
369 }
370 }
371
372 if (!wysiwyg) {
373 fputs("</pre>", fp);
374 } else {
375 fputs("</span>", fp);
376 }
377
378 fputs("\n</body>\n</html>\n", fp);
379 if (fclose(fp) != 0) {
380 failedWrite = true;
381 }
382 }
383 if (failedWrite) {
384 FailedSaveMessageBox(saveName);
385 }
386 }
387
388