Implemented and documented DOM

This commit is contained in:
Felix Krause 2016-02-22 21:56:30 +01:00
parent 33a7f93480
commit 27670f63c9
10 changed files with 499 additions and 140 deletions

View File

@ -84,3 +84,24 @@ You can choose from multiple presentation styles. ``psJson`` is not able to
process some features of ``YamlStream`` s, the other styles support all features
and are guaranteed to round-trip to the same ``YamlStream`` if you parse the
generated YAML character stream again.
The Document Object Model
=========================
Much like XML, YAML also defines a *document object model*. If you cannot or do
not want to load a YAML character stream to native Nim types, you can instead
load it into a ``YamlDocument``. This ``YamlDocument`` can also be serialized
into a YAML character stream. All tags will be preserved exactly as they are
when transforming from and to a ``YamlDocument``. The only important thing to
remember is that when a value has no tag, it will get the non-specific tag
``"!"`` for quoted scalars and ``"?"`` for all other nodes.
While tags are preserved, anchors will be resolved during loading and re-added
during serialization. It is allowed for a ``YamlNode`` to occur multiple times
within a ``YamlDocument``, in which case it will be serialized once and referred
to afterwards via aliases.
The document object model is provided for completeness, but you are encouraged
to use native Nim types as start- or endpoint instead. That may be significantly
faster, as every ``YamlNode`` is allocated on the heap and subject to garbage
collection.

View File

@ -1,6 +1,6 @@
<svg version="1.1"
baseProfile="full"
width="600" height="340"
width="600" height="420"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
@ -47,6 +47,16 @@
<path d="M 0 60 q 0 10 10 10 h 115" fill="transparent" stroke="black"/>
<path d="M 225 70 h 90 q 10 0 10 -10 v -60" fill="transparent" stroke="black"/>
</g>
<g id="dom-right">
<rect x="330" y="45" width="100" height="30" stroke="black" fill="white" stroke-width="1"/>
<line x1="170" y1="60" x2="330" y2="60" stroke="black" stroke-width="2"/>
<line x1="430" y1="60" x2="583" y2="60" stroke="black" stroke-width="2" marker-end="url(#Triangle)"/>
</g>
<g id="dom-left">
<rect x="330" y="5" width="100" height="30" stroke="black" fill="white" stroke-width="1"/>
<line x1="330" y1="20" x2="182" y2="20" stroke="black" stroke-width="2" marker-end="url(#Triangle)"/>
<line x1="430" y1="20" x2="595" y2="20" stroke="black" stroke-width="2"/>
</g>
<g id="whole-right">
<rect x="275" y="5" width="100" height="30" stroke="black" fill="white" stroke-width="1"/>
<line x1="5" y1="20" x2="275" y2="20" stroke="black" stroke-width="2"/>
@ -59,11 +69,11 @@
</g>
</defs>
<line x1="145" y1="0" x2="145" y2="340" stroke="gray"/>
<line x1="145" y1="0" x2="145" y2="420" stroke="gray"/>
<text x="55" y="25" fill="gray">Application</text>
<text x="160" y="25" fill="gray">YAML</text>
<g id="boxes" text-anchor="middle" transform="translate(0 150)">
<g id="boxes" text-anchor="middle" transform="translate(0 190)">
<g id="native" transform="translate(0 0)">
<use xlink:href="#box"/>
<text x="60" y="25" font-style="italic">Native</text>
@ -73,7 +83,9 @@
<g id="representation" transform="translate(170 0)">
<use xlink:href="#box"/>
<text x="60" y="25" font-style="italic">Representation</text>
<text x="60" y="45" font-weight="bold">not implemented</text>
<a xlink:href="yaml.html#YamlDocument" target="_top">
<text x="60" y="45" font-weight="bold">YamlDocument</text>
</a>
</g>
<g id="serialization" transform="translate(320 0)">
@ -93,7 +105,7 @@
</g>
</g>
<g id="atomics" text-anchor="middle" transform="translate(0 90)">
<g id="atomics" text-anchor="middle" transform="translate(0 130)">
<g id="represent" transform="translate(65 10)">
<use xlink:href="#atomic-right-long"/>
<text x="80" y="25" font-style="italic">represent</text>
@ -101,7 +113,9 @@
<g id="serialize" transform="translate(235 10)">
<use xlink:href="#atomic-right"/>
<text x="70" y="25" font-style="italic" font-weight="bold">serialize</text>
<a xlink:href="yaml.html#serialize,YamlDocument,TagLibrary,AnchorStyle" target="_top">
<text x="70" y="25" font-style="italic" font-weight="bold">serialize</text>
</a>
</g>
<g id="present" transform="translate(385 10)">
@ -118,7 +132,9 @@
<g id="compose" transform="translate(235 120)">
<use xlink:href="#atomic-left"/>
<text x="70" y="35" font-style="italic" font-weight="bold">compose</text>
<a xlink:href="yaml.html#compose,YamlStream,TagLibrary" target="_top">
<text x="70" y="35" font-style="italic" font-weight="bold">compose</text>
</a>
</g>
<g id="parse" transform="translate(385 120)">
@ -129,7 +145,7 @@
</g>
</g>
<g id="skipping-things" text-anchor="middle" transform="translate(0 50)">
<g id="skipping-things" text-anchor="middle" transform="translate(0 90)">
<g id="skipping-represent" transform="translate(55 10)">
<use xlink:href="#skipping-right"/>
<a xlink:href="yaml.html#represent,T,TagStyle,AnchorStyle" target="_top">
@ -151,7 +167,19 @@
<text x="325" y="25" font-style="italic" font-weight="bold">dump</text>
</a>
</g>
<g id="load" transform="translate(0 280)">
<g id="dumpDOM">
<use xlink:href="#dom-right"/>
<a xlink:href="yaml.html#dumpDOM,YamlDocument,Stream,PresentationStyle,AnchorStyle,int" target="_top">
<text x="380" y="65" font-weight="bold">dumpDOM</text>
</a>
</g>
<g id="loadDOM" transform="translate(0 320)">
<use xlink:href="#dom-left"/>
<a xlink:href="yaml.html#loadDOM,Stream" target="_top">
<text x="380" y="25" font-weight="bold">loadDOM</text>
</a>
</g>
<g id="load" transform="translate(0 360)">
<use xlink:href="#whole-left"/>
<a xlink:href="yaml.html#load,Stream,K" target="_top">
<text x="325" y="25" font-style="italic" font-weight="bold">load</text>

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -103,7 +103,7 @@ doc.file = """
<link href='http://fonts.googleapis.com/css?family=Source+Code+Pro:400,500,600' rel='stylesheet' type='text/css'/>
</head>
<body>
<a href="https://github.com/flyx/NimYAML"><img style="position: absolute; top: 0; right: 0; border: 0; z-index: 10;" src="https://camo.githubusercontent.com/652c5b9acfaddf3a9c326fa6bde407b87f7be0f4/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6f72616e67655f6666373630302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_orange_ff7600.png"></a>
<a href="https://github.com/flyx/NimYAML"><img style="position: fixed; top: 0; right: 0; border: 0; z-index: 10;" src="https://camo.githubusercontent.com/652c5b9acfaddf3a9c326fa6bde407b87f7be0f4/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6f72616e67655f6666373630302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_orange_ff7600.png"></a>
<header>
<a class="pagetitle" href="/">NimYAML</a>
<a href="index.html">Home</a>

204
private/dom.nim Normal file
View File

@ -0,0 +1,204 @@
proc newYamlNode*(content: string, tag: string = "?"): YamlNode =
new(result)
result.kind = yScalar
result.content = content
result.tag = tag
proc newYamlNode*(children: openarray[YamlNode], tag: string = "?"):
YamlNode =
new(result)
result.kind = ySequence
result.children = @children
result.tag = tag
proc newYamlNode*(pairs: openarray[tuple[key, value: YamlNode]],
tag: string = "?"): YamlNode =
new(result)
result.kind = yMapping
result.pairs = @pairs
result.tag = tag
proc initYamlDoc*(root: YamlNode): YamlDocument =
result.root = root
proc composeNode(s: var YamlStream, tagLib: TagLibrary,
c: ConstructionContext):
YamlNode {.raises: [YamlStreamError, YamlConstructionError].} =
let start = s.next()
new(result)
try:
case start.kind
of yamlStartMap:
result.tag = tagLib.uri(start.mapTag)
result.kind = yMapping
result.pairs = newSeq[tuple[key, value: YamlNode]]()
while s.peek().kind != yamlEndMap:
let
key = composeNode(s, tagLib, c)
value = composeNode(s, tagLib, c)
result.pairs.add((key: key, value: value))
discard s.next()
if start.mapAnchor != yAnchorNone:
assert(not c.refs.hasKey(start.mapAnchor))
c.refs[start.mapAnchor] = cast[pointer](result)
of yamlStartSequence:
result.tag = tagLib.uri(start.seqTag)
result.kind = ySequence
result.children = newSeq[YamlNode]()
while s.peek().kind != yamlEndSequence:
result.children.add(composeNode(s, tagLib, c))
if start.seqAnchor != yAnchorNone:
assert(not c.refs.hasKey(start.seqAnchor))
c.refs[start.seqAnchor] = cast[pointer](result)
discard s.next()
of yamlScalar:
result.tag = tagLib.uri(start.scalarTag)
result.kind = yScalar
result.content = start.scalarContent
if start.scalarAnchor != yAnchorNone:
assert(not c.refs.hasKey(start.scalarAnchor))
c.refs[start.scalarAnchor] = cast[pointer](result)
of yamlAlias:
result = cast[YamlNode](c.refs[start.aliasTarget])
else: assert false, "Malformed YamlStream"
except KeyError:
raise newException(YamlConstructionError,
"Wrong tag library: TagId missing")
proc compose*(s: var YamlStream, tagLib: TagLibrary): YamlDocument
{.raises: [YamlStreamError, YamlConstructionError].} =
var context = newConstructionContext()
assert s.next().kind == yamlStartDocument
result.root = composeNode(s, tagLib, context)
assert s.next().kind == yamlEndDocument
proc loadDOM*(s: Stream): YamlDocument
{.raises: [IOError, YamlParserError, YamlConstructionError].} =
var
tagLib = initExtendedTagLibrary()
parser = newYamlParser(tagLib)
events = parser.parse(s)
try:
result = compose(events, tagLib)
except YamlStreamError:
let e = getCurrentException()
if e.parent of YamlParserError:
raise (ref YamlParserError)(e.parent)
elif e.parent of IOError:
raise (ref IOError)(e.parent)
else: assert false, "Never happens: " & e.parent.repr
proc serializeNode(n: YamlNode, c: SerializationContext, a: AnchorStyle,
tagLib: TagLibrary): RawYamlStream {.raises: [].}=
let p = cast[pointer](n)
if a != asNone and c.refs.hasKey(p):
try:
if c.refs[p] == yAnchorNone:
c.refs[p] = c.nextAnchorId
c.nextAnchorId = AnchorId(int(c.nextAnchorId) + 1)
except KeyError: assert false, "Can never happen"
result = iterator(): YamlStreamEvent {.raises: [].} =
var event: YamlStreamEvent
try: event = aliasEvent(c.refs[p])
except KeyError: assert false, "Can never happen"
yield event
return
var
tagId: TagId
anchor: AnchorId
try:
if a == asAlways:
c.refs[p] = c.nextAnchorId
c.nextAnchorId = AnchorId(int(c.nextAnchorId) + 1)
else: c.refs[p] = yAnchorNone
tagId = if tagLib.tags.hasKey(n.tag): tagLib.tags[n.tag] else:
tagLib.registerUri(n.tag)
case a
of asNone: anchor = yAnchorNone
of asTidy: anchor = cast[AnchorId](n)
of asAlways: anchor = c.refs[p]
except KeyError: assert false, "Can never happen"
result = iterator(): YamlStreamEvent =
case n.kind
of yScalar:
yield scalarEvent(n.content, tagId, anchor)
of ySequence:
yield startSeqEvent(tagId, anchor)
for item in n.children:
var events = serializeNode(item, c, a, tagLib)
while true:
let event = events()
if finished(events): break
yield event
yield endSeqEvent()
of yMapping:
yield startMapEvent(tagId, anchor)
for i in n.pairs:
var events = serializeNode(i.key, c, a, tagLib)
while true:
let event = events()
if finished(events): break
yield event
events = serializeNode(i.value, c, a, tagLib)
while true:
let event = events()
if finished(events): break
yield event
yield endMapEvent()
template processAnchoredEvent(target: expr, c: SerializationContext): stmt =
try:
let anchorId = c.refs[cast[pointer](target)]
if anchorId != yAnchorNone:
target = anchorId
else: target = yAnchorNone
except KeyError: assert false, "Can never happen"
yield event
proc serialize*(doc: YamlDocument, tagLib: TagLibrary, a: AnchorStyle = asTidy):
YamlStream {.raises: [].} =
var
context = newSerializationContext(a)
events = serializeNode(doc.root, context, a, tagLib)
if a == asTidy:
var backend = iterator(): YamlStreamEvent {.raises: [].} =
var output = newSeq[YamlStreamEvent]()
while true:
let event = events()
if finished(events): break
output.add(event)
yield startDocEvent()
for event in output.mitems():
case event.kind
of yamlScalar:
processAnchoredEvent(event.scalarAnchor, context)
of yamlStartMap: processAnchoredEvent(event.mapAnchor, context)
of yamlStartSequence:
processAnchoredEvent(event.seqAnchor, context)
else: yield event
yield endDocEvent()
result = initYamlStream(backend)
else:
var backend = iterator(): YamlStreamEvent {.raises: [].} =
yield startDocEvent()
while true:
let event = events()
if finished(events): break
yield event
yield endDocEvent()
result = initYamlStream(backend)
proc dumpDOM*(doc: YamlDocument, target: Stream,
style: PresentationStyle = psDefault,
anchorStyle: AnchorStyle = asTidy, indentationStep: int = 2)
{.raises: [YamlPresenterJsonError, YamlPresenterOutputError].} =
## Dump a YamlDocument as YAML character stream.
var
tagLib = initExtendedTagLibrary()
events = serialize(doc, tagLib,
if style == psJson: asNone else: anchorStyle)
try:
present(events, target, tagLib, style, indentationStep)
except YamlStreamError:
# serializing object does not raise any errors, so we can ignore this
assert false, "Can never happen"

View File

@ -9,8 +9,9 @@ proc newConstructionContext(): ConstructionContext =
proc newSerializationContext(s: AnchorStyle): SerializationContext =
new(result)
result.refsList = newSeq[RefNodeData]()
result.refs = initTable[pointer, AnchorId]()
result.style = s
result.nextAnchorId = 0.AnchorId
proc initSerializationTagLibrary(): TagLibrary {.raises: [].} =
result = initTagLibrary()
@ -494,41 +495,49 @@ proc representObject*[O](value: ref O, ts: TagStyle, c: SerializationContext):
elif c.style == asNone:
result = representObject(value[], ts, c)
else:
let
p = cast[pointer](value)
for i in countup(0, c.refsList.high):
if p == c.refsList[i].p:
c.refsList[i].count.inc()
result = iterator(): YamlStreamEvent =
yield aliasEvent(if c.style == asAlways: AnchorId(i) else:
cast[AnchorId](p))
return
c.refsList.add(initRefNodeData(p))
let
a = if c.style == asAlways: AnchorId(c.refsList.high) else:
cast[AnchorId](p)
childTagStyle = if ts == tsAll: tsAll else: tsRootOnly
result = iterator(): YamlStreamEvent =
var child = representObject(value[], childTagStyle, c)
var first = child()
assert(not finished(child))
case first.kind
of yamlStartMap:
first.mapAnchor = a
if ts == tsNone: first.mapTag = yTagQuestionMark
of yamlStartSequence:
first.seqAnchor = a
if ts == tsNone: first.seqTag = yTagQuestionMark
of yamlScalar:
first.scalarAnchor = a
if ts == tsNone and guessType(first.scalarContent) != yTypeNull:
first.scalarTag = yTagQuestionMark
else: discard
yield first
while true:
let event = child()
if finished(child): break
let p = cast[pointer](value)
if c.refs.hasKey(p):
try:
if c.refs[p] == yAnchorNone:
c.refs[p] = c.nextAnchorId
c.nextAnchorId = AnchorId(int(c.nextAnchorId) + 1)
except KeyError: assert false, "Can never happen"
result = iterator(): YamlStreamEvent {.raises: [].} =
var event: YamlStreamEvent
try: event = aliasEvent(c.refs[p])
except KeyError: assert false, "Can never happen"
yield event
return
try:
if c.style == asAlways:
c.refs[p] = c.nextAnchorId
c.nextAnchorId = AnchorId(int(c.nextAnchorId) + 1)
else: c.refs[p] = yAnchorNone
let
a = if c.style == asAlways: c.refs[p] else: cast[AnchorId](p)
childTagStyle = if ts == tsAll: tsAll else: tsRootOnly
result = iterator(): YamlStreamEvent =
var child = representObject(value[], childTagStyle, c)
var first = child()
assert(not finished(child))
case first.kind
of yamlStartMap:
first.mapAnchor = a
if ts == tsNone: first.mapTag = yTagQuestionMark
of yamlStartSequence:
first.seqAnchor = a
if ts == tsNone: first.seqTag = yTagQuestionMark
of yamlScalar:
first.scalarAnchor = a
if ts == tsNone and guessType(first.scalarContent) != yTypeNull:
first.scalarTag = yTagQuestionMark
else: discard
yield first
while true:
let event = child()
if finished(child): break
yield event
except KeyError: assert false, "Can never happen"
proc construct*[T](s: var YamlStream, target: var T)
{.raises: [YamlConstructionError, YamlStreamError].} =
@ -577,29 +586,11 @@ proc load*[K](input: Stream, target: var K)
else:
assert(false)
proc setAnchor(a: var AnchorId, q: var seq[RefNodeData], n: var AnchorId)
proc setAnchor(a: var AnchorId, q: var Table[pointer, AnchorId])
{.inline.} =
if a != yAnchorNone:
let p = cast[pointer](a)
for i in countup(0, q.len - 1):
if p == q[i].p:
if q[i].count > 1:
assert(q[i].anchor == yAnchorNone)
q[i].anchor = n
a = n
n = AnchorId(int(n) + 1)
else: a = yAnchorNone
break
proc setAliasAnchor(a: var AnchorId, q: var seq[RefNodeData]) {.inline.} =
let p = cast[pointer](a)
for i in countup(0, q.len - 1):
if p == q[i].p:
assert q[i].count > 1
assert q[i].anchor != yAnchorNone
a = q[i].anchor
return
assert(false)
try:a = q[cast[pointer](a)]
except KeyError: assert false, "Can never happen"
proc represent*[T](value: T, ts: TagStyle = tsRootOnly,
a: AnchorStyle = asTidy): YamlStream {.raises: [].} =
@ -621,21 +612,17 @@ proc represent*[T](value: T, ts: TagStyle = tsRootOnly,
objQueue.add(event)
except Exception:
assert(false)
var next = 0.AnchorId
var backend = iterator(): YamlStreamEvent =
for i in countup(0, objQueue.len - 1):
var event = objQueue[i]
case event.kind
of yamlStartMap:
event.mapAnchor.setAnchor(context.refsList, next)
event.mapAnchor.setAnchor(context.refs)
of yamlStartSequence:
event.seqAnchor.setAnchor(context.refsList, next)
event.seqAnchor.setAnchor(context.refs)
of yamlScalar:
event.scalarAnchor.setAnchor(context.refsList, next)
of yamlAlias:
event.aliasTarget.setAliasAnchor(context.refsList)
else:
discard
event.scalarAnchor.setAnchor(context.refs)
else: discard
yield event
result = initYamlStream(backend)
else:

73
test/common.nim Normal file
View File

@ -0,0 +1,73 @@
import "../yaml"
proc printDifference*(expected, actual: YamlStreamEvent) =
if expected.kind != actual.kind:
echo "expected " & $expected.kind & ", got " & $actual.kind
else:
case expected.kind
of yamlScalar:
if expected.scalarTag != actual.scalarTag:
echo "[\"", actual.scalarContent, "\".tag] expected tag ",
expected.scalarTag, ", got ", actual.scalarTag
elif expected.scalarAnchor != actual.scalarAnchor:
echo "[scalarEvent] expected anchor ", expected.scalarAnchor,
", got ", actual.scalarAnchor
elif expected.scalarContent != actual.scalarContent:
let msg = "[scalarEvent] expected content \"" &
expected.scalarContent & "\", got \"" &
actual.scalarContent & "\" "
if expected.scalarContent.len != actual.scalarContent.len:
echo msg, "(length does not match)"
else:
for i in 0..expected.scalarContent.high:
if expected.scalarContent[i] != actual.scalarContent[i]:
echo msg, "(first different char at pos ", i,
": expected ",
cast[int](expected.scalarContent[i]),
", got ",
cast[int](actual.scalarContent[i]), ")"
break
else:
echo "[scalarEvent] Unknown difference"
of yamlStartMap:
if expected.mapTag != actual.mapTag:
echo "[map.tag] expected ", expected.mapTag, ", got ",
actual.mapTag
elif expected.mapAnchor != actual.mapAnchor:
echo "[map.anchor] expected ", expected.mapAnchor, ", got ",
actual.mapAnchor
else:
echo "[map.tag] Unknown difference"
of yamlStartSequence:
if expected.seqTag != actual.seqTag:
echo "[seq.tag] expected ", expected.seqTag, ", got ",
actual.seqTag
elif expected.seqAnchor != actual.seqAnchor:
echo "[seq.anchor] expected ", expected.seqAnchor, ", got ",
actual.seqAnchor
else:
echo "[seq] Unknown difference"
of yamlAlias:
if expected.aliasTarget != actual.aliasTarget:
echo "[alias] expected ", expected.aliasTarget, ", got ",
actual.aliasTarget
else:
echo "[alias] Unknown difference"
else:
echo "Unknown difference in event kind " & $expected.kind
template ensure*(input: var YamlStream,
expected: varargs[YamlStreamEvent]) {.dirty.} =
var i = 0
for token in input:
if i >= expected.len:
echo "received more tokens than expected (next token = ",
token.kind, ")"
fail()
break
if token != expected[i]:
echo "at token #" & $i & ":"
printDifference(expected[i], token)
fail()
break
i.inc()

88
test/dom.nim Normal file
View File

@ -0,0 +1,88 @@
import "../yaml"
import unittest, common
suite "DOM":
test "DOM: Composing simple Scalar":
let
input = newStringStream("scalar")
result = loadDOM(input)
assert result.root.kind == yScalar
assert result.root.content == "scalar"
assert result.root.tag == "?"
test "DOM: Serializing simple Scalar":
let input = initYamlDoc(newYamlNode("scalar"))
var result = serialize(input, initExtendedTagLibrary())
ensure(result, startDocEvent(), scalarEvent("scalar"), endDocEvent())
test "DOM: Composing sequence":
let
input = newStringStream("- !!str a\n- !!bool no")
result = loadDOM(input)
assert result.root.kind == ySequence
assert result.root.tag == "?"
assert result.root.children.len == 2
assert result.root.children[0].kind == yScalar
assert result.root.children[0].tag == "tag:yaml.org,2002:str"
assert result.root.children[0].content == "a"
assert result.root.children[1].kind == yScalar
assert result.root.children[1].tag == "tag:yaml.org,2002:bool"
assert result.root.children[1].content == "no"
test "DOM: Serializing sequence":
let input = initYamlDoc(newYamlNode([
newYamlNode("a", "tag:yaml.org,2002:str"),
newYamlNode("no", "tag:yaml.org,2002:bool")]))
var result = serialize(input, initExtendedTagLibrary())
ensure(result, startDocEvent(), startSeqEvent(),
scalarEvent("a", yTagString), scalarEvent("no", yTagBoolean),
endSeqEvent(), endDocEvent())
test "DOM: Composing mapping":
let
input = newStringStream("--- !!map\n!foo bar: [a, b]")
result = loadDOM(input)
assert result.root.kind == yMapping
assert result.root.tag == "tag:yaml.org,2002:map"
assert result.root.pairs.len == 1
assert result.root.pairs[0].key.kind == yScalar
assert result.root.pairs[0].key.tag == "!foo"
assert result.root.pairs[0].key.content == "bar"
assert result.root.pairs[0].value.kind == ySequence
assert result.root.pairs[0].value.children.len == 2
test "DOM: Serializing mapping":
let input = initYamlDoc(newYamlNode([
(key: newYamlNode("bar"), value: newYamlNode([newYamlNode("a"),
newYamlNode("b")]))]))
var result = serialize(input, initExtendedTagLibrary())
ensure(result, startDocEvent(), startMapEvent(), scalarEvent("bar"),
startSeqEvent(), scalarEvent("a"), scalarEvent("b"),
endSeqEvent(), endMapEvent(), endDocEvent())
test "DOM: Composing with anchors":
let
input = newStringStream("- &a foo\n- &b bar\n- *a\n- *b")
result = loadDOM(input)
assert result.root.kind == ySequence
assert result.root.children.len == 4
assert result.root.children[0].kind == yScalar
assert result.root.children[0].content == "foo"
assert result.root.children[1].kind == yScalar
assert result.root.children[1].content == "bar"
assert result.root.children[0] == result.root.children[2]
assert result.root.children[1] == result.root.children[3]
test "DOM: Serializing with anchors":
let
a = newYamlNode("a")
b = newYamlNode("b")
input = initYamlDoc(newYamlNode([a, b, newYamlNode("c"), a, b]))
var result = serialize(input, initExtendedTagLibrary())
ensure(result, startDocEvent(), startSeqEvent(),
scalarEvent("a", anchor=0.AnchorId),
scalarEvent("b", anchor=1.AnchorId), scalarEvent("c"),
aliasEvent(0.AnchorId), aliasEvent(1.AnchorId), endSeqEvent(),
endDocEvent())
test "DOM: Serializing with all anchors":
let
a = newYamlNode("a")
input = initYamlDoc(newYamlNode([a, newYamlNode("b"), a]))
var result = serialize(input, initExtendedTagLibrary(), asAlways)
ensure(result, startDocEvent(), startSeqEvent(anchor=0.AnchorId),
scalarEvent("a", anchor=1.AnchorId),
scalarEvent("b", anchor=2.AnchorId), aliasEvent(1.AnchorId),
endSeqEvent(), endDocEvent())

View File

@ -1,73 +1,12 @@
import "../yaml"
import unittest
proc printDifference(expected, actual: YamlStreamEvent) =
if expected.kind != actual.kind:
echo "expected " & $expected.kind & ", got " & $actual.kind
else:
case expected.kind
of yamlScalar:
if expected.scalarTag != actual.scalarTag:
echo "[\"", actual.scalarContent, "\".tag] expected tag ",
expected.scalarTag, ", got ", actual.scalarTag
elif expected.scalarAnchor != actual.scalarAnchor:
echo "[scalarEvent] expected anchor ", expected.scalarAnchor,
", got ", actual.scalarAnchor
elif expected.scalarContent != actual.scalarContent:
let msg = "[scalarEvent] expected content \"" &
expected.scalarContent & "\", got \"" &
actual.scalarContent & "\" "
if expected.scalarContent.len != actual.scalarContent.len:
echo msg, "(length does not match)"
else:
for i in 0..expected.scalarContent.high:
if expected.scalarContent[i] != actual.scalarContent[i]:
echo msg, "(first different char at pos ", i,
": expected ",
cast[int](expected.scalarContent[i]),
", got ",
cast[int](actual.scalarContent[i]), ")"
break
else:
echo "[scalarEvent] Unknown difference"
of yamlStartMap:
if expected.mapTag != actual.mapTag:
echo "[map.tag] expected ", expected.mapTag, ", got ",
actual.mapTag
else:
echo "[map.tag] Unknown difference"
of yamlStartSequence:
if expected.seqTag != actual.seqTag:
echo "[seq.tag] expected ", expected.seqTag, ", got ",
actual.seqTag
of yamlAlias:
if expected.aliasTarget != actual.aliasTarget:
echo "[alias] expected ", expected.aliasTarget, ", got ",
actual.aliasTarget
else:
echo "[alias] Unknown difference"
else:
echo "Unknown difference in event kind " & $expected.kind
import unittest, common
template ensure(input: string, expected: varargs[YamlStreamEvent]) {.dirty.} =
var
i = 0
parser = newYamlParser(tagLib)
events = parser.parse(newStringStream(input))
try:
for token in events:
if i >= expected.len:
echo "received more tokens than expected (next token = ",
token.kind, ")"
fail()
break
if token != expected[i]:
echo "at token #" & $i & ":"
printDifference(expected[i], token)
fail()
break
i.inc()
try: ensure(events, expected)
except YamlParserError:
let e = cast[ref YamlParserError](getCurrentException())
echo "Parser error:", getCurrentExceptionMsg()

View File

@ -1 +1 @@
import parsing, constructingJson, serializing
import parsing, constructingJson, serializing, dom

View File

@ -218,12 +218,30 @@ type
SerializationContext* = ref object
## Context information for the process of serializing YAML from Nim
## values.
refsList: seq[RefNodeData]
refs: Table[pointer, AnchorId]
style: AnchorStyle
nextAnchorId: AnchorId
RawYamlStream* = iterator(): YamlStreamEvent {.raises: [].} ## \
## Stream of ``YamlStreamEvent``s returned by ``representObject`` procs.
YamlNodeKind* = enum
yScalar, yMapping, ySequence
YamlNode* = ref YamlNodeObj not nil
## Represents a node in a ``YamlDocument``.
YamlNodeObj* = object
tag*: string
case kind*: YamlNodeKind
of yScalar: content*: string
of ySequence: children*: seq[YamlNode]
of yMapping: pairs*: seq[tuple[key, value: YamlNode]]
YamlDocument* = object
## Represents a YAML document.
root*: YamlNode
YamlLoadingError* = object of Exception
## Base class for all exceptions that may be raised during the process
## of loading a YAML character stream.
@ -521,4 +539,5 @@ include private.presenter
include private.hints
include private.fastparse
include private.streams
include private.serialization
include private.serialization
include private.dom