#include #include #include #include #include #include // XXX for ctime -- remove once ctime is removed enum { too_long_for_filename = 128 }; #include "dbsubst.hpp" #include "filters.hpp" #include "fpublish.hpp" // for enum file_publish_methods #include "arrindex.hpp" #include "dbforum.hpp" #include "forumgen.hpp" #include "database.hpp" static bool svec_has_elem(const ScriptVector &v, const ScriptVariable &el) { int i; for(i = 0; i < v.Length(); i++) { if(v[i] == el) return true; } return false; } bool ListItemData::HasTag(const ScriptVariable &t) const { return svec_has_elem(tags, t); } bool ListItemData::HasFlag(const ScriptVariable &f) const { return svec_has_elem(flags, f); } bool CommentData::HasFlag(const ScriptVariable &f) const { return svec_has_elem(flags, f); } /* We need this structure for the ``set''-based lists only */ struct SetListIndex { ScriptVariable set_id, tag; ScriptVector items; SetListIndex *next; }; ///////////////////////////////// // BlockGroups must be defined before the Database's destructor struct BlockGroupData { ScriptVariable id; ScriptVariable begin, end, block_templ; struct BlockData *first; BlockGroupData *next_grp; BlockGroupData(const char *i, const char *b, const char *e, const char *bt) : id(i), begin(b), end(e), block_templ(bt), first(0), next_grp(0) {} ~BlockGroupData(); }; struct BlockData { ScriptVariable id; int weight; ScriptVariable tag, title, body; BlockData *next; BlockData(const char *i, int w, const char *tg, const char *tit, const char *b) : id(i), weight(w), tag(tg), title(tit), body(b), next(0) {} }; BlockGroupData::~BlockGroupData() { if(next_grp) delete next_grp; while(first) { BlockData *tmp = first; first = first->next; delete tmp; } } ///////////////////////////////// Database::Database() : subst(0), filtmaker(0), current_list_data(0), current_list_item_data(0), first_block_group(0), first_set_list(0) { inifile = new IniFileParser; // subst object is to be created after loading the inifile, // because some of the "variables" are initialized by calling // methods of the database, so these methods must already // be prepared to return their respective values } Database::~Database() { if(subst) delete subst; delete inifile; if(filtmaker) delete filtmaker; while(first_set_list) { SetListIndex *tmp = first_set_list; first_set_list = first_set_list->next; delete tmp; } if(first_block_group) delete first_block_group; } void Database::SetOptSelector(const ScriptVariable &whatfor, const ScriptVariable &sel) { if(whatfor.IsInvalid() || whatfor == "") { default_opt_selector = sel; return; } int i; for(i = 0; i+1 < opt_selectors.Length(); i += 2) { if(opt_selectors[i] == whatfor) { opt_selectors[i+1] = sel; return; } } opt_selectors.AddItem(whatfor); opt_selectors.AddItem(sel); } bool Database::Load(const char *filename) { bool ok = inifile->Load(filename); if(subst) delete subst; subst = new DatabaseSubstitution(this); if(default_opt_selector.IsValid()) inifile->SetTextParameter("general", 0, "opt_selector", default_opt_selector.c_str()); return ok; } void Database::MakeErrorMessage(class ScriptVariable &msg) const { msg = ""; int el = inifile->GetLastErrorLine(); if(el != -1) msg += ScriptNumber(el) + ": "; else msg += " "; msg += inifile->GetLastErrorDescription(); } bool Database::GetExtraFiles(ScriptVector &ef) { const char *tmp = inifile->GetTextParameter("general", 0, "inifiles", 0); if(!tmp) return false; ScriptVector v((*subst)(tmp), " ,\t\r\n"); if(v.Length() < 1) return false; ef = v; inifile->SetTextParameter("general", 0, "inifiles", ""); return true; } ScriptVariable Database::GetHtmlSnippet(const ScriptVariable &name) const { return inifile->GetTextParameter("html", 0, name.c_str(), ""); } ScriptVariable Database::BuildGenericPart(const ScriptVariable &templ) const { return (*subst)(templ); } #if 0 ScriptVariable Database::GetSourcePrefix() const { if(source_prefix.IsInvalid()) const_cast(this)->source_prefix = inifile->GetTextParameter("general", 0, "srcdir", ""); return source_prefix; } #endif ScriptVariable Database::GetFilePrefix() const { if(file_prefix.IsInvalid()) { ScriptVariable fp = inifile->GetTextParameter("general", 0, "rootdir", "html"); fp = (*subst)(fp); if(fp.Range(-1,1)[0] != '/') fp += '/'; const_cast(this)->file_prefix = fp; } return file_prefix; } ScriptVariable Database::GetSpoolDir() const { return (*subst)(inifile-> GetTextParameter("general", 0, "spooldir", "_spool")); } void Database::SetFilePrefix(ScriptVariable s) { file_prefix = (s.Range(-1,1)[0] == '/') ? s : s + '/'; } ScriptVariable Database::GetBaseUri() const { if(base_uri.IsInvalid()) { const char *tmp = inifile->GetTextParameter("general", 0, "base_uri", "/"); if(tmp) const_cast(this)->base_uri = (*subst)(tmp); } return base_uri; } ScriptVariable Database::GetBaseUrl() const { return (*subst)(inifile->GetTextParameter("general", 0, "base_url", "")); } ScriptVariable Database::GetOption(const ScriptVariable §ion, const ScriptVariable &name) const { const char *tmp; int sel_idx = -1; int i; for(i = 0; i+1 < opt_selectors.Length(); i += 2) { if(opt_selectors[i] == section) { sel_idx = i + 1; break; } } if(sel_idx == -1) { tmp = inifile->GetModifiedTextParameter("general", 0, "opt_selector", section.c_str(), ""); ScriptVector &opsel = const_cast(this)->opt_selectors; opsel.AddItem(section); opsel.AddItem(tmp); sel_idx = opsel.Length() - 1; // the last, which is just added } // NB: now sel_idx is valid, as well as opt_selectors[sel_idx] const char *sstr = section.c_str(); const char *nstr = name.c_str(); const char *selstr = opt_selectors[sel_idx].c_str(); tmp = selstr && *selstr ? inifile->GetModifiedTextParameter("options", sstr, nstr, selstr, 0) : inifile->GetTextParameter("options", sstr, nstr, 0); if(!tmp) return ScriptVariableInv(); return tmp; } int Database::GetGenfiles(class ScriptVector &names) const { return GetSectionNames("genfile", names); } int Database::GetPages(class ScriptVector &names) const { return GetSectionNames("page", names); } int Database::GetLists(class ScriptVector &names) const { return GetSectionNames("list", names); } int Database::GetSets(class ScriptVector &names) const { return GetSectionNames("pageset", names); } int Database::GetCollections(class ScriptVector &names) const { return GetSectionNames("collection", names); } int Database::GetBinaries(class ScriptVector &names) const { return GetSectionNames("binary", names); } int Database::GetAliasSections(ScriptVector &names) const { return GetSectionNames("aliases", names); } int Database::GetSectionNames(const char *gn, class ScriptVector &names) const { int cnt = inifile->GetSectionCount(gn); int i; names.Clear(); for(i = 0; i < cnt; i++) names[i] = inifile->GetSectionName(gn, i); return cnt; } static int extract_chmod(IniFileParser *ini, const char *grp, const char *id) { const char *tmp = ini->GetTextParameter(grp, id, "chmod", 0); if(!tmp) return 0; long mode; bool ok = ScriptVariable(tmp).GetLong(mode, 8); return ok ? mode : 0; } bool Database::BuildGenericFile(const ScriptVariable &id, ScriptVariable &path, int &chmod_value, ScriptVariable &cont) const { const char *nm = inifile->GetTextParameter("genfile", id.c_str(), "path", 0); path = nm ? (*subst)(nm) : id; chmod_value = extract_chmod(inifile, "genfile", id.c_str()); const char *cn = inifile->GetTextParameter("genfile", id.c_str(), "content", 0); if(!cn) return false; cont = (*subst)(cn); return true; } /* pages */ ScriptVariable Database::GetPageFilename(const ScriptVariable &name) const { const char *path = inifile->GetTextParameter("page", name.c_str(), "path", 0); if(path && *path) { ScriptVariable pt(path); pt = (*subst)(pt); pt.Trim(); if(pt == "" || pt == "." || pt == "-") return ScriptVariableInv(); return GetFilePrefix() + pt; } return GetFilePrefix() + name; } int Database::GetPageChmod(const ScriptVariable &pg_id) const { return extract_chmod(inifile, "page", pg_id.c_str()); } ScriptVariable Database::BuildPage(const ScriptVariable &name) const { const char *nc = name.c_str(); const char *tmpl = inifile->GetTextParameter("page", nc, "template", 0); if(!tmpl) { const char *res = inifile->GetTextParameter("page", nc, "body", 0); if(!res) return ScriptVariableInv(); return (*subst)(res); } return ExpandTemplate(tmpl, "page", nc); } static bool boolean_value_from_string(const char *str) { return str && ScriptVariable(str).Trim().Tolower() == "yes"; } static bool boolean_value_from_scrvar(ScriptVariable s) { return s.IsValid() && s.Trim().Tolower() == "yes"; } /* lists */ static list_source_type srctype_by_name(ScriptVariable s) { s.Trim(); s.Tolower(); if(s == "ini") return listsrc_ini; if(s == "set") return listsrc_set; return listsrc_unknown; } static void reverse_script_vector(ScriptVector &v) { int i, j; for(i = 0, j = v.Length()-1; i < j; i++, j--) { ScriptVariable t = v[i]; v[i] = v[j]; v[j] = t; } } bool Database::IsListEmbedded(const ScriptVariable &list_id) const { const char *lsid = list_id.c_str(); const char *tmp = inifile->GetTextParameter("list", lsid, "embedded", 0); return boolean_value_from_string(tmp); } bool Database::GetListData(const ScriptVariable &list_id, ListData &data) const { data.id = list_id; data.embedded = IsListEmbedded(list_id); const char *lsid = list_id.c_str(); const char *srcstr = inifile->GetTextParameter("list", lsid, "source", 0); if(srcstr) { ScriptWordVector v(srcstr); data.srctype = srctype_by_name(v[0]); if(data.srctype == listsrc_unknown) return false; data.srcname = v.Length() < 2 ? list_id : v[1]; data.tag = v.Length() < 3 ? "" : v[2]; if(data.srctype == listsrc_set && (data.tag.IsInvalid() || data.tag == "")) { return false; } } else { #if 0 data.srctype = listsrc_ini; data.srcname = list_id; #else return false; #endif } const char *tmp; tmp = inifile->GetTextParameter("list", lsid, "reverse", 0); data.reverse = boolean_value_from_string(tmp); tmp = inifile->GetTextParameter("list", lsid, "reverse_source", 0); bool reverse_source = boolean_value_from_string(tmp); data.list_header = inifile->GetTextParameter("list", lsid, "list_header", ""); data.list_footer = inifile->GetTextParameter("list", lsid, "list_footer", ""); data.list_item_templ = inifile->GetTextParameter("list", lsid, "list_item_template", ""); if(data.embedded) { data.items_per_listpage = 0; data.main_listpage_name.Invalidate(); data.listpage_name_templ.Invalidate(); } else { data.items_per_listpage = inifile->GetIntegerParameter("list",lsid, "items_per_listpage", 0); tmp = inifile->GetTextParameter("list", lsid, "main_listpage_name", 0); data.main_listpage_name = tmp ? ScriptVariable(tmp) : list_id; tmp = inifile->GetTextParameter("list",lsid, "listpage_name_templ", 0); data.listpage_name_templ = tmp ? ScriptVariable(tmp) : list_id + "_p%n%"; } if(data.srctype == listsrc_ini) { ScriptVariable auxp = inifile->GetTextParameter("list", lsid, "aux_params", ""); data.aux_params.Clear(); data.aux_params = ScriptVector(auxp, " ,\t\r\n"); } tmp = inifile->GetTextParameter("list", lsid, "pages", 0); data.pages = boolean_value_from_string(tmp); if(data.pages) { data.itempage_templ = inifile->GetTextParameter("list", lsid, "itempage_template", ""); data.itempage_tail_templ = inifile->GetTextParameter("list",lsid,"itempage_tail_template",""); data.comments_conf = inifile->GetTextParameter("list", lsid, "comments", ""); } switch(data.srctype) { case listsrc_ini: GetSectionNames(data.srcname.c_str(), data.items); break; case listsrc_set: ScanSetTagItems(data.srcname, data.tag, data.items); break; default: return false; } if(reverse_source) reverse_script_vector(data.items); int last_only = inifile->GetIntegerParameter("list", lsid, "last_items_only", 0); if(last_only > 0 && data.items.Length() > last_only) { data.items.Remove(0, data.items.Length() - last_only); } return true; } void Database::GetListNavigationInfo(const ScriptVariable &name, int &items_per_page, ScriptVariable &set_id, ScriptVariable &tag) const { const char *lsid = name.c_str(); set_id = ""; tag = ""; const char *srcstr = inifile->GetTextParameter("list", lsid, "source", 0); if(srcstr) { ScriptWordVector v(srcstr); if(v[0] == "set") { set_id = v[1]; tag = v[2]; } } items_per_page = inifile->GetIntegerParameter("list", lsid, "items_per_listpage", 0); } bool Database::GetSetListIndexInfo(const ScriptVariable &set_id, const ScriptVariable &tag, const ScriptVariable &item_id, int &index, ScriptVariable &prev, ScriptVariable &next) const { SetListIndex *sli = const_cast(this)->ProvideSetListIndex(set_id, tag); if(!sli) return false; int i; int len = sli->items.Length(); for(i = 0; i < len; i++) if(sli->items[i] == item_id) { index = i + 1; // they are 1-based prev = i > 0 ? sli->items[i-1] : ScriptVariableInv(); next = i < len-1 ? sli->items[i+1] : ScriptVariableInv(); return true; } return false; } // XXX this function should perhaps disappear! // instead, the database should provide a method to generate textual date static void fill_date_from_unixtime(ListItemData &data) { if(data.unixtime > 0) { time_t tt = data.unixtime; data.date = ScriptVariable(asctime(gmtime(&tt))); data.date.Trim(); data.date += " UTC"; // XXX we need more flexibility here! } else { data.date = ScriptVariable("NODATE[") + data.item_id + "]"; } } bool Database::GetListItemData(const ListData &lsdata, int idx, ListItemData &data) const { const ScriptVariable &ls_name = lsdata.srcname; const ScriptVariable &item_id = lsdata.items[idx]; ScriptVariableInv inval; data.make_separate_directory = false; // for ``ini-sourced'' lists the opposit isn't supported data.item_id = item_id; data.index = idx+1; // remember they are 1-based data.prev_id = idx > 0 ? lsdata.items[idx-1] : inval; data.next_id = idx+1 < lsdata.items.Length() ? lsdata.items[idx+1] : inval; const char *lsc = ls_name.c_str(); const char *idc = item_id.c_str(); const char *utime_str = inifile->GetTextParameter(lsc, idc, "unixtime", 0); if(!utime_str || !*utime_str) { data.unixtime = 0; } else { long long t; bool ok = ScriptVariable(utime_str).GetLongLong(t, 10); data.unixtime = ok ? t : -1; } const char *date_str = inifile->GetTextParameter(lsc, idc, "date", 0); if(date_str) data.date = (*subst)(date_str); else fill_date_from_unixtime(data); data.title = (*subst)(inifile->GetTextParameter(lsc, idc, "title", "")); data.descr = (*subst)(inifile->GetTextParameter(lsc, idc, "descr", "")); ScriptVariable tagsstr = (*subst)(inifile->GetTextParameter(lsc, idc, "tags", "")); data.tags.Clear(); data.tags = ScriptVector(tagsstr, ",", " \t\r\n"); data.comments = inifile->GetTextParameter(lsc, idc, "comments", ""); int i; for(i = 0; i < lsdata.aux_params.Length(); i++) { const char *ap = inifile->GetTextParameter(lsc, idc, lsdata.aux_params[i].c_str(), 0); if(!ap) continue; data.aux_params.AddItem(lsdata.aux_params[i]); data.aux_params.AddItem((*subst)(ap)); } data.text = (*subst)(inifile->GetTextParameter(lsc, idc, "text", "")); return true; // XXXXXXX well... is this really okay? } ScriptVariable Database::GetListPgpath(const ScriptVariable &ls_id, int num) const { if(num < 1) { const char *tmp = inifile->GetTextParameter("list", ls_id.c_str(), "main_listpage_name", 0); if(tmp) return (*subst)(tmp); } ScriptVariable res = inifile->GetTextParameter("list", ls_id.c_str(), "listpage_name_templ", ""); if(res == "") { res = ls_id + "_p" + ScriptNumber(num) + ".html"; } else { ScriptMacroprocessor sub_sub(subst); add_index_macros(sub_sub, num); res = sub_sub(res); } return res; } ScriptVariable Database::GetListFilename(const ScriptVariable &ls_id, int num) const { return GetFilePrefix() + GetListPgpath(ls_id, num); } ScriptVariable Database::GetListHref(const ScriptVariable &ls_id, int num) const { return GetBaseUri() + GetListPgpath(ls_id, num); } ScriptVariable Database::GetListItemFilename(const ScriptVariable &ls_id, const ScriptVariable &pg_id, int pg_idx) const { ScriptVariable tmp = inifile->GetTextParameter("list", ls_id.c_str(), "itempage_name", ""); if(tmp == "") tmp = ls_id + "_" + pg_id + "%_idx%.html"; ScriptMacroprocessor sub_sub(subst); add_index_macros(sub_sub, pg_idx); return GetFilePrefix() + sub_sub(tmp); } ScriptVariable Database::GetListItemCommentmapFname( const ScriptVariable &ls_id, const ScriptVariable &pg_id) const { const char *tmp = inifile->GetTextParameter("list", ls_id.c_str(), "commentmap", 0); if(!tmp) return ScriptVariableInv(); return GetFilePrefix() + (*subst)(tmp); } ScriptVariable Database::GetListItemHref(const ScriptVariable &ls_id, const ScriptVariable &pg_id, int pg_idx) const { ScriptVariable tmp = inifile->GetTextParameter("list", ls_id.c_str(), "itempage_name", ""); ScriptMacroprocessor sub_sub(subst); add_index_macros(sub_sub, pg_idx); return GetBaseUri() + sub_sub(tmp); } // IMPORTANT: there must be a single object for substitutions inside the // database, being created within the constructor // It must be able to "know" and "forget" the ListData/ListItem structs ScriptVariable Database::BuildListHead(const ListData &lsd) const { //SetListData(&lsd); ScriptVariable res = (*subst)(lsd.list_header); //ForgetListCmtData(); return res; } ScriptVariable Database::BuildListTail(const ListData &lsd) const { //SetListData(&lsd); ScriptVariable res = (*subst)(lsd.list_footer); //ForgetListCmtData(); return res; } ScriptVariable Database::BuildListItem(const ListData &lsd, const ListItemData &itd) const { return BuildListItemPart(lsd, itd, lsd.list_item_templ); } ScriptVariable Database::BuildListItemPart(const ListData &lsd, const ListItemData &itd, const ScriptVariable &templ) const { //SetListData(&lsd, &itd); ScriptVariable res = (*subst)(templ); //ForgetListCmtData(); return res; } void Database::BuildListItemPage(const ListData &lsd, const ListItemData &itd, ScriptVariable &main, ScriptVariable &tail, ForumGenerator **fgp) const { main = BuildListItemPart(lsd, itd, lsd.itempage_templ); tail = BuildListItemPart(lsd, itd, lsd.itempage_tail_templ); *fgp = GetComments(lsd.comments_conf); } #if 0 void Database::FillArrayDataForList(const ListData &lsd, ArrayData &ad) const { int itemcnt = lsd.items.Length(); int perpage = lsd.items_per_listpage; ad.item_count = itemcnt; ad.items_per_page = perpage; ad.page_count = perpage <= 0 ? 1 : (itemcnt ? (itemcnt-1)/perpage+1 : 0); ad.reverse = lsd.reverse; ad.href_array.Clear(); ScriptMacroprocessor sub_sub(subst); ScriptVariable v_idx; // NB: no zero index is used here! sub_sub.AddMacro(new ScriptMacroScrVar("idx", &v_idx)); sub_sub.AddMacro(new ScriptMacroScrVar("idx0", &v_idx)); sub_sub.AddMacro(new ScriptMacroScrVar("_idx", &v_idx)); int i; for(i = 1; i <= ad.page_count; i++) { v_idx = ScriptNumber(i); ad.href_array[i] = GetBaseUri() + sub_sub(lsd.listpage_name_templ); } if(ad.reverse) { ad.href_first = ScriptVariableInv(); ad.href_last = GetBaseUri() + lsd.main_listpage_name; } else { ad.href_first = GetBaseUri() + lsd.main_listpage_name; ad.href_last = ScriptVariableInv(); } ad.current_page = -1; } void Database::FillArrayDataForListItem(const ListData &lsd, const ListItemData &itd, ForumGenerator *fgp, ArrayData &ad) const { int pgcnt; bool reverse; ad.href_array.Clear(); if(!fgp || !fgp->GetArrayInfo(pgcnt, reverse) || pgcnt < 2) { ad.item_count = 0; ad.items_per_page = 0; ad.page_count = 0; ad.current_page = -1; return; } ad.item_count = pgcnt; // XXXX do we really need these two fields?! ad.items_per_page = 1; ad.page_count = pgcnt; ad.reverse = reverse; int i; for(i = 1; i <= pgcnt; i++) ad.href_array[i] = GetListItemHref(lsd.id, itd.item_id, i); ad.href_first = GetListItemHref(lsd.id, itd.item_id, 0); ad.href_last = ScriptVariableInv(); ad.current_page = -1; } #endif #if 0 ScriptVariable Database::BuildListSegment(const ListData &lsd, const ScriptVariable &tag, const ScriptVariable &templ) const { ScriptVariable res(""); int i; for(i = 0; i < lsd.items.Length(); i++) { ListItemData itd; if(!GetListItemData(lsd, i, itd)) continue; if(itd.HasTag(tag)) res += BuildListItem(lsd, itd, templ); } return res; } #endif static int method_by_name(const char *name) { ScriptVariable s(name); s.Trim(); s.Tolower(); if(s == "copy") return fpm_copy; if(s == "link") return fpm_link; if(s == "symlink") return fpm_symlink; return fpm_none; } static int symlinks_mode_by_name(const char *name) { ScriptVariable s(name); s.Trim(); s.Tolower(); if(s == "follow") return fpm_slnk_follow; if(s == "preserve") return fpm_slnk_preserve; #if 0 // XXX perhaps this should differ from the unknown case? if(s == "" || s == "ignore") return 0; #endif return 0; } static int extract_publish_method(IniFileParser *ini, const char *grp, const char *id) { const char *tmp; int res; tmp = ini->GetTextParameter(grp, id, "publish_method", "none"); res = method_by_name(tmp); tmp = ini->GetTextParameter(grp, id, "publish_recursive", 0); if(boolean_value_from_string(tmp)) res |= fpm_recursive; tmp = ini->GetTextParameter(grp, id, "publish_hidden", 0); if(boolean_value_from_string(tmp)) res |= fpm_includehidden; tmp = ini->GetTextParameter(grp, id, "publish_symlinks", ""); res |= symlinks_mode_by_name(tmp); long mode = extract_chmod(ini, grp, id); if(mode) res |= (mode & fpm_mode_mask); return res; } ScriptVariable Database::GetSetSourceDir(const ScriptVariable &set_id) const { const char *tmp = inifile->GetTextParameter("pageset", set_id.c_str(), "sourcedir", 0); #if 0 if(!tmp) return GetSourcePrefix() + set_id; if(*tmp == '/') return tmp; return GetSourcePrefix() + tmp; #endif if(!tmp) return set_id; return tmp; } static int extract_make_subdirs_field(const char *s) { if(!s) return pageset_subdir_bysource; ScriptVariable sv(s); sv.Trim(); sv.Tolower(); if(sv == "always") return pageset_subdir_always; if(sv == "never") return pageset_subdir_never; return pageset_subdir_bysource; } bool Database:: GetSetData(const ScriptVariable &set_id, PageSetData &data) const { const char *tmp; ScriptVariable str; data.id = set_id; data.source_dir = GetSetSourceDir(set_id); const char *idc = set_id.c_str(); #if 0 // from now on, these fields may be modified with the pg ``type'' data.page_templ = inifile->GetTextParameter("pageset", idc, "page_template", ""); data.page_tail_templ = inifile->GetTextParameter("pageset", idc, "page_tail_template", ""); #endif #if 0 tmp = inifile->GetTextParameter("pageset", idc, "auxfiles", 0); data.aux_files.Clear(); if(tmp) data.aux_files = ScriptWordVector(tmp); #endif data.publish_method = extract_publish_method(inifile, "pageset", idc); tmp = inifile->GetTextParameter("pageset", idc, "make_subdirs", 0); data.make_subdirs = extract_make_subdirs_field(tmp); tmp = inifile->GetTextParameter("pageset", idc, "setdirname", 0); data.setdirname = tmp ? ScriptVariable(tmp) : set_id; tmp = inifile->GetTextParameter("pageset", idc, "pagedirname", 0); data.pagedirname = tmp ? ScriptVariable(tmp) : "%[li:id]"; tmp = inifile->GetTextParameter("pageset", idc, "indexfilename", 0); data.indexfilename = tmp ? ScriptVariable(tmp) : "index.html"; tmp = inifile->GetTextParameter("pageset", idc, "compagename", 0); data.compagename = tmp ? ScriptVariable(tmp) : "c%idx%.html"; tmp = inifile->GetTextParameter("pageset", idc, "pagefilename", 0); data.pagefilename = tmp ? ScriptVariable(tmp) : "%[li:id]%[_idx].html"; data.comments_conf = inifile->GetTextParameter("pageset", idc, "comments", ""); tmp = inifile->GetTextParameter("pageset", idc, "commentmap", 0); data.cmap_filename = tmp ? ScriptVariable(tmp) : ScriptVariableInv(); tmp = inifile->GetModifiedTextParameter("pageset", idc, "commentmap", "nodir", 0); data.cmap_filename_nodir = tmp ? ScriptVariable(tmp) : ScriptVariableInv(); data.page_ids.Clear(); return true; } void Database::ScanSetDirectory(PageSetData &data) const { ReadDir dir(data.source_dir.c_str()); const char *nm; while((nm = dir.Next())) { if(*nm == '.' || *nm == '_') continue; FileStat st((data.source_dir + "/" + nm).c_str()); if(!st.Exists() || (!st.IsDir() && !st.IsRegularFile())) continue; // X-X-X implement sorting here! X-X-X really?... // perhaps no, because it should be done upon dates not ids // *NO* actually, all lists must already be built inside the // database, it is senseless to sort every time instead of // maintaining the lists in the sorted state data.page_ids.AddItem(nm); } } bool Database::ScanSetTagItems(const ScriptVariable &set_id, const ScriptVariable &tag, ScriptVector &items) const { SetListIndex *tmp = const_cast(this)->ProvideSetListIndex(set_id, tag); if(!tmp) return false; items = tmp->items; return true; #if 0 ScriptVariable sd = GetSetSourceDir(set_id); ReadStream f; if(!f.FOpen((sd + "/" + tag).c_str())) return false; items.Clear(); ScriptVector line; while(f.ReadLine(line, 2, ":", " \t\r\n")) { if(line.Length() < 2) continue; long idx; if(!line[0].GetLong(idx, 10)) continue; // REMEMBER, indices are from 1, not 0, but the items indexed from 0 if(idx < 1 || idx > THALASSA_LIST_INDEX_LIMIT) continue; items[idx-1] = line[1]; } return true; #endif } FilterChainSet *Database::MakeFormatFilter(const HeadedTextMessage &msg) const { ProvideFilterChainMaker(); return filtmaker->MakeChainSet(msg.GetHeaders()); } bool Database::GetSetItemData(const PageSetData &setd, int idx, ListItemData *itd) const { if(idx >= setd.page_ids.Length()) return false; return GetSetItemDataById(setd.id, setd.page_ids[idx], itd); } #if 0 static void read_set_item_navigation_hints(const char *path, ListItemData *itd) { ReadStream nf; if(nf.FOpen(path)) { ScriptVariable wholefile; nf.ReadUntilEof(wholefile); nf.FClose(); ScriptVector strs(wholefile, "\n"); if(strs.Length() > 0) { itd->nav_hints_count = strs.Length(); itd->nav_hints = new NavigationHints[strs.Length()]; int i; for(i = 0; i < strs.Length(); i++) { ScriptTokenVector nav(strs[i], ":", " \t\r\n"); itd->nav_hints[i].key = nav[0]; long ix; itd->nav_hints[i].index = (nav[1].GetLong(ix, 10) && ix >= 0) ? ix : -1; itd->nav_hints[i].prev = nav[2]; itd->nav_hints[i].cur = nav[3]; itd->nav_hints[i].next = nav[4]; } itd->index = itd->nav_hints[0].index; itd->prev_id = itd->nav_hints[0].prev; itd->next_id = itd->nav_hints[0].next; } } } #endif static void scan_set_item_dir_for_files(ScriptVariable path, ScriptVector &v) { v.Clear(); ReadDir dir(path.c_str()); const char *nm; while((nm = dir.Next())) { if(*nm == '.' || *nm == '_') continue; ScriptVariable nms(nm); if(nms == PAGESET_MAIN_FILE_NAME) continue; FileStat st((path + "/" + nms).c_str()); if(!st.Exists() || !st.IsRegularFile()) continue; v.AddItem(nm); } } bool Database:: GetSetItemSource(const ScriptVariable &set_id, const ScriptVariable &page_id, ScriptVariable &srcd, ScriptVariable &fname, bool &dedic_dir) const { static ScriptVariable saved_set_id(0), set_src_path(0); if(saved_set_id != set_id) { saved_set_id = set_id; set_src_path = GetSetSourceDir(set_id); } srcd = set_src_path + "/" + page_id; FileStat st(srcd.c_str()); if(!st.Exists()) return false; if(st.IsRegularFile()) { fname = srcd; dedic_dir = false; } else { fname = srcd + "/" PAGESET_MAIN_FILE_NAME; dedic_dir = true; } return true; } bool Database::GetSetItemDataById(const ScriptVariable &set_id, const ScriptVariable &page_id, ListItemData *itd) const { itd->item_id = page_id; ScriptVariable srcd, fname; bool it_is_dir; bool ok = GetSetItemSource(set_id, page_id, srcd, fname, it_is_dir); if(!ok) { itd->title = fname + ": file doesn't exist"; return false; } itd->make_separate_directory = it_is_dir; FILE *s = fopen(fname.c_str(), "r"); if(!s) { itd->title = fname + ": couldn't open file"; return false; } int c; HeadedTextMessage parser(false); while((c = fgetc(s)) != EOF) { if(!parser.FeedChar(c)) // ok, int break; } fclose(s); long teaser_len = -1; FilterChainSet *filt = MakeFormatFilter(parser); int i; const ScriptVector& hdr = parser.GetHeaders(); for(i = 0; i < hdr.Length()-1; i+=2) { if(hdr[i] == "id" || hdr[i] == "encoding" || hdr[i] == "format") { // no storing continue; } if(hdr[i] == "unixtime") { bool ok = hdr[i+1].GetLongLong(itd->unixtime, 10); if(!ok) itd->unixtime = -1; continue; } if(hdr[i] == "type") { itd->pgtype = hdr[i+1]; // no conversion here, it's a enum id continue; } if(hdr[i] == "flags") { itd->flags.Clear(); itd->flags = ScriptVector(hdr[i+1], ",", " \t\r\n"); continue; } if(hdr[i] == "comments") { itd->comments = hdr[i+1]; // no conversion here, it's a enum id continue; } if(hdr[i] == "teaser_len") { bool ok = hdr[i+1].GetLong(teaser_len, 10); if(!ok) teaser_len = -1; continue; } // don't forget there's also filt->ConvertData // for the data actually generated by the system // NOT entered by users if(hdr[i] == "descr") { itd->descr = filt->ConvertContent(hdr[i+1]); continue; } // the rest of fields are converted as Userdata ScriptVariable val = filt->ConvertUserdata(hdr[i+1]); if(hdr[i] == "date") { itd->date = val; continue; } if(hdr[i] == "title") { itd->title = val; continue; } if(hdr[i] == "tags") { itd->tags.Clear(); itd->tags = ScriptVector(val, ",", " \t\r\n"); continue; } // okay, no fields for this parameter in the structure // just store it in the aux_params vector itd->aux_params.AddItem(hdr[i]); itd->aux_params.AddItem(val); } if(itd->date == "") fill_date_from_unixtime(*itd); itd->text = filt->ConvertContent(parser.GetBody()); if(teaser_len > 0 && (itd->descr.IsInvalid() || itd->descr == "")) { ScriptVariable b = parser.GetBody(); //parser.GetBody() = ""; // optimization itd->descr = filt->ConvertContent(b.Range(0, teaser_len).Get()); } delete filt; if(it_is_dir) scan_set_item_dir_for_files(srcd, itd->files); else itd->files.Clear(); return true; } void Database::GetSetItemFilenames(const PageSetData &setd, bool separ_dir, ScriptVariable &dir, ScriptVariable &idxfl, ScriptVariable &href, ScriptVariable &cmap) const { if(separ_dir) { href = (*subst)(setd.setdirname + "/" + setd.pagedirname); dir = GetFilePrefix() + href; idxfl = dir + "/" + setd.indexfilename; } else { ScriptVariable d = (*subst)(setd.setdirname); ScriptMacroprocessor sub_sub(subst); add_index_macros(sub_sub, 0); // XXX really 0, not -1 ? ScriptVariable p = sub_sub(setd.pagefilename); dir = GetFilePrefix() + d; idxfl = dir + "/" + p; href = d + "/" + p; } if(href[0] != '/') href.Range(0,0).Replace("/"); ScriptVariable m = separ_dir ? setd.cmap_filename : setd.cmap_filename_nodir; cmap = m.IsValid() ? dir + "/" + (*subst)(m) : ScriptVariableInv(); } void Database:: GetSetSubitemFilename(const PageSetData &setd, bool separ_dir, int subidx, ScriptVariable &fname, ScriptVariable &href) const { ScriptMacroprocessor sub_sub(subst); add_index_macros(sub_sub, subidx); ScriptVariable dir; ScriptVariable locpart; if(separ_dir) { dir = GetFilePrefix() + sub_sub(setd.setdirname + "/" + setd.pagedirname); locpart = sub_sub(setd.compagename); } else { dir = GetFilePrefix() + sub_sub(setd.setdirname); locpart = sub_sub(setd.pagefilename); } fname = dir + "/" + locpart; href = locpart; } void Database:: BuildSetItemPage(const PageSetData &psd, const ListItemData &itd, ScriptVariable &main, ScriptVariable &tail, ForumGenerator **fgp) const { const char *idc = psd.id.c_str(); const char *ts = itd.pgtype.c_str(); ScriptVariable page_templ = inifile->GetModifiedTextParameter("pageset", idc, "page_template", ts, ""); ScriptVariable page_tail_templ = inifile->GetModifiedTextParameter("pageset", idc, "page_tail_template", ts, ""); //SetListData(0, &itd); main = (*subst)(page_templ); tail = (*subst)(page_tail_templ); if(itd.comments == "enabled" || itd.comments == "readonly") *fgp = GetComments(psd.comments_conf); else *fgp = new EmptyForumGenerator(this); //ForgetListCmtData(); } bool Database::GetCollectionData(const ScriptVariable &id, ScriptVariable &srcdir, ScriptVariable &dstdir, int &method) const { const char *idc = id.c_str(); srcdir = (*subst)( inifile->GetTextParameter("collection", idc, "sourcedir", idc)); dstdir = GetFilePrefix() + (*subst)( inifile->GetTextParameter("collection", idc, "destdir", idc)); method = extract_publish_method(inifile, "collection", idc); return (method & fpm_method_mask) != fpm_none; } bool Database::GetBinaryData(const ScriptVariable &id, ScriptVariable &src, ScriptVariable &dst, int &method) const { const char *idc = id.c_str(); src = (*subst)(inifile->GetTextParameter("binary", idc, "source", idc)); dst = GetFilePrefix() + (*subst)(inifile->GetTextParameter("binary", idc, "dest", idc)); method = extract_publish_method(inifile, "binary", idc); return (method & fpm_method_mask) != fpm_none; } void Database::GetAliases(const ScriptVariable &id, ScriptVector &result) const { result.Clear(); const char *tmp = inifile->GetTextParameter("aliases", id.c_str(), "aliases", 0); if(!tmp) return; ScriptVector lines((*subst)(tmp), "\n", " \t\r"); int n = lines.Length(); int i; for(i = 0; i < n; i++) { ScriptVector toks(lines[i], ":", " \t\r"); if(toks.Length() < 2) // XXX we simply ignore ill-formed lines continue; // this might be a bad idea result.AddItem(toks[0]); result.AddItem(toks[1]); } } void Database::GetAliasDirs(const ScriptVariable &id, ScriptVector &res) const { const char *s = inifile->GetTextParameter("aliases", id.c_str(), "force_dirs", ""); res = ScriptVector(s, ",", " \t\r\n"); } ScriptVariable Database::GetAliasDirFilename(const ScriptVariable &id) const { return inifile->GetTextParameter("aliases", id.c_str(), "dir_file_name", ".htaccess"); } ScriptVariable Database::BuildAliasDirFile(const ScriptVariable &id, const ScriptVariable &target_uri) const { ScriptVariable templ = inifile->GetTextParameter("aliases", id.c_str(), "dir_file_template", ""); ScriptMacroprocessor sub_sub(subst); sub_sub.AddMacro(new ScriptMacroConst("target", target_uri)); return sub_sub.Process(templ); } #if 0 bool Database::GetTextMaybeFile(const char *g, const char *gs, const char *p, ScriptVariable &res) const { res = inifile->GetTextParameter(g, gs, p, ""); if(res.Length() >= too_long_for_filename) return true; res.Trim(" \r\t"); // don't strip end-of-line! if(res[0] != '>') return true; res.Range(0,1).Erase(); res.Trim(); #if 0 res = GetSourcePrefix() + res; #endif ReadStream s; if(!s.FOpen(res.c_str())) { res += ": couldn't open file"; return false; } s.ReadUntilEof(res); s.FClose(); return true; } #endif ScriptVariable Database::ExpandTemplate(const char *templ, const char *g, const char *s) const { ScriptVariable body = inifile->GetTextParameter("template", templ, "body", ""); ScriptVariable par = inifile->GetTextParameter("template", templ, "params", ""); ScriptWordVector paramsv(par); ScriptMacroprocessor sub_sub(subst); int i; for(i = 0; i < paramsv.Length(); i++) { ScriptVariable val = inifile->GetTextParameter(g, s, paramsv[i].c_str(), ""); sub_sub.AddMacro(new ScriptMacroConst(paramsv[i], (*subst)(val))); } //ScriptSubstitutionDictionary dv(dict, false); //return subst->Substitute(body, &dv); return sub_sub.Process(body); } void Database::ProvideFilterChainMaker() const { if(filtmaker) return; const char *enc = inifile->GetTextParameter("format", 0, "encoding", 0); const char *tags = inifile->GetTextParameter("format", 0, "tags", ""); const char *attrs = inifile->GetTextParameter("format", 0, "tag_attributes", 0); if(!attrs) attrs = "a=href img=src img=alt"; ScriptVariable enc_sv = enc ? (*subst)(enc) : ScriptVariableInv(); ScriptVariable tags_sv = (*subst)(tags); ScriptVariable attrs_sv = (*subst)(attrs); const_cast(this)->filtmaker = new FilterChainMaker(enc ? enc_sv.c_str() : 0, tags_sv.c_str(), attrs_sv.c_str()); } SetListIndex *Database:: ProvideSetListIndex(const ScriptVariable &set_id, const ScriptVariable &tag) { SetListIndex *tmp; for(tmp = first_set_list; tmp; tmp = tmp->next) if(tmp->set_id == set_id && tmp->tag == tag) return tmp; ScriptVariable sd = GetSetSourceDir(set_id); if(sd.IsInvalid() || sd == "") return 0; ReadStream f; if(!f.FOpen((sd + "/_" + tag).c_str())) return 0; tmp = new SetListIndex; tmp->next = first_set_list; tmp->set_id = set_id; tmp->tag = tag; first_set_list = tmp; tmp->items.Clear(); ScriptVariable line; while(f.ReadLine(line)) { line.Trim(); if(line == "") continue; tmp->items.AddItem(line); } return tmp; } bool Database:: GetIndexBarStyle(const ScriptVariable &name, IndexBarStyle &s) const { const char *sn = name.c_str(); if(!sn || !*sn) return false; s.begin = inifile->GetTextParameter("indexbar", sn, "begin", ""); s.end = inifile->GetTextParameter("indexbar", sn, "end", ""); s.textbreak = inifile->GetTextParameter("indexbar", sn, "break", ""); s.link = inifile->GetTextParameter("indexbar", sn, "link", ""); s.greylink = inifile->GetTextParameter("indexbar", sn, "greylink", ""); s.curpos = inifile->GetTextParameter("indexbar", sn, "curpos", ""); s.textprev = inifile->GetTextParameter("indexbar", sn, "textprev", ""); s.textnext = inifile->GetTextParameter("indexbar", sn, "textnext", ""); s.textfirst = inifile->GetTextParameter("indexbar", sn, "textfirst", ""); s.textlast = inifile->GetTextParameter("indexbar", sn, "textlast", ""); s.tailsize = inifile->GetIntegerParameter("indexbar", sn, "tailsize", 2); return s.link.IsValid() && s.link != ""; } #if 0 ScriptVariable Database::BuildArrayIndex(const IndexBarStyle &style, const ScriptVariable &anchor) const { if(!array_data) return ScriptVariableInv(); IndexInformation ixi; ixi.anchor = anchor; return build_array_index(&ixi, array_data); } #endif ArrayData* Database::InstallArrayData(ArrayData *p) { ArrayData *tmp = array_data; array_data = p; return tmp; } void Database::SetMacroData(const ListData *ld, const ListItemData *lid) const { subst->SetData(ld, lid); const_cast(this)->current_list_data = ld; const_cast(this)->current_list_item_data = lid; } void Database::ForgetMacroData() const { subst->ForgetData(); const_cast(this)->current_list_data = 0; const_cast(this)->current_list_item_data = 0; } void Database::SaveMacroRealm(Database::MacroRealm &buf) const { buf.ld = current_list_data; buf.lid = current_list_item_data; } void Database::RestoreMacroRealm(const Database::MacroRealm &buf) const { SetMacroData(buf.ld, buf.lid); } static inline bool is_whitespace(int c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; } ScriptVariable Database::BuildMenu(const ScriptVariable &id, const ScriptVariable &cur) const { const char *idc = id.c_str(); if(!idc || !*idc) return ScriptVariableInv(); const char *items = inifile->GetTextParameter("menu", idc, "items", 0); if(!items) return ScriptVariableInv(); while(*items && is_whitespace(*items)) items++; if(!*items) return ScriptVariableInv(); char delim[2] = { 0, 0 }; delim[0] = *items; items++; ScriptVector items_v((*subst)(items), delim, " \t\r\n"); int len = items_v.Length(); len = (len + 3) / 4; // count of items, full or partial ScriptVariable link_templ = inifile->GetTextParameter("menu", idc, "link", ""); ScriptVariable curpos_templ = inifile->GetTextParameter("menu", idc, "curpos", ""); ScriptVariable tag_templ = inifile->GetTextParameter("menu", idc, "tag", ""); ScriptVariable break_templ = inifile->GetTextParameter("menu", idc, "break", ""); const char *sep_cstr = inifile->GetTextParameter("menu", idc, "separator", ""); ScriptVariable separator = (*subst)(sep_cstr); ScriptVariable result = (*subst)(inifile->GetTextParameter("menu", idc, "begin", "")); int i; for(i = 0; i < len; i++) { if(i > 0) result += separator; ScriptVariable uri = items_v[i*4+1]; ScriptVariable *t = 0; if(uri.IsInvalid()) uri = ""; uri.Trim(); if(uri == "") { ScriptVariable lab = items_v[i*4]; lab.Trim(); t = lab == "" ? &break_templ : &tag_templ; } else if(cur.IsValid() && cur != "" && cur == items_v[i*4 + 3]) { t = &curpos_templ; } else { t = &link_templ; } result += subst->Process(*t, items_v, i * 4, 4); } result += (*subst)(inifile->GetTextParameter("menu", idc, "end", "")); return result; } /////////////////////////////////////////////////////////////// // comments infrastructure ScriptVariable Database::GetCommentsPath(ScriptVariable conf) const { ScriptVector cflines((*subst)(conf), "\n", " \t\r"); ScriptWordVector cfw(cflines[0]); if(cfw.Length() < 2) return ScriptVariableInv(); return cfw[1]; } ForumGenerator *Database::GetComments(ScriptVariable conf) const { ScriptVector cflines((*subst)(conf), "\n", " \t\r"); ScriptWordVector cfw(cflines[0]); if(cfw.Length() < 2) { return new ErrorForumGenerator(this, ScriptVariable("comments: style and path must be specified")); } ScriptVariable style = cfw[0]; ScriptVariable path = cfw[1]; ScriptVector auxparamdict; int i; for(i = 1 /* NB: 1, sic! */; i < cflines.Length(); i++) { ScriptVariable::Substring w1; cflines[i].Whole().FetchWord(w1); ScriptVariable name = w1.Get(); if(name.IsInvalid()) continue; name.Trim(); if(name == "") continue; ScriptVariable value = w1.After().Get(); if(value.IsInvalid()) continue; value.Trim(); auxparamdict.AddItem(name); auxparamdict.AddItem(value); } ForumData *fd = new ForumData; const char *st = style.c_str(); static const char cmtst[] = "commentstyle"; fd->type = (*subst)(inifile->GetTextParameter(cmtst, st, "type", "")); fd->top = inifile->GetTextParameter(cmtst, st, "top_template", ""); fd->bottom = inifile->GetTextParameter(cmtst, st, "bottom_template", ""); #if 0 fd->indent = inifile->GetTextParameter(cmtst, st, "indent_template", ""); fd->unindent = inifile->GetTextParameter(cmtst, st, "unindent_template", ""); #endif fd->comment_templ = inifile->GetTextParameter(cmtst, st, "comment_template", ""); fd->tail = inifile->GetTextParameter(cmtst, st, "comment_tail_template", ""); fd->no_comments_templ = inifile->GetTextParameter(cmtst, st, "no_comments", ""); ScriptVariable s; s = (*subst)(inifile->GetTextParameter(cmtst, st, "reverse", "no")); fd->reverse = boolean_value_from_scrvar(s); s = (*subst)(inifile->GetTextParameter(cmtst,st,"hidden_hold_place","no")); fd->hidden_hold_place = boolean_value_from_scrvar(s); s = (*subst)(inifile->GetTextParameter(cmtst, st, "perpage", "0")); long ppg; bool ok = s.GetLong(ppg, 10); fd->per_page = ok ? ppg : 0; #if 0 ScriptVariable custom_params = inifile->GetTextParameter(cmtst, st, "custom_params", ""); ScriptVector cpvec(custom_params, " \t\n,"); #else ScriptVector cpvec; #endif if(fd->type == "tree") { cpvec.AddItem("indent_template"); cpvec.AddItem("unindent_template"); } else if(fd->type == "thread") { // to be determined... } for(i = 0; i < cpvec.Length(); i++) { const char *s = inifile->GetTextParameter(cmtst, st, cpvec[i].c_str(), 0); if(!s || !*s) continue; fd->custom_params.AddItem(cpvec[i]); fd->custom_params.AddItem(s); } CommentDir cmtdir(path, auxparamdict); CommentTree *tree = cmtdir.GetTree(); if(!tree) { delete fd; return new ErrorForumGenerator(this, ScriptVariable("error scanning the comment tree")); } cmtdir.ReleaseTree(); if(fd->type == "list") return new PlainForumGenerator(this, fd, tree); if(fd->type == "tree") { if(fd->per_page == 0) return new SingleTreeForumGenerator(this, fd, tree); return new ForestForumGenerator(this, fd, tree); } if(fd->type == "thread") return new ThreadPageForumGenerator(this, fd, tree); // still here?! ScriptVariable errmsg("unsupported comment style "); errmsg += fd->type; delete fd; delete tree; return new ErrorForumGenerator(this, errmsg); } #if 0 ScriptVariable Database::BuildCommentSection(ForumGenerator *fg, const ScriptVariable &href, ScriptVariable &cmtmap) const { if(!fg) return ScriptVariableInv(); ScriptVariable res; bool ok; ok = fg->NextPage(href, res); if(!ok) { cmtmap = fg->MakeCommentMap(); //CancelComments(fg); return ScriptVariableInv(); } cmtmap = ScriptVariableInv(); return res; } #endif ScriptVariable Database:: BuildCommentPart(const CommentData &cdt, const ScriptVariable &templ) const { subst->SetCmtData(&cdt); ScriptVariable res = (*subst)(templ); subst->SetCmtData(0); return res; } #if 0 void Database::CancelComments(Database::comment_iter *ci) const { delete *(ForumGenerator**)ci; *ci = 0; } #endif ///////////////////////////////////////// // blocks infrastructure static void add_block(BlockGroupData **first_group, const char *grp_id, const char *blk_id, int weight, const char *tag, const char *title, const char *body) { BlockGroupData **grpp = first_group; while(*grpp && (*grpp)->id != grp_id) grpp = &((*grpp)->next_grp); if(!*grpp) *grpp = new BlockGroupData(grp_id, "", "", "%blk:title% %blk:body%"); // now it is guaranteed that *grpp points to the desired group object BlockData **blkp = &((*grpp)->first); while(*blkp && (*blkp)->weight < weight) blkp = &((*blkp)->next); BlockData *bd = new BlockData(blk_id, weight, tag, title, body); bd->next = *blkp; *blkp = bd; } void Database::BuildBlockData() { if(first_block_group) return; static const char blkg[] = "blockgroup"; ScriptVector groups; GetSectionNames(blkg, groups); BlockGroupData *last = 0; int i; for(i = 0; i < groups.Length(); i++) { const char *gn = groups[i].c_str(); const char *begin = inifile->GetTextParameter(blkg, gn, "begin", ""); const char *end = inifile->GetTextParameter(blkg, gn, "end", ""); const char *blkt = inifile->GetTextParameter(blkg, gn, "block_template", ""); BlockGroupData *bgd = new BlockGroupData(gn, begin, end, blkt); if(last) last->next_grp = bgd; else first_block_group = bgd; last = bgd; } static const char block[] = "block"; ScriptVector blocknames; GetSectionNames(block, blocknames); for(i = 0; i < blocknames.Length(); i++) { const char *bn = blocknames[i].c_str(); const char *grpid = inifile->GetTextParameter(block, bn, "group", "=NOGRP="); int weight = inifile->GetIntegerParameter(block, bn, "weight", 0); const char *tag = inifile->GetTextParameter(block, bn, "tag", ""); const char *title = inifile->GetTextParameter(block, bn, "title", ""); const char *body = inifile->GetTextParameter(block, bn, "body", ""); add_block(&first_block_group, grpid, bn, weight, tag, title, body); } } ScriptVariable Database:: BuildBlocks(const ScriptVariable &group, const ScriptVariable &aux) const { const_cast(this)->BuildBlockData(); BlockGroupData *grp = first_block_group; while(grp && grp->id != group) grp = grp->next_grp; if(!grp) return ""; ScriptVariable empty; ScriptVector auxvec(aux.IsValid() ? aux : empty, ",", " \t\r\n"); ScriptVariable res; ScriptMacroprocessor sub_sub(subst); ScriptVector valvec; valvec.AddItem("title"); valvec.AddItem(""); // [1] valvec.AddItem("body"); valvec.AddItem(""); // [3] valvec.AddItem("id"); valvec.AddItem(""); // [5] valvec.AddItem("tag"); valvec.AddItem(""); // [7] sub_sub.AddMacro(new ScriptMacroDictionary("blk", valvec, false)); BlockData *blk; for(blk = grp->first; blk; blk = blk->next) { if(blk->tag.IsInvalid() || blk->tag == "" || (current_list_item_data && (svec_has_elem(current_list_item_data->tags, blk->tag))) || (svec_has_elem(auxvec, blk->tag))) { if(res == "") res += (*subst)(grp->begin); // may be sub_sub? valvec[1] = (*subst)(blk->title); valvec[3] = (*subst)(blk->body); valvec[5] = blk->id; valvec[7] = blk->tag; res += sub_sub.Process(grp->block_templ); } } if(res != "") res += (*subst)(grp->end); return res; }