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
' '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,' ')"/>
<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 ]
