diff --git a/build.sbt b/build.sbt index d55a8bb..8d21ef7 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ organization := "org.geneontology" name := "owl-diff" -version := "1.2.2" +version := "1.3-SNAPSHOT" publishMavenStyle := true @@ -22,7 +22,7 @@ homepage := Some(url("https://github.com/balhoff/owl-diff")) scalaVersion := "2.13.15" -crossScalaVersions := Seq("2.12.20", "2.13.15") +crossScalaVersions := Seq("2.13.15") scalacOptions := Seq("-unchecked", "-deprecation", "-encoding", "utf8") diff --git a/src/main/scala/org/geneontology/owl/differ/Differ.scala b/src/main/scala/org/geneontology/owl/differ/Differ.scala index 52bda1e..34a0788 100644 --- a/src/main/scala/org/geneontology/owl/differ/Differ.scala +++ b/src/main/scala/org/geneontology/owl/differ/Differ.scala @@ -4,7 +4,7 @@ import org.semanticweb.owlapi.model._ import org.semanticweb.owlapi.model.parameters.Imports import org.semanticweb.owlapi.util.AxiomSubjectProvider -import scala.collection.JavaConverters._ +import scala.jdk.CollectionConverters._ object Differ { diff --git a/src/main/scala/org/geneontology/owl/differ/render/BasicDiffRenderer.scala b/src/main/scala/org/geneontology/owl/differ/render/BasicDiffRenderer.scala index 3e214f5..fd26023 100644 --- a/src/main/scala/org/geneontology/owl/differ/render/BasicDiffRenderer.scala +++ b/src/main/scala/org/geneontology/owl/differ/render/BasicDiffRenderer.scala @@ -1,40 +1,69 @@ package org.geneontology.owl.differ.render +import org.apache.commons.io.output.ByteArrayOutputStream import org.geneontology.owl.differ.Differ.BasicDiff import org.geneontology.owl.differ.ShortFormFunctionalSyntaxObjectRenderer import org.semanticweb.owlapi.model.{OWLImportsDeclaration, OWLObject} import org.semanticweb.owlapi.util.ShortFormProvider +import java.io.{OutputStream, PrintWriter, Writer} +import java.nio.charset.StandardCharsets +import scala.util.Using + object BasicDiffRenderer { - def renderPlain(diff: BasicDiff): String = { - if (diff.isEmpty) "Ontologies are identical" - else { - val (left, right) = groups(diff) - val leftRendered = left.map(_.item.toString) - val rightRendered = right.map(_.item.toString) - if ((diff.left.id == diff.right.id) || (diff.left.id.isAnonymous && diff.right.id.isAnonymous)) format(leftRendered, rightRendered) - else format(leftRendered + diff.left.id.toString, rightRendered + diff.right.id.toString) + def renderPlainStream(diff: BasicDiff, stream: OutputStream): Unit = + Using.resource(new PrintWriter(stream)) { writer => + renderPlainWriter(diff, writer) } - } - def render(diff: BasicDiff, shortFormProvider: ShortFormProvider): String = { - if (diff.isEmpty) "Ontologies are identical" - else { - val renderer = new ShortFormFunctionalSyntaxObjectRenderer(shortFormProvider) - val (left, right) = groups(diff) - val leftRendered = left.map { - case OWLObjectItem(item) => renderer.render(item) - case OWLImportItem(item) => item.toString + def renderPlainWriter(diff: BasicDiff, givenWriter: Writer): Unit = + Using.resource(new PrintWriter(givenWriter)) { writer => + if (diff.isEmpty) writer.println("Ontologies are identical") + else { + val (left, right) = groups(diff) + val leftRendered = left.map(_.item.toString) + val rightRendered = right.map(_.item.toString) + if ((diff.left.id == diff.right.id) || (diff.left.id.isAnonymous && diff.right.id.isAnonymous)) format(leftRendered, rightRendered, writer) + else format(leftRendered + diff.left.id.toString, rightRendered + diff.right.id.toString, writer) } - val rightRendered = right.map { - case OWLObjectItem(item) => renderer.render(item) - case OWLImportItem(item) => item.toString + } + + def renderPlain(diff: BasicDiff): String = + Using.resource(new ByteArrayOutputStream()) { stream => + renderPlainStream(diff, stream) + stream.toString(StandardCharsets.UTF_8) + } + + def renderStream(diff: BasicDiff, shortFormProvider: ShortFormProvider, stream: OutputStream): Unit = + Using.resource(new PrintWriter(stream)) { writer => + renderWriter(diff, shortFormProvider, writer) + } + + def renderWriter(diff: BasicDiff, shortFormProvider: ShortFormProvider, givenWriter: Writer): Unit = + Using.resource(new PrintWriter(givenWriter)) { writer => + if (diff.isEmpty) writer.println("Ontologies are identical") + else { + val renderer = new ShortFormFunctionalSyntaxObjectRenderer(shortFormProvider) + val (left, right) = groups(diff) + val leftRendered = left.map { + case OWLObjectItem(item) => renderer.render(item) + case OWLImportItem(item) => item.toString + } + val rightRendered = right.map { + case OWLObjectItem(item) => renderer.render(item) + case OWLImportItem(item) => item.toString + } + if ((diff.left.id == diff.right.id) || (diff.left.id.isAnonymous && diff.right.id.isAnonymous)) format(leftRendered, rightRendered, writer) + else format(leftRendered + diff.left.id.toString, rightRendered + diff.right.id.toString, writer) } - if ((diff.left.id == diff.right.id) || (diff.left.id.isAnonymous && diff.right.id.isAnonymous)) format(leftRendered, rightRendered) - else format(leftRendered + diff.left.id.toString, rightRendered + diff.right.id.toString) } - } + + def render(diff: BasicDiff, shortFormProvider: ShortFormProvider): String = + Using.resource(new ByteArrayOutputStream()) { stream => + renderStream(diff, shortFormProvider, stream) + stream.toString(StandardCharsets.UTF_8) + } private def groups(diff: BasicDiff): (Set[OWLItem[_]], Set[OWLItem[_]]) = { val leftUnique: Set[OWLItem[_]] = diff.left.imports.map(OWLImportItem) ++ diff.left.annotations.map(OWLObjectItem) ++ diff.left.axioms.map(OWLObjectItem) @@ -42,15 +71,15 @@ object BasicDiffRenderer { (leftUnique, rightUnique) } - private def format(removedLines: Set[String], addedLines: Set[String]): String = { + private def format(removedLines: Set[String], addedLines: Set[String], writer: PrintWriter): Unit = { import org.geneontology.owl.differ.Util.replaceNewlines - val removedSorted = removedLines.map(replaceNewlines).map(ax => s"- $ax").toSeq.sorted.mkString("\n") - val addedSorted = addedLines.map(replaceNewlines).map(ax => s"+ $ax").toSeq.sorted.mkString("\n") - s"""${removedLines.size} axioms in left ontology but not in right ontology: -$removedSorted${if (removedSorted.nonEmpty) "\n" else ""} -${addedLines.size} axioms in right ontology but not in left ontology: -$addedSorted -""" + val removedSorted = removedLines.map(replaceNewlines).map(ax => s"- $ax").toSeq.sorted + val addedSorted = addedLines.map(replaceNewlines).map(ax => s"+ $ax").toSeq.sorted + writer.println(s"${removedLines.size} axioms in left ontology but not in right ontology:") + removedSorted.foreach(writer.println) + writer.println() + writer.println(s"${addedLines.size} axioms in right ontology but not in left ontology:") + addedSorted.foreach(writer.println) } private sealed trait OWLItem[T] { diff --git a/src/main/scala/org/geneontology/owl/differ/render/HTMLDiffRenderer.scala b/src/main/scala/org/geneontology/owl/differ/render/HTMLDiffRenderer.scala index 4b57747..a31edcc 100644 --- a/src/main/scala/org/geneontology/owl/differ/render/HTMLDiffRenderer.scala +++ b/src/main/scala/org/geneontology/owl/differ/render/HTMLDiffRenderer.scala @@ -1,6 +1,7 @@ package org.geneontology.owl.differ.render import com.google.common.base.Optional +import org.apache.commons.io.output.ByteArrayOutputStream import org.apache.commons.text.StringEscapeUtils import org.geneontology.owl.differ.Differ._ import org.geneontology.owl.differ.ManchesterSyntaxOWLObjectRenderer @@ -10,7 +11,10 @@ import org.semanticweb.owlapi.apibinding.OWLManager import org.semanticweb.owlapi.model._ import org.semanticweb.owlapi.util.{AnnotationValueShortFormProvider, SimpleIRIShortFormProvider, SimpleShortFormProvider} -import scala.collection.JavaConverters._ +import java.io.{OutputStream, PrintWriter, Writer} +import java.nio.charset.StandardCharsets +import scala.jdk.CollectionConverters._ +import scala.util.Using object HTMLDiffRenderer { @@ -18,123 +22,141 @@ object HTMLDiffRenderer { private val rdfsLabel = factory.getRDFSLabel - def render(diff: GroupedDiff, renderingOntologyProvider: OWLOntologySetProvider): String = { - val shortFormProvider = new HTMLSafeShortFormProvider(new AnnotationValueShortFormProvider(renderingOntologyProvider, new SimpleShortFormProvider(), new HTMLSafeIRIShortFormProvider(new SimpleIRIShortFormProvider()), List(rdfsLabel).asJava, Map.empty[OWLAnnotationProperty, java.util.List[String]].asJava)) - val htmlLinkShortFormProvider = new HTMLLinkShortFormProvider(shortFormProvider) - val labelRenderer = new ManchesterSyntaxOWLObjectRenderer() - labelRenderer.setShortFormProvider(shortFormProvider) - val htmlRenderer = new ManchesterSyntaxOWLObjectRenderer() - htmlRenderer.setShortFormProvider(htmlLinkShortFormProvider) - val groupsToLabels: Map[Grouping, String] = diff.groups.keys.map { - case group @ IRIGrouping(iri) => group -> labelRenderer.render(factory.getOWLClass(iri)) // send as OWLClass because labels aren't rendered for IRIs - case group @ GCIGrouping => group -> "GCIs" - case group @ RuleGrouping => group -> "Rules" - case group @ NonIRIGrouping(obj) => group -> labelRenderer.render(obj) - case group @ OntologyImportGrouping => group -> "Ontology imports" - case group @ OntologyAnnotationGrouping => group -> "Ontology annotations" - }.toMap - val sortedGroups = OntologyImportGrouping :: OntologyAnnotationGrouping :: (diff.groups - OntologyImportGrouping - OntologyAnnotationGrouping).keys.toList.sortBy(groupsToLabels) - - def htmlForObject(obj: OWLObject): String = { - val (annotations, objToRender) = obj match { - case hasAnnotations: HasAnnotations => - val inner = hasAnnotations.getAnnotations.asScala.map(htmlForObject(_)).mkString("\n") - val objWithoutAnnotations = obj match { - case ax: OWLAxiom => ax.getAxiomWithoutAnnotations - case ann: OWLAnnotation => factory.getOWLAnnotation(ann.getProperty, ann.getValue) - case _ => obj - } - s"" -> objWithoutAnnotations - case _ => "" -> obj + def renderWriter(diff: GroupedDiff, renderingOntologyProvider: OWLOntologySetProvider, givenWriter: Writer): Unit = + Using.resource(new PrintWriter(givenWriter)) { writer => + val shortFormProvider = new HTMLSafeShortFormProvider(new AnnotationValueShortFormProvider(renderingOntologyProvider, new SimpleShortFormProvider(), new HTMLSafeIRIShortFormProvider(new SimpleIRIShortFormProvider()), List(rdfsLabel).asJava, Map.empty[OWLAnnotationProperty, java.util.List[String]].asJava)) + val htmlLinkShortFormProvider = new HTMLLinkShortFormProvider(shortFormProvider) + val labelRenderer = new ManchesterSyntaxOWLObjectRenderer() + labelRenderer.setShortFormProvider(shortFormProvider) + val htmlRenderer = new ManchesterSyntaxOWLObjectRenderer() + htmlRenderer.setShortFormProvider(htmlLinkShortFormProvider) + val groupsToLabels: Map[Grouping, String] = diff.groups.keys.map { + case group @ IRIGrouping(iri) => group -> labelRenderer.render(factory.getOWLClass(iri)) // send as OWLClass because labels aren't rendered for IRIs + case group @ GCIGrouping => group -> "GCIs" + case group @ RuleGrouping => group -> "Rules" + case group @ NonIRIGrouping(obj) => group -> labelRenderer.render(obj) + case group @ OntologyImportGrouping => group -> "Ontology imports" + case group @ OntologyAnnotationGrouping => group -> "Ontology annotations" + }.toMap + val sortedGroups = OntologyImportGrouping :: OntologyAnnotationGrouping :: (diff.groups - OntologyImportGrouping - OntologyAnnotationGrouping).keys.toList.sortBy(groupsToLabels) + val header = + s""" + + + + + + + OWL diff + + + +

Ontology comparison

+

Left

+ +

Right

+ + """ + writer.println(header) + + def htmlForObject(obj: OWLObject): String = { + val (annotations, objToRender) = obj match { + case hasAnnotations: HasAnnotations => + val inner = hasAnnotations.getAnnotations.asScala.map(htmlForObject(_)).mkString("\n") + val objWithoutAnnotations = obj match { + case ax: OWLAxiom => ax.getAxiomWithoutAnnotations + case ann: OWLAnnotation => factory.getOWLAnnotation(ann.getProperty, ann.getValue) + case _ => obj + } + s"" -> objWithoutAnnotations + case _ => "" -> obj + } + s"""
  • ${htmlRenderer.render(objToRender)} $annotations
  • """ } - s"""
  • ${htmlRenderer.render(objToRender)} $annotations
  • """ - } - def changeList(header: String, axioms: Seq[OWLObject]) = - s""" -

    $header

    + def changeList(header: String, axioms: Seq[OWLObject]) = + s""" +

    $header

    - """ - - def frameElement(header: String, headerIRI: String, removedList: String, addedList: String) = - s"""
    -

    $header $headerIRI

    - $removedList - $addedList -
    """ - - val content = (for { - group <- sortedGroups - } yield { - def sortKey(modifiedItem: ModifiedOntologyContent[_]): String = modifiedItem match { - case ModifiedOntologyAnnotation(item, _) => item.toString - case ModifiedImport(item, _) => item.toString - case ModifiedAxiom(item, _) => item.getAxiomType match { - case AxiomType.DECLARATION => s"1-${item.toString}" - case _ => s"2-${item.toString}" + """ + + def frameElement(header: String, headerIRI: String, removedList: String, addedList: String) = + s"""
    +

    $header + $headerIRI +

    + $removedList + $addedList +
    + """ + + for { + group <- sortedGroups + } yield { + def sortKey(modifiedItem: ModifiedOntologyContent[_]): String = modifiedItem match { + case ModifiedOntologyAnnotation(item, _) => item.toString + case ModifiedImport(item, _) => item.toString + case ModifiedAxiom(item, _) => item.getAxiomType match { + case AxiomType.DECLARATION => s"1-${item.toString}" + case _ => s"2-${item.toString}" + } } - } - val removed = diff.groups(group).filterNot(_.added).toSeq.sortBy(sortKey) - val added = diff.groups(group).filter(_.added).toSeq.sortBy(sortKey) - val removedList = if (removed.nonEmpty) changeList("Removed", removed.map(_.owlObject)) else "" - val addedList = if (added.nonEmpty) changeList("Added", added.map(_.owlObject)) else "" - val header = groupsToLabels(group) - val headerIRI = group match { - case IRIGrouping(iri) => s"- $iri" - case _ => "" + writer.println() + val removed = diff.groups(group).filterNot(_.added).toSeq.sortBy(sortKey) + val added = diff.groups(group).filter(_.added).toSeq.sortBy(sortKey) + val removedList = if (removed.nonEmpty) changeList("Removed", removed.map(_.owlObject)) else "" + val addedList = if (added.nonEmpty) changeList("Added", added.map(_.owlObject)) else "" + val header = groupsToLabels(group) + val headerIRI = group match { + case IRIGrouping(iri) => s"- $iri" + case _ => "" + } + val frame = frameElement(header, headerIRI, removedList, addedList) + writer.println(frame) } - frameElement(header, headerIRI, removedList, addedList) - }).mkString("\n\n") - s""" - - - - - - - OWL diff - - - -

    Ontology comparison

    -

    Left

    - -

    Right

    - -$content - - """ - } + writer.println("") + } + + def renderStream(diff: GroupedDiff, renderingOntologyProvider: OWLOntologySetProvider, stream: OutputStream): Unit = + Using.resource(new PrintWriter(stream)) { writer => + renderWriter(diff, renderingOntologyProvider, writer) + } + + def render(diff: GroupedDiff, renderingOntologyProvider: OWLOntologySetProvider): String = + Using.resource(new ByteArrayOutputStream()) { stream => + renderStream(diff, renderingOntologyProvider, stream) + stream.toString(StandardCharsets.UTF_8) + } private def optionalIRI(iriOpt: Optional[IRI]): String = (for { iri <- iriOpt.toOption diff --git a/src/main/scala/org/geneontology/owl/differ/render/MarkdownGroupedDiffRenderer.scala b/src/main/scala/org/geneontology/owl/differ/render/MarkdownGroupedDiffRenderer.scala index 3171517..86e37ab 100644 --- a/src/main/scala/org/geneontology/owl/differ/render/MarkdownGroupedDiffRenderer.scala +++ b/src/main/scala/org/geneontology/owl/differ/render/MarkdownGroupedDiffRenderer.scala @@ -1,6 +1,7 @@ package org.geneontology.owl.differ.render import com.google.common.base.Optional +import org.apache.commons.io.output.ByteArrayOutputStream import org.geneontology.owl.differ.Differ._ import org.geneontology.owl.differ.ManchesterSyntaxOWLObjectRenderer import org.geneontology.owl.differ.Util.OptionalOption @@ -10,7 +11,10 @@ import org.semanticweb.owlapi.io.OWLObjectRenderer import org.semanticweb.owlapi.model._ import org.semanticweb.owlapi.util.AnnotationValueShortFormProvider -import scala.collection.JavaConverters._ +import java.io.{OutputStream, PrintWriter, Writer} +import java.nio.charset.StandardCharsets +import scala.jdk.CollectionConverters._ +import scala.util.Using object MarkdownGroupedDiffRenderer { @@ -18,50 +22,25 @@ object MarkdownGroupedDiffRenderer { private val rdfsLabel = factory.getRDFSLabel - def render(diff: GroupedDiff, renderingOntologyProvider: OWLOntologySetProvider): String = { - val labelProvider = new AnnotationValueShortFormProvider(List(rdfsLabel).asJava, Map.empty[OWLAnnotationProperty, java.util.List[String]].asJava, renderingOntologyProvider) - val markdownLinkProvider = new MarkdownLinkShortFormProvider(labelProvider) - val labelRenderer = new ManchesterSyntaxOWLObjectRenderer() - labelRenderer.setShortFormProvider(labelProvider) - val markdownRenderer = new ManchesterSyntaxOWLObjectRenderer() - markdownRenderer.setShortFormProvider(markdownLinkProvider) - val groupsToLabels: Map[Grouping, String] = diff.groups.keys.map { - case group @ IRIGrouping(iri) => group -> labelRenderer.render(factory.getOWLClass(iri)) // send as OWLClass because labels aren't rendered for IRIs - case group @ GCIGrouping => group -> "GCIs" - case group @ RuleGrouping => group -> "Rules" - case group @ NonIRIGrouping(obj) => group -> labelRenderer.render(obj) - case group @ OntologyImportGrouping => group -> "Ontology imports" - case group @ OntologyAnnotationGrouping => group -> "Ontology annotations" - }.toMap - val sortedGroups = OntologyImportGrouping :: OntologyAnnotationGrouping :: (diff.groups - OntologyImportGrouping - OntologyAnnotationGrouping).keys.toList.sortBy(groupsToLabels) - val content = (for { - group <- sortedGroups - } yield { - def sortKey(modifiedItem: ModifiedOntologyContent[_]): String = { - modifiedItem match { - case ModifiedOntologyAnnotation(item, _) => item.toString - case ModifiedImport(item, _) => item.toString - case ModifiedAxiom(item, _) => item.getAxiomType match { - case AxiomType.DECLARATION => s"1-${item.toString}" - case _ => s"2-${item.toString}" - } - } - } - - val removed = diff.groups(group).filterNot(_.added).toSeq.sortBy(sortKey) - val added = diff.groups(group).filter(_.added).toSeq.sortBy(sortKey) - val removedList = if (removed.nonEmpty) changeList("Removed", removed.map(_.owlObject).map(item => markdownForObject(item, markdownRenderer))) else "" - val addedList = if (added.nonEmpty) changeList("Added", added.map(_.owlObject).map(item => markdownForObject(item, markdownRenderer))) else "" - val header = groupsToLabels(group) - val headerIRI = group match { - case IRIGrouping(iri) => Some(iri.toString) - case _ => None - } - frameElement(header, headerIRI, removedList, addedList) - }).mkString("\n\n") - - val header = - s"""# Ontology comparison + def renderWriter(diff: GroupedDiff, renderingOntologyProvider: OWLOntologySetProvider, givenWriter: Writer): Unit = { + Using.resource(new PrintWriter(givenWriter)) { writer => + val labelProvider = new AnnotationValueShortFormProvider(List(rdfsLabel).asJava, Map.empty[OWLAnnotationProperty, java.util.List[String]].asJava, renderingOntologyProvider) + val markdownLinkProvider = new MarkdownLinkShortFormProvider(labelProvider) + val labelRenderer = new ManchesterSyntaxOWLObjectRenderer() + labelRenderer.setShortFormProvider(labelProvider) + val markdownRenderer = new ManchesterSyntaxOWLObjectRenderer() + markdownRenderer.setShortFormProvider(markdownLinkProvider) + val groupsToLabels: Map[Grouping, String] = diff.groups.keys.map { + case group @ IRIGrouping(iri) => group -> labelRenderer.render(factory.getOWLClass(iri)) // send as OWLClass because labels aren't rendered for IRIs + case group @ GCIGrouping => group -> "GCIs" + case group @ RuleGrouping => group -> "Rules" + case group @ NonIRIGrouping(obj) => group -> labelRenderer.render(obj) + case group @ OntologyImportGrouping => group -> "Ontology imports" + case group @ OntologyAnnotationGrouping => group -> "Ontology annotations" + }.toMap + val sortedGroups = OntologyImportGrouping :: OntologyAnnotationGrouping :: (diff.groups - OntologyImportGrouping - OntologyAnnotationGrouping).keys.toList.sortBy(groupsToLabels) + val header = + s"""# Ontology comparison ## Left - Ontology IRI: ${optionalIRI(diff.left.getOntologyIRI)} @@ -72,9 +51,49 @@ object MarkdownGroupedDiffRenderer { - Ontology IRI: ${optionalIRI(diff.right.getOntologyIRI)} - Version IRI: ${optionalIRI(diff.right.getVersionIRI)} - Loaded from: `${diff.rightSource}`""" - s"$header\n\n$content" + writer.println(header) + + for { + group <- sortedGroups + } { + def sortKey(modifiedItem: ModifiedOntologyContent[_]): String = { + modifiedItem match { + case ModifiedOntologyAnnotation(item, _) => item.toString + case ModifiedImport(item, _) => item.toString + case ModifiedAxiom(item, _) => item.getAxiomType match { + case AxiomType.DECLARATION => s"1-${item.toString}" + case _ => s"2-${item.toString}" + } + } + } + + writer.println() + val removed = diff.groups(group).filterNot(_.added).toSeq.sortBy(sortKey) + val added = diff.groups(group).filter(_.added).toSeq.sortBy(sortKey) + val removedList = if (removed.nonEmpty) changeList("Removed", removed.map(_.owlObject).map(item => markdownForObject(item, markdownRenderer))) else "" + val addedList = if (added.nonEmpty) changeList("Added", added.map(_.owlObject).map(item => markdownForObject(item, markdownRenderer))) else "" + val header = groupsToLabels(group) + val headerIRI = group match { + case IRIGrouping(iri) => Some(iri.toString) + case _ => None + } + val frame = frameElement(header, headerIRI, removedList, addedList) + writer.println(frame) + } + } } + def renderStream(diff: GroupedDiff, renderingOntologyProvider: OWLOntologySetProvider, stream: OutputStream): Unit = + Using.resource(new PrintWriter(stream)) { writer => + renderWriter(diff, renderingOntologyProvider, writer) + } + + def render(diff: GroupedDiff, renderingOntologyProvider: OWLOntologySetProvider): String = + Using.resource(new ByteArrayOutputStream()) { stream => + renderStream(diff, renderingOntologyProvider, stream) + stream.toString(StandardCharsets.UTF_8) + } + def markdownForObject(obj: OWLObject, renderer: OWLObjectRenderer, level: Int = 0): String = { val (annotations, objToRender) = obj match { case hasAnnotations: HasAnnotations => @@ -100,5 +119,4 @@ object MarkdownGroupedDiffRenderer { private def optionalIRI(iriOpt: Optional[IRI]): String = iriOpt.toOption.map(iri => s"`$iri`").getOrElse("*None*") - }