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/widoco/Constants.java b/src/main/java/widoco/Constants.java index c569c1d5..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"; @@ -1510,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 ecb34925..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()) { 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/resources/lode/cs.xml b/src/main/resources/lode/cs.xml index 3f7fe75f..20dcc210 100644 --- a/src/main/resources/lode/cs.xml +++ b/src/main/resources/lode/cs.xml @@ -88,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 b0f735f1..e0abe4e0 100644 --- a/src/main/resources/lode/de.xml +++ b/src/main/resources/lode/de.xml @@ -89,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 6574fc90..389601b0 100644 --- a/src/main/resources/lode/en.xml +++ b/src/main/resources/lode/en.xml @@ -90,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 3a4a35ec..0c0e9988 100644 --- a/src/main/resources/lode/es.xml +++ b/src/main/resources/lode/es.xml @@ -88,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 2cc73565..8d3fd795 100644 --- a/src/main/resources/lode/extra.css +++ b/src/main/resources/lode/extra.css @@ -46,6 +46,11 @@ h1 { color:mediumpurple; } +.type-ep { + cursor:help; + color:mediumpurple; +} + .type-ap { cursor:help; color:var(--type-ap); diff --git a/src/main/resources/lode/extraction.xsl b/src/main/resources/lode/extraction.xsl index 2dafcced..ce714212 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 "> + + + + + + " @@ -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 "> + + * + * 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