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