What is a content object type?

As the name suggests, content object types are different types of content objects. After installing XIMS, there are different groups of content object types available, ranging from container object types like Folder and DepartmentRoot to XML object types like Document and sDocBookXML, to binary object types like File and Image. All content object types share the same data storage property structure. In case of the DBI DataProvider that properties are available through the ci_documents and ci_content tables. Content objects are stored in two tables to separate language-dependant from language-independant data and to enable content versioning. Using that data model, it is possible to create content in several languages sharing the same place in the hierarchy and sharing the other language independant properties.

[ top ]

Creating content object types using cot_creator.pl

Work on three layers is needed to create a new content object type. First, the content object type has to be defined at the data storage layer. Using the DBI DataProvider, this means creating an entry in the ci_object_type table. Second, the content object type has to be defined at the application logic layer. An object-, application-, and exporter-class is needed for that. Third, at the presentation logic layer, XSL-Stylesheets are needed to provide the user interface for the content object type specific management needs.

cot_creator.pl is a tool that will save you some of that work. To get a first idea of what it is about, let us take a look at the synopsis:

   Usage: ./cot_creator.pl [-h|-n object_type_name [-i isa] [-f data_format_name]
            [-c -m mime_type -s suffix] [-o outputdir] [-u dbusername]
            [-p dbpassword] [-d debuglevel] ]

        -h Prints this screen
        -n The name of the object type you want to create
        -i The super class of the object-, application-, and exporter-
           class; defaults to XIMS::Object, XIMS::CGI, and
           XIMS::Exporter::XML respectively
        -f The name of the data format (list of df)
            If you want to create a new data format, you have to set the
            following three arguments:
            -c flag to actually create the data format
            -m mime-type
            -s suffix
        -u If set, overrides XIMS::Config::DBUser. You may need this if
           the database user specified in XIMS::Config::DBUser has
           insufficient privileges to create object types or data
           formats. For Pg, for example the user default user 'xims' has
           the privileges, whereas 'ximsrun' does not.
        -p If set, overrides XIMS::Config::DBPassword
        -o Output directory of template modules and stylesheets,
           defaults to '.'
        -d If set, overrides XIMS::Config::DebugLevel.

cot_creator.pl does the following: First, it adds the object type to the database, after that it creates basic object-, application-, and exporter-classes, as well as XSL-Stylesheets for the events, create, default, edit and publish. The following examples should give an idea of how that looks like in practice.

[ top ]

Example content object types

Text

Tired to write well-balanced Documents just to save little notes? Or you do have legacy plain text files which you just want to deposit in the XIMS data storage and use features like the ACL system to manage them? An object type Text to manage such plain text files will help with that. Let us start out with cot_creator.pl to create it:

./cot_creator.pl -n Text -f Text -u xims -o /tmp -d 1

This will generate the object type Text in the database (you may have to adjust the database username -u) and output the following files:

  • /tmp/ot_creator_out/bin/text.pm

  • /tmp/ot_creator_out/lib/XIMS/Exporter/Text.pm

  • /tmp/ot_creator_out/lib/XIMS/Importer/FileSystem/Text.pm

  • /tmp/ot_creator_out/lib/XIMS/Text.pm

  • /tmp/ot_creator_out/www/ximsroot/skins/skinname/stylesheets/language/text_create.xsl

  • /tmp/ot_creator_out/www/ximsroot/skins/skinname/stylesheets/language/text_default.xsl

  • /tmp/ot_creator_out/www/ximsroot/skins/skinname/stylesheets/language/text_edit.xsl

  • /tmp/ot_creator_out/www/ximsroot/stylesheets/exporter/export_text.xsl

After copying the files to their respective path in your XIMS installation - usually somewhere below /usr/local/xims - we'll focus at the newly created application-class /usr/local/xims/bin/text.pm first.

Before overriding or adding event handlers it is a good idea to get acquainted with the event handlers of the super class to know where different or additional logic is needed. After reviewing the basic event handlers of XIMS::CGI, it can be seen that we need to override event_store() to handle the body-field. Additionally, as plaint text documents are not well-formed - at least most of the time they aren't - we have to XML-escape the body before it gets stored. The following code snippet shows how that looks like:

sub event_store {
    XIMS::Debug( 5, "called" );
    my ( $self, $ctxt ) = @_;

    return 0 unless $self->init_store_object( $ctxt ) # handles common params
                    and defined $ctxt->object();

    my $body $self->param( 'body' ); # get the body
    if ( length $body ) {
        my $object = $ctxt->object();
        $object->body( XIMS::xml_escape( $body ) ); # xml-escape the body, plaintext files are not well-formed
    }

    return $self->SUPER::event_store( $ctxt ); # does the actual $object->update or $object->store
}

Text objects can now be created and edited. However, at event_default() they are shown in one big ugly unformatted chunk of text. To change that, we have several implementation options. It's still Perl, so TIMTOWTDI. We could override event_default() and replace all newlines with '<br />'s, all spaces with '&#160;'s using regular expression substitutions. We could update text_default.xsl to wrap the body inside a <pre>-tag. For a little XSLT practice, we also can update text_default.xsl to convert newlines and spaces to their HTML representation. Adding the following two XSL templates to text_default.xsl implements that conversion:

<xsl:template match="body">
    <xsl:call-template name="brspace-replace">
        <xsl:with-param name="word" select="."/>
    </xsl:call-template>
</xsl:template>

<xsl:template name="brspace-replace">
    <xsl:param name="word"/>
    <xsl:param name="foundbr" select="0"/>
    <xsl:param name="foundspace" select="0"/>
    <xsl:variable name="cr"><xsl:text>
</xsl:text></xsl:variable>
    <xsl:variable name="space"><xsl:text> </xsl:text></xsl:variable>
    <xsl:choose>
        <xsl:when test="contains($word,$cr) and ($foundbr = 0 or $foundspace = 1)">
            <xsl:if test="$foundspace = 0">
                <xsl:value-of select="substring-before($word,$cr)"/>
            </xsl:if>
            <br/>
            <xsl:call-template name="brspace-replace">
                <xsl:with-param name="word" select="substring-after($word,$cr)"/>
                <xsl:with-param name="foundbr" select="1"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:when test="contains($word,$space) and ($foundspace = 0 or $foundbr = 1)">
            <xsl:value-of select="translate(substring-before($word,$cr),$space,'&#160;')"/>
            <xsl:call-template name="brspace-replace">
                <xsl:with-param name="word" select="substring-after($word,$space)"/>
                <xsl:with-param name="foundspace" select="1"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$word"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

Now we got all default events running besides one. We can create, display, edit, delete, or manage the ACL of Text objects, what we still can not do, is to publish or unpublish them. To achieve that, Exporter::Text needs updating. cot_creator.pl sets the value of the @ISA variable to XIMS::Exporter::XML per default. As Text objects are not XML objects, we have to alter that to a super class that simply outputs the body of the Text object during export. Setting @ISA to XIMS::Exporter::Binary does the job here. Still, exporting a Text objects does not yield the result we need because the published objects get output as is, and that means XML-escaped like they are stored in the database. To change that, we have to override the create() method taking the logic of XIMS::Exporter::Binary::create() and adding a call to XIMS::xml_unescape():

sub create {
    XIMS::Debug( 5, "called" );

    my ( $self, %param ) = @_;
    my $document_path =  $self->{Exportfile} || $self->{Basedir} . '/' . $self->{Object}->location;

    XIMS::Debug( 4, "trying to write the object to $document_path" );

    # create the item on disk
    my $document_fh = IO::File->new( $document_path, 'w' );
    if ( defined $document_fh ) {
        print $document_fh XIMS::xml_unescape( $self->{Object}->body() ); # xml-unescape the body content
        $document_fh->close;
        XIMS::Debug( 4, "document written" );
    }
    else {
        XIMS::Debug( 2, "Error writing file '$document_path': $!" );
        return undef;
    }

    XIMS::Debug( 4, "toggling publish state of the object" );
    $self->toggle_publish_state( '1' );

    return 1;
}

Similar to the generated the Exporter class we have to change the value of the @ISA variable to XIMS::Importer::FileSystem::Binary for the generated Importer class. Also, while we need the Text object's content to be XML-unescaped during exporting, we want it to be XML-escaped during importing. To achieve that, we have to adapt the generated XIMS::Importer::FileSystem::Text class accordingly by overriding handle_data():

use XIMS;

sub handle_data {
    XIMS::Debug( 5, "called" );
    my $self = shift;
    my $location = shift;

    my $object = $self->SUPER::handle_data( $location );

    my $data = $self->get_binref( $location );
    $object->body( XIMS::xml_escape( $$data ) );

    return $object;
}

Object type Text is now fully functional but yet open for extensions. For example, one can implement an additional plain text file upload facility with a few lines of code. See text::event_store(), text_edit.xsl, and text_create.xsl in the XIMS distribution for an example of that functionality.

CSS

With the second example for creating new content object types we want to show how to add object type specific events. Next to the basic events provided by the super class XIMS::Text we want to validate the CSS objects using the CPAN CSS::Tiny module. Before implementing that, let us first create the object type:

./cot_creator.pl -n CSS -i XIMS::Text -f CSS -u xims -o /tmp -d 1

Again, we have to copy the generated files to their respective path of the XIMS installation. After that, we already have a working new content object type that just misses the basic formatting of the CSS objects during event default. Copying the templates or the whole file of text_default.xsl to css_default.xsl helps with that.

We need the CPAN CSS::Tiny module to implement CSS validation. If you do not have it installed you may do it by issuing the command

perl -MCPAN -e 'install CSS::Tiny'

With an installed and working CSS::Tiny module we want to add an event_parse_css() to our css.pm application class, using the read_string() method of CSS::Tiny module to parse the body.

To actually get that method running, we must not forget to load CSS::Tiny and adjust registerEvents() not to use $_[0]->SUPER::registerEvents() but to just return the list of events including parse_css. Unfortunately, there is no such thing like a SUPER::SUPER->method in Perl.

After updating the final bits, the whole css.pm looks like this:

package css;

use strict;
use vars qw( $VERSION @ISA );
use text;

use CSS::Tiny;

# version string (for makemaker, so don't touch!)
$VERSION = do { my @r = (q$Revision: 1.2 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r };

@ISA = qw( text );

# (de)register events here
sub registerEvents {
    return qw(
          default
          create
          edit
          store
          delete
          delete_prompt
          obj_acllist
          obj_aclgrant
          obj_aclrevoke
          publish
          publish_prompt
          unpublish
          cancel
          parse_css
          );
}

sub event_parse_css {
    XIMS::Debug( 5, "called" );
    my ( $self, $ctxt ) = @_;

    return 0 if $self->SUPER::event_default( $ctxt );

    my $body = $ctxt->object->body();             # grep the body
    my $css = CSS::Tiny->read_string( $body );    # parse it
    if ( $CSS::Tiny::errstr ) {
        $ctxt->session->error_msg( "Parse failure" );      # set error message
        $ctxt->session->verbose_msg( $CSS::Tiny::errstr );
    }
    else {
        $ctxt->session->message( "Parse ok. Parsed CSS:" );
        $ctxt->session->verbose_msg( $css->write_string() );
    }

    $ctxt->properties->application->styleprefix( "common" );           # this will tell CGI::XMLApplication to use the
    $ctxt->properties->application->style( "message_window_plain" );   # file 'common_message_window_plain.xsl' for XSL transformation

    return 0;
}

1;

Now, we a got a working new event. As a final step, we want to make it available through the user interface. Inserting the following fragment before the footer table in css_default.xsl provides the necessary link to activate it in a new window:

<table align="center" width="98.7%" class="footer">
    <tr>
        <td>
            <a href="{$xims_box}{$goxims_content}{$absolute_path}?parse_css=1" target="_new">Validate CSS</a>
        </td>
    </tr>
</table>

[ top ]