cdesktopenv/cde/programs/dtdocbook/instant/util.c

1279 lines
35 KiB
C

/*
* CDE - Common Desktop Environment
*
* Copyright (c) 1993-2012, The Open Group. All rights reserved.
*
* These libraries and programs are free software; you can
* redistribute them and/or modify them under the terms of the GNU
* Lesser General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* These libraries and programs are distributed in the hope that
* they 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 these libraries and programs; if not, write
* to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
* Floor, Boston, MA 02110-1301 USA
*/
/*
* Copyright 1993 Open Software Foundation, Inc., Cambridge, Massachusetts.
* All rights reserved.
*/
/*
* Copyright (c) 1994
* Open Software Foundation, Inc.
*
* Permission is hereby granted to use, copy, modify and freely distribute
* the software in this file and its documentation for any purpose without
* fee, provided that the above copyright notice appears in all copies and
* that both the copyright notice and this permission notice appear in
* supporting documentation. Further, provided that the name of Open
* Software Foundation, Inc. ("OSF") not be used in advertising or
* publicity pertaining to distribution of the software without prior
* written permission from OSF. OSF makes no representations about the
* suitability of this software for any purpose. It is provided "as is"
* without express or implied warranty.
*/
/* ________________________________________________________________________
*
* General utility functions for 'instant' program. These are used
* throughout the rest of the program.
*
* Entry points for this module:
* Split(s, &n, flags) split string into n tokens
* NewMap(slot_incr) create a new mapping structure
* FindMapping(map, name) find mapping by name; return mapping
* FindMappingVal(map, name) find mapping by name; return value
* SetMapping(map, s) set mapping based on string
* OpenFile(filename) open file, looking in inst path
* FindElementPath(elem, s) find path to element
* PrintLocation(ele, fp) print location of element in tree
* NearestOlderElem(elem, name) find prev elem up tree with name
* OutputString(s, fp, track_pos) output string
* AddElemName(name) add elem to list of known elements
* AddAttName(name) add att name to list of known atts
* FindAttByName(elem, name) find an elem's att by name
* FindContext(elem, lev, context) find context of elem
* QRelation(elem, name, rel_flag) find relation elem has to named elem
* DescendTree(elem, enter_f, leave_f, data_f, dp) descend doc tree,
* calling functions for each elem/node
* ________________________________________________________________________
*/
#ifndef lint
static char *RCSid =
"$TOG: util.c /main/13 1997/10/09 16:09:50 bill $";
#endif
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <memory.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#if !defined(CSRG_BASED)
#include <values.h>
#endif
#include "general.h"
/* forward references */
static char *LookupSDATA(char *);
static int CheckOutputBuffer(int length);
static OutputBuffer_t outputBuffer; /* init'd to all 0 by compiler */
/* ______________________________________________________________________ */
/* "Split" a string into tokens. Given a string that has space-separated
* (space/tab) tokens, return a pointer to an array of pointers to the
* tokens. Like what the shell does with *argv[]. The array can be is
* static or allocated. Space can be allocated for string, or allocated.
* Arguments:
* Pointer to string to pick apart.
* Pointer to max number of tokens to find; actual number found is
* returned. If 0 or null pointer, use a 'sane' maximum number (hard-
* code). If more tokens than the number specified, make last token be
* a single string composed of the rest of the tokens (includes spaces).
* Flag. Bit 0 says whether to make a copy of input string (since we'll
* clobber parts of it). To free the string, use the pointer to
* the first token returned by the function (or *ret_value).
* Bit 1 says whether to allocate the vector itself. If not, use
* (and return) a static vector.
* Return:
* Pointer to the provided string (for convenience of caller).
*/
char **
Split(
char *s, /* input string */
int *ntok, /* # of tokens desired (input)/found (return) */
int flag /* dup string? allocate a vector? */
)
{
int maxnt, i=0;
int n_alloc;
char **tokens;
static char *local_tokens[100];
/* Figure max number of tokens (maxnt) to find. 0 means find them all. */
if (ntok == NULL)
maxnt = 100;
else {
if (*ntok <= 0 || *ntok > 100) maxnt = 100; /* arbitrary size */
else maxnt = *ntok;
*ntok = 0;
}
if (!s) return 0; /* no string */
/* Point to 1st token (there may be initial space) */
while (*s && IsWhite(*s)) s++; /* skip initial space, if any */
if (*s == EOS) return 0; /* none found? */
/* See if caller wants us to copy the input string. */
if (flag & S_STRDUP) s = strdup(s);
/* See if caller wants us to allocate the returned vector. */
if (flag & S_ALVEC) {
n_alloc = 20;
Malloc(n_alloc, tokens, char *);
/* if caller did not specify max tokens to find, set to more than
* there will possibly ever be */
if (!ntok || !(*ntok)) maxnt = 10000;
}
else tokens = local_tokens;
i = 0; /* index into vector */
tokens[0] = s; /* s already points to 1st token */
while (i<maxnt) {
tokens[i] = s; /* point vector member at start of token */
i++;
/* If we allocated vector, see if we need more space. */
if ((flag & S_ALVEC) && i >= n_alloc) {
n_alloc += 20;
Realloc(n_alloc, tokens, char *);
}
if (i >= maxnt) break; /* is this the last one? */
while (*s && !IsWhite(*s)) s++; /* skip past end of token */
if (*s == EOS) break; /* at end of input string? */
if (*s) *s++ = EOS; /* terminate token string */
while (*s && IsWhite(*s)) s++; /* skip space - to next token */
}
if (ntok) *ntok = i; /* return number of tokens found */
tokens[i] = 0; /* null-terminate vector */
return tokens;
}
/* ______________________________________________________________________ */
/* Mapping routines. These are used for name-value pairs, like attributes,
* variables, and counters. A "Map" is an opaque data structure used
* internally by these routines. The caller gets one when creating a new
* map, then hands it to other routines that need it. A "Mapping" is a
* name/value pair. The user has access to this.
* Here's some sample usage:
*
* Map *V;
* V = NewMap(20);
* SetMappingNV(V, "home", "/users/bowe");
* printf("Home: %s\n", FindMappingVal(V, "home");
*/
/* Allocate new map structure. Only done once for each map/variable list.
* Arg:
* Number of initial slots to allocate space for. This is also the
* "chunk size" - how much to allocate when we use up the given space.
* Return:
* Pointer to the (opaque) map structure. (User passes this to other
* mapping routines.)
*/
Map_t *
NewMap(
int slot_increment
)
{
Map_t *M;
Calloc(1, M, Map_t);
if (!slot_increment) slot_increment = 1;
M->slot_incr = slot_increment;
return M;
}
/* Given pointer to a Map and a name, find the mapping.
* Arguments:
* Pointer to map structure (as returned by NewMap().
* Variable name.
* Return:
* Pointer to the matching mapping structure, or null if not found.
*/
Mapping_t *
FindMapping(
Map_t *M,
char *name
)
{
int i;
Mapping_t *m;
if (!M || M->n_used == 0) return NULL;
for (m=M->maps,i=0; i<M->n_used; i++)
if (m[i].name[0] == name[0] && !strcmp(m[i].name, name)) return &m[i];
return NULL;
}
/* Given pointer to a Map and a name, return string value of the mapping.
* Arguments:
* Pointer to map structure (as returned by NewMap().
* Variable name.
* Return:
* Pointer to the value (string), or null if not found.
*/
char *
FindMappingVal(
Map_t *M,
char *name
)
{
Mapping_t *m;
if (!M || M->n_used == 0) return NULL;
if ((m = FindMapping(M, name))) return m->sval;
return NULL;
}
/* Set a mapping/variable in Map M. Input string is a name-value pair where
* there is some amount of space after the name. The correct mapping is done.
* Arguments:
* Pointer to map structure (as returned by NewMap().
* Pointer to variable name (string).
* Pointer to variable value (string).
*/
void
SetMappingNV(
Map_t *M,
char *name,
char *value
)
{
FILE *pp;
char buf[LINESIZE], *cp, *s;
int i;
Mapping_t *m;
/* First, look to see if it's a "well-known" variable. */
if (!strcmp(name, "verbose")) { verbose = atoi(value); return; }
if (!strcmp(name, "warnings")) { warnings = atoi(value); return; }
if (!strcmp(name, "foldcase")) { fold_case = atoi(value); return; }
m = FindMapping(M, name); /* find existing mapping (if set) */
/* Need more slots for mapping structures? Allocate in clumps. */
if (M->n_used == 0) {
M->n_alloc = M->slot_incr;
Malloc(M->n_alloc, M->maps, Mapping_t);
}
else if (M->n_used >= M->n_alloc) {
M->n_alloc += M->slot_incr;
Realloc(M->n_alloc, M->maps, Mapping_t);
}
/* OK, we have a string mapping */
if (m) { /* exists - just replace value */
free(m->sval);
m->sval = strdup(value);
if (value) m->sval = strdup(value);
else m->sval = NULL;
}
else {
if (name) { /* just in case */
m = &M->maps[M->n_used];
M->n_used++;
m->name = strdup(name);
if (value) m->sval = strdup(value);
else m->sval = NULL;
}
}
if (value)
{
/* See if the value is a command to run. If so, run the command
* and replace the value with the output.
*/
s = value;
if (*s == '!') {
s++; /* point to command */
if ((pp = popen(s, "r"))) { /* run cmd, read its output */
i = 0;
cp = buf;
while (fgets(cp, LINESIZE-i, pp)) {
i += strlen(cp);
cp = &buf[i];
if (i >= LINESIZE) {
fprintf(stderr,
"Prog execution of variable '%s' too long.\n",
m->name);
break;
}
}
free(m->sval);
stripNL(buf);
m->sval = strdup(buf);
pclose(pp);
}
else {
sprintf(buf, "Could not start program '%s'", s);
perror(buf);
}
}
}
}
/* Separate name and value from input string, then pass to SetMappingNV.
* Arguments:
* Pointer to map structure (as returned by NewMap().
* Pointer to variable name and value (string), in form "name value".
*/
void
SetMapping(
Map_t *M,
char *s
)
{
char buf[LINESIZE];
char *name, *val;
if (!M) {
fprintf(stderr, "SetMapping: Map not initialized.\n");
return;
}
snprintf(buf, sizeof(buf), "%s", s);
name = val = buf;
while (*val && !IsWhite(*val)) val++; /* point past end of name */
if (*val) {
*val++ = EOS; /* terminate name */
while (*val && IsWhite(*val)) val++; /* point to value */
}
if (name) SetMappingNV(M, name, val);
}
/* ______________________________________________________________________ */
/* Opens a file for reading. If not found in current directory, try
* lib directories (from TPT_LIB env variable, or -l option).
* Arguments:
* Filename (string).
* Return:
* FILE pointer to open file, or null if it not found or can't open.
*/
FILE *
OpenFile(
char *filename
)
{
FILE *fp;
char buf[LINESIZE];
int i;
static char **libdirs;
static int nlibdirs = -1;
if ((fp=fopen(filename, "r"))) return fp;
if (*filename == '/') return NULL; /* full path specified? */
if (nlibdirs < 0) {
char *cp, *s;
if (tpt_lib) {
s = strdup(tpt_lib);
for (cp=s; *cp; cp++) if (*cp == ':') *cp = ' ';
nlibdirs = 0;
libdirs = Split(s, &nlibdirs, S_ALVEC);
}
else nlibdirs = 0;
}
for (i=0; i<nlibdirs; i++) {
sprintf(buf, "%s/%s", libdirs[i], filename);
if ((fp=fopen(buf, "r"))) return fp;
}
return NULL;
}
/* ______________________________________________________________________ */
/* This will find the path to an tag. The format is the:
* tag1(n1):tag2(n2):tag3
* where the tags are going down the tree and the numbers indicate which
* child (the first is numbered 1) the next tag is.
* Returns pointer to the string just written to (so you can use this
* function as a printf arg).
* Arguments:
* Pointer to element under consideration.
* String to write path into (provided by caller).
* Return:
* Pointer to the provided string (for convenience of caller).
*/
char *
FindElementPath(
Element_t *e,
char *s
)
{
Element_t *ep;
int i, e_path[MAX_DEPTH];
char *cp;
/* Move up the tree, noting "birth order" of each element encountered */
for (ep=e; ep->parent; ep=ep->parent)
e_path[ep->depth-1] = ep->my_eorder;
/* Move down the tree, printing the element names to the string. */
for (cp=s,i=0,ep=DocTree; i<e->depth; ep=ep->econt[e_path[i]],i++) {
sprintf(cp, "%s(%d) ", ep->gi, e_path[i]);
cp += strlen(cp);
}
sprintf(cp, "%s", e->gi);
return s;
}
/* ______________________________________________________________________ */
/* Print some location info about a tag. Helps user locate error.
* Messages are indented 2 spaces (convention for multi-line messages).
* Arguments:
* Pointer to element under consideration.
* FILE pointer of where to print.
*/
void
PrintLocation(
Element_t *e,
FILE *fp
)
{
char *s, buf[LINESIZE];
if (!e || !fp) return;
fprintf(fp, " Path: %s\n", FindElementPath(e, buf));
if ((s=NearestOlderElem(e, "TITLE")))
fprintf(fp, " Position hint: TITLE='%s'\n", s);
if (e->lineno) {
if (e->infile)
fprintf(fp, " At or near instance file: %s, line: %d\n",
e->infile, e->lineno);
else
fprintf(fp, " At or near instance line: %d\n", e->lineno);
}
if (e->id)
fprintf(fp, " ID: %s\n", e->id);
}
/* ______________________________________________________________________ */
/* Finds the data part of the nearest "older" tag (up the tree, and
* preceding) whose tag name matches the argument, or "TITLE", if null.
* Returns a pointer to the first chunk of character data.
* Arguments:
* Pointer to element under consideration.
* Name (GI) of element we'll return data from.
* Return:
* Pointer to that element's data content.
*/
char *
NearestOlderElem(
Element_t *e,
char *name
)
{
int i;
Element_t *ep;
if (!e) return 0;
if (!name) name = "TITLE"; /* useful default */
for (; e->parent; e=e->parent) /* move up tree */
for (i=0; i<=e->my_eorder; i++) { /* check preceding sibs */
ep = e->parent;
if (!strcmp(name, ep->econt[i]->gi))
return ep->econt[i]->ndcont ?
ep->econt[i]->dcont[0] : "-empty-";
}
return NULL;
}
/* ______________________________________________________________________ */
/* Expands escaped strings in the input buffer (things like tabs, newlines,
* octal characters - using C style escapes) if outputting the buffer to
* the specified fp. If fp is NULL, we're only preparing the output
* for the interpreter so don't expand escaped strings. The hat/anchor
* character forces that position to appear at the beginning of a line.
* The cursor position is kept track of (optionally) so that this can be
* done.
* Arguments:
* Pointer to element under consideration.
* FILE pointer of where to print.
* Flag saying whether or not to keep track of our position in the output
* stream. (We want to when writing to a file, but not for stderr.)
*/
void
OutputString(
char *s,
FILE *fp,
int track_pos
)
{
char c = 0, *sdata, *cp;
static int char_pos; /* remembers our character position */
static int interp_pos; /* like char_pos but when output is to interp */
int *ppos; /* points to appropriate line position var */
if (fp)
ppos = &char_pos; /* writing to file */
else
ppos = &interp_pos; /* buffer will be read by interpreter */
if (!s) s = "^"; /* no string - go to start of line */
for ( ; *s; s++) {
if (fp && (*s == '\\')) { /* don't expand for interpreter */
s++;
if (track_pos) (*ppos)++;
switch (*s) {
default: c = *s; break;
case 's': c = ' '; break;
case 't': c = TAB; break;
case 'n': c = NL; *ppos = 0; break;
case 'r': c = CR; *ppos = 0; break;
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
/* for octal numbers (C style) of the form \012 */
c = *s - '0';
for (s++; ((*s >= '0') && (*s <= '7')); s++)
c = (c << 3) + (*s - '0');
s--;
break;
case '|': /* SDATA */
s++; /* point past \| */
sdata = s;
/* find matching/closing \| */
cp = s;
while (*cp && *cp != '\\' && cp[1] != '|') cp++;
if (!*cp) break;
*cp = EOS; /* terminate sdata string */
cp++;
s = cp; /* s now points to | */
cp = LookupSDATA(sdata);
if (cp) OutputString(cp, fp, track_pos);
else {
/* not found - output sdata thing in brackets */
Putc('[', fp);
FPuts(sdata, fp);
Putc(']', fp);
}
c = 0;
break;
}
}
else { /* not escaped - just pass the character */
c = *s;
/* If caller wants us to track position, see if it's an anchor
* (ie, align at a newline). */
if (track_pos) {
if (c == ANCHOR) {
/* If we're already at the start of a line, don't do
* another newline. */
if (*ppos != 0) c = NL;
else c = 0;
}
else (*ppos)++;
if (c == NL) *ppos = 0;
}
else if (c == ANCHOR) c = NL;
}
if (c) Putc(c, fp);
}
}
/* ______________________________________________________________________ */
/* resets the output buffer
*/
void ClearOutputBuffer(void)
{
outputBuffer.current = outputBuffer.base;
}
/* ______________________________________________________________________ */
/* determines if there is already some text in the output buffer,
* returns 1 if so, else 0
*/
int OutputBufferActive(void)
{
return (outputBuffer.current != outputBuffer.base);
}
/* ______________________________________________________________________ */
/* terminates output buffer with a null char and returns the buffer
*/
char *GetOutputBuffer(void)
{
if (CheckOutputBuffer(1))
*(outputBuffer.current)++ = '\0';
return outputBuffer.base;
}
/* ______________________________________________________________________ */
/* insures that there's enough room in outputBuffer to hold a string
* of the given length.
* Arguments: the length of the string
*/
static int CheckOutputBuffer(
int length
)
{
char *oldBase;
int oldSize, incr = OBUF_INCR;
while (length > incr) incr += OBUF_INCR;
if ((outputBuffer.current - outputBuffer.base + length)
>
outputBuffer.size) {
oldBase = outputBuffer.base;
oldSize = outputBuffer.size;
outputBuffer.size += incr;
outputBuffer.base =
outputBuffer.base ?
realloc(outputBuffer.base, outputBuffer.size) :
malloc(outputBuffer.size);
if (outputBuffer.base == NULL) {
outputBuffer.base = oldBase;
outputBuffer.size = oldSize;
return 0;
}
outputBuffer.current =
outputBuffer.base + (outputBuffer.current - oldBase);
}
return 1;
}
/* ______________________________________________________________________ */
/* local version of fflush(3S)
*
* special cases a FILE of NULL to simply return success
*
*/
int FFlush(FILE *stream)
{
if (stream) return fflush(stream);
return 0;
}
/* ______________________________________________________________________ */
/* local version of putc(3S)
*
* special cases a FILE of NULL by working into a buffer for later
* use by the interpreter
*
* extra special hack: call Tcl interpreter with the character; worry
* about "stream" somo other time, we'll default to stdout
*/
int Putc(
int c,
FILE *stream
)
{
int result;
int j;
char *pc;
char *tcl_str;
Tcl_DString tcl_dstr;
Tcl_Encoding tcl_enc;
static int i = 0;
static char argBuf[8];
static char commandBuf[] = "OutputString \" ";
if (stream) {
argBuf[i++] = c;
mblen(NULL, 0);
if (mblen(argBuf, i) == -1) {
if (i < MB_CUR_MAX) {
return c;
}
else {
i = 0;
fprintf(stderr,
"An invalid multi-byte character was found in the input.\n");
return EOF;
}
}
pc = &(commandBuf[14]);
switch (c) { /* escape those things that throw off tcl */
case '{':
case '}':
case '"':
case '\'':
case '[':
case ']':
case '$':
case '\\':
*pc++ = '\\';
}
for (j = 0; j < i; ++j) *pc++ = argBuf[j];
i = 0;
*pc++ = '"';
*pc++ = 0;
tcl_enc = Tcl_GetEncoding(NULL, NULL);
tcl_str = Tcl_ExternalToUtfDString(tcl_enc, commandBuf, -1, &tcl_dstr);
result = Tcl_Eval(interpreter, tcl_str);
Tcl_DStringFree(&tcl_dstr);
if (result != TCL_OK) {
fprintf(stderr,
"interpreter error \"%s\" at line %d executing:\n",
Tcl_GetStringResult(interpreter),
Tcl_GetErrorLine(interpreter));
fprintf(stderr, "\"%s\"\n", commandBuf);
return EOF;
}
return c;
}
if ((CheckOutputBuffer(1)) == 0)
return EOF; /* out of space and can't grow the buffer */
*(outputBuffer.current)++ = (char) c;
return c;
}
/* ______________________________________________________________________ */
/* local version of fputs(3S)
*
* special cases a FILE of NULL by working into a buffer for later
* use by the interpreter
*/
int FPuts(
const char *s,
FILE *stream
)
{
static char commandBuf[128] = "OutputString \"";
char *pBuff,*pb;
const char *ps;
int sLength;
int result;
char *tcl_str;
Tcl_DString tcl_dstr;
Tcl_Encoding tcl_enc;
if ((sLength = strlen(s)) == 0)
return 0; /* no need to call CheckOutputBuffer() */
if (stream) {
if (sLength > 100/2) { /* assume that every char must be escaped */
pBuff = malloc(sLength + 14 + 1);
commandBuf[14] = 0;
strcpy(pBuff, commandBuf);
} else
pBuff = commandBuf;
ps = s;
pb = pBuff + 14;
do {
switch (*ps) { /* escape those things that throw off Tcl */
case '{':
case '}':
case '"':
case '\'':
case '[':
case ']':
case '\\':
*pb++ = '\\';
}
*pb++ = *ps++;
} while (*ps);
*pb++ = '"';
*pb = 0;
tcl_enc = Tcl_GetEncoding(NULL, NULL);
tcl_str = Tcl_ExternalToUtfDString(tcl_enc, pBuff, -1, &tcl_dstr);
result = Tcl_Eval(interpreter, tcl_str);
Tcl_DStringFree(&tcl_dstr);
if (result != TCL_OK) {
fprintf(stderr,
"interpreter error \"%s\" at line %d executing:\n",
Tcl_GetStringResult(interpreter),
Tcl_GetErrorLine(interpreter));
fprintf(stderr, "\"%s\"\n", pBuff);
if (pBuff != commandBuf) free(pBuff);
return EOF;
}
if (pBuff != commandBuf) free(pBuff);
return 0;
}
if ((CheckOutputBuffer(sLength)) == 0)
return EOF; /* out of space and can't grow the buffer */
strncpy(outputBuffer.current, s, sLength);
outputBuffer.current += sLength;
return sLength; /* arbitrary non-negative number */
}
/* ______________________________________________________________________ */
/* Figure out value of SDATA entity.
* We rememeber lookup hits in a "cache" (a shorter list), and look in
* cache before general list. Typically there will be LOTS of entries
* in the general list and only a handful in the hit list. Often, if an
* entity is used once, it'll be used again.
* Arguments:
* Pointer to SDATA entity token in ESIS.
* Return:
* Mapped value of the SDATA entity.
*/
static char *
LookupSDATA(
char *s
)
{
char *v;
static Map_t *Hits; /* remember lookup hits */
/* SDL SDL SDL SDL --- special (i.e., hack); see below */
/* we're going to replace the "123456" below with the SDATA */
/* 0123456789 012 */
static char spcString[] = "<SPC NAME=\"[123456]\">\0";
static char spc[sizeof(spcString)];
/* If we have a hit list, check it. */
if (Hits) {
if ((v = FindMappingVal(Hits, s))) return v;
}
v = FindMappingVal(SDATAmap, s);
/* If mapping found, remember it, then return it. */
if ((v = FindMappingVal(SDATAmap, s))) {
if (!Hits) Hits = NewMap(IMS_sdatacache);
SetMappingNV(Hits, s, v);
return v;
}
/* SDL SDL SDL SDL --- special (i.e., hack)
Special case sdata values of six letters surrounded by square
brackets. Just convert them over to the SDL <SPC> stuff
*/
if ((strlen(s) == 8) &&
(s[0] == '[') &&
(s[7] == ']')) {
if (strcmp(s, "[newlin]") == 0) {
return "&\n";
} else {
strcpy(spc, spcString);
strncpy(spc+12, s+1, 6);
return spc;
}
}
fprintf(stderr, "Error: Could not find SDATA substitution '%s'.\n", s);
return NULL;
}
/* ______________________________________________________________________ */
/* Add tag 'name' of length 'len' to list of tag names (if not there).
* This is a list of null-terminated strings so that we don't have to
* keep using the name length.
* Arguments:
* Pointer to element name (GI) to remember.
* Return:
* Pointer to the SAVED element name (GI).
*/
char *
AddElemName(
char *name
)
{
int i;
static int n_alloc=0; /* number of slots allocated so far */
/* See if it's already in the list. */
for (i=0; i<nUsedElem; i++)
if (UsedElem[i][0] == name[0] && !strcmp(UsedElem[i], name))
return UsedElem[i];
/* Allocate slots in blocks of N, so we don't have to call malloc
* so many times. */
if (n_alloc == 0) {
n_alloc = IMS_elemnames;
Calloc(n_alloc, UsedElem, char *);
}
else if (nUsedElem >= n_alloc) {
n_alloc += IMS_elemnames;
Realloc(n_alloc, UsedElem, char *);
}
UsedElem[nUsedElem] = strdup(name);
return UsedElem[nUsedElem++];
}
/* ______________________________________________________________________ */
/* Add attrib name to list of attrib names (if not there).
* This is a list of null-terminated strings so that we don't have to
* keep using the name length.
* Arguments:
* Pointer to attr name to remember.
* Return:
* Pointer to the SAVED attr name.
*/
char *
AddAttName(
char *name
)
{
int i;
static int n_alloc=0; /* number of slots allocated so far */
/* See if it's already in the list. */
for (i=0; i<nUsedAtt; i++)
if (UsedAtt[i][0] == name[0] && !strcmp(UsedAtt[i], name))
return UsedAtt[i];
/* Allocate slots in blocks of N, so we don't have to call malloc
* so many times. */
if (n_alloc == 0) {
n_alloc = IMS_attnames;
Calloc(n_alloc, UsedAtt, char *);
}
else if (nUsedAtt >= n_alloc) {
n_alloc += IMS_attnames;
Realloc(n_alloc, UsedAtt, char *);
}
UsedAtt[nUsedAtt] = strdup(name);
return UsedAtt[nUsedAtt++];
}
/* ______________________________________________________________________ */
/* Find an element's attribute value given element pointer and attr name.
* Typical use:
* a=FindAttByName("TYPE", t);
* do something with a->val;
* Arguments:
* Pointer to element under consideration.
* Pointer to attribute name.
* Return:
* Pointer to the value of the attribute.
*/
/*
Mapping_t *
FindAttByName(
Element_t *e,
char *name
)
{
int i;
if (!e) return NULL;
for (i=0; i<e->natts; i++)
if (e->atts[i].name[0] == name[0] && !strcmp(e->atts[i].name, name))
return &(e->atts[i]);
return NULL;
}
*/
char *
FindAttValByName(
Element_t *e,
char *name
)
{
int i;
if (!e) return NULL;
for (i=0; i<e->natts; i++)
if (e->atts[i].name[0] == name[0] && !strcmp(e->atts[i].name, name))
return e->atts[i].sval;
return NULL;
}
/* ______________________________________________________________________ */
/* Find context of a tag, 'levels' levels up the tree.
* Space for string is passed by caller.
* Arguments:
* Pointer to element under consideration.
* Number of levels to look up tree.
* String to write path into (provided by caller).
* Return:
* Pointer to the provided string (for convenience of caller).
*/
char *
FindContext(
Element_t *e,
int levels,
char *con
)
{
char *s;
Element_t *ep;
int i;
if (!e) return NULL;
s = con;
*s = EOS;
for (i=0,ep=e->parent; ep && levels; ep=ep->parent,i++,levels--) {
if (i != 0) *s++ = ' ';
strcpy(s, ep->gi);
s += strlen(s);
}
return con;
}
/* ______________________________________________________________________ */
/* Tests relationship (specified by argument/flag) between given element
* (structure pointer) and named element.
* Returns pointer to matching tag if found, null otherwise.
* Arguments:
* Pointer to element under consideration.
* Pointer to name of elem whose relationsip we are trying to determine.
* Relationship we are testing.
* Return:
* Pointer to the provided string (for convenience of caller).
*/
Element_t *
QRelation(
Element_t *e,
char *s,
Relation_t rel
)
{
int i;
Element_t *ep;
if (!e) return 0;
/* we'll call e the "given element" */
switch (rel)
{
case REL_Parent:
if (!e->parent || !e->parent->gi) return 0;
if (!strcmp(e->parent->gi, s)) return e->parent;
break;
case REL_Child:
for (i=0; i<e->necont; i++)
if (!strcmp(s, e->econt[i]->gi)) return e->econt[i];
break;
case REL_Ancestor:
if (!e->parent || !e->parent->gi) return 0;
for (ep=e->parent; ep; ep=ep->parent)
if (!strcmp(ep->gi, s)) return ep;
break;
case REL_Descendant:
if (e->necont == 0) return 0;
/* check immediate children first */
for (i=0; i<e->necont; i++)
if (!strcmp(s, e->econt[i]->gi)) return e->econt[i];
/* then children's children (recursively) */
for (i=0; i<e->necont; i++)
if ((ep=QRelation(e->econt[i], s, REL_Descendant)))
return ep;
break;
case REL_Sibling:
if (!e->parent) return 0;
ep = e->parent;
for (i=0; i<ep->necont; i++)
if (!strcmp(s, ep->econt[i]->gi) && i != e->my_eorder)
return ep->econt[i];
break;
case REL_Preceding:
if (!e->parent || e->my_eorder == 0) return 0;
ep = e->parent;
for (i=0; i<e->my_eorder; i++)
if (!strcmp(s, ep->econt[i]->gi)) return ep->econt[i];
break;
case REL_ImmPreceding:
if (!e->parent || e->my_eorder == 0) return 0;
ep = e->parent->econt[e->my_eorder-1];
if (!strcmp(s, ep->gi)) return ep;
break;
case REL_Following:
if (!e->parent || e->my_eorder == (e->parent->necont-1))
return 0; /* last? */
ep = e->parent;
for (i=(e->my_eorder+1); i<ep->necont; i++)
if (!strcmp(s, ep->econt[i]->gi)) return ep->econt[i];
break;
case REL_ImmFollowing:
if (!e->parent || e->my_eorder == (e->parent->necont-1))
return 0; /* last? */
ep = e->parent->econt[e->my_eorder+1];
if (!strcmp(s, ep->gi)) return ep;
break;
case REL_Cousin:
if (!e->parent) return 0;
/* Now, see if element's parent has that thing as a child. */
return QRelation(e->parent, s, REL_Child);
break;
case REL_Is1stContent:
/* first confirm that our parent is an "s" */
if (!(ep = QRelation(e, s, REL_Parent))) return 0;
/* then check that we are the first content in that parent */
if ((ep->cont->type == CMD_OPEN) && (ep->cont->ch.elem == e))
return ep;
break;
case REL_HasOnlyContent:
/* first confirm that we have a child of "s" */
if (!(ep = QRelation(e, s, REL_Child))) return 0;
/* then check that it is our only content */
if (e->ncont == 1) return 0;
break;
case REL_None:
case REL_Unknown:
fprintf(stderr, "You cannot query 'REL_None' or 'REL_Unknown'.\n");
break;
}
return NULL;
}
/* Given a relationship name (string), determine enum symbol for it.
* Arguments:
* Pointer to relationship name.
* Return:
* Relation_t enum.
*/
Relation_t
FindRelByName(
char *relname
)
{
if (!strcmp(relname, "?")) {
fprintf(stderr, "Supported query/relationships %s\n%s\n%s.\n",
"child, parent, ancestor, descendant,",
"sibling, sibling+, sibling+1, sibling-, sibling-1,",
"cousin, isfirstcon, hasonlycon");
return REL_None;
}
else if (StrEq(relname, "child")) return REL_Child;
else if (StrEq(relname, "parent")) return REL_Parent;
else if (StrEq(relname, "ancestor")) return REL_Ancestor;
else if (StrEq(relname, "descendant")) return REL_Descendant;
else if (StrEq(relname, "sibling")) return REL_Sibling;
else if (StrEq(relname, "sibling-")) return REL_Preceding;
else if (StrEq(relname, "sibling-1")) return REL_ImmPreceding;
else if (StrEq(relname, "sibling+")) return REL_Following;
else if (StrEq(relname, "sibling+1")) return REL_ImmFollowing;
else if (StrEq(relname, "cousin")) return REL_Cousin;
else if (StrEq(relname, "isfirstcon")) return REL_Is1stContent;
else if (StrEq(relname, "hasonlycon")) return REL_HasOnlyContent;
else fprintf(stderr, "Unknown relationship: %s\n", relname);
return REL_Unknown;
}
/* ______________________________________________________________________ */
/* This will descend the element tree in-order. (enter_f)() is called
* upon entering the node. Then all children (data and child elements)
* are operated on, calling either DescendTree() with a pointer to
* the child element or (data_f)() for each non-element child node.
* Before leaving the node (ascending), (leave_f)() is called. enter_f
* and leave_f are passed a pointer to this node and data_f is passed
* a pointer to the data/content (which includes the data itself and
* type information). dp is an opaque pointer to any data the caller
* wants to pass.
* Arguments:
* Pointer to element under consideration.
* Pointer to procedure to call when entering element.
* Pointer to procedure to call when leaving element.
* Pointer to procedure to call for each "chunk" of content data.
* Void data pointer, passed to the avobe 3 procedures.
*/
void
DescendTree(
Element_t *e,
void (*enter_f)(),
void (*leave_f)(),
void (*data_f)(),
void *dp
)
{
int i;
if (enter_f) (enter_f)(e, dp);
for (i=0; i<e->ncont; i++) {
if (e->cont[i].type == CMD_OPEN)
DescendTree(e->cont[i].ch.elem, enter_f, leave_f, data_f, dp);
else
if (data_f) (data_f)(&e->cont[i], dp);
}
if (leave_f) (leave_f)(e, dp);
}
/* ______________________________________________________________________ */
/* Add element, 'e', whose ID is 'idval', to a list of IDs.
* This makes it easier to find an element by ID later.
* Arguments:
* Pointer to element under consideration.
* Element's ID attribute value (a string).
*/
void
AddID(
Element_t *e,
char *idval
)
{
static ID_t *id_last;
if (!IDList) {
Malloc(1, id_last, ID_t);
IDList = id_last;
}
else {
Malloc(1, id_last->next, ID_t);
id_last = id_last->next;
}
id_last->elem = e;
id_last->id = idval;
}
/* ______________________________________________________________________ */
/* Return pointer to element who's ID is given.
* Arguments:
* Element's ID attribute value (a string).
* Return:
* Pointer to element whose ID matches.
*/
Element_t *
FindElemByID(
char *idval
)
{
ID_t *id;
for (id=IDList; id; id=id->next)
if (id->id[0] == idval[0] && !strcmp(id->id, idval)) return id->elem;
return 0;
}
/* ______________________________________________________________________ */