4 * Copyright (C) 2008 Adam Williams <broadcast at earthling dot net>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 #include "bcsignals.h"
28 #include "arraylist.h"
31 #include "mainerror.h"
33 // messes up cutads link
35 #define eprintf printf
37 static const char left_delm = '<', right_delm = '>';
38 const char *FileXML::xml_header = "<?xml version=\"1.0\"?>\n";
39 const int FileXML::xml_header_size = strlen(xml_header);
41 XMLBuffer::XMLBuffer(long buf_size, long max_size, int del)
44 bfr = new unsigned char[bsz];
49 share_lock = new Mutex("XMLBuffer::share_lock", 1);
52 XMLBuffer::XMLBuffer(const char *buf, long buf_size, int del)
54 bfr = (unsigned char *)buf;
60 share_lock = new Mutex("XMLBuffer::share_lock");
63 XMLBuffer::XMLBuffer(long buf_size, char *buf, int del)
65 bfr = (unsigned char *)buf;
71 share_lock = new Mutex("XMLBuffer::share_lock");
74 XMLBuffer::~XMLBuffer()
76 if( destroy ) delete [] bfr;
80 int XMLBuffer::demand(long len)
83 if( !destroy ) return 0;
85 len += sz/2 + BCTEXTLEN;
86 unsigned char *np = new unsigned char[len];
87 if( sz > 0 ) memcpy(np,bfr,sz);
89 outp = np + (outp-bfr);
90 lmt = np + len; bsz = len;
91 delete [] bfr; bfr = np;
96 int XMLBuffer::write(const char *bp, int len)
98 if( len <= 0 ) return 0;
99 if( !destroy && lmt-inp < len ) len = lmt-inp;
106 int XMLBuffer::read(char *bp, int len)
108 long size = inp - outp;
109 if( size <= 0 && len > 0 ) return -1;
110 if( len > size ) len = size;
111 memmove(bp,outp,len);
116 void XMLBuffer::copy_from(XMLBuffer *xbuf)
118 xbuf->share_lock->lock("XMLBuffer::copy_from");
119 share_lock->lock("XMLBuffer::copy_from");
121 write((const char*)xbuf->pos(0), xbuf->otell());
122 iseek(xbuf->itell());
123 xbuf->share_lock->unlock();
124 share_lock->unlock();
128 // Precision in base 10
129 // for float is 6 significant figures
130 // for double is 16 significant figures
132 XMLTag::Property::Property(const char *pp, const char *vp)
134 //printf("Property %s = %s\n",pp, vp);
139 XMLTag::Property::~Property()
154 properties.remove_all_objects();
158 const char *XMLTag::get_property(const char *property, char *value)
160 int i = properties.size();
161 while( --i >= 0 && strcasecmp(properties[i]->prop, property) );
163 strcpy(value, properties[i]->value);
170 const char *XMLTag::get_property_text(int i) {
171 return i < properties.size() ? properties[i]->prop : "";
173 int XMLTag::get_property_int(int i) {
174 return i < properties.size() ? atol(properties[i]->value) : 0;
176 float XMLTag::get_property_float(int i) {
177 return i < properties.size() ? atof(properties[i]->value) : 0.;
179 const char* XMLTag::get_property(const char *prop) {
180 for( int i=0; i<properties.size(); ++i ) {
181 if( !strcasecmp(properties[i]->prop, prop) )
182 return properties[i]->value;
186 int32_t XMLTag::get_property(const char *prop, int32_t dflt) {
187 const char *cp = get_property(prop);
188 return !cp ? dflt : atol(cp);
190 int64_t XMLTag::get_property(const char *prop, int64_t dflt) {
191 const char *cp = get_property(prop);
192 return !cp ? dflt : strtoll(cp,0,0);
194 float XMLTag::get_property(const char *prop, float dflt) {
195 const char *cp = get_property(prop);
196 return !cp ? dflt : atof(cp);
198 double XMLTag::get_property(const char *prop, double dflt) {
199 const char *cp = get_property(prop);
200 return !cp ? dflt : atof(cp);
204 int XMLTag::set_title(const char *text) {
208 int XMLTag::set_property(const char *text, const char *value) {
209 properties.append(new XMLTag::Property(text, value));
212 int XMLTag::set_property(const char *text, int32_t value)
214 char text_value[BCSTRLEN];
215 sprintf(text_value, "%d", value);
216 set_property(text, text_value);
219 int XMLTag::set_property(const char *text, int64_t value)
221 char text_value[BCSTRLEN];
222 sprintf(text_value, "%jd", value);
223 set_property(text, text_value);
226 int XMLTag::set_property(const char *text, float value)
228 char text_value[BCSTRLEN];
229 if (value - (float)((int64_t)value) == 0)
230 sprintf(text_value, "%jd", (int64_t)value);
232 sprintf(text_value, "%.6e", value);
233 set_property(text, text_value);
236 int XMLTag::set_property(const char *text, double value)
238 char text_value[BCSTRLEN];
239 if (value - (double)((int64_t)value) == 0)
240 sprintf(text_value, "%jd", (int64_t)value);
242 sprintf(text_value, "%.16e", value);
243 set_property(text, text_value);
248 int XMLTag::reset_tag()
251 properties.remove_all_objects();
255 int XMLTag::write_tag(FileXML *xml)
257 XMLBuffer *buf = xml->buffer;
259 buf->next(left_delm);
260 buf->write(title, strlen(title));
263 for( int i=0; i<properties.size(); ++i ) {
264 const char *prop = properties[i]->prop;
265 const char *value = properties[i]->value;
266 int plen = strlen(prop), vlen = strlen(value);
267 bool need_quotes = !vlen || strchr(value,' ') || strchr(value,'\n');
269 xml->append_text(prop, plen);
271 if( need_quotes ) buf->next('\"');
272 xml->append_text(value, vlen);
273 if( need_quotes ) buf->next('\"');
276 buf->next(right_delm);
280 #define ERETURN(s) do { \
281 printf("XMLTag::read_tag:%d tag \"%s\"%s\n",__LINE__,title,s);\
283 #define EOB_RETURN(s) ERETURN(", end of buffer");
285 int XMLTag::read_tag(FileXML *xml)
287 XMLBuffer *buf = xml->buffer;
289 long prop_start, prop_end;
290 long value_start, value_end;
292 int ch = buf->next();
295 while( ch>=0 && ws(ch) ) ch = buf->next();
296 if( ch < 0 ) EOB_RETURN();
299 ttl = buf->itell() - 1;
300 for( int i=0; i<MAX_TITLE && ch>=0; ++i, ch=buf->next() ) {
301 if( ch == right_delm || ch == '=' || ws(ch) ) break;
303 if( ch < 0 ) EOB_RETURN();
304 len = buf->itell()-1 - ttl;
305 if( len >= MAX_TITLE ) ERETURN(", title too long");
308 memmove(title, buf->pos(ttl), len);
311 // no title but first property.
318 while( ch >= 0 && ch != right_delm ) {
319 // find tag start, skip header leadin
320 while( ch >= 0 && (ch==left_delm || ws(ch)) )
322 // find end of property name
323 prop_start = buf->itell()-1;
324 while( ch >= 0 && (ch!=right_delm && ch!='=' && !ws(ch)) )
326 if( ch < 0 ) EOB_RETURN();
327 prop_end = buf->itell()-1;
329 while( ch >= 0 && ws(ch) )
331 if( ch == '=' ) ch = buf->next();
332 while( ch >= 0 && ws(ch) )
334 if( ch < 0 ) EOB_RETURN();
335 // set terminating char
342 value_start = buf->itell()-1;
344 // old edl bug work-around, allow nl in quoted string
345 if( ch==term || ch==right_delm ) break;
346 if( ch=='\n' && term!='\"' ) break;
349 if( ch < 0 ) EOB_RETURN();
350 value_end = buf->itell()-1;
352 int plen = prop_end-prop_start;
353 if( !plen ) continue;
354 int vlen = value_end-value_start;
355 char prop[plen+1], value[vlen+1];
356 const char *coded_prop = (const char *)buf->pos(prop_start);
357 const char *coded_value = (const char *)buf->pos(value_start);
358 // props should not have coded text
359 memcpy(prop, coded_prop, plen);
361 xml->decode(value, coded_value, vlen);
362 if( prop_end > prop_start ) {
363 Property *property = new Property(prop, value);
364 properties.append(property);
366 // skip the terminating char
367 if( ch != right_delm ) ch = buf->next();
369 if( !properties.size() && !title[0] ) ERETURN(", emtpy");
375 FileXML::FileXML(int coded)
379 buffer = new XMLBuffer();
386 if( !shared ) delete buffer;
387 else buffer->share_lock->unlock();
392 int FileXML::terminate_string()
398 int FileXML::rewind()
406 int FileXML::append_newline()
408 append_data("\n", 1);
412 int FileXML::append_tag()
415 append_text(tag.string, tag.used);
420 int FileXML::append_text(const char *text)
423 append_text(text, strlen(text));
427 int FileXML::append_data(const char *text)
430 append_data(text, strlen(text));
434 int FileXML::append_data(const char *text, long len)
436 if( text != 0 && len > 0 )
437 buffer->write(text, len);
441 int FileXML::append_text(const char *text, long len)
443 if( text != 0 && len > 0 ) {
444 int size = coded_length(text, len);
445 char coded_text[size+1];
446 encode(coded_text, text, len);
447 buffer->write(coded_text, size);
453 char* FileXML::get_data()
455 char *data = (char *)buffer->cstr();
456 long ofs = buffer->itell();
459 char* FileXML::string()
461 return (char *)buffer->cstr();
464 long FileXML::length()
466 return buffer->otell();
469 char* FileXML::read_text(const char *tag_title)
471 if( !tag_title ) tag_title = tag.title;
472 int ch = buffer->next();
473 // filter out first char is new line
474 if( ch == '\n' ) ch = buffer->next();
475 long ipos = buffer->itell();
476 if( ch >= 0 ) --ipos;
478 // scan for delimiter
480 while( ch >= 0 && ch != left_delm ) ch = buffer->next();
482 pos = buffer->itell()-1;
483 if( (ch = buffer->next()) != '/' ) continue;
484 const char *cp = tag_title;
485 while( (ch=buffer->next()) >= 0 && ch == *cp ) ++cp;
488 while( ch == ' ' ) ch = buffer->next();
489 if( ch == right_delm ) break;
492 pos = buffer->itell();
494 long len = pos - ipos;
495 if( len >= output_length ) {
497 output_length = len+1;
498 output = new char[output_length];
501 decode(output,(const char *)buffer->pos(ipos), len);
506 int FileXML::read_tag()
508 int ch = buffer->next();
510 while( ch >= 0 && ch != left_delm ) ch = buffer->next();
511 if( ch < 0 ) return 1;
513 return tag.read_tag(this);
516 int FileXML::skip_tag()
518 char tag_title[sizeof(tag.title)];
519 strcpy(tag_title, tag.title);
521 while( !read_tag() ) {
522 if( tag.title[0] == tag_title[0] ) {
523 if( !strcasecmp(&tag_title[1], &tag.title[1]) ) ++n;
525 else if( tag.title[0] != '/' ) continue;
526 else if( strcasecmp(&tag_title[0], &tag.title[1]) ) continue;
527 else if( --n <= 0 ) return 0;
532 int FileXML::read_data_until(const char *tag_end, XMLBuffer *xbuf, int skip)
534 long ipos = buffer->itell();
536 for( int ch=buffer->next(); ch>=0; ch=buffer->next() ) {
537 if( pos < 0 ) { // looking for next tag
538 if( ch == left_delm ) {
539 ipos = buffer->itell()-1;
546 // check for end of match
547 if( !tag_end[pos] && ch == right_delm ) break;
548 // if mismatched, copy prefix to out
549 if( tag_end[pos] != ch ) {
550 xbuf->next(left_delm);
551 for( int i=0; i<pos; ++i )
552 xbuf->next(tag_end[i]);
559 // if end tag is reached, pos is left on the < of the end tag
560 if( !skip && pos >= 0 && !tag_end[pos] )
562 return xbuf->otell();
565 int FileXML::read_text_until(const char *tag_end, XMLBuffer *xbuf, int skip)
567 int len = read_data_until(tag_end, xbuf, skip);
568 char *cp = xbuf->cstr();
573 int FileXML::write_to_file(const char *filename)
575 FILE *out = fopen(filename, "wb");
577 eprintf("write_to_file %d \"%s\": %m\n", __LINE__, filename);
580 int ret = write_to_file(out, filename);
585 int FileXML::write_to_file(FILE *file, const char *filename)
587 strcpy(this->filename, filename);
588 const char *str = string();
589 long len = strlen(str);
590 // Position may have been rewound after storing
591 if( !fwrite(xml_header, xml_header_size, 1, file) ||
592 ( len > 0 && !fwrite(str, len, 1, file) ) ) {
593 eprintf("\"%s\": %m\n", filename);
599 int FileXML::read_from_file(const char *filename, int ignore_error)
602 strcpy(this->filename, filename);
603 FILE *in = fopen(filename, "rb");
606 eprintf("\"%s\" %m\n", filename);
609 fseek(in, 0, SEEK_END);
610 long length = ftell(in);
611 fseek(in, 0, SEEK_SET);
612 char *fbfr = new char[length+1];
614 (void)fread(fbfr, length, 1, in);
616 buffer = new XMLBuffer(fbfr, length, 1);
621 int FileXML::read_from_string(char *string)
623 strcpy(this->filename, "");
624 long length = strlen(string);
625 char *sbfr = new char[length+1];
626 strcpy(sbfr, string);
628 buffer = new XMLBuffer(sbfr, length, 1);
632 void FileXML::set_coding(int coding)
636 decode = XMLBuffer::decode_data;
637 encode = XMLBuffer::encode_data;
638 coded_length = XMLBuffer::encoded_length;
641 decode = XMLBuffer::copy_data;
642 encode = XMLBuffer::copy_data;
643 coded_length = XMLBuffer::copy_length;
647 int FileXML::get_coding()
652 int FileXML::set_shared_input(XMLBuffer *xbuf)
654 strcpy(this->filename, "");
657 xbuf->share_lock->lock("FileXML::set_shared_input");
664 int FileXML::set_shared_output(XMLBuffer *xbuf)
666 strcpy(this->filename, "");
669 xbuf->share_lock->lock("FileXML::set_shared_output");
677 // ================================ XML tag
679 int XMLTag::title_is(const char *tp)
681 return !strcasecmp(title, tp) ? 1 : 0;
684 char* XMLTag::get_title()
689 int XMLTag::get_title(char *value)
691 if( title[0] != 0 ) strcpy(value, title);
695 int XMLTag::test_property(char *property, char *value)
697 for( int i=0; i<properties.size(); ++i ) {
698 if( !strcasecmp(properties[i]->prop, property) &&
699 !strcasecmp(properties[i]->value, value) )
705 static inline int xml_cmp(const char *np, const char *sp)
707 const char *tp = ++np;
708 while( *np ) { if( *np != *sp ) return 0; ++np; ++sp; }
712 char *XMLBuffer::decode_data(char *bp, const char *sp, int n)
715 if( n < 0 ) n = strlen(sp);
716 const char *ep = sp + n;
718 if( (n=*sp++) != '&' ) { *bp++ = n; continue; }
719 switch( (n=*sp++) ) {
721 if( (n=xml_cmp("amp;", sp)) ) { *bp++ = '&'; sp += n; continue; }
724 if( (n=xml_cmp("gt;", sp)) ) { *bp++ = '>'; sp += n; continue; }
727 if( (n=xml_cmp("lt;", sp)) ) { *bp++ = '<'; sp += n; continue; }
730 if( (n=xml_cmp("quot;", sp)) ) { *bp++ = '"'; sp += n; continue; }
732 case '#': { // &#<num>;
733 char *cp = 0; int v = strtoul(sp,&cp,10);
734 if( *cp == ';' ) { *bp++ = (char)v; sp = cp+1; continue; }
743 while( --n >= 0 ) *bp++ = *sp++;
750 char *XMLBuffer::encode_data(char *bp, const char *sp, int n)
753 if( n < 0 ) n = strlen(sp);
754 const char *cp, *ep = sp + n;
758 case '<': cp = "<"; break;
759 case '>': cp = ">"; break;
760 case '&': cp = "&"; break;
761 case '"': cp = """; break;
762 default: *bp++ = ch; continue;
764 while( *cp != 0 ) *bp++ = *cp++;
770 long XMLBuffer::encoded_length(const char *sp, int n)
773 if( n < 0 ) n = strlen(sp);
774 const char *ep = sp + n;
778 case '<': len += 4; break;
779 case '>': len += 4; break;
780 case '&': len += 5; break;
781 case '"': len += 6; break;
782 default: ++len; break;
788 char *XMLBuffer::copy_data(char *bp, const char *sp, int n)
790 int len = n < 0 ? strlen(sp) : n;
797 long XMLBuffer::copy_length(const char *sp, int n)
799 int len = n < 0 ? strlen(sp) : n;