1/****************************************************************************
2 * Copyright (C) 2012-2016 Woboq GmbH
3 * Olivier Goffart <contact at woboq.com>
4 * https://woboq.com/codebrowser.html
5 *
6 * This file is part of the Woboq Code Browser.
7 *
8 * Commercial License Usage:
9 * Licensees holding valid commercial licenses provided by Woboq may use
10 * this file in accordance with the terms contained in a written agreement
11 * between the licensee and Woboq.
12 * For further information see https://woboq.com/codebrowser.html
13 *
14 * Alternatively, this work may be used under a Creative Commons
15 * Attribution-NonCommercial-ShareAlike 3.0 (CC-BY-NC-SA 3.0) License.
16 * http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US
17 * This license does not allow you to use the code browser to assist the
18 * development of your commercial software. If you intent to do so, consider
19 * purchasing a commercial licence.
20 ****************************************************************************/
21
22#include "generator.h"
23#include "stringbuilder.h"
24#include "filesystem.h"
25
26#include "../global.h"
27
28#include <fstream>
29#include <iostream>
30#include <llvm/Support/raw_ostream.h>
31#include <llvm/Support/FileSystem.h>
32#include <llvm/ADT/StringExtras.h>
33#include <clang/Basic/Version.h>
34
35template<int N>
36static void bufferAppend(llvm::SmallVectorImpl<char> &buffer, const char (&val)[N]) {
37 buffer.append(val, val + N - 1);
38}
39
40llvm::StringRef Generator::escapeAttr(llvm::StringRef s, llvm::SmallVectorImpl< char >& buffer)
41{
42 buffer.clear();
43 unsigned len = s.size();
44 for (unsigned i = 0 ; i < len; ++i) {
45 char c = s[i];
46 switch (c) {
47 default: buffer.push_back(Elt: c); break;
48 case '<': bufferAppend(buffer, val: "&lt;"); break;
49 case '>': bufferAppend(buffer, val: "&gt;"); break;
50 case '&': bufferAppend(buffer, val: "&amp;"); break;
51 case '\"': bufferAppend(buffer, val: "&quot;"); break;
52 case '\'': bufferAppend(buffer, val: "&apos;"); break;
53 }
54 }
55 return llvm::StringRef(buffer.begin(), buffer.size());
56}
57
58void Generator::escapeAttr(llvm::raw_ostream &os, llvm::StringRef s)
59{
60 unsigned len = s.size();
61 for (unsigned i = 0 ; i < len; ++i) {
62 char c = s[i];
63 switch (c) {
64 default:
65 os << c; break;
66
67 case '<': os << "&lt;"; break;
68 case '>': os << "&gt;"; break;
69 case '&': os << "&amp;"; break;
70 case '\"': os << "&quot;"; break;
71 case '\'': os << "&apos;"; break;
72 }
73 }
74
75}
76
77// ATTENTION: Keep in sync with `replace_invalid_filename_chars` functions in filesystem.cpp and in .js files
78llvm::StringRef Generator::escapeAttrForFilename(llvm::StringRef s, llvm::SmallVectorImpl< char >& buffer)
79{
80 buffer.clear();
81 unsigned len = s.size();
82 for (unsigned i = 0 ; i < len; ++i) {
83 char c = s[i];
84 switch (c) {
85 default: buffer.push_back(Elt: c); break;
86 case ':': bufferAppend(buffer, val: "."); break;
87 }
88 }
89 return llvm::StringRef(buffer.begin(), buffer.size());
90}
91
92void Generator::Tag::open(llvm::raw_ostream &myfile) const
93{
94 myfile << "<" << name;
95 if (!attributes.empty())
96 myfile << " " << attributes;
97
98 if (len) {
99 myfile << ">";
100 if (!innerHtml.empty())
101 myfile << innerHtml;
102 } else {
103 // Unfortunately, html5 won't allow <a /> or <span /> tags, they need to be explicitly closed
104 // myfile << "/>";
105 if (!innerHtml.empty())
106 myfile << ">" << innerHtml << "</" << name << ">";
107 else
108 myfile << "></" << name << ">";
109 }
110}
111
112void Generator::Tag::close(llvm::raw_ostream &myfile) const
113{
114 myfile << "</" << name << ">";
115}
116
117void Generator::generate(llvm::StringRef outputPrefix, std::string dataPath, const std::string &filename,
118 const char* begin, const char* end, llvm::StringRef footer, llvm::StringRef warningMessage,
119 const std::set<std::string> &interestingDefinitions)
120{
121 std::string real_filename = outputPrefix % "/" % filename % ".html";
122 // Make sure the parent directory exist:
123 create_directories(path: llvm::StringRef(real_filename).rsplit(Separator: '/').first);
124
125#if CLANG_VERSION_MAJOR==3 && CLANG_VERSION_MINOR<=5
126 std::string error;
127 llvm::raw_fd_ostream myfile(real_filename.c_str(), error, llvm::sys::fs::F_None);
128 if (!error.empty()) {
129 std::cerr << "Error generating " << real_filename << " ";
130 std::cerr << error<< std::endl;
131 return;
132 }
133#else
134 std::error_code error_code;
135#if CLANG_VERSION_MAJOR >= 13
136 llvm::raw_fd_ostream myfile(real_filename, error_code, llvm::sys::fs::OF_None);
137#else
138 llvm::raw_fd_ostream myfile(real_filename, error_code, llvm::sys::fs::F_None);
139#endif
140 if (error_code) {
141 std::cerr << "Error generating " << real_filename << " ";
142 std::cerr << error_code.message() << std::endl;
143 return;
144 }
145#endif
146
147 int count = std::count(first: filename.begin(), last: filename.end(), value: '/');
148 std::string root_path = "..";
149 for (int i = 0; i < count - 1; i++) {
150 root_path += "/..";
151 }
152
153 if (dataPath.size() && dataPath[0] == '.')
154 dataPath = root_path % "/" % dataPath;
155
156 myfile << "<!doctype html>\n" // Use HTML 5 doctype
157 "<html>\n<head>\n";
158 myfile << "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">";
159 myfile << "<title>" << llvm::StringRef(filename).rsplit(Separator: '/').second.str() << " source code [" << filename << "] - Woboq Code Browser</title>\n";
160 if (interestingDefinitions.size() > 0) {
161 std::string interestingDefitionsStr = llvm::join(Begin: interestingDefinitions.begin(), End: interestingDefinitions.end(), Separator: ",");
162 myfile << "<meta name=\"woboq:interestingDefinitions\" content=\"" << interestingDefitionsStr << " \"/>\n";
163 }
164 myfile << "<link rel=\"stylesheet\" href=\"" << dataPath << "/qtcreator.css\" title=\"QtCreator\"/>\n";
165 myfile << "<link rel=\"alternate stylesheet\" href=\"" << dataPath << "/kdevelop.css\" title=\"KDevelop\"/>\n";
166 myfile << "<script type=\"text/javascript\" src=\"" << dataPath << "/jquery/jquery.min.js\"></script>\n";
167 myfile << "<script type=\"text/javascript\" src=\"" << dataPath << "/jquery/jquery-ui.min.js\"></script>\n";
168 myfile << "<script>var file = '"<< filename <<"'; var root_path = '"<< root_path <<"'; var data_path = '"<< dataPath <<"'; var ecma_script_api_version = 2;";
169 if (!projects.empty()) {
170 myfile << "var projects = {";
171 bool first = true;
172 for (auto it: projects) {
173 if (!first) myfile << ", ";
174 first = false;
175 myfile << "\"" << it.first << "\" : \"" << it.second << "\"";
176 }
177 myfile << "};";
178 }
179 myfile << "</script>\n";
180 myfile << "<script src='" << dataPath << "/codebrowser.js'></script>\n";
181
182 myfile << "</head>\n<body><div id='header'><h1 id='breadcrumb'><span>Browse the source code of </span>";
183 // FIXME: If interestingDefitions has only 1 class, add it to the h1
184
185 {
186 int i = 0;
187 llvm::StringRef tail = filename;
188 while (i < count - 1) {
189 myfile << "<a href='..";
190 for (int f = 0; f < count - i - 2; ++f) {
191 myfile << "/..";
192 }
193 auto split = tail.split(Separator: '/');
194 myfile << "'>" << split.first.str() << "</a>/";
195
196 tail = split.second;
197 ++i;
198 }
199 auto split = tail.split(Separator: '/');
200 myfile << "<a href='./'>" << split.first.str() << "</a>/";
201 myfile << "<a href='" << split.second.str() << ".html'>" << split.second.str() << "</a>";
202 }
203 myfile << "</h1></div>\n<hr/><div id='content'>";
204
205 if (!warningMessage.empty()) {
206 myfile << "<p class=\"warnmsg\">";
207 myfile.write(Ptr: warningMessage.begin(), Size: warningMessage.size());
208 myfile << "</p>\n";
209 }
210
211 //** here we put the code
212 myfile << "<table class=\"code\">\n";
213
214
215 const char *c = begin;
216 unsigned int line = 1;
217 const char *bufferStart = c;
218
219 auto tags_it = tags.cbegin();
220 const char *next_start = tags_it != tags.cend() ? (begin + tags_it->pos) : end;
221 const char *next_end = end;
222 const char *next = next_start;
223
224
225 auto flush = [&]() {
226 if (bufferStart != c)
227 myfile.write(Ptr: bufferStart, Size: c - bufferStart);
228 bufferStart = c;
229 };
230
231 myfile << "<tr><th id=\"1\">"<< 1 << "</th><td>";
232
233 std::deque<const Tag*> stack;
234
235
236 while (true) {
237 if (c == next) {
238 flush();
239 while (!stack.empty() && c >= next_end) {
240 const Tag *top = stack.back();
241 stack.pop_back();
242 top->close(myfile);
243 next_end = end;
244 if (!stack.empty()) {
245 top = stack.back();
246 next_end = begin + top->pos + top->len;
247 }
248 }
249 if (c >= end)
250 break;
251 assert(c < end);
252 while (c == next_start && tags_it != tags.cend()) {
253 assert(c == begin + tags_it->pos);
254 tags_it->open(myfile);
255 if (tags_it->len) {
256 stack.push_back(x: &(*tags_it));
257 next_end = c + tags_it->len;
258 }
259
260 tags_it++;
261 next_start = tags_it != tags.cend() ? (begin + tags_it->pos) : end;
262 };
263
264 next = std::min(a: next_end, b: next_start);
265 //next = std::min(end, next);
266 }
267
268 switch (*c) {
269 case '\n':
270 flush();
271 ++bufferStart; //skip the new line
272 ++line;
273 for (auto it = stack.crbegin(); it != stack.crend(); ++it)
274 (*it)->close(myfile);
275 myfile << "</td></tr>\n"
276 "<tr><th id=\"" << line << "\">"<< line << "</th><td>";
277 for (auto it = stack.cbegin(); it != stack.cend(); ++it)
278 (*it)->open(myfile);
279 break;
280 case '&': flush(); ++bufferStart; myfile << "&amp;"; break;
281 case '<': flush(); ++bufferStart; myfile << "&lt;"; break;
282 case '>': flush(); ++bufferStart; myfile << "&gt;"; break;
283 default: break;
284 }
285 ++c;
286 }
287
288
289 myfile << "</td></tr>\n"
290 "</table>"
291 "<hr/>";
292
293 if (!warningMessage.empty()) {
294 myfile << "<p class=\"warnmsg\">";
295 myfile.write(Ptr: warningMessage.begin(), Size: warningMessage.size());
296 myfile << "</p>\n";
297 }
298
299 myfile << "<p id='footer'>\n";
300
301 myfile.write(Ptr: footer.begin(), Size: footer.size());
302
303 myfile << "<br />Powered by <a href='https://woboq.com'><img alt='Woboq' src='https://code.woboq.org/woboq-16.png' width='41' height='16' /></a> <a href='https://code.woboq.org'>Code Browser</a> "
304 CODEBROWSER_VERSION "\n<br/>Generator usage only permitted with license.</p>\n</div></body></html>\n";
305}
306

source code of codebrowser/generator/generator.cpp