The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
 * Copyright (C) 1992-2004 Dominic Mitchell. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* @(#) $Id: Genx.xs 985 2005-10-16 08:42:22Z dom $ */

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "genx.h"

/* 
 * We use a typemap to change the underscore into a double colon.
 * This makes it easier to get things of the right class used.
 */

typedef genxWriter    XML_Genx;
typedef genxNamespace XML_Genx_Namespace;
typedef genxElement   XML_Genx_Element;
typedef genxAttribute XML_Genx_Attribute;

/*
 * Initialize the hash inside the writer, reusing the existing one if
 * possible.  This should be called by each StartDocFoo().
 */

static HV *
initSelfUserData( genxWriter w )
{
    HV *self = (HV *)genxGetUserData( w );
    if ( self != NULL ) {
        hv_clear( self );
    } else {
        self = newHV();
        genxSetUserData( w, self );
    }
    return self;
}

/*
 * DEBUG -- uncomment to use.  This is just a convenience function for
 * seeing what's inside an object.
 */

#ifdef notdef
static void
dump_self( genxWriter w, const char *msg )
{
    dSP;
    HV *self = (HV *)genxGetUserData( w );
    CV *dump;
    ENTER;
    SAVETMPS;

    /* Set up the stack. */
    PUSHMARK(SP);
    /* Don't bother creating a reference here. */
    XPUSHs((SV *)self);
    PUTBACK;

    SPAGAIN;                    /* XXX Necessary? */

    if ( msg )
        warn( msg );

    if ( self != NULL ) {
        (void)eval_pv("use Devel::Peek;", TRUE);
        if ((dump = get_cv("Devel::Peek::Dump", FALSE)) != NULL ) {
            call_sv( (SV *)dump, G_VOID );
        } else {
            warn("Devel::Peek not loaded!");
        }
    } else {
        warn("No hash in self to dump!");
    }

    FREETMPS;
    LEAVE;
}
#endif

static genxStatus
sender_write( void *userData, constUtf8 s )
{
    dSP;
    HV *self = (HV *)userData;
    SV **svp;
    SV *str = newSVpv( (const char *)s, 0 );
    ENTER;
    SAVETMPS;

    /* genx guarantees that thus will be UTF-8, so tell Perl that. */
    SvUTF8_on(str);

    /* Set up the stack. */
    PUSHMARK(SP);
    XPUSHs(sv_2mortal(str));
    XPUSHs(sv_2mortal(newSVpv("write", 5)));
    PUTBACK;

    /* Do the business. */
    if ((svp = hv_fetch( self, "callback", 8, 0 )))
        (void)call_sv( *svp, G_VOID );

    SPAGAIN;                    /* XXX Necessary? */

    FREETMPS;
    LEAVE;
    return GENX_SUCCESS;
}

static genxStatus
sender_write_bounded( void *userData, constUtf8 start, constUtf8 end )
{
    dSP;
    HV *self = (HV *)userData;
    SV **svp;
    SV *str = newSVpv((const char *)start, end - start);
    ENTER;
    SAVETMPS;

    /* genx guarantees that thus will be UTF-8, so tell Perl that. */
    SvUTF8_on(str);

    /* Set up the stack. */
    PUSHMARK(SP);
    XPUSHs(sv_2mortal(str));
    XPUSHs(sv_2mortal(newSVpv("write_bounded", 13)));
    PUTBACK;

    /* Do the business. */
    if ((svp = hv_fetch( self, "callback", 8, 0 )))
        (void)call_sv( *svp, G_VOID );

    SPAGAIN;                    /* XXX Necessary? */

    FREETMPS;
    LEAVE;
    return GENX_SUCCESS;
}

static genxStatus
sender_flush( void *userData )
{
    dSP;
    HV *self = (HV *)userData;
    SV **svp;
    ENTER;
    SAVETMPS;

    /* Set up the stack. */
    PUSHMARK(SP);
    XPUSHs(sv_2mortal(newSVpv("", 0)));
    XPUSHs(sv_2mortal(newSVpv("flush", 5)));
    PUTBACK;

    /* Do the business. */
    if ((svp = hv_fetch( self, "callback", 8, 0 )))
        (void)call_sv( *svp, G_VOID );

    SPAGAIN;                    /* XXX Necessary? */

    FREETMPS;
    LEAVE;
    return GENX_SUCCESS;
}

static genxSender sender = {
    sender_write,
    sender_write_bounded,
    sender_flush
};

/*
 * Some helper functions for automatically appending genx output into a
 * string.  The string is stored inside a hash, which genx's userData
 * field holds for us.
 */

static genxStatus
string_sender_write( void *userData, constUtf8 s )
{
    HV *self = (HV *)userData;
    SV **svp;
    ENTER;
    SAVETMPS;
    if ((svp = hv_fetch( self, "string", 6, 0 )))
        sv_catpv( *svp, s );
    FREETMPS;
    LEAVE;
    return GENX_SUCCESS;
}

static genxStatus
string_sender_write_bounded( void *userData, constUtf8 start, constUtf8 end )
{
    HV *self = (HV *)userData;
    SV **svp;
    ENTER;
    SAVETMPS;
    if ((svp = hv_fetch( self, "string", 6, 0 )))
        sv_catpvn( *svp, start, end - start );
    FREETMPS;
    LEAVE;
    return GENX_SUCCESS;
}

static genxStatus
string_sender_flush( void *userData ) {
    return GENX_SUCCESS;
}

static genxSender string_sender = {
    string_sender_write,
    string_sender_write_bounded,
    string_sender_flush
};

/*
 * Small utility function to throw the correct exception.
 */
static void
croak_on_genx_error( genxWriter w, genxStatus st )
{
    char *msg;

    if ( st == GENX_SUCCESS ) {
        msg = NULL;
    } else if ( w ) {
        msg = genxLastErrorMessage( w );
    } else {
        /* 
         * If we don't have a writer object handy, make one for this
         * purpose.  This is slow, but unavoidable.
         */
        w = genxNew( NULL, NULL, NULL );
        msg = genxGetErrorMessage( w, st );
        genxDispose( w );
        w = NULL;
    }

    /*
     * We rely on the writer object to store the associated status code
     * for us.
     */
    if ( msg )
        croak( msg );
}

/*
 * Extract the namespace URI from an SV.  If it's a string, just use
 * that string.  If it's a namespace object, extract the uri from there.
 * If it's undef, return NULL to indicate no namespace.
 */
static constUtf8
sv_to_namespace_uri( SV* thing )
{
    /* not defined? */
    if (!SvTRUE(thing))
        return NULL;

    if (sv_isobject(thing) && sv_derived_from(thing, "XML::Genx::Namespace")) {
        /* This is similiar to the typemap T_PTROBJ_SPECIAL. */
        IV tmp = SvIV((SV*)SvRV(thing));
        genxNamespace ns = INT2PTR(genxNamespace, tmp);
        /* I added this "back door" call to genx. */
        constUtf8 uri = (constUtf8)genxGetNamespaceUri(ns);
        return uri;
    } else {
        return (constUtf8)SvPV_nolen(thing);
    }
}

MODULE = XML::Genx	PACKAGE = XML::Genx	PREFIX=genx

PROTOTYPES: DISABLE

# We work around the typemap and do things ourselves since it's
# otherwise hard to get the class name correct.  Doing things this way
# ensures that we are subclassable.  Example taken from Digest::MD5.
void
new( klass )
    char* klass
  INIT:
    XML_Genx w;
  PPCODE:
    w = genxNew( NULL, NULL, NULL );
    ST( 0 ) = sv_newmortal();
    sv_setref_pv( ST(0), klass, (void*)w );
    SvREADONLY_on(SvRV(ST(0)));
    XSRETURN( 1 );

void
DESTROY( w )
    XML_Genx w
  PREINIT:
    HV *self;
  CODE:
    self = (HV *)genxGetUserData( w );
    /* 
     * Ensure that Perl can clean up this hash now that nothing's
     * referencing it.
     */
    if ( self != NULL )
        SvREFCNT_dec( self );
    genxDispose( w );

genxStatus
genxStartDocFile( w, fh )
    XML_Genx w
    FILE *fh
  PREINIT:
    struct stat st;
    HV *self;
    SV *fhsv;
  INIT:
    self = initSelfUserData( w );
    /* 
     * Sometimes we get back a filehandle with an invalid file
     * descriptor instead of NULL.  So use fstat() to check that it's
     * actually live and usable.
     *
     * Many thanks to http://www.testdrive.hp.com/ for providing a
     * service that let me find this out when I couldn't reproduce it
     * on my own box.
     */
    if ( fh == NULL || fstat(fileno(fh), &st) == -1 )
      croak( "Bad filehandle" );
    /* Store a the filehandle in ourselves. */
    fhsv = SvROK( ST(1) ) ? SvRV( ST(1) ) : ST(1);
    if (!hv_store( self, "fh", 2, SvREFCNT_inc(fhsv), 0))
        SvREFCNT_dec( fhsv );
  POSTCALL:
    croak_on_genx_error( w, RETVAL );

genxStatus
genxStartDocSender( w, callback )
    XML_Genx w
    SV *callback
  PREINIT:
    HV *self;
  CODE:
    self = initSelfUserData( w );
    if (!hv_store( self, "callback", 8, SvREFCNT_inc(callback), 0 ))
        SvREFCNT_dec( callback );
    RETVAL = genxStartDocSender( w, &sender );
  POSTCALL:
    croak_on_genx_error( w, RETVAL );
  OUTPUT:
    RETVAL

genxStatus
genxEndDocument( w )
    XML_Genx w
  PREINIT:
    HV *self;
  POSTCALL:
    self = (HV *)genxGetUserData( w );
    /* Decrement the reference count on the filehandle. */
    hv_delete( self, "fh", 2, G_DISCARD );
    croak_on_genx_error( w, RETVAL );

# Take a variable length list so that we can make the namespace
# parameter optional.  Even when present, it will only be used if it's
# a true value.
genxStatus
genxStartElementLiteral( w, ... )
    XML_Genx w
  PREINIT:
    constUtf8  xmlns;
    constUtf8  name;
  INIT:
    if ( items == 2 ) {
        xmlns = NULL;
        name  = (constUtf8)SvPV_nolen(ST(1));
    } else if ( items == 3 ) {
        xmlns = sv_to_namespace_uri(ST(1));
        name  = (constUtf8)SvPV_nolen(ST(2));
    } else {
        croak( "Usage: w->StartElementLiteral([xmlns],name)" );
    }
  CODE:
    RETVAL = genxStartElementLiteral( w, xmlns, name );
  POSTCALL:
    croak_on_genx_error( w, RETVAL );
  OUTPUT:
    RETVAL

# Same design as StartElementLiteral().
genxStatus
genxAddAttributeLiteral( w, ... )
    XML_Genx w
  PREINIT:
    constUtf8  xmlns;
    constUtf8  name;
    constUtf8  value;
  INIT:
    if ( items == 3 ) {
        xmlns = NULL;
        name  = (constUtf8)SvPV_nolen(ST(1));
        value = (constUtf8)SvPV_nolen(ST(2));
    } else if ( items == 4 ) {
        xmlns = sv_to_namespace_uri(ST(1));
        name  = (constUtf8)SvPV_nolen(ST(2));
        value = (constUtf8)SvPV_nolen(ST(3));
    } else {
        croak( "Usage: w->AddAttributeLiteral([xmlns],name,value)" );
    }
  CODE:
    RETVAL = genxAddAttributeLiteral( w, xmlns, name, value );
  POSTCALL:
    croak_on_genx_error( w, RETVAL );
  OUTPUT:
    RETVAL

genxStatus
genxEndElement( w )
    XML_Genx w
  POSTCALL:
    croak_on_genx_error( w, RETVAL );

char *
genxLastErrorMessage( w )
    XML_Genx w

# This is an extension of the genx API.
int
genxLastErrorCode( w )
    XML_Genx w
  CODE:
    RETVAL = genxGetStatusCode( w );
  OUTPUT:
    RETVAL

char *
genxGetErrorMessage( w, st )
    XML_Genx w
    genxStatus st

genxStatus
genxAddText( w, start )
    XML_Genx w
    constUtf8 start
  POSTCALL:
    croak_on_genx_error( w, RETVAL );

genxStatus
genxAddCharacter( w, c )
    XML_Genx w
    int c
  POSTCALL:
    croak_on_genx_error( w, RETVAL );

genxStatus
genxComment( w, text )
    XML_Genx w
    constUtf8 text
  POSTCALL:
    croak_on_genx_error( w, RETVAL );

genxStatus
genxPI( w, target, text );
    XML_Genx w
    constUtf8 target
    constUtf8 text
  POSTCALL:
    croak_on_genx_error( w, RETVAL );

genxStatus
genxUnsetDefaultNamespace( w )
    XML_Genx w
  POSTCALL:
    croak_on_genx_error( w, RETVAL );

char *
genxGetVersion( class )
    char * class
  CODE:
    /* avoid unused variable warning. */
    (void)class;
    RETVAL = genxGetVersion();
  OUTPUT:
    RETVAL

# We need to map an undef prefix to NULL.  But we want to pass an
# empty prefix straight through as that means "default".
void
genxDeclareNamespace( w, uri, ... )
    XML_Genx w
    constUtf8  uri
  PREINIT:
    constUtf8     prefix;
    XML_Genx_Namespace ns;
    genxStatus    st = GENX_SUCCESS;
  INIT:
    if ( items == 2 )
        prefix = NULL;
    else if ( items == 3 )
        prefix = SvOK(ST(2)) ? (constUtf8)SvPV_nolen(ST(2)) : NULL;
    else
        croak( "usage: w->DeclareNamespace(uri,[defaultPrefix])" );
  PPCODE:
    ns = genxDeclareNamespace( w, uri, prefix, &st );
    croak_on_genx_error( w, st );
    ST( 0 ) = sv_newmortal();
    sv_setref_pv( ST(0), "XML::Genx::Namespace", (void*)ns );
    SvREADONLY_on(SvRV(ST(0)));
    XSRETURN( 1 );

void
genxDeclareElement( w, ... )
    XML_Genx    w
  PREINIT:
    genxStatus         st = GENX_SUCCESS;
    XML_Genx_Element   el;
    XML_Genx_Namespace ns;
    constUtf8          type;
  PPCODE:
    if ( items == 2 ) {
        ns = (XML_Genx_Namespace) NULL;
        type = (constUtf8)SvPV_nolen(ST(1));
    } else if ( items == 3 ) {
        /*  Bleargh, would be nice to be able to reuse typemap here */
        if (!SvOK(ST(1))) {
            ns = (XML_Genx_Namespace) NULL;
        } else if (sv_derived_from(ST(1), "XML::Genx::Namespace")) {
            IV tmp = SvIV((SV*)SvRV(ST(1)));
            ns = INT2PTR(XML_Genx_Namespace, tmp);
        } else {
            croak("ns is not undef or of type XML::Genx::Namespace");
        }
        type = (constUtf8)SvPV_nolen(ST(2));
    } else {
        croak( "Usage: w->DeclareElement([ns],type)" );
    }
    el = genxDeclareElement( w, ns, type, &st );
    croak_on_genx_error( w, st );
    ST( 0 ) = sv_newmortal();
    sv_setref_pv( ST(0), "XML::Genx::Element", (void*)el );
    SvREADONLY_on(SvRV(ST(0)));
    XSRETURN( 1 );

void
genxDeclareAttribute( w, ... )
    XML_Genx    w
  PREINIT:
    genxStatus         st = GENX_SUCCESS;
    XML_Genx_Attribute at;
    XML_Genx_Namespace ns;
    constUtf8          name;
  PPCODE:
    if ( items == 2 ) {
        ns = (XML_Genx_Namespace) NULL;
        name = (constUtf8)SvPV_nolen(ST(1));
    } else if ( items == 3 ) {
        /*  Bleargh, would be nice to be able to reuse typemap here */
        if (!SvOK(ST(1))) {
            ns = (XML_Genx_Namespace) NULL;
        } else if (sv_derived_from(ST(1), "XML::Genx::Namespace")) {
            IV tmp = SvIV((SV*)SvRV(ST(1)));
            ns = INT2PTR(XML_Genx_Namespace, tmp);
        } else {
            croak("ns is not undef or of type XML::Genx::Namespace");
        }
        name = (constUtf8)SvPV_nolen(ST(2));
    } else {
        croak( "Usage: w->DeclareAttribute([ns],name)" );
    }
    at = genxDeclareAttribute( w, ns, name, &st );
    if ( at && st == GENX_SUCCESS ) {
        ST( 0 ) = sv_newmortal();
        sv_setref_pv( ST(0), "XML::Genx::Attribute", (void*)at );
        SvREADONLY_on(SvRV(ST(0)));
        XSRETURN( 1 );
    } else {
        XSRETURN_UNDEF;
    }

SV *
genxScrubText( w, in )
    XML_Genx w
    SV *in
  CODE:
    RETVAL = newSVsv( in );
    (void)genxScrubText( w, SvPV_nolen( in ), SvPV_nolen( RETVAL ) );
    /* Fix up the new length. */
    SvCUR_set( RETVAL, strlen( SvPV_nolen( RETVAL ) ) );
  OUTPUT:
    RETVAL

MODULE = XML::Genx	PACKAGE = XML::Genx::Namespace	PREFIX=genx

utf8
genxGetNamespacePrefix( ns )
    XML_Genx_Namespace ns

genxStatus
genxAddNamespace(ns, ...);
    XML_Genx_Namespace ns
  PREINIT:
    utf8 prefix;
  CODE:
    if ( items == 1 )
        prefix = NULL;
    else if ( items == 2 )
        prefix = SvOK(ST(1)) ? (utf8)SvPV_nolen(ST(1)) : NULL;
    else
        croak( "Usage: ns->AddNamespace([prefix])" );
    RETVAL = genxAddNamespace( ns, prefix );
  POSTCALL:
    croak_on_genx_error( genxGetNamespaceWriter( ns ), RETVAL );
  OUTPUT:
    RETVAL

MODULE = XML::Genx	PACKAGE = XML::Genx::Element	PREFIX=genx

genxStatus
genxStartElement( e )
    XML_Genx_Element e
  POSTCALL:
    croak_on_genx_error( genxGetElementWriter( e ), RETVAL );

MODULE = XML::Genx	PACKAGE = XML::Genx::Attribute	PREFIX=genx

genxStatus
genxAddAttribute( a, value )
    XML_Genx_Attribute a
    constUtf8 value
  POSTCALL:
      croak_on_genx_error( genxGetAttributeWriter( a ), RETVAL );

MODULE = XML::Genx	PACKAGE = XML::Genx::Constants

# It would really be very good indeed to get these automatically generated...
genxStatus
GENX_SUCCESS()
  PROTOTYPE:
  CODE:
    RETVAL = GENX_SUCCESS;
  OUTPUT:
    RETVAL

genxStatus
GENX_BAD_UTF8()
  PROTOTYPE:
  CODE:
    RETVAL = GENX_BAD_UTF8;
  OUTPUT:
    RETVAL

genxStatus
GENX_NON_XML_CHARACTER()
  PROTOTYPE:
  CODE:
    RETVAL = GENX_NON_XML_CHARACTER;
  OUTPUT:
    RETVAL

genxStatus
GENX_BAD_NAME()
  PROTOTYPE:
  CODE:
    RETVAL = GENX_BAD_NAME;
  OUTPUT:
    RETVAL

genxStatus
GENX_ALLOC_FAILED()
  PROTOTYPE:
  CODE:
    RETVAL = GENX_ALLOC_FAILED;
  OUTPUT:
    RETVAL

genxStatus
GENX_BAD_NAMESPACE_NAME()
  PROTOTYPE:
  CODE:
    RETVAL = GENX_BAD_NAMESPACE_NAME;
  OUTPUT:
    RETVAL

genxStatus
GENX_INTERNAL_ERROR()
  PROTOTYPE:
  CODE:
    RETVAL = GENX_INTERNAL_ERROR;
  OUTPUT:
    RETVAL

genxStatus
GENX_DUPLICATE_PREFIX()
  PROTOTYPE:
  CODE:
    RETVAL = GENX_DUPLICATE_PREFIX;
  OUTPUT:
    RETVAL

genxStatus
GENX_SEQUENCE_ERROR()
  PROTOTYPE:
  CODE:
    RETVAL = GENX_SEQUENCE_ERROR;
  OUTPUT:
    RETVAL

genxStatus
GENX_NO_START_TAG()
  PROTOTYPE:
  CODE:
    RETVAL = GENX_NO_START_TAG;
  OUTPUT:
    RETVAL

genxStatus
GENX_IO_ERROR()
  PROTOTYPE:
  CODE:
    RETVAL = GENX_IO_ERROR;
  OUTPUT:
    RETVAL

genxStatus
GENX_MISSING_VALUE()
  PROTOTYPE:
  CODE:
    RETVAL = GENX_MISSING_VALUE;
  OUTPUT:
    RETVAL

genxStatus
GENX_MALFORMED_COMMENT()
  PROTOTYPE:
  CODE:
    RETVAL = GENX_MALFORMED_COMMENT;
  OUTPUT:
    RETVAL

genxStatus
GENX_XML_PI_TARGET()
  PROTOTYPE:
  CODE:
    RETVAL = GENX_XML_PI_TARGET;
  OUTPUT:
    RETVAL

genxStatus
GENX_MALFORMED_PI()
  PROTOTYPE:
  CODE:
    RETVAL = GENX_MALFORMED_PI;
  OUTPUT:
    RETVAL

genxStatus
GENX_DUPLICATE_ATTRIBUTE()
  PROTOTYPE:
  CODE:
    RETVAL = GENX_DUPLICATE_ATTRIBUTE;
  OUTPUT:
    RETVAL

genxStatus
GENX_ATTRIBUTE_IN_DEFAULT_NAMESPACE()
  PROTOTYPE:
  CODE:
    RETVAL = GENX_ATTRIBUTE_IN_DEFAULT_NAMESPACE;
  OUTPUT:
    RETVAL

genxStatus
GENX_DUPLICATE_NAMESPACE()
  PROTOTYPE:
  CODE:
    RETVAL = GENX_DUPLICATE_NAMESPACE;
  OUTPUT:
    RETVAL

genxStatus
GENX_BAD_DEFAULT_DECLARATION()
  PROTOTYPE:
  CODE:
    RETVAL = GENX_BAD_DEFAULT_DECLARATION;
  OUTPUT:
    RETVAL

MODULE = XML::Genx	PACKAGE = XML::Genx::Simple	PREFIX=genx

# Our own add on.  This provides a way of getting the output of genx
# into a string without the overhead of popping back into Perl the whole
# time.
genxStatus
genxStartDocString( w )
    XML_Genx w
  PREINIT:
    HV *self;
  CODE:
    self = initSelfUserData( w );
    /* No need to inc ref count as we're creating the SV here. */
    (void)hv_store( self, "string", 6, newSVpv("", 0), 0 );
    RETVAL = genxStartDocSender( w, &string_sender );
  OUTPUT:
    RETVAL

SV *
genxGetDocString( w )
    XML_Genx w
  PREINIT:
    HV *self;
    SV **svp;
  CODE:
    self = (HV *)genxGetUserData( w );
    /* 
     * Fetch the string out of ourselves.  Ensure that it gets sent back
     * as UTF-8, which genx guarantees for us.
     */
    if ((svp = hv_fetch(self, "string", 6, 0))) {
        SvUTF8_on( *svp );
        SvREFCNT_inc( *svp );
        RETVAL = *svp;
    } else {
        RETVAL = &PL_sv_undef;
    }
  OUTPUT:
    RETVAL