1 /* $Id: logic.c,v 1.20 2007-04-23 21:05:23 adam Exp $
2 Copyright (c) 2006-2007, Index Data.
4 This file is part of Pazpar2.
6 Pazpar2 is free software; you can redistribute it and/or modify it under
7 the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 2, or (at your option) any later
11 Pazpar2 is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 You should have received a copy of the GNU General Public License
17 along with Pazpar2; see the file LICENSE. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
23 \brief high-level logic; mostly user sessions and settings
31 #include <sys/socket.h>
37 #include <yaz/marcdisp.h>
38 #include <yaz/comstack.h>
39 #include <yaz/tcpip.h>
40 #include <yaz/proto.h>
41 #include <yaz/readconf.h>
42 #include <yaz/pquery.h>
43 #include <yaz/otherinfo.h>
44 #include <yaz/yaz-util.h>
46 #include <yaz/query-charset.h>
47 #include <yaz/querytowrbuf.h>
48 #if YAZ_VERSIONL >= 0x020163
49 #include <yaz/oid_db.h>
58 #include <yaz/timing.h>
61 #include <netinet/in.h>
66 #include "termlists.h"
68 #include "relevance.h"
76 // Note: Some things in this structure will eventually move to configuration
77 struct parameters global_parameters =
98 // Recursively traverse query structure to extract terms.
99 void pull_terms(NMEM nmem, struct ccl_rpn_node *n, char **termlist, int *num)
111 pull_terms(nmem, n->u.p[0], termlist, num);
112 pull_terms(nmem, n->u.p[1], termlist, num);
115 nmem_strsplit(nmem, " ", n->u.t.term, &words, &numwords);
116 for (i = 0; i < numwords; i++)
117 termlist[(*num)++] = words[i];
124 char *normalize_mergekey(char *buf, int skiparticle)
126 char *p = buf, *pout = buf;
131 char articles[] = "the den der die des an a "; // must end in space
133 while (*p && !isalnum(*p))
136 while (*p && *p != ' ' && pout - firstword < 62)
137 *(pout++) = tolower(*(p++));
140 if (!strstr(articles, firstword))
147 while (*p && !isalnum(*p))
150 *(pout++) = tolower(*(p++));
153 while (*p && !isalnum(*p))
160 while (pout > buf && *pout == ' ');
165 // Extract what appears to be years from buf, storing highest and
167 static int extract_years(const char *buf, int *first, int *last)
176 while (*buf && !isdigit(*buf))
179 for (e = buf; *e && isdigit(*e); e++)
183 int value = atoi(buf);
184 if (*first < 0 || value < *first)
186 if (*last < 0 || value > *last)
195 static void add_facet(struct session *s, const char *type, const char *value)
201 for (i = 0; i < s->num_termlists; i++)
202 if (!strcmp(s->termlists[i].name, type))
204 if (i == s->num_termlists)
206 if (i == SESSION_MAX_TERMLISTS)
208 yaz_log(YLOG_FATAL, "Too many termlists");
212 s->termlists[i].name = nmem_strdup(s->nmem, type);
213 s->termlists[i].termlist = termlist_create(s->nmem, s->expected_maxrecs, 15);
214 s->num_termlists = i + 1;
216 termlist_insert(s->termlists[i].termlist, value);
219 xmlDoc *normalize_record(struct session_database *sdb, Z_External *rec)
221 struct database_retrievalmap *m;
222 struct database *db = sdb->database;
226 // First normalize to XML
231 if (rec->which != Z_External_octet)
233 yaz_log(YLOG_WARN, "Unexpected external branch, probably BER %s",
237 buf = (char*) rec->u.octet_aligned->buf;
238 len = rec->u.octet_aligned->len;
239 if (yaz_marc_read_iso2709(sdb->yaz_marc, buf, len) < 0)
241 yaz_log(YLOG_WARN, "Failed to decode MARC %s", db->url);
245 yaz_marc_write_using_libxml2(sdb->yaz_marc, 1);
246 if (yaz_marc_write_xml(sdb->yaz_marc, &res,
247 "http://www.loc.gov/MARC21/slim", 0, 0) < 0)
249 yaz_log(YLOG_WARN, "Failed to encode as XML %s",
253 rdoc = xmlNewDoc((xmlChar *) "1.0");
254 xmlDocSetRootElement(rdoc, res);
260 "Unknown native_syntax in normalize_record from %s",
265 if (global_parameters.dump_records){
267 "Input Record (normalized) from %s\n----------------\n",
269 #if LIBXML_VERSION >= 20600
270 xmlDocFormatDump(stderr, rdoc, 1);
272 xmlDocDump(stderr, rdoc);
276 for (m = sdb->map; m; m = m->next){
282 new = xsltApplyStylesheet(m->stylesheet, rdoc, 0);
283 root= xmlDocGetRootElement(new);
284 if (!new || !root || !(root->children))
286 yaz_log(YLOG_WARN, "XSLT transformation failed from %s",
296 // do it another way to detect transformation errors right now
297 // but does not seem to work either!
299 xsltTransformContextPtr ctxt;
300 ctxt = xsltNewTransformContext(m->stylesheet, rdoc);
301 new = xsltApplyStylesheetUser(m->stylesheet, rdoc, 0, 0, 0, ctxt);
302 if ((ctxt->state == XSLT_STATE_ERROR) ||
303 (ctxt->state == XSLT_STATE_STOPPED)){
304 yaz_log(YLOG_WARN, "XSLT transformation failed from %s",
305 cl->database->database->url);
316 if (global_parameters.dump_records)
318 fprintf(stderr, "Record from %s\n----------------\n",
320 #if LIBXML_VERSION >= 20600
321 xmlDocFormatDump(stderr, rdoc, 1);
323 xmlDocDump(stderr, rdoc);
329 // Retrieve first defined value for 'name' for given database.
330 // Will be extended to take into account user associated with session
331 char *session_setting_oneval(struct session_database *db, int offset)
333 if (!db->settings[offset])
335 return db->settings[offset]->value;
340 // Initialize YAZ Map structures for MARC-based targets
341 static int prepare_yazmarc(struct session_database *sdb)
347 yaz_log(YLOG_WARN, "No settings for %s", sdb->database->url);
350 if ((s = session_setting_oneval(sdb, PZ_NATIVESYNTAX)) && !strncmp(s, "iso2709", 7))
352 char *encoding = "marc-8s", *e;
355 // See if a native encoding is specified
356 if ((e = strchr(s, ';')))
359 sdb->yaz_marc = yaz_marc_create();
360 yaz_marc_subfield_str(sdb->yaz_marc, "\t");
362 cm = yaz_iconv_open("utf-8", encoding);
366 "Unable to map from %s to UTF-8 for target %s",
367 encoding, sdb->database->url);
370 yaz_marc_iconv(sdb->yaz_marc, cm);
375 // Prepare XSLT stylesheets for record normalization
376 // Structures are allocated on the session_wide nmem to avoid having
377 // to recompute this for every search. This would lead
378 // to leaking if a single session was to repeatedly change the PZ_XSLT
379 // setting. However, this is not a realistic use scenario.
380 static int prepare_map(struct session *se, struct session_database *sdb)
386 yaz_log(YLOG_WARN, "No settings on %s", sdb->database->url);
389 if ((s = session_setting_oneval(sdb, PZ_XSLT)))
392 struct database_retrievalmap **m = &sdb->map;
395 nmem_strsplit(se->session_nmem, ",", s, &stylesheets, &num);
396 for (i = 0; i < num; i++)
398 (*m) = nmem_malloc(se->session_nmem, sizeof(**m));
400 if (!((*m)->stylesheet = conf_load_stylesheet(stylesheets[i])))
402 yaz_log(YLOG_FATAL, "Unable to load stylesheet: %s",
410 yaz_log(YLOG_WARN, "No Normalization stylesheet for target %s",
415 // This analyzes settings and recomputes any supporting data structures
417 static int prepare_session_database(struct session *se, struct session_database *sdb)
421 yaz_log(YLOG_WARN, "No settings associated with %s", sdb->database->url);
424 if (sdb->settings[PZ_NATIVESYNTAX] && !sdb->yaz_marc)
426 if (prepare_yazmarc(sdb) < 0)
429 if (sdb->settings[PZ_XSLT] && !sdb->map)
431 if (prepare_map(se, sdb) < 0)
438 void session_set_watch(struct session *s, int what, session_watchfun fun, void *data)
440 s->watchlist[what].fun = fun;
441 s->watchlist[what].data = data;
444 void session_alert_watch(struct session *s, int what)
446 if (!s->watchlist[what].fun)
448 (*s->watchlist[what].fun)(s->watchlist[what].data);
449 s->watchlist[what].fun = 0;
450 s->watchlist[what].data = 0;
453 //callback for grep_databases
454 static void select_targets_callback(void *context, struct session_database *db)
456 struct session *se = (struct session*) context;
457 struct client *cl = client_create();
458 client_set_database(cl, db);
459 client_set_session(cl, se);
462 // Associates a set of clients with a session;
463 // Note: Session-databases represent databases with per-session setting overrides
464 int select_targets(struct session *se, struct database_criterion *crit)
467 client_destroy(se->clients);
469 return session_grep_databases(se, crit, select_targets_callback);
472 int session_active_clients(struct session *s)
477 for (c = s->clients; c; c = client_next_in_session(c))
478 if (client_is_active(c))
484 // parses crit1=val1,crit2=val2|val3,...
485 static struct database_criterion *parse_filter(NMEM m, const char *buf)
487 struct database_criterion *res = 0;
494 nmem_strsplit(m, ",", buf, &values, &num);
495 for (i = 0; i < num; i++)
500 struct database_criterion *new = nmem_malloc(m, sizeof(*new));
501 char *eq = strchr(values[i], '=');
504 yaz_log(YLOG_WARN, "Missing equal-sign in filter");
508 new->name = values[i];
509 nmem_strsplit(m, "|", eq, &subvalues, &subnum);
511 for (subi = 0; subi < subnum; subi++)
513 struct database_criterion_value *newv = nmem_malloc(m, sizeof(*newv));
514 newv->value = subvalues[subi];
515 newv->next = new->values;
524 char *search(struct session *se, char *query, char *filter)
526 int live_channels = 0;
528 struct database_criterion *criteria;
530 yaz_log(YLOG_DEBUG, "Search");
532 nmem_reset(se->nmem);
533 criteria = parse_filter(se->nmem, filter);
535 live_channels = select_targets(se, criteria);
538 int maxrecs = live_channels * global_parameters.toget;
539 se->num_termlists = 0;
540 se->reclist = reclist_create(se->nmem, maxrecs);
541 // This will be initialized in send_search()
542 se->total_records = se->total_hits = se->total_merged = 0;
543 se->expected_maxrecs = maxrecs;
550 for (cl = se->clients; cl; cl = client_next_in_session(cl))
552 if (prepare_session_database(se, client_get_database(cl)) < 0)
553 return "CONFIG_ERROR";
554 if (client_parse_query(cl, query) < 0) // Query must parse for all targets
558 for (cl = se->clients; cl; cl = client_next_in_session(cl))
560 client_prep_connection(cl);
566 // Creates a new session_database object for a database
567 static void session_init_databases_fun(void *context, struct database *db)
569 struct session *se = (struct session *) context;
570 struct session_database *new = nmem_malloc(se->session_nmem, sizeof(*new));
571 int num = settings_num();
577 new->settings = nmem_malloc(se->session_nmem, sizeof(struct settings *) * num);
578 memset(new->settings, 0, sizeof(struct settings*) * num);
581 for (i = 0; i < num; i++)
582 new->settings[i] = db->settings[i];
584 new->next = se->databases;
588 // Doesn't free memory associated with sdb -- nmem takes care of that
589 static void session_database_destroy(struct session_database *sdb)
591 struct database_retrievalmap *m;
593 for (m = sdb->map; m; m = m->next)
594 xsltFreeStylesheet(m->stylesheet);
596 yaz_marc_destroy(sdb->yaz_marc);
599 // Initialize session_database list -- this represents this session's view
600 // of the database list -- subject to modification by the settings ws command
601 void session_init_databases(struct session *se)
604 grep_databases(se, 0, session_init_databases_fun);
607 // Probably session_init_databases_fun should be refactored instead of
609 static struct session_database *load_session_database(struct session *se, char *id)
611 struct database *db = find_database(id, 0);
613 session_init_databases_fun((void*) se, db);
614 // New sdb is head of se->databases list
615 return se->databases;
618 // Find an existing session database. If not found, load it
619 static struct session_database *find_session_database(struct session *se, char *id)
621 struct session_database *sdb;
623 for (sdb = se->databases; sdb; sdb = sdb->next)
624 if (!strcmp(sdb->database->url, id))
626 return load_session_database(se, id);
629 // Apply a session override to a database
630 void session_apply_setting(struct session *se, char *dbname, char *setting, char *value)
632 struct session_database *sdb = find_session_database(se, dbname);
633 struct setting *new = nmem_malloc(se->session_nmem, sizeof(*new));
634 int offset = settings_offset(setting);
638 yaz_log(YLOG_WARN, "Unknown setting %s", setting);
642 new->target = dbname;
645 new->next = sdb->settings[offset];
646 sdb->settings[offset] = new;
648 // Force later recompute of settings-driven data structures
649 // (happens when a search starts and client connections are prepared)
652 case PZ_NATIVESYNTAX:
655 yaz_marc_destroy(sdb->yaz_marc);
662 struct database_retrievalmap *m;
663 // We don't worry about the map structure -- it's in nmem
664 for (m = sdb->map; m; m = m->next)
665 xsltFreeStylesheet(m->stylesheet);
672 void destroy_session(struct session *s)
674 struct session_database *sdb;
676 yaz_log(YLOG_LOG, "Destroying session");
678 client_destroy(s->clients);
679 for (sdb = s->databases; sdb; sdb = sdb->next)
680 session_database_destroy(sdb);
681 nmem_destroy(s->nmem);
682 wrbuf_destroy(s->wrbuf);
685 struct session *new_session(NMEM nmem)
688 struct session *session = nmem_malloc(nmem, sizeof(*session));
690 yaz_log(YLOG_DEBUG, "New Pazpar2 session");
692 session->total_hits = 0;
693 session->total_records = 0;
694 session->num_termlists = 0;
695 session->reclist = 0;
696 session->requestid = -1;
697 session->clients = 0;
698 session->expected_maxrecs = 0;
699 session->session_nmem = nmem;
700 session->nmem = nmem_create();
701 session->wrbuf = wrbuf_alloc();
702 session_init_databases(session);
703 for (i = 0; i <= SESSION_WATCH_MAX; i++)
705 session->watchlist[i].data = 0;
706 session->watchlist[i].fun = 0;
712 struct hitsbytarget *hitsbytarget(struct session *se, int *count)
714 static struct hitsbytarget res[1000]; // FIXME MM
718 for (cl = se->clients; cl; cl = client_next_in_session(cl))
720 char *name = session_setting_oneval(client_get_database(cl), PZ_NAME);
722 res[*count].id = client_get_database(cl)->database->url;
723 res[*count].name = *name ? name : "Unknown";
724 res[*count].hits = client_get_hits(cl);
725 res[*count].records = client_get_num_records(cl);
726 res[*count].diagnostic = client_get_diagnostic(cl);
727 res[*count].state = client_get_state_str(cl);
728 res[*count].connected = client_get_connection(cl) ? 1 : 0;
735 struct termlist_score **termlist(struct session *s, const char *name, int *num)
739 for (i = 0; i < s->num_termlists; i++)
740 if (!strcmp((const char *) s->termlists[i].name, name))
741 return termlist_highscore(s->termlists[i].termlist, num);
745 #ifdef MISSING_HEADERS
746 void report_nmem_stats(void)
748 size_t in_use, is_free;
750 nmem_get_memory_in_use(&in_use);
751 nmem_get_memory_free(&is_free);
753 yaz_log(YLOG_LOG, "nmem stat: use=%ld free=%ld",
754 (long) in_use, (long) is_free);
758 struct record_cluster *show_single(struct session *s, int id)
760 struct record_cluster *r;
762 reclist_rewind(s->reclist);
763 while ((r = reclist_read_record(s->reclist)))
769 struct record_cluster **show(struct session *s, struct reclist_sortparms *sp, int start,
770 int *num, int *total, int *sumhits, NMEM nmem_show)
772 struct record_cluster **recs = nmem_malloc(nmem_show, *num
773 * sizeof(struct record_cluster *));
774 struct reclist_sortparms *spp;
777 yaz_timing_t t = yaz_timing_create();
780 for (spp = sp; spp; spp = spp->next)
781 if (spp->type == Metadata_sortkey_relevance)
783 relevance_prepare_read(s->relevance, s->reclist);
786 reclist_sort(s->reclist, sp);
788 *total = s->reclist->num_records;
789 *sumhits = s->total_hits;
791 for (i = 0; i < start; i++)
792 if (!reclist_read_record(s->reclist))
799 for (i = 0; i < *num; i++)
801 struct record_cluster *r = reclist_read_record(s->reclist);
811 yaz_log(YLOG_LOG, "show %6.5f %3.2f %3.2f",
812 yaz_timing_get_real(t), yaz_timing_get_user(t),
813 yaz_timing_get_sys(t));
814 yaz_timing_destroy(&t);
819 void statistics(struct session *se, struct statistics *stat)
824 memset(stat, 0, sizeof(*stat));
825 for (cl = se->clients; cl; cl = client_next_in_session(cl))
827 if (!client_get_connection(cl))
828 stat->num_no_connection++;
829 switch (client_get_state(cl))
831 case Client_Connecting: stat->num_connecting++; break;
832 case Client_Initializing: stat->num_initializing++; break;
833 case Client_Searching: stat->num_searching++; break;
834 case Client_Presenting: stat->num_presenting++; break;
835 case Client_Idle: stat->num_idle++; break;
836 case Client_Failed: stat->num_failed++; break;
837 case Client_Error: stat->num_error++; break;
842 stat->num_hits = se->total_hits;
843 stat->num_records = se->total_records;
845 stat->num_clients = count;
848 void start_http_listener(void)
851 struct conf_server *ser = global_parameters.server;
853 if (*global_parameters.listener_override)
854 strcpy(hp, global_parameters.listener_override);
857 strcpy(hp, ser->host ? ser->host : "");
862 sprintf(hp + strlen(hp), "%d", ser->port);
868 void start_proxy(void)
871 struct conf_server *ser = global_parameters.server;
873 if (*global_parameters.proxy_override)
874 strcpy(hp, global_parameters.proxy_override);
875 else if (ser->proxy_host || ser->proxy_port)
877 strcpy(hp, ser->proxy_host ? ser->proxy_host : "");
882 sprintf(hp + strlen(hp), "%d", ser->proxy_port);
888 http_set_proxyaddr(hp, ser->myurl ? ser->myurl : "");
891 void start_zproxy(void)
893 struct conf_server *ser = global_parameters.server;
895 if (*global_parameters.zproxy_override){
896 yaz_log(YLOG_LOG, "Z39.50 proxy %s",
897 global_parameters.zproxy_override);
901 else if (ser->zproxy_host || ser->zproxy_port)
905 strcpy(hp, ser->zproxy_host ? ser->zproxy_host : "");
906 if (ser->zproxy_port)
913 sprintf(hp + strlen(hp), "%d", ser->zproxy_port);
915 strcpy(global_parameters.zproxy_override, hp);
916 yaz_log(YLOG_LOG, "Z39.50 proxy %s",
917 global_parameters.zproxy_override);
924 // Master list of connections we're handling events to
925 static IOCHAN channel_list = 0;
926 void pazpar2_add_channel(IOCHAN chan)
928 chan->next = channel_list;
932 void pazpar2_event_loop()
934 event_loop(&channel_list);
937 struct record *ingest_record(struct client *cl, Z_External *rec,
940 xmlDoc *xdoc = normalize_record(client_get_database(cl), rec);
943 struct record_cluster *cluster;
944 struct session *se = client_get_session(cl);
945 xmlChar *mergekey, *mergekey_norm;
948 struct conf_service *service = global_parameters.server->service;
953 root = xmlDocGetRootElement(xdoc);
954 if (!(mergekey = xmlGetProp(root, (xmlChar *) "mergekey")))
956 yaz_log(YLOG_WARN, "No mergekey found in record");
961 res = nmem_malloc(se->nmem, sizeof(struct record));
964 res->metadata = nmem_malloc(se->nmem,
965 sizeof(struct record_metadata*) * service->num_metadata);
966 memset(res->metadata, 0, sizeof(struct record_metadata*) * service->num_metadata);
968 mergekey_norm = (xmlChar *) nmem_strdup(se->nmem, (char*) mergekey);
970 normalize_mergekey((char *) mergekey_norm, 0);
972 cluster = reclist_insert(se->reclist,
973 global_parameters.server->service,
974 res, (char *) mergekey_norm,
976 if (global_parameters.dump_records)
977 yaz_log(YLOG_LOG, "Cluster id %d from %s (#%d)", cluster->recid,
978 client_get_database(cl)->database->url, record_no);
981 /* no room for record */
985 relevance_newrec(se->relevance, cluster);
987 for (n = root->children; n; n = n->next)
995 if (n->type != XML_ELEMENT_NODE)
997 if (!strcmp((const char *) n->name, "metadata"))
999 struct conf_metadata *md = 0;
1000 struct conf_sortkey *sk = 0;
1001 struct record_metadata **wheretoput, *newm;
1005 type = xmlGetProp(n, (xmlChar *) "type");
1006 value = xmlNodeListGetString(xdoc, n->children, 0);
1008 if (!type || !value)
1011 // First, find out what field we're looking at
1012 for (imeta = 0; imeta < service->num_metadata; imeta++)
1013 if (!strcmp((const char *) type, service->metadata[imeta].name))
1015 md = &service->metadata[imeta];
1016 if (md->sortkey_offset >= 0)
1017 sk = &service->sortkeys[md->sortkey_offset];
1022 yaz_log(YLOG_WARN, "Ignoring unknown metadata element: %s", type);
1026 // Find out where we are putting it
1027 if (md->merge == Metadata_merge_no)
1028 wheretoput = &res->metadata[imeta];
1030 wheretoput = &cluster->metadata[imeta];
1033 newm = nmem_malloc(se->nmem, sizeof(struct record_metadata));
1035 if (md->type == Metadata_type_generic)
1038 for (p = (char *) value; *p && isspace(*p); p++)
1040 for (pe = p + strlen(p) - 1;
1041 pe > p && strchr(" ,/.:([", *pe); pe--)
1043 newm->data.text = nmem_strdup(se->nmem, p);
1046 else if (md->type == Metadata_type_year)
1048 if (extract_years((char *) value, &first, &last) < 0)
1053 yaz_log(YLOG_WARN, "Unknown type in metadata element %s", type);
1056 if (md->type == Metadata_type_year && md->merge != Metadata_merge_range)
1058 yaz_log(YLOG_WARN, "Only range merging supported for years");
1061 if (md->merge == Metadata_merge_unique)
1063 struct record_metadata *mnode;
1064 for (mnode = *wheretoput; mnode; mnode = mnode->next)
1065 if (!strcmp((const char *) mnode->data.text, newm->data.text))
1069 newm->next = *wheretoput;
1073 else if (md->merge == Metadata_merge_longest)
1076 strlen(newm->data.text) > strlen((*wheretoput)->data.text))
1081 char *s = nmem_strdup(se->nmem, newm->data.text);
1082 if (!cluster->sortkeys[md->sortkey_offset])
1083 cluster->sortkeys[md->sortkey_offset] =
1084 nmem_malloc(se->nmem, sizeof(union data_types));
1085 normalize_mergekey(s,
1086 (sk->type == Metadata_sortkey_skiparticle));
1087 cluster->sortkeys[md->sortkey_offset]->text = s;
1091 else if (md->merge == Metadata_merge_all || md->merge == Metadata_merge_no)
1093 newm->next = *wheretoput;
1096 else if (md->merge == Metadata_merge_range)
1098 assert(md->type == Metadata_type_year);
1102 (*wheretoput)->data.number.min = first;
1103 (*wheretoput)->data.number.max = last;
1105 cluster->sortkeys[md->sortkey_offset] = &newm->data;
1109 if (first < (*wheretoput)->data.number.min)
1110 (*wheretoput)->data.number.min = first;
1111 if (last > (*wheretoput)->data.number.max)
1112 (*wheretoput)->data.number.max = last;
1117 union data_types *sdata = cluster->sortkeys[md->sortkey_offset];
1118 yaz_log(YLOG_LOG, "SK range: %d-%d", sdata->number.min, sdata->number.max);
1123 yaz_log(YLOG_WARN, "Don't know how to merge on element name %s", md->name);
1126 relevance_countwords(se->relevance, cluster,
1127 (char *) value, md->rank);
1130 if (md->type == Metadata_type_year)
1133 sprintf(year, "%d", last);
1134 add_facet(se, (char *) type, year);
1137 sprintf(year, "%d", first);
1138 add_facet(se, (char *) type, year);
1142 add_facet(se, (char *) type, (char *) value);
1149 yaz_log(YLOG_WARN, "Unexpected element %s in internal record", n->name);
1158 relevance_donerecord(se->relevance, cluster);
1159 se->total_records++;
1169 * indent-tabs-mode: nil
1171 * vim: shiftwidth=4 tabstop=8 expandtab