resolveLinks.c   [plain text]


/*
 * Tool name:  resolveLinks
 * Synopsis:   Creates links between HTML pages using specially
 *             formed anchors.
 *
 * Last Updated: $Date: 2011/08/16 16:54:00 $
 *
 * Copyright (c) 2002-2011 Apple Computer, Inc.  All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 *
 */
/*!
    @header
        Resolves links between HTML fies generated by <code>headerdoc2html</code> and
        <code>gatherheaderdoc</code>.
    @indexgroup HeaderDoc Tools
 */

#include <inttypes.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <dirent.h>
#include <sys/param.h>
#include <libxml/HTMLparser.h>
#include <libxml/HTMLtree.h>
#include <libxml/tree.h>
#include <libxml/parserInternals.h>
#include <sys/types.h>
#include <fcntl.h>
#include <dirent.h>
#include <libgen.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>

// #define ENABLE_CORE_DUMPS

#ifdef ENABLE_CORE_DUMPS
#include <sys/resource.h>
#endif


#ifdef USE_STRCOMPAT
#include "strcompat.h"
#endif

#ifndef C99
    #define FMT_SIZE_T "%zu"
#else
    #define FMT_SIZE_T "%lu"
#endif

// #define DEPTH_TYPE int64_t
// #define DEPTH_TYPE_PRINTF "%lld"

/*! @parseOnly */
#define DEPTH_TYPE int

#define DEPTH_TYPE_PRINTF "%d"

#define PART_REF 1
#define PART_LANG 2
#define PART_REST 4
#define PART_ALL 7


// #define TEST_DELAY

#include <stdarg.h>

int safe_asprintf(char **ret, const char *format, ...) __attribute__((format (printf, 2, 3)));


/****************************************************************
 *                      Tunable Parameters                      *
 ****************************************************************/

#undef DEBUG_NAMEFROMAPPLEREF
#undef NEAR_MATCH_DEBUG // 1 2


// #define NTHREADS 8
#define MAXTHREADS 16
#define ERRS stderr
// #define MAX(a, b) ((a<b) ? b : a)
#define MAXSEEDFILES 1024
#define MAXEXTREFS 1024

/* undef or def */
#undef NOAVL

/* undef, 1, or 2 */
#undef TESTING_AVL

#ifdef TESTING_AVL
int testing_avl = 1;
#else
int testing_avl = 0;
#endif

/*!
    @abstract
	Workaround for a bug in <code>realpath</code>.
    @discussion
	In Mac OS X v10.6 and earlier, there is a bug
	in realpath such that it can return a static
	(non-malloc) chunk of memory if you pass
	a path of "/" with a NULL buffer.  (Oops.)

	This bug is specific to Mac OS X v10.6 and
	earlier, but the workaround is harmless enough
	that it isn't work special casing it.
 */
char *realpath_workaround(char *path, char *buffer)
{
    if (!buffer) {
	if (!strcmp(path, "/")) {
		// fprintf(stderr, "Averted another crash.\n");
		return strdup(path);
	}
    }

    return realpath(path, buffer);
}

#define realpath(a, b) realpath_workaround(a, b)



/****************************************************************
 *                       Data  structures                       *
 ****************************************************************/

/*! @abstract
	The parts of an API reference (UID).
    @field refpart
	The reference type (<code>apple_ref</code>, for example).
    @field langpart
	The language portion of the UID.
    @field rest
	The rest of the UID.
 */
typedef struct refparts {
    char *refpart;
    char *langpart;
    char *rest;
} *refparts_t;

#define RP_CACHE

/*!
    @abstract
	A node in a tree of nodes that each describe information
	about a possible link destination.
    @param basepath
	The seed file base path (specified by the <code>-S</code> 
        flag) associated with the file in question, or an empty string.
    @param filename
	The relative file path (either as passed on the command line or
	as read from the seed file).
    @param fullpath
	A cache for the {@link nodepath} function.
    @param xref
	The API reference marker(s) string.
    @param title
	The title attribute as parsed from the HTML.
    @param fromseed
	Contains <code>1</code> if the API reference marker was read from a seed file,
	or <code>0</code> if it was not.
    @param force_absolute
	Contains <code>1</code> if the <code>-a</code> flag was passed for the seed
	file in question (or prior to the first seed file), else <code>0</code>.
    @param left
	The left subtree of the tree of cross references (sorted by the
	<code>xref</code> field).
    @param right
	The right subtree of the tree of cross references (sorted by the
	<code>xref</code> field).
    @param dup
	A chain of additional cross-reference nodes with matching
	values for <code>xref</code>.  Used only when disambiguating
	duplicate link destinations.
 */
typedef struct _xrefnode {
    char *basepath;
    char *filename;
#ifdef RP_CACHE
    char *filename_rp;
#endif
    char *fullpath; //
    char *xref;
    char *title;
    int fromseed : 1;
    int force_absolute : 1;
    DEPTH_TYPE maxleftdepth, maxrightdepth;
    struct _xrefnode *left, *right, *dup, *parent;
} *xrefnode_t;

/*!
    @abstract
	A node in a tree of pointers to {@link xrefnode_t} nodes sorted by
	the name of the API symbol.
    @discussion
	Used for looking up certain machine-generated or
	ambiguously described symbols as a last resort.

	The {@link namehead} variable contains the root of this tree.
    @param name
	The name of the symbol described by <code>node</code>.
    @param node
	A node in the {@link nodehead} tree.
    @param left
	The left subtree.
    @param right
	The right subtree.
 */
typedef struct _xrefbyname
{
    char *name;
    xrefnode_t node;
    DEPTH_TYPE maxleftdepth, maxrightdepth;
    struct _xrefbyname *left, *right, *dup, *parent;
} *xrefbyname_t;

/*!
    @abstract
	A node in a list that cantains pointers to HTML parse tree nodes
	that matched a specific pattern.
    @discussion
	Used by the {@link nodelist} function.
    @param node
	The HTML parse tree node pointer.
    @param next
	The next node in the list.
    @param prev
	The previous node in the list.
 */
struct nodelistitem {
    xmlNode *node;
    struct nodelistitem *next;
    struct nodelistitem *prev;
};

/*!
    @abstract
	A node in a list that cantains pointers to HTML parse tree nodes
	that matched a specific pattern.
    @discussion
	Used by the {@link nodelist} function.
 */
typedef struct fileref {
    char name[MAXPATHLEN];
    struct fileref *next;
    struct fileref *threadnext;
} *fileref_t;

/*!
    @abstract
	An object returned by {@link searchref}.
    @param uri
	The URI for the best matching destination.
    @param obj
	The tree object that matched.  Used to gain access to the
	<code>fromseed</code> and <code>force_absolute</code>
	fields.
 */
typedef struct searchobj {
    char *uri;
    xrefnode_t obj;
} *searchobj_t;


/****************************************************************
 *                          Prototypes                          *
 ****************************************************************/

void checkDoc(xmlNode *node, xmlNode *parent, xmlNode *prev, htmlDocPtr dp);
int ispartialmatch(char *partial_ref, char *complete_ref);
struct nodelistitem *nodelist(char *name, xmlNode * root);
void openlogfile();
void closelogfile();
int isURI(char *filename);
int insertName(xrefbyname_t node, xrefbyname_t tree);
char *xmlNodeGetRawString(htmlDocPtr dp, xmlNode * node, int whatever);
xmlNode *xmlNodeForComment(xmlChar *commentguts, htmlDocPtr dp);
char *resolve(char *xref, char *filename, int *retarget, char **frametgt);
static void *resolve_main(void *ref);
void setup_redirection(void);
char *nameFromAppleRef(char *ref);
xrefnode_t refByName(char *name, char *basepath);
void redirect_stderr_to_null(void);
void restore_stderr(void);
void writeXRefFile(char *filename, char *indir);
int readXRefFile(char *filename, char *basepath, int force_absolute);
void writeFile(xmlNode * node, htmlDocPtr dp, char *filename);
void gatherXRefs(xmlNode * node, htmlDocPtr dp, char *filename);
void resolveLinks(xmlNode * node, htmlDocPtr dp, char *filename, char *filename_rp);
char *proptext(char *name, struct _xmlAttr *prop);
void parseUsage(xmlNode * node);
int isCommentedAnchor(char *commentString);
void addAttribute(xmlNode * node, char *attname, char *attstring);
char *propString(xmlNode * node);
fileref_t getFiles(char *curPath);
void print_statistics(void);
int resolve_mainsub(int pos);
int countfiles(fileref_t rethead);
int onSameLevel(xmlNode * a, xmlNode * b);
int tailcompare(char *string, char *tail);
void writeFile_sub(xmlNode * node, htmlDocPtr dp, FILE * fp,
		   int this_node_and_children_only);
void addXRefSub(xrefnode_t newnode, xrefnode_t tree);
char *relpath(char *origPath, char *filename, int isDir);
char *fixpath(char *name);
int has_target(xmlNode * node);
char *ts_basename(char *path);
char *ts_dirname(char *path);
int ishdindex(char *filename);
void printusage();
int printNodeRange(xmlNode * start, xmlNode * end);
int test_xrefnode_t_tree();
int test_xrefbyname_t_tree();

void *db_malloc(size_t length);
void db_free(void *ptr);
//void *malloc_long(size_t length) { return malloc(length * 30); }
//#define malloc malloc_long
// #define malloc db_malloc
// #define free db_free
void searchfree(searchobj_t obj);
void rebalance_xrefnode_t_tree(xrefnode_t node, xrefnode_t fromnode);
void rebalance_xrefbyname_t_tree(xrefbyname_t node, xrefbyname_t fromnode);
DEPTH_TYPE verify_xrefnode_t_tree(xrefnode_t node);
DEPTH_TYPE verify_xrefbyname_t_tree(xrefbyname_t node);
void free_xrefnode_t_tree(xrefnode_t node);
void free_xrefbyname_t_tree(xrefbyname_t node);
char *nodefilenamerealpath(xrefnode_t node);

#ifdef ENABLE_CORE_DUMPS
static int EnableCoreDumps(void);
#endif


/****************************************************************
 *                            Globals                           *
 ****************************************************************/

/*!
    @abstract
	Disables emission of percent success/failure.
    @discussion
	To prevent bogus test failures caused by differences in
	floating point representation, this flag disables
	that bit of the output.

	In the future, this flag may be overloaded for other
	purposes; its sole purpose is to tell resolveLinks that
	it is being run from within the test framework.
 */
int nopercent = 0;

/*!
    @abstract
	The top of the tree of cross reference nodes sorted by
	the API reference symbol marker.
 */
xrefnode_t nodehead = NULL;

/*!
    @abstract
	The top of the tree of cross reference nodes sorted by
	the name of the symbol.
 */
xrefbyname_t namehead = NULL;

/*!
    @abstract
	Storage for the <code>-d</code> flag.
 */
int debugging = 0;

/*!
    @abstract
	Broken out storage for the file debug bit from the <code>-d</code> flag.
 */
int filedebug = 0;

/*!
    @abstract
	Broken out storage for the write debug bit from the <code>-d</code> flag.
 */
int writedebug = 0;

/*!
    @abstract
	Broken out storage for the relpath debug bit from the <code>-d</code> flag.
    @discussion
	Used for debugging bugs in the code that calculates relative paths
	between two absolute paths.
 */
int debug_relpath = 0;

/*!
    @abstract
	Broken out storage for the relpath warn_each bit from the <code>-d</code> flag.
    @discussion
	If set, prints the file name and other info for each unresolvable link request.
 */
int warn_each = 0;

/*!
    @abstract
	Broken out storage for the reparent debug bit from the <code>-d</code> flag.
    @discussion
	Used for debugging bugs in the code that converts commented-out link
	requests into live links.
 */
int debug_reparent = 0;

/* @abstract
	Set quick_test to 1 to gather and parse and write without actually
	resolving, or 2 to not do much of anything.
 */
int quick_test = 0;

/* @abstract
	Set nowrite to 1 to disable writes, 2 to write to a temp file but not rename over anything.
 */
int nowrite = 0;

/*!
    @abstract
	File descriptor for storing a copy of stderr.
    @discussion
	Used by {@link setup_redirection}, {@link redirect_stderr_to_null},
	and {@link restore_stderr}.
 */
int stderrfd = -1;

/*!
    @abstract
	File descriptor for <code>/dev/null</code>.
    @discussion
	Used by {@link setup_redirection}, {@link redirect_stderr_to_null},
	and {@link restore_stderr}.
 */
int nullfd = -1;

/*!
    @abstract
	A file pointer to a log file.
    @discussion
	Points to a file in /tmp for storing a detailed log of link resolution failures, etc.
 */
FILE *logfile = NULL;

/*!
    @abstract
	The temporary filename for the log file, as generated by <code>mkstemp</code>.
 */
char *logname = NULL;

/*!
    @abstract
	The number of successfully resolved links.
 */
int resolved = 0;

/*!
    @abstract
	The number of unresolved links.
 */
int unresolved = 0;

/*!
    @abstract
	The number of unresolved links that were not machine-generated
	by automated processes.
 */
int unresolved_explicit = 0;

/*!
    @abstract
	The total number of files processed.
 */
int nfiles = 0;

/*!
    @abstract
	The number of broken link requests (such as link requests without
	an end marker).
 */
int broken = 0;

/*!
    @abstract
	The number of normal (non-logicalPath-based) links.
 */
int plain = 0;

/*!
    @abstract
	Storage for the exit status of helper threads.
 */
int thread_exit[MAXTHREADS];

/*!
    @abstract
	Per-thread storage for an index into each thread's
	array of files to be processed.
 */
int thread_processed_files[MAXTHREADS];

/*!
    @abstract
	Number of threads to use (from the <code>-t</code> flag).
 */
int nthreads = 2;

/*!
    @abstract
	Number of duplicate API reference markers encountered.
 */
int duplicates = 0;

/*!
    @abstract
	Set to 1 while new reference markers are being added from
	seed files, 0 otherwise.
    @discussion
	Used to set the <code>fromseed</code> field in each
	{@link xrefnode_t} object.
 */
int seeding_in_progress = 0;


/*!
    @abstract
	The number of external cross-reference seed files encountered during
	command-line argument handling.
 */
int nextrefs = 0;

/*!
    @abstract
	The array of external cross-reference seed files encountered during
	command-line argument handling.
 */
char *extrefs[MAXEXTREFS];

/*!
    @abstract
	The array of files to be handled by each helper thread.
 */
fileref_t threadfiles[MAXTHREADS];
/*!
    @abstract
	Storage for the <code>-i</code> flag.
 */
char *global_installedpath = NULL;
/*!
    @abstract
	Storage for the <code>-b</code> flag.
 */
char *global_basepath = "/";
/*!
    @abstract
	Set to 1 if the <code>-b</code> flag is passed in (to distinguish
	it definitively from the default value).
 */
int global_basepath_set = 0;

/*!
    @abstract
	A global copy of <code>argv[0]</code> so that it doesn't have to be
	passed around everywhere for use in error messages.
 */
char *progname;

/*!
    @abstract
	The input directory, as passed in on the command line.
 */
char *inputDirectory = NULL;

/*!
    @abstract
	Set to 1 if the <code>-a</code> flag is passed prior to any
	<code>-s</code> flags, else 0.
 */
int force_absolute_globally = 0;


/*!
    @abstract
	Set to 1 to disable printing the dots.  This makes debugging in
	Instruments less traumatic.
 */
int nodot = 0;


/*!
    @abstract
	Set to 1 to disable partial matching.
    @discussion
	By default, resolveLinks looks for partial matching in C++ symbols.
	If you do not need this, you can significantly improve performance on
	larger docs by disabling this feature.
 */
int global_option_disable_partial_ref_matching = 0;

/*!
    @abstract
	Set to 1 to disable by-name matching.
    @discussion
	By default, resolveLinks allows certain link requests to contain only
	a name and searches for all matching references.  If you do not need
	this, you can significantly improve performance by disabling it.
 */
int global_option_disable_name_matching = 0;

/****************************************************************
 *                             Code                             *
 ****************************************************************/

/*!
  @abstract The main program body.
  @discussion
    This tool processes links (both in anchor form and in a commented-out
    form) and named anchors, rewriting link destinations to point to those
    anchors.

    @note Debugging Note
	If {@link nthreads} is negative, the files will be copied and
	whitespace reformatting will occur automatically, but resolution will be
	skipped.  This is used for debugging.

  @apiuid //apple_ref/c/func/resolvelinks_main
 */
int main(int argc, char *argv[])
{
    htmlDocPtr dp;

    xmlNode *root;

    int cumulative_exit = 0;

    char *filename;

    char *cwd;

    fileref_t files, curfile;

    char *xref_output_file = NULL;

    char *seedfiles[MAXSEEDFILES];
    char *seedfilebase[MAXSEEDFILES];
    int seedfileabsolute[MAXSEEDFILES];

#ifdef ENABLE_CORE_DUMPS
    EnableCoreDumps();
#endif

    bzero(seedfiles, (sizeof(seedfiles[0]) * MAXSEEDFILES));
    bzero(seedfilebase, (sizeof(seedfilebase[0]) * MAXSEEDFILES));
    bzero(seedfileabsolute, (sizeof(seedfileabsolute[0]) * MAXSEEDFILES));

    int nseedfiles = 0;

    test_xrefnode_t_tree();
    test_xrefbyname_t_tree();
#if TESTING_AVL > 1
    sleep(20);
    exit(-1);
#endif

    if (0) {
	printf("%d\n", ishdindex("/Users/dg/XManL/manpages/index.html"));
	printf("%d\n",
	       ishdindex
	       ("/Users/dg/headerdoc-techpubs/framework_output/force_feedback/ForceFeedback_h/index.html"));
	printf("%d\n",
	       ishdindex
	       ("/Users/dg/headerdoc-techpubs/ExampleHeaders/enumsTest/index.html"));
	exit(0);
    }

    setup_redirection();

    if (argc < 1) {
	fprintf(ERRS, "resolveLinks: No arguments given.\n");
	printusage();
	exit(-1);
    }

    /* Test code for db_malloc (debugging malloc) */
    if (0) {
	char *test;

	printf("MALLOCTEST\n");
	test = malloc(37 * sizeof(char));
	if (!test) {
		fprintf(stderr, "Out of memory in test code\n");
		exit(-1);
	}
	free(test);
	printf("MALLOCTEST DONE\n");
	sleep(5);
    }
#ifdef LIBXML_TEST_VERSION
    LIBXML_TEST_VERSION;
#endif

    if (argc < 2) {
	// fprintf(ERRS, "Usage: resolveLinks <directory>\n");
	printusage();
	exit(-1);
    }
    progname = argv[0];

    {
	int temp, debug_flags;

	while ((temp = getopt(argc, argv, "ab:d:Dhi:nNpPr:s:S:t:Tx:"))) {
	    if (temp == -1)
		break;
	    switch (temp) {
	    case 'a':
		if (!nseedfiles) {
			force_absolute_globally = 1;
		} else {
			seedfileabsolute[nseedfiles-1] = 1;
		}
		break;
	    case 'b':
		global_basepath = realpath(optarg, NULL); // Lives for duration of tool
		if (!global_basepath) {
			fprintf(stderr, "Could not set global_basepath to %s\n", optarg);
			perror("resolveLinks");
			exit(-1);
		}
		if (optarg[strlen(optarg)-1] == '/' && strcmp(global_basepath, "/")) {
			// Restore closing slash; it matters.
			char *temp = global_basepath;

			// fprintf(stderr, "Trailing slash\n");
			safe_asprintf(&global_basepath, "%s/", temp); // Lives for life of tool.
			if (!global_basepath) { fprintf(stderr, "Out of memory.\n"); exit(1); }
			free(temp);
		// } else {
			// fprintf(stderr, "NO trailing slash\n");
		}
		global_basepath_set = 1;
		printf("Set global base path to %s\n", global_basepath);
		break;
	    case 'i':
		global_installedpath = optarg;
		// printf("global_installedpath is %s\n", global_installedpath);
		break;
	    case 'r':
		if (nextrefs > MAXEXTREFS) {
		    fprintf(ERRS,
			    "Maximum number of external reference anchor types (%d) exceeded.  Extra files ignored.\n",
			    MAXSEEDFILES);
		} else {
		    extrefs[nextrefs++] = optarg;
		    // printf("EXT REF: %s\n", extrefs[nextrefs-1]);
		}
		break;
	    case 's':
		if (nseedfiles > MAXSEEDFILES) {
		    fprintf(ERRS,
			    "Maximum number of seed files (%d) exceeded.  Extra files ignored.\n",
			    MAXSEEDFILES);
		} else {
		    seedfiles[nseedfiles++] = optarg;
		}
		break;
	    case 'S':
		if (!nseedfiles) {
		    fprintf(ERRS,
			"Error: the -S flag must follow a -s flag.\n");
		    exit(-1);
		}
		if (optarg[0] != '/') {
		    char *temp = realpath(optarg, NULL); // Lives until tool exits.

		    if (!temp) {
			fprintf(ERRS,
			    "Error: Seed file base path \"%s\" is not an absolute path and\n"
			    "does not exist relative to the current directory.\n",
			    optarg);
			exit(-1);
		    }
		    seedfilebase[nseedfiles-1] = temp;
		} else {
			seedfilebase[nseedfiles-1] = optarg;
		}
		// fprintf(stderr, "Seed file %s BASE PATH %s\n", seedfiles[nseedfiles-1], seedfilebase[nseedfiles-1]);
		break;
	    case 'x':
		xref_output_file = optarg;
		break;
	    case 'T':
		testing_avl = 1;
		break;
	    case 't':
		nthreads = atoi(optarg);
		break;
	    case 'd':
		debug_flags = atoi(optarg);
		debugging = ((debug_flags & 1) != 0);
		filedebug = ((debug_flags & 2) != 0);
		writedebug = ((debug_flags & 4) != 0);
		debug_relpath = ((debug_flags & 8) != 0);
		debug_reparent = ((debug_flags & 16) != 0);
		warn_each = ((debug_flags & 32) != 0);

		nthreads = 0;	// Disable multithreaded processing for debugging.

		break;
	    case 'D':
		nodot = 1;
		break;
	    case 'n':
		nowrite = 1;
		break;
	    case 'N':
		global_option_disable_name_matching = 1;
		break;
	    case 'P':
		global_option_disable_partial_ref_matching = 1;
		break;
	    case 'p':
		nopercent = 1;
		break;
	    case 'h':
	    default:
		printusage();
		exit(-1);
	    }
	}

	inputDirectory = argv[optind];

 	cwd = getcwd(NULL, 0);
 	if (chdir(inputDirectory)) {
		// if (errno == ENOTDIR) {
		perror(inputDirectory);
		// fprintf(ERRS, "Usage: resolveLinks <directory> [nthreads]\n");
		printusage();
		// } else {
		// perror(directory);
		// // fprintf(ERRS, "Usage: resolveLinks <directory> [nthreads]\n");
		// printusage();
		// }
		exit(-1);
 	}
	if (chdir(cwd)) {
		// if (errno == ENOTDIR) {
		perror(inputDirectory);
		// fprintf(ERRS, "Usage: resolveLinks <directory> [nthreads]\n");
		printusage();
		// } else {
		// perror(directory);
		// // fprintf(ERRS, "Usage: resolveLinks <directory> [nthreads]\n");
		// printusage();
		// }
		exit(-1);
	}

	inputDirectory = realpath(argv[optind], NULL); // Lives for duration of tool.
	if (!inputDirectory) {
		fprintf(stderr, "Could not open input directory.\n");
		perror(inputDirectory);
		printusage();
		exit(-1);
	}
	if (argv[optind][strlen(argv[optind])-1] == '/') {
		// Restore closing slash; it matters.
		char *temp = inputDirectory;

		// fprintf(stderr, "Trailing slash\n");
		safe_asprintf(&inputDirectory, "%s/", temp); // Lives for life of tool.
		if (!inputDirectory) { fprintf(stderr, "Out of memory.\n"); exit(1); }
		free(temp);
	// } else {
		// fprintf(stderr, "NO trailing slash\n");
	}

	// *argc = *argc - optind;
	// *argv = *argv + optind;
    }
    if (debug_reparent)
	nthreads = 0;		// Disable multithreaded processing for debugging.


    // printf("Number of seed files: %d\nNumber of threads: %d\nDirectory: %s\n", nseedfiles, nthreads, inputDirectory);
    // { int i; for (i=0; i<nseedfiles; i++) { printf("Seed file %d: %s\n", i, seedfiles[i]); }}


    // if (argc == 3) { nthreads = atoi(argv[2]); }

    if (nseedfiles) {
	int i;

	seeding_in_progress = 1;
	printf("Loading seed files.\n");
	for (i = 0; i < nseedfiles; i++) {
	    // printf("Seed file %d: %s\n", i, seedfiles[i]);
	    int ret = readXRefFile(seedfiles[i], seedfilebase[i] ? seedfilebase[i] : "/", seedfileabsolute[i]);

	    if (!ret) {
		fprintf(ERRS, "%s: xref seed file %s missing or malformed.\n",
			progname, seedfiles[i]);
	    }
	}

	seeding_in_progress = 0;
    }
    duplicates = 0;

    {
	if (!chdir(inputDirectory)) {
		/* Directory change successful.  Find files. */
		char *newdir = getcwd(NULL, 0);

		char *allyourbase = ts_basename(newdir);
		char *ayb_fp = NULL;

		free(newdir);
		printf("Finding files.\n");
		ayb_fp = fixpath(allyourbase);

		// fprintf(stderr, "AYB: %s FP: %s\n", allyourbase, ayb_fp);

		if (!((files = getFiles(ayb_fp)))) {
		    fprintf(ERRS, "No HTML files found.\n");
		    exit(-1);
		}
		if (chdir("..")) {
			char *tempcwd = getcwd(NULL, MAXPATHLEN);
			fprintf(stderr, "Could not change to directory \"..\" from \"%s\"\n", tempcwd);
			perror("resolveLinks");
			free(tempcwd);
			exit(-1);
		}
		free(allyourbase);
	} else {
		fprintf(stderr, "Could not change into directory \"%s\"\n", inputDirectory);
		perror(inputDirectory);
		exit(-1);
	}
    }

    nfiles = countfiles(files);

    if (debugging || filedebug) {
	for (curfile = files; curfile; curfile = curfile->next) {
	    printf("\nWill check %s", curfile->name);
	}
    }

    printf("\nChecking for cross-references\n");
    if (nthreads >= 0) {
	for (curfile = files; curfile; curfile = curfile->next) {
	    filename = curfile->name;

	    if (!nodot) printf(".");
	    fflush(stdout);
	    if (debugging) {
		printf("FILE: %s\n", filename);
		fflush(stdout);
	    }

	    redirect_stderr_to_null();
	    if (!(dp = htmlParseFile(filename, ""))) {
		restore_stderr();
		fprintf(ERRS,
			"error: resolveLinks: could not parse HTML file %s\n",
			filename);
		fprintf(ERRS, "CWD is %s\n", getcwd(NULL, 0));
		exit(-1);
	    }
	    root = xmlDocGetRootElement(dp);
	    restore_stderr();

	    if (quick_test < 2)
		gatherXRefs(root, dp, filename);
	    xmlFreeDoc(dp);
	}


	fprintf(stderr, "\nWriting xref file\n");
	writeXRefFile(xref_output_file, cwd);

	verify_xrefnode_t_tree(nodehead);
	verify_xrefbyname_t_tree(namehead);

#ifdef TESTING_AVL
	sleep(20);
	exit(-1);
#endif

    } else {
	quick_test = 1;
    }

    if (nowrite == 1)
	exit(0);		// We're done.

#ifdef OLD_CODE
    {
	fileref_t next = files;

	int count = 0;

	for (count = 0; count < nthreads; count++) {
	    threadfiles[count % nthreads] = NULL;
	}
	for (curfile = files; next; curfile = next, count++) {
	    next = curfile->next;
	    curfile->next = threadfiles[count % nthreads];
	    threadfiles[count % nthreads] = curfile;
	}

	for (count = 0; count < nthreads; count++) {
	    if (debugging)
		printf("FILES[%d] = %d\n", count,
		       countfiles(threadfiles[count]));
	}
    }
#endif


    if (nthreads > 0) {
	printf("\nResolving links (multithreaded)\n");
	/* Spawn off multiple processing threads. */
	pthread_t threads[MAXTHREADS];

	int thread_exists[MAXTHREADS];
	int i;

	// Not really necessary, but makes Xcode's static analyzer happy.
	bzero(&thread_exists, sizeof(thread_exists));

	for (i = 0; i < nthreads; i++) {
	    uintptr_t temp_i = i;

	    pthread_attr_t *attr = NULL;

	    thread_exists[i] = 1;
	    if (pthread_create
		(&threads[i], attr, resolve_main, (void *) temp_i)) {
		printf("Thread %d failed.  Running in main thread.\n", i);
		resolve_mainsub(i);
		thread_exists[i] = 0;
	    }
	}
	for (i = 0; i < nthreads; i++) {
	    int joinret;

	    if (debugging)
		printf("JOINING %d\n", i);
	    if (thread_exists[i]) {
		if ((joinret = pthread_join(threads[i], NULL))) {
		    if (debugging)
			printf("JOIN RETURNED %d\n", joinret);
		}
	    }
	    cumulative_exit =
		thread_exit[i] ? thread_exit[i] : cumulative_exit;
	    if (debugging)
		printf("thread_exit[%d] = %d\n", i, thread_exit[i]);
	}
    } else {
	/* Resolve links in the main thread */
	if (!nthreads)
	    printf("\nResolving links (single threaded)\n");
	cumulative_exit = resolve_mainsub(0);
    }
    printf("\nDone\n");

    print_statistics();

    /* Clean up just to be polite. */
    xmlCleanupParser();

    if (cumulative_exit) {
	printf("Exiting with status %d\n", cumulative_exit);
    }

#ifndef TEST_DELAY
    if (testing_avl)
#endif
	sleep(20); // Useful for leak detection.

    exit(cumulative_exit);
}


/*!
    @abstract
	Frees a {@link refparts_t} object.
    @param rp
	The {@link refparts_t} object to free.
 */
void refpartsfree(refparts_t rp)
{
    if (rp->refpart) free(rp->refpart);
    if (rp->langpart) free(rp->langpart);
    if (rp->rest) free(rp->rest);
    free(rp);
}



/*! @abstract The main pthread body for link resolution */
static void *resolve_main(void *ref)
{
    int ret;

    if (debugging)
	printf("Thread %d spawned.\n", (int) (uintptr_t) ref);	// Intentional truncation to integer.

// sleep(5*((int)ref));

    ret = resolve_mainsub((int) (uintptr_t) ref);
    thread_exit[(int) (uintptr_t) ref] = ret;
    pthread_exit(NULL);
}

#define OLD_LIBXML
/*!
  @abstract
    Processes all of the files assigned to a given thread.
  @discussion
    This function takes a single argument (the thread number) and
    processes all of the files in the file list corresponding with
    that thread number.
 */
int resolve_mainsub(int pos)
{
    int localDebug = 0;

    fileref_t files, curfile;

    char *filename;

    htmlDocPtr dp;

    xmlNode *root;

    char tempname[MAXNAMLEN];

#ifndef OLD_LIBXML
    htmlParserCtxtPtr ctxt;

    int options =
	HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING | HTML_PARSE_NONET;

#endif


    files = threadfiles[pos];
    thread_processed_files[pos] = 0;
#ifndef OLD_LIBXML
    int ks = 1;
#endif

    for (curfile = files; curfile; curfile = curfile->threadnext) {
	thread_processed_files[pos]++;

	filename = curfile->name;

	if (debugging || writedebug || debug_reparent || localDebug)
	    printf("READING FILE: %s\n", filename);

	// if (nthreads > 0) {
	// sprintf(tempname, "/tmp/resolveLinks.%d.%d", getpid(), (int)pthread_self());
	// } else {
	snprintf(tempname, MAXNAMLEN, "%s-temp%d-%d", filename, getpid(),
		 pos);
	// }

	redirect_stderr_to_null();
#ifdef OLD_LIBXML
	if (!(dp = htmlParseFile(filename, NULL)))
#else
	ctxt = htmlCreateFileParserCtxt(filename, NULL);

	if (!ctxt) {
	    fprintf(ERRS, "error: could not create context\n");
	    exit(-1);
	}
	// if (!(dp = htmlCtxtReadFile(ctxt, filename, "", options)))
	ctxt->options = options;
	ctxt->space = &ks;
	ctxt->sax->ignorableWhitespace = NULL;
	ctxt->keepBlanks = 1;
	// printf("PRE:\n");
	/* printf("SAX: %p IWS: %p\n", ctxt->sax,
	       ctxt->sax ? ctxt->sax->ignorableWhitespace : 0); */
	// printf("KB: %d\n", ctxt->keepBlanks);
	htmlParseDocument(ctxt);
	dp = ctxt->myDoc;
	// printf("POST: \n");
	/* printf("SAX: %p IWS: %p\n", ctxt->sax,
	       ctxt->sax ? ctxt->sax->ignorableWhitespace : 0); */
	// printf("KB: %d\n", ctxt->keepBlanks);
	if (!dp)
#endif
	{
	    restore_stderr();
	    fprintf(ERRS,
		    "error: resolveLinks: could not parse HTML file %s in resolve_mainsub().\n",
		    curfile->name);
	    return -1;
	}

	root = xmlDocGetRootElement(dp);
	restore_stderr();

	if (!quick_test) {
	    char *filename_rp = realpath(filename, NULL);
	    resolveLinks(root, dp, filename, filename_rp);
	    free(filename_rp);
	    if (!nodot) printf(".");
	    fflush(stdout);
	} else {
	    printf("X");
	    fflush(stdout);
	}

	if (debugging || writedebug || debug_reparent)
	    printf("WRITING FILE: %s\n", filename);

	writeFile(root, dp, tempname);
	// printf("TREE:\n");
	// writeFile_sub(root, dp, stdout, 1);
	// printf("TREEDONE.\n");
	// exit(-1);

	xmlFreeDoc(dp);

	if (nowrite == 2) {
	    fprintf(stderr, "TEMPNAME: %s\n", tempname);
	} else if (rename(tempname, filename)) {
	    fprintf(ERRS, "error: error renaming temp file over original.\n");
	    perror("resolveLinks");
	    return -1;
	}
#ifndef OLD_LIBXML
	htmlFreeParserCtxt(ctxt);
#endif
    }

    return 0;
}


char *textmatching(char *name, xmlNode * cur, int missing_ok, int recurse);

xmlNode *nodematching(char *name, xmlNode * cur, int recurse);

/*! @abstract
	Takes a line from an xref cache file, splits it into
	its components, and adds the xref into the xref tree.
    @discussion
	Each line in an xref file is in the following format:

	<ul>
	<li>Relative URI fragment or absolute URI (<code>/foo/bar.html</code> or <code>http://www.example.com/foo.html</code>, for example)
	<li>control-A
	<li>API reference (<code>//apple_ref/c/func/myFunc</code>, for example)
	<li>control-A
	<li>Link title (used to provide debug info)
	</ul>

	Note: This format is subject to change at any time.  No forwards or backwards compatibility
	is guaranteed between different versions of the resolveLinks tool.
 */
int addXRefFromLine(char *line, char *basepath, int force_absolute)
{
    char *iter = line;

    char *xref = NULL;

    char *title = NULL;

    xrefnode_t newnode;

    if (!strcmp(line, "Cross-references seen (for debugging only)"))
	return -1;
    if (!strlen(line))
	return -1;

    while (*iter && *iter != 1) {
	iter++;
    }
    if (*iter) {
	*iter = '\0';
	iter++;
	xref = iter;
    }
    while (*iter && *iter != 1) {
	iter++;
    }
    if (*iter) {
	*iter = '\0';
	iter++;
	title = iter;
    }
    if (!xref || !title) {
	fprintf(ERRS, "warning: Corrupted line in xref file.\n");
	// fprintf(ERRS, "%x %x\n", xref, title);
	return 0;
    }

    if (!((newnode = malloc(sizeof(*newnode))))) { // This stays around until the tool exits.
	fprintf(ERRS, "error: Out of memory reading xref file.\n");
	exit(-1);
    }

    newnode->filename = strdup(line);
#ifdef RP_CACHE
    newnode->filename_rp = NULL;
#endif
    newnode->basepath = strdup(basepath);
    newnode->fullpath = NULL;
    newnode->xref = strdup(xref);
    newnode->title = strdup(title);
    newnode->left = NULL;
    newnode->right = NULL;
    newnode->parent = NULL;
    newnode->maxleftdepth = 0;
    newnode->maxrightdepth = 0;
    newnode->dup = NULL;
    newnode->fromseed = seeding_in_progress;
    newnode->force_absolute = force_absolute;

    // printf("From File: Title was %s\n", title);
    if (debugging) {
	printf("Added xref: uid=%s, filename_or_uri=%s, title=%s", newnode->xref, newnode->filename, newnode->title);
    }

    xrefbyname_t newname = malloc(sizeof(*newname)); // This stays around until the tool exits.
    newname->name = nameFromAppleRef(newnode->xref);
    newname->node = newnode;
    newname->left = NULL;
    newname->right = NULL;
    newname->dup = NULL;
    newname->parent = NULL;
    newname->maxleftdepth = 0;
    newname->maxrightdepth = 0;

    if (!global_option_disable_name_matching) {
	if (namehead) {
		// fprintf(stderr, "Inserting name\n");
		insertName(newname, namehead);
	} else {
		// fprintf(stderr, "Setting name as root\n");
		namehead = newname;
	}
    }

    if (nodehead) {
	addXRefSub(newnode, nodehead);
    } else {
	nodehead = newnode;
    }

    return 1;
}

/*!
  @abstract
    This function reads a cross-reference cache file.

  @discussion
    This is intended to allow eventual incorporation of cross-references
    that do not live in the same directory (or even on the same machine.
    It is currently unused.
 */
int readXRefFile(char *filename, char *basepath, int force_absolute)
{
    FILE *fp;

    char line[4098];

    int ret = 1;

    if (!((fp = fopen(filename, "r")))) {
	return 0;
    }

    while (1) {
	if (fgets(line, 4096, fp) == NULL)
	    break;
	if (line[strlen(line) - 1] != '\n') {
	    fprintf(ERRS, "warning: ridiculously long line in xref file.\n");
	    ret = 0;
	} else {
	    line[strlen(line) - 1] = '\0';
	}
	if (!addXRefFromLine(line, basepath, force_absolute))
	    ret = 0;
    }

    fclose(fp);
    return ret;
}

/*!
    @abstract Walks the tree and writes cross-references to a cache file.
    @discussion Used by {@link writeXRefFile}.
 */
void writeXRefFileSub(xrefnode_t node, FILE * fp)
{
    if (!node)
	return;

    writeXRefFileSub(node->left, fp);

    //fprintf(fp, "filename=\"%s\" id=\"%s\" title=\"%s\"\n",
    // node->filename, node->xref, node->title ? node->title : "");



    /* Only write what we processed this pass, not stuff from seed files. */
    if (!node->fromseed) {
	char *currentpath;

	if (!global_basepath_set) {
		/* By default, provide the relative path. */
		safe_asprintf(&currentpath, "%s", node->filename); // freed.
		if (!currentpath) { fprintf(stderr, "Out of memory.\n"); exit(1); }
	} else {
		/* Make path relative to base path (-b) */
		currentpath = relpath(nodefilenamerealpath(node), global_basepath, 1); // relative to -b
	}

	// printf("PATH: %s\n", currentpath);

	fprintf(fp, "%s%c%s%c%s\n",
	    currentpath, 1, node->xref, 1, node->title ? node->title : "");
	free(currentpath);
    }

    writeXRefFileSub(node->dup, fp);
    writeXRefFileSub(node->right, fp);
}

/*!
    @abstract
        Calculates the final absolute filesystem path for a link destination.
    @discussion
        This takes into account the base path, whether the path is a (known) URI
        or not, and the global installation path (if set, else the global
        base path).

	This is similar to {@link nodepath}, except that it gives the path
        in terms of where the user intends the file to live after installation,
        rather than where the file lives currently.
    @result
	Returns an allocated chunk of memory (allocated by <code>realpath()</code>)
	that must be released with a call to <code>free()</code>.
 */
char *installedpath(char *filename)
{
	/* Make path relative to base path (-b) */
	char *realpath_var = realpath(filename, NULL);
	char *currentpath = relpath(realpath_var, global_basepath, 1); // relative to -b
	char *relativeto = global_installedpath ? global_installedpath : global_basepath;
	char *installedpath = NULL;

	if (!global_installedpath) {
		free(currentpath);
		return realpath_var;
	}
	free(realpath_var);

	safe_asprintf(&installedpath, "%s%s%s", relativeto, ((relativeto[strlen(relativeto)-1] == '/' || currentpath[0] == '/') ? "" : "/"), filename);
	if (!installedpath) { fprintf(stderr, "Out of memory.\n"); exit(1); }

	// printf("global_installedpath set to %s.  Using %s instead.\n", global_installedpath, installedpath);

	free(currentpath);
	return installedpath;
}


/*!
  @abstract Writes a cross-reference cache file.
  @discussion
    A cross-reference cache file can be used to provide an initial seed for
    the resolver.  It contains the information needed to create a link to
    any of the content processed (subject to passing the correct flags to
    strip off leading path components and prepend new leading path components).

    This allows you to resolve the contents of multiple independent projects
    iteratively.  For example, you could have two projects, A and B, each of
    which exports a cross-reference cache file, and also imports the cache file
    from the other project when you build it.  By doing this, the two projects
    can know nothing about each other except for the location of the cache
    file and the relative path on the server, yet can still link to one
    another.
 */
void writeXRefFile(char *filename, char *indir)
{
    FILE *fp;

    char *outfile = "/tmp/xref_out";

    if (filename)
	outfile = filename;

    char *cwd = getcwd(NULL, 0);
    if (chdir(indir)) {
	fprintf(stderr, "Could not change to directory \"%s\"\n", indir);
	perror(indir);
	exit(-1);
    }

    fprintf(stderr, "Writing cross-references to file %s\n", outfile);

    if (!((fp = fopen(outfile, "w")))) {
	fprintf(stderr, "Could not write cros-references to file.\n");
	perror("resolvelinks");
	if (chdir(cwd)) {
		fprintf(stderr, "Could not change to directory \"%s\"\n", indir);
		perror(indir);
		exit(-1);
	}
	free(cwd);
	return;
    }

    /* We have to change back to the previous directory here or else calls
       to realpath() from writeXRefFileSub won't work. */
    if (chdir(cwd)) {
	fprintf(stderr, "Could not change to directory \"%s\"\n", indir);
	perror(indir);
	exit(-1);
    }

    free(cwd);

    if (!filename) fprintf(fp, "Cross-references seen (for debugging only)\n\n");

    writeXRefFileSub(nodehead, fp);

    fclose(fp);
}


/*!
    @abstract
        Calculates the absolute filesystem path for a relative path.

    @discussion
        This differs from {@link installedpath} because it returns the current
        location of the files, not the final installed location.

        It takes into account the base path specified for the
        folder containing the file in question and adds it back on.

        If the path is a (known) URI, it returns the value unmodified.
 */
char *nodepath(xrefnode_t node)
{

    if (!node->fullpath) {
	char *retval;

	// printf("in nodepath: basepath is \"%s\", filename is \"%s\", ", node->basepath, node->filename);

	if (isURI(node->filename)) {
		// printf("ISURI\n");
		retval = strdup(node->filename);
	} else if (node->basepath[0]) {
		// From cache.
		safe_asprintf(&retval, "%s%s%s", node->basepath, ((node->basepath[strlen(node->basepath)-1] == '/' || node->filename[0] == '/') ? "" : "/"), node->filename);
		if (!retval) { fprintf(stderr, "Out of memory.\n"); exit(1); }
	} else {
		retval = nodefilenamerealpath(node);
	}
	// printf("result is %s\n", retval);
	node->fullpath = retval;
    }

    return node->fullpath;
}



/*! @abstract Recursive tree walk subroutine used by
    {@link addXRef} when adding cross-references to the xref tree.
 */
void addXRefSub(xrefnode_t newnode, xrefnode_t tree)
{
    int pos = strcmp(newnode->xref, tree->xref);

    if (!newnode->title)
	newnode->title = strdup("");
    // tree->fromseed = seeding_in_progress;

    if (pos < 0) {
	/* We go left */
	if (tree->left)
	    addXRefSub(newnode, tree->left);
	else {
	    tree->left = newnode;
	    newnode->parent = tree;
	    rebalance_xrefnode_t_tree(tree, newnode);
	}
    } else if (!pos) {
	xrefnode_t iter;

	int drop = 0;

	for (iter = tree; iter; iter = iter->dup) {
	    if (!strcmp(nodepath(newnode), nodepath(iter))) {
		if (!strcmp(newnode->title, iter->title)) {
		    // printf("Exact dup.  Dropping.\n");
		    drop = 1;
		    if (!newnode->fromseed)
			iter->fromseed = 0;
		}
	    }
	    // if (iter->title) printf("TITLE FOUND: %s\n", iter->title);
	    if (debugging)
		printf("Dup: %s %s %s == %s %s %s\n", iter->title,
		       nodepath(iter), iter->xref, newnode->title,
		       nodepath(newnode), newnode->xref);
	    if (!iter->dup) {
		// end of the chain.
		if (!drop) {
		    iter->dup = newnode;
		}
		if (!iter->fromseed) {
		    /* Not from the seed file. */
		    duplicates++;
		    openlogfile();
		    if (logfile) {
			fprintf(logfile, "Dup: %s %s %s == %s %s %s\n",
				iter->title, nodepath(iter), iter->xref,
				newnode->title, nodepath(newnode),
				newnode->xref);
		    }
		}
		break;
	    }
	}
    } else {
	/* We go right */
	if (tree->right)
	    addXRefSub(newnode, tree->right);
	else {
	    tree->right = newnode;
	    newnode->parent = tree;
	    rebalance_xrefnode_t_tree(tree, newnode);
	}
    }
}

/*! @abstract Inserts a new cross-reference into the
    cross-reference tree for later use in link resolution.
 */
void addXRef(xmlNode * node, char *filename)
{
    xrefnode_t newnode;

    char *tempstring;

    char *bufptr;

    char *nextstring;

    if (!node) {
	printf("warning: addXRef called on null node\n");
	return;
    }

    bufptr = proptext("name", node->properties);
    if (!bufptr) {
	printf("warning: addXRef called on anchor with no name property\n");
    }

    if (debugging) {
	printf("STRL " FMT_SIZE_T "\n", strlen(bufptr));
	fflush(stdout);
    }

    tempstring = bufptr;

    while (tempstring && *tempstring) {
	for (nextstring = tempstring; *nextstring && (*nextstring != ' ');
	     nextstring++);
	if (*nextstring) {
	    *nextstring = '\0';
	    nextstring++;
	} else {
	    nextstring = NULL;
	}
	newnode = malloc(sizeof(*newnode));
	if (!newnode) {
		fprintf(stderr, "Out of memory in addXRef[2]\n");
		exit(-1);
	}
	if (strlen(tempstring)) {
	    newnode->basepath = "";
	    newnode->filename = filename;
#ifdef RP_CACHE
	    newnode->filename_rp = NULL;
#endif
	    newnode->fullpath = NULL;
	    safe_asprintf(&newnode->xref, "%s", tempstring);
	    if (!newnode->xref) { fprintf(stderr, "Out of memory.\n"); exit(1); }
	    // newnode->xref = pt;
	    newnode->title = proptext("title", node->properties);
	    newnode->left = NULL;
	    newnode->right = NULL;
	    newnode->parent = NULL;
	    newnode->maxleftdepth = 0;
	    newnode->maxrightdepth = 0;
	    newnode->dup = NULL;
	    newnode->fromseed = seeding_in_progress;
	    newnode->force_absolute = 0;	// This is not called when
						// reading from a file, so
						// no need to fill this in.

	    // printf("FILENAME \"%s\" XREF \"%s\" TITLE \"%s\"\n", newnode->filename, newnode->xref, newnode->title);

	    xrefbyname_t newname = malloc(sizeof(*newname));
	    newname->name = nameFromAppleRef(newnode->xref);
	    newname->node = newnode;
	    newname->left = NULL;
	    newname->right = NULL;
	    newname->dup = NULL;
	    newname->parent = NULL;
	    newname->maxleftdepth = 0;
	    newname->maxrightdepth = 0;
	    newname->parent = NULL;

	    if (namehead) {
		insertName(newname, namehead);
	    } else {
		namehead = newname;
	    }

	    if (nodehead) {
		addXRefSub(newnode, nodehead);
	    } else {
		nodehead = newnode;
	    }

	    tempstring = nextstring;
	}
    }
    free(bufptr); // release the buffer as a whole.
}

/*! @abstract Walks the parse tree of an HTML file, gathers all
    API reference anchors, and adds then into the xref tree with
    {@link addXRef}.
 */
void gatherXRefs(xmlNode * node, htmlDocPtr dp, char *filename)
{
    if (!node)
	return;
    if (node->name && !strcmp((char *) node->name, "a")) {
	char *pt = proptext("name", node->properties);

	char *pos = pt;

	while (pos && *pos && *pos == ' ')
	    pos++;

	if (pos) {
	    if (debugging)
		printf("MAYBE: %s\n", pos);
	    if ((pos[0] == '/') && (pos[1] == '/')) {
		/* It has a name property that starts with two
		   slashes.  It's an apple_ref. */
		if (debugging)
		    printf("YO: %s\n", pos);
		addXRef(node, filename);
	    } else {
		if (debugging)
		    printf("NOT A REF\n");
	    }
	}
	free(pt);
    }

    gatherXRefs(node->children, dp, filename);
    gatherXRefs(node->next, dp, filename);
}

/*! @abstract Returns true if the text (from an HTML comment) represents the
    start of a link request
 */
int isStartOfLinkRequest(char *text)
{
    int retval = isCommentedAnchor(text);

    if (debug_reparent) {
	printf("isSOLR: %s: %d\n", text, retval);
    }
    return retval;
}

/*! @abstract Returns true if the text (from an HTML comment) represents the
    end of a link request
 */
int isEndOfLinkRequest(char *text)
{
    char *ptr = text;

    if (!ptr)
	return 0;

    while (ptr && *ptr == ' ')
	ptr++;
    if (ptr[0] != '/' || ptr[1] != 'a' || ptr[2] != ' ') {
	return 0;
    }
    if (debugging)
	printf("ENDLINK\n");
    return 1;
}


/*! @abstract
        The actual link resolution function.

    @discussion
        Walks the parse tree of an HTML document and does the actual link
        resolution, inserting href attributes where applicable, converting
        anchors that fail to resolve into comments, and converting resolvable
        commented links back into anchors.
 */
void resolveLinks(xmlNode * node, htmlDocPtr dp, char *filename, char *filename_rp)
{
    while (node) {

	if (node->name && !strcmp((char *) node->name, "comment")) {
	    if (debugging) {
		printf("comment: \"%s\"\n", node->content);
	    }
	    if (isStartOfLinkRequest((char *) node->content)) {
		xmlNode *close = NULL;

		struct nodelistitem *nodelisthead = NULL;

		struct nodelistitem *nodelistiterator = NULL;

		struct nodelistitem *orignodelisthead = NULL;

		if (debugging || debug_reparent)
		    printf("SOLR\n");
		if (node->next) {
		    /* The node list is in reverse order of match. Skip to the last node.
		       Later, we'll work backwards so that we find the first link end
		       comment.
		     */
		    nodelisthead = nodelist("comment", node->next);
		    orignodelisthead = nodelisthead;

		    while (nodelisthead && nodelisthead->next)
			nodelisthead = nodelisthead->next;
		}
		nodelistiterator = nodelisthead;

		/* Iterate backwards. */
		while (nodelistiterator && !close) {

		    if (debugging || debug_reparent)
			printf("NODE: %s\n", nodelistiterator->node->name);
		    if (debugging || debug_reparent)
			printf("NODETEXT: %s\nEONODETEXT\n",
			       nodelistiterator->node->
			       content ? (char *) nodelistiterator->node->
			       content : "(null)");

		    if (nodelistiterator->node->name
			&& !strcmp((char *) nodelistiterator->node->name,
				   "comment")
			&& isEndOfLinkRequest((char *) nodelistiterator->
					      node->content)) {
			if (debugging || debug_reparent)
			    printf("Is EOLR\n");
			close = nodelistiterator->node;
		    } else {
			if (debugging || debug_reparent) {
			    printf("No match.  Content was \"%s\"\n",
				   nodelistiterator->node->content);
			}
		    }

		    nodelistiterator = nodelistiterator->prev;
		}
		while (orignodelisthead) {
			struct nodelistitem *temp = orignodelisthead->next;
			free(orignodelisthead);
			orignodelisthead = temp;
		}
		if (close) {
		    if (debug_reparent) {
			printf("Printing nodes between start and end.\n");
			printNodeRange(node, close);
			printf
			    ("Done printing nodes between start and end.\n");
		    }

		    /* Link Request. */

		    xmlNode *clone = xmlNodeForComment(node->content, dp);

		    // fprintf(stderr, "CLONE: %p\n", clone);

		    char *lp = proptext("logicalPath", clone->properties);

		    char *frametgt = proptext("target", node->properties);

		    int retarget = (!frametgt || !strlen(frametgt));

		    // if (!strncmp(lp, "//apple_ref/doc/uid/", 20)) {
			// fprintf(stderr, "Apple UID[1]\n");
		    // }

		    char *target =
			resolve(lp, filename, &retarget, &frametgt);
		    if (debugging)
			printf
			    ("RETARGET SHOULD HAVE BEEN %d (frametgt is %p)\n",
			     retarget, frametgt);
		    if (debugging)
			printf("EOLR\n");

		    if (debugging) {
			printf("LP: \"%s\"\n", lp);
		    }

		    if (target) {
			xmlNode *lastnode;

			int samelevel = 0;

			resolved++;

			/* Make link live */
			if (debugging)
			    printf("FOUND!\n");

			samelevel = onSameLevel(node, close);
			if (samelevel) {
			    /* If close is on the same level as open,
			       the last node inside the anchor is
			       the one before the close.  */
			    // fprintf(stderr, "NODE CONTENT: %s\n", close->content);

			    lastnode = close->prev;

			    // lastnode->next = close->next;
			    // if (close->next)
				// close->next->prev = close->prev;
			    // close->next = NULL; close->prev = NULL;
			    xmlUnlinkNode(close);
			    xmlFreeNode(close);
			    // close = NULL; // Don't do this or the loop won't terminate right.
			} else {
			    /* Just change the end tag to an empty text container.  It's safer. */
			    xmlNode *newnode = xmlNewText((xmlChar *)(""));
			    if (!newnode) {
				fprintf(stderr, "Could not allocate memory in resolveLinks()\n");
				exit(-1);
			    }

			    xmlReplaceNode(close, newnode);
			    xmlFreeNode(close);

			    close = newnode;

			    // close->name = (unsigned char *) strdup("text");
			    // close->content = (unsigned char *) strdup("");
			    // close->type = XML_TEXT_NODE;

			    lastnode = close->parent;
			    while (lastnode && !onSameLevel(lastnode, node)) {
				lastnode = lastnode->parent;
			    }
			}
			if (lastnode) {
			    xmlNode *iter;

			    if (debugging)
				printf("LAST NODE FOUND.\n");
			    /* We successfully resolved this 
			       commented-out link and successfully
			       found the commend marker indicating
			       the end of the anchor.

			       Because the close tag could be
			       in some illegal place (splitting
			       an element), we work our way up
			       the tree until we are at the same
			       level as the start node and make
			       whatever node we find be the
			       last child.

			       The effect of this is that if the
			       close tag splits some element,
			       that entire element will end up
			       as part of the link.  It isn't
			       worth trying to split an element
			       in half to make up for an
			       incorrectly-generated link request.

			       In an ideal world, that link-end
			       comment should always be on the
			       same level, but in practice, this
			       has often failed to be the case.

			       In any case, once we have the
			       tailnode (the rightmost node
			       to move), we reparent everything
			       from node through tailnode as a
			       child of node.  The tailnode
			       (link-end comment) gets turned
			       into an empty text element (which
			       effectively deletes it without
			       requiring as much effort).
			     */

			    xmlReplaceNode(node, clone);
			    xmlFreeNode(node);
			    node = clone;
			    clone = NULL;

			    xmlNodeSetName(node, (xmlChar *)("a"));
			    node->type = XML_ELEMENT_NODE;

			    addAttribute(node, "href", target);
			    addAttribute(node, "logicalPath", lp);
			    if (frametgt) {
				if (debugging)
				    printf("FRAMETGT FOUND\n");
				addAttribute(node, "target", frametgt);
			    } else {
				if (debugging)
				    printf("NO FRAMETGT FOUND\n");
				char *pos;

				int index = 1;

				pos = target;
				while (*pos && *pos != '?' && *pos != '#')
				    pos++;
				if (pos < target + 10) {
				    index = 0;
				} else {
				    pos -= 10;
				    if (strncmp(pos, "index.html", 10))
					index = 0;
				}
				if (index) {
				    addAttribute(node, "target", "_top");
				}
			    }
			    if (node->content) free(node->content);
			    node->content = NULL;	/* @@@ LEAK? @@@ */
			    /* Reparent everything from the open comment to the close comment under an anchor tag. */
			    node->children = node->next;
			    if (node->children != NULL) {
				node->children->prev = NULL;
				for (iter = node->children;
				     iter && iter != lastnode;
				     iter = iter->next) {

				    iter->parent = node;
				    if (debug_reparent) {
					printf("REPARENTING: \n");
					writeFile_sub(node, dp, stdout, 1);
					printf("DONE REPARENTING\n");
				    }
				}
				if (iter != lastnode) {
				    fprintf(stderr,
					    "warning: reparenting failed.\n");
				}
			    } else {
				// ERROR: could happen when the @link is missing the second (text) element after the apiref!
				fprintf(stderr,
					"error: some @link is missing the second field; see resulting file %s to find out which one.\n",
					filename);
			    }
			    lastnode->parent = node;
			    if (lastnode->next) {
				lastnode->next->prev = node;
			    }
			    node->next = lastnode->next;
			    lastnode->next = NULL;
			} else {
			    /* We ran off the top of the tree! */
			    fprintf(ERRS,
				    "NESTING PROBLEM: close link request marker nested higher than open marker.\n");
			    fprintf(ERRS, "Giving up.\n");
			}
			free(target);
		    } else {
			char *full_filename = strdup(filename_rp ? filename_rp : filename);
			char *printed_filename = full_filename;

			if (!full_filename) {
				fprintf(stderr, "Out of memory in resolveLinks[1]\n");
				exit(-1);
			}

			// Let's get full file name to display error messages containing full path to file
			if (warn_each)
			    fprintf(ERRS,
				    "\n%s:0: error: unable to resolve link %s.\n",
				    printed_filename, lp);

			unresolved++;

			char *machineGenerated = proptext("machineGenerated", clone->properties);
			if (!machineGenerated || strcmp(machineGenerated, "true")) {
				/* Oops.  It's a user-entered link. */
				unresolved_explicit++;
			}
			if (debugging) printf("machineGenerated: %s\n", machineGenerated ? machineGenerated : "(null)");

			openlogfile();
			if (logfile) {
			    if ((!machineGenerated) || (strcmp(machineGenerated, "true"))) {
			    	fprintf(logfile, "Unresolved: %s %s\n", printed_filename, lp);
			    } else {
				fprintf(logfile, "Unresolved (machine-generated): %s %s\n", printed_filename, lp);
			    }
			}
			free(full_filename);

			if (machineGenerated) free(machineGenerated);
		    }
		    free(frametgt);
		    free(lp);
		    if (clone) { xmlFreeNode(clone); }
		} else {
		    char *full_filename = malloc(PATH_MAX * sizeof(char));

		    char *printed_filename = full_filename;

		     if (!full_filename) {
			fprintf(stderr, "Out of memory in resolveLinks[2]\n");
			exit(-1);
		     }
		    // Let's get full file name to display error messages containing full path to file
		    if (!realpath(filename, full_filename)) // Not a leak; returns pointer into full_filename.
			printed_filename = filename;

		    fprintf(ERRS,
			    "\n%s:0: error: broken link.  No closing link request comment found.\n",
			    printed_filename);

		    openlogfile();
		    if (logfile) {
			fprintf(logfile,
				"Broken link.  No closing link request comment found in \"%s\".  Open comment was:\n%s\n",
				printed_filename, node->content);
		    }
		    free(full_filename);
		    broken++;
		}
	    }
	} else if (node->name && !strcmp((char *) node->name, "a")) {
	    /* Handle the already-live link */
	    int retarget = (!has_target(node));

	    char *lp = proptext("logicalPath", node->properties);

	    // char *name = proptext("name", node->properties);
	    // char *href = proptext("href", node->properties);

	    if (lp) {
		char *frametgt = proptext("target", node->properties);

		// if (!strncmp(lp, "//apple_ref/doc/uid/", 20)) {
			// fprintf(stderr, "Apple UID[2]\n");
		// }

		char *target = resolve(lp, filename, &retarget, &frametgt);
		free(frametgt);

		if (target) {
		    if (debugging)
			printf("FOUND!\n");
		    addAttribute(node, "href", target);
		    resolved++;
		    free(target);
		} else {
		    xmlNode *iter = node->children, *tailnode;

		    /* We couldn't resolve this live link.
		       turn it back into a comment, inserting
		       a close comment tag after its last child
		       and reparenting its children as children
		       of its parent (as its "next" node). */

		    char *full_filename = malloc(PATH_MAX * sizeof(char));
		    char *printed_filename = full_filename;

		     if (!full_filename) {
			fprintf(stderr, "Out of memory in resolveLinks[3]\n");
			exit(-1);
		     }
		    // Let's get full file name to display error messages containing full path to file
		    if (!realpath(filename, full_filename)) // Not a leak; returns pointer into full_filename.
			printed_filename = filename;

		    free(full_filename); 

		    if (warn_each)
			fprintf(ERRS,
				"%s:0: error: unable to resolve link %s.\n",
				printed_filename, lp);

		    unresolved++;

		    char *machineGenerated = proptext("machineGenerated", node->properties);
		    if (!machineGenerated || strcmp(machineGenerated, "true")) {
			/* Oops.  It's a user-entered link. */
			unresolved_explicit++;
		    }
		    if (debugging) printf("machineGenerated: %s\n", machineGenerated ? machineGenerated : "(null)");

		    openlogfile();
		    if (logfile) {
			if ((!machineGenerated) || (strcmp(machineGenerated, "true"))) {
			    fprintf(logfile, "Unresolved: %s %s\n", printed_filename, lp);
			} else {
			    fprintf(logfile, "Unresolved (machine-generated): %s %s\n", printed_filename, lp);
			}
		    }
		    if (machineGenerated) free(machineGenerated);

		    if (debugging)
			printf("Disabling link\n");



		    tailnode = xmlNewComment((xmlChar *)(" /a ")); // malloc(sizeof(*tailnode)); // This gets freed with the tree.
		    if (!tailnode) {
			fprintf(stderr, "Out of memory in resolveLinks[4]\n");
			exit(-1);
		    }
		    // bzero(tailnode, sizeof(*tailnode));

		    xmlNode *headnode = node;

		    // fprintf(stderr, "HERE: Node is %p, children is %p\n", node, node->children);

		    while (iter && iter->next) {
			xmlNode *next = iter->next;

			// fprintf(stderr, "Adding node %s after %s\n", iter->content, node->content);

			xmlUnlinkNode(iter);
			xmlAddNextSibling(node, iter);
			//iter->parent = node->parent;
			//iter = iter->next;
			if (debug_reparent) {
			    printf("REPARENTING: \n");
			    writeFile_sub(node, dp, stdout, 1);
			    printf("DONE REPARENTING\n");
			}
			node = iter;
			iter = next;
		    }
		    /* Iter now contains the last child of node */
		    if (iter) {
			// fprintf(stderr, "ITER: %p\n", iter);
			xmlUnlinkNode(iter);
			if (!xmlAddNextSibling(node, iter)) {
				fprintf(stderr, "Add Next failed!\n");
			}
			node = iter;
		    }

		    xmlAddNextSibling(node, tailnode);

		    node = headnode;

		    char *tempPropString = propString(headnode);
		    xmlNode *newhead = xmlNewComment((xmlChar *)tempPropString);
		    free(tempPropString);

		    // tailnode->next = node->next;

		    // fprintf(stderr, "DEBUG OUT:\n");
		    // writeFile_sub(xmlDocGetRootElement(dp), dp, stderr, 1);
		    // fprintf(stderr, "END DEBUG OUT\n");

		    if (!xmlReplaceNode(node, newhead)) {
			fprintf(stderr, "Replace failed!\n");
			exit(-1);
		    }
		    xmlFreeNode(node);
		    node = newhead;
		    
		    // xmlNodeSetName(node, (xmlChar *)("comment"));
		    // node->name = (unsigned char *) strdup("comment");

		    // node->type = XML_COMMENT_NODE;

		    // xmlNodeSetName(tailnode, (xmlChar *)("comment"));
		    // tailnode->name = (unsigned char *) strdup("comment");
		    // tailnode->type = XML_COMMENT_NODE;
		    // if (tailnode->content) free(tailnode->content);
		    // tailnode->content = (unsigned char *) strdup(" /a ");
		    // if (node->content) free(node->content);
		    // node->content = (unsigned char *) propString(node);

		    if (debugging)
			printf("PS: \"%s\"\n", node->content);
		}
		free(lp);
	    } else {
		if (debugging)
		    printf("Not a logicalPath link.  Skipping.\n");
		plain++;
	    }
	} else {
	    if (debugging) {
		printf("%s: \"%s\"\n", node->name, node->content);
	    }
	}

	resolveLinks(node->children, dp, filename, filename_rp);
	// resolveLinks(node->next, dp, filename, filename_rp);
	node = node->next;
    }
}

/*! @abstract Returns true if the two nodes are at the same level in the XML
    parse tree, else false.
 */
int onSameLevel(xmlNode * a, xmlNode * b)
{
    xmlNode *iter;

    if (a == b)
	return -1;

    for (iter = a->prev; iter; iter = iter->prev) {
	if (iter == b) {
	    return 1;
	}
    }
    for (iter = a->next; iter; iter = iter->next) {
	if (iter == b) {
	    return 1;
	}
    }

    return 0;
}

/*! @abstract Writes an HTML parse tree to a file on disk. */
void writeFile(xmlNode * node, htmlDocPtr dp, char *filename)
{
#ifdef OLD_CODE
    FILE *fp;

    if (debugging)
	writeFile_sub(node, dp, stdout, 0);
    if (!((fp = fopen(filename, "w")))) {
	fprintf(ERRS, "error: could not open file %s for writing\n",
		filename);
	exit(-1);
    }

    writeFile_sub(node, dp, fp, 0);

    fclose(fp);
#else

    // xmlNode *root = xmlDocGetRootElement(dp);
    // checkDoc(root, NULL, NULL, dp);

    if (!htmlGetMetaEncoding(dp)) {
	// fprintf(stderr, "No encoding.  Setting to ascii\n");
	htmlSetMetaEncoding(dp, (unsigned char *) "ascii");
    }
    // fprintf(stderr, "META ENCODING PRE: %s\n", htmlGetMetaEncoding(dp));

    // BUGBUGBUG: libxml2 actually writes HTML into the encoding field in the
    // meta tag, resulting in an unreadable HTML file.
    // int ret = htmlSaveFileEnc(filename, dp, "HTML");

    int ret = htmlSaveFile(filename, dp);

    // printf("META ENCODING: %s\n", htmlGetMetaEncoding(dp));

    if (ret <= 0) {
	fprintf(ERRS, "Failed to save file \"%s\"\n", filename);
	perror("xmlSaveFile");

	fprintf(stderr, "PRINTING TREE DUMP\n");

	writeFile_sub(xmlDocGetRootElement(dp), dp, stdout, 1);

	exit(-1);
    }
#endif
}

/*! @abstract Returns a string containing the text representation of a node's properties.
 */
char *propString(xmlNode * node)
{
    xmlAttr *prop = node->properties;

    char *propstring;

    size_t len = 1;

    while (prop) {
	// fprintf(fp, " %s=\"%s\"", prop->name, prop->children->content);
	if (prop->children) {
	    len +=
		strlen((char *) prop->name) +
		strlen((char *) prop->children->content) + 4;
	}
	prop = prop->next;
    }
    len += 5;
    propstring = malloc(len * sizeof(char));
    if (!propstring) {
	fprintf(stderr, "Out of memory in propString\n");
	exit(-1);
    }

    strlcpy(propstring, "a", len);

    prop = node->properties;
    while (prop) {
	if (prop->children) {
	    strlcat(propstring, " ", len);
	    strlcat(propstring, (char *) prop->name, len);
	    strlcat(propstring, "=\"", len);
	    strlcat(propstring, (char *) prop->children->content, len);
	    strlcat(propstring, "\"", len);
	}

	prop = prop->next;
    }

    // printf("propstring was %s\n", propstring);

    return propstring;
}

/*! @abstract Writes a node's properties to a file. */
void writeProps(xmlNode * node, FILE * fp)
{
    xmlAttr *prop = node->properties;

    while (prop) {
	if (prop->children) {
	    fprintf(fp, " %s=\"%s\"", prop->name, prop->children->content);
	}
	prop = prop->next;
    }

}

/*! @abstract
        Walks a tree recursively and writes an HTML parse tree to disk.
    @discussion
        Used by {@link writeFile}.
 */
void writeFile_sub(xmlNode * node, htmlDocPtr dp, FILE * fp,
		   int this_node_and_children_only)
{
    if (!node)
	return;

    // if (debugging) printf("DP: 0x%x NODE: 0x%x\n", (long)dp, (long)node);
    // fprintf(fp, "RS: %s\n", xmlNodeListGetRawString(dp, node, 0));

    if (node->name) {
	if (!strcmp((char *) node->name, "text")) {
	    char *temp = xmlNodeGetRawString(dp, node, 0);
	    fprintf(fp, "%s", temp);
	    free(temp);
	} else if (!strcmp((char *) node->name, "comment")) {
	    fprintf(fp, "<!--%s-->", node->content);
	} else {
	    fprintf(fp, "<%s", node->name);
	    writeProps(node, fp);
	    fprintf(fp, ">");
	}
    }

    writeFile_sub(node->children, dp, fp, 0);

    if (strcmp((char *) node->name, "text")
	&& strcmp((char *) node->name, "comment")) {
	fprintf(fp, "</%s>", node->name);
    }

    if (!this_node_and_children_only)
	writeFile_sub(node->next, dp, fp, 0);
    return;
}

/*! @brief
    Returns the first node whose element name matches a given node.

    If <code>recurse</code> is false, it begins at the current node and moves
    sideways across the parse tree to the next node until it runs
    out of nodes.

    If <code>recurse</code> is true, it processes all of the children of each node
    before moving across to the next one.

    This function is part of the xml2man library code and is not used in
    this tool.
    @apiuid //apple_ref/c/func/resolvelinks_nodematching
 */
xmlNode *nodematching(char *name, xmlNode * cur, int recurse)
{
    xmlNode *temp = NULL;

    while (cur) {
	if (!cur->name)
	    break;
	if (!strcasecmp((char *) cur->name, name))
	    break;
	if (recurse) {
	    if ((temp = nodematching(name, cur->children, recurse))) {
		return temp;
	    }
	}
	cur = cur->next;
    }

    return cur;
}

/*! @brief
    Returns the text contents of the first node whose element name matches a given node.

    This function is related to {@link //apple_ref/c/func/resolvelinks_nodematching nodematching}.

    If the node name is "text" or "comment", it returns the text of the
    node itself.  Otherwise, it returns the text of the node's first
    child.

    This function is part of the xml2man library code and is not used in
    this tool.
    @apiuid //apple_ref/c/func/resolvelinks_textmatching
 */
char *textmatching(char *name, xmlNode * node, int missing_ok, int recurse)
{
    xmlNode *cur = nodematching(name, node, recurse);

    char *ret = NULL;

    if (!cur) {
	if (!missing_ok) {
	    fprintf(ERRS, "Invalid or missing contents for %s.\n", name);
	}
    } else if (cur && cur->children && cur->children->content) {
	ret = (char *) cur->children->content;
    } else if (!strcasecmp(name, "text")) {
	ret = (char *) cur->content;
    } else {
	fprintf(ERRS, "Missing/invalid contents for %s.\n", name);
    }

    return ret;
}

/*! @abstract
	Returns the text contents of a named attribute
	in a list of attributes.
    @result
	Returns an object that must be released with a call to <code>free()</code>.
 */
char *proptext(char *name, struct _xmlAttr *prop)
{
    for (; prop; prop = prop->next) {
	if (!strcasecmp((char *) prop->name, name)) {
	    if (prop->children && prop->children->content) {
// printf("CHILD: %s\n", prop->children->content);
		return strdup((char *) prop->children->content);
	    }
	}
    }
    return NULL;
}

/*! @brief Returns the value of a numeric property.

    This function is part of the xml2man library code and is not used in
    this tool.
    @apiuid //apple_ref/c/func/resolvelinks_propval
 */
int propval(char *name, struct _xmlAttr *prop)
{
    for (; prop; prop = prop->next) {
	if (!strcasecmp((char *) prop->name, name)) {
	    if (prop->children && prop->children->content) {
		return atoi((char *) prop->children->content);
	    }
	}
    }
    /* Assume 0 */
    return 0;
}

/*!
    @abstract
        A function that throws away parse errors without spewing warnings.
 */
void quietErrors(void *userData, xmlErrorPtr error)
{

}

/*!
    @brief Searches a parse tree for the first anchor.
 */
xmlNode *findAnchor(xmlNode *node)
{
	// fprintf(stderr, "NODEID: %p\n", node);

	if (!node) return NULL;

	// fprintf(stderr, "NODE: %s\n", node->name);

	if (node->name && !strcmp((char *) node->name, "a")) {
		return node;
	}

	xmlNode *temp = findAnchor(node->children);
	if (temp) return temp;

	return findAnchor(node->next);
}

/*!
    @brief Returns a parsed XML node for a commented-out link.
 */
xmlNode *xmlNodeForComment(xmlChar *commentguts, htmlDocPtr parentDoc)
{
	char *data = NULL;

	if (!commentguts) return NULL;

	while ((*commentguts == ' ') || (*commentguts == '\t')) {
		commentguts++;
	}

	safe_asprintf(&data, "<html><body><p><%s></a>", commentguts);
	if (!data) { fprintf(stderr, "Out of memory.\n"); exit(1); }

	// fprintf(stderr, "DATA: %s\n", data);

	char *encoding = (char *)htmlGetMetaEncoding(parentDoc);
	if (!encoding) encoding = "ascii";

	htmlParserCtxtPtr context = htmlCreateMemoryParserCtxt((char *)"<p></p>", 7);
	xmlSetStructuredErrorFunc(context, quietErrors);

	htmlDocPtr dp = htmlCtxtReadDoc(context, (xmlChar *)data, "", encoding, HTML_PARSE_NOWARNING|HTML_PARSE_NOERROR | HTML_PARSE_RECOVER);

	// htmlDocPtr dp = htmlParseDoc((xmlChar *)data, encoding);

	// fprintf(stderr, "DP: %p\n", dp);
	// fprintf(stderr, "ROOT: %p\n", xmlDocGetRootElement(dp));

	xmlNode *node = findAnchor(xmlDocGetRootElement(dp));

	xmlNode *retval = node ? xmlCopyNode(node, 2) : NULL;

	xmlFreeDoc(dp);
	free(data);

	htmlFreeParserCtxt(context);

	return retval;
}

/*! @brief
    Checks a comment to see if it looks like a commented-out anchor with
    a logicalPath attribute.
 */
int isCommentedAnchor(char *commentString)
{
    char *ptr = commentString;

    if (!commentString)
	return 0;

// printf("commentString: %s\n", commentString);

    while (*ptr == ' ' || *ptr == '\t' || *ptr == '\r' || *ptr == '\n')
	ptr++;
    if (*ptr != 'a')
	return 0;
    ptr++;
    while (*ptr == ' ' || *ptr == '\t' || *ptr == '\r' || *ptr == '\n')
	ptr++;

    return strstr(ptr, "logicalPath") ? 1 : 0;
}


/*! @abstract
	Gets the raw text (with entities intact) for a
	single node.
    @discussion
	This is similar to <code>xmlNodeListGetRawString</code> (part of
	libxml) except that it does not traverse the node tree.
 */
char *xmlNodeGetRawString(htmlDocPtr dp, xmlNode * node, int whatever)
{
    xmlNode copynode;

    bcopy(node, &copynode, sizeof(copynode));
    copynode.next = NULL;

    return (char *) xmlNodeListGetRawString(dp, &copynode, whatever);
}


/*! @abstract
	Divides an API reference into some of its constituent parts.
    @returns
	Returns the parts in a dynamically allocated {@link refparts_t}
	structure that must be released with a call to <code>free()</code>.
 */
refparts_t getrefparts(char *origref, int parts)
{
    char *refpart = origref;

    char *langpart;

    char *rest;

    char *refpartcopy = NULL, *langpartcopy = NULL;

    struct refparts *retval = malloc(sizeof(*retval));

    int refpartlen = 0, langpartlen = 0;


    if (origref[0] != '/' || origref[0] != '/') {
	free(retval);
	return NULL;
    }

    refpart += 2;
    langpart = refpart;
    while (*langpart && *langpart != '/') {
	langpart++;
	refpartlen++;
    }
    langpart++;
    rest = langpart;
    while (*rest && *rest != '/') {
	rest++;
	langpartlen++;
    }
    rest++;

    if (!strlen(refpart) || !strlen(langpart) || !strlen(rest)) {
	fprintf(ERRS,
		"warning: malformed apple_ref %s has less than three parts.\n",
		origref);
	free(retval);
	return NULL;
    }

    if (parts & PART_REF) {
	refpartcopy = malloc((refpartlen + 1) * sizeof(char)); // released with object.
	if (!refpartcopy) {
		fprintf(stderr, "Out of memory in getrefparts[1]\n");
		exit(-1);
	}
	strncpy(refpartcopy, refpart, refpartlen);
	refpartcopy[refpartlen] = '\0';
	retval->refpart = refpartcopy;
    } else {
	retval->refpart = NULL;
    }

    if (parts & PART_LANG) {
	langpartcopy = malloc((langpartlen + 1) * sizeof(char)); // released with object.
	if (!langpartcopy) {
		fprintf(stderr, "Out of memory in getrefparts[2]\n");
		exit(-1);
	}
	strncpy(langpartcopy, langpart, langpartlen);
	langpartcopy[langpartlen] = '\0';
	retval->langpart = langpartcopy;
    } else {
	retval->langpart = NULL;
    }

    // printf("LPC: \"%s\"\n", langpartcopy);

    if (parts & PART_REST) {
	safe_asprintf(&retval->rest, "%s", rest);
	if (!retval->rest) { fprintf(stderr, "Out of memory.\n"); exit(1); }
    } else {
	retval->rest = NULL;
    }

    return retval;
}

/*! @abstract Rewrites an API reference, changing the
    <code>apple_ref</code> bit to the external ref specified by
    <code>extref</code>.
 */
char *refRefChange(char *ref, char *extref)
{
    refparts_t rp = getrefparts(ref, PART_ALL);

    char *langpart, *rest, *retval;

    // int length;

    if (!rp)
	return NULL;
    langpart = rp->langpart;
    rest = rp->rest;

    // printf("LANGPART: \"%s\"\n", rp->langpart);

    /* length = "//" + refpart + "/" + "C" + "/" + rest + "\0" */
    //length = 2 + strlen(extref) + 1 + strlen(langpart) + 1 + strlen(rest) + 1;

    //retval = malloc((length + 1) * sizeof(char));
    //if (!retval) {
	//fprintf(stderr, "Out of memory in refRefChange\n");
	//exit(-1);
    //}
    // snprintf(retval, (length + 1), "//%s/%s/%s", extref, langpart, rest);

    safe_asprintf(&retval, "//%s/%s/%s", extref, langpart, rest);
    if (!retval) { fprintf(stderr, "Out of memory.\n"); exit(1); }

    refpartsfree(rp);

    return retval;
}

/*! @abstract Takes an API reference and rewrites it, changing the
    language to the language specified by <code>lang</code>.
 */
char *refLangChange(char *ref, char *lang)
{
    refparts_t rp = getrefparts(ref, PART_ALL);

    char *refpart, *rest, *retval;

    // int length;

    if (!rp)
	return NULL;
    refpart = rp->refpart;
    rest = rp->rest;

    // printf("LANGPART: \"%s\"\n", rp->langpart);

    /* length = "//" + refpart + "/" + "C" + "/" + rest + "\0" */
    // length = 2 + strlen(refpart) + 1 + strlen(lang) + 1 + strlen(rest) + 1;

    safe_asprintf(&retval, "//%s/%s/%s", refpart, lang, rest); // returned
    if (!retval) {
	fprintf(stderr, "Out of memory in refLangChange\n");
	exit(-1);
    }

    refpartsfree(rp);
    return retval;
}

/*! @abstract Takes a filename (absolute paths only) and an anchor within that file
    and concatenates them into a full URL.
 */
char *makeurl(char *rawfilename, char *offset, int retarget, int relativeToInput)
{
#if 1
    char *filename = rawfilename;
    char *buf = NULL;
    char *relpath_var = NULL;

    if (relativeToInput) {
	char *realpath_var = strdup(rawfilename); // realpath(rawfilename, NULL);
	relpath_var = relpath(realpath_var, inputDirectory, 1);
	free(realpath_var);

	filename = relpath_var;
    }

    char *dir = ts_dirname(filename);

    char *base = ts_basename(filename);

    char *updir = ts_dirname(dir);

    char *upbase = ts_basename(dir);

    char *indexpath = NULL;

    // int len = (strlen(filename)+strlen(offset)+2) * sizeof(char);

    // printf("FILENAME IN makeurl: %s\n", rawfilename);

    if (isURI(rawfilename)) {
	if (relpath_var) free(relpath_var);
	free(dir);
	free(base);
	free(updir);
	free(upbase);
	return strdup(rawfilename);
    }

    if (debugging)
	printf("RETARGET (INITIAL): %d\n", retarget);

    safe_asprintf(&indexpath, "%s/index.html", updir);
    if (!indexpath) { fprintf(stderr, "Out of memory.\n"); exit(1); }
    if (retarget
	&& (!strcmp(base, "index.html")
	    || !strcmp(base, "CompositePage.html"))) {
	if (debugging)
	    printf("Going to an index.html file.  Not retargetting.\n");
	if (debugging)
	    printf("FILENAME: %s\nOFFSET: %s\n", filename, offset);
	retarget = 0;
	if (indexpath) free(indexpath);
	indexpath = NULL;
    }

    if (retarget && !ishdindex(indexpath)) {
	if (debugging)
	    fprintf(stderr, "\nNo index found at %s.\n", indexpath);
	if (debugging)
	    fprintf(stderr,
		    "DIR: %s\nBASE: %s\nUPDIR: %s\nUPBASE: %s\nORIG_FILENAME: %s\n",
		    dir, base, updir, upbase, filename);
	retarget = 0;
	if (indexpath) free(indexpath);
	indexpath = NULL;
    }
    // if (retarget) {
    // len = strlen(indexpath) + 1 /*?*/ + strlen(upbase) + 1 /*/*/ + strlen(base) + 1 /*#*/ + strlen(offset) + 1 /*NULL*/;
    // }

    // buf = malloc(len);

    // if (!buf) return "BROKEN";

    if (debugging)
	printf("RETARGET: %d\n", retarget);

    if (retarget) {
	safe_asprintf(&buf, "%s?%s/%s#%s", indexpath, upbase, base, offset);
	if (!buf) { fprintf(stderr, "Out of memory.\n"); exit(1); }
    } else {
	safe_asprintf(&buf, "%s#%s", filename, offset);
	if (!buf) { fprintf(stderr, "Out of memory.\n"); exit(1); }
	// strcpy(buf, filename);
	// strcat(buf, "#");
	// strcat(buf, offset);
    }

    free(dir);
    free(base);
    free(updir);
    free(upbase);
    if (indexpath) free(indexpath);

    if (relpath_var) free(relpath_var);
    return buf;
#else
    return strdup(filename);
#endif
}

/*!
    @abstract
        Returns the number of leading path parts that match.
    @discussion
	This is used to determine which of multiple possible link
	destinations is the closest match (for resolving
	API reference symbol conflicts).
    @param a
	The path of the candidate link destination.
    @param b
	The path of the file the link itself is in.
    @param isbelowme
        On return, contains 1 if the match ended early because the
	candidate destination (<code>a</code>) is deeper in the hierarchy than
        the source (<code>b</code>).
 */
int matchingPathParts(char *a, char *b, int *isbelowme)
{
    char *aiter, *biter;

    int found;

    /* Remove leading ./ and any extra slashes after it. */
    found = 0;
    for (aiter = a; *aiter;) {
	if (*aiter == '.' && *(aiter + 1) == '/') {
	    aiter += 2;
	    found = 1;
	} else if (found && *aiter == '/') {
	    aiter++;
	} else
	    break;
    }
    found = 0;
    for (biter = b; *biter;) {
	if (*biter == '.' && *(biter + 1) == '/') {
	    biter += 2;
	    found = 1;
	} else if (found && *biter == '/') {
	    biter++;
	} else
	    break;
    }

    found = 0;
    for (;; aiter++, biter++) {
	// printf("CMP: %c %c\n", *aiter, *biter);
	if (*aiter == '/' && !(*biter)) {
	    char *aiterx;

	    int moreslashes = 0;

	    for (aiterx = aiter + 1; *aiterx; aiterx++) {
		if (*aiterx == '/')
		    moreslashes = 1;
	    }
	    *isbelowme = !moreslashes;
	    // printf("SlashEnd-1\n");
	    found++;
	}
	if (!(*aiter) && *biter == '/') {
	    char *biterx;

	    int moreslashes = 0;

	    for (biterx = biter + 1; *biterx; biterx++) {
		if (*biterx == '/')
		    moreslashes = 1;
	    }
	    *isbelowme = !moreslashes;
	    // printf("SlashEnd-2\n");
	    found++;
	}
	if (*aiter != *biter) {
	    // printf("NE\n");
	    break;
	}
	if (*aiter == '/') {
	    // printf("Slash\n");
	    found++;
	}
	if (!(*aiter && *biter))
	    break;
    }

    // printf("Compare: %s to %s: %d\n", a, b, found);
    return found;
}


/*! @abstract Searches for an xref in the xref tree,
    returning the filename and anchor within that file, concatenated using
    {@link makeurl}.
 */
searchobj_t searchref(char *xref, xrefnode_t tree, int retarget, char *basepath)
{
    int pos;

    if (!tree)
	return NULL;

    while (tree) {
	int partial_match = 0;

	pos = strcmp(xref, tree->xref);

	if (!global_option_disable_partial_ref_matching) {
	    if (pos) { // Don't calculcate unnecessarily.
		partial_match = ispartialmatch(xref, tree->xref);
	    }
	}

	if (partial_match || !pos) {
		/* We found one.  Find the closest match. */
		xrefnode_t iter, maxmatchnode = NULL, maxmatchnode_nocache = NULL;
	
		int maxmatch = -1;
	
		int maxmatch_nocache = -1;
	
		for (iter = tree; iter; iter = iter->dup) {
		    int belowme;
	
		    int match = matchingPathParts(nodepath(iter), basepath, &belowme);
	
		    // printf("Checking %s: ", nodepath(iter));
		    if (match > maxmatch || (match == maxmatch && belowme)) {
			// printf("%d is better than %d.  ", match, maxmatch);
			maxmatch = match;
			maxmatchnode = iter;
		    }
		    if (!iter->fromseed) {
			if (match > maxmatch_nocache
			    || (match == maxmatch_nocache && belowme)) {
			    // printf("Ooh.  a real one!  ");
			    maxmatch_nocache = match;
			    maxmatchnode_nocache = iter;
			}
		    }
		    // printf("\n");
		}
		if (maxmatchnode)
		    tree = maxmatchnode;
		if (maxmatchnode_nocache)
		    tree = maxmatchnode_nocache;

		searchobj_t retval = malloc(sizeof(*retval)); // returned

		/* If not from cache, strip leading path parts.. */
		retval->uri = makeurl(nodepath(tree), tree->xref, retarget, (tree->basepath[0] ? 0 : 1));
		retval->obj = tree;

		return retval;
	} else if (pos < 0) {
		/* We go left */
		// return searchref(xref, tree->left, retarget, basepath);
		tree = tree->left;
	} else {
		/* We go right */
		// return searchref(xref, tree->right, retarget, basepath);
		tree = tree->right;
	}
    }
    return NULL;
}

/*! @abstract Attempts to resolve a space-delimited list of
    cross-references, allowing language fallback (c++ to C, etc.),
    and returns the URL associated with it.
 */
char *resolve(char *xref, char *filename, int *retarget, char **frametgt)
{
    searchobj_t targetobj = NULL;
    char *curref = xref;

    char *dn = ts_dirname(filename);
    char *basepath = realpath(dn, NULL);

    char *writeptr = xref;

    free(dn);

    if (!xref) {
	free(basepath);
	return NULL;
    }

    while (writeptr) {
	while (*writeptr && *writeptr != ' ')
	    writeptr++;
	if (*writeptr == ' ')
	    *writeptr = '\0';
	else
	    writeptr = 0;

	if (strlen(curref)) {
	    char *altLangRef1 = NULL;

	    char *altLangRef2 = NULL;

	    char *altLangRef3 = NULL;

	    refparts_t rp;

	    int i;

	    if (debugging) {
		printf("SEARCHING FOR \"%s\"\n", curref);
	    }

	    for (i = -1; i <= nextrefs; i++) {
		char *newcurref = NULL;
		int freenewcurref = 0;

		char *refpart;

		if ((rp = getrefparts(curref, PART_ALL))) {
		    int isanysymbol = 0;

		    if (!global_option_disable_name_matching) {
			if (!strncmp(rp->rest, "anysymbol/", 10)) {
			    xrefnode_t temp = refByName(rp->rest + 10, basepath);
			    if (temp && temp->xref) {
				isanysymbol = 1;
				safe_asprintf(&newcurref, "%s", temp->xref);
				if (!newcurref) { fprintf(stderr, "Out of memory.\n"); exit(1); }
				freenewcurref = 1;
			    }
			}
		    }
		    if (!isanysymbol) {
		        if (i == nextrefs) {
			    refpart = strdup("apple_ref");
		        } else if (i == -1) {
			    refpart = strdup(rp->refpart);
		        } else {
			    refpart = strdup(extrefs[i]);
		        }
		        newcurref = refRefChange(curref, refpart);
			free(refpart);
			freenewcurref = 1;

		        if (!strcmp(rp->langpart, "cpp") ||
			    !strcmp(rp->langpart, "occ") ||
			    !strcmp(rp->langpart, "C")) {
			    altLangRef1 = refLangChange(newcurref, "c");
			    altLangRef2 = refLangChange(newcurref, "cpp");
			    altLangRef3 = refLangChange(newcurref, "occ");
		        }
		    }
		    refpartsfree(rp);
		} else {
		    newcurref = curref;
		}

		if (altLangRef1 && debugging) {
		    printf("ALSO SEARCHING FOR \"%s\"\n", altLangRef1);
		}
		if (altLangRef2 && debugging) {
		    printf("ALSO SEARCHING FOR \"%s\"\n", altLangRef2);
		}
		if (altLangRef3 && debugging) {
		    printf("ALSO SEARCHING FOR \"%s\"\n", altLangRef3);
		}

		targetobj =
		    searchref(newcurref, nodehead, *retarget, basepath);

		if ((!targetobj) && altLangRef1)
		    targetobj =
			searchref(altLangRef1, nodehead, *retarget, basepath);

		if ((!targetobj) && altLangRef2)
		    targetobj =
			searchref(altLangRef2, nodehead, *retarget, basepath);
		if ((!targetobj) && altLangRef3)
		    targetobj =
			searchref(altLangRef3, nodehead, *retarget, basepath);

		if (freenewcurref) free(newcurref);
		if (altLangRef1) free(altLangRef1);
		if (altLangRef2) free(altLangRef2);
		if (altLangRef3) free(altLangRef3);

		if (targetobj)
		    break;
	    }

	    if (targetobj) {
		char *target = strdup(targetobj->uri);

		if (debugging)
		    printf("Mapping %s to %s\n", xref, target);
		if (isURI(target)) {
		    *retarget = 1;

		    if (*frametgt) free(*frametgt);

		    *frametgt = strdup("_top");

		    searchfree(targetobj);
		    free(basepath);
		    return target;
		} else {
		    // printf("Target: %s filename %s\n", target, filename);
		    // @@@
		    if (targetobj->obj->fromseed && (targetobj->obj->force_absolute || force_absolute_globally)) {
			char *retval = strdup(target);
			free(targetobj);
			free(basepath);
			return retval;
		    } else if (!targetobj->obj->fromseed) {
			char *abspath = realpath(filename, NULL);
			char *reltoinput = relpath(abspath, inputDirectory, 0);
			char *relpath_var = relpath(target, reltoinput, 0);
			searchfree(targetobj);
			free(abspath);
			free(reltoinput);
			free(basepath);
			return relpath_var;
		    } else {
			char *ip =installedpath(filename);
			char *retval = relpath(target, ip, 0);
			searchfree(targetobj);
			free(ip);
			free(basepath);
			return retval;
		    }
		}
		searchfree(targetobj);
	    }
	}

	if (writeptr) {
	    *writeptr = ' ';
	    writeptr++;
	    curref = writeptr;
	}
    }

    if (debugging)
	printf("Ref not found\n");

    free(basepath);
    return NULL;
}

void nodelist_rec(char *name, xmlNode * cur, struct nodelistitem **nl);

/*! @abstract Recursively descends an HTML parse tree,
    returning a list of nodes matching a given name.
 */
struct nodelistitem *nodelist(char *name, xmlNode * root)
{
    struct nodelistitem *nl = NULL;

    nodelist_rec(name, root, &nl);
    return nl;
}

// int nlr_depth = 0;

/*! @abstract The recursive tree walk subroutine used by
    the {@link nodelist} function.
 */
void nodelist_rec(char *name, xmlNode * cur, struct nodelistitem **nl)
{
    struct nodelistitem *nli = NULL;

    // nlr_depth++;
    // fprintf(stderr, "nodelist_rec depth %d\n", nlr_depth);

    while (cur) {
	if (cur->name && !strcmp((char *) cur->name, name)) {
	    nli = malloc(sizeof(*nli)); // returned through *nl
	    if (nli) {
		nli->node = cur;
		nli->next = *nl;
		nli->prev = NULL;
		if (*nl) {
			(*nl)->prev = nli;
		}
		*nl = nli;
	    } else {
		fprintf(stderr, "Out of memory in nodelist_rec\n");
		exit(-1);
	    }
	}
	nodelist_rec(name, cur->children, nl);
	// nodelist_rec(name, cur->next, nl);
	cur = cur->next;
    }
    // nlr_depth--;
}

/*! @abstract Adds an attribute to an HTML node, deleting
    any preexisting attribute with the same name as it does so.
    @discussion
	This differs from the core XML routines because it performs
	the matching in a casse-insensitive fashion like HTML does.
 */
void addAttribute(xmlNode * node, char *attname, char *attstring)
{
    xmlAttr *properties;
    xmlAttrPtr myprop;

    xmlAttr *delprop = NULL;

    for (properties = node->properties; properties;
	 properties = properties->next) {
	if (delprop) {
	    xmlUnsetProp(node, delprop->name);
	    delprop = NULL;
	}
	if (!strcasecmp((char *) properties->name, attname)) {
	    delprop = properties;
	}
    }
    if (delprop) {
	xmlUnsetProp(node, delprop->name);
    }

    myprop = xmlSetProp(node, (xmlChar *)attname, (xmlChar *)attstring);

    if (!myprop) {
	fprintf(stderr, "resolveLinks: out of memory in addAttribute\n");
	exit(-1);
    }

    // myprop->parent = node;
}

/*! @abstract Returns a list of HTML files within a given directory. */
fileref_t getFiles(char *curPath)
{
    fileref_t rethead = NULL, rettail = NULL;
    DIR *dirp;
    struct dirent *entp;
    int localDebug = 0;
    char *cp = curPath;

    // fprintf(stderr, "curPath: %s\n", curPath);

    // Wow, what a side effect.  If you call basename() and pass the value
    // around (in Linux), then call opendir(), apparently opendir() wipes
    // the block of memory returned by the previous call to basename()!!!!

    dirp = opendir(".");

    // Suddenly empty here.  Oops.
    // fprintf(stderr, "curPath[2]: %s\n", curPath);

    if (!dirp)
	return NULL;

    // fprintf(stderr, "CP[3]: %s\n", curPath);
    // fprintf(stderr, "cp: %s\n", cp);

    curPath = cp;

    while ((entp = readdir(dirp))) {
	/* Ignore the current directory, the parent directory, and
	   version control info directories for CVS, subversion,
	   GIT, Bazaar, and Mercurial. */
	if (entp->d_type == DT_DIR && strcmp(entp->d_name, ".") &&
	    strcmp(entp->d_name, "..") && strcmp(entp->d_name, "CVS") &&
	    strcmp(entp->d_name, ".svn") && strcmp(entp->d_name, ".git") &&
	    strcmp(entp->d_name, ".bzr") && strcmp(entp->d_name, ".hg")) {

	    fileref_t recreturn;

	    char *newpath = NULL;

	    if (!nodot) printf(".");
	    fflush(stdout);

	    if (chdir(entp->d_name)) {
		perror("resolveLinks");
		return NULL;
	    }

	    safe_asprintf(&newpath, "%s/%s", curPath, entp->d_name); // freed later.
	    if (!newpath) {
		perror("resolveLinks");
		return NULL;
	    }

	    if (debugging || localDebug)
		printf("CURPATH: \"%s\" NP: %s\n", curPath, newpath);

	    if (debugging || localDebug)
		printf("Recursing into %s.\n", newpath);

	    recreturn = getFiles(newpath);
	    free(newpath);

	    if (debugging || localDebug)
		printf("Recursing out.\n");

	    if (debugging || localDebug)
		printf("OLD COUNT: %d\n", countfiles(rethead));
	    if (debugging || localDebug)
		printf("INS COUNT: %d\n", countfiles(recreturn));
	    if (rettail) {
		rettail->next = recreturn;
		while (rettail && rettail->next) {
		    rettail = rettail->next;
		    // printf("NEXT\n");
		}
		if (debugging || localDebug)
		    printf("CONCATENATING LISTS\n");
	    } else {
		rethead = rettail = recreturn;
		while (rettail && rettail->next) {
		    rettail = rettail->next;
		    // printf("NEXT\n");
		}
		if (debugging || localDebug)
		    printf("NEW LIST\n");
	    }
	    if (debugging || localDebug)
		printf("NEW COUNT: %d\n", countfiles(rethead));
	    if (chdir("..")) {
		char *tempcwd = getcwd(NULL, MAXPATHLEN);
		fprintf(stderr, "Could not change to directory \"..\" from \"%s\"\n", tempcwd);
		perror("resolveLinks");
		free(tempcwd);
		exit(-1);
	    }
	} else if (tailcompare(entp->d_name, ".htm")
		   || tailcompare(entp->d_name, ".html")
		   || tailcompare(entp->d_name, ".shtml")
		   || tailcompare(entp->d_name, ".shtml")) {

	    if (!nodot) printf(".");
	    fflush(stdout);

	    /* HTML FILE */
	    if (debugging || localDebug)
		printf("HTML FILE %s\n", entp->d_name);
	    fileref_t newent = malloc(sizeof(*newent));

	    if (!newent) {
		perror("resolveLinks");
		exit(-1);
	    }
	    newent->next = NULL;
	    strlcpy(newent->name, curPath, MAXPATHLEN);
	    strlcat(newent->name, "/", MAXPATHLEN);
	    strlcat(newent->name, entp->d_name, MAXPATHLEN);

	    // fprintf(stderr, "newent->name: %s\n", newent->name);

	    if (rettail) {
		rettail->next = newent;
		rettail = newent;
	    } else
		rethead = rettail = newent;

	    if (nthreads) {
		newent->threadnext = threadfiles[nfiles % nthreads];
		threadfiles[nfiles % nthreads] = newent;
	    } else {
		newent->threadnext = threadfiles[0];
		threadfiles[0] = newent;
	    }

	    if (debugging || localDebug)
		printf("NEWCOUNT: %d\n", countfiles(rethead));
	}
    }
    // countfiles(rethead);

    closedir(dirp);
    return rethead;
}

/*! @abstract
        Counts the number of files in a list of files.
    @discussion
        This function is used for debugging purposes.
 */
int countfiles(fileref_t rethead)
{
    fileref_t iter;

    int stagecount = 0;

    for (iter = rethead; iter; iter = iter->next)
	stagecount++;
    // printf("stage: %d\n", stagecount);
    return stagecount;
}

/*! @abstract Compares the end of a filename to a given substring.
    @result
        Returns 1 on a match or 0 on failure.
 */
int tailcompare(char *string, char *tail)
{
    char *pos = &string[strlen(string) - strlen(tail)];

    if (strlen(string) < strlen(tail))
	return 0;

    if (debugging)
	printf("LENGTHS: " FMT_SIZE_T " " FMT_SIZE_T " " FMT_SIZE_T "\n",
	       strlen(string), strlen(tail), strlen(string) - strlen(tail));

    if (debugging)
	printf("Comparing: \"%s\" to \"%s\"\n", pos, tail);

    if (!strcasecmp(pos, tail)) {
	if (debugging)
	    printf("MATCH\n");
	return 1;
    }
    return 0;
}

/*! @abstract Prints cumulative statistics about this run of the tool. */
void print_statistics(void)
{
    int i, nprocessedfiles = 0, ttlreqs = resolved + unresolved + broken;

    if (nthreads) {
	for (i = 0; i < nthreads; i++) {
		nprocessedfiles += thread_processed_files[i];
	}
    } else {
	nprocessedfiles = thread_processed_files[0];
    }

    printf
	("=====================================================================\n");
    printf("  Statistics:\n\n");
    printf("         files: %3d\n", nfiles);
    printf("     processed: %3d\n", nprocessedfiles);
    printf("    total reqs: %3d\n", ttlreqs);
    printf("      resolved: %3d\n", resolved);
    printf("    unresolved: %3d (%d machine-generated, %d explicit)\n", unresolved, (unresolved - unresolved_explicit), unresolved_explicit);
    printf("        broken: %3d\n", broken);
    printf("         plain: %3d\n", plain);
    printf("    duplicates: %3d\n", duplicates);
    printf("         total: %3d\n", plain + broken + resolved + unresolved);

    if ((ttlreqs) && (!nopercent)) {
	float percent = (((float) resolved / (float) ttlreqs) * 100.0f);

	printf("    %% resolved: %f\n", percent);
    }

    if (logname) {
	printf("\nFor a detailed resolver report, see %s\n\n", logname);
    }
    closelogfile();

}

/*!
    @abstract
        Rounds up a number to the nearest 4-byte boundary.
 */
int round4(int k)
{
    return ((k + 3) & ~0x3);
}

#undef malloc
#undef free

/*! @abstract Debug version of <code>malloc</code> that uses guard bytes
    to detect buffer overflows.
 */
void *db_malloc(size_t length)
{
    int *ret = malloc(round4(length) + 12);
    if (!ret) {
	fprintf(stderr, "Out of memory in db_malloc\n");
	exit(-1);
    }

    ret[0] = round4(length);
    ret[1] = 0xdeadbeef;
    ret[(round4(length) / 4) + 2] = 0xdeadbeef;

    ret++;
    ret++;

    return ret;
}

/*! @abstract Debug version of <code>free</code> that uses guard bytes
    to detect buffer overflows.
 */
void db_free(void *ptr)
{
    int *intptr = ptr, *intcheckptr;

    char *tail = ptr;

    intptr--;
    if (*intptr != 0xdeadbeef) {
	printf("warning: freeing region not allocated by db_malloc\n");
	free(ptr);
    } else {
	intptr--;
	tail += (*intptr);
	intcheckptr = (int *) tail;
	// printf("Length was %d\n", *intptr);
	if (*intcheckptr != 0xdeadbeef) {
	    printf("warning: region scribbled off end\n");
	}
	free(intptr);
    }
}

/*!
    @abstract
        Opens a file descriptor to <code>/dev/null</code> for use by
        {@link redirect_stderr_to_null}.
 */
void setup_redirection(void)
{
    int fail = 0, localDebug = 0;

    nullfd = open("/dev/null", (O_RDWR | O_NONBLOCK), 0);
    stderrfd = dup(STDERR_FILENO);

    if (nullfd == -1) {
	fprintf(ERRS, "warning: Could not open /dev/null!\n");
	fail = 1;
    }
    if (stderrfd == -1) {
	fprintf(ERRS, "warning: Could not dup stderr!\n");
	fail = 1;
    }
    if (!fail && localDebug) {
	printf("Dup successful\n");
    }
}

/*!
    @abstract
        Redirects standard error to <code>/dev/null</code>
    @discussion
        Before calling this, you must first call {@link setup_redirection}.
 */
void redirect_stderr_to_null(void)
{
    if ((nullfd != -1) && (stderrfd != -1)) {
	dup2(nullfd, STDERR_FILENO);
    }
}

/*!
    @abstract
        Restores standard error after a call to {@link redirect_stderr_to_null}.
 */
void restore_stderr(void)
{
    if ((nullfd != -1) && (stderrfd != -1)) {
	dup2(stderrfd, STDERR_FILENO);
    }
}

/*!
    @abstract
        Returns an array of indices to parts of a path.
 */
int *partsOfPath(char *path)
{
    /* The number of path parts in a path can't be more than
       half the number of characters because we don't include
       empty path parts. */
    int *list = malloc(((strlen(path) / 2) + 2) * sizeof(int)); // returned.
    if (!list) {
	fprintf(stderr, "Out of memory in partsOfPath\n");
	exit(-1);
    }

    int listpos = 0;

    int pos = 0;

    while (path && path[pos] == '/')
	pos++;
    list[listpos++] = pos;
    while (path && path[pos]) {
	if (path[pos] == '/') {
	    while (path[pos] == '/')
		pos++;

	    list[listpos++] = pos;
	    if (path[pos] == '\0')
		break;
	}
	pos++;
    }

    list[listpos] = -3;
    return list;
}

/*!
    @abstract
        Returns a newly allocated string containing a range of characters from a source string.
 */
char *malloccopypart(char *source, int start, int length)
{
    char *ret = malloc((length + 1) * sizeof(char)); // returned.
    if (!ret) {
	fprintf(stderr, "Out of memory in malloccopypart\n");
	exit(-1);
    }

    strncpy(ret, &source[start], length);
    ret[length] = '\0';
    return ret;
}

/*!
    @abstract
        Truncates a link at the first hash mark (<code>#</code>) in a
        jump link and returns its position (or <code>-1</code> if not found).
 */
int getHashPos(char *a)
{
	char *pos = strstr(a, "#");
	if (pos) {
		*pos = '\0'; // Null terminate at this point.
		return pos - a;
	}
	return -1;
}

/*! @abstract
	Generates a relative path from one file to another.
    @discussion
	The value returned is the relative path of the file specified
	by <code>target</code> with respect to the file specified by
	<code>fromFile</code>.

	@important
	Both files must be either relative or absolute paths; mixing
	absolute and relative paths in the same call is not allowed.
    @result
	Returns the relative path in a newly allocated chunk of memory
	that must be released with <code>free()</code>.
 */
char *relpath(char *target, char *fromFile, int isDir)
{
    char *iter = fromFile;

    int pathparts = 0;

    size_t alloc_len;

    // int fromLen = strlen(fromFile);
    // int fromhashpos = getHashPos(fromFile);
    // int targetLen = strlen(target);
    // int targethashpos = getHashPos(target);
    int *base_pathparts = partsOfPath(fromFile);
    int *target_pathparts = partsOfPath(target);

    int i;

    int startpos_in_target = 0;

    size_t fromFileLen = strlen(fromFile);
    size_t targetLen = strlen(target);

    for (i = 0; ((base_pathparts[i] != -3) && (target_pathparts[i] != -3));
	 i++) {
	int start_of_base = base_pathparts[i];

	int start_of_target = target_pathparts[i];

	int end_of_base =
	    (base_pathparts[i + 1] ==
	    -3) ? fromFileLen : base_pathparts[i + 1];
	int end_of_target =
	    (target_pathparts[i + 1] ==
	    -3) ? targetLen : target_pathparts[i + 1];
	char *basepart, *targetpart;

	/* Sadly, asnprintf isn't widely available. */
	basepart =
	    malloccopypart(fromFile, start_of_base,
			   end_of_base - start_of_base);
	targetpart =
	    malloccopypart(target, start_of_target,
			   end_of_target - start_of_target);

	size_t basepartLen = end_of_base - start_of_base;
	size_t targetpartLen = end_of_target - start_of_target;

	while (basepartLen && (basepart[basepartLen - 1] == '/'))
	    basepart[basepartLen-- - 1] = '\0';

	while (targetpartLen && (targetpart[targetpartLen - 1] == '/'))
	    targetpart[targetpartLen-- - 1] = '\0';

	startpos_in_target = start_of_target;

	if (strcmp(basepart, targetpart)) {
	    int j;

	    for (j = i; base_pathparts[j] != -3; j++);	// empty loop

	    pathparts = j - i - 1;	// Subtract 1 for filename.

	    free(basepart);
	    free(targetpart);
	    break;
	}

	free(basepart);
	free(targetpart);
    }
    free(base_pathparts);
    free(target_pathparts);

    // while (*iter) {
    // if (*iter == '/') pathparts++;
    // iter++;
    // }

    alloc_len = ((targetLen + (3 * pathparts) + 1) * sizeof(char));
    iter = malloc(alloc_len); // returned
    if (!iter) {
	fprintf(stderr, "Out of memory in relpath\n");
	exit(-1);
    }
    iter[0] = '\0';
    while (pathparts) {
	strlcat(iter, "../", alloc_len);
	pathparts--;
    }
    strlcat(iter, &target[startpos_in_target], alloc_len);

    if (debug_relpath) {
	printf("OP: %s\nFN: %s\nRP: %s\n", target, fromFile, iter);
    }

    return fixpath(iter);
}

/*! @brief
    Fixes a path by removing double slashes and trailing slashes
    from a path.

    NOTE: This function has a side-effect.  The string passed via the
    name argument is modified in place.  Since it can only shrink, not grow,
    it wasn't worth the potential for memory leaks to avoid this
    side effect.
 */
char *fixpath(char *name)
{
    char *from_iter = name, *to_iter;

    char *lastc = NULL;

    /* int abspath = 0; */

    int inhashorquery = 0;

    if (!name) return NULL;

/*
    if (name && (*name == '/')) {
	abspath = 1;
	// printf("ABSPATH\n");
	// } else {
	// printf("RELPATH\n");
    }
 */

    /* Remove double slashes */
    to_iter = name;
    while (from_iter && *from_iter && *to_iter) {
	if (*from_iter == '#' || *from_iter == '?') {
		inhashorquery = 1;
	}
	// printf("CHAR %c\n", *from_iter);
	if (!inhashorquery) {
		while (*from_iter == '/' && lastc && *lastc == '/') {
		    // printf("Skipping slash\n");
		    from_iter++;
		}
	}
	if (from_iter != to_iter) *to_iter = *from_iter;

	lastc = to_iter;
	from_iter++;
	to_iter++;
    }
    *to_iter = '\0';

    /* Remove trailing slash */
    if (lastc && (*lastc == '/'))
	*lastc = '\0';

    return name;
}

/*!
    @abstract
        Returns whether an HTML node has a valid <code>target</code> property set
        (but not if the <code>retarget</code> property is also set).
 */
int has_target(xmlNode * node)
{
    char *target = proptext("target", node->properties);

    char *retarget = proptext("retarget", node->properties);

    if (retarget && !strcasecmp(retarget, "yes")) {
	if (target) free(target);
	if (retarget) free(retarget);
	return 0;
    }
    if (target && (target[0] != '\0')) {
	free(target);
	return 1;
    }

    return 0;
}

/*!
    @abstract
        Thread-safe wrapper for <code>dirname</code> function.
 */
char *ts_dirname(char *path)
{
    static pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;

    char *orig = NULL, *copy = NULL, *junk = NULL;

    while (pthread_mutex_lock(&mylock));

    // junk = malloc((strlen(path) + 1) * sizeof(char));
    // strcpy(junk, path);

    safe_asprintf(&junk, "%s", path); // freed later.
    if (!junk) { fprintf(stderr, "Out of memory.\n"); exit(1); }

    orig = dirname(junk);

    // copy = malloc((strlen(orig) + 1) * sizeof(char));
    // strcpy(copy, orig);

    safe_asprintf(&copy, "%s", orig); // returned.
    if (!copy) { fprintf(stderr, "Out of memory.\n"); exit(1); }
    free(junk);

    while (pthread_mutex_unlock(&mylock));

    return copy;
}

/*!
    @abstract
        Thread-safe wrapper for <code>basename</code> function.
 */
char *ts_basename(char *path)
{
    static pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;

    char *orig = NULL, *copy = NULL, *junk = NULL;

    while (pthread_mutex_lock(&mylock));

    // junk = malloc((strlen(path) + 1) * sizeof(char));
    // strcpy(junk, path);

    safe_asprintf(&junk, "%s", path); // freed later.
    if (!junk) { fprintf(stderr, "Out of memory.\n"); exit(1); }
    orig = basename(junk);

    if (orig == NULL) {
	while (pthread_mutex_unlock(&mylock));
	return NULL;
    }

    // copy = malloc((strlen(orig) + 1) * sizeof(char));
    // strcpy(copy, orig);

    safe_asprintf(&copy, "%s", orig); // returned.
    if (!copy) { fprintf(stderr, "Out of memory.\n"); exit(1); }
    free(junk);

    while (pthread_mutex_unlock(&mylock));

    return copy;
}

/*!
    @abstract
        Returns whether the filename looks like a frameset-based
	<code>index.html</code> file generated by HeaderDoc.
 */
int ishdindex(char *filename)
{
    FILE *fp;

    if (!(fp = fopen(filename, "r")))
	return 0;
    int found_head = 0;

    char line[4097];

    while (!found_head) {
	if (!fgets(line, 4096, fp)) {
	    fclose(fp);
	    return 0;
	}
	if (strcasestr(line, "<head")) {
	    found_head = 1;
	}
    }
    while (found_head == 1) {
	if (!fgets(line, 4096, fp)) {
	    fclose(fp);
	    return 0;
	}
	if (strcasestr
	    (line, "<meta name=\"generator\" content=\"HeaderDoc\"")) {
	    found_head = 2;
	}
	if (strcasestr
	    (line, "<meta name=\"formatter\" content=\"gatherHeaderDoc\"")) {
	    fclose(fp);
	    return 0;
	}
	if (strcasestr(line, "</head")) {
	    fclose(fp);
	    return 0;
	}
    }
    while (1) {
	if (!fgets(line, 4096, fp)) {
	    fclose(fp);
	    return 0;
	}
	if (strcasestr(line, "<frameset")) {
	    fclose(fp);
	    return 1;
	}
    }
    fclose(fp);
    return 0;
}


int printNodeRangeSub(xmlNode * start, xmlNode * end, int leading);

/*!
    @abstract
        Dumps information about a range of HTML nodes for
        debugging purposes.
 */
int printNodeRange(xmlNode * start, xmlNode * end)
{
    return printNodeRangeSub(start, end, 0);
}

/*!
    @abstract
        The recursive portion of {@link printNodeRange}.
 */
int printNodeRangeSub(xmlNode * start, xmlNode * end, int leading)
{
    xmlNode *iter = start;

    char *leadspace = malloc(leading + 1); // released later.
    if (!leadspace) {
	fprintf(stderr, "Out of memory in printNodeRangeSub\n");
	exit(-1);
    }

    memset(leadspace, ' ', leading);
    leadspace[leading] = '\0';

    while (iter && iter != end) {
	printf("%sNODE: %s CONTENT: %s\n", leadspace,
	       iter->name ? (char *) iter->name : "(no name)",
	       iter->content ? (char *) iter->content : "(null)");
	if (iter->children) {
	    printf("%sCHILDREN:\n", leadspace);
	    if (printNodeRangeSub(iter->children, end, leading + 8)) {
		free(leadspace);
		return 1;
	    }
	}
	iter = iter->next;
    }
    if (iter && (iter == end))
	printf("%sNODE: %s CONTENT: %s\n", leadspace,
	       iter->name ? (char *) iter->name : "(no name)",
	       iter->content ? (char *) iter->content : "(null)");
    free(leadspace);
    return (iter == end);
}

/*!
    @abstract
        Opens the log file.
 */
void openlogfile()
{
    if (logfile) return;

    safe_asprintf(&logname, "/tmp/resolvelinks.linkreport.XXXXXXXXXXXXX");
    if (!logname) { fprintf(stderr, "Out of memory.\n"); exit(1); }
    int fd = mkstemp(logname);

    logfile = fdopen(fd, "w");
}

/*!
    @abstract
        Closes the log file.
 */
void closelogfile()
{
    if (logfile) fclose(logfile);
    logfile = NULL;
}

/*!
    @abstract
        Prints command-line usage information.
 */
void printusage()
{
    fprintf(ERRS,
	    "\nUsage:\n\n");
    fprintf(ERRS,
	    "    resolveLinks [-s xref_seed_file] [-t nthreads] [-d debug_level] [ -r ext_ref ] <directory>\n\n");
    // fprintf(ERRS, "Usage: resolveLinks [options] <directory>\n");
    fprintf(ERRS, "Common options are:\n");
    fprintf(ERRS,
	    "\t-b <base path to strip from output seed file>  (default /)\n");
    fprintf(ERRS,
	    "\t-d <debug flags>                               (default 0)\n");
    fprintf(ERRS,
	    "\t-i <installed path where the HTML will end up> (default is -b value)\n");
    fprintf(ERRS,
	    "\t-r <alternative to apple_ref>                  (can be used multiple times)\n");
    fprintf(ERRS,
	    "\t-s <seed file>                                 (can be used multiple times)\n");
    fprintf(ERRS,
	    "\t-S <base path for previous seed file>          (can be used multiple times)\n");
    fprintf(ERRS,
	    "\t-t <number of threads>                         (default 2)\n");
    fprintf(ERRS,
	    "\t-x <output seed file>\n");
    fprintf(ERRS,
	    "\nFor more details, see man resolveLinks.\n\n");
}

/*!
    @abstract
        Returns whether a filename is a recognized URI scheme.
 */
int isURI(char *filename)
{
    // 
    if ((strncmp(filename, "file://", 7) == 0 && filename[7] != '\0') ||
	(strncmp(filename, "ftp://", 6) == 0 && filename[6] != '\0') ||
	(strncmp(filename, "http://", 7) == 0 && filename[7] != '\0') ||
	(strncmp(filename, "https://", 8) == 0 && filename[8] != '\0')) {
	// printf("IS A URI.\n");
	return 1;
    }
    // printf("IS NOT A URI.\n");
    return 0;
}

/*!
    @abstract
	Returns the next part of an API reference and null-terminates the
	current one.
    @discussion
	This function null-terminates the current part of an API reference
	by replacing the next slash (<code>/</code>) with a null byte
	(<code>\0</code>), then returns a pointer to the next part.

        @warning
		Modifies the string pointed to by <code>data</code>.

    @returns
	Returns the next part of the API reference (the first character
	after the slash), or <code>NULL</code> if the string in
	<code>data</code> does not contain a slash).

 */
char *getNextXRefPart(char *data)
{
    char *retval = NULL;
    char *pos;

    if (!data) return NULL;

    for (pos = data; pos && *pos && *pos != '/'; pos++);

    if (*pos) {
	retval = pos+1;
    }

    *pos = '\0';

    return retval;
}

/*!
    @abstract
        Returns the actual object name from an API reference (UID).
 */
char *nameFromAppleRef(char *ref)
{
    char *refcopy;
    char *ret = NULL;
    refparts_t rp;

    safe_asprintf(&refcopy, "%s", ref);
    if (!refcopy) { fprintf(stderr, "Out of memory.\n"); exit(1); }
    rp = getrefparts(refcopy, PART_REST);

    if ((!rp) || (!rp->rest)) {
	if (rp) refpartsfree(rp);

	free(refcopy);
	safe_asprintf(&ret, "%s", ref);
	if (!ret) { fprintf(stderr, "Out of memory.\n"); exit(1); }

#ifdef DEBUG_NAMEFROMAPPLEREF
	fprintf(stderr, "Returning %s [1]\n", ret);
#endif

	return ret;
    }

    char *symbol_type = rp->rest;
    char *field1 = getNextXRefPart(rp->rest); /* Terminates symbol_type and returns the next field. */
    char *field2 = getNextXRefPart(field1); /* Terminates field1 and returns the next field. */
    char *field3 = getNextXRefPart(field2); /* Terminates field2 and returns the next field. */
    char *field4 = getNextXRefPart(field3); /* Terminates field3 and returns the next field. */
    char *field5 = getNextXRefPart(field4); /* Terminates field4 and returns the next field. */
    char *field6 = getNextXRefPart(field5); /* Terminates field5 and returns the next field. */

    if (!field1) {
	free(refcopy);

	safe_asprintf(&ret, "%s", rp->rest);
	if (!ret) { fprintf(stderr, "Out of memory.\n"); exit(1); }

#ifdef DEBUG_NAMEFROMAPPLEREF
	fprintf(stderr, "Returning %s [1a]\n", ret);
#endif
	refpartsfree(rp);

	return ret;
    }

    /* C++ scoped functions with prototype */
    if (!strcmp(symbol_type, "func") && field2) {
	safe_asprintf(&ret, "%s", field1);
	if (!ret) { fprintf(stderr, "Out of memory.\n"); exit(1); }
	free(refcopy);
	refpartsfree(rp);

#ifdef DEBUG_NAMEFROMAPPLEREF
	fprintf(stderr, "Returning %s [2]\n", ret);
#endif

	return ret;
    }
    /* Function templates */
    if (!strcmp(symbol_type, "ftmplt")) {
	if (field3) {
		// There must be a class.
		safe_asprintf(&ret, "%s", field2);
		if (!ret) { fprintf(stderr, "Out of memory.\n"); exit(1); }
		free(refcopy);
		refpartsfree(rp);

#ifdef DEBUG_NAMEFROMAPPLEREF
	fprintf(stderr, "Returning %s [3]\n", ret);
#endif

		return ret;
	}
	// No class.
	safe_asprintf(&ret, "%s", field1);
	if (!ret) { fprintf(stderr, "Out of memory.\n"); exit(1); }
	free(refcopy);
	refpartsfree(rp);

#ifdef DEBUG_NAMEFROMAPPLEREF
	fprintf(stderr, "Returning %s [4]\n", ret);
#endif

	return ret;
    }

    /* Class and instance methods are special. */
    if (!strcmp(symbol_type, "instm")) {
	safe_asprintf(&ret, "%s", field2);
	if (!ret) { fprintf(stderr, "Out of memory.\n"); exit(1); }
	free(refcopy);
	refpartsfree(rp);

#ifdef DEBUG_NAMEFROMAPPLEREF
	fprintf(stderr, "Returning %s [5]\n", ret);
#endif

	return ret;
    }
    if (!strcmp(symbol_type, "intfm")) {
	safe_asprintf(&ret, "%s", field2);
	if (!ret) { fprintf(stderr, "Out of memory.\n"); exit(1); }
	free(refcopy);
	refpartsfree(rp);

#ifdef DEBUG_NAMEFROMAPPLEREF
	fprintf(stderr, "Returning %s [6]\n", ret);
#endif

	return ret;
    }
    if (!strcmp(symbol_type, "intfcm")) {
	safe_asprintf(&ret, "%s", field2);
	if (!ret) { fprintf(stderr, "Out of memory.\n"); exit(1); }
	free(refcopy);
	refpartsfree(rp);

#ifdef DEBUG_NAMEFROMAPPLEREF
	fprintf(stderr, "Returning %s [7]\n", ret);
#endif

	return ret;
    }
    if (!strcmp(symbol_type, "intfp")) {
	safe_asprintf(&ret, "%s", field2);
	if (!ret) { fprintf(stderr, "Out of memory.\n"); exit(1); }
	free(refcopy);
	refpartsfree(rp);

#ifdef DEBUG_NAMEFROMAPPLEREF
	fprintf(stderr, "Returning %s [8]\n", ret);
#endif

	return ret;
    }
    if (!strcmp(symbol_type, "instp")) {
	safe_asprintf(&ret, "%s", field2);
	if (!ret) { fprintf(stderr, "Out of memory.\n"); exit(1); }
	free(refcopy);
	refpartsfree(rp);

#ifdef DEBUG_NAMEFROMAPPLEREF
	fprintf(stderr, "Returning %s [9]\n", ret);
#endif

	return ret;
    }

    /* Otherwise return the last nonempty field. */
    if (field6) {
	safe_asprintf(&ret, "%s", field6);
	if (!ret) { fprintf(stderr, "Out of memory.\n"); exit(1); }
	free(refcopy);
	refpartsfree(rp);

#ifdef DEBUG_NAMEFROMAPPLEREF
	fprintf(stderr, "Returning %s [10]\n", ret);
#endif

	return ret;
    }
    if (field5) {
	safe_asprintf(&ret, "%s", field5);
	if (!ret) { fprintf(stderr, "Out of memory.\n"); exit(1); }
	free(refcopy);
	refpartsfree(rp);

#ifdef DEBUG_NAMEFROMAPPLEREF
	fprintf(stderr, "Returning %s [11]\n", ret);
#endif

	return ret;
    }
    if (field4) {
	safe_asprintf(&ret, "%s", field4);
	if (!ret) { fprintf(stderr, "Out of memory.\n"); exit(1); }
	free(refcopy);
	refpartsfree(rp);

#ifdef DEBUG_NAMEFROMAPPLEREF
	fprintf(stderr, "Returning %s [12]\n", ret);
#endif

	return ret;
    }
    if (field3) {
	safe_asprintf(&ret, "%s", field3);
	if (!ret) { fprintf(stderr, "Out of memory.\n"); exit(1); }
	free(refcopy);
	refpartsfree(rp);

#ifdef DEBUG_NAMEFROMAPPLEREF
	fprintf(stderr, "Returning %s [13]\n", ret);
#endif

	return ret;
    }
    if (field2) {
	safe_asprintf(&ret, "%s", field2);
	if (!ret) { fprintf(stderr, "Out of memory.\n"); exit(1); }
	free(refcopy);
	refpartsfree(rp);

#ifdef DEBUG_NAMEFROMAPPLEREF
	fprintf(stderr, "Returning %s [14]\n", ret);
#endif

	return ret;
    }

    safe_asprintf(&ret, "%s", field1);
    if (!ret) { fprintf(stderr, "Out of memory.\n"); exit(1); }
    free(refcopy);
    refpartsfree(rp);

#ifdef DEBUG_NAMEFROMAPPLEREF
    fprintf(stderr, "Returning %s [15] (ref was %s)\n", ret, ref);
#endif

    return ret;
}

/*!
    @abstract
	Inserts a name node into the tree used by {@link refByName}.
 */
int insertName(xrefbyname_t node, xrefbyname_t tree)
{
    int cmp = strcmp(node->name, tree->name);

#if NEAR_MATCH_DEBUG > 1
	fprintf(stderr, "Checking for %s (%p) in %s (%p)\n", node->name, node, tree->name, tree);
#endif

    if (cmp < 0) {
	if (tree->left) {
		return insertName(node, tree->left);
	}
	node->parent = tree;
	tree->left = node;
	rebalance_xrefbyname_t_tree(tree, node);
    } else if (cmp > 0) {
	if (tree->right) {
		return insertName(node, tree->right);
	}
	node->parent = tree;
	tree->right = node;
	rebalance_xrefbyname_t_tree(tree, node);
    } else {
#ifdef NEAR_MATCH_DEBUG
	fprintf(stderr, "Dup found for %s\n", node->name);
#endif
	if (tree->dup) {
		return insertName(node, tree->dup);
	}
	node->parent = tree->parent;
	tree->dup = node;
	/* No rebalance needed. */
    }
    return 0;
}


xrefnode_t refByNameRec(char *name, xrefbyname_t pos, char *basepath);

/*!
    @abstract
        Searches the (binary) tree of names for a symbol matching
        that name.
    @discussion
        This is a fallback for when exact matching fails.  It allows
        certain "shot in the dark" link requests to match against
        something less exact.
    @result
        Returns the cross-reference node (which provides the list of
        candidate API reference markers) that matches the specified name.
 */
xrefnode_t refByName(char *name, char *basepath)
{
    return refByNameRec(name, namehead, basepath);
}

/*!
    @abstract
        Recursive portion of {@link refByName}.
 */
xrefnode_t refByNameRec(char *name, xrefbyname_t pos, char *basepath)
{
    int cmp;

    if (!pos) return NULL;

    cmp = strcmp(name, pos->name);
    if (!cmp) {
	/* We found one.  Find the closest match. */
	xrefbyname_t iter;
	xrefnode_t nodeiter, maxmatchnode = NULL, maxmatchnode_nocache = NULL;

	int maxmatch = -1;
	int maxmatch_nocache = -1;

	// fprintf(stderr, "Would return %p\n", pos->node);

	for (iter = pos; iter; iter = iter->dup) {
	    int belowme;

#ifdef NEAR_MATCH_DEBUG
	    fprintf(stderr, "Outer loop: %p (dup chain is %p)\n", iter, iter->dup);
#endif

	    for (nodeiter = iter->node; nodeiter; nodeiter = nodeiter->dup) {
		int match = matchingPathParts(nodepath(nodeiter), basepath, &belowme);

#ifdef NEAR_MATCH_DEBUG
		fprintf(stderr, "Inner loop: %p (dup chain is %p): match is %d maxmatch is %d\n", nodeiter, nodeiter->dup, match, maxmatch);
#endif

#ifdef NEAR_MATCH_DEBUG
		fprintf(stderr, "Checking %s against %s\n", nodepath(nodeiter), basepath);
#endif
		if (match > maxmatch || (match == maxmatch && belowme)) {
#ifdef NEAR_MATCH_DEBUG
			fprintf(stderr, "%d is better than %d.  ", match, maxmatch);
#endif
			maxmatch = match;
			maxmatchnode = nodeiter;
		}
		if (!nodeiter->fromseed) {
		    if (match > maxmatch_nocache
		            || (match == maxmatch_nocache && belowme)) {

#ifdef NEAR_MATCH_DEBUG
			fprintf(stderr, "Ooh.  a real one!  ");
#endif
			maxmatch_nocache = match;
			maxmatchnode_nocache = nodeiter;
		    }
		}
#ifdef NEAR_MATCH_DEBUG
	    fprintf(stderr, "\n");
#endif
	    }
	}
	if (maxmatchnode) {
#ifdef NEAR_MATCH_DEBUG
	    fprintf(stderr, "Returning %p\n", maxmatchnode);
#endif
	    return maxmatchnode;
	}
	if (maxmatchnode_nocache) {
#ifdef NEAR_MATCH_DEBUG
	    fprintf(stderr, "Returning %p\n", maxmatchnode_nocache);
#endif
	    return maxmatchnode_nocache;
	}
#ifdef NEAR_MATCH_DEBUG
	fprintf(stderr, "Punting (returning %p)\n", pos->node);
#endif

	return pos->node;
    }
    if (cmp < 0) return refByNameRec(name, pos->left, basepath);
    return refByNameRec(name, pos->right, basepath);
}

/*!
    @abstract
	Returns whether a known symbol marker is a
	suitable partial match for a partial symbol marker in
	a link request.
    @discussion
	The API reference convention for languages with classes
	allows for multiple methods with the same name and
	different parameters and/or return types.  The unfortunate
	result of this is that it is impossible to programmatically
	generate a "best guess" link request that matches it
	precisely because there is no way to guess the return type
	or parameter signature.

	This function looks at the link request and checks to see
	if it is of one of the symbol types that are affected by
	this limitation.  If so, it appends a slash to the
	link request and checks to see if it is an exact match
	for the first part of the actual link destination (including
	the slash).  If it is, this function returns <code>1</code>.
	If any of these conditions fail, it returns <code>0</code>.

	This is used in {@link searchref} to determine whether to
	treat the link request as a match in spite of its
	inexactness.
    @param partial_ref
	The (possibly partial) API reference marker from the link request.
    @param complete_ref
	The (known complete) API reference marker from the tree of
	known link destinations.
 */
int ispartialmatch(char *partial_ref, char *complete_ref)
{
    char *refcopy;
    refparts_t rp;

    safe_asprintf(&refcopy, "%s", partial_ref);
    if (!refcopy) { fprintf(stderr, "Out of memory.\n"); exit(1); }
    rp = getrefparts(refcopy, PART_REST);
    free(refcopy);

    if (!rp) return 0;

    char *symbol_type = rp->rest;
    char *field1 = getNextXRefPart(rp->rest); /* Terminates symbol_type and returns the next field. */
    char *field2 = getNextXRefPart(field1); /* Terminates field1 and returns the next field. */
    char *field3 = getNextXRefPart(field2); /* Terminates field2 and returns the next field. */
    (void) getNextXRefPart(field3); /* Terminates field3 and returns the next field. */

    /* C++ scoped functions with prototype */
    if (!strcmp(symbol_type, "instm") ||
        !strcmp(symbol_type, "intfm") ||
        !strcmp(symbol_type, "clm") ||
        !strcmp(symbol_type, "ftmplt")) {
		if (!field3) {
			/* Return value and parameter signature Data missing */
			// char *temp;

			// safe_asprintf(&temp, "%s/", partial_ref);
			if (!strncmp(partial_ref, complete_ref, strlen(partial_ref))) {
				// fprintf(stderr, "Returning partial match\n");
				refpartsfree(rp);
				return 1;
			}
		}
    }

    refpartsfree(rp);
    return 0;
}

/*!
    @abstract
	Releases a {@link searchobj_t} object.
 */
void searchfree(searchobj_t obj)
{
    if (obj->uri) free(obj->uri);
    free(obj);
}

// #define TYPE xrefnode_t
// #define TOP nodehead

// void fix_xrefnode_t_to_head(xrefnode_t node)

/*!
    @abstract
        When used, declares the function fix_X_to_head where X is a
        type name.
    @discussion
	Part of the AVL code.  The constructed function walks from
        the current node up to the head, fixing any incorrect
        maximum depth values as it does so.
    @hidecontents
 */
#define FIX_TYPE_TO_TOP(TYPE, TOP) void fix_##TYPE##_to_head(TYPE node) \
{ \
	while (node) { \
		if (node->left) { \
			if (node->left->maxleftdepth > node->left->maxrightdepth) { \
				node->maxleftdepth = node->left->maxleftdepth + 1; \
			} else { \
				node->maxleftdepth = node->left->maxrightdepth + 1; \
			} \
		} else { \
			node->maxleftdepth = 0; \
		} \
		if (node->right) { \
			if (node->right->maxleftdepth > node->right->maxrightdepth) { \
				node->maxrightdepth = node->right->maxleftdepth + 1; \
			} else { \
				node->maxrightdepth = node->right->maxrightdepth + 1; \
			} \
		} else { \
			node->maxrightdepth = 0; \
		} \
		node = node->parent; \
	} \
}

/*!
    @abstract
	Walks the {@link xrefnode_t} AVL tree from
        the current node up to the head, fixing any incorrect
        maximum depth values as it does so.
 */
FIX_TYPE_TO_TOP(xrefnode_t, nodehead);

/*!
    @abstract
	Walks the {@link xrefbyname_t} AVL tree from
        the current node up to the head, fixing any incorrect
        maximum depth values as it does so.
 */
FIX_TYPE_TO_TOP(xrefbyname_t, namehead)

// xrefnode_t rotate_xrefnode_t_rightright(xrefnode_t node)

/*!
    @abstract
        When used, declares the function rotate_X_rightright where X is a
        type name.
    @discussion
	Part of the AVL code.  The constructed function handles rotations
        in which the right child of the right child needs to be pulled up.
    @hidecontents
 */
#define ROTATE_RIGHTRIGHT(TYPE, TOP) TYPE rotate_##TYPE##_rightright(TYPE node) \
{ \
    TYPE newtop = node->right; \
    TYPE temp = node->parent; \
 \
    if (!node->right) { \
	fprintf(stderr, "WARNING: node->right NULL in rightright  (Bug in AVL tree.)\n"); \
	return node; \
    } \
    if (!node->right->right) { \
	fprintf(stderr, "WARNING: node->right->right NULL in rightright.  (Bug in AVL tree.)\n"); \
	return node; \
    } \
 \
    if (testing_avl) { \
	fprintf(stderr, "rightright at %p; right is %p rr is %p\n", node, node->right, node->right->right); \
    } \
 \
    if (!temp) { \
	/* Change the top of tree. */ \
	TOP = newtop; \
    } else if (temp->left == node) { \
	/* Change parent to reference the new top. */ \
	temp->left = newtop; \
    } else { \
	/* Change parent to reference the new top. */ \
	temp->right = newtop; \
    } \
 \
    newtop->parent = temp; \
    node->parent = newtop; \
 \
    node->right = newtop->left; \
    if (node->right) node->right->parent = node; \
    node->maxrightdepth = newtop->maxleftdepth; \
 \
    newtop->left = node; \
    if (node->maxleftdepth > node->maxrightdepth) { \
	newtop->maxleftdepth = node->maxleftdepth + 1; \
    } else { \
	newtop->maxleftdepth = node->maxrightdepth + 1; \
    } \
 \
    /* fix_xrefnode_t_to_head(node); */ \
    fix_##TYPE##_to_head(node); \
 \
    if (testing_avl) { \
	/* verify_xrefnode_t_tree(nodehead); */ \
	verify_##TYPE##_tree(TOP); \
    } \
 \
    return newtop; \
}

/*!
    @abstract
	Handles rotations of the {@link xrefnode_t} AVL tree
        in which the right child of the right child needs to be pulled up.
 */
ROTATE_RIGHTRIGHT(xrefnode_t, nodehead)

/*!
    @abstract
	Handles rotations of the {@link xrefbyname_t} AVL tree
        in which the right child of the right child needs to be pulled up.
 */
ROTATE_RIGHTRIGHT(xrefbyname_t, namehead)


// xrefnode_t rotate_xrefnode_t_rightleft(xrefnode_t node)
/*!
    @abstract
        When used, declares the function rotate_X_rightleft where X is a
        type name.
    @discussion
	Part of the AVL code.  The constructed function handles rotations
        in which the left child of the right child needs to be pulled up.
    @hidecontents
 */
#define ROTATE_RIGHTLEFT(TYPE, TOP) TYPE rotate_##TYPE##_rightleft(TYPE node) \
{ \
    TYPE oldright = node->right; \
 \
    if (!node->right) { \
	fprintf(stderr, "WARNING: node->right NULL in rightleft.  (Bug in AVL tree.)\n"); \
	return node; \
    } \
    if (!node->right->left) { \
	fprintf(stderr, "WARNING: node->right->left NULL in rightleft.  (Bug in AVL tree.)\n"); \
	return node; \
    } \
 \
    if (testing_avl) { \
	fprintf(stderr, "rightleft at %p; right is %p rl is %p\n", node, node->right, node->right->left); \
    } \
 \
    node->right = oldright->left; \
    node->maxrightdepth = oldright->maxleftdepth; \
 \
    oldright->left = node->right->right; \
    oldright->maxleftdepth = node->right->maxrightdepth; \
 \
    oldright->parent = node->right; \
    node->right->right = oldright; \
 \
    node->right->right->parent = node->right; \
    node->right->parent = node; \
 \
    if (oldright->left) oldright->left->parent = oldright; \
 \
    if (oldright->maxrightdepth > oldright->maxleftdepth) { \
	node->right->maxrightdepth = oldright->maxrightdepth + 1; \
    } else { \
	node->right->maxrightdepth = oldright->maxleftdepth + 1; \
    } \
    if (node->right->maxrightdepth > node->right->maxleftdepth) { \
	node->maxrightdepth = node->right->maxrightdepth + 1; \
    } else { \
	node->maxrightdepth = node->right->maxleftdepth + 1; \
    } \
 \
    /* fix_xrefnode_t_to_head(node); */ \
    fix_##TYPE##_to_head(node); \
 \
    if (testing_avl) { \
	/* verify_xrefnode_t_tree(nodehead); */ \
	verify_##TYPE##_tree(TOP); \
    } \
 \
    /* return rotate_xrefnode_t_rightright(node); */ \
    return rotate_##TYPE##_rightright(node); \
}

/*!
    @abstract
	Handles rotations of the {@link xrefnode_t} AVL tree
        in which the left child of the right child needs to be pulled up.
 */
ROTATE_RIGHTLEFT(xrefnode_t, nodehead)

/*!
    @abstract
	Handles rotations of the {@link xrefbyname_t} AVL tree
        in which the left child of the right child needs to be pulled up.
 */
ROTATE_RIGHTLEFT(xrefbyname_t, namehead)

// xrefnode_t rotate_xrefnode_t_leftleft(xrefnode_t node)
/*!
    @abstract
        When used, declares the function rotate_X_leftleft where X is a
        type name.
    @discussion
	Part of the AVL code.  The constructed function handles rotations
        in which the left child of the left child needs to be pulled up.
    @hidecontents
 */
#define ROTATE_LEFTLEFT(TYPE, TOP) TYPE rotate_##TYPE##_leftleft(TYPE node) \
{ \
    TYPE newtop = node->left; \
    TYPE temp = node->parent; \
 \
    if (!node->left) { \
	fprintf(stderr, "WARNING: node->left NULL in leftleft.  (Bug in AVL tree.)\n"); \
	return node; \
    } \
    if (!node->left->left) { \
	fprintf(stderr, "WARNING: node->left->left NULL in leftleft.  (Bug in AVL tree.)\n"); \
	return node; \
    } \
 \
    if (testing_avl) { \
	fprintf(stderr, "leftleft at %p; left is %p ll is %p\n", node, node->left, node->left->left); \
    } \
 \
    if (!temp) { \
	/* Change the top of tree. */ \
	TOP = newtop; \
    } else if (temp->left == node) { \
	/* Change parent to reference the new top. */ \
	temp->left = newtop; \
    } else { \
	/* Change parent to reference the new top. */ \
	temp->right = newtop; \
    } \
 \
    newtop->parent = temp; \
    node->parent = newtop; \
 \
    node->left = newtop->right; \
    if (node->left) node->left->parent = node; \
    node->maxleftdepth = newtop->maxrightdepth; \
 \
    newtop->right = node; \
    if (node->maxleftdepth > node->maxrightdepth) { \
	newtop->maxrightdepth = node->maxleftdepth + 1; \
    } else { \
	newtop->maxrightdepth = node->maxrightdepth + 1; \
    } \
 \
    /* fix_xrefnode_t_to_head(node); */ \
    fix_##TYPE##_to_head(node); \
 \
    if (testing_avl) { \
	/* verify_xrefnode_t_tree(nodehead); */ \
	verify_##TYPE##_tree(TOP); \
    } \
    return newtop; \
}

/*!
    @abstract
	Handles rotations of the {@link xrefnode_t} AVL tree
        in which the left child of the left child needs to be pulled up.
 */
ROTATE_LEFTLEFT(xrefnode_t, nodehead)

/*!
    @abstract
	Handles rotations of the {@link xrefbyname_t} AVL tree
        in which the left child of the left child needs to be pulled up.
 */
ROTATE_LEFTLEFT(xrefbyname_t, namehead)

// xrefnode_t rotate_xrefnode_t_leftright(xrefnode_t node)

/*!
    @abstract
        When used, declares the function rotate_X_leftright where X is a
        type name.
    @discussion
	Part of the AVL code.  The constructed function handles rotations
        in which the right child of the left child needs to be pulled up.
    @hidecontents
 */
#define ROTATE_LEFTRIGHT(TYPE, TOP) TYPE rotate_##TYPE##_leftright(TYPE node) \
{ \
    TYPE oldleft = node->left; \
 \
    if (!node->left) { \
	fprintf(stderr, "WARNING: node->left NULL in leftright.  (Bug in AVL tree.)\n"); \
	return node; \
    } \
    if (!node->left->right) { \
	fprintf(stderr, "WARNING: node->left->right NULL in leftright.  (Bug in AVL tree.)\n"); \
	return node; \
    } \
 \
    if (testing_avl) { \
	fprintf(stderr, "leftright at %p; left is %p lr is %p\n", node, node->left, node->left->right); \
    } \
 \
    node->left = oldleft->right; \
    node->maxleftdepth = oldleft->maxrightdepth; \
 \
    oldleft->right = node->left->left; \
    oldleft->maxrightdepth = node->left->maxleftdepth; \
 \
    oldleft->parent = node->left; \
    node->left->left = oldleft; \
    node->left->left->parent = node->left; \
    node->left->parent = node; \
 \
    if (oldleft->right) oldleft->right->parent = oldleft; \
 \
    if (oldleft->maxleftdepth > oldleft->maxrightdepth) { \
	node->left->maxleftdepth = oldleft->maxleftdepth + 1; \
    } else { \
	node->left->maxleftdepth = oldleft->maxrightdepth + 1; \
    } \
    if (node->left->maxleftdepth > node->left->maxrightdepth) { \
	node->maxleftdepth = node->left->maxleftdepth + 1; \
    } else { \
	node->maxleftdepth = node->left->maxrightdepth + 1; \
    } \
 \
    /* fix_xrefnode_t_to_head(node); */ \
    fix_##TYPE##_to_head(node); \
 \
    if (testing_avl) { \
	/* verify_xrefnode_t_tree(nodehead); */ \
	verify_##TYPE##_tree(TOP); \
    } \
 \
    /* return rotate_xrefnode_t_leftleft(node); */ \
    return rotate_##TYPE##_leftleft(node); \
}

/*!
    @abstract
	Handles rotations of the {@link xrefnode_t} AVL tree
        in which the right child of the left child needs to be pulled up.
 */
ROTATE_LEFTRIGHT(xrefnode_t, nodehead)

/*!
    @abstract
	Handles rotations of the {@link xrefbyname_t} AVL tree
        in which the right child of the left child needs to be pulled up.
 */
ROTATE_LEFTRIGHT(xrefbyname_t, namehead)

// void rebalance_xrefnode_t_tree(xrefnode_t node, xrefnode_t fromnode)

/*!
    @abstract
        When used, declares the function rebalance_X_tree where X is a
        type name.
    @discussion
	Part of the AVL code.  The constructed function rebalances the
        AVL tree after insertion of the node <code>fromnode</code>
        as a child of the node <code>node</code>.
    @hidecontents
 */
#ifdef NOAVL
#define REBALANCE_TREE(TYPE) void rebalance_##TYPE##_tree(TYPE node, \
	TYPE fromnode) { \
		return; \
	}
#else

#define REBALANCE_TREE(TYPE) void rebalance_##TYPE##_tree(TYPE node, \
	TYPE fromnode) \
{ \
	while (node) { \
		DEPTH_TYPE balance; \
 \
		if (node->left == fromnode) { \
			if (node->maxleftdepth < fromnode->maxleftdepth + 1) node->maxleftdepth = fromnode->maxleftdepth + 1; \
			if (node->maxleftdepth < fromnode->maxrightdepth + 1) node->maxleftdepth = fromnode->maxrightdepth + 1; \
		} else { \
			if (node->maxrightdepth < fromnode->maxleftdepth + 1) node->maxrightdepth = fromnode->maxleftdepth + 1; \
			if (node->maxrightdepth < fromnode->maxrightdepth + 1) node->maxrightdepth = fromnode->maxrightdepth + 1; \
		} \
 \
		balance = node->maxrightdepth - node->maxleftdepth; \
 \
		if (balance > 1) { \
			DEPTH_TYPE rightbalance = node->right->maxrightdepth - node->right->maxleftdepth; \
 \
			if (rightbalance >= 0) { \
				node = rotate_##TYPE##_rightright(node); \
				/* node = rotate_xrefnode_t_rightright(node); */ \
			} else { \
				node = rotate_##TYPE##_rightleft(node); \
				/* node = rotate_xrefnode_t_rightleft(node); */ \
			} \
		} else if (balance < -1) {\
			DEPTH_TYPE leftbalance = node->left->maxrightdepth - node->left->maxleftdepth; \
 \
			if (leftbalance >= 0) { \
				node = rotate_##TYPE##_leftright(node); \
				/* node = rotate_xrefnode_t_leftright(node); */ \
			} else { \
				node = rotate_##TYPE##_leftleft(node); \
				/* node = rotate_xrefnode_t_leftleft(node); */ \
			} \
		} \
 \
		fromnode = node; \
		node = node->parent; \
	} \
}
#endif

/*!
    @abstract
	Rebalances the {@link xrefnode_t} AVL tree after insertion of
        the node <code>fromnode</code> as a child of the node <code>node</code>.
    @param fromnode
        The newly inserted node initially.  On recursion, the child of the
        current node in the direction of the newly inserted node.
    @param node
        The node that needs to be rebalanced. (Initially, the parent of the
        new node.)
 */
REBALANCE_TREE(xrefnode_t)

/*!
    @abstract
	Rebalances the {@link xrefbyname_t} AVL tree after insertion of
        the node <code>fromnode</code> as a child of the node <code>node</code>.
    @param fromnode
        The newly inserted node initially.  On recursion, the child of the
        current node in the direction of the newly inserted node.
    @param node
        The node that needs to be rebalanced. (Initially, the parent of the
        new node.)
 */
REBALANCE_TREE(xrefbyname_t)

// void rebalance_xrefbyname_t_tree(xrefbyname_t node)

/* DEPTH_TYPE verify_xrefnode_t_tree_sub(xrefnode_t node, DEPTH_TYPE *errors) */
/*!
    @abstract
        When used, declares the function verify_X_tree_sub where X is a
        type name.
    @discussion
	Part of the AVL code.  The constructed function verifies the
        depth values at all levels of an AVL tree.
    @hidecontents
 */
#ifdef NOAVL
#define VERIFY_TREE_SUB(TYPE) int verify_##TYPE##_tree_sub(TYPE node, DEPTH_TYPE *errors) \
{ \
    return -1; \
}
#else

#define VERIFY_TREE_SUB(TYPE) int verify_##TYPE##_tree_sub(TYPE node, DEPTH_TYPE *errors) \
 \
{ \
    if (!node) return -1; /* leaf nodes are 0, so -1 + 1 = 0; */ \
 \
    /* DEPTH_TYPE maxleft = verify_xrefnode_t_tree_sub(node->left, errors); */ \
    DEPTH_TYPE maxleft = verify_##TYPE##_tree_sub(node->left, errors); \
    /* DEPTH_TYPE maxright = verify_xrefnode_t_tree_sub(node->right, errors); */ \
    DEPTH_TYPE maxright = verify_##TYPE##_tree_sub(node->right, errors); \
 \
    if (node->maxleftdepth != maxleft + 1) { \
	fprintf(stderr, "Node %p left depth incorrect.  Expected " DEPTH_TYPE_PRINTF " got " DEPTH_TYPE_PRINTF ".\n", node, maxleft+1, node->maxleftdepth); \
	/* node->maxleftdepth = maxleft + 1; */ \
	(*errors)++; \
    } \
    /* else { \
	fprintf(stderr, "Node %p left depth AWESOME.  Expected " DEPTH_TYPE_PRINTF " got " DEPTH_TYPE_PRINTF ".\n", node, maxleft+1, node->maxleftdepth); \
    } */ \
 \
    if (node->maxrightdepth != maxright + 1) { \
	(*errors)++; \
	fprintf(stderr, "Node %p right depth incorrect.  Expected " DEPTH_TYPE_PRINTF " got " DEPTH_TYPE_PRINTF ".\n", node, maxright+1, node->maxrightdepth); \
    } \
    /* else { \
	fprintf(stderr, "Node %p right depth AWESOME.  Expected " DEPTH_TYPE_PRINTF " got " DEPTH_TYPE_PRINTF ".\n", node, maxright+1, node->maxrightdepth); \
    } */ \
 \
    if (maxleft > maxright) return maxleft+1; \
    return maxright+1; \
}

#endif

/*!
    @abstract
	Verifies the depth values at all levels of an {@link xrefnode_t}
        AVL tree.
 */
VERIFY_TREE_SUB(xrefnode_t)

/*!
    @abstract
	Verifies the depth values at all levels of an {@link xrefbyname_t}
        AVL tree.
 */
VERIFY_TREE_SUB(xrefbyname_t)

// DEPTH_TYPE verify_xrefnode_t_tree(xrefnode_t node)
/*!
    @abstract
        When used, declares the function verify_X_tree where X is a
        type name.
    @discussion
	Part of the AVL code.  The constructed function verifies the
        depth values at all levels of an AVL tree and returns the
        number of errors.
    @hidecontents
 */
#define VERIFY_TREE(TYPE) int verify_##TYPE##_tree(TYPE node) \
{ \
    DEPTH_TYPE errors = 0; \
 \
    /* (void) verify_xrefnode_t_tree_sub(node, &errors); */ \
    (void) verify_##TYPE##_tree_sub(node, &errors); \
 \
    return errors; \
}

/*!
    @abstract
	Verifies the depth values at all levels of an {@link xrefnode_t}
        AVL tree and returns the number of errors.
 */
VERIFY_TREE(xrefnode_t)

/*!
    @abstract
	Verifies the depth values at all levels of an {@link xrefbyname_t}
        AVL tree and returns the number of errors.
 */
VERIFY_TREE(xrefbyname_t)

/*!
    @abstract
        Generates a line in a cross-reference file
        for the specified path, UID, and title.
    @discussion
        Used by {@link test_xrefnode_t_tree}.
 */
char *nodeline(char *path, char *xref, char *title)
{
    static char *retval = NULL;

    if (retval) free(retval);

    safe_asprintf(&retval, "%s%c%s%c%s\n",
	    path, 1, xref, 1, title);
    if (!retval) { fprintf(stderr, "Out of memory.\n"); exit(1); }

    return retval;
}

/*!
    @abstract
        Tests the {@link xrefbyname_t} tree (used for
        looking up symbols by name).
    @discussion
        The resolveLinks tool runs some internal
        consistency checks on its AVL tree code at
        each launch.  This runs some of those tests.

        This test is not implemented.  However, the tree
        does get abused significantly between the
        resolveLinks test suite and the built-in
        consistency tests in {@link test_xrefnode_t_tree}
 */
int test_xrefbyname_t_tree()
{
    return 0;
}

/*!
    @abstract
        Tests the {@link xrefbyname_t} tree (used for
        looking up symbols by name).
    @discussion
        The resolveLinks tool runs some internal
        consistency checks on its AVL tree code at
        each launch.  This runs some of those tests.
 */
int test_xrefnode_t_tree()
{
    DEPTH_TYPE retval = 0;

    if (testing_avl) fprintf(stderr, "Testing AVL tree\n");

    addXRefFromLine(
	nodeline("path_100", "xref_100", "title"),
	"/", 0);
    addXRefFromLine(
	nodeline("path_200", "xref_200", "title"),
	"/", 0);
    addXRefFromLine(
	nodeline("path_300", "xref_300", "title"),
	"/", 0); /* triggers rightright rotation at root. */

    /* Result:
	  200
	  / \ 
	100 300
     */

    addXRefFromLine(
	nodeline("path_250", "xref_250", "title"),
	"/", 0);
    addXRefFromLine(
	nodeline("path_350", "xref_350", "title"),
	"/", 0);
    addXRefFromLine(
	nodeline("path_400", "xref_400", "title"),
	"/", 0); /* triggers rightright rotation at 300 and root (200) */

    /*
	       200
	      /  \ 
            100  300
                /   \ 
               250  350
                       \ 
                       400

	       200
	      /  \ 
            100  350
                /   \ 
               300  400
              /
            250

	       350
	      /  \ 
            200  300
            /  \    \ 
          100  250  400
     */
    addXRefFromLine(
	nodeline("path_50", "xref_50", "title"),
	"/", 0); /* triggers leftleft rotation at 10, 200 and root (300) */
    addXRefFromLine(
	nodeline("path_75", "xref_75", "title"),
	"/", 0);
    addXRefFromLine(
	nodeline("path_65", "xref_65", "title"),
	"/", 0);
    addXRefFromLine(
	nodeline("path_68", "xref_68", "title"),
	"/", 0); /* triggers leftright, leftleft */
    addXRefFromLine(
	nodeline("path_440", "xref_440", "title"),
	"/", 0);
    addXRefFromLine(
	nodeline("path_460", "xref_460", "title"),
	"/", 0);
    addXRefFromLine(
	nodeline("path_450", "xref_450", "title"),
	"/", 0);

    retval += verify_xrefnode_t_tree(nodehead);

    /* Reset */
    free_xrefnode_t_tree(nodehead);
    free_xrefbyname_t_tree(namehead);

    if (testing_avl) fprintf(stderr, "AVL tree test 1 done.\n");

    addXRefFromLine(
	nodeline("path_200", "xref_200", "title"),
	"/", 0);

    addXRefFromLine(
	nodeline("path_100", "xref_100", "title"),
	"/", 0);

    addXRefFromLine(
	nodeline("path_050", "xref_050", "title"),
	"/", 0); /* leftleft at root */

    addXRefFromLine(
	nodeline("path_025", "xref_025", "title"),
	"/", 0);

    addXRefFromLine(
	nodeline("path_010", "xref_010", "title"),
	"/", 0); /* leftleft at non-root */

    retval += verify_xrefnode_t_tree(nodehead);

    /* Reset */
    free_xrefnode_t_tree(nodehead);
    free_xrefbyname_t_tree(namehead);

    if (testing_avl) fprintf(stderr, "AVL tree test 2 done.\n");

    addXRefFromLine(
	nodeline("path_200", "xref_200", "title"),
	"/", 0);

    addXRefFromLine(
	nodeline("path_300", "xref_300", "title"),
	"/", 0);

    addXRefFromLine(
	nodeline("path_400", "xref_400", "title"),
	"/", 0); /* leftleft at root */

    addXRefFromLine(
	nodeline("path_500", "xref_500", "title"),
	"/", 0); /* leftleft at root */

    addXRefFromLine(
	nodeline("path_600", "xref_600", "title"),
	"/", 0); /* leftleft at root */

    retval += verify_xrefnode_t_tree(nodehead);

    /* Reset */
    free_xrefnode_t_tree(nodehead);
    free_xrefbyname_t_tree(namehead);

    if (testing_avl) fprintf(stderr, "AVL tree test 3 done.\n");

    addXRefFromLine(
	nodeline("path_200", "xref_200", "title"),
	"/", 0);

    addXRefFromLine(
	nodeline("path_300", "xref_300", "title"),
	"/", 0);

    addXRefFromLine(
	nodeline("path_250", "xref_250", "title"),
	"/", 0); /* leftleft at root */

    addXRefFromLine(
	nodeline("path_350", "xref_350", "title"),
	"/", 0); /* leftleft at root */

    addXRefFromLine(
	nodeline("path_325", "xref_325", "title"),
	"/", 0); /* leftleft at root */

    retval += verify_xrefnode_t_tree(nodehead);

    /* Reset */
    free_xrefnode_t_tree(nodehead);
    free_xrefbyname_t_tree(namehead);

    if (testing_avl) fprintf(stderr, "AVL tree test 4 done.\n");

    addXRefFromLine(
	nodeline("path_200", "xref_200", "title"),
	"/", 0);

    addXRefFromLine(
	nodeline("path_100", "xref_100", "title"),
	"/", 0);

    addXRefFromLine(
	nodeline("path_150", "xref_150", "title"),
	"/", 0); /* leftleft at root */

    addXRefFromLine(
	nodeline("path_050", "xref_050", "title"),
	"/", 0); /* leftleft at root */

    addXRefFromLine(
	nodeline("path_075", "xref_075", "title"),
	"/", 0); /* leftleft at root */

    retval += verify_xrefnode_t_tree(nodehead);

    /* Reset */
    free_xrefnode_t_tree(nodehead);
    free_xrefbyname_t_tree(namehead);

    if (testing_avl) fprintf(stderr, "AVL tree test 5 done.\n");

    if (testing_avl) fprintf(stderr, "AVL tree test finished\n");

    return retval ? -1 : 0;
}

/*!
    @abstract
        Recursively releases nodes in the {@link xrefnode_t}
        tree (used for looking up symbols by API reference marker).
    @discussion
        The resolveLinks tool runs some internal
        consistency checks on its AVL tree code at
        each launch.  This ensures that those checks
        do not leak.
 */
void free_xrefnode_t_tree_sub(xrefnode_t node)
{
    if (!node) return;

    free_xrefnode_t_tree(node->left);
    free_xrefnode_t_tree(node->right);
    free_xrefnode_t_tree(node->dup);

    if (node->filename) free(node->filename);
    if (node->basepath) free(node->basepath);
    if (node->xref) free(node->xref);
    if (node->title) free(node->title);
    if (node->fullpath) free(node->fullpath);

    free(node);
}

/*!
    @abstract
        Releases the {@link xrefnode_t} tree (used for
        looking up symbols by API reference marker).
    @discussion
        The resolveLinks tool runs some internal
        consistency checks on its AVL tree code at
        each launch.  This ensures that those checks
        do not leak.
 */
void free_xrefnode_t_tree(xrefnode_t node)
{
    free_xrefnode_t_tree_sub(node);
    nodehead = NULL;
}

/*!
    @abstract
        Recursively releases nodes in the {@link xrefbyname_t}
        tree (used for looking up symbols by name).
    @discussion
        The resolveLinks tool runs some internal
        consistency checks on its AVL tree code at
        each launch.  This ensures that those checks
        do not leak.
 */
void free_xrefbyname_t_tree_sub(xrefbyname_t node)
{
    if (!node) return;
    free_xrefbyname_t_tree_sub(node->left);
    free_xrefbyname_t_tree_sub(node->right);
    if (node->name) free(node->name);

    free(node);
}

/*!
    @abstract
        Releases the {@link xrefbyname_t} tree (used for
        looking up symbols by name).
    @discussion
        The resolveLinks tool runs some internal
        consistency checks on its AVL tree code at
        each launch.  This ensures that those checks
        do not leak.
 */
void free_xrefbyname_t_tree(xrefbyname_t node)
{
    free_xrefbyname_t_tree_sub(node);
    namehead = NULL;
}

/*!
    @abstract
        Returns (and caches) the <code>realpath</code> result on the node's
	{@link //apple_ref/doc/typedeffield/xrefnode_t/filename filename} field.
 */
char *nodefilenamerealpath(xrefnode_t node)
{
#ifdef RP_CACHE
    if (isURI(node->filename)) {
	return node->filename;
    }
    if (!node->filename_rp) {
	node->filename_rp = realpath(node->filename, NULL);
    }
    return node->filename_rp;
#else
    return realpath(node->filename, NULL);
#endif
}

#ifdef ENABLE_CORE_DUMPS
/*!
    @abstract
        Debug code.  Not actually used during normal operation.
 */
static int EnableCoreDumps(void)
{
    struct rlimit   limit;

    // fprintf(stderr, "Enabling core dumps for debugging.\n");
    limit.rlim_cur = RLIM_INFINITY;
    limit.rlim_max = RLIM_INFINITY;
    return setrlimit(RLIMIT_CORE, &limit);
}
#endif


/*!
    @abstract
        Debug code.  Not actually used during normal operation.
 */
void checkDoc(xmlNode *node, xmlNode *parent, xmlNode *prev, htmlDocPtr dp)
{
    while(node) {
	if (node->doc != dp) {
		fprintf(stderr, "WARNING: Node %p has wrong doc pointer (expected %p got %p.", node, dp, node->doc);
		fprintf(stderr, "  Name: %s\n", node->name);
		fprintf(stderr, "  Content: %s\n", node->content);
	}
	if (node->prev != prev) {
		fprintf(stderr, "WARNING: Node %p has wrong prev pointer (expected %p got %p).", node, prev, node->prev);
		fprintf(stderr, "  Name: %s\n", node->name);
		fprintf(stderr, "  Content: %s\n", node->content);
	}
	if (node->parent != parent) {
		fprintf(stderr, "WARNING: Node %p has wrong parent pointer (expected %p got %p).", node, parent, node->parent);
		fprintf(stderr, "  Name: %s\n", node->name);
		fprintf(stderr, "  Content: %s\n", node->content);
	}
	// if (node->properties) checkDoc(node->properties, NULL, NULL, dp);
	if (node->children) checkDoc(node->children, node, NULL, dp);

	prev = node;
	node = node->next;
    }
}

/*!
    @abstract
	Compatibility shim for Linux
    @discussion
	Unlike the BSD implementation of <code>asprintf</code>,
	the Linux implementation does not guarantee that the
	variable pointed to by <code>ret</code> is set to
	<code>NULL</code> in the event of an error.

	Because it is poor programming practice to accept a
	pointer from a system routine without checking to see
	if the routine returned NULL, this results in a rather
	messy pair of checks in order to get the desired
	behavior (one check for the return value, then another
	for the pointer).

	This function works around that flaw in the Linux
	implementation by simply checking the return value,
	then setting the variable pointed to by <code>ret</code>
	to NULL in the event of an error.
 */
int safe_asprintf(char **ret, const char *format, ...)
{
    va_list ap;
    int retval;

    va_start(ap, format);
    retval = vasprintf(ret, format, ap);
    if (ret && (retval < 0)) {
	*ret = NULL;
    }

    return retval;
}


// #if ((LIBXML_VERSION < 20609) || ((LIBXML_VERSION == 20609) && !defined(__APPLE__)))
// #include "workaround.c"
// #endif