148 lines
5.8 KiB
Ruby
148 lines
5.8 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
|
|
|
|
def self.getExperienceFile(datapackage, dir, logfile)
|
|
model_file = nil # Initialize model_file to nil
|
|
base_name = ""
|
|
# Extract the desired field (assuming the field is named 'model_file')
|
|
thumb_file = datapackage['image']
|
|
if thumb_file && ! thumb_file.empty?
|
|
# HEURISTIC: XR Fragments sidecarfile https://xrfragment.org/#%F0%9F%93%9C%20level0%3A%20File
|
|
# Get the base name of the thumbnail file without its extension
|
|
base_name = File.basename(thumb_file, File.extname(thumb_file))
|
|
XRForge.log("✅ thumbnail sidecar-file '#{thumb_file}' detected", logfile)
|
|
# Loop over the list of extensions
|
|
MODEL_EXT.each do |ext|
|
|
# Construct the filename with the current extension
|
|
filename = "#{base_name}#{ext}"
|
|
|
|
# Check if the file exists
|
|
if File.exist?(filename)
|
|
XRForge.log("✅ 3D file '#{filename}' detected (heuristic: XRFragment sidecarfile)", logfile)
|
|
model_file = "#{dir.gsub("/mnt/","")}/#{filename}" # Store the found filename
|
|
break # Stop the loop once a file is found
|
|
else
|
|
# Log a message for the file that was not found, but don't stop
|
|
XRForge.log("⚠️ 3D file '#{filename}' not detected", logfile)
|
|
end
|
|
end
|
|
else
|
|
# HEURISTIC: get first suitable 3D file
|
|
datapackage['resources'].each do |resource|
|
|
ext = File.extname(resource['path'])
|
|
if MODEL_EXT.include?( ext )
|
|
model_file = resource['path']
|
|
XRForge.log("✅ 3D file '#{model_file}' detected (heuristic: first-found)", logfile)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
return File.basename(model_file)
|
|
end
|
|
|
|
end
|