log = fopen(USERDATADIR."logVOTable","w"); } function addLog($msg) { if (DEBUG_MODE) fprintf($this->log,$msg); } function load($fileName) { $this->is_little_endian = array_values(unpack('L1L', pack('V', 1)))[0] == 1; libxml_set_streams_context(stream_context_create([ 'http' => [ 'method' => 'GET', 'timeout' => '5' ]])); // BRE - Add proxy host if exists $context = ProxyUtils::getStreamContextWithProxy(); if (isset($context)) { libxml_set_streams_context($context); } $this->xml = new DomDocument(); if (!@$this->xml->load($fileName)) { $this->votable_error = 'Can not load xml file.'; return false; } $this->checkIDAttribute(); $rootNamespace = $this->xml->lookupNamespaceUri($this->xml->namespaceURI); $this->xp = new domxpath($this->xml); $this->xp->registerNameSpace('x', $rootNamespace); return true; } function getVotableError() { return $this->votable_error; } function isValidSchema() { if ($this->votable_error != false) { return false; } if (!$this->xml) { $this->votable_error = "The returned file is not XML."; return false; } $infos = $this->xp->query($this->queryResourceInfo()); foreach($infos as $info) { if($info->getAttribute('value') == 'ERROR') { $this->votable_error = $info->textContent; return false; } } //ToDo - BRE - add validation!! return TRUE; if (DEBUG_MODE) libxml_use_internal_errors(true); $vers = $this->getVersion(); $this->addLog("VOTable version : ".$vers."\n"); $result = FALSE; switch ($vers) { case '1.2' : $result = $this->xml->schemaValidate(XMLPATH.'VOTable-1.2.xsd'); case '1.0' : $result = $this->xml->schemaValidate(XMLPATH.'VOTable-1.0.xsd'); default : $result = $this->xml->schemaValidate(XMLPATH.'VOTable-1.1.xsd'); } if (DEBUG_MODE) { $errors = libxml_get_errors(); foreach ($errors as $error) { $msg = ''; switch ($error->level) { case LIBXML_ERR_WARNING: $msg .= ("WARNING ".$error->code.": "); break; case LIBXML_ERR_ERROR: $msg .= ("ERROR ".$error->code.": "); break; case LIBXML_ERR_FATAL: $msg .= ("FATAL ".$error->code.": "); break; } $msg .= ($error->message." - In line : ".$error->line." - Of file : ".$error->file."\n"); $this->addLog($msg); } libxml_use_internal_errors(false); } return $result; } protected function queryResource() { return "//x:RESOURCE"; } protected function queryResourceInfo() { return $this->queryResource()."/x:INFO"; } protected function queryTable() { return $this->queryResource()."/x:TABLE"; } protected function queryDescription() { return $this->queryTable()."/x:DESCRIPTION"; } protected function queryFields() { return $this->queryTable()."/x:FIELD"; } protected function queryField($field_id) { return $this->queryFields()."[@ID='".$field_id."']"; } protected function queryFieldByName($field_id) { return $this->queryFields()."[@name='".$field_id."']"; } protected function queryFieldDescription($field_id) { return $this->queryField($field_id)."/x:DESCRIPTION"; } protected function queryData() { return $this->queryTable()."/x:DATA"; } protected function queryTableData() { return $this->queryData()."/x:TABLEDATA"; } protected function queryTR() { return $this->queryTableData()."/x:TR"; } protected function queryBinaryData() { return $this->queryData()."/x:BINARY"; } protected function queryStream() { return $this->queryBinaryData()."/x:STREAM"; } // public function getVersion() { if (!$this->xml) return ''; $root = $this->xml->documentElement; return $root->getAttribute('version'); } public function getDescription() { if (!$this->xp) return ''; $desc = $this->xp->query($this->queryDescription()); if ($desc->length < 1) return ''; return $desc->item(0)->nodeValue; } public function isData() { $trs = $this->xml->getElementsByTagName('TR'); if ($trs->length > 0) return true; return false; } public function getFirstTR() { if (!isset($this->xp)) return NULL; $tabledatas = $this->xp->query($this->queryTableData()); if ($tabledatas->length < 1) return NULL; $tabledata = $tabledatas->item(0); $node = $tabledata->firstChild; if (!isset($node)) return NULL; if (($node->nodeType != XML_ELEMENT_NODE) || ($node->nodeName != "TR")) return $this->getNextTR($node); return $node; } public function getNextTR($tr) { if (!isset($this->xp) || !isset($tr) || !isset($tr->nextSibling)) return NULL; if (($tr->nextSibling->nodeType != XML_ELEMENT_NODE) || ($tr->nextSibling->nodeName != "TR")) return $this->getNextTR($tr->nextSibling); return $tr->nextSibling; } public function getTDValueByFieldIndex($tr,$field_index) { if (!$this->xp) return NULL; $tds = $tr->getElementsByTagName("TD"); if (($tds->length < 1) || ($field_index >= $tds->length)) return NULL; return $tds->item($field_index)->nodeValue; } protected function isTimeField($field) { if (!$this->xp) return FALSE; return (($field->getAttribute("ucd") == "time.epoch") && ($field->getAttribute("xtype") == "dateTime") || ($field->getAttribute("ucd") == "TIME") && ($field->getAttribute("unit") == "iso-8601")); } public function getTimeFieldIndex() { if (!$this->xp) return -1; $fields = $this->xp->query($this->queryFields()); if ($fields->length < 1) return -1; for ($i = 0; $i < $fields->length; $i++) if ($this->isTimeField($fields->item($i))) return $i; return -1; } protected function getFieldByID($field_id) { if (!$this->xp) return NULL; $fields = $this->xp->query($this->queryFields()); if ($fields->length < 1) return NULL; foreach ($fields as $field) if ($field->getAttribute("ID") == $field_id) return $field; return NULL; } protected function getFieldByName($field_id) { if (!$this->xp) return NULL; $fields = $this->xp->query($this->queryFieldByName($field_id)); if ($fields->length < 1) return NULL; foreach ($fields as $field) if ($field->getAttribute("name") == $field_id) return $field; return NULL; } protected function checkIDAttribute() { $fields = $this->xml->getElementsByTagName('FIELD'); $i = 0; foreach ($fields as $field) { $i++; if (!$field->hasAttribute("ID")) { $field->setAttribute("ID", "col".$i); } } $this->xml->saveXML(); } public function getFieldIndexByID($field_id) { if (!$this->xp) return -1; $fields = $this->xp->query($this->queryFields()); if ($fields->length < 1) return -1; for ($i = 0; $i < $fields->length; $i++) if ($fields->item($i)->getAttribute("ID") == $field_id) return $i; return -1; } public function getStartStop() { if (!$this->xp) return '0 0'; $timeIndex = $this->getTimeFieldIndex(); if ($timeIndex < 0) return '0 0'; $tr = $this->getFirstTR(); if (!isset($tr)) return '0 0'; $start = $this->getTDValueByFieldIndex($tr,$timeIndex); $stop = $start; while (isset($tr)) { $stop = $this->getTDValueByFieldIndex($tr,$timeIndex); $tr = $this->getNextTR($tr); } if (!isset($start)) $start = 0; else $start = strtotime($start); if (!isset($stop)) $stop = 0; else $stop = strtotime($stop); return $start." ".$stop; } public function getFieldInfoByID($field_id) { if (!$this->xp) return array("id" => $field_id, "error" => "No file loaded"); $field = $this->getFieldByID($field_id); if (!$field) $field = $this->getFieldByName($field_id); if (!$field) return array("id" => $field_id, "error" => "This field doesn't exist"); return $this->getFieldInfo($field); } /** Get the size of a row according to datatype array length. */ private function get_row_size($field_node) { $datatype = $field_node->getAttribute("datatype"); if($datatype == 'boolean') { return 1; } switch($datatype) { case 'unsignedByte': case 'char': $block_size = 1; break; case 'unicodeChar': case 'short': $block_size = 2; break; case 'int': case 'float': $block_size = 4; break; case 'long': case 'double': case 'float_complex': $block_size = 8; break; case 'double_complex': $block_size = 16; default: $block_size = 0; break; } if($field_node->getAttribute("arraysize") == NULL) { $array_size = $block_size; } else if("*" == $field_node->getAttribute("arraysize")) { $array_size = unpack("Ns", substr($this->stream, $this->c, 4))["s"] * $block_size; $this->c+=4; } else { $array_size = (int)($field_node->getAttribute("arraysize")) * $block_size; } return $array_size; } /** Get the VOTable stream content.*/ public function parseStream() { if (! $this->isValidSchema()) { error_log('There is an error on the VOTable: ' . $this->votable_error); return null; } $data = Array(); $fields = $this->xp->query($this->queryFields()); $nb_columns = $fields->length; $row = Array(); $n_value = 0; // index of current value $this->c = 0; // initialize cursor position. $query_stream = $this->xp->query($this->queryStream())->item(0); if($query_stream == NULL) { $this->votable_error = "There is no STREAM node in the VOTable file."; return null; } $this->stream = base64_decode($query_stream->textContent); $stream_len = strlen($this->stream); if($stream_len == 0) { $this->votable_error = "no result"; return null; } while($this->c < strlen($this->stream)) { $col_id = $n_value % $nb_columns; $field_node = $fields->item($col_id); if($col_id == 0) { $row = Array(); } $row[$field_node->getAttribute("ID")] = $this->process_datablock($field_node); if($col_id == $nb_columns-1) { array_push($data, $row); } $n_value+=1; } return $data; } private function JDTodate($jd) { list($month, $day, $year) = split('/', JDToGregorian($jd)); return "$day/$month/$year"; } private function process_datablock($field_node) { $data_type = $field_node->getAttribute("datatype"); $row_size = $this->get_row_size($field_node); $substr = substr($this->stream, $this->c, $row_size); switch ($data_type) { case 'boolean': case 'unsignedByte': $b = $substr; $res = $b == 'T' || $b == 't' || $b == '1'; break; case 'char': $res = $row_size !=0 ? utf8_encode($substr) : NULL; case 'unicodeChar': $res = $row_size !=0 ? utf8_encode(str_replace("\0", '', $substr)) : NULL; break; case 'short': $res = unpack('ss', $substr)['s']; $res = is_nan($res) ? NULL : $res; break; case 'int': $res = unpack('Ns', $substr)['s']; $res = is_nan($res) ? NULL : $res; break; case 'long': $res = unpack('Js', $substr)['s']; // /!\ J -> PHP 5.6 only $res = is_nan($res) ? NULL : $res; break; case 'float': $res = unpack('fs', $substr)['s']; // If machine is little endian: if($this->is_little_endian) { $res = unpack('f1f', strrev(pack('f', $res)))['f']; } $res = is_nan($res) ? NULL : $res; break; case 'double': $res = unpack('ds', $substr)['s']; // If machine is little endian: if($this->is_little_endian) { $res = unpack('d1d', strrev(pack('d', $res)))['d']; } $res = is_nan($res) ? NULL : $res; break; default: $res = NULL; error_log("Unknown datatype: $data_type"); break; } $this->c+=$row_size; return $res; } public function getFieldInfo($field) { if (!$this->xp) return array("id" => $field_id, "error" => "No file loaded"); $description = ''; $desc = $field->getElementsByTagName("DESCRIPTION"); if ($desc->length >= 1) $description = $desc->item(0)->nodeValue; $size = $field->getAttribute("arraysize"); if ($size == '') $size = 1; else $size = intval($size); switch ($field->getAttribute("datatype")) { case "short" : $type = "SHORT"; break; case "int" : $type = "INTEGER"; break; case "long" : case "double" : $type = "DOUBLE"; break; default : $type = "FLOAT"; } if (!$field->getAttribute("ID")) $id = "col".$n; else $id = $field->getAttribute("ID"); return array("id" => $field->getAttribute("ID"), "type" => $type, "name" => $field->getAttribute("name"), "ucd" => $field->getAttribute("ucd"), "unit" => $field->getAttribute("unit"), "size" => $size, "description" => $description ); } public function getFieldsInfo() { if (!$this->xp) return array("error" => "No file loaded"); $fields_info = array(); $fields = $this->xp->query($this->queryFields()); if ($fields->length < 1) return $fields_info; foreach ($fields as $field) { if ($this->isTimeField($field)) continue; array_push($fields_info,$this->getFieldInfo($field)); } return $fields_info; } public function getSamplings() { if (!$this->xp) return array("minSampling" => 0, "maxSampling" => 0); $timeIndex = $this->getTimeFieldIndex(); if ($timeIndex < 0) return array("minSampling" => 0, "maxSampling" => 0); $tr = $this->getFirstTR(); if (!$tr) return array("minSampling" => 0, "maxSampling" => 0); $prevTime = 0; while (isset($tr)) { $time = $this->getTDValueByFieldIndex($tr,$timeIndex); if (isset($time)) { $time = strtotime($time); if (($prevTime > 0) && ($time-$prevTime > 0)) $deltaT[$time-$prevTime]++; $prevTime = $time; } $tr = $this->getNextTR($tr); } $minSampling = +1.e31; $maxSampling = 0.0; foreach ($deltaT as $key => $value) { if ($value/count($deltaT) < 0.10) continue; if ($key < $minSampling) $minSampling = $key; if ($key > $maxSampling) $maxSampling = $key; } return array("minSampling" => $minSampling, "maxSampling" => $maxSampling); } /* * Add vector data made from components to IMPEX VOT */ public function addVectorToVot($paramID, $fileName) { if (strpos($paramID, ',') === false) // LATMOS Ganymede components are defined with spaces! $argsArr = explode(' ',$paramID); else $argsArr = explode(',',$paramID); $fields = $this->xml->getElementsByTagName('FIELD'); $table = $this->xml->getElementsByTagName('TABLE')->item(0); $data = $this->xml->getElementsByTagName('DATA')->item(0); $i=0; $find = false; foreach ($fields as $field) { if ($field->getAttribute('name') == $argsArr[0]) { $unit = $field->getAttribute('unit'); $ucd = $field->getAttribute('ucd'); $datatype = $field->getAttribute('datatype'); $firstTD = $i; $find = true; break; } $i++; } if ($find) { $new_field = $this->xml->createElement('FIELD'); $new_field->setAttribute('ID', $paramID); $new_field->setAttribute('name', $paramID); $new_field->setAttribute('datatype', $datatype); $new_field->setAttribute('arraysize', '3'); $new_field->setAttribute('unit', $unit); $new_field->setAttribute('ucd', $ucd); $colN = $fields->length + 1; $new_field->setAttribute('ID', 'col'.$colN); $table->insertBefore($new_field,$data); $trs = $this->xml->getElementsByTagName('TR'); foreach($trs as $tr) { $tds = $tr->getElementsByTagName('TD'); $value = trim($tds->item($firstTD)->nodeValue).' '.trim($tds->item($firstTD + 1)->nodeValue).' '.trim($tds->item($firstTD + 2)->nodeValue); $td = $this->xml->createElement('TD', $value); $tr->appendChild($td); } $this->xml->save($fileName); } } } ?>