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