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(c); break;
48 case '<': bufferAppend(buffer, "&lt;"); break;
49 case '>': bufferAppend(buffer, "&gt;"); break;
50 case '&': bufferAppend(buffer, "&amp;"); break;
51 case '\"': bufferAppend(buffer, "&quot;"); break;
52 case '\'': bufferAppend(buffer, "&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(c); break;
86 case ':': bufferAppend(buffer, "."); 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 } else {
101 // Unfortunately, html5 won't allow <a /> or <span /> tags, they need to be explicitly closed
102 // myfile << "/>";
103 myfile << "></" << name << ">";
104 }
105}
106
107void Generator::Tag::close(llvm::raw_ostream &myfile) const
108{
109 myfile << "</" << name << ">";
110}
111
112void Generator::generate(llvm::StringRef outputPrefix, std::string dataPath, const std::string &filename,
113 const char* begin, const char* end, llvm::StringRef footer, llvm::StringRef warningMessage,
114 const std::set<std::string> &interestingDefinitions)
115{
116 std::string real_filename = outputPrefix % "/" % filename % ".html";
117 // Make sure the parent directory exist:
118 create_directories(llvm::StringRef(real_filename).rsplit('/').first);
119
120#if CLANG_VERSION_MAJOR==3 && CLANG_VERSION_MINOR<=5
121 std::string error;
122 llvm::raw_fd_ostream myfile(real_filename.c_str(), error, llvm::sys::fs::F_None);
123 if (!error.empty()) {
124 std::cerr << "Error generating " << real_filename << " ";
125 std::cerr << error<< std::endl;
126 return;
127 }
128#else
129 std::error_code error_code;
130 llvm::raw_fd_ostream myfile(real_filename, error_code, llvm::sys::fs::F_None);
131 if (error_code) {
132 std::cerr << "Error generating " << real_filename << " ";
133 std::cerr << error_code.message() << std::endl;
134 return;
135 }
136#endif
137
138 int count = std::count(filename.begin(), filename.end(), '/');
139 std::string root_path = "..";
140 for (int i = 0; i < count - 1; i++) {
141 root_path += "/..";
142 }
143
144 if (dataPath.size() && dataPath[0] == '.')
145 dataPath = root_path % "/" % dataPath;
146
147 myfile << "<!doctype html>\n" // Use HTML 5 doctype
148 "<html>\n<head>\n";
149 myfile << "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">";
150 myfile << "<title>" << llvm::StringRef(filename).rsplit('/').second.str() << " source code [" << filename << "] - Woboq Code Browser</title>\n";
151 if (interestingDefinitions.size() > 0) {
152 std::string interestingDefitionsStr = llvm::join(interestingDefinitions.begin(), interestingDefinitions.end(), ",");
153 myfile << "<meta name=\"woboq:interestingDefinitions\" content=\"" << interestingDefitionsStr << " \"/>\n";
154 }
155 myfile << "<link rel=\"stylesheet\" href=\"" << dataPath << "/qtcreator.css\" title=\"QtCreator\"/>\n";
156 myfile << "<link rel=\"alternate stylesheet\" href=\"" << dataPath << "/kdevelop.css\" title=\"KDevelop\"/>\n";
157 myfile << "<script type=\"text/javascript\" src=\"" << dataPath << "/jquery/jquery.min.js\"></script>\n";
158 myfile << "<script type=\"text/javascript\" src=\"" << dataPath << "/jquery/jquery-ui.min.js\"></script>\n";
159 myfile << "<script>var file = '"<< filename <<"'; var root_path = '"<< root_path <<"'; var data_path = '"<< dataPath <<"'; var ecma_script_api_version = 2;";
160 if (!projects.empty()) {
161 myfile << "var projects = {";
162 bool first = true;
163 for (auto it: projects) {
164 if (!first) myfile << ", ";
165 first = false;
166 myfile << "\"" << it.first << "\" : \"" << it.second << "\"";
167 }
168 myfile << "};";
169 }
170 myfile << "</script>\n";
171 myfile << "<script src='" << dataPath << "/codebrowser.js'></script>\n";
172
173 myfile << "</head>\n<body><div id='header'><h1 id='breadcrumb'><span>Browse the source code of </span>";
174 // FIXME: If interestingDefitions has only 1 class, add it to the h1
175
176 {
177 int i = 0;
178 llvm::StringRef tail = filename;
179 while (i < count - 1) {
180 myfile << "<a href='..";
181 for (int f = 0; f < count - i - 2; ++f) {
182 myfile << "/..";
183 }
184 auto split = tail.split('/');
185 myfile << "'>" << split.first.str() << "</a>/";
186
187 tail = split.second;
188 ++i;
189 }
190 auto split = tail.split('/');
191 myfile << "<a href='./'>" << split.first.str() << "</a>/";
192 myfile << "<a href='" << split.second.str() << ".html'>" << split.second.str() << "</a>";
193 }
194 myfile << "</h1></div>\n<hr/><div id='content'>";
195
196 if (!warningMessage.empty()) {
197 myfile << "<p class=\"warnmsg\">";
198 myfile.write(warningMessage.begin(), warningMessage.size());
199 myfile << "</p>\n";
200 }
201
202 //** here we put the code
203 myfile << "<table class=\"code\">\n";
204
205
206 const char *c = begin;
207 unsigned int line = 1;
208 const char *bufferStart = c;
209
210 auto tags_it = tags.cbegin();
211 const char *next_start = tags_it != tags.cend() ? (begin + tags_it->pos) : end;
212 const char *next_end = end;
213 const char *next = next_start;
214
215
216 auto flush = [&]() {
217 if (bufferStart != c)
218 myfile.write(bufferStart, c - bufferStart);
219 bufferStart = c;
220 };
221
222 myfile << "<tr><th id=\"1\">"<< 1 << "</th><td>";
223
224 std::deque<const Tag*> stack;
225
226
227 while (true) {
228 if (c == next) {
229 flush();
230 while (!stack.empty() && c >= next_end) {
231 const Tag *top = stack.back();
232 stack.pop_back();
233 top->close(myfile);
234 next_end = end;
235 if (!stack.empty()) {
236 top = stack.back();
237 next_end = begin + top->pos + top->len;
238 }
239 }
240 if (c >= end)
241 break;
242 assert(c < end);
243 while (c == next_start && tags_it != tags.cend()) {
244 assert(c == begin + tags_it->pos);
245 tags_it->open(myfile);
246 if (tags_it->len) {
247 stack.push_back(&(*tags_it));
248 next_end = c + tags_it->len;
249 }
250
251 tags_it++;
252 next_start = tags_it != tags.cend() ? (begin + tags_it->pos) : end;
253 };
254
255 next = std::min(next_end, next_start);
256 //next = std::min(end, next);
257 }
258
259 switch (*c) {
260 case '\n':
261 flush();
262 ++bufferStart; //skip the new line
263 ++line;
264 for (auto it = stack.crbegin(); it != stack.crend(); ++it)
265 (*it)->close(myfile);
266 myfile << "</td></tr>\n"
267 "<tr><th id=\"" << line << "\">"<< line << "</th><td>";
268 for (auto it = stack.cbegin(); it != stack.cend(); ++it)
269 (*it)->open(myfile);
270 break;
271 case '&': flush(); ++bufferStart; myfile << "&amp;"; break;
272 case '<': flush(); ++bufferStart; myfile << "&lt;"; break;
273 case '>': flush(); ++bufferStart; myfile << "&gt;"; break;
274 default: break;
275 }
276 ++c;
277 }
278
279
280 myfile << "</td></tr>\n"
281 "</table>"
282 "<hr/>";
283
284 if (!warningMessage.empty()) {
285 myfile << "<p class=\"warnmsg\">";
286 myfile.write(warningMessage.begin(), warningMessage.size());
287 myfile << "</p>\n";
288 }
289
290 myfile << "<p id='footer'>\n";
291
292 myfile.write(footer.begin(), footer.size());
293
294 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> "
295 CODEBROWSER_VERSION "\n<br/>Generator usage only permitted with license.</p>\n</div></body></html>\n";
296}
297
298