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