rework keyframe hide popup, keyframe auto render, textbox set_selection wide text
[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::read_data_until(const char *tag_end, char *out, int len)
476 {
477         long ipos = buffer->itell();
478         int opos = 0, pos = -1;
479         int ch = buffer->next();
480         for( int olen=len-1; ch>=0 && opos<olen; ch=buffer->next() ) {
481                 if( pos < 0 ) { // looking for next tag
482                         if( ch == left_delm ) {
483                                 ipos = buffer->itell()-1;
484                                 pos = 0;
485                         }
486                         else
487                                 out[opos++] = ch;
488                         continue;
489                 }
490                 // check for end of match
491                 if( !tag_end[pos] && ch == right_delm ) break;
492                 // if mismatched, copy prefix to out
493                 if( tag_end[pos] != ch ) {
494                         out[opos++] = left_delm;
495                         for( int i=0; i<pos && opos<olen; ++i )
496                                 out[opos++] = tag_end[i];
497                         if( opos < olen ) out[opos++] = ch;
498                         pos = -1;
499                         continue;
500                 }
501                 ++pos;
502         }
503 // if end tag is reached, pos is left on the < of the end tag
504         if( pos >= 0 && !tag_end[pos] )
505                 buffer->iseek(ipos);
506         return opos;
507 }
508
509 int FileXML::read_text_until(const char *tag_end, char *out, int len)
510 {
511         char data[len+1];
512         int opos = read_data_until(tag_end, data, len);
513         decode(out, data, opos);
514         return 0;
515 }
516
517 int FileXML::write_to_file(const char *filename)
518 {
519         FILE *out = fopen(filename, "wb");
520         if( !out ) {
521                 eprintf("write_to_file %d \"%s\": %m\n", __LINE__, filename);
522                 return 1;
523         }
524         int ret = write_to_file(out, filename);
525         fclose(out);
526         return ret;
527 }
528
529 int FileXML::write_to_file(FILE *file, const char *filename)
530 {
531         strcpy(this->filename, filename);
532         const char *str = string();
533         long len = strlen(str);
534 // Position may have been rewound after storing
535         if( !fwrite(xml_header, xml_header_size, 1, file) ||
536             ( len > 0 && !fwrite(str, len, 1, file) ) ) {
537                 eprintf("\"%s\": %m\n", filename);
538                 return 1;
539         }
540         return 0;
541 }
542
543 int FileXML::read_from_file(const char *filename, int ignore_error)
544 {
545         
546         strcpy(this->filename, filename);
547         FILE *in = fopen(filename, "rb");
548         if( !in ) {
549                 if(!ignore_error) 
550                         eprintf("\"%s\" %m\n", filename);
551                 return 1;
552         }
553         fseek(in, 0, SEEK_END);
554         long length = ftell(in);
555         fseek(in, 0, SEEK_SET);
556         char *fbfr = new char[length+1];
557         delete buffer;
558         (void)fread(fbfr, length, 1, in);
559         fbfr[length] = 0;
560         buffer = new XMLBuffer(fbfr, length, 1);
561         fclose(in);
562         return 0;
563 }
564
565 int FileXML::read_from_string(char *string)
566 {
567         strcpy(this->filename, "");
568         long length = strlen(string);
569         char *sbfr = new char[length+1];
570         strcpy(sbfr, string);
571         delete buffer;
572         buffer = new XMLBuffer(sbfr, length, 1);
573         return 0;
574 }
575
576 void FileXML::set_coding(int coding)
577 {
578         coded = coding;
579         if( coded ) {
580                 decode = XMLBuffer::decode_data;
581                 encode = XMLBuffer::encode_data;
582                 coded_length = XMLBuffer::encoded_length;
583         }
584         else {
585                 decode = XMLBuffer::copy_data;
586                 encode = XMLBuffer::copy_data;
587                 coded_length = XMLBuffer::copy_length;
588         }
589 }
590
591 int FileXML::get_coding()
592 {
593         return coded;
594 }
595
596 int FileXML::set_shared_input(char *shared_string, long avail, int coded)
597 {
598         strcpy(this->filename, "");
599         delete buffer;
600         buffer = new XMLBuffer(shared_string, avail, 0);
601         set_coding(coded);
602         return 0;
603 }
604
605 int FileXML::set_shared_output(char *shared_string, long avail, int coded)
606 {
607         strcpy(this->filename, "");
608         delete buffer;
609         buffer = new XMLBuffer(avail, shared_string, 0);
610         set_coding(coded);
611         return 0;
612 }
613
614
615
616 // ================================ XML tag
617
618
619
620 int XMLTag::title_is(const char *tp)
621 {
622         return !strcasecmp(title, tp) ? 1 : 0;
623 }
624
625 char* XMLTag::get_title()
626 {
627         return title;
628 }
629
630 int XMLTag::get_title(char *value)
631 {
632         if( title[0] != 0 ) strcpy(value, title);
633         return 0;
634 }
635
636 int XMLTag::test_property(char *property, char *value)
637 {
638         for( int i=0; i<properties.size(); ++i ) {
639                 if( !strcasecmp(properties[i]->prop, property) &&
640                     !strcasecmp(properties[i]->value, value) )
641                         return 1;
642         }
643         return 0;
644 }
645
646 static inline int xml_cmp(const char *np, const char *sp)
647 {
648    const char *tp = ++np;
649    while( *np ) { if( *np != *sp ) return 0;  ++np; ++sp; }
650    return np - tp;
651 }
652
653 char *XMLBuffer::decode_data(char *bp, const char *sp, int n)
654 {
655    char *ret = bp;
656    if( n < 0 ) n = strlen(sp);
657    const char *ep = sp + n;
658    while( sp < ep ) {
659       if( (n=*sp++) != '&' ) { *bp++ = n; continue; }
660       switch( (n=*sp++) ) {
661       case 'a': // &amp;
662          if( (n=xml_cmp("amp;", sp)) ) { *bp++ = '&';  sp += n;  continue; }
663          break;
664       case 'g': // &gt;
665          if( (n=xml_cmp("gt;", sp)) ) { *bp++ = '>';  sp += n;  continue; }
666          break;
667       case 'l': // &lt;
668          if( (n=xml_cmp("lt;", sp)) ) { *bp++ = '<';  sp += n;  continue; }
669          break;
670       case 'q': // &quot;
671          if( (n=xml_cmp("quot;", sp)) ) { *bp++ = '"';  sp += n;  continue; }
672          break;
673       case '#': { // &#<num>;
674          char *cp = 0;  int v = strtoul(sp,&cp,10);
675          if( *cp == ';' ) { *bp++ = (char)v;  sp = cp+1;  continue; }
676          n = cp - sp; }
677          break;
678       default:
679          *bp++ = '&';
680          *bp++ = (char)n;
681          continue;
682       }
683       sp -= 2;  n += 2;
684       while( --n >= 0 ) *bp++ = *sp++;
685    }
686    *bp = 0;
687    return ret;
688 }
689
690
691 char *XMLBuffer::encode_data(char *bp, const char *sp, int n)
692 {
693         char *ret = bp;
694         if( n < 0 ) n = strlen(sp);
695         const char *cp, *ep = sp + n;
696         while( sp < ep ) {
697                 int ch = *sp++;
698                 switch( ch ) {
699                 case '<':  cp = "&lt;";    break;
700                 case '>':  cp = "&gt;";    break;
701                 case '&':  cp = "&amp;";   break;
702                 case '"':  cp = "&quot;";  break;
703                 default:  *bp++ = ch;      continue;
704                 }
705                 while( *cp != 0 ) *bp++ = *cp++;
706         }
707         *bp = 0;
708         return ret;
709 }
710
711 long XMLBuffer::encoded_length(const char *sp, int n)
712 {
713         long len = 0;
714         if( n < 0 ) n = strlen(sp);
715         const char *ep = sp + n;
716         while( sp < ep ) {
717                 int ch = *sp++;
718                 switch( ch ) {
719                 case '<':  len += 4;  break;
720                 case '>':  len += 4;  break;
721                 case '&':  len += 5;  break;
722                 case '"':  len += 6;  break;
723                 default:   ++len;     break;
724                 }
725         }
726         return len;
727 }
728
729 char *XMLBuffer::copy_data(char *bp, const char *sp, int n)
730 {
731         int len = n < 0 ? strlen(sp) : n;
732         memmove(bp,sp,len);
733         bp[len] = 0;
734         return bp;
735 }
736
737 long XMLBuffer::copy_length(const char *sp, int n)
738 {
739         int len = n < 0 ? strlen(sp) : n;
740         return len;
741 }
742