source: branches/New_Config_Format-branch/gsdl/src/recpt/formattools.cpp@ 1279

Last change on this file since 1279 was 1279, checked in by sjboddie, 24 years ago

merged changes to trunk into New_Config_Format branch

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 20.9 KB
Line 
1/**********************************************************************
2 *
3 * formattools.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 * $Id: formattools.cpp 1279 2000-07-12 22:21:53Z sjboddie $
25 *
26 *********************************************************************/
27
28/*
29 $Log$
30 Revision 1.20.2.3 2000/07/12 22:21:39 sjboddie
31 merged changes to trunk into New_Config_Format branch
32
33
34 Revision 1.20.2.2 2000/06/30 00:46:17 nzdl
35 caught New_Config_Format-branch up with changes to trunk
36
37 Revision 1.21 2000/06/30 00:40:39 sjboddie
38 Tidied up a bit. Fixed bug in formattools. Nested If/Or's should now
39 work within formatstrings.
40
41 Revision 1.20.2.1 2000/04/09 23:16:48 sjboddie
42 Added DocumentColumns stuff to New_Config_Format-branch branch
43
44 Revision 1.20 2000/04/07 04:40:44 sjboddie
45 Reverted back to old DocumentHeader, DocumentTitles, DocumentImages etc.
46 from DocumentColumns stuff. I'll move the DocumentColumns stuff to a
47 separate development branch (New_Config_Format-branch) for now. The plan
48 is to redesign the configuration file format a bit and limit the number of
49 distributions floating around that take different configuration formats).
50
51 Revision 1.19 2000/04/03 07:28:24 sjboddie
52 replaced old DocumentIcon and DocumentContents format options
53 with DocumentColumn stuff
54
55 Revision 1.18 2000/03/31 03:04:31 nzdl
56 tidied up some of the browsing code - replaced DocumentImages,
57 DocumentTitles and DocumentHeading with DocumentIcon
58
59 Revision 1.17 2000/01/26 20:10:31 sjboddie
60 changed the default order of detach/expand/highlight buttons
61
62 Revision 1.16 2000/01/25 22:33:31 sjboddie
63 added DocumentUseHTML
64
65 Revision 1.15 1999/12/13 02:45:16 davidb
66 Support for more than one metavalue for the same metadata name
67
68 Revision 1.14 1999/10/30 22:23:11 sjboddie
69 moved table functions from browsetools
70
71 Revision 1.13 1999/10/14 23:01:24 sjboddie
72 changes for new browsing support
73
74 Revision 1.12 1999/10/10 08:14:07 sjboddie
75 - metadata now returns mp rather than array
76 - redesigned browsing support (although it's not finished so
77 won't currently work ;-)
78
79 Revision 1.11 1999/09/28 20:38:19 rjmcnab
80 fixed a couple of bugs
81
82 Revision 1.10 1999/09/07 04:56:55 sjboddie
83 added GPL notice
84
85 Revision 1.9 1999/09/02 00:31:25 rjmcnab
86 fixed small error.
87
88 Revision 1.8 1999/08/20 00:56:38 sjboddie
89 added cgisafe option - you can now do something like [cgisafe:Title] if
90 you want Title to be entered safely into a url
91
92 Revision 1.7 1999/08/10 22:38:08 sjboddie
93 added some more format options
94
95 Revision 1.6 1999/07/30 02:25:42 sjboddie
96 made format_date function global
97
98 Revision 1.5 1999/07/21 05:00:00 sjboddie
99 added some date formatting
100
101 Revision 1.4 1999/07/20 03:02:15 sjboddie
102 added an [icon] option, added ability to call get_formatted_string
103 with icon and link arguments set
104
105 Revision 1.3 1999/07/09 02:44:35 sjboddie
106 fixed parent(All) function so it only outputs parents and not current
107 level meta
108
109 Revision 1.2 1999/07/08 20:48:33 rjmcnab
110 Added ability to print the result number
111
112 Revision 1.1 1999/07/07 05:49:34 sjboddie
113 had another crack at the format string code - created a new formattools
114 module. It can now handle {If} and {Or} statements although there's a
115 bug preventing nested if's and or's.
116
117 */
118
119
120#include "formattools.h"
121#include "cgiutils.h"
122#include <assert.h>
123
124// a few function prototypes
125static text_t format_string (ResultDocInfo_t &docinfo, format_t *formatlistptr,
126 const text_t &link, const text_t &icon,
127 const text_t &text, bool highlight);
128
129static bool parse_action (text_t::const_iterator &here, const text_t::const_iterator &end,
130 format_t *formatlistptr, text_tset &metadata, bool &getParents);
131
132void metadata_t::clear() {
133 metaname.clear();
134 metacommand = mNone;
135 parentcommand = pNone;
136 parentoptions.clear();
137}
138
139void decision_t::clear() {
140 command = dMeta;
141 meta.clear();
142}
143
144void format_t::clear() {
145 command = comText;
146 decision.clear();
147 text.clear();
148 meta.clear();
149 nextptr = NULL;
150 ifptr = NULL;
151 elseptr = NULL;
152 orptr = NULL;
153}
154
155void formatinfo_t::clear() {
156 DocumentColumns = "2";
157 DocumentColumnsTotalWidth = "_pagewidth_";
158 DocumentColumnLeft = "[Title]<br>[Buttons]";
159 DocumentColumnLeftWidth = "200";
160 DocumentColumnRight = "[TOC]";
161 DocumentArrowsBottom = true;
162 DocumentButtons.erase (DocumentButtons.begin(), DocumentButtons.end());
163 DocumentButtons.push_back ("Expand Text");
164 DocumentButtons.push_back ("Expand Contents");
165 DocumentButtons.push_back ("Detach");
166 DocumentButtons.push_back ("Highlight");
167 DocumentText = "[Text]";
168 formatstrings.erase (formatstrings.begin(), formatstrings.end());
169 DocumentUseHTML = false;
170}
171
172// simply checks to see if formatstring begins with a <td> tag
173bool is_table_content (const text_t &formatstring) {
174 text_t::const_iterator here = formatstring.begin();
175 text_t::const_iterator end = formatstring.end();
176
177 while (here != end) {
178 if (*here != ' ') {
179 if ((*here == '<') && ((here+3) < end)) {
180 if ((*(here+1) == 't' || *(here+1) == 'T') &&
181 (*(here+2) == 'd' || *(here+2) == 'D') &&
182 (*(here+3) == '>' || *(here+3) == ' '))
183 return true;
184 } else return false;
185 }
186 here ++;
187 }
188 return false;
189}
190
191bool is_table_content (const format_t *formatlistptr) {
192
193 if (formatlistptr == NULL) return false;
194
195 if (formatlistptr->command == comText)
196 return is_table_content (formatlistptr->text);
197
198 return false;
199}
200
201// returns false if key isn't in formatstringmap
202bool get_formatstring (const text_t &key, const text_tmap &formatstringmap,
203 text_t &formatstring) {
204
205 formatstring.clear();
206 text_tmap::const_iterator it = formatstringmap.find(key);
207 if (it == formatstringmap.end()) return false;
208 formatstring = (*it).second;
209 return true;
210}
211
212// tries to find "key1key2" then "key1" then "key2"
213bool get_formatstring (const text_t &key1, const text_t &key2,
214 const text_tmap &formatstringmap,
215 text_t &formatstring) {
216
217 formatstring.clear();
218 text_tmap::const_iterator it = formatstringmap.find(key1 + key2);
219 if (it != formatstringmap.end()) {
220 formatstring = (*it).second;
221 return true;
222 }
223 it = formatstringmap.find(key1);
224 if (it != formatstringmap.end()) {
225 formatstring = (*it).second;
226 return true;
227 }
228 it = formatstringmap.find(key2);
229 if (it != formatstringmap.end()) {
230 formatstring = (*it).second;
231 return true;
232 }
233 return false;
234}
235
236
237// returns a date of form _textmonthnn_ 31, 1999
238// input is date of type 19991231
239// at least the year must be present in date
240text_t format_date (const text_t &date) {
241
242 if (date.size() < 4) return "";
243
244 text_t::const_iterator datebegin = date.begin();
245
246 text_t year = substr (datebegin, datebegin+4);
247
248 if (date.size() < 6) return year;
249
250 text_t month = "_textmonth" + substr (datebegin+4, datebegin+6) + "_";
251 int imonth = month.getint();
252 if (imonth < 0 || imonth > 12) return year;
253
254 if (date.size() < 8) return month + ", " + year;
255
256 text_t day = substr (datebegin+6, datebegin+8);
257 if (day[0] == '0') day = substr (day.begin()+1, day.end());
258 int iday = day.getint();
259 if (iday < 0 || iday > 31) return month + ", " + year;
260
261 return month + " " + day + ", " + year;
262}
263
264static void get_parent_options (text_t &instring, metadata_t &metaoption) {
265
266 assert (instring.size() > 7);
267 if (instring.size() <= 7) return;
268
269 text_t meta, com, op;
270 bool inbraces = false;
271 bool inquotes = false;
272 bool foundcolon = false;
273 text_t::const_iterator here = instring.begin()+6;
274 text_t::const_iterator end = instring.end();
275 while (here != end) {
276 if (*here == '(') inbraces = true;
277 else if (*here == ')') inbraces = false;
278 else if (*here == '\'' && !inquotes) inquotes = true;
279 else if (*here == '\'' && inquotes) inquotes = false;
280 else if (*here == ':' && !inbraces) foundcolon = true;
281 else if (foundcolon) meta.push_back (*here);
282 else if (inquotes) op.push_back (*here);
283 else com.push_back (*here);
284 here ++;
285 }
286 instring = meta;
287 if (com.empty())
288 metaoption.parentcommand = pImmediate;
289 else if (com == "Top")
290 metaoption.parentcommand = pTop;
291 else if (com == "All") {
292 metaoption.parentcommand = pAll;
293 metaoption.parentoptions = op;
294 }
295}
296
297static void parse_meta (text_t &meta, metadata_t &metaoption,
298 text_tset &metadata, bool &getParents) {
299
300 if (meta.size() > 8 && (substr(meta.begin(), meta.begin()+8) == "cgisafe:")) {
301 metaoption.metacommand = mCgiSafe;
302 meta = substr (meta.begin()+8, meta.end());
303 }
304
305 if (meta.size() > 7 && (substr (meta.begin(), meta.begin()+6) == "parent")) {
306 getParents = true;
307 get_parent_options (meta, metaoption);
308 }
309
310 metadata.insert (meta);
311 metaoption.metaname = meta;
312}
313
314static void parse_meta (text_t &meta, format_t *formatlistptr,
315 text_tset &metadata, bool &getParents) {
316
317 if (meta == "link")
318 formatlistptr->command = comLink;
319 else if (meta == "/link")
320 formatlistptr->command = comEndLink;
321
322 else if (meta == "num")
323 formatlistptr->command = comNum;
324
325 else if (meta == "icon")
326 formatlistptr->command = comIcon;
327
328 else if (meta == "Text")
329 formatlistptr->command = comDoc;
330
331 else if (meta == "highlight")
332 formatlistptr->command = comHighlight;
333
334 else if (meta == "/highlight")
335 formatlistptr->command = comEndHighlight;
336
337 else {
338 formatlistptr->command = comMeta;
339 parse_meta (meta, formatlistptr->meta, metadata, getParents);
340 }
341}
342
343static bool parse_string (const text_t &formatstring, format_t *formatlistptr,
344 text_tset &metadata, bool &getParents) {
345
346 text_t text;
347 text_t::const_iterator here = formatstring.begin();
348 text_t::const_iterator end = formatstring.end();
349
350 while (here != end) {
351
352 if (*here == '\\') {
353 here ++;
354 if (here != end) text.push_back (*here);
355
356 } else if (*here == '{') {
357 if (!text.empty()) {
358 formatlistptr->command = comText;
359 formatlistptr->text = text;
360 formatlistptr->nextptr = new format_t();
361 formatlistptr = formatlistptr->nextptr;
362
363 text.clear();
364 }
365 if (parse_action (++here, end, formatlistptr, metadata, getParents)) {
366 formatlistptr->nextptr = new format_t();
367 formatlistptr = formatlistptr->nextptr;
368 if (here == end) break;
369 }
370 } else if (*here == '[') {
371 if (!text.empty()) {
372 formatlistptr->command = comText;
373 formatlistptr->text = text;
374 formatlistptr->nextptr = new format_t();
375 formatlistptr = formatlistptr->nextptr;
376
377 text.clear();
378 }
379 text_t meta;
380 here ++;
381 while (*here != ']') {
382 if (here == end) return false;
383 meta.push_back (*here);
384 here ++;
385 }
386 parse_meta (meta, formatlistptr, metadata, getParents);
387 formatlistptr->nextptr = new format_t();
388 formatlistptr = formatlistptr->nextptr;
389
390 } else
391 text.push_back (*here);
392
393 if (here != end) here ++;
394 }
395 if (!text.empty()) {
396 formatlistptr->command = comText;
397 formatlistptr->text = text;
398 formatlistptr->nextptr = new format_t();
399 formatlistptr = formatlistptr->nextptr;
400
401 }
402 return true;
403}
404
405
406static bool parse_action (text_t::const_iterator &here, const text_t::const_iterator &end,
407 format_t *formatlistptr, text_tset &metadata, bool &getParents) {
408
409 text_t::const_iterator it = findchar (here, end, '}');
410 if (it == end) return false;
411
412 text_t com = substr (here, it);
413 here = findchar (it, end, '{');
414 if (here == end) return false;
415 else here ++;
416
417 if (com == "If") formatlistptr->command = comIf;
418 else if (com == "Or") formatlistptr->command = comOr;
419 else return false;
420
421 int curlycount = 0;
422 int commacount = 0;
423 text_t text;
424 while (here != end) {
425
426 if (*here == '\\') {
427 here++;
428 if (here != end) text.push_back(*here);
429
430 } else if (*here == '{') {curlycount ++; text.push_back(*here);}
431 else if (*here == '}' && curlycount > 0) {
432 curlycount --;
433 text.push_back(*here);
434 }
435
436 else if ((*here == ',' || *here == '}') && curlycount <= 0) {
437
438 if (formatlistptr->command == comOr) {
439 // the {Or}{this, or this, or this, or this} statement
440 // or'ed statements may be either [metadata] or plain text
441 format_t *or_ptr;
442
443 // find the next unused orptr
444 if (formatlistptr->orptr == NULL) {
445 formatlistptr->orptr = new format_t();
446 or_ptr = formatlistptr->orptr;
447 } else {
448 or_ptr = formatlistptr->orptr;
449 while (or_ptr->nextptr != NULL)
450 or_ptr = or_ptr->nextptr;
451 or_ptr->nextptr = new format_t();
452 or_ptr = or_ptr->nextptr;
453 }
454
455 text_t::const_iterator beginbracket = text.begin();
456 text_t::const_iterator endbracket = (text.end() - 1);
457 if ((*beginbracket == '[') && (*endbracket == ']')) {
458 // it's metadata
459 text_t meta = substr (beginbracket+1, endbracket);
460 parse_meta (meta, or_ptr, metadata, getParents);
461
462 } else {
463 parse_string (text, or_ptr, metadata, getParents);
464 }
465 text.clear();
466
467 } else {
468 // the {If}{decide,do,else} statement
469 if (commacount == 0) {
470 // If decision only supports metadata at present
471
472 // remove the surrounding square brackets
473 text_t::const_iterator beginbracket = text.begin();
474 text_t::const_iterator endbracket = (text.end() - 1);
475 if ((*beginbracket == '[') && (*endbracket == ']')) {
476 text_t meta = substr (beginbracket+1, endbracket);
477 parse_meta (meta, formatlistptr->decision.meta, metadata, getParents);
478 commacount ++;
479 text.clear();
480 }
481
482 } else if (commacount == 1) {
483 formatlistptr->ifptr = new format_t();
484 parse_string (text, formatlistptr->ifptr, metadata, getParents);
485 commacount ++;
486 text.clear();
487
488 } else if (commacount == 2) {
489 formatlistptr->elseptr = new format_t();
490 parse_string (text, formatlistptr->elseptr, metadata, getParents);
491 commacount ++;
492 text.clear();
493 }
494 }
495 if (*here == '}') break;
496
497 } else text.push_back(*here);
498
499 if (here != end) here ++;
500 }
501
502 return true;
503}
504
505
506bool parse_formatstring (const text_t &formatstring, format_t *formatlistptr,
507 text_tset &metadata, bool &getParents) {
508
509 formatlistptr->clear();
510 getParents = false;
511
512 return (parse_string (formatstring, formatlistptr, metadata, getParents));
513}
514
515
516// note: all the format_date stuff is assuming that all Date metadata is going to
517// be of the form yyyymmdd, this is of course, crap ;)
518
519static text_t get_meta (ResultDocInfo_t &docinfo, const metadata_t &meta) {
520
521 // make sure we have the requested metadata
522 MetadataInfo_tmap::iterator it = docinfo.metadata.find (meta.metaname);
523 if (it == docinfo.metadata.end()) return "";
524
525 MetadataInfo_t *parent = docinfo.metadata[meta.metaname].parent;
526
527 switch (meta.parentcommand) {
528 case pNone:
529 {
530 text_t classifier_metaname = docinfo.classifier_metadata_type;
531 int metaname_index
532 = (classifier_metaname == meta.metaname) ? docinfo.classifier_metadata_offset : 0;
533 text_t metadata_item = docinfo.metadata[meta.metaname].values[metaname_index];
534
535 if (meta.metaname == "Date")
536 return format_date (metadata_item);
537 if (meta.metacommand == mCgiSafe)
538 return cgi_safe (metadata_item);
539 else return metadata_item;
540 }
541
542 case pImmediate:
543 if (parent != NULL) {
544 if (meta.metaname == "Date")
545 return format_date (parent->values[0]);
546 if (meta.metacommand == mCgiSafe)
547 return cgi_safe (parent->values[0]);
548 else return parent->values[0];
549 }
550 break;
551
552 case pTop:
553 if (parent != NULL) {
554 while (parent->parent != NULL) parent = parent->parent;
555
556 if (meta.metaname == "Date")
557 return format_date (parent->values[0]);
558 if (meta.metacommand == mCgiSafe)
559 return cgi_safe (parent->values[0]);
560 else return parent->values[0];
561 }
562 break;
563
564 case pAll:
565 MetadataInfo_t *parent = docinfo.metadata[meta.metaname].parent;
566 if (parent != NULL) {
567 text_tarray tmparray;
568 while (parent != NULL) {
569 tmparray.push_back (parent->values[0]);
570 parent = parent->parent;
571 }
572 bool first = true;
573 text_t tmp;
574 text_tarray::reverse_iterator here = tmparray.rbegin();
575 text_tarray::reverse_iterator end = tmparray.rend();
576 while (here != end) {
577 if (!first) tmp += meta.parentoptions;
578 if (meta.metaname == "Date") tmp += format_date (*here);
579 else tmp += *here;
580 first = false;
581 here ++;
582 }
583 if (meta.metacommand == mCgiSafe) return cgi_safe (tmp);
584 else return tmp;
585 }
586 }
587 return "";
588}
589
590static text_t get_or (ResultDocInfo_t &docinfo, format_t *orptr,
591 const text_t &link, const text_t &icon,
592 const text_t &text, bool highlight) {
593
594 text_t tmp;
595 while (orptr != NULL) {
596
597 tmp = format_string (docinfo, orptr, link, icon, text, highlight);
598 if (!tmp.empty()) return tmp;
599
600 orptr = orptr->nextptr;
601 }
602 return "";
603}
604
605static text_t get_if (ResultDocInfo_t &docinfo, const decision_t &decision,
606 format_t *ifptr, format_t *elseptr, const text_t &link,
607 const text_t &icon, const text_t &text, bool highlight) {
608
609 // not much of a choice yet ...
610 if (decision.command == dMeta) {
611 if (get_meta (docinfo, decision.meta) != "") {
612 if (ifptr != NULL)
613 return get_formatted_string (docinfo, ifptr, link, icon, text, highlight);
614 }
615 else {
616 if (elseptr != NULL)
617 return get_formatted_string (docinfo, elseptr, link, icon, text, highlight);
618 }
619 }
620 return "";
621}
622
623text_t format_string (ResultDocInfo_t &docinfo, format_t *formatlistptr,
624 const text_t &link, const text_t &icon,
625 const text_t &text, bool highlight) {
626
627 if (formatlistptr == NULL) return "";
628
629 switch (formatlistptr->command) {
630 case comText:
631 return formatlistptr->text;
632 case comLink:
633 return link;
634 case comEndLink:
635 if (link.empty()) return "";
636 else return "</a>";
637 case comIcon:
638 return icon;
639 case comNum:
640 return docinfo.result_num;
641 case comMeta:
642 return get_meta (docinfo, formatlistptr->meta);
643 case comDoc:
644 return text;
645 case comHighlight:
646 if (highlight) return "<b>";
647 break;
648 case comEndHighlight:
649 if (highlight) return "</b>";
650 break;
651 case comIf:
652 return get_if (docinfo, formatlistptr->decision, formatlistptr->ifptr,
653 formatlistptr->elseptr, link, icon, text, highlight);
654 case comOr:
655 return get_or (docinfo, formatlistptr->orptr, link, icon, text, highlight);
656 }
657 return "";
658}
659
660
661text_t get_formatted_string (ResultDocInfo_t &docinfo, format_t *formatlistptr,
662 const text_t &link, const text_t &icon) {
663
664 text_t text;
665
666 text_t ft;
667 while (formatlistptr != NULL) {
668 ft += format_string (docinfo, formatlistptr, link, icon, text, false);
669 formatlistptr = formatlistptr->nextptr;
670 }
671 return ft;
672}
673
674
675text_t get_formatted_string (ResultDocInfo_t &docinfo, format_t *formatlistptr) {
676
677 text_t link = "<a href=\"_httpdocument_&cl=search&d=" + docinfo.OID + "\">";
678 text_t icon = "_icontext_";
679 text_t text;
680
681 text_t ft;
682 while (formatlistptr != NULL) {
683 ft += format_string (docinfo, formatlistptr, link, icon, text, false);
684 formatlistptr = formatlistptr->nextptr;
685 }
686 return ft;
687}
688
689
690text_t get_formatted_string (ResultDocInfo_t &docinfo, format_t *formatlistptr,
691 const text_t &text) {
692
693 text_t link = "<a href=\"_httpdocument_&cl=search&d=" + docinfo.OID + "\">";
694 text_t icon = "_icontext_";
695
696 text_t ft;
697 while (formatlistptr != NULL) {
698 ft += format_string (docinfo, formatlistptr, link, icon, text, false);
699 formatlistptr = formatlistptr->nextptr;
700 }
701 return ft;
702}
703
704
705text_t get_formatted_string (ResultDocInfo_t &docinfo, format_t *formatlistptr,
706 const text_t &link, const text_t &icon, const text_t &text) {
707
708 text_t ft;
709 while (formatlistptr != NULL) {
710 ft += format_string (docinfo, formatlistptr, link, icon, text, false);
711 formatlistptr = formatlistptr->nextptr;
712 }
713 return ft;
714}
715
716text_t get_formatted_string (ResultDocInfo_t &docinfo, format_t *formatlistptr,
717 const text_t &link, const text_t &icon, bool highlight) {
718
719 text_t text, ft;
720 while (formatlistptr != NULL) {
721 ft += format_string (docinfo, formatlistptr, link, icon, text, highlight);
722 formatlistptr = formatlistptr->nextptr;
723 }
724 return ft;
725}
726
727text_t get_formatted_string (ResultDocInfo_t &docinfo, format_t *formatlistptr,
728 const text_t &link, const text_t &icon,
729 const text_t &text, bool highlight) {
730
731 text_t ft;
732 while (formatlistptr != NULL) {
733 ft += format_string (docinfo, formatlistptr, link, icon, text, highlight);
734 formatlistptr = formatlistptr->nextptr;
735 }
736 return ft;
737}
738
739
Note: See TracBrowser for help on using the repository browser.