/* 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.
was directly followed by a close-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 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 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
click here for image.
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,
"Image %s"
"", filGetFilename(link), link);
text[len] = 0;
return text;
} /* makeHtmlImagePage */
/************************************************************/
/* reads the directory of helpDir and constructs an HTML-page
containing a
-list of all HTML-files in helpDir */
/************************************************************/
static char *makeHtmlIndex (STORE_HANDLE handle)
{
char *cp, *text, *s ;
int i, len ;
FilDir dirList;
if(!(dirList = filDirCreate(helpGetDir(), "html", "r")) )
{
messout ("Can't open help directory %s\n"
"(%s)",
helpGetDir(), messSysErrorText()) ;
return 0 ;
}
len = 0 ;
/* determine the length of the text to be returned */
len += 39+15+5+6 ; /* for header */
for (i = 0 ; i < filDirMax(dirList) ; i++)
{
s = filDirEntry(dirList, i) ;
len += strlen(s)*2 + strlen("html") + 19;
/* this is the length of each line as written
to the string by sprintf(cp,"- ...") below */
}
text = (char*)halloc ((len+1) * sizeof(char), handle) ;
cp = text ;
sprintf (cp,
"Index of Help Directory\n"
"
Index
\n"
"\n") ;
cp += 39+15+5 ;
for (i = 0 ; i < filDirMax(dirList) ; i++)
{
s = filDirEntry(dirList, i) ;
sprintf (cp, "- %s\n",
s, "html", s) ;
cp += strlen(s)*2 + strlen("html") + 19;
}
sprintf (cp, "
\n") ;
text[len] = 0 ;
messfree (dirList) ;
return text ;
} /* makeHtmlIndex */
/************************************************************/
/*************************************************************
***************** HTML Parsing package *********************
*** currently very crude parser, will fall over any bad ****
*** whether Mosaic, Netscape or MSIE can deal with or not. **
************************************************************/
static HtmlNode *parseHtmlText(char *text, STORE_HANDLE handle)
/* return root node of html parse-tree,
generated from the HTML source text */
{
char *cp = text;
HtmlNode *node;
if (!text) return 0;
/* start recursion */
parseSection (&cp, &node, handle) ;
return node; /* return root-node */
} /* parseHtmlText */
/************************************************************/
static void skipSpaces (char **cp)
{
while (**cp && isspace((int)**cp)) { ++(*cp) ; }
} /* skipSpaces */
/************************************************************/
static void replaceEscapeCodes (char *cp)
{
char *s ;
/*
quotation mark " --> " " --> "
ampersand & --> & & --> &
less-than sign < --> < < --> <
greater-than sign > --> > > --> >
*/
s = cp ;
while (*s)
{
if (strncasecmp (s, """, 5) == 0)
{
s[0] = '"' ; s[1] = 0 ;
strcat (s+1, s+5) ;
}
else if (strncasecmp (s, "&", 5) == 0)
{
s[0] = '&' ; s[1] = 0 ;
strcat (s+1, s+5) ;
}
else if (strncasecmp (s, "<", 5) == 0)
{
s[0] = '<' ; s[1] = 0 ;
strcat (s+1, s+5) ;
}
else if (strncasecmp (s, ">", 5) == 0)
{
s[0] = '>' ; s[1] = 0 ;
strcat (s+1, s+5) ;
}
else if (strncasecmp (s, """, 6) == 0)
{
s[0] = '"' ; s[1] = 0 ;
strcat (s+1, s+6) ;
}
else if (strncasecmp (s, "&", 5) == 0)
{
s[0] = '&' ; s[1] = 0 ;
strcat (s+1, s+5) ;
}
else if (strncasecmp (s, "<", 4) == 0)
{
s[0] = '<' ; s[1] = 0 ;
strcat (s+1, s+4) ;
}
else if (strncasecmp (s, ">", 4) == 0)
{
s[0] = '>' ; s[1] = 0 ;
strcat (s+1, s+4) ;
}
else if (strncasecmp (s, " ", 4) == 0)
{
s[0] = ' ' ; s[1] = 0 ;
strcat (s+1, s+6) ;
}
++s ;
}
return ;
} /* replaceEscapeCodes */
/************************************************************/
static HtmlNode *makeNode (HtmlNodeType type, STORE_HANDLE handle)
/* allocate a node and initialise the type */
{
HtmlNode *newnode ;
newnode = (HtmlNode*)halloc (sizeof(HtmlNode), handle) ;
newnode->type = type ;
return (newnode) ;
} /* makeNode */
/************************************************************/
static BOOL parseHtml (char **cp, HtmlNode **resultnode, STORE_HANDLE handle)
{
HtmlNode *node, *leftnode ;
*cp += 6 ; /* skip */
skipSpaces (cp) ;
node = makeNode (HTML_DOC, handle) ;
if (!(parseSection (cp, &leftnode, handle)))
{
fprintf (stderr, "Help warning : text inside not valid !!\n") ;
}
skipSpaces (cp) ;
if (strncasecmp (*cp, "", 7) == 0)
{
*cp += 7 ;
}
else
{
fprintf (stderr, "Help warning : tag not closed by !!\n") ;
}
node->left = leftnode ;
node->right = 0 ;
*resultnode = node ;
return TRUE ;
} /* parseHtml */
/************************************************************/
static BOOL parseHead (char **cp, HtmlNode **resultnode, STORE_HANDLE handle)
{
HtmlNode *node, *leftnode ;
*cp += 6 ; /* skip */
skipSpaces (cp) ;
node = makeNode (HTML_HEAD, handle) ;
if (!(parseSection (cp, &leftnode, handle)))
{
fprintf (stderr, "Help warning : HTML inside not valid !!\n") ;
}
skipSpaces (cp) ;
if (strncasecmp (*cp, "", 7) == 0)
{
*cp += 7 ;
}
else
{
fprintf (stderr, "Help warning : tag not closed by !!\n") ;
}
node->left = leftnode ;
node->right = 0 ;
*resultnode = node ;
return TRUE ;
} /* parseHead */
/************************************************************/
static BOOL parseBody (char **cp, HtmlNode **resultnode, STORE_HANDLE handle)
{
HtmlNode *node, *leftnode ;
*cp += 1 ; /* skip '>' */
skipSpaces (cp) ;
node = makeNode (HTML_BODY, handle) ;
if (!(parseSection (cp, &leftnode, handle)))
{
fprintf (stderr, "Help warning : HTML inside not valid !!\n") ;
}
skipSpaces (cp) ;
if (strncasecmp (*cp, "", 7) == 0)
{
*cp += 7 ;
}
else
{
fprintf (stderr, "Help warning : tag not closed by !!\n") ;
}
node->left = leftnode ;
node->right = 0 ;
*resultnode = node ;
return TRUE ;
} /* parseBody */
/************************************************************/
static BOOL parseComment (char **cp, HtmlNode **resultnode, STORE_HANDLE handle)
{
HtmlNode *node ;
int len ;
char *start ;
*cp += 4 ; /* skip