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