Complete XML files are generated now...
[jelmer/ptabtools.git] / ptb2xml.c
1 /*
2         (c) 2004: Jelmer Vernooij <jelmer@samba.org>
3
4         This program is free software; you can redistribute it and/or modify
5         it under the terms of the GNU General Public License as published by
6         the Free Software Foundation; either version 2 of the License, or
7         (at your option) any later version.
8
9         This program is distributed in the hope that it will be useful,
10         but WITHOUT ANY WARRANTY; without even the implied warranty of
11         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12         GNU General Public License for more details.
13
14         You should have received a copy of the GNU General Public License
15         along with this program; if not, write to the Free Software
16         Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19 #include <stdio.h>
20 #include <errno.h>
21 #include <popt.h>
22 #include <sys/time.h>
23 #include <time.h>
24 #include <libxml/xmlmemory.h>
25 #include <libxml/parser.h>
26 #include "ptb.h"
27
28 #define SMART_ADD_CHILD_STRING(parent, name, contents) if(contents) { xmlNodePtr tmp = xmlNewNode(NULL, name); xmlNodeSetContent(tmp, contents); xmlAddChild(parent, tmp); }
29 #define SMART_ADD_CHILD_INT(parent, name, contents) { char tmpc[100]; xmlNodePtr tmp = xmlNewNode(NULL, name); g_snprintf(tmpc, 100, "%d", contents); xmlNodeSetContent(tmp, tmpc); xmlAddChild(parent, tmp); }
30
31 xmlNodePtr xml_write_font(struct ptb_font *font)
32 {
33         xmlNodePtr xfont = xmlNewNode(NULL, "font");
34         char tmp[100];
35         g_snprintf(tmp, 100, "%d", font->size); xmlSetProp(xfont, "size", tmp);
36         g_snprintf(tmp, 100, "%d", font->thickness); xmlSetProp(xfont, "thickness", tmp);
37         g_snprintf(tmp, 100, "%d", font->underlined); xmlSetProp(xfont, "underlined", tmp);
38         g_snprintf(tmp, 100, "%d", font->italic); xmlSetProp(xfont, "italic", tmp);
39         xmlSetProp(xfont, "family", font->family);
40         return xfont;
41 }
42
43 xmlNodePtr xml_write_directions(GList *directions)
44 {
45         xmlNodePtr xdirections = xmlNewNode(NULL, "directions");
46         GList *gl = directions;
47
48         while(gl) {
49                 struct ptb_direction *direction = gl->data;
50                 xmlNodePtr xdirection = xmlNewNode(NULL, "direction");
51                 xmlAddChild(xdirections, xdirection);
52
53                 gl = gl->next;
54         }
55         return xdirections;
56 }
57
58 xmlNodePtr xml_write_rhythmslashes(GList *rhythmslashs)
59 {
60         xmlNodePtr xrhythmslashs = xmlNewNode(NULL, "rhythmslashs");
61         GList *gl = rhythmslashs;
62
63         while(gl) {
64                 struct ptb_rhythmslash *rhythmslash = gl->data;
65                 xmlNodePtr xrhythmslash = xmlNewNode(NULL, "rhythmslash");
66                 xmlAddChild(xrhythmslashs, xrhythmslash);
67                 
68                 gl = gl->next;
69         }
70         
71         return xrhythmslashs;
72 }
73
74 xmlNodePtr xml_write_chordtexts(GList *chordtexts)
75 {
76         xmlNodePtr xchordtexts = xmlNewNode(NULL, "chordtexts");
77         GList *gl = chordtexts;
78
79         while(gl) {
80                 struct ptb_chordtext *chordtext = gl->data;
81                 xmlNodePtr xchordtext = xmlNewNode(NULL, "chordtext");
82                 xmlAddChild(xchordtexts, xchordtext);
83
84                 SMART_ADD_CHILD_INT(xchordtext, "note1", chordtext->name[0]);
85                 SMART_ADD_CHILD_INT(xchordtext, "note2", chordtext->name[1]);
86                 SMART_ADD_CHILD_INT(xchordtext, "offset", chordtext->offset);
87                 SMART_ADD_CHILD_INT(xchordtext, "additions", chordtext->additions);
88                 SMART_ADD_CHILD_INT(xchordtext, "alterations", chordtext->alterations);
89                 SMART_ADD_CHILD_INT(xchordtext, "properties", chordtext->properties);
90
91                 gl = gl->next;
92         }
93         return xchordtexts;
94 }
95
96 xmlNodePtr xml_write_musicbars(GList *musicbars)
97 {
98         xmlNodePtr xmusicbars = xmlNewNode(NULL, "musicbars");
99         GList *gl = musicbars;
100
101         while(gl) {
102                 struct ptb_musicbar *musicbar = gl->data;
103                 xmlNodePtr xmusicbar = xmlNewNode(NULL, "musicbar");
104                 xmlAddChild(xmusicbars, xmusicbar);
105
106                 {
107                         char tmp[100];
108                         g_snprintf(tmp, 100, "%c", musicbar->letter);
109                         xmlSetProp(xmusicbar, "letter", tmp);
110                 }
111
112                 xmlNodeSetContent(xmusicbar, musicbar->description);
113
114                 gl = gl->next;
115         }
116         return xmusicbars;
117 }
118
119 xmlNodePtr xml_write_linedatas(GList *linedatas)
120 {
121         xmlNodePtr xlinedatas = xmlNewNode(NULL, "linedatas");
122         GList *gl = linedatas;
123
124         while(gl) {
125                 struct ptb_linedata *linedata = gl->data;
126                 xmlNodePtr xlinedata = xmlNewNode(NULL, "linedata");
127                 xmlAddChild(xlinedatas, xlinedata);
128
129                 SMART_ADD_CHILD_INT(xlinedata, "tone", linedata->tone);
130                 SMART_ADD_CHILD_INT(xlinedata, "properties", linedata->properties);
131                 SMART_ADD_CHILD_INT(xlinedata, "transcribe", linedata->transcribe);
132                 SMART_ADD_CHILD_INT(xlinedata, "conn_to_next", linedata->conn_to_next);
133
134                 gl = gl->next;
135         }
136         return xlinedatas;
137 }
138
139 xmlNodePtr xml_write_positions(GList *positions)
140 {
141         xmlNodePtr xpositions = xmlNewNode(NULL, "positions");
142         GList *gl = positions;
143
144         while(gl) {
145                 struct ptb_position *position = gl->data;
146                 xmlNodePtr xposition = xmlNewNode(NULL, "position");
147                 xmlAddChild(xpositions, xposition);
148
149                 SMART_ADD_CHILD_INT(xposition, "offset", position->offset);
150                 SMART_ADD_CHILD_INT(xposition, "length", position->length);
151                 SMART_ADD_CHILD_INT(xposition, "properties", position->properties);
152                 SMART_ADD_CHILD_INT(xposition, "let_ring", position->let_ring);
153                 SMART_ADD_CHILD_INT(xposition, "fermenta", position->fermenta);
154
155                 xmlAddChild(xposition, xml_write_linedatas(position->linedatas));
156
157                 gl = gl->next;
158         }
159         return xpositions;
160 }
161
162 xmlNodePtr xml_write_staffs(GList *staffs)
163 {
164         xmlNodePtr xstaffs = xmlNewNode(NULL, "staffs");
165         GList *gl = staffs;
166
167         while(gl) {
168                 struct ptb_staff *staff = gl->data;
169                 xmlNodePtr xstaff = xmlNewNode(NULL, "staff");
170                 xmlAddChild(xstaffs, xstaff);
171
172                 SMART_ADD_CHILD_INT(xstaff, "highest_note", staff->highest_note);
173                 SMART_ADD_CHILD_INT(xstaff, "lowest_note", staff->lowest_note);
174                 SMART_ADD_CHILD_INT(xstaff, "properties", staff->properties);
175
176                 xmlAddChild(xstaff, xml_write_positions(staff->positions1));
177                 xmlAddChild(xstaff, xml_write_musicbars(staff->musicbars));
178                 
179                 gl = gl->next;
180         }
181         return xstaffs;
182 }
183
184 xmlNodePtr xml_write_sections(GList *sections) 
185 {
186         xmlNodePtr sctns = xmlNewNode(NULL, "sections");
187         GList *gl = sections;
188
189         while(gl) {
190                 struct ptb_section *section = gl->data;
191                 xmlNodePtr meter_type;
192                 xmlNodePtr xsection = xmlNewNode(NULL, "section");
193
194                 xmlAddChild(sctns, xsection);
195
196                 {
197                 char tmp[100];
198                 g_snprintf(tmp, 100, "%c", section->letter);
199                 xmlSetProp(xsection, "letter", tmp);
200                 }
201
202                 switch(section->end_mark) {
203                 case END_MARK_TYPE_NORMAL:
204                         SMART_ADD_CHILD_STRING(xsection, "end-mark", "normal");
205                         break;
206                 case END_MARK_TYPE_REPEAT:
207                         SMART_ADD_CHILD_STRING(xsection, "end-mark", "repeat");
208                         break;
209                 }
210
211                 meter_type = xmlNewNode(NULL, "meter-type");
212                 xmlAddChild(xsection, meter_type);
213
214                 if(section->meter_type & METER_TYPE_BEAM_2) SMART_ADD_CHILD_STRING(meter_type, "beam_2", "");
215                 if(section->meter_type & METER_TYPE_BEAM_3) SMART_ADD_CHILD_STRING(meter_type, "beam_3", "");
216                 if(section->meter_type & METER_TYPE_BEAM_4) SMART_ADD_CHILD_STRING(meter_type, "beam_4", "");
217                 if(section->meter_type & METER_TYPE_BEAM_5) SMART_ADD_CHILD_STRING(meter_type, "beam_5", "");
218                 if(section->meter_type & METER_TYPE_BEAM_6) SMART_ADD_CHILD_STRING(meter_type, "beam_6", "");
219                 if(section->meter_type & METER_TYPE_COMMON) SMART_ADD_CHILD_STRING(meter_type, "common", "");
220                 if(section->meter_type & METER_TYPE_CUT) SMART_ADD_CHILD_STRING(meter_type, "cut", "");
221                 if(section->meter_type & METER_TYPE_SHOW) SMART_ADD_CHILD_STRING(meter_type, "show", "");
222
223                 SMART_ADD_CHILD_INT(xsection, "beat", section->beat_value);
224                 SMART_ADD_CHILD_INT(xsection, "metronome-pulses-per-measure", section->metronome_pulses_per_measure);
225                 SMART_ADD_CHILD_INT(xsection, "properties", section->properties);
226                 SMART_ADD_CHILD_INT(xsection, "key-extra", section->key_extra);
227                 SMART_ADD_CHILD_INT(xsection, "position-width", section->position_width);
228                 SMART_ADD_CHILD_STRING(xsection, "description", section->description);
229
230                 xmlAddChild(xsection, xml_write_chordtexts(section->chordtexts));
231                 xmlAddChild(xsection, xml_write_rhythmslashes(section->rhythmslashes));
232                 xmlAddChild(xsection, xml_write_directions(section->directions));
233                 xmlAddChild(xsection, xml_write_staffs(section->staffs));
234
235                 gl = gl->next;
236         }
237
238         return sctns;
239 }
240
241 xmlNodePtr xml_write_guitars(GList *guitars) 
242 {
243         xmlNodePtr gtrs = xmlNewNode(NULL, "guitars");
244         GList *gl = guitars;
245
246         while(gl) {
247                 char tmp[100];
248                 int i;
249                 struct ptb_guitar *gtr = gl->data;
250                 xmlNodePtr xgtr = xmlNewNode(NULL, "guitar");
251                 xmlNodePtr strings;
252                 xmlAddChild(gtrs, xgtr);
253
254                 g_snprintf(tmp, 100, "%d", gtr->index);
255                 xmlSetProp(xgtr, "id", tmp);
256
257                 strings = xmlNewNode(NULL, "strings");
258                 xmlAddChild(xgtr, strings);
259
260                 for(i = 0; i < gtr->nr_strings; i++) {
261                         SMART_ADD_CHILD_INT(strings, "string", gtr->strings[i]);
262                 }
263
264                 SMART_ADD_CHILD_INT(xgtr, "reverb", gtr->reverb);
265                 SMART_ADD_CHILD_INT(xgtr, "chorus", gtr->chorus);
266                 SMART_ADD_CHILD_INT(xgtr, "tremolo", gtr->tremolo);
267                 SMART_ADD_CHILD_INT(xgtr, "pan", gtr->pan);
268                 SMART_ADD_CHILD_INT(xgtr, "capo", gtr->capo);
269                 SMART_ADD_CHILD_INT(xgtr, "initial_volume", gtr->initial_volume);
270                 SMART_ADD_CHILD_INT(xgtr, "midi_instrument", gtr->midi_instrument);
271                 SMART_ADD_CHILD_INT(xgtr, "half_up", gtr->half_up);
272                 SMART_ADD_CHILD_INT(xgtr, "simulate", gtr->simulate);
273
274                 gl = gl->next;
275         }
276         
277         return gtrs;
278 }
279
280 xmlNodePtr xml_write_guitarins(GList *guitarins)
281 {
282         GList *gl = guitarins;
283         xmlNodePtr xguitarins = xmlNewNode(NULL, "guitarins");
284         
285         while(gl) {
286                 struct ptb_guitarin *guitarin = gl->data;
287                 xmlNodePtr xguitarin = xmlNewNode(NULL, "guitarin");
288                 xmlAddChild(xguitarins, xguitarin);
289                 
290                 SMART_ADD_CHILD_INT(xguitarin, "offset", guitarin->offset);
291                 SMART_ADD_CHILD_INT(xguitarin, "section", guitarin->section);
292                 SMART_ADD_CHILD_INT(xguitarin, "staff", guitarin->staff);
293                 SMART_ADD_CHILD_INT(xguitarin, "rhythm_slash", guitarin->rhythm_slash);
294                 SMART_ADD_CHILD_INT(xguitarin, "staff_in", guitarin->staff_in);
295
296                 gl = gl->next;
297         }
298
299         return xguitarins;
300 }
301
302 xmlNodePtr xml_write_tempomarkers(GList *tempomarkers)
303 {
304         GList *gl = tempomarkers;
305         xmlNodePtr xtempomarkers = xmlNewNode(NULL, "tempomarkers");
306         
307         while(gl) {
308                 struct ptb_tempomarker *tempomarker = gl->data;
309                 xmlNodePtr xtempomarker = xmlNewNode(NULL, "tempomarker");
310                 xmlAddChild(xtempomarkers, xtempomarker);
311                 
312                 SMART_ADD_CHILD_INT(xtempomarker, "type", tempomarker->type);
313                 SMART_ADD_CHILD_INT(xtempomarker, "bpm", tempomarker->bpm);
314                 xmlNodeSetContent(xtempomarker, tempomarker->description);
315
316                 gl = gl->next;
317         }
318
319         return xtempomarkers;
320 }
321
322 xmlNodePtr xml_write_dynamics(GList *dynamics)
323 {
324         GList *gl = dynamics;
325         xmlNodePtr xdynamics = xmlNewNode(NULL, "dynamics");
326         
327         while(gl) {
328                 struct ptb_dynamic *dynamic = gl->data;
329                 xmlNodePtr xdynamic = xmlNewNode(NULL, "dynamic");
330                 xmlAddChild(xdynamics, xdynamic);
331                 
332                 SMART_ADD_CHILD_INT(xdynamic, "offset", dynamic->offset);
333
334                 gl = gl->next;
335         }
336
337         return xdynamics;
338 }
339
340 xmlNodePtr xml_write_chorddiagrams(GList *chorddiagrams)
341 {
342         GList *gl = chorddiagrams;
343         xmlNodePtr xchorddiagrams = xmlNewNode(NULL, "chorddiagrams");
344         
345         while(gl) {
346                 struct ptb_chorddiagram *chorddiagram = gl->data;
347                 int i;
348                 xmlNodePtr xchorddiagram = xmlNewNode(NULL, "chorddiagram");
349                 xmlNodePtr strings = xmlNewNode(NULL, "strings");
350                 xmlAddChild(xchorddiagrams, xchorddiagram);
351                 xmlAddChild(xchorddiagram, strings);
352                 
353                 SMART_ADD_CHILD_INT(xchorddiagram, "note1", chorddiagram->name[0]);
354                 SMART_ADD_CHILD_INT(xchorddiagram, "note2", chorddiagram->name[1]);
355                 SMART_ADD_CHILD_INT(xchorddiagram, "frets", chorddiagram->frets);
356                 SMART_ADD_CHILD_INT(xchorddiagram, "type", chorddiagram->type);
357
358                 for(i = 0; i < chorddiagram->nr_strings; i++) {
359                         SMART_ADD_CHILD_INT(strings, "string", chorddiagram->tones[i]);
360                 }
361                 
362                 gl = gl->next;
363         }
364
365         return xchorddiagrams;
366 }
367
368 xmlNodePtr xml_write_sectionsymbols(GList *sectionsymbols)
369 {
370         GList *gl = sectionsymbols;
371         xmlNodePtr xsectionsymbols = xmlNewNode(NULL, "sectionsymbols");
372         
373         while(gl) {
374                 struct ptb_sectionsymbol *sectionsymbol = gl->data;
375                 xmlNodePtr xsectionsymbol = xmlNewNode(NULL, "sectionsymbol");
376                 xmlAddChild(xsectionsymbols, xsectionsymbol);
377                 
378                 SMART_ADD_CHILD_INT(xsectionsymbol, "repeat-ending", sectionsymbol->repeat_ending);
379
380                 gl = gl->next;
381         }
382
383         return xsectionsymbols;
384 }
385
386 xmlNodePtr xml_write_floatingtexts(GList *floatingtexts)
387 {
388         GList *gl = floatingtexts;
389         xmlNodePtr xfloatingtexts = xmlNewNode(NULL, "floatingtexts");
390         
391         while(gl) {
392                 struct ptb_floatingtext *floatingtext = gl->data;
393                 xmlNodePtr xfloatingtext = xmlNewNode(NULL, "floatingtext");
394                 xmlAddChild(xfloatingtexts, xfloatingtext);
395                 
396                 SMART_ADD_CHILD_INT(xfloatingtext, "beginpos", floatingtext->beginpos);
397
398                 switch(floatingtext->alignment) {
399                 case ALIGN_LEFT:
400                         SMART_ADD_CHILD_STRING(xfloatingtext, "alignment", "left");
401                         break;
402                 case ALIGN_RIGHT:
403                         SMART_ADD_CHILD_STRING(xfloatingtext, "alignment", "right");
404                         break;
405                 case ALIGN_CENTER:
406                         SMART_ADD_CHILD_STRING(xfloatingtext, "alignment", "center");
407                         break;
408                 }
409                 xmlNodeSetContent(xfloatingtext, floatingtext->text);
410
411                 xmlAddChild(xfloatingtext, xml_write_font(&floatingtext->font));
412
413                 gl = gl->next;
414         }
415
416         return xfloatingtexts;
417 }
418
419 xmlNodePtr xml_write_instrument(struct ptbf *bf, int i)
420 {
421         char tmp[100];
422         xmlNodePtr instrument = xmlNewNode(NULL, "instrument");
423         g_snprintf(tmp, 100, "%d", i);
424         xmlSetProp(instrument, "id", tmp);
425
426         xmlAddChild(instrument, xml_write_guitars(bf->instrument[i].guitars));
427         xmlAddChild(instrument, xml_write_sections(bf->instrument[i].sections));
428         xmlAddChild(instrument, xml_write_guitarins(bf->instrument[i].guitarins));
429         xmlAddChild(instrument, xml_write_chorddiagrams(bf->instrument[i].chorddiagrams));
430         xmlAddChild(instrument, xml_write_tempomarkers(bf->instrument[i].tempomarkers));
431         xmlAddChild(instrument, xml_write_dynamics(bf->instrument[i].dynamics));
432         xmlAddChild(instrument, xml_write_floatingtexts(bf->instrument[i].floatingtexts));
433         xmlAddChild(instrument, xml_write_sectionsymbols(bf->instrument[i].sectionsymbols));
434         return instrument;
435 }
436
437 xmlNodePtr xml_write_song_header(struct ptb_hdr *hdr)
438 {
439         xmlNodePtr song = xmlNewNode(NULL, "song");
440
441         SMART_ADD_CHILD_STRING(song, "title", hdr->class_info.song.title); 
442         SMART_ADD_CHILD_STRING(song, "artist", hdr->class_info.song.artist); 
443         SMART_ADD_CHILD_STRING(song, "words-by", hdr->class_info.song.words_by); 
444         SMART_ADD_CHILD_STRING(song, "music-by", hdr->class_info.song.music_by); 
445         SMART_ADD_CHILD_STRING(song, "arranged-by", hdr->class_info.song.arranged_by); 
446         SMART_ADD_CHILD_STRING(song, "guitar-transcribed-by", hdr->class_info.song.guitar_transcribed_by); 
447         SMART_ADD_CHILD_STRING(song, "bass-transcribed-by", hdr->class_info.song.bass_transcribed_by); 
448         SMART_ADD_CHILD_STRING(song, "lyrics", hdr->class_info.song.lyrics);
449         SMART_ADD_CHILD_STRING(song, "copyright", hdr->class_info.song.copyright);
450
451         /* FIXME: Sub stuff */
452
453         return song;
454 }
455
456 xmlNodePtr xml_write_lesson_header(struct ptb_hdr *hdr)
457 {
458         xmlNodePtr lesson = xmlNewNode(NULL, "lesson");
459
460         SMART_ADD_CHILD_STRING(lesson, "title", hdr->class_info.lesson.title); 
461         SMART_ADD_CHILD_STRING(lesson, "artist", hdr->class_info.lesson.artist); 
462         SMART_ADD_CHILD_STRING(lesson, "author", hdr->class_info.lesson.author);
463         SMART_ADD_CHILD_STRING(lesson, "copyright", hdr->class_info.lesson.copyright);
464
465         switch(hdr->class_info.lesson.level) {
466         case LEVEL_BEGINNER: xmlSetProp(lesson, "level", "beginner"); break;
467         case LEVEL_INTERMEDIATE: xmlSetProp(lesson, "level", "intermediate"); break;
468         case LEVEL_ADVANCED: xmlSetProp(lesson, "level", "advanced"); break;
469         }
470
471         /* FIXME: Style */
472
473         return lesson;
474 }
475
476 xmlNodePtr xml_write_header(struct ptb_hdr *hdr) 
477 {
478         xmlNodePtr header = xmlNewNode(NULL, "header");
479         switch(hdr->classification) {
480         case CLASSIFICATION_SONG:
481                 xmlSetProp(header, "classification", "song");
482                 xmlAddChild(header, xml_write_song_header(hdr));
483                 break;
484         case CLASSIFICATION_LESSON:
485                 xmlSetProp(header, "classification", "lesson");
486                 xmlAddChild(header, xml_write_lesson_header(hdr));
487                 break;
488         }
489         return header;
490 }
491
492 int main(int argc, const char **argv) 
493 {
494         struct ptbf *ret;
495         int debugging = 0;
496         xmlNodePtr root_node;
497         xmlDocPtr doc;
498         xmlNodePtr comment;
499         int c, i;
500         int version = 0;
501         char *output = NULL;
502         poptContext pc;
503         struct poptOption options[] = {
504                 POPT_AUTOHELP
505                 {"debug", 'd', POPT_ARG_NONE, &debugging, 0, "Turn on debugging output" },
506                 {"outputfile", 'o', POPT_ARG_STRING, &output, 0, "Write to specified file", "FILE" },
507                 {"version", 'v', POPT_ARG_NONE, &version, 'v', "Show version information" },
508                 POPT_TABLEEND
509         };
510
511         pc = poptGetContext(argv[0], argc, argv, options, 0);
512         poptSetOtherOptionHelp(pc, "file.ptb");
513         while((c = poptGetNextOpt(pc)) >= 0) {
514                 switch(c) {
515                 case 'v':
516                         printf("ptb2ascii Version "PTB_VERSION"\n");
517                         printf("(C) 2004 Jelmer Vernooij <jelmer@samba.org>\n");
518                         exit(0);
519                         break;
520                 }
521         }
522                         
523         ptb_set_debug(debugging);
524         
525         if(!poptPeekArg(pc)) {
526                 poptPrintUsage(pc, stderr, 0);
527                 return -1;
528         }
529         ret = ptb_read_file(poptGetArg(pc));
530         
531         if(!ret) {
532                 perror("Read error: ");
533                 return -1;
534         } 
535
536         doc = xmlNewDoc(BAD_CAST "1.0");
537         root_node = xmlNewNode(NULL, BAD_CAST "powertab");
538         xmlDocSetRootElement(doc, root_node);
539
540         comment = xmlNewComment("\nGenerated by ptb2xml, part of ptabtools. \n"
541                                                         "(C) 2004 by Jelmer Vernooij <jelmer@samba.org>\n"
542                                                         "See http://jelmer.vernstok.nl/oss/ptabtools/ for details\n");
543         xmlAddChild(root_node, comment);
544
545         xmlAddChild(root_node, xml_write_header(&ret->hdr));
546
547         for(i = 0; i < 2; i++) {
548                 xmlAddChild(root_node, xml_write_instrument(ret, i));
549         }
550
551         xmlSaveFormatFileEnc(output?output:"-", doc, "UTF-8", 1);
552
553         xmlFreeDoc(doc);
554
555         xmlCleanupParser();
556
557         return 0;
558 }