1 /*
2 * Copyright 1993 Open Software Foundation, Inc., Cambridge, Massachusetts.
3 * All rights reserved.
4 */
5 /*
6 #pragma ident "%Z%%M% %I% %E% SMI"
7 * Copyright (c) 1994
8 * Open Software Foundation, Inc.
9 *
10 * Permission is hereby granted to use, copy, modify and freely distribute
11 * the software in this file and its documentation for any purpose without
12 * fee, provided that the above copyright notice appears in all copies and
13 * that both the copyright notice and this permission notice appear in
14 * supporting documentation. Further, provided that the name of Open
15 * Software Foundation, Inc. ("OSF") not be used in advertising or
16 * publicity pertaining to distribution of the software without prior
17 * written permission from OSF. OSF makes no representations about the
18 * suitability of this software for any purpose. It is provided "as is"
19 * without express or implied warranty.
20 */
21 /*
22 * Copyright (c) 1996 X Consortium
23 * Copyright (c) 1995, 1996 Dalrymple Consulting
24 *
25 * Permission is hereby granted, free of charge, to any person obtaining a copy
26 * of this software and associated documentation files (the "Software"), to deal
27 * in the Software without restriction, including without limitation the rights
28 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
29 * copies of the Software, and to permit persons to whom the Software is
30 * furnished to do so, subject to the following conditions:
31 *
32 * The above copyright notice and this permission notice shall be included in
33 * all copies or substantial portions of the Software.
34 *
35 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
36 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
37 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
38 * X CONSORTIUM OR DALRYMPLE CONSULTING BE LIABLE FOR ANY CLAIM, DAMAGES OR
39 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
40 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
41 * OTHER DEALINGS IN THE SOFTWARE.
42 *
43 * Except as contained in this notice, the names of the X Consortium and
44 * Dalrymple Consulting shall not be used in advertising or otherwise to
45 * promote the sale, use or other dealings in this Software without prior
46 * written authorization.
47 */
48 /* ________________________________________________________________________
49 *
50 * Program to read an SGML document instance, creating any of several things:
51 *
52 * "translated" output for formatting applications (given a trans. spec)
53 * validation report (given a appropriate trans spec)
54 * tree of the document's structure
55 * statistics about the element usage
56 * summary of the elements used
57 * context of each element used
58 * IDs of each element
59 *
60 * A C structure is created for each element, which includes:
61 * name, attributes, parent, children, content
62 * The tree is descended, and the desired actions performed.
63 *
64 * Takes input from James Clark's "sgmls" program (v. 1.1).
65 * ________________________________________________________________________
66 */
67
68 #ifndef lint
69 static char *RCSid =
70 "$Header: /usr/src/docbook-to-man/Instant/RCS/main.c,v 1.12 1998/06/28 20:10:39 fld Exp fld $";
71 #endif
72
73 #include <stdio.h>
74 #include <stdlib.h>
75 #include <ctype.h>
76 #include <string.h>
77 #include <memory.h>
78 #include <errno.h>
79 #include <sys/types.h>
80 #include <sys/stat.h>
81 #include <sys/file.h>
82 #include <time.h>
83
84 #define STORAGE
85 #include "general.h"
86
87 static int do_context, do_tree, do_summ, do_stats, do_validate, do_idlist;
88 static int do_DATAhack = 0;
89 static char *this_prog;
90 static char *in_file, *out_file;
91 static char *tranfile, *cmapfile, *sdatafile;
92 static char *start_id;
93 static char *last_file;
94 static int last_lineno;
95
96 extern int BOFTTextThresh;
97
98 /* forward references */
99 static void HandleArgs(int, char *[]);
100 static void Initialize1();
101 static void Initialize2();
102 static void ReadInstance(char *);
103 static void DoHelpMessage();
104 extern void Browse();
105
106 /* external reference to version number */
107 extern char _HeadVeRsIoN_[];
108
109 /* ______________________________________________________________________ */
110 /* Program entry point. Look at args, read instance, dispatch to the
111 * correct routines to do the work, and finish.
112 */
113
114 int
main(int ac,char * av[])115 main(
116 int ac,
117 char *av[]
118 )
119 {
120 Initialize1(av[0]);
121 HandleArgs(ac, av);
122 Initialize2();
123
124 ReadInstance(in_file);
125
126 if (interactive) {
127 Browse(); /* this will handle interactive commands */
128 }
129 else {
130 /* Perform tasks based on command line flags... */
131 if (tranfile) {
132 Element_t *e;
133 /* If user wants to start at a particular ID, point to that
134 * element. Else, point to the top of the tree. */
135 if (start_id) {
136 if (!(e=FindElemByID(start_id))) {
137 fprintf(stderr, "Error: Can not find element with ID %s\n",
138 start_id);
139 exit(1);
140 }
141 }
142 else e = DocTree;
143 if (sdatafile) ReadSDATA(sdatafile);
144 if (cmapfile) ReadCharMap(cmapfile);
145 /* If we're doing validation, make output file pointer null.
146 * This means that we generate no output, except error messages. */
147 if (do_validate) outfp = NULL;
148 DoTranslate(e, tranfile, outfp);
149 }
150 if (do_summ) PrintElemSummary(DocTree);
151 if (do_tree) PrintElemTree(DocTree);
152 if (do_stats) PrintStats(DocTree);
153 if (do_context) PrintContext(DocTree);
154 if (do_idlist) PrintIDList();
155 }
156 if (out_file && outfp) fclose(outfp);
157
158 return 0;
159 }
160
161 /* ______________________________________________________________________ */
162 /* Initialization stuff done before dealing with args.
163 * Arguments:
164 * Name of program (string).
165 */
166
167 static void
Initialize1(char * myname)168 Initialize1(
169 char *myname
170 )
171 {
172 time_t tnow;
173 struct tm *nowtm;
174 char *cp, buf[100];
175 extern int gethostname(char *, int); /* not in a system .h file... */
176
177 /* where we try to find data/library files */
178 if (!(tpt_lib=getenv(TPT_LIB))) tpt_lib = DEF_TPT_LIB;
179
180 /* set some global variables */
181 warnings = 1;
182 fold_case = 1;
183 this_prog = myname;
184
185 /* setup global variable mapping */
186 Variables = NewMap(IMS_variables);
187
188 /* set some pre-defined variables */
189 SetMappingNV(Variables, "user", (cp=getenv("USER")) ? cp : "UnknownUser" );
190 time(&tnow);
191 nowtm = localtime(&tnow);
192 strftime(buf, 100, "%a %d %b %Y, %R", nowtm);
193 SetMappingNV(Variables, "date", buf);
194 if (gethostname(buf, 100) < 0) strcpy(buf, "unknown-host");
195 SetMappingNV(Variables, "host", buf);
196 SetMappingNV(Variables, "transpec", tranfile ? tranfile : "??");
197 }
198
199 /* Initialization stuff done after dealing with args. */
200
201 static void
Initialize2()202 Initialize2()
203 {
204 SetMappingNV(Variables, "transpec", tranfile ? tranfile : "??");
205
206 /* If user wants to send output to a file, open the file, and set
207 * the file pointer. Else we send output to standard out. */
208 if (out_file) {
209 if (!(outfp = fopen(out_file, "w"))) {
210 fprintf(stderr, "Could not open output '%s' file for writing.\n%s",
211 out_file, strerror(errno));
212 exit(1);
213 }
214 }
215 else outfp = stdout;
216 }
217
218 /* ______________________________________________________________________ */
219 /* Set a variable. If it is one of the "known" variables, set the
220 * variable in the C code (this program).
221 * Arguments:
222 * Variable name/value string - separated by an '=' (eg, "myname=Sally").
223 */
224 static void
CmdLineSetVariable(char * var)225 CmdLineSetVariable(
226 char *var
227 )
228 {
229 char *cp, buf[100], **tok;
230 int n;
231
232 /* Turn '=' into a space, to isolate the name. Then set variable. */
233 strcpy(buf, var);
234 if ((cp=strchr(buf, '='))) {
235 /* we have "var=value" */
236 *cp = ' ';
237 n = 2;
238 tok = Split(buf, &n, 0);
239 /* see if variable name matches one of our internal ones */
240 if (!strcmp(tok[0], "verbose")) verbose = atoi(tok[1]);
241 else if (!strcmp(tok[0], "warnings")) warnings = atoi(tok[1]);
242 else if (!strcmp(tok[0], "foldcase")) fold_case = atoi(tok[1]);
243 else SetMappingNV(Variables, tok[0], tok[1]);
244 }
245 else {
246 fprintf(stderr, "Expected an '=' in variable assignment: %s. Ignored\n",
247 var);
248 }
249 }
250
251 /* ______________________________________________________________________ */
252 /* Bounce through arguments, setting variables and flags.
253 * Arguments:
254 * Argc and Argv, as passed to main().
255 */
256 static void
HandleArgs(int ac,char * av[])257 HandleArgs(
258 int ac,
259 char *av[]
260 )
261 {
262 int c, errflag=0;
263 extern char *optarg;
264 extern int optind;
265
266 while ((c=getopt(ac, av, "df:t:vc:s:o:huSxIl:bHVWi:D:Z")) != EOF) {
267 switch (c) {
268 case 't': tranfile = optarg; break;
269 case 'v': do_validate = 1; break;
270 case 's': sdatafile = optarg; break;
271 case 'c': cmapfile = optarg; break;
272 case 'h': do_tree = 1; break;
273 case 'u': do_summ = 1; break;
274 case 'S': do_stats = 1; break;
275 case 'x': do_context = 1; break;
276 case 'I': do_idlist = 1; break;
277 case 'l': tpt_lib = optarg; break;
278 case 'i': start_id = optarg; break;
279 case 'o': out_file = optarg; break;
280 case 'd': do_DATAhack = 1; break;
281 case 'f': BOFTTextThresh = atoi(optarg); break;
282 case 'b': interactive = 1; break;
283 case 'W': warnings = 0; break;
284 case 'V': verbose = 1; break;
285 case 'Z': slave = 1; break;
286 case 'H': DoHelpMessage(); exit(0); break;
287 case 'D': CmdLineSetVariable(optarg); break;
288 case '?': errflag = 1; break;
289 }
290 if (errflag) {
291 fprintf(stderr, "Try '%s -H' for help.\n", this_prog);
292 exit(1);
293 }
294 }
295
296 /* input (ESIS) file name */
297 if (optind < ac) in_file = av[optind];
298
299 /* If doing interactive/browsing, we can't take ESIS from stdin. */
300 if (interactive && !in_file) {
301 fprintf(stderr,
302 "You must specify ESIS file on cmd line for browser mode.\n");
303 exit(1);
304 }
305 }
306
307 /* ______________________________________________________________________ */
308 /* Simply print out a help/usage message.
309 */
310
311 static char *help_msg[] = {
312 "",
313 " -t file Print translated output using translation spec in <file>",
314 " -s file <file> contains a list of SDATA entity mappings",
315 " -c file <file> contains a list of character mappings",
316 " -v Validate using translation spec specified with -t",
317 " -i id Consider only subtree starting at element with ID <id>",
318 " -b Interactive browser",
319 " -S Print statistics (how often elements occur, etc.)",
320 " -u Print element usage summary (# of children, depth, etc.)",
321 " -x Print context of each element",
322 " -h Print document hierarchy as a tree",
323 " -o file Write output to <file>. Default is standard output.",
324 " -l dir Set library directory to <dir>. (or env. variable TPT_LIB)",
325 " -I List all IDs used in the instance",
326 " -W Do not print warning messages",
327 " -H Print this help message",
328 " -Dvar=val Set variable 'var' to value 'val'",
329 " file Take input from named file. If not specified, assume stdin.",
330 " File should be output from the 'sgmls' program (ESIS).",
331 NULL
332 };
333
334 static void
DoHelpMessage()335 DoHelpMessage()
336 {
337 char **s = help_msg;
338 printf("usage: %s [option ...] [file]", this_prog);
339 while (*s) puts(*s++);
340 printf("\nVersion: %s\n", _HeadVeRsIoN_);
341 }
342
343 /* ______________________________________________________________________ */
344 /* Remember an external entity for future reference.
345 * Arguments:
346 * Pointer to entity structure to remember.
347 */
348
349 static void
AddEntity(Entity_t * ent)350 AddEntity(
351 Entity_t *ent
352 )
353 {
354 static Entity_t *last_ent;
355
356 if (!Entities) {
357 Malloc(1, Entities, Entity_t);
358 last_ent = Entities;
359 }
360 else {
361 Malloc(1, last_ent->next, Entity_t);
362 last_ent = last_ent->next;
363 }
364 *last_ent = *ent;
365
366 }
367
368 /* Find an entity, given its entity name.
369 * Arguments:
370 * Name of entity to retrieve.
371 */
372 Entity_t *
FindEntity(char * ename)373 FindEntity(
374 char *ename
375 )
376 {
377 Entity_t *n;
378 for (n=Entities; n; n=n->next)
379 if (StrEq(ename, n->ename)) return n;
380 return 0;
381 }
382
383 /* Accumulate lines up to the open tag. Attributes, line number,
384 * entity info, notation info, etc., all come before the open tag.
385 */
386 static Element_t *
AccumElemInfo(FILE * fp)387 AccumElemInfo(
388 FILE *fp
389 )
390 {
391 char buf[LINESIZE+1];
392 int c;
393 int i, na;
394 char *cp, *atval;
395 Mapping_t a[100];
396 Element_t *e;
397 Entity_t ent, *ent2;
398 char **tok;
399 static int Index=0;
400 static Element_t *last_e;
401
402
403 Calloc(1, e, Element_t);
404 memset(&ent, 0, sizeof ent); /* clean space for entity info */
405
406 /* Also, keep a linked list of elements, so we can easily scan through */
407 if (last_e) last_e->next = e;
408 last_e = e;
409
410 e->index = Index++; /* just a unique number for identification */
411
412 /* in case these are not set for this element in the ESIS */
413 e->lineno = last_lineno;
414 e->infile = last_file;
415
416 na = 0;
417 while (1) {
418 if ((c = getc(fp)) == EOF) break;
419 fgets(buf, LINESIZE, fp);
420 stripNL(buf);
421 switch (c) {
422 case EOF: /* End of input */
423 fprintf(stderr, "Error: Unexpectedly reached end of ESIS.\n");
424 exit(1);
425 break;
426
427 case CMD_OPEN: /* (gi */
428 e->gi = AddElemName(buf);
429 if (na > 0) {
430 Malloc(na, e->atts, Mapping_t);
431 memcpy(e->atts, a, na*sizeof(Mapping_t));
432 e->natts = na;
433 na = 0;
434 }
435 /* Check if this elem has a notation attr. If yes, and there
436 is no notation specified, recall the previous one. (feature
437 of sgmls - it does not repeat notation stuff if we the same
438 is used twice in a row) */
439 if (((atval=FindAttValByName(e, "NAME")) ||
440 (atval=FindAttValByName(e, "ENTITYREF")) ||
441 (atval=FindAttValByName(e, "EXTERNAL"))) && /* HACK */
442 (ent2=FindEntity(atval))) {
443 e->entity = ent2;
444 }
445
446 return e;
447 break;
448
449 case CMD_ATT: /* Aname val */
450 i = 3;
451 tok = Split(buf, &i, 0);
452 if (!strcmp(tok[1], "IMPLIED")) break; /* skip IMPLIED atts. */
453 if (!strcmp(tok[1], "CDATA") || !strcmp(tok[1], "TOKEN") ||
454 !strcmp(tok[1], "ENTITY") ||!strcmp(tok[1], "NOTATION"))
455 {
456 a[na].name = AddAttName(tok[0]);
457 a[na].sval = AddAttName(tok[2]);
458 na++;
459 }
460 else {
461 fprintf(stderr, "Error: Bad attr line (%d): A%s %s...\n",
462 e->lineno, tok[0], tok[1]);
463 }
464 break;
465
466 case CMD_LINE: /* Llineno */
467 /* These lines come in 2 forms: "L123" and "L123 file.sgml".
468 * Filename is given only at 1st occurance. Remember it.
469 */
470 if ((cp = strchr(buf, ' '))) {
471 cp++;
472 last_file = strdup(cp);
473 }
474 last_lineno = e->lineno = atoi(buf);
475 e->infile = last_file;
476 break;
477
478 case CMD_DATA: /* -data */
479 fprintf(stderr, "Error: Data in AccumElemInfo, line %d:\n%c%s\n",
480 e->lineno, c,buf);
481 /*return e;*/
482 exit(1);
483 break;
484
485 case CMD_D_ATT: /* Dename name val */
486
487 case CMD_NOTATION: /* Nnname */
488 case CMD_PI: /* ?pi */
489 /* This should be reworked soon, as it
490 forces all PI's before the first GI
491 to be ignored. -CSS */
492 break;
493
494 case CMD_EXT_ENT: /* Eename typ nname */
495 i = 3;
496 tok = Split(buf, &i, 0);
497 ent.ename = strdup(tok[0]);
498 ent.type = strdup(tok[1]);
499 ent.nname = strdup(tok[2]);
500 AddEntity(&ent);
501 break;
502 case CMD_INT_ENT: /* Iename typ text */
503 fprintf(stderr, "Error: Got CMD_INT_ENT in AccumElemInfo: %s\n", buf);
504 break;
505 case CMD_SYSID: /* ssysid */
506 ent.sysid = strdup(buf);
507 break;
508 case CMD_PUBID: /* ppubid */
509 ent.pubid = strdup(buf);
510 break;
511 case CMD_FILENAME: /* ffilename */
512 ent.fname = strdup(buf);
513 break;
514
515 case CMD_CLOSE: /* )gi */
516 case CMD_SUBDOC: /* Sename */
517 case CMD_SUBDOC_S: /* {ename */
518 case CMD_SUBDOC_E: /* }ename */
519 case CMD_EXT_REF: /* &name */
520 case CMD_APPINFO: /* #text */
521 case CMD_CONFORM: /* C */
522 default:
523 fprintf(stderr, "Error: Unexpected input in AccumElemInfo, %d:\n%c%s\n",
524 e->lineno, c,buf);
525 exit(1);
526 break;
527 }
528 }
529 if ( e && e->gi )
530 fprintf(stderr, "Error: End of AccumElemInfo - should not be here: %s\n", e->gi);
531 else
532 fprintf(stderr, "Invalid SGML. File cannot be formatted\n");
533 /* return e;*/
534 exit(1);
535 }
536
537 /* Read ESIS lines.
538 */
539
540 #define BASECONTSIZE 500 /* starting size for number of children */
541 #define GROWCONTSIZE 500 /* if we need to grow that, by this much */
542
543 static Element_t *
ReadESIS(FILE * fp,int depth)544 ReadESIS(
545 FILE *fp,
546 int depth
547 )
548 {
549 char *buf;
550 int i, c, ncont, contsize;
551 Element_t *e;
552 Content_t *cont;
553
554 Malloc( LINESIZE+1, buf, char );
555 contsize = BASECONTSIZE; /* starting content size */
556 Malloc( contsize, cont, Content_t );
557
558 /* Read input stream - the output of "sgmls", called "ESIS". */
559 e = AccumElemInfo(fp);
560 e->depth = depth;
561
562 ncont = 0;
563 while (1) {
564 if ((c = getc(fp)) == EOF) break;
565 if ( ncont >= contsize ) {
566 contsize += GROWCONTSIZE;
567 Realloc( contsize, cont, Content_t );
568 }
569
570 switch (c) {
571 case EOF: /* End of input */
572 break;
573
574 case CMD_DATA: /* -data */
575 fgets(buf, LINESIZE, fp);
576 stripNL(buf);
577 if (do_DATAhack && (buf[0] == '\\') && (buf[1] == 'n') ) {
578 buf[0] = -1; /* simulate "^" command */
579 memcpy(&buf[1], &buf[2], strlen(buf)-1);
580 }
581 cont[ncont].ch.data = strdup(buf);
582 cont[ncont].type = CMD_DATA;
583 ncont++;
584 break;
585
586 case CMD_PI: /* ?pi */
587 fgets(buf, LINESIZE, fp);
588 stripNL(buf);
589 cont[ncont].type = CMD_PI;
590 cont[ncont].ch.data = strdup(buf);
591 ncont++;
592 break;
593
594 case CMD_CLOSE: /* )gi */
595 fgets(buf, LINESIZE, fp);
596 stripNL(buf);
597 if (ncont) {
598 e->ncont = ncont;
599 Malloc(ncont, e->cont, Content_t);
600 for (i=0; i<ncont; i++) e->cont[i] = cont[i];
601 }
602 free(buf);
603 free(cont);
604 return e;
605 break;
606
607 case CMD_OPEN: /* (gi */
608 /*fprintf(stderr, "+++++ OPEN +++\n");*/
609 /* break;*/
610
611 case CMD_ATT: /* Aname val */
612 case CMD_D_ATT: /* Dename name val */
613 case CMD_NOTATION: /* Nnname */
614 case CMD_EXT_ENT: /* Eename typ nname */
615 case CMD_INT_ENT: /* Iename typ text */
616 case CMD_SYSID: /* ssysid */
617 case CMD_PUBID: /* ppubid */
618 case CMD_FILENAME: /* ffilename */
619 ungetc(c, fp);
620 cont[ncont].ch.elem = ReadESIS(fp, depth+1);
621 cont[ncont].type = CMD_OPEN;
622 cont[ncont].ch.elem->parent = e;
623 ncont++;
624 break;
625
626 case CMD_LINE: /* Llineno */
627 fgets(buf, LINESIZE, fp);
628 break; /* ignore these here */
629
630 case CMD_SUBDOC: /* Sename */
631 case CMD_SUBDOC_S: /* {ename */
632 case CMD_SUBDOC_E: /* }ename */
633 case CMD_EXT_REF: /* &name */
634 case CMD_APPINFO: /* #text */
635 case CMD_CONFORM: /* C */
636 default:
637 fgets(buf, LINESIZE, fp);
638 fprintf(stderr, "Error: Unexpected input at %d: '%c%s'\n",
639 e->lineno, c, buf);
640 exit(1);
641 break;
642 }
643 }
644 if( e && e->gi)
645 fprintf(stderr, "Error: End of ReadESIS - should not be here: %s\n", e->gi);
646 else
647 fprintf(stderr, "Error: Invalid SGML: End of ReadESIS - should not be here:\n");
648
649 free(buf);
650 free(cont);
651 return NULL;
652 }
653
654 /* ______________________________________________________________________ */
655 /* Read input stream, creating a tree in memory of the elements and data.
656 * Arguments:
657 * Filename where instance's ESIS is.
658 */
659 static void
ReadInstance(char * filename)660 ReadInstance(
661 char *filename
662 )
663 {
664 int i, n;
665 FILE *fp;
666 Element_t *e;
667 char *idatt;
668
669 if (filename) { /* if we specified input file. else stdin */
670 if ((fp=fopen(filename, "r")) == NULL) {
671 perror(filename);
672 exit(1);
673 }
674 }
675 else fp = stdin;
676 last_file = filename;
677 DocTree = ReadESIS(fp, 0);
678 if (filename) fclose(fp);
679
680 /* Traverse tree, filling in econt and figuring out which child
681 * (ie. what birth order) each element is. */
682 DocTree->my_eorder = -1;
683 for (e=DocTree; e; e=e->next) {
684
685 /* count element children */
686 for (i=0,n=0; i<e->ncont; i++) if (IsContElem(e,i)) n++;
687 if (n > 0) Calloc(n, e->econt, Element_t *);
688 for (i=0; i<e->ncont; i++)
689 if (IsContElem(e,i)) e->econt[e->necont++] = ContElem(e,i);
690
691 /* count data children */
692 for (i=0,n=0; i<e->ncont; i++) if (IsContData(e,i)) n++;
693 if (n > 0) Calloc(n, e->dcont, char *);
694 for (i=0; i<e->ncont; i++)
695 if (IsContData(e,i)) e->dcont[e->ndcont++] = ContData(e,i);
696
697 /* where in child order order */
698 for (i=0; i<e->necont; i++)
699 e->econt[i]->my_eorder = i;
700
701 /* Does this element have an ID? */
702 for (i=0; i<e->natts; i++) {
703 if ((idatt=FindAttValByName(e, "ID"))) {
704 AddID(e, idatt);
705 /* remember ID value for quick reference */
706 e->id = idatt;
707 break;
708 }
709 }
710 }
711 return;
712 }
713
714 /* ______________________________________________________________________ */
715