1 /*****************************************************************************\
3 * Name : marks_maker_javascript *
4 * Author : Chris Koeritz *
8 * Turns a link database in HOOPLE format into a web page, when given a *
9 * suitable template file. The template file must have the phrase: *
10 * $INSERT_LINKS_HERE *
11 * at the point where the generated links are supposed to be stored. *
13 *******************************************************************************
14 * Copyright (c) 2005-$now By Author. This program is free software; you can *
15 * redistribute it and/or modify it under the terms of the GNU General Public *
16 * License as published by the Free Software Foundation; either version 2 of *
17 * the License or (at your option) any later version. This is online at: *
18 * http://www.fsf.org/copyleft/gpl.html *
19 * Please send any updates to: fred@gruntose.com *
20 \*****************************************************************************/
22 #include "bookmark_tree.h"
24 #include <application/command_line.h>
25 #include <application/hoople_main.h>
26 #include <basis/astring.h>
27 #include <basis/functions.h>
28 #include <basis/guards.h>
29 #include <filesystem/byte_filer.h>
30 #include <filesystem/filename.h>
31 #include <loggers/file_logger.h>
32 #include <structures/stack.h>
33 #include <structures/static_memory_gremlin.h>
34 #include <textual/list_parsing.h>
35 #include <timely/time_stamp.h>
37 using namespace application;
38 using namespace basis;
39 using namespace filesystem;
40 using namespace loggers;
41 using namespace nodes;
42 using namespace structures;
43 using namespace textual;
44 using namespace timely;
47 // uncomment to have more debugging noise.
50 #define BASE_LOG(s) program_wide_logger::get().log(s, ALWAYS_PRINT)
52 #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), \
53 a_sprintf("line %d: ", _categories._line_number) + s, ALWAYS_PRINT)
55 const int MAX_FILE_SIZE = 4 * MEGABYTE;
56 // the largest file we'll read.
58 ////////////////////////////////////////////////////////////////////////////
60 class marks_maker_javascript : public application_shell
63 marks_maker_javascript() : application_shell(), _need_closure(false),
64 _loader_count(0), _link_spool(0), _functions_pending(0) {}
65 DEFINE_CLASS_NAME("marks_maker_javascript");
66 virtual int execute();
67 int print_instructions(const filename &program_name);
69 int write_marks_page(const astring &output_filename,
70 const astring &template_filename);
71 // given a tree of links, this writes out a web page to "output_filename"
72 // using a template file "template_filename".
75 bookmark_tree _categories; // our tree of categories.
76 bool _need_closure; // true if our <div> needs a closure.
77 int _loader_count; // count of the loader functions.
78 int _link_spool; // count of which link we're writing.
79 stack<astring> _functions_pending; // used for javascript node functions.
81 //this needs to gather any strings that would have gone into functions.
82 //instead, they need to be written into the current node's string.
83 //when a new function def would be seen, then we need to push a new node
84 //for accumulating the text.
86 // these handle outputting text for categories and links.
87 void write_category(inner_mark_tree *node, astring &output);
88 void write_link(inner_mark_tree *node, const link_record &linko,
92 ////////////////////////////////////////////////////////////////////////////
94 int marks_maker_javascript::print_instructions(const filename &program_name)
96 a_sprintf to_show("%s:\n\
97 This program needs three filenames as command line parameters. The -i flag\n\
98 is used to specify the input filename, the -t flag specifies a template web\n\
99 page which is used as the wrapper around the links, and the -o flag specifies\n\
100 the web page to be created. The input file is expected to be in the HOOPLE\n\
101 link database format. The output file will be created from the template file\n\
102 by finding the phrase $INSERT_LINKS_HERE in it and replacing that with html\n\
103 formatted link and categories from the input file. Another tag of $TODAYS_DATE\n\
104 will be replaced with the date when the output file is regenerated.\n\
105 The HOOPLE link format is documented here:\n\
106 http://hoople.org/guides/link_database/format_manifesto.txt\n\
107 ", program_name.basename().raw().s(), program_name.basename().raw().s());
108 program_wide_logger::get().log(to_show, ALWAYS_PRINT);
112 void marks_maker_javascript::write_category(inner_mark_tree *node, astring &output)
114 FUNCDEF("write_category");
115 // output a javascript line for the category.
117 int node_num = node->_uid;
118 inner_mark_tree *parent = dynamic_cast<inner_mark_tree *>(node->parent());
119 int parent_node = parent? parent->_uid : -1;
120 // the parent node for root is a negative one.
121 astring chewed_name = node->name();
122 for (int i = chewed_name.end(); i >= 0; i--) {
123 // escape any raw single quotes that we see.
124 if (chewed_name[i] == '\'') {
125 chewed_name.zap(i, i);
126 chewed_name.insert(i, "\\'");
129 output += a_sprintf(" b.add(%d, %d, '%s');\n", node_num, parent_node,
133 void marks_maker_javascript::write_link(inner_mark_tree *node, const link_record &linko,
136 FUNCDEF("write_link");
137 // write a javascript link definition.
138 int parent_node = node->_uid;
139 astring chewed_name = linko._description;
140 for (int i = chewed_name.end(); i >= 0; i--) {
141 // escape any raw single quotes that we see.
142 if (chewed_name[i] == '\'') {
143 chewed_name.zap(i, i);
144 chewed_name.insert(i, "\\'");
149 // this just appears to be a comment line.
150 if (!linko._description) return; // it's a nothing line.
153 //hmmm: probably not what we want.
154 //hmmm: why not, again?
155 output += linko._description;
157 output += parser_bits::platform_eol_to_chars();
162 // generate a function header if the number of links is a multiple of 100.
163 if (! (_link_spool % 100) ) {
165 // close out the previous function and set a timeout.
166 output += " setTimeout('run_tree_loaders()', 0);\n";
170 output += a_sprintf("function tree_loader_%d() {\n", _loader_count++);
174 output += a_sprintf(" b.add(%d, %d, '%s', '%s');\n",
175 linko._uid, parent_node, chewed_name.s(), linko._url.s());
178 int marks_maker_javascript::execute()
183 command_line cmds(_global_argc, _global_argv); // process the command line parameters.
184 astring input_filename; // we'll store our link database name here.
185 astring output_filename; // where the web page we're creating goes.
186 astring template_filename; // the wrapper html code that we'll stuff.
187 if (!cmds.get_value('i', input_filename, false))
188 return print_instructions(cmds.program_name());
189 if (!cmds.get_value('o', output_filename, false))
190 return print_instructions(cmds.program_name());
191 if (!cmds.get_value('t', template_filename, false))
192 return print_instructions(cmds.program_name());
194 BASE_LOG(astring("input file: ") + input_filename);
195 BASE_LOG(astring("output file: ") + output_filename);
196 BASE_LOG(astring("template file: ") + template_filename);
198 int ret = _categories.read_csv_file(input_filename);
201 ret = write_marks_page(output_filename, template_filename);
207 int marks_maker_javascript::write_marks_page(const astring &output_filename,
208 const astring &template_filename)
210 FUNCDEF("write_marks_page");
212 // this is our accumulator of links. it is the semi-final result that will
213 // be injected into the template file.
215 // add our target layer so that we can write to a useful place.
216 long_string += "<div class=\"marks_target\" id=\"martarg\">Marks Target</div>\n";
218 // add the tree style and creation of the tree object into the text.
219 long_string += "\n<div class=\"dtree\">\n";
220 long_string += "<script type=\"text/javascript\">\n";
221 long_string += "<!--\n";
223 long_string += "function open_mark(url) {\n";
224 long_string += " window.open(url, '', '');\n";
225 long_string += "}\n";
227 // the list of functions is used for calling into the tree loaders
229 long_string += " b = new dTree('b');\n";
230 /// long_string += " b.config.inOrder = true;\n";
231 long_string += " b.config.useCookies = false;\n";
232 long_string += " b.config.folderLinks = false;\n";
234 // traverse the tree in prefix order.
235 tree::iterator itty = _categories.access_root().start(tree::prefix);
237 while ( (curr = itty.next()) ) {
238 inner_mark_tree *nod = (inner_mark_tree *)curr;
239 // print out the category on this node.
240 write_category(nod, long_string);
242 // print the link for all of the ones stored at this node.
243 for (int i = 0; i < nod->_links.elements(); i++) {
244 link_record *lin = nod->_links.borrow(i);
245 write_link(nod, *lin, long_string);
249 // close the block of script in the output.
250 long_string += " setTimeout('run_tree_loaders()', 0);\n";
251 long_string += "}\n\n";
253 long_string += a_sprintf("function tree_loader_%d()"
254 "{ setTimeout('run_tree_loaders()', 0); }\n", _loader_count++);
256 long_string += "\nconst max_funcs = 1000;\n";
257 long_string += "var loader_functions = new Array(max_funcs);\n";
258 long_string += "var curr_func = 0;\n";
259 long_string += "var done_rendering = false;\n\n";
261 long_string += a_sprintf("for (var i = 0; i < %d; i++) {\n", _loader_count);
262 long_string += " loader_functions[curr_func++] "
263 "= 'tree_loader_' + i + '()';\n";
264 long_string += "}\n";
266 long_string += "var run_index = 0;\n";
267 long_string += "function run_tree_loaders() {\n";
268 long_string += " if (done_rendering) return;\n";
269 long_string += " if (run_index >= curr_func) {\n";
271 long_string += " if (document.getElementById) {\n";
272 long_string += " x = document.getElementById('martarg');\n";
273 long_string += " x.innerHTML = '';\n";
274 long_string += " x.innerHTML = b;\n";
275 long_string += " } else { document.write(b); }\n";
276 //not a very graceful degradation. we should use the other options from:
277 // http://www.quirksmode.org/js/layerwrite.html
278 long_string += " done_rendering = true;\n";
279 long_string += " return;\n";
280 long_string += " }\n";
281 long_string += " var next_func = loader_functions[run_index++];\n";
282 long_string += " setTimeout(next_func, 0);\n";
283 long_string += "}\n";
285 long_string += a_sprintf(" run_tree_loaders();\n", _loader_count);
287 long_string += "//-->\n";
288 long_string += "</script>\n";
289 long_string += "<p><a href=\"javascript: b.openAll();\">open all</a> | "
290 "<a href=\"javascript: b.closeAll();\">close all</a></p>\n";
291 long_string += "</div>\n";
293 byte_filer template_file(template_filename, "r");
294 astring full_template;
295 if (!template_file.good())
296 non_continuable_error(class_name(), func, "the template file could not be opened");
297 template_file.read(full_template, MAX_FILE_SIZE);
298 template_file.close();
300 // javascript output needs some extra junk added to the header section.
301 int indy = full_template.ifind("</title>");
303 non_continuable_error(class_name(), func, "the template file is missing "
304 "a <head> declaration");
305 //hmmm: the path here must be configurable!
306 full_template.insert(indy + 8, "\n\n"
307 "<link rel=\"StyleSheet\" href=\"/yeti/javascript/dtree/dtree.css\" "
308 "type=\"text/css\" />\n"
309 "<script type=\"text/javascript\" src=\"/yeti/javascript/"
310 "dtree/dtree.js\"></script>\n");
312 // replace the tag with the long string we created.
313 bool found_it = full_template.replace("$INSERT_LINKS_HERE", long_string);
315 non_continuable_error(class_name(), func, "the template file is missing "
316 "the insertion point");
317 full_template.replace("$TODAYS_DATE", time_stamp::notarize(true));
319 filename outname(output_filename);
320 if (outname.exists()) {
321 non_continuable_error(class_name(), func, astring("the output file ")
322 + output_filename + " already exists. It would be over-written if "
326 byte_filer output_file(output_filename, "w");
327 if (!output_file.good())
328 non_continuable_error(class_name(), func, "the output file could not be opened");
329 // write the newly generated web page out now.
330 output_file.write(full_template);
335 // BASE_LOG(_categories.access_root().text_form());
337 BASE_LOG(a_sprintf("wrote %d links in %d categories.",
338 _categories.link_count(), _categories.category_count()));
339 BASE_LOG(astring(""));
344 ////////////////////////////////////////////////////////////////////////////
346 HOOPLE_MAIN(marks_maker_javascript, )