/* File: helpsubs.c * Author: Fred Wobus (fw@sanger.ac.uk) * Copyright (C) J Thierry-Mieg and R Durbin, 1998 * ------------------------------------------------------------------- * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ------------------------------------------------------------------- * This file is part of the ACEDB genome database package, written by * Richard Durbin (MRC LMB, UK) rd@sanger.ac.uk, and * Jean Thierry-Mieg (CRBM du CNRS, France) mieg@kaa.cnrs-mop.fr * * SCCS: %W% %G% * Description: controls the help system, provides HTML parsing * Exported functions: * HISTORY: * Last edited: Nov 23 18:22 1999 (fw) * * Oct 12 12:27 1998 (fw): checkSubject now case-insensitive * * Oct 8 17:23 1998 (fw): removed warning, in case that an open-list tag (e.g. ). The warning tried to enforce that every type of list only has a certain type of items. * * Oct 8 11:36 1998 (fw): helpSubjectGetFilename takes over logic from readHelpfile to locate the file containing the help for a particular subject * Created: Tue Aug 18 16:11:07 1998 (fw) *------------------------------------------------------------------- */ #include "help_.h" #include "aceio.h" /************************************************************/ static char *makeHtmlIndex (STORE_HANDLE handle); static char *makeHtmlImagePage (char *link, STORE_HANDLE handle); static HtmlNode *parseHtmlText (char *text, STORE_HANDLE handle); static BOOL parseSection (char **cp, HtmlNode **resultnode, STORE_HANDLE handle); /************************************************************/ /************ directory where help files are stored *********/ /* help files are found in this directory and in its * first level subdirectories (not recursivly yet) ************************************************************/ static char *helpDir = NULL; /************************************************************/ /* function to register the helpOnRoutine This can be called at any stage (before the first helpOn, or later on, it will affect the system next time helpOn is called. */ /************************************************************/ static struct helpContextStruct helpContext = { NULL, NULL }; struct helpContextStruct helpOnRegister (struct helpContextStruct context) /* call with func = 0x0 just to check whether anything has been registered yet */ { struct helpContextStruct old = helpContext; helpContext = context; return old; } /* helpOnRegister */ /************************************************************/ /* Sets the helpDir; */ /************************************************************/ char *helpSetDir (char *dirname) { if (!dirname) messcrash("helpSetDir() called with NULL pointer"); if (helpDir) messfree(helpDir); helpDir = filGetName (dirname,0,"rd", 0); return helpDir; } /* helpGetDir */ /************************************************************/ /* return the current helpDirectory or initialise if not previously set */ char *helpGetDir (void) { if (helpDir) return helpDir; else return ""; } /* helpGetDir */ /************************************************************/ /* pop up help on the given subject, depending on the registered display function, that will be textual, in the built-in simple HTML browser or even launch an external browser to display the help document */ /************************************************************/ BOOL helpOn (char *subject) { char *helpFilename; BOOL ok; if (!filCheckName(helpGetDir(), "", "rd")) { messout ("Sorry, No help available ! " "Could not open the HTML help directory " "%s\n" "(%s)", helpGetDir(), messSysErrorText()); return FALSE; } helpFilename = helpSubjectGetFilename(subject); /* may be NULL if file could not be found, the registered helpOnRoutine has to cope with this case and may decide to display an index instead */ if (helpContext.user_func) ok = (*helpContext.user_func)(helpFilename, helpContext.user_pointer); else { ACEOUT fo = aceOutCreateToStdout (0); ok = helpPrint (helpFilename, (void*)fo); /* textual help to stdout as default */ aceOutDestroy (fo); } return ok; } /* helpOn */ /************************************************************/ char *helpSubjectGetFilename (char *subject) /* this function attempts to find the file name corresponding to a particular help-subject. It will attempt to find a matching file according to the current settings of helpDir. the subject '?' will just return ? again. This is a special code within the help system to tell the help display function that the user required some kind of help. Usually the helpOnRegister'd function would display a dynamically created index of the help-directory. this function can be even cleverer by doing keyword searches on and <H1> strings in files that might be relevant of no obvious match is found. */ { static char filename_array[MAXPATHLEN] = ""; char *filename = &filename_array[0]; char *subject_copy; FilDir dirList; if (subject == NULL) return NULL; if (strlen(subject) == 0) return NULL; if (strcmp(subject, "?") == 0) { /* return ? to signal that the calling function needs to display a dynamically created index or show some kind of help. */ /* if the construct page = htmlPageCreate(helpGetFilename(subject_requested)); is used, the resulting page will therefor be a marked up directory listing of helpsubjects */ strcpy (filename, "?"); return filename; } subject_copy = strnew (subject, 0); strcpy (filename, ""); /* intialise, if this is non-empty at the end of the loop, we found a matching helpfile */ while (TRUE) { /* simple attempt to locate file - path/helpDir/subject.html */ sprintf(filename, "%s/%s.html", helpGetDir(), subject_copy); if (filCheckName(filename, 0, "r")) break; /* another attempt to file subject.shtml */ sprintf(filename, "%s/%s.shtml", helpGetDir(), subject_copy); if (filCheckName(filename, 0, "r")) break; /* advanced attempt, try to find a matching file from the list of available ones by scanning the directory contents of the helpdirectory */ if ((dirList = filDirCreate(helpGetDir(), "html", "r")) ) { int i; int matches; char *s; /* first look for an exact case-insensitive match */ strcpy (filename, ""); for (i = 0 ; i < filDirMax(dirList) ; i++) { s = filDirEntry(dirList, i); if (strcasecmp (s, subject_copy) == 0) { sprintf(filename, "%s%s%s.%s", helpGetDir(), SUBDIR_DELIMITER_STR, s, "html"); if (filCheckName(filename, 0, "r")) break; /* exit for-loop */ strcpy (filename, ""); } } if (strlen(filename) > 0) break; /* exit while(true) loop */ /* count the number of filenames starting with the given subject string */ matches = 0; for (i = 0 ; i < filDirMax(dirList) ; i++) { s = filDirEntry(dirList, i); if (strncasecmp (s, subject_copy, strlen(subject_copy)) == 0) { sprintf(filename, "%s%s%s.%s", helpGetDir(), SUBDIR_DELIMITER_STR, s, "html"); ++matches; } } if (matches == 0) { strcpy (filename, ""); /* not found */ } else if (matches == 1) { /* the one exact match (already in filename string) is the complete filename */ if (filCheckName(filename, 0, "r")) break; /* exit while(true) loop */ } else if (matches > 1) { /* construct a filename that we know won't work. But it may be used by the help display function to give a meaningful message to say that this subject is ambiguos. The returned filename is then considered a template, similar to 'ls subject*' so the help-display function may give a list of possible matching subjects. */ sprintf(filename, "%s%s%s", helpGetDir(), SUBDIR_DELIMITER_STR, subject_copy); break; } messfree (dirList); } /* endif dirList */ /* file didn't exist, whichever way we tried so far, so we try to chop off the last bit of the subject name. In case trySubject was "Tree_Clone_Inside", we now go through the look again with "Tree_Clone" and re-try. */ if (strchr (subject_copy, '_')) { int j; j = strlen (subject_copy); while (subject_copy[j--] != '_') ; /* find the last _ char */ subject_copy[j + 1] = '\0'; } else { /* If we run out of trailing components, then we exit * anyway. */ strcpy (filename, ""); break; /* exit while(true)loop */ } } /* end-while(true) */ messfree (subject_copy); if (strcmp(filename, "") != 0) return filename; /* success */ if ((strcasecmp(subject, "index") == 0) || (strcasecmp(subject, "home") == 0) || (strcasecmp(subject, "toc") == 0)) { /* we asked for some kind of index-page but couldn't find it, so we can always try to return the question mark '?' which will ask the calling function to display a dynamically created index of help-subjects. */ strcpy (filename, "?"); return filename; } return NULL; /* failure - no file found */ } /* helpSubjectGetFilename */ /************************************************************/ /* helpPackage utility to find out the filename of a given link reference. Absolute filenames are returned unchanged, but relative filenames are expanded to be the full path of the helpfile. Can be used for html/gif files referred to by the HREF of anchor tags or the SRC or IMG tags */ /* NOTE: the pointer returned is a static copy, which is re-used everytime it is called. If the calling function wants to mess about with the returned string, a copy has to be made. NULL is returned if the resulting file can't be opened. the calling function can inspect the result of messSysErrorText(), the report the resaon for failure */ /************************************************************/ char *helpLinkGetFilename (char *link) { static char link_path_array[MAXPATHLEN] = ""; char *link_path = &link_path_array[0]; if (link[0] == SUBDIR_DELIMITER) /* absolute path (UNIX) */ { strcpy (link_path, link); } else /* relative path */ { BOOL found = FALSE; FilDir dirs; strcpy (link_path, helpGetDir()); strcat (link_path, SUBDIR_DELIMITER_STR); strcat (link_path, link); if (!(filCheckName(link_path, "", "r"))) { int i = 0; dirs = filDirCreate(helpGetDir(), "", "rd"); while (!found && i < filDirMax(dirs)) { strcpy (link_path, helpGetDir()); strcat (link_path, SUBDIR_DELIMITER_STR); strcat (link_path, filDirEntry(dirs, i++)); strcat (link_path, SUBDIR_DELIMITER_STR); strcat (link_path, link); found = filCheckName(link_path, "", "r") ? TRUE : FALSE; } messfree (dirs); } } if (filCheckName(link_path, "", "r")) return link_path; return NULL; } /* helpLinkGetFilename */ /************************************************************/ /****************** ***********************/ /************** private helpPackage functions ***************/ /****************** ***********************/ /************************************************************/ HtmlPage *htmlPageCreate (char *helpFilename, STORE_HANDLE handle) /* complemeted by htmlPageDestroy */ { FILE *fil; HtmlPage *page = 0; if (!helpFilename) /* we could get a NULL filename */ return 0; /* here, which might come from helpSubjectGetFilename() that couldn't find a file matching the subject */ /* create a page with a marked up directory listing */ if (strcmp(helpFilename, "?") == 0) { page = halloc (sizeof(HtmlPage), handle); page->handle = handleHandleCreate(handle); page->htmlText = makeHtmlIndex(page->handle); if (!(page->root = parseHtmlText(page->htmlText, page->handle))) htmlPageDestroy(page); return page; } if (!(filCheckName(helpFilename, "", "r"))) return 0; /* prevent error caused by unsucsessful filopen */ /* create a page inlining the image */ if (strcasecmp (helpFilename + (strlen(helpFilename)-4), ".gif") == 0) { page = halloc (sizeof(HtmlPage), handle); page->handle = handleHandleCreate(handle); page->htmlText = makeHtmlImagePage(helpFilename, page->handle); if (!(page->root = parseHtmlText(page->htmlText, page->handle))) htmlPageDestroy(page); return page; } /* assume HTML page */ if ((fil = filopen(helpFilename, "", "r"))) { page = htmlPageCreateFromFile (fil, handle); filclose (fil); } return page; } /* htmlPageCreate */ /************************************************************/ HtmlPage *htmlPageCreateFromFile (FILE *fil, STORE_HANDLE handle) { HtmlPage *page; int fileSize; if (!fil) return (HtmlPage*)0; /* determine filesize */ rewind (fil); fseek (fil, 0, SEEK_END); fileSize = ftell (fil); rewind (fil); if (fileSize == 0) return (HtmlPage*)0; /* if we have a positive fileSize, we are pretty much guaranteed, that we'll get some HTML text and a parsetree */ page = halloc (sizeof(HtmlPage), handle); page->handle = handleHandleCreate(handle); /* grab the contents of the file */ page->htmlText = halloc ((fileSize + 1) * sizeof(char), page->handle); fread (page->htmlText, sizeof (char), fileSize, fil); page->htmlText[fileSize] = '\0'; /* add string terminator */ /* get parsetree */ page->root = parseHtmlText(page->htmlText, page->handle); return page; } /* htmlPageCreateFromFile */ /************************************************************/ void htmlPageDestroy (HtmlPage *page) /* only to be used if the page wasn't created on a handle */ { if (!page) return; /* clear all memory used during parsing of the page */ handleDestroy (page->handle); /* clear the memory taken up by the structure itself */ messfree (page); return; } /* htmlPageDestroy */ /************************************************************/ void htmlStripSpaces (char *cp) /* utility to get rid of multiple spaces from a string */ /* we use it on node->text, where the text isn't within <PRE> tags */ { char *s ; int i ; /* strip unwanted white spaces from the text */ for (i = 0; i < strlen(cp); ++i) if (isspace ((int)cp[i])) cp[i] = ' ' ; while ((s = strstr (cp, " "))) { s[1] = 0 ; strcat (cp, s+2) ; } if (cp[strlen(cp)-1] == ' ') cp[strlen(cp)-1] = '\0' ; return ; } /* htmlStripSpaces */ /************************************************************/ /****************** ***********************/ /****************** static functions ***********************/ /****************** ***********************/ /************************************************************/ /************************************************************/ /* as the helpviewer supports inlined images, it is easy to display image, even when they're not inlined as in <A HREF=image.gif>click here for image</A>. We just return a container page, that inlines the image */ /************************************************************/ static char *makeHtmlImagePage (char *link, STORE_HANDLE handle) { char *text; int len; len = 0; len = 7+6+strlen(filGetFilename(link))+8+10+strlen(link)+2; text = halloc((len+1)*sizeof(char), handle); sprintf (text, "<TITLE>Image %s" "", filGetFilename(link), link); text[len] = 0; return text; } /* makeHtmlImagePage */ /************************************************************/ /* reads the directory of helpDir and constructs an HTML-page containing a