/* file: loghtml.c G. Moody 25 January 2009 Last revised: 7 June 2010 ------------------------------------------------------------------------------- loghtml: create an HTML file from a MIMIC II log (annotation file) Copyright (C) 2010 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/). _______________________________________________________________________________ */ #include #include #include #include #include /* 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) { char p[256], sr[7]; strncpy(sr, nvp->value, 6); sr[6] = '\0'; sprintf(p, "%s " "[view]", sr, nvp->value, nvp->value); SSTRCPY(nvp->value, p); } else if (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]); }