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("&nbsp; ", 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 &nbsp; 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("&nbsp;", 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("&nbsp;", 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("&nbsp; ", 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("&lt;", fp);
347 					break;
348 				case '>':
349 					fputs("&gt;", fp);
350 					break;
351 				case '&':
352 					fputs("&amp;", 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