Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add `--label-langs-priority` option to `diff` for choosing entity labels by language preference in the `pretty` format

### Fixed

- Fix problem with catalog file for compressed ontologies [#1281]
Expand Down
23 changes: 23 additions & 0 deletions docs/diff.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,29 @@ See [release-diff.txt](/examples/release-diff.txt) for an example.

The default "plain" output is in OWL Functional syntax with IRIs. You can include entity labels with `--labels true`. In addition, Markdown and HTML diff formats (based on Manchester syntax) are available. You can select the desired format using the `--format` (or `-f`) option, with possible values `plain`, `pretty` (text with labels and CURIEs), `html`, or `markdown`.

### Label Languages

When an entity has labels in more than one language, you can control which one is shown in the `pretty` format with `--label-langs-priority`, a comma-separated list of [language tags](https://www.rfc-editor.org/info/bcp47) in priority order:

robot diff --left lang-left.owl \
--right lang-right.owl \
--format pretty \
--label-langs-priority en-GB,en \
--output results/lang-diff.txt

Here the `dog` class carries `de`, `en`, and `en-GB` labels; with `en-GB,en` its British label `hound` is chosen. Selection rules:

- A more general tag matches a more specific one — for example, `en` matches a label tagged `en-GB`. Each label is bound to the *most specific* tag in your list that it matches, and the label bound to the *earliest* listed tag wins.
- Listing a specific tag after a general one deprioritizes it. With `en,en-GB`, a label tagged `en-GB` binds to `en-GB` (position 2), while a label tagged `en-US` binds to `en` (position 1, by cascade) — so the `en-US` label is shown. To prefer British labels instead, list `en-GB` first.
- Use the token `none` to prefer labels that have no language tag (for example, `--label-langs-priority en,none`).
- Use `*` as a catch-all that matches any label (for example, `--label-langs-priority en,*` prefers English but falls back to any available label). `*` is the least specific match, so any listed language is preferred over it.
- When more than one label is bound to the winning tag (for example, two labels tagged `en`), the alphabetically first is chosen.
- If an entity has labels but none in a preferred language, its alphabetically first label is used, so a labelled entity is never shown as its IRI. Entities with no label at all are unaffected.

Tag matching follows [RFC 4647](https://www.rfc-editor.org/rfc/rfc4647.html) Basic Filtering (a tag like `en` matches any label tagged `en` or beginning `en-`, and `*` matches anything); ROBOT then uses your list's priority order and the tie-breaks above to choose a single label.

This option currently applies only to the `pretty` format; the `markdown` and `html` formats are not yet affected.

You can also compare ontologies by IRI with `--left-iri` and `--right-iri`. You may want to compare a local file to a release, in which case:
<!-- DO NOT TEST -->
```
Expand Down
6 changes: 6 additions & 0 deletions docs/examples/lang-diff.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
2 axioms in left ontology but not in right ontology:
- OntologyID(OntologyIRI(<https://github.com/ontodev/robot/examples/lang-left.owl>) VersionIRI(<null>))
- SubClassOf(<https://github.com/ontodev/robot/examples/lang.owl#puppy> <https://github.com/ontodev/robot/examples/lang.owl#dog>[hound])

1 axioms in right ontology but not in left ontology:
+ OntologyID(OntologyIRI(<https://github.com/ontodev/robot/examples/lang-right.owl>) VersionIRI(<null>))
20 changes: 20 additions & 0 deletions docs/examples/lang-left.owl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0"?>
<rdf:RDF xmlns="https://github.com/ontodev/robot/examples/lang.owl#"
xml:base="https://github.com/ontodev/robot/examples/lang.owl"
xmlns:owl="http://www.w3.org/2002/07/owl#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
<owl:Ontology rdf:about="https://github.com/ontodev/robot/examples/lang-left.owl"/>

<owl:Class rdf:about="https://github.com/ontodev/robot/examples/lang.owl#dog">
<rdfs:label xml:lang="de">Hund</rdfs:label>
<rdfs:label xml:lang="en">dog</rdfs:label>
<rdfs:label xml:lang="en-GB">hound</rdfs:label>
</owl:Class>

<owl:Class rdf:about="https://github.com/ontodev/robot/examples/lang.owl#puppy">
<rdfs:subClassOf rdf:resource="https://github.com/ontodev/robot/examples/lang.owl#dog"/>
</owl:Class>
</rdf:RDF>
18 changes: 18 additions & 0 deletions docs/examples/lang-right.owl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0"?>
<rdf:RDF xmlns="https://github.com/ontodev/robot/examples/lang.owl#"
xml:base="https://github.com/ontodev/robot/examples/lang.owl"
xmlns:owl="http://www.w3.org/2002/07/owl#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
<owl:Ontology rdf:about="https://github.com/ontodev/robot/examples/lang-right.owl"/>

<owl:Class rdf:about="https://github.com/ontodev/robot/examples/lang.owl#dog">
<rdfs:label xml:lang="de">Hund</rdfs:label>
<rdfs:label xml:lang="en">dog</rdfs:label>
<rdfs:label xml:lang="en-GB">hound</rdfs:label>
</owl:Class>

<owl:Class rdf:about="https://github.com/ontodev/robot/examples/lang.owl#puppy"/>
</rdf:RDF>
15 changes: 15 additions & 0 deletions robot-command/src/main/java/org/obolibrary/robot/DiffCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ public DiffCommand() {
null, "labels", true, "if true, append labels after entity IRIs in the text format output");
o.addOption(
"f", "format", true, "format for diff output: plain (default) | pretty | html | markdown");
o.addOption(
null,
LanguagePreference.OPTION_NAME,
true,
"comma-separated language tags, in priority order, for choosing entity labels in the"
+ " 'pretty' format (e.g. 'en-GB,en,"
+ LanguagePreference.NO_LANG_TOKEN
+ "'); use '"
+ LanguagePreference.NO_LANG_TOKEN
+ "' for values with no language tag");
options = o;
}

Expand Down Expand Up @@ -159,6 +169,11 @@ public CommandState execute(CommandState state, String[] args) throws Exception
Map<String, String> options = new HashMap<>();
options.put("labels", CommandLineHelper.getDefaultValue(line, "labels", "false"));
options.put("format", CommandLineHelper.getDefaultValue(line, "format", "plain"));
String preferredLangs =
CommandLineHelper.getOptionalValue(line, LanguagePreference.OPTION_NAME);
if (preferredLangs != null) {
options.put(LanguagePreference.OPTION_NAME, preferredLangs);
}

DiffOperation.compare(leftOntology, rightOntology, ioHelper, writer, options);
writer.close();
Expand Down
34 changes: 27 additions & 7 deletions robot-core/src/main/java/org/obolibrary/robot/DiffOperation.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
import org.geneontology.owl.differ.render.MarkdownGroupedDiffRenderer;
import org.geneontology.owl.differ.shortform.DoubleShortFormProvider;
import org.geneontology.owl.differ.shortform.OBOShortenerShortFormProvider;
import org.obolibrary.robot.providers.PreferredLanguageShortFormProvider;
import org.semanticweb.owlapi.apibinding.OWLManager;
import org.semanticweb.owlapi.model.*;
import org.semanticweb.owlapi.util.AnnotationValueShortFormProvider;
import org.semanticweb.owlapi.util.DefaultPrefixManager;
import org.semanticweb.owlapi.util.ShortFormProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -104,6 +106,18 @@ public static boolean compare(
format = "pretty";
}

// An ordered list of preferred label languages (empty means "no preference").
List<String> preferredLangs =
LanguagePreference.parse(OptionsHelper.getOption(options, LanguagePreference.OPTION_NAME));
if (!preferredLangs.isEmpty() && !format.equals("pretty")) {
// Only the "pretty" format currently supports language-prioritized labels; the markdown and
// html renderers build their own label provider inside the owl-diff library.
logger.warn(
"The --{} option only affects the 'pretty' diff format; it is ignored for format '{}'.",
LanguagePreference.OPTION_NAME,
format);
}

Differ.BasicDiff diff = Differ.diff(ontology1, ontology2);

if (diff.isEmpty()) {
Expand All @@ -127,13 +141,19 @@ public static boolean compare(
break;
case "pretty":
DefaultPrefixManager pm = ioHelper.getPrefixManager();
AnnotationValueShortFormProvider labelProvider =
new AnnotationValueShortFormProvider(
ontologyProvider,
pm,
pm,
Collections.singletonList(OWLManager.getOWLDataFactory().getRDFSLabel()),
Collections.emptyMap());
List<OWLAnnotationProperty> labelProperties =
Collections.singletonList(OWLManager.getOWLDataFactory().getRDFSLabel());
ShortFormProvider labelProvider;
if (preferredLangs.isEmpty()) {
// No language preference: keep the pre-existing behavior exactly.
labelProvider =
new AnnotationValueShortFormProvider(
ontologyProvider, pm, pm, labelProperties, Collections.emptyMap());
} else {
labelProvider =
new PreferredLanguageShortFormProvider(
ontologyProvider, labelProperties, preferredLangs, pm);
}
OBOShortenerShortFormProvider iriProvider = new OBOShortenerShortFormProvider(pm);
DoubleShortFormProvider doubleProvider =
new DoubleShortFormProvider(iriProvider, labelProvider);
Expand Down
Loading
Loading