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