Class: YamlExporter::Exporter::BlockScalarTree
- Inherits:
-
Psych::Visitors::YAMLTree
- Object
- Psych::Visitors::YAMLTree
- YamlExporter::Exporter::BlockScalarTree
- Defined in:
- lib/yaml_exporter/exporter.rb
Overview
Renders any LiteralString value as a literal block scalar (|). Plain
Strings and every other type fall through to Psych's default handling,
so the output is byte-identical to YAML.dump whenever no LiteralString
is present.
Astral-plane characters (codepoints >= U+10000 — emoji, CJK Ext B, …)
are the one thing that defeats block style: libyaml's emitter treats any
4-byte UTF-8 character as non-printable, and a non-printable character
can only be written escaped (\U0001F4A1), which exists only in
double-quoted style. So a single emoji silently drops the whole value
back to an inline quoted scalar. BMP characters (umlauts, accents, ✓, →)
are unaffected.
To keep block style we swap each astral character out for a Private Use Area sentinel — which libyaml does consider printable — before emitting, then swap the real characters back into the finished document. The sentinels never survive in the output, and the round-trip is exact: the parser reads literal astral characters in block scalars without trouble.
Constant Summary collapse
- ASTRAL =
/[\u{10000}-\u{10FFFF}]/- SENTINEL_OPEN =
U+E000/U+E001 are Private Use Area: printable to libyaml, and they never carry meaning in real text, so a
<open>digits<close>token cannot collide with exported content. "\u{E000}"- SENTINEL_CLOSE =
"\u{E001}"- SENTINEL =
/#{SENTINEL_OPEN}\d+#{SENTINEL_CLOSE}/.freeze
Instance Method Summary collapse
- #accept(target) ⇒ Object
-
#escape_astral(text) ⇒ Object
Replaces each astral character with a sentinel token, remembering the reverse mapping so #restore_astral can put the real characters back.
- #restore_astral(yaml) ⇒ Object
Instance Method Details
#accept(target) ⇒ Object
100 101 102 103 104 105 106 |
# File 'lib/yaml_exporter/exporter.rb', line 100 def accept(target) if target.is_a?(LiteralString) return @emitter.scalar(escape_astral(target.to_s), nil, nil, true, true, Psych::Nodes::Scalar::LITERAL) end super end |
#escape_astral(text) ⇒ Object
Replaces each astral character with a sentinel token, remembering the reverse mapping so #restore_astral can put the real characters back.
110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/yaml_exporter/exporter.rb', line 110 def escape_astral(text) return text unless text.match?(ASTRAL) text.gsub(ASTRAL) do |char| forward[char] ||= begin token = "#{SENTINEL_OPEN}#{substitutions.size}#{SENTINEL_CLOSE}" substitutions[token] = char token end end end |
#restore_astral(yaml) ⇒ Object
122 123 124 125 126 |
# File 'lib/yaml_exporter/exporter.rb', line 122 def restore_astral(yaml) return yaml if substitutions.empty? yaml.gsub(SENTINEL) { |token| substitutions.fetch(token) } end |