remove whitespace at eol
[goodguy/history.git] / cinelerra-5.1 / cinelerra / filexml.C
1
2 /*
3  * CINELERRA
4  * Copyright (C) 2008 Adam Williams <broadcast at earthling dot net>
5  *
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.
10  *
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.
15  *
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
19  *
20  */
21
22 #include <stdio.h>
23 #include <stdint.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include "bcsignals.h"
28 #include "arraylist.h"
29 #include "cstrdup.h"
30 #include "filexml.h"
31 #include "mainerror.h"
32
33 // messes up cutads link
34 #undef eprintf
35 #define eprintf printf
36
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);
40
41 XMLBuffer::XMLBuffer(long buf_size, long max_size, int del)
42 {
43         bsz = buf_size;
44         bfr = new unsigned char[bsz];
45         inp = outp = bfr;
46         lmt = bfr + bsz;
47         isz = max_size;
48         destroy = del;
49 }
50
51 XMLBuffer::XMLBuffer(const char *buf, long buf_size, int del)
52 {       // reading
53         bfr = (unsigned char *)buf;
54         bsz = buf_size;
55         outp = bfr;
56         lmt = inp = bfr+bsz;
57         isz = bsz;
58         destroy = del;
59 }
60
61 XMLBuffer::XMLBuffer(long buf_size, const char *buf, int del)
62 {       // writing
63         bfr = (unsigned char *)buf;
64         bsz = buf_size;
65         inp = bfr;
66         lmt = outp = bfr+bsz;
67         isz = bsz;
68         destroy = del;
69 }
70
71 XMLBuffer::~XMLBuffer()
72 {
73         if( destroy ) delete [] bfr;
74 }
75
76 unsigned char *&XMLBuffer::demand(long len)
77 {
78         if( len > bsz ) {
79                 len += BCTEXTLEN;
80                 unsigned char *np = new unsigned char[len];
81                 if( inp > bfr ) memcpy(np,bfr,inp-bfr);
82                 inp = np + (inp-bfr);
83                 outp = np + (outp-bfr);
84                 lmt = np + len;  bsz = len;
85                 delete [] bfr;   bfr = np;
86         }
87         return bfr;
88 }
89
90 int XMLBuffer::write(const char *bp, int len)
91 {
92         if( !destroy && lmt-inp < len ) len = lmt-inp;
93         if( len <= 0 ) return 0;
94         demand(otell()+len);
95         memmove(inp,bp,len);
96         inp += len;
97         return len;
98 }
99
100 int XMLBuffer::read(char *bp, int len)
101 {
102         long size = inp - outp;
103         if( size <= 0 && len > 0 ) return -1;
104         if( len > size ) len = size;
105         memmove(bp,outp,len);
106         outp += len;
107         return len;
108 }
109
110
111 // Precision in base 10
112 // for float is 6 significant figures
113 // for double is 16 significant figures
114
115 XMLTag::Property::Property(const char *pp, const char *vp)
116 {
117 //printf("Property %s = %s\n",pp, vp);
118         prop = cstrdup(pp);
119         value = cstrdup(vp);
120 }
121
122 XMLTag::Property::~Property()
123 {
124         delete [] prop;
125         delete [] value;
126 }
127
128
129 XMLTag::XMLTag()
130 {
131         string = 0;
132         avail = used = 0;
133 }
134
135 XMLTag::~XMLTag()
136 {
137         properties.remove_all_objects();
138         delete [] string;
139 }
140
141 const char *XMLTag::get_property(const char *property, char *value)
142 {
143         int i = properties.size();
144         while( --i >= 0 && strcasecmp(properties[i]->prop, property) );
145         if( i >= 0 )
146                 strcpy(value, properties[i]->value);
147         else
148                 *value = 0;
149         return value;
150 }
151
152 //getters
153 const char *XMLTag::get_property_text(int i) {
154         return i < properties.size() ? properties[i]->prop : "";
155 }
156 int XMLTag::get_property_int(int i) {
157         return i < properties.size() ? atol(properties[i]->value) : 0;
158 }
159 float XMLTag::get_property_float(int i) {
160         return i < properties.size() ? atof(properties[i]->value) : 0.;
161 }
162 const char* XMLTag::get_property(const char *prop) {
163         for( int i=0; i<properties.size(); ++i ) {
164                 if( !strcasecmp(properties[i]->prop, prop) )
165                         return properties[i]->value;
166         }
167         return 0;
168 }
169 int32_t XMLTag::get_property(const char *prop, int32_t dflt) {
170         const char *cp = get_property(prop);
171         return !cp ? dflt : atol(cp);
172 }
173 int64_t XMLTag::get_property(const char *prop, int64_t dflt) {
174         const char *cp = get_property(prop);
175         return !cp ? dflt : strtoll(cp,0,0);
176 }
177 float XMLTag::get_property(const char *prop, float dflt) {
178         const char *cp = get_property(prop);
179         return !cp ? dflt : atof(cp);
180 }
181 double XMLTag::get_property(const char *prop, double dflt) {
182         const char *cp = get_property(prop);
183         return !cp ? dflt : atof(cp);
184 }
185
186 //setters
187 int XMLTag::set_title(const char *text) {
188         strcpy(title, text);
189         return 0;
190 }
191 int XMLTag::set_property(const char *text, const char *value) {
192         properties.append(new XMLTag::Property(text, value));
193         return 0;
194 }
195 int XMLTag::set_property(const char *text, int32_t value)
196 {
197         char text_value[BCSTRLEN];
198         sprintf(text_value, "%d", value);
199         set_property(text, text_value);
200         return 0;
201 }
202 int XMLTag::set_property(const char *text, int64_t value)
203 {
204         char text_value[BCSTRLEN];
205         sprintf(text_value, "%jd", value);
206         set_property(text, text_value);
207         return 0;
208 }
209 int XMLTag::set_property(const char *text, float value)
210 {
211         char text_value[BCSTRLEN];
212         if (value - (float)((int64_t)value) == 0)
213                 sprintf(text_value, "%jd", (int64_t)value);
214         else
215                 sprintf(text_value, "%.6e", value);
216         set_property(text, text_value);
217         return 0;
218 }
219 int XMLTag::set_property(const char *text, double value)
220 {
221         char text_value[BCSTRLEN];
222         if (value - (double)((int64_t)value) == 0)
223                 sprintf(text_value, "%jd", (int64_t)value);
224         else
225                 sprintf(text_value, "%.16e", value);
226         set_property(text, text_value);
227         return 0;
228 }
229
230
231 int XMLTag::reset_tag()
232 {
233         used = 0;
234         properties.remove_all_objects();
235         return 0;
236 }
237
238 int XMLTag::write_tag(FileXML *xml)
239 {
240         XMLBuffer *buf = xml->buffer;
241 // title header
242         buf->next(left_delm);
243         buf->write(title, strlen(title));
244
245 // properties
246         for( int i=0; i<properties.size(); ++i ) {
247                 const char *prop = properties[i]->prop;
248                 const char *value = properties[i]->value;
249                 int plen = strlen(prop), vlen = strlen(value);
250                 bool need_quotes = !vlen || strchr(value,' ');
251                 buf->next(' ');
252                 xml->append_text(prop, plen);
253                 buf->next('=');
254                 if( need_quotes ) buf->next('\"');
255                 xml->append_text(value, vlen);
256                 if( need_quotes ) buf->next('\"');
257         }
258
259         buf->next(right_delm);
260         return 0;
261 }
262
263 #define ERETURN(s) do { \
264   printf("XMLTag::read_tag:%d tag \"%s\"%s\n",__LINE__,title,s);\
265   return 1; } while(0)
266 #define EOB_RETURN(s) ERETURN(", end of buffer");
267
268 int XMLTag::read_tag(FileXML *xml)
269 {
270         XMLBuffer *buf = xml->buffer;
271         int len, term;
272         long prop_start, prop_end;
273         long value_start, value_end;
274         long ttl;
275         int ch = buf->next();
276         title[0] = 0;
277 // skip ws
278         while( ch>=0 && ws(ch) ) ch = buf->next();
279         if( ch < 0 ) EOB_RETURN();
280
281 // read title
282         ttl = buf->itell() - 1;
283         for( int i=0; i<MAX_TITLE && ch>=0; ++i, ch=buf->next() ) {
284                 if( ch == right_delm || ch == '=' || ws(ch) ) break;
285         }
286         if( ch < 0 ) EOB_RETURN();
287         len = buf->itell()-1 - ttl;
288         if( len >= MAX_TITLE ) ERETURN(", title too long");
289 // if title
290         if( ch != '=' ) {
291                 memmove(title, buf->pos(ttl), len);
292                 title[len] = 0;
293         }
294 // no title but first property.
295         else {
296                 title[0] = 0;
297                 buf->iseek(ttl);
298                 ch = buf->next();
299         }
300 // read properties
301         while( ch >= 0 && ch != right_delm ) {
302 // find tag start, skip header leadin
303                 while( ch >= 0 && (ch==left_delm || ws(ch)) )
304                         ch = buf->next();
305 // find end of property name
306                 prop_start = buf->itell()-1;
307                 while( ch >= 0 && (ch!=right_delm && ch!='=' && !ws(ch)) )
308                         ch = buf->next();
309                 if( ch < 0 ) EOB_RETURN();
310                 prop_end = buf->itell()-1;
311 // skip ws = ws
312                 while( ch >= 0 && ws(ch) )
313                         ch = buf->next();
314                 if( ch == '=' ) ch = buf->next();
315                 while( ch >= 0 && ws(ch) )
316                         ch = buf->next();
317                 if( ch < 0 ) EOB_RETURN();
318 // set terminating char
319                 if( ch == '\"' ) {
320                         term = ch;
321                         ch = buf->next();
322                 }
323                 else
324                         term = ' ';
325                 value_start = buf->itell()-1;
326                 while( ch >= 0 && (ch!=term && ch!=right_delm && ch!='\n') )
327                         ch = buf->next();
328                 if( ch < 0 ) EOB_RETURN();
329                 value_end = buf->itell()-1;
330 // add property
331                 int plen = prop_end-prop_start;
332                 if( !plen ) continue;
333                 int vlen = value_end-value_start;
334                 char prop[plen+1], value[vlen+1];
335                 const char *coded_prop = (const char *)buf->pos(prop_start);
336                 const char *coded_value = (const char *)buf->pos(value_start);
337 // props should not have coded text
338                 memcpy(prop, coded_prop, plen);
339                 prop[plen] = 0;
340                 xml->decode(value, coded_value, vlen);
341                 if( prop_end > prop_start ) {
342                         Property *property = new Property(prop, value);
343                         properties.append(property);
344                 }
345 // skip the terminating char
346                 if( ch != right_delm ) ch = buf->next();
347         }
348         if( !properties.size() && !title[0] ) ERETURN(", emtpy");
349         return 0;
350 }
351
352
353
354 FileXML::FileXML(int coded)
355 {
356         output = 0;
357         output_length = 0;
358         buffer = new XMLBuffer();
359         set_coding(coded);
360 }
361
362 FileXML::~FileXML()
363 {
364         delete buffer;
365         delete [] output;
366 }
367
368
369 int FileXML::terminate_string()
370 {
371         append_data("", 1);
372         return 0;
373 }
374
375 int FileXML::rewind()
376 {
377         terminate_string();
378         buffer->iseek(0);
379         return 0;
380 }
381
382
383 int FileXML::append_newline()
384 {
385         append_data("\n", 1);
386         return 0;
387 }
388
389 int FileXML::append_tag()
390 {
391         tag.write_tag(this);
392         append_text(tag.string, tag.used);
393         tag.reset_tag();
394         return 0;
395 }
396
397 int FileXML::append_text(const char *text)
398 {
399         if( text != 0 )
400                 append_text(text, strlen(text));
401         return 0;
402 }
403
404 int FileXML::append_data(const char *text)
405 {
406         if( text != 0 )
407                 append_data(text, strlen(text));
408         return 0;
409 }
410
411 int FileXML::append_data(const char *text, long len)
412 {
413         if( text != 0 && len > 0 )
414                 buffer->write(text, len);
415         return 0;
416 }
417
418 int FileXML::append_text(const char *text, long len)
419 {
420         if( text != 0 && len > 0 ) {
421                 int size = coded_length(text, len);
422                 char coded_text[size+1];
423                 encode(coded_text, text, len);
424                 buffer->write(coded_text, size);
425         }
426         return 0;
427 }
428
429
430 char* FileXML::get_data()
431 {
432         long ofs = buffer->itell();
433         return (char *)buffer->pos(ofs);
434 }
435 char* FileXML::string()
436 {
437         return (char *)buffer->str();
438 }
439
440 long FileXML::length()
441 {
442         return buffer->otell();
443 }
444
445 char* FileXML::read_text()
446 {
447         int ch = buffer->next();
448 // filter out first char is new line
449         if( ch == '\n' ) ch = buffer->next();
450         long ipos = buffer->itell()-1;
451 // scan for delimiter
452         while( ch >= 0 && ch != left_delm ) ch = buffer->next();
453         long pos = buffer->itell()-1;
454         if( ch >= 0 ) buffer->iseek(pos);
455         long len = pos - ipos;
456         if( len >= output_length ) {
457                 delete [] output;
458                 output_length = len+1;
459                 output = new char[output_length];
460         }
461         decode(output,(const char *)buffer->pos(ipos), len);
462         return output;
463 }
464
465 int FileXML::read_tag()
466 {
467         int ch = buffer->next();
468 // scan to next tag
469         while( ch >= 0 && ch != left_delm ) ch = buffer->next();
470         if( ch < 0 ) return 1;
471         tag.reset_tag();
472         return tag.read_tag(this);
473 }
474
475 int FileXML::skip_tag()
476 {
477         char tag_title[sizeof(tag.title)];
478         strcpy(tag_title, tag.title);
479         int n = 1;
480         while( !read_tag() ) {
481                 if( tag.title[0] == tag_title[0] ) {
482                         if( !strcasecmp(&tag_title[1], &tag.title[1]) ) ++n;
483                 }
484                 else if( tag.title[0] != '/' ) continue;
485                 else if( strcasecmp(&tag_title[0], &tag.title[1]) ) continue;
486                 else if( --n <= 0 ) return 0;
487         }
488         return 1;
489 }
490
491 int FileXML::read_data_until(const char *tag_end, char *out, int len)
492 {
493         long ipos = buffer->itell();
494         int opos = 0, pos = -1;
495         int ch = buffer->next();
496         for( int olen=len-1; ch>=0 && opos<olen; ch=buffer->next() ) {
497                 if( pos < 0 ) { // looking for next tag
498                         if( ch == left_delm ) {
499                                 ipos = buffer->itell()-1;
500                                 pos = 0;
501                         }
502                         else
503                                 out[opos++] = ch;
504                         continue;
505                 }
506                 // check for end of match
507                 if( !tag_end[pos] && ch == right_delm ) break;
508                 // if mismatched, copy prefix to out
509                 if( tag_end[pos] != ch ) {
510                         out[opos++] = left_delm;
511                         for( int i=0; i<pos && opos<olen; ++i )
512                                 out[opos++] = tag_end[i];
513                         if( opos < olen ) out[opos++] = ch;
514                         pos = -1;
515                         continue;
516                 }
517                 ++pos;
518         }
519 // if end tag is reached, pos is left on the < of the end tag
520         if( pos >= 0 && !tag_end[pos] )
521                 buffer->iseek(ipos);
522         return opos;
523 }
524
525 int FileXML::read_text_until(const char *tag_end, char *out, int len)
526 {
527         char data[len+1];
528         int opos = read_data_until(tag_end, data, len);
529         decode(out, data, opos);
530         return 0;
531 }
532
533 int FileXML::write_to_file(const char *filename)
534 {
535         FILE *out = fopen(filename, "wb");
536         if( !out ) {
537                 eprintf("write_to_file %d \"%s\": %m\n", __LINE__, filename);
538                 return 1;
539         }
540         int ret = write_to_file(out, filename);
541         fclose(out);
542         return ret;
543 }
544
545 int FileXML::write_to_file(FILE *file, const char *filename)
546 {
547         strcpy(this->filename, filename);
548         const char *str = string();
549         long len = strlen(str);
550 // Position may have been rewound after storing
551         if( !fwrite(xml_header, xml_header_size, 1, file) ||
552             ( len > 0 && !fwrite(str, len, 1, file) ) ) {
553                 eprintf("\"%s\": %m\n", filename);
554                 return 1;
555         }
556         return 0;
557 }
558
559 int FileXML::read_from_file(const char *filename, int ignore_error)
560 {
561
562         strcpy(this->filename, filename);
563         FILE *in = fopen(filename, "rb");
564         if( !in ) {
565                 if(!ignore_error)
566                         eprintf("\"%s\" %m\n", filename);
567                 return 1;
568         }
569         fseek(in, 0, SEEK_END);
570         long length = ftell(in);
571         fseek(in, 0, SEEK_SET);
572         char *fbfr = new char[length+1];
573         delete buffer;
574         (void)fread(fbfr, length, 1, in);
575         fbfr[length] = 0;
576         buffer = new XMLBuffer(fbfr, length, 1);
577         fclose(in);
578         return 0;
579 }
580
581 int FileXML::read_from_string(char *string)
582 {
583         strcpy(this->filename, "");
584         long length = strlen(string);
585         char *sbfr = new char[length+1];
586         strcpy(sbfr, string);
587         delete buffer;
588         buffer = new XMLBuffer(sbfr, length, 1);
589         return 0;
590 }
591
592 void FileXML::set_coding(int coding)
593 {
594         coded = coding;
595         if( coded ) {
596                 decode = XMLBuffer::decode_data;
597                 encode = XMLBuffer::encode_data;
598                 coded_length = XMLBuffer::encoded_length;
599         }
600         else {
601                 decode = XMLBuffer::copy_data;
602                 encode = XMLBuffer::copy_data;
603                 coded_length = XMLBuffer::copy_length;
604         }
605 }
606
607 int FileXML::get_coding()
608 {
609         return coded;
610 }
611
612 int FileXML::set_shared_input(char *shared_string, long avail, int coded)
613 {
614         strcpy(this->filename, "");
615         delete buffer;
616         buffer = new XMLBuffer(shared_string, avail, 0);
617         set_coding(coded);
618         return 0;
619 }
620
621 int FileXML::set_shared_output(char *shared_string, long avail, int coded)
622 {
623         strcpy(this->filename, "");
624         delete buffer;
625         buffer = new XMLBuffer(avail, shared_string, 0);
626         set_coding(coded);
627         return 0;
628 }
629
630
631
632 // ================================ XML tag
633
634
635
636 int XMLTag::title_is(const char *tp)
637 {
638         return !strcasecmp(title, tp) ? 1 : 0;
639 }
640
641 char* XMLTag::get_title()
642 {
643         return title;
644 }
645
646 int XMLTag::get_title(char *value)
647 {
648         if( title[0] != 0 ) strcpy(value, title);
649         return 0;
650 }
651
652 int XMLTag::test_property(char *property, char *value)
653 {
654         for( int i=0; i<properties.size(); ++i ) {
655                 if( !strcasecmp(properties[i]->prop, property) &&
656                     !strcasecmp(properties[i]->value, value) )
657                         return 1;
658         }
659         return 0;
660 }
661
662 static inline int xml_cmp(const char *np, const char *sp)
663 {
664    const char *tp = ++np;
665    while( *np ) { if( *np != *sp ) return 0;  ++np; ++sp; }
666    return np - tp;
667 }
668
669 char *XMLBuffer::decode_data(char *bp, const char *sp, int n)
670 {
671    char *ret = bp;
672    if( n < 0 ) n = strlen(sp);
673    const char *ep = sp + n;
674    while( sp < ep ) {
675       if( (n=*sp++) != '&' ) { *bp++ = n; continue; }
676       switch( (n=*sp++) ) {
677       case 'a': // &amp;
678          if( (n=xml_cmp("amp;", sp)) ) { *bp++ = '&';  sp += n;  continue; }
679          break;
680       case 'g': // &gt;
681          if( (n=xml_cmp("gt;", sp)) ) { *bp++ = '>';  sp += n;  continue; }
682          break;
683       case 'l': // &lt;
684          if( (n=xml_cmp("lt;", sp)) ) { *bp++ = '<';  sp += n;  continue; }
685          break;
686       case 'q': // &quot;
687          if( (n=xml_cmp("quot;", sp)) ) { *bp++ = '"';  sp += n;  continue; }
688          break;
689       case '#': { // &#<num>;
690          char *cp = 0;  int v = strtoul(sp,&cp,10);
691          if( *cp == ';' ) { *bp++ = (char)v;  sp = cp+1;  continue; }
692          n = cp - sp; }
693          break;
694       default:
695          *bp++ = '&';
696          *bp++ = (char)n;
697          continue;
698       }
699       sp -= 2;  n += 2;
700       while( --n >= 0 ) *bp++ = *sp++;
701    }
702    *bp = 0;
703    return ret;
704 }
705
706
707 char *XMLBuffer::encode_data(char *bp, const char *sp, int n)
708 {
709         char *ret = bp;
710         if( n < 0 ) n = strlen(sp);
711         const char *cp, *ep = sp + n;
712         while( sp < ep ) {
713                 int ch = *sp++;
714                 switch( ch ) {
715                 case '<':  cp = "&lt;";    break;
716                 case '>':  cp = "&gt;";    break;
717                 case '&':  cp = "&amp;";   break;
718                 case '"':  cp = "&quot;";  break;
719                 default:  *bp++ = ch;      continue;
720                 }
721                 while( *cp != 0 ) *bp++ = *cp++;
722         }
723         *bp = 0;
724         return ret;
725 }
726
727 long XMLBuffer::encoded_length(const char *sp, int n)
728 {
729         long len = 0;
730         if( n < 0 ) n = strlen(sp);
731         const char *ep = sp + n;
732         while( sp < ep ) {
733                 int ch = *sp++;
734                 switch( ch ) {
735                 case '<':  len += 4;  break;
736                 case '>':  len += 4;  break;
737                 case '&':  len += 5;  break;
738                 case '"':  len += 6;  break;
739                 default:   ++len;     break;
740                 }
741         }
742         return len;
743 }
744
745 char *XMLBuffer::copy_data(char *bp, const char *sp, int n)
746 {
747         int len = n < 0 ? strlen(sp) : n;
748         memmove(bp,sp,len);
749         bp[len] = 0;
750         return bp;
751 }
752
753 long XMLBuffer::copy_length(const char *sp, int n)
754 {
755         int len = n < 0 ? strlen(sp) : n;
756         return len;
757 }
758