diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5774379f..a64a5b19 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,11 +54,11 @@ jobs: # https://github.com/docker/metadata-action (lower cases image name, etc.) - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v5.4.0 + uses: docker/metadata-action@v5.5.1 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - name: Build and push - uses: docker/build-push-action@v5.1.0 + uses: docker/build-push-action@v5.3.0 with: push: ${{ github.event_name != 'pull_request' && github.actor != 'dependabot[bot]' }} tags: ${{ steps.meta.outputs.tags }} diff --git a/README.md b/README.md index 6011b5b3..50c752f5 100644 --- a/README.md +++ b/README.md @@ -132,58 +132,61 @@ docker run -ti --rm \ ### Options -`-ontFile PATH` [required (unless -ontURI is used)]: Load a local ontology file (from PATH) to document. This option is incompatible with -ontURI +`-analytics CODE`: Add a code snippet for Google analytics to track your HTML documentation. You need to add your CODE next to the flag. For example: UA-1234 -`-ontURI URI` [required (unless -ontFile is used)]: Load an ontology to document from its URI. This option is incompatible with -ontFile +`-confFile PATH`: Load your own configuration file for the ontology metadata. Incompatible with -getOntologyMetadata. See [the configuration documentation](doc/configuration/configuration_doc.md) for more information about the accepted fields. -`-outFolder folderName`: Specifies the name of the folder where to save the documentation. By default is 'myDocumentation' +`-crossRef`: ONLY generate the overview and cross reference sections. The index document will NOT be generated. The htaccess, provenance page, etc., will not be generated unless requested by other flags. This flag is intended to be used only after a first version of the documentation exists. -`-confFile PATH`: Load your own configuration file for the ontology metadata. Incompatible with -getOntologyMetadata. See [the configuration documentation](doc/configuration/configuration_doc.md) for more information about the accepted fields. +`-displayDirectImportsOnly`: Only those imported ontologies that are directly imported in the ontology being documented. -`-getOntologyMetadata`: Extract ontology metadata from the given ontology +`-doNotDisplaySerializations`: The serializations of the ontology will not be displayed. -`-oops`: Create an html page with the evaluation from the OOPS service (http://oops.linkeddata.es/) +`-excludeIntroduction`: Skip the introduction section in the documentation. -`-rewriteAll`: Replace any existing files when documenting an ontology (e.g., from a previous execution) +`-getOntologyMetadata`: Extract ontology metadata from the given ontology -`-crossRef`: ONLY generate the overview and cross reference sections. The index document will NOT be generated. The htaccess, provenance page, etc., will not be generated unless requested by other flags. This flag is intended to be used only after a first version of the documentation exists. +`--help`: Shows a help message and exits. -`-saveConfig PATH`: Save a configuration file on PATH with the properties of a given ontology +`-htaccess`: Create a bundle for publication ready to be deployed on your Apache server. -`-useCustomStyle`: Export the documentation using alternate css files (by Daniel Vila). -`-lang LANG1-LANG2`: Generate documentation in multiple languages (separated by "-"). Note that if the language is not supported, the system will load the labels in english. For example: en-pt-es +`-ignoreIndividuals`: Individuals will not be included in the documentation. + +`-includeAnnotationProperties`: Include annotation properties defined in your ontology in the documentation (by default they are not included) `-includeImportedOntologies`: Indicates whether the terms of the imported ontologies of the current ontology should be documented as well or not. -`-htaccess`: Create a bundle for publication ready to be deployed on your Apache server. +`-import`: imports a local ontology (e.g., if you don't want to load an online ontology, you may load its local version) -`-webVowl`: Create a visualization based on WebVowl (http://vowl.visualdataweb.org/webvowl/index.html#) in the documentation. +`-lang LANG1-LANG2`: Generate documentation in multiple languages (separated by "-"). Note that if the language is not supported, the system will load the labels in english. For example: en-pt-es `-licensius`: Use the Licensius web services (http://licensius.com/apidoc/index.html) to retrieve license metadata. Only works if the -getOntologyMetadata flag is enabled. -`-ignoreIndividuals`: Individuals will not be included in the documentation. +`-noPlaceHolderText`: Do not add any placeholder text (this will remove intro, abstract (if empty) and description sections). -`-includeAnnotationProperties`: Include annotation properties defined in your ontology in the documentation (by default they are not included) +`-ontFile PATH` [required (unless -ontURI is used)]: Load a local ontology file (from PATH) to document. This option is incompatible with -ontURI -`-analytics CODE`: Add a code snippet for Google analytics to track your HTML documentation. You need to add your CODE next to the flag. For example: UA-1234 +`-outFolder folderName`: Specifies the name of the folder where to save the documentation. By default is 'myDocumentation' -`-doNotDisplaySerializations`: The serializations of the ontology will not be displayed. +`-ontURI URI` [required (unless -ontFile is used)]: Load an ontology to document from its URI. This option is incompatible with -ontFile -`-displayDirectImportsOnly`: Only those imported ontologies that are directly imported in the ontology being documented. +`-oops`: Create an html page with the evaluation from the OOPS service (http://oops.linkeddata.es/) + +`-rewriteAll`: Replace any existing files when documenting an ontology (e.g., from a previous execution) `-rewriteBase PATH`: Change the default rewrite base path. The default value is "/". This flag can only be used with the htaccess option. -`-excludeIntroduction`: Skip the introduction section in the documentation. +`-saveConfig PATH`: Save a configuration file on PATH with the properties of a given ontology `-uniteSections`: Write all HTML sections into a single HTML document. -`-noPlaceHolderText`: Do not add any placeholder text (this will remove intro, abstract (if empty) and description sections). - -`--help`: Shows a help message and exits. +`-useCustomStyle`: Export the documentation using alternate css files (by Daniel Vila). `--version`: Shows the version of WIDOCO. +`-webVowl`: Create a visualization based on WebVowl (http://vowl.visualdataweb.org/webvowl/index.html#) in the documentation. + ## How can I make WIDOCO automatically recognize my vocabulary annotations? There are two alternative ways for making WIDOCO get your vocabulary metadata annotations and use them automatically to document the ontology. diff --git a/pom.xml b/pom.xml index fcbd88b7..c66f3625 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,11 @@ 3.9.0 - + + org.jsoup + jsoup + 1.17.2 + com.github.VisualDataWeb OWL2VOWL diff --git a/src/main/java/oops/OOPSevaluation.java b/src/main/java/oops/OOPSevaluation.java index fad0073e..346c3998 100644 --- a/src/main/java/oops/OOPSevaluation.java +++ b/src/main/java/oops/OOPSevaluation.java @@ -280,11 +280,13 @@ else if (code.contentEquals("P36")) { collect(Collectors.toList())).iterator(); if(elementos.hasNext()){ String first = ((OWLAnnotationAssertionAxiom)elementos.next()).getValue().asLiteral().get().getLiteral(); - String second = ((OWLAnnotationAssertionAxiom)elementos.next()).getValue().asLiteral().get().getLiteral(); - evaluationOutput = evaluationOutput + "
  • " + "" + first + "" + " may not be inverse of " - + "" + second + "" + "
  • "; - evaluationOutput = evaluationOutput + ""; + if (elementos.hasNext()) { + String second = ((OWLAnnotationAssertionAxiom) elementos.next()).getValue().asLiteral().get().getLiteral(); + evaluationOutput = evaluationOutput + "
  • " + "" + first + "" + " may not be inverse of " + + "" + second + "" + "
  • "; + evaluationOutput = evaluationOutput + ""; + } } } break; @@ -324,10 +326,12 @@ else if (code.contentEquals("P36")) { collect(Collectors.toList())).iterator(); while(elementos.hasNext()) { String first = ((OWLAnnotationAssertionAxiom) elementos.next()).getValue().asLiteral().get().toString(); - String second = ((OWLAnnotationAssertionAxiom) elementos.next()).getValue().asLiteral().get().toString(); - evaluationOutput = evaluationOutput + "
  • " + "" + first + "" + " , " + "" + second + "" + "
  • "; + if(elementos.hasNext()) { + String second = ((OWLAnnotationAssertionAxiom) elementos.next()).getValue().asLiteral().get().toString(); + evaluationOutput = evaluationOutput + "
  • " + "" + first + "" + " , " + "" + second + "" + "
  • "; + } } evaluationOutput = evaluationOutput + ""; } @@ -347,10 +351,12 @@ else if (code.contentEquals("P36")) { collect(Collectors.toList())).iterator(); while(elementos.hasNext()) { String first = ((OWLAnnotationAssertionAxiom) elementos.next()).getValue().asLiteral().get().toString(); - String second = ((OWLAnnotationAssertionAxiom) elementos.next()).getValue().asLiteral().get().toString(); - evaluationOutput = evaluationOutput + "
  • " + "" + first + "" + " , " + "" + second + "" + "
  • "; + if(elementos.hasNext()) { + String second = ((OWLAnnotationAssertionAxiom) elementos.next()).getValue().asLiteral().get().toString(); + evaluationOutput = evaluationOutput + "
  • " + "" + first + "" + " , " + "" + second + "" + "
  • "; + } } evaluationOutput = evaluationOutput + ""; } diff --git a/src/main/java/widoco/Configuration.java b/src/main/java/widoco/Configuration.java index 48255cff..5829962d 100644 --- a/src/main/java/widoco/Configuration.java +++ b/src/main/java/widoco/Configuration.java @@ -121,6 +121,7 @@ public class Configuration { private boolean includeAllSectionsInOneDocument; //boolean to indicate all sections should be included in a single big HTML private HashMap namespaceDeclarations; //Namespace declarations to be included in the documentation. private String introText;// in case there is an explicit annotation in the ontology + private List imports = new ArrayList<>(); /** * Variable to keep track of possible errors in the changelog. If there are @@ -1050,6 +1051,12 @@ public void setOntologyURI(String ontologyURI) { this.mainOntologyMetadata.setNamespaceURI(ontologyURI); } + public void setImports(List imports) { + for (String importStr:imports) { + this.imports.add(new File(importStr)); + } + } + public void setPublishProvenance(boolean publishProvenance) { this.publishProvenance = publishProvenance; } @@ -1074,6 +1081,10 @@ public String getReferencesPath() { return referencesPath; } + public List getImports() { + return this.imports; + } + public boolean isIncludeAbstract() { return includeAbstract; } diff --git a/src/main/java/widoco/Constants.java b/src/main/java/widoco/Constants.java index 10fcd0c7..9f9088ad 100644 --- a/src/main/java/widoco/Constants.java +++ b/src/main/java/widoco/Constants.java @@ -25,6 +25,7 @@ import widoco.entities.Agent; import widoco.entities.Ontology; +import widoco.ExternalPropertyParser; /** * @@ -337,6 +338,7 @@ public class Constants { public static final String LANG_CLASSES = "classes"; public static final String LANG_OBJ_PROP = "objProp"; public static final String LANG_DATA_PROP = "dataProp"; + public static final String LANG_EXT_PROP = "extProp"; public static final String LANG_ANN_PROP = "annProp"; public static final String LANG_NAMED_INDIV = "namedIndiv"; public static final String LANG_TABLE_OF_CONTENTS = "tableOfContents"; @@ -732,16 +734,11 @@ public static String getIndexDocument(String resourcesFolderName, Configuration String document = OPENING; /* Style selection */ if (c.isUseW3CStyle()) { - document += " \n" + - " \n" + " \n" + " \n"; + document += getW3CStyleDoc(resourcesFolderName); } else { - document += " \n" - + " \n"; + document += htmlStyleSheet(resourcesFolderName+"/yeti.css","screen") + +htmlStyleSheet(resourcesFolderName+"/site.css","screen"); } // add a favicon (rdf logo) document += ""; @@ -862,7 +859,25 @@ public static String getIndexDocument(String resourcesFolderName, Configuration return document; } - + + private static String htmlStyleSheet(String resource,String media) { + return "\n"; + } + + private static String getW3CStyleDoc(String rsrcFolder){ + return htmlStyleSheet(rsrcFolder+"/primer.css","screen") + + htmlStyleSheet(rsrcFolder+"/rec.css","screen") + + htmlStyleSheet(rsrcFolder+"/extra.css","screen") + + htmlStyleSheet(rsrcFolder+"/owl.css","screen") + + htmlStyleSheet(rsrcFolder+"/dark.css","(prefers-color-scheme: dark)") + + htmlStyleSheet(rsrcFolder+"/light.css","(prefers-color-scheme: light)") + + htmlStyleSheet(rsrcFolder+"/slider.css","screen") + + "\n" + + "" + +"
    \n" + +"\t\n" + +"
    \n"; + } /** * Function that includes all the sections as part of the index document. * This makes the documentation more difficult to edit, but simpler for production @@ -884,14 +899,11 @@ public static String getUnifiedIndexDocument(String resourcesFolderName, Configu String document = OPENING; /* Style selection */ if (c.isUseW3CStyle()) { - document += " " + " " + " " + " "; + document += getW3CStyleDoc(resourcesFolderName); + } else { - document += " " - + " "; + document += htmlStyleSheet(resourcesFolderName+"/yeti.css","screen") + +htmlStyleSheet(resourcesFolderName+"/site.css","screen"); } // add a title to the document if (c.getMainOntology().getTitle() != null && !"".equals(c.getMainOntology().getTitle())) @@ -1500,25 +1512,30 @@ public static String getLegend( + " " + lang.getProperty(Constants.LANG_BACK) + " ToC\n" + "
    \n" - + (includesClass ? + + (includesClass || ExternalPropertyParser.hasClasses()? "c: " + lang.getProperty(Constants.LANG_CLASSES) + "
    \n" : "") - + (includesProperty ? + + (includesProperty || ExternalPropertyParser.hasObjProps()? "op: " + lang.getProperty(Constants.LANG_OBJ_PROP) + "
    \n" : "") - + (includesDatatypeProperty ? + + (includesDatatypeProperty || ExternalPropertyParser.hasDataProps()? "dp: " + lang.getProperty(Constants.LANG_DATA_PROP) + "
    \n" : "") - + (includesNamedIndividual ? + + (includesNamedIndividual || ExternalPropertyParser.hasNamedIndiv() ? "ni: " - + lang.getProperty(Constants.LANG_NAMED_INDIV) + "\n" + + lang.getProperty(Constants.LANG_NAMED_INDIV) + "
    \n" + : "") + + (ExternalPropertyParser.hasExternalProps() ? + "ep: " + + lang.getProperty(Constants.LANG_EXT_PROP) + "\n" : "") + "
    \n" + "" + "\n"; diff --git a/src/main/java/widoco/CreateResources.java b/src/main/java/widoco/CreateResources.java index 9cfc6416..cc197798 100644 --- a/src/main/java/widoco/CreateResources.java +++ b/src/main/java/widoco/CreateResources.java @@ -70,7 +70,7 @@ public static void generateDocumentation(String outFolder, Configuration c, File logger.info("- ontology IRI: " + c.getOntologyURI()); lodeContent = LODEGeneration.getLODEhtml(c, lodeResources); LODEParser lode = new LODEParser(lodeContent, c, languageFile); - + if (c.isCreateHTACCESS()) { File fOut = new File(folderOut); if (!fOut.exists()) { @@ -477,6 +477,18 @@ private static void createFolderStructure(String s, Configuration c, Properties new File(resources.getAbsolutePath() + File.separator + "extra.css")); WidocoUtils.copyLocalResource("/lode/owl.css", new File(resources.getAbsolutePath() + File.separator + "owl.css")); + WidocoUtils.copyLocalResource("/darkmode/dark.css", + new File(resources.getAbsolutePath() + File.separator + "dark.css")); + WidocoUtils.copyLocalResource("/darkmode/light.css", + new File(resources.getAbsolutePath() + File.separator + "light.css")); + WidocoUtils.copyLocalResource("/darkmode/slider.css", + new File(resources.getAbsolutePath() + File.separator + "slider.css")); + WidocoUtils.copyLocalResource("/darkmode/dark-mode-toggle.mjs", + new File(resources.getAbsolutePath() + File.separator + "dark-mode-toggle.mjs")); + WidocoUtils.copyLocalResource("/darkmode/sun.svg", + new File(resources.getAbsolutePath() + File.separator + "sun.svg")); + WidocoUtils.copyLocalResource("/darkmode/moon.svg", + new File(resources.getAbsolutePath() + File.separator + "moon.svg")); } else { WidocoUtils.copyLocalResource("/lode/bootstrap-yeti.css", new File(resources.getAbsolutePath() + File.separator + "yeti.css")); diff --git a/src/main/java/widoco/ExternalPropertyParser.java b/src/main/java/widoco/ExternalPropertyParser.java new file mode 100644 index 00000000..38380f67 --- /dev/null +++ b/src/main/java/widoco/ExternalPropertyParser.java @@ -0,0 +1,380 @@ +/* + * Copyright (c) 2024 Victor Chavez + * + * SPDX-License-Identifier: Apache-2.0 + */ +package widoco; + +import org.semanticweb.owlapi.model.*; +import org.semanticweb.owlapi.model.parameters.Imports; + +import java.io.InputStream; +import java.util.List; +import java.util.Optional; +import java.util.Properties; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.semanticweb.owlapi.search.EntitySearcher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +public class ExternalPropertyParser { + private enum PropertyType { + OBJECT_PROPERTY, + DATA_PROPERTY, + NAMED_INDIVIDUAL, + CLASS, + ANNOTATION_PROPERTY, + EXTERNAL_PROPERTY, + } + + private OWLOntology ontology; + private static boolean uses_ep = false; + private static boolean uses_op = false; + private static boolean uses_ap = false; + private static boolean uses_dp = false; + private static boolean uses_ni = false; + private static boolean uses_c = false; + + private Properties lang; + + private final static String OP_PROP = "objectproperty"; + private final static String DP_PROP = "dataproperty"; + private final static String CLASS_PROP = "class"; + private final static String NI_PROP = "namedindividual"; + private final static String AP_PROP = "annotationproperty"; + private final static String EP_PROP = "externalproperty"; + + private String ontologyNSURI = ""; + private String langPrefix =""; + + + private static final Logger logger = LoggerFactory.getLogger(ExternalPropertyParser.class); + + public static boolean hasExternalProps(){ + return uses_ep; + } + + public static boolean hasClasses(){ + return uses_c; + } + + public static boolean hasObjProps(){ + return uses_op; + } + + public static boolean hasAnnotProps(){ + return uses_ap; + } + + public static boolean hasDataProps(){ + return uses_dp; + } + + public static boolean hasNamedIndiv(){ + return uses_ni; + } + + public void setOntology(OWLOntology ont,String uri) { + ontology = ont; + ontologyNSURI = uri; + } + + /** + * Set the language for the external parser. + * Load the lode/XX.xml resource with the specific language + * and save it to a Property object + * @param lang + */ + public void setLang(String lang) { + String resource = "/lode/"+lang+".xml"; + langPrefix = lang; + Properties properties = new Properties(); + try (InputStream inputStream = ExternalPropertyParser.class.getResourceAsStream(resource)) { + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + org.w3c.dom.Document document = dBuilder.parse(inputStream); + document.getDocumentElement().normalize(); + // Get all elements inside + org.w3c.dom.NodeList nodeList = document.getElementsByTagName("labels"); + // Iterate through each element + for (int temp = 0; temp < nodeList.getLength(); temp++) { + org.w3c.dom.Node node = nodeList.item(temp); + if (node.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) { + org.w3c.dom.Element element = (org.w3c.dom.Element) node; + // Get all child nodes (tags) inside + org.w3c.dom.NodeList childNodes = element.getChildNodes(); + // Iterate through each child node + for (int i = 0; i < childNodes.getLength(); i++) { + org.w3c.dom.Node childNode = childNodes.item(i); + if (childNode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) { + // Convert the tag name and text content into Properties + properties.setProperty(childNode.getNodeName(), childNode.getTextContent()); + } + } + } + } + } catch (Exception e) { + this.lang = null; + } + this.lang = properties; + } + + /** + * Parse html content with external property tags, i.e., the sup tag + * with class type-ep and attempt to find the type of property type + * within the ontology. + * The main purpose of this utility is to add metadata from the ontology + * since the xslt transform cannot look for external properties + * outisde of the xml rdf serialization of the ontology when Widoco + * does not document the imported ontologies. + * @param htmlContent Html content where the external properties are located. + * Typically this is the named individuals section + * @return + */ + public String parse(String htmlContent) { + + Document document = Jsoup.parseBodyFragment(htmlContent, "UTF-8"); + // Find all tags with tags having class "type-ep" + Elements superscripts = document.select("a + sup.type-ep"); + // Modify the tag based on the property type + for (Element superscript : superscripts) { + Element owlObjTextRef = superscript.previousElementSibling(); + String href_val = owlObjTextRef.attr("href"); + boolean hrefToHash = href_val.startsWith("#"); + String search_iri; + if (hrefToHash) { + search_iri = owlObjTextRef.attr("title"); + } else { + search_iri = href_val; + } + PropertyType type = getPropertyType(search_iri); + String class_name; + String text; + String title; + if (type == PropertyType.OBJECT_PROPERTY) { + class_name = "type-op"; + text="op"; + if(lang != null) { + title = lang.getProperty(OP_PROP); + } else { + title = "object property"; + } + if (!uses_op) { + uses_op = true; + } + } else if(type == PropertyType.DATA_PROPERTY) { + class_name = "type-dp"; + text="dp"; + if (!uses_dp) { + uses_dp = true; + } + if(lang != null) { + title = lang.getProperty(DP_PROP); + } else { + title = "data property"; + } + } else if (type == PropertyType.NAMED_INDIVIDUAL) { + class_name = "type-ni"; + text="ni"; + if (!uses_ni) { + uses_ni = true; + } + if(lang != null) { + title = lang.getProperty(NI_PROP); + } else { + title = "named individual"; + } + } else if (type == PropertyType.CLASS) { + class_name = "type-c"; + text="c"; + if (!uses_c) { + uses_c = true; + } + if(lang != null) { + title = lang.getProperty(CLASS_PROP); + } else { + title = "class"; + } + } else if (type == PropertyType.ANNOTATION_PROPERTY) { + class_name = "type-ap"; + text="ap"; + if (!uses_ap) { + uses_ap = true; + } + if(lang != null) { + title = lang.getProperty(AP_PROP); + } else { + title = "annotation property"; + } + } else { + class_name = "type-ep"; + text ="ep"; + if (!uses_ep) { + uses_ep = true; + } + if(lang != null) { + title = lang.getProperty(EP_PROP); + } else { + title = "external property"; + } + } + if (!hrefToHash) { + // In case the iri belongs to the ontology but + // through xslt transformation it did not get + // referenced properly + if (href_val.contains(ontologyNSURI)) { + // remove the "target="_blank" since the IRI is in + // the ontology + String newRef = href_val.replace(ontologyNSURI,""); + owlObjTextRef.removeAttr("target"); + owlObjTextRef.attr("href",newRef); + } + } + if (type != PropertyType.EXTERNAL_PROPERTY) { + String label = findLabel(search_iri,langPrefix); + if (label != null) { + owlObjTextRef.text(label); + } + } + superscript.text(text); + superscript.attr("class",class_name); + superscript.attr("title",title); + } + return document.body().html(); + } + + /** + * Find a label for a External Property from + * the loaded ontology + * @param obj_iri IRI whose rdfs:label will be searched + * @param langPrefix Preffered language of the label, if not found + * it will try to find any label. + * @return + */ + private String findLabel(String obj_iri, String langPrefix) { + IRI iri = IRI.create(obj_iri); + OWLEntity entity = ontology.entitiesInSignature(iri).findFirst().orElse(null); + if (entity != null) { + java.util.Optional labelAnnotation = getLabelAnnotation(entity, langPrefix); + + if (labelAnnotation.isPresent()) { + return labelAnnotation.get().getValue().asLiteral().get().getLiteral(); + } + } + return null; + } + + /** + * Get the rdfs:label in the form of OWLAnnotation + * This will look in the main ontology and also in the imported ontologies + * @param entity OWLEntity whose label will be searched + * @param langPrefix Preferred language of the label + * @return + */ + private Optional getLabelAnnotation(OWLEntity entity, String langPrefix) { + Optional label = getLabel(entity, langPrefix, ontology); + if (!label.isEmpty()) return label; + + // Check imported ontologies + for (OWLOntology importedOntology : ontology.getImports()) { + String importedIRI = importedOntology.getOntologyID().getOntologyIRI().get().toString(); + if (!entity.getIRI().toString().contains(importedIRI)) { + continue; + } + label = getLabel(entity, langPrefix, importedOntology); + if (label != null) return label; + } + return Optional.empty(); + } + + /** + * Search for an rdfs:label based on the language preference and an ontology + * @param entity OWLEntity whose rdfs:label will be searched + * @param langPrefix Preferred language + * @param ontology Ontology used as reference for the search + * @return OWLAnnotation with the rdfs:label + */ + private Optional getLabel(OWLEntity entity, String langPrefix, OWLOntology ontology) { + List annotationList = EntitySearcher.getAnnotations(entity, ontology).collect(Collectors.toList()); + + Predicate isValidLabel = annotation -> + annotation.getProperty().isLabel() && + annotation.getValue().asLiteral().isPresent(); + + Optional labelWithLang = annotationList.stream() + .filter(isValidLabel.and(annotation -> + annotation.getValue().asLiteral().get().getLang().equals(langPrefix))) + .findFirst(); + + if (labelWithLang.isPresent()) { + return labelWithLang; // Found the annotation with the specified langPrefix + } + + // If langPrefix is not found, try to find the annotation with langPrefix = "" + Optional labelWithEmptyLang = annotationList.stream() + .filter(isValidLabel.and(annotation -> + annotation.getValue().asLiteral().get().getLang().isEmpty())) + .findFirst(); + + if (labelWithEmptyLang.isPresent()) { + return labelWithEmptyLang; // Found the annotation with langPrefix = "" + } + + // If neither langPrefix nor "" is found, return the first available label + return annotationList.stream() + .filter(isValidLabel) + .findFirst(); + } + + /** + * Find the type of property an owl entity marked as external property + * with the xslt transformation + * @param entityIRI IRI of the owl entity to look for + * @return Property type, if not found it will return PropertyType.propertyIRI + */ + private PropertyType getPropertyType(String entityIRI) { + IRI propertyIRIObject = IRI.create(entityIRI); + Set objectProperties = ontology.getObjectPropertiesInSignature(Imports.INCLUDED); + for (OWLObjectProperty objectProperty : objectProperties) { + if (objectProperty.getIRI().equals(propertyIRIObject)) { + return PropertyType.OBJECT_PROPERTY; + } + } + Set dataProperties = ontology.getDataPropertiesInSignature(Imports.INCLUDED); + for (OWLDataProperty dataProperty : dataProperties) { + if (dataProperty.getIRI().equals(propertyIRIObject)) { + return PropertyType.DATA_PROPERTY; + } + } + Set individuals = ontology.getIndividualsInSignature(Imports.INCLUDED); + for (OWLNamedIndividual individual : individuals) { + if (individual.getIRI().equals(propertyIRIObject)) { + return PropertyType.NAMED_INDIVIDUAL; + } + } + Set classes = ontology.getClassesInSignature(Imports.INCLUDED); + for (OWLClass owl_class : classes) { + if (owl_class.getIRI().equals(propertyIRIObject)) { + return PropertyType.CLASS; + } + } + Set annotationProperties = ontology.getAnnotationPropertiesInSignature(Imports.INCLUDED); + for (OWLAnnotationProperty annotationProperty : annotationProperties) { + if (annotationProperty.getIRI().equals(propertyIRIObject)) { + return PropertyType.ANNOTATION_PROPERTY; + } + } + return PropertyType.EXTERNAL_PROPERTY; + } + +} diff --git a/src/main/java/widoco/LODEParser.java b/src/main/java/widoco/LODEParser.java index 2c583158..f5056477 100644 --- a/src/main/java/widoco/LODEParser.java +++ b/src/main/java/widoco/LODEParser.java @@ -41,6 +41,8 @@ import org.w3c.dom.NodeList; import org.xml.sax.SAXException; +import widoco.ExternalPropertyParser; + /** * Class made for parsing and manipulating LODE's html. This class contains most * of the TemplateGeneratorOLD class @@ -69,6 +71,7 @@ public class LODEParser { private String swrlrules; private String swrlruleslist; Configuration c; + ExternalPropertyParser extParser; /** * Constructor for the LODE parser. The reason for creating this class is to reuse certain parts of @@ -84,6 +87,9 @@ public class LODEParser { public LODEParser(String lodeContent, Configuration c, Properties langFile) { replacements = new HashMap(); this.c = c; + extParser = new ExternalPropertyParser(); + extParser.setOntology(c.getMainOntology().getOWLAPIModel(),c.getMainOntology().getNamespaceURI()); + extParser.setLang(c.getCurrentLanguage()); parse(lodeContent, langFile); //System.out.println(lodeContent); } @@ -190,6 +196,7 @@ private void parse(String content, Properties langFile) { classes = classes.replace("

    " + langFile.getProperty(Constants.LANG_CLASSES) + "

    ", "

    " + langFile.getProperty(Constants.LANG_CLASSES) + "

    "); + classes = extParser.parse(classes); break; case "objectproperties": propertyList = getTermList(html.item(i)); @@ -197,6 +204,7 @@ private void parse(String content, Properties langFile) { properties = properties.replace("

    " + langFile.getProperty(Constants.LANG_OBJ_PROP) + "

    ", "

    " + langFile.getProperty(Constants.LANG_OBJ_PROP) + "

    "); + properties = extParser.parse(properties); break; case "dataproperties": dataPropList = (getTermList(html.item(i))); @@ -204,6 +212,7 @@ private void parse(String content, Properties langFile) { dataProp = dataProp.replace("

    " + langFile.getProperty(Constants.LANG_DATA_PROP) + "

    ", "

    " + langFile.getProperty(Constants.LANG_DATA_PROP) + "

    "); + dataProp = extParser.parse(dataProp); break; case "annotationproperties": annotationPropList = (getTermList(html.item(i))); @@ -212,6 +221,7 @@ private void parse(String content, Properties langFile) { "

    " + langFile.getProperty(Constants.LANG_ANN_PROP) + "

    ", "

    " + langFile.getProperty(Constants.LANG_ANN_PROP) + "

    "); + annotationProp = extParser.parse(annotationProp); break; case "namedindividuals": namedIndividualList = (getTermList(html.item(i))); @@ -220,6 +230,7 @@ private void parse(String content, Properties langFile) { "

    " + langFile.getProperty(Constants.LANG_NAMED_INDIV) + "

    ", "

    " + langFile.getProperty(Constants.LANG_NAMED_INDIV) + "

    "); + namedIndividuals = extParser.parse(namedIndividuals); break; /*missing: rules!*/ case "rules": @@ -245,6 +256,7 @@ private void parse(String content, Properties langFile) { swrlrules = (nodeToString(html.item(i))); swrlrules = swrlrules.replace("

    SWRL rules

    ", "

    SWRL rules

    "); + swrlrules = extParser.parse(swrlrules); break; } } diff --git a/src/main/java/widoco/WidocoUtils.java b/src/main/java/widoco/WidocoUtils.java index 7af6f599..1788a029 100644 --- a/src/main/java/widoco/WidocoUtils.java +++ b/src/main/java/widoco/WidocoUtils.java @@ -38,6 +38,7 @@ import org.semanticweb.owlapi.apibinding.OWLManager; import org.semanticweb.owlapi.io.FileDocumentSource; import org.semanticweb.owlapi.model.*; +import org.semanticweb.owlapi.util.AutoIRIMapper; /** * Some useful methods reused across different classes @@ -69,6 +70,13 @@ public static void loadModelToDocument(Configuration c) throws Exception { ((CatalogIRIMapper) jenaCatalogMapper).printMap(); OWLOntologyLoaderConfiguration loadingConfig = new OWLOntologyLoaderConfiguration(); loadingConfig = loadingConfig.setMissingImportHandlingStrategy(MissingImportHandlingStrategy.SILENT); + if (c.getImports()!=null){ + for(File importDir:c.getImports()){ + AutoIRIMapper mapper = new AutoIRIMapper(importDir, true); + manager.getIRIMappers().add(mapper); + } + } + OWLOntology ontology = manager .loadOntologyFromOntologyDocument(new FileDocumentSource(new File(c.getOntologyPath())), loadingConfig); c.getMainOntology().setMainOntology(ontology); diff --git a/src/main/java/widoco/gui/GuiController.java b/src/main/java/widoco/gui/GuiController.java index d29eca74..1363c438 100644 --- a/src/main/java/widoco/gui/GuiController.java +++ b/src/main/java/widoco/gui/GuiController.java @@ -21,7 +21,9 @@ import java.io.IOException; import java.io.InputStream; import java.net.URI; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import java.util.Properties; import javax.swing.JFrame; import javax.swing.JOptionPane; @@ -101,11 +103,12 @@ public GuiController(String[] args) { // get the arguments String outFolder = "myDocumentation" + (new Date().getTime()), ontology = "", rb = null, configOutFile = null; + List imports = new ArrayList<>(); boolean isFromFile = false, oops = false, rewriteAll = false, getOntoMetadata = true, useW3Cstyle = true, includeImportedOntologies = false, htAccess = false, webVowl = false, errors = false, licensius = false, generateOnlyCrossRef = false, includeNamedIndividuals = true, includeAnnotationProperties = false, displaySerializations = true, displayDirectImportsOnly = false, excludeIntroduction = false, uniteSections = false, - placeHolderText = true; + placeHolderText = true, localImports=false; String confPath = ""; String code = null;// for tracking analytics. String[] languages = null; @@ -131,6 +134,11 @@ public GuiController(String[] args) { ontology = args[i + 1]; i++; break; + case "-import": + imports.add(args[i + 1]); + localImports = true; + i++; + break; case "-oops": oops = true; break; @@ -267,6 +275,9 @@ public GuiController(String[] args) { } if (!isFromFile) this.config.setOntologyURI(ontology); + if(localImports) { + this.config.setImports(imports); + } logger.info("Processed configuration, loading ontology now. isFromFile=" + (isFromFile ? "true" : "false")); diff --git a/src/main/resources/darkmode/dark-mode-toggle.mjs b/src/main/resources/darkmode/dark-mode-toggle.mjs new file mode 100644 index 00000000..e6fda594 --- /dev/null +++ b/src/main/resources/darkmode/dark-mode-toggle.mjs @@ -0,0 +1,372 @@ +/** + * Copyright 2019 Google LLC + * + * 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 + * + * https://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. + */ + +// @license © 2019 Google LLC. Licensed under the Apache License, Version 2.0. +const doc = document; +let store = {}; +try { + store = localStorage; +} catch (err) { + // Do nothing. The user probably blocks cookies. +} +const PREFERS_COLOR_SCHEME = 'prefers-color-scheme'; +const MEDIA = 'media'; +const LIGHT = 'light'; +const DARK = 'dark'; +const MQ_DARK = `(${PREFERS_COLOR_SCHEME}:${DARK})`; +const MQ_LIGHT = `(${PREFERS_COLOR_SCHEME}:${LIGHT})`; +const LINK_REL_STYLESHEET = 'link[rel=stylesheet]'; +const REMEMBER = 'remember'; +const LEGEND = 'legend'; +const TOGGLE = 'toggle'; +const SWITCH = 'switch'; +const APPEARANCE = 'appearance'; +const PERMANENT = 'permanent'; +const MODE = 'mode'; +const COLOR_SCHEME_CHANGE = 'colorschemechange'; +const PERMANENT_COLOR_SCHEME = 'permanentcolorscheme'; +const ALL = 'all'; +const NOT_ALL = 'not all'; +const NAME = 'dark-mode-toggle'; +const DEFAULT_URL = 'https://googlechromelabs.github.io/dark-mode-toggle/demo/'; + +// See https://html.spec.whatwg.org/multipage/common-dom-interfaces.html ↵ +// #reflecting-content-attributes-in-idl-attributes. +const installStringReflection = (obj, attrName, propName = attrName) => { + Object.defineProperty(obj, propName, { + enumerable: true, + get() { + const value = this.getAttribute(attrName); + return value === null ? '' : value; + }, + set(v) { + this.setAttribute(attrName, v); + }, + }); +}; + +const installBoolReflection = (obj, attrName, propName = attrName) => { + Object.defineProperty(obj, propName, { + enumerable: true, + get() { + return this.hasAttribute(attrName); + }, + set(v) { + if (v) { + this.setAttribute(attrName, ''); + } else { + this.removeAttribute(attrName); + } + }, + }); +}; + +const template = doc.createElement('template'); +// ⚠️ Note: this is a minified version of `src/template-contents.tpl`. +// Compress the CSS with https://cssminifier.com/, then paste it here. +// eslint-disable-next-line max-len +template.innerHTML = `
    `; + +export class DarkModeToggle extends HTMLElement { + static get observedAttributes() { + return [MODE, APPEARANCE, PERMANENT, LEGEND, LIGHT, DARK, REMEMBER]; + } + + constructor() { + super(); + + installStringReflection(this, MODE); + installStringReflection(this, APPEARANCE); + installStringReflection(this, LEGEND); + installStringReflection(this, LIGHT); + installStringReflection(this, DARK); + installStringReflection(this, REMEMBER); + + installBoolReflection(this, PERMANENT); + + this._darkCSS = null; + this._lightCSS = null; + + doc.addEventListener(COLOR_SCHEME_CHANGE, (event) => { + this.mode = event.detail.colorScheme; + this._updateRadios(); + this._updateCheckbox(); + }); + + doc.addEventListener(PERMANENT_COLOR_SCHEME, (event) => { + this.permanent = event.detail.permanent; + this._permanentCheckbox.checked = this.permanent; + }); + + this._initializeDOM(); + } + + _initializeDOM() { + const shadowRoot = this.attachShadow({mode: 'open'}); + shadowRoot.append(template.content.cloneNode(true)); + + // We need to support `media="(prefers-color-scheme: dark)"` (with space) + // and `media="(prefers-color-scheme:dark)"` (without space) + this._darkCSS = doc.querySelectorAll( + `${LINK_REL_STYLESHEET}[${MEDIA}*=${PREFERS_COLOR_SCHEME}][${MEDIA}*="${DARK}"]`, + ); + this._lightCSS = doc.querySelectorAll( + `${LINK_REL_STYLESHEET}[${MEDIA}*=${PREFERS_COLOR_SCHEME}][${MEDIA}*="${LIGHT}"]`, + ); + + // Get DOM references. + this._lightRadio = shadowRoot.querySelector('[part=lightRadio]'); + this._lightLabel = shadowRoot.querySelector('[part=lightLabel]'); + this._darkRadio = shadowRoot.querySelector('[part=darkRadio]'); + this._darkLabel = shadowRoot.querySelector('[part=darkLabel]'); + this._darkCheckbox = shadowRoot.querySelector('[part=toggleCheckbox]'); + this._checkboxLabel = shadowRoot.querySelector('[part=toggleLabel]'); + this._legendLabel = shadowRoot.querySelector('legend'); + this._permanentAside = shadowRoot.querySelector('aside'); + this._permanentCheckbox = shadowRoot.querySelector( + '[part=permanentCheckbox]', + ); + this._permanentLabel = shadowRoot.querySelector('[part=permanentLabel]'); + } + + connectedCallback() { + // Does the browser support native `prefers-color-scheme`? + const hasNativePrefersColorScheme = matchMedia(MQ_DARK).media !== NOT_ALL; + // Listen to `prefers-color-scheme` changes. + if (hasNativePrefersColorScheme) { + matchMedia(MQ_DARK).addListener(({matches}) => { + if (this.permanent) { + return; + } + this.mode = matches ? DARK : LIGHT; + this._dispatchEvent(COLOR_SCHEME_CHANGE, {colorScheme: this.mode}); + }); + } + // Set initial state, giving preference to a remembered value, then the + // native value (if supported), and eventually defaulting to a light + // experience. + let rememberedValue = false; + try { + rememberedValue = store.getItem(NAME); + } catch (err) { + // Do nothing. The user probably blocks cookies. + } + if (rememberedValue && [DARK, LIGHT].includes(rememberedValue)) { + this.mode = rememberedValue; + this._permanentCheckbox.checked = true; + this.permanent = true; + } else if (hasNativePrefersColorScheme) { + this.mode = matchMedia(MQ_LIGHT).matches ? LIGHT : DARK; + } + if (!this.mode) { + this.mode = LIGHT; + } + if (this.permanent && !rememberedValue) { + try { + store.setItem(NAME, this.mode); + } catch (err) { + // Do nothing. The user probably blocks cookies. + } + } + + // Default to toggle appearance. + if (!this.appearance) { + this.appearance = TOGGLE; + } + + // Update the appearance to either of toggle or switch. + this._updateAppearance(); + + // Update the radios + this._updateRadios(); + + // Make the checkbox reflect the state of the radios + this._updateCheckbox(); + + // Synchronize the behavior of the radio and the checkbox. + [this._lightRadio, this._darkRadio].forEach((input) => { + input.addEventListener('change', () => { + this.mode = this._lightRadio.checked ? LIGHT : DARK; + this._updateCheckbox(); + this._dispatchEvent(COLOR_SCHEME_CHANGE, {colorScheme: this.mode}); + }); + }); + this._darkCheckbox.addEventListener('change', () => { + this.mode = this._darkCheckbox.checked ? DARK : LIGHT; + this._updateRadios(); + this._dispatchEvent(COLOR_SCHEME_CHANGE, {colorScheme: this.mode}); + }); + + // Make remembering the last mode optional + this._permanentCheckbox.addEventListener('change', () => { + this.permanent = this._permanentCheckbox.checked; + this._dispatchEvent(PERMANENT_COLOR_SCHEME, { + permanent: this.permanent, + }); + }); + + // Finally update the mode and let the world know what's going on + this._updateMode(); + this._dispatchEvent(COLOR_SCHEME_CHANGE, {colorScheme: this.mode}); + this._dispatchEvent(PERMANENT_COLOR_SCHEME, { + permanent: this.permanent, + }); + } + + attributeChangedCallback(name, oldValue, newValue) { + if (name === MODE) { + if (![LIGHT, DARK].includes(newValue)) { + throw new RangeError(`Allowed values: "${LIGHT}" and "${DARK}".`); + } + // Only show the dialog programmatically on devices not capable of hover + // and only if there is a label + if (matchMedia('(hover:none)').matches && this.remember) { + this._showPermanentAside(); + } + if (this.permanent) { + try { + store.setItem(NAME, this.mode); + } catch (err) { + // Do nothing. The user probably blocks cookies. + } + } + this._updateRadios(); + this._updateCheckbox(); + this._updateMode(); + } else if (name === APPEARANCE) { + if (![TOGGLE, SWITCH].includes(newValue)) { + throw new RangeError(`Allowed values: "${TOGGLE}" and "${SWITCH}".`); + } + this._updateAppearance(); + } else if (name === PERMANENT) { + if (this.permanent) { + if (this.mode) { + try { + store.setItem(NAME, this.mode); + } catch (err) { + // Do nothing. The user probably blocks cookies. + } + } + } else { + try { + store.removeItem(NAME); + } catch (err) { + // Do nothing. The user probably blocks cookies. + } + } + this._permanentCheckbox.checked = this.permanent; + } else if (name === LEGEND) { + this._legendLabel.textContent = newValue; + } else if (name === REMEMBER) { + this._permanentLabel.textContent = newValue; + } else if (name === LIGHT) { + this._lightLabel.textContent = newValue; + if (this.mode === LIGHT) { + this._checkboxLabel.textContent = newValue; + } + } else if (name === DARK) { + this._darkLabel.textContent = newValue; + if (this.mode === DARK) { + this._checkboxLabel.textContent = newValue; + } + } + } + + _dispatchEvent(type, value) { + this.dispatchEvent( + new CustomEvent(type, { + bubbles: true, + composed: true, + detail: value, + }), + ); + } + + _updateAppearance() { + // Hide or show the light-related affordances dependent on the appearance, + // which can be "switch" or "toggle". + const appearAsToggle = this.appearance === TOGGLE; + this._lightRadio.hidden = appearAsToggle; + this._lightLabel.hidden = appearAsToggle; + this._darkRadio.hidden = appearAsToggle; + this._darkLabel.hidden = appearAsToggle; + this._darkCheckbox.hidden = !appearAsToggle; + this._checkboxLabel.hidden = !appearAsToggle; + } + + _updateRadios() { + if (this.mode === LIGHT) { + this._lightRadio.checked = true; + } else { + this._darkRadio.checked = true; + } + } + + _updateCheckbox() { + if (this.mode === LIGHT) { + this._checkboxLabel.style.setProperty( + `--${NAME}-checkbox-icon`, + `var(--${NAME}-light-icon,url("${DEFAULT_URL}moon.png"))`, + ); + this._checkboxLabel.textContent = this.light; + if (!this.light) { + this._checkboxLabel.ariaLabel = DARK; + } + this._darkCheckbox.checked = false; + } else { + this._checkboxLabel.style.setProperty( + `--${NAME}-checkbox-icon`, + `var(--${NAME}-dark-icon,url("${DEFAULT_URL}sun.png"))`, + ); + this._checkboxLabel.textContent = this.dark; + if (!this.dark) { + this._checkboxLabel.ariaLabel = LIGHT; + } + this._darkCheckbox.checked = true; + } + } + + _updateMode() { + if (this.mode === LIGHT) { + this._lightCSS.forEach((link) => { + link.media = ALL; + link.disabled = false; + }); + this._darkCSS.forEach((link) => { + link.media = NOT_ALL; + link.disabled = true; + }); + } else { + this._darkCSS.forEach((link) => { + link.media = ALL; + link.disabled = false; + }); + this._lightCSS.forEach((link) => { + link.media = NOT_ALL; + link.disabled = true; + }); + } + } + + _showPermanentAside() { + this._permanentAside.style.visibility = 'visible'; + setTimeout(() => { + this._permanentAside.style.visibility = 'hidden'; + }, 3000); + } +} + +customElements.define(NAME, DarkModeToggle); diff --git a/src/main/resources/darkmode/dark.css b/src/main/resources/darkmode/dark.css new file mode 100644 index 00000000..3a9bba6c --- /dev/null +++ b/src/main/resources/darkmode/dark.css @@ -0,0 +1,25 @@ +:root { + color-scheme: dark; + --bg-status-box: rgb(0, 90, 156); + --background-color: #3e3e42; + --border: white; + --htag-text: #83E9F0; + --bg-backlink: #6e6e67; + --text-backlink: white; + --text-color: rgb(240 240 240); + --shadow-color: rgb(240 240 240 / 50%); + --accent-color: rgb(0 0 240 / 50%); + --owl-bg-pre: rgb(15 15 15); + --link: #DDC1F2; + --link-visited: #BD7DEE; + --link-active: blue; + --href-hover: gray; + --bg-hlist: #67676E; + --bg-description: #535358; + --literal:#49C00D; + --type-ap: #EB4A4A; + --type-op: #75E1FC; + --type-dp: #75FA32; + --type-ni: #f3cdcd; +} + diff --git a/src/main/resources/darkmode/light.css b/src/main/resources/darkmode/light.css new file mode 100644 index 00000000..f4a54957 --- /dev/null +++ b/src/main/resources/darkmode/light.css @@ -0,0 +1,24 @@ +:root { + color-scheme: light; + --bg-status-box: rgb(0, 90, 156); + --background-color: white; + --border: navy; + --htag-text: #005A9C; + --bg-backlink: #F4FFFF; + --text-backlink: black; + --text-color: rgb(15 15 15); + --shadow-color: rgb(15 15 15 / 50%); + --accent-color: rgb(240 0 0 / 50%); + --owl-bg: #F9F9F9; + --link: #00c; + --link-visited: #609; + --link-active: #c00; + --href-hover: #ffa; + --bg-hlist: #F4FFFF; + --bg-description:rgb(242, 243, 244); + --literal:green; + --type-ap: maroon; + --type-op: navy; + --type-dp: green; + --type-ni: brown; +} diff --git a/src/main/resources/darkmode/moon.svg b/src/main/resources/darkmode/moon.svg new file mode 100644 index 00000000..fad89a4e --- /dev/null +++ b/src/main/resources/darkmode/moon.svg @@ -0,0 +1,7 @@ + + + + moon + + + diff --git a/src/main/resources/darkmode/slider.css b/src/main/resources/darkmode/slider.css new file mode 100644 index 00000000..a6aa55c9 --- /dev/null +++ b/src/main/resources/darkmode/slider.css @@ -0,0 +1,78 @@ +.darkmode { + position: fixed; + left: 0px; + bottom: 0px; +} + +dark-mode-toggle.slider::part(toggleLabel) { + display: inline-block; + position: relative; + height: calc(var(--dark-mode-toggle-icon-size, 1rem) * 2); + width: calc(var(--dark-mode-toggle-icon-size, 1rem) * 3.5); + background-color: #b7bbbd; + border-radius: var(--dark-mode-toggle-icon-size, 1rem); + transition: 0.4s; +} + +dark-mode-toggle.slider[mode="dark"]::part(toggleLabel) { + background-color: #4e5255; +} + +dark-mode-toggle.slider::part(toggleLabel)::before { + display: flex; + align-items: center; + justify-content: center; + position: absolute; + top: calc(var(--dark-mode-toggle-icon-size, 1rem) * 0.25); + left: calc(var(--dark-mode-toggle-icon-size, 1rem) * 0.25); + height: calc(var(--dark-mode-toggle-icon-size, 1rem) * 1.5); + width: calc(var(--dark-mode-toggle-icon-size, 1rem) * 1.5); + border-radius: 100%; + box-shadow: 0 0.15em 0.3em rgb(0 0 0 / 15%), 0 0.2em 0.5em rgb(0 0 0 / 30%); + background-color: #fff; + color: #333; + transition: 0.4s; + content: ""; + background-position: center; + background-size: var(--dark-mode-toggle-icon-size, 1rem); + background-image: var(--dark-mode-toggle-light-icon, url("sun.svg")); + box-sizing: border-box; +} + +dark-mode-toggle.slider[mode="dark"]::part(toggleLabel)::before { + left: calc(100% - var(--dark-mode-toggle-icon-size, 1rem) * 1.75); + border-color: #000; + background-color: #ccc; + color: #000; + background-size: var(--dark-mode-toggle-icon-size, 1rem); + background-image: var(--dark-mode-toggle-dark-icon, url("moon.svg")); + filter: var(--dark-mode-toggle-icon-filter, invert(100%)); + box-shadow: 0 0.5px hsl(0deg 0% 100% / 16%); +} + +dark-mode-toggle.slider::part(toggleLabel)::after { + display: flex; + align-items: center; + justify-content: center; + position: absolute; + top: calc(var(--dark-mode-toggle-icon-size, 1rem) * 0.25); + left: calc(100% - var(--dark-mode-toggle-icon-size, 1rem) * 1.75); + height: calc(var(--dark-mode-toggle-icon-size, 1rem) * 1.5); + width: calc(var(--dark-mode-toggle-icon-size, 1rem) * 1.5); + border-radius: 100%; + color: #333; + content: ""; + background-position: center; + background-size: var(--dark-mode-toggle-icon-size, 1rem); + background-image: var(--dark-mode-toggle-dark-icon, url("moon.svg")); + background-repeat: no-repeat; + box-sizing: border-box; + opacity: 0.5; +} + +dark-mode-toggle.slider[mode="dark"]::part(toggleLabel)::after { + left: calc(var(--dark-mode-toggle-icon-size, 1rem) * 0.25); + background-image: var(--dark-mode-toggle-light-icon, url("sun.svg")); + filter: var(--dark-mode-toggle-icon-filter, invert(100%)); +} + diff --git a/src/main/resources/darkmode/sun.svg b/src/main/resources/darkmode/sun.svg new file mode 100644 index 00000000..0b18941c --- /dev/null +++ b/src/main/resources/darkmode/sun.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/lode/cs.xml b/src/main/resources/lode/cs.xml index 54807f22..20dcc210 100644 --- a/src/main/resources/lode/cs.xml +++ b/src/main/resources/lode/cs.xml @@ -70,6 +70,7 @@ vyvinuto nebo Obsah tříd + Obsah SWRL Obsah objektových vlastností Obsah datových vlastností Obsah anotačních vlastností @@ -87,4 +88,5 @@ range includes uses rule redakční poznámka + externí vlastnosti
    diff --git a/src/main/resources/lode/de.xml b/src/main/resources/lode/de.xml index 33903ea4..e0abe4e0 100644 --- a/src/main/resources/lode/de.xml +++ b/src/main/resources/lode/de.xml @@ -70,6 +70,7 @@ erzeugt, entwickelt durch oder Klassenübersicht + Übersicht der SWRL Übersicht der Objekteigenschaften Übersicht der Dateneigenschaften Übersicht der Anmerkungseigenschaften @@ -88,4 +89,5 @@ Szenarien verwendet die Regel redaktionelle Anmerkung + externe Eigenschaft
    \ No newline at end of file diff --git a/src/main/resources/lode/en.xml b/src/main/resources/lode/en.xml index f40f8068..389601b0 100644 --- a/src/main/resources/lode/en.xml +++ b/src/main/resources/lode/en.xml @@ -71,6 +71,7 @@ developed by or Class ToC + SWRL ToC Object Property ToC Data Property ToC Annotation Property ToC @@ -89,4 +90,5 @@ editorial note used by rule (in antecedent) used by rule (in consequent) + external property
    \ No newline at end of file diff --git a/src/main/resources/lode/es.xml b/src/main/resources/lode/es.xml index 2ab01307..0c0e9988 100644 --- a/src/main/resources/lode/es.xml +++ b/src/main/resources/lode/es.xml @@ -70,6 +70,7 @@ desarrollado por o Índice de clases + Índice de SWRL Índice de propiedades de objetos Índice de propiedades de datos Índice de propiedades de anotación @@ -87,4 +88,5 @@ el rango incluye utiliza regla nota editorial + propiedad externa \ No newline at end of file diff --git a/src/main/resources/lode/extra.css b/src/main/resources/lode/extra.css index 16689d3f..8d3fd795 100644 --- a/src/main/resources/lode/extra.css +++ b/src/main/resources/lode/extra.css @@ -3,13 +3,13 @@ body { } h1 { - line-height: 110%; + line-height: 110%; } .hlist { - border: 1px solid navy; + border: 1px solid var(--border); padding:5px; - background-color: #F4FFFF; + background-color: var(--bg-hlist); } .hlist li { @@ -21,7 +21,7 @@ h1 { } .entity { - border: 1px solid navy; + border: 1px solid var(--border); margin:5px 0px 5px 0px; padding: 5px; } @@ -33,22 +33,32 @@ h1 { .type-op { cursor:help; - color:navy; + color:var(--type-op); } .type-dp { cursor:help; - color:green; + color:var(--type-dp); +} + +.type-ep { + cursor:help; + color:mediumpurple; +} + +.type-ep { + cursor:help; + color:mediumpurple; } .type-ap { cursor:help; - color:maroon; + color:var(--type-ap); } .type-ni { cursor:help; - color:brown; + color:var(--type-ni); } .logic { @@ -59,7 +69,7 @@ h1 { h3 { margin-top: 3px; padding-bottom: 5px; - border-bottom: 1px solid navy; + border-bottom: 1px solid var(--border); } h2 { @@ -77,13 +87,13 @@ dt { .description { border-top: 1px dashed gray; border-bottom: 1px dashed gray; - background-color: rgb(242, 243, 244); + background-color: var(--bg-description); margin-top:5px; padding-bottom:5px; } .description dl { - background-color: rgb(242, 243, 244); + background-color: var(--bg-description); } .description ul { @@ -98,7 +108,8 @@ dt { color:black; padding: 2px; border: 1px dotted navy; - background-color: #F4FFFF; + background-color: var(--bg-backlink); + color: var(--text-backlink); } .imageblock { @@ -119,6 +130,6 @@ dt { } .literal { - color:green; + color:var(--literal); font-style:italic; } \ No newline at end of file diff --git a/src/main/resources/lode/extraction.xsl b/src/main/resources/lode/extraction.xsl index 2dafcced..1fa54b6c 100644 --- a/src/main/resources/lode/extraction.xsl +++ b/src/main/resources/lode/extraction.xsl @@ -1,6 +1,6 @@ + + @@ -1201,6 +1208,7 @@ http://www.oxygenxml.com/ns/doc/xsl "> +
    @@ -1210,6 +1218,12 @@ http://www.oxygenxml.com/ns/doc/xsl "> + + + + + + " @@ -1308,7 +1322,7 @@ http://www.oxygenxml.com/ns/doc/xsl "> + select="/rdf:RDF/(owl:ObjectProperty|rdf:Property|owl:DatatypeProperty|owl:AnnotationProperty)[some $res in (rdfs:domain|schema:domainIncludes)/(@*:resource|(owl:Class|rdfs:Class|owl:Class/owl:unionOf/rdf:Description)/@*:about) satisfies $res = $about]/(@*:about|@*:ID)"/>
    @@ -1331,7 +1345,7 @@ http://www.oxygenxml.com/ns/doc/xsl "> + select="/rdf:RDF/(owl:ObjectProperty|rdf:Property|owl:DatatypeProperty|owl:AnnotationProperty)[some $res in (rdfs:range|schema:rangeIncludes)/(@*:resource|(owl:Class|rdfs:Class|owl:Class/owl:unionOf/rdf:Description)/@*:about) satisfies $res = $about]/(@*:about|@*:ID)"/>
    @@ -1938,9 +1952,17 @@ http://www.oxygenxml.com/ns/doc/xsl "> ap - + ni + + + ep + @@ -2050,6 +2072,7 @@ http://www.oxygenxml.com/ns/doc/xsl "> + + select="exists($rdf/(owl:ObjectProperty|owl:DatatypeProperty|owl:AnnotationProperty)[some $res in (rdfs:domain|schema:domainIncludes)/(@*:resource|(owl:Class|rdfs:Class|owl:Class/owl:unionOf/rdf:Description)/@*:about) satisfies $res = $el/(@*:about|@*:ID)])"/> @@ -2079,7 +2102,7 @@ http://www.oxygenxml.com/ns/doc/xsl "> + select="exists($rdf/(if ($type = 'property') then owl:DatatypeProperty | owl:ObjectProperty else owl:AnnotationProperty)[some $res in rdfs:subPropertyOf/(@*:resource|(owl:Class|rdfs:Class|owl:Class/owl:unionOf/rdf:Description)/@*:about) satisfies $res = $el/(@*:about|@*:ID)])"/> diff --git a/src/main/resources/lode/lodeprimer.css b/src/main/resources/lode/lodeprimer.css index 3136dac0..5a73ae3a 100644 --- a/src/main/resources/lode/lodeprimer.css +++ b/src/main/resources/lode/lodeprimer.css @@ -84,7 +84,7 @@ div.fssyntax pre, div.rdfxml pre, div.owlxml pre, div.turtle pre, div.manchester /* The actual status box */ .status div { display: block; - background: rgb(0, 90, 156); + background: var(--bg-status-box); color: white; width: 24em; padding-top: 0.3em; diff --git a/src/main/resources/lode/owl.css b/src/main/resources/lode/owl.css index 9c008760..8a7bf08b 100644 --- a/src/main/resources/lode/owl.css +++ b/src/main/resources/lode/owl.css @@ -211,7 +211,7 @@ table.canonicalparsing td.two { /* override mediawiki's beautiful DL styling... */ dl { - background: white; + background: var(--background-color); width: 100%; border: none; margin-top: 0; @@ -240,9 +240,9 @@ color: red /* just copying from wiki, so it stays through TR. Currently affects Primer, at least */ pre { - background-color:#F9F9F9; + background-color: var(--owl-bg); border:1px dashed #2F6FAB; - color:black; + color:var(--text-color); line-height:1.1em; padding:1em; } \ No newline at end of file diff --git a/src/main/resources/lode/rec.css b/src/main/resources/lode/rec.css index d8a1ff2d..dd541eb9 100644 --- a/src/main/resources/lode/rec.css +++ b/src/main/resources/lode/rec.css @@ -11,16 +11,16 @@ body { padding: 2em 1em 2em 70px; margin: 0; font-family: sans-serif; - color: black; - background: white; + color: var(--text-color); + background: var(--background-color); background-position: top left; background-attachment: fixed; background-repeat: no-repeat; counter-reset:section; } -:link { color: #00C; background: transparent } -:visited { color: #609; background: transparent } -a:active { color: #C00; background: transparent } +:link { color: var(--link); background: transparent } +:visited { color: var(--link-visited); background: transparent } +a:active { color: var(--link-active); background: transparent } a:link img, a:visited img { border-style: none } /* no border on img links */ @@ -41,7 +41,7 @@ h3.list:before{counter-increment:subsection;content: counter(section) "." counte h3.list{margin-top: 20px; border-bottom: 0px; } /* background should be transparent, but WebTV has a bug */ -h1, h2, h3 { color: #005A9C; background: white } +h1, h2, h3 { color: var(--htag-text); background: var(--background-color) } h1 { font: 170% sans-serif } h2 { font: 140% sans-serif } h3 { font: 120% sans-serif } @@ -59,7 +59,7 @@ p.copyright { font-size: small } p.copyright small { font-size: small } @media screen { /* hide from IE3 */ -a[href]:hover { background: #ffa } +a[href]:hover { background: var(--href-hover) } } pre { margin-left: 2em } diff --git a/src/main/resources/lode/swrl-module.xsl b/src/main/resources/lode/swrl-module.xsl index 9b9c456b..6fa6dc07 100644 --- a/src/main/resources/lode/swrl-module.xsl +++ b/src/main/resources/lode/swrl-module.xsl @@ -69,7 +69,11 @@ Copyright (C) 2023, Victor Chavez - + + + +

    diff --git a/src/main/resources/widoco/de.properties b/src/main/resources/widoco/de.properties index 621d0e79..b177d631 100644 --- a/src/main/resources/widoco/de.properties +++ b/src/main/resources/widoco/de.properties @@ -31,6 +31,7 @@ crossRefPlaceHolder=Dieser Abschnitt enthält Details für jede Klasse u classes=Klassen objProp=Object Properties dataProp=Data Properties +extProp=External Properties annProp=Annotation Properties namedIndiv=Named Individuals referencesTitle=Referenzen diff --git a/src/main/resources/widoco/en.properties b/src/main/resources/widoco/en.properties index b185f8e8..b8776594 100644 --- a/src/main/resources/widoco/en.properties +++ b/src/main/resources/widoco/en.properties @@ -31,6 +31,7 @@ crossRefPlaceHolder=This section provides details for each class and property de classes=Classes objProp=Object Properties dataProp=Data Properties +extProp=External Properties annProp=Annotation Properties namedIndiv=Named Individuals referencesTitle=References diff --git a/src/main/resources/widoco/es.properties b/src/main/resources/widoco/es.properties index d5a1e739..4b55162f 100644 --- a/src/main/resources/widoco/es.properties +++ b/src/main/resources/widoco/es.properties @@ -31,6 +31,7 @@ crossRefPlaceHolder=Esta sección introduce más detalles sobre cada classes=Clases objProp=Propiedades de objeto dataProp=Propiedades de datos +extProp=Propiedades externas annProp=Propiedades usadas para anotación namedIndiv=Individuos referencesTitle=Referencias diff --git a/src/test/java/widoco/ExternalEntitiesTest.java b/src/test/java/widoco/ExternalEntitiesTest.java new file mode 100644 index 00000000..51f5ce1f --- /dev/null +++ b/src/test/java/widoco/ExternalEntitiesTest.java @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2024 Victor Chavez + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * This class tests the correct parsing of external properties that cannot + * be transformed with the xslt by analyzing the html generated output. + */ +package widoco; + +import org.jsoup.nodes.Element; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; + +import java.io.File; +import java.util.ArrayList; + +import static org.junit.Assert.fail; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.select.Elements; + + +/** + * + * @author vChavezB + */ +public class ExternalEntitiesTest { + + /** + * Class to store fact properties + */ + class Fact { + private String predicateIRI; + private String predicateType; + private String objectIRI; + private String objectType; + + public Fact(String predicateIRI, String predicateType, String objectIRI, String objectType) { + this.predicateIRI = predicateIRI; + this.predicateType = predicateType; + this.objectIRI = objectIRI; + this.objectType = objectType; + } + + public String getPredicateIRI() { + return predicateIRI; + } + + public String getPredicateType() { + return predicateType; + } + + public String getObjectIRI() { + return objectIRI; + } + + public String getObjectType() { + return objectType; + } + + } + static String docUri = "myDoc"; + Configuration c; + static final private String ONT_NS = "http://www.external-entity.com/testCase/"; + public ExternalEntitiesTest() { + c = new Configuration(); + //set up where the files will be written. Otherwise, an error will be produced + c.setDocumentationURI(docUri); + c.setOverwriteAll(true); + } + + @BeforeClass + public static void setUpClass() { + + } + + @AfterClass + public static void tearDownClass() { + deleteFiles(new File (docUri)); + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + deleteFiles(c.getTmpFile()); + } + + private static void deleteFiles(File folder){ + String[]entries = folder.list(); + for(String s: entries){ + File currentFile = new File(folder.getPath(),s); + if(currentFile.isDirectory()){ + deleteFiles(currentFile); + } + else{ + currentFile.delete(); + } + } + folder.delete(); + } + + /** + * Get div element for a specific class with id + * @param doc + * @param className + * @param id + * @return + */ + private static Element getDiv(Document doc, String className, String id){ + Elements elements = doc.getElementsByAttributeValue("class", className); + + // Iterate over the elements and filter by id + for (Element element : elements) { + if (id.equals(element.id())) { + return element; + } + } + return null; + } + + + /** + * Find first superclass in html of a crossref document + * for a specific class + * @param doc the Jsoup document with the crossref section + * @param class_iri the class iri to look for + * @return Type of superclass. Expected values (type-c,type-ep), null if not found + */ + private static String getSuperClassType(Document doc, String class_iri) { + Element ExtProjectElement = getDiv(doc, "entity", class_iri); + Element ddElement = ExtProjectElement.select("dt:containsOwn(has super-classes) + dd").first(); + Element supElement = ddElement.select("sup").first(); + // Check if the sup element was found + if (supElement != null) { + // Get the entity descriptor class + return supElement.attr("class"); + } else { + return null; + } + } + + /** + * Get the descriptor type located in the sup tag for an individual + * @param doc + * @param individual_iri + * @return + */ + private static String getIndividualClassType(Document doc, String individual_iri) { + Element classEntity = getDiv(doc, "entity", individual_iri); + Element ddElement = classEntity.select("dt:containsOwn(belongs to) + dd").first(); + Element supElement = ddElement.select("sup").first(); + if (supElement != null) { + // Get the entity descriptor class + return supElement.attr("class"); + } else { + return null; + } + } + + /** + * Get the type of class for and individual + * @param doc + * @param individual_iri + * @return + */ + private static String getIndividualClassIRI(Document doc, String individual_iri) { + Element individualEntity = getDiv(doc, "entity", individual_iri); + Element ddElement = individualEntity.select("dt:containsOwn(belongs to) + dd").first(); + Element aElement = ddElement.select("a").first(); + if (aElement != null) { + // Get the entity descriptor class + return aElement.attr("title"); + } else { + return null; + } + } + + /** + * Get a list of individual facts for a specific iri + * @param doc + * @param individual_iri + * @return + */ + private ArrayList getIndividualFacts(Document doc, String individual_iri) { + Element individualEntity = getDiv(doc, "entity", individual_iri); + Element factsDtElement = individualEntity.select("dt:containsOwn(has facts)").first(); + // Create a list to store Fact instances + ArrayList factList = new ArrayList<>(); + if (factsDtElement != null) { + // Find following dd elements + Elements factDDElements = factsDtElement.nextElementSiblings().select("dd"); + + for (Element factElement : factDDElements) { + Elements aElements = factElement.select("a"); + Elements supElements = factElement.select("sup"); + Element predicateA = aElements.get(0); + Element predicateSup = supElements.get(0); + if (predicateA != null && predicateSup != null) { + String predicateIRI = predicateA.attr("title"); + String predicateType = predicateSup.attr("class"); + String objectIRI = ""; + String objectType = ""; + Element spanElement = factElement.selectFirst("span"); + if (aElements.size()==2 && spanElement==null) { + Element nextAElement = factElement.select("a").get(1); + Element nextSupElement = factElement.select("sup").get(1); + if (nextAElement != null && nextSupElement != null) { + objectIRI = nextAElement.attr("title"); + objectType = nextSupElement.attr("class"); + } + } else { + // Literal + if (spanElement!=null) { + objectIRI = spanElement.attr("class"); + objectType = spanElement.text(); + } + } + Fact fact = new Fact(predicateIRI, predicateType, objectIRI, objectType); + factList.add(fact); + } + } + } + return factList; + } + + /** + * Test an individual and its expected class iri and descriptor type + * @param doc + * @param iri + * @param expectedClassIRI + * @param expectedType + */ + static void testIndividual(Document doc, String iri,String expectedClassIRI,String expectedType) { + String entityType = getIndividualClassType(doc,iri); + String classIRI = getIndividualClassIRI(doc,iri); + assert(entityType.equals(expectedType)); + assert(classIRI.equals(expectedClassIRI)); + } + + + /** + * Helper function to assert a fact + * @param fact + * @param expectedPredicateIRI + * @param expectedPredicateType + * @param expectedObjectIRI + * @param expectedObjType + */ + static void testFact(Fact fact,String expectedPredicateIRI, String expectedPredicateType, String expectedObjectIRI, String expectedObjType) { + assert(fact.getPredicateIRI().equals(expectedPredicateIRI)); + assert(fact.getPredicateType().equals(expectedPredicateType)); + assert(fact.getObjectIRI().equals(expectedObjectIRI)); + assert(fact.getObjectType().equals(expectedObjType)); + } + + /** + * Test that parsing external entity works + * Generate the html and look for the facts and + * entity descriptors generated with sup tags. + */ + @org.junit.Test + public void testExternalEntityOntology() { + System.out.println("Testing Ontology: External Entity"); + + try{ + String pathToOnto = "test" + File.separator + "external-entity.ttl"; + c.setFromFile(true); + this.c.setOntologyPath(pathToOnto); + //read the model from file + WidocoUtils.loadModelToDocument(c); + CreateResources.generateDocumentation(c.getDocumentationURI(), c, c.getTmpFile()); + File crossRefFile = new File(c.getDocumentationURI()+"/sections/crossref-en.html"); + Document crossRefDoc = Jsoup.parse(crossRefFile, "UTF-8"); + // Look for superclass of ExtProject + // i.e., http://xmlns.com/foaf/0.1/Project should be recognized as type-c + String extProjectSuperClassType = getSuperClassType(crossRefDoc,ONT_NS+"ExtProject"); + assert(extProjectSuperClassType!=null); + assert(extProjectSuperClassType.equals("type-c")); + testIndividual(crossRefDoc,ONT_NS+"PersonA","http://www.w3.org/2000/10/swap/pim/contact#Person","type-c"); + testIndividual(crossRefDoc,ONT_NS+"PersonB",ONT_NS+"LocalPerson","type-c"); + testIndividual(crossRefDoc,ONT_NS+"Project1",ONT_NS+"ExtProject","type-c"); + ArrayList personAFacts = getIndividualFacts(crossRefDoc, ONT_NS + "PersonA"); + assert(personAFacts.size() == 1); + testFact(personAFacts.get(0),"http://my-external-ont.com/ext/Annotation","type-ap", + "literal","\"external annotation\"@en"); + ArrayList personBFacts = getIndividualFacts(crossRefDoc, ONT_NS + "PersonB"); + assert(personBFacts.size() == 2); + testFact(personBFacts.get(0),"http://xmlns.com/foaf/0.1/knows","type-op", + "http://www.external-entity.com/testCase/PersonA","type-ni"); + testFact(personBFacts.get(1),"http://xmlns.com/foaf/0.1/age","type-dp", + "literal","\"30\"^^integer"); + + ArrayList project1Facts = getIndividualFacts(crossRefDoc, ONT_NS + "Project1"); + assert(project1Facts.size() == 2); + testFact(project1Facts.get(0),"http://xmlns.com/foaf/0.1/fundedBy","type-op", + "http://www.external-entity.com/testCase/PersonA","type-ni"); + testFact(project1Facts.get(1),"http://xmlns.com/foaf/0.1/title","type-dp", + "literal","\"The External Project\"@en"); + + }catch(Exception e){ + fail("Error while running test "+e.getMessage()); + } + } +} diff --git a/test/catalog-v001.xml b/test/catalog-v001.xml new file mode 100644 index 00000000..83347859 --- /dev/null +++ b/test/catalog-v001.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/test/external-entity.ttl b/test/external-entity.ttl new file mode 100644 index 00000000..e86da35c --- /dev/null +++ b/test/external-entity.ttl @@ -0,0 +1,64 @@ +@prefix : . +@prefix ext: . +@prefix owl: . +@prefix rdf: . +@prefix xml: . +@prefix xsd: . +@prefix rdfs: . +@prefix vann: . +@base . + + rdf:type owl:Ontology ; + owl:imports ; + vann:preferredNamespaceUri "http://www.external-entity.com/testCase" . + +################################################################# +# Annotation properties +################################################################# + +### http://my-external-ont.com/ext/Annotation +ext:Annotation rdf:type owl:AnnotationProperty . + + +### http://purl.org/vocab/vann/preferredNamespaceUri +vann:preferredNamespaceUri rdf:type owl:AnnotationProperty . + + +################################################################# +# Classes +################################################################# + +### http://www.external-entity.com/testCase/ExtProject +:ExtProject rdf:type owl:Class ; + rdfs:subClassOf . + + +### http://www.external-entity.com/testCase/LocalPerson +:LocalPerson rdf:type owl:Class . + + +################################################################# +# Individuals +################################################################# + +### http://www.external-entity.com/testCase/PersonA +:PersonA rdf:type owl:NamedIndividual , + ; + ext:Annotation "external annotation"@en . + + +### http://www.external-entity.com/testCase/PersonB +:PersonB rdf:type owl:NamedIndividual , + :LocalPerson ; + :PersonA ; + 30 . + + +### http://www.external-entity.com/testCase/Project1 +:Project1 rdf:type owl:NamedIndividual , + :ExtProject ; + :PersonA ; + "The External Project"@en . + + +### Generated by the OWL API (version 4.5.26.2023-11-26T01:45:50Z) https://github.com/owlcs/owlapi