///|
priv enum YamlAction { Value(Json,Int,Int,String); Text(String) }
///|
fn yaml_string(value : String) -> String {
let out=StringBuilder(); out.write_char('"')
for c in value {
match c {
'"' => out.write_string("\\u0022")
'{' => out.write_string("\\u007b")
'}' => out.write_string("\\u007d")
'\\' => out.write_string("\\\\")
'\n' => out.write_string("\\n")
'\r' => out.write_string("\\r")
'\t' => out.write_string("\\t")
_ => if c.to_int() < 32 || c.to_int() == 8232 || c.to_int() == 8233 {
let encoded=Json(c.to_string()).stringify()
out.write_string(encoded[1:encoded.length()-1].to_owned())
} else { out.write_char(c) }
}
}
out.write_char('"'); out.to_string()
}
///|
/// Write typed metadata as block YAML, because the pinned native shape-data
/// lexer closes at the first unquoted '}'. An explicit stack bounds traversal.
pub fn canonical_yaml(value : Json, depth_limit? : Int = 256, work_limit? : Int = 1000000, output_limit? : Int = 16777216) -> String? {
let stack=[YamlAction::Value(value,0,0,"")]; let out=StringBuilder(); let mut work=0; let mut output=0
while stack.pop() is Some(action) {
match action {
Text(text) => {
if text.length() > work_limit-work || text.length() > output_limit-output { return None }
work+=text.length(); output+=text.length(); out.write_string(text)
}
Value(value,depth,indent,prefix) => {
if depth > depth_limit || work >= work_limit { return None }; work+=1
let spaces=" ".repeat(indent)
match value {
Object(fields) if !fields.is_empty() => {
if fields.length() > work_limit-work { return None }
let keys=fields.keys().to_array(); keys.sort_by((a,b) => a.lexical_compare(b))
let child_indent=if prefix == "" { indent } else { indent+2 }
for i=keys.length()-1; i>=0; i=i-1 {
let key=keys[i]
if key.length() > work_limit-work { return None }
stack.push(Value(fields.get(key).unwrap(),depth+1,child_indent,yaml_string(key)+": "))
}
if prefix != "" { stack.push(Text(spaces+prefix.trim_end().to_owned()+"\n")) }
}
Array(values) if !values.is_empty() => {
if values.length() > work_limit-work { return None }
let child_indent=if prefix == "" { indent } else { indent+2 }
for i=values.length()-1; i>=0; i=i-1 { stack.push(Value(values[i],depth+1,child_indent,"- ")) }
if prefix != "" { stack.push(Text(spaces+prefix.trim_end().to_owned()+"\n")) }
}
_ => {
let scalar=match value {
Object(_) => "!!map"
Array(_) => "[]"
String(text) => { if text.length() > work_limit-work { return None }; yaml_string(text) }
Number(number,..) if number.is_nan() => ".nan"
Number(number,..) if number.is_pos_inf() => ".inf"
Number(number,..) if number.is_neg_inf() => "-.inf"
_ => value.stringify()
}
stack.push(Text(spaces+prefix+scalar+"\n"))
}
}
}
}
}
Some(out.to_string())
}