--- 'triple' --- (others ???) * [s] => --- uri --- * [p] => --- uri --- * [o] => --- uri or bnode or literal --- * [s_type] => --- 'uri' or 'bnode' --- * [p_type] => --- 'uri' --- (others ???) * [o_type] => => --- 'uri' or 'bnode' oder 'literal' --- * [o_datatype] => --- datatype or empty --- * [o_lang] => --- lang code or empty --- * ) * */ class Data { /** * Version of the class */ const version = '1.0.0'; /** * Name of the fDebug context */ const debugcontext = 'RDFA_DATA'; private $debugger; private $core; private $aTriples; // --------------------------------------- /** * Creates a data class instance. * In order to access data, you have to call rdfa::Data::parse(). */ public function __construct() { // setup debugger $this->debugger = \fDebug::getInstance(); $this->core = new \rdfint_core\Core(); // initialize some vars $this->aTriples = array(); } // public function __construct // ######################################################## // private interface // ######################################################## // debug formatting helpers /** * Return string with debug output, representing data from an ARC triple array. * Values not being speciefied (==Null) are not included in the text. * The object or value is enclosed in double quotes, if it is a literal */ private function _debug_formatArcTriple( $aTriple) { $subject = $aTriple[ 's']; $predicate = $aTriple[ 'p']; $object = $aTriple[ 'o']; $objecttype = (isset( $aTriple[ 'o_type'])) ? $aTriple[ 'o_type'] : ''; $litSubject = ($subject == NULL) ? "" : $this->_shrinkFormatted( $subject); $litPredicate = ($predicate == NULL) ? "" : $this->_shrinkFormatted( $predicate); if ($object == NULL) $litObject = ""; else { if ($objecttype == 'literal') $litObject = '"' . $object . '"'; else $litObject = $this->_shrinkFormatted( $object); } $result = trim( "$litSubject $litPredicate $litObject"); return $result; } /** * Return string with debug output, representing data the specified values. * Values not being speciefied (==Null) are not included in the text. * The object or value is enclosed in double quotes, if it is a literal */ private function _debug_formatTripleFromParms( $subject, $predicate, $object) { return $this->_debug_formatArcTriple( array( 's' => $subject, 'p' => $predicate, 'o' => $object)); } // -------------------------------------------------------- /** * Shrink URI to CURI, but return if mapping not successful. */ private function _shrinkFormatted( $uri) { $curie = $this->_shrink( $uri); if ($curie == $uri) $curie = "<$uri>"; return $curie; } // private function _shrinkFormatted // -------------------------------------------------------- /** * Filters out requested triples by specified subject/predicate/object * Returns array of matching triples in ARC triple array format */ private function _filterTriples( $subject = NULL, $predicate = NULL, $object = NULL, &$debugmessage_result) { $aresult = $this->core->filterTriples( $this->aTriples, $subject, $predicate, $object); // show result in debugger $triplecount = count( $this->aTriples); $resultcount = count( $aresult); if ($resultcount == 0) $debugmessage_result = "No match in $triplecount triples!"; else { $debugmessage_result = "Matches in $triplecount triples: \n"; foreach ($aresult as $aTriple) { $debugmessage_result .= $this->_debug_formatArcTriple( $aTriple)."\n"; } } return $aresult; } // private function _filterTriples // -------------------------------------------------------- /** * Checks if a given subject exists in the RDF data. */ private function _subjectExists( $subject) { $uriSubject = $this->_shrink( $subject); foreach ($this->aTriples as $aTriple) { if ( $uriSubject == $this->_shrink( $aTriple[ 's'])) return true; } return false; } // private function _subjectExists // -------------------------------------------------------- // CURRENTLY NOT FULLY IMPLEMENTED // adjust blank node IDs so that same blank nodes of a // triple sets has the same id as in the currently contained // triple set. // NOTE: a blank nodes may not be adjusted if there are // two different values for the same blank node in the two // triple sets, as then the two blank nodes may represent a // multivalue property private function _adjustBlankNodes( $aTriplesToAdd) { // foreach ($this->aTriples as $aTriple) { // if ($aTriple[ 'o_type'] == 'bnode') // echo $aTriple[ 'o']."\n"; // } return $aTriplesToAdd; } // -------------------------------------------------------- // add an array of ARC triple arrays // to the data hold by the instance private function _addTriples( $aTriples) { if (!is_array( $aTriples)) return; // adjust blank node IDs first $aTripleAdusted = $this->_adjustBlankNodes( $aTriples); // add adjusted triples foreach ($aTripleAdusted as $aTriple) { $this->_addTriple( $aTriple); } } // private function _addTriples // -------------------------------------------------------- // add an ARC triple array // to the data hold by the instance // the triple is only merged if not yet included ! private function _addTriple( $aTriple) { if (!is_array( $aTriple)) return; if ((isset( $aTriple['type']) && ($aTriple['type'] != 'triple'))) return; // if triple is not contained, add it! if (array_search( $aTriple, $this->aTriples) === false) $this->aTriples[] = $aTriple; } // private function _addTriple // -------------------------------------------------------- /** * Checks if callers caller is from own library code. * Helps to reduce unwanted ddebug output */ private function _calledFromOwnCode() { // check for own namespace in backtrace $trace = array_slice(debug_backtrace( false), 2, 1); $class = (isset( $trace[0]['class'])) ? $trace[0]['class'] : ''; return (strpos( $class, 'rdfa\\') === 0); } // private function _calledFromOwnCode // ######################################################## // public interface // ######################################################## /** * \name Mapping API */ /**@{ */ /***************** DOXYGEN GROUP START - Mapping APIs */ /** * Sets short-hand IRI mappings that are used by the API to map URIs to CURIEs. * * \param prefix Namespace prefix of the mapping * \param uriNamespace Namespace base URI of the mapping * \retval void * * \note The return value * - is not described in the \c RDFa \c API specification * - should be a string in the \c RDF \c API, but the value is not described */ public function setMapping( $prefix, $uriNamespace) { $debugmessage = $this->core->setMapping( $prefix, $uriNamespace); if ($debugmessage != false) $this->debugger->sendMessage( $debugmessage, self::debugcontext); return; } // public function setMapping // -------------------------------------------------------- /** * Resolves a CURI to a URI. * * If the prefix is not known then this method will return null. * * \param curie CURIE to be resolved to a URI * \retval string URI * \retval NULL Mapping could not be found * * \note This method is a library specific extension to the RDF API and RDFa API */ public function _resolve( $curie) { return $this->core->resolve( $curie); } // public function _resolve // -------------------------------------------------------- /** * Shrinks a URI to a CURIE. * * If no mapping exists for the given URI, the URI is returned. * * \param uri URI to be shrinked to a CURIE * \retval string CURIE or URI * * \note This method is a library specific extension to the RDF API and RDFa API */ public function _shrink( $uri) { return $this->core->shrink( $uri); } // public function _shrink /**@} */ /***************** DOXYGEN GROUP ENDS - Mapping APIs */ // -------------------------------------------------------- /** * Parses RDF data from a URI. * * \param toparse Resource to parse from. This can be a URI or an object of SparqlQuery * \retval boolean true: the parsed graph contains data * \retval boolean false: the parsed graph contains no data * * \note * - This method is defined for the RDF API, but a library specific extension to the RDFa API. * - Parsing from a SPARQL CONSTRUCT query by using a SparqlQuery object, is a library specific * extension to both the RDF API and RDFa API. * - On all consecutive calls to parse, the newly parsed graph is merged into the existing graph. * Such a feature is not described in the W3C specs */ public function parse( $toparse) { // check type to parse if ((is_object( $toparse)) && ('\\' . get_class( $toparse) == '\\rdfa\\SparqlQuery')) { // receive RDF data from raw SPARQL query $query = $toparse; $parseSource = "SPARQL query"; $statement = $query->getStatement(); $debugMessage = "Parsing RDF data from SPARQL query\n" . "Executing statement: \n" . $statement; $this->debugger->sendMessage( $debugMessage, self::debugcontext); $newTriples = $query->run(); } else { // load RDF data from URI $uri = $toparse; $parseSource = "ARC parser"; $debugMessage = "Parsing RDF data from: $uri\n"; $this->debugger->sendMessage( $debugMessage, self::debugcontext); $core = new \rdfint_core\Core(); $newTriples = $core->parse( $uri); } // could something be parsed ? $result = (count( $newTriples) > 0); // determine operation mode $fMerge = (count( $this->aTriples) != 0); if ($fMerge) $operation = "merged"; else $operation = "loaded"; // merge new triples into existing data $this->_addTriples( $newTriples); $debugMessage = count( $newTriples) . " triple(s) $operation from $parseSource\n"; foreach ($newTriples as $aTriple) { $debugMessage .= $this->_debug_formatArcTriple( $aTriple)."\n"; } if ($fMerge) $debugMessage .= "\n" . count( $this->aTriples) . " triples total\n"; $this->debugger->sendMessage( $debugMessage, self::debugcontext); return $result; } // public function parse // -------------------------------------------------------- /** * \name Basic API */ /**@{ */ /***************** DOXYGEN GROUP START - Basic APIs */ /** * Retrieves a list of all values expressed in the RDF Data that match the given subject and property. * * \param property Property that the subject should be linked with * \param value Value that the specified property should have * \retval array List of subjects * \retval boolean false: the graph contains no data * * If no arguments are provided, all values from within the RDF data are returned. */ public function getSubjects( $property = Null, $value = NULL) { $predicate = $property; $object = $value; $aTriplesResult = $this->_filterTriples( NULL, $predicate, $object, $debugmessage_filterresult); // build result $aresult = false; foreach ($aTriplesResult as $aTriple) { $aresult[]= $this->_shrink( $aTriple[ 's']); } // make result entries unique if ($aresult !== false) $aresult = array_values( array_unique( $aresult)); // build debug message if (!$this->_calledFromOwnCode()) { $formattedTriple = $this->_debug_formatTripleFromParms( NULL, $predicate, $object); if (!$aresult) $debugmessage = "No subjects found for: $formattedTriple\n"; else { $debugmessage = count( $aresult) . " subjects found"; if ($formattedTriple != '') $debugmessage .= " for: $formattedTriple"; $debugmessage .= "\n"; foreach( $aresult as $result) { $debugmessage .= "$result\n"; } } $this->debugger->sendMessage( "$debugmessage\n$debugmessage_filterresult", self::debugcontext); } // if (!$this->_calledFromOwnCode()) return $aresult; } // public function getSubjects // -------------------------------------------------------- /** * Retrieves a list of all properties expressed in the RDF data that match the given subject. * * \param subject Subject to be searched * \retval array List of properties * \retval boolean false: the subject was not found * * If a subject is not provided, all properties expressed in the RDF data are returned. */ public function getProperties( $subject = Null) { $aTriplesResult = $this->_filterTriples( $subject, Null, Null, $debugmessage_filterresult); // build result $aresult = false; foreach ($aTriplesResult as $aTriple) { $aresult[]= $this->_shrink( $aTriple[ 'p']); } // make result entries unique if ($aresult !== false) $aresult = array_values( array_unique( $aresult)); // build debug message if (!$this->_calledFromOwnCode()) { if (!$aresult) { if ( $subject = Null) $debugmessage = "No properties found\n"; else $debugmessage = "No properties found for subject: $subject\n"; } else { $debugmessage = count( $aresult) . " properties found for: $subject\n"; foreach( $aresult as $result) { $debugmessage .= "$result\n"; } } $this->debugger->sendMessage( "$debugmessage\n$debugmessage_filterresult", self::debugcontext); } // if (!$this->_calledFromOwnCode()) return $aresult; } // public function getProperties // -------------------------------------------------------- /** * Gets an associative list of unique properties with their values * expressed in the RDF data that match the given subject. * * \param subject Subject to be searched * \retval array Associative list of unqique properties and their values * \retval boolean false: the subject could not be found * * If a subject isn't provided, all unique properties expressed in the RDF data are returned. * * \note * - All non-unique properties are \c NOT returned ! * - This method is a library specific extension to the RDF API and RDFa API */ public function _getUniqueProperties( $subject = Null) { $aTriplesResult = $this->_filterTriples( $subject, Null, Null, $debugmessage_filterresult); // build result $aresult = false; $afound = array(); foreach ($aTriplesResult as $pos => $aTriple) { // isolate predicate and check if it was already found $thispredicate = $this->_shrink( $aTriple[ 'p']); if (isset( $afound[ $thispredicate])) { unset( $aresult[ $thispredicate]); continue; } else { $afound[ $thispredicate] = true; $aresult[ $thispredicate] = $this->_shrink( $aTriple[ 'o']); } } // build debug message if (!$this->_calledFromOwnCode()) { if (!$aresult) { if ( $subject = Null) $debugmessage = "No unique properties found\n"; else $debugmessage = "No unique properties found for subject: $subject\n"; } else { $debugmessage = count( $aresult) . " unique properties found for: $subject\n"; foreach( $aresult as $result) { $debugmessage .= "$result\n"; } } $this->debugger->sendMessage( "$debugmessage\n$debugmessage_filterresult", self::debugcontext); } // if (!$this->_calledFromOwnCode()) return $aresult; } // public function _getUniqueProperties // -------------------------------------------------------- /** * Retrieves a list of all values expressed in the RDF data that match the given subject and property. * * \param subject Subject to be searched * \param property Property to be searched * \retval array List of values * \retval boolean false: a matching subject was not found * * If no arguments are provided, all values expressed in the RDF data are returned. */ public function getValues( $subject = Null, $property = Null) { $predicate = $property; $aTriplesResult = $this->_filterTriples( $subject, $predicate, Null, $debugmessage_filterresult); // build result $aresult = false; foreach ($aTriplesResult as $aTriple) { $aresult[]= $this->_shrink( $aTriple[ 'o']); } // make result entries unique if ($aresult !== false) $aresult = array_values( array_unique( $aresult)); // build debug message if (!$this->_calledFromOwnCode()) { $formattedTriple = $this->_debug_formatTripleFromParms( $subject, $predicate, Null); if ($aresult === false) { if ( $subject = Null) $debugmessage = "No values found\n"; else $debugmessage = "No values found for subject: $formattedTriple\n"; } else { $debugmessage = count( $aresult) . " values found for: $formattedTriple\n"; foreach( $aresult as $result) { $debugmessage .= "$result\n"; } } $this->debugger->sendMessage( "$debugmessage\n$debugmessage_filterresult", self::debugcontext); } // if (!$this->_calledFromOwnCode()) return $aresult; } // public function getValues // -------------------------------------------------------- /** * Retrieves the first available value expressed in the RDF data that matches the given subject and property. * * \param subject Subject to be searched * \param property Property to be searched * \retval string First value of the property * \retval boolean false: the subject could not be found * * If no arguments are provided, the first value expressed in the RDF data is returned. * * \note This method is a library specific extension to the RDF API and RDFa API */ public function _getFirstValue( $subject = Null, $property = Null) { $predicate = $property; $aTriplesResult = $this->_filterTriples( $subject, $predicate, Null, $debugmessage_filterresult); // build result $result = false; foreach ($aTriplesResult as $aTriple) { $result = $this->_shrink( $aTriple[ 'o']); break; } // build debug message if (!$this->_calledFromOwnCode()) { $formattedTriple = $this->_debug_formatTripleFromParms( $subject, $predicate, Null); if (!$result) { if ($subject == Null) $debugmessage = "No value found\n"; else $debugmessage = "No value found for: $formattedTriple\n"; } else { $debugmessage = "Value $result found for: $formattedTriple\n"; $triplecount = count( $aTriplesResult); if ($triplecount == 1) $debugmessage .= "One value available"; else $debugmessage .= "First of $triplecount values available"; } $this->debugger->sendMessage( "$debugmessage\n$debugmessage_filterresult", self::debugcontext); } // if (!$this->_calledFromOwnCode()) return $result; } // public function _getFirstValue // -------------------------------------------------------- /** * Sets a property value for a subject. * * \param subject Subject to get the property set as URI or CURIE * \param property Property to be added as URI or CURIE * \param value Value to be set for the property as URI, CURIE or literal value * \param type The type of the value, either 'uri', 'literal' or 'bnode' * (case-insensitive). Specifying the first character is sufficient. * If not specified, strings starting with 'http://' are taken as a URI, * strings starting with '_:' as a blank node, otherwise as a literal * \retval boolean true: property value set \n * false: subject not found or invalid value type specified * * \see rdfa::Projection::set * * \note This method is a library specific extension to the RDF API and RDFa API */ public function _setValue( $subject, $property, $value, $type = NULL) { $predicate = $property; $object = $value; // resolve namespace prefixes $uriSubject = $this->_resolve( $subject); $uriPredicate = $this->_resolve( $predicate); $uriObject = $this->_resolve( $object); // resolve method may return NULL, then use original value $uriSubject = ($uriSubject === NULL) ? $subject : $uriSubject; $uriPredicate = ($uriPredicate === NULL) ? $predicate : $uriPredicate; $uriObject = ($uriObject === NULL) ? $object : $uriObject; // check what type we have // This is not clear in the API. It could be interpreted that the only acceptable value is a literal // We have to check what the intention is and adjust it accordingly or enhance the specs if ($type == null) { if (preg_match('#^http://.?#', $value)) $type = 'uri'; elseif (preg_match('#/^_:.?/#', $value)) $type = 'bnode'; else $type = 'literal'; } if (!preg_match('#^(uri|bnode|literal)$#', strtolower($type))) return false; // construct the aTriple array $aTriple = array(); $aTriple['type'] = 'triple'; $aTriple['s'] = $uriSubject; $aTriple['p'] = $uriPredicate; $aTriple['o'] = $uriObject; $aTriple['s_type'] = 'uri'; $aTriple['p_type'] = 'uri'; $aTriple['o_type'] = strtolower($type); $aTriple['o_datatype'] = ''; // data types, currently not supported by the API $aTriple['o_lang'] = ''; // language, same same $this->_addTriple( $aTriple); return true; } // public function _setValue /**@} */ /***************** DOXYGEN GROUP ENDS - Basic APIs */ // -------------------------------------------------------- /** * \name Projection API */ /**@{ */ /***************** DOXYGEN GROUP START - Projection APIs */ /** * Retrieves a single Projection given a subject and an optional template. * A template can be provided for the purpose of building the Projection in an application-specific way. * * \param subject Subject to be searched * \param template Associative array( URI/CURIE => membername) as a template to be applied to the projection object * \retval rdfa::Projection The projection on the subject * \retval boolean false: the subject was not found * */ public function getProjection( $subject, $template = Null) { if ($subject == Null) return false; if ((!$template == Null) && (!is_array( $template))) { $this->debugger->sendError( "Invalid type specified as template (must be array) " . get_class() ."::getProjection", self::debugcontext); return false; } $fLogMessages = (!$this->_calledFromOwnCode()); if (! $this->_subjectExists( $subject)) { if ($fLogMessages) $this->debugger->sendMessage( "Cannot get projection for subject: $subject", self::debugcontext); $result = false; } else { if ($fLogMessages) $this->debugger->sendMessage( "Get projection for subject: $subject", self::debugcontext); $result = new \rdfa\Projection( $this, $subject, $template); } return $result; } // public function getProjection // -------------------------------------------------------- /** * Retrieves a list of Projections given an optional property and value to match against. * A template can be provided for the purpose of building the Projection in an application-specific way. * * \param property Property that the subject of the projections should be linked with * \param value Value that the specified property should have * \param template Associative array( URI/CURIE => membername) as a template to be applied to the projection object * \retval array List of rdfa::Projection as projections on the matching subjects * \retval boolean false: a matching subject was not found * * If no arguments are provided, projections are created for all subjects from within the RDF data. */ public function getProjections( $property = Null, $value = Null, $template = Null) { if ((!$template == Null) && (!is_array( $template))) { $this->debugger->sendError( "Invalid type specified as template (must be array) for " . get_class() ."::getProjections", self::debugcontext); return false; } // providing log output about call $predicate = $property; $object = $value; $fLogMessages = (!$this->_calledFromOwnCode()); $formattedTriple = $this->_debug_formatTripleFromParms( Null, $predicate, $object); if ($formattedTriple != '') $formattedTriple = " for: $formattedTriple"; $asubjects = $this->getSubjects( $property, $value); if ($asubjects == false) { if ($fLogMessages) $this->debugger->sendMessage( "Cannot get projections $formattedTriple", self::debugcontext); $aprojection = false; } else { $count = count( $asubjects); if ($fLogMessages) $this->debugger->sendMessage( "Getting $count projections $formattedTriple", self::debugcontext); $aprojection = array(); foreach ($asubjects as $subject) { $aprojection[] = new \rdfa\Projection( $this, $subject, $template); } } return $aprojection; } // public function getProjection // -------------------------------------------------------- /** * Retrieves a list of Projections based on a set of selection criteria. * A template can be provided for the purpose of building the Projection in an application-specific way. * * \param query An associative array( URI/CURIE => value) specifying a multiple property filter * \param template An associative array( URI/CURIE => membername) as a template to be applied to the projection object * \retval array List of rdfa::Projection as projections on the matching subjects * \retval boolean false: a matching subject was not found * * If no arguments are provided, projections are created for all subjects from within the RDF data. */ public function query( $query, $template = Null) { if ((!$query == Null) && (!is_array( $query))) { $this->debugger->sendError( "Invalid type specified as query (must be array) for " . get_class() ."::query", self::debugcontext); return false; } if (count( $query) == 0) { $this->debugger->sendError( "Empty query array specified " . get_class() ."::query", self::debugcontext); return false; } if ((!$template == Null) && (!is_array( $template))) { $this->debugger->sendError( "Invalid type specified as template (must be array) " . get_class() ."::query", self::debugcontext); return false; } // providing log output about query $fLogMessages = (!$this->_calledFromOwnCode()); $debugmessage = "Querying for projections\n"; foreach ($query as $property => $value) { $debugmessage .= "filter: $property $value\n"; } if ($fLogMessages) $this->debugger->sendMessage( "$debugmessage", self::debugcontext); // do initial search list( $property, $value) = each( $query); $asubjects = $this->getSubjects( $property, $value); if ($asubjects == false) return false; // examine properties $aprojection_test = array(); $count = count( $asubjects); if ($fLogMessages) $this->debugger->sendMessage( "Testing $count subjects", self::debugcontext); $aFilteredSubjects = array(); foreach ($asubjects as $subject) { $debugmessage .= "$subject\n"; foreach ($query as $property => $value) { $avalues = $this->getValues( $subject, $property); if ($avalues == false) { // filter this subject: property not found $aFilteredSubjects[ $subject] = "Property $property not found"; break; } else { if (array_search( $value, $avalues, true) === false) { // filter this projection: specific value not found $aFilteredSubjects[ $subject] = "Property $property does not have value: $value"; break; } } } } $count = count( $aFilteredSubjects); if ($count == 0) { $debugmessage = "No projections filtered\n"; } else { $debugmessage = "Filtering $count subjects\n"; foreach ( $aFilteredSubjects as $subject => $reason) { $debugmessage .= "$subject: $reason\n"; } } if ($fLogMessages) $this->debugger->sendMessage( "$debugmessage", self::debugcontext); // take over unfiltered projections $aprojection = array(); foreach ($asubjects as $subject) { if (array_key_exists( $subject, $aFilteredSubjects) === false) $aprojection[] = new \rdfa\Projection( $this, $subject, $template); } // create projections on unfiltered subjects foreach ($aprojection as $projection) { $debugmessage .= "{$projection->getSubject()}\n"; } $count = count( $aprojection); $debugmessage = "Returning $count projections \n"; if ($fLogMessages) $this->debugger->sendMessage( "$debugmessage", self::debugcontext); return $aprojection; } // public function query /**@} */ /***************** DOXYGEN GROUP ENDS - Projection APIs */ /** * Returns a serialization of the triple data. * * \param type Supported RDF serialization MIME Media Types according to http://www.iana.org/assignments/media-types/index.html * \retval string Serialization of the graph data * \retval boolean false: the serialization type is invalid * * Currently supported mime type identifiers are: * * - application/rdf+xml * - application/rdfxml * - text/turtle * - application/x-turtle * - text/n3 * - application/json * * In addition to those, the internal type identifiers of this library can be used as well: * - rdfxml * - turtle * - n3 * - json * * \note This method is a library specific extension to the RDF API and RDFa API */ public function _serialize( $type ) { $core = new \rdfint_core\Core(); return $core->serialize( $this->aTriples, $type); } // public function _serialize } // class Data ?>