/**
* @file
* This file is about yaml decode.
*/
package yaml4cj.yaml
/**
* The Function is decode
*
* @param data of Array<UInt8>
*
* @return Type of JsonValue
* @since 0.30.4
*/
public func decode(data: Array<UInt8>): JsonValue {
initResolve()
decode(data, false)
}
/**
* The Function is decode
*
* @param data of Array<UInt8>
* @param strict of Bool
*
* @return Type of JsonValue
* @since 0.30.4
*/
public func decode(data: Array<UInt8>, strict: Bool): JsonValue {
initResolve()
let p = Parser(data)
try {
let r = p.parse()
match (r) {
case Some(n) =>
let d = Decoder(strict)
let (out, _) = d.decode(n)
if (d.terrors.size > 0) {
throw TypeError(d.terrors.toArray())
}
return out
case _ => return JsonNull()
}
} catch (err: Exception) {
throw err
} finally {
p.destroy()
}
}
class Decoder {
var doc: ?Node = None
var aliases: HashMap<Node, Bool>
var terrors: ArrayList<String> = ArrayList<String>()
var strict: Bool
var decodeCount: Int64 = 0
var aliasCount: Int64 = 0
var aliasDepth: Int64 = 0
init(strict: Bool) {
this.strict = strict
this.aliases = HashMap<Node, Bool>()
}
func decode(n: Node): (JsonValue, Bool) {
this.decodeCount++
if (this.aliasDepth > 0) {
this.aliasCount++
}
// if (this.aliasCount > 100 && this.decodeCount > 1000 && (Float64(this.aliasCount) / Float64(this.decodeCount)) >
// allowedAliasRatio(this.decodeCount)) {
// failf("document contains excessive aliasing")
// }
match (n.kind) {
case ParserNodeType_DOCUMENT_NODE => return document(n)
case ParserNodeType_ALIAS_NODE => return alias(n)
case ParserNodeType_SCALAR_NODE => return scalar(n)
case ParserNodeType_MAPPING_NODE => return mapping(n)
case ParserNodeType_SEQUENCE_NODE => return sequence(n)
case _ => throw Exception("internal error: unknown node kind: ${n.kind}")
}
}
func document(n: Node): (JsonValue, Bool) {
if (n.children.size == 1) {
this.doc = n
let (out, _) = decode(n.children[0].getOrThrow())
return (out, true)
}
return (JsonNull(), false)
}
func alias(n: Node): (JsonValue, Bool) {
if (match (this.aliases.get(n)) {
case Some(r) => r
case _ => false
}) {
failf("anchor '${n.value}' value contains itself")
}
this.aliases[n] = true
this.aliasDepth++
let (out, good) = decode(n.alias.getOrThrow())
this.aliasDepth--
this.aliases.remove(n)
return (out, good)
}
func scalar(n: Node): (JsonValue, Bool) {
var tag = ""
var resolved: JsonValue = JsonNull()
if (n.tag == "" && !n.implicit) {
tag = YAML_STR_TAG
resolved = JsonString(n.value)
} else {
let (t, r) = resolve(n.tag, n.value)
tag = t
resolved = r
if (tag == YAML_BINARY_TAG) {
let data = fromBase64String(resolved.asString().getValue().replace("\n", ""))
match (data) {
case Some(d) =>
let resolve = JsonArray()
for (i in d) {
resolve.add(JsonInt(Int64(i)))
}
resolved = resolve
case _ => failf("!!binary value contains invalid base64 data")
}
}
}
return (resolved, true)
}
func mapping(n: Node): (JsonValue, Bool) {
let parent = JsonObject()
let children = n.children
for (i in 0..children.size : 2) {
if (isMerge(children[i].getOrThrow())) {
continue
}
let (k, kok) = decode(children[i].getOrThrow())
if (kok) {
let (v, vok) = decode(children[i + 1].getOrThrow())
if (vok) {
if (k is JsonArray) {
(k as JsonArray).getOrThrow()
}
parent.put(decodeKey(k), v)
}
}
}
(parent, true)
}
func sequence(n: Node): (JsonValue, Bool) {
let parent = JsonArray()
for (v in n.children) {
let (item, ok) = decode(v.getOrThrow())
if (ok) {
parent.add(item)
}
}
(parent, true)
}
}
let aliasRatioRangeLow = 400000
let aliasRatioRangeHigh = 4000000
let aliasRatioRange = Float64(aliasRatioRangeHigh - aliasRatioRangeLow)
/*
func allowedAliasRatio(decodeCount: Int64): Float64 {
if (decodeCount <= aliasRatioRangeLow) {
0.99
} else if (decodeCount >= aliasRatioRangeHigh) {
0.10
} else {
0.99 - 0.89 * (Float64(decodeCount - aliasRatioRangeLow) / aliasRatioRange)
}
}
*/
func isMerge(n: Node): Bool {
return n.kind == ParserNodeType_SCALAR_NODE && n.value == "<<" && (n.implicit == true || n.tag == YAML_MERGE_TAG)
}
func decodeKey(k: JsonValue): String {
if (k is JsonArray) {
var s = ""
var i = 0
for (v in k.asArray().getItems()) {
if (i > 0) {
s += ","
}
s += decodeKey(v)
i++
}
s
} else if (k is JsonObject) {
k.asObject().toJsonString()
} else if (k is JsonString) {
k.asString().getValue()
} else {
k.toString()
}
}