<?php

/* RDFInt - RDF Interfaces for PHP
 * Copyright 2011 netlabs.org
 * Author: Christian Langanke, Adrian Gschwend
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

namespace rdfa;

/**
 *  \class  Data
 *  \brief  This class implements a RDF data class as specified in the 
 *          RDF API and RDFa specs of W3C.
 *  \author Christian Langanke
 *  \author Adrian Gschwend
 *  \date   2011
 */

/*
 * This class internally uses the ARC triple array format for storing RDF data:
 *
 *    Array
 *    (
 *        [type] => --- '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 $aTriples;
  private $aNamespace;

  // ---------------------------------------

  /**
   * 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();

    // initialize some vars
    $this->aTriples = array();
    $this->aNamespace = 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));
  }

  // --------------------------------------------------------

  /**
   * 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) {

    $searchTriple = array ( 's' =>  $subject, 'p' =>  $predicate, 'o' =>  $object);

    // resolve namespace prefixes
    $uriSubject   = $this->resolve( $subject);
    $uriPredicate = $this->resolve( $predicate);
    $uriObject    = $this->resolve( $object);

    // resolve may return NULL, then use original value
    $uriSubject   = ($uriSubject   === NULL) ? $subject   : $uriSubject;
    $uriPredicate = ($uriPredicate === NULL) ? $predicate : $uriPredicate;
    $uriObject    = ($uriObject    === NULL) ? $object    : $uriObject;

    // filter all available triples
    $aresult = array();
    foreach ($this->aTriples as $aTriple) {

      // must be triple
      if ((isset( $aTriple[ 'type'])) && 
          ($aTriple[ 'type'] != 'triple'))
        continue;

      // check subject and predicate match if given
      if (($uriSubject != NULL)   && (strcmp( $uriSubject, $aTriple[ 's'])))
        continue;

      if (($uriPredicate != NULL) && (strcmp( $uriPredicate, $aTriple[ 'p'])))
        continue;

      // check object if given
      if ($uriObject != NULL) {

        // check object match
        if (strcmp( $uriObject, $aTriple[ 'o']))
          continue;

      } //  if ($uriObject != NULL) {

      // store in result array
      $aresult[] = $aTriple;

    } // foreach ($aTriples as $aTriple)

    // 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->resolve( $subject);
    $uriSubject = ($uriSubject === NULL) ? $subject   : $uriSubject;    
    foreach ($this->aTriples as $aTriple) {
      if ( $uriSubject == $this->shrink( $aTriple[ 's']))
        return true;
    }

    return false;

  } // private function _subjectExists

  // --------------------------------------------------------

  /**
   * Add mapping to internal namespace list.
   * This method is called internally, not producing debug output.
   */
  private function _addNamespaceMapping( $prefix, $uriNamespace) {

    if ($prefix == '')
      return false;

    // add colon to prefix as internally req.
    if (substr( $prefix, -1) != ':')
      $prefix = "$prefix:";

    if (isset( $this->aNamespace[ $prefix])) {
      $oldUriNamespace = $this->aNamespace[ $prefix];
      if ($oldUriNamespace == $uriNamespace)
        $debugmessage = "Namespace mapping exists: @prefix $prefix <$uriNamespace> .";
      else
        $debugmessage = "Namespace mapping overwritten: @prefix $prefix <$uriNamespace> .\n" .
                        "Old mapping was: @prefix $prefix <$oldUriNamespace>";
    } else {
      $debugmessage = "Namespace mapping added: @prefix $prefix <$uriNamespace> .";
    }

    // set URI
    $this->aNamespace[ $prefix] = $uriNamespace;
    return $debugmessage;

  } // private function _addNamespaceMapping

  // --------------------------------------------------------

  /**
   *  Shrink URI to CURI, but return <URI> if mapping not successful.
   */
  private function _shrinkFormatted( $uri) {

    $curie = $this->shrink( $uri);
    if ($curie == $uri)
      $curie = "<$uri>";
    return $curie;

  }  // private function _shrinkFormatted

  // --------------------------------------------------------

  // 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
   */
  public function setMapping( $prefix, $uriNamespace) {
    $debugmessage = $this->_addNamespaceMapping( $prefix, $uriNamespace);
    if ($debugmessage != false)
      $this->debugger->sendMessage( $debugmessage,
                                    self::debugcontext);
  } // public function setMapping

  // --------------------------------------------------------

  /**
   * Resolves a CURI to a URI.
   *
   * If the prefix is not known then this method will return null.
   *
   * \param curie            URI to be mapped to a CURIE
   *
   * <strong>NOTE: This method is a library specific extension to the RDF API and RDFa API.</strong>
   */
  public function resolve( $curie) {

    $replacecount = 1;
    $uri = NULL;
    if ($curie != NULL) {
      if ($this->aNamespace != NULL) {
        if ((strpos( $uri, ":/") !== false) ||
            (strpos( $uri, "_:") === 0)) {
          $uri = $curi;
        } else {
          // check for namespaces
          foreach ($this->aNamespace as $prefix => $uriNamespace) {
            // check for prefix match
            $posPrefix = strpos( $curie, $prefix);
            if ($posPrefix === 0) {
              // replace prefix and bail out
              $uri = str_replace( $prefix, $uriNamespace, $curie, $replacecount);
              break;
            }
          } // foreach ($this->aNamespace
        } // if ((strpos ...
      } // if ($aNamespace != NULL) {
    }  // if ($uri != NULL) {

    return $uri;

  } //  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
   *
   * <strong>NOTE: This method is a library specific extension to the RDF API and RDFa API.</strong>
   */
  public function shrink( $uri) {

    $replacecount = 1;
    if ($uri != NULL) {
      if ($this->aNamespace != NULL) {
        if (strpos( $uri, ":/") !== false) {
          foreach ($this->aNamespace as $prefix => $uriNamespace) {
            // search namespace URI
            $posNamespace = strpos( $uri, $uriNamespace);
            if ($posNamespace === false)
              continue;
            // replace namespace URI and bail out
            $uri = str_replace( $uriNamespace, $prefix, $uri, &$replacecount);
            break;
          } // foreach ($aNamespace
        } // if (strpos( $uri, ":/") !== false)
      } // if ($aNamespace != NULL) {
    }  // if ($uri != NULL) {

    return $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
   *
   * <strong>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.
   * </strong>
   */
  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);

      $index = $query->run();
      $newTriples = \ARC2::getTriplesFromIndex( $index);

    } else {

      // load RDF data from URI
      $uri = $toparse;
      $parseSource = "ARC parser";

      $debugMessage = "Parsing RDF data from: $uri\n";
      $this->debugger->sendMessage( $debugMessage, self::debugcontext);

      $arcParser = \ARC2::getRDFParser();
      $arcParser->parse( $uri);
      $newTriples = $arcParser->getTriples();

    }

    // 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);

  } //   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
   *
   * 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
   *
   * 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

  // --------------------------------------------------------

  /**
   * Retrieves an associative list of unique properties with their values expressed in the RDF data
   * that match the given subject. All non-unique properties are <i>NOT</i> returned !
   *
   * \param subject   subject to be searched
   *
   * If a subject isn't provided, all unique properties expressed in the RDF data are returned.
   *
   * <strong>NOTE: This method is a library specific extension to the RDF API and RDFa API.</strong>
   */
  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
   *
   * 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
   *
   * If no arguments are provided, the first value expressed in the RDF data is returned.
   *
   * <strong>NOTE: This method is a library specific extension to the RDF API and RDFa API.</strong>
   */
  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

  /**@} */ /***************** 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
   *
   */
  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
   *
   * 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   associative array( URI/CURIE => membername) as a template to be applied to the projection object
   *
   * 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;

    // create projections for examination
    $aprojection_test = array();
    $count = count( $asubjects);
    if ($fLogMessages) $this->debugger->sendMessage( "Getting $count projections for filter test",
                                                     self::debugcontext);
    foreach ($asubjects as $subject) {
      $aprojection_test[] = new \rdfa\Projection( $this, $subject, $template);
      $debugmessage .= "$subject\n";
    }

    // determine which projections have to be filtered out
    $aFilteredSubjects = array();
    foreach ($aprojection_test as $projection) {
      $subject = $projection->getSubject();
      foreach ($query as $property => $value) {
        $avalues = $projection->getAll( $property);
        if ($avalues == false) {
          // filter this projection: 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 projections\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 ($aprojection_test as $projection) {
      if (array_key_exists( $projection->getSubject(), $aFilteredSubjects) === false) {
        $aprojection[] = $projection;
      } else {
        unset( $projection);
      }
    }

    // create log output
    $count = count( $aprojection);
    $debugmessage =  "Returning $count projections \n";
    foreach ($aprojection as $projection) {
      $debugmessage .= "{$projection->getSubject()}\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
   *
   * 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
   *
   * <strong>NOTE: This method is a library specific extension to the RDF API and RDFa API.</strong>
   */
  public function serialize( $type ) {
    // set the supported types and shortcut them to the ones we use in there
    $validTypes = array( 'application/rdf+xml' => 'rdfxml' ,
                         'application/rdfxml' => 'rdfxml',
                         'text/turtle' => 'turtle',
                         'application/x-turtle' => 'turtle',
                         'text/n3' => 'n3',
                         'application/json' => 'json');

    // check if explicit mime type is specified
    if (isset( $validTypes[ $type])) {
      $type = $validTypes[ $type];
    }  else {
      // check if internal type is given
      $internalTypes = array_unique( array_flip( $validTypes));
      if (!isset( $internalTypes[ $type]))
        return false;
    }

    switch ($type) {

      case 'rdfxml':
        $ser = \ARC2::getRDFXMLSerializer();
        break;
      
      case 'turtle':
        $ser = \ARC2::getTurtleSerializer();
        break;
        
      case 'n3':
        $ser = \ARC2::getNTriplesSerializer();
        break;
        
      case 'json':
        $ser = \ARC2::getRDFJSONSerializer();
        break;
      
    } // switch ($type)

    return $ser->getSerializedTriples( $this->aTriples);

  } // public function serialize

} // class Data

?>