Fixes and improvements to DOM API.

* added loadFlattened. Fixes #117
 * fixed an error where recursive nodes were not loaded
   properly into YamlNodes.
 * made dump with asNone raise an error when a cycle is
   encountered, instead of running into an endless loop.
This commit is contained in:
Felix Krause 2022-09-07 16:23:50 +02:00
parent 5f7677d914
commit 7bd562e37e
3 changed files with 52 additions and 23 deletions

View File

@ -19,7 +19,7 @@
## The ``YamlNode`` objects in the DOM can be used similarly to the ``JsonNode``
## objects of Nim's `json module <http://nim-lang.org/docs/json.html>`_.
import tables, streams, hashes, sets, strutils
import std / [tables, streams, hashes, sets, strutils]
import data, stream, taglib, serialization, private/internal, parser,
presenter
@ -174,6 +174,7 @@ proc constructChild*(s: var YamlStream, c: ConstructionContext,
fields: newTable[YamlNode, YamlNode](),
mapStyle: start.mapStyle,
startPos: start.startPos, endPos: start.endPos)
addAnchor(c, start.mapProperties.anchor)
while s.peek().kind != yamlEndMap:
var
key: YamlNode = nil
@ -184,28 +185,27 @@ proc constructChild*(s: var YamlStream, c: ConstructionContext,
raise newException(YamlConstructionError,
"Duplicate key: " & $key)
discard s.next()
addAnchor(c, start.mapProperties.anchor)
of yamlStartSeq:
result = YamlNode(tag: start.seqProperties.tag,
kind: ySequence,
elems: newSeq[YamlNode](),
seqStyle: start.seqStyle,
startPos: start.startPos, endPos: start.endPos)
addAnchor(c, start.seqProperties.anchor)
while s.peek().kind != yamlEndSeq:
var item: YamlNode = nil
constructChild(s, c, item)
result.elems.add(item)
addAnchor(c, start.seqProperties.anchor)
discard s.next()
of yamlScalar:
result = YamlNode(tag: start.scalarProperties.tag,
kind: yScalar, scalarStyle: start.scalarStyle,
startPos: start.startPos, endPos: start.endPos)
addAnchor(c, start.scalarProperties.anchor)
when defined(gcArc) or defined(gcOrc):
result.content = move start.scalarContent
else:
shallowCopy(result.content, start.scalarContent)
addAnchor(c, start.scalarProperties.anchor)
of yamlAlias:
result = cast[YamlNode](c.refs.getOrDefault(start.aliasTarget).p)
else: internalError("Malformed YamlStream")
@ -342,3 +342,26 @@ iterator mpairs*(node: var YamlNode):
## *yMapping*. Values can be modified.
doAssert node.kind == yMapping
for key, value in node.fields.mpairs: yield (key, value)
proc loadFlattened*[K](input: Stream | string, target: var K)
{.raises: [YamlConstructionError, YamlSerializationError, IOError, OSError,
YamlParserError].} =
## Replaces all aliases with the referenced nodes in the input, then loads
## the resulting YAML into K. Can be used when anchors & aliases are used like
## variables in the input, to avoid having to define `ref` types for the
## anchored data.
var node: YamlNode
load(input, node)
var stream = represent(node, tsNone, asNone)
try:
var e = stream.next()
yAssert(e.kind == yamlStartStream)
construct(stream, target)
e = stream.next()
yAssert(e.kind == yamlEndStream)
except YamlStreamError:
let e = (ref YamlStreamError)(getCurrentException())
if e.parent of IOError: raise (ref IOError)(e.parent)
if e.parent of OSError: raise (ref OSError)(e.parent)
elif e.parent of YamlParserError: raise (ref YamlParserError)(e.parent)
else: internalError("Unexpected exception: " & $e.parent.name)

View File

@ -46,10 +46,10 @@ type
AnchorStyle* = enum
## How ref object should be serialized.
##
## - ``asNone``: No anchors will be outputted. Values present at
## - ``asNone``: No anchors will be written. Values present at
## multiple places in the content that should be serialized will be
## fully serialized at every occurence. If the content is cyclic, this
## will lead to an endless loop!
## will raise a YamlSerializationError.
## - ``asTidy``: Anchors will only be generated for objects that
## actually occur more than once in the content to be serialized.
## This is a bit slower and needs more memory than ``asAlways``.

View File

@ -16,7 +16,7 @@
## type. Please consult the serialization guide on the NimYAML website for more
## information.
import tables, typetraits, strutils, macros, streams, times, parseutils, options
import std / [tables, typetraits, strutils, macros, streams, times, parseutils, options]
import data, parser, taglib, presenter, stream, private/internal, hints, annotations
export data, stream, macros, annotations, options
# *something* in here needs externally visible `==`(x,y: AnchorId),
@ -1251,21 +1251,26 @@ proc representChild*[T](value: seq[T], ts: TagStyle, c: SerializationContext) =
proc representChild*[O](value: ref O, ts: TagStyle, c: SerializationContext) =
if isNil(value): c.put(scalarEvent("~", yTagNull))
elif c.style == asNone: representChild(value[], ts, c)
else:
var val: tuple[a: Anchor, referenced: bool]
let p = cast[pointer](value)
if c.refs.hasKey(p):
val = c.refs.getOrDefault(p)
yAssert(val.a != yAnchorNone)
if not val.referenced:
c.refs[p] = (val.a, true)
c.put(aliasEvent(val.a))
return
if c.style != asNone:
val = (c.nextAnchorId.Anchor, false)
c.refs[p] = val
nextAnchor(c.nextAnchorId, len(c.nextAnchorId) - 1)
# when c.style == asNone, `referenced` is used as indicator that we are
# currently in the process of serializing this node. This enables us to
# detect cycles and raise an error.
var val = c.refs.getOrDefault(p, (c.nextAnchorId.Anchor, c.style == asNone))
if val.a != c.nextAnchorId.Anchor:
if c.style == asNone:
if val.referenced:
raise newException(YamlSerializationError,
"tried to serialize cyclic graph with asNone")
else:
val = c.refs.getOrDefault(p)
yAssert(val.a != yAnchorNone)
if not val.referenced:
c.refs[p] = (val.a, true)
c.put(aliasEvent(val.a))
return
c.refs[p] = val
nextAnchor(c.nextAnchorId, len(c.nextAnchorId) - 1)
let
childTagStyle = if ts == tsAll: tsAll else: tsRootOnly
origPut = c.put
@ -1273,19 +1278,20 @@ proc representChild*[O](value: ref O, ts: TagStyle, c: SerializationContext) =
var ex = e
case ex.kind
of yamlStartMap:
ex.mapProperties.anchor = val.a
if c.style != asNone: ex.mapProperties.anchor = val.a
if ts == tsNone: ex.mapProperties.tag = yTagQuestionMark
of yamlStartSeq:
ex.seqProperties.anchor = val.a
if c.style != asNone: ex.seqProperties.anchor = val.a
if ts == tsNone: ex.seqProperties.tag = yTagQuestionMark
of yamlScalar:
ex.scalarProperties.anchor = val.a
if c.style != asNone: ex.scalarProperties.anchor = val.a
if ts == tsNone and guessType(ex.scalarContent) != yTypeNull:
ex.scalarProperties.tag = yTagQuestionMark
else: discard
c.put = origPut
c.put(ex)
representChild(value[], childTagStyle, c)
if c.style == asNone: c.refs[p] = (val.a, false)
proc representChild*[T](value: Option[T], ts: TagStyle,
c: SerializationContext) =