168aff4b0c156b233049cde2f6652c5280e23506
[melted] / src / framework / mlt_properties.c
1 /*
2 * mlt_properties.c -- base properties class
3 * Copyright (C) 2003-2004 Ushodaya Enterprises Limited
4 * Author: Charles Yates <charles.yates@pandora.be>
5 * Contributor: Dan Dennedy <dan@dennedy.org>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21
22 #include "mlt_properties.h"
23 #include "mlt_property.h"
24 #include "mlt_deque.h"
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <ctype.h>
30 #include <stdarg.h>
31
32 #include <sys/types.h>
33 #include <dirent.h>
34
35 /* ---------------- // Private Implementation // ---------------- */
36
37 /** Private implementation of the property list.
38 */
39
40 typedef struct
41 {
42 int hash[ 199 ];
43 char **name;
44 mlt_property *value;
45 int count;
46 int size;
47 mlt_properties mirror;
48 int ref_count;
49 }
50 property_list;
51
52 /** Memory leak checks.
53 */
54
55 //#define _MLT_PROPERTY_CHECKS_ 2
56
57 #ifdef _MLT_PROPERTY_CHECKS_
58 static int properties_created = 0;
59 static int properties_destroyed = 0;
60 #endif
61
62 /** Basic implementation.
63 */
64
65 int mlt_properties_init( mlt_properties this, void *child )
66 {
67 if ( this != NULL )
68 {
69 #ifdef _MLT_PROPERTY_CHECKS_
70 // Increment number of properties created
71 properties_created ++;
72 #endif
73
74 // NULL all methods
75 memset( this, 0, sizeof( struct mlt_properties_s ) );
76
77 // Assign the child of the object
78 this->child = child;
79
80 // Allocate the local structure
81 this->local = calloc( sizeof( property_list ), 1 );
82
83 // Increment the ref count
84 ( ( property_list * )this->local )->ref_count = 1;
85 }
86
87 // Check that initialisation was successful
88 return this != NULL && this->local == NULL;
89 }
90
91 /** Constructor for stand alone object.
92 */
93
94 mlt_properties mlt_properties_new( )
95 {
96 // Construct a standalone properties object
97 mlt_properties this = calloc( sizeof( struct mlt_properties_s ), 1 );
98
99 // Initialise this
100 mlt_properties_init( this, NULL );
101
102 // Return the pointer
103 return this;
104 }
105
106 /** Load properties from a .properties file (DEPRECATED).
107 */
108
109 mlt_properties mlt_properties_load( const char *filename )
110 {
111 // Construct a standalone properties object
112 mlt_properties this = mlt_properties_new( );
113
114 if ( this != NULL )
115 {
116 // Open the file
117 FILE *file = fopen( filename, "r" );
118
119 // Load contents of file
120 if ( file != NULL )
121 {
122 // Temp string
123 char temp[ 1024 ];
124 char last[ 1024 ] = "";
125
126 // Read each string from the file
127 while( fgets( temp, 1024, file ) )
128 {
129 // Chomp the string
130 temp[ strlen( temp ) - 1 ] = '\0';
131
132 // Check if the line starts with a .
133 if ( temp[ 0 ] == '.' )
134 {
135 char temp2[ 1024 ];
136 sprintf( temp2, "%s%s", last, temp );
137 strcpy( temp, temp2 );
138 }
139 else if ( strchr( temp, '=' ) )
140 {
141 strcpy( last, temp );
142 *( strchr( last, '=' ) ) = '\0';
143 }
144
145 // Parse and set the property
146 if ( strcmp( temp, "" ) && temp[ 0 ] != '#' )
147 mlt_properties_parse( this, temp );
148 }
149
150 // Close the file
151 fclose( file );
152 }
153 }
154
155 // Return the pointer
156 return this;
157 }
158
159 static inline int generate_hash( const char *name )
160 {
161 int hash = 0;
162 int i = 1;
163 while ( *name )
164 hash = ( hash + ( i ++ * ( *name ++ & 31 ) ) ) % 199;
165 return hash;
166 }
167
168 /** Special case - when a container (such as fezzik) is protecting another
169 producer, we need to ensure that properties are passed through to the
170 real producer.
171 */
172
173 static inline void mlt_properties_do_mirror( mlt_properties this, const char *name )
174 {
175 property_list *list = this->local;
176 if ( list->mirror != NULL )
177 {
178 char *value = mlt_properties_get( this, name );
179 if ( value != NULL )
180 mlt_properties_set( list->mirror, name, value );
181 }
182 }
183
184 /** Maintain ref count to allow multiple uses of an mlt object.
185 */
186
187 int mlt_properties_inc_ref( mlt_properties this )
188 {
189 if ( this != NULL )
190 {
191 property_list *list = this->local;
192 return ++ list->ref_count;
193 }
194 return 0;
195 }
196
197 /** Maintain ref count to allow multiple uses of an mlt object.
198 */
199
200 int mlt_properties_dec_ref( mlt_properties this )
201 {
202 if ( this != NULL )
203 {
204 property_list *list = this->local;
205 return -- list->ref_count;
206 }
207 return 0;
208 }
209
210 /** Return the ref count of this object.
211 */
212
213 int mlt_properties_ref_count( mlt_properties this )
214 {
215 if ( this != NULL )
216 {
217 property_list *list = this->local;
218 return list->ref_count;
219 }
220 return 0;
221 }
222
223 /** Mirror properties set on 'this' to 'that'.
224 */
225
226 void mlt_properties_mirror( mlt_properties this, mlt_properties that )
227 {
228 property_list *list = this->local;
229 list->mirror = that;
230 }
231
232 /** Inherit all serialisable properties from that into this.
233 */
234
235 int mlt_properties_inherit( mlt_properties this, mlt_properties that )
236 {
237 int count = mlt_properties_count( that );
238 int i = 0;
239 for ( i = 0; i < count; i ++ )
240 {
241 char *value = mlt_properties_get_value( that, i );
242 if ( value != NULL )
243 {
244 char *name = mlt_properties_get_name( that, i );
245 mlt_properties_set( this, name, value );
246 }
247 }
248 return 0;
249 }
250
251 /** Pass all properties from 'that' that match the prefix to 'this' (excluding the prefix).
252 */
253
254 int mlt_properties_pass( mlt_properties this, mlt_properties that, const char *prefix )
255 {
256 int count = mlt_properties_count( that );
257 int length = strlen( prefix );
258 int i = 0;
259 for ( i = 0; i < count; i ++ )
260 {
261 char *name = mlt_properties_get_name( that, i );
262 if ( !strncmp( name, prefix, length ) )
263 {
264 char *value = mlt_properties_get_value( that, i );
265 if ( value != NULL )
266 mlt_properties_set( this, name + length, value );
267 }
268 }
269 return 0;
270 }
271
272 /** Locate a property by name
273 */
274
275 static inline mlt_property mlt_properties_find( mlt_properties this, const char *name )
276 {
277 property_list *list = this->local;
278 mlt_property value = NULL;
279 int key = generate_hash( name );
280 int i = list->hash[ key ] - 1;
281
282 if ( i >= 0 )
283 {
284 // Check if we're hashed
285 if ( list->count > 0 &&
286 name[ 0 ] == list->name[ i ][ 0 ] &&
287 !strcmp( list->name[ i ], name ) )
288 value = list->value[ i ];
289
290 // Locate the item
291 for ( i = list->count - 1; value == NULL && i >= 0; i -- )
292 if ( name[ 0 ] == list->name[ i ][ 0 ] && !strcmp( list->name[ i ], name ) )
293 value = list->value[ i ];
294 }
295
296 return value;
297 }
298
299 /** Add a new property.
300 */
301
302 static mlt_property mlt_properties_add( mlt_properties this, const char *name )
303 {
304 property_list *list = this->local;
305 int key = generate_hash( name );
306
307 // Check that we have space and resize if necessary
308 if ( list->count == list->size )
309 {
310 list->size += 50;
311 list->name = realloc( list->name, list->size * sizeof( const char * ) );
312 list->value = realloc( list->value, list->size * sizeof( mlt_property ) );
313 }
314
315 // Assign name/value pair
316 list->name[ list->count ] = strdup( name );
317 list->value[ list->count ] = mlt_property_init( );
318
319 // Assign to hash table
320 if ( list->hash[ key ] == 0 )
321 list->hash[ key ] = list->count + 1;
322
323 // Return and increment count accordingly
324 return list->value[ list->count ++ ];
325 }
326
327 /** Fetch a property by name - this includes add if not found.
328 */
329
330 static mlt_property mlt_properties_fetch( mlt_properties this, const char *name )
331 {
332 // Try to find an existing property first
333 mlt_property property = mlt_properties_find( this, name );
334
335 // If it wasn't found, create one
336 if ( property == NULL )
337 property = mlt_properties_add( this, name );
338
339 // Return the property
340 return property;
341 }
342
343 /** Pass property 'name' from 'that' to 'this'
344 * Who to blame: Zach <zachary.drew@gmail.com>
345 */
346
347 void mlt_properties_pass_property( mlt_properties this, mlt_properties that, const char *name )
348 {
349 // Make sure the source property isn't null.
350 mlt_property that_prop = mlt_properties_find( that, name );
351 if( that_prop == NULL )
352 return;
353
354 mlt_property_pass( mlt_properties_fetch( this, name ), that_prop );
355 }
356
357 /** Pass all properties from 'that' to 'this' as found in comma seperated 'list'.
358 * Who to blame: Zach <zachary.drew@gmail.com>
359 */
360
361 int mlt_properties_pass_list( mlt_properties this, mlt_properties that, const char *list )
362 {
363 char *props = strdup( list );
364 char *ptr = props;
365 char *delim = " ,\t\n"; // Any combination of spaces, commas, tabs, and newlines
366 int count, done = 0;
367
368 while( !done )
369 {
370 count = strcspn( ptr, delim );
371
372 if( ptr[count] == '\0' )
373 done = 1;
374 else
375 ptr[count] = '\0'; // Make it a real string
376
377 mlt_properties_pass_property( this, that, ptr );
378
379 ptr += count + 1;
380 ptr += strspn( ptr, delim );
381 }
382
383 free( props );
384
385 return 0;
386 }
387
388
389 /** Set the property.
390 */
391
392 int mlt_properties_set( mlt_properties this, const char *name, const char *value )
393 {
394 int error = 1;
395
396 // Fetch the property to work with
397 mlt_property property = mlt_properties_fetch( this, name );
398
399 // Set it if not NULL
400 if ( property == NULL )
401 {
402 fprintf( stderr, "Whoops - %s not found (should never occur)\n", name );
403 }
404 else if ( value == NULL )
405 {
406 error = mlt_property_set_string( property, value );
407 mlt_properties_do_mirror( this, name );
408 }
409 else if ( *value != '@' )
410 {
411 error = mlt_property_set_string( property, value );
412 mlt_properties_do_mirror( this, name );
413 }
414 else if ( value[ 0 ] == '@' )
415 {
416 int total = 0;
417 int current = 0;
418 char id[ 255 ];
419 char op = '+';
420
421 value ++;
422
423 while ( *value != '\0' )
424 {
425 int length = strcspn( value, "+-*/" );
426
427 // Get the identifier
428 strncpy( id, value, length );
429 id[ length ] = '\0';
430 value += length;
431
432 // Determine the value
433 if ( isdigit( id[ 0 ] ) )
434 current = atof( id );
435 else
436 current = mlt_properties_get_int( this, id );
437
438 // Apply the operation
439 switch( op )
440 {
441 case '+':
442 total += current;
443 break;
444 case '-':
445 total -= current;
446 break;
447 case '*':
448 total *= current;
449 break;
450 case '/':
451 total /= current;
452 break;
453 }
454
455 // Get the next op
456 op = *value != '\0' ? *value ++ : ' ';
457 }
458
459 error = mlt_property_set_int( property, total );
460 mlt_properties_do_mirror( this, name );
461 }
462
463 mlt_events_fire( this, "property-changed", name, NULL );
464
465 return error;
466 }
467
468 /** Set or default the property.
469 */
470
471 int mlt_properties_set_or_default( mlt_properties this, const char *name, const char *value, const char *def )
472 {
473 return mlt_properties_set( this, name, value == NULL ? def : value );
474 }
475
476 /** Get a string value by name.
477 */
478
479 char *mlt_properties_get( mlt_properties this, const char *name )
480 {
481 mlt_property value = mlt_properties_find( this, name );
482 return value == NULL ? NULL : mlt_property_get_string( value );
483 }
484
485 /** Get a name by index.
486 */
487
488 char *mlt_properties_get_name( mlt_properties this, int index )
489 {
490 property_list *list = this->local;
491 if ( index >= 0 && index < list->count )
492 return list->name[ index ];
493 return NULL;
494 }
495
496 /** Get a string value by index.
497 */
498
499 char *mlt_properties_get_value( mlt_properties this, int index )
500 {
501 property_list *list = this->local;
502 if ( index >= 0 && index < list->count )
503 return mlt_property_get_string( list->value[ index ] );
504 return NULL;
505 }
506
507 /** Get a data value by index.
508 */
509
510 void *mlt_properties_get_data_at( mlt_properties this, int index, int *size )
511 {
512 property_list *list = this->local;
513 if ( index >= 0 && index < list->count )
514 return mlt_property_get_data( list->value[ index ], size );
515 return NULL;
516 }
517
518 /** Return the number of items in the list.
519 */
520
521 int mlt_properties_count( mlt_properties this )
522 {
523 property_list *list = this->local;
524 return list->count;
525 }
526
527 /** Set a value by parsing a name=value string
528 */
529
530 int mlt_properties_parse( mlt_properties this, const char *namevalue )
531 {
532 char *name = strdup( namevalue );
533 char *value = NULL;
534 int error = 0;
535 char *ptr = strchr( name, '=' );
536
537 if ( ptr )
538 {
539 *( ptr ++ ) = '\0';
540
541 if ( *ptr != '\"' )
542 {
543 value = strdup( ptr );
544 }
545 else
546 {
547 ptr ++;
548 value = strdup( ptr );
549 if ( value != NULL && value[ strlen( value ) - 1 ] == '\"' )
550 value[ strlen( value ) - 1 ] = '\0';
551 }
552 }
553 else
554 {
555 value = strdup( "" );
556 }
557
558 error = mlt_properties_set( this, name, value );
559
560 free( name );
561 free( value );
562
563 return error;
564 }
565
566 /** Get a value associated to the name.
567 */
568
569 int mlt_properties_get_int( mlt_properties this, const char *name )
570 {
571 mlt_property value = mlt_properties_find( this, name );
572 return value == NULL ? 0 : mlt_property_get_int( value );
573 }
574
575 /** Set a value associated to the name.
576 */
577
578 int mlt_properties_set_int( mlt_properties this, const char *name, int value )
579 {
580 int error = 1;
581
582 // Fetch the property to work with
583 mlt_property property = mlt_properties_fetch( this, name );
584
585 // Set it if not NULL
586 if ( property != NULL )
587 {
588 error = mlt_property_set_int( property, value );
589 mlt_properties_do_mirror( this, name );
590 }
591
592 mlt_events_fire( this, "property-changed", name, NULL );
593
594 return error;
595 }
596
597 /** Get a value associated to the name.
598 */
599
600 int64_t mlt_properties_get_int64( mlt_properties this, const char *name )
601 {
602 mlt_property value = mlt_properties_find( this, name );
603 return value == NULL ? 0 : mlt_property_get_int64( value );
604 }
605
606 /** Set a value associated to the name.
607 */
608
609 int mlt_properties_set_int64( mlt_properties this, const char *name, int64_t value )
610 {
611 int error = 1;
612
613 // Fetch the property to work with
614 mlt_property property = mlt_properties_fetch( this, name );
615
616 // Set it if not NULL
617 if ( property != NULL )
618 {
619 error = mlt_property_set_int64( property, value );
620 mlt_properties_do_mirror( this, name );
621 }
622
623 mlt_events_fire( this, "property-changed", name, NULL );
624
625 return error;
626 }
627
628 /** Get a value associated to the name.
629 */
630
631 double mlt_properties_get_double( mlt_properties this, const char *name )
632 {
633 mlt_property value = mlt_properties_find( this, name );
634 return value == NULL ? 0 : mlt_property_get_double( value );
635 }
636
637 /** Set a value associated to the name.
638 */
639
640 int mlt_properties_set_double( mlt_properties this, const char *name, double value )
641 {
642 int error = 1;
643
644 // Fetch the property to work with
645 mlt_property property = mlt_properties_fetch( this, name );
646
647 // Set it if not NULL
648 if ( property != NULL )
649 {
650 error = mlt_property_set_double( property, value );
651 mlt_properties_do_mirror( this, name );
652 }
653
654 mlt_events_fire( this, "property-changed", name, NULL );
655
656 return error;
657 }
658
659 /** Get a value associated to the name.
660 */
661
662 mlt_position mlt_properties_get_position( mlt_properties this, const char *name )
663 {
664 mlt_property value = mlt_properties_find( this, name );
665 return value == NULL ? 0 : mlt_property_get_position( value );
666 }
667
668 /** Set a value associated to the name.
669 */
670
671 int mlt_properties_set_position( mlt_properties this, const char *name, mlt_position value )
672 {
673 int error = 1;
674
675 // Fetch the property to work with
676 mlt_property property = mlt_properties_fetch( this, name );
677
678 // Set it if not NULL
679 if ( property != NULL )
680 {
681 error = mlt_property_set_position( property, value );
682 mlt_properties_do_mirror( this, name );
683 }
684
685 mlt_events_fire( this, "property-changed", name, NULL );
686
687 return error;
688 }
689
690 /** Get a value associated to the name.
691 */
692
693 void *mlt_properties_get_data( mlt_properties this, const char *name, int *length )
694 {
695 mlt_property value = mlt_properties_find( this, name );
696 return value == NULL ? NULL : mlt_property_get_data( value, length );
697 }
698
699 /** Set a value associated to the name.
700 */
701
702 int mlt_properties_set_data( mlt_properties this, const char *name, void *value, int length, mlt_destructor destroy, mlt_serialiser serialise )
703 {
704 int error = 1;
705
706 // Fetch the property to work with
707 mlt_property property = mlt_properties_fetch( this, name );
708
709 // Set it if not NULL
710 if ( property != NULL )
711 error = mlt_property_set_data( property, value, length, destroy, serialise );
712
713 mlt_events_fire( this, "property-changed", name, NULL );
714
715 return error;
716 }
717
718 /** Rename a property.
719 */
720
721 int mlt_properties_rename( mlt_properties this, const char *source, const char *dest )
722 {
723 mlt_property value = mlt_properties_find( this, dest );
724
725 if ( value == NULL )
726 {
727 property_list *list = this->local;
728 int i = 0;
729
730 // Locate the item
731 for ( i = 0; i < list->count; i ++ )
732 {
733 if ( !strcmp( list->name[ i ], source ) )
734 {
735 free( list->name[ i ] );
736 list->name[ i ] = strdup( dest );
737 list->hash[ generate_hash( dest ) ] = i + 1;
738 break;
739 }
740 }
741 }
742
743 return value != NULL;
744 }
745
746 /** Dump the properties.
747 */
748
749 void mlt_properties_dump( mlt_properties this, FILE *output )
750 {
751 property_list *list = this->local;
752 int i = 0;
753 for ( i = 0; i < list->count; i ++ )
754 if ( mlt_properties_get( this, list->name[ i ] ) != NULL )
755 fprintf( output, "%s=%s\n", list->name[ i ], mlt_properties_get( this, list->name[ i ] ) );
756 }
757
758 void mlt_properties_debug( mlt_properties this, const char *title, FILE *output )
759 {
760 if ( output == NULL ) output = stderr;
761 fprintf( output, "%s: ", title );
762 if ( this != NULL )
763 {
764 property_list *list = this->local;
765 int i = 0;
766 fprintf( output, "[ ref=%d", list->ref_count );
767 for ( i = 0; i < list->count; i ++ )
768 if ( mlt_properties_get( this, list->name[ i ] ) != NULL )
769 fprintf( output, ", %s=%s", list->name[ i ], mlt_properties_get( this, list->name[ i ] ) );
770 else
771 fprintf( output, ", %s=%p", list->name[ i ], mlt_properties_get_data( this, list->name[ i ], NULL ) );
772 fprintf( output, " ]" );
773 }
774 fprintf( output, "\n" );
775 }
776
777 int mlt_properties_save( mlt_properties this, const char *filename )
778 {
779 int error = 1;
780 FILE *f = fopen( filename, "w" );
781 if ( f != NULL )
782 {
783 mlt_properties_dump( this, f );
784 fclose( f );
785 error = 0;
786 }
787 return error;
788 }
789
790 /* This is a very basic cross platform fnmatch replacement - it will fail in
791 ** many cases, but for the basic *.XXX and YYY*.XXX, it will work ok.
792 */
793
794 static int mlt_fnmatch( const char *wild, const char *file )
795 {
796 int f = 0;
797 int w = 0;
798
799 while( f < strlen( file ) && w < strlen( wild ) )
800 {
801 if ( wild[ w ] == '*' )
802 {
803 w ++;
804 if ( w == strlen( wild ) )
805 f = strlen( file );
806 while ( f != strlen( file ) && tolower( file[ f ] ) != tolower( wild[ w ] ) )
807 f ++;
808 }
809 else if ( wild[ w ] == '?' || tolower( file[ f ] ) == tolower( wild[ w ] ) )
810 {
811 f ++;
812 w ++;
813 }
814 else if ( wild[ 0 ] == '*' )
815 {
816 w = 0;
817 }
818 else
819 {
820 return 0;
821 }
822 }
823
824 return strlen( file ) == f && strlen( wild ) == w;
825 }
826
827 static int mlt_compare( const void *this, const void *that )
828 {
829 return strcmp( mlt_property_get_string( *( mlt_property * )this ), mlt_property_get_string( *( mlt_property * )that ) );
830 }
831
832 /* Obtains an optionally sorted list of the files found in a directory with a specific wild card.
833 * Entries in the list have a numeric name (running from 0 to count - 1). Only values change
834 * position if sort is enabled. Designed to be posix compatible (linux, os/x, mingw etc).
835 */
836
837 int mlt_properties_dir_list( mlt_properties this, const char *dirname, const char *pattern, int sort )
838 {
839 DIR *dir = opendir( dirname );
840
841 if ( dir )
842 {
843 char key[ 20 ];
844 struct dirent *de = readdir( dir );
845 char fullname[ 1024 ];
846 while( de != NULL )
847 {
848 sprintf( key, "%d", mlt_properties_count( this ) );
849 snprintf( fullname, 1024, "%s/%s", dirname, de->d_name );
850 if ( pattern == NULL )
851 mlt_properties_set( this, key, fullname );
852 else if ( de->d_name[ 0 ] != '.' && mlt_fnmatch( pattern, de->d_name ) )
853 mlt_properties_set( this, key, fullname );
854 de = readdir( dir );
855 }
856
857 closedir( dir );
858 }
859
860 if ( sort && mlt_properties_count( this ) )
861 {
862 property_list *list = this->local;
863 qsort( list->value, mlt_properties_count( this ), sizeof( mlt_property ), mlt_compare );
864 }
865
866 return mlt_properties_count( this );
867 }
868
869 /** Close the list.
870 */
871
872 void mlt_properties_close( mlt_properties this )
873 {
874 if ( this != NULL && mlt_properties_dec_ref( this ) <= 0 )
875 {
876 if ( this->close != NULL )
877 {
878 this->close( this->close_object );
879 }
880 else
881 {
882 property_list *list = this->local;
883 int index = 0;
884
885 #if _MLT_PROPERTY_CHECKS_ == 1
886 // Show debug info
887 mlt_properties_debug( this, "Closing", stderr );
888 #endif
889
890 #ifdef _MLT_PROPERTY_CHECKS_
891 // Increment destroyed count
892 properties_destroyed ++;
893
894 // Show current stats - these should match when the app is closed
895 fprintf( stderr, "Created %d, destroyed %d\n", properties_created, properties_destroyed );
896 #endif
897
898 // Clean up names and values
899 for ( index = list->count - 1; index >= 0; index -- )
900 {
901 free( list->name[ index ] );
902 mlt_property_close( list->value[ index ] );
903 }
904
905 // Clear up the list
906 free( list->name );
907 free( list->value );
908 free( list );
909
910 // Free this now if this has no child
911 if ( this->child == NULL )
912 free( this );
913 }
914 }
915 }
916
917 /** Determine if the properties list is really just a sequence or ordered list.
918 Returns 0 if false or 1 if true.
919 */
920 int mlt_properties_is_sequence( mlt_properties properties )
921 {
922 int i;
923 int n = mlt_properties_count( properties );
924 for ( i = 0; i < n; i++ )
925 if ( ! isdigit( mlt_properties_get_name( properties, i )[0] ) )
926 return 0;
927 return 1;
928 }
929
930 /** YAML Tiny Parser
931 * YAML is a nifty text format popular in the Ruby world as a cleaner,
932 * less verbose alternative to XML. See this Wikipedia topic for an overview:
933 * http://en.wikipedia.org/wiki/YAML
934 * The YAML specification is at:
935 * http://yaml.org/
936 * YAML::Tiny is a Perl module that specifies a subset of YAML that we are
937 * using here (for the same reasons):
938 * http://search.cpan.org/~adamk/YAML-Tiny-1.25/lib/YAML/Tiny.pm
939 */
940
941 struct yaml_parser_context
942 {
943 mlt_deque stack;
944 unsigned int level;
945 unsigned int index;
946 char block;
947 char *block_name;
948 unsigned int block_indent;
949
950 };
951 typedef struct yaml_parser_context *yaml_parser;
952
953 static unsigned int ltrim( char **s )
954 {
955 unsigned int i = 0;
956 char *c = *s;
957 int n = strlen( c );
958 for ( i = 0; i < n && *c == ' '; i++, c++ );
959 *s = c;
960 return i;
961 }
962
963 static unsigned int rtrim( char *s )
964 {
965 int n = strlen( s );
966 int i;
967 for ( i = n; i > 0 && s[i - 1] == ' '; --i )
968 s[i - 1] = 0;
969 return n - i;
970 }
971
972 static int parse_yaml( yaml_parser context, const char *namevalue )
973 {
974 char *name_ = strdup( namevalue );
975 char *name = name_;
976 char *value = NULL;
977 int error = 0;
978 char *ptr = strchr( name, ':' );
979 unsigned int indent = ltrim( &name );
980 mlt_properties properties = mlt_deque_peek_front( context->stack );
981
982 // Ascending one more levels in the tree
983 if ( indent < context->level )
984 {
985 unsigned int i;
986 unsigned int n = ( context->level - indent ) / 2;
987 for ( i = 0; i < n; i++ )
988 mlt_deque_pop_front( context->stack );
989 properties = mlt_deque_peek_front( context->stack );
990 context->level = indent;
991 }
992
993 // Descending a level in the tree
994 else if ( indent > context->level && context->block == 0 )
995 {
996 context->level = indent;
997 }
998
999 // If there is a colon that is not part of a block
1000 if ( ptr && ( indent == context->level ) )
1001 {
1002 // Reset block processing
1003 if ( context->block_name )
1004 {
1005 free( context->block_name );
1006 context->block_name = NULL;
1007 context->block = 0;
1008 }
1009
1010 // Terminate the name and setup the value pointer
1011 *( ptr ++ ) = 0;
1012
1013 // Trim comment
1014 char *comment = strchr( ptr, '#' );
1015 if ( comment )
1016 {
1017 *comment = 0;
1018 }
1019
1020 // Trim leading and trailing spaces from bare value
1021 ltrim( &ptr );
1022 rtrim( ptr );
1023
1024 // No value means a child
1025 if ( strcmp( ptr, "" ) == 0 )
1026 {
1027 mlt_properties child = mlt_properties_new();
1028 mlt_properties_set_data( properties, name, child, 0,
1029 ( mlt_destructor )mlt_properties_close, NULL );
1030 mlt_deque_push_front( context->stack, child );
1031 context->index = 0;
1032 free( name_ );
1033 return error;
1034 }
1035
1036 // A dash indicates a sequence item
1037 if ( name[0] == '-' )
1038 {
1039 mlt_properties child = mlt_properties_new();
1040 char key[20];
1041
1042 snprintf( key, sizeof(key), "%d", context->index++ );
1043 mlt_properties_set_data( properties, key, child, 0,
1044 ( mlt_destructor )mlt_properties_close, NULL );
1045 mlt_deque_push_front( context->stack, child );
1046
1047 name ++;
1048 context->level += ltrim( &name ) + 1;
1049 properties = child;
1050 }
1051
1052 // Value is quoted
1053 if ( *ptr == '\"' )
1054 {
1055 ptr ++;
1056 value = strdup( ptr );
1057 if ( value && value[ strlen( value ) - 1 ] == '\"' )
1058 value[ strlen( value ) - 1 ] = 0;
1059 }
1060
1061 // Value is folded or unfolded block
1062 else if ( *ptr == '|' || *ptr == '>' )
1063 {
1064 context->block = *ptr;
1065 context->block_name = strdup( name );
1066 context->block_indent = 0;
1067 value = strdup( "" );
1068 }
1069
1070 // Bare value
1071 else
1072 {
1073 value = strdup( ptr );
1074 }
1075 }
1076
1077 // A list of scalars
1078 else if ( name[0] == '-' )
1079 {
1080 // Reset block processing
1081 if ( context->block_name )
1082 {
1083 free( context->block_name );
1084 context->block_name = NULL;
1085 context->block = 0;
1086 }
1087
1088 char key[20];
1089
1090 snprintf( key, sizeof(key), "%d", context->index++ );
1091 ptr = name + 1;
1092
1093 // Trim comment
1094 char *comment = strchr( ptr, '#' );
1095 if ( comment )
1096 *comment = 0;
1097
1098 // Trim leading and trailing spaces from bare value
1099 ltrim( &ptr );
1100 rtrim( ptr );
1101
1102 // Value is quoted
1103 if ( *ptr == '\"' )
1104 {
1105 ptr ++;
1106 value = strdup( ptr );
1107 if ( value && value[ strlen( value ) - 1 ] == '\"' )
1108 value[ strlen( value ) - 1 ] = 0;
1109 }
1110
1111 // Value is folded or unfolded block
1112 else if ( *ptr == '|' || *ptr == '>' )
1113 {
1114 context->block = *ptr;
1115 context->block_name = strdup( key );
1116 context->block_indent = 0;
1117 value = strdup( "" );
1118 }
1119
1120 // Bare value
1121 else
1122 {
1123 value = strdup( ptr );
1124 }
1125
1126 free( name_ );
1127 name = name_ = strdup( key );
1128 }
1129
1130 // Non-folded block
1131 else if ( context->block == '|' )
1132 {
1133 if ( context->block_indent == 0 )
1134 context->block_indent = indent;
1135 if ( indent > context->block_indent )
1136 name = &name_[ context->block_indent ];
1137 rtrim( name );
1138 char *old_value = mlt_properties_get( properties, context->block_name );
1139 value = calloc( 1, strlen( old_value ) + strlen( name ) + 2 );
1140 strcpy( value, old_value );
1141 if ( strcmp( old_value, "" ) )
1142 strcat( value, "\n" );
1143 strcat( value, name );
1144 name = context->block_name;
1145 }
1146
1147 // Folded block
1148 else if ( context->block == '>' )
1149 {
1150 ltrim( &name );
1151 rtrim( name );
1152 char *old_value = mlt_properties_get( properties, context->block_name );
1153
1154 // Blank line (prepended with spaces) is new line
1155 if ( strcmp( name, "" ) == 0 )
1156 {
1157 value = calloc( 1, strlen( old_value ) + 2 );
1158 strcat( value, old_value );
1159 strcat( value, "\n" );
1160 }
1161 // Concatenate with space
1162 else
1163 {
1164 value = calloc( 1, strlen( old_value ) + strlen( name ) + 2 );
1165 strcat( value, old_value );
1166 if ( strcmp( old_value, "" ) && old_value[ strlen( old_value ) - 1 ] != '\n' )
1167 strcat( value, " " );
1168 strcat( value, name );
1169 }
1170 name = context->block_name;
1171 }
1172
1173 else
1174 {
1175 value = strdup( "" );
1176 }
1177
1178 error = mlt_properties_set( properties, name, value );
1179
1180 free( name_ );
1181 free( value );
1182
1183 return error;
1184 }
1185
1186 mlt_properties mlt_properties_parse_yaml( const char *filename )
1187 {
1188 // Construct a standalone properties object
1189 mlt_properties this = mlt_properties_new( );
1190
1191 if ( this )
1192 {
1193 // Open the file
1194 FILE *file = fopen( filename, "r" );
1195
1196 // Load contents of file
1197 if ( file )
1198 {
1199 // Temp string
1200 char temp[ 1024 ];
1201 char *ptemp = &temp[ 0 ];
1202
1203 // Parser context
1204 yaml_parser context = calloc( 1, sizeof( struct yaml_parser_context ) );
1205 context->stack = mlt_deque_init();
1206 mlt_deque_push_front( context->stack, this );
1207
1208 // Read each string from the file
1209 while( fgets( temp, 1024, file ) )
1210 {
1211 // Check for end-of-stream
1212 if ( strncmp( ptemp, "...", 3 ) == 0 )
1213 break;
1214
1215 // Chomp the string
1216 temp[ strlen( temp ) - 1 ] = '\0';
1217
1218 // Skip blank lines, comment lines, and document separator
1219 if ( strcmp( ptemp, "" ) && ptemp[ 0 ] != '#' && strncmp( ptemp, "---", 3 )
1220 && strncmp( ptemp, "%YAML", 5 ) && strncmp( ptemp, "% YAML", 6 ) )
1221 parse_yaml( context, temp );
1222 }
1223
1224 // Close the file
1225 fclose( file );
1226 mlt_deque_close( context->stack );
1227 if ( context->block_name )
1228 free( context->block_name );
1229 free( context );
1230 }
1231 }
1232
1233 // Return the pointer
1234 return this;
1235 }
1236
1237 /** YAML Tiny Serialiser
1238 */
1239
1240 /* strbuf is a self-growing string buffer */
1241
1242 #define STRBUF_GROWTH (1024)
1243
1244 struct strbuf_s
1245 {
1246 size_t size;
1247 char *string;
1248 };
1249
1250 typedef struct strbuf_s *strbuf;
1251
1252 static strbuf strbuf_new( )
1253 {
1254 strbuf buffer = calloc( 1, sizeof( struct strbuf_s ) );
1255 buffer->size = STRBUF_GROWTH;
1256 buffer->string = calloc( 1, buffer->size );
1257 return buffer;
1258 }
1259
1260 static void strbuf_close( strbuf buffer )
1261 {
1262 // We do not free buffer->string; strbuf user must save that pointer
1263 // and free it.
1264 if ( buffer )
1265 free( buffer );
1266 }
1267
1268 static char *strbuf_printf( strbuf buffer, const char *format, ... )
1269 {
1270 while ( buffer->string )
1271 {
1272 va_list ap;
1273 va_start( ap, format );
1274 size_t len = strlen( buffer->string );
1275 size_t remain = buffer->size - len - 1;
1276 int need = vsnprintf( buffer->string + len, remain, format, ap );
1277 va_end( ap );
1278 if ( need > -1 && need < remain )
1279 break;
1280 buffer->string[ len ] = 0;
1281 buffer->size += need + STRBUF_GROWTH;
1282 buffer->string = realloc( buffer->string, buffer->size );
1283 }
1284 return buffer->string;
1285 }
1286
1287 static inline void indent_yaml( strbuf output, int indent )
1288 {
1289 int j;
1290 for ( j = 0; j < indent; j++ )
1291 strbuf_printf( output, " " );
1292 }
1293
1294 static void output_yaml_block_literal( strbuf output, const char *value, int indent )
1295 {
1296 char *v = strdup( value );
1297 char *sol = v;
1298 char *eol = strchr( sol, '\n' );
1299
1300 while ( eol )
1301 {
1302 indent_yaml( output, indent );
1303 *eol = '\0';
1304 strbuf_printf( output, "%s\n", sol );
1305 sol = eol + 1;
1306 eol = strchr( sol, '\n' );
1307 }
1308 indent_yaml( output, indent );
1309 strbuf_printf( output, "%s\n", sol );
1310 }
1311
1312 static void serialise_yaml( mlt_properties this, strbuf output, int indent, int is_parent_sequence )
1313 {
1314 property_list *list = this->local;
1315 int i = 0;
1316
1317 for ( i = 0; i < list->count; i ++ )
1318 {
1319 // This implementation assumes that all data elements are property lists.
1320 // Unfortunately, we do not have run time type identification.
1321 mlt_properties child = mlt_property_get_data( list->value[ i ], NULL );
1322
1323 if ( mlt_properties_is_sequence( this ) )
1324 {
1325 // Ignore hidden/non-serialisable items
1326 if ( list->name[ i ][ 0 ] != '_' )
1327 {
1328 // Indicate a sequence item
1329 indent_yaml( output, indent );
1330 strbuf_printf( output, "- " );
1331
1332 // If the value can be represented as a string
1333 const char *value = mlt_properties_get( this, list->name[ i ] );
1334 if ( value && strcmp( value, "" ) )
1335 {
1336 // Determine if this is an unfolded block literal
1337 if ( strchr( value, '\n' ) )
1338 {
1339 strbuf_printf( output, "|\n" );
1340 output_yaml_block_literal( output, value, indent + strlen( list->name[ i ] ) + strlen( "|" ) );
1341 }
1342 else
1343 {
1344 strbuf_printf( output, "%s\n", value );
1345 }
1346 }
1347 }
1348 // Recurse on child
1349 if ( child )
1350 serialise_yaml( child, output, indent + 2, 1 );
1351 }
1352 else
1353 {
1354 // Assume this is a normal map-oriented properties list
1355 const char *value = mlt_properties_get( this, list->name[ i ] );
1356
1357 // Ignore hidden/non-serialisable items
1358 // If the value can be represented as a string
1359 if ( list->name[ i ][ 0 ] != '_' && value && strcmp( value, "" ) )
1360 {
1361 if ( is_parent_sequence == 0 )
1362 indent_yaml( output, indent );
1363 else
1364 is_parent_sequence = 0;
1365
1366 // Determine if this is an unfolded block literal
1367 if ( strchr( value, '\n' ) )
1368 {
1369 strbuf_printf( output, "%s: |\n", list->name[ i ] );
1370 output_yaml_block_literal( output, value, indent + strlen( list->name[ i ] ) + strlen( ": " ) );
1371 }
1372 else
1373 {
1374 strbuf_printf( output, "%s: %s\n", list->name[ i ], value );
1375 }
1376 }
1377
1378 // Output a child as a map item
1379 if ( child )
1380 {
1381 indent_yaml( output, indent );
1382 strbuf_printf( output, "%s:\n", list->name[ i ] );
1383
1384 // Recurse on child
1385 serialise_yaml( child, output, indent + 2, 0 );
1386 }
1387 }
1388 }
1389 }
1390
1391 /** Serialise the properties as a string of YAML Tiny.
1392 The caller must free the returned string.
1393 */
1394
1395 char *mlt_properties_serialise_yaml( mlt_properties this )
1396 {
1397 strbuf b = strbuf_new();
1398 strbuf_printf( b, "---\n" );
1399 serialise_yaml( this, b, 0, 0 );
1400 strbuf_printf( b, "...\n" );
1401 char *ret = b->string;
1402 strbuf_close( b );
1403 return ret;
1404 }