ftp.nice.ch/pub/next/unix/network/www/lynx.2.7.2.NIHS.bs.tar.gz#/lynx.2.7.2.NIHS.bs/src/WWW/Library/Implementation/HTAnchor.c

This is HTAnchor.c in view mode; [Download] [Up]

/*	Hypertext "Anchor" Object				HTAnchor.c
**	==========================
**
** An anchor represents a region of a hypertext document which is linked to
** another anchor in the same or a different document.
**
** History
**
**         Nov 1990  Written in Objective-C for the NeXT browser (TBL)
**	24-Oct-1991 (JFG), written in C, browser-independant 
**	21-Nov-1991 (JFG), first complete version
**
**	(c) Copyright CERN 1991 - See Copyright.html
*/

#define HASH_SIZE 101		/* Arbitrary prime. Memory/speed tradeoff */

#include "HTUtils.h"
#include "tcp.h"
#include <ctype.h>
#include "HTAnchor.h"
#include "HTParse.h"
#include "UCAux.h"
#include "UCMap.h"

#include "LYLeaks.h"

#define FREE(x) if (x) {free(x); x = NULL;}

extern LYUCcharset LYCharSet_UC[];

#ifdef NOT_DEFINED
/*
 *	This is the hashing function used to determine which list in the
 *		adult_table a parent anchor should be put in.  This is a
 *		much simpler function than the original used.
 */
#define HASH_FUNCTION(cp_address) ((unsigned short int)strlen(cp_address) *\
	(unsigned short int)TOUPPER(*cp_address) % HASH_SIZE)
#endif /* NOT_DEFINED */
/*
 *	This is the original function.  We'll use it again. - FM
 */ 
PRIVATE int HASH_FUNCTION ARGS1(
	CONST char *,	cp_address)
{
    int hash;
    unsigned char *p;

    for (p = (unsigned char *)cp_address, hash = 0; *p; p++)
    	hash = (int) (hash * 3 + (*(unsigned char *)p)) % HASH_SIZE;

    return hash;
}

typedef struct _HyperDoc Hyperdoc;
#ifdef VMS
struct _HyperDoc {
	int junk;	/* VMS cannot handle pointers to undefined structs */
};
#endif /* VMS */

PRIVATE HTList **adult_table = 0;  /* Point to table of lists of all parents */

/*				Creation Methods
**				================
**
**	Do not use "new" by itself outside this module. In order to enforce
**	consistency, we insist that you furnish more information about the
**	anchor you are creating : use newWithParent or newWithAddress.
*/
PRIVATE HTParentAnchor * HTParentAnchor_new NOARGS
{
    HTParentAnchor *newAnchor = 
       (HTParentAnchor *)calloc(1, sizeof(HTParentAnchor));  /* zero-filled */
    newAnchor->parent = newAnchor;
    newAnchor->bookmark = NULL;		/* Bookmark filename. - FM */
    newAnchor->isISMAPScript = FALSE;	/* Lynx appends ?0,0 if TRUE. - FM */
    newAnchor->isHEAD = FALSE;		/* HEAD request if TRUE. - FM */
    newAnchor->safe = FALSE;		/* Safe. - FM */
    newAnchor->FileCache = NULL;	/* Path to a disk-cached copy. - FM */
    newAnchor->SugFname = NULL;		/* Suggested filename. - FM */
    newAnchor->RevTitle = NULL;		/* TITLE for a LINK with REV. - FM */
    newAnchor->cache_control = NULL;	/* Cache-Control. - FM */
    newAnchor->no_cache = FALSE;	/* no-cache? - FM */
    newAnchor->content_type = NULL;	/* Content-Type. - FM */
    newAnchor->content_language = NULL;	/* Content-Language. - FM */
    newAnchor->content_encoding = NULL;	/* Compression algorith. - FM */
    newAnchor->content_base = NULL;	/* Content-Base. - FM */
    newAnchor->content_disposition = NULL; /* Content-Disposition. - FM */
    newAnchor->content_location = NULL;	/* Content-Location. - FM */
    newAnchor->content_md5 = NULL;	/* Content-MD5. - FM */
    newAnchor->content_length = 0;	/* Content-Length. - FM */
    newAnchor->date = NULL;		/* Date. - FM */
    newAnchor->expires = NULL;		/* Expires. - FM */
    newAnchor->last_modified = NULL;	/* Last-Modified. - FM */
    newAnchor->server = NULL;		/* Server. - FM */
    return newAnchor;
}

PRIVATE HTChildAnchor * HTChildAnchor_new NOARGS
{
    return (HTChildAnchor *)calloc(1, sizeof(HTChildAnchor)); /* zero-filled */
}


#ifdef CASE_INSENSITIVE_ANCHORS
/*	Case insensitive string comparison
**	----------------------------------
** On entry,
**	s	Points to one string, null terminated
**	t	points to the other.
** On exit,
**	returns	YES if the strings are equivalent ignoring case
**		NO if they differ in more than  their case.
*/
PRIVATE BOOL HTEquivalent ARGS2(
	CONST char *,	s,
	CONST char *,	t)
{
    if (s && t) {  /* Make sure they point to something */
        for (; *s && *t; s++, t++) {
            if (TOUPPER(*s) != TOUPPER(*t)) {
	        return NO;
	    }
        }
        return TOUPPER(*s) == TOUPPER(*t);
    } else {
        return s == t;  /* Two NULLs are equivalent, aren't they ? */
    }
}

#else

/*	Case sensitive string comparison
**	----------------------------------
** On entry,
**	s	Points to one string, null terminated
**	t	points to the other.
** On exit,
**	returns	YES if the strings are identical or both NULL
**		NO if they differ.
*/
PRIVATE BOOL HTIdentical ARGS2(
	CONST char *,	s,
	CONST char *,	t)
{
    if (s && t) {  /* Make sure they point to something */
        for (; *s && *t; s++, t++) {
            if (*s != *t) {
	        return NO;
	    }
        }
        return *s == *t;
    } else {
        return s == t;  /* Two NULLs are identical, aren't they ? */
    }
}
#endif /* CASE_INSENSITIVE_ANCHORS */


/*	Create new or find old sub-anchor
**	---------------------------------
**
**	Me one is for a new anchor being edited into an existing
**	document. The parent anchor must already exist.
*/
PUBLIC HTChildAnchor * HTAnchor_findChild ARGS2(
	HTParentAnchor *,	parent,
	CONST char *,		tag)
{
    HTChildAnchor *child;
    HTList *kids;

    if (!parent) {
        if (TRACE)
	    fprintf(stderr, "HTAnchor_findChild called with NULL parent.\n");
        return NULL;
    }
    if ((kids = parent->children) != 0) {
	/*
	**  Parent has children.  Search them.
	*/
        if (tag && *tag) {		/* TBL */
	    while (NULL != (child=(HTChildAnchor *)HTList_nextObject(kids))) {
#ifdef CASE_INSENSITIVE_ANCHORS
	        if (HTEquivalent(child->tag, tag)) { /* Case insensitive */
#else
	        if (HTIdentical(child->tag, tag)) {  /* Case sensitive - FM */
#endif /* CASE_INSENSITIVE_ANCHORS */
		    if (TRACE)
		        fprintf(stderr,
	      "Child anchor %p of parent %p with name `%s' already exists.\n",
		    		(void *)child, (void *)parent, tag);
		    return child;
	        }
	    }
        }  /*  end if tag is void */
    } else {  /* parent doesn't have any children yet : create family */
        parent->children = HTList_new();
    }

    child = HTChildAnchor_new();
    if (TRACE)
        fprintf(stderr,
		"new Anchor %p named `%s' is child of %p\n",
       		(void *)child, 
		tag ? tag : (CONST char *)"",
		(void *)parent); /* int for apollo */
    HTList_addObject (parent->children, child);
    child->parent = parent;
    StrAllocCopy(child->tag, tag);
    return child;
}


/*	Create or find a child anchor with a possible link
**	--------------------------------------------------
**
**	Create new anchor with a given parent and possibly
**	a name, and possibly a link to a _relatively_ named anchor.
**	(Code originally in ParseHTML.h)
*/
PUBLIC HTChildAnchor * HTAnchor_findChildAndLink ARGS4(
	HTParentAnchor *,	parent,	/* May not be 0   */
	CONST char *,		tag,	/* May be "" or 0 */
	CONST char *,		href,	/* May be "" or 0 */
	HTLinkType *,		ltype)	/* May be 0	  */
{
    HTChildAnchor * child = HTAnchor_findChild(parent, tag);

    if (TRACE)
        fprintf(stderr,"Entered HTAnchor_findChildAndLink\n");

    if (href && *href) {
        char *relative_to = HTAnchor_address((HTAnchor *)parent);
        DocAddress parsed_doc;
        HTAnchor * dest;

        parsed_doc.address = HTParse(href, relative_to, PARSE_ALL);
        parsed_doc.post_data = NULL;
        parsed_doc.post_content_type = NULL;
        parsed_doc.bookmark = NULL;
        parsed_doc.isHEAD = FALSE;
        parsed_doc.safe = FALSE;
        dest = HTAnchor_findAddress(&parsed_doc);

        HTAnchor_link((HTAnchor *)child, dest, ltype);
        FREE(parsed_doc.address);
        FREE(relative_to);
    }
    return child;
}

/*
**  Function for freeing the adult hash table. - FM
*/
PRIVATE void free_adult_table NOARGS
{
    int i_counter;
    HTList * HTAp_freeme;
    HTParentAnchor * parent;
    /*
     *  Loop through all lists.
     */
    for (i_counter = 0; i_counter < HASH_SIZE; i_counter++) {
	/*
	**  Loop through the list.
	*/
	while (adult_table[i_counter] != NULL) {
	    /*
	    **  Free off items - FM
	    */
	    HTAp_freeme = adult_table[i_counter];
	    adult_table[i_counter] = HTAp_freeme->next;
	    if (HTAp_freeme->object) {
		parent = (HTParentAnchor *)HTAp_freeme->object;
		HTAnchor_delete(parent);
	    }
	    FREE(HTAp_freeme);
	}
    }
    FREE(adult_table);
}

/*	Create new or find old named anchor
**	-----------------------------------
**
**	Me one is for a reference which is found in a document, and might
**	not be already loaded.
**	Note: You are not guaranteed a new anchor -- you might get an old one,
**	like with fonts.
*/
PUBLIC HTAnchor * HTAnchor_findAddress ARGS1(
	CONST DocAddress *,	newdoc)
{
    /* Anchor tag specified ? */
    char *tag = HTParse(newdoc->address, "", PARSE_ANCHOR);

    if (TRACE)
	fprintf(stderr,"Entered HTAnchor_findAddress\n");

    /*
    **  If the address represents a sub-anchor, we recursively load its
    **  parent, then we create a child anchor within that document.
    */
    if (*tag) {
        DocAddress parsed_doc;
        HTParentAnchor * foundParent;
        HTChildAnchor * foundAnchor;

        parsed_doc.address = HTParse(newdoc->address, "", 
		PARSE_ACCESS | PARSE_HOST | PARSE_PATH | PARSE_PUNCTUATION);
        parsed_doc.post_data = newdoc->post_data;
        parsed_doc.post_content_type = newdoc->post_content_type;
        parsed_doc.bookmark = newdoc->bookmark;
        parsed_doc.isHEAD = newdoc->isHEAD;
        parsed_doc.safe = newdoc->safe;

        foundParent = (HTParentAnchor *)HTAnchor_findAddress(&parsed_doc);
        foundAnchor = HTAnchor_findChild (foundParent, tag);
        FREE(parsed_doc.address);
        FREE(tag);
        return (HTAnchor *)foundAnchor;
    } else {
        /*
	**  If the address has no anchor tag, 
	**  check whether we have this node.
	*/
        int hash;
        HTList * adults;
        HTList *grownups;
        HTParentAnchor * foundAnchor;

        FREE(tag);

        /*
	**  Select list from hash table,
	*/
        hash = HASH_FUNCTION(newdoc->address);
        if (!adult_table) {
            adult_table = (HTList **)calloc(HASH_SIZE, sizeof(HTList *));
	    atexit(free_adult_table);
        }
        if (!adult_table[hash])
            adult_table[hash] = HTList_new();
        adults = adult_table[hash];

        /*
	**  Search list for anchor.
	*/
        grownups = adults;
        while (NULL != (foundAnchor =
			(HTParentAnchor *)HTList_nextObject(grownups))) {
#ifdef CASE_INSENSITIVE_ANCHORS
            if (HTEquivalent(foundAnchor->address, newdoc->address) &&
	        HTEquivalent(foundAnchor->post_data, newdoc->post_data) &&
	        foundAnchor->isHEAD == newdoc->isHEAD)
#else
            if (HTIdentical(foundAnchor->address, newdoc->address) &&
	        HTIdentical(foundAnchor->post_data, newdoc->post_data) &&
	        foundAnchor->isHEAD == newdoc->isHEAD)
#endif /* CASE_INSENSITIVE_ANCHORS */
	    {
	        if (TRACE)
		    fprintf(stderr,
		    	    "Anchor %p with address `%s' already exists.\n",
			    (void *)foundAnchor, newdoc->address);
	         return (HTAnchor *)foundAnchor;
             }
        }
    
        /*
	**  Node not found: create new anchor.
	*/
        foundAnchor = HTParentAnchor_new();
        if (TRACE)
	    fprintf(stderr,
	    	    "New anchor %p has hash %d and address `%s'\n",
    	    	    (void *)foundAnchor, hash, newdoc->address);
        StrAllocCopy(foundAnchor->address, newdoc->address);
        if (newdoc->post_data)
	    StrAllocCopy(foundAnchor->post_data, newdoc->post_data);
        if (newdoc->post_content_type)
	    StrAllocCopy(foundAnchor->post_content_type,
	    		 newdoc->post_content_type);
        if (newdoc->bookmark)
	    StrAllocCopy(foundAnchor->bookmark, newdoc->bookmark);
        foundAnchor->isHEAD = newdoc->isHEAD;
        foundAnchor->safe = newdoc->safe;
        HTList_addObject (adults, foundAnchor);
        return (HTAnchor *)foundAnchor;
    }
}


/*	Delete an anchor and possibly related things (auto garbage collection)
**	--------------------------------------------
**
**	The anchor is only deleted if the corresponding document is not loaded.
**	All outgoing links from parent and children are deleted, and this anchor
**	is removed from the sources list of all its targets.
**	We also try to delete the targets whose documents are not loaded.
**	If this anchor's source list is empty, we delete it and its children.
*/
PRIVATE void deleteLinks ARGS1(
	HTAnchor *,	me)
{
    /*
     *  Memory leaks fixed.
     *  05-27-94 Lynx 2-3-1 Garrett Arch Blythe
     */

    /*
     *  Anchor is NULL, do nothing.
     */
    if (!me) {
	return;
    }

    /*
     *  Unregister me with our mainLink destination anchor's parent.
     */
    if (me->mainLink.dest) {
	HTParentAnchor *parent = me->mainLink.dest->parent;

	/*
	 *  Remove me from the parent's sources so that the
	 *  parent knows one less anchor is it's dest.
	 */
	if (!HTList_isEmpty(parent->sources)) {
	    /*
	     *  Really should only need to deregister once.
	     */
	    HTList_removeObject(parent->sources, (void *)me);
	}

	/*
	 *  Test here to avoid calling overhead.
	 *  If the parent has no loaded document, then we should
	 *  tell it to attempt to delete itself.
	 *  Don't do this jass if the anchor passed in is the same
	 *  as the anchor to delete.
	 *  Also, don't do this if the destination parent is our
	 *  parent.
	 */
	if (!parent->document &&
	    parent != (HTParentAnchor *)me &&
	    me->parent != parent) {
	    HTAnchor_delete(parent);
	}

	/*
	 *  At this point, we haven't a mainLink.  Set it to be
	 *  so.
	 *  Leave the HTAtom pointed to by type up to other code to
	 *  handle (reusable, near static).
	 */
	me->mainLink.dest = NULL;
	me->mainLink.type = NULL;
    }

    /*
     *  Check for extra destinations in our links list.
     */
    if (!HTList_isEmpty(me->links)) {
	HTLink *target;
	HTParentAnchor *parent;

	/*
	 *  Take out our extra non mainLinks one by one, calling
	 *  their parents to know that they are no longer
	 *  the destination of me's anchor.
	 */
	while ((target = (HTLink *)HTList_removeLastObject(me->links)) != 0) {
	    parent = target->dest->parent;
	    if (!HTList_isEmpty(parent->sources)) {
	        /*
	         *  Only need to tell destination parent
	         *  anchor once.
	         */
	        HTList_removeObject(parent->sources, (void *)me);
	    }

	    /*
	     *  Avoid calling overhead.
	     *  If the parent hasn't a loaded document, then
	     *     we will attempt to have the parent
	     *     delete itself.
	     *  Don't call twice if this is the same anchor
	     *     that we are trying to delete.
	     *  Also, don't do this if we are trying to delete
	     *     our parent.
	     */
	    if (!parent->document &&
	        (HTParentAnchor *)me != parent &&
	        me->parent != parent) {
	        HTAnchor_delete(parent);
	    }
	    /*
	     *  The link structure has to be deleted, too!
	     *  That was missing, but this code probably never
	     *  got exercised by Lynx.  - KW
	     */
	    FREE(target);
        }

        /*
         *  At this point, me no longer has any destination in
         *  the links list.  Get rid of it.
         */
        if (me->links) {
	    HTList_delete(me->links);
	    me->links = NULL;
        }
    }

    /*
     *  Catch in case links list exists but nothing in it.
     */
    if (me->links) {
	HTList_delete(me->links);
	me->links = NULL;
    }
}

PUBLIC BOOL HTAnchor_delete ARGS1(
	HTParentAnchor *,	me)
{
    /*
     *  Memory leaks fixed.
     * 05-27-94 Lynx 2-3-1 Garrett Arch Blythe
     */
    HTList *cur;
    HTChildAnchor *child;

    /*
     *  Do nothing if nothing to do.
     */
    if (!me) {
	return(NO);
    }

    /*
     *  Don't delete if document is loaded or being loaded.
     */
    if (me->document || me->underway) {
	return(NO);
    }

    /*
     *  Recursively try to delete destination anchors of this parent.
     *  In any event, this will tell all destination anchors that we
     *  no longer consider them a destination.
     */
    deleteLinks((HTAnchor *)me);

    /*
     *  There are still incoming links to this one (we are the
     *  destination of another anchor).
     *  Don't actually delete this anchor, but children are OK to
     *  delete their links.
     */
    if (!HTList_isEmpty(me->sources)) {
        /*
	 *  Delete all outgoing links from children, do not
	 *  delete the children, though.
	 */
	if (!HTList_isEmpty(me->children)) {
	    cur = me->children; 
	    while ((child = (HTChildAnchor *)HTList_nextObject(cur)) != 0) {
		if (child != NULL) {
		    deleteLinks((HTAnchor *)child);
		}
	    }
	}

	/*
	 *  Can't delete parent, still have sources.
	 */
	return(NO);
    }

    /*
     *  No more incoming links : kill everything
     *  First, recursively delete children and their links.
     */
    if (!HTList_isEmpty(me->children)) {
	while ((child = (HTChildAnchor *)HTList_removeLastObject(
							me->children)) != 0) {
	    if (child) {
		deleteLinks((HTAnchor *)child);
		if (child->tag) {
		    FREE(child->tag);
		}
		FREE(child);
	    }
	}
    }

    /*
     *  Delete our empty list of children.
     */
    if (me->children) {
	HTList_delete(me->children);
	me->children = NULL;
    }

    /*
     * Delete our empty list of sources.
     */
    if (me->sources) {
	HTList_delete(me->sources);
	me->sources = NULL;
    }

    /*
     *  Delete the methods list.
     */
    if (me->methods) {
	/*
	 *  Leave what the methods point to up in memory for
	 *  other code (near static stuff).
	 */
	HTList_delete(me->methods);
	me->methods = NULL;
    }

    /*
     *  Free up all allocated members.
     */
    FREE(me->charset);
    FREE(me->isIndexAction);
    FREE(me->isIndexPrompt);
    FREE(me->title);
    FREE(me->physical);
    FREE(me->post_data);
    FREE(me->post_content_type);
    FREE(me->bookmark);
    FREE(me->owner);
    FREE(me->RevTitle);
    if (me->FileCache) {
	FILE *fd;
	if ((fd = fopen(me->FileCache, "r")) != NULL) {
	    fclose(fd);
	    remove(me->FileCache);
	}
	FREE(me->FileCache);
    }
    FREE(me->SugFname);
    FREE(me->cache_control);
    FREE(me->content_type);
    FREE(me->content_language);
    FREE(me->content_encoding);
    FREE(me->content_base);
    FREE(me->content_disposition);
    FREE(me->content_location);
    FREE(me->content_md5);
    FREE(me->date);
    FREE(me->expires);
    FREE(me->last_modified);
    FREE(me->server);
    FREE(me->UCStages);
 
    /*
     *  Remove ourselves from the hash table's list.
     */
    if (adult_table) {
	unsigned short int usi_hash = HASH_FUNCTION(me->address);

	if (adult_table[usi_hash])  {
	    HTList_removeObject(adult_table[usi_hash], (void *)me);
	}
    }

    /*
     *  Original code wanted a way to clean out the HTFormat if no
     *  longer needed (ref count?).  I'll leave it alone since
     *  those HTAtom objects are a little harder to know where
     *  they are being referenced all at one time. (near static)
     */

    /*
     *  Free the address.
     */
    FREE(me->address);

    /*
     *  Finally, kill the parent anchor passed in.
     */
    FREE(me);

    return(YES);
}


/*		Move an anchor to the head of the list of its siblings
**		------------------------------------------------------
**
**	This is to ensure that an anchor which might have already existed
**	is put in the correct order as we load the document.
*/
PUBLIC void HTAnchor_makeLastChild ARGS1(
	HTChildAnchor *,	me)
{
    if (me->parent != (HTParentAnchor *)me) {  /* Make sure it's a child */
        HTList * siblings = me->parent->children;
	HTList_removeObject (siblings, me);
	HTList_addObject (siblings, me);
    }
}

/*	Data access functions
**	---------------------
*/
PUBLIC HTParentAnchor * HTAnchor_parent ARGS1(
	HTAnchor *,	me)
{
    return me ? me->parent : NULL;
}

PUBLIC void HTAnchor_setDocument ARGS2(
	HTParentAnchor *,	me,
	HyperDoc *,		doc)
{
    if (me)
        me->document = doc;
}

PUBLIC HyperDoc * HTAnchor_document ARGS1(
	HTParentAnchor *,	me)
{
    return me ? me->document : NULL;
}


/*  We don't want code to change an address after anchor creation... yet ?
PUBLIC void HTAnchor_setAddress ARGS2(
	HTAnchor *,	me,
	char *,		addr)
{
    if (me)
        StrAllocCopy (me->parent->address, addr);
}
*/

PUBLIC char * HTAnchor_address ARGS1(
	HTAnchor *,	me)
{
    char *addr = NULL;

    if (me) {
        if (((HTParentAnchor *)me == me->parent) ||
    	    !((HTChildAnchor *)me)->tag) {  /* it's an adult or no tag */
            StrAllocCopy(addr, me->parent->address);
        } else {  /* it's a named child */
            addr = malloc(2 +
	    		  strlen(me->parent->address) +
			  strlen(((HTChildAnchor *)me)->tag));
            if (addr == NULL)
	        outofmem(__FILE__, "HTAnchor_address");
            sprintf(addr, "%s#%s",
	    		   me->parent->address, ((HTChildAnchor *)me)->tag);
        }
    }
    return addr;
}

PUBLIC void HTAnchor_setFormat ARGS2(
	HTParentAnchor *,	me,
	HTFormat,		form)
{
    if (me)
        me->format = form;
}

PUBLIC HTFormat HTAnchor_format ARGS1(
	HTParentAnchor *,	me)
{
    return me ? me->format : NULL;
}

PUBLIC void HTAnchor_setIndex ARGS2(
	HTParentAnchor *,	me,
	char *,			address)
{
    if (me) {
        me->isIndex = YES;
	StrAllocCopy(me->isIndexAction, address);
    }
}

PUBLIC void HTAnchor_setPrompt ARGS2(
	HTParentAnchor *,	me,
	char *,			prompt)
{
    if (me) {
        StrAllocCopy(me->isIndexPrompt, prompt);
    }
}

PUBLIC BOOL HTAnchor_isIndex ARGS1(
	HTParentAnchor *,	me)
{
    return me ? me->isIndex : NO;
}

/*	Whether Anchor has been designated as an ISMAP link
**	(normally by presence of an ISMAP attribute on A or IMG) - KW
*/
PUBLIC BOOL HTAnchor_isISMAPScript ARGS1(
	HTAnchor *,	me)
{
    return me ? me->parent->isISMAPScript : NO;
}

PUBLIC BOOL HTAnchor_hasChildren ARGS1(
	HTParentAnchor *,	me)
{
    return me ? ! HTList_isEmpty(me->children) : NO;
}

/*	Title handling.
*/
PUBLIC CONST char * HTAnchor_title ARGS1(
	HTParentAnchor *,	me)
{
    return me ? me->title : NULL;
}

PUBLIC void HTAnchor_setTitle ARGS2(
	HTParentAnchor *,	me,
	CONST char *,		title)
{
    int i;

    if (me) {
        StrAllocCopy(me->title, title);
        for (i = 0; me->title[i]; i++) {
            if ((unsigned char)me->title[i] == 1 ||
	        (unsigned char)me->title[i] == 2) {
	        me->title[i] = ' ';
	    }
        }
    }
}

PUBLIC void HTAnchor_appendTitle ARGS2(
	HTParentAnchor *,	me,
	CONST char *,		title)
{
    int i;

    if (me) {
        StrAllocCat(me->title, title);
        for (i = 0; me->title[i]; i++) {
            if ((unsigned char)me->title[i] == 1 ||
	        (unsigned char)me->title[i] == 2) {
	        me->title[i] = ' ';
	    }
        }
    }
}

/*	Bookmark handling.
*/
PUBLIC CONST char * HTAnchor_bookmark ARGS1(
	HTParentAnchor *,	me)
{
    return me ? me->bookmark : NULL;
}

PUBLIC void HTAnchor_setBookmark ARGS2(
	HTParentAnchor *,	me,
	CONST char *,		bookmark)
{
    if (me)
        StrAllocCopy(me->bookmark, bookmark);
}

/*	Owner handling.
*/
PUBLIC CONST char * HTAnchor_owner ARGS1(
	HTParentAnchor *,	me)
{
    return (me ? me->owner : NULL);
}

PUBLIC void HTAnchor_setOwner ARGS2(
	HTParentAnchor *,	me,
	CONST char *,		owner)
{
    if (me) {
        StrAllocCopy(me->owner, owner);
    }
}

/*      TITLE handling in LINKs with REV="made" or REV="owner". - FM
*/
PUBLIC CONST char * HTAnchor_RevTitle ARGS1(
	HTParentAnchor *,	me)
{
    return (me ? me->RevTitle : NULL);
}

PUBLIC void HTAnchor_setRevTitle ARGS2(
	HTParentAnchor *,	me,
	CONST char *,		title)
{
    int i;

    if (me) {
        StrAllocCopy(me->RevTitle, title);
        for (i = 0; me->RevTitle[i]; i++) {
            if ((unsigned char)me->RevTitle[i] == 1 ||
	        (unsigned char)me->RevTitle[i] == 2) {
	        me->RevTitle[i] = ' ';
	    }
        }
    }
}

/*	Suggested filename handling. - FM
**	(will be loaded if we had a Content-Disposition
**	 header or META element with filename=name.suffix)
*/
PUBLIC CONST char * HTAnchor_SugFname ARGS1(
	HTParentAnchor *,	me)
{
    return me ? me->SugFname : NULL;
}

/*	Content-Encoding handling. - FM
**	(will be loaded if we had a Content-Encoding
**	 header.)
*/
PUBLIC CONST char * HTAnchor_content_encoding ARGS1(
	HTParentAnchor *,	me)
{
    return me ? me->content_encoding : NULL;
}

/*	Content-Type handling. - FM
*/
PUBLIC CONST char * HTAnchor_content_type ARGS1(
	HTParentAnchor *,	me)
{
    return me ? me->content_type : NULL;
}

/*	Last-Modified header handling. - FM
*/
PUBLIC CONST char * HTAnchor_last_modified ARGS1(
	HTParentAnchor *,	me)
{
    return me ? me->last_modified : NULL;
}

/*	Date header handling. - FM
*/
PUBLIC CONST char * HTAnchor_date ARGS1(
	HTParentAnchor *,	me)
{
    return me ? me->date : NULL;
}

/*	Server header handling. - FM
*/
PUBLIC CONST char * HTAnchor_server ARGS1(
	HTParentAnchor *,	me)
{
    return me ? me->server : NULL;
}

/*	Safe header handling. - FM
*/
PUBLIC BOOL HTAnchor_safe ARGS1(
	HTParentAnchor *,	me)
{
    return me ? me->safe : FALSE;
}

/*	Content-Base header handling. - FM
*/
PUBLIC CONST char * HTAnchor_content_base ARGS1(
	HTParentAnchor *,	me)
{
    return me ? me->content_base : NULL;
}

/*	Content-Location header handling. - FM
*/
PUBLIC CONST char * HTAnchor_content_location ARGS1(
	HTParentAnchor *,	me)
{
    return me ? me->content_location : NULL;
}

/*	Link me Anchor to another given one
**	-------------------------------------
*/
PUBLIC BOOL HTAnchor_link ARGS3(
	HTAnchor *,	source,
	HTAnchor *,	destination,
	HTLinkType *,	type)
{
    if (!(source && destination))
        return NO;  /* Can't link to/from non-existing anchor */
    if (TRACE)
        fprintf(stderr,
  		"Linking anchor %p to anchor %p\n", source, destination);
    if (!source->mainLink.dest) {
        source->mainLink.dest = destination;
        source->mainLink.type = type;
    } else {
        HTLink * newLink = (HTLink *)calloc (1, sizeof (HTLink));
        if (newLink == NULL)
	    outofmem(__FILE__, "HTAnchor_link");
        newLink->dest = destination;
        newLink->type = type;
        if (!source->links)
            source->links = HTList_new();
        HTList_addObject (source->links, newLink);
    }
    if (!destination->parent->sources)
        destination->parent->sources = HTList_new();
    HTList_addObject (destination->parent->sources, source);
    return YES;  /* Success */
}


/*	Manipulation of links
**	---------------------
*/
PUBLIC HTAnchor * HTAnchor_followMainLink ARGS1(
	HTAnchor *,	me)
{
    return me->mainLink.dest;
}

PUBLIC HTAnchor * HTAnchor_followTypedLink ARGS2(
	HTAnchor *,	me,
	HTLinkType *,	type)
{
    if (me->mainLink.type == type)
        return me->mainLink.dest;
    if (me->links) {
        HTList *links = me->links;
        HTLink *link;
        while (NULL != (link=(HTLink *)HTList_nextObject(links))) {
            if (link->type == type) {
	        return link->dest;
	    }
	}
    }
    return NULL;  /* No link of me type */
}


/*	Make main link
*/
PUBLIC BOOL HTAnchor_makeMainLink ARGS2(
	HTAnchor *,	me,
	HTLink *,	movingLink)
{
    /* Check that everything's OK */
    if (!(me && HTList_removeObject (me->links, movingLink))) {
        return NO;  /* link not found or NULL anchor */
    } else {
        /* First push current main link onto top of links list */
        HTLink *newLink = (HTLink *)calloc (1, sizeof (HTLink));
        if (newLink == NULL)
	    outofmem(__FILE__, "HTAnchor_makeMainLink");
        memcpy((void *)newLink,
	       (CONST char *)&me->mainLink, sizeof (HTLink));
        HTList_addObject (me->links, newLink);

        /* Now make movingLink the new main link, and free it */
        memcpy((void *)&me->mainLink,
	       (CONST void *)movingLink, sizeof (HTLink));
        FREE(movingLink);
        return YES;
    }
}


/*	Methods List
**	------------
*/
PUBLIC HTList * HTAnchor_methods ARGS1(
	HTParentAnchor *,	me)
{
    if (!me->methods) {
        me->methods = HTList_new();
    }
    return me->methods;
}

/*	Protocol
**	--------
*/
PUBLIC void * HTAnchor_protocol ARGS1(
	HTParentAnchor *,	me)
{
    return me->protocol;
}

PUBLIC void HTAnchor_setProtocol ARGS2(
	HTParentAnchor *,	me,
	void*,			protocol)
{
    me->protocol = protocol;
}

/*	Physical Address
**	----------------
*/
PUBLIC char * HTAnchor_physical ARGS1(
	HTParentAnchor *,	me)
{
    return me->physical;
}

PUBLIC void HTAnchor_setPhysical ARGS2(
	HTParentAnchor *,	me,
	char *,			physical)
{
    if (me) {
        StrAllocCopy(me->physical, physical);
    }
}

/*
**  We store charset info in the HTParentAnchor object, for several
**  "stages".  (See UCDefs.h)
**  A stream method is supposed to know what stage in the model it is.
** 
**  General model       MIME     ->  parser  ->  structured  ->  HText
**  e.g., text/html
**      from HTTP:      HTMIME.c ->  SGML.c  ->  HTML.c      ->  GridText.c
**     text/plain  
**      from file:      HTFile.c ->  HTPlain.c               ->  GridText.c
**
**  The lock/set_by is used to lock e.g. a charset set by an explicit
**  HTTP MIME header against overriding by a HTML META tag - the MIME 
**  header has higher priority.  Defaults (from -assume_.. options etc.) 
**  will not override charset explicitly given by server.
**
**  Some advantages of keeping this in the HTAnchor:
**  - Global variables are bad.
**  - Can remember a charset given by META tag when toggling to SOURCE view.
**  - Can remember a charset given by <A CHARSET=...> href in another doc.
**
**  We don't modify the HTParentAnchor's charset element
**  here, that one will only be set when explicitly given.
*/
PUBLIC LYUCcharset * HTAnchor_getUCInfoStage ARGS2(
	HTParentAnchor *,	me,
	int,			which_stage)
{
    if (me && !me->UCStages) {
	int i;
	int chndl = UCLYhndl_for_unspec;
	UCAnchorInfo * stages = (UCAnchorInfo*)calloc(1,
						      sizeof(UCAnchorInfo));
	if (stages == NULL)
            outofmem(__FILE__, "HTAnchor_getUCInfoStage");
	for (i = 0; i < UCT_STAGEMAX; i++) {
	    stages->s[i].C.MIMEname = "";
	    stages->s[i].LYhndl = -1;
	}
	if (me->charset) {
	    chndl = UCGetLYhndl_byMIME(me->charset);
	    if (chndl < 0) {
		chndl = UCLYhndl_for_unrec;
	    }
	}
	if (chndl >= 0) {
	    memcpy(&stages->s[UCT_STAGE_MIME].C, &LYCharSet_UC[chndl],
		   sizeof(LYUCcharset));
	    stages->s[UCT_STAGE_MIME].lock = UCT_SETBY_DEFAULT;
	} else {
	    /*
	     *  Should not happen...
	     */
	    stages->s[UCT_STAGE_MIME].C.UChndl = -1;
	    stages->s[UCT_STAGE_MIME].lock = UCT_SETBY_NONE;
	}
	stages->s[UCT_STAGE_MIME].LYhndl = chndl;
	me->UCStages = stages;
    }
    if (me) {
	return &me->UCStages->s[which_stage].C;
    }
    return NULL;
}

PUBLIC int HTAnchor_getUCLYhndl ARGS2(
	HTParentAnchor *,	me,
	int, 			which_stage)
{
    if (me) {
	if (!me->UCStages) {
	    /*
	     *  This will allocate and initialize, if not yet done.
	     */
	    (void) HTAnchor_getUCInfoStage(me, which_stage);
	}
	if (me->UCStages->s[which_stage].lock > UCT_SETBY_NONE) {
	    return me->UCStages->s[which_stage].LYhndl;
	}
    }
    return -1;
}

PUBLIC LYUCcharset * HTAnchor_setUCInfoStage ARGS4(
	HTParentAnchor *,	me,
	int,			LYhndl,
	int,			which_stage,
	int,			set_by)
{
    if (me) {
	/*
	 *  This will allocate and initialize, if not yet done.
	 */
	LYUCcharset * p = HTAnchor_getUCInfoStage(me, which_stage);
	/*
	 *  Can we override?
	 */
	if (set_by >= me->UCStages->s[which_stage].lock) {
	    me->UCStages->s[which_stage].lock = set_by;
	    me->UCStages->s[which_stage].LYhndl = LYhndl;
	    if (LYhndl >= 0) {
		memcpy(p, &LYCharSet_UC[LYhndl], sizeof(LYUCcharset));
	    }
	    else {
		p->UChndl = -1;
	    }
	    return p;
	}
    }
    return NULL;
}

PUBLIC LYUCcharset * HTAnchor_resetUCInfoStage ARGS4(
	HTParentAnchor *,	me,
	int,			LYhndl,
	int,			which_stage,
	int,			set_by)
{
    if (!me || !me->UCStages)
	return NULL;
    me->UCStages->s[which_stage].lock = set_by;
    me->UCStages->s[which_stage].LYhndl = LYhndl;
    return &me->UCStages->s[which_stage].C;
}

/*
**  A set_by of (-1) means use the lock value from the from_stage.
*/
PUBLIC LYUCcharset * HTAnchor_copyUCInfoStage ARGS4(
	HTParentAnchor *,	me,
	int,			to_stage,
	int,			from_stage,
	int,			set_by)
{
    if (me) {
	/*
	 *  This will allocate and initialize, if not yet done.
	 */
	LYUCcharset * p_from = HTAnchor_getUCInfoStage(me, from_stage);
	LYUCcharset * p_to = HTAnchor_getUCInfoStage(me, to_stage);
	/*
	 *  Can we override?
	 */
	if (set_by == -1)
	    set_by = me->UCStages->s[from_stage].lock;
	if (set_by == UCT_SETBY_NONE)
	    set_by = UCT_SETBY_DEFAULT;
	if (set_by >= me->UCStages->s[to_stage].lock) {
	    me->UCStages->s[to_stage].lock = set_by;
	    me->UCStages->s[to_stage].LYhndl =
		me->UCStages->s[from_stage].LYhndl;
	    if (p_to != p_from)
		memcpy(p_to, p_from, sizeof(LYUCcharset));
	    return p_to;
	}
    }
    return NULL;
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.