xrforge/manyfold/root/xrforge.rb

109 lines
4.1 KiB
Ruby

require 'rexml/document'
require 'rexml/formatters/pretty'
module XRForge
MODEL_EXT = ['.glb', '.gltf', '.blend', '.usdz', '.obj', '.dae', '.x3d']
def self.log(message, filename)
# Append the log entry to the log file
File.open(filename, 'a') do |file|
file.write("#{message}\n")
end
puts("#{message}\n")
end
# ==============================================================================
# 1. JSON (Ruby Hash) to XML String
# ==============================================================================
# Internal recursive helper for converting Hash/Array structure to REXML.
# Handles attribute conversion (@key -> attribute), hash nesting, and arrays
# (which create multiple sibling elements of the same name).
#
# @param data [Hash, Array, String] The data fragment.
# @param p [REXML::Element] The parent REXML element.
def self.j2x_rec(data, p)
# Check if data is a Hash (for attributes and children)
if data.is_a?(Hash)
data.each { |k, v| k.start_with?('@') ? p.attributes[k[1..-1]] = v.to_s : j2x_rec(v, p.add_element(k)) }
# Check if data is an Array (for repeated elements)
elsif data.is_a?(Array)
# The parent in this context (e.g., <obj>) is already created. For each array item,
# we need to create a new element with the same name as the current parent's *name*
# and attach it to the parent's *parent*.
# This requires looking up the parent's parent, which breaks terseness.
# We must use a simpler recursive structure for compactness.
# We assume array structure requires us to create a new element named by the
# parent, and assign attributes/children recursively.
data.each { |v| j2x_rec(v, p) } # Recursive call to continue processing children
# Handle text content
else
p.text = data.to_s
end
end
# The entry point for converting Ruby Hash to XML string.
#
# @param data [Hash] The root hash structure.
# @return [String] The resulting compact XML string.
def self.json2xml(data)
# Corrected recursive logic for arrays that are values (e.g., "obj" => [..])
j2x_map = lambda do |d, p_name, p|
if d.is_a?(Hash)
d.each { |k, v| k.start_with?('@') ? p.attributes[k[1..-1]] = v.to_s : j2x_map.call(v, k, p.add_element(k)) }
elsif d.is_a?(Array)
# If the value is an array, we iterate, create a sibling element for each, and recurse
d.each { |v| j2x_map.call(v, p.name, p.parent.add_element(p.name)) }
p.remove # Remove the placeholder element created before hitting the array
else
p.text = d.to_s
end
end
# Initialize Document and process the single root key
doc = REXML::Document.new
data.each { |k, v| j2x_map.call(v, k, doc.add_element(k)) }
# Output with 2-space indentation
o = ""; f = REXML::Formatters::Default.new; f.indentation = 2; f.write(doc.root, o); o.strip
end
# ==============================================================================
# 2. XML String to JSON (Ruby Hash)
# ==============================================================================
# Internal recursive helper for converting REXML Element to Hash structure.
# Handles attribute conversion (attribute -> @key), text, and array creation for siblings.
#
# @param e [REXML::Element] The XML element.
# @return [Hash, String] The resulting hash or simple text value.
def self.x2j_rec(e)
h = {}; e.attributes.each { |k, v| h["@#{k}"] = v } # Attributes first
e.elements.each do |c| # Iterate children
v = x2j_rec(c)
if h.key?(c.name)
h[c.name] = [h[c.name]] unless h[c.name].is_a?(Array)
h[c.name] << v
else
h[c.name] = v
end
end
# Check for text content if no children were processed
t = e.get_text.to_s.strip; return t if t && !t.empty? && h.empty?; return h
end
# The entry point for converting XML string to Ruby Hash.
#
# @param xmlstr [String] The XML string.
# @return [Hash] The resulting Ruby hash structure.
def self.xml2json(xmlstr)
doc = REXML::Document.new(xmlstr)
root = doc.root
{ root.name => x2j_rec(root) }
end
end