Imported Robodoc.
[robodoc.git] / Source / links.c
1 /*
2 Copyright (C) 1994-2007  Frans Slothouber, Jacco van Weert, Petteri Kettunen,
3 Bernd Koesling, Thomas Aglassinger, Anthon Pang, Stefan Kost, David Druffner,
4 Sasha Vasko, Kai Hofmann, Thierry Pierron, Friedrich Haase, and Gergely Budai.
5
6 This file is part of ROBODoc
7
8 ROBODoc is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 3 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
21 */
22
23 /****h* ROBODoc/Links
24  * FUNCTION
25  *   This module contains functions to manipulate links.
26  *   Links are derived from headers.  They are used to create
27  *   links in the documentation between a word and the part of
28  *   the documentation that explains something about that word.
29  *   (For instance a function name or variable name).
30  *   In addition to the links derived from the headers links are
31  *   also derived from the names of all the sourcefiles.
32  * MODIFICATION HISTORY
33  *   ????-??-??   Frans Slothouber  V1.0 
34  *   2003-02-03   Frans Slothouber  Refactoring
35  *******
36  * $Header: /cvsroot/robodoc/robo/Source/links.c,v 1.43 2007/07/10 19:13:52 gumpu Exp $
37  */
38
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <ctype.h>
43 #include <assert.h>
44
45 #include "globals.h"
46 #include "robodoc.h"
47 #include "headers.h"
48 #include "util.h"
49 #include "links.h"
50 #include "document.h"
51 #include "part.h"
52 #include "file.h"
53
54 #ifdef DMALLOC
55 #include <dmalloc.h>
56 #endif
57
58 /* TODO Documentation */
59 unsigned int        link_index_size = 0;
60 struct RB_link    **link_index = NULL;
61 struct RB_link    **case_sensitive_link_index = NULL;
62
63
64 /* Local functions */
65
66 static struct RB_link *RB_Alloc_Link( char *label_name, char *object_name,
67                                       char *file_name );
68
69 /* TODO Documentation */
70 int link_cmp( void *l1, void *l2 )
71 {
72     struct RB_link *link1 = l1;
73     struct RB_link *link2 = l2;
74
75     return RB_Str_Case_Cmp( link1->object_name, link2->object_name );
76 }
77
78
79 int case_sensitive_link_cmp( void *l1, void *l2 )
80 {
81     struct RB_link *link1 = l1;
82     struct RB_link *link2 = l2;
83
84     return strcmp( link1->object_name, link2->object_name );
85 }
86
87 char * function_name( char * full_name )
88 {
89     char * name = strchr( full_name, '/' );
90
91     if( name ) return name + 1;
92     else return full_name;
93 }
94
95 /****f* Links/RB_CollectLinks
96  * FUNCTION
97  *   Convert header information into link information.
98  *      RB_header -> RB_link conversion
99  * SYNOPSIS
100  */
101 void
102 RB_CollectLinks( struct RB_Document *document, 
103                  struct RB_header **headers,
104                  unsigned long count )
105 /*
106  * INPUTS
107  *   * document -- 
108  *   * headers  -- the array with headers.
109  *   * count    -- number of headers in the array
110  * OUTPUT
111  *   * link_index -- an array with links
112  *   * link_index_size -- the number of links in the array.
113  * SOURCE
114  */
115
116 {
117     unsigned long        i, j;
118     int  k;
119     struct RB_Part     *i_part;
120
121     for ( i = j = 0; i < count; ++i ) 
122     {
123         j += headers[i]->no_names - 1;
124     }
125
126     link_index_size = count + j;
127
128     if ( ( document->actions.do_multidoc ) &&
129             ! ( document->actions.do_one_file_per_header )
130        )
131     {
132         for ( i_part = document->parts; i_part; i_part = i_part->next )
133         {
134             if ( i_part->headers ) 
135             {
136                 link_index_size++;
137             }
138         }
139     }
140
141     link_index =
142         ( struct RB_link ** ) calloc( link_index_size,
143                                       sizeof( struct RB_link ** ) );
144     case_sensitive_link_index =
145         ( struct RB_link ** ) calloc( link_index_size,
146                                       sizeof( struct RB_link ** ) );
147
148     for ( i = j = 0; i < count; ++i )
149     {
150         struct RB_link     *link;
151         struct RB_header   *header;
152
153         header = headers[i];
154         assert( header->unique_name );
155         assert( header->file_name );
156         for( k = 0; k < header->no_names; j++, k++ )
157         {
158             link = RB_Alloc_Link( header->unique_name, function_name(header->names[k]),
159                                   header->file_name );
160             link->htype = header->htype;
161             link->is_internal = header->is_internal;
162             link_index[j] = link;
163             case_sensitive_link_index[j] = link;
164         }
165     }
166
167     /* If we are in multidoc mode, we also create links
168      * for all the source files.
169      */
170
171     if ( ( document->actions.do_multidoc ) &&
172             /* but not for one file per header multidocs */
173        ! ( document->actions.do_one_file_per_header )
174        )
175     {
176         for ( i_part = document->parts; i_part; i_part = i_part->next )
177         {
178             if ( i_part->headers ) 
179             {
180                 struct RB_link     *link;
181
182                 link =
183                     RB_Alloc_Link( "robo_top_of_doc", i_part->filename->name,
184                             RB_Get_FullDocname( i_part->filename ) );
185                 i_part->filename->link = link;
186                 link->htype = RB_FindHeaderType( HT_SOURCEHEADERTYPE );
187                 link_index[j] = link;
188                 case_sensitive_link_index[j] = link;
189                 ++j;
190             }
191             else
192             {
193                 i_part->filename->link = NULL;
194             }
195         }
196     }
197
198     /* Sort all the links so we can use a binary search */
199     RB_QuickSort( (void **)link_index, 0, link_index_size - 1, link_cmp );
200     RB_QuickSort( (void **)case_sensitive_link_index, 0, link_index_size - 1, case_sensitive_link_cmp );
201 }
202
203 /*****/
204
205
206 /****f* Links/RB_Free_Links
207  * FUNCTION
208  *   Deallocate all the memory used to store the links.
209  * SYNOPSIS
210  */
211 void RB_Free_Links( void )
212 /*
213  * INPUTS
214  *   o link_index_size
215  *   o link_index[]
216  * BUGS
217  *   Should use RB_Free_Link instead of doing
218  *   everything by it self.
219  * SOURCE
220  */
221 {
222     struct RB_link     *cur_link;
223     unsigned int        i;
224
225     for ( i = 0; i < link_index_size; ++i )
226     {
227         cur_link = link_index[i];
228         free( cur_link->object_name );
229         free( cur_link->label_name );
230         free( cur_link->file_name );
231         free( cur_link );
232     }
233     free( link_index );
234 }
235
236 /*******/
237
238 /* TODO Documentation */
239
240 int RB_Number_Of_Links( struct RB_HeaderType* header_type, char* file_name, int internal )
241 {
242     struct RB_link     *cur_link;
243     int                 n = 0;
244     unsigned int        i;
245
246     for ( i = 0; i < link_index_size; ++i )
247     {
248         cur_link = link_index[i];
249         if ( RB_CompareHeaderTypes( cur_link->htype, header_type ) &&
250              ( ( cur_link->is_internal && internal ) ||
251                ( !cur_link->is_internal && !internal ) ) )
252         {
253             if ( file_name )
254             {
255                 if ( strcmp( file_name, cur_link->file_name ) == 0 )
256                 {
257                     n++;
258                 }
259             }
260             else
261             {
262                 n++;
263             }
264         }
265     }
266     return n;
267 }
268
269 /****f* Links/Find_Link [3.0h]
270  * NAME
271  *   Find_Link -- try to match word with a link
272  * FUNCTION
273  *   Searches for the given word in the list of links and
274  *   headers.  There are three passes (or four, when the C option
275  *   is selected). Each pass uses a different definition of "word":
276  *   o In the first pass it is any thing that ends with a 'space', a '.' 
277  *     or a ','.
278  *   o In the second pass it is any string that consists of alpha
279  *     numerics, '_', ':', '.', or '-'.  
280  *   o In the third pass (for C) it is any string that consists 
281  *     of alpha numerics or '_'.
282  * SYNOPSIS
283  */
284
285 int
286 Find_Link( char *word_begin, 
287            char **object_name, 
288            char **label_name,
289            char **file_name )
290 /*
291  * INPUTS
292  *   o word_begin  - pointer to a word (a string).
293  *   o object_name  - pointer to a pointer to a string
294  *   o file_name   - pointer to a pointer to a string
295  * SIDE EFFECTS
296  *   label_name & file_name are modified
297  * RESULT
298  *   o object_name   -- points to the object if a match was found,
299  *                      NULL otherwise.
300  *   o file_name     -- points to the file name if a match was found,
301  *                      NULL otherwise.
302  *   o label_name    -- points to the labelname if a match was found,
303  *   o TRUE          -- a match was found.
304  *   o FALSE         -- no match was found.
305  * NOTES
306  *   This is a rather sensitive algorithm. Don't mess with it
307  *   too much.
308  * SOURCE
309  */
310
311 {
312     char               *cur_char = NULL, old_char;
313     int                 low_index, high_index, cur_index, state, pass;
314     unsigned int        length = 0;
315
316     for ( pass = 0; pass < 3; pass++ )
317     {
318         switch ( pass )
319         {
320         case 0:
321             {
322                 for ( cur_char = word_begin;
323                       ( *cur_char == '_' ) || utf8_isalnum( *cur_char ) || utf8_ispunct( *cur_char );
324                       cur_char++ );
325                 break;
326             }
327         case 1:
328             {
329                 for ( cur_char = word_begin;
330                       utf8_isalnum( *cur_char ) || ( *cur_char == '_' ) ||
331                       ( *cur_char == '-' ) || ( *cur_char == '.' ) ||
332                       ( *cur_char == ':' ); cur_char++ );
333                 break;
334             }
335         case 2:
336             {
337                 for ( cur_char = word_begin;
338                       utf8_isalnum( *cur_char ) || ( *cur_char == '_'); 
339                       cur_char++ );
340                 break;
341             }
342         }
343
344         if ( ( ( *( cur_char - 1 ) ) == ',' ) || ( ( *( cur_char - 1 ) ) == '.' ) )
345         {
346             cur_char--;
347         }
348
349         old_char = *cur_char;
350         *cur_char = '\0';       /*
351                                  * End the word with a '\0' 
352                                  */
353         if ( strlen( word_begin ) == length )
354         {
355             /* Do not test the same word over and over. If
356              * the current string and the string of the previous
357              * pass are the same length, they are the same. */
358
359             /* RB_Say ("Skipping (pass %d) \"%s\"\n", SAY_INFO, pass, word_begin);  */
360         }
361         else
362         {
363             length = strlen( word_begin );
364             /* RB_Say ("Testing (pass %d) \"%s\"\n", SAY_INFO, pass, word_begin); */
365             /*
366              * Search case sensitive for a link 
367              */
368             for ( cur_index = 0, low_index = 0, high_index =
369                     link_index_size - 1; high_index >= low_index; )
370             {
371                 cur_index = ( high_index - low_index ) / 2 + low_index;
372                 state = strcmp( word_begin, case_sensitive_link_index[cur_index]->object_name );
373                 if ( state < 0 )
374                 {
375                     high_index = cur_index - 1;
376                 }
377                 else if ( state == 0 )
378                 {
379                     *object_name = case_sensitive_link_index[cur_index]->object_name;
380                     *label_name = case_sensitive_link_index[cur_index]->label_name;
381                     *file_name = case_sensitive_link_index[cur_index]->file_name;
382                     RB_Say( "linking \"%s\"->\"%s\" from \"%s\"\n", SAY_DEBUG,
383                             word_begin, *object_name, *file_name );
384                     *cur_char = old_char;
385                     return ( TRUE );
386                 }
387                 else if ( state > 0 )
388                 {
389                     low_index = cur_index + 1;
390                 }
391             }
392
393             /*
394              * Search case insensitive for a link.
395              * But only when the user asks for this.
396              */
397             if ( course_of_action.do_ignore_case_when_linking ) 
398             {
399
400                 for ( cur_index = 0, low_index = 0, high_index =
401                         link_index_size - 1; high_index >= low_index; )
402                 {
403                     cur_index = ( high_index - low_index ) / 2 + low_index;
404                     state = RB_Str_Case_Cmp( word_begin, link_index[cur_index]->object_name );
405                     if ( state < 0 )
406                     {
407                         high_index = cur_index - 1;
408                     }
409                     else if ( state == 0 )
410                     {
411                         *object_name = link_index[cur_index]->object_name;
412                         *label_name = link_index[cur_index]->label_name;
413                         *file_name = link_index[cur_index]->file_name;
414                         RB_Say( "linking \"%s\"->\"%s\" from \"%s\"\n", SAY_DEBUG,
415                                 word_begin, *object_name, *file_name );
416                         *cur_char = old_char;
417                         return ( TRUE );
418                     }
419                     else if ( state > 0 )
420                     {
421                         low_index = cur_index + 1;
422                     }
423                 }
424             }
425         }
426         *cur_char = old_char;
427         *file_name = NULL;
428         *object_name = NULL;
429         *label_name = NULL;
430     }
431     return ( FALSE );
432 }
433
434 /*****/
435
436
437 /****f* Links/RB_Alloc_Link [2.01]
438  * NAME
439  *   RB_Alloc_Link              -- oop
440  * FUNCTION
441  *   allocate struct + strings
442  * SYNOPSIS
443  */
444 static struct RB_link *
445 RB_Alloc_Link( char *label_name, char *object_name, char *file_name )
446 /* 
447  * INPUTS
448  *   char *label_name -- strings to copy into the link
449  *   char *file_name
450  * RESULT
451  *   struct RB_link *  -- ready-to-use
452  * AUTHOR
453  *   Koessi
454  * SEE ALSO
455  *   RB_StrDup(), RB_Free_Link()
456  * SOURCE
457  */
458
459 {
460     struct RB_link     *new_link;
461
462     assert( object_name );
463     assert( label_name );
464     assert( file_name );
465     RB_Say( "Allocating a link (%s %s %s)\n", SAY_DEBUG, object_name, label_name, file_name );
466     new_link = malloc( sizeof( struct RB_link ) );
467     memset( new_link, 0, sizeof( struct RB_link ) );
468
469     new_link->file_name = RB_StrDup( file_name );
470     new_link->object_name = RB_StrDup( object_name );
471     new_link->label_name = RB_StrDup( label_name );
472     return ( new_link );
473 }
474
475 /*****/
476
477 /****f* Links/RB_Free_Link
478  * NAME
479  *   RB_Free_Link               -- oop
480  * FUNCTION
481  *   free struct + strings
482  * SYNOPSIS
483  */
484 void RB_Free_Link( struct RB_link *arg_link )
485 /*
486  * INPUTS
487  *   struct RB_link *link
488  * AUTHOR
489  *   Koessi
490  * SEE ALSO
491  *   RB_Alloc_Link(), RB_Close_The_Shop()
492  * SOURCE
493  */
494
495 {
496     if ( arg_link )
497     {
498         if ( arg_link->label_name )
499         {
500             free( arg_link->label_name );
501         }
502         if ( arg_link->file_name )
503         {
504             free( arg_link->file_name );
505         }
506         free( arg_link );
507     }
508 }
509
510 /******/
511