Skip to content

Commit

Permalink
W-14668223: support @list parsing and emission
Browse files Browse the repository at this point in the history
  • Loading branch information
arielmirra committed Dec 29, 2023
1 parent 7cae9f7 commit 4a559dd
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import amf.core.internal.metamodel.Type._
import amf.core.internal.metamodel._
import amf.core.internal.metamodel.document.{BaseUnitModel, FragmentModel, ModuleModel, SourceMapModel}
import amf.core.internal.metamodel.domain.extensions.DomainExtensionModel
import amf.core.internal.parser.domain.{FieldEntry, Value}
import amf.core.internal.parser.domain.{Annotations, FieldEntry, Value}
import amf.core.internal.plugins.document.graph.JsonLdKeywords
import amf.core.internal.plugins.document.graph.emitter.flattened.utils.{Emission, EmissionQueue, Metadata}
import org.yaml.builder.DocBuilder
Expand Down Expand Up @@ -280,36 +280,61 @@ class FlattenedJsonLdEmitter[T](
pending.tryEnqueue(e)
}

def createArrayLikeValues(seq: AmfArray, b: Part[T]): Unit = seq.values.foreach { v =>
emitArrayMember(v, b)
}

def emitArrayMember(element: AmfElement, b: Part[T]): Unit = {
element match {
case obj: AmfObject => emitObjMember(obj, b, inArray = true)
case scalar: AmfScalar => emitScalarMember(scalar, b)
case arr: AmfArray =>
b.list { b =>
createArrayValues(Type.Array(Type.Any), arr, b, Value(arr, Annotations()))
}
}
}

override protected def createSortedArray(
a: Type,
v: Value,
b: Part[T],
parent: String,
sources: Value => Unit
): Unit = {
val seq = v.value.asInstanceOf[AmfArray].values
sources(v)
b.obj { b =>
val id = s"$parent/list"
createIdNode(b, id)
val e = new Emission((part: Part[T]) => {
part.obj { rb =>
createIdNode(rb, id)
rb.entry(JsonLdKeywords.Type, ctx.emitIri((Namespace.Rdfs + "Seq").iri()))
seq.zipWithIndex.foreach { case (e, i) =>
rb.entry(
ctx.emitIri((Namespace.Rdfs + s"_${i + 1}").iri()),
{ b =>
emitArrayMember(a, e, b)
}
)
if (options.governanceMode) {
def emitList: Part[T] => Unit = (b: Part[T]) => {
val seq = v.value.asInstanceOf[AmfArray]
sources(v)
createArrayLikeValues(seq, b)
}

b.obj(_.entry("@list", _.list(emitList)))
} else {
val seq = v.value.asInstanceOf[AmfArray].values
sources(v)
b.obj { b =>
val id = s"$parent/list"
createIdNode(b, id)
val e = new Emission((part: Part[T]) => {
part.obj { rb =>
createIdNode(rb, id)
rb.entry(JsonLdKeywords.Type, ctx.emitIri((Namespace.Rdfs + "Seq").iri()))
seq.zipWithIndex.foreach { case (e, i) =>
rb.entry(
ctx.emitIri((Namespace.Rdfs + s"_${i + 1}").iri()),
{ b =>
emitArrayMember(a, e, b)
}
)
}
}
}
}) with Metadata
e.id = Some(id)
e.isDeclaration = ctx.emittingDeclarations
e.isReference = ctx.emittingReferences
pending.tryEnqueue(e)
}) with Metadata
e.id = Some(id)
e.isDeclaration = ctx.emittingDeclarations
e.isReference = ctx.emittingReferences
pending.tryEnqueue(e)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package amf.core.internal.plugins.document.graph.parser
import amf.core.client.scala.model.document.SourceMap
import amf.core.client.scala.model.domain.extensions.{CustomDomainProperty, DomainExtension}
import amf.core.client.scala.model.domain._
import amf.core.client.scala.vocabulary.Namespace
import amf.core.internal.annotations.DomainExtensionAnnotation
import amf.core.internal.metamodel.Type.Iri
import amf.core.internal.metamodel.domain.{DomainElementModel, ExternalSourceElementModel, LinkableElementModel}
import amf.core.internal.metamodel.domain.extensions.DomainExtensionModel
import amf.core.internal.metamodel.{Field, ModelDefaultBuilder, Type}
import amf.core.internal.metamodel.{Field, ModelDefaultBuilder, Obj, Type}
import amf.core.internal.parser.{YMapOps, YNodeLikeOps}
import amf.core.internal.parser.domain.{Annotations, FieldEntry}
import amf.core.internal.plugins.document.graph.JsonLdKeywords
Expand Down Expand Up @@ -287,4 +288,42 @@ abstract class CommonGraphParser(implicit ctx: GraphParserContext) extends Graph
private def findType(typeString: String): Option[ModelDefaultBuilder] = {
ctx.config.registryContext.findType(expandUriFromContext(typeString))
}

protected def parseListMembers(members: List[YNode], listType: Type): Seq[AmfElement] = {
val fn = parseListMember(listType)
members.flatMap(fn(_))
}

private def parseListMember(listType: Type): YNode => Option[AmfElement] = (member: YNode) => {
listType match {
case _: Obj => parse(member.as[YMap])
case Type.Any => Some(typedValue(member, ctx.graphContext))
case _ =>
try { Some(str(value(listType, member))) }
catch {
case _: Exception => None
}
}
}

private def getEntryKeyString(entry: YMapEntry): String = entry.key.as[String]

protected def getArrayMapMembers(node: YMap): List[YNode] = {
node.entries
.filter { entry =>
val property = getEntryKeyString(entry)
val sortedMemberPrefix = (Namespace.Rdfs + "_").iri()
property.startsWith(compactUriFromContext(sortedMemberPrefix))
}
.sortBy(getEntryKeyString)
.map(_.value)
.toList
}

protected def getListMembers(node: YMapEntry): List[YNode] = {
node.value.value match {
case sequence: YSequence => sequence.nodes.toList
case _ => List.empty
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,24 +78,16 @@ class EmbeddedGraphParser(private val aliases: Map[String, String])(implicit val
findType(stringTypes, id, map)
}

private def parseList(listElement: Type, node: YMap): Seq[AmfElement] = {
val buffer = ListBuffer[YNode]()
node.entries.sortBy(_.key.as[String]).foreach { entry =>
if (entry.key.as[String].startsWith(compactUriFromContext((Namespace.Rdfs + "_").iri()))) {
buffer += entry.value.as[Seq[YNode]].head
}
}
buffer.flatMap { n =>
listElement match {
case _: Obj => parse(n.as[YMap])
case Type.Any => Some(typedValue(n, ctx.graphContext))
case _ =>
try { Some(str(value(listElement, n))) }
catch {
case _: Exception => None
}
}
private def parseSortedArray(listType: Type, rawNode: YMap): Seq[AmfElement] = {
val members: List[YNode] = rawNode.key(JsonLdKeywords.List) match {
case Some(node) =>
// get members of the @list
getListMembers(node)
case None =>
// get members of the sorted array implementation that uses a map with entries with _i for index
getArrayMapMembers(rawNode)
}
parseListMembers(members, listType)
}

override protected def parseLinkableProperties(map: YMap, instance: DomainElement with Linkable): Unit = {
Expand Down Expand Up @@ -143,7 +135,7 @@ class EmbeddedGraphParser(private val aliases: Map[String, String])(implicit val
case Type.DateTime => Some(date(node))
case Type.Date => Some(date(node))
case Type.Any => Some(any(node))
case l: SortedArray => Some(AmfArray(parseList(l.element, node.as[YMap])))
case l: SortedArray => Some(AmfArray(parseSortedArray(l.element, node.as[YMap])))
case a: Array => yNodeSeq(node, a)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,31 +205,20 @@ class FlattenedGraphParser(startingPoint: String, overrideAliases: Map[String, S
findType(typeIris, id, map)
}

protected def parseSortedArray(listElement: Type, rawNode: YMap): Seq[AmfElement] = {
def key(entry: YMapEntry): String = entry.key.as[String]
contentOfNode(rawNode) match {
private def parseSortedArray(listType: Type, rawNode: YMap): Seq[AmfElement] = {
val members: List[YNode] = rawNode.key(JsonLdKeywords.List) match {
case Some(node) =>
// Sorted array members
val members = node.entries.filter { entry =>
val property = key(entry)
val sortedMemberPrefix = (Namespace.Rdfs + "_").iri()
property.startsWith(compactUriFromContext(sortedMemberPrefix))
}
// get members of the @list
getListMembers(node)

// Parse members
members.sortBy(key).flatMap { entry =>
listElement match {
case _: Obj => parse(entry.value.as[YMap])
case Type.Any => Some(typedValue(entry.value, ctx.graphContext))
case _ =>
try { Some(str(value(listElement, entry.value))) }
catch {
case _: Exception => None
}
}
case None =>
// get members of the sorted array implementation that uses a map with entries with _i for index
contentOfNode(rawNode) match {
case Some(node) => getArrayMapMembers(node)
case None => List.empty
}
case None => Seq.empty // Error already handled by contentOfNode
}
parseListMembers(members, listType)
}

private def parseNode(map: YMap, id: String, model: ModelDefaultBuilder): Option[AmfObject] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,11 @@
"http://a.ml/vocabularies/document#DomainElement"
],
"http://www.w3.org/ns/shacl#in": {
"@id": "amf://id2/list"
}
},
{
"@id": "amf://id2/list",
"@type": "http://www.w3.org/2000/01/rdf-schema#Seq",
"http://www.w3.org/2000/01/rdf-schema#_1": {
"@id": "amf://id2"
"@list": [
{
"@id": "amf://id2"
}
]
}
}
]
Expand Down
17 changes: 17 additions & 0 deletions shared/src/test/scala/amf/core/render/GovernanceModeTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,21 @@ class GovernanceModeTest extends AsyncFunSuite with FileAssertionTest {
result <- assertDifferences(file, golden)
} yield result
}

test("lists should be parsed with @list when using Governance Mode with Flattened JsonLd") {
val golden = "file://shared/src/test/resources/render/governance-mode-list-flattened.jsonld"
val client = AMFGraphConfiguration
.predefined()
.withRenderOptions(
RenderOptions().withPrettyPrint.withGovernanceMode.withoutSourceMaps.withoutSourceInformation
)
.baseUnitClient()

for {
parsed <- client.parse(golden)
rendered = client.render(parsed.baseUnit)
file <- writeTemporaryFile(golden)(rendered)
result <- assertDifferences(file, golden)
} yield result
}
}

0 comments on commit 4a559dd

Please sign in to comment.