/* file: pbsqs.c G. Moody 7 February 2012 Last revised: 13 May 2012 ------------------------------------------------------------------------------- PhysioBank Simple Query Server Copyright (C) 2012 George B. Moody This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. You may contact the author by e-mail (george@mit.edu) or postal mail (MIT Room E25-505A, Cambridge, MA 02139 USA). For updates to this software, please visit PhysioNet (http://www.physionet.org/). _______________________________________________________________________________ This code can be compiled with standalone.c into a standalone program with a command-line interface, or with daemon.c into a daemon that communicates via port 9967 (otherwise). See Makefile for compilation details. */ #include "pbsqs.h" /* headers, enums, constants, macros, typedefs, globals */ FILE *ifile, *ofile, *efile; void newline(void); char *strcasestr(char *haystack, char *needle); long str2sec(char *string); char *sec2str(long t); static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; RAMFile *mem = (RAMFile *)userp; mem->memory = realloc(mem->memory, mem->size + realsize + 1); if (mem->memory == NULL) { /* out of memory! */ fprintf(efile, "insufficient memory for in-memory index\n"); exit(EXIT_FAILURE); } memcpy(&(mem->memory[mem->size]), contents, realsize); mem->size += realsize; mem->memory[mem->size] = 0; return realsize; } size_t geturl(char *url, RAMFile *rf) { CURL *urlc; CURLcode res; rf->memory = malloc(1); rf->size = 0; curl_global_init(CURL_GLOBAL_ALL); if (urlc = curl_easy_init()) { curl_easy_setopt(urlc, CURLOPT_URL, url); curl_easy_setopt(urlc, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); curl_easy_setopt(urlc, CURLOPT_WRITEDATA, (void *)rf); curl_easy_setopt(urlc, CURLOPT_USERAGENT, "libcurl/1.0"); res = curl_easy_perform(urlc); curl_easy_cleanup(urlc); } curl_global_cleanup(); return (rf->size); } RAMFile indeximg, helpimg, shelpimg; void cleanup(void) { if (ie0) { free(ie0); ie0 = NULL; } if (indeximg.memory) { free(indeximg.memory); indeximg.memory = NULL; } if (shelpimg.memory) { free(shelpimg.memory); shelpimg.memory = NULL; } if (helpimg.memory) { free(helpimg.memory); helpimg.memory = NULL; } } int nie = 0, nrec = 1; Classname cn[] = { /* Annotations */ AnnM, "AnnM", /* machine-generated annotations */ AnnR, "AnnR", /* reference annotations */ /* Info */ AgeSex, "AgeSex", /* age and sex */ Med, "Med", /* medications */ Diag, "Diag", /* diagnoses */ Info, "Info", /* other metadata */ /* Signals */ BP, "BP", /* blood pressure */ CO2, "CO2", /* carbon dioxide [must precede "CO"] */ CO, "CO", /* cardiac output */ ECG, "ECG", /* electrocardiogram */ EEG, "EEG", /* electroencephalogram */ EMG, "EMG", /* electromyogram */ EOG, "EOG", /* electrooculogram */ EP, "EP", /* evoked potential */ Flow, "Flow", /* air flow */ HR, "HR", /* heart rate */ Noise, "Noise", /* for stress testing */ O2, "O2", /* oxygen */ PLETH, "PLETH", /* plethysmogram */ Pos, "Pos", /* body position */ Resp, "Resp", /* respiration */ Sound, "Sound", /* sound */ ST, "ST", /* ECG ST segment level */ Status, "Status", /* of patient or monitor */ Stim, "Stim", /* stimulus */ SV, "SV", /* stroke volume */ Temp, "Temp", /* temperature */ Unknown, "unknown", /* unknown */ INVALID, "" /* catchall [must be last] */ }; int parsepbi() { char *p = indeximg.memory, *pe = p + indeximg.size, *q, *r, *s; int i, n; if (p == NULL) return (0); /* Count the lines in the index and replace newlines with nulls. */ nie = 0; do { if (*p == '\n') { *p = '\0'; nie++; } } while (++p < pe); /* Allocate the index entries. */ if ((ie0 = calloc(nie, sizeof(IndexEntry))) == NULL) { fprintf(efile, "insufficient memory for index entries\n"); return (0); } iee = ie0 + nie; /* Parse the index, one line on each iteration. */ for (p = indeximg.memory, ie = ie0; ie < iee; ie++) { for (i = 0; i < 7; i++) { ie->field[i] = p; while (*p && *p != '\t') p++; if (*p) *p++ = '\0'; } if (*p == '\0') p++; q = ie->IX_CLASS; for (i = 0; cn[i].id != INVALID; i++) { r = cn[i].name; n = strlen(r); s = q + n; if (strncmp(r, q, n) == 0) { ie->IP_CLASST = cn[i].id; ie->IP_CLASSI = *s ? abs(atoi(s)) : 0; break; } } switch (ie->IP_CLASST) { case AnnM: case AnnR: ie->IP_FREQ = atof(ie->IX_FREQ); ie->IP_ANN = atoi(ie->IX_NANN); ie->IP_DUR = atoi(ie->IX_DUR); for (q = ie->IX_ANAME; *q && *q != '/'; q++) ; if (*q == '/') ie->IX_ANTYPE = q; break; case AgeSex: ie->IP_AGE = atoi(ie->IX_AGE); break; case Med: case Diag: case Info: case Unknown: case INVALID: break; default: /* all signal classes */ ie->IP_FREQ = atof(ie->IX_FREQ); ie->IP_DUR = atoi(ie->IX_DUR); if (strcmp(ie->IX_GAIN, "no calibration") == 0 || strncmp(ie->IX_GAIN, "inf ", 4) == 0) { ie->IP_GAIN = 1; ie->IX_UNITS = "?"; } else { for (q = ie->IX_GAIN; *q && *q != ' '; q++) ; *q++ = '\0'; ie->IP_GAIN = atof(ie->IX_GAIN); if (ie->IP_GAIN > 1e15) { ie->IP_GAIN = 1; ie->IX_UNITS = "?"; } else { for ( ; *q && *q != '/'; q++) ; ie->IX_UNITS = ++q; } } break; } } /* Collapse the record pointers. In the index, all entries that refer to any given record are consecutive. In this loop, we change the record pointers in entries for each record so that they point to the *same* string. Later on, we'll be able to check if two entries point to the same record by checking to see if their record pointers are equal. */ for (ie = ie0, nrec = 1; ie < iee-1; ie++) { if ((n = strcmp(ie->IX_RECORD, (ie+1)->IX_RECORD)) == 0) (ie+1)->IX_RECORD = ie->IX_RECORD; else if (n > 0) { fprintf(efile, "Index contains out-of-order records (%s precedes %s).\n" "Sort it using 'LC_ALL=C sort -k 1,1 -s new'.\n" "Exiting!\n", ie->IX_RECORD, (ie+1)->IX_RECORD); cleanup(); exit(1); } else nrec++; } return (nie); } int lines, showlist, verbose; Query parsequery(char *querystring) { static Query q; char *p, *r, *s, *t; /* If there is no more input, force the server to exit. */ if ((p = querystring) == NULL || strncasecmp(p, "exit", 4) == 0 || strncasecmp(p, "quit", 4) == 0) { p = NULL; q.type = qt_exit; return (q); } /* Parse the query string. Begin by skipping any initial whitespace. */ while (*p && (*p == ' ' || *p == '\t')) p++; /* Determine the query type. */ if (*p == '?' || strncasecmp(p, "help", 4) == 0) { q.type = qt_help; q.iname = querystring; return (q); } if (*p == '#') { q.type = qt_count; while (*++p && (*p == ' ' || *p == '\t')) ; /* skip whitespace after '#' */ } else if (*p == '+') { q.type = qt_long; while (*++p && (*p == ' ' || *p == '\t')) ; /* skip whitespace after '+' */ } else if (*p) q.type = qt_list; /* Divide the rest of the querystring into tokens. */ r = p; while (*p && *p != ' ' && *p != '\t' && *p != '\n') p++; if (*p) *p++ = '\0'; while (*p && (*p == ' ' || *p == '\t')) p++; s = p; while (*p && *p != ' ' && *p != '\t') p++; if (*p) *p++ = '\0'; while (*p && (*p == ' ' || *p == '\t')) p++; if (*p == '"' || *p == '\'') { /* last token is quoted */ t = ++p; /* discard opening quotation mark */ while (*p && *p != '\n') p++; /* keep any whitespace */ *--p = '\0'; /* discard closing quotation mark */ } else if (*p == '\0') { t = ""; } else { t = p; while (*p && *p != '\n' && *p != ' ' && *p != '\t') p++; *p = '\0'; /* discard anything after a space or tab */ } /* If there is no item, return a no-op query (do not cause an exit). */ if (*r == '\0') { q.type = qt_null; return (q); } /* Determine the query's subject. Note that the subject may be modified when the query's pattern is examined below. */ q.iname = r; q.inst = 0; if (strcasecmp(r, "Age") == 0) q.item = qi_age; else if (strcasecmp(r, "Diag") == 0) q.item = qi_diag; else if (strcasecmp(r, "Info") == 0) q.item = qi_info; else if (strcasecmp(r, "Med") == 0) q.item = qi_med; else if (strcasecmp(r, "Record") ==0) q.item = qi_rec; else if (strcasecmp(r, "Sex") == 0) q.item = qi_sex; else if (*r == '@') q.item = qi_ann; else if (*r == '"' || *r == '\'') q.item = qi_sig; else if (*r == '/') q.item = qi_ant; else { if (strncasecmp(r, "AnnM", 4) == 0) { q.item = qi_ann; q.id = AnnM; if (*(r+4)) q.inst = abs(atoi(r+4)); } else if (strncasecmp(r, "AnnR", 4) == 0) { q.item = qi_ann; q.id = AnnR; if (*(r+4)) q.inst = abs(atoi(r+4)); } else if (strncasecmp(r, "BP", 2) == 0) { q.item = qi_cls; q.id = BP; if (*(r+2)) q.inst = abs(atoi(r+2)); } else if (strncasecmp(r, "CO2", 3) == 0) { q.item = qi_cls; q.id = CO2; if (*(r+3)) q.inst = abs(atoi(r+3)); } else if (strncasecmp(r, "CO", 2) == 0) { q.item = qi_cls; q.id = CO; if (*(r+2)) q.inst = abs(atoi(r+2)); } else if (strncasecmp(r, "ECG", 3) == 0) { q.item = qi_cls; q.id = ECG; if (*(r+3)) q.inst = abs(atoi(r+3)); } else if (strncasecmp(r, "EEG", 3) == 0) { q.item = qi_cls; q.id = EEG; if (*(r+3)) q.inst = abs(atoi(r+3)); } else if (strncasecmp(r, "EMG", 3) == 0) { q.item = qi_cls; q.id = EMG; if (*(r+3)) q.inst = abs(atoi(r+3)); } else if (strncasecmp(r, "EOG", 3) == 0) { q.item = qi_cls; q.id = EOG; if (*(r+3)) q.inst = abs(atoi(r+3)); } else if (strncasecmp(r, "EP", 2) == 0) { q.item = qi_cls; q.id = EP; if (*(r+2)) q.inst = abs(atoi(r+2)); } else if (strncasecmp(r, "Flow", 4) == 0) { q.item = qi_cls; q.id = Flow; if (*(r+4)) q.inst = abs(atoi(r+4)); } else if (strncasecmp(r, "HR", 2) == 0) { q.item = qi_cls; q.id = HR; if (*(r+2)) q.inst = abs(atoi(r+2)); } else if (strncasecmp(r, "Noise", 5) == 0) { q.item = qi_cls; q.id = Noise; if (*(r+5)) q.inst = abs(atoi(r+5)); } else if (strncasecmp(r, "O2", 3) == 0) { q.item = qi_cls; q.id = O2; if (*(r+2)) q.inst = abs(atoi(r+2)); } else if (strncasecmp(r, "PLETH", 5) == 0) { q.item = qi_cls; q.id = PLETH; if (*(r+5)) q.inst = abs(atoi(r+5)); } else if (strncasecmp(r, "Pos", 3) == 0) { q.item = qi_cls; q.id = Pos; if (*(r+3)) q.inst = abs(atoi(r+3)); } else if (strncasecmp(r, "Resp", 4) == 0) { q.item = qi_cls; q.id = Resp; if (*(r+4)) q.inst = abs(atoi(r+4)); } else if (strncasecmp(r, "Sound", 5) == 0) { q.item = qi_cls; q.id = Sound; if (*(r+5)) q.inst = abs(atoi(r+5)); } else if (strncasecmp(r, "ST", 2) == 0) { q.item = qi_cls; q.id = ST; if (*(r+2)) q.inst = abs(atoi(r+2)); } else if (strncasecmp(r, "Status", 6) == 0) { q.item = qi_cls; q.id = Status; if (*(r+6)) q.inst = abs(atoi(r+6)); } else if (strncasecmp(r, "SV", 2) == 0) { q.item = qi_cls; q.id = SV; if (*(r+2)) q.inst = abs(atoi(r+2)); } else if (strncasecmp(r, "Temp", 4) == 0) { q.item = qi_cls; q.id = Temp; if (*(r+4)) q.inst = abs(atoi(r+4)); } else if (strncasecmp(r, "Temp", 4) == 0) { q.item = qi_cls; q.id = Temp; if (*(r+4)) q.inst = abs(atoi(r+4)); } else if (strncasecmp(r, "unknown", 7) == 0) { q.item = qi_cls; q.id = Unknown; if (*(r+7)) q.inst = abs(atoi(r+7)); } } /* Determine the query's comparison operator. */ if (strcmp(s, "<") == 0) q.comp = qc_lt; else if (strcmp(s, "<=") == 0) q.comp = qc_le; else if (strcmp(s, "=") == 0) q.comp = qc_eq; else if (strcmp(s, ">=") == 0) q.comp = qc_ge; else if (strcmp(s, ">") == 0) q.comp = qc_gt; else if (strcmp(s, "~") == 0) q.comp = qc_sim; else if (strcmp(s, "!~") == 0) q.comp = qc_dif; else if (strcmp(s, "!=") == 0) q.comp = qc_ne; else q.comp = qc_def; /* Determine the query's pattern (the value to be matched). */ q.sval = t; for (p = t; *p && *p != ':'; p++) ; if (*p == ':') { /* string contains ':', assume it's a duration in [hh:]mm:ss form */ q.fval = q.ival = str2sec(t); if (q.item == qi_rec) q.item = qi_rec_d; else if (q.item == qi_sig) q.item = qi_sig_d; else if (q.item == qi_cls) q.item = qi_cls_d; else if (q.item == qi_ann) q.item = qi_ann_d; else if (q.item == qi_ant) q.item = qi_ant_d; } else if (strcmp(p-2, "Hz") == 0) { /* string ends with "Hz", assume it's a sampling frequency */ q.ival = q.fval = atof(t); if (q.item == qi_rec) q.item = qi_rec_f; else if (q.item == qi_sig) q.item = qi_sig_f; else if (q.item == qi_cls) q.item = qi_cls_f; else if (q.item == qi_ann) q.item = qi_ann_f; else if (q.item == qi_ant) q.item = qi_ant_f; } else if (p = strstr(t, "adu/")) { /* string contains "adu/", assume it's a gain */ q.ival = q.fval = atof(t); if (q.item == qi_rec) q.item = qi_rec_g; else if (q.item == qi_sig) q.item = qi_sig_g; else if (q.item == qi_cls) q.item = qi_cls_g; } else { /* none of the above; if it's a number, and we're looking for an annotator, assume it's a number of annotations */ q.ival = q.fval = atof(t); if (q.item == qi_ann) q.item = qi_ann_n; else if (q.item == qi_ant) q.item = qi_ant_n; } return (q); } int search(Query q) { char *p, *pe, *r = NULL; int matches = 0; /* Choose the query type */ switch (q.item) { case qi_age: /* search for records by age */ for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST == AgeSex && NMATCH(ie->IP_AGE, q.comp, q.ival)) OUT2("%d", ie->IP_AGE) break; case qi_ann: /* search for annotators */ if (*q.iname != '@') { /* not a literal annotator name */ for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST == q.id && SMATCH(ie->IX_CLASS, q.comp, q.sval)) OUT2("%s", ie->IX_ANAME) } else { /* literal annotator name */ p = q.iname + 1; for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST <= AnnR && SMATCH(ie->IX_ANAME, q.comp, p)) OUT2("%s", ie->IX_ANAME) } break; case qi_ann_d: /* search for annotators by duration */ if (*q.iname != '@') { /* not a literal annotator name */ for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST == q.id && NMATCH(ie->IP_DUR, q.comp, q.ival)) OUT3("%s\t%s", ie->IX_ANAME, sec2str(ie->IP_DUR)) } else { /* literal annotator name */ p = q.iname + 1; for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST <= AnnR && strcmp(ie->IX_ANAME, p) == 0 && NMATCH(ie->IP_DUR, q.comp, q.ival)) OUT2("%s", sec2str(ie->IP_DUR)) } break; case qi_ann_f: /* search for annotators by sampling frequency */ if (*q.iname != '@') { /* not a literal annotator name */ for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST == q.id && NMATCH(ie->IP_FREQ, q.comp, q.fval)) OUT3("%s\t%gHz", ie->IX_ANAME, ie->IP_FREQ) } else { /* literal annotator name */ p = q.iname + 1; for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST <= AnnR && strcmp(ie->IX_ANAME, p) == 0 && NMATCH(ie->IP_FREQ, q.comp, q.fval)) OUT2("%gHz", ie->IP_FREQ) } break; case qi_ann_n: /* search for annotators by number of annotations */ if (*q.iname != '@') { /* not a literal annotator name */ for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST == q.id && NMATCH(ie->IP_ANN, q.comp, q.ival)) OUT3("%s\t%d", ie->IX_ANAME, ie->IP_ANN) } else { /* literal annotator name */ p = q.iname + 1; for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST <= AnnR && strcmp(ie->IX_ANAME, p) == 0 && NMATCH(ie->IP_ANN, q.comp, q.ival)) OUT2("%d", ie->IP_ANN) } break; case qi_ant: /* search for annotations by type */ for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST <= AnnR && ie->IX_ANTYPE && strcmp(ie->IX_ANTYPE, q.iname) == 0) OUT2("%s", ie->IX_ANAME) break; case qi_ant_d: /* search for annotations by type and duration */ for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST <= AnnR && ie->IX_ANTYPE && strcmp(ie->IX_ANTYPE, q.iname) == 0 && NMATCH(ie->IP_DUR, q.comp, q.ival)) OUT3("%s\t%s", ie->IX_ANAME, sec2str(ie->IP_DUR)) break; case qi_ant_f: /* search for annotations by type and samp frequency */ for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST <= AnnR && ie->IX_ANTYPE && strcmp(ie->IX_ANTYPE, q.iname) == 0 && NMATCH(ie->IP_FREQ, q.comp, q.fval)) OUT3("%s\t%gHz", ie->IX_ANAME, ie->IP_FREQ) break; case qi_ant_n: /* search for annotations by type and incidence */ for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST <= AnnR && ie->IX_ANTYPE && strcmp(ie->IX_ANTYPE, q.iname) == 0 && NMATCH(ie->IP_ANN, q.comp, q.ival)) OUT3("%s\t%d", ie->IX_ANAME, ie->IP_ANN) break; case qi_cls: /* search for signals by class */ for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST == q.id && (q.inst == 0 || q.inst == ie->IP_CLASSI)) OUT2("%s", ie->IX_SIGNAL) break; case qi_cls_d: /* search for signals by class and duration */ for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST == q.id && (q.inst == 0 || q.inst == ie->IP_CLASSI) && NMATCH(ie->IP_DUR, q.comp, q.ival)) OUT3("%s\t%s", ie->IX_SIGNAL, sec2str(ie->IP_DUR)) break; case qi_cls_f: /* search for signals by class and sampling frequency */ for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST == q.id && (q.inst == 0 || q.inst == ie->IP_CLASSI) && NMATCH(ie->IP_FREQ, q.comp, q.fval)) OUT3("%s\t%gHz", ie->IX_SIGNAL, ie->IP_FREQ) break; case qi_cls_g: /* search for signals by class and gain */ for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST == q.id && (q.inst == 0 || q.inst == ie->IP_CLASSI) && NMATCH(ie->IP_GAIN, q.comp, q.fval)) OUT4("%s\t%g adu/%s", ie->IX_SIGNAL, ie->IP_GAIN,ie->IX_UNITS) break; case qi_diag: /* search for diagnoses */ for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST == Diag && SCMATCH(ie->IX_INFO, q.comp, q.sval)) OUT2("%s", ie->IX_INFO) break; case qi_info: /* search for unclassified metadata */ for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST == Info && SCMATCH(ie->IX_INFO, q.comp, q.sval)) OUT2("%s", ie->IX_INFO) break; case qi_med: /* search for medications */ for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST == Med && SCMATCH(ie->IX_INFO, q.comp, q.sval)) OUT2("%s", ie->IX_INFO) break; case qi_rec: /* search for records */ for (ie = ie0, p = NULL; ie < iee; ie++) if (ie->IX_RECORD != p && SMATCH(ie->IX_RECORD, q.comp, q.sval)) { p = ie->IX_RECORD; matches++; if (showlist) fprintf(ofile, "%s", ie->IX_RECORD), newline(); } break; case qi_rec_d: /* search for records by duration */ for (ie = ie0, p = NULL; ie < iee; ie++) if (ie->IX_RECORD != p && NMATCH(ie->IP_DUR, q.comp, q.ival)) { p = ie->IX_RECORD; matches++; if (showlist > 1) fprintf(ofile, "%s\t%s", ie->IX_RECORD, sec2str(ie->IP_DUR)), newline(); else if (showlist) fprintf(ofile, "%s", ie->IX_RECORD), newline(); } break; case qi_rec_f: /* search for records by sampling frequency */ for (ie = ie0, p = NULL; ie < iee; ie++) if (ie->IX_RECORD != p && NMATCH(ie->IP_FREQ, q.comp, q.fval)) { p = ie->IX_RECORD; matches++; if (showlist > 1) fprintf(ofile, "%s\t%gHz", ie->IX_RECORD, ie->IP_FREQ), newline(); else if (showlist) fprintf(ofile, "%s", ie->IX_RECORD), newline(); } break; case qi_rec_g: /* search for records by gain */ for (ie = ie0, p = NULL; ie < iee; ie++) if (ie->IX_RECORD != p && NMATCH(ie->IP_GAIN, q.comp, q.fval) && strcmp(ie->IX_UNITS, q.sval) == 0) { p = ie->IX_RECORD; matches++; if (showlist > 1) fprintf(ofile, "%s\t%g adu/%s", ie->IX_RECORD, ie->IP_GAIN, ie->IX_UNITS), newline(); else if (showlist) fprintf(ofile, "%s", ie->IX_RECORD), newline(); } break; case qi_sex: /* search for records by sex */ for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST == AgeSex && SMATCH(ie->IX_SEX, q.comp, q.sval)) OUT2("%s", ie->IX_SEX); break; case qi_sig: /* search for signals by name */ p = q.iname + 1; *(pe = p + strlen(p) - 1) = '\0'; for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST >= BP && strcmp(ie->IX_SIGNAL, p) == 0) OUT2("%s", ie->IX_SIGNAL) *pe = *(p-1); break; case qi_sig_d: /* search for signals by name and duration */ p = q.iname + 1; *(pe = p + strlen(p) - 1) = '\0'; for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST >= BP && strcmp(ie->IX_SIGNAL, p) == 0 && NMATCH(ie->IP_DUR, q.comp, q.ival)) OUT2("%s", sec2str(ie->IP_DUR)) *pe = *(p-1); break; case qi_sig_f: /* search for signals by name and sampling frequency */ p = q.iname + 1; *(pe = p + strlen(p) - 1) = '\0'; for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST >= BP && strcmp(ie->IX_SIGNAL, p) == 0 && NMATCH(ie->IP_FREQ, q.comp, q.fval)) OUT2("%gHz", ie->IP_FREQ) *pe = *(p-1); break; case qi_sig_g: /* search for signals by name and gain */ p = q.iname + 1; *(pe = p + strlen(p) - 1) = '\0'; for (ie = ie0; ie < iee; ie++) if (ie->IP_CLASST >= BP && strcmp(ie->IX_SIGNAL, p) == 0 && NMATCH(ie->IP_GAIN, q.comp, q.fval)) OUT3("%g adu/%s", ie->IP_GAIN, ie->IX_UNITS) *pe = *(p-1); break; } return (matches); } /* strcasestr: case-insensitive substring search */ char *strcasestr(char *haystack, char *needle) { char *hp, *np; if (needle == NULL || *needle == '\0') return haystack; while (*haystack) { hp = haystack; np = needle; do { if (!*np) return haystack; } while (tolower(*hp++) == tolower(*np++)); haystack++; } return (NULL); } /* str2sec: convert string in [h]:m:s format to number of seconds */ long str2sec(char *string){ char *p; double x; x = atof(string); if (p=strchr(string, ':')){ x*=60; x+=atof(++p); if (p=strchr(p, ':')){ x*=60; x+=atof(++p); } } return ((long)(x+0.5)); } /* sec2str: convert number of seconds to string in [h]:m:s format */ char *sec2str(long t) { int hours, minutes, seconds; static char time_string[16]; hours = t/3600; minutes = (t/60) % 60; seconds = t % 60; if (hours) sprintf(time_string, "%2d:%02d:%02d", hours, minutes, seconds); else sprintf(time_string, "%2d:%02d", minutes, seconds); return (time_string); } static void printimage(RAMFile *i) /* used only by help(), below */ { char *p, *r; p = i->memory; r = p + i->size; while (p < r) { if (*p == '\n') newline(); else putchar(*p); p++; } } void help(Query q) { lines = 2; if (*(q.iname) == '?') { if (shelpimg.memory == NULL) geturl(SHORTHELP_URL, &shelpimg); printimage(&shelpimg); } else { if (helpimg.memory == NULL) geturl(HELP_URL, &helpimg); printimage(&helpimg); } } /* rn[] is used only in main(), defined in daemon.c and standalone.c */ Compname rn[] = { qc_lt, "<", /* < (less than) */ qc_le, "<=", /* <= (less than or equal to) */ qc_eq, "=", /* = (equal to) */ qc_ge, ">=", /* >= (greater than or equal to) */ qc_gt, ">", /* > (greater than) */ qc_sim, "~", /* ~ (similar to: within 10% for numeric comparisons, contained within for string comparisons) */ qc_dif, "!~", /* !~ (different: by >10% for numeric comparisons, not contained within for string comparisons) */ qc_ne, "!=", /* != (not equal to) */ qc_def, "?" /* ? (defined) */ };