<?php
// ----------------------------------------------------------------------------------
// Class: RdfSerializer
// ----------------------------------------------------------------------------------
/**
* An RDF seralizer.
* Seralizes models to RDF syntax. It supports the xml:base and xml:lang directive.
* You can choose between different output syntaxes by using the configuration methods
* or changing the configuration default values in constants.php.
* This class is based on the java class edu.unika.aifb.rdf.api.syntax.RDFSerializer
* by Boris Motik.
*
* History:
* 10-03-2002 : Bug in collectNamespaces() fixed.
* 09-15-2002 : First version of this class.
*
* @version V0.1
* @author Chris Bizer <hide@address.com>, Boris Motik <hide@address.com>
*
* @package syntax
* @access public
*
*/
class RdfSerializer extends Object {
// configuration
var $use_entities;
var $use_attributes;
var $sort_model;
var $rdf_qnames;
var $use_xml_declaration;
// properties
var $m_defaultNamespaces = array();
var $m_namespaces = array();
var $m_nextAutomaticPrefixIndex;
var $m_out;
var $m_baseURI;
var $m_statements = array();
var $m_currentSubject;
var $m_rdfIDElementText;
var $m_rdfAboutElementText;
var $m_rdfResourceElementText;
var $m_groupTypeStatement;
var $m_attributeStatements = array();
var $m_contentStatements = array();
var $rdf_qname_prefix;
/**
* Constructor
*
* @access public
*/
function RdfSerializer() {
// default serializer configuration
$this->use_entities = SER_USE_ENTITIES;
$this->use_attributes = SER_USE_ATTRIBUTES;
$this->sort_model = SER_SORT_MODEL;
$this->rdf_qnames = SER_RDF_QNAMES;
$this->use_xml_declaration = SER_XML_DECLARATION;
// add default namespaces
$this->addNamespacePrefix(RDF_NAMESPACE_PREFIX, RDF_NAMESPACE_URI);
$this->addNamespacePrefix(RDF_SCHEMA_PREFIX, RDF_SCHEMA_URI);
}
/**
* Serializer congiguration: Sort Model
* Flag if the serializer should sort the model by subject before serializing.
* TRUE makes the RDF code more compact.
* TRUE is default. Default can be changed in constants.php.
*
* @param boolean
* @access public
*/
function configSortModel($bool) {
$this->sort_model = $bool;
}
/**
* Serializer congiguration: Use Entities
* Flag if the serializer should use entities for URIs.
* TRUE makes the RDF code more compact.
* FALSE is default. Default can be changed in constants.php.
*
* @param boolean
* @access public
*/
function configUseEntities($bool) {
$this->use_entities = $bool;
}
/**
* Serializer congiguration: Use Attributes
* Flag if the serializer should serialize triples as XML attributes where possible.
* TRUE makes the RDF code more compact.
* FALSE is default. Default can be changed in constants.php.
*
* @param boolean
* @access public
*/
function configUseAttributes($bool) {
$this->use_attributes = $bool;
}
/**
* Serializer congiguration: Use Qnames
* Flag if the serializer should use qualified names for RDF reserved words.
* TRUE makes the RDF code more compact.
* TRUE is default. Default can be changed in constants.php.
*
* @param boolean
* @access public
*/
function configUseQnames($bool) {
$this->rdf_qnames = $bool;
}
/**
* Serializer congiguration: Use XML Declaration
* Flag if the serializer should start documents with the xml declaration
* <?xml version="1.0" encoding="UTF-8" ?>.
* TRUE is default. Default can be changed in constants.php.
*
* @param boolean
* @access public
*/
function configUseXmlDeclaration($bool) {
$this->use_xml_declaration = $bool;
}
/**
* Adds a new prefix/namespace combination.
*
* @param String $prefix
* @param String $prefix
* @access public
*/
function addNamespacePrefix($prefix, $namespace) {
$this->m_defaultNamespaces[$prefix] = $namespace;
}
/**
* Serializes a model to RDF syntax.
* RDF syntax can be changed by config_use_attributes($boolean), config_use_entities($boolean),
* config_sort_model($boolean).
*
* @param object Model $model
* @param String $encoding
* @return string
* @access public
*/
function & serialize(&$model, $encoding = DEFAULT_ENCODING) {
// define rdf prefix (qname or not)
if ($this->rdf_qnames)
$this->rdf_qname_prefix = RDF_NAMESPACE_PREFIX . ":";
else
$this->rdf_qname_prefix = "";
// check if model is empty
if ($model->size() == 0) return "<". $this->rdf_qname_prefix . RDF_RDF ." />";
// copy default namespaces
foreach($this->m_defaultNamespaces as $prefix => $namespace)
$this->m_namespaces[$prefix] = $namespace;
// set base URI
if ($model->getBaseURI()==NULL)
$this->m_baseURI="opaque:uri";
else
$this->m_baseURI=$model->getBaseURI();
// sort the array of statements
// Todo: This is awfull. Write own sorting function.
if ($this->sort_model) {
foreach($model->triples as $key => $statement) {
$stmkey = $statement->subj->getURI() .
$statement->pred->getURI() .
$statement->obj->getLabel();
$this->m_statements[$stmkey] = $statement;
}
ksort($this->m_statements);
} else {
$this->m_statements = $model->triples;
}
// collects namespaces
$this->m_nextAutomaticPrefixIndex=0;
$this->collectNamespaces($model);
// start writing the contents
if ($this->use_xml_declaration)
$this->m_out = "<?xml version='1.0' encoding='". $encoding ."'?>" . LINEFEED;
// write entitie declarations
if($this->use_entities) {
$this->m_out .= "<!DOCTYPE " . $this->rdf_qname_prefix .
RDF_RDF." [" . LINEFEED;
$this->writeEntityDeclarations();
$this->m_out .= LINEFEED . "]>" . LINEFEED;
}
// start the RDF text
$this->m_out .= "<" . $this->rdf_qname_prefix . RDF_RDF;
// write the xml:base
if ($model->getBaseURI() != NULL)
$this->m_out .= LINEFEED. INDENTATION ."xml:base=\"".$model->getBaseURI()."\"";
// write namespaces declarations
$this->writeNamespaceDeclarations();
$this->m_out .=">". LINEFEED;
// write triples
$this->writeDescriptions();
$this->m_out .= LINEFEED;
$this->m_out .="</" . $this->rdf_qname_prefix . RDF_RDF .">";
$this->m_namespaces=null;
$this->m_statements=null;
$this->m_currentSubject=null;
$this->m_groupTypeStatement=null;
$this->m_attributeStatements=null;
$this->m_contentStatements=null;
$this->m_rdfResourceElementText=null;
return $this->m_out;
}
/**
* @access private
*/
function writeEntityDeclarations() {
foreach($this->m_namespaces as $prefix => $namespace) {
$this->m_out .= INDENTATION . "<!ENTITY ". $prefix . " '" . $namespace ."'>".LINEFEED;
}
}
/**
* @access private
*/
function writeNamespaceDeclarations() {
foreach($this->m_namespaces as $prefix => $namespace) {
if ($prefix == RDF_NAMESPACE_PREFIX && !$this->rdf_qnames) {
if($this->use_entities) {
$this->m_out .= LINEFEED . INDENTATION .XML_NAMESPACE_DECLARATION_PREFIX .
"=\"&" .$prefix.";\"";
} else {
$this->m_out .= LINEFEED . INDENTATION .XML_NAMESPACE_DECLARATION_PREFIX .
"=\"" .$namespace."\"";
}
} else {
if($this->use_entities) {
$this->m_out .= LINEFEED . INDENTATION .XML_NAMESPACE_DECLARATION_PREFIX .
$prefix."=\"&" .$prefix.";\"";
} else {
$this->m_out .= LINEFEED . INDENTATION .XML_NAMESPACE_DECLARATION_PREFIX .
$prefix."=\"" .$namespace."\"";
}
}
}
}
/**
* @access private
*/
function writeDescriptions() {
$this->m_groupTypeStatement = NULL;
$this->m_attributeStatements = array();
$this->m_contentStatements = array();
$this->m_currentSubject = NULL;
foreach($this->m_statements as $key => $statement) {
$subject = $statement->getSubject();
$predicate = $statement->getPredicate();
$object = $statement->getobject();
// write Group and update current subject if nessesary
if ($this->m_currentSubject==NULL || !$this->m_currentSubject->equals($subject)) {
$this->writeGroup();
$this->m_currentSubject=$subject;
}
// classify the statement
if (($predicate->getURI() == RDF_NAMESPACE_URI.RDF_TYPE) && is_a($object, "Resource"))
$this->m_groupTypeStatement = $statement;
elseif ($this->canAbbreviateValue($object) &&
$this->use_attributes &&
$this->checkForDoubleAttributes($predicate))
$this->m_attributeStatements[] = $statement;
else
$this->m_contentStatements[] = $statement;
}
$this->writeGroup();
}
/**
* @access private
*/
function writeGroup() {
if ($this->m_currentSubject==NULL || ($this->m_groupTypeStatement==NULL && (count($this->m_attributeStatements)==0) && (count($this->m_contentStatements)==0)))
return;
if ($this->m_groupTypeStatement!=NULL)
$outerElementName=$this->getElementText($this->m_groupTypeStatement->obj->getURI());
else
$outerElementName = $this->rdf_qname_prefix . RDF_DESCRIPTION;
$this->m_out .= LINEFEED . "<";
$this->m_out .= $outerElementName;
$this->m_out .= " ";
$this->writeSubjectURI($this->m_currentSubject->getURI());
// attribute Statements
if ($this->use_attributes)
$this->writeAttributeStatements();
if (count($this->m_contentStatements)==0)
$this->m_out .= "/>" . LINEFEED;
else {
$this->m_out .= ">" . LINEFEED;
// content statements
$this->writeContentStatements();
$this->m_out .= "</";
$this->m_out .= $outerElementName;
$this->m_out .= '>'. LINEFEED;
}
$this->m_groupTypeStatement = NULL;
$this->m_attributeStatements = array();
$this->m_contentStatements = array();
}
/**
* @access private
*/
function checkForDoubleAttributes($predicate) {
foreach($this->m_attributeStatements as $key => $statement) {
if ($statement->pred->equals($predicate))
return FALSE;
}
return TRUE;
}
/**
* @access private
*/
function relativizeURI($uri) {
$uri_namespace = RDFUtil::guessNamespace($uri);
if ($uri_namespace == $this->m_baseURI) {
return RDFUtil::guessName($uri);
} else {
return $uri;
}
}
/**
* @access private
*/
function writeSubjectURI($currentSubjectURI) {
$relativizedURI = $this->relativizeURI($currentSubjectURI);
if (!($relativizedURI == $currentSubjectURI)) {
$this->m_out .= $this->rdf_qname_prefix . RDF_ID;
$this->m_out .= "=\"";
$this->m_out .= $relativizedURI;
} else {
$this->m_out .= $this->rdf_qname_prefix . RDF_ABOUT;
$this->m_out .= "=\"";
$this->writeAbsoluteResourceReference($relativizedURI);
}
$this->m_out .= "\"";
}
/**
* @access private
*/
function writeAttributeStatements() {
foreach($this->m_attributeStatements as $key => $statement) {
$this->m_out .= LINEFEED;
$this->m_out .= INDENTATION;
$this->m_out .= $this->getElementText($statement->pred->getURI());
$this->m_out .= "=";
$value=$statement->obj->getLabel();
$quote=$this->getValueQuoteType($value);
$this->m_out .= $quote;
$this->m_out .= RDFUtil::escapeValue($value);
$this->m_out .= $quote;
}
}
/**
* @access private
*/
function writeContentStatements() {
foreach($this->m_contentStatements as $key => $statement) {
$this->m_out .= INDENTATION;
$this->m_out .= '<';
$predicateElementText=$this->getElementText($statement->pred->getURI());
$this->m_out .= $predicateElementText;
if (is_a($statement->obj, "Resource")) {
$this->writeResourceReference($statement->obj->getURI());
$this->m_out .= "/>" . LINEFEED;
} else {
if(is_a($statement->obj, "Literal") and $statement->obj->getLanguage()!= NULL)
$this->m_out .= " " . XML_NAMESPACE_PREFIX . ":" . XML_LANG . "=\"".$statement->obj->getLanguage()."\"";
$this->m_out .= '>';
$this->writeTextValue($statement->obj->getLabel());
$this->m_out .= "</";
$this->m_out .= $predicateElementText;
$this->m_out .= '>' . LINEFEED;
}
}
}
/**
* @access private
*/
function writeResourceReference($rebaseURI) {
$this->m_out .= ' ';
$this->m_out .= $this->rdf_qname_prefix . RDF_RESOURCE;
$this->m_out .= "=\"";
$relativizedURI = $this->relativizeURI($rebaseURI);
if (!($relativizedURI == $rebaseURI))
$this->m_out .= "#" . $relativizedURI;
else
$this->writeAbsoluteResourceReference($rebaseURI);
$this->m_out .= "\"";
}
/**
* @access private
*/
function writeAbsoluteResourceReference($rebaseURI) {
$namespace=RDFUtil::guessNamespace($rebaseURI);
$localName=RDFUtil::guessName($rebaseURI);
$text=$rebaseURI;
if ($namespace!="" and $this->use_entities) {
$prefix= array_search($namespace, $this->m_namespaces);
$text="&".$prefix.";".$localName;
}
$this->m_out .= $text;
}
/**
* @access private
*/
function writeTextValue($textValue) {
if ($this->getValueQuoteType($textValue)==USE_CDATA)
$this->writeEscapedCDATA($textValue);
else
$this->m_out .= RDFUtil::escapeValue($textValue);
}
/**
* @access private
*/
function writeEscapedCDATA($textValue) {
$this->m_out .= "<![CDATA[" . $textValue . "]]>";
}
/**
* @access private
*/
function getValueQuoteType($textValue) {
$quote=USE_ANY_QUOTE;
$hasBreaks=FALSE;
$whiteSpaceOnly=TRUE;
for ($i=0; $i<strlen($textValue); $i++) {
$c=$textValue{$i};
if ($c==LINEFEED)
$hasBreaks=TRUE;
if ($c=="\"" || $c=="\'") {
if ($quote==USE_ANY_QUOTE)
$quote=($c=="\"") ? "\'" : "\"";
elseif ($c==$quote)
return USE_CDATA;
}
if (!($c == " "))
$whiteSpaceOnly = FALSE;
}
if ($whiteSpaceOnly || $hasBreaks)
return USE_CDATA;
return $quote==USE_ANY_QUOTE ? '"' : $quote;
}
/**
* @access private
*/
function canAbbreviateValue($node) {
if (is_a($node, "Literal")) {
$value= $node->getLabel();
if (strlen($value)<MAX_ALLOWED_ABBREVIATED_LENGTH) {
$c=$this->getValueQuoteType($value);
return $c=='"' || $c=='\'';
}
}
return FALSE;
}
/**
* @access private
*/
function getElementText($elementName) {
$namespace=RDFUtil::guessNamespace($elementName);
$localName=RDFUtil::guessName($elementName);
if ($namespace=="")
return $localName;
$prefix=array_search($namespace, $this->m_namespaces);
if ($prefix===FALSE) {
$errmsg = RDFAPI_ERROR . "(class: Serializer; method: getElementText): Prefix for element '" . $elementName . "' cannot be found.";
trigger_error($errmsg, E_USER_ERROR);
}
if ($prefix != RDF_NAMESPACE_PREFIX)
return $prefix.":".$localName;
else
return $this->rdf_qname_prefix . $localName;
}
/**
* @access private
*/
function collectNamespaces($model) {
foreach($model->triples as $key => $value) {
$this->collectNamespace($value->getPredicate());
if ($this->use_entities) {
$this->collectNamespace($value->getSubject());
if(!is_a($value->getObject(), "Literal"))
$this->collectNamespace($value->getObject());
}
}
}
/**
* @access private
*/
function collectNamespace($resource) {
$namespace=RDFUtil::getNamespace($resource);
if (!in_array($namespace, $this->m_namespaces)) {
$prefix = array_search( $namespace, $this->m_defaultNamespaces);
if ($prefix===FALSE)
$prefix=$this->getNextNamespacePrefix();
$this->m_namespaces[$prefix] = $namespace;
}
}
/**
* @access private
*/
function getNextNamespacePrefix() {
$this->m_nextAutomaticPrefixIndex++;
return GENERAL_PREFIX_BASE . $this->m_nextAutomaticPrefixIndex;
}
}
?>