1 /* Hypertext "Anchor" Object HTAnchor.c
2 ** ==========================
4 ** An anchor represents a region of a hypertext document which is linked to
5 ** another anchor in the same or a different document.
9 ** Nov 1990 Written in Objective-C for the NeXT browser (TBL)
10 ** 24-Oct-1991 (JFG), written in C, browser-independant
11 ** 21-Nov-1991 (JFG), first complete version
13 ** (c) Copyright CERN 1991 - See Copyright.html
16 #define HASH_SIZE 101 /* Arbitrary prime. Memory/speed tradeoff */
24 typedef struct _HyperDoc Hyperdoc;
27 int junk; /* VMS cannot handle pointers to undefined structs */
31 PRIVATE HTList **adult_table=0; /* Point to table of lists of all parents */
36 ** Do not use "new" by itself outside this module. In order to enforce
37 ** consistency, we insist that you furnish more information about the
38 ** anchor you are creating : use newWithParent or newWithAddress.
41 PRIVATE HTParentAnchor * HTParentAnchor_new
44 HTParentAnchor *newAnchor =
45 (HTParentAnchor *) calloc (1, sizeof (HTParentAnchor)); /* zero-filled */
46 newAnchor->parent = newAnchor;
50 PRIVATE HTChildAnchor * HTChildAnchor_new
53 return (HTChildAnchor *) calloc (1, sizeof (HTChildAnchor)); /* zero-filled */
57 /* Case insensitive string comparison
58 ** ----------------------------------
60 ** s Points to one string, null terminated
61 ** t points to the other.
63 ** returns YES if the strings are equivalent ignoring case
64 ** NO if they differ in more than their case.
67 PRIVATE BOOL equivalent
68 ARGS2 (CONST char *,s, CONST char *,t)
70 if (s && t) { /* Make sure they point to something */
71 for ( ; *s && *t ; s++, t++) {
72 if (TOUPPER(*s) != TOUPPER(*t))
75 return TOUPPER(*s) == TOUPPER(*t);
77 return s == t; /* Two NULLs are equivalent, aren't they ? */
81 /* Create new or find old sub-anchor
82 ** ---------------------------------
84 ** This one is for a new anchor being edited into an existing
85 ** document. The parent anchor must already exist.
88 PRIVATE HTChildAnchor * HTAnchor_findChild
89 ARGS2 (HTParentAnchor *,parent, CONST char *,tag)
95 if (TRACE) printf ("HTAnchor_findChild called with NULL parent.\n");
98 if (kids = parent->children) { /* parent has children : search them */
99 if (tag && *tag) { /* TBL */
100 while (child = HTList_nextObject (kids)) {
101 if (equivalent(child->tag, tag)) { /* Case sensitive 920226 */
103 "Child anchor %p of parent %p with name `%s' already exists.\n",
108 } /* end if tag is void */
109 } else /* parent doesn't have any children yet : create family */
110 parent->children = HTList_new ();
112 child = HTChildAnchor_new ();
113 if (TRACE) printf("new Anchor %p named `%s' is child of %p\n",
114 child, (int)tag ? tag : (CONST char *)"" , parent); /* int for apollo */
115 HTList_addObject (parent->children, child);
116 child->parent = parent;
117 StrAllocCopy(child->tag, tag);
122 /* Create or find a child anchor with a possible link
123 ** --------------------------------------------------
125 ** Create new anchor with a given parent and possibly
126 ** a name, and possibly a link to a _relatively_ named anchor.
127 ** (Code originally in ParseHTML.h)
129 PUBLIC HTChildAnchor * HTAnchor_findChildAndLink
131 HTParentAnchor *,parent, /* May not be 0 */
132 CONST char *,tag, /* May be "" or 0 */
133 CONST char *,href, /* May be "" or 0 */
134 HTLinkType *,ltype /* May be 0 */
137 HTChildAnchor * child = HTAnchor_findChild(parent, tag);
139 char * parsed_address;
141 parsed_address = HTParse(href, HTAnchor_address((HTAnchor *) parent),
143 dest = HTAnchor_findAddress(parsed_address);
144 HTAnchor_link((HTAnchor *) child, dest, ltype);
145 free(parsed_address);
151 /* Create new or find old named anchor
152 ** -----------------------------------
154 ** This one is for a reference which is found in a document, and might
155 ** not be already loaded.
156 ** Note: You are not guaranteed a new anchor -- you might get an old one,
160 HTAnchor * HTAnchor_findAddress
161 ARGS1 (CONST char *,address)
163 char *tag = HTParse (address, "", PARSE_ANCHOR); /* Anchor tag specified ? */
165 /* If the address represents a sub-anchor, we recursively load its parent,
166 then we create a child anchor within that document. */
168 char *docAddress = HTParse(address, "", PARSE_ACCESS | PARSE_HOST |
169 PARSE_PATH | PARSE_PUNCTUATION);
170 HTParentAnchor * foundParent =
171 (HTParentAnchor *) HTAnchor_findAddress (docAddress);
172 HTChildAnchor * foundAnchor = HTAnchor_findChild (foundParent, tag);
175 return (HTAnchor *) foundAnchor;
178 else { /* If the address has no anchor tag, check whether we have this node */
183 HTParentAnchor * foundAnchor;
187 /* Select list from hash table */
188 for(p=address, hash=0; *p; p++) hash = (hash * 3 + *p) % HASH_SIZE;
190 adult_table = (HTList**) calloc(HASH_SIZE, sizeof(HTList*));
191 if (!adult_table[hash]) adult_table[hash] = HTList_new();
192 adults = adult_table[hash];
194 /* Search list for anchor */
196 while (foundAnchor = HTList_nextObject (grownups)) {
197 if (equivalent(foundAnchor->address, address)) {
198 if (TRACE) printf("Anchor %p with address `%s' already exists.\n",
199 foundAnchor, address);
200 return (HTAnchor *) foundAnchor;
204 /* Node not found : create new anchor */
205 foundAnchor = HTParentAnchor_new ();
206 if (TRACE) printf("New anchor %p has hash %d and address `%s'\n",
207 foundAnchor, hash, address);
208 StrAllocCopy(foundAnchor->address, address);
209 HTList_addObject (adults, foundAnchor);
210 return (HTAnchor *) foundAnchor;
215 /* Delete an anchor and possibly related things (auto garbage collection)
216 ** --------------------------------------------
218 ** The anchor is only deleted if the corresponding document is not loaded.
219 ** All outgoing links from parent and children are deleted, and this anchor
220 ** is removed from the sources list of all its targets.
221 ** We also try to delete the targets whose documents are not loaded.
222 ** If this anchor's source list is empty, we delete it and its children.
225 PRIVATE void deleteLinks
226 ARGS1 (HTAnchor *,this)
231 /* Recursively try to delete target anchors */
232 if (this->mainLink.dest) {
233 HTParentAnchor *parent = this->mainLink.dest->parent;
234 HTList_removeObject (parent->sources, this);
235 if (! parent->document) /* Test here to avoid calling overhead */
236 HTAnchor_delete (parent);
238 if (this->links) { /* Extra destinations */
240 while (target = HTList_removeLastObject (this->links)) {
241 HTParentAnchor *parent = target->dest->parent;
242 HTList_removeObject (parent->sources, this);
243 if (! parent->document) /* Test here to avoid calling overhead */
244 HTAnchor_delete (parent);
249 PUBLIC BOOL HTAnchor_delete
250 ARGS1 (HTParentAnchor *,this)
252 HTChildAnchor *child;
254 /* Don't delete if document is loaded */
258 /* Recursively try to delete target anchors */
259 deleteLinks ((HTAnchor *) this);
261 if (! HTList_isEmpty (this->sources)) { /* There are still incoming links */
262 /* Delete all outgoing links from children, if any */
263 HTList *kids = this->children;
264 while (child = HTList_nextObject (kids))
265 deleteLinks ((HTAnchor *) child);
266 return NO; /* Parent not deleted */
269 /* No more incoming links : kill everything */
270 /* First, recursively delete children */
271 while (child = HTList_removeLastObject (this->children)) {
272 deleteLinks ((HTAnchor *) child);
277 /* Now kill myself */
278 HTList_delete (this->children);
279 HTList_delete (this->sources);
280 free (this->address);
281 /* Devise a way to clean out the HTFormat if no longer needed (ref count?) */
283 return YES; /* Parent deleted */
287 /* Move an anchor to the head of the list of its siblings
288 ** ------------------------------------------------------
290 ** This is to ensure that an anchor which might have already existed
291 ** is put in the correct order as we load the document.
294 void HTAnchor_makeLastChild
295 ARGS1(HTChildAnchor *,this)
297 if (this->parent != (HTParentAnchor *) this) { /* Make sure it's a child */
298 HTList * siblings = this->parent->children;
299 HTList_removeObject (siblings, this);
300 HTList_addObject (siblings, this);
304 /* Data access functions
305 ** ---------------------
308 extern HTParentAnchor * HTAnchor_parent
309 ARGS1 (HTAnchor *,this)
311 return this ? this->parent : NULL;
314 void HTAnchor_setDocument
315 ARGS2 (HTParentAnchor *,this, HyperDoc *,doc)
318 this->document = doc;
321 HyperDoc * HTAnchor_document
322 ARGS1 (HTParentAnchor *,this)
324 return this ? this->document : NULL;
328 /* We don't want code to change an address after anchor creation... yet ?
329 void HTAnchor_setAddress
330 ARGS2 (HTAnchor *,this, char *,addr)
333 StrAllocCopy (this->parent->address, addr);
337 char * HTAnchor_address
338 ARGS1 (HTAnchor *,this)
342 if (((HTParentAnchor *) this == this->parent) ||
343 !((HTChildAnchor *) this)->tag) { /* it's an adult or no tag */
344 StrAllocCopy (addr, this->parent->address);
346 else { /* it's a named child */
347 addr = malloc (2 + strlen (this->parent->address)
348 + strlen (((HTChildAnchor *) this)->tag));
349 if (addr == NULL) outofmem(__FILE__, "HTAnchor_address");
350 sprintf (addr, "%s#%s", this->parent->address,
351 ((HTChildAnchor *) this)->tag);
359 void HTAnchor_setFormat
360 ARGS2 (HTParentAnchor *,this, HTFormat *,form)
366 HTFormat * HTAnchor_format
367 ARGS1 (HTParentAnchor *,this)
369 return this ? this->format : NULL;
374 void HTAnchor_setIndex
375 ARGS1 (HTParentAnchor *,this)
381 BOOL HTAnchor_isIndex
382 ARGS1 (HTParentAnchor *,this)
384 return this ? this->isIndex : NO;
389 BOOL HTAnchor_hasChildren
390 ARGS1 (HTParentAnchor *,this)
392 return this ? ! HTList_isEmpty(this->children) : NO;
397 CONST char * HTAnchor_title
398 ARGS1 (HTParentAnchor *,this)
400 return this ? this->title : 0;
403 void HTAnchor_setTitle
404 ARGS2(HTParentAnchor *,this, CONST char *,title)
406 StrAllocCopy(this->title, title);
409 void HTAnchor_appendTitle
410 ARGS2(HTParentAnchor *,this, CONST char *,title)
412 StrAllocCat(this->title, title);
415 /* Link this Anchor to another given one
416 ** -------------------------------------
420 ARGS3(HTAnchor *,source, HTAnchor *,destination, HTLinkType *,type)
422 if (! (source && destination))
423 return NO; /* Can't link to/from non-existing anchor */
424 if (TRACE) printf ("Linking anchor %p to anchor %p\n", source, destination);
425 if (! source->mainLink.dest) {
426 source->mainLink.dest = destination;
427 source->mainLink.type = type;
429 HTLink * newLink = (HTLink *) malloc (sizeof (HTLink));
430 if (newLink == NULL) outofmem(__FILE__, "HTAnchor_link");
431 newLink->dest = destination;
432 newLink->type = type;
434 source->links = HTList_new ();
435 HTList_addObject (source->links, newLink);
437 if (!destination->parent->sources)
438 destination->parent->sources = HTList_new ();
439 HTList_addObject (destination->parent->sources, source);
440 return YES; /* Success */
444 /* Manipulation of links
445 ** ---------------------
448 HTAnchor * HTAnchor_followMainLink
449 ARGS1 (HTAnchor *,this)
451 return this->mainLink.dest;
454 HTAnchor * HTAnchor_followTypedLink
455 ARGS2 (HTAnchor *,this, HTLinkType *,type)
457 if (this->mainLink.type == type)
458 return this->mainLink.dest;
460 HTList *links = this->links;
462 while (link = HTList_nextObject (links))
463 if (link->type == type)
466 return NULL; /* No link of this type */
469 BOOL HTAnchor_makeMainLink
470 ARGS2 (HTAnchor *,this, HTLink *,movingLink)
472 /* Check that everything's OK */
473 if (! (this && HTList_removeObject (this->links, movingLink)))
474 return NO; /* link not found or NULL anchor */
476 /* First push current main link onto top of links list */
477 HTLink *newLink = (HTLink*) malloc (sizeof (HTLink));
478 if (newLink == NULL) outofmem(__FILE__, "HTAnchor_makeMainLink");
479 memcpy (newLink, & this->mainLink, sizeof (HTLink));
480 HTList_addObject (this->links, newLink);
482 /* Now make movingLink the new main link, and free it */
483 memcpy (& this->mainLink, movingLink, sizeof (HTLink));