/*---------------------------------------------------------------------------*/
/* xml.cpp                                                                   */
/* copyright (c) innovaphone 2001 - 2022                                     */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include <platform.h>
#include "../ilib/str.h"

#include "../ilib/xml.h"

#define XML_ALLOC_SIZE  128

USING_NAMESPACE

xml_io::xml_io(char * buffer, bool keep_whitespaces, bool no_empty_b)
{
    this->buffer = buffer;
    this->keep_whitespaces = keep_whitespaces;
    this->no_empty_b = no_empty_b;
    init();
}

void xml_io::init()
{
    error = false;
    index = 0;
    current_id = 0;
    id_count = 0;
    href_count = 0;
    id_list = 0;
    href_list = 0;
    namespc = "";
    encoding = 0;

    last = buffer;
    eod = buffer;
    base = 0xffff;
    state = 0;
    noesc = 0;
}

byte xml_io::read()
{
    char * p = last;
    error = 0;
    read(p);
    eod = p;
    ASSERT(index <= XML_MAX_TAG, "XML read max index.");
    return error;
}

void xml_io::read(char * &p)
{
#define XML_READ_START          0
#define XML_READ_TAG_START      1
#define XML_READ_TAG_END        2
#define XML_READ_ATTRIB_NAME    3
#define XML_READ_ATTRIB_VALUE   4
#define XML_READ_CONTENT        5
#define XML_READ_END            6

    static void (xml_io::*tbl[])(char * &p) = {
      &xml_io::read_start,
      &xml_io::read_tag_start,
      &xml_io::read_tag_end,
      &xml_io::read_attrib_name,
      &xml_io::read_attrib_value,
      &xml_io::read_content };
    while(!error && state!=XML_READ_END) {
        (this->*tbl[state])(p);
        if (index > XML_MAX_TAG - 10) {
            error = XML_ERROR_INCOMPLETE;
            p = p + strlen(p);
        }
    }
}

void xml_io::read_start(char * & p)
{
    if(!*p) {
        error = XML_ERROR_FATAL;
        return;
    }

    //UTF-8 Byte Order Mark present and denoting endianess?
    if(p && (byte)p[0]==0xef && (byte)p[1]==0xbb && (byte)p[2]==0xbf) p += 3;

    while(whitespace(*p)) p++;
    if(*p) {
        if(*p=='<') {
            p++;
            last = p;
            state = XML_READ_TAG_START;
            base = 0xffff;
        }
        else {
            error = XML_ERROR_FATAL;
        }
    }
}

void xml_io::read_tag_start(char * & p)
{
    char * start;

        // skip comments
    if(*p=='!' && p[1]=='-' && p[2]=='-') {
        p+=3;
        bool end=false;

        while(*p && !end) {
            p++;
            if(*p=='-' && p[1]=='-' && p[2]=='>' ) {
                p+=3;
                end=true;
            }
        }
        if(!end) {
            error = XML_ERROR_INCOMPLETE;
            state = XML_READ_TAG_START;
            return;
        }
        else {
            last = p;

            state = XML_READ_CONTENT;
            return;
        }
    }

        // skip <![CDATA[ ... ]]>
    if(*p=='!' && p[1]=='[' && p[2]=='C' && p[3]=='D' && p[4]=='A' && p[5]=='T' && p[6]=='A' && p[7]=='[') {
        p+=8;
        char * start=p;
        char * end=0;

        while(*p && !end) {
            p++;
            if(*p==']' && p[1]==']' && p[2]=='>' ) {
                end=p;
                p+=3;
            }
        }
        if(!end) {
            error = XML_ERROR_INCOMPLETE;
            state = XML_READ_TAG_START;
            return;
        }
        else {
            *end=0;
            add(XML_TYPE_CONTENT,base,start,(word)(end-start));
            last = p;
            state = XML_READ_CONTENT;
            return;
        }
    }

        // skip <? ... ?>
    if(*p=='?') {
        p++;
        bool end=false;
        if(p[0]=='x' && p[1]=='m' && p[2]=='l' && p[3]==' ') {
            p +=4;
            while(!encoding) {
                while(whitespace(*p)) p++;
                if(!*p || *p=='?') break;
                char * attrib = p;
                while(*p && *p!='=' && *p!='?') p++;
                if(*p!='=' || p[1]!='"') break;
                p+=2;
                char * value = p;
                while(*p && *p!='"' && *p!='?') p++;
                if(*p!='"') break;
                if(!memcmp(attrib,"encoding",8)) {
                    encoding = value;
                    *p = 0;
                }
                p++;
            }
        }
        else {
            p++;
        }

        while(*p && !end) {
            if(*p=='?' && p[1]=='>' ) {
                p++;
                end = true;
            }
            p++;
        }
        if(!end) {
            error = XML_ERROR_INCOMPLETE;
            state = XML_READ_TAG_START;
            return;
        }
        else {
            last = p;

            state = XML_READ_CONTENT;
            return;
        }
    }

    start = p;

    while(*p && !whitespace(*p) && *p!='/' && *p!='>') p++;

    if(*p) {
        if(*p=='/') {
            if(p[1]) {
                *p=0;
                set_complete(add(XML_TYPE_TAG,base,start,0));
                p++;
                if(*p=='>') {
                    p++;
                    last = p;
                    if(base==0xffff) {
                        state = XML_READ_END;
                    }
                    else {
                        state = XML_READ_CONTENT;
                    }
                }
                else {
                    error = XML_ERROR_FATAL;
                }
            }
            else {
                p++;
                error = XML_ERROR_INCOMPLETE;
            }
        }
        else if(*p=='>') {
            *p=0;
            base = add(XML_TYPE_TAG,base,start,0);
            p++;
            last = p;

            state = XML_READ_CONTENT;
        }
        else {
            *p=0;
            base = add(XML_TYPE_TAG,base,start,0);
            p++;
            last = p;

            state = XML_READ_ATTRIB_NAME;
        }
    }
    else {
        error = XML_ERROR_INCOMPLETE;
    }
}

void xml_io::read_tag_end(char * & p)
{
    if(*p=='/') {
        p++;
        //char * start = p;
        while(*p && *p!='>') p++;
        if(*p) {
            *p=0;
            p++;
            last = p;
            set_complete(base);
            base = get_base(base);
            if(base==0xffff) {
                state = XML_READ_END;
            }
            else {
                state = XML_READ_CONTENT;
            }
        }
        else {
            error = XML_ERROR_INCOMPLETE;
            state = XML_READ_TAG_END;
        }
        return;
    }
    error = XML_ERROR_FATAL;
    state = XML_READ_TAG_END;
}

void xml_io::read_attrib_name(char * & p)
{
        // skip any whitespace in front of a attrib
    char * start;
    while(whitespace(*p)) p++;

    if(*p=='>') {
        p++;
        last = p;
        state = XML_READ_CONTENT;
        return;
    }
    else if(*p=='/') {
        p++;
        if(*p=='>') {
            p++;
            last=p;
            set_complete(base);
            base = get_base(base);
            if(base==0xffff) {
                state = XML_READ_END;
            }
            else {
                state = XML_READ_CONTENT;
            }
            return;
        }
        else if(*p==0) {
            error = XML_ERROR_INCOMPLETE;
        }
        else {
            error = XML_ERROR_FATAL;
        }
        state = XML_READ_ATTRIB_NAME;
        return;
    }
    start = p;
    while(*p && *p!='=' && !whitespace(*p)) p++;

    if(*p) {
        *p=0;
        add(XML_TYPE_ATTRIB,base,start,(word)(p-start));
        p++;
        last = p;

        state = XML_READ_ATTRIB_VALUE;
    }
    else {
        error = XML_ERROR_INCOMPLETE;
        state = XML_READ_ATTRIB_NAME;
    }
}

void xml_io::read_attrib_value(char * & p)
{
        // skip any whitespace
    while(whitespace(*p)) p++;

    if(*p=='"' || *p=='\'') {
        char open = *p++;
        char * start = p;

        while(*p && *p!=open) p++;

        if(*p) {
            *p=0;
            word len = xml_to_str(start,start);
            add(XML_TYPE_ATTRIB_VALUE,base,start,len);
            p++;
            last = p;

            state = XML_READ_ATTRIB_NAME;
            return;
        }
        else {
            error = XML_ERROR_INCOMPLETE;
            state = XML_READ_ATTRIB_VALUE;
            return;
        }
    }
    else if(*p) {
        error = XML_ERROR_FATAL;
    }
    else {
        error = XML_ERROR_INCOMPLETE;
    }
    state = XML_READ_ATTRIB_VALUE;
}

void xml_io::read_content(char * & p)
{
        // skip any whitespace
    while(whitespace(*p) && !keep_whitespaces) p++;
    if(*p) {
        char * start = p;
        while(*p && *p!='<') p++;
        if(*p) {
            if(p[1]) {
                *p=0;
                if (p != start) {
                    add(XML_TYPE_CONTENT,base,start,(word)(p-start));
                }
                p++;
                last = p;

                if(*p=='/') {
                    state = XML_READ_TAG_END;
                }
                else {
                    state = XML_READ_TAG_START;
                }
                return;
            }
            else {
                p++;
            }
        }
    }
    error = XML_ERROR_INCOMPLETE;
    state = XML_READ_CONTENT;
}

bool xml_io::save(char * buffer, int sz, int & len, byte & state, word & base)
{
    len = (int)(eod-last);
    if(len > sz) return false;
    memcpy(buffer,last,len);
    state = this->state;
    base = this->base;
    return true;
}

void xml_io::restore(byte state, word base)
{
    this->state = state;
    this->base = base;
}

#if !defined(PLATFORM_NO_PACKET)
void xml_io::save(class packet * & save_xml_io, class packet * & save_buffer)
{
    save_xml_io = new packet(this,(byte *)&content[index]-(byte *)this);
    save_buffer = new packet(buffer, ofs());
}

void xml_io::restore(class packet * & save_xml_io, class packet * & save_buffer)
{
    if(save_xml_io && save_buffer) {
        char * old_buffer = buffer;
        save_xml_io->get_head(this,(byte *)&content[XML_MAX_TAG]-(byte *)this);
        if (buffer != old_buffer) {
            last += (old_buffer - buffer);
            eod += (old_buffer - buffer);
            for (word i = 0; i < index; i++) {
                if (content[i].info) {
                    content[i].info += (old_buffer - buffer);
                }
            }
        }
        buffer = old_buffer;
        save_buffer->get_head(buffer,save_buffer->length());
        delete save_xml_io;
        save_xml_io = 0;
        delete save_buffer;
        save_buffer = 0;
    }
}
#endif


#if defined(PLATFORM_NO_PACKET)
void xml_io::save(char * & save_xml_io, unsigned & xml_len, char * & save_buffer, unsigned & buffer_len)
{
    xml_len = (unsigned)((byte *)&content[index] - (byte *)this);
    save_xml_io = (char *)malloc(xml_len);
    memcpy(save_xml_io, this, xml_len);
    buffer_len = this->ofs();
    save_buffer = (char *)malloc(buffer_len);
    memcpy(save_buffer, buffer, buffer_len);
}


void xml_io::restore(char * & save_xml_io, unsigned xml_len, char * & save_buffer, unsigned buffer_len)
{
    if (save_xml_io && save_buffer) {
        char * old_buffer = buffer;
        memcpy((void *)this, save_xml_io, xml_len);
        if (buffer != old_buffer) {
            last += (old_buffer - buffer);
            eod += (old_buffer - buffer);
            for (word i = 0; i < index; i++) {
                if (content[i].info) {
                    content[i].info += (old_buffer - buffer);
                }
            }
        }
        buffer = old_buffer;
        memcpy(buffer, save_buffer, buffer_len);
        free(save_xml_io);
        save_xml_io = 0;
        free(save_buffer);
        save_buffer = 0;
    }
}
#endif


void xml_io::save_incomplete(word root, word base, class xml_io & to)
{
    char * to_p = to.buffer;
    word to_base = 0xffff;
    to.init();
    if(base==0xffff) {
        copy_out(root,to,to_base,to_p);
//        to.base = root;
    }
    else {
        copy_out(base,to,to_base,to_p);
        copy_in(base,to,to_base,to_p);
    }
    memcpy(to_p,last,eod-last);
    to.last = to_p;
    to.eod = to.last+(eod-last);
    to.state = state;
}

void xml_io::copy_out(word base, class xml_io & to, word & to_base, char * & to_p)
{
    if(content[base].base!=0xffff) {
        copy_out(content[base].base,to,to_base,to_p);
    }
    strcpy(to_p,content[base].info);
    to_base = to.add(XML_TYPE_TAG,to_base,to_p);
    to_p +=strlen(to_p)+1;
    to.base = to_base;
}

void xml_io::copy_in(word base, class xml_io & to, word to_base, char * & to_p)
{
    word end=base+content[base].count;
    if(this->base==base) to.base=to_base;
    for(base++; base<=end; base=nextindex(base)) {
        if(content[base].type==XML_TYPE_TAG) {
            strcpy(to_p,content[base].info);
            word tag = to.add(XML_TYPE_TAG,to_base,to_p);
            to_p +=strlen(to_p)+1;
            copy_in(base,to,tag,to_p);
        }
        else {
            word l = content[base].count;
            memcpy(to_p,content[base].info,l);
            to.add(content[base].type,to_base,to_p,l);
            to_p +=l;
            *to_p = 0;
            to_p++;
        }
    }
}

/*---------------------------------------------------------------------------*/
/* legacy decode function                                                    */
/*---------------------------------------------------------------------------*/

bool xml_io::decode(bool href, word * decoded)
{
    struct xml_id_content id_buffer[XML_MAX_ID];
    struct xml_id_content href_buffer[XML_MAX_ID];
    if(href) {
        id_list = id_buffer;
        href_list = href_buffer;
    }
    else {
        id_list = 0;
        href_list = 0;
    }

    char * start = buffer;
    read_tag(buffer);
    if (decoded) *decoded = (word)(buffer - start);
    if(!index || content[0].type!=XML_TYPE_TAG) return false;
    return !error;
}

bool xml_io::is_tagstart(const char * p )
{
    bool found=false;
    if(*p=='<') {
        found=true;
        p++;
        if(*p=='/') p++;
        for(const char *p1=namespc; *p1;p1++,p++) {
            if(*p!=*p1) found=false;
        }
    }
    return found;
}

bool xml_io::find_id(word handle, const char * href)
{
    word i;
    for(i=0; i<id_count && strcmp(href,id_list[i].id); i++);
    if(i<id_count) {
        word id_handle = id_list[i].handle;
        if(content[id_handle].type==XML_TYPE_CONTENT) {
            content[handle+content[handle].href].info = content[id_handle].info;
            content[handle+content[handle].href].count = content[id_handle].count;
            content[handle].href = 0;
        }
        else {
            content[handle+content[handle].href].type = XML_TYPE_HREF;
            content[handle+content[handle].href].info = (char *)(void *)(uintp)id_handle;
            content[handle+content[handle].href].count = 0;
        }
        return true;
    }
    return false;
}

bool xml_io::find_href(const char * id, word handle)
{
    word i;
    for(i=0; i<href_count && strcmp(id,href_list[i].id); i++);
    if(i<href_count) {
        word href_handle = href_list[i].handle + content[href_list[i].handle].href;
        if(content[handle].type==XML_TYPE_TAG) {
            content[href_handle].type = XML_TYPE_HREF;
            content[href_handle].info = (char *)(void *)(uintp)handle;
            content[href_handle].count = 0;
        }
        else {
            content[href_list[i].handle].href = 0;
            content[href_handle].type = XML_TYPE_CONTENT;
            content[href_handle].info = content[handle].info;
            content[href_handle].count = content[handle].count;
        }
        return true;
    }
    return false;
}

#define NEXT_INDEX	{ if (++index >= XML_MAX_TAG) { error = true; return false; } }

bool xml_io::read_tag(char * & p)
{
    char * start;
    char * end;
    word current = index;
    char * href = 0;

        // skip any whitespace in front of a tag or content
    for( ; *p && whitespace(*p); p++);

        // end of data here is an error
    if(!*p) {
        error = true;
        return false;
    }

        // skip comments and <? ... ?>
    while((*p=='<' && p[1]=='!' && p[2]=='-' && p[3]=='-') || (*p=='<' && p[1]=='?')) {
        if (p[1] == '!') {
            p+=4;
            bool endcomment=false;

            while(*p &&!endcomment) {
                p++;
                if(*p=='-' && p[1]=='-' && p[2]=='>' ) {
                    p+=3;
                    endcomment=true;
                }
            }
            if(!endcomment) {
                error = true;
                return false;
            }
            for( ; *p && whitespace(*p); p++);
        }
        else {
            p+=2;
            bool end=false;

            while(*p &&!end) {
                p++;
                if(*p=='?' && p[1]=='>' ) {
                    p+=2;
                    end=true;
                }
            }
            if(!end) {
                error = true;
                return false;
            }
            for( ; *p && whitespace(*p); p++);
        }
    }

        // if it does not start with '<' it is content
        // check namespace
    if(!is_tagstart(p)) {
        if(keep_whitespaces) {
            for( ; *p && p > buffer && *(p-1) && whitespace(*(p-1)); p--);
        }
        content[current].type = XML_TYPE_CONTENT;
        content[current].href = 0;
        content[current].info = p;
        NEXT_INDEX;
        while(*p && !is_tagstart(p)) p++;
        content[current].count = (word)(p-content[current].info);
        if(current_id) {
            if(!find_href(current_id,current)) {
                if(id_count < XML_MAX_ID) {
                    id_list[id_count].id = current_id;
                    id_list[id_count].handle = current;
                    id_count++;
                }
            }
            current_id = 0;
        }
        return true;
    }

        // if it is an end tag, return without further updating the read pointer
    if(p[1]=='/') {
        return false;
    }

        // terminating zero
    *p++ = 0;

        // remember the start tag, so that we can check it against the
        // end tag
    start = p;

    content[current].type = XML_TYPE_TAG;
    content[current].count = 0;
    content[current].href = 0;
    content[current].info = start;
    NEXT_INDEX;

    if(current_id) {
        if(!find_href(current_id,current_id_tag)) {
			if(id_count < XML_MAX_ID) {
				id_list[id_count].id = current_id;
				id_list[id_count].handle = current_id_tag;
				id_count++;
			}
        }
        current_id = 0;
    }

        // read the attributes of the tag
    word white = false;
    for( ; *p && *p!='>'; p++) {
        if(whitespace(*p)) {
            white = true;
            *p=0;
        }
        else {
            if(white) {
                white = false;

                    // attrib name starts with first non-whitespace
                    // (but / means its the end of an empty tag)
                if(*p!='/') {
                    char * attrib_name;
                    char * attrib_value;

                    content[index].type = XML_TYPE_ATTRIB;
                    content[index].count = 1;
                    content[index].href = 0;
                    content[index].info = p;
                    attrib_name = p;
                    NEXT_INDEX;

                        // attrib name is terminated by '='
                    while(*p && *p!='=') p++;

                        // end of data here is an error
                    if(!*p) {
                        error = true;
                        return false;
                    }

                        // terminating zero
                    *p++=0;

                        // attrib value starts with " or '
                    if(*p!='"' && *p!='\'') {
                        error = true;
                        return false;
                    }
                    char quotechar=*p;
                    p++;
                    content[index].type = XML_TYPE_ATTRIB_VALUE;
                    content[index].href = 0;
                    content[index].info = p;
                    attrib_value = p;

                        // attrib value ends with " or ', depending on the starting " '
                    while(*p && *p!=quotechar) p++;

                        // end of data here is an error
                    if(!*p) {
                        error = true;
                        return false;
                    }

                        // terminating zero
                    *p=0;
                    content[index].count = xml_to_str(attrib_value,attrib_value);
                    NEXT_INDEX;

                    if(id_list && href_list) {
                        if(!strcmp(attrib_name,"id")) {
                            current_id = attrib_value;
                            current_id_tag = current;
                        }
                        else if(!strcmp(attrib_name,"href")) {
                            href = attrib_value;
                        }
                    }
                }
            }
        }
    }

        // end of data here is an error
    if(!*p) {
        error = true;
        return false;
    }

        // terminating zero
    *p++=0;


        // check for empty tag
    if(p[-2]=='/') {
        p[-2] = 0;

            // for empty tags we try to resolve any content with the href/id
            // mechanism
        if(href && href[0]=='#' && index-current<8) {
            content[current].href = index-current;
            content[index].type = XML_TYPE_CONTENT;
            content[index].href = 0;

            if(!find_id(current,&href[1])) {
                content[index].info = 0;
                content[index].count = 0;

                if(href_count < XML_MAX_ID) {
                    href_list[href_count].id = &href[1];
                    href_list[href_count].handle = current;
                    href_count++;
                }
            }
            NEXT_INDEX;
        }
        content[current].count = index-current-1;
        return true;
    }

        // read nested tags/content
    while(read_tag(p) && !error);

    if(error) return false;

        // terminating zero
    *p++ = 0;

    p++;

        // remember end tag, to check against start tag
    end = p;

        // find end of tag
    for( ; *p && *p!='>'; p++);

        // end of data here is an error
    if(!*p) {
        error = true;
        return false;
    }

        // terminating zero
    *p++ = 0;

        // check end tag against start tag. We allow a generic </> end tag as well
    if(*end && strcmp(start,end)) {
        error = true;
        return false;
    }

        // write count of nested info
    content[current].count = index-current-1;

    return true;
}

bool xml_io::read_tag_content(char *& p)
{
    content[0].type = XML_TYPE_TAG;
    content[0].count = 0;
    content[0].href = 0;
    content[0].info = "";
    NEXT_INDEX;
    while (p[0] && read_tag(p) && !error);
    *p++ = 0;
    content[0].count = index - 1;
    if (error) return false;
    return true;
}

/*---------------------------------------------------------------------------*/
/* functions to walk thru the descriptor list                                */
/*---------------------------------------------------------------------------*/

word xml_io::get_first(byte type, word base)
{
    word end;
    if(base==0xffff) {
        if(!index) return 0xffff;
        end = index-1;
        base = 0;
    }
    else {
        if(content[base].href) base = (word)(uintp)content[base + content[base].href].info;
        end = base + content[base].count;
        base++;
    }

    while(base<=end && type!=XML_TYPE_ANY && content[base].type!=type) {
        base = nextindex(base);
    }
    if(base>end) return 0xffff;
    return base;
}

word xml_io::get_next(byte type, word base, word last)
{
    word end;

    if(base==0xffff) {
        end = index-1;
        base = 0;
    }
    else {
        if(content[base].href) base = (word)(uintp)content[base + content[base].href].info;
        end = base + content[base].count;
    }
    last = nextindex(last);

    while(last<=end && type!=XML_TYPE_ANY && content[last].type!=type) {
        last = nextindex(last);
    }
    if(last>end) return 0xffff;
    return last;
}

bool xml_io::get_xpath(word base, char * xpath, word * out_base, char **out_attrib)
{
    if(xpath[0]==0 || xpath[0]=='@') {
        if(out_base) *out_base = base;
        if(out_attrib) *out_attrib = xpath[0]=='@'?&xpath[1]:0;
        return true;
    }

    unsigned n;
    for(n=0; xpath[n] && xpath[n]!='/' && xpath[n]!='@'; n++);
    for(word handle=get_first(XML_TYPE_TAG,base); handle!=0xffff;handle=get_next(XML_TYPE_TAG,base,handle)) {
        char * tag = info(handle);
        if(strlen(tag)==n && !strncmp(xpath,tag,n)) {
            if(xpath[n]=='/') n++;
            return get_xpath(handle,&xpath[n],out_base,out_attrib);
        }
    }
    return false;
}

word xml_io::get_attrib_handle(word base, const char * name)
{
    word handle;
    for(handle=get_first(XML_TYPE_ATTRIB,base); handle!=0xffff;handle=get_next(XML_TYPE_ATTRIB,base,handle)) {
        if(!strcmp(name,info(handle))) {
            return get_first(XML_TYPE_ATTRIB_VALUE,handle);
        }
    }
    return 0xffff;
}

word xml_io::del_attrib(word base, const char * name)
{
    word handle;
    for(handle=get_first(XML_TYPE_ATTRIB,base); handle!=0xffff;handle=get_next(XML_TYPE_ATTRIB,base,handle)) {
        if(!strcmp(name,info(handle))) {
            content[handle].count = 0;
        }
    }
    return 0xffff;
}

void xml_io::del_attrib(word handle)
{
    if (content[handle].type == XML_TYPE_ATTRIB) content[handle].count = 0;
}

char * xml_io::get_attrib(word base, const char * name)
{
    word handle;
    for(handle=get_first(XML_TYPE_ATTRIB,base); handle!=0xffff;handle=get_next(XML_TYPE_ATTRIB,base,handle)) {
        if(!strcmp(name,info(handle))) {
            return info(get_first(XML_TYPE_ATTRIB_VALUE,handle));
        }
    }
    return 0;
}

char * xml_io::get_attrib(word base, const char * name, word & length)
{
    word handle;
    for(handle=get_first(XML_TYPE_ATTRIB,base); handle!=0xffff;handle=get_next(XML_TYPE_ATTRIB,base,handle)) {
        if(!strcmp(name,info(handle))) {
            word value = get_first(XML_TYPE_ATTRIB_VALUE,handle);
            length = count(value);
            return info(value);
        }
    }
    return 0;
}

#if !defined(PLATFORM_NO_IPADDR)
IPaddr xml_io::get_attrib_ip(word base, const char * name, bool * present)
{
    char * a = get_attrib(base,name);
    if(present) *present=true;
    if(a) return str::to_ip(a,0);
    if(present) *present=false;
    return ip_anyaddr;
}

IPaddr xml_io::get_attrib_ip6(word base, const char * name, bool * present)
{
    char * a = get_attrib(base,name);
    if(present) *present=true;
    if(a) {
        IPaddr addr;
        if(str::to_ip6(a, &addr, 0, 0)) return addr;
    }
    if(present) *present=false;
    return ip_anyaddr;
}

IPaddr_prefix xml_io::get_attrib_ip6_net(word base, const char * name, bool * present)
{
    char * a = get_attrib(base,name);
    if(present) *present=true;
    if(a) {
        IPaddr_prefix ap;
        if(str::to_ip6(a, &ap.a, &ap.p, 0)) return ap;
    }
    if(present) *present=false;

    return zero_ip_prefix;
}

Eaddr xml_io::get_attrib_eaddr(word base, const char * name, bool * present)
{
    Eaddr eaddr;
    char * a = get_attrib(base,name);
    if(present) *present=true;
    if(a && str::to_eaddr(a, 0, &eaddr)) return eaddr;
    memset(&eaddr, 0, sizeof(eaddr));
    if(present) *present=false;
    return eaddr;
}
#endif

int xml_io::get_attrib_int(word base, const char * name, bool * present)
{
    char * a = get_attrib(base,name);
    if(present) *present=true;
    if(a) return strtol(a,0,0);
    if(present) *present=false;
    return 0;
}

unsigned xml_io::get_attrib_unsigned(word base, const char * name, bool * present)
{
    char * a = get_attrib(base,name);
    if(present) *present=true;
    if(a) return strtoul(a,0,0);
    if(present) *present=false;
    return 0;
}

int xml_io::get_attrib_hex(word base, const char * name, bool * present)
{
    char * a = get_attrib(base,name);
    if(present) *present=true;
    if(a) return strtoul(a,0,16);
    if(present) *present=false;
    return 0;
}

int xml_io::get_attrib_dec(word base, const char * name, bool * present)
{
    char * a = get_attrib(base,name);
    if(present) *present=true;
    if(a) return strtol(a,0,10);
    if(present) *present=false;
    return 0;
}

bool xml_io::get_attrib_bool(word base, const char * name, bool * present)
{
    char * a = get_attrib(base, name);
    if (present) *present = a != 0;
    if(!a) return false;
    return strcmp(a, "false")!=0;
}

word xml_io::get_tag(word base, const char * name, const char * ns)
{
    word handle;
    for(handle=get_first(XML_TYPE_TAG,base); handle!=0xffff;handle=get_next(XML_TYPE_TAG,base,handle)) {
        if(name) {
            const char * tag_name = info(handle);
            if(ns) {
                char * ns_end = (char *)strchr(tag_name,':');
                if (ns[0] == 0) {
                    if(!strcmp(name,tag_name)) {
                        return handle;
                    }

                    if(!ns_end) continue;
                    int ofs = (int)(ns_end-tag_name);
                    if (!strcmp(&tag_name[ofs + 1], name)) {
                        return handle;
                    }
                }
                else {
                    if(!ns_end) continue;
                    int ofs = (int)(ns_end-tag_name);

                    if(!strncmp(tag_name,ns,ofs)) {
                        if(!strcmp(&tag_name[ofs+1],name)) {
                            return handle;
                        }
                    }
                    continue;
                }
            }
            if(!strcmp(name,tag_name)) {
                return handle;
            }
        }
        else {
            return handle;
        }
    }
    return 0xffff;
}

word xml_io::get_next_tag(word base, const char * name, word last, const char * ns)
{
    while(last!=0xffff) {
        last = get_next(XML_TYPE_TAG,base,last);
        if (last != 0xffff) {
            if (!name)
                return last;

            char * tag_name = info(last);
            if (ns) {
                char * ns_end = (char *)strchr(tag_name, ':');
                if (ns_end) {
                    int ofs = (int)(ns_end - tag_name);
                    if (!strncmp(tag_name, ns, ofs) && !strcmp(&tag_name[ofs + 1], name))
                        return last;
                }
            }
            else if (!strcmp(name, tag_name))
                return last;
        }
    }
    return 0xffff;
}

/*---------------------------------------------------------------------------*/
/* encode functions                                                          */
/*---------------------------------------------------------------------------*/

word xml_io::encode()
{
    char * p = buffer;
    if(index) write_tag(0,p);
    return (word)(p-buffer);
}

#if !defined(PLATFORM_NO_PACKET)
class packet * xml_io::encode_to_packet(class packet * data)
{
    if(!index) return data;
    if(!data) data = new packet();
    write_tag_to_packet(0,data);
    return data;
}
#endif

void xml_io::write_tag(word current, char * & p)
{
    switch(content[current].type) {
    case XML_TYPE_TAG:
        {
            word i;
            bool empty=true;

            p +=isprintf(p,"<%s",content[current].info);
            for(i=0; i<content[current].count; ) {
                if(content[current+i+1].type==XML_TYPE_ATTRIB) {
                    write_tag(current+i+1,p);
                    i++;
                }
                else if(content[current+i+1].type==XML_TYPE_CONTENT || content[current+i+1].type==XML_TYPE_CONTENT_PACKET) {
                    i++;
                    empty = false;
                }
                else if(content[current+i+1].type==XML_TYPE_TAG) {
                    i +=content[current+i+1].count+1;
                    empty = false;
                }
                else {
                    i++;
                }
            }
            if(empty) {
                p += no_empty_b && !strcmp(content[current].info, "b") ? isprintf(p, "></b>", content[current].info) : isprintf(p, "/>");
            }
            else {
                p +=isprintf(p,">");
                for(i=0; i<content[current].count; ) {
                    if(content[current+i+1].type==XML_TYPE_TAG) {
                        write_tag(current+i+1,p);
                        i +=content[current+i+1].count+1;
                    }
                    else if(content[current+i+1].type==XML_TYPE_CONTENT || content[current+i+1].type==XML_TYPE_CONTENT_PACKET) {
                        write_tag(current+i+1,p);
                        i++;
                    }
                    else {
                        i++;
                    }
                }
                p +=isprintf(p,"</%s>",content[current].info);
            }
        }
        break;
    case XML_TYPE_ATTRIB:
        if(content[current].count) {
            p +=isprintf(p," %s=",content[current].info);
            write_tag(current+1,p);
        }
        break;
    case XML_TYPE_ATTRIB_VALUE:
        *p++='"';
        p+= str_to_xml(content[current].info,content[current].count,p,noesc);
        *p++='"';
        break;
    case XML_TYPE_CONTENT:
        memcpy(p,content[current].info,content[current].count);
        p +=content[current].count;
        break;
#if !defined(PLATFORM_NO_PACKET)
    case XML_TYPE_CONTENT_PACKET:
        p += ((class packet *)content[current].info)->look_head(p,0xfffffff);
        delete (class packet *)content[current].info;
        break;
#endif
    }
}

void xml_io::write_tag_content(word current, char *& p, char noesc)
{
    this->noesc = noesc;
    if (content[current].type == XML_TYPE_TAG) {
        for (word i = 0; i < content[current].count; ) {
            if (content[current + i + 1].type == XML_TYPE_TAG) {
                write_tag(current + i + 1, p);
                i += content[current + i + 1].count + 1;
            }
            else if (content[current + i + 1].type == XML_TYPE_CONTENT || content[current + i + 1].type == XML_TYPE_CONTENT_PACKET) {
                write_tag(current + i + 1, p);
                i++;
            }
            else {
                i++;
            }
        }
    }
    this->noesc = 0;
}

#if !defined(PLATFORM_NO_PACKET)
class packet * xml_io::write_tag_to_packet(word current, class packet * data)
{
    if(!data) data = new packet();

    switch(content[current].type) {
    case XML_TYPE_TAG:
        {
            word i;
            bool empty=true;

            data->put_tail("<",1,XML_ALLOC_SIZE);
            data->put_tail(content[current].info,strlen(content[current].info),XML_ALLOC_SIZE);
            for(i=0; i<content[current].count; ) {
                if(content[current+i+1].type==XML_TYPE_ATTRIB) {
                    write_tag_to_packet(current+i+1,data);
                    i++;
                }
                else if(content[current+i+1].type==XML_TYPE_CONTENT || content[current+i+1].type==XML_TYPE_CONTENT_PACKET) {
                    i++;
                    empty = false;
                }
                else if(content[current+i+1].type==XML_TYPE_TAG) {
                    i +=content[current+i+1].count+1;
                    empty = false;
                }
                else {
                    i++;
                }
            }
            if(empty) {
                data->put_tail("/>",2,XML_ALLOC_SIZE);
            }
            else {
                data->put_tail(">",1,XML_ALLOC_SIZE);
                for(i=0; i<content[current].count; ) {
                    if(content[current+i+1].type==XML_TYPE_TAG) {
                        write_tag_to_packet(current+i+1,data);
                        i +=content[current+i+1].count+1;
                    }
                    else if(content[current+i+1].type==XML_TYPE_CONTENT || content[current+i+1].type==XML_TYPE_CONTENT_PACKET) {
                        write_tag_to_packet(current+i+1,data);
                        i++;
                    }
                    else {
                        i++;
                    }
                }
                data->put_tail("</",2,XML_ALLOC_SIZE);
                data->put_tail(content[current].info,strlen(content[current].info),XML_ALLOC_SIZE);
                data->put_tail(">",1,XML_ALLOC_SIZE);
            }
        }
        break;
    case XML_TYPE_ATTRIB:
        if(content[current].count) {
            data->put_tail(" ",1,XML_ALLOC_SIZE);
            data->put_tail(content[current].info,strlen(content[current].info),XML_ALLOC_SIZE);
            data->put_tail("=",1,XML_ALLOC_SIZE);
            write_tag_to_packet(current+1,data);
        }
        break;
    case XML_TYPE_ATTRIB_VALUE:
        data->put_tail("\"",1,XML_ALLOC_SIZE);
        {
            for(word i=0; i<content[current].count; i+=100) {
                char out[100*5];
                word l = str_to_xml(&content[current].info[i],MIN(100,content[current].count-i),out,noesc);
                data->put_tail(out,l,XML_ALLOC_SIZE);
            }
        }
        data->put_tail("\"",1,XML_ALLOC_SIZE);
        break;
    case XML_TYPE_CONTENT:
        data->put_tail(content[current].info,content[current].count,XML_ALLOC_SIZE);
        break;
    case XML_TYPE_CONTENT_PACKET:
        data->join((class packet *)content[current].info);
        break;
    }
    return data;
}

class packet * xml_io::write_tag_content_to_packet(word current, class packet * data, char noesc)
{
    this->noesc = noesc;
    if(!data) data = new packet();

    if (content[current].type == XML_TYPE_TAG) {
        for (word i = 0; i < content[current].count; ) {
            if (content[current + i + 1].type == XML_TYPE_TAG) {
                write_tag_to_packet(current + i + 1, data);
                i += content[current + i + 1].count + 1;
            }
            else if (content[current + i + 1].type == XML_TYPE_CONTENT || content[current + i + 1].type == XML_TYPE_CONTENT_PACKET) {
                write_tag_to_packet(current + i + 1, data);
                i++;
            }
            else {
                i++;
            }
        }
    }
    this->noesc = 0;
    return data;
}
#endif

/*---------------------------------------------------------------------------*/
/* functions to build up the descriptor list                                 */
/*---------------------------------------------------------------------------*/

word xml_io::add(byte type, word base, const char * s, word len)
{
    word current;
    if(base>=index) {
        current = 0;
    }
    else {
        current = base + content[base].count + 1;
    }
    if(current!=index) return 0;

    ASSERT(current < XML_MAX_TAG, "XML_MAX_TAG");

    content[current].type = type;
    if(type!=XML_TYPE_TAG) {
        if(len==0xffff) len = (word)strlen(s);
        content[current].count = len;
    }
    else {
        content[current].count = 0;
    }
    content[current].base = base;
    content[current].flags = 0;
    content[current].href = 0;
    content[current].info = s;

    while(base<index) {
        content[base].count++;
        base = content[base].base;
    }

    index++;
    return current;
}

word xml_io::add_tag(word base, const char * name)
{
    return add(XML_TYPE_TAG,base,name);
}

void xml_io::add_attrib(word base, const char * name, const char * value, word len)
{
    if(value) {
        add(XML_TYPE_ATTRIB,base,name);
        add(XML_TYPE_ATTRIB_VALUE,base,value,len);
    }
}

#if !defined(PLATFORM_NO_IPADDR)
void xml_io::add_attrib_ip4(word base, const char * name, IP4addr addr, char * & tmp)
{
    int l = isprintf(tmp,"%A",&addr);
    add(XML_TYPE_ATTRIB,base,name);
    add(XML_TYPE_ATTRIB_VALUE,base,tmp,l);
    tmp +=(l+1);
}

void xml_io::add_attrib_ip(word base, const char * name, const IPaddr & a, char * & tmp)
{
    int l = isprintf(tmp,"%a",&a);
    add(XML_TYPE_ATTRIB,base,name);
    add(XML_TYPE_ATTRIB_VALUE,base,tmp,l);
    tmp +=(l+1);
}

void xml_io::add_attrib_ip(word base, const char * name, const IPaddr_prefix & a, char * & tmp)
{
    int l = a.p<128?isprintf(tmp,"%a/%i",&a.a,a.p):isprintf(tmp,"%a",&a.a);
    add(XML_TYPE_ATTRIB,base,name);
    add(XML_TYPE_ATTRIB_VALUE,base,tmp,l);
    tmp +=(l+1);
}
#endif

void xml_io::add_attrib_int(word base, const char * name, int c, char * & tmp)
{
    int l = isprintf(tmp,"%i",c);
    add(XML_TYPE_ATTRIB,base,name);
    add(XML_TYPE_ATTRIB_VALUE,base,tmp,l);
    tmp +=(l+1);
}

void xml_io::add_attrib_unsigned(word base, const char * name, dword c, char * & tmp)
{
    int l = isprintf(tmp,"%u",c);
    add(XML_TYPE_ATTRIB,base,name);
    add(XML_TYPE_ATTRIB_VALUE,base,tmp,l);
    tmp +=(l+1);
}

void xml_io::add_attrib_hex(word base, const char * name, int c, char * & tmp)
{
    int l = isprintf(tmp,"0x%x",c);
    add(XML_TYPE_ATTRIB,base,name);
    add(XML_TYPE_ATTRIB_VALUE,base,tmp,l);
    tmp +=(l+1);
}

void xml_io::add_attrib_hexstring(word base, const char * name, const byte * hex, word hex_len, char * & tmp)
{
    if (hex) {
        str::from_hexmem(hex,hex_len,tmp);
        add(XML_TYPE_ATTRIB,base,name);
        add(XML_TYPE_ATTRIB_VALUE,base,tmp,hex_len*2);
        tmp +=(hex_len*2+1);
    }
}

void xml_io::add_attrib_url(word base, const char * name, const char * value, char * & tmp)
{
    int l = (int)str::from_url(value,tmp,0xffff);
    add(XML_TYPE_ATTRIB,base,name);
    add(XML_TYPE_ATTRIB_VALUE,base,tmp,l);
    tmp +=(l+1);
}

#if !defined(PLATFORM_NO_IPADDR)
void xml_io::add_attrib_id(word base, const char * name, ulong64 id, char * & tmp)
{
    int l = isprintf(tmp,"%I",id);
    add(XML_TYPE_ATTRIB,base,name);
    add(XML_TYPE_ATTRIB_VALUE,base,tmp,l);
    tmp +=(l+1);
}
#endif

void xml_io::add_attrib_bool(word base, const char * name, bool value)
{
    add(XML_TYPE_ATTRIB,base,name);
    add(XML_TYPE_ATTRIB_VALUE,base,value? "true":"false");
}

#if !defined(PLATFORM_NO_IPADDR)
void xml_io::add_attrib_unicode(word base, const char * name, word * n, word len, char * & tmp)
{
    int l = isprintf(tmp,"%.*N",len,n);
    add(XML_TYPE_ATTRIB,base,name);
    add(XML_TYPE_ATTRIB_VALUE,base,tmp,l);
    tmp +=(l+1);
}
#endif

void xml_io::add_attrib_printf(word base, const char * name, char * & tmp, const char * format, ...)
{
    va_list ap;
    va_start(ap,format);
    int l = ivsprintf(tmp, format, ap);
    va_end(ap);
    add(XML_TYPE_ATTRIB,base,name);
    add(XML_TYPE_ATTRIB_VALUE,base,tmp,l);
    tmp +=(l+1);
}

void xml_io::add_attrib_printfv(word base, const char * name, char * & tmp, const char * format, va_list ap)
{
    int l = ivsprintf(tmp, format, ap);
    add(XML_TYPE_ATTRIB,base,name);
    add(XML_TYPE_ATTRIB_VALUE,base,tmp,l);
    tmp +=(l+1);
}

void xml_io::add_content(word base, const char * s, word len)
{
    add(XML_TYPE_CONTENT,base,s,len);
}

#if !defined(PLATFORM_NO_PACKET)
void xml_io::add_content(word base, class packet * data)
{
    if(data) add(XML_TYPE_CONTENT_PACKET,base,(char *)data,0);
}
#endif

void xml_io::change_info(word handle, char * info)
{
    if(content[handle].type==XML_TYPE_ATTRIB_VALUE) {
        content[handle].count = (word)strlen(info);
    }
    else if(content[handle].type==XML_TYPE_CONTENT) {
        content[handle].count = (word)strlen(info);
    }
    content[handle].info = info;
}

/*---------------------------------------------------------------------------*/
/* static conversion functions                                               */
/*---------------------------------------------------------------------------*/

#if !defined(PLATFORM_NO_PACKET)
class packet * xml_io::str_to_xml_packet(class packet * data, const char * buffer, word length, char noesc)
{
    if(!data) data = new packet();
    const byte * p = (const byte *)buffer;
    while(length && *p) {
        for( ; length && *p!='<' && *p!='>' && (*p!='"' || noesc=='\"') && (*p!='\'' || noesc=='\'') && *p!='&' && *p; p++,length--);
        data->put_tail(buffer,p-(const byte *)buffer,XML_ALLOC_SIZE);
        if(length && *p) {
            length--;
            switch(*p) {
            case '<':
                data->put_tail("&lt;",4,XML_ALLOC_SIZE);
                break;
            case '>':
                data->put_tail("&gt;",4,XML_ALLOC_SIZE);
                break;
            case '"':
                if (noesc == '\'') data->put_tail("\'", 1);
                else data->put_tail("&quot;",6,XML_ALLOC_SIZE);
                break;
            case '\'':
                if (noesc == '\"') data->put_tail("\"", 1);
                else data->put_tail("&apos;",6,XML_ALLOC_SIZE);
                break;
            case '&':
                data->put_tail("&amp;",5,XML_ALLOC_SIZE);
                break;
            }
            buffer = (const char *)++p;
        }
    }
    return data;
}

class packet * xml_io::latin1_to_xml_packet(class packet * data, const char * buffer, word length)
{
    if(!data) data = new packet();
    const byte * p = (const byte *)buffer;
    while(length && *p) {
        for( ; length && *p!='<' && *p!='>' && *p!='"' && *p!='\'' && *p!='&' && !(*p&0x80) && *p; p++,length--);
        data->put_tail(buffer,p-(const byte *)buffer,XML_ALLOC_SIZE);
        if(length && *p) {
            length--;
            switch(*p) {
            case '<':
                data->put_tail("&lt;",4,XML_ALLOC_SIZE);
                break;
            case '>':
                data->put_tail("&gt;",4,XML_ALLOC_SIZE);
                break;
            case '"':
                data->put_tail("&quot;",6,XML_ALLOC_SIZE);
                break;
            case '\'':
                data->put_tail("&apos;",6,XML_ALLOC_SIZE);
                break;
            case '&':
                data->put_tail("&amp;",5,XML_ALLOC_SIZE);
                break;
            default:
                {
                    byte b[2] = {static_cast<unsigned char>(0xc0 | ((*p) >>6)), static_cast<unsigned char>(0x80 | ((*p) &0x3f))};
                    data->put_tail(b,2,XML_ALLOC_SIZE);
                }
                break;
            }
            buffer = (const char *)++p;
        }
    }
    return data;
}
#endif

word xml_io::str_to_xml(const char * buffer, word length, char * out, char noesc)
{
    word count = 0;
    const byte * p = (const byte *)buffer;
    while(length && *p) {
        for( ; length && *p!='<' && *p!='>' && (*p!='"' || noesc=='\"') && (*p!='\'' || noesc=='\'') && *p!='&' && *p; p++,length--);

        memcpy(&out[count],buffer,p-(byte *)buffer);
        count +=(word)(p-(const byte *)buffer);
        if(length && *p) {
            length--;
            switch(*p) {
            case '<':
                memcpy(&out[count],"&lt;",4);
                count +=4;
                break;
            case '>':
                memcpy(&out[count],"&gt;",4);
                count +=4;
                break;
            case '"':
                if (noesc == '\'') {
                    out[count++] = '\'';
                }
                else {
                    memcpy(&out[count],"&quot;",6);
                    count +=6;
                }
                break;
            case '\'':
                if (noesc == '\"') {
                    out[count++] = '\"';
                }
                else {
                    memcpy(&out[count],"&apos;",6);
                    count +=6;
                }
                break;
            case '&':
                memcpy(&out[count],"&amp;",5);
                count +=5;
                break;
            case '\r':
                memcpy(&out[count],"&#xD;",5);
                count +=5;
                break;
            case '\n':
                memcpy(&out[count],"&#xA;",5);
                count +=5;
                break;
            }
            buffer = (const char *)++p;
        }
    }
    return count;
}

word xml_io::latin1_to_xml(const char * buffer, word length, char * out, bool latin1)
{
    word count = 0;
    const byte * p = (const byte *)buffer;
    while(length && *p) {
        if(latin1) {
            for( ; length && *p!='<' && *p!='>' && *p!='"' && *p!='\'' && *p!='&' && *p!='\r' && *p!='\n' && *p; p++,length--);
        }
        else {
            for( ; length && *p!='<' && *p!='>' && *p!='"' && *p!='\'' && *p!='&' && *p!='\r' && *p!='\n' && !(*p&0x80) && *p; p++,length--);
        }
        memcpy(&out[count],buffer,p-(const byte *)buffer);
        count +=(word)(p-(byte *)buffer);
        if(length && *p) {
            length--;
            switch(*p) {
            case '<':
                memcpy(&out[count],"&lt;",4);
                count +=4;
                break;
            case '>':
                memcpy(&out[count],"&gt;",4);
                count +=4;
                break;
            case '"':
                memcpy(&out[count],"&quot;",6);
                count +=6;
                break;
            case '\'':
                memcpy(&out[count],"&apos;",6);
                count +=6;
                break;
            case '&':
                memcpy(&out[count],"&amp;",5);
                count +=5;
                break;
            case '\r':
                memcpy(&out[count],"&#xD;",5);
                count +=5;
                break;
            case '\n':
                memcpy(&out[count],"&#xA;",5);
                count +=5;
                break;
            default:
                {
                    byte b[2] = {static_cast<unsigned char>(0xc0 | ((*p) >>6)), static_cast<unsigned char>(0x80 | ((*p) &0x3f))};
                    out[count++] = b[0];
                    out[count++] = b[1];
                }
                break;
            }
            buffer = (const char *)++p;
        }
    }
    return count;
}

word xml_io::unicode_to_xml(const word * buffer, word length, char * out)
{
    if(!length) return 0;
    word count = 0;
    const word * p = buffer;
    while(length && *p) {
        for( ; length && *p!=(word)'<' && *p!=(word)'>' && *p!=(word)'"' && *p!=(word)'\'' && *p!=(word)'&' && *p!='\r' && *p!='\n' && !(*p &0xff80) && *p; p++,length--);
        for(const word * x = (const word *)buffer; x<p; x++, count++) out[count] = (const byte)*x;
        if(length && *p) {
            length--;
            switch((char)*p) {
            case '<':
                memcpy(&out[count],"&lt;",4);
                count +=4;
                break;
            case '>':
                memcpy(&out[count],"&gt;",4);
                count +=4;
                break;
            case '"':
                memcpy(&out[count],"&quot;",6);
                count +=6;
                break;
            case '\'':
                memcpy(&out[count],"&apos;",6);
                count +=6;
                break;
            case '&':
                memcpy(&out[count],"&amp;",5);
                count +=5;
                break;
            case '\r':
                memcpy(&out[count],"&#xD;",5);
                count +=5;
                break;
            case '\n':
                memcpy(&out[count],"&#xA;",5);
                count +=5;
                break;
            default:
                if(!(*p &0xf800)){
                    byte b[2] = {static_cast<unsigned char>(0xc0 | ((byte)((*p) >>6))), static_cast<unsigned char>(0x80 | ((byte)(*p) &0x3f))};
                    out[count++] = b[0];
                    out[count++] = b[1];
                }
                else {
                    byte b[3] = {static_cast<unsigned char>(0xe0 | ((byte)((*p) >>12))), static_cast<unsigned char>(0x80 | (((byte)((*p) >>6)) &0x3f)), static_cast<unsigned char>(0x80 | ((byte)(*p) &0x3f))};
                    out[count++] = b[0];
                    out[count++] = b[1];
                    out[count++] = b[2];
                }
                break;
            }
            buffer = ++p;
        }
    }
    return count;
}

#if !defined(PLATFORM_NO_PACKET)
word xml_io::xml_packet_to_str(char * buffer, class packet * data)
{
    class packet_ptr ptr;
    int flength;
    word length = 0;
    byte * fdata;
    char c='&';
    word start=0;
    while((fdata=(byte *)data->read_fragment(&ptr,&flength))!=0) {
        for(int i=0; i<flength; ) {
            for( ; i<flength && fdata[i]!=c; i++) buffer[length++] = fdata[i];
            if(i<flength) {
                if(c=='&') {
                    c=';';
                    start = length;
                }
                else {
                    c='&';
                    buffer[length]=0;
                    if(buffer[start+1]=='#') {
                        if(buffer[start+2]=='x') {
                            buffer[start] = (char) strtoul(&buffer[start+3],0,16);
                        }
                        else {
                            buffer[start] = (char) strtoul(&buffer[start+2],0,10);
                        }
                    }
                    else if(!strcmp(&buffer[start],"&lt")) {
                        buffer[start] = '<';
                    }
                    else if(!strcmp(&buffer[start],"&gt")) {
                        buffer[start] = '>';
                    }
                    else if(!strcmp(&buffer[start],"&amp")) {
                        buffer[start] = '&';
                    }
                    else if(!strcmp(&buffer[start],"&quot")) {
                        buffer[start] = '"';
                    }
                    else if(!strcmp(&buffer[start],"&apos")) {
                        buffer[start] = '\'';
                    }
                    length = start+1;
                    i++;
                }
            }
        }
    }
    return length;
}

word xml_io::xml_packet_to_latin1(char * buffer, class packet * data)
{
    class packet_ptr ptr;
    int flength;
    word length = 0;
    byte * fdata;
    char c='&';
    word start=0;
    byte utf8_len=0;
    while((fdata=(byte *)data->read_fragment(&ptr,&flength))!=0) {
        for(int i=0; i<flength; ) {
            for( ; i<flength && fdata[i]!=c && !(fdata[i] &0x80) && !utf8_len; i++) buffer[length++] = fdata[i];
            if(i<flength) {
                if(fdata[i] &0x80 || utf8_len) {
                    if(!utf8_len) {
                        if((fdata[i] & 0xe0)==0xc0) utf8_len = 1;
                        else if((fdata[i] & 0xf0)==0xe0) utf8_len = 2;
                        else if((fdata[i] & 0xf8)==0xf0) utf8_len = 3;
                        else if((fdata[i] & 0xfc)==0xf8) utf8_len = 4;
                        else if((fdata[i] & 0xfe)==0xfc) utf8_len = 5;
                        start = length;
                        buffer[length++] = fdata[i++];
                    }
                    else {
                        utf8_len--;
                        if(!utf8_len) {
                            buffer[length++] = fdata[i++];
                            buffer[start] = buffer[start]<<6 | (buffer[start+1]&0x3f);
                            length = start+1;
                        }
                    }
                }
                else {
                    if(c=='&') {
                        c=';';
                        start = length;
                    }
                    else {
                        c='&';
                        buffer[length]=0;
                        if(buffer[start+1]=='#') {
                            if(buffer[start+2]=='x') {
                                buffer[start] = (char) strtoul(&buffer[start+3],0,16);
                            }
                            else {
                                buffer[start] = (char) strtoul(&buffer[start+2],0,10);
                            }
                        }
                        else if(!strcmp(&buffer[start],"&lt")) {
                            buffer[start] = '<';
                        }
                        else if(!strcmp(&buffer[start],"&gt")) {
                            buffer[start] = '>';
                        }
                        else if(!strcmp(&buffer[start],"&amp")) {
                            buffer[start] = '&';
                        }
                        else if(!strcmp(&buffer[start],"&quot")) {
                            buffer[start] = '"';
                        }
                        else if(!strcmp(&buffer[start],"&apos")) {
                            buffer[start] = '\'';
                        }
                        length = start+1;
                        i++;
                    }
                }
            }
        }
    }
    return length;
}
#endif

word xml_io::xml_to_str(char * buffer, const char * in, word in_len)
{
    word length = 0;
    char c='&';
    word start=0;
    for(int i=0; i < in_len && in[i]; ) {
        for( ; i < in_len && in[i] && in[i]!=c; i++) buffer[length++] = in[i];
        if(i < in_len && in[i]) {
            if(c=='&') {
                c=';';
                start = length;
            }
            else {
                c='&';
                buffer[length]=0;
                if(buffer[start+1]=='#') {
                    unsigned x;
                    if(buffer[start+2]=='x') {
                        x = strtoul(&buffer[start+3],0,16);
                    }
                    else {
                        x = strtoul(&buffer[start+2],0,10);
                    }
                    if(x<128) {
                        buffer[start] = (char) x;
                    }
                    else if(x<2048) {
                        buffer[start++] = (char)((x>>6) | 0xc0);
                        buffer[start] = (char)((x & 0x3f) | 0x80);
                    }
                    else if(x<65536) {
                        buffer[start++] = (char)((x>>12) | 0xe0);
                        buffer[start++] = (char)(((x>>6) & 0x3f) | 0x80);
                        buffer[start] = (char)((x & 0x3f) | 0x80);
                    }
                    else {
                        buffer[start] = '.';
                    }
                }
                else if(!strcmp(&buffer[start],"&lt")) {
                    buffer[start] = '<';
                }
                else if(!strcmp(&buffer[start],"&gt")) {
                    buffer[start] = '>';
                }
                else if(!strcmp(&buffer[start],"&amp")) {
                    buffer[start] = '&';
                }
                else if(!strcmp(&buffer[start],"&quot")) {
                    buffer[start] = '"';
                }
                else if(!strcmp(&buffer[start],"&apos")) {
                    buffer[start] = '\'';
                }
                else if(!strcmp(&buffer[start],"&nbsp")) {
                    buffer[start++] = (char)0xc2;
                    buffer[start] = (char)0xa0;
                }
                length = start+1;
                i++;
            }
        }
    }
    if (c == ';') length = start;
    buffer[length] = 0;
    return length;
}

word xml_io::xml_to_latin1(char * buffer, const char * in, bool latin1)
{
    word length = 0;
    char c='&';
    word start=0;
    byte utf8_len=0;
    for(int i=0; in[i]; ) {
        if(latin1) {
            for( ; in[i] && in[i]!=c; i++) buffer[length++] = in[i];
        }
        else {
            for( ; in[i] && in[i]!=c && !(in[i] &0x80) && !utf8_len; i++) buffer[length++] = in[i];
        }
        if(in[i]) {
            if(!latin1 && ((in[i] &0x80) || utf8_len)) {
                if(!utf8_len) {
                    if((in[i] & 0xe0)==0xc0) utf8_len = 1;
                    else if((in[i] & 0xf0)==0xe0) utf8_len = 2;
                    else if((in[i] & 0xf8)==0xf0) utf8_len = 3;
                    else if((in[i] & 0xfc)==0xf8) utf8_len = 4;
                    else if((in[i] & 0xfe)==0xfc) utf8_len = 5;
                    start = length;
                    buffer[length++] = in[i++];
                }
                else {
                    utf8_len--;
                    if(!utf8_len) {
                        buffer[length++] = in[i++];
                        buffer[start] = (((byte)buffer[start])<<6) | (((byte)buffer[start+1])&0x3f);
                        length = start+1;
                    }
                }
            }
            else {
                if(c=='&') {
                    c=';';
                    start = length;
                }
                else {
                    c='&';
                    buffer[length]=0;
                    if(buffer[start+1]=='#') {
                        if(buffer[start+2]=='x') {
                            buffer[start] = (char) strtoul(&buffer[start+3],0,16);
                        }
                        else {
                            buffer[start] = (char) strtoul(&buffer[start+2],0,10);
                        }
                    }
                    else if(!strcmp(&buffer[start],"&lt")) {
                        buffer[start] = '<';
                    }
                    else if(!strcmp(&buffer[start],"&gt")) {
                        buffer[start] = '>';
                    }
                    else if(!strcmp(&buffer[start],"&amp")) {
                        buffer[start] = '&';
                    }
                    else if(!strcmp(&buffer[start],"&quot")) {
                        buffer[start] = '"';
                    }
                    else if(!strcmp(&buffer[start],"&apos")) {
                        buffer[start] = '\'';
                    }
                    length = start+1;
                    i++;
                }
            }
        }
    }
    buffer[length] = 0;
    return length;
}

int xml_io::wellformed(const char * xml, unsigned maxlen)
{
    if (!xml) return false;
    unsigned l = (unsigned)strnlen(xml, maxlen);
    char * b = (char *)alloca(l + 1);
    word decoded;
    memcpy(b, xml, l);
    b[l] = 0;
    class xml_io x(b);
    if (x.decode(false, &decoded)) {
        if (l == decoded) return l;
        return -(int)decoded;
    }
    return 0;
}

#if !defined(PLATFORM_NO_PACKET)
class packet * xml_io::str_packet_to_xml_packet(class packet * data)
{
    class packet * out = new packet();
    for(class packet_ptr ptr; !ptr.eop();) {
        int l;
        char * b = (char *)data->read_fragment(&ptr,&l);
        str_to_xml_packet(out,b,(word)l);
    }
    return out;
}

class packet * xml_io::latin1_packet_to_xml_packet(class packet * data)
{
    class packet * out = new packet();
    for(class packet_ptr ptr; !ptr.eop();) {
        int l;
        char * b = (char *)data->read_fragment(&ptr,&l);
        latin1_to_xml_packet(out,b,(word)l);
    }
    return out;
}
#endif

#if !defined(PLATFORM_NO_DEBUG)
void xml_io::dump()
{
    static const char * types [] = {"TAG","ATTRIB","VALUE","CONTENT","PACKET","HREF"};

    for(unsigned i=0; i<index && i<256; i++) {
        debug->iprintf("(%u): base=%u count=%u href=%u %s=%s(%u)",
            i,
            content[i].base,
            content[i].count,
            content[i].href,
            types[content[i].type],
            content[i].type!=XML_TYPE_HREF?content[i].info:"->",
            content[i].type!=XML_TYPE_HREF?0:(word)(uintp)content[i].info);
    }
}
#endif
