--- /dev/null
+
+/*
+ * CINELERRA
+ * Copyright (C) 2008 Adam Williams <broadcast at earthling dot net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "bcsignals.h"
+#include "arraylist.h"
+#include "cstrdup.h"
+#include "filexml.h"
+#include "mainerror.h"
+
+// messes up cutads link
+#undef eprintf
+#define eprintf printf
+
+static const char left_delm = '<', right_delm = '>';
+const char *FileXML::xml_header = "<?xml version=\"1.0\"?>\n";
+const int FileXML::xml_header_size = strlen(xml_header);
+
+XMLBuffer::XMLBuffer(long buf_size, long max_size, int del)
+{
+ bsz = buf_size;
+ bfr = new unsigned char[bsz];
+ inp = outp = bfr;
+ lmt = bfr + bsz;
+ isz = max_size;
+ destroy = del;
+}
+
+XMLBuffer::XMLBuffer(const char *buf, long buf_size, int del)
+{ // reading
+ bfr = (unsigned char *)buf;
+ bsz = buf_size;
+ outp = bfr;
+ lmt = inp = bfr+bsz;
+ isz = bsz;
+ destroy = del;
+}
+
+XMLBuffer::XMLBuffer(long buf_size, const char *buf, int del)
+{ // writing
+ bfr = (unsigned char *)buf;
+ bsz = buf_size;
+ inp = bfr;
+ lmt = outp = bfr+bsz;
+ isz = bsz;
+ destroy = del;
+}
+
+XMLBuffer::~XMLBuffer()
+{
+ if( destroy ) delete [] bfr;
+}
+
+unsigned char *&XMLBuffer::demand(long len)
+{
+ if( len > bsz ) {
+ len += BCTEXTLEN;
+ unsigned char *np = new unsigned char[len];
+ if( inp > bfr ) memcpy(np,bfr,inp-bfr);
+ inp = np + (inp-bfr);
+ outp = np + (outp-bfr);
+ lmt = np + len; bsz = len;
+ delete [] bfr; bfr = np;
+ }
+ return bfr;
+}
+
+int XMLBuffer::write(const char *bp, int len)
+{
+ if( !destroy && lmt-inp < len ) len = lmt-inp;
+ if( len <= 0 ) return 0;
+ demand(otell()+len);
+ memmove(inp,bp,len);
+ inp += len;
+ return len;
+}
+
+int XMLBuffer::read(char *bp, int len)
+{
+ long size = inp - outp;
+ if( size <= 0 && len > 0 ) return -1;
+ if( len > size ) len = size;
+ memmove(bp,outp,len);
+ outp += len;
+ return len;
+}
+
+
+// Precision in base 10
+// for float is 6 significant figures
+// for double is 16 significant figures
+
+XMLTag::Property::Property(const char *pp, const char *vp)
+{
+//printf("Property %s = %s\n",pp, vp);
+ prop = cstrdup(pp);
+ value = cstrdup(vp);
+}
+
+XMLTag::Property::~Property()
+{
+ delete [] prop;
+ delete [] value;
+}
+
+
+XMLTag::XMLTag()
+{
+ string = 0;
+ avail = used = 0;
+}
+
+XMLTag::~XMLTag()
+{
+ properties.remove_all_objects();
+ delete [] string;
+}
+
+const char *XMLTag::get_property(const char *property, char *value)
+{
+ int i = properties.size();
+ while( --i >= 0 && strcasecmp(properties[i]->prop, property) );
+ if( i >= 0 )
+ strcpy(value, properties[i]->value);
+ else
+ *value = 0;
+ return value;
+}
+
+//getters
+const char *XMLTag::get_property_text(int i) {
+ return i < properties.size() ? properties[i]->prop : "";
+}
+int XMLTag::get_property_int(int i) {
+ return i < properties.size() ? atol(properties[i]->value) : 0;
+}
+float XMLTag::get_property_float(int i) {
+ return i < properties.size() ? atof(properties[i]->value) : 0.;
+}
+const char* XMLTag::get_property(const char *prop) {
+ for( int i=0; i<properties.size(); ++i ) {
+ if( !strcasecmp(properties[i]->prop, prop) )
+ return properties[i]->value;
+ }
+ return 0;
+}
+int32_t XMLTag::get_property(const char *prop, int32_t dflt) {
+ const char *cp = get_property(prop);
+ return !cp ? dflt : atol(cp);
+}
+int64_t XMLTag::get_property(const char *prop, int64_t dflt) {
+ const char *cp = get_property(prop);
+ return !cp ? dflt : strtoll(cp,0,0);
+}
+float XMLTag::get_property(const char *prop, float dflt) {
+ const char *cp = get_property(prop);
+ return !cp ? dflt : atof(cp);
+}
+double XMLTag::get_property(const char *prop, double dflt) {
+ const char *cp = get_property(prop);
+ return !cp ? dflt : atof(cp);
+}
+
+//setters
+int XMLTag::set_title(const char *text) {
+ strcpy(title, text);
+ return 0;
+}
+int XMLTag::set_property(const char *text, const char *value) {
+ properties.append(new XMLTag::Property(text, value));
+ return 0;
+}
+int XMLTag::set_property(const char *text, int32_t value)
+{
+ char text_value[BCSTRLEN];
+ sprintf(text_value, "%d", value);
+ set_property(text, text_value);
+ return 0;
+}
+int XMLTag::set_property(const char *text, int64_t value)
+{
+ char text_value[BCSTRLEN];
+ sprintf(text_value, "%jd", value);
+ set_property(text, text_value);
+ return 0;
+}
+int XMLTag::set_property(const char *text, float value)
+{
+ char text_value[BCSTRLEN];
+ if (value - (float)((int64_t)value) == 0)
+ sprintf(text_value, "%jd", (int64_t)value);
+ else
+ sprintf(text_value, "%.6e", value);
+ set_property(text, text_value);
+ return 0;
+}
+int XMLTag::set_property(const char *text, double value)
+{
+ char text_value[BCSTRLEN];
+ if (value - (double)((int64_t)value) == 0)
+ sprintf(text_value, "%jd", (int64_t)value);
+ else
+ sprintf(text_value, "%.16e", value);
+ set_property(text, text_value);
+ return 0;
+}
+
+
+int XMLTag::reset_tag()
+{
+ used = 0;
+ properties.remove_all_objects();
+ return 0;
+}
+
+int XMLTag::write_tag(FileXML *xml)
+{
+ XMLBuffer *buf = xml->buffer;
+// title header
+ buf->next(left_delm);
+ buf->write(title, strlen(title));
+
+// properties
+ for( int i=0; i<properties.size(); ++i ) {
+ const char *prop = properties[i]->prop;
+ const char *value = properties[i]->value;
+ int plen = strlen(prop), vlen = strlen(value);
+ bool need_quotes = !vlen || strchr(value,' ');
+ buf->next(' ');
+ xml->append_text(prop, plen);
+ buf->next('=');
+ if( need_quotes ) buf->next('\"');
+ xml->append_text(value, vlen);
+ if( need_quotes ) buf->next('\"');
+ }
+
+ buf->next(right_delm);
+ return 0;
+}
+
+#define ERETURN(s) do { \
+ printf("XMLTag::read_tag:%d tag \"%s\"%s\n",__LINE__,title,s);\
+ return 1; } while(0)
+#define EOB_RETURN(s) ERETURN(", end of buffer");
+
+int XMLTag::read_tag(FileXML *xml)
+{
+ XMLBuffer *buf = xml->buffer;
+ int len, term;
+ long prop_start, prop_end;
+ long value_start, value_end;
+ long ttl;
+ int ch = buf->next();
+ title[0] = 0;
+// skip ws
+ while( ch>=0 && ws(ch) ) ch = buf->next();
+ if( ch < 0 ) EOB_RETURN();
+
+// read title
+ ttl = buf->itell() - 1;
+ for( int i=0; i<MAX_TITLE && ch>=0; ++i, ch=buf->next() ) {
+ if( ch == right_delm || ch == '=' || ws(ch) ) break;
+ }
+ if( ch < 0 ) EOB_RETURN();
+ len = buf->itell()-1 - ttl;
+ if( len >= MAX_TITLE ) ERETURN(", title too long");
+// if title
+ if( ch != '=' ) {
+ memmove(title, buf->pos(ttl), len);
+ title[len] = 0;
+ }
+// no title but first property.
+ else {
+ title[0] = 0;
+ buf->iseek(ttl);
+ ch = buf->next();
+ }
+// read properties
+ while( ch >= 0 && ch != right_delm ) {
+// find tag start, skip header leadin
+ while( ch >= 0 && (ch==left_delm || ws(ch)) )
+ ch = buf->next();
+// find end of property name
+ prop_start = buf->itell()-1;
+ while( ch >= 0 && (ch!=right_delm && ch!='=' && !ws(ch)) )
+ ch = buf->next();
+ if( ch < 0 ) EOB_RETURN();
+ prop_end = buf->itell()-1;
+// skip ws = ws
+ while( ch >= 0 && ws(ch) )
+ ch = buf->next();
+ if( ch == '=' ) ch = buf->next();
+ while( ch >= 0 && ws(ch) )
+ ch = buf->next();
+ if( ch < 0 ) EOB_RETURN();
+// set terminating char
+ if( ch == '\"' ) {
+ term = ch;
+ ch = buf->next();
+ }
+ else
+ term = ' ';
+ value_start = buf->itell()-1;
+ while( ch >= 0 && (ch!=term && ch!=right_delm && ch!='\n') )
+ ch = buf->next();
+ if( ch < 0 ) EOB_RETURN();
+ value_end = buf->itell()-1;
+// add property
+ int plen = prop_end-prop_start;
+ if( !plen ) continue;
+ int vlen = value_end-value_start;
+ char prop[plen+1], value[vlen+1];
+ const char *coded_prop = (const char *)buf->pos(prop_start);
+ const char *coded_value = (const char *)buf->pos(value_start);
+// props should not have coded text
+ memcpy(prop, coded_prop, plen);
+ prop[plen] = 0;
+ xml->decode(value, coded_value, vlen);
+ if( prop_end > prop_start ) {
+ Property *property = new Property(prop, value);
+ properties.append(property);
+ }
+// skip the terminating char
+ if( ch != right_delm ) ch = buf->next();
+ }
+ if( !properties.size() && !title[0] ) ERETURN(", emtpy");
+ return 0;
+}
+
+
+
+FileXML::FileXML(int coded)
+{
+ output = 0;
+ output_length = 0;
+ buffer = new XMLBuffer();
+ set_coding(coded);
+}
+
+FileXML::~FileXML()
+{
+ delete buffer;
+ delete [] output;
+}
+
+
+int FileXML::terminate_string()
+{
+ append_data("", 1);
+ return 0;
+}
+
+int FileXML::rewind()
+{
+ terminate_string();
+ buffer->iseek(0);
+ return 0;
+}
+
+
+int FileXML::append_newline()
+{
+ append_data("\n", 1);
+ return 0;
+}
+
+int FileXML::append_tag()
+{
+ tag.write_tag(this);
+ append_text(tag.string, tag.used);
+ tag.reset_tag();
+ return 0;
+}
+
+int FileXML::append_text(const char *text)
+{
+ if( text != 0 )
+ append_text(text, strlen(text));
+ return 0;
+}
+
+int FileXML::append_data(const char *text)
+{
+ if( text != 0 )
+ append_data(text, strlen(text));
+ return 0;
+}
+
+int FileXML::append_data(const char *text, long len)
+{
+ if( text != 0 && len > 0 )
+ buffer->write(text, len);
+ return 0;
+}
+
+int FileXML::append_text(const char *text, long len)
+{
+ if( text != 0 && len > 0 ) {
+ int size = coded_length(text, len);
+ char coded_text[size+1];
+ encode(coded_text, text, len);
+ buffer->write(coded_text, size);
+ }
+ return 0;
+}
+
+
+char* FileXML::get_data()
+{
+ long ofs = buffer->itell();
+ return (char *)buffer->pos(ofs);
+}
+char* FileXML::string()
+{
+ return (char *)buffer->str();
+}
+
+long FileXML::length()
+{
+ return buffer->otell();
+}
+
+char* FileXML::read_text()
+{
+ int ch = buffer->next();
+// filter out first char is new line
+ if( ch == '\n' ) ch = buffer->next();
+ long ipos = buffer->itell()-1;
+// scan for delimiter
+ while( ch >= 0 && ch != left_delm ) ch = buffer->next();
+ long pos = buffer->itell()-1;
+ if( ch >= 0 ) buffer->iseek(pos);
+ long len = pos - ipos;
+ if( len >= output_length ) {
+ delete [] output;
+ output_length = len+1;
+ output = new char[output_length];
+ }
+ decode(output,(const char *)buffer->pos(ipos), len);
+ return output;
+}
+
+int FileXML::read_tag()
+{
+ int ch = buffer->next();
+// scan to next tag
+ while( ch >= 0 && ch != left_delm ) ch = buffer->next();
+ if( ch < 0 ) return 1;
+ tag.reset_tag();
+ return tag.read_tag(this);
+}
+
+int FileXML::read_data_until(const char *tag_end, char *out, int len)
+{
+ long ipos = buffer->itell();
+ int opos = 0, pos = -1;
+ int ch = buffer->next();
+ for( int olen=len-1; ch>=0 && opos<olen; ch=buffer->next() ) {
+ if( pos < 0 ) { // looking for next tag
+ if( ch == left_delm ) {
+ ipos = buffer->itell()-1;
+ pos = 0;
+ }
+ else
+ out[opos++] = ch;
+ continue;
+ }
+ // check for end of match
+ if( !tag_end[pos] && ch == right_delm ) break;
+ // if mismatched, copy prefix to out
+ if( tag_end[pos] != ch ) {
+ out[opos++] = left_delm;
+ for( int i=0; i<pos && opos<olen; ++i )
+ out[opos++] = tag_end[i];
+ if( opos < olen ) out[opos++] = ch;
+ pos = -1;
+ continue;
+ }
+ ++pos;
+ }
+// if end tag is reached, pos is left on the < of the end tag
+ if( pos >= 0 && !tag_end[pos] )
+ buffer->iseek(ipos);
+ return opos;
+}
+
+int FileXML::read_text_until(const char *tag_end, char *out, int len)
+{
+ char data[len+1];
+ int opos = read_data_until(tag_end, data, len);
+ decode(out, data, opos);
+ return 0;
+}
+
+int FileXML::write_to_file(const char *filename)
+{
+ FILE *out = fopen(filename, "wb");
+ if( !out ) {
+ eprintf("write_to_file %d \"%s\": %m\n", __LINE__, filename);
+ return 1;
+ }
+ int ret = write_to_file(out, filename);
+ fclose(out);
+ return ret;
+}
+
+int FileXML::write_to_file(FILE *file, const char *filename)
+{
+ strcpy(this->filename, filename);
+ const char *str = string();
+ long len = strlen(str);
+// Position may have been rewound after storing
+ if( !fwrite(xml_header, xml_header_size, 1, file) ||
+ ( len > 0 && !fwrite(str, len, 1, file) ) ) {
+ eprintf("\"%s\": %m\n", filename);
+ return 1;
+ }
+ return 0;
+}
+
+int FileXML::read_from_file(const char *filename, int ignore_error)
+{
+
+ strcpy(this->filename, filename);
+ FILE *in = fopen(filename, "rb");
+ if( !in ) {
+ if(!ignore_error)
+ eprintf("\"%s\" %m\n", filename);
+ return 1;
+ }
+ fseek(in, 0, SEEK_END);
+ long length = ftell(in);
+ fseek(in, 0, SEEK_SET);
+ char *fbfr = new char[length+1];
+ delete buffer;
+ (void)fread(fbfr, length, 1, in);
+ fbfr[length] = 0;
+ buffer = new XMLBuffer(fbfr, length, 1);
+ fclose(in);
+ return 0;
+}
+
+int FileXML::read_from_string(char *string)
+{
+ strcpy(this->filename, "");
+ long length = strlen(string);
+ char *sbfr = new char[length+1];
+ strcpy(sbfr, string);
+ delete buffer;
+ buffer = new XMLBuffer(sbfr, length, 1);
+ return 0;
+}
+
+void FileXML::set_coding(int coding)
+{
+ coded = coding;
+ if( coded ) {
+ decode = XMLBuffer::decode_data;
+ encode = XMLBuffer::encode_data;
+ coded_length = XMLBuffer::encoded_length;
+ }
+ else {
+ decode = XMLBuffer::copy_data;
+ encode = XMLBuffer::copy_data;
+ coded_length = XMLBuffer::copy_length;
+ }
+}
+
+int FileXML::get_coding()
+{
+ return coded;
+}
+
+int FileXML::set_shared_input(char *shared_string, long avail, int coded)
+{
+ strcpy(this->filename, "");
+ delete buffer;
+ buffer = new XMLBuffer(shared_string, avail, 0);
+ set_coding(coded);
+ return 0;
+}
+
+int FileXML::set_shared_output(char *shared_string, long avail, int coded)
+{
+ strcpy(this->filename, "");
+ delete buffer;
+ buffer = new XMLBuffer(avail, shared_string, 0);
+ set_coding(coded);
+ return 0;
+}
+
+
+
+// ================================ XML tag
+
+
+
+int XMLTag::title_is(const char *tp)
+{
+ return !strcasecmp(title, tp) ? 1 : 0;
+}
+
+char* XMLTag::get_title()
+{
+ return title;
+}
+
+int XMLTag::get_title(char *value)
+{
+ if( title[0] != 0 ) strcpy(value, title);
+ return 0;
+}
+
+int XMLTag::test_property(char *property, char *value)
+{
+ for( int i=0; i<properties.size(); ++i ) {
+ if( !strcasecmp(properties[i]->prop, property) &&
+ !strcasecmp(properties[i]->value, value) )
+ return 1;
+ }
+ return 0;
+}
+
+static inline int xml_cmp(const char *np, const char *sp)
+{
+ const char *tp = ++np;
+ while( *np ) { if( *np != *sp ) return 0; ++np; ++sp; }
+ return np - tp;
+}
+
+char *XMLBuffer::decode_data(char *bp, const char *sp, int n)
+{
+ char *ret = bp;
+ if( n < 0 ) n = strlen(sp);
+ const char *ep = sp + n;
+ while( sp < ep ) {
+ if( (n=*sp++) != '&' ) { *bp++ = n; continue; }
+ switch( (n=*sp++) ) {
+ case 'a': // &
+ if( (n=xml_cmp("amp;", sp)) ) { *bp++ = '&'; sp += n; continue; }
+ break;
+ case 'g': // >
+ if( (n=xml_cmp("gt;", sp)) ) { *bp++ = '>'; sp += n; continue; }
+ break;
+ case 'l': // <
+ if( (n=xml_cmp("lt;", sp)) ) { *bp++ = '<'; sp += n; continue; }
+ break;
+ case 'q': // "
+ if( (n=xml_cmp("quot;", sp)) ) { *bp++ = '"'; sp += n; continue; }
+ break;
+ case '#': { // &#<num>;
+ char *cp = 0; int v = strtoul(sp,&cp,10);
+ if( *cp == ';' ) { *bp++ = (char)v; sp = cp+1; continue; }
+ n = cp - sp; }
+ break;
+ default:
+ *bp++ = '&';
+ *bp++ = (char)n;
+ continue;
+ }
+ sp -= 2; n += 2;
+ while( --n >= 0 ) *bp++ = *sp++;
+ }
+ *bp = 0;
+ return ret;
+}
+
+
+char *XMLBuffer::encode_data(char *bp, const char *sp, int n)
+{
+ char *ret = bp;
+ if( n < 0 ) n = strlen(sp);
+ const char *cp, *ep = sp + n;
+ while( sp < ep ) {
+ int ch = *sp++;
+ switch( ch ) {
+ case '<': cp = "<"; break;
+ case '>': cp = ">"; break;
+ case '&': cp = "&"; break;
+ case '"': cp = """; break;
+ default: *bp++ = ch; continue;
+ }
+ while( *cp != 0 ) *bp++ = *cp++;
+ }
+ *bp = 0;
+ return ret;
+}
+
+long XMLBuffer::encoded_length(const char *sp, int n)
+{
+ long len = 0;
+ if( n < 0 ) n = strlen(sp);
+ const char *ep = sp + n;
+ while( sp < ep ) {
+ int ch = *sp++;
+ switch( ch ) {
+ case '<': len += 4; break;
+ case '>': len += 4; break;
+ case '&': len += 5; break;
+ case '"': len += 6; break;
+ default: ++len; break;
+ }
+ }
+ return len;
+}
+
+char *XMLBuffer::copy_data(char *bp, const char *sp, int n)
+{
+ int len = n < 0 ? strlen(sp) : n;
+ memmove(bp,sp,len);
+ bp[len] = 0;
+ return bp;
+}
+
+long XMLBuffer::copy_length(const char *sp, int n)
+{
+ int len = n < 0 ? strlen(sp) : n;
+ return len;
+}
+