Coro で Producer-Consumer パターン

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編 を参考に Coro で Producer-Consumer パターンを実装。

Producer-Consumer パターン は メッセージキューだが、キューが一杯のときはキューに入れようとする待たされる。キューが空のときキューを読もうとすると待たされる。


#!/usr/bin/perl
use strict;
use warnings;

package Maker;
use Coro;
use Coro::Timer;

sub new {
    my ( $class, %args ) = @_;
    bless \%args, $class;
}

sub run {
    my $self = shift;
    my $coro = async {
        do {
            my $queue = $self->{queue};

            my $msg;
            $msg = "message1";
            print "Maker: put $msg\n";
            $queue->put($msg);

            $msg = "message2";
            print "Maker: put $msg\n";
            $queue->put($msg);

            $msg = "message3";
            print "Maker: put $msg\n";
            $queue->put($msg);

            $msg = "message4";
            print "Maker: put $msg\n";
            $queue->put($msg);

            $msg = "end";
            print "Maker: put $msg\n";
            $queue->put($msg);
        };
    };
}

package Eater;
use Coro;

sub new {
    my ( $class, %args ) = @_;
    bless \%args, $class;
}

sub run {
    my $self = shift;
    my $coro = async {
        do {
            my $queue = $self->{queue};
            while (1) {
                my $msg = $queue->take;
                print "Eater: take $msg\n";
                last if $msg =~ m/^end$/;
            }
        };
    };

    return $coro;
}

package Queue;
use Coro;
use Coro::Semaphore;
use Coro::Signal;

sub new {
    my ( $class, %args ) = @_;
    my %defaults = ( max => 3 );
    %args = ( %defaults, %args );
    $args{signal}   = Coro::Signal->new;
    $args{queue}    = [];
    $args{put_sem}  = Coro::Semaphore->new;
    $args{take_sem} = Coro::Semaphore->new;
    bless \%args, $class;
}

sub put {
    my $self  = shift;
    my $guard = $self->{put_sem}->guard();

    while ( $self->{max} <= scalar( @{ $self->{queue} } ) ) {
        print qq{no space on the table. waiting for "put"\n};
        $self->{signal}->wait();
    }
    push @{ $self->{queue} }, $_[0];
    $self->{signal}->broadcast();
}

sub take {
    my $self  = shift;
    my $guard = $self->{take_sem}->guard();

    while ( scalar( @{ $self->{queue} } ) == 0 ) {
        print qq{no cake on the table. waiting for "put"\n};
        $self->{signal}->wait();
    }
    my $ret = shift @{ $self->{queue} };
    $self->{signal}->broadcast();
    return $ret;
}

package main;
use Coro;

my $queue = Queue->new( max   => 3 );
my $eater = Eater->new( queue => $queue );
my $maker = Maker->new( queue => $queue );

my $maker_coro = $maker->run();
my $eater_coro = $eater->run();

$maker_coro->join();
$eater_coro->join();

出力

Maker: put message1
Maker: put message2
Maker: put message3
Maker: put message4
no space on the table. waiting for "put"
Eater: take message1
Eater: take message2
Eater: take message3
no cake on the table. waiting for "put"
Maker: put end
Eater: take message4
Eater: take end

pic ファイル

.PS

copy "sequence.pic";

boxwid = 1.3;

# Define the objects
object(M,":maker");
object(T,":table");
object(E,":eater");
step();

# Message sequences
active(M);
active(E);

message(M,T,"put");
active(T);
rmessage(T,M,"");
inactive(T);

message(M,T,"put");
active(T);
rmessage(T,M,"");
inactive(T);

message(M,T,"put");
active(T);
rmessage(T,M,"");
inactive(T);

message(M,T,"put");
active(T);
comment(T,T2,up 0.5 right 0.2,wid 1.5 ht 0.5 "no space on the table." "waiting for \"take\".");
step();
message(E,T,"take");
active(T);
rmessage(T,E,"");
inactive(T);
rmessage(T,M,"");
inactive(T);

message(E,T,"take");
active(T);
rmessage(T,E,"");
inactive(T);

message(E,T,"take");
active(T);
rmessage(T,E,"");
inactive(T);

message(E,T,"take");
active(T);
rmessage(T,E,"");
inactive(T);

message(E,T,"take");
active(T);
comment(T,T2,up 0.5 left 0.2,wid 1.5 ht 0.5 "no cake on the table." "waiting for \"put\".");
step();
message(M,T,"put");
active(T);
rmessage(T,M,"");
inactive(T);
rmessage(T,E,"");
inactive(T);

complete(M);
complete(T);
complete(E);

.PE

Coro で Balking パターン

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編 を参考に Coro で Balking パターンを実装。Balking パターン途中で止めること(Balkする=途中で止める)。この例だと「保存しようとしたけど、既に保存されていたらやめちゃおう。」


use strict;
use warnings;

package Changer;
use Coro;
use Coro::Timer;

sub new {
    my ( $class, %args ) = @_;
    bless \%args, $class;
}

sub run {
    my $self = shift;
    my $coro = async {
        do {
            my $data = $self->{data};

            my $text;
            $text = "data1";
            print "Changer: change $text\n";
            $data->change($text);

            print "Changer: save\n";
            $data->save();

            $text = "data2";
            print "Changer: change $text\n";
            $data->change($text);

            print "Changer: save\n";
            $data->save();

            $text = "data3";
            print "Changer: change $text\n";
            $data->change($text);

            print "Changer: sleep...\n";
            Coro::Timer::sleep(2);

            print "Changer: save\n";
            $data->save();
        };
    };
}

package Saver;
use Coro;
use Coro::Timer;

sub new {
    my ( $class, %args ) = @_;
    my %defaults = ( period => 3 );
    %args = ( %defaults, %args );
    bless \%args, $class;
}

sub run {
    my $self = shift;
    my $coro = async {
        do {
            my $period = $self->{period};
            my $data   = $self->{data};
            while (1) {
                print "Saver  : save\n";
                $data->save();
                Coro::Timer::sleep($period);
            }
        };
    };

    return $coro;
}

package Data;
use Coro;
use Coro::Semaphore;

sub new {
    my ( $class, %args ) = @_;
    $args{sem}     = Coro::Semaphore->new;
    $args{changed} = 0;

    bless \%args, $class;
}

sub change {
    my $self = shift;
    $self->{content} = shift;
    $self->{changed} = 1;
}

sub save {
    my $self  = shift;
    my $guard = $self->{sem}->guard();

    if ( $self->{changed} == 1 ) {
        print "Data   : do_save\n";
        $self->do_save();
        $self->{changed} = 0;
    }
    else {
        print "Data   : don't do_save and balk\n";
    }
}

sub do_save {
    my $self = shift;
    my $file = $self->{file};
    open my $fh, '>', $file or die "$!:$file";
    print {$fh} $self->{content};
    close $fh or die "$!:$file";
}

package main;
use Coro;

my $data = Data->new( file => 'data.txt' );
my $saver = Saver->new( data => $data, period => 1 );
my $changer = Changer->new( data => $data );

my $changer_coro = $changer->run();
my $saver_coro   = $saver->run();

$changer_coro->join();
$saver_coro->cancel();

出力

$ perl -w Balking.pl
Changer: change data1
Changer: save
Data   : do_save
Changer: change data2
Changer: save
Data   : do_save
Changer: change data3
Changer: sleep...
Saver  : save
Data   : do_save
Saver  : save
Data   : don't do_save and balk
Changer: save
Data   : don't do_save and balk
Saver  : save
Data   : don't do_save and balk

pic ファイル

.PS

copy "sequence.pic";

boxwid = 1.3;

# Define the objects
object(C,":changer");
object(D,":data");
object(S,":saver");
step();

# Message sequences
active(C);
active(S);
comment(S,T2,down 0.25 right 0.25,wid 1.25 ht 0.25 "save periodically");

message(C,D,"change");
active(D);
rmessage(D,C,"");
inactive(D);

message(C,D,"save");
active(D);
message(D,D,"do_save");
active(D);
step();
inactive(D);
rmessage(D,C,"");
inactive(D);

message(C,D,"change");
active(D);
rmessage(D,C,"");
inactive(D);

message(S,D,"save");
active(D);
message(D,D,"do_save");
active(D);
step();
inactive(D);
rmessage(D,S,"");
inactive(D);

message(C,D,"save");
active(D);
comment(D,T2,up 0.5 left 0.5,wid 1 ht 0.5 "don't do_save," "and balk");
rmessage(D,C,"");
inactive(D);

complete(C);
complete(D);
complete(S);

.PE

Coro で Guarded-Suspension パターン (Coro::Signal使用)

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編 の Guarded-Suspension パターンを Coro で実装。前回は Coro::Channelを使ったが、Coro::Channelを使うとキューが一杯になると、ブロックしてしまう。

use strict;
use warnings;
use Coro;
use Coro::Channel;

my $max = 2;
my $ch  = Coro::Channel->new($max);

my $put_coro = async {
    do {
        for my $count ( 1 .. 10 ) {
            print "send $count\n";
            $ch->put($count);
        }
    };
};

my $get_coro = async {
    do {
        while (1) {
            my $count = $ch->get;
            print "recv $count\n";
        }
    };
};

$put_coro->join();
$get_coro->cancel();

上のスクリプトを実行すると、2 つメッセージを送ると送信側がブロックするのがわかる。

send 1
send 2
recv 1
recv 2
send 3
send 4
recv 3
recv 4
send 5
send 6
recv 5
recv 6
send 7
send 8
recv 7
recv 8
send 9
send 10
recv 9
recv 10

そこで、書籍のとおりにシグナル (この例では Coro::Signal) を使って Guarded-Suspension パターンを実装する。キューが一杯になると破棄している。

use strict;
use warnings;

package Client;
use Coro;
use Coro::Timer;

sub new {
    my ( $class, %args ) = @_;
    bless \%args, $class;
}

sub run {
    my $self = shift;
    my $coro = async {
        do {
            my $queue = $self->{queue};

            my $msg;
            $msg = "message1";
            print "Client: put $msg\n";
            $queue->put($msg);

            $msg = "message2";
            print "Client: put $msg\n";
            $queue->put($msg);

            print "Client: sleep...\n";
            Coro::Timer::sleep(1);

            $msg = "end";
            print "Client: put $msg\n";
            $queue->put($msg);
        };
    };
    return $coro;
}

package Server;
use Coro;

sub new {
    my ( $class, %args ) = @_;
    bless \%args, $class;
}

sub run {
    my $self = shift;
    my $coro = async {
        do {
            my $queue = $self->{queue};
            while (1) {
                my $msg = $queue->get;
                print "Server: get $msg\n";
                last if $msg =~ m/^end$/;
            }
        };
    };

    return $coro;
}

package Queue;
use Coro;
use Coro::Semaphore;
use Coro::Signal;

sub new {
    my ( $class, %args ) = @_;
    my %defaults = ( max => 3 );
    %args = ( %defaults, %args );
    $args{signal}  = Coro::Signal->new;
    $args{queue}   = [];
    $args{put_sem} = Coro::Semaphore->new;
    $args{get_sem} = Coro::Semaphore->new;
    bless \%args, $class;
}

sub put {
    my $self  = shift;
    my $guard = $self->{put_sem}->guard();

    if ($self->{max} <= scalar( @{ $self->{queue} } )) {
        warn "no space in queue";
        return;
    }
    push @{ $self->{queue} }, $_[0];
    $self->{signal}->broadcast();
}

sub get {
    my $self  = shift;
    my $guard = $self->{get_sem}->guard();

    while ( scalar( @{ $self->{queue} } ) == 0 ) {
        $self->{signal}->wait();
    }
    return shift @{ $self->{queue} };
}

package main;
use Coro;

my $queue = Queue->new( max => 5 );
my $server = Server->new( queue => $queue );
my $client = Client->new( queue => $queue );

my $client_coro = $client->run();
my $server_coro = $server->run();

$client_coro->join();
$server_coro->join();

出力

Client: put message1
Client: put message2
Client: sleep...
Server: get message1
Server: get message2
Client: put end
Server: get end

Coro で Guarded-Suspension パターン (Coro::Channel使用)

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編 の Guarded-Suspension パターンを Coro で実装。Guarded-Suspension パターンは要するにメッセージ送受信。Coro::Channel を使ったが、 Coro::Channel を使うと厳密には、Producer-Consumer パターンになるっぽい。


use strict;
use warnings;

package Client;
use Coro;
use Coro::Timer;

sub new {
    my ( $class, %args ) = @_;
    bless \%args, $class;
}

sub run {
    my $self = shift;
    my $coro = async {
        do {
            my $ch = $self->{channel};

            my $msg;
            $msg = "message1";
            print "Client: put $msg\n";
            $ch->put($msg);

            $msg = "message2";
            print "Client: put $msg\n";
            $ch->put($msg);

            print "Client: sleep...\n";
            Coro::Timer::sleep(1);

            $msg = "end";
            print "Client: put $msg\n";
            $ch->put($msg);
        };
    };
    return $coro;
}

package Server;
use Coro;

sub new {
    my ( $class, %args ) = @_;
    bless \%args, $class;
}

sub run {
    my $self = shift;
    my $coro = async {
        do {
            my $ch = $self->{channel};
            while (1) {
                my $msg = $ch->get;
                print "Server: get $msg\n";
                last if $msg =~ m/^end$/;
            }
        };
    };

    return $coro;
}

package main;
use Coro;
use Coro::Channel;

my $channel = Coro::Channel->new;
my $server  = Server->new( channel => $channel );
my $client  = Client->new( channel => $channel );

my $client_coro = $client->run();
my $server_coro = $server->run();

$client_coro->join();
$server_coro->join();

結果出力

Client: put message1
Client: put message2
Client: sleep...
Server: get message1
Server: get message2
Client: put end
Server: get end

pic ファイル

.PS

copy "sequence.pic";

boxwid = 1.3;

# Define the objects
object(CL,":client");
object(CH,":channel");
object(S,":server");
step();

# Message sequences
active(CL);
active(S);

message(CL,CH,"put");
active(CH);
rmessage(CH,CL,"");
inactive(CH);

message(CL,CH,"put");
active(CH);
rmessage(CH,CL,"");
inactive(CH);

message(S,CH,"get");
active(CH);
rmessage(CH,S,"");
inactive(CH);

message(S,CH,"get");
active(CH);
rmessage(CH,S,"");
inactive(CH);

message(S,CH,"get");
active(CH);
comment(CH,T2,up 0.5 left 0.5,wid 1 ht 0.5 "waiting for" "put");

message(CL,CH,"put");
active(CH);
rmessage(CH,CL,"");
inactive(CH);
rmessage(CH,S,"");
inactive(CH);

complete(CL);
complete(CH);
complete(S);

.PE

GNU PIC の出力画像を綺麗にする

GNU PIC の出力画像が綺麗でないことが不満だったが、一旦SVGファイルにしてから、png などの画像にすれば綺麗になることがわかった。

今までの画像出力


文字が滲んでいるし、曲線のギザギザも気になる。フォントを変えても文字の滲みは改善しない。

SVG に直してから変換した画像出力。


文字がスッキリしているし、曲線も滑らかになった。

今までのスクリプト

pic2plot コマンドで ppm ファイルにして、余計な余白を削除している。

#!/bin/sh

PIC_FILE=$1
if [ ! -f "$PIC_FILE" ]; then
    echo "Usage : "`basename $0`" pic_file [gif_file]"
    exit 1
fi

if [ $# -eq 2 ]
then
    GIF_FILE=$2
else
    GIF_FILE=`echo $PIC_FILE | sed 's/[^.]*$/gif/'`
fi

perl -pe 's/\x0D\x0A|\x0D/\x0A/g' $PIC_FILE | \
    iconv -t EUC-JP | \
    pic2plot -T pnm -F HersheyEUC --bitmap-size '1024x1024' |\
    pnmcrop |\
    (ppmtogif 2> /dev/null) > $GIF_FILE
改良したスクリプト

pic2plot コマンドの出力を SVG にしてから、 ppm -> 最終画像 に変換するようにした(ついでに、 gif から png に変更)。一番のポイントは rsvg-convert を使って画像変換するようにしたこと。

#!/bin/sh

PIC_FILE=$1
if [ ! -f "$PIC_FILE" ]; then
    echo "Usage : "`basename $0`" pic_file [png_file]"
    exit 1
fi

if [ $# -eq 2 ]
then
    PNG_FILE=$2
else
    PNG_FILE=`echo $PIC_FILE | sed 's/[^.]*$/png/'`
fi

case "$OS" in
    Windows* ) # for cygwin
        FONT='Arial-Roman'
        PNG2PPM='pngtopam'
        ;;
    *)
        FONT='Helvetica'
        PNG2PPM='pngtopnm'
        ;;
esac

perl -pe 's/\x0D\x0A|\x0D/\x0A/g' $PIC_FILE | \
    iconv -t EUC-JP | \
    pic2plot -T svg -F $FONT --bitmap-size '1024x1024' | \
    rsvg-convert -f png | $PNG2PPM | pnmcrop | pnmtopng > $PNG_FILE
PIC ファイル
.PS
ellipse "document";
arrow;
box width 0.6 "\fIpic\fP(1)"
arrow;
box width 1.1 "\fIgtbl\fP(1) or \fIgeqn\fP(1)" "(optional)" dashed;
arrow;
box width 0.6 "\fIgtroff\fP(1)";
arrow;
ellipse "PostScript"
.PE

Streaming API で XMLの読み出し

Libxml2 の Streaming API っていうのは、 XML を頭から解析するときに役立つ API。SAXと違ってハンドラを登録する必要がないから楽。

#include <stdio.h>
#include <libxml/tree.h>
#include <libxml/xmlreader.h>

void processNode(xmlTextReaderPtr reader)
{
    const xmlChar *name;
    const xmlChar *value;
    int            ret;

    /* Print node infos */
    name = xmlTextReaderConstName(reader);
    if (NULL == name) {
        name = BAD_CAST "--";
    }
    printf("%d %d %s %d %d %d, ",
           xmlTextReaderDepth(reader),
           xmlTextReaderNodeType(reader),
           name,
           xmlTextReaderIsEmptyElement(reader),
           xmlTextReaderHasValue(reader),
           xmlTextReaderHasAttributes(reader));

    /* Print value */
    if (1 == xmlTextReaderHasValue(reader)) {
        printf("value: ");
        value = xmlTextReaderConstValue(reader);
        if (value == NULL) {
            printf("--, ");
        }
        else {
            if (xmlStrlen(value) > 40) {
                printf("%.40s..., ", value);
            }
            else {
                printf("%s,", value);
            }
        }
    }

    /* Print attribute */
    if (1 == xmlTextReaderHasAttributes(reader)) {
        printf("attr: ");
        ret = xmlTextReaderMoveToFirstAttribute(reader);
        while(1 == ret) {
            printf("%s = %s, ",
                   xmlTextReaderConstName(reader),
                   xmlTextReaderConstValue(reader));
            ret = xmlTextReaderMoveToNextAttribute(reader);
        }
    }

    printf("\n");

}

int main(int argc, char **argv)
{
    xmlTextReaderPtr  reader;
    char             *input_file;
    int               ret;

    if (argc != 2) {
        return 1;
    }

    input_file = argv[1];

    LIBXML_TEST_VERSION;

    /* Read document */
    reader = xmlReaderForFile(input_file, NULL, 0);
    if (NULL == reader) {
        fprintf(stderr, "Failed to parse %s\n", input_file);
        return;
    }

    /* Parse XML */
    while (1 == (ret = xmlTextReaderRead(reader))) {
        processNode(reader);
    }

    if (0 != ret) {
        fprintf(stderr, "%s : failed to parse\n", input_file);
    }

    /* Free reader */
    xmlFreeTextReader(reader);

    xmlCleanupParser();

    return 0;
}

解析対象 XML

<?xml version="1.0" encoding="UTF-8" ?>
<cars>
  <car country="US" type="car">
    <price>150</price>
    <img file="car1.jpg"/>
  </car>
  <car country="JP" type="truck">
    <price>500</price>
    <img file="car2.jpg"/>
  </car>
</cars>

上のXMLを解析すると出力は次のようになる。

0 1 cars 0 0 0, 
1 14 #text 0 1 0, value: 
  ,
1 1 car 0 0 1, attr: country = US, type = car, 
2 14 #text 0 1 0, value: 
    ,
2 1 price 0 0 0, 
3 3 #text 0 1 0, value: 150,
2 15 price 0 0 0, 
2 14 #text 0 1 0, value: 
    ,
2 1 img 1 0 1, attr: file = car1.jpg, 
2 14 #text 0 1 0, value: 
  ,
1 15 car 0 0 1, attr: country = US, type = car, 
1 14 #text 0 1 0, value: 
  ,
1 1 car 0 0 1, attr: country = JP, type = truck, 
2 14 #text 0 1 0, value: 
    ,
2 1 price 0 0 0, 
3 3 #text 0 1 0, value: 500,
2 15 price 0 0 0, 
2 14 #text 0 1 0, value: 
    ,
2 1 img 1 0 1, attr: file = car2.jpg, 
2 14 #text 0 1 0, value: 
  ,
1 15 car 0 0 1, attr: country = JP, type = truck, 
1 14 #text 0 1 0, value: 
,
0 15 cars 0 0 0, 

Writing API で XMLの書き出し

Libxml2 の Writing API っていうのは、 XML を頭から出力するときに役立つ API

#include <stdio.h>
#include <string.h>
#include <libxml/encoding.h>
#include <libxml/xmlwriter.h>

#define MY_ENCODING "UTF-8"

/*****************************************************************************************/
/* output                                                                                */
/* ------                                                                                */
/* <?xml version="1.0" encoding="UTF-8"?>           : xmlTextWriterStartDocument &       */
/*                                                  :   xmlTextWriterWriteAttribute      */
/* <EXAMPLE>                                        : xmlTextWriterStartElement          */
/*   <!--This is a comment-->                       : xmlTextWriterWriteComment          */
/*   <ORDER version="1.0" xml:lang="de">            : xmlTextWriterStartElement  &       */
/*                                                  :   xmlTextWriterWriteAttribute      */
/*     <!--This is formatted comment-->             : xmlTextWriterWriteFormatComment    */
/*     <ENTRY>                                      : xmlTextWriterStartElement          */
/*       <ARTICLE>&lt;Test&gt;</ARTICLE>            : xmlTextWriterWriteElement          */
/*       <ENTRY_NO>Formatted Element 0010/ENTRY_NO> : xmlTextWriterWriteFormatElement    */
/*     </ENTRY>                                     : xmlTextWriterEndElement            */
/*     <FOOT>Formatted string 1</FOOT>              : xmlTextWriterStartElement &        */
/*                                                  :   xmlTextWriterWriteFormatString & */
/*                                                  :   xmlTextWriterEndElement          */
/*   </ORDER>                                       : xmlTextWriterEndDocument           */
/* </EXAMPLE>                                       :                                    */
/*****************************************************************************************/

void testXmlwriterFilename(const char *uri)
{
    int               ret;
    xmlTextWriterPtr  writer;

    /* Create a new XmlWriter for uri, with no compression. */
    writer = xmlNewTextWriterFilename(uri, 0);
    if (writer == NULL) {
        fprintf(stderr, "Error creating the xml writer\n");
        goto LAST;
    }

    /* Start the document with the xml default for the version,
     * encoding MY_ENCODING and the default for the standalone
     * declaration. */
    ret = xmlTextWriterStartDocument(writer, NULL, MY_ENCODING, NULL);
    if (ret < 0) {
        fprintf(stderr, "Error at xmlTextWriterStartDocument\n");
        goto LAST;
    }

    /* Start an element named "EXAMPLE". Since thist is the first
     * element, this will be the root element of the document. */
    ret = xmlTextWriterStartElement(writer, BAD_CAST "EXAMPLE");
    if (ret < 0) {
        fprintf(stderr, "Error at xmlTextWriterStartElement\n");
        goto LAST;
    }

    /* Write a comment as child of EXAMPLE */
    ret = xmlTextWriterWriteComment(writer, "This is a comment");
    if (ret < 0) {
        fprintf(stderr, "Error at xmlTextWriterWriteComment\n");
        goto LAST;
    }

    /* Start an element named "ORDER" as child of EXAMPLE. */
    ret = xmlTextWriterStartElement(writer, BAD_CAST "ORDER");
    if (ret < 0) {
        fprintf(stderr, "Error at xmlTextWriterStartElement\n");
        goto LAST;
    }

    /* Add an attribute with name "version" and value "1.0" to ORDER. */
    ret = xmlTextWriterWriteAttribute(writer, BAD_CAST "version",
                                     BAD_CAST "1.0");
    if (ret < 0) {
        fprintf(stderr, "Error at xmlTextWriterWriteAttribute\n");
        goto LAST;
    }

    /* Add an attribute with name "xml:lang" and value "de" to ORDER. */
    ret = xmlTextWriterWriteAttribute(writer, BAD_CAST "xml:lang",
                                     BAD_CAST "de");
    if (ret < 0) {
        fprintf(stderr, "Error at xmlTextWriterWriteAttribute\n");
        goto LAST;
    }

    /* Write a comment as child of ORDER */
    ret = xmlTextWriterWriteFormatComment(writer,
                                          "This is formatted comment",
                                          "%d", 1);
    if (ret < 0) {
        fprintf(stderr, "Error at xmlTextWriterWriteFormatComment\n");
        goto LAST;
    }

    /* Start an element named "ENTRY" */
    ret = xmlTextWriterStartElement(writer, BAD_CAST "ENTRY");
    if (ret < 0) {
        fprintf(stderr, "Error at xmlTextWriterStartElement\n");
        goto LAST;
    }

    /* Write an element named "ARTICLE" as child of ENTRY. */
    ret = xmlTextWriterWriteElement(writer, BAD_CAST "ARTICLE",
                                   BAD_CAST "<Test>");
    if (ret < 0) {
        fprintf(stderr, "Error at xmlTextWriterWriteElement\n");
        goto LAST;
    }

    /* Write an element named "ENTRY_NO" as child of ENTRY. */
    ret = xmlTextWriterWriteFormatElement(writer,
                                          BAD_CAST "ENTRY_NO",
                                          "Formatted element %04d",
                                         10);
    if (ret < 0) {
        fprintf(stderr, "Error at xmlTextWriterWriteFormatElement\n");
        goto LAST;
    }

    /* Close the element named ENTRY. */
    ret = xmlTextWriterEndElement(writer);
    if (ret < 0) {
        fprintf(stderr, "Error at xmlTextWriterEndElement\n");
        goto LAST;
    }

    /* Start an element named "FOOT" */
    ret = xmlTextWriterStartElement(writer, BAD_CAST "FOOT");
    if (ret < 0) {
        fprintf(stderr, "Error at xmlTextWriterStartElement\n");
        goto LAST;
    }

    /* Write Text */
    ret = xmlTextWriterWriteFormatString(writer, "Formatted string %d", 1);
    if (ret < 0) {
        fprintf(stderr, "Error at xmlTextWriterWriteFormatString\n");
        goto LAST;
    }

    /* Close the element named FOOT. */
    ret = xmlTextWriterEndElement(writer);
    if (ret < 0) {
        fprintf(stderr, "Error at xmlTextWriterEndElement\n");
        goto LAST;
    }

    /* Here we could close the elements ORDER and EXAMPLE using the
     * function xmlTextWriterEndElement */
    ret = xmlTextWriterEndDocument(writer);
    if (ret < 0) {
        fprintf(stderr, "Error at xmlTextWriterEndDocument\n");
        goto LAST;
    }

 LAST:
    xmlFreeTextWriter(writer);
}

int main(int argc, char **argv)
{
    char *output_file;
    int   ret;

    if (argc != 2) {
        return 1;
    }

    output_file = argv[1];

    LIBXML_TEST_VERSION;

    testXmlwriterFilename(output_file);

    xmlCleanupParser();

    return;
}

出力ファイル。素の状態だと見づらいので、xmllint --formatの出力。

<?xml version="1.0" encoding="UTF-8"?>
<EXAMPLE>
  <!--This is a comment-->
  <ORDER version="1.0" xml:lang="de">
    <!--This is formatted comment-->
    <ENTRY>
      <ARTICLE>&lt;Test&gt;</ARTICLE>
      <ENTRY_NO>Formatted element 0010</ENTRY_NO>
    </ENTRY>
    <FOOT>Formatted string 1</FOOT>
  </ORDER>
</EXAMPLE>