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