Refactored template loading to its own class for performance
[biaweb2.git] / biawebdocumenttree.hpp
1 #ifndef __BIAWEBDOCUMENTTREE__
2 #define __BIAWEBDOCUMENTTREE__
3 #include <memory>
4 #include <list>
5 #include <array>
6 #include <iostream>
7 #include <fstream>
8 #include <filesystem>
9 #include <sys/stat.h>
10 #include "biawebdocument.hpp"
11 #include "biawebstrings.hpp"
12 #include "biawebutil.hpp"
13 #include "biawebdoclist.hpp"
14 #include "biawebtemplate.hpp"
15
16 // class to implement a document tree - both with or without subtrees
17 namespace biaweb {
18 class DocumentTree {
19 protected:
20 // the pointer to the parent tree if there is one or nullptr
21 DocumentTree* parent;
22 // child trees
23 std::list<DocumentTree> children;
24 // title of this tree
25 std::string title;
26 // summary for this tree - this is displayed in the index.html file of
27 // this tree before the list of articles in the tree
28 std::string summary;
29 // file stub of this tree
30 std::string stub;
31 // list of documents in this tree
32 std::list<Document> docs;
33 // set the parent - protected function as this has to be
34 // called only by add_child
35 void set_parent (DocumentTree *parent) {
36 this->parent = parent;
37 }
38
39 public:
40 // method to build a document tree from a path
41 void document_tree_builder (std::string srcpath);
42
43 // create new top level document tree
44 DocumentTree (std::string title, std::string stub = "") {
45 this->title = escape_html (title);
46 // if stub is not empty set it
47 if (stub != "")
48 this->stub = stub;
49 // make the stub from the title
50 else
51 this->stub = convert_title (title);
52 this->parent = nullptr;
53 }
54
55 // set the summary for this tree
56 void set_summary (std::string summary) {
57 this->summary = summary;
58 }
59
60 // set the summary for this tree as markdown text
61 void set_markdown_summary (std::string summary) {
62 this->summary = convert_to_markdown (summary);
63 }
64
65 std::string get_summary () {
66 return this->summary;
67 }
68
69 // sort the documents as per creation time from latest to oldest
70 void sort_documents_creation_time () {
71 this->docs.sort ([] (Document &a, Document &b)
72 {return (a.get_creation_date() > b.get_creation_date()); });
73 }
74
75 // create the document index for this tree
76 void create_tree_html (Template *t, std::string destdir);
77
78 // set the title
79 void set_title (std::string title) {
80 this->title = escape_html (title);
81 // if no stub is set
82 if (this->stub == "")
83 this->stub = convert_title (title);
84 }
85
86 void set_stub (std::string stub) {
87 this->stub = stub;
88 }
89
90 std::string get_title () {
91 return this->title;
92 }
93
94 std::string get_stub () {
95 return this->stub;
96 }
97
98 // get the child level of this tree
99 unsigned int get_level ();
100
101 // get the stub hierarchy
102 std::string stub_hierarchy ();
103
104 // add a child tree to this tree
105 void add_child (DocumentTree *child) {
106 child->set_parent (this);
107 this->children.push_back (*child);
108 }
109
110 // add a document to this tree
111 void add_document (Document *doc) {
112 this->docs.push_back (*doc);
113 }
114
115 // print a visual representation of this tree with levels
116 void visualize_tree ();
117
118 // get a pointer to the parent of this tree
119 DocumentTree *get_parent () {
120 return this->parent;
121 }
122 };
123
124 // get the tree level - 0 if top level
125 unsigned int DocumentTree::get_level () {
126 unsigned int lev = 0;
127 DocumentTree *par = this->get_parent ();
128 while (par != nullptr) {
129 lev ++;
130 par = par->get_parent ();
131 }
132 return lev;
133 }
134
135 // get the stub hierarchy for this tree
136 std::string DocumentTree::stub_hierarchy () {
137 std::list<std::string> levels;
138 DocumentTree *par = this->get_parent();
139 while (par!= nullptr) {
140 levels.push_front (par->get_stub());
141 par = par->get_parent ();
142 }
143 std::string stub_str;
144 for (std::string level : levels) {
145 // if stub is empty, don't append a /
146 if (level != "")
147 stub_str += level + "/";
148 }
149 return stub_str;
150 }
151
152 // print the representation of this tree
153 void DocumentTree::visualize_tree () {
154 // print the tree level
155 std::cout << std::setw(3) << std::left << this->get_level ();
156 // indent as per the level
157 for (unsigned int i = 0; i < this->get_level(); i ++)
158 std::cout << "+--";
159 // print the title of this tree
160 std::cout << this->title << std::endl;
161 // recurse through the child trees if any and so on
162 for (DocumentTree child : children)
163 child.visualize_tree ();
164 }
165
166 // create the tree - the index file for this tree and all the documents and
167 // the child trees recursively - using the template specified
168 void DocumentTree::create_tree_html (Template *tpl, std::string destdir) {
169
170 // create a document to represent the index of the tree.
171 std::unique_ptr<Document> index (new Document (this->title));
172 index.get()->set_index ();
173 // set the file name path
174 std::string filepath = destdir + "/" + this->stub_hierarchy () + this->stub;
175 // set the url path - this should not have destdir as it is not part
176 // of the site tree
177 std::string urlpath = this->stub_hierarchy () + this->stub;
178 // if urlpath is not empty then append a / to the end of the URL. This
179 // is so that the base URL is not absolute
180 if (urlpath != "")
181 urlpath += "/";
182
183 // create the sidebars
184 // First sidebar
185 // Create a link to the index page and
186 // If this tree has a parent, create a sidebar link to the level up
187 std::unique_ptr<SideBar> bar1 (new SideBar());
188 GenericLinkItem item0;
189 bar1.get()->set_title (tpl->get_stringbit (NAVIGATION));
190 item0.set_item_text (tpl->get_stringbit (INDEX));
191 item0.set_item_url (urlpath + "index.html");
192 bar1.get()->add_sidebar_item (item0);
193 if (this->get_parent() != nullptr) {
194 GenericLinkItem item1;
195 item1.set_item_text (tpl->get_stringbit(GO_UP));
196 item1.set_item_url (this->stub_hierarchy() + "index.html");
197 bar1.get()->add_sidebar_item (item1);
198 }
199 index.get()->add_side_bar (*bar1.get());
200
201 // create a sidebar for the child levels if there are children
202 std::unique_ptr<SideBar> bar2 (new SideBar ());
203 bar2.get()->set_title (tpl->get_stringbit (SUB_CAT) + this->title);
204 for (DocumentTree tree : this->children) {
205 // we use site relative URLs that rely on the base href tag
206 // so for biaweb generated sites, the base href tag should be
207 // used in the main template
208 GenericLinkItem item (tree.get_title(), urlpath +
209 tree.stub + "/" + "index.html");
210 bar2.get()->add_sidebar_item (item);
211 }
212 index.get()->add_side_bar (*bar2.get());
213
214 // create the path and then the index file
215 std::filesystem::create_directories (filepath);
216
217 // Create the list of documents in this tree with links
218 std::unique_ptr<DocList> article_list (new DocList ());
219 article_list.get()->set_title (this->title + ": " + tpl->get_stringbit (ARTICLES_LIST));
220 // sort the documents as per creation time and then add the document
221 // links - newest documents should appear above older ones.
222 sort_documents_creation_time ();
223
224 // create the navigation bit
225 std::shared_ptr<NavigationBit> navbit (new NavigationBit ());
226 auto par1 = this;
227 // get the link to each level in the hierarchy and add it as
228 // an inline list
229 while (par1 != nullptr) {
230 if (par1->parent != nullptr)
231 navbit.get()->add_link_item (GenericLinkItem(par1->stub,
232 par1->stub_hierarchy() + par1->stub + "/index.html"));
233 else
234 navbit.get()->add_link_item (GenericLinkItem(tpl->get_stringbit(HOME), "index.html"));
235 par1 = par1->parent;
236 }
237
238 for (Document doc : this->docs) {
239 DocListItem item (doc.get_title(),
240 urlpath + doc.get_filename() + ".html",
241 doc.get_creation_date(), doc.get_modified_date ());
242 article_list.get()->add_document_item (item);
243 // output the document also, add the navigation bit and side bars
244
245 doc.set_navigation_bit (*navbit.get());
246 doc.add_side_bar (*bar1.get());
247 doc.add_side_bar (*bar2.get());
248 doc.output_to_html (tpl, filepath);
249 }
250
251 // add the navigation bit
252 index.get()->set_navigation_bit (*navbit.get());
253 // index should contain the summary followed by the article list
254 index.get()->set_content (this->summary + article_list.get()->to_html(tpl));
255
256 // output the index file
257 index.get()->output_to_html (tpl, filepath);
258
259 // recursively create index for children
260 for (DocumentTree tree : this->children)
261 tree.create_tree_html (tpl, destdir);
262 }
263
264 // build a document tree from a filesystem path recursively
265 void DocumentTree::document_tree_builder (std::string srcpath_str) {
266 std::filesystem::path srcpath (srcpath_str);
267 this->title = srcpath.stem().string ();
268
269 // Get the directories to this child and add them as sub document
270 // trees
271 try {
272 for (auto fsitem : std::filesystem::directory_iterator (srcpath) )
273 {
274 // if it is a directory then build the tree for that directory
275 if (fsitem.is_directory ()) {
276 std::shared_ptr <DocumentTree> doctree
277 (new DocumentTree (fsitem.path().filename().string()));
278
279 this->add_child (doctree.get());
280 }
281 // add the regular files as documents in the tree
282 else if (fsitem.is_regular_file ()) {
283 // if it is an index file (specially named as index
284 // or index.md or whatever) directly add
285 // the contents to the summary of the Doctree
286 if (fsitem.path().stem().string() == "index")
287 {
288 std::string infilestr = load_from_file (fsitem.path ());
289 this->set_markdown_summary (infilestr);
290 }
291 // else it is a non-index file-
292 // create a Document and add it to the tree
293 else {
294 std::ifstream infile (fsitem.path ());
295 std::shared_ptr<Document> doc
296 (new Document (infile));
297 infile.close ();
298
299 // file modified date from system
300 struct stat buf;
301 if (stat (fsitem.path().string().c_str(), &buf) == 0)
302 doc.get()->set_modified_date (buf.st_mtim.tv_sec);
303
304 this->add_document (doc.get());
305 }
306 }
307 }
308 }
309 catch (std::filesystem::filesystem_error) {
310 std::cout << NO_SUCH_PATH_ERROR << std::endl;
311 }
312
313 // add the trees for the children recursively
314 for (DocumentTree &child : this->children)
315 child.document_tree_builder (srcpath_str + "/" + child.title);
316 }
317 }
318
319 #endif