/* definitions of WFDB_FILE and safe string macros */
#include "mimic2.h" /* includes stringtosubtyp() and subtyptodesc() */
/* safe strncpy -- allocates destination string using SUALLOC from wfdblib.h */
#define SUSTRNCPY(P, Q, N) { if (Q) { \
SUALLOC(P, (size_t)N+1, 1); strncpy(P, Q, N); } }
struct nv { /* name-value pair */
char *name;
char *value;
};
struct nvl { /* name-value list */
int count;
int type;
struct nv *par;
};
typedef struct nv NV;
typedef struct nvl NVL;
/* Make a name-value list from an annotation. */
NVL *anntonvl(WFDB_Annotation *annot)
{
char *p, *q;
int c = 0, state = 0;
NV *nvp = NULL;
NVL *nvlp = NULL;
if (annot->aux == NULL || *(annot->aux) == 0)
return (NULL);
for (p = q = annot->aux+1; *q; q++) {
if (*q == '=') {
p = q+1;
state = 1;
}
else if (*q == '\t')
if (state == 1) {
p = q+1;
c++;
state = 2;
}
}
if (state == 1) /* last name/value pair was not followed by a tab */
if (q > p) c++; /* there was a value, so increment the counter */
if (c == 0)
return (NULL); /* no name/value pairs */
SALLOC(nvp, c, sizeof(NV));
SALLOC(nvlp, 1, sizeof(NVL));
nvlp->count = c;
nvlp->type = annot->subtyp;
nvlp->par = nvp;
for (p = q = annot->aux+1; *(q-1); q++) {
if (*q == '=') {
SUSTRNCPY(nvp->name, p, (int)((long)q-(long)p));
p = q+1;
state = 1;
}
else if (*q == '\t' || *q == '\0')
if (state == 1) {
SUSTRNCPY(nvp->value, p, (int)((long)q-(long)p));
p = q+1;
nvp++;
state = 2;
}
}
return (nvlp);
}
/* Release memory allocated for a name-value list. */
freenvl(NVL *nvlp) {
NV *nvp;
if (nvlp == NULL) return;
nvp = nvlp->par;
while (nvlp->count-- > 0) {
SFREE(nvp->name);
SFREE(nvp->value);
nvp++;
}
SFREE(nvlp->par);
}
/* Free not only the list pointers, but also the pointer to the list. */
freenvlp(NVL *nvlp) {
freenvl(nvlp);
SFREE(nvlp);
}
/* Load a code dictionary into a name-value list. */
NVL load_dict(char *dict_name)
{
char buf[1024], *p, *q;
int c = 0;
NV *nvp;
NVL nvl = { 0, 0, NULL };
WFDB_FILE *dict = wfdb_fopen(wfdbfile(dict_name, NULL), "r");
if (dict == NULL) return (nvl);
while (wfdb_fgets(buf, sizeof(buf), dict))
c++;
if (c == 0) return (nvl);
SALLOC(nvp, c, sizeof(NV));
nvl.count = c;
nvl.type = 0; /* unused */
nvl.par = nvp;
wfdb_fseek(dict, 0L, SEEK_SET);
while (wfdb_fgets(buf, sizeof(buf), dict)) {
for (p = q = buf; *q; q++)
if (*q == '\t' || *q == ' ') {
SUSTRNCPY(nvp->name, p, (int)((long)q-(long)p));
p = ++q;
for ( ; *q; q++)
;
q--;
while (*q == '\n' || *q == '\t' || *q == ' ')
q--;
if (++q > p)
{ SUSTRNCPY(nvp->value, p, (int)((long)q-(long)p)); }
else
nvp->value = NULL;
nvp++;
break;
}
}
wfdb_fclose(dict);
return (nvl);
}
/* replace a coded value with its dictionary translation, if available. */
void decode_value(NV *nvp, NVL dict)
{
int c;
NV *p;
if (nvp == NULL || nvp->value == NULL || dict.count < 1) return;
for (c = 0, p = dict.par; c < dict.count; c++, p++)
if (strcmp(nvp->value, p->name) == 0) {
SSTRCPY(nvp->value, p->value);
return;
}
return;
}
static NVL ch_id_dict = { -1, 0, NULL };
static NVL io_id_dict = { -1, 0, NULL };
static NVL me_id_dict = { -1, 0, NULL };
static NVL cg_dict = { -1, 0, NULL };
static NVL cu_dict = { -1, 0, NULL };
WFDB_Time tp = -1;
void lookup(NV *nvp, int source)
{
if (strcmp(nvp->name, "id") == 0) {
if (source == 4 || source == 2) {
if (ch_id_dict.count == -1)
ch_id_dict = load_dict("dictionaries/ch-id-dict");
decode_value(nvp, ch_id_dict);
}
else if (source == 9 || source == 10 || source == 1 || source == 12) {
if (me_id_dict.count == -1)
me_id_dict = load_dict("dictionaries/me-id-dict");
decode_value(nvp, me_id_dict);
}
else if (source == 7 || source == 8 || source == 13) {
if (io_id_dict.count == -1)
io_id_dict = load_dict("dictionaries/io-id-dict");
decode_value(nvp, io_id_dict);
}
else if (source == 14 || source == 15) {
char p[256], sr[7];
strncpy(sr, nvp->value, 6); sr[6] = '\0';
sprintf(p, "%s "
"[view]",
sr, nvp->value, sr, nvp->value);
SSTRCPY(nvp->value, p);
}
}
else if (strcmp(nvp->name, "cu") == 0 || strcmp(nvp->name, "de") == 0) {
if (cu_dict.count == -1)
cu_dict = load_dict("dictionaries/cu-dict");
decode_value(nvp, cu_dict);
}
else if (strcmp(nvp->name, "cg") == 0) {
if (cg_dict.count == -1)
cg_dict = load_dict("dictionaries/cg-dict");
decode_value(nvp, cg_dict);
}
else if (strcmp(nvp->name, "io") == 0) {
if (io_id_dict.count == -1)
io_id_dict = load_dict("dictionaries/io-id-dict");
decode_value(nvp, io_id_dict);
}
else if (strcmp(nvp->name, "so") == 0) {
if (me_id_dict.count == -1)
me_id_dict = load_dict("dictionaries/me-id-dict");
decode_value(nvp, me_id_dict);
}
else if (strcmp(nvp->name, "t0") == 0 || strcmp(nvp->name, "tf") == 0) {
char *p = timstr(-(tp + atol(nvp->value)));
SSTRCPY(nvp->value, p);
}
return;
}
void cleanup(void)
{
freenvl(&ch_id_dict);
freenvl(&io_id_dict);
freenvl(&me_id_dict);
freenvl(&cg_dict);
freenvl(&cu_dict);
}
char *pname;
main(int argc, char **argv)
{
char *record = NULL, *r, *prog_name();
char logrevtime[40];
int a = 0, i, lflag = 1, sp, sflag = 0, submatch;
NV *nvp;
NVL *nvlp;
struct tm *now;
time_t t;
WFDB_Anninfo ai;
WFDB_Annotation annot;
WFDB_Time from_time = 0, to_time = 0;
void help();
pname = prog_name(argv[0]);
t = time((time_t *)NULL); /* get current time from system clock */
now = localtime(&t);
/* Interpret command-line options. */
for (i = 1 ; i < argc; i++) {
if (*argv[i] == '-') switch (*(argv[i]+1)) {
case 'a': /* annotator follows */
if (++i >= argc) {
(void)fprintf(stderr, "%s: annotator must follow -a\n", pname);
exit(1);
}
ai.name = argv[i];
break;
case 'e': /* leave ids in encoded form */
lflag = 0;
break;
case 'f': /* starting time follows */
if (++i >= argc) {
(void)fprintf(stderr, "%s: starting time must follow -f\n",
pname);
exit(1);
}
from_time = i; /* to be converted to sample intervals below */
break;
case 'h': /* print usage summary and quit */
help();
exit(0);
break;
case 'r': /* input record name follows */
if (++i >= argc) {
(void)fprintf(stderr,
"%s: input record name must follow -r\n", pname);
exit(1);
}
record = argv[i];
break;
case 's': /* source code follows */
if (++i >= argc) {
(void)fprintf(stderr,
"%s: two-character source code must follow -s\n",
pname);
exit(1);
}
submatch = stringtosubtyp(argv[i]);
sflag = 1;
break;
case 't': /* ending time follows */
if (++i >= argc) {
(void)fprintf(stderr, "%s: end time must follow -t\n", pname);
exit(1);
}
to_time = i;
break;
default:
(void)fprintf(stderr, "%s: unrecognized option %s\n", pname, argv[i]);
exit(1);
break;
}
else {
(void)fprintf(stderr, "%s: unrecognized argument %s\n",
pname, argv[i]);
exit(1);
}
}
if (record == NULL || ai.name == NULL) {
help();
exit(1);
}
if (sampfreq(record) < 0.)
(void)setsampfreq(1.); /* default for logs is 1 second resolution */
ai.stat = WFDB_READ;
if (annopen(record, &ai, 1) < 0) /* open annotation file */
exit(2);
/* Handle -f and -t options if present. */
if (from_time)
from_time = strtim(argv[(int)from_time]);
if (from_time < 0L) from_time = -from_time;
if (to_time)
to_time = strtim(argv[(int)to_time]);
else
to_time = strtim("e");
if (to_time < 0L) to_time = -to_time;
if (to_time > 0L && to_time < from_time) {
long tt;
tt = from_time; from_time = to_time; to_time = tt;
}
/* Write the page header. */
for (r = record+strlen(record)-1; r > record; r--)
if (*r == '/') { r++; break; } /* discard any path information */
printf("Log for record %s\n", r);
printf("Log for record %s
\n",
r);
printf("This log is also available as text (%s.txt)"
" and as a (binary) annotation file (%s.log).\n",
r, r, r, r);
strftime(logrevtime, sizeof(logrevtime), "%A, %d-%b-%Y %H:%M:%S %Z", now);
printf("
Generated: %s
(All dates below are surrogates.)
\n",
logrevtime);
/* Read the annotations. */
while (getann(0, &annot) >= 0 && (to_time == 0L || annot.time <= to_time)) {
if (annot.time < from_time) continue; /* skip entries before start */
if (sflag && (annot.subtyp != submatch)) continue;
/* if -s option was used, skip entries not of the selected type */
if (nvlp = anntonvl(&annot)) { /* process non-empty log annotation */
if (annot.time != tp) { /* new timestamp found */
if (tp != -1) /* complete the previous entry if any */
printf("\n
\n");
tp = annot.time; sp = 0;
/* Print the timestamp and define an anchor here. */
printf("%s", a++, timstr(-annot.time));
/* Print links to first, previous, next, last timestamps */
printf(" start prev"
" next end\n\n",
a-2, a);
}
if (annot.subtyp != sp) { /* new subtype found */
if (sp != 0) printf("\n"); /* complete previous if any */
sp = annot.subtyp;
/* Decode the subtype (source code) and print it. */
printf("- %s\n
\n", subtyptodesc(annot.subtyp));
}
else
printf("\n");
/* Print name/value pairs associated with this entry. */
for (i = 0, nvp = nvlp->par; i < nvlp->count; i++, nvp++) {
if (lflag)
lookup(nvp, annot.subtyp);
printf("%s: %s\n", nvp->name, nvp->value);
}
freenvlp(nvlp); /* release allocated memory for re-use */
}
}
/* Write the page footer. */
printf("
\n
End of record %s"
" start prev\n"
"\n", r, a-1);
cleanup();
exit(0);
}
char *prog_name(s)
char *s;
{
char *p = s + strlen(s);
#ifdef MSDOS
while (p >= s && *p != '\\' && *p != ':') {
if (*p == '.')
*p = '\0'; /* strip off extension */
if ('A' <= *p && *p <= 'Z')
*p += 'a' - 'A'; /* convert to lower case */
p--;
}
#else
while (p >= s && *p != '/')
p--;
#endif
return (p+1);
}
static char *help_strings[] = {
"usage: %s -r RECORD -a ANNOTATOR [OPTIONS ...]\n",
"where RECORD and ANNOTATOR specify the input, and OPTIONS may include:",
" -e leave ids in encoded form",
" -f TIME start at specified TIME",
" -h print this usage summary",
" -s SOURCE print annotations with specified SOURCE only",
" -t TIME stop at specified TIME",
NULL
};
void help()
{
int i;
(void)fprintf(stderr, help_strings[0], pname);
for (i = 1; help_strings[i] != NULL; i++)
(void)fprintf(stderr, "%s\n", help_strings[i]);
}