All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.opencaesar.owl.doc.OwlDocApp Maven / Gradle / Ivy

There is a newer version: 2.14.0
Show newest version
/**
 * Copyright 2019 California Institute of Technology ("Caltech").
 * U.S. Government sponsorship acknowledged.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.opencaesar.owl.doc;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.apache.jena.graph.Graph;
import org.apache.jena.ontology.AnnotationProperty;
import org.apache.jena.ontology.DatatypeProperty;
import org.apache.jena.ontology.Individual;
import org.apache.jena.ontology.ObjectProperty;
import org.apache.jena.ontology.OntClass;
import org.apache.jena.ontology.OntDocumentManager;
import org.apache.jena.ontology.OntModel;
import org.apache.jena.ontology.OntModelSpec;
import org.apache.jena.ontology.OntProperty;
import org.apache.jena.ontology.Ontology;
import org.apache.jena.ontology.Restriction;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.rdf.model.Property;
import org.apache.jena.rdf.model.RDFList;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.rdf.model.StmtIterator;
import org.apache.jena.riot.Lang;
import org.apache.jena.riot.RDFLanguages;
import org.apache.jena.vocabulary.OWL2;
import org.apache.jena.vocabulary.RDF;
import org.apache.jena.vocabulary.RDFS;
import org.apache.log4j.Appender;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jgrapht.alg.TransitiveReduction;
import org.jgrapht.alg.cycle.TiernanSimpleCycles;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.SimpleDirectedGraph;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;

import com.beust.jcommander.IParameterValidator;
import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;

import net.sourceforge.plantuml.code.TranscoderUtil;

/**
 * A utility for running a reasoner on the set of ontologies in scope of an OASIS XML catalog.
 */
public class OwlDocApp {

	/**
	 * The default OWL file extensions
	 */
	public static final String[] DEFAULT_EXTENSIONS = {"owl", "ttl"};

	private static final List extensions = Arrays.asList(
		"fss", "owl", "rdf", "xml", "n3", "ttl", "rj", "nt", "jsonld", "trig", "trix", "nq"
	);
	
	private static final String CSS_DEFAULT = "default.css";
	private static final String CSS_MAIN = "main.css";

	private static final String IMG_ONTOLOGY = "https://help.eclipse.org/latest/topic/org.eclipse.jdt.doc.user/images/org.eclipse.jdt.ui/obj16/package_obj.svg";
	private static final String IMG_CLASS = "https://help.eclipse.org/latest/topic/org.eclipse.jdt.doc.user/images/org.eclipse.jdt.ui/obj16/methpub_obj.svg";
	private static final String IMG_DATATYPE = "https://help.eclipse.org/latest/topic/org.eclipse.jdt.doc.user/images/org.eclipse.jdt.ui/obj16/methpri_obj.svg";
	private static final String IMG_PROPERTY = "https://help.eclipse.org/latest/topic/org.eclipse.jdt.doc.user/images/org.eclipse.jdt.ui/obj16/methpro_obj.svg";
	private static final String IMG_INDIVIDUAL = "https://help.eclipse.org/latest/topic/org.eclipse.jdt.doc.user/images/org.eclipse.jdt.ui/obj16/field_public_obj.svg";
	private static final String IMG_ITEM = "https://help.eclipse.org/latest/topic/org.eclipse.jdt.doc.user/images/org.eclipse.jdt.ui/obj16/methdef_obj.svg";
	
	private static class Options {
		@Parameter(
			names = { "--input-catalog-path", "-c"},
			description = "path to the input OWL catalog (Required)",
			validateWith = CatalogPathValidator.class,
			required = true)
		private String inputCatalogPath;
		
		@Parameter(
			names={"--input-catalog-title", "-t"}, 
			description="Title of OML input catalog (Optional)")
		private String inputCatalogTitle = "OWL Ontology Index";

		@Parameter(
			names={"--input-catalog-version", "-v"}, 
			description="Version of OML input catalog (Optional)")
		private String inputCatalogVersion = "";
			
		@Parameter(
			names = { "--input-ontology-iri", "-i"},
			description = "iri of input OWL ontology (Optional, by default all ontologies in catalog)")
		private List inputOntologyIris = new ArrayList<>();
				
		@Parameter(
			names = {"--input-file-extension", "-e"},
			description = "input file extension (owl and ttl by default, options: owl, rdf, xml, rj, ttl, n3, nt, trig, nq, trix, jsonld, fss)",
			validateWith = FileExtensionValidator.class)
	    private List inputFileExtensions = new ArrayList<>(Arrays.asList(DEFAULT_EXTENSIONS));
		
		@Parameter(
			names= { "--output-folder-path", "-o" }, 
			description="Path of Bikeshed output folder", 
			validateWith=OutputFolderPathValidator.class, 
			required=true)
		private String outputFolderPath = ".";

		@Parameter(
			names= { "--output-case-sensitive","-s" }, 
			description="Whether output paths are case sensitive", 
			help=true)
		private boolean outputCaseSensitive;
			
		@Parameter(
			names= { "--css-file-path","-css" }, 
			description="Path of a css file", 
			converter = URLConverter.class,
			help=true)
		private URL cssFilePath = OwlDocApp.class.getClassLoader().getResource(CSS_DEFAULT);

		@Parameter(
			names = {"--debug", "-d"},
			description = "Shows debug logging statements")
		private boolean debug;
		
		@Parameter(
			names = {"--help", "-h"},
			description = "Displays summary of options",
			help = true)
		private boolean help;
	}

	private final Options options = new Options();

	private class OwlModel {
		private OntModel ontModel;
		private List ontologies;
		private List classes;
		private List datatypes;
		private List annotationProperties;
		private List datatypeProperties;
		private List objectProperties;
		private List individuals;
		
		public OwlModel(OntModel ontModel) {
			this.ontModel = ontModel;
			ontologies = sortByIri(ontModel.listOntologies().filterKeep(i-> hasTerms(i)).toList());
			classes = sortByName(ontModel.listNamedClasses().toList());
			datatypes = sortByName(ontModel.listSubjectsWithProperty(RDF.type, RDFS.Datatype).toList());
			annotationProperties = sortByName(ontModel.listAnnotationProperties().toList());
			objectProperties = sortByName(ontModel.listObjectProperties().toList());
			datatypeProperties = sortByName(ontModel.listDatatypeProperties().toList());
			individuals = sortByName(ontModel.listIndividuals().toList());
			
			// Ignore DC elements to avoid clutter
			ontologies.removeIf(i -> i.hasURI("http://purl.org/dc/elements/1.1"));
			annotationProperties.removeIf(i -> i.getURI().startsWith("http://purl.org/dc/elements/1.1"));

			// Ignore OML to avoid clutter
			ontologies.removeIf(i -> i.hasURI("http://opencaesar.io/oml"));
			annotationProperties.removeIf(i -> i.getURI().startsWith("http://opencaesar.io/oml"));
		}

		private boolean hasTerms(Ontology o) {
			var iri = o.getURI();
			var resources = ontModel.listResourcesWithProperty(RDF.type)
					.filterDrop(i -> i.isAnon())
					.filterKeep(i -> i.getURI().startsWith(iri))
					.toList();
			resources.removeIf(i -> ontModel.getOntology(i.getURI()) != null);
			return !resources.isEmpty();
		}
	}
	
	private final static Logger LOGGER = Logger.getLogger(OwlDocApp.class);

	/**
	 * Application for running a reasoner on the set of ontologies in scope of an OASIS XML catalog.
	 * @param args Application arguments.
	 * @throws Exception Error
	 */
	public static void main(final String... args) throws Exception {
		final OwlDocApp app = new OwlDocApp();
		final JCommander builder = JCommander.newBuilder().addObject(app.options).build();
		builder.parse(args);
		if (app.options.help) {
			builder.usage();
			return;
		}
		if (app.options.debug) {
			final Appender appender = LogManager.getRootLogger().getAppender("stdout");
			((AppenderSkeleton) appender).setThreshold(Level.DEBUG);
		}
		app.run();
	}

	/**
	 * Creates a new OwlDocApp object
	 */
	public OwlDocApp() {
	}
	
	private void run() throws Exception {
		LOGGER.info("=================================================================");
		LOGGER.info("                        S T A R T");
		LOGGER.info("                     OWL Doc " + getAppVersion());
		LOGGER.info("=================================================================");
        LOGGER.info(("Input Catalog Path = " + options.inputCatalogPath));
        LOGGER.info(("Input Catalog Title = " + options.inputCatalogTitle));
        LOGGER.info(("Input Catalog Version = " + options.inputCatalogVersion));
        LOGGER.info(("Input Ontology Iris = " + options.inputOntologyIris));
        LOGGER.info(("Input File Extensions = " + options.inputFileExtensions));
        LOGGER.info(("Output Folder Path = " + options.outputFolderPath));
        LOGGER.info(("Output Case Sensitive = " + options.outputCaseSensitive));
	    	    	    
		OwlCatalog catalog = OwlCatalog.create(new File(options.inputCatalogPath).toURI());
		Map fileMap = catalog.getFileUriMap(options.inputFileExtensions);
		OntDocumentManager mgr = new OntDocumentManager();
		for (var entry : fileMap.entrySet()) {
			mgr.addAltEntry(entry.getKey(), entry.getValue().toString());
		}
		
		if (options.inputOntologyIris.isEmpty()) {
			options.inputOntologyIris.addAll(fileMap.keySet());
		}
		
		OntModelSpec s = new OntModelSpec(OntModelSpec.OWL_MEM);
		s.setDocumentManager(mgr);
		OntModel ontModel = ModelFactory.createOntologyModel(s);
		for (var iri : options.inputOntologyIris) {
			mgr.getFileManager().readModelInternal(ontModel, iri);
		}
		
		OwlModel owlModel = new OwlModel(ontModel);
		final var outputFiles = new HashMap();
		
		outputFiles.putAll(copyCssFile());
		
		// create output files
		outputFiles.putAll(generateIndexFile(owlModel));
		outputFiles.putAll(generateSummaryFile(owlModel));
		outputFiles.putAll(generateNavigationFile(owlModel));
		outputFiles.putAll(generateClassHierarchyFile(owlModel));
		outputFiles.putAll(generateClassesFile(owlModel));
		outputFiles.putAll(generateDatatypesFile(owlModel));
		outputFiles.putAll(generatePropertiesFile(owlModel.annotationProperties, "Annotation Properties"));
		outputFiles.putAll(generatePropertiesFile(owlModel.datatypeProperties, "Datatype Properties"));
		outputFiles.putAll(generatePropertiesFile(owlModel.objectProperties, "Object Properties"));
		outputFiles.putAll(generateIndividualsFile(owlModel));
		
		// save output files				
		outputFiles.forEach((file, result) -> {
			BufferedWriter out = null;
			try {
				file.getParentFile().mkdirs();
				final var filePath = file.getCanonicalPath();
				out = new BufferedWriter(new FileWriter(filePath));

				LOGGER.info("Saving: "+filePath);
			    out.write(result.toString());
			    
			    if (file.getName().endsWith(".sh")) {
					file.setExecutable(true);
			    }
			}
			catch (IOException e) {
			    System.out.println(e);
			}
			finally {
				if (out != null) {
					try {
						out.close();
					} catch (IOException e) {
						// no handling
					}
				}
			}
			
		});

		ontModel.close();
		
    	LOGGER.info("=================================================================");
		LOGGER.info("                          E N D");
		LOGGER.info("=================================================================");
	}
	
	//----------------------------------------------------------------------------------
	
	private Map copyCssFile() throws IOException {
		var inputStream = options.cssFilePath.openStream();
		 Scanner s = new Scanner(inputStream);
		 String content = s.useDelimiter("\\A").hasNext() ? s.next() : "";
		 s.close();
		var file = new File(options.outputFolderPath+File.separator+CSS_MAIN);
		return Collections.singletonMap(file, content);
	}
	
	private Map generateIndexFile(OwlModel owlModel) throws IOException {
		final var content = new StringBuffer();
		
		content.append(
			"""
			
			   
			      Ontology Documentation
			   
			   
			      
			         
			         
			      
			      
			   
			
			""");
		
		final var file = new File(options.outputFolderPath+File.separator+"index.html").getCanonicalFile();
		return Collections.singletonMap(file, content.toString());
	}

	private Map generateSummaryFile(OwlModel owlModel) throws IOException {
		final var content = new StringBuffer();
		final var path = options.outputFolderPath+File.separator+"summary.html";

		DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");  
	    LocalDateTime now = LocalDateTime.now();  
	    var timestamp = dtf.format(now); 
	    var image = getOntologiesImage(CSS_DEFAULT, owlModel);

		content.append(String.format(
			"""
			
			   
			      %s
			      
			   
			   
		          

%s v%s

Updated on: %s


""", options.inputCatalogTitle, getRelativeCSSPath(path), options.inputCatalogTitle, options.inputCatalogVersion, timestamp, image)); final var file = new File(path).getCanonicalFile(); return Collections.singletonMap(file, content.toString()); } private Map generateNavigationFile(OwlModel owlModel) throws IOException { final var files = new HashMap(); final var path = options.outputFolderPath+File.separator+"navigation.html"; final var elements = new StringBuffer(); for (var s : owlModel.ontologies) { var iri = s.getURI(); var uri = URI.create(iri); var relativePath = uri.getAuthority()+uri.getPath()+hashCode(iri); var path1 = options.outputFolderPath+File.separator+relativePath+".html"; var path2 = options.outputFolderPath+File.separator+relativePath+"_2.html"; elements.append(String.format( """ %s """, IMG_ONTOLOGY, getRelativePath(path, path2), iri)); var axioms = getAxioms(path1, s.listProperties()); var classes = getTerms(path1, "Classes", iri, IMG_CLASS, owlModel.classes); var datatypes = getTerms(path1, "Datatypes", iri, IMG_DATATYPE, owlModel.datatypes); var annotationProperties = getTerms(path1, "Annotation Properties", iri, IMG_PROPERTY, owlModel.annotationProperties); var datatypeProperties = getTerms(path1, "Datatype Properties", iri, IMG_PROPERTY, owlModel.datatypeProperties); var objectProperties = getTerms(path1, "Object Properties", iri, IMG_PROPERTY, owlModel.objectProperties); var individuals = getTerms(path1, "Individuals", iri, IMG_INDIVIDUAL, owlModel.individuals); final var content1 = new StringBuffer(String.format( """ %s

%s

%s %s %s %s %s %s """, iri, getRelativeCSSPath(path2), getRelativePath(path2, path1), iri, classes, datatypes, annotationProperties, datatypeProperties, objectProperties, individuals )); files.put(new File(path2).getCanonicalFile(), content1.toString()); final var content2 = new StringBuffer(String.format( """ %s

Ontology %s


%s """, iri, getRelativeCSSPath(path1), iri, axioms )); files.put(new File(path1).getCanonicalFile(), content2.toString()); }; String content = String.format( """ Ontologies

Index

 Class Hierarchy
 All Classes
 All Datatypes
 All Annotation Properties
 All Datatype Properties
 All Object Properties
 All Individuals

Ontologies

%s
""", getRelativeCSSPath(path), elements.toString().replaceAll("\n", "\n\t\t\t\t\t")); final var file = new File(path).getCanonicalFile(); files.put(file, content.toString()); return files; } private Map generateClassesFile(OwlModel owlModel) throws IOException { final var files = new HashMap(); final var path = options.outputFolderPath+File.separator+"classes.html"; final var elements = new StringBuffer(); Function qualifiedType = i -> { if (i.getOnProperty().isDatatypeProperty()) return i.getProperty(OWL2.onDataRange).getObject().asResource(); else return i.getProperty(OWL2.onClass).getObject().asResource(); }; for (var s : owlModel.classes) { var localName = localName(s); var iri = s.getURI(); var uri = URI.create(iri); var relativePath = uri.getAuthority()+uri.getPath()+(uri.getFragment() == null ? "" : File.separator+uri.getFragment())+hashCode(iri); var path1 = options.outputFolderPath+File.separator+relativePath+".html"; elements.append(String.format( """ %s """, IMG_CLASS, getRelativePath(path, path1), localName)); Function restrictionFunc = i -> { var restriction = (Restriction)i; var property = asString(path1, restriction.getOnProperty()); if (restriction.isAllValuesFromRestriction()) { property += " → all values from " + asString(path1, restriction.asAllValuesFromRestriction().getAllValuesFrom()); } else if (restriction.isSomeValuesFromRestriction()) { property += " has some values from " + asString(path1, restriction.asSomeValuesFromRestriction().getSomeValuesFrom()); } else if (restriction.isHasValueRestriction()) { property += " → value of "+asString(path1, restriction.asHasValueRestriction().getHasValue()); } else if (restriction.isMinCardinalityRestriction()) { property += " → min cardinality of "+restriction.asMinCardinalityRestriction().getMinCardinality(); } else if (restriction.isMaxCardinalityRestriction()) { property += " → max cardinality of "+restriction.asMaxCardinalityRestriction().getMaxCardinality(); } else if (restriction.isCardinalityRestriction()) { property += " → exact cardinality of "+restriction.asCardinalityRestriction().getCardinality(); } else if (restriction.getProperty(OWL2.minQualifiedCardinality) != null) { property += " → min cardinality of "+restriction.getProperty(OWL2.minQualifiedCardinality).getInt(); property += " "+ asString(path1, qualifiedType.apply(restriction)); } else if (restriction.getProperty(OWL2.maxQualifiedCardinality) != null) { property += " → max cardinality of "+restriction.getProperty(OWL2.maxQualifiedCardinality).getInt(); property += " "+ asString(path1, qualifiedType.apply(restriction)); } else if (restriction.getProperty(OWL2.qualifiedCardinality) != null) { property += " → exact cardinality of "+restriction.getProperty(OWL2.qualifiedCardinality).getInt(); property += " "+ asString(path1, qualifiedType.apply(restriction)); } return property; }; var image = getClassImage(path1, s); var axioms = getAxioms(path1, s.listProperties()); var superClasses = getNodes(path1, "directSuperClassOf", IMG_CLASS, s.listSubClasses(true).filterDrop(i -> i.isAnon()).toList()); var properties = getNodes("directDomainOf", owlModel.ontModel.listSubjectsWithProperty(RDFS.domain, s).toList(), IMG_PROPERTY, i -> asString(path1, i)+" → "+asString(path1, owlModel.ontModel.getOntProperty(i.asResource().getURI()).listRange().toList())); var restrictedProperties = getNodes("directHasRestrictionOn", s.listSuperClasses(true).filterKeep(i -> i.isRestriction()).mapWith(i -> i.asRestriction()).toList(), IMG_PROPERTY, restrictionFunc); final var content = new StringBuffer(String.format( """ %s

%s
Class %s


%s %s %s %s """, abbreviatedIri(s), getRelativeCSSPath(path1), iri, abbreviatedIri(s), image, axioms, superClasses, properties, restrictedProperties)); files.put(new File(path1).getCanonicalFile(), content.toString()); }; String content = String.format( """ All Classes

All Classes

%s
""", getRelativeCSSPath(path), elements.toString().replaceAll("\n", "\n\t\t\t\t\t")); final var file = new File(path).getCanonicalFile(); files.put(file, content.toString()); return files; } private Map generateClassHierarchyFile(OwlModel owlModel) throws IOException { final var path = options.outputFolderPath+File.separator+"class-hierarchy.html"; final var elements = new StringBuffer(); Deque stack = new ArrayDeque<>(); Map levels = new HashMap<>(); stack.addAll(owlModel.ontModel.listHierarchyRootClasses().filterDrop(i -> i.isAnon()).toList()); stack.forEach(i -> levels.put(i, 0)); while (!stack.isEmpty()) { OntClass aClass = stack.pop(); StringBuffer spaces = new StringBuffer(); int indentLevel = levels.get(aClass); for (int i = 0; i < indentLevel; i++) { spaces.append("    "); } var iri = aClass.getURI(); var uri = URI.create(iri); var relativePath = uri.getAuthority()+uri.getPath()+(uri.getFragment() == null ? "" : File.separator+uri.getFragment())+hashCode(iri); var path1 = options.outputFolderPath+File.separator+relativePath+".html"; elements.append(String.format( """ %s %s """, spaces.toString(), IMG_CLASS, getRelativePath(path, path1), aClass.getLocalName())); List subClasses = aClass.listSubClasses(true).filterDrop(i -> i.isAnon()).toList(); for (OntClass subClass : subClasses) { levels.put(subClass, indentLevel+1); stack.push(subClass); } } final var contents = String.format( """ Class Hierarchy

Class Hierarchy

%s
""", getRelativeCSSPath(path), elements.toString().replaceAll("\n", "\n\t\t\t\t\t")); final var file = new File(path).getCanonicalFile(); return Collections.singletonMap(file, contents.toString()); } private Map generateDatatypesFile(OwlModel owlModel) throws IOException { final var files = new HashMap(); final var path = options.outputFolderPath+File.separator+"datatypes.html"; final var elements = new StringBuffer(); for (var s : owlModel.datatypes) { var localName = localName(s); var iri = s.getURI(); var uri = URI.create(iri); var relativePath = uri.getAuthority()+uri.getPath()+(uri.getFragment() == null ? "" : File.separator+uri.getFragment())+hashCode(iri); var path1 = options.outputFolderPath+File.separator+relativePath+".html"; elements.append(String.format( """ %s """, IMG_DATATYPE, getRelativePath(path, path1), localName)); var listOfLiterals = owlModel.ontModel.listObjectsOfProperty(s, OWL2.equivalentClass).toList().stream() .map(i -> i.asResource()) .filter(i -> i.hasProperty(OWL2.oneOf)) .flatMap(i -> i.getProperty(OWL2.oneOf).getObject().as(RDFList.class).asJavaList().stream()) .collect(Collectors.toList()); var axioms = getAxioms(path1, s.listProperties()); var literals = getNodes(path1, "rdf:typeOf", IMG_ITEM, listOfLiterals); final var content = new StringBuffer(String.format( """ %s

%s
Datatype %s


%s %s """, abbreviatedIri(s), getRelativeCSSPath(path1), iri, abbreviatedIri(s), axioms, literals)); files.put(new File(path1).getCanonicalFile(), content.toString()); }; String content = String.format( """ All Datatypes

All Datatypes

%s
""", getRelativeCSSPath(path), elements.toString().replaceAll("\n", "\n\t\t\t\t\t")); final var file = new File(path).getCanonicalFile(); files.put(file, content.toString()); return files; } private Map generatePropertiesFile(List properties, String title) throws IOException { final var files = new HashMap(); final var path = options.outputFolderPath+File.separator+title.replace(' ', '_').toLowerCase()+".html"; final var elements = new StringBuffer(); for (var s : properties) { var localName = localName(s); var iri = s.getURI(); var uri = URI.create(iri); var relativePath = uri.getAuthority()+uri.getPath()+(uri.getFragment() == null ? "" : File.separator+uri.getFragment())+hashCode(iri); var path1 = options.outputFolderPath+File.separator+relativePath+".html"; elements.append(String.format( """ %s """, IMG_PROPERTY, getRelativePath(path, path1), localName)); var axioms = getAxioms(path1, s.listProperties()); final var content = new StringBuffer(String.format( """ %s

%s
Property %s


%s """, abbreviatedIri(s), getRelativeCSSPath(path1), iri, abbreviatedIri(s), axioms)); files.put(new File(path1).getCanonicalFile(), content.toString()); }; String content = String.format( """ All %s

All %s

%s
""", title, getRelativeCSSPath(path), title, elements.toString().replaceAll("\n", "\n\t\t\t\t\t")); final var file = new File(path).getCanonicalFile(); files.put(file, content.toString()); return files; } private Map generateIndividualsFile(OwlModel owlModel) throws IOException { final var files = new HashMap(); final var path = options.outputFolderPath+File.separator+"individuals.html"; final var elements = new StringBuffer(); for (var s : owlModel.individuals) { var localName = localName(s); var iri = s.getURI(); var uri = URI.create(iri); var relativePath = uri.getAuthority()+uri.getPath()+(uri.getFragment() == null ? "" : File.separator+uri.getFragment())+hashCode(iri); var path1 = options.outputFolderPath+File.separator+relativePath+".html"; elements.append(String.format( """ %s """, IMG_INDIVIDUAL, getRelativePath(path, path1), localName)); var axioms = getAxioms(path1, s.listProperties()); final var content = new StringBuffer(String.format( """ %s

%s
Individual %s


%s """, abbreviatedIri(s), getRelativeCSSPath(path1), iri, abbreviatedIri(s), axioms)); files.put(new File(path1).getCanonicalFile(), content.toString()); }; String content = String.format( """ All Individuals

All Individuals

%s
""", getRelativeCSSPath(path), elements.toString().replaceAll("\n", "\n\t\t\t\t\t")); final var file = new File(path).getCanonicalFile(); files.put(file, content.toString()); return files; } private String getAxioms(String contextFilePath, StmtIterator i) { var propertyToValues = new TreeMap>(new Comparator() { @Override public int compare(Property o1, Property o2) { return o1.getURI().compareTo(o2.getURI()); } }); while (i.hasNext()) { var stmt = i.next(); var property = stmt.getPredicate(); var object = stmt.getObject(); if (!object.isAnon()) { var values = propertyToValues.get(property); if (values == null) { propertyToValues.put(property, values = new TreeSet(new Comparator() { @Override public int compare(RDFNode o1, RDFNode o2) { return o1.toString().compareTo(o2.toString()); } })); } values.add(object); } } var propertiesWithValues = propertyToValues.keySet().stream() .filter(j -> !propertyToValues.get(j).isEmpty()) .collect(Collectors.toList()); var axioms = new StringBuffer(); for (var property : propertiesWithValues.stream().filter(j -> propertyToValues.get(j).iterator().next().isLiteral()).collect(Collectors.toList())) { axioms.append(getNodes(contextFilePath, abbreviatedIri(property), IMG_ITEM, propertyToValues.get(property))); } for (var property : propertiesWithValues.stream().filter(j -> propertyToValues.get(j).iterator().next().isResource()).collect(Collectors.toList())) { axioms.append(getNodes(contextFilePath, abbreviatedIri(property), IMG_ITEM, propertyToValues.get(property))); } return axioms.toString(); } private String getTerms(String contextFilePath, String title, String ontologyIri, String image, Collection terms) { var ontoloyTerms = terms.stream() .filter(i -> i.getURI().startsWith(ontologyIri)) .collect(Collectors.toList()); var table = getTable(ontoloyTerms, image, i -> asLocalName(contextFilePath, (Resource)i)); return ontoloyTerms.isEmpty() ? "" : String.format( """

%s

%s """, title, table); } private String getNodes(String contextFilePath, String title, String image, Collection terms) { return getNodes(title, terms, image, i -> asString(contextFilePath, i)); } private String getNodes(String title, Collection terms, String image, Function getLabel) { var table = getTable(terms, image, getLabel); return terms.isEmpty() ? "" : String.format( """

%s

%s
""", title, table); } private String getTable(Collection nodes, String image, Function getLabel) { var s = new StringBuffer(); for (var node : sortNodes(nodes, getLabel)) { s.append(String.format( """ %s """, image, getLabel.apply(node) )); } return s.length() == 0 ? "" : String.format( """ %s

""", s); } private String getClassImage(String contextFilePath, OntClass ontClass) { String thisClass = String.format( """ class "%s" [[%s]] """, abbreviatedIri(ontClass), path(contextFilePath, ontClass.getURI())); var superClasses = String.join("\n", ontClass.listSuperClasses(true).filterKeep(i -> i.isURIResource()).mapWith(i -> String.format( """ class "%s" [[%s]] "%s" -up-|> "%s" """, abbreviatedIri(i), path(contextFilePath, ontClass.getURI()), abbreviatedIri(ontClass), abbreviatedIri(i) )).toList()); var subClasses = String.join("\n", ontClass.listSubClasses(true).filterKeep(i -> i.isURIResource()).mapWith(i -> String.format( """ class "%s" [[%s]] hide "%s" members "%s" -up-|> "%s" """, abbreviatedIri(i), path(contextFilePath, ontClass.getURI()), abbreviatedIri(i), abbreviatedIri(i), abbreviatedIri(ontClass) )).toList()); return plantUmlImage(thisClass+superClasses+subClasses); } /** * Import Edge */ public static class Import extends DefaultEdge { private static final long serialVersionUID = 1L; /** Default Constructor */ public Import() {} public Ontology getSource() {return (Ontology)super.getSource();} public Ontology getTarget() {return (Ontology)super.getTarget();} }; private String getOntologiesImage(String contextFilePath, OwlModel owlModel) { var graph = new SimpleDirectedGraph(Import.class); var packages = String.join("\n", owlModel.ontologies.stream().map(i -> { graph.addVertex(i); return String.format( """ package "%s" {} """, i.getURI()); }).toList()); owlModel.ontologies.stream().forEach(i -> i.listImports().filterKeep(k -> owlModel.ontologies.contains(k)).mapWith(k -> k.asOntology()).forEach(j -> { graph.addEdge(i, j); })); var algorithm = new TiernanSimpleCycles(graph); var cycles = algorithm.findSimpleCycles(); while (!cycles.isEmpty()) { var maxCycle = cycles.stream().max(Comparator.comparingInt(List::size)).orElse(null); graph.removeEdge(maxCycle.get(0), maxCycle.get(1)); cycles = algorithm.findSimpleCycles(); } TransitiveReduction.INSTANCE.reduce(graph); var imports = String.join("\n", graph.edgeSet().stream().map(i -> String.format( """ "%s" -.> "%s" """, i.getSource().getURI(), i.getTarget().getURI()) ).collect(Collectors.toList())); return plantUmlImage("set separator none\n"+ packages + imports); } //---------------------------------------------------------------------------------- private static List sortNodes(Collection nodes, Function getLabel) { var sorted = new ArrayList<>(nodes); sorted.sort((x1, x2) -> getLabel.apply(x1).compareTo(getLabel.apply(x2))); return sorted; } private static List sortResourcesi(List resources, Function getLabel) { var filtered = resources.stream().filter(i -> !i.isAnon()).collect(Collectors.toList()); filtered.sort((x1, x2) -> getLabel.apply(x1).compareTo(getLabel.apply(x2))); return filtered; } private static List sortByIri(List resources) { return sortResourcesi(resources, i -> ((Resource)i).getURI()); } private static List sortByName(List resources) { return sortResourcesi(resources, i -> localName((Resource)i)); } private static final Pattern LINK = Pattern.compile("\\{\\{([^\\s\\}]+)\s*(.*?)\\}\\}"); private String replaceLinks(String contextFilePath, String input, OntModel ontModel) { Matcher matcher = LINK.matcher(input); return matcher.replaceAll(match -> { String url = match.group(1); String text = match.group(2); String name = url; if (url.contains("#")) { String[] iri = url.split("#"); name = iri[1]; } else if (url.contains("/")) { int index = url.lastIndexOf('/'); name = url.substring(index+1); } else { String[] iri = url.split(":"); if (iri.length == 2) { String prefix = iri[0]; name = iri[1]; String ns = null; for (Graph g : ontModel.getSubGraphs()) { ns = g.getPrefixMapping().getNsPrefixURI(prefix); if (ns != null) { break; } } url = ns+name; } } if (text.length() == 0) { text = name; } return String.format("%s", path(contextFilePath, url), text); }); } private static String plantUmlImage(String diagram) { try { return TranscoderUtil.getDefaultTranscoder().encode(diagram); } catch (IOException e) { e.printStackTrace(); return ""; } } private String hashCode(String s) { return options.outputCaseSensitive ? "" : "_"+Math.abs(s.hashCode()); } private String asString(String contextFilePath, RDFNode node) { if (node.isResource()) { var resource = node.asResource(); var iri = resource.getURI(); var label = abbreviatedIri(resource); if (isExternalResource(resource)) { return String.format("%s", iri, label); } else { var path = path(contextFilePath, iri); return String.format("%s", path, label); } } return replaceLinks(contextFilePath, wrapUntaggedTextWithPreTag(node.toString()), (OntModel)node.getModel()); } private String asLocalName(String contextFilePath, Resource resource) { var iri = resource.getURI(); var label = resource.getLocalName(); if (isExternalResource(resource)) { return String.format("%s", iri, label); } else { var path = path(contextFilePath, iri); return String.format("%s", path, label); } } private String asString(String contextFilePath, List nodes) { return String.join(" & ", nodes.stream() .map(i -> asString(contextFilePath, i)) .collect(Collectors.toList())); } private boolean isExternalResource(Resource r) { var uri = r.getURI(); var ontModel = (OntModel) r.getModel(); var ontologies = ontModel.listOntologies().toList(); for (var ontology : ontologies) { var ontologyURI = ontology.getURI(); if (uri.startsWith(ontologyURI)) { return false; } } return true; } private String path(String contextFilePath, String iri) { var uri = URI.create(iri); var relativePath = uri.getAuthority()+uri.getPath()+(uri.getFragment() == null ? "" : File.separator+uri.getFragment())+hashCode(iri); var path = options.outputFolderPath+File.separator+relativePath+".html"; return getRelativePath(contextFilePath, path); } private static String abbreviatedIri(Resource resource) { var localName = localName(resource); String prefix = prefix(resource); return (prefix != null) ? prefix+":"+localName : resource.getURI(); } private static String prefix(Resource resource) { var namespace = namespace(resource); String prefix = resource.getModel().getGraph().getPrefixMapping().getNsURIPrefix(namespace); return prefix; } private static String localName(Resource resource) { var iri = resource.getURI(); int index = iri.lastIndexOf("#"); if (index == -1) { index = iri.lastIndexOf("/"); } return (index != -1) ? iri.substring(index+1) : resource.getLocalName(); } private static String namespace(Resource resource) { var iri = resource.getURI(); int index = iri.lastIndexOf("#"); if (index == -1) { index = iri.lastIndexOf("/"); } return (index != -1) ? iri.substring(0, index+1) : resource.getNameSpace(); } private static String wrapUntaggedTextWithPreTag(String html) { Document document = Jsoup.parseBodyFragment(html); StringBuilder processedHtml = new StringBuilder(); if (document.body().childNodes().size() == 1) { return html; } for (Node node : document.body().childNodes()) { if (node instanceof TextNode) { String untaggedText = ((TextNode)node).getWholeText(); if (!untaggedText.isEmpty()) { processedHtml.append("

").append(untaggedText).append("
"); } } else { processedHtml.append(node.outerHtml()); } } return processedHtml.toString(); } private String getRelativeCSSPath(String contextFilePath) { return getRelativePath(contextFilePath, options.outputFolderPath+File.separator+CSS_MAIN); } private String getRelativePath(String contextFilePath, String targetFilePath) { var path1 = Paths.get(contextFilePath).getParent(); var path2 = Paths.get(targetFilePath); return path1.relativize(path2).toString(); } //---------------------------------------------------------------------------------- /** * A parameter validator for an OASIS XML catalog path. */ public static class CatalogPathValidator implements IParameterValidator { /** * Creates a new CatalogPath object */ public CatalogPathValidator() { } @Override public void validate(final String name, final String value) throws ParameterException { File file = new File(value); if (!file.exists() || !file.getName().endsWith("catalog.xml")) { throw new ParameterException("Parameter " + name + " should be a valid OWL catalog path"); } } } /** * A parameter validator for a file with one of the supported extensions */ public static class FileExtensionValidator implements IParameterValidator { /** * Creates a new FileExtensionValidator object */ public FileExtensionValidator() { } @Override public void validate(final String name, final String value) throws ParameterException { if (!extensions.contains(value)) { throw new ParameterException("Parameter " + name + " should be a valid extension, got: " + value + " recognized extensions are: " + extensions.stream().reduce( (x,y) -> x + " " + y) ); } } } /** * A parameter validator for an output RDF file. */ public static class OutputFileExtensionValidator implements IParameterValidator { /** * Creates a new OutputFileExtensionValidator object */ public OutputFileExtensionValidator() { } @Override public void validate(final String name, final String value) throws ParameterException { Lang lang = RDFLanguages.fileExtToLang(value); if (lang == null) { throw new ParameterException("Parameter " + name + " should be a valid RDF output extension, got: " + value + " recognized RDF extensions are: "+extensions); } } } /** * The validator for output folder paths */ public static class OutputFolderPathValidator implements IParameterValidator { /** * Creates a new OutputFolderPath object */ public OutputFolderPathValidator() {} @Override public void validate(String name, String value) throws ParameterException { final var directory = new File(value).getAbsoluteFile(); if (!directory.isDirectory()) { final var created = directory.mkdirs(); if (!created) { throw new ParameterException("Parameter " + name + " should be a valid folder path"); } } } } /** * The converter for URL params */ public static class URLConverter implements IStringConverter{ /** * Creates a new URLConverter object */ public URLConverter() {} @Override public URL convert(String value) { try { return new URL(value); } catch (MalformedURLException e) { return OwlDocApp.class.getClassLoader().getResource(CSS_DEFAULT); } } } /** * Get application version id from properties file. * * @return version string from build.properties or UNKNOWN */ private String getAppVersion() { var version = this.getClass().getPackage().getImplementationVersion(); return (version != null) ? version : ""; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy