catalog.c revision b031cef5b537d8eda30f508627f08bc642bbd31d
1/**
2 * catalog.c: set of generic Catalog related routines
3 *
4 * Reference:  SGML Open Technical Resolution TR9401:1997.
5 *             http://www.jclark.com/sp/catalog.htm
6 *
7 *             XML Catalogs Working Draft 06 August 2001
8 *             http://www.oasis-open.org/committees/entity/spec-2001-08-06.html
9 *
10 * See Copyright for the status of this software.
11 *
12 * Daniel.Veillard@imag.fr
13 */
14
15#define IN_LIBXML
16#include "libxml.h"
17
18#ifdef LIBXML_CATALOG_ENABLED
19#ifdef HAVE_SYS_TYPES_H
20#include <sys/types.h>
21#endif
22#ifdef HAVE_SYS_STAT_H
23#include <sys/stat.h>
24#endif
25#ifdef HAVE_UNISTD_H
26#include <unistd.h>
27#endif
28#ifdef HAVE_FCNTL_H
29#include <fcntl.h>
30#endif
31#ifdef HAVE_STDLIB_H
32#include <stdlib.h>
33#endif
34#include <string.h>
35#include <libxml/xmlmemory.h>
36#include <libxml/hash.h>
37#include <libxml/uri.h>
38#include <libxml/parserInternals.h>
39#include <libxml/catalog.h>
40#include <libxml/xmlerror.h>
41#include <libxml/threads.h>
42#include <libxml/globals.h>
43
44#define MAX_DELEGATE	50
45#define MAX_CATAL_DEPTH	50
46
47/**
48 * TODO:
49 *
50 * macro to flag unimplemented blocks
51 * XML_CATALOG_PREFER user env to select between system/public prefered
52 * option. C.f. Richard Tobin <richard@cogsci.ed.ac.uk>
53 *> Just FYI, I am using an environment variable XML_CATALOG_PREFER with
54 *> values "system" and "public".  I have made the default be "system" to
55 *> match yours.
56 */
57#define TODO 								\
58    xmlGenericError(xmlGenericErrorContext,				\
59	    "Unimplemented block at %s:%d\n",				\
60            __FILE__, __LINE__);
61
62#define XML_URN_PUBID "urn:publicid:"
63#define XML_CATAL_BREAK ((xmlChar *) -1)
64#ifndef XML_XML_DEFAULT_CATALOG
65#define XML_XML_DEFAULT_CATALOG "file:///etc/xml/catalog"
66#endif
67#ifndef XML_SGML_DEFAULT_CATALOG
68#define XML_SGML_DEFAULT_CATALOG "file:///etc/sgml/catalog"
69#endif
70
71#if defined(_WIN32) && defined(_MSC_VER)
72#undef XML_XML_DEFAULT_CATALOG
73static char XML_XML_DEFAULT_CATALOG[256] = "file:///etc/xml/catalog";
74void* __stdcall GetModuleHandleA(const char*);
75unsigned long __stdcall GetModuleFileNameA(void*, char*, unsigned long);
76#endif
77
78static xmlChar *xmlCatalogNormalizePublic(const xmlChar *pubID);
79static int xmlExpandCatalog(xmlCatalogPtr catal, const char *filename);
80
81/************************************************************************
82 *									*
83 *			Types, all private				*
84 *									*
85 ************************************************************************/
86
87typedef enum {
88    XML_CATA_REMOVED = -1,
89    XML_CATA_NONE = 0,
90    XML_CATA_CATALOG,
91    XML_CATA_BROKEN_CATALOG,
92    XML_CATA_NEXT_CATALOG,
93    XML_CATA_GROUP,
94    XML_CATA_PUBLIC,
95    XML_CATA_SYSTEM,
96    XML_CATA_REWRITE_SYSTEM,
97    XML_CATA_DELEGATE_PUBLIC,
98    XML_CATA_DELEGATE_SYSTEM,
99    XML_CATA_URI,
100    XML_CATA_REWRITE_URI,
101    XML_CATA_DELEGATE_URI,
102    SGML_CATA_SYSTEM,
103    SGML_CATA_PUBLIC,
104    SGML_CATA_ENTITY,
105    SGML_CATA_PENTITY,
106    SGML_CATA_DOCTYPE,
107    SGML_CATA_LINKTYPE,
108    SGML_CATA_NOTATION,
109    SGML_CATA_DELEGATE,
110    SGML_CATA_BASE,
111    SGML_CATA_CATALOG,
112    SGML_CATA_DOCUMENT,
113    SGML_CATA_SGMLDECL
114} xmlCatalogEntryType;
115
116typedef struct _xmlCatalogEntry xmlCatalogEntry;
117typedef xmlCatalogEntry *xmlCatalogEntryPtr;
118struct _xmlCatalogEntry {
119    struct _xmlCatalogEntry *next;
120    struct _xmlCatalogEntry *parent;
121    struct _xmlCatalogEntry *children;
122    xmlCatalogEntryType type;
123    xmlChar *name;
124    xmlChar *value;
125    xmlChar *URL;  /* The expanded URL using the base */
126    xmlCatalogPrefer prefer;
127    int dealloc;
128    int depth;
129    struct _xmlCatalogEntry *group;
130};
131
132typedef enum {
133    XML_XML_CATALOG_TYPE = 1,
134    XML_SGML_CATALOG_TYPE
135} xmlCatalogType;
136
137#define XML_MAX_SGML_CATA_DEPTH 10
138struct _xmlCatalog {
139    xmlCatalogType type;	/* either XML or SGML */
140
141    /*
142     * SGML Catalogs are stored as a simple hash table of catalog entries
143     * Catalog stack to check against overflows when building the
144     * SGML catalog
145     */
146    char *catalTab[XML_MAX_SGML_CATA_DEPTH];	/* stack of catals */
147    int          catalNr;	/* Number of current catal streams */
148    int          catalMax;	/* Max number of catal streams */
149    xmlHashTablePtr sgml;
150
151    /*
152     * XML Catalogs are stored as a tree of Catalog entries
153     */
154    xmlCatalogPrefer prefer;
155    xmlCatalogEntryPtr xml;
156};
157
158/************************************************************************
159 *									*
160 *			Global variables				*
161 *									*
162 ************************************************************************/
163
164/*
165 * Those are preferences
166 */
167static int xmlDebugCatalogs = 0;   /* used for debugging */
168static xmlCatalogAllow xmlCatalogDefaultAllow = XML_CATA_ALLOW_ALL;
169static xmlCatalogPrefer xmlCatalogDefaultPrefer = XML_CATA_PREFER_PUBLIC;
170
171/*
172 * Hash table containing all the trees of XML catalogs parsed by
173 * the application.
174 */
175static xmlHashTablePtr xmlCatalogXMLFiles = NULL;
176
177/*
178 * The default catalog in use by the application
179 */
180static xmlCatalogPtr xmlDefaultCatalog = NULL;
181
182/*
183 * A mutex for modifying the shared global catalog(s)
184 * xmlDefaultCatalog tree.
185 * It also protects xmlCatalogXMLFiles
186 * The core of this readers/writer scheme is in xmlFetchXMLCatalogFile()
187 */
188static xmlRMutexPtr xmlCatalogMutex = NULL;
189
190/*
191 * Whether the catalog support was initialized.
192 */
193static int xmlCatalogInitialized = 0;
194
195/************************************************************************
196 *									*
197 * 			Catalog error handlers				*
198 *									*
199 ************************************************************************/
200
201/**
202 * xmlCatalogErrMemory:
203 * @extra:  extra informations
204 *
205 * Handle an out of memory condition
206 */
207static void
208xmlCatalogErrMemory(const char *extra)
209{
210    __xmlRaiseError(NULL, NULL, NULL, NULL, NULL, XML_FROM_CATALOG,
211                    XML_ERR_NO_MEMORY, XML_ERR_ERROR, NULL, 0,
212		    extra, NULL, NULL, 0, 0,
213		    "Memory allocation failed : %s\n", extra);
214}
215
216/**
217 * xmlCatalogErr:
218 * @catal: the Catalog entry
219 * @node: the context node
220 * @msg:  the error message
221 * @extra:  extra informations
222 *
223 * Handle a catalog error
224 */
225static void
226xmlCatalogErr(xmlCatalogEntryPtr catal, xmlNodePtr node, int error,
227               const char *msg, const xmlChar *str1, const xmlChar *str2,
228	       const xmlChar *str3)
229{
230    __xmlRaiseError(NULL, NULL, NULL, catal, node, XML_FROM_CATALOG,
231                    error, XML_ERR_ERROR, NULL, 0,
232		    (const char *) str1, (const char *) str2,
233		    (const char *) str3, 0, 0,
234		    msg, str1, str2, str3);
235}
236
237
238/************************************************************************
239 *									*
240 *			Allocation and Freeing				*
241 *									*
242 ************************************************************************/
243
244/**
245 * xmlNewCatalogEntry:
246 * @type:  type of entry
247 * @name:  name of the entry
248 * @value:  value of the entry
249 * @prefer:  the PUBLIC vs. SYSTEM current preference value
250 * @group:  for members of a group, the group entry
251 *
252 * create a new Catalog entry, this type is shared both by XML and
253 * SGML catalogs, but the acceptable types values differs.
254 *
255 * Returns the xmlCatalogEntryPtr or NULL in case of error
256 */
257static xmlCatalogEntryPtr
258xmlNewCatalogEntry(xmlCatalogEntryType type, const xmlChar *name,
259	   const xmlChar *value, const xmlChar *URL, xmlCatalogPrefer prefer,
260	   xmlCatalogEntryPtr group) {
261    xmlCatalogEntryPtr ret;
262    xmlChar *normid = NULL;
263
264    ret = (xmlCatalogEntryPtr) xmlMalloc(sizeof(xmlCatalogEntry));
265    if (ret == NULL) {
266        xmlCatalogErrMemory("allocating catalog entry");
267	return(NULL);
268    }
269    ret->next = NULL;
270    ret->parent = NULL;
271    ret->children = NULL;
272    ret->type = type;
273    if (type == XML_CATA_PUBLIC || type == XML_CATA_DELEGATE_PUBLIC) {
274        normid = xmlCatalogNormalizePublic(name);
275        if (normid != NULL)
276            name = (*normid != 0 ? normid : NULL);
277    }
278    if (name != NULL)
279	ret->name = xmlStrdup(name);
280    else
281	ret->name = NULL;
282    if (normid != NULL)
283        xmlFree(normid);
284    if (value != NULL)
285	ret->value = xmlStrdup(value);
286    else
287	ret->value = NULL;
288    if (URL == NULL)
289	URL = value;
290    if (URL != NULL)
291	ret->URL = xmlStrdup(URL);
292    else
293	ret->URL = NULL;
294    ret->prefer = prefer;
295    ret->dealloc = 0;
296    ret->depth = 0;
297    ret->group = group;
298    return(ret);
299}
300
301static void
302xmlFreeCatalogEntryList(xmlCatalogEntryPtr ret);
303
304/**
305 * xmlFreeCatalogEntry:
306 * @ret:  a Catalog entry
307 *
308 * Free the memory allocated to a Catalog entry
309 */
310static void
311xmlFreeCatalogEntry(xmlCatalogEntryPtr ret) {
312    if (ret == NULL)
313	return;
314    /*
315     * Entries stored in the file hash must be deallocated
316     * only by the file hash cleaner !
317     */
318    if (ret->dealloc == 1)
319	return;
320
321    if (xmlDebugCatalogs) {
322	if (ret->name != NULL)
323	    xmlGenericError(xmlGenericErrorContext,
324		    "Free catalog entry %s\n", ret->name);
325	else if (ret->value != NULL)
326	    xmlGenericError(xmlGenericErrorContext,
327		    "Free catalog entry %s\n", ret->value);
328	else
329	    xmlGenericError(xmlGenericErrorContext,
330		    "Free catalog entry\n");
331    }
332
333    if (ret->name != NULL)
334	xmlFree(ret->name);
335    if (ret->value != NULL)
336	xmlFree(ret->value);
337    if (ret->URL != NULL)
338	xmlFree(ret->URL);
339    xmlFree(ret);
340}
341
342/**
343 * xmlFreeCatalogEntryList:
344 * @ret:  a Catalog entry list
345 *
346 * Free the memory allocated to a full chained list of Catalog entries
347 */
348static void
349xmlFreeCatalogEntryList(xmlCatalogEntryPtr ret) {
350    xmlCatalogEntryPtr next;
351
352    while (ret != NULL) {
353	next = ret->next;
354	xmlFreeCatalogEntry(ret);
355	ret = next;
356    }
357}
358
359/**
360 * xmlFreeCatalogHashEntryList:
361 * @ret:  a Catalog entry list
362 *
363 * Free the memory allocated to list of Catalog entries from the
364 * catalog file hash.
365 */
366static void
367xmlFreeCatalogHashEntryList(xmlCatalogEntryPtr catal) {
368    xmlCatalogEntryPtr children, next;
369
370    if (catal == NULL)
371	return;
372
373    children = catal->children;
374    while (children != NULL) {
375	next = children->next;
376	children->dealloc = 0;
377	children->children = NULL;
378	xmlFreeCatalogEntry(children);
379	children = next;
380    }
381    catal->dealloc = 0;
382    xmlFreeCatalogEntry(catal);
383}
384
385/**
386 * xmlCreateNewCatalog:
387 * @type:  type of catalog
388 * @prefer:  the PUBLIC vs. SYSTEM current preference value
389 *
390 * create a new Catalog, this type is shared both by XML and
391 * SGML catalogs, but the acceptable types values differs.
392 *
393 * Returns the xmlCatalogPtr or NULL in case of error
394 */
395static xmlCatalogPtr
396xmlCreateNewCatalog(xmlCatalogType type, xmlCatalogPrefer prefer) {
397    xmlCatalogPtr ret;
398
399    ret = (xmlCatalogPtr) xmlMalloc(sizeof(xmlCatalog));
400    if (ret == NULL) {
401        xmlCatalogErrMemory("allocating catalog");
402	return(NULL);
403    }
404    memset(ret, 0, sizeof(xmlCatalog));
405    ret->type = type;
406    ret->catalNr = 0;
407    ret->catalMax = XML_MAX_SGML_CATA_DEPTH;
408    ret->prefer = prefer;
409    if (ret->type == XML_SGML_CATALOG_TYPE)
410	ret->sgml = xmlHashCreate(10);
411    return(ret);
412}
413
414/**
415 * xmlFreeCatalog:
416 * @catal:  a Catalog
417 *
418 * Free the memory allocated to a Catalog
419 */
420void
421xmlFreeCatalog(xmlCatalogPtr catal) {
422    if (catal == NULL)
423	return;
424    if (catal->xml != NULL)
425	xmlFreeCatalogEntryList(catal->xml);
426    if (catal->sgml != NULL)
427	xmlHashFree(catal->sgml,
428		(xmlHashDeallocator) xmlFreeCatalogEntry);
429    xmlFree(catal);
430}
431
432/************************************************************************
433 *									*
434 *			Serializing Catalogs				*
435 *									*
436 ************************************************************************/
437
438#ifdef LIBXML_OUTPUT_ENABLED
439/**
440 * xmlCatalogDumpEntry:
441 * @entry:  the catalog entry
442 * @out:  the file.
443 *
444 * Serialize an SGML Catalog entry
445 */
446static void
447xmlCatalogDumpEntry(xmlCatalogEntryPtr entry, FILE *out) {
448    if ((entry == NULL) || (out == NULL))
449	return;
450    switch (entry->type) {
451	case SGML_CATA_ENTITY:
452	    fprintf(out, "ENTITY "); break;
453	case SGML_CATA_PENTITY:
454	    fprintf(out, "ENTITY %%"); break;
455	case SGML_CATA_DOCTYPE:
456	    fprintf(out, "DOCTYPE "); break;
457	case SGML_CATA_LINKTYPE:
458	    fprintf(out, "LINKTYPE "); break;
459	case SGML_CATA_NOTATION:
460	    fprintf(out, "NOTATION "); break;
461	case SGML_CATA_PUBLIC:
462	    fprintf(out, "PUBLIC "); break;
463	case SGML_CATA_SYSTEM:
464	    fprintf(out, "SYSTEM "); break;
465	case SGML_CATA_DELEGATE:
466	    fprintf(out, "DELEGATE "); break;
467	case SGML_CATA_BASE:
468	    fprintf(out, "BASE "); break;
469	case SGML_CATA_CATALOG:
470	    fprintf(out, "CATALOG "); break;
471	case SGML_CATA_DOCUMENT:
472	    fprintf(out, "DOCUMENT "); break;
473	case SGML_CATA_SGMLDECL:
474	    fprintf(out, "SGMLDECL "); break;
475	default:
476	    return;
477    }
478    switch (entry->type) {
479	case SGML_CATA_ENTITY:
480	case SGML_CATA_PENTITY:
481	case SGML_CATA_DOCTYPE:
482	case SGML_CATA_LINKTYPE:
483	case SGML_CATA_NOTATION:
484	    fprintf(out, "%s", (const char *) entry->name); break;
485	case SGML_CATA_PUBLIC:
486	case SGML_CATA_SYSTEM:
487	case SGML_CATA_SGMLDECL:
488	case SGML_CATA_DOCUMENT:
489	case SGML_CATA_CATALOG:
490	case SGML_CATA_BASE:
491	case SGML_CATA_DELEGATE:
492	    fprintf(out, "\"%s\"", entry->name); break;
493	default:
494	    break;
495    }
496    switch (entry->type) {
497	case SGML_CATA_ENTITY:
498	case SGML_CATA_PENTITY:
499	case SGML_CATA_DOCTYPE:
500	case SGML_CATA_LINKTYPE:
501	case SGML_CATA_NOTATION:
502	case SGML_CATA_PUBLIC:
503	case SGML_CATA_SYSTEM:
504	case SGML_CATA_DELEGATE:
505	    fprintf(out, " \"%s\"", entry->value); break;
506	default:
507	    break;
508    }
509    fprintf(out, "\n");
510}
511
512/**
513 * xmlDumpXMLCatalogNode:
514 * @catal:  top catalog entry
515 * @catalog: pointer to the xml tree
516 * @doc: the containing document
517 * @ns: the current namespace
518 * @cgroup: group node for group members
519 *
520 * Serializes a Catalog entry, called by xmlDumpXMLCatalog and recursively
521 * for group entries
522 */
523static void xmlDumpXMLCatalogNode(xmlCatalogEntryPtr catal, xmlNodePtr catalog,
524		    xmlDocPtr doc, xmlNsPtr ns, xmlCatalogEntryPtr cgroup) {
525    xmlNodePtr node;
526    xmlCatalogEntryPtr cur;
527    /*
528     * add all the catalog entries
529     */
530    cur = catal;
531    while (cur != NULL) {
532        if (cur->group == cgroup) {
533	    switch (cur->type) {
534	        case XML_CATA_REMOVED:
535		    break;
536	        case XML_CATA_BROKEN_CATALOG:
537	        case XML_CATA_CATALOG:
538		    if (cur == catal) {
539			cur = cur->children;
540		        continue;
541		    }
542		    break;
543		case XML_CATA_NEXT_CATALOG:
544		    node = xmlNewDocNode(doc, ns, BAD_CAST "nextCatalog", NULL);
545		    xmlSetProp(node, BAD_CAST "catalog", cur->value);
546		    xmlAddChild(catalog, node);
547                    break;
548		case XML_CATA_NONE:
549		    break;
550		case XML_CATA_GROUP:
551		    node = xmlNewDocNode(doc, ns, BAD_CAST "group", NULL);
552		    xmlSetProp(node, BAD_CAST "id", cur->name);
553		    if (cur->value != NULL) {
554		        xmlNsPtr xns;
555			xns = xmlSearchNsByHref(doc, node, XML_XML_NAMESPACE);
556			if (xns != NULL)
557			    xmlSetNsProp(node, xns, BAD_CAST "base",
558			    		 cur->value);
559		    }
560		    switch (cur->prefer) {
561			case XML_CATA_PREFER_NONE:
562		            break;
563			case XML_CATA_PREFER_PUBLIC:
564		            xmlSetProp(node, BAD_CAST "prefer", BAD_CAST "public");
565			    break;
566			case XML_CATA_PREFER_SYSTEM:
567		            xmlSetProp(node, BAD_CAST "prefer", BAD_CAST "system");
568			    break;
569		    }
570		    xmlDumpXMLCatalogNode(cur->next, node, doc, ns, cur);
571		    xmlAddChild(catalog, node);
572	            break;
573		case XML_CATA_PUBLIC:
574		    node = xmlNewDocNode(doc, ns, BAD_CAST "public", NULL);
575		    xmlSetProp(node, BAD_CAST "publicId", cur->name);
576		    xmlSetProp(node, BAD_CAST "uri", cur->value);
577		    xmlAddChild(catalog, node);
578		    break;
579		case XML_CATA_SYSTEM:
580		    node = xmlNewDocNode(doc, ns, BAD_CAST "system", NULL);
581		    xmlSetProp(node, BAD_CAST "systemId", cur->name);
582		    xmlSetProp(node, BAD_CAST "uri", cur->value);
583		    xmlAddChild(catalog, node);
584		    break;
585		case XML_CATA_REWRITE_SYSTEM:
586		    node = xmlNewDocNode(doc, ns, BAD_CAST "rewriteSystem", NULL);
587		    xmlSetProp(node, BAD_CAST "systemIdStartString", cur->name);
588		    xmlSetProp(node, BAD_CAST "rewritePrefix", cur->value);
589		    xmlAddChild(catalog, node);
590		    break;
591		case XML_CATA_DELEGATE_PUBLIC:
592		    node = xmlNewDocNode(doc, ns, BAD_CAST "delegatePublic", NULL);
593		    xmlSetProp(node, BAD_CAST "publicIdStartString", cur->name);
594		    xmlSetProp(node, BAD_CAST "catalog", cur->value);
595		    xmlAddChild(catalog, node);
596		    break;
597		case XML_CATA_DELEGATE_SYSTEM:
598		    node = xmlNewDocNode(doc, ns, BAD_CAST "delegateSystem", NULL);
599		    xmlSetProp(node, BAD_CAST "systemIdStartString", cur->name);
600		    xmlSetProp(node, BAD_CAST "catalog", cur->value);
601		    xmlAddChild(catalog, node);
602		    break;
603		case XML_CATA_URI:
604		    node = xmlNewDocNode(doc, ns, BAD_CAST "uri", NULL);
605		    xmlSetProp(node, BAD_CAST "name", cur->name);
606		    xmlSetProp(node, BAD_CAST "uri", cur->value);
607		    xmlAddChild(catalog, node);
608		    break;
609		case XML_CATA_REWRITE_URI:
610		    node = xmlNewDocNode(doc, ns, BAD_CAST "rewriteURI", NULL);
611		    xmlSetProp(node, BAD_CAST "uriStartString", cur->name);
612		    xmlSetProp(node, BAD_CAST "rewritePrefix", cur->value);
613		    xmlAddChild(catalog, node);
614		    break;
615		case XML_CATA_DELEGATE_URI:
616		    node = xmlNewDocNode(doc, ns, BAD_CAST "delegateURI", NULL);
617		    xmlSetProp(node, BAD_CAST "uriStartString", cur->name);
618		    xmlSetProp(node, BAD_CAST "catalog", cur->value);
619		    xmlAddChild(catalog, node);
620		    break;
621		case SGML_CATA_SYSTEM:
622		case SGML_CATA_PUBLIC:
623		case SGML_CATA_ENTITY:
624		case SGML_CATA_PENTITY:
625		case SGML_CATA_DOCTYPE:
626		case SGML_CATA_LINKTYPE:
627		case SGML_CATA_NOTATION:
628		case SGML_CATA_DELEGATE:
629		case SGML_CATA_BASE:
630		case SGML_CATA_CATALOG:
631		case SGML_CATA_DOCUMENT:
632		case SGML_CATA_SGMLDECL:
633		    break;
634	    }
635        }
636	cur = cur->next;
637    }
638}
639
640static int
641xmlDumpXMLCatalog(FILE *out, xmlCatalogEntryPtr catal) {
642    int ret;
643    xmlDocPtr doc;
644    xmlNsPtr ns;
645    xmlDtdPtr dtd;
646    xmlNodePtr catalog;
647    xmlOutputBufferPtr buf;
648
649    /*
650     * Rebuild a catalog
651     */
652    doc = xmlNewDoc(NULL);
653    if (doc == NULL)
654	return(-1);
655    dtd = xmlNewDtd(doc, BAD_CAST "catalog",
656	       BAD_CAST "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN",
657BAD_CAST "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd");
658
659    xmlAddChild((xmlNodePtr) doc, (xmlNodePtr) dtd);
660
661    ns = xmlNewNs(NULL, XML_CATALOGS_NAMESPACE, NULL);
662    if (ns == NULL) {
663	xmlFreeDoc(doc);
664	return(-1);
665    }
666    catalog = xmlNewDocNode(doc, ns, BAD_CAST "catalog", NULL);
667    if (catalog == NULL) {
668	xmlFreeNs(ns);
669	xmlFreeDoc(doc);
670	return(-1);
671    }
672    catalog->nsDef = ns;
673    xmlAddChild((xmlNodePtr) doc, catalog);
674
675    xmlDumpXMLCatalogNode(catal, catalog, doc, ns, NULL);
676
677    /*
678     * reserialize it
679     */
680    buf = xmlOutputBufferCreateFile(out, NULL);
681    if (buf == NULL) {
682	xmlFreeDoc(doc);
683	return(-1);
684    }
685    ret = xmlSaveFormatFileTo(buf, doc, NULL, 1);
686
687    /*
688     * Free it
689     */
690    xmlFreeDoc(doc);
691
692    return(ret);
693}
694#endif /* LIBXML_OUTPUT_ENABLED */
695
696/************************************************************************
697 *									*
698 *			Converting SGML Catalogs to XML			*
699 *									*
700 ************************************************************************/
701
702/**
703 * xmlCatalogConvertEntry:
704 * @entry:  the entry
705 * @catal:  pointer to the catalog being converted
706 *
707 * Convert one entry from the catalog
708 */
709static void
710xmlCatalogConvertEntry(xmlCatalogEntryPtr entry, xmlCatalogPtr catal) {
711    if ((entry == NULL) || (catal == NULL) || (catal->sgml == NULL) ||
712	(catal->xml == NULL))
713	return;
714    switch (entry->type) {
715	case SGML_CATA_ENTITY:
716	    entry->type = XML_CATA_PUBLIC;
717	    break;
718	case SGML_CATA_PENTITY:
719	    entry->type = XML_CATA_PUBLIC;
720	    break;
721	case SGML_CATA_DOCTYPE:
722	    entry->type = XML_CATA_PUBLIC;
723	    break;
724	case SGML_CATA_LINKTYPE:
725	    entry->type = XML_CATA_PUBLIC;
726	    break;
727	case SGML_CATA_NOTATION:
728	    entry->type = XML_CATA_PUBLIC;
729	    break;
730	case SGML_CATA_PUBLIC:
731	    entry->type = XML_CATA_PUBLIC;
732	    break;
733	case SGML_CATA_SYSTEM:
734	    entry->type = XML_CATA_SYSTEM;
735	    break;
736	case SGML_CATA_DELEGATE:
737	    entry->type = XML_CATA_DELEGATE_PUBLIC;
738	    break;
739	case SGML_CATA_CATALOG:
740	    entry->type = XML_CATA_CATALOG;
741	    break;
742	default:
743	    xmlHashRemoveEntry(catal->sgml, entry->name,
744		               (xmlHashDeallocator) xmlFreeCatalogEntry);
745	    return;
746    }
747    /*
748     * Conversion successful, remove from the SGML catalog
749     * and add it to the default XML one
750     */
751    xmlHashRemoveEntry(catal->sgml, entry->name, NULL);
752    entry->parent = catal->xml;
753    entry->next = NULL;
754    if (catal->xml->children == NULL)
755	catal->xml->children = entry;
756    else {
757	xmlCatalogEntryPtr prev;
758
759	prev = catal->xml->children;
760	while (prev->next != NULL)
761	    prev = prev->next;
762	prev->next = entry;
763    }
764}
765
766/**
767 * xmlConvertSGMLCatalog:
768 * @catal: the catalog
769 *
770 * Convert all the SGML catalog entries as XML ones
771 *
772 * Returns the number of entries converted if successful, -1 otherwise
773 */
774int
775xmlConvertSGMLCatalog(xmlCatalogPtr catal) {
776
777    if ((catal == NULL) || (catal->type != XML_SGML_CATALOG_TYPE))
778	return(-1);
779
780    if (xmlDebugCatalogs) {
781	xmlGenericError(xmlGenericErrorContext,
782		"Converting SGML catalog to XML\n");
783    }
784    xmlHashScan(catal->sgml,
785		(xmlHashScanner) xmlCatalogConvertEntry,
786		&catal);
787    return(0);
788}
789
790/************************************************************************
791 *									*
792 *			Helper function					*
793 *									*
794 ************************************************************************/
795
796/**
797 * xmlCatalogUnWrapURN:
798 * @urn:  an "urn:publicid:" to unwrap
799 *
800 * Expand the URN into the equivalent Public Identifier
801 *
802 * Returns the new identifier or NULL, the string must be deallocated
803 *         by the caller.
804 */
805static xmlChar *
806xmlCatalogUnWrapURN(const xmlChar *urn) {
807    xmlChar result[2000];
808    unsigned int i = 0;
809
810    if (xmlStrncmp(urn, BAD_CAST XML_URN_PUBID, sizeof(XML_URN_PUBID) - 1))
811	return(NULL);
812    urn += sizeof(XML_URN_PUBID) - 1;
813
814    while (*urn != 0) {
815	if (i > sizeof(result) - 4)
816	    break;
817	if (*urn == '+') {
818	    result[i++] = ' ';
819	    urn++;
820	} else if (*urn == ':') {
821	    result[i++] = '/';
822	    result[i++] = '/';
823	    urn++;
824	} else if (*urn == ';') {
825	    result[i++] = ':';
826	    result[i++] = ':';
827	    urn++;
828	} else if (*urn == '%') {
829	    if ((urn[1] == '2') && (urn[2] == 'B'))
830		result[i++] = '+';
831	    else if ((urn[1] == '3') && (urn[2] == 'A'))
832		result[i++] = ':';
833	    else if ((urn[1] == '2') && (urn[2] == 'F'))
834		result[i++] = '/';
835	    else if ((urn[1] == '3') && (urn[2] == 'B'))
836		result[i++] = ';';
837	    else if ((urn[1] == '2') && (urn[2] == '7'))
838		result[i++] = '\'';
839	    else if ((urn[1] == '3') && (urn[2] == 'F'))
840		result[i++] = '?';
841	    else if ((urn[1] == '2') && (urn[2] == '3'))
842		result[i++] = '#';
843	    else if ((urn[1] == '2') && (urn[2] == '5'))
844		result[i++] = '%';
845	    else {
846		result[i++] = *urn;
847		urn++;
848		continue;
849	    }
850	    urn += 3;
851	} else {
852	    result[i++] = *urn;
853	    urn++;
854	}
855    }
856    result[i] = 0;
857
858    return(xmlStrdup(result));
859}
860
861/**
862 * xmlParseCatalogFile:
863 * @filename:  the filename
864 *
865 * parse an XML file and build a tree. It's like xmlParseFile()
866 * except it bypass all catalog lookups.
867 *
868 * Returns the resulting document tree or NULL in case of error
869 */
870
871xmlDocPtr
872xmlParseCatalogFile(const char *filename) {
873    xmlDocPtr ret;
874    xmlParserCtxtPtr ctxt;
875    char *directory = NULL;
876    xmlParserInputPtr inputStream;
877    xmlParserInputBufferPtr buf;
878
879    ctxt = xmlNewParserCtxt();
880    if (ctxt == NULL) {
881	if (xmlDefaultSAXHandler.error != NULL) {
882	    xmlDefaultSAXHandler.error(NULL, "out of memory\n");
883	}
884	return(NULL);
885    }
886
887    buf = xmlParserInputBufferCreateFilename(filename, XML_CHAR_ENCODING_NONE);
888    if (buf == NULL) {
889	xmlFreeParserCtxt(ctxt);
890	return(NULL);
891    }
892
893    inputStream = xmlNewInputStream(ctxt);
894    if (inputStream == NULL) {
895	xmlFreeParserCtxt(ctxt);
896	return(NULL);
897    }
898
899    inputStream->filename = (char *) xmlCanonicPath((const xmlChar *)filename);
900    inputStream->buf = buf;
901    inputStream->base = inputStream->buf->buffer->content;
902    inputStream->cur = inputStream->buf->buffer->content;
903    inputStream->end =
904	&inputStream->buf->buffer->content[inputStream->buf->buffer->use];
905
906    inputPush(ctxt, inputStream);
907    if ((ctxt->directory == NULL) && (directory == NULL))
908        directory = xmlParserGetDirectory(filename);
909    if ((ctxt->directory == NULL) && (directory != NULL))
910        ctxt->directory = directory;
911    ctxt->valid = 0;
912    ctxt->validate = 0;
913    ctxt->loadsubset = 0;
914    ctxt->pedantic = 0;
915    ctxt->dictNames = 1;
916
917    xmlParseDocument(ctxt);
918
919    if (ctxt->wellFormed)
920	ret = ctxt->myDoc;
921    else {
922        ret = NULL;
923        xmlFreeDoc(ctxt->myDoc);
924        ctxt->myDoc = NULL;
925    }
926    xmlFreeParserCtxt(ctxt);
927
928    return(ret);
929}
930
931/**
932 * xmlLoadFileContent:
933 * @filename:  a file path
934 *
935 * Load a file content into memory.
936 *
937 * Returns a pointer to the 0 terminated string or NULL in case of error
938 */
939static xmlChar *
940xmlLoadFileContent(const char *filename)
941{
942#ifdef HAVE_STAT
943    int fd;
944#else
945    FILE *fd;
946#endif
947    int len;
948    long size;
949
950#ifdef HAVE_STAT
951    struct stat info;
952#endif
953    xmlChar *content;
954
955    if (filename == NULL)
956        return (NULL);
957
958#ifdef HAVE_STAT
959    if (stat(filename, &info) < 0)
960        return (NULL);
961#endif
962
963#ifdef HAVE_STAT
964    if ((fd = open(filename, O_RDONLY)) < 0)
965#else
966    if ((fd = fopen(filename, "rb")) == NULL)
967#endif
968    {
969        return (NULL);
970    }
971#ifdef HAVE_STAT
972    size = info.st_size;
973#else
974    if (fseek(fd, 0, SEEK_END) || (size = ftell(fd)) == EOF || fseek(fd, 0, SEEK_SET)) {        /* File operations denied? ok, just close and return failure */
975        fclose(fd);
976        return (NULL);
977    }
978#endif
979    content = xmlMallocAtomic(size + 10);
980    if (content == NULL) {
981        xmlCatalogErrMemory("allocating catalog data");
982        return (NULL);
983    }
984#ifdef HAVE_STAT
985    len = read(fd, content, size);
986#else
987    len = fread(content, 1, size, fd);
988#endif
989    if (len < 0) {
990        xmlFree(content);
991        return (NULL);
992    }
993#ifdef HAVE_STAT
994    close(fd);
995#else
996    fclose(fd);
997#endif
998    content[len] = 0;
999
1000    return(content);
1001}
1002
1003/**
1004 * xmlCatalogNormalizePublic:
1005 * @pubID:  the public ID string
1006 *
1007 *  Normalizes the Public Identifier
1008 *
1009 * Implements 6.2. Public Identifier Normalization
1010 * from http://www.oasis-open.org/committees/entity/spec-2001-08-06.html
1011 *
1012 * Returns the new string or NULL, the string must be deallocated
1013 *         by the caller.
1014 */
1015static xmlChar *
1016xmlCatalogNormalizePublic(const xmlChar *pubID)
1017{
1018    int ok = 1;
1019    int white;
1020    const xmlChar *p;
1021    xmlChar *ret;
1022    xmlChar *q;
1023
1024    if (pubID == NULL)
1025        return(NULL);
1026
1027    white = 1;
1028    for (p = pubID;*p != 0 && ok;p++) {
1029        if (!xmlIsBlank_ch(*p))
1030            white = 0;
1031        else if (*p == 0x20 && !white)
1032            white = 1;
1033        else
1034            ok = 0;
1035    }
1036    if (ok && !white)	/* is normalized */
1037        return(NULL);
1038
1039    ret = xmlStrdup(pubID);
1040    q = ret;
1041    white = 0;
1042    for (p = pubID;*p != 0;p++) {
1043        if (xmlIsBlank_ch(*p)) {
1044            if (q != ret)
1045                white = 1;
1046        } else {
1047            if (white) {
1048                *(q++) = 0x20;
1049                white = 0;
1050            }
1051            *(q++) = *p;
1052        }
1053    }
1054    *q = 0;
1055    return(ret);
1056}
1057
1058/************************************************************************
1059 *									*
1060 *			The XML Catalog parser				*
1061 *									*
1062 ************************************************************************/
1063
1064static xmlCatalogEntryPtr
1065xmlParseXMLCatalogFile(xmlCatalogPrefer prefer, const xmlChar *filename);
1066static void
1067xmlParseXMLCatalogNodeList(xmlNodePtr cur, xmlCatalogPrefer prefer,
1068	                   xmlCatalogEntryPtr parent, xmlCatalogEntryPtr cgroup);
1069static xmlChar *
1070xmlCatalogListXMLResolve(xmlCatalogEntryPtr catal, const xmlChar *pubID,
1071	              const xmlChar *sysID);
1072static xmlChar *
1073xmlCatalogListXMLResolveURI(xmlCatalogEntryPtr catal, const xmlChar *URI);
1074
1075
1076/**
1077 * xmlGetXMLCatalogEntryType:
1078 * @name:  the name
1079 *
1080 * lookup the internal type associated to an XML catalog entry name
1081 *
1082 * Returns the type associated with that name
1083 */
1084static xmlCatalogEntryType
1085xmlGetXMLCatalogEntryType(const xmlChar *name) {
1086    xmlCatalogEntryType type = XML_CATA_NONE;
1087    if (xmlStrEqual(name, (const xmlChar *) "system"))
1088	type = XML_CATA_SYSTEM;
1089    else if (xmlStrEqual(name, (const xmlChar *) "public"))
1090	type = XML_CATA_PUBLIC;
1091    else if (xmlStrEqual(name, (const xmlChar *) "rewriteSystem"))
1092	type = XML_CATA_REWRITE_SYSTEM;
1093    else if (xmlStrEqual(name, (const xmlChar *) "delegatePublic"))
1094	type = XML_CATA_DELEGATE_PUBLIC;
1095    else if (xmlStrEqual(name, (const xmlChar *) "delegateSystem"))
1096	type = XML_CATA_DELEGATE_SYSTEM;
1097    else if (xmlStrEqual(name, (const xmlChar *) "uri"))
1098	type = XML_CATA_URI;
1099    else if (xmlStrEqual(name, (const xmlChar *) "rewriteURI"))
1100	type = XML_CATA_REWRITE_URI;
1101    else if (xmlStrEqual(name, (const xmlChar *) "delegateURI"))
1102	type = XML_CATA_DELEGATE_URI;
1103    else if (xmlStrEqual(name, (const xmlChar *) "nextCatalog"))
1104	type = XML_CATA_NEXT_CATALOG;
1105    else if (xmlStrEqual(name, (const xmlChar *) "catalog"))
1106	type = XML_CATA_CATALOG;
1107    return(type);
1108}
1109
1110/**
1111 * xmlParseXMLCatalogOneNode:
1112 * @cur:  the XML node
1113 * @type:  the type of Catalog entry
1114 * @name:  the name of the node
1115 * @attrName:  the attribute holding the value
1116 * @uriAttrName:  the attribute holding the URI-Reference
1117 * @prefer:  the PUBLIC vs. SYSTEM current preference value
1118 * @cgroup:  the group which includes this node
1119 *
1120 * Finishes the examination of an XML tree node of a catalog and build
1121 * a Catalog entry from it.
1122 *
1123 * Returns the new Catalog entry node or NULL in case of error.
1124 */
1125static xmlCatalogEntryPtr
1126xmlParseXMLCatalogOneNode(xmlNodePtr cur, xmlCatalogEntryType type,
1127			  const xmlChar *name, const xmlChar *attrName,
1128			  const xmlChar *uriAttrName, xmlCatalogPrefer prefer,
1129			  xmlCatalogEntryPtr cgroup) {
1130    int ok = 1;
1131    xmlChar *uriValue;
1132    xmlChar *nameValue = NULL;
1133    xmlChar *base = NULL;
1134    xmlChar *URL = NULL;
1135    xmlCatalogEntryPtr ret = NULL;
1136
1137    if (attrName != NULL) {
1138	nameValue = xmlGetProp(cur, attrName);
1139	if (nameValue == NULL) {
1140	    xmlCatalogErr(ret, cur, XML_CATALOG_MISSING_ATTR,
1141			  "%s entry lacks '%s'\n", name, attrName, NULL);
1142	    ok = 0;
1143	}
1144    }
1145    uriValue = xmlGetProp(cur, uriAttrName);
1146    if (uriValue == NULL) {
1147	xmlCatalogErr(ret, cur, XML_CATALOG_MISSING_ATTR,
1148		"%s entry lacks '%s'\n", name, uriAttrName, NULL);
1149	ok = 0;
1150    }
1151    if (!ok) {
1152	if (nameValue != NULL)
1153	    xmlFree(nameValue);
1154	if (uriValue != NULL)
1155	    xmlFree(uriValue);
1156	return(NULL);
1157    }
1158
1159    base = xmlNodeGetBase(cur->doc, cur);
1160    URL = xmlBuildURI(uriValue, base);
1161    if (URL != NULL) {
1162	if (xmlDebugCatalogs > 1) {
1163	    if (nameValue != NULL)
1164		xmlGenericError(xmlGenericErrorContext,
1165			"Found %s: '%s' '%s'\n", name, nameValue, URL);
1166	    else
1167		xmlGenericError(xmlGenericErrorContext,
1168			"Found %s: '%s'\n", name, URL);
1169	}
1170	ret = xmlNewCatalogEntry(type, nameValue, uriValue, URL, prefer, cgroup);
1171    } else {
1172	xmlCatalogErr(ret, cur, XML_CATALOG_ENTRY_BROKEN,
1173		"%s entry '%s' broken ?: %s\n", name, uriAttrName, uriValue);
1174    }
1175    if (nameValue != NULL)
1176	xmlFree(nameValue);
1177    if (uriValue != NULL)
1178	xmlFree(uriValue);
1179    if (base != NULL)
1180	xmlFree(base);
1181    if (URL != NULL)
1182	xmlFree(URL);
1183    return(ret);
1184}
1185
1186/**
1187 * xmlParseXMLCatalogNode:
1188 * @cur:  the XML node
1189 * @prefer:  the PUBLIC vs. SYSTEM current preference value
1190 * @parent:  the parent Catalog entry
1191 * @cgroup:  the group which includes this node
1192 *
1193 * Examines an XML tree node of a catalog and build
1194 * a Catalog entry from it adding it to its parent. The examination can
1195 * be recursive.
1196 */
1197static void
1198xmlParseXMLCatalogNode(xmlNodePtr cur, xmlCatalogPrefer prefer,
1199	               xmlCatalogEntryPtr parent, xmlCatalogEntryPtr cgroup)
1200{
1201    xmlChar *uri = NULL;
1202    xmlChar *URL = NULL;
1203    xmlChar *base = NULL;
1204    xmlCatalogEntryPtr entry = NULL;
1205
1206    if (cur == NULL)
1207        return;
1208    if (xmlStrEqual(cur->name, BAD_CAST "group")) {
1209        xmlChar *prop;
1210	xmlCatalogPrefer pref = XML_CATA_PREFER_NONE;
1211
1212        prop = xmlGetProp(cur, BAD_CAST "prefer");
1213        if (prop != NULL) {
1214            if (xmlStrEqual(prop, BAD_CAST "system")) {
1215                prefer = XML_CATA_PREFER_SYSTEM;
1216            } else if (xmlStrEqual(prop, BAD_CAST "public")) {
1217                prefer = XML_CATA_PREFER_PUBLIC;
1218            } else {
1219		xmlCatalogErr(parent, cur, XML_CATALOG_PREFER_VALUE,
1220                              "Invalid value for prefer: '%s'\n",
1221			      prop, NULL, NULL);
1222            }
1223            xmlFree(prop);
1224	    pref = prefer;
1225        }
1226	prop = xmlGetProp(cur, BAD_CAST "id");
1227	base = xmlGetNsProp(cur, BAD_CAST "base", XML_XML_NAMESPACE);
1228	entry = xmlNewCatalogEntry(XML_CATA_GROUP, prop, base, NULL, pref, cgroup);
1229	xmlFree(prop);
1230    } else if (xmlStrEqual(cur->name, BAD_CAST "public")) {
1231	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_PUBLIC,
1232		BAD_CAST "public", BAD_CAST "publicId", BAD_CAST "uri", prefer, cgroup);
1233    } else if (xmlStrEqual(cur->name, BAD_CAST "system")) {
1234	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_SYSTEM,
1235		BAD_CAST "system", BAD_CAST "systemId", BAD_CAST "uri", prefer, cgroup);
1236    } else if (xmlStrEqual(cur->name, BAD_CAST "rewriteSystem")) {
1237	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_REWRITE_SYSTEM,
1238		BAD_CAST "rewriteSystem", BAD_CAST "systemIdStartString",
1239		BAD_CAST "rewritePrefix", prefer, cgroup);
1240    } else if (xmlStrEqual(cur->name, BAD_CAST "delegatePublic")) {
1241	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_DELEGATE_PUBLIC,
1242		BAD_CAST "delegatePublic", BAD_CAST "publicIdStartString",
1243		BAD_CAST "catalog", prefer, cgroup);
1244    } else if (xmlStrEqual(cur->name, BAD_CAST "delegateSystem")) {
1245	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_DELEGATE_SYSTEM,
1246		BAD_CAST "delegateSystem", BAD_CAST "systemIdStartString",
1247		BAD_CAST "catalog", prefer, cgroup);
1248    } else if (xmlStrEqual(cur->name, BAD_CAST "uri")) {
1249	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_URI,
1250		BAD_CAST "uri", BAD_CAST "name",
1251		BAD_CAST "uri", prefer, cgroup);
1252    } else if (xmlStrEqual(cur->name, BAD_CAST "rewriteURI")) {
1253	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_REWRITE_URI,
1254		BAD_CAST "rewriteURI", BAD_CAST "uriStartString",
1255		BAD_CAST "rewritePrefix", prefer, cgroup);
1256    } else if (xmlStrEqual(cur->name, BAD_CAST "delegateURI")) {
1257	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_DELEGATE_URI,
1258		BAD_CAST "delegateURI", BAD_CAST "uriStartString",
1259		BAD_CAST "catalog", prefer, cgroup);
1260    } else if (xmlStrEqual(cur->name, BAD_CAST "nextCatalog")) {
1261	entry = xmlParseXMLCatalogOneNode(cur, XML_CATA_NEXT_CATALOG,
1262		BAD_CAST "nextCatalog", NULL,
1263		BAD_CAST "catalog", prefer, cgroup);
1264    }
1265    if (entry != NULL) {
1266        if (parent != NULL) {
1267	    entry->parent = parent;
1268	    if (parent->children == NULL)
1269		parent->children = entry;
1270	    else {
1271		xmlCatalogEntryPtr prev;
1272
1273		prev = parent->children;
1274		while (prev->next != NULL)
1275		    prev = prev->next;
1276		prev->next = entry;
1277	    }
1278	}
1279	if (entry->type == XML_CATA_GROUP) {
1280	    /*
1281	     * Recurse to propagate prefer to the subtree
1282	     * (xml:base handling is automated)
1283	     */
1284            xmlParseXMLCatalogNodeList(cur->children, prefer, parent, entry);
1285	}
1286    }
1287    if (base != NULL)
1288	xmlFree(base);
1289    if (uri != NULL)
1290	xmlFree(uri);
1291    if (URL != NULL)
1292	xmlFree(URL);
1293}
1294
1295/**
1296 * xmlParseXMLCatalogNodeList:
1297 * @cur:  the XML node list of siblings
1298 * @prefer:  the PUBLIC vs. SYSTEM current preference value
1299 * @parent:  the parent Catalog entry
1300 * @cgroup:  the group which includes this list
1301 *
1302 * Examines a list of XML sibling nodes of a catalog and build
1303 * a list of Catalog entry from it adding it to the parent.
1304 * The examination will recurse to examine node subtrees.
1305 */
1306static void
1307xmlParseXMLCatalogNodeList(xmlNodePtr cur, xmlCatalogPrefer prefer,
1308	                   xmlCatalogEntryPtr parent, xmlCatalogEntryPtr cgroup) {
1309    while (cur != NULL) {
1310	if ((cur->ns != NULL) && (cur->ns->href != NULL) &&
1311	    (xmlStrEqual(cur->ns->href, XML_CATALOGS_NAMESPACE))) {
1312	    xmlParseXMLCatalogNode(cur, prefer, parent, cgroup);
1313	}
1314	cur = cur->next;
1315    }
1316    /* TODO: sort the list according to REWRITE lengths and prefer value */
1317}
1318
1319/**
1320 * xmlParseXMLCatalogFile:
1321 * @prefer:  the PUBLIC vs. SYSTEM current preference value
1322 * @filename:  the filename for the catalog
1323 *
1324 * Parses the catalog file to extract the XML tree and then analyze the
1325 * tree to build a list of Catalog entries corresponding to this catalog
1326 *
1327 * Returns the resulting Catalog entries list
1328 */
1329static xmlCatalogEntryPtr
1330xmlParseXMLCatalogFile(xmlCatalogPrefer prefer, const xmlChar *filename) {
1331    xmlDocPtr doc;
1332    xmlNodePtr cur;
1333    xmlChar *prop;
1334    xmlCatalogEntryPtr parent = NULL;
1335
1336    if (filename == NULL)
1337        return(NULL);
1338
1339    doc = xmlParseCatalogFile((const char *) filename);
1340    if (doc == NULL) {
1341	if (xmlDebugCatalogs)
1342	    xmlGenericError(xmlGenericErrorContext,
1343		    "Failed to parse catalog %s\n", filename);
1344	return(NULL);
1345    }
1346
1347    if (xmlDebugCatalogs)
1348	xmlGenericError(xmlGenericErrorContext,
1349		"%d Parsing catalog %s\n", xmlGetThreadId(), filename);
1350
1351    cur = xmlDocGetRootElement(doc);
1352    if ((cur != NULL) && (xmlStrEqual(cur->name, BAD_CAST "catalog")) &&
1353	(cur->ns != NULL) && (cur->ns->href != NULL) &&
1354	(xmlStrEqual(cur->ns->href, XML_CATALOGS_NAMESPACE))) {
1355
1356	parent = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL,
1357				    (const xmlChar *)filename, NULL, prefer, NULL);
1358        if (parent == NULL) {
1359	    xmlFreeDoc(doc);
1360	    return(NULL);
1361	}
1362
1363	prop = xmlGetProp(cur, BAD_CAST "prefer");
1364	if (prop != NULL) {
1365	    if (xmlStrEqual(prop, BAD_CAST "system")) {
1366		prefer = XML_CATA_PREFER_SYSTEM;
1367	    } else if (xmlStrEqual(prop, BAD_CAST "public")) {
1368		prefer = XML_CATA_PREFER_PUBLIC;
1369	    } else {
1370		xmlCatalogErr(NULL, cur, XML_CATALOG_PREFER_VALUE,
1371			      "Invalid value for prefer: '%s'\n",
1372			      prop, NULL, NULL);
1373	    }
1374	    xmlFree(prop);
1375	}
1376	cur = cur->children;
1377	xmlParseXMLCatalogNodeList(cur, prefer, parent, NULL);
1378    } else {
1379	xmlCatalogErr(NULL, (xmlNodePtr) doc, XML_CATALOG_NOT_CATALOG,
1380		      "File %s is not an XML Catalog\n",
1381		      filename, NULL, NULL);
1382	xmlFreeDoc(doc);
1383	return(NULL);
1384    }
1385    xmlFreeDoc(doc);
1386    return(parent);
1387}
1388
1389/**
1390 * xmlFetchXMLCatalogFile:
1391 * @catal:  an existing but incomplete catalog entry
1392 *
1393 * Fetch and parse the subcatalog referenced by an entry
1394 *
1395 * Returns 0 in case of success, -1 otherwise
1396 */
1397static int
1398xmlFetchXMLCatalogFile(xmlCatalogEntryPtr catal) {
1399    xmlCatalogEntryPtr doc;
1400
1401    if (catal == NULL)
1402	return(-1);
1403    if (catal->URL == NULL)
1404	return(-1);
1405    if (catal->children != NULL)
1406	return(-1);
1407
1408    /*
1409     * lock the whole catalog for modification
1410     */
1411    xmlRMutexLock(xmlCatalogMutex);
1412    if (catal->children != NULL) {
1413	/* Okay someone else did it in the meantime */
1414	xmlRMutexUnlock(xmlCatalogMutex);
1415	return(0);
1416    }
1417
1418    if (xmlCatalogXMLFiles != NULL) {
1419	doc = (xmlCatalogEntryPtr)
1420	    xmlHashLookup(xmlCatalogXMLFiles, catal->URL);
1421	if (doc != NULL) {
1422	    if (xmlDebugCatalogs)
1423		xmlGenericError(xmlGenericErrorContext,
1424		    "Found %s in file hash\n", catal->URL);
1425
1426	    if (catal->type == XML_CATA_CATALOG)
1427		catal->children = doc->children;
1428	    else
1429		catal->children = doc;
1430	    catal->dealloc = 0;
1431	    xmlRMutexUnlock(xmlCatalogMutex);
1432	    return(0);
1433	}
1434	if (xmlDebugCatalogs)
1435	    xmlGenericError(xmlGenericErrorContext,
1436		"%s not found in file hash\n", catal->URL);
1437    }
1438
1439    /*
1440     * Fetch and parse. Note that xmlParseXMLCatalogFile does not
1441     * use the existing catalog, there is no recursion allowed at
1442     * that level.
1443     */
1444    doc = xmlParseXMLCatalogFile(catal->prefer, catal->URL);
1445    if (doc == NULL) {
1446	catal->type = XML_CATA_BROKEN_CATALOG;
1447	xmlRMutexUnlock(xmlCatalogMutex);
1448	return(-1);
1449    }
1450
1451    if (catal->type == XML_CATA_CATALOG)
1452	catal->children = doc->children;
1453    else
1454	catal->children = doc;
1455
1456    doc->dealloc = 1;
1457
1458    if (xmlCatalogXMLFiles == NULL)
1459	xmlCatalogXMLFiles = xmlHashCreate(10);
1460    if (xmlCatalogXMLFiles != NULL) {
1461	if (xmlDebugCatalogs)
1462	    xmlGenericError(xmlGenericErrorContext,
1463		"%s added to file hash\n", catal->URL);
1464	xmlHashAddEntry(xmlCatalogXMLFiles, catal->URL, doc);
1465    }
1466    xmlRMutexUnlock(xmlCatalogMutex);
1467    return(0);
1468}
1469
1470/************************************************************************
1471 *									*
1472 *			XML Catalog handling				*
1473 *									*
1474 ************************************************************************/
1475
1476/**
1477 * xmlAddXMLCatalog:
1478 * @catal:  top of an XML catalog
1479 * @type:  the type of record to add to the catalog
1480 * @orig:  the system, public or prefix to match (or NULL)
1481 * @replace:  the replacement value for the match
1482 *
1483 * Add an entry in the XML catalog, it may overwrite existing but
1484 * different entries.
1485 *
1486 * Returns 0 if successful, -1 otherwise
1487 */
1488static int
1489xmlAddXMLCatalog(xmlCatalogEntryPtr catal, const xmlChar *type,
1490	      const xmlChar *orig, const xmlChar *replace) {
1491    xmlCatalogEntryPtr cur;
1492    xmlCatalogEntryType typ;
1493    int doregister = 0;
1494
1495    if ((catal == NULL) ||
1496	((catal->type != XML_CATA_CATALOG) &&
1497	 (catal->type != XML_CATA_BROKEN_CATALOG)))
1498	return(-1);
1499    if (catal->children == NULL) {
1500	xmlFetchXMLCatalogFile(catal);
1501    }
1502    if (catal->children == NULL)
1503	doregister = 1;
1504
1505    typ = xmlGetXMLCatalogEntryType(type);
1506    if (typ == XML_CATA_NONE) {
1507	if (xmlDebugCatalogs)
1508	    xmlGenericError(xmlGenericErrorContext,
1509		    "Failed to add unknown element %s to catalog\n", type);
1510	return(-1);
1511    }
1512
1513    cur = catal->children;
1514    /*
1515     * Might be a simple "update in place"
1516     */
1517    if (cur != NULL) {
1518	while (cur != NULL) {
1519	    if ((orig != NULL) && (cur->type == typ) &&
1520		(xmlStrEqual(orig, cur->name))) {
1521		if (xmlDebugCatalogs)
1522		    xmlGenericError(xmlGenericErrorContext,
1523			    "Updating element %s to catalog\n", type);
1524		if (cur->value != NULL)
1525		    xmlFree(cur->value);
1526		if (cur->URL != NULL)
1527		    xmlFree(cur->URL);
1528		cur->value = xmlStrdup(replace);
1529		cur->URL = xmlStrdup(replace);
1530		return(0);
1531	    }
1532	    if (cur->next == NULL)
1533		break;
1534	    cur = cur->next;
1535	}
1536    }
1537    if (xmlDebugCatalogs)
1538	xmlGenericError(xmlGenericErrorContext,
1539		"Adding element %s to catalog\n", type);
1540    if (cur == NULL)
1541	catal->children = xmlNewCatalogEntry(typ, orig, replace,
1542		                             NULL, catal->prefer, NULL);
1543    else
1544	cur->next = xmlNewCatalogEntry(typ, orig, replace,
1545		                       NULL, catal->prefer, NULL);
1546    if (doregister) {
1547	cur = xmlHashLookup(xmlCatalogXMLFiles, catal->URL);
1548	if (cur != NULL)
1549	    cur->children = catal->children;
1550    }
1551
1552    return(0);
1553}
1554
1555/**
1556 * xmlDelXMLCatalog:
1557 * @catal:  top of an XML catalog
1558 * @value:  the value to remove from the catalog
1559 *
1560 * Remove entries in the XML catalog where the value or the URI
1561 * is equal to @value
1562 *
1563 * Returns the number of entries removed if successful, -1 otherwise
1564 */
1565static int
1566xmlDelXMLCatalog(xmlCatalogEntryPtr catal, const xmlChar *value) {
1567    xmlCatalogEntryPtr cur;
1568    int ret = 0;
1569
1570    if ((catal == NULL) ||
1571	((catal->type != XML_CATA_CATALOG) &&
1572	 (catal->type != XML_CATA_BROKEN_CATALOG)))
1573	return(-1);
1574    if (value == NULL)
1575	return(-1);
1576    if (catal->children == NULL) {
1577	xmlFetchXMLCatalogFile(catal);
1578    }
1579
1580    /*
1581     * Scan the children
1582     */
1583    cur = catal->children;
1584    while (cur != NULL) {
1585	if (((cur->name != NULL) && (xmlStrEqual(value, cur->name))) ||
1586	    (xmlStrEqual(value, cur->value))) {
1587	    if (xmlDebugCatalogs) {
1588		if (cur->name != NULL)
1589		    xmlGenericError(xmlGenericErrorContext,
1590			    "Removing element %s from catalog\n", cur->name);
1591		else
1592		    xmlGenericError(xmlGenericErrorContext,
1593			    "Removing element %s from catalog\n", cur->value);
1594	    }
1595	    cur->type = XML_CATA_REMOVED;
1596	}
1597	cur = cur->next;
1598    }
1599    return(ret);
1600}
1601
1602/**
1603 * xmlCatalogXMLResolve:
1604 * @catal:  a catalog list
1605 * @pubID:  the public ID string
1606 * @sysID:  the system ID string
1607 *
1608 * Do a complete resolution lookup of an External Identifier for a
1609 * list of catalog entries.
1610 *
1611 * Implements (or tries to) 7.1. External Identifier Resolution
1612 * from http://www.oasis-open.org/committees/entity/spec-2001-08-06.html
1613 *
1614 * Returns the URI of the resource or NULL if not found
1615 */
1616static xmlChar *
1617xmlCatalogXMLResolve(xmlCatalogEntryPtr catal, const xmlChar *pubID,
1618	              const xmlChar *sysID) {
1619    xmlChar *ret = NULL;
1620    xmlCatalogEntryPtr cur;
1621    int haveDelegate = 0;
1622    int haveNext = 0;
1623
1624    /*
1625     * protection against loops
1626     */
1627    if (catal->depth > MAX_CATAL_DEPTH) {
1628	xmlCatalogErr(catal, NULL, XML_CATALOG_RECURSION,
1629		      "Detected recursion in catalog %s\n",
1630		      catal->name, NULL, NULL);
1631	return(NULL);
1632    }
1633    catal->depth++;
1634
1635    /*
1636     * First tries steps 2/ 3/ 4/ if a system ID is provided.
1637     */
1638    if (sysID != NULL) {
1639	xmlCatalogEntryPtr rewrite = NULL;
1640	int lenrewrite = 0, len;
1641	cur = catal;
1642	haveDelegate = 0;
1643	while (cur != NULL) {
1644	    switch (cur->type) {
1645		case XML_CATA_SYSTEM:
1646		    if (xmlStrEqual(sysID, cur->name)) {
1647			if (xmlDebugCatalogs)
1648			    xmlGenericError(xmlGenericErrorContext,
1649				    "Found system match %s\n", cur->name);
1650			catal->depth--;
1651			return(xmlStrdup(cur->URL));
1652		    }
1653		    break;
1654		case XML_CATA_REWRITE_SYSTEM:
1655		    len = xmlStrlen(cur->name);
1656		    if ((len > lenrewrite) &&
1657			(!xmlStrncmp(sysID, cur->name, len))) {
1658			lenrewrite = len;
1659			rewrite = cur;
1660		    }
1661		    break;
1662		case XML_CATA_DELEGATE_SYSTEM:
1663		    if (!xmlStrncmp(sysID, cur->name, xmlStrlen(cur->name)))
1664			haveDelegate++;
1665		    break;
1666		case XML_CATA_NEXT_CATALOG:
1667		    haveNext++;
1668		    break;
1669		default:
1670		    break;
1671	    }
1672	    cur = cur->next;
1673	}
1674	if (rewrite != NULL) {
1675	    if (xmlDebugCatalogs)
1676		xmlGenericError(xmlGenericErrorContext,
1677			"Using rewriting rule %s\n", rewrite->name);
1678	    ret = xmlStrdup(rewrite->URL);
1679	    if (ret != NULL)
1680		ret = xmlStrcat(ret, &sysID[lenrewrite]);
1681	    catal->depth--;
1682	    return(ret);
1683	}
1684	if (haveDelegate) {
1685	    const xmlChar *delegates[MAX_DELEGATE];
1686	    int nbList = 0, i;
1687
1688	    /*
1689	     * Assume the entries have been sorted by decreasing substring
1690	     * matches when the list was produced.
1691	     */
1692	    cur = catal;
1693	    while (cur != NULL) {
1694		if ((cur->type == XML_CATA_DELEGATE_SYSTEM) &&
1695		    (!xmlStrncmp(sysID, cur->name, xmlStrlen(cur->name)))) {
1696		    for (i = 0;i < nbList;i++)
1697			if (xmlStrEqual(cur->URL, delegates[i]))
1698			    break;
1699		    if (i < nbList) {
1700			cur = cur->next;
1701			continue;
1702		    }
1703		    if (nbList < MAX_DELEGATE)
1704			delegates[nbList++] = cur->URL;
1705
1706		    if (cur->children == NULL) {
1707			xmlFetchXMLCatalogFile(cur);
1708		    }
1709		    if (cur->children != NULL) {
1710			if (xmlDebugCatalogs)
1711			    xmlGenericError(xmlGenericErrorContext,
1712				    "Trying system delegate %s\n", cur->URL);
1713			ret = xmlCatalogListXMLResolve(
1714				cur->children, NULL, sysID);
1715			if (ret != NULL) {
1716			    catal->depth--;
1717			    return(ret);
1718			}
1719		    }
1720		}
1721		cur = cur->next;
1722	    }
1723	    /*
1724	     * Apply the cut algorithm explained in 4/
1725	     */
1726	    catal->depth--;
1727	    return(XML_CATAL_BREAK);
1728	}
1729    }
1730    /*
1731     * Then tries 5/ 6/ if a public ID is provided
1732     */
1733    if (pubID != NULL) {
1734	cur = catal;
1735	haveDelegate = 0;
1736	while (cur != NULL) {
1737	    switch (cur->type) {
1738		case XML_CATA_PUBLIC:
1739		    if (xmlStrEqual(pubID, cur->name)) {
1740			if (xmlDebugCatalogs)
1741			    xmlGenericError(xmlGenericErrorContext,
1742				    "Found public match %s\n", cur->name);
1743			catal->depth--;
1744			return(xmlStrdup(cur->URL));
1745		    }
1746		    break;
1747		case XML_CATA_DELEGATE_PUBLIC:
1748		    if (!xmlStrncmp(pubID, cur->name, xmlStrlen(cur->name)) &&
1749			(cur->prefer == XML_CATA_PREFER_PUBLIC))
1750			haveDelegate++;
1751		    break;
1752		case XML_CATA_NEXT_CATALOG:
1753		    if (sysID == NULL)
1754			haveNext++;
1755		    break;
1756		default:
1757		    break;
1758	    }
1759	    cur = cur->next;
1760	}
1761	if (haveDelegate) {
1762	    const xmlChar *delegates[MAX_DELEGATE];
1763	    int nbList = 0, i;
1764
1765	    /*
1766	     * Assume the entries have been sorted by decreasing substring
1767	     * matches when the list was produced.
1768	     */
1769	    cur = catal;
1770	    while (cur != NULL) {
1771		if ((cur->type == XML_CATA_DELEGATE_PUBLIC) &&
1772		    (cur->prefer == XML_CATA_PREFER_PUBLIC) &&
1773		    (!xmlStrncmp(pubID, cur->name, xmlStrlen(cur->name)))) {
1774
1775		    for (i = 0;i < nbList;i++)
1776			if (xmlStrEqual(cur->URL, delegates[i]))
1777			    break;
1778		    if (i < nbList) {
1779			cur = cur->next;
1780			continue;
1781		    }
1782		    if (nbList < MAX_DELEGATE)
1783			delegates[nbList++] = cur->URL;
1784
1785		    if (cur->children == NULL) {
1786			xmlFetchXMLCatalogFile(cur);
1787		    }
1788		    if (cur->children != NULL) {
1789			if (xmlDebugCatalogs)
1790			    xmlGenericError(xmlGenericErrorContext,
1791				    "Trying public delegate %s\n", cur->URL);
1792			ret = xmlCatalogListXMLResolve(
1793				cur->children, pubID, NULL);
1794			if (ret != NULL) {
1795			    catal->depth--;
1796			    return(ret);
1797			}
1798		    }
1799		}
1800		cur = cur->next;
1801	    }
1802	    /*
1803	     * Apply the cut algorithm explained in 4/
1804	     */
1805	    catal->depth--;
1806	    return(XML_CATAL_BREAK);
1807	}
1808    }
1809    if (haveNext) {
1810	cur = catal;
1811	while (cur != NULL) {
1812	    if (cur->type == XML_CATA_NEXT_CATALOG) {
1813		if (cur->children == NULL) {
1814		    xmlFetchXMLCatalogFile(cur);
1815		}
1816		if (cur->children != NULL) {
1817		    ret = xmlCatalogListXMLResolve(cur->children, pubID, sysID);
1818		    if (ret != NULL) {
1819			catal->depth--;
1820			return(ret);
1821		    }
1822		}
1823	    }
1824	    cur = cur->next;
1825	}
1826    }
1827
1828    catal->depth--;
1829    return(NULL);
1830}
1831
1832/**
1833 * xmlCatalogXMLResolveURI:
1834 * @catal:  a catalog list
1835 * @URI:  the URI
1836 * @sysID:  the system ID string
1837 *
1838 * Do a complete resolution lookup of an External Identifier for a
1839 * list of catalog entries.
1840 *
1841 * Implements (or tries to) 7.2.2. URI Resolution
1842 * from http://www.oasis-open.org/committees/entity/spec-2001-08-06.html
1843 *
1844 * Returns the URI of the resource or NULL if not found
1845 */
1846static xmlChar *
1847xmlCatalogXMLResolveURI(xmlCatalogEntryPtr catal, const xmlChar *URI) {
1848    xmlChar *ret = NULL;
1849    xmlCatalogEntryPtr cur;
1850    int haveDelegate = 0;
1851    int haveNext = 0;
1852    xmlCatalogEntryPtr rewrite = NULL;
1853    int lenrewrite = 0, len;
1854
1855    if (catal == NULL)
1856	return(NULL);
1857
1858    if (URI == NULL)
1859	return(NULL);
1860
1861    /*
1862     * First tries steps 2/ 3/ 4/ if a system ID is provided.
1863     */
1864    cur = catal;
1865    haveDelegate = 0;
1866    while (cur != NULL) {
1867	switch (cur->type) {
1868	    case XML_CATA_URI:
1869		if (xmlStrEqual(URI, cur->name)) {
1870		    if (xmlDebugCatalogs)
1871			xmlGenericError(xmlGenericErrorContext,
1872				"Found URI match %s\n", cur->name);
1873		    return(xmlStrdup(cur->URL));
1874		}
1875		break;
1876	    case XML_CATA_REWRITE_URI:
1877		len = xmlStrlen(cur->name);
1878		if ((len > lenrewrite) &&
1879		    (!xmlStrncmp(URI, cur->name, len))) {
1880		    lenrewrite = len;
1881		    rewrite = cur;
1882		}
1883		break;
1884	    case XML_CATA_DELEGATE_URI:
1885		if (!xmlStrncmp(URI, cur->name, xmlStrlen(cur->name)))
1886		    haveDelegate++;
1887		break;
1888	    case XML_CATA_NEXT_CATALOG:
1889		haveNext++;
1890		break;
1891	    default:
1892		break;
1893	}
1894	cur = cur->next;
1895    }
1896    if (rewrite != NULL) {
1897	if (xmlDebugCatalogs)
1898	    xmlGenericError(xmlGenericErrorContext,
1899		    "Using rewriting rule %s\n", rewrite->name);
1900	ret = xmlStrdup(rewrite->URL);
1901	if (ret != NULL)
1902	    ret = xmlStrcat(ret, &URI[lenrewrite]);
1903	return(ret);
1904    }
1905    if (haveDelegate) {
1906	const xmlChar *delegates[MAX_DELEGATE];
1907	int nbList = 0, i;
1908
1909	/*
1910	 * Assume the entries have been sorted by decreasing substring
1911	 * matches when the list was produced.
1912	 */
1913	cur = catal;
1914	while (cur != NULL) {
1915	    if (((cur->type == XML_CATA_DELEGATE_SYSTEM) ||
1916	         (cur->type == XML_CATA_DELEGATE_URI)) &&
1917		(!xmlStrncmp(URI, cur->name, xmlStrlen(cur->name)))) {
1918		for (i = 0;i < nbList;i++)
1919		    if (xmlStrEqual(cur->URL, delegates[i]))
1920			break;
1921		if (i < nbList) {
1922		    cur = cur->next;
1923		    continue;
1924		}
1925		if (nbList < MAX_DELEGATE)
1926		    delegates[nbList++] = cur->URL;
1927
1928		if (cur->children == NULL) {
1929		    xmlFetchXMLCatalogFile(cur);
1930		}
1931		if (cur->children != NULL) {
1932		    if (xmlDebugCatalogs)
1933			xmlGenericError(xmlGenericErrorContext,
1934				"Trying URI delegate %s\n", cur->URL);
1935		    ret = xmlCatalogListXMLResolveURI(
1936			    cur->children, URI);
1937		    if (ret != NULL)
1938			return(ret);
1939		}
1940	    }
1941	    cur = cur->next;
1942	}
1943	/*
1944	 * Apply the cut algorithm explained in 4/
1945	 */
1946	return(XML_CATAL_BREAK);
1947    }
1948    if (haveNext) {
1949	cur = catal;
1950	while (cur != NULL) {
1951	    if (cur->type == XML_CATA_NEXT_CATALOG) {
1952		if (cur->children == NULL) {
1953		    xmlFetchXMLCatalogFile(cur);
1954		}
1955		if (cur->children != NULL) {
1956		    ret = xmlCatalogListXMLResolveURI(cur->children, URI);
1957		    if (ret != NULL)
1958			return(ret);
1959		}
1960	    }
1961	    cur = cur->next;
1962	}
1963    }
1964
1965    return(NULL);
1966}
1967
1968/**
1969 * xmlCatalogListXMLResolve:
1970 * @catal:  a catalog list
1971 * @pubID:  the public ID string
1972 * @sysID:  the system ID string
1973 *
1974 * Do a complete resolution lookup of an External Identifier for a
1975 * list of catalogs
1976 *
1977 * Implements (or tries to) 7.1. External Identifier Resolution
1978 * from http://www.oasis-open.org/committees/entity/spec-2001-08-06.html
1979 *
1980 * Returns the URI of the resource or NULL if not found
1981 */
1982static xmlChar *
1983xmlCatalogListXMLResolve(xmlCatalogEntryPtr catal, const xmlChar *pubID,
1984	              const xmlChar *sysID) {
1985    xmlChar *ret = NULL;
1986    xmlChar *urnID = NULL;
1987    xmlChar *normid;
1988
1989    if (catal == NULL)
1990        return(NULL);
1991    if ((pubID == NULL) && (sysID == NULL))
1992	return(NULL);
1993
1994    normid = xmlCatalogNormalizePublic(pubID);
1995    if (normid != NULL)
1996        pubID = (*normid != 0 ? normid : NULL);
1997
1998    if (!xmlStrncmp(pubID, BAD_CAST XML_URN_PUBID, sizeof(XML_URN_PUBID) - 1)) {
1999	urnID = xmlCatalogUnWrapURN(pubID);
2000	if (xmlDebugCatalogs) {
2001	    if (urnID == NULL)
2002		xmlGenericError(xmlGenericErrorContext,
2003			"Public URN ID %s expanded to NULL\n", pubID);
2004	    else
2005		xmlGenericError(xmlGenericErrorContext,
2006			"Public URN ID expanded to %s\n", urnID);
2007	}
2008	ret = xmlCatalogListXMLResolve(catal, urnID, sysID);
2009	if (urnID != NULL)
2010	    xmlFree(urnID);
2011	if (normid != NULL)
2012	    xmlFree(normid);
2013	return(ret);
2014    }
2015    if (!xmlStrncmp(sysID, BAD_CAST XML_URN_PUBID, sizeof(XML_URN_PUBID) - 1)) {
2016	urnID = xmlCatalogUnWrapURN(sysID);
2017	if (xmlDebugCatalogs) {
2018	    if (urnID == NULL)
2019		xmlGenericError(xmlGenericErrorContext,
2020			"System URN ID %s expanded to NULL\n", sysID);
2021	    else
2022		xmlGenericError(xmlGenericErrorContext,
2023			"System URN ID expanded to %s\n", urnID);
2024	}
2025	if (pubID == NULL)
2026	    ret = xmlCatalogListXMLResolve(catal, urnID, NULL);
2027	else if (xmlStrEqual(pubID, urnID))
2028	    ret = xmlCatalogListXMLResolve(catal, pubID, NULL);
2029	else {
2030	    ret = xmlCatalogListXMLResolve(catal, pubID, urnID);
2031	}
2032	if (urnID != NULL)
2033	    xmlFree(urnID);
2034	if (normid != NULL)
2035	    xmlFree(normid);
2036	return(ret);
2037    }
2038    while (catal != NULL) {
2039	if (catal->type == XML_CATA_CATALOG) {
2040	    if (catal->children == NULL) {
2041		xmlFetchXMLCatalogFile(catal);
2042	    }
2043	    if (catal->children != NULL) {
2044		ret = xmlCatalogXMLResolve(catal->children, pubID, sysID);
2045		if (ret != NULL) {
2046                    if (normid != NULL)
2047                        xmlFree(normid);
2048		    return(ret);
2049                }
2050	    }
2051	}
2052	catal = catal->next;
2053    }
2054	if (normid != NULL)
2055	    xmlFree(normid);
2056    return(ret);
2057}
2058
2059/**
2060 * xmlCatalogListXMLResolveURI:
2061 * @catal:  a catalog list
2062 * @URI:  the URI
2063 *
2064 * Do a complete resolution lookup of an URI for a list of catalogs
2065 *
2066 * Implements (or tries to) 7.2. URI Resolution
2067 * from http://www.oasis-open.org/committees/entity/spec-2001-08-06.html
2068 *
2069 * Returns the URI of the resource or NULL if not found
2070 */
2071static xmlChar *
2072xmlCatalogListXMLResolveURI(xmlCatalogEntryPtr catal, const xmlChar *URI) {
2073    xmlChar *ret = NULL;
2074    xmlChar *urnID = NULL;
2075
2076    if (catal == NULL)
2077        return(NULL);
2078    if (URI == NULL)
2079	return(NULL);
2080
2081    if (!xmlStrncmp(URI, BAD_CAST XML_URN_PUBID, sizeof(XML_URN_PUBID) - 1)) {
2082	urnID = xmlCatalogUnWrapURN(URI);
2083	if (xmlDebugCatalogs) {
2084	    if (urnID == NULL)
2085		xmlGenericError(xmlGenericErrorContext,
2086			"URN ID %s expanded to NULL\n", URI);
2087	    else
2088		xmlGenericError(xmlGenericErrorContext,
2089			"URN ID expanded to %s\n", urnID);
2090	}
2091	ret = xmlCatalogListXMLResolve(catal, urnID, NULL);
2092	if (urnID != NULL)
2093	    xmlFree(urnID);
2094	return(ret);
2095    }
2096    while (catal != NULL) {
2097	if (catal->type == XML_CATA_CATALOG) {
2098	    if (catal->children == NULL) {
2099		xmlFetchXMLCatalogFile(catal);
2100	    }
2101	    if (catal->children != NULL) {
2102		ret = xmlCatalogXMLResolveURI(catal->children, URI);
2103		if (ret != NULL)
2104		    return(ret);
2105	    }
2106	}
2107	catal = catal->next;
2108    }
2109    return(ret);
2110}
2111
2112/************************************************************************
2113 *									*
2114 *			The SGML Catalog parser				*
2115 *									*
2116 ************************************************************************/
2117
2118
2119#define RAW *cur
2120#define NEXT cur++;
2121#define SKIP(x) cur += x;
2122
2123#define SKIP_BLANKS while (IS_BLANK_CH(*cur)) NEXT;
2124
2125/**
2126 * xmlParseSGMLCatalogComment:
2127 * @cur:  the current character
2128 *
2129 * Skip a comment in an SGML catalog
2130 *
2131 * Returns new current character
2132 */
2133static const xmlChar *
2134xmlParseSGMLCatalogComment(const xmlChar *cur) {
2135    if ((cur[0] != '-') || (cur[1] != '-'))
2136	return(cur);
2137    SKIP(2);
2138    while ((cur[0] != 0) && ((cur[0] != '-') || ((cur[1] != '-'))))
2139	NEXT;
2140    if (cur[0] == 0) {
2141	return(NULL);
2142    }
2143    return(cur + 2);
2144}
2145
2146/**
2147 * xmlParseSGMLCatalogPubid:
2148 * @cur:  the current character
2149 * @id:  the return location
2150 *
2151 * Parse an SGML catalog ID
2152 *
2153 * Returns new current character and store the value in @id
2154 */
2155static const xmlChar *
2156xmlParseSGMLCatalogPubid(const xmlChar *cur, xmlChar **id) {
2157    xmlChar *buf = NULL, *tmp;
2158    int len = 0;
2159    int size = 50;
2160    xmlChar stop;
2161    int count = 0;
2162
2163    *id = NULL;
2164
2165    if (RAW == '"') {
2166        NEXT;
2167	stop = '"';
2168    } else if (RAW == '\'') {
2169        NEXT;
2170	stop = '\'';
2171    } else {
2172	stop = ' ';
2173    }
2174    buf = (xmlChar *) xmlMallocAtomic(size * sizeof(xmlChar));
2175    if (buf == NULL) {
2176        xmlCatalogErrMemory("allocating public ID");
2177	return(NULL);
2178    }
2179    while (IS_PUBIDCHAR_CH(*cur) || (*cur == '?')) {
2180	if ((*cur == stop) && (stop != ' '))
2181	    break;
2182	if ((stop == ' ') && (IS_BLANK_CH(*cur)))
2183	    break;
2184	if (len + 1 >= size) {
2185	    size *= 2;
2186	    tmp = (xmlChar *) xmlRealloc(buf, size * sizeof(xmlChar));
2187	    if (tmp == NULL) {
2188		xmlCatalogErrMemory("allocating public ID");
2189		xmlFree(buf);
2190		return(NULL);
2191	    }
2192	    buf = tmp;
2193	}
2194	buf[len++] = *cur;
2195	count++;
2196	NEXT;
2197    }
2198    buf[len] = 0;
2199    if (stop == ' ') {
2200	if (!IS_BLANK_CH(*cur)) {
2201	    xmlFree(buf);
2202	    return(NULL);
2203	}
2204    } else {
2205	if (*cur != stop) {
2206	    xmlFree(buf);
2207	    return(NULL);
2208	}
2209	NEXT;
2210    }
2211    *id = buf;
2212    return(cur);
2213}
2214
2215/**
2216 * xmlParseSGMLCatalogName:
2217 * @cur:  the current character
2218 * @name:  the return location
2219 *
2220 * Parse an SGML catalog name
2221 *
2222 * Returns new current character and store the value in @name
2223 */
2224static const xmlChar *
2225xmlParseSGMLCatalogName(const xmlChar *cur, xmlChar **name) {
2226    xmlChar buf[XML_MAX_NAMELEN + 5];
2227    int len = 0;
2228    int c;
2229
2230    *name = NULL;
2231
2232    /*
2233     * Handler for more complex cases
2234     */
2235    c = *cur;
2236    if ((!IS_LETTER(c) && (c != '_') && (c != ':'))) {
2237	return(NULL);
2238    }
2239
2240    while (((IS_LETTER(c)) || (IS_DIGIT(c)) ||
2241            (c == '.') || (c == '-') ||
2242	    (c == '_') || (c == ':'))) {
2243	buf[len++] = c;
2244	cur++;
2245	c = *cur;
2246	if (len >= XML_MAX_NAMELEN)
2247	    return(NULL);
2248    }
2249    *name = xmlStrndup(buf, len);
2250    return(cur);
2251}
2252
2253/**
2254 * xmlGetSGMLCatalogEntryType:
2255 * @name:  the entry name
2256 *
2257 * Get the Catalog entry type for a given SGML Catalog name
2258 *
2259 * Returns Catalog entry type
2260 */
2261static xmlCatalogEntryType
2262xmlGetSGMLCatalogEntryType(const xmlChar *name) {
2263    xmlCatalogEntryType type = XML_CATA_NONE;
2264    if (xmlStrEqual(name, (const xmlChar *) "SYSTEM"))
2265	type = SGML_CATA_SYSTEM;
2266    else if (xmlStrEqual(name, (const xmlChar *) "PUBLIC"))
2267	type = SGML_CATA_PUBLIC;
2268    else if (xmlStrEqual(name, (const xmlChar *) "DELEGATE"))
2269	type = SGML_CATA_DELEGATE;
2270    else if (xmlStrEqual(name, (const xmlChar *) "ENTITY"))
2271	type = SGML_CATA_ENTITY;
2272    else if (xmlStrEqual(name, (const xmlChar *) "DOCTYPE"))
2273	type = SGML_CATA_DOCTYPE;
2274    else if (xmlStrEqual(name, (const xmlChar *) "LINKTYPE"))
2275	type = SGML_CATA_LINKTYPE;
2276    else if (xmlStrEqual(name, (const xmlChar *) "NOTATION"))
2277	type = SGML_CATA_NOTATION;
2278    else if (xmlStrEqual(name, (const xmlChar *) "SGMLDECL"))
2279	type = SGML_CATA_SGMLDECL;
2280    else if (xmlStrEqual(name, (const xmlChar *) "DOCUMENT"))
2281	type = SGML_CATA_DOCUMENT;
2282    else if (xmlStrEqual(name, (const xmlChar *) "CATALOG"))
2283	type = SGML_CATA_CATALOG;
2284    else if (xmlStrEqual(name, (const xmlChar *) "BASE"))
2285	type = SGML_CATA_BASE;
2286    return(type);
2287}
2288
2289/**
2290 * xmlParseSGMLCatalog:
2291 * @catal:  the SGML Catalog
2292 * @value:  the content of the SGML Catalog serialization
2293 * @file:  the filepath for the catalog
2294 * @super:  should this be handled as a Super Catalog in which case
2295 *          parsing is not recursive
2296 *
2297 * Parse an SGML catalog content and fill up the @catal hash table with
2298 * the new entries found.
2299 *
2300 * Returns 0 in case of success, -1 in case of error.
2301 */
2302static int
2303xmlParseSGMLCatalog(xmlCatalogPtr catal, const xmlChar *value,
2304	            const char *file, int super) {
2305    const xmlChar *cur = value;
2306    xmlChar *base = NULL;
2307    int res;
2308
2309    if ((cur == NULL) || (file == NULL))
2310        return(-1);
2311    base = xmlStrdup((const xmlChar *) file);
2312
2313    while ((cur != NULL) && (cur[0] != 0)) {
2314	SKIP_BLANKS;
2315	if (cur[0] == 0)
2316	    break;
2317	if ((cur[0] == '-') && (cur[1] == '-')) {
2318	    cur = xmlParseSGMLCatalogComment(cur);
2319	    if (cur == NULL) {
2320		/* error */
2321		break;
2322	    }
2323	} else {
2324	    xmlChar *sysid = NULL;
2325	    xmlChar *name = NULL;
2326	    xmlCatalogEntryType type = XML_CATA_NONE;
2327
2328	    cur = xmlParseSGMLCatalogName(cur, &name);
2329	    if (name == NULL) {
2330		/* error */
2331		break;
2332	    }
2333	    if (!IS_BLANK_CH(*cur)) {
2334		/* error */
2335		break;
2336	    }
2337	    SKIP_BLANKS;
2338	    if (xmlStrEqual(name, (const xmlChar *) "SYSTEM"))
2339                type = SGML_CATA_SYSTEM;
2340	    else if (xmlStrEqual(name, (const xmlChar *) "PUBLIC"))
2341                type = SGML_CATA_PUBLIC;
2342	    else if (xmlStrEqual(name, (const xmlChar *) "DELEGATE"))
2343                type = SGML_CATA_DELEGATE;
2344	    else if (xmlStrEqual(name, (const xmlChar *) "ENTITY"))
2345                type = SGML_CATA_ENTITY;
2346	    else if (xmlStrEqual(name, (const xmlChar *) "DOCTYPE"))
2347                type = SGML_CATA_DOCTYPE;
2348	    else if (xmlStrEqual(name, (const xmlChar *) "LINKTYPE"))
2349                type = SGML_CATA_LINKTYPE;
2350	    else if (xmlStrEqual(name, (const xmlChar *) "NOTATION"))
2351                type = SGML_CATA_NOTATION;
2352	    else if (xmlStrEqual(name, (const xmlChar *) "SGMLDECL"))
2353                type = SGML_CATA_SGMLDECL;
2354	    else if (xmlStrEqual(name, (const xmlChar *) "DOCUMENT"))
2355                type = SGML_CATA_DOCUMENT;
2356	    else if (xmlStrEqual(name, (const xmlChar *) "CATALOG"))
2357                type = SGML_CATA_CATALOG;
2358	    else if (xmlStrEqual(name, (const xmlChar *) "BASE"))
2359                type = SGML_CATA_BASE;
2360	    else if (xmlStrEqual(name, (const xmlChar *) "OVERRIDE")) {
2361		xmlFree(name);
2362		cur = xmlParseSGMLCatalogName(cur, &name);
2363		if (name == NULL) {
2364		    /* error */
2365		    break;
2366		}
2367		xmlFree(name);
2368		continue;
2369	    }
2370	    xmlFree(name);
2371	    name = NULL;
2372
2373	    switch(type) {
2374		case SGML_CATA_ENTITY:
2375		    if (*cur == '%')
2376			type = SGML_CATA_PENTITY;
2377		case SGML_CATA_PENTITY:
2378		case SGML_CATA_DOCTYPE:
2379		case SGML_CATA_LINKTYPE:
2380		case SGML_CATA_NOTATION:
2381		    cur = xmlParseSGMLCatalogName(cur, &name);
2382		    if (cur == NULL) {
2383			/* error */
2384			break;
2385		    }
2386		    if (!IS_BLANK_CH(*cur)) {
2387			/* error */
2388			break;
2389		    }
2390		    SKIP_BLANKS;
2391		    cur = xmlParseSGMLCatalogPubid(cur, &sysid);
2392		    if (cur == NULL) {
2393			/* error */
2394			break;
2395		    }
2396		    break;
2397		case SGML_CATA_PUBLIC:
2398		case SGML_CATA_SYSTEM:
2399		case SGML_CATA_DELEGATE:
2400		    cur = xmlParseSGMLCatalogPubid(cur, &name);
2401		    if (cur == NULL) {
2402			/* error */
2403			break;
2404		    }
2405		    if (type != SGML_CATA_SYSTEM) {
2406		        xmlChar *normid;
2407
2408		        normid = xmlCatalogNormalizePublic(name);
2409		        if (normid != NULL) {
2410		            if (name != NULL)
2411		                xmlFree(name);
2412		            if (*normid != 0)
2413		                name = normid;
2414		            else {
2415		                xmlFree(normid);
2416		                name = NULL;
2417		            }
2418		        }
2419		    }
2420		    if (!IS_BLANK_CH(*cur)) {
2421			/* error */
2422			break;
2423		    }
2424		    SKIP_BLANKS;
2425		    cur = xmlParseSGMLCatalogPubid(cur, &sysid);
2426		    if (cur == NULL) {
2427			/* error */
2428			break;
2429		    }
2430		    break;
2431		case SGML_CATA_BASE:
2432		case SGML_CATA_CATALOG:
2433		case SGML_CATA_DOCUMENT:
2434		case SGML_CATA_SGMLDECL:
2435		    cur = xmlParseSGMLCatalogPubid(cur, &sysid);
2436		    if (cur == NULL) {
2437			/* error */
2438			break;
2439		    }
2440		    break;
2441		default:
2442		    break;
2443	    }
2444	    if (cur == NULL) {
2445		if (name != NULL)
2446		    xmlFree(name);
2447		if (sysid != NULL)
2448		    xmlFree(sysid);
2449		break;
2450	    } else if (type == SGML_CATA_BASE) {
2451		if (base != NULL)
2452		    xmlFree(base);
2453		base = xmlStrdup(sysid);
2454	    } else if ((type == SGML_CATA_PUBLIC) ||
2455		       (type == SGML_CATA_SYSTEM)) {
2456		xmlChar *filename;
2457
2458		filename = xmlBuildURI(sysid, base);
2459		if (filename != NULL) {
2460		    xmlCatalogEntryPtr entry;
2461
2462		    entry = xmlNewCatalogEntry(type, name, filename,
2463			                       NULL, XML_CATA_PREFER_NONE, NULL);
2464		    res = xmlHashAddEntry(catal->sgml, name, entry);
2465		    if (res < 0) {
2466			xmlFreeCatalogEntry(entry);
2467		    }
2468		    xmlFree(filename);
2469		}
2470
2471	    } else if (type == SGML_CATA_CATALOG) {
2472		if (super) {
2473		    xmlCatalogEntryPtr entry;
2474
2475		    entry = xmlNewCatalogEntry(type, sysid, NULL, NULL,
2476			                       XML_CATA_PREFER_NONE, NULL);
2477		    res = xmlHashAddEntry(catal->sgml, sysid, entry);
2478		    if (res < 0) {
2479			xmlFreeCatalogEntry(entry);
2480		    }
2481		} else {
2482		    xmlChar *filename;
2483
2484		    filename = xmlBuildURI(sysid, base);
2485		    if (filename != NULL) {
2486			xmlExpandCatalog(catal, (const char *)filename);
2487			xmlFree(filename);
2488		    }
2489		}
2490	    }
2491	    /*
2492	     * drop anything else we won't handle it
2493	     */
2494	    if (name != NULL)
2495		xmlFree(name);
2496	    if (sysid != NULL)
2497		xmlFree(sysid);
2498	}
2499    }
2500    if (base != NULL)
2501	xmlFree(base);
2502    if (cur == NULL)
2503	return(-1);
2504    return(0);
2505}
2506
2507/************************************************************************
2508 *									*
2509 *			SGML Catalog handling				*
2510 *									*
2511 ************************************************************************/
2512
2513/**
2514 * xmlCatalogGetSGMLPublic:
2515 * @catal:  an SGML catalog hash
2516 * @pubID:  the public ID string
2517 *
2518 * Try to lookup the catalog local reference associated to a public ID
2519 *
2520 * Returns the local resource if found or NULL otherwise.
2521 */
2522static const xmlChar *
2523xmlCatalogGetSGMLPublic(xmlHashTablePtr catal, const xmlChar *pubID) {
2524    xmlCatalogEntryPtr entry;
2525    xmlChar *normid;
2526
2527    if (catal == NULL)
2528	return(NULL);
2529
2530    normid = xmlCatalogNormalizePublic(pubID);
2531    if (normid != NULL)
2532        pubID = (*normid != 0 ? normid : NULL);
2533
2534    entry = (xmlCatalogEntryPtr) xmlHashLookup(catal, pubID);
2535    if (entry == NULL) {
2536	if (normid != NULL)
2537	    xmlFree(normid);
2538	return(NULL);
2539    }
2540    if (entry->type == SGML_CATA_PUBLIC) {
2541	if (normid != NULL)
2542	    xmlFree(normid);
2543	return(entry->URL);
2544    }
2545    if (normid != NULL)
2546        xmlFree(normid);
2547    return(NULL);
2548}
2549
2550/**
2551 * xmlCatalogGetSGMLSystem:
2552 * @catal:  an SGML catalog hash
2553 * @sysID:  the system ID string
2554 *
2555 * Try to lookup the catalog local reference for a system ID
2556 *
2557 * Returns the local resource if found or NULL otherwise.
2558 */
2559static const xmlChar *
2560xmlCatalogGetSGMLSystem(xmlHashTablePtr catal, const xmlChar *sysID) {
2561    xmlCatalogEntryPtr entry;
2562
2563    if (catal == NULL)
2564	return(NULL);
2565
2566    entry = (xmlCatalogEntryPtr) xmlHashLookup(catal, sysID);
2567    if (entry == NULL)
2568	return(NULL);
2569    if (entry->type == SGML_CATA_SYSTEM)
2570	return(entry->URL);
2571    return(NULL);
2572}
2573
2574/**
2575 * xmlCatalogSGMLResolve:
2576 * @catal:  the SGML catalog
2577 * @pubID:  the public ID string
2578 * @sysID:  the system ID string
2579 *
2580 * Do a complete resolution lookup of an External Identifier
2581 *
2582 * Returns the URI of the resource or NULL if not found
2583 */
2584static const xmlChar *
2585xmlCatalogSGMLResolve(xmlCatalogPtr catal, const xmlChar *pubID,
2586	              const xmlChar *sysID) {
2587    const xmlChar *ret = NULL;
2588
2589    if (catal->sgml == NULL)
2590	return(NULL);
2591
2592    if (pubID != NULL)
2593	ret = xmlCatalogGetSGMLPublic(catal->sgml, pubID);
2594    if (ret != NULL)
2595	return(ret);
2596    if (sysID != NULL)
2597	ret = xmlCatalogGetSGMLSystem(catal->sgml, sysID);
2598    return(NULL);
2599}
2600
2601/************************************************************************
2602 *									*
2603 *			Specific Public interfaces			*
2604 *									*
2605 ************************************************************************/
2606
2607/**
2608 * xmlLoadSGMLSuperCatalog:
2609 * @filename:  a file path
2610 *
2611 * Load an SGML super catalog. It won't expand CATALOG or DELEGATE
2612 * references. This is only needed for manipulating SGML Super Catalogs
2613 * like adding and removing CATALOG or DELEGATE entries.
2614 *
2615 * Returns the catalog parsed or NULL in case of error
2616 */
2617xmlCatalogPtr
2618xmlLoadSGMLSuperCatalog(const char *filename)
2619{
2620    xmlChar *content;
2621    xmlCatalogPtr catal;
2622    int ret;
2623
2624    content = xmlLoadFileContent(filename);
2625    if (content == NULL)
2626        return(NULL);
2627
2628    catal = xmlCreateNewCatalog(XML_SGML_CATALOG_TYPE, xmlCatalogDefaultPrefer);
2629    if (catal == NULL) {
2630	xmlFree(content);
2631	return(NULL);
2632    }
2633
2634    ret = xmlParseSGMLCatalog(catal, content, filename, 1);
2635    xmlFree(content);
2636    if (ret < 0) {
2637	xmlFreeCatalog(catal);
2638	return(NULL);
2639    }
2640    return (catal);
2641}
2642
2643/**
2644 * xmlLoadACatalog:
2645 * @filename:  a file path
2646 *
2647 * Load the catalog and build the associated data structures.
2648 * This can be either an XML Catalog or an SGML Catalog
2649 * It will recurse in SGML CATALOG entries. On the other hand XML
2650 * Catalogs are not handled recursively.
2651 *
2652 * Returns the catalog parsed or NULL in case of error
2653 */
2654xmlCatalogPtr
2655xmlLoadACatalog(const char *filename)
2656{
2657    xmlChar *content;
2658    xmlChar *first;
2659    xmlCatalogPtr catal;
2660    int ret;
2661
2662    content = xmlLoadFileContent(filename);
2663    if (content == NULL)
2664        return(NULL);
2665
2666
2667    first = content;
2668
2669    while ((*first != 0) && (*first != '-') && (*first != '<') &&
2670	   (!(((*first >= 'A') && (*first <= 'Z')) ||
2671	      ((*first >= 'a') && (*first <= 'z')))))
2672	first++;
2673
2674    if (*first != '<') {
2675	catal = xmlCreateNewCatalog(XML_SGML_CATALOG_TYPE, xmlCatalogDefaultPrefer);
2676	if (catal == NULL) {
2677	    xmlFree(content);
2678	    return(NULL);
2679	}
2680        ret = xmlParseSGMLCatalog(catal, content, filename, 0);
2681	if (ret < 0) {
2682	    xmlFreeCatalog(catal);
2683	    xmlFree(content);
2684	    return(NULL);
2685	}
2686    } else {
2687	catal = xmlCreateNewCatalog(XML_XML_CATALOG_TYPE, xmlCatalogDefaultPrefer);
2688	if (catal == NULL) {
2689	    xmlFree(content);
2690	    return(NULL);
2691	}
2692        catal->xml = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL,
2693		       NULL, BAD_CAST filename, xmlCatalogDefaultPrefer, NULL);
2694    }
2695    xmlFree(content);
2696    return (catal);
2697}
2698
2699/**
2700 * xmlExpandCatalog:
2701 * @catal:  a catalog
2702 * @filename:  a file path
2703 *
2704 * Load the catalog and expand the existing catal structure.
2705 * This can be either an XML Catalog or an SGML Catalog
2706 *
2707 * Returns 0 in case of success, -1 in case of error
2708 */
2709static int
2710xmlExpandCatalog(xmlCatalogPtr catal, const char *filename)
2711{
2712    int ret;
2713
2714    if ((catal == NULL) || (filename == NULL))
2715	return(-1);
2716
2717
2718    if (catal->type == XML_SGML_CATALOG_TYPE) {
2719	xmlChar *content;
2720
2721	content = xmlLoadFileContent(filename);
2722	if (content == NULL)
2723	    return(-1);
2724
2725        ret = xmlParseSGMLCatalog(catal, content, filename, 0);
2726	if (ret < 0) {
2727	    xmlFree(content);
2728	    return(-1);
2729	}
2730	xmlFree(content);
2731    } else {
2732	xmlCatalogEntryPtr tmp, cur;
2733	tmp = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL,
2734		       NULL, BAD_CAST filename, xmlCatalogDefaultPrefer, NULL);
2735
2736	cur = catal->xml;
2737	if (cur == NULL) {
2738	    catal->xml = tmp;
2739	} else {
2740	    while (cur->next != NULL) cur = cur->next;
2741	    cur->next = tmp;
2742	}
2743    }
2744    return (0);
2745}
2746
2747/**
2748 * xmlACatalogResolveSystem:
2749 * @catal:  a Catalog
2750 * @sysID:  the system ID string
2751 *
2752 * Try to lookup the catalog resource for a system ID
2753 *
2754 * Returns the resource if found or NULL otherwise, the value returned
2755 *      must be freed by the caller.
2756 */
2757xmlChar *
2758xmlACatalogResolveSystem(xmlCatalogPtr catal, const xmlChar *sysID) {
2759    xmlChar *ret = NULL;
2760
2761    if ((sysID == NULL) || (catal == NULL))
2762	return(NULL);
2763
2764    if (xmlDebugCatalogs)
2765	xmlGenericError(xmlGenericErrorContext,
2766		"Resolve sysID %s\n", sysID);
2767
2768    if (catal->type == XML_XML_CATALOG_TYPE) {
2769	ret = xmlCatalogListXMLResolve(catal->xml, NULL, sysID);
2770	if (ret == XML_CATAL_BREAK)
2771	    ret = NULL;
2772    } else {
2773	const xmlChar *sgml;
2774
2775	sgml = xmlCatalogGetSGMLSystem(catal->sgml, sysID);
2776	if (sgml != NULL)
2777	    ret = xmlStrdup(sgml);
2778    }
2779    return(ret);
2780}
2781
2782/**
2783 * xmlACatalogResolvePublic:
2784 * @catal:  a Catalog
2785 * @pubID:  the public ID string
2786 *
2787 * Try to lookup the catalog local reference associated to a public ID in that catalog
2788 *
2789 * Returns the local resource if found or NULL otherwise, the value returned
2790 *      must be freed by the caller.
2791 */
2792xmlChar *
2793xmlACatalogResolvePublic(xmlCatalogPtr catal, const xmlChar *pubID) {
2794    xmlChar *ret = NULL;
2795
2796    if ((pubID == NULL) || (catal == NULL))
2797	return(NULL);
2798
2799    if (xmlDebugCatalogs)
2800	xmlGenericError(xmlGenericErrorContext,
2801		"Resolve pubID %s\n", pubID);
2802
2803    if (catal->type == XML_XML_CATALOG_TYPE) {
2804	ret = xmlCatalogListXMLResolve(catal->xml, pubID, NULL);
2805	if (ret == XML_CATAL_BREAK)
2806	    ret = NULL;
2807    } else {
2808	const xmlChar *sgml;
2809
2810	sgml = xmlCatalogGetSGMLPublic(catal->sgml, pubID);
2811	if (sgml != NULL)
2812	    ret = xmlStrdup(sgml);
2813    }
2814    return(ret);
2815}
2816
2817/**
2818 * xmlACatalogResolve:
2819 * @catal:  a Catalog
2820 * @pubID:  the public ID string
2821 * @sysID:  the system ID string
2822 *
2823 * Do a complete resolution lookup of an External Identifier
2824 *
2825 * Returns the URI of the resource or NULL if not found, it must be freed
2826 *      by the caller.
2827 */
2828xmlChar *
2829xmlACatalogResolve(xmlCatalogPtr catal, const xmlChar * pubID,
2830                   const xmlChar * sysID)
2831{
2832    xmlChar *ret = NULL;
2833
2834    if (((pubID == NULL) && (sysID == NULL)) || (catal == NULL))
2835        return (NULL);
2836
2837    if (xmlDebugCatalogs) {
2838         if ((pubID != NULL) && (sysID != NULL)) {
2839             xmlGenericError(xmlGenericErrorContext,
2840                             "Resolve: pubID %s sysID %s\n", pubID, sysID);
2841         } else if (pubID != NULL) {
2842             xmlGenericError(xmlGenericErrorContext,
2843                             "Resolve: pubID %s\n", pubID);
2844         } else {
2845             xmlGenericError(xmlGenericErrorContext,
2846                             "Resolve: sysID %s\n", sysID);
2847         }
2848    }
2849
2850    if (catal->type == XML_XML_CATALOG_TYPE) {
2851        ret = xmlCatalogListXMLResolve(catal->xml, pubID, sysID);
2852	if (ret == XML_CATAL_BREAK)
2853	    ret = NULL;
2854    } else {
2855        const xmlChar *sgml;
2856
2857        sgml = xmlCatalogSGMLResolve(catal, pubID, sysID);
2858        if (sgml != NULL)
2859            ret = xmlStrdup(sgml);
2860    }
2861    return (ret);
2862}
2863
2864/**
2865 * xmlACatalogResolveURI:
2866 * @catal:  a Catalog
2867 * @URI:  the URI
2868 *
2869 * Do a complete resolution lookup of an URI
2870 *
2871 * Returns the URI of the resource or NULL if not found, it must be freed
2872 *      by the caller.
2873 */
2874xmlChar *
2875xmlACatalogResolveURI(xmlCatalogPtr catal, const xmlChar *URI) {
2876    xmlChar *ret = NULL;
2877
2878    if ((URI == NULL) || (catal == NULL))
2879	return(NULL);
2880
2881    if (xmlDebugCatalogs)
2882	xmlGenericError(xmlGenericErrorContext,
2883		"Resolve URI %s\n", URI);
2884
2885    if (catal->type == XML_XML_CATALOG_TYPE) {
2886	ret = xmlCatalogListXMLResolveURI(catal->xml, URI);
2887	if (ret == XML_CATAL_BREAK)
2888	    ret = NULL;
2889    } else {
2890	const xmlChar *sgml;
2891
2892	sgml = xmlCatalogSGMLResolve(catal, NULL, URI);
2893	if (sgml != NULL)
2894            sgml = xmlStrdup(sgml);
2895    }
2896    return(ret);
2897}
2898
2899#ifdef LIBXML_OUTPUT_ENABLED
2900/**
2901 * xmlACatalogDump:
2902 * @catal:  a Catalog
2903 * @out:  the file.
2904 *
2905 * Dump the given catalog to the given file.
2906 */
2907void
2908xmlACatalogDump(xmlCatalogPtr catal, FILE *out) {
2909    if ((out == NULL) || (catal == NULL))
2910	return;
2911
2912    if (catal->type == XML_XML_CATALOG_TYPE) {
2913	xmlDumpXMLCatalog(out, catal->xml);
2914    } else {
2915	xmlHashScan(catal->sgml,
2916		    (xmlHashScanner) xmlCatalogDumpEntry, out);
2917    }
2918}
2919#endif /* LIBXML_OUTPUT_ENABLED */
2920
2921/**
2922 * xmlACatalogAdd:
2923 * @catal:  a Catalog
2924 * @type:  the type of record to add to the catalog
2925 * @orig:  the system, public or prefix to match
2926 * @replace:  the replacement value for the match
2927 *
2928 * Add an entry in the catalog, it may overwrite existing but
2929 * different entries.
2930 *
2931 * Returns 0 if successful, -1 otherwise
2932 */
2933int
2934xmlACatalogAdd(xmlCatalogPtr catal, const xmlChar * type,
2935              const xmlChar * orig, const xmlChar * replace)
2936{
2937    int res = -1;
2938
2939    if (catal == NULL)
2940	return(-1);
2941
2942    if (catal->type == XML_XML_CATALOG_TYPE) {
2943        res = xmlAddXMLCatalog(catal->xml, type, orig, replace);
2944    } else {
2945        xmlCatalogEntryType cattype;
2946
2947        cattype = xmlGetSGMLCatalogEntryType(type);
2948        if (cattype != XML_CATA_NONE) {
2949            xmlCatalogEntryPtr entry;
2950
2951            entry = xmlNewCatalogEntry(cattype, orig, replace, NULL,
2952                                       XML_CATA_PREFER_NONE, NULL);
2953	    if (catal->sgml == NULL)
2954		catal->sgml = xmlHashCreate(10);
2955            res = xmlHashAddEntry(catal->sgml, orig, entry);
2956        }
2957    }
2958    return (res);
2959}
2960
2961/**
2962 * xmlACatalogRemove:
2963 * @catal:  a Catalog
2964 * @value:  the value to remove
2965 *
2966 * Remove an entry from the catalog
2967 *
2968 * Returns the number of entries removed if successful, -1 otherwise
2969 */
2970int
2971xmlACatalogRemove(xmlCatalogPtr catal, const xmlChar *value) {
2972    int res = -1;
2973
2974    if ((catal == NULL) || (value == NULL))
2975	return(-1);
2976
2977    if (catal->type == XML_XML_CATALOG_TYPE) {
2978	res = xmlDelXMLCatalog(catal->xml, value);
2979    } else {
2980	res = xmlHashRemoveEntry(catal->sgml, value,
2981		(xmlHashDeallocator) xmlFreeCatalogEntry);
2982	if (res == 0)
2983	    res = 1;
2984    }
2985    return(res);
2986}
2987
2988/**
2989 * xmlNewCatalog:
2990 * @sgml:  should this create an SGML catalog
2991 *
2992 * create a new Catalog.
2993 *
2994 * Returns the xmlCatalogPtr or NULL in case of error
2995 */
2996xmlCatalogPtr
2997xmlNewCatalog(int sgml) {
2998    xmlCatalogPtr catal = NULL;
2999
3000    if (sgml) {
3001	catal = xmlCreateNewCatalog(XML_SGML_CATALOG_TYPE,
3002		                    xmlCatalogDefaultPrefer);
3003        if ((catal != NULL) && (catal->sgml == NULL))
3004	    catal->sgml = xmlHashCreate(10);
3005    } else
3006	catal = xmlCreateNewCatalog(XML_XML_CATALOG_TYPE,
3007		                    xmlCatalogDefaultPrefer);
3008    return(catal);
3009}
3010
3011/**
3012 * xmlCatalogIsEmpty:
3013 * @catal:  should this create an SGML catalog
3014 *
3015 * Check is a catalog is empty
3016 *
3017 * Returns 1 if the catalog is empty, 0 if not, amd -1 in case of error.
3018 */
3019int
3020xmlCatalogIsEmpty(xmlCatalogPtr catal) {
3021    if (catal == NULL)
3022	return(-1);
3023
3024    if (catal->type == XML_XML_CATALOG_TYPE) {
3025	if (catal->xml == NULL)
3026	    return(1);
3027	if ((catal->xml->type != XML_CATA_CATALOG) &&
3028	    (catal->xml->type != XML_CATA_BROKEN_CATALOG))
3029	    return(-1);
3030	if (catal->xml->children == NULL)
3031	    return(1);
3032        return(0);
3033    } else {
3034	int res;
3035
3036	if (catal->sgml == NULL)
3037	    return(1);
3038	res = xmlHashSize(catal->sgml);
3039	if (res == 0)
3040	    return(1);
3041	if (res < 0)
3042	    return(-1);
3043    }
3044    return(0);
3045}
3046
3047/************************************************************************
3048 *									*
3049 *   Public interfaces manipulating the global shared default catalog	*
3050 *									*
3051 ************************************************************************/
3052
3053/**
3054 * xmlInitializeCatalogData:
3055 *
3056 * Do the catalog initialization only of global data, doesn't try to load
3057 * any catalog actually.
3058 * this function is not thread safe, catalog initialization should
3059 * preferably be done once at startup
3060 */
3061static void
3062xmlInitializeCatalogData(void) {
3063    if (xmlCatalogInitialized != 0)
3064	return;
3065
3066    if (getenv("XML_DEBUG_CATALOG"))
3067	xmlDebugCatalogs = 1;
3068    xmlCatalogMutex = xmlNewRMutex();
3069
3070    xmlCatalogInitialized = 1;
3071}
3072/**
3073 * xmlInitializeCatalog:
3074 *
3075 * Do the catalog initialization.
3076 * this function is not thread safe, catalog initialization should
3077 * preferably be done once at startup
3078 */
3079void
3080xmlInitializeCatalog(void) {
3081    if (xmlCatalogInitialized != 0)
3082	return;
3083
3084    xmlInitializeCatalogData();
3085    xmlRMutexLock(xmlCatalogMutex);
3086
3087    if (getenv("XML_DEBUG_CATALOG"))
3088	xmlDebugCatalogs = 1;
3089
3090    if (xmlDefaultCatalog == NULL) {
3091	const char *catalogs;
3092	char *path;
3093	const char *cur, *paths;
3094	xmlCatalogPtr catal;
3095	xmlCatalogEntryPtr *nextent;
3096
3097	catalogs = (const char *) getenv("XML_CATALOG_FILES");
3098	if (catalogs == NULL)
3099#if defined(_WIN32) && defined(_MSC_VER)
3100    {
3101		void* hmodule;
3102		hmodule = GetModuleHandleA("libxml2.dll");
3103		if (hmodule == NULL)
3104			hmodule = GetModuleHandleA(NULL);
3105		if (hmodule != NULL) {
3106			char buf[256];
3107			unsigned long len = GetModuleFileNameA(hmodule, buf, 255);
3108			if (len != 0) {
3109				char* p = &(buf[len]);
3110				while (*p != '\\' && p > buf)
3111					p--;
3112				if (p != buf) {
3113					xmlChar* uri;
3114					strncpy(p, "\\..\\etc\\catalog", 255 - (p - buf));
3115					uri = xmlCanonicPath(buf);
3116					if (uri != NULL) {
3117						strncpy(XML_XML_DEFAULT_CATALOG, uri, 255);
3118						xmlFree(uri);
3119					}
3120				}
3121			}
3122		}
3123		catalogs = XML_XML_DEFAULT_CATALOG;
3124    }
3125#else
3126	    catalogs = XML_XML_DEFAULT_CATALOG;
3127#endif
3128
3129	catal = xmlCreateNewCatalog(XML_XML_CATALOG_TYPE,
3130		xmlCatalogDefaultPrefer);
3131	if (catal != NULL) {
3132	    /* the XML_CATALOG_FILES envvar is allowed to contain a
3133	       space-separated list of entries. */
3134	    cur = catalogs;
3135	    nextent = &catal->xml;
3136	    while (*cur != '\0') {
3137		while (xmlIsBlank_ch(*cur))
3138		    cur++;
3139		if (*cur != 0) {
3140		    paths = cur;
3141		    while ((*cur != 0) && (!xmlIsBlank_ch(*cur)))
3142			cur++;
3143		    path = (char *) xmlStrndup((const xmlChar *)paths, cur - paths);
3144		    if (path != NULL) {
3145			*nextent = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL,
3146				NULL, BAD_CAST path, xmlCatalogDefaultPrefer, NULL);
3147			if (*nextent != NULL)
3148			    nextent = &((*nextent)->next);
3149			xmlFree(path);
3150		    }
3151		}
3152	    }
3153	    xmlDefaultCatalog = catal;
3154	}
3155    }
3156
3157    xmlRMutexUnlock(xmlCatalogMutex);
3158}
3159
3160
3161/**
3162 * xmlLoadCatalog:
3163 * @filename:  a file path
3164 *
3165 * Load the catalog and makes its definitions effective for the default
3166 * external entity loader. It will recurse in SGML CATALOG entries.
3167 * this function is not thread safe, catalog initialization should
3168 * preferably be done once at startup
3169 *
3170 * Returns 0 in case of success -1 in case of error
3171 */
3172int
3173xmlLoadCatalog(const char *filename)
3174{
3175    int ret;
3176    xmlCatalogPtr catal;
3177
3178    if (!xmlCatalogInitialized)
3179	xmlInitializeCatalogData();
3180
3181    xmlRMutexLock(xmlCatalogMutex);
3182
3183    if (xmlDefaultCatalog == NULL) {
3184	catal = xmlLoadACatalog(filename);
3185	if (catal == NULL) {
3186	    xmlRMutexUnlock(xmlCatalogMutex);
3187	    return(-1);
3188	}
3189
3190	xmlDefaultCatalog = catal;
3191	xmlRMutexUnlock(xmlCatalogMutex);
3192	return(0);
3193    }
3194
3195    ret = xmlExpandCatalog(xmlDefaultCatalog, filename);
3196    xmlRMutexUnlock(xmlCatalogMutex);
3197    return(ret);
3198}
3199
3200/**
3201 * xmlLoadCatalogs:
3202 * @pathss:  a list of directories separated by a colon or a space.
3203 *
3204 * Load the catalogs and makes their definitions effective for the default
3205 * external entity loader.
3206 * this function is not thread safe, catalog initialization should
3207 * preferably be done once at startup
3208 */
3209void
3210xmlLoadCatalogs(const char *pathss) {
3211    const char *cur;
3212    const char *paths;
3213    xmlChar *path;
3214
3215    if (pathss == NULL)
3216	return;
3217
3218    cur = pathss;
3219    while ((cur != NULL) && (*cur != 0)) {
3220	while (xmlIsBlank_ch(*cur)) cur++;
3221	if (*cur != 0) {
3222	    paths = cur;
3223	    while ((*cur != 0) && (*cur != ':') && (!xmlIsBlank_ch(*cur)))
3224		cur++;
3225	    path = xmlStrndup((const xmlChar *)paths, cur - paths);
3226	    if (path != NULL) {
3227		xmlLoadCatalog((const char *) path);
3228		xmlFree(path);
3229	    }
3230	}
3231	while (*cur == ':')
3232	    cur++;
3233    }
3234}
3235
3236/**
3237 * xmlCatalogCleanup:
3238 *
3239 * Free up all the memory associated with catalogs
3240 */
3241void
3242xmlCatalogCleanup(void) {
3243    if (xmlCatalogInitialized == 0)
3244        return;
3245
3246    xmlRMutexLock(xmlCatalogMutex);
3247    if (xmlDebugCatalogs)
3248	xmlGenericError(xmlGenericErrorContext,
3249		"Catalogs cleanup\n");
3250    if (xmlCatalogXMLFiles != NULL)
3251	xmlHashFree(xmlCatalogXMLFiles,
3252		    (xmlHashDeallocator)xmlFreeCatalogHashEntryList);
3253    xmlCatalogXMLFiles = NULL;
3254    if (xmlDefaultCatalog != NULL)
3255	xmlFreeCatalog(xmlDefaultCatalog);
3256    xmlDefaultCatalog = NULL;
3257    xmlDebugCatalogs = 0;
3258    xmlCatalogInitialized = 0;
3259    xmlRMutexUnlock(xmlCatalogMutex);
3260    xmlFreeRMutex(xmlCatalogMutex);
3261}
3262
3263/**
3264 * xmlCatalogResolveSystem:
3265 * @sysID:  the system ID string
3266 *
3267 * Try to lookup the catalog resource for a system ID
3268 *
3269 * Returns the resource if found or NULL otherwise, the value returned
3270 *      must be freed by the caller.
3271 */
3272xmlChar *
3273xmlCatalogResolveSystem(const xmlChar *sysID) {
3274    xmlChar *ret;
3275
3276    if (!xmlCatalogInitialized)
3277	xmlInitializeCatalog();
3278
3279    ret = xmlACatalogResolveSystem(xmlDefaultCatalog, sysID);
3280    return(ret);
3281}
3282
3283/**
3284 * xmlCatalogResolvePublic:
3285 * @pubID:  the public ID string
3286 *
3287 * Try to lookup the catalog reference associated to a public ID
3288 *
3289 * Returns the resource if found or NULL otherwise, the value returned
3290 *      must be freed by the caller.
3291 */
3292xmlChar *
3293xmlCatalogResolvePublic(const xmlChar *pubID) {
3294    xmlChar *ret;
3295
3296    if (!xmlCatalogInitialized)
3297	xmlInitializeCatalog();
3298
3299    ret = xmlACatalogResolvePublic(xmlDefaultCatalog, pubID);
3300    return(ret);
3301}
3302
3303/**
3304 * xmlCatalogResolve:
3305 * @pubID:  the public ID string
3306 * @sysID:  the system ID string
3307 *
3308 * Do a complete resolution lookup of an External Identifier
3309 *
3310 * Returns the URI of the resource or NULL if not found, it must be freed
3311 *      by the caller.
3312 */
3313xmlChar *
3314xmlCatalogResolve(const xmlChar *pubID, const xmlChar *sysID) {
3315    xmlChar *ret;
3316
3317    if (!xmlCatalogInitialized)
3318	xmlInitializeCatalog();
3319
3320    ret = xmlACatalogResolve(xmlDefaultCatalog, pubID, sysID);
3321    return(ret);
3322}
3323
3324/**
3325 * xmlCatalogResolveURI:
3326 * @URI:  the URI
3327 *
3328 * Do a complete resolution lookup of an URI
3329 *
3330 * Returns the URI of the resource or NULL if not found, it must be freed
3331 *      by the caller.
3332 */
3333xmlChar *
3334xmlCatalogResolveURI(const xmlChar *URI) {
3335    xmlChar *ret;
3336
3337    if (!xmlCatalogInitialized)
3338	xmlInitializeCatalog();
3339
3340    ret = xmlACatalogResolveURI(xmlDefaultCatalog, URI);
3341    return(ret);
3342}
3343
3344#ifdef LIBXML_OUTPUT_ENABLED
3345/**
3346 * xmlCatalogDump:
3347 * @out:  the file.
3348 *
3349 * Dump all the global catalog content to the given file.
3350 */
3351void
3352xmlCatalogDump(FILE *out) {
3353    if (out == NULL)
3354	return;
3355
3356    if (!xmlCatalogInitialized)
3357	xmlInitializeCatalog();
3358
3359    xmlACatalogDump(xmlDefaultCatalog, out);
3360}
3361#endif /* LIBXML_OUTPUT_ENABLED */
3362
3363/**
3364 * xmlCatalogAdd:
3365 * @type:  the type of record to add to the catalog
3366 * @orig:  the system, public or prefix to match
3367 * @replace:  the replacement value for the match
3368 *
3369 * Add an entry in the catalog, it may overwrite existing but
3370 * different entries.
3371 * If called before any other catalog routine, allows to override the
3372 * default shared catalog put in place by xmlInitializeCatalog();
3373 *
3374 * Returns 0 if successful, -1 otherwise
3375 */
3376int
3377xmlCatalogAdd(const xmlChar *type, const xmlChar *orig, const xmlChar *replace) {
3378    int res = -1;
3379
3380    if (!xmlCatalogInitialized)
3381	xmlInitializeCatalogData();
3382
3383    xmlRMutexLock(xmlCatalogMutex);
3384    /*
3385     * Specific case where one want to override the default catalog
3386     * put in place by xmlInitializeCatalog();
3387     */
3388    if ((xmlDefaultCatalog == NULL) &&
3389	(xmlStrEqual(type, BAD_CAST "catalog"))) {
3390	xmlDefaultCatalog = xmlCreateNewCatalog(XML_XML_CATALOG_TYPE,
3391		                          xmlCatalogDefaultPrefer);
3392	xmlDefaultCatalog->xml = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL,
3393				    orig, NULL,  xmlCatalogDefaultPrefer, NULL);
3394
3395	xmlRMutexUnlock(xmlCatalogMutex);
3396	return(0);
3397    }
3398
3399    res = xmlACatalogAdd(xmlDefaultCatalog, type, orig, replace);
3400    xmlRMutexUnlock(xmlCatalogMutex);
3401    return(res);
3402}
3403
3404/**
3405 * xmlCatalogRemove:
3406 * @value:  the value to remove
3407 *
3408 * Remove an entry from the catalog
3409 *
3410 * Returns the number of entries removed if successful, -1 otherwise
3411 */
3412int
3413xmlCatalogRemove(const xmlChar *value) {
3414    int res;
3415
3416    if (!xmlCatalogInitialized)
3417	xmlInitializeCatalog();
3418
3419    xmlRMutexLock(xmlCatalogMutex);
3420    res = xmlACatalogRemove(xmlDefaultCatalog, value);
3421    xmlRMutexUnlock(xmlCatalogMutex);
3422    return(res);
3423}
3424
3425/**
3426 * xmlCatalogConvert:
3427 *
3428 * Convert all the SGML catalog entries as XML ones
3429 *
3430 * Returns the number of entries converted if successful, -1 otherwise
3431 */
3432int
3433xmlCatalogConvert(void) {
3434    int res = -1;
3435
3436    if (!xmlCatalogInitialized)
3437	xmlInitializeCatalog();
3438
3439    xmlRMutexLock(xmlCatalogMutex);
3440    res = xmlConvertSGMLCatalog(xmlDefaultCatalog);
3441    xmlRMutexUnlock(xmlCatalogMutex);
3442    return(res);
3443}
3444
3445/************************************************************************
3446 *									*
3447 *	Public interface manipulating the common preferences		*
3448 *									*
3449 ************************************************************************/
3450
3451/**
3452 * xmlCatalogGetDefaults:
3453 *
3454 * Used to get the user preference w.r.t. to what catalogs should
3455 * be accepted
3456 *
3457 * Returns the current xmlCatalogAllow value
3458 */
3459xmlCatalogAllow
3460xmlCatalogGetDefaults(void) {
3461    return(xmlCatalogDefaultAllow);
3462}
3463
3464/**
3465 * xmlCatalogSetDefaults:
3466 * @allow:  what catalogs should be accepted
3467 *
3468 * Used to set the user preference w.r.t. to what catalogs should
3469 * be accepted
3470 */
3471void
3472xmlCatalogSetDefaults(xmlCatalogAllow allow) {
3473    if (xmlDebugCatalogs) {
3474	switch (allow) {
3475	    case XML_CATA_ALLOW_NONE:
3476		xmlGenericError(xmlGenericErrorContext,
3477			"Disabling catalog usage\n");
3478		break;
3479	    case XML_CATA_ALLOW_GLOBAL:
3480		xmlGenericError(xmlGenericErrorContext,
3481			"Allowing only global catalogs\n");
3482		break;
3483	    case XML_CATA_ALLOW_DOCUMENT:
3484		xmlGenericError(xmlGenericErrorContext,
3485			"Allowing only catalogs from the document\n");
3486		break;
3487	    case XML_CATA_ALLOW_ALL:
3488		xmlGenericError(xmlGenericErrorContext,
3489			"Allowing all catalogs\n");
3490		break;
3491	}
3492    }
3493    xmlCatalogDefaultAllow = allow;
3494}
3495
3496/**
3497 * xmlCatalogSetDefaultPrefer:
3498 * @prefer:  the default preference for delegation
3499 *
3500 * Allows to set the preference between public and system for deletion
3501 * in XML Catalog resolution. C.f. section 4.1.1 of the spec
3502 * Values accepted are XML_CATA_PREFER_PUBLIC or XML_CATA_PREFER_SYSTEM
3503 *
3504 * Returns the previous value of the default preference for delegation
3505 */
3506xmlCatalogPrefer
3507xmlCatalogSetDefaultPrefer(xmlCatalogPrefer prefer) {
3508    xmlCatalogPrefer ret = xmlCatalogDefaultPrefer;
3509
3510    if (prefer == XML_CATA_PREFER_NONE)
3511	return(ret);
3512
3513    if (xmlDebugCatalogs) {
3514	switch (prefer) {
3515	    case XML_CATA_PREFER_PUBLIC:
3516		xmlGenericError(xmlGenericErrorContext,
3517			"Setting catalog preference to PUBLIC\n");
3518		break;
3519	    case XML_CATA_PREFER_SYSTEM:
3520		xmlGenericError(xmlGenericErrorContext,
3521			"Setting catalog preference to SYSTEM\n");
3522		break;
3523	    case XML_CATA_PREFER_NONE:
3524		break;
3525	}
3526    }
3527    xmlCatalogDefaultPrefer = prefer;
3528    return(ret);
3529}
3530
3531/**
3532 * xmlCatalogSetDebug:
3533 * @level:  the debug level of catalogs required
3534 *
3535 * Used to set the debug level for catalog operation, 0 disable
3536 * debugging, 1 enable it
3537 *
3538 * Returns the previous value of the catalog debugging level
3539 */
3540int
3541xmlCatalogSetDebug(int level) {
3542    int ret = xmlDebugCatalogs;
3543
3544    if (level <= 0)
3545        xmlDebugCatalogs = 0;
3546    else
3547	xmlDebugCatalogs = level;
3548    return(ret);
3549}
3550
3551/************************************************************************
3552 *									*
3553 *   Minimal interfaces used for per-document catalogs by the parser	*
3554 *									*
3555 ************************************************************************/
3556
3557/**
3558 * xmlCatalogFreeLocal:
3559 * @catalogs:  a document's list of catalogs
3560 *
3561 * Free up the memory associated to the catalog list
3562 */
3563void
3564xmlCatalogFreeLocal(void *catalogs) {
3565    xmlCatalogEntryPtr catal;
3566
3567    if (!xmlCatalogInitialized)
3568	xmlInitializeCatalog();
3569
3570    catal = (xmlCatalogEntryPtr) catalogs;
3571    if (catal != NULL)
3572	xmlFreeCatalogEntryList(catal);
3573}
3574
3575
3576/**
3577 * xmlCatalogAddLocal:
3578 * @catalogs:  a document's list of catalogs
3579 * @URL:  the URL to a new local catalog
3580 *
3581 * Add the new entry to the catalog list
3582 *
3583 * Returns the updated list
3584 */
3585void *
3586xmlCatalogAddLocal(void *catalogs, const xmlChar *URL) {
3587    xmlCatalogEntryPtr catal, add;
3588
3589    if (!xmlCatalogInitialized)
3590	xmlInitializeCatalog();
3591
3592    if (URL == NULL)
3593	return(catalogs);
3594
3595    if (xmlDebugCatalogs)
3596	xmlGenericError(xmlGenericErrorContext,
3597		"Adding document catalog %s\n", URL);
3598
3599    add = xmlNewCatalogEntry(XML_CATA_CATALOG, NULL, URL, NULL,
3600	                     xmlCatalogDefaultPrefer, NULL);
3601    if (add == NULL)
3602	return(catalogs);
3603
3604    catal = (xmlCatalogEntryPtr) catalogs;
3605    if (catal == NULL)
3606	return((void *) add);
3607
3608    while (catal->next != NULL)
3609	catal = catal->next;
3610    catal->next = add;
3611    return(catalogs);
3612}
3613
3614/**
3615 * xmlCatalogLocalResolve:
3616 * @catalogs:  a document's list of catalogs
3617 * @pubID:  the public ID string
3618 * @sysID:  the system ID string
3619 *
3620 * Do a complete resolution lookup of an External Identifier using a
3621 * document's private catalog list
3622 *
3623 * Returns the URI of the resource or NULL if not found, it must be freed
3624 *      by the caller.
3625 */
3626xmlChar *
3627xmlCatalogLocalResolve(void *catalogs, const xmlChar *pubID,
3628	               const xmlChar *sysID) {
3629    xmlCatalogEntryPtr catal;
3630    xmlChar *ret;
3631
3632    if (!xmlCatalogInitialized)
3633	xmlInitializeCatalog();
3634
3635    if ((pubID == NULL) && (sysID == NULL))
3636	return(NULL);
3637
3638    if (xmlDebugCatalogs) {
3639        if ((pubID != NULL) && (sysID != NULL)) {
3640            xmlGenericError(xmlGenericErrorContext,
3641                            "Local Resolve: pubID %s sysID %s\n", pubID, sysID);
3642        } else if (pubID != NULL) {
3643            xmlGenericError(xmlGenericErrorContext,
3644                            "Local Resolve: pubID %s\n", pubID);
3645        } else {
3646            xmlGenericError(xmlGenericErrorContext,
3647                            "Local Resolve: sysID %s\n", sysID);
3648        }
3649    }
3650
3651    catal = (xmlCatalogEntryPtr) catalogs;
3652    if (catal == NULL)
3653	return(NULL);
3654    ret = xmlCatalogListXMLResolve(catal, pubID, sysID);
3655    if ((ret != NULL) && (ret != XML_CATAL_BREAK))
3656	return(ret);
3657    return(NULL);
3658}
3659
3660/**
3661 * xmlCatalogLocalResolveURI:
3662 * @catalogs:  a document's list of catalogs
3663 * @URI:  the URI
3664 *
3665 * Do a complete resolution lookup of an URI using a
3666 * document's private catalog list
3667 *
3668 * Returns the URI of the resource or NULL if not found, it must be freed
3669 *      by the caller.
3670 */
3671xmlChar *
3672xmlCatalogLocalResolveURI(void *catalogs, const xmlChar *URI) {
3673    xmlCatalogEntryPtr catal;
3674    xmlChar *ret;
3675
3676    if (!xmlCatalogInitialized)
3677	xmlInitializeCatalog();
3678
3679    if (URI == NULL)
3680	return(NULL);
3681
3682    if (xmlDebugCatalogs)
3683	xmlGenericError(xmlGenericErrorContext,
3684		"Resolve URI %s\n", URI);
3685
3686    catal = (xmlCatalogEntryPtr) catalogs;
3687    if (catal == NULL)
3688	return(NULL);
3689    ret = xmlCatalogListXMLResolveURI(catal, URI);
3690    if ((ret != NULL) && (ret != XML_CATAL_BREAK))
3691	return(ret);
3692    return(NULL);
3693}
3694
3695/************************************************************************
3696 *									*
3697 *			Deprecated interfaces				*
3698 *									*
3699 ************************************************************************/
3700/**
3701 * xmlCatalogGetSystem:
3702 * @sysID:  the system ID string
3703 *
3704 * Try to lookup the catalog reference associated to a system ID
3705 * DEPRECATED, use xmlCatalogResolveSystem()
3706 *
3707 * Returns the resource if found or NULL otherwise.
3708 */
3709const xmlChar *
3710xmlCatalogGetSystem(const xmlChar *sysID) {
3711    xmlChar *ret;
3712    static xmlChar result[1000];
3713    static int msg = 0;
3714
3715    if (!xmlCatalogInitialized)
3716	xmlInitializeCatalog();
3717
3718    if (msg == 0) {
3719	xmlGenericError(xmlGenericErrorContext,
3720		"Use of deprecated xmlCatalogGetSystem() call\n");
3721	msg++;
3722    }
3723
3724    if (sysID == NULL)
3725	return(NULL);
3726
3727    /*
3728     * Check first the XML catalogs
3729     */
3730    if (xmlDefaultCatalog != NULL) {
3731	ret = xmlCatalogListXMLResolve(xmlDefaultCatalog->xml, NULL, sysID);
3732	if ((ret != NULL) && (ret != XML_CATAL_BREAK)) {
3733	    snprintf((char *) result, sizeof(result) - 1, "%s", (char *) ret);
3734	    result[sizeof(result) - 1] = 0;
3735	    return(result);
3736	}
3737    }
3738
3739    if (xmlDefaultCatalog != NULL)
3740	return(xmlCatalogGetSGMLSystem(xmlDefaultCatalog->sgml, sysID));
3741    return(NULL);
3742}
3743
3744/**
3745 * xmlCatalogGetPublic:
3746 * @pubID:  the public ID string
3747 *
3748 * Try to lookup the catalog reference associated to a public ID
3749 * DEPRECATED, use xmlCatalogResolvePublic()
3750 *
3751 * Returns the resource if found or NULL otherwise.
3752 */
3753const xmlChar *
3754xmlCatalogGetPublic(const xmlChar *pubID) {
3755    xmlChar *ret;
3756    static xmlChar result[1000];
3757    static int msg = 0;
3758
3759    if (!xmlCatalogInitialized)
3760	xmlInitializeCatalog();
3761
3762    if (msg == 0) {
3763	xmlGenericError(xmlGenericErrorContext,
3764		"Use of deprecated xmlCatalogGetPublic() call\n");
3765	msg++;
3766    }
3767
3768    if (pubID == NULL)
3769	return(NULL);
3770
3771    /*
3772     * Check first the XML catalogs
3773     */
3774    if (xmlDefaultCatalog != NULL) {
3775	ret = xmlCatalogListXMLResolve(xmlDefaultCatalog->xml, pubID, NULL);
3776	if ((ret != NULL) && (ret != XML_CATAL_BREAK)) {
3777	    snprintf((char *) result, sizeof(result) - 1, "%s", (char *) ret);
3778	    result[sizeof(result) - 1] = 0;
3779	    return(result);
3780	}
3781    }
3782
3783    if (xmlDefaultCatalog != NULL)
3784	return(xmlCatalogGetSGMLPublic(xmlDefaultCatalog->sgml, pubID));
3785    return(NULL);
3786}
3787
3788#endif /* LIBXML_CATALOG_ENABLED */
3789