diff --git a/controller/RestController.php b/controller/RestController.php index 0e22e6fab..c3a1c2808 100644 --- a/controller/RestController.php +++ b/controller/RestController.php @@ -200,6 +200,7 @@ public function vocabularyInformation($request) 'skos' => 'http://www.w3.org/2004/02/skos/core#', 'onki' => 'http://schema.onki.fi/onki#', 'dct' => 'http://purl.org/dc/terms/', + 'dcterms' =>'http://purl.org/dc/terms/', 'uri' => '@id', 'type' => '@type', 'title' => 'rdfs:label', @@ -208,6 +209,7 @@ public function vocabularyInformation($request) 'defaultLanguage' => 'onki:defaultLanguage', 'languages' => 'onki:language', 'label' => 'rdfs:label', + 'subject' => 'dcterms:subject', 'prefLabel' => 'skos:prefLabel', 'title' => 'dct:title', '@language' => $request->getLang(), @@ -254,6 +256,7 @@ public function vocabularyStatistics($request) 'void' => 'http://rdfs.org/ns/void#', 'onki' => 'http://schema.onki.fi/onki#', 'uri' => '@id', + 'dcterms' =>'http://purl.org/dc/terms/', 'id' => 'onki:vocabularyIdentifier', 'concepts' => 'void:classPartition', 'label' => 'rdfs:label', @@ -549,6 +552,7 @@ private function returnDataResults($results, $format) { 'dct' => 'http://purl.org/dc/terms/', 'dc11' => 'http://purl.org/dc/elements/1.1/', 'uri' => '@id', + 'dcterms' =>'http://purl.org/dc/terms/', 'type' => '@type', 'lang' => '@language', 'value' => '@value', @@ -734,6 +738,29 @@ public function hierarchy($request) if (empty($results)) { return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>"); } + + // set the "top" key from the "tops" key + foreach ($results as $value) { + $uri = $value['uri']; + if (isset($value['tops'])) { + if ($request->getVocab()->getMainConceptScheme() != null) { + foreach ($results[$uri]['tops'] as $top) { + // if a value in 'tops' matches the main concept scheme, take it + if ($top == $request->getVocab()->getMainConceptScheme()) { + $results[$uri]['top'] = $top; + break; + } + } + // if the main concept scheme was not found, set 'top' to the first 'tops' (sorted alphabetically on the URIs) + if (! isset($results[$uri]['top'])) { + $results[$uri]['top'] = $results[$uri]['tops'][0]; + } + } else { + // no main concept scheme set on the vocab, take the first value of 'tops' (sorted alphabetically) + $results[$uri]['top'] = $results[$uri]['tops'][0]; + } + } + } if ($request->getVocab()->getConfig()->getShowHierarchy()) { $schemes = $request->getVocab()->getConceptSchemes($request->getLang()); @@ -743,12 +770,13 @@ public function hierarchy($request) } } - + /* encode the results in a JSON-LD compatible array */ $topconcepts = $request->getVocab()->getTopConcepts(array_keys($schemes), $request->getLang()); + foreach ($topconcepts as $top) { if (!isset($results[$top['uri']])) { - $results[$top['uri']] = array('uri' => $top['uri'], 'top' => $top['topConceptOf'], 'prefLabel' => $top['label'], 'hasChildren' => $top['hasChildren']); + $results[$top['uri']] = array('uri' => $top['uri'], 'top'=>$top['topConceptOf'], 'tops'=>array($top['topConceptOf']), 'prefLabel' => $top['label'], 'hasChildren' => $top['hasChildren']); if (isset($top['notation'])) { $results[$top['uri']]['notation'] = $top['notation']; } diff --git a/model/GlobalConfig.php b/model/GlobalConfig.php index 4080e94fe..8138a0b5b 100644 --- a/model/GlobalConfig.php +++ b/model/GlobalConfig.php @@ -1,243 +1,243 @@ -languages = $LANGUAGES; - } - } catch (Exception $e) { - echo "Error: " . $e->getMessage(); - return; - } - } - - private function getConstant($name, $default) - { - if (defined($name) && constant($name)) { - return constant($name); - } - return $default; - } - - /** - * Returns the UI languages specified in the configuration or defaults to - * only show English - * @return array - */ - public function getLanguages() - { - if ($this->languages) { - return $this->languages; - } - return array('en' => 'en_GB.utf8'); - } - - /** - * Returns the vocabulary configuration file specified the configuration - * or vocabularies.ttl if not found. - * @return string - */ - public function getVocabularyConfigFile() - { - return $this->getConstant('VOCABULARIES_FILE', 'vocabularies.ttl'); - } - - /** - * Returns the external HTTP request timeout in seconds or the default value - * of 5 seconds if not specified in the configuration. - * @return integer - */ - public function getHttpTimeout() - { - return $this->getConstant('HTTP_TIMEOUT', 5); - } - - /** - * Returns the SPARQL HTTP request timeout in seconds or the default value - * of 20 seconds if not specified in the configuration. - * @return integer - */ - public function getSparqlTimeout() - { - return $this->getConstant('SPARQL_TIMEOUT', 20); - } - - /** - * Returns the sparql endpoint address defined in the configuration. If - * not then defaulting to http://localhost:3030/ds/sparql - * @return string - */ - public function getDefaultEndpoint() - { - return $this->getConstant('DEFAULT_ENDPOINT', 'http://localhost:3030/ds/sparql'); - } - - /** - * @return string - */ - public function getSparqlGraphStore() - { - return $this->getConstant('SPARQL_GRAPH_STORE', null); - } - - /** - * Returns the maximum number of items to return in transitive queries if defined - * in the configuration or the default value of 1000. - * @return integer - */ - public function getDefaultTransitiveLimit() - { - return $this->getConstant('DEFAULT_TRANSITIVE_LIMIT', 1000); - } - - /** - * Returns the maximum number of items to load at a time if defined - * in the configuration or the default value of 20. - * @return integer - */ - public function getSearchResultsSize() - { - return $this->getConstant('SEARCH_RESULTS_SIZE', 20); - } - - /** - * Returns the configured location for the twig template cache and if not - * defined defaults to "/tmp/skosmos-template-cache" - * @return string - */ - public function getTemplateCache() - { - return $this->getConstant('TEMPLATE_CACHE', '/tmp/skosmos-template-cache'); - } - - /** - * Returns the defined sparql-query extension eg. "JenaText" or - * if not defined falling back to SPARQL 1.1 - * @return string - */ - public function getDefaultSparqlDialect() - { - return $this->getConstant('DEFAULT_SPARQL_DIALECT', 'Generic'); - } - - /** - * Returns the feedback address defined in the configuration. - * @return string - */ - public function getFeedbackAddress() - { - return $this->getConstant('FEEDBACK_ADDRESS', null); - } - - /** - * Returns true if exception logging has been configured. - * @return boolean - */ - public function getLogCaughtExceptions() - { - return $this->getConstant('LOG_CAUGHT_EXCEPTIONS', FALSE); - } - - /** - * Returns true if browser console logging has been enabled, - * @return boolean - */ - public function getLoggingBrowserConsole() - { - return $this->getConstant('LOG_BROWSER_CONSOLE', FALSE); - } - - /** - * Returns the name of a log file if configured, or NULL otherwise. - * @return string - */ - public function getLoggingFilename() - { - return $this->getConstant('LOG_FILE_NAME', null); - } - - /** - * @return string - */ - public function getServiceName() - { - return $this->getConstant('SERVICE_NAME', 'Skosmos'); - } - - /** - * @return string - */ - public function getServiceTagline() - { - return $this->getConstant('SERVICE_TAGLINE', null); - } - - /** - * @return string - */ - public function getServiceLogo() - { - return $this->getConstant('SERVICE_LOGO', null); - } - - /** - * @return string - */ - public function getCustomCss() - { - return $this->getConstant('CUSTOM_CSS', null); - } - - /** - * @return boolean - */ - public function getUiLanguageDropdown() - { - return $this->getConstant('UI_LANGUAGE_DROPDOWN', FALSE); - } - - /** - * @return string - */ - public function getBaseHref() - { - return $this->getConstant('BASE_HREF', null); - } - - /** - * @return string - */ - public function getGlobalPlugins() - { - return explode(' ', $this->getConstant('GLOBAL_PLUGINS', null)); - } - - /** - * @return boolean - */ - public function getHoneypotEnabled() - { - return $this->getConstant('UI_HONEYPOT_ENABLED', TRUE); - } - - /** - * @return integer - */ - public function getHoneypotTime() - { - return $this->getConstant('UI_HONEYPOT_TIME', 5); - } - -} +languages = $LANGUAGES; + } + } catch (Exception $e) { + echo "Error: " . $e->getMessage(); + return; + } + } + + private function getConstant($name, $default) + { + if (defined($name) && constant($name)) { + return constant($name); + } + return $default; + } + + /** + * Returns the UI languages specified in the configuration or defaults to + * only show English + * @return array + */ + public function getLanguages() + { + if ($this->languages) { + return $this->languages; + } + return array('en' => 'en_GB.utf8'); + } + + /** + * Returns the vocabulary configuration file specified the configuration + * or vocabularies.ttl if not found. + * @return string + */ + public function getVocabularyConfigFile() + { + return $this->getConstant('VOCABULARIES_FILE', 'vocabularies.ttl'); + } + + /** + * Returns the external HTTP request timeout in seconds or the default value + * of 5 seconds if not specified in the configuration. + * @return integer + */ + public function getHttpTimeout() + { + return $this->getConstant('HTTP_TIMEOUT', 5); + } + + /** + * Returns the SPARQL HTTP request timeout in seconds or the default value + * of 20 seconds if not specified in the configuration. + * @return integer + */ + public function getSparqlTimeout() + { + return $this->getConstant('SPARQL_TIMEOUT', 20); + } + + /** + * Returns the sparql endpoint address defined in the configuration. If + * not then defaulting to http://localhost:3030/ds/sparql + * @return string + */ + public function getDefaultEndpoint() + { + return $this->getConstant('DEFAULT_ENDPOINT', 'http://localhost:3030/ds/sparql'); + } + + /** + * @return string + */ + public function getSparqlGraphStore() + { + return $this->getConstant('SPARQL_GRAPH_STORE', null); + } + + /** + * Returns the maximum number of items to return in transitive queries if defined + * in the configuration or the default value of 1000. + * @return integer + */ + public function getDefaultTransitiveLimit() + { + return $this->getConstant('DEFAULT_TRANSITIVE_LIMIT', 1000); + } + + /** + * Returns the maximum number of items to load at a time if defined + * in the configuration or the default value of 20. + * @return integer + */ + public function getSearchResultsSize() + { + return $this->getConstant('SEARCH_RESULTS_SIZE', 20); + } + + /** + * Returns the configured location for the twig template cache and if not + * defined defaults to "/tmp/skosmos-template-cache" + * @return string + */ + public function getTemplateCache() + { + return $this->getConstant('TEMPLATE_CACHE', 'tmp/skosmos-template-cache'); + } + + /** + * Returns the defined sparql-query extension eg. "JenaText" or + * if not defined falling back to SPARQL 1.1 + * @return string + */ + public function getDefaultSparqlDialect() + { + return $this->getConstant('DEFAULT_SPARQL_DIALECT', 'Generic'); + } + + /** + * Returns the feedback address defined in the configuration. + * @return string + */ + public function getFeedbackAddress() + { + return $this->getConstant('FEEDBACK_ADDRESS', null); + } + + /** + * Returns true if exception logging has been configured. + * @return boolean + */ + public function getLogCaughtExceptions() + { + return $this->getConstant('LOG_CAUGHT_EXCEPTIONS', FALSE); + } + + /** + * Returns true if browser console logging has been enabled, + * @return boolean + */ + public function getLoggingBrowserConsole() + { + return $this->getConstant('LOG_BROWSER_CONSOLE', FALSE); + } + + /** + * Returns the name of a log file if configured, or NULL otherwise. + * @return string + */ + public function getLoggingFilename() + { + return $this->getConstant('LOG_FILE_NAME', null); + } + + /** + * @return string + */ + public function getServiceName() + { + return $this->getConstant('SERVICE_NAME', 'Skosmos'); + } + + /** + * @return string + */ + public function getServiceTagline() + { + return $this->getConstant('SERVICE_TAGLINE', null); + } + + /** + * @return string + */ + public function getServiceLogo() + { + return $this->getConstant('SERVICE_LOGO', null); + } + + /** + * @return string + */ + public function getCustomCss() + { + return $this->getConstant('CUSTOM_CSS', null); + } + + /** + * @return boolean + */ + public function getUiLanguageDropdown() + { + return $this->getConstant('UI_LANGUAGE_DROPDOWN', FALSE); + } + + /** + * @return string + */ + public function getBaseHref() + { + return $this->getConstant('BASE_HREF', null); + } + + /** + * @return string + */ + public function getGlobalPlugins() + { + return explode(' ', $this->getConstant('GLOBAL_PLUGINS', null)); + } + + /** + * @return boolean + */ + public function getHoneypotEnabled() + { + return $this->getConstant('UI_HONEYPOT_ENABLED', TRUE); + } + + /** + * @return integer + */ + public function getHoneypotTime() + { + return $this->getConstant('UI_HONEYPOT_TIME', 5); + } + +} diff --git a/model/Model.php b/model/Model.php index 2452b2109..cda20781a 100644 --- a/model/Model.php +++ b/model/Model.php @@ -57,7 +57,6 @@ public function __construct($config) public function getConfig() { return $this->globalConfig; } - /** * Initializes the configuration from the vocabularies.ttl file */ diff --git a/model/Vocabulary.php b/model/Vocabulary.php index 194a0f64d..44b46c4ec 100644 --- a/model/Vocabulary.php +++ b/model/Vocabulary.php @@ -229,6 +229,21 @@ public function getDefaultConceptScheme() return array_pop($conceptSchemeURIs); } + /** + * Return the URI of the default concept scheme of this vocabulary, as set in the vocabulary configuration, or null + * if not set. + * @return string default concept scheme URI, or null if undefined + **/ + + public function getMainConceptScheme(){ + $conceptScheme = $this->resource->get("skosmos:mainConceptScheme"); + if ($conceptScheme) { + return $conceptScheme->getUri(); + }else{ + return null; + } + } + /** * Return the top concepts of a concept scheme in the vocabulary. * @param string $conceptScheme URI of concept scheme whose top concepts to return. If not set, diff --git a/model/sparql/GenericSparql.php b/model/sparql/GenericSparql.php index 544229b6e..2ce6511c4 100644 --- a/model/sparql/GenericSparql.php +++ b/model/sparql/GenericSparql.php @@ -31,6 +31,9 @@ class GenericSparql { */ private $qnamecache = array(); + + + /** * Requires the following three parameters. * @param string $endpoint SPARQL endpoint address. @@ -595,9 +598,14 @@ public function queryConceptScheme($conceptscheme) { private function generateQueryConceptSchemesQuery($lang) { $fcl = $this->generateFromClause(); $query = <<label)) { $conceptscheme['label'] = $row->label->getValue(); @@ -637,6 +648,11 @@ private function transformQueryConceptSchemesResults($result) { if (isset($row->title)) { $conceptscheme['title'] = $row->title->getValue(); } + // add dcterms:subject and their labels in the result + if(isset($row->domain) && isset($row->domainLabel)){ + $conceptscheme['subject']['uri']=$row->domain->getURI(); + $conceptscheme['subject']['prefLabel']=$row->domainLabel->getValue(); + } $ret[$row->cs->getURI()] = $conceptscheme; } @@ -1066,6 +1082,7 @@ private function transformConceptSearchResult($row, $vocabs, $fields) $hit['prefLabel'] = $row->label->getValue(); } + if (isset($row->label)) { $hit['lang'] = $row->label->getLang(); } @@ -1743,7 +1760,8 @@ private function generateParentListQuery($uri, $lang, $fallback, $props) { $propertyClause = implode('|', $props); $query = << a skos:Concept . OPTIONAL { @@ -1756,7 +1774,8 @@ private function generateParentListQuery($uri, $lang, $fallback, $props) { ?broad skos:prefLabel ?lab . FILTER (langMatches(lang(?lab), "$fallback")) } - OPTIONAL { # fallback - other language case + OPTIONAL { + # fallback - other language case# ?broad skos:prefLabel ?lab . } OPTIONAL { ?broad skos:notation ?nota . } @@ -1770,7 +1789,8 @@ private function generateParentListQuery($uri, $lang, $fallback, $props) { ?children skos:prefLabel ?childlab . FILTER (langMatches(lang(?childlab), "$fallback")) } - OPTIONAL { # fallback - other language case + OPTIONAL { + # fallback - other language case# ?children skos:prefLabel ?childlab . } OPTIONAL { @@ -1795,6 +1815,7 @@ private function generateParentListQuery($uri, $lang, $fallback, $props) { private function transformParentListResults($result, $lang) { $ret = array(); + $topConceptsList=array(); foreach ($result as $row) { if (!isset($row->broad)) { // existing concept but no broaders @@ -1807,8 +1828,10 @@ private function transformParentListResults($result, $lang) if (isset($row->exact)) { $ret[$uri]['exact'] = $row->exact->getUri(); } - if (isset($row->top)) { - $ret[$uri]['top'] = $row->top->getUri(); + if (isset($row->tops)) { + $topConceptsList=explode(" ", $row->tops->getValue()); + sort($topConceptsList); + $ret[$uri]['tops'] =$topConceptsList; } if (isset($row->children)) { if (!isset($ret[$uri]['narrower'])) { diff --git a/resource/js/hierarchy.js b/resource/js/hierarchy.js index 19b069381..86b5fe85c 100644 --- a/resource/js/hierarchy.js +++ b/resource/js/hierarchy.js @@ -128,22 +128,68 @@ function createConceptObject(conceptUri, conceptData) { * @param {Object} parentData */ function attachTopConceptsToSchemes(schemes, currentNode, parentData) { + var foundFirstLevel = false; + for (var i = 0; i < schemes.length; i++) { - if (schemes[i].uri === parentData[currentNode.uri].top) { - if(Object.prototype.toString.call(schemes[i].children) !== '[object Array]' ) { - schemes[i].children = []; - } - schemes[i].children.push(currentNode); - // the hierarchy response contains the parent info before the topConcepts so it's a safe to open the first one without broaders - if (!schemes.opened && !currentNode.broader) { - schemes[i].state = currentNode.state; - schemes.opened = true; - } - } + foundFirstLevel = true; + + // search if top concept uri is equal to scheme uri on first level + // search in all the 'tops' key + for (var j = 0; j < parentData[currentNode.uri].tops.length; j++) { + var currentTop = parentData[currentNode.uri].tops[j]; + // we found a scheme at the first level that is equal to one of the 'tops' value + if(schemes[i].uri===currentTop){ + if(Object.prototype.toString.call(schemes[i].children) !== '[object Array]' ) { + schemes[i].children = []; + } + // append to a first-level node that corresponds to a concept scheme + schemes[i].children.push(currentNode); + + // the hierarchy response contains the parent info before the topConcepts so it's a safe to open the first one without broaders + if (!schemes[i].opened && !currentNode.broader) { + schemes[i].state = currentNode.state; + schemes.opened = true; + } + break; + } + } + + // search if top concept uri is equal to scheme children uri, if there are children (second level) + // this may be the case if schemes are organised by subjects : subjects will be first-level nodes, concepts schemes will be second level + for (var h = 0; h 1) ? schemes : []; - - for(var conceptUri in parentData) { - if (parentData.hasOwnProperty(conceptUri)) { - var branchHelper, - exactMatchFound; - currentNode = createConceptObject(conceptUri, parentData[conceptUri]); - /* if a node has the property topConceptOf set it as the root node. - * Or just setting the last node as a root if nothing else has been found - */ - if (parentData[conceptUri].top || ( loopIndex === Object.size(parentData)-1) && rootArray.length === 0 || !currentNode.parents && rootArray.length === 0) { - if (rootArray.length === 0) { - branchHelper = currentNode; - } - // if there are multiple concept schemes attach the topConcepts to the concept schemes - if (schemes.length > 1 && (parentData[conceptUri].top)) { - schemes = attachTopConceptsToSchemes(schemes, currentNode, parentData); - } - else { - rootArray.push(currentNode); - } - } - if (exactMatchFound) { // combining branches if we have met a exact match during the previous iteration. - currentNode.children.push(branchHelper); - branchHelper = undefined; - exactMatchFound = false; - } - // here we have iterated far enough to find the merging point of the trees. - if (branchHelper && parentData[branchHelper.uri].exact === currentNode.uri) { - exactMatchFound = true; - } - setNode(currentNode); - loopIndex++; - } - } + if (parentData === undefined || parentData === null) { return; } + + var loopIndex = 0, // for adding the last concept as a root if no better candidates have been found. + currentNode, + rootArray = (schemes.length > 1) ? schemes : []; + // console.log(JSON.stringify(parentData, null, 4)); + for(var conceptUri in parentData) { + if (parentData.hasOwnProperty(conceptUri)) { + var branchHelper, + exactMatchFound; + currentNode = createConceptObject(conceptUri, parentData[conceptUri]); + /* if a node has the property topConceptOf set it as the root node. + * Or just setting the last node as a root if nothing else has been found + */ + if (parentData[conceptUri].top || ( loopIndex === Object.size(parentData)-1) && rootArray.length === 0 || !currentNode.parents && rootArray.length === 0) { + if (rootArray.length === 0) { + branchHelper = currentNode; + } + // if there are multiple concept schemes attach the topConcepts to the concept schemes + if (schemes.length > 1 && (parentData[conceptUri].top)) { + schemes = attachTopConceptsToSchemes(schemes, currentNode, parentData); - // Iterating over the nodes to make sure all concepts have their children set. - appendChildrenToParents(); - // avoiding the issue with multiple inheritance by deep copying the whole tree object before giving it to jsTree - return JSON.parse(JSON.stringify(rootArray)); + } + else { + rootArray.push(currentNode); + } + } + if (exactMatchFound) { // combining branches if we have met a exact match during the previous iteration. + currentNode.children.push(branchHelper); + branchHelper = undefined; + exactMatchFound = false; + } + // here we have iterated far enough to find the merging point of the trees. + if (branchHelper && parentData[branchHelper.uri].exact === currentNode.uri) { + exactMatchFound = true; + } + setNode(currentNode); + loopIndex++; + } + } + + // Iterating over the nodes to make sure all concepts have their children set. + appendChildrenToParents(); + // avoiding the issue with multiple inheritance by deep copying the whole tree object before giving it to jsTree + + // console.log(JSON.stringify(rootArray, null, 4)); + return JSON.parse(JSON.stringify(rootArray)); } function getConceptHref(conceptData) { + if (conceptData.uri.indexOf(window.uriSpace) !== -1) { var page = conceptData.uri.substr(window.uriSpace.length); if (/[^a-zA-Z0-9\.]/.test(page) || page.indexOf("/") > -1 ) { @@ -259,19 +309,22 @@ function appendChildrenToParents() { function createObjectsFromNarrowers(narrowerResponse) { var childArray = []; - for (var i = 0; i < narrowerResponse.narrower.length; i++) { - var conceptObject = narrowerResponse.narrower[i]; - var childObject = { - text : getLabel(conceptObject), - a_attr : getConceptHref(conceptObject), - uri: conceptObject.uri, - parents: narrowerResponse.uri, - state: { opened: false, disabled: false, selected: false } - }; - childObject.children = conceptObject.hasChildren ? true : false; - setNode(childObject); - childArray.push(childObject); - } + + for (var i = 0; i < narrowerResponse.narrower.length; i++) { + var conceptObject = narrowerResponse.narrower[i]; + var childObject = { + text : getLabel(conceptObject), + a_attr : getConceptHref(conceptObject), + uri: conceptObject.uri, + parents: narrowerResponse.uri, + state: { opened: false, disabled: false, selected: false } + }; + childObject.children = conceptObject.hasChildren ? true : false; + setNode(childObject); + childArray.push(childObject); + } + + return childArray; } @@ -281,7 +334,7 @@ function getParams(node) { return $.param({'uri' : nodeId, 'lang' : clang}); } -function pickLabelFromScheme(scheme) { +function pickLabel(scheme) { var label = ''; if (scheme.prefLabel) label = scheme.prefLabel; @@ -294,20 +347,96 @@ function pickLabelFromScheme(scheme) { function schemeRoot(schemes) { var topArray = []; + + + // Step 1 : gather domain list + var domains=[]; for (var i = 0; i < schemes.length; i++) { - var scheme = schemes[i]; - var label = pickLabelFromScheme(scheme); - if (label !== '') { // hiding schemes without a label/title - var schemeObject = { - text: label, - a_attr : { "href" : vocab + '/' + lang + '/page/?uri=' + scheme.uri, 'class': 'scheme'}, - uri: scheme.uri, - children: true, - state: { opened: false } - }; - topArray.push(schemeObject); + if(schemes[i].subject != null) { + var schemeDomain = schemes[i].subject.uri; + + // test if domain was already found + var found = false; + for (var k = 0; k < domains.length; k++) { + if(domains[k].uri===schemeDomain){ + found = true; + break; + } + } + + // if not found, store it in domain list + if(!found) { + domains.push(schemes[i].subject); + } } } + + // Step 2 : create tree nodes for each domain + + for (var i = 0; i < domains.length; i++) { + var theDomain = domains[i]; + var theDomainLabel = pickLabel(theDomain); + + // avoid creating entries with empty labels + if(theDomainLabel != '') { + // Step 2.1 : create domain node without children + var domainObject = { + text: theDomainLabel, + // note that the class 'domain' will make sure the node will be sorted _before_ others (see the 'sort' functio nat the end) + a_attr : { "href" : vocab + '/' + lang + '/page/?uri=' + theDomain.uri, 'class': 'domain'}, + uri: theDomain.uri, + children: [], + state: { opened: false } + }; + + // Step 2.2 : find the concept schemes in this domain and add them as children + for (var k = 0; k < schemes.length; k++) { + var theScheme = schemes[k]; + var theSchemeLabel = pickLabel(theScheme); + + // avoid creating entries with empty labels + if(theSchemeLabel != '') { + if((theScheme.subject) != null && (theScheme.subject.uri===theDomain.uri)) { + domainObject.children.push( + { + text: theSchemeLabel, + a_attr:{ "href" : vocab + '/' + lang + '/page/?uri=' + theScheme.uri, 'class': 'scheme'}, + uri: theScheme.uri, + children: true, + state: { opened: false } + } + ); + } + } + } + topArray.push(domainObject); + } + + } + + // Step 3 : add the schemes without any subjects after the subjects node + for (var k = 0; k < schemes.length; k++) { + var theScheme = schemes[k]; + + if(theScheme.subject == null) { + // avoid creating entries with empty labels + var theSchemeLabel = pickLabel(theScheme); + if(theSchemeLabel != '') { + topArray.push( + { + text:theSchemeLabel, + a_attr:{ "href" : vocab + '/' + lang + '/page/?uri=' + theScheme.uri, 'class': 'scheme'}, + uri: theScheme.uri, + children: true, + state: { opened: false } + } + ); + } + } + } + + // console.log(JSON.stringify(topArray, null, 4)); + return topArray; } @@ -325,6 +454,7 @@ function addConceptsToScheme(topConcept, childObject, schemes) { return schemes; } + function topConceptsToSchemes(topConcepts, schemes) { var childArray = schemes.length > 1 ? schemes : []; for (var i in topConcepts) { @@ -427,7 +557,29 @@ function getTreeConfiguration() { } }, 'plugins' : ['sort'], - 'sort' : function (a,b) { return naturalCompare(this.get_text(a).toLowerCase(), this.get_text(b).toLowerCase()); } + 'sort' : function (a,b) { + var nodeA = this.get_node(a); + var nodeB = this.get_node(b); + // make sure the tree nodes with class 'domain' are sorted before the others + if(nodeA.a_attr && nodeA.a_attr['class']) { + if(nodeB.a_attr && nodeB.a_attr['class']) { + if(nodeA.a_attr['class'] == 'domain' && nodeB.a_attr['class'] == 'domain') { + return naturalCompare(this.get_text(a).toLowerCase(), this.get_text(b).toLowerCase()); + } + else if(nodeA.a_attr['class'] == 'domain' && nodeB.a_attr['class'] != 'domain') { + return -1; + } + else if(nodeA.a_attr['class'] != 'domain' && nodeB.a_attr['class'] == 'domain') { + return 1; + } + } else { + return naturalCompare(this.get_text(a).toLowerCase(), this.get_text(b).toLowerCase()); + } + } else { + return naturalCompare(this.get_text(a).toLowerCase(), this.get_text(b).toLowerCase()); + } + // return naturalCompare(this.get_text(a).toLowerCase(), this.get_text(b).toLowerCase()); + } }); } diff --git a/rest.php b/rest.php index 6b0e5ea89..b964ddfa2 100644 --- a/rest.php +++ b/rest.php @@ -7,6 +7,7 @@ header("Access-Control-Allow-Origin: *"); // enable CORS for the whole REST API + try { $config = new GlobalConfig(); $model = new Model($config); diff --git a/tests/GenericSparqlTest.php b/tests/GenericSparqlTest.php index 7afc2065e..c3d2144fd 100644 --- a/tests/GenericSparqlTest.php +++ b/tests/GenericSparqlTest.php @@ -350,6 +350,49 @@ public function testQueryConceptSchemes() $this->assertEquals('Test conceptscheme', $label['label']); } } + + /** + * @covers GenericSparql::queryConceptSchemes + * @covers GenericSparql::generateQueryConceptSchemesQuery + * @covers GenericSparql::transformQueryConceptSchemesResults + */ + public function testQueryConceptSchemesSubject() + { + $sparql = new GenericSparql('http://localhost:3030/ds/sparql', 'http://www.skosmos.skos/test-concept-schemes/', $this->model); + + $actual = $sparql->queryConceptSchemes('en'); + $expected = array( + 'http://exemple.fr/domains' => array( + 'prefLabel' => 'Special Domains Concept Scheme' + ), + 'http://exemple.fr/mt1' => array( + 'prefLabel' => 'Micro-Thesaurus 1', + 'subject' => array( + 'uri' => 'http://exemple.fr/d1', + 'prefLabel' => 'Domain 1' + ) + ), + 'http://exemple.fr/mt2' => array( + 'prefLabel' => 'Micro-Thesaurus 2', + 'subject' => array( + 'uri' => 'http://exemple.fr/d1', + 'prefLabel' => 'Domain 1' + ) + ), + 'http://exemple.fr/mt3' => array( + 'prefLabel' => 'Micro-Thesaurus 3', + 'subject' => array( + 'uri' => 'http://exemple.fr/d2', + 'prefLabel' => 'Domain 2' + ) + ), + 'http://exemple.fr/thesaurus' => array( + 'prefLabel' => 'The Thesaurus' + ), + ); + + $this->assertEquals($expected, $actual); + } /** * @covers GenericSparql::queryConcepts @@ -738,7 +781,7 @@ public function testQueryParentList() ); $props = array ( 'uri' => 'http://www.skosmos.skos/test/ta1', - 'top' => 'http://www.skosmos.skos/test/conceptscheme', + 'tops' => array('http://www.skosmos.skos/test/conceptscheme'), 'prefLabel' => 'Fish', ); $narrowers = array ( diff --git a/tests/VocabularyTest.php b/tests/VocabularyTest.php index cf5eebb2b..36764fd00 100644 --- a/tests/VocabularyTest.php +++ b/tests/VocabularyTest.php @@ -434,6 +434,6 @@ public function testGetConceptInfo() { $this->assertInstanceOf('Concept', $concept[0]); $this->assertEquals(1, sizeof($concept)); } - + } diff --git a/tests/test-vocab-data/test-concept-schemes.ttl b/tests/test-vocab-data/test-concept-schemes.ttl new file mode 100644 index 000000000..9dca0ce4d --- /dev/null +++ b/tests/test-vocab-data/test-concept-schemes.ttl @@ -0,0 +1,56 @@ +@prefix skos: . +@prefix ex: . +@prefix dcterms: . + +ex:thesaurus a skos:ConceptScheme ; + skos:prefLabel "The Thesaurus"@en . + +ex:mt1 a skos:ConceptScheme ; + skos:prefLabel "Micro-Thesaurus 1"@en ; + dcterms:subject ex:d1 . + +ex:mt2 a skos:ConceptScheme ; + skos:prefLabel "Micro-Thesaurus 2"@en ; + dcterms:subject ex:d1 . + +ex:mt3 a skos:ConceptScheme ; + skos:prefLabel "Micro-Thesaurus 3"@en ; + dcterms:subject ex:d2 . + +### Begin Domains + +ex:domains a skos:ConceptScheme ; + skos:prefLabel "Special Domains Concept Scheme"@en . + +ex:d1 a skos:Concept ; + skos:inScheme ex:domains ; + skos:topConceptOf ex:domains ; + skos:prefLabel "Domain 1"@en . + +ex:d2 a skos:Concept ; + skos:inScheme ex:domains ; + skos:topConceptOf ex:domains ; + skos:prefLabel "Domain 2"@en . + +#### End Domains + +ex:c1 a skos:Concept ; + skos:prefLabel "Concept 1"@en ; + skos:inScheme ex:thesaurus , ex:mt1 ; + skos:topConceptOf ex:thesaurus , ex:mt1 ; + skos:narrower ex:c1.1 . + +ex:c1.1 a skos:Concept ; + skos:prefLabel "Concept 1.1"@en ; + skos:inScheme ex:thesaurus , ex:mt1 ; + skos:broader ex:c1 . + +ex:c2 a skos:Concept ; + skos:prefLabel "Concept 2"@en ; + skos:inScheme ex:thesaurus , ex:mt2 ; + skos:topConceptOf ex:thesaurus , ex:mt2 . + +ex:c3 a skos:Concept ; + skos:prefLabel "Concept 3"@en ; + skos:inScheme ex:thesaurus , ex:mt3 ; + skos:topConceptOf ex:thesaurus , ex:mt3 . \ No newline at end of file diff --git a/tests/testvocabularies.ttl b/tests/testvocabularies.ttl index 69ce68b97..0eadcff52 100644 --- a/tests/testvocabularies.ttl +++ b/tests/testvocabularies.ttl @@ -65,6 +65,19 @@ skosmos:sparqlGraph . +:test-concept-schemes a skosmos:Vocabulary, void:Dataset ; + dc11:title "Test concept schemes"@en ; + dc:subject :cat_general ; + void:dataDump , + ; + void:uriSpace "http://www.exemple.fr/"; + skosmos:arrayClass isothes:ThesaurusArray ; + skosmos:groupClass skos:Collection ; + void:sparqlEndpoint ; + skosmos:language "en"; + skosmos:defaultLanguage "en"; + skosmos:sparqlGraph . + :showDeprecated a skosmos:Vocabulary, void:Dataset ; dc11:title "Show deprecated test vocabulary"@en ; dc:subject :cat_general ;