Module: Odin::Transform::FormatExporters
- Defined in:
- lib/odin/transform/format_exporters.rb
Class Method Summary collapse
-
.dyn_to_odin_value(dv) ⇒ Object
Convert a scalar DynValue into the corresponding Odin value, carrying ECMAScript-style numeric formatting through the raw field.
-
.flatten_odin_paths(builder, prefix, value, modifiers) ⇒ Object
Flatten a DynValue tree into path -> Odin value assignments on the builder.
-
.format_double_raw(v) ⇒ Object
ECMAScript number form: whole floats render without a fractional part.
- .format_modifier_prefix(mods) ⇒ Object
- .format_percent_raw(v) ⇒ Object
-
.json_number(v) ⇒ Object
ECMAScript JSON.stringify form: whole-valued finite floats render as integers; others keep their numeric value.
-
.odin_modifiers_for(path, modifiers) ⇒ Object
Build the document Modifiers for a path from tracked transform modifiers.
-
.to_csv(value, delimiter: ",", header: true) ⇒ Object
── CSV Export ──.
-
.to_fixed_width(value, columns:, line_width: nil) ⇒ Object
── Fixed-Width Export ──.
-
.to_flat_kvp(value) ⇒ Object
── Flat KVP Export ──.
-
.to_flat_yaml(value) ⇒ Object
── Flat YAML Export ──.
-
.to_json(value, pretty: true, indent: 2, nulls: nil, empty_arrays: nil) ⇒ Object
── JSON Export ──.
-
.to_odin(value, header: true, modifiers: {}) ⇒ Object
── ODIN Export ──.
-
.to_xml(value, root: "root", declaration: true, indent: 2) ⇒ Object
── XML Export ──.
Class Method Details
.dyn_to_odin_value(dv) ⇒ Object
Convert a scalar DynValue into the corresponding Odin value, carrying ECMAScript-style numeric formatting through the raw field.
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/odin/transform/format_exporters.rb', line 221 def self.dyn_to_odin_value(dv) case dv.type when :null then Odin::Types::OdinNull.new when :bool then Odin::Types::OdinBoolean.new(dv.value) when :integer then Odin::Types::OdinInteger.new(dv.value) when :float Odin::Types::OdinNumber.new(dv.value, raw: format_double_raw(dv.value)) when :float_raw Odin::Types::OdinNumber.new(dv.value.to_f, raw: dv.value.to_s) when :string then Odin::Types::OdinString.new(dv.value) when :currency Odin::Types::OdinCurrency.new(dv.value.to_f, currency_code: dv.currency_code, decimal_places: dv.decimal_places || 2) when :currency_raw Odin::Types::OdinCurrency.new(dv.value.to_f, currency_code: dv.currency_code, decimal_places: dv.decimal_places || 2, raw: dv.value.to_s) when :percent Odin::Types::OdinPercent.new(dv.value, raw: format_percent_raw(dv.value)) when :date then Odin::Types::OdinDate.new(dv.value, raw: dv.value.to_s) when :timestamp then Odin::Types::OdinTimestamp.new(dv.value, raw: dv.value.to_s) when :time t = dv.value.to_s Odin::Types::OdinTime.new(t.start_with?("T") ? t : "T#{t}") when :duration then Odin::Types::OdinDuration.new(dv.value.to_s) when :reference then Odin::Types::OdinReference.new(dv.value.to_s) when :binary then Odin::Types::OdinBinary.new(dv.value.to_s) else Odin::Types::OdinString.new(dv.value.to_s) end end |
.flatten_odin_paths(builder, prefix, value, modifiers) ⇒ Object
Flatten a DynValue tree into path -> Odin value assignments on the builder.
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
# File 'lib/odin/transform/format_exporters.rb', line 191 def self.flatten_odin_paths(builder, prefix, value, modifiers) if value.object? value.value.each do |key, child| path = prefix.empty? ? key.to_s : "#{prefix}.#{key}" flatten_odin_paths(builder, path, child, modifiers) end elsif value.array? value.value.each_with_index do |child, idx| path = "#{prefix}[#{idx}]" flatten_odin_paths(builder, path, child, modifiers) end else builder.set(prefix, dyn_to_odin_value(value), modifiers: odin_modifiers_for(prefix, modifiers)) end end |
.format_double_raw(v) ⇒ Object
ECMAScript number form: whole floats render without a fractional part.
252 253 254 255 |
# File 'lib/odin/transform/format_exporters.rb', line 252 def self.format_double_raw(v) return v.to_i.to_s if v.finite? && v == v.to_i && v.abs < 1e15 format_double(v) end |
.format_modifier_prefix(mods) ⇒ Object
683 684 685 686 687 688 689 690 691 |
# File 'lib/odin/transform/format_exporters.rb', line 683 def self.format_modifier_prefix(mods) return "" if mods.nil? || mods.empty? prefix = +"" prefix << "!" if mods[:required] prefix << "-" if mods[:deprecated] prefix << "*" if mods[:confidential] prefix end |
.format_percent_raw(v) ⇒ Object
257 258 259 |
# File 'lib/odin/transform/format_exporters.rb', line 257 def self.format_percent_raw(v) v == v.to_i.to_f && v.abs < 1e15 ? "#{v.to_i}.0" : v.to_s end |
.json_number(v) ⇒ Object
ECMAScript JSON.stringify form: whole-valued finite floats render as integers; others keep their numeric value.
351 352 353 354 355 |
# File 'lib/odin/transform/format_exporters.rb', line 351 def self.json_number(v) return v unless v.is_a?(Float) return v unless v.finite? v == v.to_i && v.abs < 1e15 ? v.to_i : v end |
.odin_modifiers_for(path, modifiers) ⇒ Object
Build the document Modifiers for a path from tracked transform modifiers.
208 209 210 211 212 213 214 215 216 217 |
# File 'lib/odin/transform/format_exporters.rb', line 208 def self.odin_modifiers_for(path, modifiers) return nil if modifiers.nil? || !modifiers.key?(path) mods = modifiers[path] return nil if mods.nil? || mods.empty? Odin::Types::OdinModifiers.new( required: mods.include?(:required) || mods.include?(Odin::Transform::FieldModifier::REQUIRED), deprecated: mods.include?(:deprecated) || mods.include?(Odin::Transform::FieldModifier::DEPRECATED), confidential: mods.include?(:confidential) || mods.include?(Odin::Transform::FieldModifier::CONFIDENTIAL) ) end |
.to_csv(value, delimiter: ",", header: true) ⇒ Object
── CSV Export ──
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/odin/transform/format_exporters.rb', line 88 def self.to_csv(value, delimiter: ",", header: true) return "" unless value.array? items = value.value return "" if items.empty? # Collect all column names from all rows columns = [] items.each do |item| next unless item.object? item.value.each_key do |k| columns << k unless columns.include?(k) end end return "" if columns.empty? lines = [] # Header row (unless suppressed) if header lines << columns.map { |c| csv_escape(c, delimiter) }.join(delimiter) end # Data rows items.each do |item| next unless item.object? row = columns.map do |col| v = item.value[col] v ? csv_escape(dynvalue_to_string(v), delimiter) : "" end lines << row.join(delimiter) end lines.join("\n") + "\n" end |
.to_fixed_width(value, columns:, line_width: nil) ⇒ Object
── Fixed-Width Export ──
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/odin/transform/format_exporters.rb', line 128 def self.to_fixed_width(value, columns:, line_width: nil) rows = if value.array? value.value else [value] end total_width = line_width || columns.map { |c| (c[:pos] || 0) + (c[:len] || 0) }.max || 80 lines = rows.map do |row| line = " " * total_width columns.each do |col| pos = col[:pos] || 0 len = col[:len] || 0 pad_char = col[:pad] || " " align = col[:align] || :left name = col[:name] raw_val = if row.object? v = row.value[name] v ? dynvalue_to_string(v) : "" else "" end # Truncate if needed raw_val = raw_val[0...len] if raw_val.length > len padded = if align == :right raw_val.rjust(len, pad_char) else raw_val.ljust(len, pad_char) end # Write into line at position padded.chars.each_with_index do |ch, i| line[pos + i] = ch if pos + i < total_width end end line.rstrip end lines.join("\n") + "\n" end |
.to_flat_kvp(value) ⇒ Object
── Flat KVP Export ──
263 264 265 266 267 268 269 270 |
# File 'lib/odin/transform/format_exporters.rb', line 263 def self.to_flat_kvp(value) return "" unless value.object? lines = [] flatten_for_kvp(value.value, "", lines) lines.sort! lines.join("\n") + "\n" end |
.to_flat_yaml(value) ⇒ Object
── Flat YAML Export ──
274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/odin/transform/format_exporters.rb', line 274 def self.to_flat_yaml(value) return "" unless value.object? sb = +"" # Sort top-level keys alphabetically sorted_keys = value.value.keys.sort sorted_keys.each do |key| val = value.value[key] write_yaml_value(sb, key, val, 0) end sb end |
.to_json(value, pretty: true, indent: 2, nulls: nil, empty_arrays: nil) ⇒ Object
── JSON Export ──
12 13 14 15 16 17 18 19 20 21 22 23 |
# File 'lib/odin/transform/format_exporters.rb', line 12 def self.to_json(value, pretty: true, indent: 2, nulls: nil, empty_arrays: nil) ruby_obj = dynvalue_to_json_obj(value) ruby_obj = omit_nulls(ruby_obj) if nulls == "omit" ruby_obj = omit_empty_arrays(ruby_obj) if empty_arrays == "omit" if indent == 0 JSON.generate(ruby_obj) elsif pretty JSON.pretty_generate(ruby_obj, indent: " " * indent) else JSON.generate(ruby_obj) end end |
.to_odin(value, header: true, modifiers: {}) ⇒ Object
── ODIN Export ──
175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/odin/transform/format_exporters.rb', line 175 def self.to_odin(value, header: true, modifiers: {}) builder = Odin::Types::OdinDocumentBuilder.new builder.("odin", Odin::Types::OdinString.new("1.0.0")) if header if value.object? flatten_odin_paths(builder, "", value, modifiers) elsif value.array? flatten_odin_paths(builder, "items", value, modifiers) else builder.set("value", dyn_to_odin_value(value)) end Odin.stringify(builder.build, use_headers: true) end |
.to_xml(value, root: "root", declaration: true, indent: 2) ⇒ Object
── XML Export ──
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/odin/transform/format_exporters.rb', line 55 def self.to_xml(value, root: "root", declaration: true, indent: 2) xml = +"" xml << %{<?xml version="1.0" encoding="UTF-8"?>\n} if declaration indent_str = " " * indent include_ns = needs_odin_namespace?(value) ns_attr = include_ns ? ' xmlns:odin="https://odin.foundation/ns"' : "" if value.object? # If the object has exactly one key, use that as root keys = value.value.keys if keys.size == 1 && value.value[keys[0]].object? xml << render_xml_element(keys[0], value.value[keys[0]], 0, indent_str: indent_str, include_ns: include_ns, is_root: true) else xml << "<#{root}#{ns_attr}>\n" value.value.each do |k, v| xml << render_xml_element(k, v, 1, indent_str: indent_str, include_ns: include_ns) end xml << "</#{root}>\n" end elsif value.array? xml << "<#{root}#{ns_attr}>\n" value.value.each do |item| xml << render_xml_element("item", item, 1, indent_str: indent_str, include_ns: include_ns) end xml << "</#{root}>\n" else type_attr = include_ns ? dv_xml_type_attr(value) : "" xml << "<#{root}#{ns_attr}#{type_attr}>#{xml_escape(dynvalue_to_string(value))}</#{root}>\n" end xml end |