1/*
2 * xmlcatalog.c : a small utility program to handle XML catalogs
3 *
4 * See Copyright for the status of this software.
5 *
6 * daniel@veillard.com
7 */
8
9#include "libxml.h"
10
11#include <string.h>
12#include <stdio.h>
13#include <stdarg.h>
14
15#ifdef HAVE_STDLIB_H
16#include <stdlib.h>
17#endif
18
19#ifdef HAVE_LIBREADLINE
20#include <readline/readline.h>
21#ifdef HAVE_LIBHISTORY
22#include <readline/history.h>
23#endif
24#endif
25
26#include <libxml/xmlmemory.h>
27#include <libxml/uri.h>
28#include <libxml/catalog.h>
29#include <libxml/parser.h>
30#include <libxml/globals.h>
31
32#if defined(LIBXML_CATALOG_ENABLED) && defined(LIBXML_OUTPUT_ENABLED)
33static int shell = 0;
34static int sgml = 0;
35static int noout = 0;
36static int create = 0;
37static int add = 0;
38static int del = 0;
39static int convert = 0;
40static int no_super_update = 0;
41static int verbose = 0;
42static char *filename = NULL;
43
44
45#ifndef XML_SGML_DEFAULT_CATALOG
46#define XML_SGML_DEFAULT_CATALOG "/etc/sgml/catalog"
47#endif
48
49/************************************************************************
50 * 									*
51 * 			Shell Interface					*
52 * 									*
53 ************************************************************************/
54/**
55 * xmlShellReadline:
56 * @prompt:  the prompt value
57 *
58 * Read a string
59 *
60 * Returns a pointer to it or NULL on EOF the caller is expected to
61 *     free the returned string.
62 */
63static char *
64xmlShellReadline(const char *prompt) {
65#ifdef HAVE_LIBREADLINE
66    char *line_read;
67
68    /* Get a line from the user. */
69    line_read = readline (prompt);
70
71    /* If the line has any text in it, save it on the history. */
72    if (line_read && *line_read)
73	add_history (line_read);
74
75    return (line_read);
76#else
77    char line_read[501];
78    char *ret;
79    int len;
80
81    if (prompt != NULL)
82	fprintf(stdout, "%s", prompt);
83    if (!fgets(line_read, 500, stdin))
84        return(NULL);
85    line_read[500] = 0;
86    len = strlen(line_read);
87    ret = (char *) malloc(len + 1);
88    if (ret != NULL) {
89	memcpy (ret, line_read, len + 1);
90    }
91    return(ret);
92#endif
93}
94
95static void usershell(void) {
96    char *cmdline = NULL, *cur;
97    int nbargs;
98    char command[100];
99    char arg[400];
100    char *argv[20];
101    int i, ret;
102    xmlChar *ans;
103
104    while (1) {
105	cmdline = xmlShellReadline("> ");
106	if (cmdline == NULL)
107	    return;
108
109	/*
110	 * Parse the command itself
111	 */
112	cur = cmdline;
113	nbargs = 0;
114	while ((*cur == ' ') || (*cur == '\t')) cur++;
115	i = 0;
116	while ((*cur != ' ') && (*cur != '\t') &&
117	       (*cur != '\n') && (*cur != '\r')) {
118	    if (*cur == 0)
119		break;
120	    command[i++] = *cur++;
121	}
122	command[i] = 0;
123	if (i == 0) {
124	    free(cmdline);
125	    continue;
126	}
127
128	/*
129	 * Parse the argument string
130	 */
131	memset(arg, 0, sizeof(arg));
132	while ((*cur == ' ') || (*cur == '\t')) cur++;
133	i = 0;
134	while ((*cur != '\n') && (*cur != '\r') && (*cur != 0)) {
135	    if (*cur == 0)
136		break;
137	    arg[i++] = *cur++;
138	}
139	arg[i] = 0;
140
141	/*
142	 * Parse the arguments
143	 */
144	i = 0;
145	nbargs = 0;
146	cur = arg;
147	memset(argv, 0, sizeof(argv));
148	while (*cur != 0) {
149	    while ((*cur == ' ') || (*cur == '\t')) cur++;
150	    if (*cur == '\'') {
151		cur++;
152		argv[i] = cur;
153		while ((*cur != 0) && (*cur != '\'')) cur++;
154		if (*cur == '\'') {
155		    *cur = 0;
156		    nbargs++;
157		    i++;
158		    cur++;
159		}
160	    } else if (*cur == '"') {
161		cur++;
162		argv[i] = cur;
163		while ((*cur != 0) && (*cur != '"')) cur++;
164		if (*cur == '"') {
165		    *cur = 0;
166		    nbargs++;
167		    i++;
168		    cur++;
169		}
170	    } else {
171		argv[i] = cur;
172		while ((*cur != 0) && (*cur != ' ') && (*cur != '\t'))
173		    cur++;
174		*cur = 0;
175		nbargs++;
176		i++;
177		cur++;
178	    }
179	}
180
181	/*
182	 * start interpreting the command
183	 */
184        if (!strcmp(command, "exit"))
185	    break;
186        if (!strcmp(command, "quit"))
187	    break;
188        if (!strcmp(command, "bye"))
189	    break;
190	if (!strcmp(command, "public")) {
191	    if (nbargs != 1) {
192		printf("public requires 1 arguments\n");
193	    } else {
194		ans = xmlCatalogResolvePublic((const xmlChar *) argv[0]);
195		if (ans == NULL) {
196		    printf("No entry for PUBLIC %s\n", argv[0]);
197		} else {
198		    printf("%s\n", (char *) ans);
199		    xmlFree(ans);
200		}
201	    }
202	} else if (!strcmp(command, "system")) {
203	    if (nbargs != 1) {
204		printf("system requires 1 arguments\n");
205	    } else {
206		ans = xmlCatalogResolveSystem((const xmlChar *) argv[0]);
207		if (ans == NULL) {
208		    printf("No entry for SYSTEM %s\n", argv[0]);
209		} else {
210		    printf("%s\n", (char *) ans);
211		    xmlFree(ans);
212		}
213	    }
214	} else if (!strcmp(command, "add")) {
215	    if (sgml) {
216		if ((nbargs != 3) && (nbargs != 2)) {
217		    printf("add requires 2 or 3 arguments\n");
218		} else {
219		    if (argv[2] == NULL)
220			ret = xmlCatalogAdd(BAD_CAST argv[0], NULL,
221					    BAD_CAST argv[1]);
222		    else
223			ret = xmlCatalogAdd(BAD_CAST argv[0], BAD_CAST argv[1],
224					    BAD_CAST argv[2]);
225		    if (ret != 0)
226			printf("add command failed\n");
227		}
228	    } else {
229		if ((nbargs != 3) && (nbargs != 2)) {
230		    printf("add requires 2 or 3 arguments\n");
231		} else {
232		    if (argv[2] == NULL)
233			ret = xmlCatalogAdd(BAD_CAST argv[0], NULL,
234					    BAD_CAST argv[1]);
235		    else
236			ret = xmlCatalogAdd(BAD_CAST argv[0], BAD_CAST argv[1],
237					    BAD_CAST argv[2]);
238		    if (ret != 0)
239			printf("add command failed\n");
240		}
241	    }
242	} else if (!strcmp(command, "del")) {
243	    if (nbargs != 1) {
244		printf("del requires 1\n");
245	    } else {
246		ret = xmlCatalogRemove(BAD_CAST argv[0]);
247		if (ret <= 0)
248		    printf("del command failed\n");
249
250	    }
251	} else if (!strcmp(command, "resolve")) {
252	    if (nbargs != 2) {
253		printf("resolve requires 2 arguments\n");
254	    } else {
255		ans = xmlCatalogResolve(BAD_CAST argv[0],
256			                BAD_CAST argv[1]);
257		if (ans == NULL) {
258		    printf("Resolver failed to find an answer\n");
259		} else {
260		    printf("%s\n", (char *) ans);
261		    xmlFree(ans);
262		}
263	    }
264	} else if (!strcmp(command, "dump")) {
265	    if (nbargs != 0) {
266		printf("dump has no arguments\n");
267	    } else {
268		xmlCatalogDump(stdout);
269	    }
270	} else if (!strcmp(command, "debug")) {
271	    if (nbargs != 0) {
272		printf("debug has no arguments\n");
273	    } else {
274		verbose++;
275		xmlCatalogSetDebug(verbose);
276	    }
277	} else if (!strcmp(command, "quiet")) {
278	    if (nbargs != 0) {
279		printf("quiet has no arguments\n");
280	    } else {
281		if (verbose > 0)
282		    verbose--;
283		xmlCatalogSetDebug(verbose);
284	    }
285	} else {
286	    if (strcmp(command, "help")) {
287		printf("Unrecognized command %s\n", command);
288	    }
289	    printf("Commands available:\n");
290	    printf("\tpublic PublicID: make a PUBLIC identifier lookup\n");
291	    printf("\tsystem SystemID: make a SYSTEM identifier lookup\n");
292	    printf("\tresolve PublicID SystemID: do a full resolver lookup\n");
293	    printf("\tadd 'type' 'orig' 'replace' : add an entry\n");
294	    printf("\tdel 'values' : remove values\n");
295	    printf("\tdump: print the current catalog state\n");
296	    printf("\tdebug: increase the verbosity level\n");
297	    printf("\tquiet: decrease the verbosity level\n");
298	    printf("\texit:  quit the shell\n");
299	}
300	free(cmdline); /* not xmlFree here ! */
301    }
302}
303
304/************************************************************************
305 * 									*
306 * 			Main						*
307 * 									*
308 ************************************************************************/
309static void usage(const char *name) {
310    /* split into 2 printf's to avoid overly long string (gcc warning) */
311    printf("\
312Usage : %s [options] catalogfile entities...\n\
313\tParse the catalog file and query it for the entities\n\
314\t--sgml : handle SGML Super catalogs for --add and --del\n\
315\t--shell : run a shell allowing interactive queries\n\
316\t--create : create a new catalog\n\
317\t--add 'type' 'orig' 'replace' : add an XML entry\n\
318\t--add 'entry' : add an SGML entry\n", name);
319    printf("\
320\t--del 'values' : remove values\n\
321\t--noout: avoid dumping the result on stdout\n\
322\t         used with --add or --del, it saves the catalog changes\n\
323\t         and with --sgml it automatically updates the super catalog\n\
324\t--no-super-update: do not update the SGML super catalog\n\
325\t-v --verbose : provide debug informations\n");
326}
327int main(int argc, char **argv) {
328    int i;
329    int ret;
330    int exit_value = 0;
331
332
333    if (argc <= 1) {
334	usage(argv[0]);
335	return(1);
336    }
337
338    LIBXML_TEST_VERSION
339    for (i = 1; i < argc ; i++) {
340	if (!strcmp(argv[i], "-"))
341	    break;
342
343	if (argv[i][0] != '-')
344	    break;
345	if ((!strcmp(argv[i], "-verbose")) ||
346	    (!strcmp(argv[i], "-v")) ||
347	    (!strcmp(argv[i], "--verbose"))) {
348	    verbose++;
349	    xmlCatalogSetDebug(verbose);
350	} else if ((!strcmp(argv[i], "-noout")) ||
351	    (!strcmp(argv[i], "--noout"))) {
352            noout = 1;
353	} else if ((!strcmp(argv[i], "-shell")) ||
354	    (!strcmp(argv[i], "--shell"))) {
355	    shell++;
356            noout = 1;
357	} else if ((!strcmp(argv[i], "-sgml")) ||
358	    (!strcmp(argv[i], "--sgml"))) {
359	    sgml++;
360	} else if ((!strcmp(argv[i], "-create")) ||
361	    (!strcmp(argv[i], "--create"))) {
362	    create++;
363	} else if ((!strcmp(argv[i], "-convert")) ||
364	    (!strcmp(argv[i], "--convert"))) {
365	    convert++;
366	} else if ((!strcmp(argv[i], "-no-super-update")) ||
367	    (!strcmp(argv[i], "--no-super-update"))) {
368	    no_super_update++;
369	} else if ((!strcmp(argv[i], "-add")) ||
370	    (!strcmp(argv[i], "--add"))) {
371	    if (sgml)
372		i += 2;
373	    else
374		i += 3;
375	    add++;
376	} else if ((!strcmp(argv[i], "-del")) ||
377	    (!strcmp(argv[i], "--del"))) {
378	    i += 1;
379	    del++;
380	} else {
381	    fprintf(stderr, "Unknown option %s\n", argv[i]);
382	    usage(argv[0]);
383	    return(1);
384	}
385    }
386
387    for (i = 1; i < argc; i++) {
388	if ((!strcmp(argv[i], "-add")) ||
389	    (!strcmp(argv[i], "--add"))) {
390	    if (sgml)
391		i += 2;
392	    else
393		i += 3;
394	    continue;
395	} else if ((!strcmp(argv[i], "-del")) ||
396	    (!strcmp(argv[i], "--del"))) {
397	    i += 1;
398
399	    /* No catalog entry specified */
400	    if (i == argc || (sgml && i + 1 == argc)) {
401		fprintf(stderr, "No catalog entry specified to remove from\n");
402		usage (argv[0]);
403		return(1);
404	    }
405
406	    continue;
407	} else if (argv[i][0] == '-')
408	    continue;
409	filename = argv[i];
410	    ret = xmlLoadCatalog(argv[i]);
411	    if ((ret < 0) && (create)) {
412		xmlCatalogAdd(BAD_CAST "catalog", BAD_CAST argv[i], NULL);
413	    }
414	break;
415    }
416
417    if (convert)
418        ret = xmlCatalogConvert();
419
420    if ((add) || (del)) {
421	for (i = 1; i < argc ; i++) {
422	    if (!strcmp(argv[i], "-"))
423		break;
424
425	    if (argv[i][0] != '-')
426		continue;
427	    if (strcmp(argv[i], "-add") && strcmp(argv[i], "--add") &&
428		strcmp(argv[i], "-del") && strcmp(argv[i], "--del"))
429		continue;
430
431	    if (sgml) {
432		/*
433		 * Maintenance of SGML catalogs.
434		 */
435		xmlCatalogPtr catal = NULL;
436		xmlCatalogPtr super = NULL;
437
438		catal = xmlLoadSGMLSuperCatalog(argv[i + 1]);
439
440		if ((!strcmp(argv[i], "-add")) ||
441		    (!strcmp(argv[i], "--add"))) {
442		    if (catal == NULL)
443			catal = xmlNewCatalog(1);
444		    xmlACatalogAdd(catal, BAD_CAST "CATALOG",
445					 BAD_CAST argv[i + 2], NULL);
446
447		    if (!no_super_update) {
448			super = xmlLoadSGMLSuperCatalog(XML_SGML_DEFAULT_CATALOG);
449			if (super == NULL)
450			    super = xmlNewCatalog(1);
451
452			xmlACatalogAdd(super, BAD_CAST "CATALOG",
453					     BAD_CAST argv[i + 1], NULL);
454		    }
455		} else {
456		    if (catal != NULL)
457			ret = xmlACatalogRemove(catal, BAD_CAST argv[i + 2]);
458		    else
459			ret = -1;
460		    if (ret < 0) {
461			fprintf(stderr, "Failed to remove entry from %s\n",
462				argv[i + 1]);
463			exit_value = 1;
464		    }
465		    if ((!no_super_update) && (noout) && (catal != NULL) &&
466			(xmlCatalogIsEmpty(catal))) {
467			super = xmlLoadSGMLSuperCatalog(
468				   XML_SGML_DEFAULT_CATALOG);
469			if (super != NULL) {
470			    ret = xmlACatalogRemove(super,
471				    BAD_CAST argv[i + 1]);
472			    if (ret < 0) {
473				fprintf(stderr,
474					"Failed to remove entry from %s\n",
475					XML_SGML_DEFAULT_CATALOG);
476				exit_value = 1;
477			    }
478			}
479		    }
480		}
481		if (noout) {
482		    FILE *out;
483
484		    if (xmlCatalogIsEmpty(catal)) {
485			remove(argv[i + 1]);
486		    } else {
487			out = fopen(argv[i + 1], "w");
488			if (out == NULL) {
489			    fprintf(stderr, "could not open %s for saving\n",
490				    argv[i + 1]);
491			    exit_value = 2;
492			    noout = 0;
493			} else {
494			    xmlACatalogDump(catal, out);
495			    fclose(out);
496			}
497		    }
498		    if (!no_super_update && super != NULL) {
499			if (xmlCatalogIsEmpty(super)) {
500			    remove(XML_SGML_DEFAULT_CATALOG);
501			} else {
502			    out = fopen(XML_SGML_DEFAULT_CATALOG, "w");
503			    if (out == NULL) {
504				fprintf(stderr,
505					"could not open %s for saving\n",
506					XML_SGML_DEFAULT_CATALOG);
507				exit_value = 2;
508				noout = 0;
509			    } else {
510
511				xmlACatalogDump(super, out);
512				fclose(out);
513			    }
514			}
515		    }
516		} else {
517		    xmlACatalogDump(catal, stdout);
518		}
519		i += 2;
520	    } else {
521		if ((!strcmp(argv[i], "-add")) ||
522		    (!strcmp(argv[i], "--add"))) {
523			if ((argv[i + 3] == NULL) || (argv[i + 3][0] == 0))
524			    ret = xmlCatalogAdd(BAD_CAST argv[i + 1], NULL,
525						BAD_CAST argv[i + 2]);
526			else
527			    ret = xmlCatalogAdd(BAD_CAST argv[i + 1],
528						BAD_CAST argv[i + 2],
529						BAD_CAST argv[i + 3]);
530			if (ret != 0) {
531			    printf("add command failed\n");
532			    exit_value = 3;
533			}
534			i += 3;
535		} else if ((!strcmp(argv[i], "-del")) ||
536		    (!strcmp(argv[i], "--del"))) {
537		    ret = xmlCatalogRemove(BAD_CAST argv[i + 1]);
538		    if (ret < 0) {
539			fprintf(stderr, "Failed to remove entry %s\n",
540				argv[i + 1]);
541			exit_value = 1;
542		    }
543		    i += 1;
544		}
545	    }
546	}
547
548    } else if (shell) {
549	usershell();
550    } else {
551	for (i++; i < argc; i++) {
552	    xmlURIPtr uri;
553	    xmlChar *ans;
554
555	    uri = xmlParseURI(argv[i]);
556	    if (uri == NULL) {
557		ans = xmlCatalogResolvePublic((const xmlChar *) argv[i]);
558		if (ans == NULL) {
559		    printf("No entry for PUBLIC %s\n", argv[i]);
560		    exit_value = 4;
561		} else {
562		    printf("%s\n", (char *) ans);
563		    xmlFree(ans);
564		}
565	    } else {
566                xmlFreeURI(uri);
567		ans = xmlCatalogResolveSystem((const xmlChar *) argv[i]);
568		if (ans == NULL) {
569		    printf("No entry for SYSTEM %s\n", argv[i]);
570		    ans = xmlCatalogResolveURI ((const xmlChar *) argv[i]);
571		    if (ans == NULL) {
572			printf ("No entry for URI %s\n", argv[i]);
573		        exit_value = 4;
574		    } else {
575		        printf("%s\n", (char *) ans);
576			xmlFree (ans);
577		    }
578		} else {
579		    printf("%s\n", (char *) ans);
580		    xmlFree(ans);
581		}
582	    }
583	}
584    }
585    if ((!sgml) && ((add) || (del) || (create) || (convert))) {
586	if (noout && filename && *filename) {
587	    FILE *out;
588
589	    out = fopen(filename, "w");
590	    if (out == NULL) {
591		fprintf(stderr, "could not open %s for saving\n", filename);
592		exit_value = 2;
593		noout = 0;
594	    } else {
595		xmlCatalogDump(out);
596	    }
597	} else {
598	    xmlCatalogDump(stdout);
599	}
600    }
601
602    /*
603     * Cleanup and check for memory leaks
604     */
605    xmlCleanupParser();
606    xmlMemoryDump();
607    return(exit_value);
608}
609#else
610int main(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED) {
611    fprintf(stderr, "libxml was not compiled with catalog and output support\n");
612    return(1);
613}
614#endif
615