headerdoc2html
and
gatherheaderdoc
.
@indexgroup HeaderDoc Tools
*/
#include apple_ref
, 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 -S
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 1
if the API reference marker was read from a seed file,
or 0
if it was not.
@param force_absolute
Contains 1
if the -a
flag was passed for the seed
file in question (or prior to the first seed file), else 0
.
@param left
The left subtree of the tree of cross references (sorted by the
xref
field).
@param right
The right subtree of the tree of cross references (sorted by the
xref
field).
@param dup
A chain of additional cross-reference nodes with matching
values for xref
. 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 node
.
@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
fromseed
and force_absolute
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 -d
flag.
*/
int debugging = 0;
/*!
@abstract
Broken out storage for the file debug bit from the -d
flag.
*/
int filedebug = 0;
/*!
@abstract
Broken out storage for the write debug bit from the -d
flag.
*/
int writedebug = 0;
/*!
@abstract
Broken out storage for the relpath debug bit from the -d
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 -d
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 -d
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 /dev/null
.
@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 mkstemp
.
*/
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 -t
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 fromseed
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 -i
flag.
*/
char *global_installedpath = NULL;
/*!
@abstract
Storage for the -b
flag.
*/
char *global_basepath = "/";
/*!
@abstract
Set to 1 if the -b
flag is passed in (to distinguish
it definitively from the default value).
*/
int global_basepath_set = 0;
/*!
@abstract
A global copy of argv[0]
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 -a
flag is passed prior to any
-s
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 /foo/bar.html
or http://www.example.com/foo.html
, for example)
//apple_ref/c/func/myFunc
, for example)
realpath()
)
that must be released with a call to free()
.
*/
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, "", 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 recurse
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 recurse
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 free()
.
*/
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, "<%s>", 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 *)"
", 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 toxmlNodeListGetRawString
(part of
libxml) except that it does not traverse the node tree.
*/
char *xmlNodeGetRawString(htmlDocPtr dp, xmlNode * node, int whatever)
{
xmlNode copynode;
bcopy(node, ©node, sizeof(copynode));
copynode.next = NULL;
return (char *) xmlNodeListGetRawString(dp, ©node, 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 free()
.
*/
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
apple_ref
bit to the external ref specified by
extref
.
*/
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 lang
.
*/
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 (a
) is deeper in the hierarchy than
the source (b
).
*/
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 malloc
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 free
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 /dev/null
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 /dev/null
@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 (#
) in a
jump link and returns its position (or -1
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 target
with respect to the file specified by
fromFile
.
@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 free()
.
*/
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 target
property set
(but not if the retarget
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 dirname
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(©, "%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 basename
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);
// copy = malloc((strlen(orig) + 1) * sizeof(char));
// strcpy(copy, orig);
safe_asprintf(©, "%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
index.html
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, "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 ]