source: trunk/gsdl/src/recpt/documentaction.cpp@ 7333

Last change on this file since 7333 was 7333, checked in by davidb, 20 years ago

Collage code updated to take advantage of dynamic
paramters specified as minus options to Collage
classifier.

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 43.5 KB
Line 
1/**********************************************************************
2 *
3 * documentaction.cpp --
4 * Copyright (C) 1999 The New Zealand Digital Library Project
5 *
6 * A component of the Greenstone digital library software
7 * from the New Zealand Digital Library Project at the
8 * University of Waikato, New Zealand.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 *********************************************************************/
25
26#include <string.h>
27
28#include "documentaction.h"
29#include "browsetools.h"
30#include "OIDtools.h"
31#include "querytools.h"
32#include "unitool.h"
33#include "gsdltools.h"
34#include "highlighttext.h"
35
36documentaction::documentaction () {
37 recpt = NULL;
38
39
40 // this action uses cgi variables "a", "d", "cl",
41 // "x", "gc", "gt", "gp", and "hl"
42 cgiarginfo arg_ainfo;
43 arg_ainfo.shortname = "a";
44 arg_ainfo.longname = "action";
45 arg_ainfo.multiplechar = true;
46 arg_ainfo.defaultstatus = cgiarginfo::weak;
47 arg_ainfo.argdefault = "p";
48 arg_ainfo.savedarginfo = cgiarginfo::must;
49 argsinfo.addarginfo (NULL, arg_ainfo);
50
51 arg_ainfo.shortname = "d";
52 arg_ainfo.longname = "document OID";
53 arg_ainfo.multiplechar = true;
54 arg_ainfo.defaultstatus = cgiarginfo::none;
55 arg_ainfo.argdefault = "";
56 arg_ainfo.savedarginfo = cgiarginfo::can;
57 argsinfo.addarginfo (NULL, arg_ainfo);
58
59 // whether or not a document should be retrieved from the
60 // library or the Web.
61 arg_ainfo.shortname = "il";
62 arg_ainfo.longname = "internal link preference";
63 arg_ainfo.multiplechar = false;
64 arg_ainfo.defaultstatus = cgiarginfo::weak;
65 arg_ainfo.argdefault = "l";
66 arg_ainfo.savedarginfo = cgiarginfo::must;
67 argsinfo.addarginfo (NULL, arg_ainfo);
68
69 arg_ainfo.shortname = "cl";
70 arg_ainfo.longname = "classification OID";
71 arg_ainfo.multiplechar = true;
72 arg_ainfo.defaultstatus = cgiarginfo::none;
73 arg_ainfo.argdefault = "";
74 arg_ainfo.savedarginfo = cgiarginfo::can;
75 argsinfo.addarginfo (NULL, arg_ainfo);
76
77 // in this action "gc" controls the expand/contract
78 // contents function
79 arg_ainfo.shortname = "gc";
80 arg_ainfo.longname = "expand contents";
81 arg_ainfo.multiplechar = false;
82 arg_ainfo.defaultstatus = cgiarginfo::weak;
83 arg_ainfo.argdefault = "0";
84 arg_ainfo.savedarginfo = cgiarginfo::can;
85 argsinfo.addarginfo (NULL, arg_ainfo);
86
87 // in this action "gt" controls the expand/contract
88 // text function 0 = not expanded, 1 = expand unless
89 // there are more than 10 sections containing text,
90 // 2 = expand all
91 arg_ainfo.shortname = "gt";
92 arg_ainfo.longname = "expand text";
93 arg_ainfo.multiplechar = false;
94 arg_ainfo.defaultstatus = cgiarginfo::weak;
95 arg_ainfo.argdefault = "0";
96 arg_ainfo.savedarginfo = cgiarginfo::can;
97 argsinfo.addarginfo (NULL, arg_ainfo);
98
99 // in this action "gp" is the "go to page" control
100 // used by the Book type of toc
101 arg_ainfo.shortname = "gp";
102 arg_ainfo.longname = "go to page";
103 arg_ainfo.multiplechar = true;
104 arg_ainfo.defaultstatus = cgiarginfo::none;
105 arg_ainfo.argdefault = "";
106 arg_ainfo.savedarginfo = cgiarginfo::mustnot;
107 argsinfo.addarginfo (NULL, arg_ainfo);
108
109 // in this action "hl" is the "highlighting on/
110 // highlighting off control ("hl" == "2" is a
111 // special case that forces phrase highlighting
112 // to be used even if the query string doesn't
113 // appear to be a phrase)
114 arg_ainfo.shortname = "hl";
115 arg_ainfo.longname = "highlighting on/off";
116 arg_ainfo.multiplechar = false;
117 arg_ainfo.defaultstatus = cgiarginfo::weak;
118 arg_ainfo.argdefault = "1";
119 arg_ainfo.savedarginfo = cgiarginfo::must;
120 argsinfo.addarginfo (NULL, arg_ainfo);
121
122 // "x" is 0 normally or 1 if page
123 // has been "detached"
124 arg_ainfo.shortname = "x";
125 arg_ainfo.longname = "detached page";
126 arg_ainfo.multiplechar = false;
127 arg_ainfo.defaultstatus = cgiarginfo::weak;
128 arg_ainfo.argdefault = "0";
129 arg_ainfo.savedarginfo = cgiarginfo::must;
130 argsinfo.addarginfo (NULL, arg_ainfo);
131
132 // "xx" is 0 normally or 1 if documents should be detached by default
133 arg_ainfo.shortname = "xx";
134 arg_ainfo.longname = "detach all doc pages";
135 arg_ainfo.multiplechar = false;
136 arg_ainfo.defaultstatus = cgiarginfo::weak;
137 arg_ainfo.argdefault = "0";
138 arg_ainfo.savedarginfo = cgiarginfo::must;
139 argsinfo.addarginfo (NULL, arg_ainfo);
140
141
142 // f arg is set to 1 if document is to
143 // be displayed in a frame
144 arg_ainfo.shortname = "f";
145 arg_ainfo.longname = "frame";
146 arg_ainfo.multiplechar = false;
147 arg_ainfo.defaultstatus = cgiarginfo::weak;
148 arg_ainfo.argdefault = "0";
149 arg_ainfo.savedarginfo = cgiarginfo::mustnot;
150 argsinfo.addarginfo (NULL, arg_ainfo);
151
152 // fc arg is "1" if search bar is to be included (i.e. if "fc" == 1
153 // the httpdocument macro will include "&f=1"
154 arg_ainfo.shortname = "fc";
155 arg_ainfo.longname = "include search bar";
156 arg_ainfo.multiplechar = false;
157 arg_ainfo.defaultstatus = cgiarginfo::weak;
158 arg_ainfo.argdefault = "1";
159 arg_ainfo.savedarginfo = cgiarginfo::must;
160 argsinfo.addarginfo (NULL, arg_ainfo);
161
162 //rd is whether a document will be displayed
163 //with a relevant document list
164 arg_ainfo.shortname = "rd";
165 arg_ainfo.longname = "include relevant documents";
166 arg_ainfo.multiplechar = false;
167 arg_ainfo.defaultstatus = cgiarginfo::weak;
168 arg_ainfo.argdefault = "0";
169 arg_ainfo.savedarginfo = cgiarginfo::must;
170 argsinfo.addarginfo (NULL, arg_ainfo);
171
172 //dm is the metadata that has been used for the datelist
173 arg_ainfo.shortname = "dm";
174 arg_ainfo.longname = "date metadata";
175 arg_ainfo.multiplechar = true;
176 arg_ainfo.defaultstatus = cgiarginfo::weak;
177 arg_ainfo.argdefault = "";
178 arg_ainfo.savedarginfo = cgiarginfo::must;
179 argsinfo.addarginfo (NULL, arg_ainfo);
180
181}
182
183documentaction::~documentaction () {
184}
185
186bool documentaction::check_cgiargs (cgiargsinfoclass &argsinfo, cgiargsclass &args,
187 recptprotolistclass *protos, ostream &logout) {
188
189 if(!args["d"].empty())
190 {
191 text_t docTop;
192 get_top(args["d"],docTop);
193
194 recptproto* collectproto = protos->getrecptproto (args["c"], logout);
195 if (collectproto != NULL)
196 {
197 ColInfoResponse_t *cinfo = recpt->get_collectinfo_ptr (collectproto, args["c"], logout);
198
199 if(cinfo->authenticate == "document")
200 {
201 // both are either commented out or uncomment and are empty
202 if (cinfo->public_documents.empty() && cinfo->private_documents.empty())
203 {
204 //deny everything
205 args["uan"] = "1";
206 args["ug"] = cinfo->auth_group;
207 }
208
209 // both public_documents and private_documents are turned on !
210 else if (!cinfo->public_documents.empty() && !cinfo->private_documents.empty())
211 {
212 //deny everything
213 args["uan"] = "1";
214 args["ug"] = cinfo->auth_group;
215 }
216
217 // if public_documents is set but this document isn't
218 // listed in it we need to authenticate
219 else if ((!cinfo->public_documents.empty()) &&
220 (cinfo->public_documents.find(docTop) == cinfo->public_documents.end()))
221 {
222 args["uan"] = "1";
223 args["ug"] = cinfo->auth_group;
224 }
225
226 // if private_documents is set and this document is
227 // listed in it we need to authenticate
228 else if ((!cinfo->private_documents.empty()) &&
229 (cinfo->private_documents.find(docTop) != cinfo->private_documents.end()))
230 {
231 args["uan"] = "1";
232 args["ug"] = cinfo->auth_group;
233 }
234
235 }
236 }
237 }
238 // check gc argument
239 int arg_gc = args.getintarg("gc");
240 if (arg_gc < 0 || arg_gc > 2) {
241 logout << "Warning: \"gc\" argument out of range (" << arg_gc << ")\n";
242 cgiarginfo *gcinfo = argsinfo.getarginfo ("gc");
243 if (gcinfo != NULL) args["gc"] = gcinfo->argdefault;
244 }
245
246 // check gt argument (may be either 0, 1 or 2)
247 int arg_gt = args.getintarg("gt");
248 if (arg_gt != 0 && arg_gt != 1 && arg_gt != 2) {
249 logout << "Warning: \"gt\" argument out of range (" << arg_gt << ")\n";
250 cgiarginfo *gtinfo = argsinfo.getarginfo ("gt");
251 if (gtinfo != NULL) args["gt"] = gtinfo->argdefault;
252 }
253
254 // check hl argument
255 int arg_hl = args.getintarg("hl");
256 if (arg_hl < 0 || arg_hl > 2) {
257 logout << "Warning: \"hl\" argument out of range (" << arg_hl << ")\n";
258 cgiarginfo *hlinfo = argsinfo.getarginfo ("hl");
259 if (hlinfo != NULL) args["hl"] = hlinfo->argdefault;
260 }
261
262 // check x argument
263 int arg_x = args.getintarg("x");
264 if (arg_x != 0 && arg_x != 1) {
265 logout << "Warning: \"x\" argument out of range (" << arg_x << ")\n";
266 cgiarginfo *xinfo = argsinfo.getarginfo ("x");
267 if (xinfo != NULL) args["x"] = xinfo->argdefault;
268 }
269
270 //checks whether rd arg is valid
271 int arg_rd = args.getintarg("rd");
272 if (arg_rd != 0 && arg_rd != 1) {
273 logout << "Warning: \"rd\" argument out of range (" << arg_rd << ")\n";
274 cgiarginfo *rdinfo = argsinfo.getarginfo ("rd");
275 if (rdinfo != NULL) args["rd"] = rdinfo->argdefault;
276 }
277
278
279 return true;
280}
281
282void documentaction::get_cgihead_info (cgiargsclass &args, recptprotolistclass *protos,
283 response_t &response,text_t &response_data,
284 ostream &logout) {
285
286 if ((args["il"] == "w") && (!args["d"].empty())) {
287
288 recptproto* collectproto = protos->getrecptproto (args["c"], logout);
289 if (collectproto != NULL) {
290
291 text_tset metadata;
292 FilterResponse_t filt_response;
293 text_t top;
294
295 metadata.insert ("URL");
296
297 // get metadata for parent document
298 get_top (args["d"], top);
299 if (get_info (top, args["c"], metadata, false, collectproto, filt_response, logout)) {
300 text_t url = filt_response.docInfo[0].metadata["URL"].values[0];
301
302 response = location;
303 response_data = url;
304 return;
305 } else {
306 // error, no URL
307 logout << "Error: documentaction::get_cgihead_info failed on get_info" << endl;
308 }
309 }
310 }
311 response = content;
312 response_data = "text/html";
313}
314
315// set_widthtspace calculates how wide the spaces in the nav bar should
316// be and sets the appropriate macro
317void documentaction::set_spacemacro (displayclass &disp, FilterResponse_t &response,
318 bool has_search_button) {
319
320 text_t width;
321 int twidth, swidth, iwidth = 0;
322
323 int numc = response.docInfo.size();
324 ResultDocInfo_tarray::iterator dochere = response.docInfo.begin();
325 ResultDocInfo_tarray::iterator docend = response.docInfo.end();
326
327 disp.expandstring ("Global", "_pagewidth_", width);
328 twidth = width.getint();
329
330 if (has_search_button) {
331 disp.expandstring ("query", "_searchwidth_", width);
332 iwidth += width.getint();
333 } else {
334 numc -= 1;
335 }
336
337 while (dochere != docend) {
338 const text_t &title = (*dochere).metadata["Title"].values[0];
339
340 disp.expandstring ("document", "_" + title + "width_", width);
341 if (width == ("_" + title + "width_"))
342 disp.expandstring ("document", "_defaultwidth_", width);
343 iwidth += width.getint();
344 dochere ++;
345 }
346 if ((twidth - iwidth) < numc) swidth = 2;
347 else {
348 swidth = twidth - iwidth;
349 if (numc > 0) swidth = swidth / numc;
350 }
351 disp.setmacro ("widthtspace", "Global", swidth);
352}
353
354// set_navbarmacros sets _navigationbar_ and _httpbrowseXXX_ macros
355// reponse contains 1 metadata field (Title)
356void documentaction::set_navbarmacros (displayclass &disp, FilterResponse_t &response,
357 bool has_search_button, cgiargsclass &args,
358 ColInfoResponse_t &cinfo) {
359
360
361 bool use_pulldown = false;
362 text_tmap::iterator it = cinfo.format.find("NavigationBar");
363 if (it != cinfo.format.end()) {
364 if (it->second == "pulldown") {
365 use_pulldown = true;
366 }
367 }
368
369 text_t topparent;
370 text_t &arg_d = args["d"];
371 text_t navigationbar = "<!-- Navigation Bar -->\n";
372
373 text_t date_extra = "";
374 get_top (args["cl"], topparent);
375 int numc = response.docInfo.size();
376 ResultDocInfo_tarray::iterator dochere = response.docInfo.begin();
377 ResultDocInfo_tarray::iterator docend = response.docInfo.end();
378
379 if (!use_pulldown) {
380 navigationbar += "<nobr>\n";
381 if (has_search_button) {
382 if (args["a"] == "q") {
383 navigationbar += "_icontabsearchgreen_";
384 } else {
385 navigationbar += "_imagesearch_";
386 }
387 }
388
389 if (has_search_button || numc == 0) navigationbar += "_imagespacer_";
390
391 bool first = true;
392 while (dochere != docend) {
393 date_extra = "";
394 if (!first) navigationbar += "_imagespacer_";
395
396 text_t title = (*dochere).metadata["Title"].values[0];
397 if (title == "Date") { // a date list
398 text_t date_meta = (*dochere).metadata["mdtype"].values[0];
399 if (date_meta == "") {
400 date_meta = "Date";
401 }
402 date_extra = "&dm="+date_meta;
403 }
404 bool unknown = false;
405
406 // test the _XXXwidth_ macro to see if image macros are
407 // defined for this type of classification - if not we'll
408 // just display the text
409 text_t tmpwidth;
410 disp.expandstring ("document", "_" + title + "width_", tmpwidth);
411 if (tmpwidth == ("_" + title + "width_")) unknown = true;
412
413 // if we're inside a document all the classification buttons should be enabled
414 if (arg_d.empty() && ((*dochere).OID == topparent)) {
415 if (unknown) navigationbar += " " + title + " ";
416 else navigationbar += "_icontab" + title + "green_";
417 } else {
418
419 // set the _httpbrowseXXX_ macro for this classification
420 if (unknown) navigationbar += " <a href=\"_httpdocument_&cl=" +
421 (*dochere).OID + date_extra+"\">" + title + "</a> ";
422 else {
423 navigationbar += "_image" + title + "_";
424 disp.setmacro ("httpbrowse" + title, "Global", "_httpdocument_&cl=" + (*dochere).OID+date_extra);
425 }
426 }
427 dochere ++;
428 first = false;
429 }
430
431 if (!has_search_button && numc == 1) navigationbar += "_imagespacer_";
432
433 navigationbar += "\n</nobr>\n";
434 navigationbar += "<!-- End of Navigation Bar -->\n";
435
436 } else {
437
438 navigationbar = "<form method=\"get\" name=\"navform\">\n";
439 navigationbar += "<select name=\"navbar\" onChange=\"location.href=";
440 navigationbar += "document.navform.navbar.options[document.navform.navbar.selectedIndex].value\">\n";
441
442 if (args["a"] != "q" && args["cl"].empty()) {
443 navigationbar += "<option value=\"\" selected>_textselectpage_</option>\n";
444 }
445
446 if (has_search_button) {
447 navigationbar += "<option value=\"_httpquery_\"";
448 if (args["a"] == "q") {
449 navigationbar += " selected";
450 }
451 navigationbar += ">_texticontabsearchgreen_</option>\n";
452 }
453
454 while (dochere != docend) {
455 text_t title = dochere->metadata["Title"].values[0];
456 if (title == "Date") { // a date list
457 text_t date_meta = (*dochere).metadata["mdtype"].values[0];
458 if (date_meta == "") {
459 date_meta = "Date";
460 }
461 date_extra = "&dm="+date_meta;
462 }
463
464 navigationbar += "<option value=\"_httpdocument_&cl=" + dochere->OID +date_extra+ "\"";
465 if (topparent == dochere->OID) {
466 navigationbar += " selected";
467 }
468 navigationbar += ">" + title + "</option>\n";
469 dochere++;
470 }
471
472 navigationbar += "</select>\n";
473 navigationbar += "</form>\n";
474 }
475
476 disp.setmacro ("navigationbar", "Global", navigationbar);
477}
478
479// define all the macros which might be used by other actions
480// to produce pages.
481void documentaction::define_external_macros (displayclass &disp, cgiargsclass &args,
482 recptprotolistclass *protos, ostream &logout) {
483
484 // define_external_macros sets the following macros:
485
486 // _navigationbar_ this is the navigation bar containing the search button
487 // and any classification buttons - it goes at the top of
488 // most pages. for now we're assuming that there'll always
489 // be a search button - we should probably check that there
490 // is a query action before making this assumption
491
492 // _httpbrowseXXX_ the http macros for each classification (i.e. if there
493 // are Title and Creator classifications _httpbrowseTitle_
494 // and _httpbrowseCreator_ will be set
495
496 // _widthtspace_ the width of the spacers between buttons in navigation
497 // bar
498
499 // _httpdocument_ has '&f=1' added if displaying document inside a frame
500
501 // _gsdltop_ macro to replace _top targets with
502
503 // _httppagehome_ overridden home url if html collections have own homepage
504
505 // _usability_ a macro for remote usability reporting
506
507 // must have a valid collection server to continue
508
509 text_t &collection = args["c"];
510 if (collection.empty()) return;
511 recptproto *collectproto = protos->getrecptproto (collection, logout);
512 if (collectproto == NULL) return;
513
514 if (recpt == NULL) {
515 logout << "ERROR (documentaction::define_external_macros): This action does not contain\n"
516 << " information about any receptionists. The method set_receptionist was\n"
517 << " probably not called from the module which instantiated this action.\n";
518 return;
519 }
520
521 outconvertclass text_t2ascii;
522 comerror_t err;
523 InfoFiltersResponse_t filterinfo;
524 FilterResponse_t response;
525 text_tset metadata;
526
527 // get info on current collection and load up formatinfo
528 // I'd prefer not to do this here as we're getting
529 // collection info every time (and probably also getting
530 // it in other places some of the time) - One day I'll
531 // fix it ... maybe - Stefan.
532 ColInfoResponse_t cinfo;
533
534 collectproto->get_collectinfo (collection, cinfo, err, logout);
535 load_formatinfo (cinfo.format, args.getintarg("gt"));
536 // ColInfoResponse_t *cinfo = recpt->get_collectinfo_ptr (collectproto, collection, logout);
537 // if (cinfo == NULL) {
538 // logout << "ERROR (documentaction::define_external_macros): get_collectinfo_ptr returned NULL\n";
539 // return;
540 // }
541 //load_formatinfo (cinfo->format, args.getintarg("gt"));
542
543 if (formatinfo.DocumentUseHTML) {
544
545 // frame stuff
546 if (args["fc"] == "1") {
547 text_t httpdocument;
548 disp.expandstring ("Global", "_httpdocument_", httpdocument);
549 httpdocument += "&f=1";
550 disp.setmacro ("httpdocument", "Global", httpdocument);
551 disp.setmacro ("gsdltop", "Global", "documenttop");
552 formatinfo.DocumentText = "[Text]";
553 }
554 text_tmap::iterator it = cinfo.format.find ("homepage");
555 if (it != cinfo.format.end()) {
556 text_t httppagehome;
557 if (get_link (args, protos, (*it).second, httppagehome, logout))
558 disp.setmacro ("httppagehome", "Global", httppagehome);
559 }
560 }
561
562 // don't want navigation bar if page is 'detached'
563 if (!args.getintarg("x")) {
564
565 collectproto->get_filterinfo (collection, filterinfo, err, logout);
566 if (err == noError) {
567 // check that there's a browse filter
568 if (filterinfo.filterNames.find ("BrowseFilter") != filterinfo.filterNames.end()) {
569
570 metadata.insert ("Title");
571 metadata.insert ("mdtype"); // in case there is a datelist
572 bool getParents = false;
573 get_children ("", collection, metadata, getParents, collectproto, response, logout);
574
575 bool has_search_button = true;
576 collectproto->is_searchable(collection, has_search_button, err, logout);
577 if (err != noError) has_search_button = true;
578
579 // calculate width of spacers and set _widthtspace_ macro
580 if (args.getintarg("v") == 0) set_spacemacro (disp, response, has_search_button);
581
582 // set _navigationbar_ macro
583 set_navbarmacros (disp, response, has_search_button, args, cinfo);
584
585 }
586 } else {
587 logout << text_t2ascii
588 << "Error (documentaction::define_external_macros()) in call to get_filterinfo() "
589 << get_comerror_string (err);
590 }
591 }
592 text_tmap::iterator usability = cinfo.format.find("Usability");
593 if(usability!=cinfo.format.end()){
594 disp.setmacro("usability","Global","_imageusab_");
595 disp.setmacro("usabinterface","Global",("_usab"+(*usability).second+"_"));
596 disp.setmacro("usabilityscript", "Global", "_usabshowscript_");
597 }
598}
599
600bool documentaction::get_link (cgiargsclass &args, recptprotolistclass *protos,
601 const text_t &inlink, text_t &outlink, ostream &logout) {
602
603 FilterResponse_t response;
604 text_tset metadata;
605 metadata.insert ("section");
606
607 // check current collection first
608 recptproto *collectproto = protos->getrecptproto (args["c"], logout);
609
610 if (get_info (inlink, args["c"], metadata, false, collectproto, response, logout)) {
611 if (!response.docInfo[0].metadata["section"].values[0].empty()) {
612 outlink = "_httpdocument_&d=" + response.docInfo[0].metadata["section"].values[0];
613 return true;
614 }
615 }
616
617 // check all the other enabled collections
618
619 if (args["ccs"] == "1" && !args["cc"].empty()) {
620 text_tarray collections;
621 splitchar (args["cc"].begin(), args["cc"].end(), ',', collections);
622
623 text_tarray::const_iterator col_here = collections.begin();
624 text_tarray::const_iterator col_end = collections.end();
625
626 while (col_here != col_end) {
627
628 // don't need to check current collection again
629 if (*col_here == args["c"]) {col_here ++; continue;}
630
631 collectproto = protos->getrecptproto (*col_here, logout);
632
633 if (get_info (inlink, *col_here, metadata, false, collectproto, response, logout)) {
634 if (!response.docInfo[0].metadata["section"].values[0].empty()) {
635 outlink = "_httpdocument_&c=" + *col_here + "&d=" +
636 response.docInfo[0].metadata["section"].values[0];
637 return true;
638 }
639 }
640 col_here ++;
641 }
642 }
643 return false;
644}
645
646void documentaction::load_formatinfo (const text_tmap &colformat, int gt) {
647
648 formatinfo.clear();
649 text_tmap::const_iterator format_here = colformat.begin();
650 text_tmap::const_iterator format_end = colformat.end();
651
652 while (format_here != format_end) {
653 if (((*format_here).first == "DocumentImages") &&
654 ((*format_here).second == "true"))
655 formatinfo.DocumentImages = true;
656 else if (((*format_here).first == "DocumentTitles") &&
657 ((*format_here).second == "false"))
658 formatinfo.DocumentTitles = false;
659 else if ((*format_here).first == "DocumentHeading")
660 formatinfo.DocumentHeading = (*format_here).second;
661 else if (((*format_here).first == "DocumentContents") &&
662 ((*format_here).second == "false"))
663 formatinfo.DocumentContents = false;
664 else if (((*format_here).first == "DocumentArrowsBottom") &&
665 ((*format_here).second == "false"))
666 formatinfo.DocumentArrowsBottom = false;
667 else if (((*format_here).first == "DocumentArrowsTop") &&
668 ((*format_here).second == "true"))
669 formatinfo.DocumentArrowsTop = true;
670 else if ((*format_here).first == "DocumentButtons")
671 splitchar ((*format_here).second.begin(), (*format_here).second.end(),
672 '|', formatinfo.DocumentButtons);
673 else if ((*format_here).first == "DocumentText")
674 formatinfo.DocumentText = (*format_here).second;
675 else if ((*format_here).first == "RelatedDocuments")
676 formatinfo.RelatedDocuments = (*format_here).second;
677 else if (((*format_here).first == "DocumentUseHTML") &&
678 ((*format_here).second == "true"))
679 formatinfo.DocumentUseHTML = true;
680 else if (((*format_here).first == "AllowExtendedOptions") &&
681 ((*format_here).second == "true"))
682 formatinfo.AllowExtendedOptions = true;
683 else
684 formatinfo.formatstrings[(*format_here).first] = (*format_here).second;
685
686 format_here ++;
687 }
688
689 // never want arrows when text is expanded
690 if (gt) {
691 formatinfo.DocumentArrowsBottom = false;
692 formatinfo.DocumentArrowsTop = false;
693 }
694}
695
696
697// define all the macros which are related to pages generated
698// by this action. we also load up the formatinfo structure
699// here (it's used in do_action as well as here)
700void documentaction::define_internal_macros (displayclass &disp, cgiargsclass &args,
701 recptprotolistclass *protos, ostream &logout) {
702
703 // define_internal_macros sets the following macros:
704
705 // _pagetitle_ the title to be displayed at the top of the browser window
706
707 // _imagethispage_ the title image to be displayed at top right of page
708
709 // _navarrowsbottom_ this may be overridden to "" when format option
710 // DocumentArrowsBottom is false
711
712 // _navarrowstop_ likewise for DocumentArrowsTop
713
714 // _httpprevarrow_ links to next and previous sections of document - used
715 // _httpnextarrow_ by DocumentArrowsBottom
716
717 // _header_ the header macro is overridden if we're not at a top level
718 // classification to remove the title block
719
720 // _thisOID_ the OID (directory) of the current document - this corresponds
721 // to the archivedir metadata element
722
723 // must have a valid collection server to continue
724
725 text_t &collection = args["c"];
726 if (collection.empty()) return;
727 recptproto *collectproto = protos->getrecptproto (collection, logout);
728 if (collectproto == NULL) return;
729
730 text_tset metadata;
731 FilterResponse_t response;
732 text_t &arg_d = args["d"];
733 text_t &arg_cl = args["cl"];
734
735 if (!formatinfo.DocumentArrowsBottom) {
736 disp.setmacro("navarrowsbottom", "document", "");
737 } else if (!formatinfo.DocumentArrowsBottom) {
738 disp.setmacro("navarrowstop", "document", "");
739 }
740
741 if (!arg_d.empty() && (formatinfo.DocumentArrowsBottom || formatinfo.DocumentArrowsTop)) {
742 // set _httpprevarrow_ and _httpnextarrow_
743 set_arrow_macros (args, collectproto, disp, logout);
744 }
745
746 metadata.insert ("Title");
747
748 bool fulltoc = false;
749
750 if (args["cl"] != "search") {
751 // see if there's a FullTOC string
752 text_t cl_top, full_toc;
753 get_top (arg_cl, cl_top);
754 if (get_formatstring (cl_top, "FullTOC", formatinfo.formatstrings, full_toc))
755 if (full_toc == "true") fulltoc = true;
756 }
757
758 if (!arg_d.empty() && !fulltoc) {
759 // we're at document level
760
761 metadata.insert ("archivedir");
762
763 comerror_t err;
764 OptionValue_tarray options;
765 // we need to do the query again for the z3950proto
766 if (collectproto->get_protocol_name(err)=="z3950proto") {
767 OptionValue_t opt;
768 opt.name="Term";opt.value=args["q"];options.push_back(opt);
769 opt.name="QueryType";
770 opt.value=(args.getintarg("t")) ? "ranked" : "boolean";
771 options.push_back(opt);
772 opt.name="Index";opt.value=args["h"];options.push_back(opt);
773 }
774
775 //do not display relation metadata
776 disp.setmacro ("relateddoc", "document", "");
777
778 //if preferences indicate relevant docs should be collected
779 //and there is no particular format specified then display
780 //this default format.
781 if(args["rd"] == "1" && formatinfo.RelatedDocuments.empty()){
782
783 text_t relation = ""; //string for displaying relation metadata
784
785 //call function in formattools.cpp which will return the text of the
786 //related documents in a vertical list. This is the default format.
787
788 if (get_info (arg_d, collection, metadata, options, false, collectproto, response, logout))
789 relation += get_related_docs(collection, collectproto, response.docInfo[0], logout);
790
791 //set macro to be the related document string
792
793 disp.setmacro ("relateddoc", "document", relation);
794 }
795
796
797 // get metadata for this document and it's parents
798 if (get_info (arg_d, collection, metadata, options,
799 true, collectproto, response, logout)) {
800
801 disp.setmacro ("header", "document", "_textheader_");
802
803 text_tarray pagetitlearray;
804 if (!response.docInfo[0].metadata["Title"].values[0].empty())
805 pagetitlearray.push_back (response.docInfo[0].metadata["Title"].values[0]);
806
807 if (args["gt"] != "1") {
808 MetadataInfo_t *parenttitle = response.docInfo[0].metadata["Title"].parent;
809 while (parenttitle != NULL) {
810 if (!parenttitle->values[0].empty())
811 pagetitlearray.push_back (parenttitle->values[0]);
812 parenttitle = parenttitle->parent;
813 }
814 }
815 reverse (pagetitlearray.begin(), pagetitlearray.end());
816 text_t pagetitle;
817 joinchar (pagetitlearray, ": ", pagetitle);
818 // remove html tags from the title
819 text_t::iterator open_tag=pagetitle.begin();
820 while (open_tag < pagetitle.end()) {
821 if (*open_tag == '<') {
822 text_t::iterator close_tag=open_tag+1;
823 text_t::iterator donechar=pagetitle.end();
824 while (close_tag < donechar) {
825 if (*close_tag == '>')
826 break;
827 ++close_tag;
828 }
829 if (close_tag < donechar) // remove this html tag, replace with space
830 *close_tag=' ';
831 pagetitle.erase(open_tag, close_tag);
832 }
833 ++open_tag;
834 }
835 disp.setmacro ("pagetitle", "document", pagetitle);
836
837 if (is_top (arg_d))
838 disp.setmacro ("thisOID", "Global", dm_safe(response.docInfo[0].metadata["archivedir"].values[0]));
839 else {
840 MetadataInfo_t *parentad = response.docInfo[0].metadata["archivedir"].parent;
841 text_t thisOID;
842 while (parentad != NULL) {
843 thisOID = parentad->values[0];
844 parentad = parentad->parent;
845 }
846 disp.setmacro ("thisOID", "Global", dm_safe(thisOID));
847 }
848 }
849 } else {
850 if (!arg_cl.empty()) {
851
852 // get metadata for top level classification
853 text_t classtop;
854 get_top (arg_cl, classtop);
855 metadata.insert ("childtype");
856 metadata.insert ("parameters");
857
858 if (get_info (classtop, collection, metadata, false, collectproto, response, logout)) {
859
860 text_t &title = response.docInfo[0].metadata["Title"].values[0];
861 bool unknown = false;
862
863 // test the _XXXwidth_ macro to see if image macros are
864 // defined for this type of classification - if not we'll
865 // just display the text
866 text_t tmp;
867 disp.expandstring ("document", "_" + title + "width_", tmp);
868 if (tmp == ("_" + title + "width_")) unknown = true;
869
870 if (unknown) {
871 disp.setmacro ("pagetitle", "document", title);
872 disp.setmacro ("imagethispage", "document", "<h2>" + title + "</h2>");
873 } else {
874 disp.setmacro ("pagetitle", "document", "_text" + title + "page_");
875 disp.setmacro ("imagethispage", "document", "_icon" + title + "page_");
876 }
877
878 //if the document is not a document from a collection
879 //we must set the macro to be an empty string
880 disp.setmacro ("relateddoc", "document", "");
881
882 // Add macros specific to the Collage/Phind classifiers
883 text_t &childtype = response.docInfo[0].metadata["childtype"].values[0];
884 if (childtype == "Collage") {
885
886 text_t::iterator a = arg_cl.begin();
887 text_t::iterator b = arg_cl.end();
888
889 bool collage = true;
890 while (a != b) {
891 if (*a == 46) collage = false;
892 a++;
893 }
894
895 if (collage) {
896 disp.setmacro ("collageclassifier", "document", "_collageapplet_");
897
898 // Next, macros that control the way the classifier is displayed
899 text_t parameters = response.docInfo[0].metadata["parameters"].values[0];
900
901 // extract key=value pairs and set as macros
902 text_t::iterator here = parameters.begin();
903 text_t::iterator end = parameters.end();
904 text_t key, value;
905
906 while (here != end) {
907 // get the next key and value pair
908 here = getdelimitstr (here, end, '=', key);
909 here = getdelimitstr (here, end, ';', value);
910
911 // store this key=value pair
912 if (!key.empty() && !value.empty()) {
913 disp.setmacro ("collage"+key, "document", value);
914 }
915 }
916
917
918 }
919 }
920
921 if (childtype == "Phind") {
922
923 // First, a macro to display the phind classifier
924 disp.setmacro ("phindclassifier", "document", "_phindapplet_");
925
926 // Next, macros that control the way the classifier is displayed
927 text_t parameters = response.docInfo[0].metadata["parameters"].values[0];
928
929 // extract key=value pairs and set as macros
930 text_t::iterator here = parameters.begin();
931 text_t::iterator end = parameters.end();
932 text_t key, value;
933
934 while (here != end) {
935 // get the next key and value pair
936 here = getdelimitstr (here, end, '=', key);
937 here = getdelimitstr (here, end, ';', value);
938
939 // store this key=value pair
940 if (!key.empty() && !value.empty()) {
941 disp.setmacro (key, "document", value);
942 }
943 }
944 } // end if (childtype == "Phind")
945 }
946 } // end if (!arg_cl.empty()) {
947 }
948}
949
950
951bool documentaction::do_action (cgiargsclass &args, recptprotolistclass *protos,
952 browsermapclass *browsers, displayclass &disp,
953 outconvertclass &outconvert, ostream &textout,
954 ostream &logout) {
955
956 // must have a valid collection server
957 recptproto *collectproto = protos->getrecptproto (args["c"], logout);
958 if (collectproto == NULL) {
959 logout << "documentaction::do_action called with NULL collectproto\n";
960 textout << outconvert << disp << "_document:header_\n"
961 << "Error: Attempt to get document without setting collection\n"
962 << "_document:footer_\n";
963 } else {
964
965 text_t OID = args["d"];
966 if (OID.empty()) OID = args["cl"];
967 if (OID.empty()) {
968 textout << outconvert << disp << "Document contains no data_document:footer_\n";
969 return true;
970 }
971
972
973 if (formatinfo.DocumentUseHTML && !args["d"].empty()) {
974
975 if (args["f"] == "1") {
976 textout << outconvert << disp
977 << "<html><head></head>\n"
978 << "<frameset rows=\"68,*\" noresize border=0>\n"
979 << "<frame scrolling=no frameborder=0 src=\"_gwcgi_?_optsite_e=_compressedoptions_&a=p&p=nav\">\n"
980 << "<frame name=\"documenttop\" frameborder=0 src=\"_gwcgi_?_optsite_e=_compressedoptions_&a=d&d="
981 << args["d"] << "\">"
982 << "<noframes>\n"
983 << "<p>You must have a frame enabled browser to view this.</p>\n"
984 << "</noframes>\n"
985 << "</frameset>\n"
986 << "</html>\n";
987 } else {
988 output_document (OID, args, collectproto, browsers, disp, outconvert, textout, logout);
989 }
990 return true;
991 }
992
993
994 textout << outconvert << disp << "_document:header_\n"
995 << "_document:content_\n";
996
997 // output the table of contents
998 output_toc (args, browsers, formatinfo, collectproto,
999 disp, outconvert, textout, logout);
1000
1001 if (formatinfo.DocumentArrowsTop && !args["d"].empty()) {
1002 textout << outconvert << disp << "_document:navarrowstop_\n";
1003 }
1004
1005 //output the related documents (may be the empty string)
1006 //will not output the docs if a format string is specified
1007 textout << outconvert << disp << "_document:relateddoc_\n";
1008
1009 // output the document text
1010 if (!args["d"].empty()) {
1011 textout << outconvert << "<p>\n";
1012 output_document (OID, args, collectproto, browsers, disp, outconvert, textout, logout);
1013 }
1014
1015 textout << outconvert << disp << "_document:footer_\n";
1016 }
1017 return true;
1018}
1019
1020void documentaction::output_text (ResultDocInfo_t &docinfo, format_t *formatlistptr,
1021 const TermInfo_tarray &terminfo, const text_t &OID,
1022 bool highlight, int hastxt, int wanttext,
1023 text_t &collection, recptproto *collectproto,
1024 browsermapclass *browsers, displayclass &disp,
1025 outconvertclass &outconvert, ostream &textout,
1026 ostream &logout, cgiargsclass &args) {
1027
1028 DocumentRequest_t docrequest;
1029 DocumentResponse_t docresponse;
1030 comerror_t err;
1031
1032 if (hastxt == 1) {
1033
1034 if (wanttext) {
1035 // get the text
1036 docrequest.OID = OID;
1037 collectproto->get_document (collection, docrequest, docresponse, err, logout);
1038
1039 // cut down on overhead by not using formattools if we only want the text
1040 // (wanttext will equal 2 if we want text and other stuff too)
1041 if (wanttext == 1)
1042 if (highlight) {
1043 highlighttext(docresponse.doc, args, terminfo, disp, outconvert, textout);
1044 } else {
1045 textout << outconvert << disp << docresponse.doc;
1046 }
1047 }
1048
1049 if (wanttext != 1) {
1050 text_tmap options;
1051 options["text"] = docresponse.doc;
1052
1053 if (formatinfo.AllowExtendedOptions) {
1054 load_extended_options(options, args, browsers, formatinfo,
1055 collectproto, disp, outconvert, logout);
1056 }
1057
1058 text_t doctext
1059 = get_formatted_string (collection, collectproto, docinfo, disp,
1060 formatlistptr, options, logout);
1061
1062 if (highlight) {
1063 highlighttext(doctext, args, terminfo, disp, outconvert, textout);
1064 } else {
1065 textout << outconvert << disp << doctext;
1066 }
1067 }
1068 }
1069}
1070
1071
1072void documentaction::output_document (const text_t &OID, cgiargsclass &args,
1073 recptproto *collectproto, browsermapclass *browsers,
1074 displayclass &disp, outconvertclass &outconvert,
1075 ostream &textout, ostream &logout) {
1076 FilterResponse_t inforesponse;
1077 FilterResponse_t queryresponse;
1078 text_tset metadata;
1079 bool getParents = false;
1080 bool highlight = false;
1081 int wanttext = 0;
1082 int arg_gt = args.getintarg("gt");
1083 text_t &collection = args["c"];
1084
1085 // if we have a query string and highlighting is turned on we need
1086 // to redo the query to get the terms for highlighting
1087
1088 if (!args["q"].empty() && args.getintarg("hl")) {
1089
1090 ColInfoResponse_t *cinfo = recpt->get_collectinfo_ptr (collectproto, collection, logout);
1091 bool segment = false;
1092 if (cinfo != NULL) {
1093 segment = cinfo->isSegmented;
1094 }
1095 FilterRequest_t request;
1096 comerror_t err;
1097 request.filterResultOptions = FRmatchTerms;
1098 text_t formattedstring = args["q"];
1099 format_querystring (formattedstring, args.getintarg("b"), segment);
1100 set_queryfilter_options (request, formattedstring, args);
1101 collectproto->filter (args["c"], request, queryresponse, err, logout);
1102 if (err != noError) {
1103 outconvertclass text_t2ascii;
1104 logout << text_t2ascii
1105 << "documentaction::output_document: call to QueryFilter failed "
1106 << "for " << args["c"] << " collection (" << get_comerror_string (err) << ")\n";
1107 highlight = false;
1108 } else {
1109 highlight = true;
1110 }
1111 }
1112
1113
1114 format_t *formatlistptr = new format_t();
1115 parse_formatstring (formatinfo.DocumentText, formatlistptr, metadata, getParents);
1116
1117 metadata.insert ("hastxt");
1118 metadata.insert ("haschildren");
1119
1120 if (formatinfo.DocumentText == "[Text]")
1121 wanttext = 1;
1122 else {
1123 char *docformat = formatinfo.DocumentText.getcstr();
1124 if (strstr (docformat, "[Text]") != NULL)
1125 wanttext = 2;
1126 delete docformat;
1127 }
1128
1129 if (get_info (OID, collection, metadata, getParents, collectproto, inforesponse, logout)) {
1130 int hastxt = inforesponse.docInfo[0].metadata["hastxt"].values[0].getint();
1131 int haschildren = inforesponse.docInfo[0].metadata["haschildren"].values[0].getint();
1132
1133 if (arg_gt == 0) {
1134 output_text (inforesponse.docInfo[0], formatlistptr, queryresponse.termInfo,
1135 OID, highlight, hastxt, wanttext, collection, collectproto,
1136 browsers, disp, outconvert, textout, logout, args);
1137
1138
1139 } else {
1140
1141 ResultDocInfo_t thisdocinfo = inforesponse.docInfo[0];
1142
1143 // text is to be expanded
1144 text_t exOID = OID;
1145 if (haschildren != 1) exOID = get_parent (OID);
1146 if (exOID.empty()) exOID = OID;
1147
1148 // if we're not in a document (i.e. we're in a top level classification)
1149 // we need to pass "is_classify = true" to get_contents so that it
1150 // doesn't recurse all the way through each document in the classification
1151 bool is_classify = false;
1152 if (args["d"].empty()) is_classify = true;
1153
1154 get_contents (exOID, is_classify, metadata, collection,
1155 collectproto, inforesponse, logout);
1156
1157 ResultDocInfo_tarray::iterator sechere = inforesponse.docInfo.begin();
1158 ResultDocInfo_tarray::iterator secend = inforesponse.docInfo.end();
1159
1160 if (arg_gt == 1) {
1161 // check if there are more than 10 sections containing text to be expanded -
1162 // if there are output warning message - this isn't a great way to do this
1163 // since the sections may be very large or very small - one day I'll fix it
1164 // -- Stefan.
1165 int seccount = 0;
1166 while (sechere != secend) {
1167 int shastxt = (*sechere).metadata["hastxt"].values[0].getint();
1168 if (shastxt == 1) seccount ++;
1169 if (seccount > 10) break;
1170 sechere ++;
1171 }
1172 if (seccount > 10) {
1173 // more than 10 sections so output warning message and text
1174 // for current section only
1175 textout << outconvert << disp << "_document:textltwarning_";
1176
1177 output_text (thisdocinfo, formatlistptr, queryresponse.termInfo,
1178 OID, highlight, hastxt, wanttext, collection,
1179 collectproto, browsers, disp, outconvert, textout, logout, args);
1180
1181 }
1182 else arg_gt = 2;
1183 }
1184
1185 if (arg_gt == 2) {
1186 // get the text for each section
1187 sechere = inforesponse.docInfo.begin();
1188 int count = 0;
1189 while (sechere != secend) {
1190 textout << outconvert << disp << "\n<p><a name=" << (*sechere).OID << "></a>\n";
1191
1192 int shastxt = (*sechere).metadata["hastxt"].values[0].getint();
1193
1194 output_text (*sechere, formatlistptr, queryresponse.termInfo,
1195 (*sechere).OID, highlight, shastxt, wanttext, collection,
1196 collectproto, browsers, disp, outconvert, textout, logout, args);
1197 count ++;
1198 sechere ++;
1199 }
1200 }
1201 }
1202 }
1203 delete formatlistptr;
1204}
1205
1206void documentaction::set_arrow_macros (cgiargsclass &args, recptproto *collectproto,
1207 displayclass &disp, ostream &logout) {
1208
1209 text_tset metadata;
1210 FilterResponse_t response;
1211 FilterResponse_t presponse;
1212
1213 int haschildren = 0;
1214 text_tarray next_siblings;
1215 text_t previous_sibling;
1216 text_t &arg_d = args["d"];
1217
1218 // get info on current section
1219 metadata.insert("haschildren");
1220 if (!get_info(arg_d, args["c"], metadata, false, collectproto, response, logout)) {
1221 logout << "error 1 in documentaction::set_arrow_macros\n";
1222 return;
1223 }
1224 haschildren = response.docInfo[0].metadata["haschildren"].values[0].getint();
1225
1226 // get OIDs of next and previous siblings of current section and
1227 // all it's parents
1228 int parentcount = countchar(arg_d.begin(), arg_d.end(), '.');
1229 text_t thisoid = arg_d;
1230 while (parentcount > 0) {
1231 get_children (get_parent(thisoid), args["c"], metadata, false,
1232 collectproto, response, logout);
1233 ResultDocInfo_tarray::iterator this_sibling = response.docInfo.begin();
1234 ResultDocInfo_tarray::iterator last_sibling = response.docInfo.end();
1235 bool first = true;
1236 while (this_sibling != last_sibling) {
1237 if ((*this_sibling).OID == thisoid) {
1238 if (!first && next_siblings.empty()) {
1239 previous_sibling = (*(this_sibling-1)).OID;
1240 int section_has_children = (*(this_sibling-1)).metadata["haschildren"].values[0].getint();
1241 // if previous sibling has children we need to recurse
1242 // down to the last descendant
1243 while (section_has_children) {
1244 get_children (previous_sibling, args["c"], metadata, false,
1245 collectproto, presponse, logout);
1246 if (!presponse.docInfo.empty()) {
1247 ResultDocInfo_tarray::iterator it = presponse.docInfo.end() - 1;
1248 previous_sibling = (*it).OID;
1249 section_has_children = (*it).metadata["haschildren"].values[0].getint();
1250 } else {
1251 section_has_children = 0; // this should never happen
1252 }
1253 }
1254 }
1255
1256 if ((this_sibling+1) != last_sibling) {
1257 next_siblings.push_back((*(this_sibling+1)).OID);
1258 } else {
1259 next_siblings.push_back("");
1260 }
1261 break;
1262 }
1263 this_sibling++;
1264 first = false;
1265 }
1266 thisoid = get_parent(thisoid);
1267 parentcount--;
1268 }
1269
1270 // work out values for next link
1271 if (haschildren) {
1272 disp.setmacro ("httpnextarrow", "document", "_httpdocument_&cl=" + args["cl"] +
1273 "&d=" + arg_d + ".fc");
1274 } else {
1275 text_tarray::const_iterator h = next_siblings.begin();
1276 text_tarray::const_iterator e = next_siblings.end();
1277 while (h != e) {
1278 if (!(*h).empty()) {
1279 disp.setmacro ("httpnextarrow", "document", "_httpdocument_&cl=" + args["cl"] +
1280 "&d=" + *h);
1281 break;
1282 }
1283 h++;
1284 }
1285 }
1286
1287 // work out value for previous link
1288 if (!previous_sibling.empty()) {
1289 disp.setmacro ("httpprevarrow", "document", "_httpdocument_&cl=" + args["cl"] +
1290 "&d=" + previous_sibling);
1291 } else {
1292 if (countchar(arg_d.begin(), arg_d.end(), '.')) {
1293 disp.setmacro ("httpprevarrow", "document", "_httpdocument_&cl=" + args["cl"] +
1294 "&d=" + get_parent(arg_d));
1295 }
1296 }
1297}
Note: See TracBrowser for help on using the repository browser.