source: trunk/src/helpers/xml.c@ 114

Last change on this file since 114 was 113, checked in by umoeller, 24 years ago

Misc updates.

  • Property svn:eol-style set to CRLF
  • Property svn:keywords set to Author Date Id Revision
File size: 110.8 KB
Line 
1
2/*
3 *@@sourcefile xml.c:
4 * XML document handling.
5 *
6 * XML support in the XWorkplace Helpers is broken into two
7 * layers:
8 *
9 * -- The bottom layer is implemented by the @expat parser,
10 * which I have ported and hacked to the xwphelpers.
11 *
12 * See xmlparse.c for an introduction.
13 *
14 * -- Because expat requires so many callbacks and is non-validating,
15 * I have added a top layer above the expat library
16 * which is vaguely modelled after the Document Object Model
17 * (DOM) standardized by the W3C. That's this file.
18 *
19 * This top layer allows you to do two things VERY EASILY:
20 *
21 * 1) Parse an XML document (which uses expat internally)
22 * and build a DOM tree from that. See xmlCreateDOM.
23 *
24 * 2) Create a DOM tree in memory and write an XML
25 * document from that. See xmlCreateDocument.
26 *
27 * <B>XML</B>
28 *
29 * In order to understand XML myself, I have written a couple of
30 * glossary entries for the complex XML terminology. See @XML
31 * for a start.
32 *
33 * <B>Document Object Model (DOM)</B>
34 *
35 * See @DOM for a general introduction.
36 *
37 * DOM really calls for object oriented programming so the various
38 * structs can inherit from each other. Since this implementation
39 * was supposed to be a C-only interface, we cannot implement
40 * inheritance at the language level. Instead, each XML document
41 * is broken up into a tree of node structures only (see _DOMNODE),
42 * each of which has a special type. The W3C DOM allows this
43 * (and calls this the "flattened" view, as opposed to the
44 * "inheritance view").
45 *
46 * The W3C DOM specification prescribes tons of methods, which I
47 * really had no use for, so I didn't implement them. This implementation
48 * is only a DOM insofar as it uses nodes which represent @documents,
49 * @elements, @attributes, @comments, and @processing_instructions.
50 *
51 * Most notably, there are the following differences:
52 *
53 * -- External entities don't work yet. As a result, DOCTYPE's
54 * only make sense if the entire DTD is in the same document
55 * (internal subset).
56 *
57 * -- Not all node types are implemented. See _DOMNODE for
58 * the supported types.
59 *
60 * -- Only a subset of the standardized methods is implemented,
61 * and they are called differently to adhere to the xwphelpers
62 * conventions.
63 *
64 * -- DOM uses UTF-16 for its DOMString type. @expat gives UTF-8
65 * strings to all the handlers though, so all data in the DOM nodes
66 * is UTF-8 encoded. This still needs to be fixed.
67 *
68 * -- DOM defines the DOMException class. This isn't supported in C.
69 * Instead, we use special error codes which add to the standard
70 * OS/2 error codes (APIRET). All our error codes are >= 40000
71 * to avoid conflicts.
72 *
73 * It shouldn't be too difficult to write a C++ encapsulation
74 * of this though which fully implements all the DOM methods.
75 *
76 * However, we do implement node management as in the standard.
77 * See xmlCreateDomNode and xmlDeleteNode.
78 *
79 * The main entry point into this is xmlCreateDOM. See remarks
80 * there for how this will be typically used.
81 *
82 * <B>Validation</B>
83 *
84 * @expat doesn't check XML documents for whether they are @valid.
85 * In other words, expat is a non-validating XML processor.
86 *
87 * By contrast, this pseudo-DOM implementation can validate to
88 * a certain extent.
89 *
90 * -- If you pass DF_PARSEDTD to xmlCreateDOM, the DTD will be
91 * parsed and the document will be validated against it.
92 * Validation is working as far as elements and attributes
93 * are checked for proper nesting. However, we cannot fully
94 * check for proper ordering etc. in (children) mode of
95 * @element_declarations. This will only check for whether
96 * elements may appear in another element at all -- not for
97 * the correct order.
98 *
99 * -- Otherwise the @DTD entries will not be stored in the DOM
100 * nodes, and no validation occurs. Still, if a DTD exists,
101 * @expat will insert attributes that have a default value
102 * in their @attribute declaraion and have not been specified.
103 *
104 *@@header "helpers\xml.h"
105 *@@added V0.9.6 (2000-10-29) [umoeller]
106 */
107
108/*
109 * Copyright (C) 2000-2001 Ulrich M”ller.
110 * This file is part of the "XWorkplace helpers" source package.
111 * This is free software; you can redistribute it and/or modify
112 * it under the terms of the GNU General Public License as published
113 * by the Free Software Foundation, in version 2 as it comes in the
114 * "COPYING" file of the XWorkplace main distribution.
115 * This program is distributed in the hope that it will be useful,
116 * but WITHOUT ANY WARRANTY; without even the implied warranty of
117 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
118 * GNU General Public License for more details.
119 */
120
121#define OS2EMX_PLAIN_CHAR
122 // this is needed for "os2emx.h"; if this is defined,
123 // emx will define PSZ as _signed_ char, otherwise
124 // as unsigned char
125
126#define INCL_DOSERRORS
127#include <os2.h>
128
129#include <stdlib.h>
130#include <string.h>
131#include <stdio.h>
132
133#include "setup.h" // code generation and debugging options
134
135#include "expat\expat.h"
136
137#include "helpers\linklist.h"
138#include "helpers\standards.h"
139#include "helpers\stringh.h"
140#include "helpers\tree.h"
141#include "helpers\xstring.h"
142#include "helpers\xml.h"
143
144#pragma hdrstop
145
146/*
147 *@@category: Helpers\C helpers\XML
148 * see xml.c.
149 */
150
151/*
152 *@@category: Helpers\C helpers\XML\Document Object Model (DOM)
153 * see xml.c.
154 */
155
156/* ******************************************************************
157 *
158 * Error handling
159 *
160 ********************************************************************/
161
162/*
163 *@@ xmlDescribeError:
164 * returns a string describing the error corresponding to code.
165 * The code should be one of the enums that can be returned from
166 * XML_GetErrorCode.
167 *
168 *@@changed V0.9.9 (2001-02-14) [umoeller]: adjusted for new error codes
169 *@@changed V0.9.9 (2001-02-16) [umoeller]: moved this here from xmlparse.c
170 */
171
172const char* xmlDescribeError(int code)
173{
174 switch (code)
175 {
176 // start of expat (parser) errors
177 case ERROR_EXPAT_NO_MEMORY:
178 return ("Out of memory");
179
180 case ERROR_EXPAT_SYNTAX:
181 return ("Syntax error");
182 case ERROR_EXPAT_NO_ELEMENTS:
183 return ("No element found");
184 case ERROR_EXPAT_INVALID_TOKEN:
185 return ("Not well-formed (invalid token)");
186 case ERROR_EXPAT_UNCLOSED_TOKEN:
187 return ("Unclosed token");
188 case ERROR_EXPAT_PARTIAL_CHAR:
189 return ("Unclosed token");
190 case ERROR_EXPAT_TAG_MISMATCH:
191 return ("Mismatched tag");
192 case ERROR_EXPAT_DUPLICATE_ATTRIBUTE:
193 return ("Duplicate attribute");
194 case ERROR_EXPAT_JUNK_AFTER_DOC_ELEMENT:
195 return ("Junk after root element");
196 case ERROR_EXPAT_PARAM_ENTITY_REF:
197 return ("Illegal parameter entity reference");
198 case ERROR_EXPAT_UNDEFINED_ENTITY:
199 return ("Undefined entity");
200 case ERROR_EXPAT_RECURSIVE_ENTITY_REF:
201 return ("Recursive entity reference");
202 case ERROR_EXPAT_ASYNC_ENTITY:
203 return ("Asynchronous entity");
204 case ERROR_EXPAT_BAD_CHAR_REF:
205 return ("Reference to invalid character number");
206 case ERROR_EXPAT_BINARY_ENTITY_REF:
207 return ("Reference to binary entity");
208 case ERROR_EXPAT_ATTRIBUTE_EXTERNAL_ENTITY_REF:
209 return ("Reference to external entity in attribute");
210 case ERROR_EXPAT_MISPLACED_XML_PI:
211 return ("XML processing instruction not at start of external entity");
212 case ERROR_EXPAT_UNKNOWN_ENCODING:
213 return ("Unknown encoding");
214 case ERROR_EXPAT_INCORRECT_ENCODING:
215 return ("Encoding specified in XML declaration is incorrect");
216 case ERROR_EXPAT_UNCLOSED_CDATA_SECTION:
217 return ("Unclosed CDATA section");
218 case ERROR_EXPAT_EXTERNAL_ENTITY_HANDLING:
219 return ("Error in processing external entity reference");
220 case ERROR_EXPAT_NOT_STANDALONE:
221 return ("Document is not standalone");
222 case ERROR_EXPAT_UNEXPECTED_STATE:
223 return ("Unexpected parser state - please send a bug report");
224 // end of expat (parser) errors
225
226 // start of validation errors
227
228 case ERROR_DOM_UNDECLARED_ELEMENT:
229 return ("Element has not been declared");
230 case ERROR_DOM_ROOT_ELEMENT_MISNAMED:
231 return ("Root element name does not match DOCTYPE name");
232 case ERROR_DOM_INVALID_ROOT_ELEMENT:
233 return ("Invalid or duplicate root element");
234
235 case ERROR_DOM_INVALID_SUBELEMENT:
236 return ("Invalid sub-element in parent element");
237 case ERROR_DOM_DUPLICATE_ELEMENT_DECL:
238 return ("Duplicate element declaration");
239 case ERROR_DOM_DUPLICATE_ATTRIBUTE_DECL:
240 return ("Duplicate attribute declaration");
241 case ERROR_DOM_UNDECLARED_ATTRIBUTE:
242 return ("Undeclared attribute in element");
243 case ERROR_ELEMENT_CANNOT_HAVE_CONTENT:
244 return ("Element cannot have content");
245 case ERROR_DOM_INVALID_ATTRIB_VALUE:
246 return ("Invalid attribute value");
247 case ERROR_DOM_REQUIRED_ATTRIBUTE_MISSING:
248 return ("Required attribute is missing");
249 case ERROR_DOM_SUBELEMENT_IN_EMPTY_ELEMENT:
250 return ("Subelement in empty element");
251
252 case ERROR_DOM_PARSING:
253 return ("Parsing error");
254 case ERROR_DOM_VALIDITY:
255 return ("Validity error");
256
257 case ERROR_DOM_NODETYPE_NOT_SUPPORTED:
258 return ("DOM node type not supported");
259 case ERROR_DOM_NO_DOCUMENT:
260 return ("No DOM document");
261 case ERROR_DOM_NO_ELEMENT:
262 return ("No DOM element");
263 case ERROR_DOM_DUPLICATE_DOCTYPE:
264 return ("Duplicate doctype");
265 case ERROR_DOM_DOCTYPE_ROOT_NAMES_MISMATCH:
266 return ("Root element doesn't match doctype name");
267 case ERROR_DOM_INTEGRITY:
268 return ("DOM integrity error");
269 case ERROR_DOM_DUPLICATE_ATTRIBUTE:
270 return ("Duplicate attribute");
271
272 case ERROR_DOM_VALIDATE_INVALID_ELEMENT:
273 return ("Validation error: Undeclared element name");
274 case ERROR_DOM_ELEMENT_DECL_OUTSIDE_DOCTYPE:
275 return ("Element declaration outside doctype");
276 case ERROR_DOM_ATTLIST_DECL_OUTSIDE_DOCTYPE:
277 return ("Attlist declaration outside doctype");
278 }
279
280 return NULL;
281}
282
283/*
284 *@@ xmlSetError:
285 * sets the DOM's error state and stores error information
286 * and parser position.
287 *
288 *@@added V0.9.9 (2001-02-16) [umoeller]
289 */
290
291VOID xmlSetError(PXMLDOM pDom,
292 APIRET arc,
293 const char *pcszFailing,
294 BOOL fValidityError) // in: if TRUE, this is a validation error;
295 // if FALSE, this is a parser error
296{
297 pDom->arcDOM = arc;
298 pDom->pcszErrorDescription = xmlDescribeError(pDom->arcDOM);
299 pDom->ulErrorLine = XML_GetCurrentLineNumber(pDom->pParser);
300 pDom->ulErrorColumn = XML_GetCurrentColumnNumber(pDom->pParser);
301
302 if (pcszFailing)
303 {
304 if (!pDom->pxstrFailingNode)
305 pDom->pxstrFailingNode = xstrCreate(0);
306
307 xstrcpy(pDom->pxstrFailingNode, pcszFailing, 0);
308 }
309
310 if (fValidityError)
311 pDom->fInvalid = TRUE;
312}
313
314/* ******************************************************************
315 *
316 * Most basic node management
317 *
318 ********************************************************************/
319
320/*
321 *@@ CompareXStrings:
322 * tree comparison func for NodeBases.
323 * This works for all trees which contain structures
324 * whose first item is a _NODEBASE because NODEBASE's first
325 * member is a TREE.
326 *
327 * Used in two places:
328 *
329 * -- to insert _CMELEMENTDECLNODE nodes into
330 * _DOMDOCTYPENODE.ElementDeclsTree;
331 *
332 * -- to insert _CMELEMENTPARTICLE nodes into
333 * _CMELEMENTDECLNODE.ElementNamesTree.
334 *
335 *@@added V0.9.9 (2001-02-16) [umoeller]
336 *@@changed V0.9.14 (2001-08-09) [umoeller]: fixed map bug which caused the whole XML stuff to fail
337 */
338
339int TREEENTRY CompareXStrings(ULONG ul1,
340 ULONG ul2)
341{
342 return (strhcmp(((PXSTRING)ul1)->psz,
343 ((PXSTRING)ul2)->psz));
344}
345
346/*
347 *@@ xmlCreateNodeBase:
348 * creates a new NODEBASE node.
349 *
350 * Gets called from xmlCreateDomNode also to create
351 * a DOMNODE, since that in turn has a NODEBASE.
352 *
353 *@@added V0.9.9 (2001-02-16) [umoeller]
354 */
355
356APIRET xmlCreateNodeBase(NODEBASETYPE ulNodeType, // in: node type
357 ULONG cb, // in: size of struct
358 const char *pcszNodeName, // in: node name or NULL
359 ULONG ulNodeNameLength, // in: node name length
360 // or 0 to run strlen(pcszNodeName)
361 PNODEBASE *ppNew) // out: new node
362{
363 APIRET arc = NO_ERROR;
364 PNODEBASE pNewNode = (PNODEBASE)malloc(cb);
365
366 if (!pNewNode)
367 arc = ERROR_NOT_ENOUGH_MEMORY;
368 else
369 {
370 memset(pNewNode, 0, cb);
371 pNewNode->ulNodeType = ulNodeType;
372
373 pNewNode->Tree.ulKey = (ULONG)&pNewNode->strNodeName;
374
375 xstrInit(&pNewNode->strNodeName, 0);
376 if (pcszNodeName)
377 {
378 xstrcpy(&pNewNode->strNodeName,
379 pcszNodeName,
380 ulNodeNameLength);
381 }
382
383 *ppNew = pNewNode;
384 }
385
386 return (arc);
387}
388
389/*
390 *@@ xmlDeleteNode:
391 * deletes a NODEBASE and frees memory that was
392 * associated with its members.
393 *
394 * After calling this, pNode is no longer valid.
395 *
396 * If you invoke this on a DOCUMENT node, the
397 * entire DOM tree will get deleted recursively.
398 *
399 *@@added V0.9.9 (2001-02-16) [umoeller]
400 *@@changed V0.9.14 (2001-08-09) [umoeller]: fixed crash on string delete
401 */
402
403VOID xmlDeleteNode(PNODEBASE pNode)
404{
405 if (pNode)
406 {
407 PLISTNODE pNodeThis;
408 PDOMNODE pDomNode = NULL;
409
410 LINKLIST llDeleteNodes; // list that nodes to be deleted
411 // can be appended to
412 PLISTNODE pDelNode;
413 lstInit(&llDeleteNodes, FALSE);
414
415 // now handle special types and their allocations
416 switch (pNode->ulNodeType)
417 {
418 case DOMNODE_ELEMENT:
419 {
420 PDOMNODE pAttrib;
421
422 pDomNode = (PDOMNODE)pNode;
423
424 // delete all attribute nodes
425 pAttrib = (PDOMNODE)treeFirst(pDomNode->AttributesMap);
426 while (pAttrib)
427 {
428 // call delete recursively
429 PDOMNODE pNext = (PDOMNODE)treeNext((TREE*)pAttrib);
430 xmlDeleteNode((PNODEBASE)pAttrib);
431 // this will remove pAttrib from pNode's attrib
432 // tree and rebalance the tree
433 pAttrib = pNext;
434 }
435 break; }
436
437 case DOMNODE_ATTRIBUTE:
438 case DOMNODE_TEXT:
439 case DOMNODE_PROCESSING_INSTRUCTION:
440 case DOMNODE_COMMENT:
441 pDomNode = (PDOMNODE)pNode;
442 break;
443
444 case DOMNODE_DOCUMENT:
445 if (((PDOMDOCUMENTNODE)pNode)->pDocType)
446 xmlDeleteNode((PNODEBASE)((PDOMDOCUMENTNODE)pNode)->pDocType);
447 pDomNode = (PDOMNODE)pNode;
448 break;
449
450 case DOMNODE_DOCUMENT_TYPE:
451 {
452 PDOMDOCTYPENODE pDocType = (PDOMDOCTYPENODE)pNode;
453 PCMELEMENTDECLNODE pElDecl;
454 PCMATTRIBUTEDECLBASE pAttrDeclBase;
455
456 pDomNode = (PDOMNODE)pNode;
457
458 pElDecl = (PCMELEMENTDECLNODE)treeFirst(pDocType->ElementDeclsTree);
459 while (pElDecl)
460 {
461 lstAppendItem(&llDeleteNodes, pElDecl);
462 pElDecl = (PCMELEMENTDECLNODE)treeNext((TREE*)pElDecl);
463 }
464
465 pAttrDeclBase = (PCMATTRIBUTEDECLBASE)treeFirst(pDocType->AttribDeclBasesTree);
466 while (pAttrDeclBase)
467 {
468 lstAppendItem(&llDeleteNodes, pAttrDeclBase);
469 pAttrDeclBase = (PCMATTRIBUTEDECLBASE)treeNext((TREE*)pAttrDeclBase);
470 }
471
472 xstrClear(&pDocType->strPublicID);
473 xstrClear(&pDocType->strSystemID);
474 break; }
475
476 case ELEMENTPARTICLE_EMPTY:
477 case ELEMENTPARTICLE_ANY:
478 case ELEMENTPARTICLE_MIXED:
479 case ELEMENTPARTICLE_CHOICE:
480 case ELEMENTPARTICLE_SEQ:
481 case ELEMENTPARTICLE_NAME:
482 {
483 PCMELEMENTPARTICLE pp = (PCMELEMENTPARTICLE)pNode;
484 if (pp->pllSubNodes)
485 {
486 pDelNode = lstQueryFirstNode(pp->pllSubNodes);
487 while (pDelNode)
488 {
489 PCMELEMENTPARTICLE
490 pParticle = (PCMELEMENTPARTICLE)pDelNode->pItemData;
491 xmlDeleteNode((PNODEBASE)pParticle);
492 // treeDelete(pp-> // @@todo
493 pDelNode = pDelNode->pNext;
494 }
495 }
496 break; }
497
498 // case ATTRIBUTE_DECLARATION_ENUM: // this is a plain NODEBASE
499
500 case ATTRIBUTE_DECLARATION:
501 {
502 // PCMATTRIBUTEDECL pDecl = (PCMATTRIBUTEDECL)pNode;
503 break; }
504
505 case ATTRIBUTE_DECLARATION_BASE:
506 break;
507 }
508
509 if (pDomNode)
510 {
511 // recurse into child nodes
512 while (pNodeThis = lstQueryFirstNode(&pDomNode->llChildren))
513 // recurse!!
514 xmlDeleteNode((PNODEBASE)(pNodeThis->pItemData));
515 // this updates llChildren
516
517 if (pDomNode->pParentNode)
518 {
519 // node has a parent:
520 if (pNode->ulNodeType == DOMNODE_ATTRIBUTE)
521 // this is an attribute:
522 // remove from parent's attributes map
523 treeDelete(&pDomNode->pParentNode->AttributesMap,
524 NULL,
525 (TREE*)pNode);
526 else
527 // remove this node from the parent's list
528 // of child nodes before deleting this node
529 lstRemoveItem(&pDomNode->pParentNode->llChildren,
530 pNode);
531
532 pDomNode->pParentNode = NULL;
533 }
534
535 xstrFree(&pDomNode->pstrNodeValue);
536 lstClear(&pDomNode->llChildren);
537 }
538
539 pDelNode = lstQueryFirstNode(&llDeleteNodes);
540 while (pDelNode)
541 {
542 PNODEBASE pNodeBase = (PNODEBASE)pDelNode->pItemData;
543 xmlDeleteNode(pNodeBase);
544 pDelNode = pDelNode->pNext;
545 }
546
547 lstClear(&llDeleteNodes);
548
549 xstrClear(&pNode->strNodeName);
550 free(pNode);
551 }
552}
553
554/*
555 *@@ xmlCreateDomNode:
556 * creates a new DOMNODE with the specified
557 * type and parent. Other than that, the
558 * node fields are zeroed.
559 *
560 * If pParentNode is specified (which is required,
561 * unless you are creating a document node),
562 * its children list is automatically updated
563 * (unless this is an attribute node, which updates
564 * the attributes map).
565 *
566 * This returns the following errors:
567 *
568 * -- ERROR_NOT_ENOUGH_MEMORY
569 *
570 * -- ERROR_DOM_NOT_SUPPORTED: invalid ulNodeType
571 * specified.
572 *
573 * -- ERROR_DOM_WRONG_DOCUMENT: cannot find the
574 * document for this node. This happens if you do
575 * not have a document node at the root of your tree.
576 */
577
578APIRET xmlCreateDomNode(PDOMNODE pParentNode, // in: parent node or NULL if root
579 NODEBASETYPE ulNodeType, // in: DOMNODE_* type
580 const char *pcszNodeName, // in: node name or NULL
581 ULONG ulNodeNameLength, // in: node name length
582 // or 0 to run strlen(pcszNodeName)
583 PDOMNODE *ppNew) // out: new node
584{
585 PDOMNODE pNewNode = NULL;
586 APIRET arc = NO_ERROR;
587
588 ULONG cb = 0;
589
590 switch (ulNodeType)
591 {
592 case DOMNODE_DOCUMENT:
593 cb = sizeof(DOMDOCUMENTNODE);
594 break;
595
596 case DOMNODE_DOCUMENT_TYPE:
597 cb = sizeof(DOMDOCTYPENODE);
598 break;
599
600 default:
601 cb = sizeof(DOMNODE);
602 break;
603 }
604
605 if (!(arc = xmlCreateNodeBase(ulNodeType,
606 cb,
607 pcszNodeName,
608 ulNodeNameLength,
609 (PNODEBASE*)&pNewNode)))
610 {
611 pNewNode->pParentNode = pParentNode;
612
613 if (pParentNode)
614 {
615 // parent specified:
616 // check if this is an attribute
617 if (ulNodeType == DOMNODE_ATTRIBUTE)
618 {
619 // attribute:
620 // add to parent's attributes list
621 if (treeInsert(&pParentNode->AttributesMap,
622 NULL,
623 &pNewNode->NodeBase.Tree,
624 CompareXStrings))
625 arc = ERROR_DOM_DUPLICATE_ATTRIBUTE;
626 // shouldn't happen, because expat takes care of this
627 }
628 else
629 // append this new node to the parent's
630 // list of child nodes
631 lstAppendItem(&pParentNode->llChildren,
632 pNewNode);
633
634 if (!arc)
635 {
636 // set document pointer...
637 // if the parent node has a document pointer,
638 // we can copy that
639 if (pParentNode->pDocumentNode)
640 pNewNode->pDocumentNode = pParentNode->pDocumentNode;
641 else
642 // parent has no document pointer: then it is probably
643 // the document itself... check
644 if (pParentNode->NodeBase.ulNodeType == DOMNODE_DOCUMENT)
645 pNewNode->pDocumentNode = pParentNode;
646 else
647 arc = ERROR_DOM_NO_DOCUMENT;
648 }
649 }
650
651 lstInit(&pNewNode->llChildren, FALSE);
652 treeInit(&pNewNode->AttributesMap, NULL);
653 }
654
655 if (!arc)
656 *ppNew = pNewNode;
657 else
658 if (pNewNode)
659 free(pNewNode);
660
661 return (arc);
662}
663
664/* ******************************************************************
665 *
666 * Specific DOM node constructors
667 *
668 ********************************************************************/
669
670/*
671 *@@ xmlCreateElementNode:
672 * creates a new element node with the specified name.
673 *
674 *@@added V0.9.9 (2001-02-14) [umoeller]
675 */
676
677APIRET xmlCreateElementNode(PDOMNODE pParent, // in: parent node (either document or element)
678 const char *pcszElement, // in: element name (null-terminated)
679 PDOMNODE *ppNew)
680{
681 PDOMNODE pNew = NULL;
682 APIRET arc = xmlCreateDomNode(pParent,
683 DOMNODE_ELEMENT,
684 pcszElement,
685 0,
686 &pNew);
687
688 if (arc == NO_ERROR)
689 *ppNew = pNew;
690
691 return (arc);
692}
693
694/*
695 *@@ xmlCreateAttributeNode:
696 * creates a new attribute node with the specified data.
697 *
698 * NOTE: Attributes have no "parent" node, technically.
699 * They are added to a special, separate list in @DOM_ELEMENT
700 * nodes.
701 *
702 * This returns the following errors:
703 *
704 * -- Error codes from xmlCreateDomNode.
705 *
706 * -- ERROR_DOM_NO_ELEMENT: pElement is invalid or does
707 * not point to an @DOM_ELEMENT node.
708 *
709 *@@added V0.9.9 (2001-02-14) [umoeller]
710 */
711
712APIRET xmlCreateAttributeNode(PDOMNODE pElement, // in: element node
713 const char *pcszName, // in: attribute name (null-terminated)
714 const char *pcszValue, // in: attribute value (null-terminated)
715 PDOMNODE *ppNew)
716{
717 APIRET arc = NO_ERROR;
718
719 if ( !pElement
720 || pElement->NodeBase.ulNodeType != DOMNODE_ELEMENT
721 )
722 arc = ERROR_DOM_NO_ELEMENT;
723 else
724 {
725 PDOMNODE pNew = NULL;
726 arc = xmlCreateDomNode(pElement, // this takes care of adding to the list
727 DOMNODE_ATTRIBUTE,
728 pcszName,
729 0,
730 &pNew);
731 if (arc == NO_ERROR)
732 {
733 pNew->pstrNodeValue = xstrCreate(0);
734 xstrcpy(pNew->pstrNodeValue, pcszValue, 0);
735
736 *ppNew = pNew;
737 }
738 }
739
740 return (arc);
741}
742
743/*
744 *@@ xmlCreateTextNode:
745 * creates a new text node with the specified content.
746 *
747 * Note: This differs from the createText method
748 * as specified by DOM, which has no ulLength parameter.
749 * We need this for speed with @expat though.
750 *
751 *@@added V0.9.9 (2001-02-14) [umoeller]
752 */
753
754APIRET xmlCreateTextNode(PDOMNODE pParent, // in: parent element node
755 const char *pcszText, // in: ptr to start of text
756 ULONG ulLength, // in: length of *pcszText
757 PDOMNODE *ppNew)
758{
759 PDOMNODE pNew = NULL;
760 APIRET arc = xmlCreateDomNode(pParent,
761 DOMNODE_TEXT,
762 NULL,
763 0,
764 &pNew);
765 if (arc == NO_ERROR)
766 {
767 PSZ pszNodeValue = (PSZ)malloc(ulLength + 1);
768 if (!pszNodeValue)
769 {
770 arc = ERROR_NOT_ENOUGH_MEMORY;
771 xmlDeleteNode((PNODEBASE)pNew);
772 }
773 else
774 {
775 memcpy(pszNodeValue, pcszText, ulLength);
776 pszNodeValue[ulLength] = '\0';
777 pNew->pstrNodeValue = xstrCreate(0);
778 xstrset(pNew->pstrNodeValue, pszNodeValue);
779
780 *ppNew = pNew;
781 }
782 }
783
784 return (arc);
785}
786
787/*
788 *@@ xmlCreateCommentNode:
789 * creates a new comment node with the specified
790 * content.
791 *
792 *@@added V0.9.9 (2001-02-14) [umoeller]
793 */
794
795APIRET xmlCreateCommentNode(PDOMNODE pParent, // in: parent element node
796 const char *pcszText, // in: comment (null-terminated)
797 PDOMNODE *ppNew)
798{
799 PDOMNODE pNew = NULL;
800 APIRET arc = xmlCreateDomNode(pParent,
801 DOMNODE_COMMENT,
802 NULL,
803 0,
804 &pNew);
805 if (arc == NO_ERROR)
806 {
807 pNew->pstrNodeValue = xstrCreate(0);
808 xstrcpy(pNew->pstrNodeValue, pcszText, 0);
809 *ppNew = pNew;
810 }
811
812 return (arc);
813}
814
815/*
816 *@@ xmlCreatePINode:
817 * creates a new processing instruction node with the
818 * specified data.
819 *
820 *@@added V0.9.9 (2001-02-14) [umoeller]
821 */
822
823APIRET xmlCreatePINode(PDOMNODE pParent, // in: parent element node
824 const char *pcszTarget, // in: PI target (null-terminated)
825 const char *pcszData, // in: PI data (null-terminated)
826 PDOMNODE *ppNew)
827{
828 PDOMNODE pNew = NULL;
829 APIRET arc = xmlCreateDomNode(pParent,
830 DOMNODE_PROCESSING_INSTRUCTION,
831 pcszTarget,
832 0,
833 &pNew);
834 if (arc == NO_ERROR)
835 {
836 pNew->pstrNodeValue = xstrCreate(0);
837 xstrcpy(pNew->pstrNodeValue, pcszData, 0);
838
839 *ppNew = pNew;
840 }
841
842 return (arc);
843}
844
845/*
846 *@@ xmlCreateDocumentTypeNode:
847 * creates a new document type node with the
848 * specified data.
849 *
850 *@@added V0.9.9 (2001-02-14) [umoeller]
851 */
852
853APIRET xmlCreateDocumentTypeNode(PDOMDOCUMENTNODE pDocumentNode, // in: document node
854 const char *pcszDoctypeName,
855 const char *pcszSysid,
856 const char *pcszPubid,
857 int fHasInternalSubset,
858 PDOMDOCTYPENODE *ppNew)
859{
860 APIRET arc = NO_ERROR;
861
862 if (pDocumentNode->pDocType)
863 // we already have a doctype:
864 arc = ERROR_DOM_DUPLICATE_DOCTYPE;
865 else
866 {
867 // create doctype node
868 PDOMDOCTYPENODE pNew = NULL;
869 arc = xmlCreateDomNode((PDOMNODE)pDocumentNode,
870 DOMNODE_DOCUMENT_TYPE,
871 pcszDoctypeName,
872 0,
873 (PDOMNODE*)&pNew);
874
875 if (!arc)
876 {
877 // the node has already been added to the children
878 // list of the document node... in addition, set
879 // the doctype field in the document
880 pDocumentNode->pDocType = pNew;
881
882 // initialize the extra fields
883 xstrcpy(&pNew->strPublicID, pcszPubid, 0);
884 xstrcpy(&pNew->strSystemID, pcszSysid, 0);
885 pNew->fHasInternalSubset = fHasInternalSubset;
886
887 treeInit(&pNew->ElementDeclsTree, NULL);
888 treeInit(&pNew->AttribDeclBasesTree, NULL);
889
890 *ppNew = pNew;
891 }
892 }
893 return (arc);
894}
895
896/* ******************************************************************
897 *
898 * DOM level 3 content models
899 *
900 ********************************************************************/
901
902/*
903 *@@ SetupParticleAndSubs:
904 *
905 * This creates sub-particles and recurses to set them up,
906 * if necessary.
907 *
908 *@@added V0.9.9 (2001-02-16) [umoeller]
909 */
910
911APIRET SetupParticleAndSubs(PCMELEMENTPARTICLE pParticle,
912 PXMLCONTENT pModel,
913 TREE **ppElementNamesTree) // in: ptr to _CMELEMENTDECLNODE.ElementNamesTree
914 // (passed to all recursions)
915{
916 APIRET arc = NO_ERROR;
917
918 // set up member NODEBASE
919 switch (pModel->type)
920 {
921 case XML_CTYPE_EMPTY: // that's easy
922 pParticle->NodeBase.ulNodeType = ELEMENTPARTICLE_EMPTY;
923 break;
924
925 case XML_CTYPE_ANY: // that's easy
926 pParticle->NodeBase.ulNodeType = ELEMENTPARTICLE_ANY;
927 break;
928
929 case XML_CTYPE_NAME: // that's easy
930 pParticle->NodeBase.ulNodeType = ELEMENTPARTICLE_NAME;
931 xstrcpy(&pParticle->NodeBase.strNodeName, pModel->name, 0);
932 treeInsert(ppElementNamesTree,
933 NULL,
934 &pParticle->NodeBase.Tree,
935 CompareXStrings);
936 break;
937
938 case XML_CTYPE_MIXED:
939 pParticle->NodeBase.ulNodeType = ELEMENTPARTICLE_MIXED;
940 break;
941
942 case XML_CTYPE_CHOICE:
943 pParticle->NodeBase.ulNodeType = ELEMENTPARTICLE_CHOICE;
944 break;
945
946 case XML_CTYPE_SEQ:
947 pParticle->NodeBase.ulNodeType = ELEMENTPARTICLE_SEQ;
948 break;
949 }
950
951 pParticle->ulRepeater = pModel->quant;
952
953 if (pModel->numchildren)
954 {
955 // these are the three cases where we have subnodes
956 // in the XMLCONTENT... go for these and recurse
957 ULONG ul;
958 pParticle->pllSubNodes = lstCreate(FALSE);
959 for (ul = 0;
960 ul < pModel->numchildren;
961 ul++)
962 {
963 PXMLCONTENT pSubModel = &pModel->children[ul];
964 PCMELEMENTPARTICLE pSubNew = NULL;
965 arc = xmlCreateNodeBase(TYPE_UNKNOWN, // node type... for now
966 sizeof(CMELEMENTPARTICLE),
967 0,
968 0,
969 (PNODEBASE*)&pSubNew);
970 if (!arc)
971 {
972 arc = SetupParticleAndSubs(pSubNew,
973 pSubModel,
974 ppElementNamesTree);
975
976 if (!arc)
977 {
978 // no error: append sub-particle to this particle's
979 // children list
980 lstAppendItem(pParticle->pllSubNodes,
981 pSubNew);
982 // and store this particle as the parent in the
983 // sub-particle
984 pSubNew->pParentParticle = pParticle;
985 }
986 }
987
988 if (arc)
989 break;
990 }
991 }
992
993 return (arc);
994}
995
996/*
997 *@@ xmlCreateElementDecl:
998 * creates a new _CMELEMENTDECLNODE for the specified
999 * _XMLCONTENT content model (which is the @expat structure).
1000 * This recurses, if necessary.
1001 *
1002 * NOTE: As opposed to the other "create" functions,
1003 * this does not take a parent node as input. If this
1004 * returns NO_ERROR, it is the caller's responsibility
1005 * to add the produced node to the document's DOCTYPE node.
1006 *
1007 *@@added V0.9.9 (2001-02-16) [umoeller]
1008 */
1009
1010APIRET xmlCreateElementDecl(const char *pcszName,
1011 PXMLCONTENT pModel,
1012 PCMELEMENTDECLNODE *ppNew)
1013{
1014 APIRET arc = NO_ERROR;
1015 PCMELEMENTDECLNODE pNew = NULL;
1016
1017 arc = xmlCreateNodeBase(TYPE_UNKNOWN, // for now
1018 sizeof(CMELEMENTDECLNODE),
1019 pcszName,
1020 0,
1021 (PNODEBASE*)&pNew);
1022
1023 if (!arc)
1024 {
1025 treeInit(&pNew->ParticleNamesTree, NULL);
1026
1027 // set up the "particle" member and recurse into sub-particles
1028 arc = SetupParticleAndSubs(&pNew->Particle,
1029 pModel,
1030 &pNew->ParticleNamesTree);
1031
1032 if (!arc)
1033 *ppNew = pNew;
1034 else
1035 free(pNew);
1036 }
1037
1038 return (arc);
1039}
1040
1041/*
1042 *@@ ValidateElementChildren
1043 * validates the specified element against the document's @DTD,
1044 * more specifically, against the element declaration of the
1045 * new element's parent.
1046 *
1047 * This sets arcDOM in XMLDOM on errors.
1048 *
1049 * According to the XML spec, an element is valid if there
1050 * is a declaration matching the element declaration where the
1051 * element's name matches the element type, and _one_ of the
1052 * following holds:
1053 *
1054 * (1) The declaration matches EMPTY and the element has no @content. (done)
1055 *
1056 * (2) The declaration matches (children) (see @element_declaration)
1057 * and the sequence of child elements belongs to the language
1058 * generated by the regular expression in the content model, with
1059 * optional @white_space between the start-tag and the first child
1060 * element, between child elements, or between the last
1061 * child element and the end-tag. Note that a CDATA section
1062 * is never considered "whitespace", even if it contains
1063 * white space only. @@todo
1064 *
1065 * (3) The declaration matches (mixed) (see @element_declaration)
1066 * and the content consists of @content and child elements
1067 * whose types match names in the content model. (done)
1068 *
1069 * (4) The declaration matches ANY, and the types of any child
1070 * elements have been declared. (done)
1071 *
1072 * Preconditions: The element must already have been inserted
1073 * into the parent element's list, or we cannot validate sequences.
1074 *
1075 *@@added V0.9.9 (2001-02-16) [umoeller]
1076 */
1077
1078VOID ValidateElement(PXMLDOM pDom,
1079 PDOMNODE pNewElement, // in: new element
1080 PCMELEMENTDECLNODE pParentElementDecl)
1081 // in: element decl of element's parent
1082{
1083 if (pDom && pNewElement)
1084 {
1085 if (!pParentElementDecl)
1086 {
1087 // this is always missing for the root element, of course,
1088 // because the parent is the document
1089 if (pNewElement->pParentNode == (PDOMNODE)pDom->pDocumentNode)
1090 return; // that's OK
1091 else
1092 xmlSetError(pDom,
1093 ERROR_DOM_VALIDATE_INVALID_ELEMENT,
1094 pNewElement->NodeBase.strNodeName.psz,
1095 TRUE);
1096 }
1097 else
1098 {
1099 ULONG ulDeclType = pParentElementDecl->Particle.NodeBase.ulNodeType;
1100
1101 switch (ulDeclType)
1102 {
1103 case ELEMENTPARTICLE_EMPTY:
1104 // this is an error for sure
1105 xmlSetError(pDom,
1106 ERROR_DOM_SUBELEMENT_IN_EMPTY_ELEMENT,
1107 pNewElement->NodeBase.strNodeName.psz,
1108 TRUE);
1109 break;
1110
1111 case ELEMENTPARTICLE_ANY:
1112 // that's always OK
1113 break;
1114
1115 case ELEMENTPARTICLE_MIXED:
1116 case ELEMENTPARTICLE_CHOICE:
1117 case ELEMENTPARTICLE_SEQ:
1118 {
1119 PXSTRING pstrNewElementName
1120 = &pNewElement->NodeBase.strNodeName;
1121
1122 // for all these, we first need to check if
1123 // the element is allowed at all
1124 PCMELEMENTPARTICLE pParticle
1125 = (PCMELEMENTPARTICLE)treeFind(
1126 pParentElementDecl->ParticleNamesTree,
1127 (ULONG)pstrNewElementName,
1128 CompareXStrings);
1129 if (!pParticle)
1130 // not found: then this element is not allowed within this
1131 // parent
1132 xmlSetError(pDom,
1133 ERROR_DOM_INVALID_SUBELEMENT,
1134 pstrNewElementName->psz,
1135 TRUE);
1136 else
1137 {
1138 // the element is allowed at all: now check for the
1139 // lists case...
1140 switch (ulDeclType)
1141 {
1142 case ELEMENTPARTICLE_CHOICE:
1143 break;
1144
1145 case ELEMENTPARTICLE_SEQ:
1146 break;
1147 }
1148 }
1149
1150 break; }
1151 }
1152 }
1153 }
1154 else
1155 pDom->arcDOM = ERROR_INVALID_PARAMETER;
1156
1157 // yes: get the element decl from the tree
1158 /* PCMELEMENTDECLNODE pElementDecl = xmlFindElementDecl(pDom,
1159 &pElement->NodeBase.strNodeName);
1160 if (!pElementDecl)
1161 {
1162 xmlSetError(pDom,
1163 ERROR_DOM_UNDECLARED_ELEMENT,
1164 pElement->NodeBase.strNodeName.psz,
1165 TRUE);
1166 }
1167 else
1168 {
1169 // element has been declared:
1170 // check if it may appear in this element's parent...
1171 PDOMNODE pParentElement = pElement->pParentNode;
1172
1173 if (!pParentElement)
1174 pDom->arcDOM = ERROR_DOM_INTEGRITY;
1175 else switch (pParentElement->NodeBase.ulNodeType)
1176 {
1177 case DOMNODE_DOCUMENT:
1178 {
1179 // if this is the root element, compare its name
1180 // to the DOCTYPE name
1181 if (pParentElement != (PDOMNODE)pDom->pDocumentNode)
1182 xmlSetError(pDom,
1183 ERROR_DOM_INVALID_ROOT_ELEMENT,
1184 pElement->NodeBase.strNodeName.psz,
1185 TRUE);
1186 else if (strcmp(pDom->pDocumentNode->DomNode.NodeBase.strNodeName.psz,
1187 pElement->NodeBase.strNodeName.psz))
1188 // no match:
1189 xmlSetError(pDom,
1190 ERROR_DOM_ROOT_ELEMENT_MISNAMED,
1191 pElement->NodeBase.strNodeName.psz,
1192 TRUE);
1193 break; }
1194
1195 case DOMNODE_ELEMENT:
1196 {
1197 // parent of element is another element:
1198 // check the parent in the DTD and find out if
1199 // this element may appear in the parent element
1200 PCMELEMENTDECLNODE pParentElementDecl
1201 = xmlFindElementDecl(pDom,
1202 &pParentElement->NodeBase.strNodeName);
1203 if (!pParentElementDecl)
1204 pDom->arcDOM = ERROR_DOM_INTEGRITY;
1205 else
1206 {
1207 // now check the element names tree of the parent element decl
1208 // for whether this element is allowed as a sub-element at all
1209 PCMELEMENTPARTICLE pParticle
1210 = treeFindEQData(&pParentElementDecl->ParticleNamesTree,
1211 (void*)pElement->NodeBase.strNodeName.psz,
1212 CompareNodeBaseData);
1213 if (!pParticle)
1214 // not found: then this element is not allowed within this
1215 // parent
1216 xmlSetError(pDom,
1217 ERROR_DOM_INVALID_SUBELEMENT,
1218 pElement->NodeBase.strNodeName.psz,
1219 TRUE);
1220 }
1221 break; }
1222 }
1223 }
1224 */
1225}
1226
1227/*
1228 *@@ ValidateAttributeType:
1229 * validates the specified attribute's type against the
1230 * document's @DTD.
1231 *
1232 * This sets arcDOM in XMLDOM on errors.
1233 *
1234 *@@added V0.9.9 (2001-02-16) [umoeller]
1235 */
1236
1237VOID ValidateAttributeType(PXMLDOM pDom,
1238 PDOMNODE pAttrib,
1239 PCMATTRIBUTEDECLBASE *ppAttribDeclBase)
1240{
1241 PDOMNODE pElement = pAttrib->pParentNode;
1242
1243 PCMATTRIBUTEDECL pAttribDecl = xmlFindAttribDecl(pDom,
1244 &pElement->NodeBase.strNodeName,
1245 &pAttrib->NodeBase.strNodeName,
1246 ppAttribDeclBase);
1247 if (!pAttribDecl)
1248 xmlSetError(pDom,
1249 ERROR_DOM_UNDECLARED_ATTRIBUTE,
1250 pAttrib->NodeBase.strNodeName.psz,
1251 TRUE);
1252 else
1253 {
1254 // check if the attribute value is allowed
1255 switch (pAttribDecl->ulAttrType)
1256 {
1257 case CMAT_CDATA:
1258 case CMAT_ID:
1259 case CMAT_IDREF:
1260 case CMAT_IDREFS: // ###
1261 case CMAT_ENTITY:
1262 case CMAT_ENTITIES:
1263 case CMAT_NMTOKEN:
1264 case CMAT_NMTOKENS:
1265 break;
1266
1267 case CMAT_ENUM:
1268 {
1269 // enumeration: then check if it has one of the
1270 // allowed values
1271 PNODEBASE pValue = (PNODEBASE)treeFind(
1272 pAttribDecl->ValuesTree,
1273 (ULONG)pAttrib->pstrNodeValue,
1274 CompareXStrings);
1275 if (!pValue)
1276 xmlSetError(pDom,
1277 ERROR_DOM_INVALID_ATTRIB_VALUE,
1278 pAttrib->NodeBase.strNodeName.psz,
1279 TRUE);
1280 }
1281 }
1282
1283 if (pAttribDecl->ulConstraint == CMAT_FIXED_VALUE)
1284 if (strcmp(pAttrib->pstrNodeValue->psz, pAttribDecl->pstrDefaultValue->psz))
1285 // fixed value doesn't match:
1286 xmlSetError(pDom,
1287 ERROR_DOM_INVALID_ATTRIB_VALUE,
1288 pAttrib->NodeBase.strNodeName.psz,
1289 TRUE);
1290 }
1291}
1292
1293/*
1294 *@@ ValidateAllAttributes:
1295 * validates the constraints of all attributes of the specified
1296 * element against the document's @DTD.
1297 *
1298 *@@added V0.9.9 (2001-02-16) [umoeller]
1299 */
1300
1301VOID ValidateAllAttributes(PXMLDOM pDom,
1302 PCMATTRIBUTEDECLBASE pAttribDeclBase,
1303 PDOMNODE pNewElement)
1304{
1305 PCMATTRIBUTEDECL pDeclThis
1306 = (PCMATTRIBUTEDECL)treeFirst(pAttribDeclBase->AttribDeclsTree);
1307
1308 while ((pDeclThis) && (!pDom->arcDOM))
1309 {
1310 // if attribute is all optional: then we don't need
1311 // to check for whether it's here
1312 if ( (pDeclThis->ulConstraint != CMAT_IMPLIED)
1313 && (pDeclThis->ulConstraint != CMAT_DEFAULT_VALUE)
1314 // we don't have to check this case because expat
1315 // already adds default attributes for us
1316 )
1317 {
1318 // for all others , we need to find the attribute
1319 PXSTRING pstrAttrNameThis = &pDeclThis->NodeBase.strNodeName;
1320 PDOMNODE pAttrNode = (PDOMNODE)treeFind(
1321 pNewElement->AttributesMap,
1322 (ULONG)pstrAttrNameThis,
1323 CompareXStrings);
1324
1325 // now switch again
1326 switch (pDeclThis->ulConstraint)
1327 {
1328 case CMAT_REQUIRED:
1329 if (!pAttrNode)
1330 // required, but no attribute with this name exists:
1331 xmlSetError(pDom,
1332 ERROR_DOM_REQUIRED_ATTRIBUTE_MISSING,
1333 pstrAttrNameThis->psz,
1334 TRUE);
1335 break;
1336 }
1337 }
1338
1339 pDeclThis = (PCMATTRIBUTEDECL)treeNext((TREE*)pDeclThis);
1340 }
1341}
1342
1343/* ******************************************************************
1344 *
1345 * Expat stack
1346 *
1347 ********************************************************************/
1348
1349/*
1350 *@@ DOMSTACKITEM:
1351 *
1352 *@@added V0.9.9 (2001-02-16) [umoeller]
1353 */
1354
1355typedef struct _DOMSTACKITEM
1356{
1357 PDOMNODE pDomNode;
1358 PCMELEMENTDECLNODE pElementDecl;
1359
1360} DOMSTACKITEM, *PDOMSTACKITEM;
1361
1362/*
1363 *@@ PopElementStack:
1364 *
1365 * NOTE:
1366 *
1367 *@@added V0.9.9 (2001-02-16) [umoeller]
1368 */
1369
1370PDOMSTACKITEM PopElementStack(PXMLDOM pDom,
1371 PLISTNODE *ppListNode)
1372{
1373 PDOMSTACKITEM pStackItem = NULL;
1374 PLISTNODE pParentLN = lstPop(&pDom->llElementStack);
1375
1376 if (!pParentLN)
1377 pDom->arcDOM = ERROR_DOM_NO_ELEMENT;
1378 else
1379 {
1380 // we have at least one node:
1381 pStackItem = (PDOMSTACKITEM)pParentLN->pItemData;
1382
1383 if (ppListNode)
1384 *ppListNode = pParentLN;
1385 }
1386
1387 return (pStackItem);
1388}
1389
1390/*
1391 *@@ PushElementStack:
1392 *
1393 * NOTE: pDomNode will most frequently be an element
1394 * node, but will also be the document for root and
1395 * a DOCTYPE node while parsing the DTD.
1396 *
1397 *@@added V0.9.9 (2001-02-16) [umoeller]
1398 */
1399
1400VOID PushElementStack(PXMLDOM pDom,
1401 PDOMNODE pDomNode)
1402{
1403 PDOMSTACKITEM pNew = (PDOMSTACKITEM)malloc(sizeof(*pNew));
1404 if (!pNew)
1405 pDom->arcDOM = ERROR_NOT_ENOUGH_MEMORY;
1406 else
1407 {
1408 memset(pNew, 0, sizeof(*pNew));
1409 pNew->pDomNode = pDomNode;
1410
1411 // shall we validate?
1412 if ( (pDom->pDocTypeNode)
1413 && (pDomNode->NodeBase.ulNodeType == DOMNODE_ELEMENT)
1414 )
1415 pNew->pElementDecl = xmlFindElementDecl(pDom,
1416 &pDomNode->NodeBase.strNodeName);
1417
1418 lstPush(&pDom->llElementStack,
1419 pNew);
1420 }
1421}
1422
1423/* ******************************************************************
1424 *
1425 * Expat handlers
1426 *
1427 ********************************************************************/
1428
1429/*
1430 *@@ UnknownEncodingHandler:
1431 * @expat handler called when the xml
1432 * @text_declaration has an @encoding that is not
1433 * one of the four encodings built into expat.
1434 *
1435 * See XML_SetUnknownEncodingHandler.
1436 *
1437 *@@added V0.9.14 (2001-08-09) [umoeller]
1438 */
1439
1440int EXPATENTRY UnknownEncodingHandler(void *pUserData, // in: out PXMLDOM really
1441 const XML_Char *pcszName,
1442 XML_Encoding *pEncoding)
1443{
1444 PXMLDOM pDom = (PXMLDOM)pUserData;
1445
1446 ULONG ulCP;
1447 if ( (pDom->pfnGetCPData) // callback exists?
1448 && (!strncmp(pcszName, "cp", 2))
1449 && (strlen(pcszName) > 4) // at least five chars (e.g. "cp850")
1450 && (ulCP = atoi(pcszName + 2))
1451 )
1452 {
1453 // this is a PC codepage:
1454/* typedef struct _XML_Encoding
1455{
1456 int map[256];
1457 void *data;
1458 int (* EXPATENTRY convert)(void *data, const char *s);
1459 void (* EXPATENTRY release)(void *data);
1460} XML_Encoding; */
1461
1462 // ZERO(pEncoding);
1463
1464 pEncoding->convert = NULL;
1465 pEncoding->release = NULL;
1466
1467 memset(&pEncoding->map, -1, sizeof(pEncoding->map));
1468
1469 if (pDom->pfnGetCPData(pDom,
1470 ulCP,
1471 pEncoding->map))
1472 {
1473 // go check if there's any -1 chars left
1474 ULONG ul;
1475 for (ul = 0;
1476 ul < 256;
1477 ul++)
1478 {
1479 if (pEncoding->map[ul] < 0)
1480 xmlSetError(pDom,
1481 ERROR_DOM_INCOMPLETE_ENCODING_MAP,
1482 NULL,
1483 FALSE);
1484 }
1485 // return success
1486 return (1);
1487 }
1488 }
1489
1490 // error
1491 return (0);
1492}
1493
1494/*
1495 *@@ StartElementHandler:
1496 * @expat handler called when a new element is
1497 * found.
1498 *
1499 * We create a new record in the container and
1500 * push it onto our stack so we can insert
1501 * children into it. We first start with the
1502 * attributes.
1503 */
1504
1505void EXPATENTRY StartElementHandler(void *pUserData, // in: our PXMLDOM really
1506 const char *pcszElement,
1507 const char **papcszAttribs)
1508{
1509 PXMLDOM pDom = (PXMLDOM)pUserData;
1510
1511 // continue parsing only if we had no errors so far
1512 if (!pDom->arcDOM)
1513 {
1514 ULONG i;
1515
1516 PDOMSTACKITEM pSI = PopElementStack(pDom,
1517 NULL); // no free
1518 if (!pDom->arcDOM)
1519 {
1520 PDOMNODE pParent = pSI->pDomNode,
1521 pNew = NULL;
1522
1523 pDom->arcDOM = xmlCreateElementNode(pParent,
1524 pcszElement,
1525 &pNew);
1526
1527 if (!pDom->arcDOM)
1528 // OK, node is valid:
1529 // push this on the stack so we can add child elements
1530 PushElementStack(pDom,
1531 pNew);
1532
1533 // shall we validate?
1534 if ( (!pDom->arcDOM)
1535 && (pDom->pDocTypeNode)
1536 )
1537 ValidateElement(pDom,
1538 pNew, // new element
1539 pSI->pElementDecl); // parent's elem decl
1540
1541 if (!pDom->arcDOM)
1542 {
1543 PCMATTRIBUTEDECLBASE pAttribDeclBase = NULL;
1544
1545 // shall we validate?
1546 if (pDom->pDocTypeNode)
1547 // yes: get attrib decl base for speed
1548 pAttribDeclBase
1549 = xmlFindAttribDeclBase(pDom,
1550 &pNew->NodeBase.strNodeName);
1551
1552 // now for the attribs
1553 for (i = 0;
1554 (papcszAttribs[i]) && (!pDom->arcDOM);
1555 i += 2)
1556 {
1557 PDOMNODE pAttrib;
1558 pDom->arcDOM = xmlCreateAttributeNode(pNew, // element,
1559 papcszAttribs[i], // attr name
1560 papcszAttribs[i + 1], // attr value
1561 &pAttrib);
1562
1563 if (pDom->arcDOM)
1564 xmlSetError(pDom,
1565 pDom->arcDOM,
1566 papcszAttribs[i],
1567 TRUE); // validation
1568 else
1569 // shall we validate?
1570 if (pDom->pDocTypeNode)
1571 ValidateAttributeType(pDom,
1572 pAttrib,
1573 &pAttribDeclBase);
1574 }
1575
1576 // OK, now we got all attributes:
1577 // now look for defaults in the DTD,
1578 // if we shall validate
1579 if ( (pDom->pDocTypeNode)
1580 && (!pDom->arcDOM)
1581 && (pAttribDeclBase)
1582 )
1583 ValidateAllAttributes(pDom,
1584 pAttribDeclBase,
1585 pNew);
1586 }
1587 }
1588
1589 pDom->pLastWasTextNode = NULL;
1590 }
1591}
1592
1593/*
1594 *@@ EndElementHandler:
1595 * @expat handler for when parsing an element is done.
1596 * We pop the element off of our stack then.
1597 */
1598
1599void EXPATENTRY EndElementHandler(void *pUserData, // in: our PXMLDOM really
1600 const XML_Char *name)
1601{
1602 PXMLDOM pDom = (PXMLDOM)pUserData;
1603 // continue parsing only if we had no errors so far
1604 if (!pDom->arcDOM)
1605 {
1606 PLISTNODE pStackLN = NULL;
1607 PDOMSTACKITEM pSI = PopElementStack(pDom,
1608 &pStackLN);
1609
1610 if (!pDom->arcDOM)
1611 {
1612 // shall we validate?
1613 /* if (pDom->pDocTypeNode)
1614 // yes:
1615 ValidateElementChildren(pDom,
1616 pSI->pDomNode); */
1617
1618 lstRemoveNode(&pDom->llElementStack, pStackLN); // auto-free
1619 }
1620 else
1621 pDom->arcDOM = ERROR_DOM_INTEGRITY;
1622
1623 pDom->pLastWasTextNode = NULL;
1624 }
1625}
1626
1627/*
1628 *@@ CharacterDataHandler:
1629 * @expat handler for character data (@content).
1630 *
1631 * Note: expat passes chunks of content without zero-terminating
1632 * them. We must concatenate the chunks to a full text node.
1633 */
1634
1635void EXPATENTRY CharacterDataHandler(void *pUserData, // in: our PXMLDOM really
1636 const XML_Char *s,
1637 int len)
1638{
1639 PXMLDOM pDom = (PXMLDOM)pUserData;
1640
1641 // continue parsing only if we had no errors so far
1642 if (!pDom->arcDOM)
1643 {
1644 if (len)
1645 {
1646 // we need a new text node:
1647 PDOMSTACKITEM pSI = PopElementStack(pDom,
1648 NULL); // no free
1649 if (!pDom->arcDOM)
1650 {
1651 PDOMNODE pParent = pSI->pDomNode;
1652 // pNew = NULL;
1653
1654 BOOL fIsWhitespace = FALSE;
1655
1656 // shall we validate?
1657 if (pDom->pDocTypeNode)
1658 {
1659 // yes: check if the parent element allows
1660 // for content at all (must be "mixed" model)
1661
1662 // get the element decl from the tree
1663 PCMELEMENTDECLNODE pElementDecl = pSI->pElementDecl;
1664
1665 if (pElementDecl)
1666 {
1667 switch (pElementDecl->Particle.NodeBase.ulNodeType)
1668 {
1669 case ELEMENTPARTICLE_ANY:
1670 case ELEMENTPARTICLE_MIXED:
1671 // those two are okay
1672 break;
1673
1674 case ELEMENTPARTICLE_EMPTY:
1675 // that's an error for sure
1676 pDom->arcDOM = ERROR_ELEMENT_CANNOT_HAVE_CONTENT;
1677 break;
1678
1679 default:
1680 {
1681 // ELEMENTPARTICLE_CHOICE:
1682 // ELEMENTPARTICLE_SEQ:
1683 // with these two, we accept whitespace, but nothing
1684 // else... so if we have characters other than
1685 // whitespace, terminate
1686 ULONG ul;
1687 const char *p = s;
1688
1689 if (pDom->flParserFlags & DF_DROP_WHITESPACE)
1690 fIsWhitespace = TRUE;
1691
1692 for (ul = 0;
1693 ul < len;
1694 ul++, p++)
1695 if (!strchr("\r\n\t ", *p))
1696 {
1697 // other character:
1698 xmlSetError(pDom,
1699 ERROR_ELEMENT_CANNOT_HAVE_CONTENT,
1700 pParent->NodeBase.strNodeName.psz,
1701 TRUE);
1702 fIsWhitespace = FALSE;
1703 break;
1704 }
1705 }
1706 }
1707 }
1708
1709 } // end if (pDom->pDocTypeNode)
1710
1711 if (!fIsWhitespace)
1712 // this is false if any of the following
1713 // is true:
1714 // -- we are not validating at all
1715 // -- we are validating, but the the element
1716 // can have mixed content
1717 // -- we are validating and the element does
1718 // _not_ have mixed content and DF_DROP_WHITESPACE
1719 // is set, but the string is whitespace only
1720 // --> drop it then
1721
1722 if (pDom->pLastWasTextNode)
1723 {
1724 // we had a text node, and no elements or other
1725 // stuff in between:
1726 xstrcat(pDom->pLastWasTextNode->pstrNodeValue,
1727 s,
1728 len);
1729 }
1730 else
1731 {
1732 pDom->arcDOM = xmlCreateTextNode(pParent,
1733 s,
1734 len,
1735 &pDom->pLastWasTextNode);
1736 }
1737 }
1738 }
1739 }
1740}
1741
1742/*
1743 *@@ CommentHandler:
1744 * @expat handler for @comments.
1745 *
1746 * Note: This is only set if DF_PARSECOMMENTS is
1747 * flagged with xmlCreateDOM.
1748 *
1749 *@@added V0.9.9 (2001-02-14) [umoeller]
1750 */
1751
1752void EXPATENTRY CommentHandler(void *pUserData, // in: our PXMLDOM really
1753 const XML_Char *data)
1754{
1755 PXMLDOM pDom = (PXMLDOM)pUserData;
1756
1757 // continue parsing only if we had no errors so far
1758 if (!pDom->arcDOM)
1759 {
1760 // we need a new text node:
1761 PDOMSTACKITEM pSI = PopElementStack(pDom,
1762 NULL); // no free
1763 if (!pDom->arcDOM)
1764 {
1765 PDOMNODE pParent = pSI->pDomNode,
1766 pNew = NULL;
1767
1768 pDom->arcDOM = xmlCreateCommentNode(pParent,
1769 data,
1770 &pNew);
1771 }
1772 }
1773}
1774
1775/*
1776 *@@ StartDoctypeDeclHandler:
1777 * @expat handler that is called at the start of a DOCTYPE
1778 * declaration, before any external or internal subset is
1779 * parsed.
1780 *
1781 * Both pcszSysid and pcszPubid may be NULL. "fHasInternalSubset"
1782 * will be non-zero if the DOCTYPE declaration has an internal subset.
1783 *
1784 *@@added V0.9.9 (2001-02-14) [umoeller]
1785 */
1786
1787void EXPATENTRY StartDoctypeDeclHandler(void *pUserData,
1788 const XML_Char *pcszDoctypeName,
1789 const XML_Char *pcszSysid,
1790 const XML_Char *pcszPubid,
1791 int fHasInternalSubset)
1792{
1793 PXMLDOM pDom = (PXMLDOM)pUserData;
1794
1795 // continue parsing only if we had no errors so far
1796 if (!pDom->arcDOM)
1797 {
1798 // get the document node
1799 PDOMDOCUMENTNODE pDocumentNode = pDom->pDocumentNode;
1800 if (!pDocumentNode)
1801 pDom->arcDOM = ERROR_DOM_NO_DOCUMENT;
1802 else
1803 {
1804 // doctype must be null
1805 if (pDom->pDocTypeNode)
1806 pDom->arcDOM = ERROR_DOM_DUPLICATE_DOCTYPE;
1807 else
1808 {
1809 pDom->arcDOM = xmlCreateDocumentTypeNode(pDocumentNode,
1810 pcszDoctypeName,
1811 pcszSysid,
1812 pcszPubid,
1813 fHasInternalSubset,
1814 &pDom->pDocTypeNode);
1815 }
1816 }
1817 }
1818}
1819
1820/*
1821 *@@ EndDoctypeDeclHandler:
1822 * @expat handler that is called at the end of a DOCTYPE
1823 * declaration, after parsing any external subset.
1824 *
1825 *@@added V0.9.9 (2001-02-14) [umoeller]
1826 */
1827
1828void EXPATENTRY EndDoctypeDeclHandler(void *pUserData) // in: our PXMLDOM really
1829{
1830 PXMLDOM pDom = (PXMLDOM)pUserData;
1831
1832 // continue parsing only if we had no errors so far
1833 if (!pDom->arcDOM)
1834 {
1835 }
1836}
1837
1838/*
1839 *@@ NotationDeclHandler:
1840 * @expat handler for @notation_declarations.
1841 *
1842 * @@todo
1843 *
1844 *@@added V0.9.9 (2001-02-14) [umoeller]
1845 */
1846
1847void EXPATENTRY NotationDeclHandler(void *pUserData, // in: our PXMLDOM really
1848 const XML_Char *pcszNotationName,
1849 const XML_Char *pcszBase,
1850 const XML_Char *pcszSystemId,
1851 const XML_Char *pcszPublicId)
1852{
1853 PXMLDOM pDom = (PXMLDOM)pUserData;
1854
1855 // continue parsing only if we had no errors so far
1856 if (!pDom->arcDOM)
1857 {
1858 }
1859}
1860
1861/*
1862 *@@ ExternalEntityRefHandler:
1863 * @expat handler for references to @external_entities.
1864 *
1865 * This handler is also called for processing an external DTD
1866 * subset if parameter entity parsing is in effect.
1867 * (See XML_SetParamEntityParsing.)
1868 *
1869 * The pcszContext argument specifies the parsing context in the
1870 * format expected by the context argument to
1871 * XML_ExternalEntityParserCreate; pcszContext is valid only until
1872 * the handler returns, so if the referenced entity is to be
1873 * parsed later, it must be copied.
1874 *
1875 * The pcszBase parameter is the base to use for relative system
1876 * identifiers. It is set by XML_SetBase and may be null.
1877 *
1878 * The pcszPublicId parameter is the public id given in the entity
1879 * declaration and may be null (since XML doesn't require public
1880 * identifiers).
1881 *
1882 * The pcszSystemId is the system identifier specified in the
1883 * entity declaration and is never null. This is an exact copy
1884 * of what was specified in the reference.
1885 *
1886 * There are a couple of ways in which this handler differs
1887 * from others. First, this handler returns an integer. A
1888 * non-zero value should be returned for successful handling
1889 * of the external entity reference. Returning a zero indicates
1890 * failure, and causes the calling parser to return an
1891 * ERROR_EXPAT_EXTERNAL_ENTITY_HANDLING error.
1892 *
1893 * Second, instead of having pUserData as its first argument,
1894 * it receives the parser that encountered the entity reference.
1895 * This, along with the context parameter, may be used as
1896 * arguments to a call to XML_ExternalEntityParserCreate.
1897 * Using the returned parser, the body of the external entity
1898 * can be recursively parsed.
1899 *
1900 * Since this handler may be called recursively, it should not
1901 * be saving information into global or static variables.
1902 *
1903 * Your handler isn't actually responsible for parsing the entity,
1904 * but it is responsible for creating a subsidiary parser with
1905 * XML_ExternalEntityParserCreate that will do the job. That returns
1906 * an instance of XML_Parser that has handlers and other data
1907 * structures initialized from the parent parser. You may then use
1908 * XML_Parse or XML_ParseBuffer calls against that parser. Since
1909 * external entities may refer to other external entities, your
1910 * handler should be prepared to be called recursively.
1911 *
1912 *@@added V0.9.14 (2001-08-09) [umoeller]
1913 */
1914
1915int EXPATENTRY ExternalEntityRefHandler(void *pUserData, // in: our PXMLDOM really
1916 XML_Parser parser,
1917 const XML_Char *pcszContext,
1918 const XML_Char *pcszBase,
1919 const XML_Char *pcszSystemId,
1920 const XML_Char *pcszPublicId)
1921{
1922 PXMLDOM pDom = (PXMLDOM)pUserData;
1923
1924 int i = 0; // return error per default
1925
1926 APIRET arc = NO_ERROR;
1927
1928 // store the previous parser because
1929 // all the callbacks use the parser pointer
1930 XML_Parser pOldParser = pDom->pParser;
1931 pDom->pParser = NULL;
1932
1933 if ( (pDom->pfnExternalHandler)
1934 // create sub-parser and replace the one
1935 // in the DOM with it
1936 && (pDom->pParser = XML_ExternalEntityParserCreate(parser,
1937 pcszContext,
1938 "US-ASCII"))
1939 )
1940 {
1941 if ((arc = pDom->pfnExternalHandler(pDom,
1942 pDom->pParser,
1943 pcszSystemId,
1944 pcszPublicId)))
1945 {
1946 // error:
1947 // now this needs special handling, since we're
1948 // dealing with a sub-handler here...
1949
1950 if (arc == -1)
1951 // parser error: well, then xmlSetError has been
1952 // called from somewhere in the callbacks already,
1953 // and we can safely ignore this
1954 ;
1955 else
1956 {
1957 pDom->arcDOM = arc;
1958 if (pcszSystemId)
1959 {
1960 if (!pDom->pxstrFailingNode)
1961 pDom->pxstrFailingNode = xstrCreate(0);
1962 xstrcpy(pDom->pxstrFailingNode, pcszSystemId, 0);
1963 }
1964 pDom->pcszErrorDescription = xmlDescribeError(arc);
1965 pDom->ulErrorLine = XML_GetCurrentLineNumber(pDom->pParser);
1966 pDom->ulErrorColumn = XML_GetCurrentColumnNumber(pDom->pParser);
1967 }
1968 }
1969
1970 i = 1; // success
1971 }
1972 else
1973 xmlSetError(pDom,
1974 (!arc) ? ERROR_DOM_INVALID_EXTERNAL_HANDLER : arc,
1975 NULL,
1976 FALSE);
1977
1978 if (pDom->pParser)
1979 XML_ParserFree(pDom->pParser);
1980
1981 pDom->pParser = pOldParser;
1982
1983 return (i);
1984}
1985
1986/*
1987 *@@ ElementDeclHandler:
1988 * @expat handler for element declarations in a DTD. The
1989 * handler gets called with the name of the element in
1990 * the declaration and a pointer to a structure that contains
1991 * the element model.
1992 *
1993 * It is the application's responsibility to free this data
1994 * structure. @@todo
1995 *
1996 * The XML spec defines that no element may be declared more
1997 * than once.
1998 *
1999 *@@added V0.9.9 (2001-02-14) [umoeller]
2000 */
2001
2002void EXPATENTRY ElementDeclHandler(void *pUserData, // in: our PXMLDOM really
2003 const XML_Char *pcszName,
2004 XMLCONTENT *pModel)
2005{
2006 PXMLDOM pDom = (PXMLDOM)pUserData;
2007
2008 // continue parsing only if we had no errors so far
2009 if (!pDom->arcDOM)
2010 {
2011 // OK, we're in a DOCTYPE node:
2012 PDOMDOCTYPENODE pDocType = pDom->pDocTypeNode;
2013 if (!pDocType)
2014 xmlSetError(pDom,
2015 ERROR_DOM_ELEMENT_DECL_OUTSIDE_DOCTYPE,
2016 pcszName,
2017 TRUE);
2018 else
2019 {
2020 // create an element declaration and push it unto the
2021 // declarations tree
2022 PCMELEMENTDECLNODE pNew = NULL;
2023 pDom->arcDOM = xmlCreateElementDecl(pcszName,
2024 pModel,
2025 &pNew);
2026 // this recurses!!
2027 // after this, pModel is invalid
2028
2029 if (pDom->arcDOM == NO_ERROR)
2030 {
2031 // add this to the doctype's declarations tree
2032 if (treeInsert(&pDocType->ElementDeclsTree,
2033 NULL,
2034 (TREE*)pNew,
2035 CompareXStrings))
2036 // element already declared:
2037 // according to the XML specs, this is a validity
2038 // constraint, so we report a validation error
2039 xmlSetError(pDom,
2040 ERROR_DOM_DUPLICATE_ELEMENT_DECL,
2041 pNew->Particle.NodeBase.strNodeName.psz,
2042 TRUE);
2043 }
2044 }
2045 }
2046}
2047
2048/*
2049 *@@ AddEnum:
2050 *
2051 *@@added V0.9.9 (2001-02-16) [umoeller]
2052 */
2053
2054APIRET AddEnum(PCMATTRIBUTEDECL pDecl,
2055 const char *p, // in: start of name
2056 const char *pNext) // in: end of name (not included)
2057{
2058 // PSZ pszType = strhSubstr(p, pNext);
2059 PNODEBASE pNew = NULL;
2060 APIRET arc = xmlCreateNodeBase(ATTRIBUTE_DECLARATION_ENUM,
2061 sizeof(NODEBASE),
2062 p,
2063 (pNext - p),
2064 &pNew);
2065 if (!arc)
2066 treeInsert(&pDecl->ValuesTree,
2067 NULL,
2068 (TREE*)pNew,
2069 CompareXStrings);
2070
2071 return (arc);
2072}
2073
2074/*
2075 *@@ AttlistDeclHandler:
2076 * @expat handler for attlist declarations in the DTD.
2077 *
2078 * This handler is called for each attribute. So a single attlist
2079 * declaration with multiple attributes declared will generate
2080 * multiple calls to this handler.
2081 *
2082 * -- pcszElementName is the name of the element for which the
2083 * attribute is being declared.
2084 *
2085 * -- pcszAttribName has the attribute name being declared.
2086 *
2087 * -- pcszAttribType is the attribute type.
2088 * It is the string representing the type in the declaration
2089 * with whitespace removed.
2090 *
2091 * -- pcszDefault holds the default value. It will be
2092 * NULL in the case of "#IMPLIED" or "#REQUIRED" attributes.
2093 * You can distinguish these two cases by checking the
2094 * fIsRequired parameter, which will be true in the case of
2095 * "#REQUIRED" attributes. Attributes which are "#FIXED"
2096 * will have also have a TRUE fIsRequired, but they will have
2097 * the non-NULL fixed value in the pcszDefault parameter.
2098 *
2099 *@@added V0.9.9 (2001-02-14) [umoeller]
2100 */
2101
2102void EXPATENTRY AttlistDeclHandler(void *pUserData, // in: our PXMLDOM really
2103 const XML_Char *pcszElementName,
2104 const XML_Char *pcszAttribName,
2105 const XML_Char *pcszAttribType,
2106 const XML_Char *pcszDefault,
2107 int fIsRequired)
2108{
2109 PXMLDOM pDom = (PXMLDOM)pUserData;
2110
2111 // continue parsing only if we had no errors so far
2112 if (!pDom->arcDOM)
2113 {
2114 // OK, we're in a DOCTYPE node:
2115 PDOMDOCTYPENODE pDocType = pDom->pDocTypeNode;
2116 if (!pDocType)
2117 xmlSetError(pDom,
2118 ERROR_DOM_ATTLIST_DECL_OUTSIDE_DOCTYPE,
2119 pcszElementName,
2120 TRUE);
2121 else
2122 {
2123 PCMATTRIBUTEDECLBASE pThis = NULL,
2124 pCache = pDom->pAttListDeclCache;
2125
2126 // check if this is for the same attlist as the previous
2127 // call (we cache the pointer for speed)
2128 if ( (pCache)
2129 && (!strhcmp(pCache->NodeBase.strNodeName.psz,
2130 pcszElementName))
2131 )
2132 // this attdecl is for the same element:
2133 // use that (we won't have to search the tree)
2134 pThis = pDom->pAttListDeclCache;
2135
2136 if (!pThis)
2137 {
2138 // cache didn't match: look up attributes tree then...
2139 // note: cheap trick, we need an XSTRING for treeFind
2140 // but don't want malloc, so we use xstrInitSet
2141 XSTRING strElementName;
2142 xstrInitSet(&strElementName, (PSZ)pcszElementName);
2143 pThis = (PCMATTRIBUTEDECLBASE)treeFind(
2144 pDocType->AttribDeclBasesTree,
2145 (ULONG)&strElementName,
2146 CompareXStrings);
2147
2148 if (!pThis)
2149 {
2150 // still not found:
2151 // we need a new node then
2152 pDom->arcDOM = xmlCreateNodeBase(ATTRIBUTE_DECLARATION_BASE,
2153 sizeof(CMATTRIBUTEDECLBASE),
2154 strElementName.psz,
2155 strElementName.ulLength,
2156 (PNODEBASE*)&pThis);
2157 if (!pDom->arcDOM)
2158 {
2159 // initialize the subtree
2160 treeInit(&pThis->AttribDeclsTree, NULL);
2161
2162 treeInsert(&pDocType->AttribDeclBasesTree,
2163 NULL,
2164 (TREE*)pThis,
2165 CompareXStrings);
2166 }
2167 }
2168
2169 pDom->pAttListDeclCache = pThis;
2170 }
2171
2172 if (pThis)
2173 {
2174 // pThis now has either an existing or a new CMATTRIBUTEDECLBASE;
2175 // add a new attribute def (CMATTRIBUTEDEDECL) to that
2176 PCMATTRIBUTEDECL pNew = NULL;
2177 pDom->arcDOM = xmlCreateNodeBase(ATTRIBUTE_DECLARATION,
2178 sizeof(CMATTRIBUTEDECL),
2179 pcszAttribName,
2180 0,
2181 (PNODEBASE*)&pNew);
2182 if (!pDom->arcDOM)
2183 {
2184 treeInit(&pNew->ValuesTree, NULL);
2185
2186 // check the type... expat is too lazy to parse this for
2187 // us, so we must check manually. Expat only normalizes
2188 // the "type" string to kick out whitespace, so we get:
2189 // (TYPE1|TYPE2|TYPE3)
2190 if (*pcszAttribType == '(')
2191 {
2192 // enumeration:
2193 const char *p = pcszAttribType + 1,
2194 *pNext;
2195 while ( (pNext = strchr(p, '|'))
2196 && (!pDom->arcDOM)
2197 )
2198 {
2199 pDom->arcDOM = AddEnum(pNew, p, pNext);
2200 p = pNext + 1;
2201 }
2202
2203 if (!pDom->arcDOM)
2204 {
2205 pNext = strchr(p, ')');
2206 AddEnum(pNew, p, pNext);
2207
2208 pNew->ulAttrType = CMAT_ENUM;
2209 }
2210 }
2211 else if (!strcmp(pcszAttribType, "CDATA"))
2212 pNew->ulAttrType = CMAT_CDATA;
2213 else if (!strcmp(pcszAttribType, "ID"))
2214 pNew->ulAttrType = CMAT_ID;
2215 else if (!strcmp(pcszAttribType, "IDREF"))
2216 pNew->ulAttrType = CMAT_IDREF;
2217 else if (!strcmp(pcszAttribType, "IDREFS"))
2218 pNew->ulAttrType = CMAT_IDREFS;
2219 else if (!strcmp(pcszAttribType, "ENTITY"))
2220 pNew->ulAttrType = CMAT_ENTITY;
2221 else if (!strcmp(pcszAttribType, "ENTITIES"))
2222 pNew->ulAttrType = CMAT_ENTITIES;
2223 else if (!strcmp(pcszAttribType, "NMTOKEN"))
2224 pNew->ulAttrType = CMAT_NMTOKEN;
2225 else if (!strcmp(pcszAttribType, "NMTOKENS"))
2226 pNew->ulAttrType = CMAT_NMTOKENS;
2227
2228 if (!pDom->arcDOM)
2229 {
2230 if (pcszDefault)
2231 {
2232 // fixed or default:
2233 if (fIsRequired)
2234 // fixed:
2235 pNew->ulConstraint = CMAT_FIXED_VALUE;
2236 else
2237 pNew->ulConstraint = CMAT_DEFAULT_VALUE;
2238
2239 pNew->pstrDefaultValue = xstrCreate(0);
2240 xstrcpy(pNew->pstrDefaultValue, pcszDefault, 0);
2241 }
2242 else
2243 // implied or required:
2244 if (fIsRequired)
2245 pNew->ulConstraint = CMAT_REQUIRED;
2246 else
2247 pNew->ulConstraint = CMAT_IMPLIED;
2248
2249 if (treeInsert(&pThis->AttribDeclsTree,
2250 NULL,
2251 (TREE*)pNew,
2252 CompareXStrings))
2253 xmlSetError(pDom,
2254 ERROR_DOM_DUPLICATE_ATTRIBUTE_DECL,
2255 pcszAttribName,
2256 TRUE);
2257 }
2258 }
2259 }
2260 }
2261 }
2262}
2263
2264/*
2265 *@@ EntityDeclHandler:
2266 * @expat handler that will be called for all entity declarations.
2267 *
2268 * The fIsParameterEntity argument will be non-zero in the case
2269 * of parameter entities and zero otherwise.
2270 *
2271 * For internal entities (<!ENTITY foo "bar">), pcszValue will be
2272 * non-NULL and pcszSystemId, pcszPublicId, and pcszNotationName
2273 * will all be NULL. The value string is not NULL terminated; the
2274 * length is provided in the iValueLength parameter. Do not use
2275 * iValueLength to test for internal entities, since it is legal
2276 * to have zero-length values. Instead check for whether or not
2277 * pcszValue is NULL.
2278 *
2279 * The pcszNotationName argument will have a non-NULL value only
2280 * for unparsed entity declarations.
2281 *
2282 *@@added V0.9.9 (2001-02-14) [umoeller]
2283 */
2284
2285void EXPATENTRY EntityDeclHandler(void *pUserData, // in: our PXMLDOM really
2286 const XML_Char *pcszEntityName,
2287 int fIsParameterEntity,
2288 const XML_Char *pcszValue,
2289 int iValueLength,
2290 const XML_Char *pcszBase,
2291 const XML_Char *pcszSystemId,
2292 const XML_Char *pcszPublicId,
2293 const XML_Char *pcszNotationName)
2294{
2295 PXMLDOM pDom = (PXMLDOM)pUserData;
2296
2297 // continue parsing only if we had no errors so far
2298 if (!pDom->arcDOM)
2299 {
2300 }
2301}
2302
2303/* ******************************************************************
2304 *
2305 * DOM parser APIs
2306 *
2307 ********************************************************************/
2308
2309/*
2310 *@@ xmlCreateDOM:
2311 * creates an XMLDOM instance, which can be used for parsing
2312 * an XML document and building a @DOM tree from it at the
2313 * same time.
2314 *
2315 * Pass the XMLDOM returned here to xmlParse afterwards.
2316 *
2317 * Simplest possible usage:
2318 *
2319 * 1) Create a DOM instance.
2320 *
2321 + PXMLDOM pDom = NULL;
2322 + APIRET arc = xmlCreateDOM(flags, NULL, NULL, NULL, &pDom);
2323 +
2324 * 2) Give chunks of data (or an entire buffer)
2325 * to the DOM instance for parsing.
2326 *
2327 + arc = xmlParse(pDom,
2328 + pBuf,
2329 + TRUE); // if last, this will clean up the parser
2330 *
2331 * 3) Process the data in the DOM tree.
2332 *
2333 * Look at the DOMNODE definition to see how you
2334 * can traverse the data. Essentially, everything
2335 * is based on linked lists and string maps.
2336 *
2337 * A few helper functions have been added for
2338 * quick lookup. See xmlGetRootElement,
2339 * xmlGetFirstChild, xmlGetLastChild, xmlGetFirstText,
2340 * xmlGetElementsByTagName, xmlGetAttribute.
2341 *
2342 * 4) When done, call xmlFreeDOM, which will free all memory.
2343 *
2344 * The above code has limitations: only a few character
2345 * @encodings are supported, and @external_entities are
2346 * silently ignored.
2347 *
2348 * This function supports a number of callbacks and flags
2349 * to allow for maximum flexibility. Note however that
2350 * not all @expat features are supported yet.
2351 *
2352 * The following callbacks can be specified (any of these
2353 * can be NULL):
2354 *
2355 * -- pfnGetCPData should be specified if you want to
2356 * support character @encodings other than the
2357 * four that built into expat itself (see
2358 * XML_SetUnknownEncodingHandler). This is probably
2359 * a good idea to do under OS/2 since most OS/2
2360 * documents are in a PC-specific codepage such as
2361 * CP 850.
2362 *
2363 * This callback must have the following prototype:
2364 *
2365 + int APIENTRY FNGETCPDATA(PXMLDOM pDom, ULONG ulCP, int *piMap)
2366 *
2367 * The callback will only be called once for each
2368 * document if the "encoding" attribute of the
2369 * XML @text_declaration starts with "cp" (e.g.
2370 * "cp850") and will then receives the following
2371 * parameters:
2372 *
2373 * -- "pDom" will be the XMLDOM created by this function.
2374 *
2375 * -- ulCP has the IBM code page number, such as "850".
2376 *
2377 * -- piMap is an array of 256 integer values which must
2378 * be filled with the callback. Each array item index
2379 * is the codepage value, and the value of each field
2380 * is the corresponding Unicode value, or -1 if the
2381 * character is invalid (shouldn't happen with codepages).
2382 *
2383 * For example, the German o-umlaut character is
2384 * 0x94 in CP850 and 0x00f6 in Unicode. So set
2385 * the int at index 0x94 to 0x00f6.
2386 *
2387 * pvCallbackUser is a user parameter which is simply stored
2388 * in the XMLDOM struct which is returned. Since the XMLDOM
2389 * is passed to all the callbacks, you can access that pointer
2390 * from them.
2391 *
2392 * flParserFlags is any combination of the following:
2393 *
2394 * -- DF_PARSECOMMENTS: XML @comments are to be returned in
2395 * the DOM tree. Otherwise they are discarded.
2396 *
2397 * -- DF_PARSEDTD: add the @DTD of the document into the DOM tree
2398 * as well and validate the document, if a DTD was found.
2399 * Otherwise just parse and do not validate.
2400 *
2401 * DF_PARSEDTD is required for external entities to work
2402 * also.
2403 *
2404 * -- DF_FAIL_IF_NO_DTD: fail if no @DTD was found. Useful
2405 * if you want to enforce validation. @@todo
2406 *
2407 * -- DF_DROP_WHITESPACE: discard all @whitespace for those
2408 * elements that can only have element content. Whitespace
2409 * will be preserved only for elements that can have
2410 * mixed content. -- If this flag is not set, all whitespace
2411 * is preserved.
2412 *
2413 *@@added V0.9.9 (2001-02-14) [umoeller]
2414 *@@changed V0.9.14 (2001-08-09) [umoeller]: added DF_DROP_WHITESPACE support
2415 */
2416
2417APIRET xmlCreateDOM(ULONG flParserFlags, // in: DF_* parser flags
2418 PFNGETCPDATA pfnGetCPData, // in: codepage callback or NULL
2419 PFNEXTERNALHANDLER pfnExternalHandler, // in: external entity callback or NULL
2420 PVOID pvCallbackUser, // in: user param for callbacks
2421 PXMLDOM *ppDom) // out: XMLDOM struct created
2422{
2423 APIRET arc = NO_ERROR;
2424
2425 PXMLDOM pDom = (PXMLDOM)malloc(sizeof(*pDom));
2426 if (!pDom)
2427 arc = ERROR_NOT_ENOUGH_MEMORY;
2428 else
2429 {
2430 PDOMNODE pDocument = NULL;
2431
2432 memset(pDom, 0, sizeof(XMLDOM));
2433
2434 pDom->flParserFlags = flParserFlags;
2435 pDom->pfnGetCPData = pfnGetCPData;
2436 pDom->pfnExternalHandler = pfnExternalHandler;
2437 pDom->pvCallbackUser = pvCallbackUser;
2438
2439 lstInit(&pDom->llElementStack,
2440 TRUE); // auto-free
2441
2442 // create the document node
2443 if (!(arc = xmlCreateDomNode(NULL, // no parent
2444 DOMNODE_DOCUMENT,
2445 NULL,
2446 0,
2447 &pDocument)))
2448 {
2449 // store the document in the DOM
2450 pDom->pDocumentNode = (PDOMDOCUMENTNODE)pDocument;
2451
2452 // push the document on the stack so the handlers
2453 // will append to that
2454 PushElementStack(pDom,
2455 pDocument);
2456
2457 pDom->pParser = XML_ParserCreate(NULL);
2458
2459 if (!pDom->pParser)
2460 arc = ERROR_NOT_ENOUGH_MEMORY;
2461 else
2462 {
2463 if (pfnGetCPData)
2464 XML_SetUnknownEncodingHandler(pDom->pParser,
2465 UnknownEncodingHandler,
2466 pDom); // user data
2467
2468 XML_SetParamEntityParsing(pDom->pParser,
2469 XML_PARAM_ENTITY_PARSING_ALWAYS);
2470
2471 XML_SetElementHandler(pDom->pParser,
2472 StartElementHandler,
2473 EndElementHandler);
2474
2475 XML_SetCharacterDataHandler(pDom->pParser,
2476 CharacterDataHandler);
2477
2478 // XML_SetProcessingInstructionHandler(XML_Parser parser,
2479 // XML_ProcessingInstructionHandler handler);
2480
2481
2482 if (flParserFlags & DF_PARSECOMMENTS)
2483 XML_SetCommentHandler(pDom->pParser,
2484 CommentHandler);
2485
2486 if (pfnExternalHandler)
2487 XML_SetExternalEntityRefHandler(pDom->pParser,
2488 ExternalEntityRefHandler);
2489
2490 if (flParserFlags & DF_PARSEDTD)
2491 {
2492 XML_SetDoctypeDeclHandler(pDom->pParser,
2493 StartDoctypeDeclHandler,
2494 EndDoctypeDeclHandler);
2495
2496 XML_SetNotationDeclHandler(pDom->pParser,
2497 NotationDeclHandler);
2498
2499 XML_SetElementDeclHandler(pDom->pParser,
2500 ElementDeclHandler);
2501
2502 XML_SetAttlistDeclHandler(pDom->pParser,
2503 AttlistDeclHandler);
2504
2505 XML_SetEntityDeclHandler(pDom->pParser,
2506 EntityDeclHandler);
2507
2508 XML_SetParamEntityParsing(pDom->pParser,
2509 XML_PARAM_ENTITY_PARSING_ALWAYS);
2510 }
2511
2512 // XML_SetXmlDeclHandler ... do we care for this? I guess not
2513
2514 // pass the XMLDOM as user data to the handlers
2515 XML_SetUserData(pDom->pParser,
2516 pDom);
2517 }
2518 }
2519 }
2520
2521 if (arc == NO_ERROR)
2522 *ppDom = pDom;
2523 else
2524 xmlFreeDOM(pDom);
2525
2526 return (arc);
2527}
2528
2529/*
2530 *@@ xmlParse:
2531 * parses another chunk of XML data.
2532 *
2533 * If (fIsLast == TRUE), the internal @expat parser
2534 * will be freed, but not the DOM itself.
2535 *
2536 * You can pass an XML document to this function
2537 * in one flush. Set fIsLast = TRUE on the first
2538 * and only call then.
2539 *
2540 * This returns NO_ERROR if the chunk was successfully
2541 * parsed. Otherwise one of the following errors is
2542 * returned:
2543 *
2544 * -- ERROR_INVALID_PARAMETER
2545 *
2546 * -- ERROR_DOM_PARSING: an @expat parsing error occured.
2547 * This might also be memory problems.
2548 * With this error code, you will find specific
2549 * error information in the XMLDOM fields.
2550 *
2551 * -- ERROR_DOM_VALIDITY: the document is not @valid.
2552 * This can only happen if @DTD parsing was enabled
2553 * with xmlCreateDOM.
2554 * With this error code, you will find specific
2555 * error information in the XMLDOM fields.
2556 *
2557 *@@added V0.9.9 (2001-02-14) [umoeller]
2558 */
2559
2560APIRET xmlParse(PXMLDOM pDom, // in: DOM created by xmlCreateDOM
2561 const char *pcszBuf, // in: chunk of XML document data (or full document)
2562 ULONG cb, // in: size of that chunk (required)
2563 BOOL fIsLast) // in: set to TRUE if this is the last chunk
2564{
2565 APIRET arc = NO_ERROR;
2566
2567 if (!pDom)
2568 arc = ERROR_INVALID_PARAMETER;
2569 else
2570 {
2571 // go parse then
2572 if (!XML_Parse(pDom->pParser,
2573 pcszBuf,
2574 cb,
2575 fIsLast))
2576 {
2577 // expat parsing error:
2578 xmlSetError(pDom,
2579 XML_GetErrorCode(pDom->pParser),
2580 NULL,
2581 FALSE);
2582
2583 if (pDom->pDocumentNode)
2584 {
2585 xmlDeleteNode((PNODEBASE)pDom->pDocumentNode);
2586 pDom->pDocumentNode = NULL;
2587 }
2588
2589 arc = ERROR_DOM_PARSING;
2590 }
2591 else if (pDom->fInvalid)
2592 {
2593 // expat was doing OK, but the handlers' validation failed:
2594 arc = ERROR_DOM_VALIDITY;
2595 // error info has already been set
2596 }
2597 else
2598 // expat was doing OK, but maybe we have integrity errors
2599 // from our DOM callbacks:
2600 if (pDom->arcDOM)
2601 arc = pDom->arcDOM;
2602
2603 if (arc != NO_ERROR || fIsLast)
2604 {
2605 // last call or error: clean up
2606 XML_ParserFree(pDom->pParser);
2607 pDom->pParser = NULL;
2608
2609 // clean up the stack (but not the DOM itself)
2610 lstClear(&pDom->llElementStack);
2611 }
2612 }
2613
2614 return (arc);
2615}
2616
2617/*
2618 *@@ xmlFreeDOM:
2619 * cleans up all resources allocated by
2620 * xmlCreateDOM and xmlParse, including
2621 * the entire DOM tree.
2622 *
2623 * If you wish to keep any data, make
2624 * a copy of the respective pointers in pDom
2625 * or subitems and set them to NULL before
2626 * calling this function.
2627 *
2628 *@@added V0.9.9 (2001-02-14) [umoeller]
2629 */
2630
2631APIRET xmlFreeDOM(PXMLDOM pDom)
2632{
2633 APIRET arc = NO_ERROR;
2634 if (pDom)
2635 {
2636 // if the parser is still alive for some reason, close it.
2637 if (pDom->pParser)
2638 {
2639 XML_ParserFree(pDom->pParser);
2640 pDom->pParser = NULL;
2641 }
2642
2643 xmlDeleteNode((PNODEBASE)pDom->pDocumentNode);
2644
2645 if (pDom->pxstrSystemID)
2646 xstrFree(&pDom->pxstrSystemID);
2647 if (pDom->pxstrFailingNode)
2648 xstrFree(&pDom->pxstrFailingNode);
2649
2650 lstClear(&pDom->llElementStack);
2651
2652 free(pDom);
2653 }
2654
2655 return (arc);
2656}
2657
2658/* ******************************************************************
2659 *
2660 * DOM lookup
2661 *
2662 ********************************************************************/
2663
2664/*
2665 *@@ xmlFindElementDecl:
2666 * returns the _CMELEMENTDECLNODE for the element
2667 * with the specified name or NULL if there's none.
2668 *
2669 *@@added V0.9.9 (2001-02-16) [umoeller]
2670 */
2671
2672PCMELEMENTDECLNODE xmlFindElementDecl(PXMLDOM pDom,
2673 const XSTRING *pcstrElementName)
2674{
2675 PCMELEMENTDECLNODE pElementDecl = NULL;
2676
2677 PDOMDOCTYPENODE pDocTypeNode = pDom->pDocTypeNode;
2678 if ( (pDocTypeNode)
2679 && (pcstrElementName)
2680 && (pcstrElementName->ulLength)
2681 )
2682 {
2683 pElementDecl = (PCMELEMENTDECLNODE)treeFind(
2684 pDocTypeNode->ElementDeclsTree,
2685 (ULONG)pcstrElementName,
2686 CompareXStrings);
2687 }
2688
2689 return (pElementDecl);
2690}
2691
2692/*
2693 *@@ xmlFindAttribDeclBase:
2694 * returns the _CMATTRIBUTEDECLBASE for the specified
2695 * element name, or NULL if none exists.
2696 *
2697 * To find a specific attribute declaration from both
2698 * an element and an attribute name, use xmlFindAttribDecl
2699 * instead.
2700 *
2701 *@@added V0.9.9 (2001-02-16) [umoeller]
2702 */
2703
2704PCMATTRIBUTEDECLBASE xmlFindAttribDeclBase(PXMLDOM pDom,
2705 const XSTRING *pstrElementName)
2706{
2707 PDOMDOCTYPENODE pDocTypeNode = pDom->pDocTypeNode;
2708 if ( (pDocTypeNode)
2709 && (pstrElementName)
2710 && (pstrElementName->ulLength)
2711 )
2712 {
2713 return ((PCMATTRIBUTEDECLBASE)treeFind(
2714 pDocTypeNode->AttribDeclBasesTree,
2715 (ULONG)pstrElementName,
2716 CompareXStrings));
2717 }
2718
2719 return (NULL);
2720}
2721
2722/*
2723 *@@ xmlFindAttribDecl:
2724 * returns the _CMATTRIBUTEDEDECL for the specified
2725 * element and attribute name, or NULL if none exists.
2726 *
2727 *@@added V0.9.9 (2001-02-16) [umoeller]
2728 */
2729
2730PCMATTRIBUTEDECL xmlFindAttribDecl(PXMLDOM pDom,
2731 const XSTRING *pstrElementName,
2732 const XSTRING *pstrAttribName,
2733 PCMATTRIBUTEDECLBASE *ppAttribDeclBase)
2734 // in/out: attr decl base cache;
2735 // the pointer pointed to by this
2736 // must be NULL on the first call
2737{
2738 if (pstrElementName && pstrAttribName)
2739 {
2740 if (!*ppAttribDeclBase)
2741 // first call for this:
2742 *ppAttribDeclBase = xmlFindAttribDeclBase(pDom,
2743 pstrElementName);
2744 if (*ppAttribDeclBase)
2745 {
2746 return ((PCMATTRIBUTEDECL)treeFind(
2747 ((**ppAttribDeclBase).AttribDeclsTree),
2748 (ULONG)pstrAttribName,
2749 CompareXStrings));
2750 }
2751 }
2752
2753 return (NULL);
2754}
2755
2756/*
2757 *@@ xmlGetRootElement:
2758 * returns the root element node from the specified
2759 * DOM. Useful helper to start enumerating elements.
2760 *
2761 *@@added V0.9.11 (2001-04-22) [umoeller]
2762 */
2763
2764PDOMNODE xmlGetRootElement(PXMLDOM pDom)
2765{
2766 PDOMDOCUMENTNODE pDocumentNode;
2767 PLISTNODE pListNode;
2768 if ( (pDom)
2769 && (pDocumentNode = pDom->pDocumentNode)
2770 && (pListNode = lstQueryFirstNode(&pDocumentNode->DomNode.llChildren))
2771 )
2772 {
2773 return ((PDOMNODE)pListNode->pItemData);
2774 }
2775
2776 return (NULL);
2777}
2778
2779/*
2780 *@@ xmlGetFirstChild:
2781 * returns the first child node of pDomNode.
2782 * See _DOMNODE for what a "child" can be for the
2783 * various node types.
2784 *
2785 *@@added V0.9.9 (2001-02-14) [umoeller]
2786 */
2787
2788PDOMNODE xmlGetFirstChild(PDOMNODE pDomNode)
2789{
2790 PLISTNODE pListNode = lstQueryFirstNode(&pDomNode->llChildren);
2791 if (pListNode)
2792 return ((PDOMNODE)pListNode->pItemData);
2793
2794 return (0);
2795}
2796
2797/*
2798 *@@ xmlGetLastChild:
2799 * returns the last child node of pDomNode.
2800 * See _DOMNODE for what a "child" can be for the
2801 * various node types.
2802 *
2803 *@@added V0.9.9 (2001-02-14) [umoeller]
2804 */
2805
2806PDOMNODE xmlGetLastChild(PDOMNODE pDomNode)
2807{
2808 PLISTNODE pListNode = lstQueryLastNode(&pDomNode->llChildren);
2809 if (pListNode)
2810 return ((PDOMNODE)pListNode->pItemData);
2811
2812 return (0);
2813}
2814
2815/*
2816 *@@ xmlGetFirstText:
2817 * returns the first text (character data) node
2818 * of pElement or NULL if there's none.
2819 *
2820 *@@added V0.9.11 (2001-04-22) [umoeller]
2821 */
2822
2823PDOMNODE xmlGetFirstText(PDOMNODE pElement)
2824{
2825 PLISTNODE pNode;
2826 PDOMNODE pDomNodeThis;
2827
2828 for (pNode = lstQueryFirstNode(&pElement->llChildren);
2829 pNode;
2830 pNode = pNode->pNext)
2831 {
2832 if ( (pDomNodeThis = (PDOMNODE)pNode->pItemData)
2833 && (pDomNodeThis->NodeBase.ulNodeType == DOMNODE_TEXT)
2834 )
2835 return (pDomNodeThis);
2836 }
2837
2838 return (NULL);
2839}
2840
2841/*
2842 *@@ xmlGetElementsByTagName:
2843 * returns a linked list of _DOMNODE nodes which
2844 * match the specified element name. The special name
2845 * "*" matches all elements.
2846 *
2847 * pParent must be the parent element DOMNODE...
2848 * the only allowed exception is
2849 *
2850 * The caller must free the list by calling lstFree.
2851 * Returns NULL if no such elements could be found.
2852 *
2853 *@@added V0.9.9 (2001-02-14) [umoeller]
2854 */
2855
2856PLINKLIST xmlGetElementsByTagName(PDOMNODE pParent,
2857 const char *pcszName)
2858{
2859 // APIRET arc = NO_ERROR;
2860
2861 PLINKLIST pll = lstCreate(FALSE); // no free
2862 if (pll)
2863 {
2864 ULONG cItems = 0;
2865 BOOL fFindAll = !strcmp(pcszName, "*");
2866
2867 PLISTNODE pNode;
2868 PDOMNODE pDomNodeThis;
2869
2870 for (pNode = lstQueryFirstNode(&pParent->llChildren);
2871 pNode;
2872 pNode = pNode->pNext)
2873 {
2874 if ( (pDomNodeThis = (PDOMNODE)pNode->pItemData)
2875 && (pDomNodeThis->NodeBase.ulNodeType == DOMNODE_ELEMENT)
2876 && ( fFindAll
2877 || (!strcmp(pcszName, pDomNodeThis->NodeBase.strNodeName.psz))
2878 )
2879 )
2880 {
2881 // element matches:
2882 lstAppendItem(pll, pDomNodeThis);
2883 cItems++;
2884 }
2885 }
2886
2887 if (cItems)
2888 return (pll);
2889 else
2890 lstFree(&pll);
2891 }
2892
2893 return (0);
2894}
2895
2896/*
2897 *@@ xmlGetAttribute:
2898 * returns the value of pElement's attribute
2899 * with the given name or NULL.
2900 *
2901 * This is a const pointer into the element's
2902 * attribute list.
2903 *
2904 *@@added V0.9.11 (2001-04-22) [umoeller]
2905 */
2906
2907const XSTRING* xmlGetAttribute(PDOMNODE pElement,
2908 const char *pcszAttribName)
2909{
2910 XSTRING str;
2911 PDOMNODE pAttrNode;
2912 xstrInitSet(&str, (PSZ)pcszAttribName);
2913 // note, cheap trick: no malloc here, but we need
2914 // an XSTRING for treeFind
2915 pAttrNode = (PDOMNODE)treeFind(pElement->AttributesMap,
2916 (ULONG)&str,
2917 CompareXStrings);
2918 if (pAttrNode)
2919 return (pAttrNode->pstrNodeValue);
2920
2921 return (NULL);
2922}
2923
2924/* ******************************************************************
2925 *
2926 * DOM build
2927 *
2928 ********************************************************************/
2929
2930/*
2931 *@@ xmlCreateDocument:
2932 * creates a new XML document.
2933 *
2934 * This is the first step in creating a DOM
2935 * tree in memory.
2936 *
2937 * This function creates a DOCUMENT node
2938 * (which is returned) and a root ELEMENT node
2939 * within the document. For convenience, the
2940 * root ELEMENT node is returned as well.
2941 *
2942 * This does not create a DOCTYPE node in
2943 * the document.
2944 *
2945 * After this, you can add sub-elements and
2946 * attributes to the root element using
2947 * xmlCreateElementNode and xmlCreateAttributeNode.
2948 *
2949 * Use xmlDeleteNode on the DOCUMENT node
2950 * to delete the entire DOM tree.
2951 *
2952 *@@added V0.9.12 (2001-05-21) [umoeller]
2953 */
2954
2955APIRET xmlCreateDocument(const char *pcszRootElementName, // in: root element name
2956 PDOMDOCUMENTNODE *ppDocument, // out: DOCUMENT node
2957 PDOMNODE *ppRootElement) // out: root ELEMENT node within DOCUMENT
2958{
2959 APIRET arc = NO_ERROR;
2960 PDOMDOCUMENTNODE pDocument = NULL;
2961 PDOMNODE pRootElement = NULL;
2962
2963 if ( (!pcszRootElementName) || (!ppDocument) || (!ppRootElement) )
2964 arc = ERROR_INVALID_PARAMETER;
2965 else
2966 // create the document node
2967 if (!(arc = xmlCreateDomNode(NULL, // no parent
2968 DOMNODE_DOCUMENT,
2969 NULL,
2970 0,
2971 (PDOMNODE*)&pDocument)))
2972 if (!(arc = xmlCreateDomNode((PDOMNODE)pDocument, // parent
2973 DOMNODE_ELEMENT,
2974 pcszRootElementName,
2975 0,
2976 &pRootElement)))
2977 {
2978 *ppDocument = pDocument;
2979 *ppRootElement = pRootElement;
2980 }
2981
2982 return (arc);
2983}
2984
2985/*
2986 *@@ WriteNodes:
2987 * internal helper for writing out the nodes.
2988 *
2989 *@@added V0.9.12 (2001-05-21) [umoeller]
2990 */
2991
2992VOID WriteNodes(PXSTRING pxstr,
2993 PDOMNODE pDomNode) // in: node whose children are to be written (initially DOCUMENT)
2994{
2995 PLISTNODE pListNode;
2996
2997 BOOL fMixedContent = (xmlGetFirstText(pDomNode) != NULL);
2998
2999 for (pListNode = lstQueryFirstNode(&pDomNode->llChildren);
3000 (pListNode);
3001 pListNode = pListNode->pNext)
3002 {
3003 PDOMNODE pChildNode = (PDOMNODE)pListNode->pItemData;
3004
3005 switch (pChildNode->NodeBase.ulNodeType)
3006 {
3007 case DOMNODE_ELEMENT:
3008 {
3009 PDOMNODE pAttribNode;
3010 // write out opening ELEMENT tag
3011 // add a line break if this does NOT have mixed
3012 // content
3013 if (!fMixedContent)
3014 xstrcatc(pxstr, '\n');
3015
3016 xstrcatc(pxstr, '<');
3017 xstrcats(pxstr, &pChildNode->NodeBase.strNodeName);
3018
3019 // go through attributes
3020 for (pAttribNode = (PDOMNODE)treeFirst(pChildNode->AttributesMap);
3021 (pAttribNode);
3022 pAttribNode = (PDOMNODE)treeNext((TREE*)pAttribNode))
3023 {
3024 xstrcat(pxstr, "\n ", 0);
3025 xstrcats(pxstr, &pAttribNode->NodeBase.strNodeName);
3026 xstrcat(pxstr, "=\"", 0);
3027 xstrcats(pxstr, pAttribNode->pstrNodeValue);
3028 xstrcatc(pxstr, '\"');
3029 }
3030
3031 // now check... do we have child nodes?
3032 if (lstCountItems(&pChildNode->llChildren))
3033 {
3034 // yes:
3035 xstrcatc(pxstr, '>');
3036
3037 // recurse into this child element
3038 WriteNodes(pxstr, pChildNode);
3039
3040 if (!fMixedContent)
3041 xstrcatc(pxstr, '\n');
3042
3043 // write closing tag
3044 xstrcat(pxstr, "</", 0);
3045 xstrcats(pxstr, &pChildNode->NodeBase.strNodeName);
3046 xstrcatc(pxstr, '>');
3047 }
3048 else
3049 {
3050 // no child nodes:
3051 // mark this tag as "empty"
3052 xstrcat(pxstr, "/>", 0);
3053 }
3054 }
3055 break;
3056
3057 case DOMNODE_TEXT:
3058 case DOMNODE_COMMENT:
3059 // that's simple
3060 xstrcats(pxstr, pChildNode->pstrNodeValue);
3061 break;
3062
3063 case DOMNODE_DOCUMENT_TYPE:
3064 // @@todo
3065 break;
3066
3067 }
3068 }
3069}
3070
3071/*
3072 *@@ xmlWriteDocument:
3073 * creates a complete XML document in the specified
3074 * string buffer from the specified DOMDOCUMENTNODE.
3075 *
3076 * This creates a full XML document, starting with
3077 * the <?xml...?> header, the DTD (if present),
3078 * and the elements and attributes.
3079 *
3080 * The input XSTRING must be initialized. Its
3081 * contents will be overwritten, if any exists.
3082 *
3083 * Sooo... to write a full XML document to disk,
3084 * do the following:
3085 *
3086 * 1) Call xmlCreateDocument to have an empty
3087 * document with a root element created.
3088 *
3089 * 2) Add elements, subelements, and attributes
3090 * using xmlCreateElementNode and
3091 * xmlCreateAttributeNode.
3092 *
3093 * 3) Call xmlWriteDocument to have the XML
3094 * document written into an XSTRING.
3095 *
3096 * 4) Write the XSTRING to disk, e.g. using
3097 * fwrite().
3098 *
3099 * Note: You can also use doshWriteTextFile,
3100 * but you should then first convert the
3101 * line format using xstrConvertLineFormat.
3102 *
3103 * Example:
3104 *
3105 + APIRET arc = NO_ERROR;
3106 + PDOMDOCUMENTNODE pDocument = NULL;
3107 + PDOMNODE pRootElement = NULL;
3108 +
3109 + // create a DOM
3110 + if (!(arc = xmlCreateDocument("MYROOTNODE",
3111 + &pDocument,
3112 + &pRootElement)))
3113 + {
3114 + // add subelements to the root element
3115 + PDOMNODE pSubelement;
3116 + if (!(arc = xmlCreateElementNode(pRootElement,
3117 + "MYSUBELEMENT",
3118 + &pSubelement)))
3119 + {
3120 + // add an attribute
3121 + PDOMNODE pAttribute;
3122 + if (!(arc = xmlCreateAttributeNode(pSubElement,
3123 + "MYATTRIB",
3124 + "VALUE",
3125 + &pAttribute)))
3126 + {
3127 + // alright, turn this into a string
3128 + XSTRING str;
3129 + xstrInit(&str, 1000);
3130 + if (!(arc = xmlWriteDocument(pDocument,
3131 + "ISO-8859-1",
3132 + NULL, // or DOCTYPE
3133 + &str)))
3134 + {
3135 + FILE *file = fopen("myfile.xml", "w");
3136 + fwrite(str.psz,
3137 + 1,
3138 + str.ulLength,
3139 + file);
3140 + fclose(file);
3141 + }
3142 + }
3143 + }
3144 +
3145 + // this kills the entire tree
3146 + xmlDeleteNode((PNODEBASE)pDocument);
3147 +
3148 + }
3149 +
3150 *
3151 * A note about whitespace handling. Presently, this
3152 * adds line breaks after the opening tag of an
3153 * element if the element has element content only.
3154 * However, if the element has mixed content, this
3155 * line break is NOT automatically added because
3156 * white space may then be significant.
3157 *
3158 *@@added V0.9.12 (2001-05-21) [umoeller]
3159 */
3160
3161APIRET xmlWriteDocument(PDOMDOCUMENTNODE pDocument, // in: document node
3162 const char *pcszEncoding, // in: encoding string (e.g. "ISO-8859-1")
3163 const char *pcszDoctype, // in: entire DOCTYPE statement or NULL
3164 PXSTRING pxstr) // out: document
3165{
3166 APIRET arc = NO_ERROR;
3167
3168 if ( (!pDocument) || (!pcszEncoding) || (!pxstr) )
3169 arc = ERROR_INVALID_PARAMETER;
3170 else
3171 {
3172 // <?xml version="1.0" encoding="ISO-8859-1"?>
3173 xstrcpy(pxstr, "<?xml version=\"1.0\" encoding=\"", 0);
3174 xstrcat(pxstr, pcszEncoding, 0);
3175 xstrcat(pxstr, "\"?>\n", 0);
3176
3177 // write entire DOCTYPE statement
3178 if (pcszDoctype)
3179 {
3180 xstrcatc(pxstr, '\n');
3181 xstrcat(pxstr, pcszDoctype, 0);
3182 xstrcatc(pxstr, '\n');
3183 }
3184
3185 // write out children
3186 WriteNodes(pxstr, (PDOMNODE)pDocument);
3187
3188 xstrcatc(pxstr, '\n');
3189 }
3190
3191 return (arc);
3192}
3193
3194
Note: See TracBrowser for help on using the repository browser.