Initial commit of pristine erwise source
[erwise] / Cl / WWWLibrary / HTAnchor.c
1 /*      Hypertext "Anchor" Object                               HTAnchor.c
2 **      ==========================
3 **
4 ** An anchor represents a region of a hypertext document which is linked to
5 ** another anchor in the same or a different document.
6 **
7 ** History
8 **
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
12 **
13 **      (c) Copyright CERN 1991 - See Copyright.html
14 */
15
16 #define HASH_SIZE 101           /* Arbitrary prime. Memory/speed tradeoff */
17
18 #include <ctype.h>
19 #include "tcp.h"
20 #include "HTAnchor.h"
21 #include "HTUtils.h"
22 #include "HTParse.h"
23
24 typedef struct _HyperDoc Hyperdoc;
25 #ifdef vms
26 struct _HyperDoc {
27         int junk;       /* VMS cannot handle pointers to undefined structs */
28 };
29 #endif
30
31 PRIVATE HTList **adult_table=0;  /* Point to table of lists of all parents */
32
33 /*                              Creation Methods
34 **                              ================
35 **
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.
39 */
40
41 PRIVATE HTParentAnchor * HTParentAnchor_new
42   NOARGS
43 {
44   HTParentAnchor *newAnchor = 
45     (HTParentAnchor *) calloc (1, sizeof (HTParentAnchor));  /* zero-filled */
46   newAnchor->parent = newAnchor;
47   return newAnchor;
48 }
49
50 PRIVATE HTChildAnchor * HTChildAnchor_new
51   NOARGS
52 {
53   return (HTChildAnchor *) calloc (1, sizeof (HTChildAnchor));  /* zero-filled */
54 }
55
56
57 /*      Case insensitive string comparison
58 **      ----------------------------------
59 ** On entry,
60 **      s       Points to one string, null terminated
61 **      t       points to the other.
62 ** On exit,
63 **      returns YES if the strings are equivalent ignoring case
64 **              NO if they differ in more than  their case.
65 */
66
67 PRIVATE BOOL equivalent
68   ARGS2 (CONST char *,s, CONST char *,t)
69 {
70   if (s && t) {  /* Make sure they point to something */
71     for ( ; *s && *t ; s++, t++) {
72         if (TOUPPER(*s) != TOUPPER(*t))
73           return NO;
74     }
75     return TOUPPER(*s) == TOUPPER(*t);
76   } else
77     return s == t;  /* Two NULLs are equivalent, aren't they ? */
78 }
79
80
81 /*      Create new or find old sub-anchor
82 **      ---------------------------------
83 **
84 **      This one is for a new anchor being edited into an existing
85 **      document. The parent anchor must already exist.
86 */
87
88 PRIVATE HTChildAnchor * HTAnchor_findChild
89   ARGS2 (HTParentAnchor *,parent, CONST char *,tag)
90 {
91   HTChildAnchor *child;
92   HTList *kids;
93
94   if (! parent) {
95     if (TRACE) printf ("HTAnchor_findChild called with NULL parent.\n");
96     return NULL;
97   }
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 */
102                 if (TRACE) printf (
103                "Child anchor %p of parent %p with name `%s' already exists.\n",
104                     child, parent, tag);
105                 return child;
106             }
107         }
108      }  /*  end if tag is void */
109   } else  /* parent doesn't have any children yet : create family */
110     parent->children = HTList_new ();
111
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);
118   return child;
119 }
120
121
122 /*      Create or find a child anchor with a possible link
123 **      --------------------------------------------------
124 **
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)
128 */
129 PUBLIC HTChildAnchor * HTAnchor_findChildAndLink
130   ARGS4(
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 */
135        )
136 {
137   HTChildAnchor * child = HTAnchor_findChild(parent, tag);
138   if (href && *href) {
139     char * parsed_address;
140     HTAnchor * dest;
141     parsed_address = HTParse(href, HTAnchor_address((HTAnchor *) parent),
142                              PARSE_ALL);
143     dest = HTAnchor_findAddress(parsed_address);
144     HTAnchor_link((HTAnchor *) child, dest, ltype);
145     free(parsed_address);
146   }
147   return child;
148 }
149
150
151 /*      Create new or find old named anchor
152 **      -----------------------------------
153 **
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,
157 **      like with fonts.
158 */
159
160 HTAnchor * HTAnchor_findAddress
161   ARGS1 (CONST char *,address)
162 {
163   char *tag = HTParse (address, "", PARSE_ANCHOR);  /* Anchor tag specified ? */
164
165   /* If the address represents a sub-anchor, we recursively load its parent,
166      then we create a child anchor within that document. */
167   if (*tag) {
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);
173     free (docAddress);
174     free (tag);
175     return (HTAnchor *) foundAnchor;
176   }
177   
178   else { /* If the address has no anchor tag, check whether we have this node */
179     int hash;
180     CONST char *p;
181     HTList * adults;
182     HTList *grownups;
183     HTParentAnchor * foundAnchor;
184
185     free (tag);
186     
187     /* Select list from hash table */
188     for(p=address, hash=0; *p; p++) hash = (hash * 3 + *p) % HASH_SIZE;
189     if (!adult_table)
190         adult_table = (HTList**) calloc(HASH_SIZE, sizeof(HTList*));
191     if (!adult_table[hash]) adult_table[hash] = HTList_new();
192     adults = adult_table[hash];
193
194     /* Search list for anchor */
195     grownups = adults;
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;
201       }
202     }
203     
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;
211   }
212 }
213
214
215 /*      Delete an anchor and possibly related things (auto garbage collection)
216 **      --------------------------------------------
217 **
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.
223 */
224
225 PRIVATE void deleteLinks
226   ARGS1 (HTAnchor *,this)
227 {
228   if (! this)
229     return;
230
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);
237   }
238   if (this->links) {  /* Extra destinations */
239     HTLink *target;
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);
245     }
246   }
247 }
248
249 PUBLIC BOOL HTAnchor_delete
250   ARGS1 (HTParentAnchor *,this)
251 {
252   HTChildAnchor *child;
253
254   /* Don't delete if document is loaded */
255   if (this->document)
256     return NO;
257
258   /* Recursively try to delete target anchors */
259   deleteLinks ((HTAnchor *) this);
260
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 */
267   }
268
269   /* No more incoming links : kill everything */
270   /* First, recursively delete children */
271   while (child = HTList_removeLastObject (this->children)) {
272     deleteLinks ((HTAnchor *) child);
273     free (child->tag);
274     free (child);
275   }
276
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?) */
282   free (this);
283   return YES;  /* Parent deleted */
284 }
285
286
287 /*              Move an anchor to the head of the list of its siblings
288 **              ------------------------------------------------------
289 **
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.
292 */
293
294 void HTAnchor_makeLastChild
295   ARGS1(HTChildAnchor *,this)
296 {
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);
301   }
302 }
303
304 /*      Data access functions
305 **      ---------------------
306 */
307
308 extern HTParentAnchor * HTAnchor_parent
309   ARGS1 (HTAnchor *,this)
310 {
311   return this ? this->parent : NULL;
312 }
313
314 void HTAnchor_setDocument
315   ARGS2 (HTParentAnchor *,this, HyperDoc *,doc)
316 {
317   if (this)
318     this->document = doc;
319 }
320
321 HyperDoc * HTAnchor_document
322   ARGS1 (HTParentAnchor *,this)
323 {
324   return this ? this->document : NULL;
325 }
326
327
328 /* We don't want code to change an address after anchor creation... yet ?
329 void HTAnchor_setAddress
330   ARGS2 (HTAnchor *,this, char *,addr)
331 {
332   if (this)
333     StrAllocCopy (this->parent->address, addr);
334 }
335 */
336
337 char * HTAnchor_address
338   ARGS1 (HTAnchor *,this)
339 {
340   char *addr = NULL;
341   if (this) {
342     if (((HTParentAnchor *) this == this->parent) ||
343         !((HTChildAnchor *) this)->tag) {  /* it's an adult or no tag */
344       StrAllocCopy (addr, this->parent->address);
345     }
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);
352     }
353   }
354   return addr;
355 }
356
357
358
359 void HTAnchor_setFormat
360   ARGS2 (HTParentAnchor *,this, HTFormat *,form)
361 {
362   if (this)
363     this->format = form;
364 }
365
366 HTFormat * HTAnchor_format
367   ARGS1 (HTParentAnchor *,this)
368 {
369   return this ? this->format : NULL;
370 }
371
372
373
374 void HTAnchor_setIndex
375   ARGS1 (HTParentAnchor *,this)
376 {
377   if (this)
378     this->isIndex = YES;
379 }
380
381 BOOL HTAnchor_isIndex
382   ARGS1 (HTParentAnchor *,this)
383 {
384   return this ? this->isIndex : NO;
385 }
386
387
388
389 BOOL HTAnchor_hasChildren
390   ARGS1 (HTParentAnchor *,this)
391 {
392   return this ? ! HTList_isEmpty(this->children) : NO;
393 }
394
395 /*      Title handling
396 */
397 CONST char * HTAnchor_title
398   ARGS1 (HTParentAnchor *,this)
399 {
400   return this ? this->title : 0;
401 }
402
403 void HTAnchor_setTitle
404   ARGS2(HTParentAnchor *,this, CONST char *,title)
405 {
406   StrAllocCopy(this->title, title);
407 }
408
409 void HTAnchor_appendTitle
410   ARGS2(HTParentAnchor *,this, CONST char *,title)
411 {
412   StrAllocCat(this->title, title);
413 }
414
415 /*      Link this Anchor to another given one
416 **      -------------------------------------
417 */
418
419 BOOL HTAnchor_link
420   ARGS3(HTAnchor *,source, HTAnchor *,destination, HTLinkType *,type)
421 {
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;
428   } else {
429     HTLink * newLink = (HTLink *) malloc (sizeof (HTLink));
430     if (newLink == NULL) outofmem(__FILE__, "HTAnchor_link");
431     newLink->dest = destination;
432     newLink->type = type;
433     if (! source->links)
434       source->links = HTList_new ();
435     HTList_addObject (source->links, newLink);
436   }
437   if (!destination->parent->sources)
438     destination->parent->sources = HTList_new ();
439   HTList_addObject (destination->parent->sources, source);
440   return YES;  /* Success */
441 }
442
443
444 /*      Manipulation of links
445 **      ---------------------
446 */
447
448 HTAnchor * HTAnchor_followMainLink
449   ARGS1 (HTAnchor *,this)
450 {
451   return this->mainLink.dest;
452 }
453
454 HTAnchor * HTAnchor_followTypedLink
455   ARGS2 (HTAnchor *,this, HTLinkType *,type)
456 {
457   if (this->mainLink.type == type)
458     return this->mainLink.dest;
459   if (this->links) {
460     HTList *links = this->links;
461     HTLink *link;
462     while (link = HTList_nextObject (links))
463       if (link->type == type)
464         return link->dest;
465   }
466   return NULL;  /* No link of this type */
467 }
468
469 BOOL HTAnchor_makeMainLink
470   ARGS2 (HTAnchor *,this, HTLink *,movingLink)
471 {
472   /* Check that everything's OK */
473   if (! (this && HTList_removeObject (this->links, movingLink)))
474     return NO;  /* link not found or NULL anchor */
475   else {
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);
481
482     /* Now make movingLink the new main link, and free it */
483     memcpy (& this->mainLink, movingLink, sizeof (HTLink));
484     free (movingLink);
485     return YES;
486   }
487 }