1/* 2 * "$Id: mxml-file.c,v 1.10 2008/07/20 01:12:15 easysw Exp $" 3 * 4 * File loading code for mini-XML, a small XML-like file parsing library. 5 * 6 * Copyright 2003 by Michael Sweet. 7 * 8 * This program is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU Library General Public 10 * License as published by the Free Software Foundation; either 11 * version 2, or (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * Contents: 19 * 20 * stp_mxmlLoadFile() - Load a file into an XML node tree. 21 * stp_mxmlLoadFromFile() - Load a file into an XML node tree. 22 * stp_mxmlLoadString() - Load a string into an XML node tree. 23 * stp_mxmlSaveAllocString() - Save an XML node tree to an allocated string. 24 * stp_mxmlSaveFile() - Save an XML tree to a file. 25 * stp_mxmlSaveString() - Save an XML node tree to a string. 26 * mxml_add_char() - Add a character to a buffer, expanding as needed. 27 * mxml_file_getc() - Get a character from a file. 28 * mxml_load_data() - Load data into an XML node tree. 29 * mxml_parse_element() - Parse an element for any attributes... 30 * mxml_string_getc() - Get a character from a string. 31 * mxml_write_node() - Save an XML node to a file. 32 * mxml_write_string() - Write a string, escaping & and < as needed. 33 * mxml_write_ws() - Do whitespace callback... 34 */ 35 36/* 37 * Include necessary headers... 38 */ 39 40#include <gutenprint/mxml.h> 41#include "config.h" 42 43 44/* 45 * Local functions... 46 */ 47 48static int mxml_add_char(int ch, char **ptr, char **buffer, 49 int *bufsize); 50static int mxml_file_getc(void *p); 51static int mxml_file_putc(int ch, void *p); 52static stp_mxml_node_t *mxml_load_data(stp_mxml_node_t *top, void *p, 53 stp_mxml_type_t (*cb)(stp_mxml_node_t *), 54 int (*getc_cb)(void *)); 55static int mxml_parse_element(stp_mxml_node_t *node, void *p, 56 int (*getc_cb)(void *)); 57static int mxml_string_getc(void *p); 58static int mxml_string_putc(int ch, void *p); 59static int mxml_write_node(stp_mxml_node_t *node, void *p, 60 int (*cb)(stp_mxml_node_t *, int), 61 int col, 62 int (*putc_cb)(int, void *)); 63static int mxml_write_string(const char *s, void *p, 64 int (*putc_cb)(int, void *)); 65static int mxml_write_ws(stp_mxml_node_t *node, void *p, 66 int (*cb)(stp_mxml_node_t *, int), int ws, 67 int col, int (*putc_cb)(int, void *)); 68 69 70/* 71 * 'stp_mxmlLoadFile()' - Load a file into an XML node tree. 72 * 73 * The nodes in the specified file are added to the specified top node. 74 * If no top node is provided, the XML file MUST be well-formed with a 75 * single parent node like <?xml> for the entire file. The callback 76 * function returns the value type that should be used for child nodes. 77 * If STP_MXML_NO_CALLBACK is specified then all child nodes will be either 78 * STP_MXML_ELEMENT or STP_MXML_TEXT nodes. 79 */ 80 81stp_mxml_node_t * /* O - First node or NULL if the file could not be read. */ 82stp_mxmlLoadFile(stp_mxml_node_t *top, /* I - Top node */ 83 FILE *fp, /* I - File to read from */ 84 stp_mxml_type_t (*cb)(stp_mxml_node_t *)) 85 /* I - Callback function or STP_MXML_NO_CALLBACK */ 86{ 87 return (mxml_load_data(top, fp, cb, mxml_file_getc)); 88} 89 90/* 91 * 'stp_mxmlLoadFromFile()' - Load a named file into an XML node tree. 92 * 93 * The nodes in the specified file are added to the specified top node. 94 * If no top node is provided, the XML file MUST be well-formed with a 95 * single parent node like <?xml> for the entire file. The callback 96 * function returns the value type that should be used for child nodes. 97 * If STP_MXML_NO_CALLBACK is specified then all child nodes will be either 98 * STP_MXML_ELEMENT or STP_MXML_TEXT nodes. 99 */ 100 101stp_mxml_node_t * /* O - First node or NULL if the file could not be read. */ 102stp_mxmlLoadFromFile(stp_mxml_node_t *top, /* I - Top node */ 103 const char *file, /* I - File to read from */ 104 stp_mxml_type_t (*cb)(stp_mxml_node_t *)) 105 /* I - Callback function or STP_MXML_NO_CALLBACK */ 106{ 107 FILE *fp = fopen(file, "r"); 108 stp_mxml_node_t *doc; 109 if (! fp) 110 return NULL; 111 doc = stp_mxmlLoadFile(top, fp, cb); 112 fclose(fp); 113 return doc; 114} 115 116 117/* 118 * 'stp_mxmlLoadString()' - Load a string into an XML node tree. 119 * 120 * The nodes in the specified string are added to the specified top node. 121 * If no top node is provided, the XML string MUST be well-formed with a 122 * single parent node like <?xml> for the entire string. The callback 123 * function returns the value type that should be used for child nodes. 124 * If STP_MXML_NO_CALLBACK is specified then all child nodes will be either 125 * STP_MXML_ELEMENT or STP_MXML_TEXT nodes. 126 */ 127 128stp_mxml_node_t * /* O - First node or NULL if the string has errors. */ 129stp_mxmlLoadString(stp_mxml_node_t *top, /* I - Top node */ 130 const char *s, /* I - String to load */ 131 stp_mxml_type_t (*cb)(stp_mxml_node_t *)) 132 /* I - Callback function or STP_MXML_NO_CALLBACK */ 133{ 134 return (mxml_load_data(top, &s, cb, mxml_string_getc)); 135} 136 137 138/* 139 * 'stp_mxmlSaveAllocString()' - Save an XML node tree to an allocated string. 140 * 141 * This function returns a pointer to a string containing the textual 142 * representation of the XML node tree. The string should be freed 143 * using the free() function when you are done with it. NULL is returned 144 * if the node would produce an empty string or if the string cannot be 145 * allocated. 146 */ 147 148char * /* O - Allocated string or NULL */ 149stp_mxmlSaveAllocString(stp_mxml_node_t *node, /* I - Node to write */ 150 int (*cb)(stp_mxml_node_t *, int)) 151 /* I - Whitespace callback or STP_MXML_NO_CALLBACK */ 152{ 153 int bytes; /* Required bytes */ 154 char buffer[8192]; /* Temporary buffer */ 155 char *s; /* Allocated string */ 156 157 158 /* 159 * Write the node to the temporary buffer... 160 */ 161 162 bytes = stp_mxmlSaveString(node, buffer, sizeof(buffer), cb); 163 164 if (bytes <= 0) 165 return (NULL); 166 167 if (bytes < (int)(sizeof(buffer) - 1)) 168 { 169 /* 170 * Node fit inside the buffer, so just duplicate that string and 171 * return... 172 */ 173 174 return (strdup(buffer)); 175 } 176 177 /* 178 * Allocate a buffer of the required size and save the node to the 179 * new buffer... 180 */ 181 182 if ((s = malloc(bytes + 1)) == NULL) 183 return (NULL); 184 185 stp_mxmlSaveString(node, s, bytes + 1, cb); 186 187 /* 188 * Return the allocated string... 189 */ 190 191 return (s); 192} 193 194 195/* 196 * 'stp_mxmlSaveFile()' - Save an XML tree to a file. 197 * 198 * The callback argument specifies a function that returns a whitespace 199 * character or nul (0) before and after each element. If STP_MXML_NO_CALLBACK 200 * is specified, whitespace will only be added before STP_MXML_TEXT nodes 201 * with leading whitespace and before attribute names inside opening 202 * element tags. 203 */ 204 205int /* O - 0 on success, -1 on error. */ 206stp_mxmlSaveFile(stp_mxml_node_t *node, /* I - Node to write */ 207 FILE *fp, /* I - File to write to */ 208 int (*cb)(stp_mxml_node_t *, int)) 209 /* I - Whitespace callback or STP_MXML_NO_CALLBACK */ 210{ 211 int col; /* Final column */ 212 213 214 /* 215 * Write the node... 216 */ 217 218 if ((col = mxml_write_node(node, fp, cb, 0, mxml_file_putc)) < 0) 219 return (-1); 220 221 if (col > 0) 222 if (putc('\n', fp) < 0) 223 return (-1); 224 225 /* 226 * Return 0 (success)... 227 */ 228 229 return (0); 230} 231 232int /* O - 0 on success, -1 on error. */ 233stp_mxmlSaveToFile(stp_mxml_node_t *node, /* I - Node to write */ 234 const char *file, /* I - File to write to */ 235 int (*cb)(stp_mxml_node_t *, int)) 236 /* I - Whitespace callback or STP_MXML_NO_CALLBACK */ 237{ 238 FILE *fp = fopen(file, "w"); 239 int answer; 240 int status; 241 if (!fp) 242 return -1; 243 answer = stp_mxmlSaveFile(node, fp, cb); 244 status = fclose(fp); 245 if (status != 0) 246 return -1; 247 else 248 return answer; 249} 250 251/* 252 * 'stp_mxmlSaveString()' - Save an XML node tree to a string. 253 * 254 * This function returns the total number of bytes that would be 255 * required for the string but only copies (bufsize - 1) characters 256 * into the specified buffer. 257 */ 258 259int /* O - Size of string */ 260stp_mxmlSaveString(stp_mxml_node_t *node, /* I - Node to write */ 261 char *buffer, /* I - String buffer */ 262 int bufsize, /* I - Size of string buffer */ 263 int (*cb)(stp_mxml_node_t *, int)) 264 /* I - Whitespace callback or STP_MXML_NO_CALLBACK */ 265{ 266 int col; /* Final column */ 267 char *ptr[2]; /* Pointers for putc_cb */ 268 269 270 /* 271 * Write the node... 272 */ 273 274 ptr[0] = buffer; 275 ptr[1] = buffer + bufsize; 276 277 if ((col = mxml_write_node(node, ptr, cb, 0, mxml_string_putc)) < 0) 278 return (-1); 279 280 if (col > 0) 281 mxml_string_putc('\n', ptr); 282 283 /* 284 * Nul-terminate the buffer... 285 */ 286 287 if (ptr[0] >= ptr[1]) 288 buffer[bufsize - 1] = '\0'; 289 else 290 ptr[0][0] = '\0'; 291 292 /* 293 * Return the number of characters... 294 */ 295 296 return (ptr[0] - buffer); 297} 298 299 300/* 301 * 'mxml_add_char()' - Add a character to a buffer, expanding as needed. 302 */ 303 304static int /* O - 0 on success, -1 on error */ 305mxml_add_char(int ch, /* I - Character to add */ 306 char **bufptr, /* IO - Current position in buffer */ 307 char **buffer, /* IO - Current buffer */ 308 int *bufsize) /* IO - Current buffer size */ 309{ 310 char *newbuffer; /* New buffer value */ 311 312 313 if (*bufptr >= (*buffer + *bufsize - 1)) 314 { 315 /* 316 * Increase the size of the buffer... 317 */ 318 319 if (*bufsize < 1024) 320 (*bufsize) *= 2; 321 else 322 (*bufsize) += 1024; 323 324 if ((newbuffer = realloc(*buffer, *bufsize)) == NULL) 325 { 326 free(*buffer); 327 328 fprintf(stderr, "Unable to expand string buffer to %d bytes!\n", 329 *bufsize); 330 331 return (-1); 332 } 333 334 *bufptr = newbuffer + (*bufptr - *buffer); 335 *buffer = newbuffer; 336 } 337 338 *(*bufptr)++ = ch; 339 340 return (0); 341} 342 343 344/* 345 * 'mxml_file_getc()' - Get a character from a file. 346 */ 347 348static int /* O - Character or EOF */ 349mxml_file_getc(void *p) /* I - Pointer to file */ 350{ 351 return (getc((FILE *)p)); 352} 353 354 355/* 356 * 'mxml_file_putc()' - Write a character to a file. 357 */ 358 359static int /* O - 0 on success, -1 on failure */ 360mxml_file_putc(int ch, /* I - Character to write */ 361 void *p) /* I - Pointer to file */ 362{ 363 return (putc(ch, (FILE *)p)); 364} 365 366 367/* 368 * 'mxml_load_data()' - Load data into an XML node tree. 369 */ 370 371static stp_mxml_node_t * /* O - First node or NULL if the file could not be read. */ 372mxml_load_data(stp_mxml_node_t *top, /* I - Top node */ 373 void *p, /* I - Pointer to data */ 374 stp_mxml_type_t (*cb)(stp_mxml_node_t *), 375 /* I - Callback function or STP_MXML_NO_CALLBACK */ 376 int (*getc_cb)(void *)) 377 /* I - Read function */ 378{ 379 stp_mxml_node_t *node, /* Current node */ 380 *parent; /* Current parent node */ 381 int ch, /* Character from file */ 382 whitespace; /* Non-zero if whitespace seen */ 383 char *buffer, /* String buffer */ 384 *bufptr; /* Pointer into buffer */ 385 int bufsize; /* Size of buffer */ 386 stp_mxml_type_t type; /* Current node type */ 387 388 389 /* 390 * Read elements and other nodes from the file... 391 */ 392 393 if ((buffer = malloc(64)) == NULL) 394 { 395 fputs("Unable to allocate string buffer!\n", stderr); 396 return (NULL); 397 } 398 399 bufsize = 64; 400 bufptr = buffer; 401 parent = top; 402 whitespace = 0; 403 404 if (cb && parent) 405 type = (*cb)(parent); 406 else 407 type = STP_MXML_TEXT; 408 409 while ((ch = (*getc_cb)(p)) != EOF) 410 { 411 if ((ch == '<' || (isspace(ch) && type != STP_MXML_OPAQUE)) && bufptr > buffer) 412 { 413 /* 414 * Add a new value node... 415 */ 416 417 *bufptr = '\0'; 418 419 switch (type) 420 { 421 case STP_MXML_INTEGER : 422 node = stp_mxmlNewInteger(parent, strtol(buffer, &bufptr, 0)); 423 break; 424 425 case STP_MXML_OPAQUE : 426 node = stp_mxmlNewOpaque(parent, buffer); 427 break; 428 429 case STP_MXML_REAL : 430 node = stp_mxmlNewReal(parent, strtod(buffer, &bufptr)); 431 break; 432 433 case STP_MXML_TEXT : 434 node = stp_mxmlNewText(parent, whitespace, buffer); 435 break; 436 437 default : /* Should never happen... */ 438 node = NULL; 439 break; 440 } 441 442 if (*bufptr) 443 { 444 /* 445 * Bad integer/real number value... 446 */ 447 448 fprintf(stderr, "Bad %s value '%s' in parent <%s>!\n", 449 type == STP_MXML_INTEGER ? "integer" : "real", buffer, 450 parent ? parent->value.element.name : "null"); 451 break; 452 } 453 454 bufptr = buffer; 455 whitespace = isspace(ch) && type == STP_MXML_TEXT; 456 457 if (!node) 458 { 459 /* 460 * Just print error for now... 461 */ 462 463 fprintf(stderr, "Unable to add value node of type %d to parent <%s>!\n", 464 type, parent ? parent->value.element.name : "null"); 465 break; 466 } 467 } 468 else if (isspace(ch) && type == STP_MXML_TEXT) 469 whitespace = 1; 470 471 /* 472 * Add lone whitespace node if we have an element and existing 473 * whitespace... 474 */ 475 476 if (ch == '<' && whitespace && type == STP_MXML_TEXT) 477 { 478 stp_mxmlNewText(parent, whitespace, ""); 479 whitespace = 0; 480 } 481 482 if (ch == '<') 483 { 484 /* 485 * Start of open/close tag... 486 */ 487 488 bufptr = buffer; 489 490 while ((ch = (*getc_cb)(p)) != EOF) 491 if (isspace(ch) || ch == '>' || (ch == '/' && bufptr > buffer)) 492 break; 493 else if (mxml_add_char(ch, &bufptr, &buffer, &bufsize)) 494 { 495 free(buffer); 496 return (NULL); 497 } 498 else if ((bufptr - buffer) == 3 && !strncmp(buffer, "!--", 3)) 499 break; 500 501 *bufptr = '\0'; 502 503 if (!strcmp(buffer, "!--")) 504 { 505 /* 506 * Gather rest of comment... 507 */ 508 509 while ((ch = (*getc_cb)(p)) != EOF) 510 { 511 if (ch == '>' && bufptr > (buffer + 4) && 512 !strncmp(bufptr - 2, "--", 2)) 513 break; 514 else if (mxml_add_char(ch, &bufptr, &buffer, &bufsize)) 515 { 516 free(buffer); 517 return (NULL); 518 } 519 } 520 521 /* 522 * Error out if we didn't get the whole comment... 523 */ 524 525 if (ch != '>') 526 break; 527 528 /* 529 * Otherwise add this as an element under the current parent... 530 */ 531 532 *bufptr = '\0'; 533 534 if (!stp_mxmlNewElement(parent, buffer)) 535 { 536 /* 537 * Just print error for now... 538 */ 539 540 fprintf(stderr, "Unable to add comment node to parent <%s>!\n", 541 parent ? parent->value.element.name : "null"); 542 break; 543 } 544 } 545 else if (buffer[0] == '!') 546 { 547 /* 548 * Gather rest of declaration... 549 */ 550 551 do 552 { 553 if (ch == '>') 554 break; 555 else if (mxml_add_char(ch, &bufptr, &buffer, &bufsize)) 556 { 557 free(buffer); 558 return (NULL); 559 } 560 } 561 while ((ch = (*getc_cb)(p)) != EOF); 562 563 /* 564 * Error out if we didn't get the whole declaration... 565 */ 566 567 if (ch != '>') 568 break; 569 570 /* 571 * Otherwise add this as an element under the current parent... 572 */ 573 574 *bufptr = '\0'; 575 576 node = stp_mxmlNewElement(parent, buffer); 577 if (!node) 578 { 579 /* 580 * Just print error for now... 581 */ 582 583 fprintf(stderr, "Unable to add declaration node to parent <%s>!\n", 584 parent ? parent->value.element.name : "null"); 585 break; 586 } 587 588 /* 589 * Descend into this node, setting the value type as needed... 590 */ 591 592 parent = node; 593 594 if (cb && parent) 595 type = (*cb)(parent); 596 } 597 else if (buffer[0] == '/') 598 { 599 /* 600 * Handle close tag... 601 */ 602 603 if (!parent || strcmp(buffer + 1, parent->value.element.name)) 604 { 605 /* 606 * Close tag doesn't match tree; print an error for now... 607 */ 608 609 fprintf(stderr, "Mismatched close tag <%s> under parent <%s>!\n", 610 buffer, parent ? parent->value.element.name : "(null)"); 611 break; 612 } 613 614 /* 615 * Keep reading until we see >... 616 */ 617 618 while (ch != '>' && ch != EOF) 619 ch = (*getc_cb)(p); 620 621 /* 622 * Ascend into the parent and set the value type as needed... 623 */ 624 625 parent = parent->parent; 626 627 if (cb && parent) 628 type = (*cb)(parent); 629 } 630 else 631 { 632 /* 633 * Handle open tag... 634 */ 635 636 node = stp_mxmlNewElement(parent, buffer); 637 638 if (!node) 639 { 640 /* 641 * Just print error for now... 642 */ 643 644 fprintf(stderr, "Unable to add element node to parent <%s>!\n", 645 parent ? parent->value.element.name : "null"); 646 break; 647 } 648 649 if (isspace(ch)) 650 ch = mxml_parse_element(node, p, getc_cb); 651 else if (ch == '/') 652 { 653 if ((ch = (*getc_cb)(p)) != '>') 654 { 655 fprintf(stderr, "Expected > but got '%c' instead for element <%s/>!\n", 656 ch, buffer); 657 break; 658 } 659 660 ch = '/'; 661 } 662 663 if (ch == EOF) 664 break; 665 666 if (ch != '/') 667 { 668 /* 669 * Descend into this node, setting the value type as needed... 670 */ 671 672 parent = node; 673 674 if (cb && parent) 675 type = (*cb)(parent); 676 } 677 } 678 679 bufptr = buffer; 680 } 681 else if (ch == '&') 682 { 683 /* 684 * Add character entity to current buffer... Currently we only 685 * support <, &, >, , ", &#nnn;, and &#xXXXX;... 686 */ 687 688 char entity[64], /* Entity string */ 689 *entptr; /* Pointer into entity */ 690 691 692 entity[0] = ch; 693 entptr = entity + 1; 694 695 while ((ch = (*getc_cb)(p)) != EOF) 696 if (!isalnum(ch) && ch != '#') 697 break; 698 else if (entptr < (entity + sizeof(entity) - 1)) 699 *entptr++ = ch; 700 else 701 { 702 fprintf(stderr, "Entity name too long under parent <%s>!\n", 703 parent ? parent->value.element.name : "null"); 704 break; 705 } 706 707 *entptr = '\0'; 708 709 if (ch != ';') 710 { 711 fprintf(stderr, "Entity name \"%s\" not terminated under parent <%s>!\n", 712 entity, parent ? parent->value.element.name : "null"); 713 break; 714 } 715 716 if (entity[1] == '#') 717 { 718 if (entity[2] == 'x') 719 ch = strtol(entity + 3, NULL, 16); 720 else 721 ch = strtol(entity + 2, NULL, 10); 722 } 723 else if (!strcmp(entity, "&")) 724 ch = '&'; 725 else if (!strcmp(entity, ">")) 726 ch = '>'; 727 else if (!strcmp(entity, "<")) 728 ch = '<'; 729 else if (!strcmp(entity, " ")) 730 ch = 0xa0; 731 else if (!strcmp(entity, """)) 732 ch = '\"'; 733 else 734 { 735 fprintf(stderr, "Entity name \"%s;\" not supported under parent <%s>!\n", 736 entity, parent ? parent->value.element.name : "null"); 737 break; 738 } 739 740 if (ch < 128) 741 { 742 /* 743 * Plain ASCII doesn't need special encoding... 744 */ 745 746 if (mxml_add_char(ch, &bufptr, &buffer, &bufsize)) 747 { 748 free(buffer); 749 return (NULL); 750 } 751 } 752 else 753 { 754 /* 755 * Use UTF-8 encoding for the Unicode char... 756 */ 757 758 if (ch < 2048) 759 { 760 if (mxml_add_char(0xc0 | (ch >> 6), &bufptr, &buffer, &bufsize)) 761 { 762 free(buffer); 763 return (NULL); 764 } 765 if (mxml_add_char(0x80 | (ch & 63), &bufptr, &buffer, &bufsize)) 766 { 767 free(buffer); 768 return (NULL); 769 } 770 } 771 else if (ch < 65536) 772 { 773 if (mxml_add_char(0xe0 | (ch >> 12), &bufptr, &buffer, &bufsize)) 774 { 775 free(buffer); 776 return (NULL); 777 } 778 if (mxml_add_char(0x80 | ((ch >> 6) & 63), &bufptr, &buffer, &bufsize)) 779 { 780 free(buffer); 781 return (NULL); 782 } 783 if (mxml_add_char(0x80 | (ch & 63), &bufptr, &buffer, &bufsize)) 784 { 785 free(buffer); 786 return (NULL); 787 } 788 } 789 else 790 { 791 if (mxml_add_char(0xf0 | (ch >> 18), &bufptr, &buffer, &bufsize)) 792 { 793 free(buffer); 794 return (NULL); 795 } 796 if (mxml_add_char(0x80 | ((ch >> 12) & 63), &bufptr, &buffer, &bufsize)) 797 { 798 free(buffer); 799 return (NULL); 800 } 801 if (mxml_add_char(0x80 | ((ch >> 6) & 63), &bufptr, &buffer, &bufsize)) 802 { 803 free(buffer); 804 return (NULL); 805 } 806 if (mxml_add_char(0x80 | (ch & 63), &bufptr, &buffer, &bufsize)) 807 { 808 free(buffer); 809 return (NULL); 810 } 811 } 812 } 813 } 814 else if (type == STP_MXML_OPAQUE || !isspace(ch)) 815 { 816 /* 817 * Add character to current buffer... 818 */ 819 820 if (mxml_add_char(ch, &bufptr, &buffer, &bufsize)) 821 { 822 free(buffer); 823 return (NULL); 824 } 825 } 826 } 827 828 /* 829 * Free the string buffer - we don't need it anymore... 830 */ 831 832 free(buffer); 833 834 /* 835 * Find the top element and return it... 836 */ 837 838 if (parent) 839 { 840 while (parent->parent != top) 841 parent = parent->parent; 842 } 843 844 return (parent); 845} 846 847 848/* 849 * 'mxml_parse_element()' - Parse an element for any attributes... 850 */ 851 852static int /* O - Terminating character */ 853mxml_parse_element(stp_mxml_node_t *node, /* I - Element node */ 854 void *p, /* I - Data to read from */ 855 int (*getc_cb)(void *)) 856 /* I - Data callback */ 857{ 858 int ch, /* Current character in file */ 859 quote; /* Quoting character */ 860 char *name, /* Attribute name */ 861 *value, /* Attribute value */ 862 *ptr; /* Pointer into name/value */ 863 int namesize, /* Size of name string */ 864 valsize; /* Size of value string */ 865 866 867 /* 868 * Initialize the name and value buffers... 869 */ 870 871 if ((name = malloc(64)) == NULL) 872 { 873 fputs("Unable to allocate memory for name!\n", stderr); 874 return (EOF); 875 } 876 877 namesize = 64; 878 879 if ((value = malloc(64)) == NULL) 880 { 881 free(name); 882 fputs("Unable to allocate memory for value!\n", stderr); 883 return (EOF); 884 } 885 886 valsize = 64; 887 888 /* 889 * Loop until we hit a >, /, ?, or EOF... 890 */ 891 892 while ((ch = (*getc_cb)(p)) != EOF) 893 { 894#ifdef DEBUG 895 fprintf(stderr, "parse_element: ch='%c'\n", ch); 896#endif /* DEBUG */ 897 898 /* 899 * Skip leading whitespace... 900 */ 901 902 if (isspace(ch)) 903 continue; 904 905 /* 906 * Stop at /, ?, or >... 907 */ 908 909 if (ch == '/' || ch == '?') 910 { 911 /* 912 * Grab the > character and print an error if it isn't there... 913 */ 914 915 quote = (*getc_cb)(p); 916 917 if (quote != '>') 918 { 919 fprintf(stderr, "Expected '>' after '%c' for element %s, but got '%c'!\n", 920 ch, node->value.element.name, quote); 921 ch = EOF; 922 } 923 924 break; 925 } 926 else if (ch == '>') 927 break; 928 929 /* 930 * Read the attribute name... 931 */ 932 933 name[0] = ch; 934 ptr = name + 1; 935 936 while ((ch = (*getc_cb)(p)) != EOF) 937 if (isspace(ch) || ch == '=' || ch == '/' || ch == '>' || ch == '?') 938 break; 939 else if (mxml_add_char(ch, &ptr, &name, &namesize)) 940 { 941 free(name); 942 free(value); 943 return (EOF); 944 } 945 946 *ptr = '\0'; 947 948 if (ch == '=') 949 { 950 /* 951 * Read the attribute value... 952 */ 953 954 if ((ch = (*getc_cb)(p)) == EOF) 955 { 956 fprintf(stderr, "Missing value for attribute '%s' in element %s!\n", 957 name, node->value.element.name); 958 return (EOF); 959 } 960 961 if (ch == '\'' || ch == '\"') 962 { 963 /* 964 * Read quoted value... 965 */ 966 967 quote = ch; 968 ptr = value; 969 970 while ((ch = (*getc_cb)(p)) != EOF) 971 if (ch == quote) 972 break; 973 else if (mxml_add_char(ch, &ptr, &value, &valsize)) 974 { 975 free(name); 976 free(value); 977 return (EOF); 978 } 979 980 *ptr = '\0'; 981 } 982 else 983 { 984 /* 985 * Read unquoted value... 986 */ 987 988 value[0] = ch; 989 ptr = value + 1; 990 991 while ((ch = (*getc_cb)(p)) != EOF) 992 if (isspace(ch) || ch == '=' || ch == '/' || ch == '>') 993 break; 994 else if (mxml_add_char(ch, &ptr, &value, &valsize)) 995 { 996 free(name); 997 free(value); 998 return (EOF); 999 } 1000 1001 *ptr = '\0'; 1002 } 1003 } 1004 else 1005 value[0] = '\0'; 1006 1007 /* 1008 * Save last character in case we need it... 1009 */ 1010 1011 if (ch == '/' || ch == '?') 1012 { 1013 /* 1014 * Grab the > character and print an error if it isn't there... 1015 */ 1016 1017 quote = (*getc_cb)(p); 1018 1019 if (quote != '>') 1020 { 1021 fprintf(stderr, "Expected '>' after '%c' for element %s, but got '%c'!\n", 1022 ch, node->value.element.name, quote); 1023 ch = EOF; 1024 } 1025 1026 break; 1027 } 1028 else if (ch == '>') 1029 break; 1030 1031 /* 1032 * Set the attribute... 1033 */ 1034 1035 stp_mxmlElementSetAttr(node, name, value); 1036 } 1037 1038 /* 1039 * Free the name and value buffers and return... 1040 */ 1041 1042 free(name); 1043 free(value); 1044 1045 return (ch); 1046} 1047 1048 1049/* 1050 * 'mxml_string_getc()' - Get a character from a string. 1051 */ 1052 1053static int /* O - Character or EOF */ 1054mxml_string_getc(void *p) /* I - Pointer to file */ 1055{ 1056 int ch; /* Character */ 1057 const char **s; /* Pointer to string pointer */ 1058 1059 1060 s = (const char **)p; 1061 1062 if ((ch = *s[0]) != 0) 1063 { 1064 (*s)++; 1065 return (ch); 1066 } 1067 else 1068 return (EOF); 1069} 1070 1071 1072/* 1073 * 'mxml_string_putc()' - Write a character to a string. 1074 */ 1075 1076static int /* O - 0 on success, -1 on failure */ 1077mxml_string_putc(int ch, /* I - Character to write */ 1078 void *p) /* I - Pointer to string pointers */ 1079{ 1080 char **pp; /* Pointer to string pointers */ 1081 1082 1083 pp = (char **)p; 1084 1085 if (pp[0] < pp[1]) 1086 pp[0][0] = ch; 1087 1088 pp[0] ++; 1089 1090 return (0); 1091} 1092 1093 1094/* 1095 * 'mxml_write_node()' - Save an XML node to a file. 1096 */ 1097 1098static int /* O - Column or -1 on error */ 1099mxml_write_node(stp_mxml_node_t *node, /* I - Node to write */ 1100 void *p, /* I - File to write to */ 1101 int (*cb)(stp_mxml_node_t *, int), 1102 /* I - Whitespace callback */ 1103 int col, /* I - Current column */ 1104 int (*putc_cb)(int, void *)) 1105{ 1106 int i; /* Looping var */ 1107 stp_mxml_attr_t *attr; /* Current attribute */ 1108 char s[255]; /* Temporary string */ 1109 1110 1111 while (node != NULL) 1112 { 1113 /* 1114 * Print the node value... 1115 */ 1116 1117 switch (node->type) 1118 { 1119 case STP_MXML_ELEMENT : 1120 col = mxml_write_ws(node, p, cb, STP_MXML_WS_BEFORE_OPEN, col, putc_cb); 1121 1122 if ((*putc_cb)('<', p) < 0) 1123 return (-1); 1124 if (mxml_write_string(node->value.element.name, p, putc_cb) < 0) 1125 return (-1); 1126 1127 col += strlen(node->value.element.name) + 1; 1128 1129 for (i = node->value.element.num_attrs, attr = node->value.element.attrs; 1130 i > 0; 1131 i --, attr ++) 1132 { 1133 if ((col + strlen(attr->name) + strlen(attr->value) + 3) > STP_MXML_WRAP) 1134 { 1135 if ((*putc_cb)('\n', p) < 0) 1136 return (-1); 1137 1138 col = 0; 1139 } 1140 else 1141 { 1142 if ((*putc_cb)(' ', p) < 0) 1143 return (-1); 1144 1145 col ++; 1146 } 1147 1148 if (mxml_write_string(attr->name, p, putc_cb) < 0) 1149 return (-1); 1150 if ((*putc_cb)('=', p) < 0) 1151 return (-1); 1152 if ((*putc_cb)('\"', p) < 0) 1153 return (-1); 1154 if (mxml_write_string(attr->value, p, putc_cb) < 0) 1155 return (-1); 1156 if ((*putc_cb)('\"', p) < 0) 1157 return (-1); 1158 1159 col += strlen(attr->name) + strlen(attr->value) + 3; 1160 } 1161 1162 if (node->child) 1163 { 1164 /* 1165 * The ? and ! elements are special-cases and have no end tags... 1166 */ 1167 1168 if (node->value.element.name[0] == '?') 1169 { 1170 if ((*putc_cb)('?', p) < 0) 1171 return (-1); 1172 if ((*putc_cb)('>', p) < 0) 1173 return (-1); 1174 if ((*putc_cb)('\n', p) < 0) 1175 return (-1); 1176 1177 col = 0; 1178 } 1179 else if ((*putc_cb)('>', p) < 0) 1180 return (-1); 1181 else 1182 col ++; 1183 1184 col = mxml_write_ws(node, p, cb, STP_MXML_WS_AFTER_OPEN, col, putc_cb); 1185 1186 if ((col = mxml_write_node(node->child, p, cb, col, putc_cb)) < 0) 1187 return (-1); 1188 1189 if (node->value.element.name[0] != '?' && 1190 node->value.element.name[0] != '!') 1191 { 1192 col = mxml_write_ws(node, p, cb, STP_MXML_WS_BEFORE_CLOSE, col, putc_cb); 1193 1194 if ((*putc_cb)('<', p) < 0) 1195 return (-1); 1196 if ((*putc_cb)('/', p) < 0) 1197 return (-1); 1198 if (mxml_write_string(node->value.element.name, p, putc_cb) < 0) 1199 return (-1); 1200 if ((*putc_cb)('>', p) < 0) 1201 return (-1); 1202 1203 col += strlen(node->value.element.name) + 3; 1204 1205 col = mxml_write_ws(node, p, cb, STP_MXML_WS_AFTER_CLOSE, col, putc_cb); 1206 } 1207 } 1208 else if (node->value.element.name[0] == '!') 1209 { 1210 if ((*putc_cb)('>', p) < 0) 1211 return (-1); 1212 else 1213 col ++; 1214 1215 col = mxml_write_ws(node, p, cb, STP_MXML_WS_AFTER_OPEN, col, putc_cb); 1216 } 1217 else 1218 { 1219 if ((*putc_cb)('/', p) < 0) 1220 return (-1); 1221 if ((*putc_cb)('>', p) < 0) 1222 return (-1); 1223 1224 col += 2; 1225 1226 col = mxml_write_ws(node, p, cb, STP_MXML_WS_AFTER_OPEN, col, putc_cb); 1227 } 1228 break; 1229 1230 case STP_MXML_INTEGER : 1231 if (node->prev) 1232 { 1233 if (col > STP_MXML_WRAP) 1234 { 1235 if ((*putc_cb)('\n', p) < 0) 1236 return (-1); 1237 1238 col = 0; 1239 } 1240 else if ((*putc_cb)(' ', p) < 0) 1241 return (-1); 1242 else 1243 col ++; 1244 } 1245 1246 sprintf(s, "%d", node->value.integer); 1247 if (mxml_write_string(s, p, putc_cb) < 0) 1248 return (-1); 1249 1250 col += strlen(s); 1251 break; 1252 1253 case STP_MXML_OPAQUE : 1254 if (mxml_write_string(node->value.opaque, p, putc_cb) < 0) 1255 return (-1); 1256 1257 col += strlen(node->value.opaque); 1258 break; 1259 1260 case STP_MXML_REAL : 1261 if (node->prev) 1262 { 1263 if (col > STP_MXML_WRAP) 1264 { 1265 if ((*putc_cb)('\n', p) < 0) 1266 return (-1); 1267 1268 col = 0; 1269 } 1270 else if ((*putc_cb)(' ', p) < 0) 1271 return (-1); 1272 else 1273 col ++; 1274 } 1275 1276 sprintf(s, "%f", node->value.real); 1277 if (mxml_write_string(s, p, putc_cb) < 0) 1278 return (-1); 1279 1280 col += strlen(s); 1281 break; 1282 1283 case STP_MXML_TEXT : 1284 if (node->value.text.whitespace && col > 0) 1285 { 1286 if (col > STP_MXML_WRAP) 1287 { 1288 if ((*putc_cb)('\n', p) < 0) 1289 return (-1); 1290 1291 col = 0; 1292 } 1293 else if ((*putc_cb)(' ', p) < 0) 1294 return (-1); 1295 else 1296 col ++; 1297 } 1298 1299 if (mxml_write_string(node->value.text.string, p, putc_cb) < 0) 1300 return (-1); 1301 1302 col += strlen(node->value.text.string); 1303 break; 1304 } 1305 1306 /* 1307 * Next node... 1308 */ 1309 1310 node = node->next; 1311 } 1312 1313 return (col); 1314} 1315 1316 1317/* 1318 * 'mxml_write_string()' - Write a string, escaping & and < as needed. 1319 */ 1320 1321static int /* O - 0 on success, -1 on failure */ 1322mxml_write_string(const char *s, /* I - String to write */ 1323 void *p, /* I - Write pointer */ 1324 int (*putc_cb)(int, void *)) 1325 /* I - Write callback */ 1326{ 1327 char buf[255], /* Buffer */ 1328 *bufptr; /* Pointer into buffer */ 1329 1330 1331 while (*s) 1332 { 1333 if (*s == '&') 1334 { 1335 if ((*putc_cb)('&', p) < 0) 1336 return (-1); 1337 if ((*putc_cb)('a', p) < 0) 1338 return (-1); 1339 if ((*putc_cb)('m', p) < 0) 1340 return (-1); 1341 if ((*putc_cb)('p', p) < 0) 1342 return (-1); 1343 if ((*putc_cb)(';', p) < 0) 1344 return (-1); 1345 } 1346 else if (*s == '<') 1347 { 1348 if ((*putc_cb)('&', p) < 0) 1349 return (-1); 1350 if ((*putc_cb)('l', p) < 0) 1351 return (-1); 1352 if ((*putc_cb)('t', p) < 0) 1353 return (-1); 1354 if ((*putc_cb)(';', p) < 0) 1355 return (-1); 1356 } 1357 else if (*s == '>') 1358 { 1359 if ((*putc_cb)('&', p) < 0) 1360 return (-1); 1361 if ((*putc_cb)('g', p) < 0) 1362 return (-1); 1363 if ((*putc_cb)('t', p) < 0) 1364 return (-1); 1365 if ((*putc_cb)(';', p) < 0) 1366 return (-1); 1367 } 1368 else if (*s == '\"') 1369 { 1370 if ((*putc_cb)('&', p) < 0) 1371 return (-1); 1372 if ((*putc_cb)('q', p) < 0) 1373 return (-1); 1374 if ((*putc_cb)('u', p) < 0) 1375 return (-1); 1376 if ((*putc_cb)('o', p) < 0) 1377 return (-1); 1378 if ((*putc_cb)('t', p) < 0) 1379 return (-1); 1380 if ((*putc_cb)(';', p) < 0) 1381 return (-1); 1382 } 1383 else if (*s & 128) 1384 { 1385 /* 1386 * Convert UTF-8 to Unicode constant... 1387 */ 1388 1389 int ch; /* Unicode character */ 1390 1391 1392 ch = *s & 255; 1393 1394 if ((ch & 0xe0) == 0xc0) 1395 { 1396 ch = ((ch & 0x1f) << 6) | (s[1] & 0x3f); 1397 s ++; 1398 } 1399 else if ((ch & 0xf0) == 0xe0) 1400 { 1401 ch = ((((ch * 0x0f) << 6) | (s[1] & 0x3f)) << 6) | (s[2] & 0x3f); 1402 s += 2; 1403 } 1404 1405 if (ch == 0xa0) 1406 { 1407 /* 1408 * Handle non-breaking space as-is... 1409 */ 1410 1411 if ((*putc_cb)('&', p) < 0) 1412 return (-1); 1413 if ((*putc_cb)('n', p) < 0) 1414 return (-1); 1415 if ((*putc_cb)('b', p) < 0) 1416 return (-1); 1417 if ((*putc_cb)('s', p) < 0) 1418 return (-1); 1419 if ((*putc_cb)('p', p) < 0) 1420 return (-1); 1421 if ((*putc_cb)(';', p) < 0) 1422 return (-1); 1423 } 1424 else 1425 { 1426 sprintf(buf, "&#x%x;", ch); 1427 1428 for (bufptr = buf; *bufptr; bufptr ++) 1429 if ((*putc_cb)(*bufptr, p) < 0) 1430 return (-1); 1431 } 1432 } 1433 else if ((*putc_cb)(*s, p) < 0) 1434 return (-1); 1435 1436 s ++; 1437 } 1438 1439 return (0); 1440} 1441 1442 1443/* 1444 * 'mxml_write_ws()' - Do whitespace callback... 1445 */ 1446 1447static int /* O - New column */ 1448mxml_write_ws(stp_mxml_node_t *node, /* I - Current node */ 1449 void *p, /* I - Write pointer */ 1450 int (*cb)(stp_mxml_node_t *, int), 1451 /* I - Callback function */ 1452 int ws, /* I - Where value */ 1453 int col, /* I - Current column */ 1454 int (*putc_cb)(int, void *)) 1455 /* I - Write callback */ 1456{ 1457 int ch; /* Whitespace character */ 1458 1459 1460 if (cb && (ch = (*cb)(node, ws)) != 0) 1461 { 1462 if ((*putc_cb)(ch, p) < 0) 1463 return (-1); 1464 else if (ch == '\n') 1465 col = 0; 1466 else if (ch == '\t') 1467 { 1468 col += STP_MXML_TAB; 1469 col = col - (col % STP_MXML_TAB); 1470 } 1471 else 1472 col ++; 1473 } 1474 1475 return (col); 1476} 1477 1478 1479/* 1480 * End of "$Id: mxml-file.c,v 1.10 2008/07/20 01:12:15 easysw Exp $". 1481 */ 1482