diff --git a/manyfold/root/hook.d/experience_updated/300-package_janusxr.rb b/manyfold/root/hook.d/experience_updated/300-package_janusxr.rb index 653e7f6..e69de29 100755 --- a/manyfold/root/hook.d/experience_updated/300-package_janusxr.rb +++ b/manyfold/root/hook.d/experience_updated/300-package_janusxr.rb @@ -1,118 +0,0 @@ -#!/usr/bin/env ruby - -require 'json' -require_relative './../../xrforge.rb' - -# Check if a filename is provided -if ARGV.length != 1 - puts "Usage: #{$0} " - exit 1 -end - -filename = ARGV[0] - -begin - - # dont run for each file-update - if ! filename.end_with?("datapackage.json") - exit 0 - end - - # Change the directory - dir = File.dirname(filename) - Dir.chdir( File.dirname(filename) ) - # Read and parse the JSON file - data = JSON.parse( File.read( "datapackage.json" ) ) - - #if data['keywords'].empty? || data['keywords'].include?('janusxr') - - logfile = File.join( File.dirname(filename), ".xrforge/log.txt" ) - - XRForge.log("✅ starting build janusXR XR scene", logfile) - - # Extract the desired field (assuming the field is named 'model_file') - thumb_file = data['image'] - - XRForge.log("✅ thumbnail sidecar-file '#{thumb_file}' detected", logfile) - - # Get the base name of the thumbnail file without its extension - base_name = File.basename(thumb_file, File.extname(thumb_file)) - - model_file = nil # Initialize model_file to nil - - # Loop over the list of extensions - XRForge::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", 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 - - # Check if a model file was found after the loop - if model_file - XRForge.log("✅ Final model file: '#{model_file}'", logfile) - else - XRForge.log("❌ No suitable 3D file found for XR Fragments- / JanusXR-compatible experience", logfile) - end - - # Get the value of the environment variable FEDERATE_DRIVE_HOST - federate_drive_host = ENV['FEDERATE_DRIVE_HOST'] - - # Define the HTML content using a multi-line string (heredoc) - # Ruby's heredoc allows for variable interpolation (using #{}) - jml = <<~JML - - - - - - - - - - - JML - - html = <<~HTML - - - - janusxr room - - - - - #{jml} - - - - HTML - - File.write('.xrforge/janusxr.html', html) - File.write('.xrforge/scene.jml', jml) - - XRForge.log("✅ generated scene.jml", logfile) - XRForge.log("✅ generated janusxr.html", logfile) - XRForge.log(" ", logfile) - - # tag it! - if ! data['keywords'].include?('janusxr') - data['keywords'].push('janusxr') - File.write("datapackage.json", JSON.pretty_generate(data) ) - end - -rescue Errno::ENOENT - puts "File #{filename} not found" -rescue JSON::ParserError - puts "Error parsing JSON from #{filename}" -rescue => e - puts "An error occurred: #{e.message}" -end diff --git a/manyfold/root/hook.d/experience_updated/500-mastodon-post.rb b/manyfold/root/hook.d/experience_updated/500-mastodon-post.rb index 7244622..d09463b 100755 --- a/manyfold/root/hook.d/experience_updated/500-mastodon-post.rb +++ b/manyfold/root/hook.d/experience_updated/500-mastodon-post.rb @@ -53,7 +53,8 @@ begin first_item = feed.items.first if first_item description = CGI.unescapeHTML(first_item.description.to_s) - .gsub(/<[^>]*>/, '') # remove other tags + .split("\n")[0,4].join("\n") # max 5 lines + .gsub(/<[^>]*>/, '') # remove other tags #.gsub(//i, "\n") # preserve linebreaks #.gsub(/<\/p>/i, "\n") # preserve linebreaks description = description.length > 130 ? description = description[0,130] + " (..)" : description diff --git a/manyfold/root/xrforge.rb b/manyfold/root/xrforge.rb index cbc39c8..0bf2615 100644 --- a/manyfold/root/xrforge.rb +++ b/manyfold/root/xrforge.rb @@ -1,6 +1,9 @@ +require 'rexml/document' +require 'rexml/formatters/pretty' + module XRForge - MODEL_EXT = ['.glb', '.gltf', '.blend', '.usdz', '.obj', '.dae'] + MODEL_EXT = ['.glb', '.gltf', '.blend', '.usdz', '.obj', '.dae', '.x3d'] def self.log(message, filename) # Append the log entry to the log file @@ -10,4 +13,147 @@ module XRForge puts("#{message}\n") end + # ============================================================================== + # 1. JSON (Ruby Hash) to XML Conversion + # ============================================================================== + + # Recursively builds REXML elements based on the input Hash/Array structure. + # It interprets keys starting with '@' as XML attributes. + # Arrays are processed as sequential child nodes. + # + # @param data [Hash, Array] The current portion of the data structure. + # @param parent_rexml [REXML::Element] The parent REXML element to attach children/attributes to. + def self.json2xml_recursive(data, parent_rexml) + return if data.nil? + + # If the current value is an Array, we iterate over its items. + # Each item is expected to define a new child element or a structure to recurse on. + if data.is_a?(Array) + data.each do |item| + # Recursively process each item in the array against the *current* parent. + json2xml_recursive(item, parent_rexml) + end + return + end + + # The data should be a Hash containing key-value pairs representing elements or attributes. + data.each do |key, value| + if key.start_with?('@') + # 1. Attribute: set on the parent REXML element (removing the leading '@') + parent_rexml.attributes[key[1..-1]] = value.to_s + elsif value.is_a?(Hash) || value.is_a?(Array) + # 2. Child Element: create a new element and recurse + new_element = parent_rexml.add_element(key) + json2xml_recursive(value, new_element) + else + # 3. Text Content: create a new element with primitive content (e.g., strings, numbers) + new_element = parent_rexml.add_element(key) + new_element.text = value.to_s + end + end + end + + # Converts a Ruby Hash structure (JSON-like) into a compressed XML string. + # + # @param data [Hash] The root hash structure. + # @return [String] The resulting XML string. + def self.json2xml(data) + # Start with an empty REXML document + doc = REXML::Document.new + + # Data must have a single root element (e.g., 'fireboxroom') + data.each do |root_key, root_value| + root_element = REXML::Element.new(root_key) + doc.add_element(root_element) + json2xml_recursive(root_value, root_element) + # Since the input structure only defines one root key, we break after the first one. + break + end + + # Convert REXML document to XML string without the XML declaration + xml_output = "" + formatter = REXML::Formatters::Pretty.new(2) # indentlevel 2 + + # Write only the root element, ignoring the XML declaration + doc.root.write(xml_output, 0) + xml_output.strip + end + + + # ============================================================================== + # 2. XML to JSON (Ruby Hash) Conversion + # ============================================================================== + + # Recursively converts an REXML element into a Hash structure. + # Elements that appear multiple times become an Array in the Hash. + # Attributes are added with an '@' prefix. + # + # @param element [REXML::Element] The XML element to convert. + # @return [Hash, String] The resulting hash or a text string if the element has no children. + def self.xml2json_recursive(element) + hash = {} + + # 1. Handle Attributes + element.attributes.each do |name, value| + hash["@#{name}"] = value + end + + # 2. Handle Text Content + # If the element has text content but no children (e.g., 10.99), + # return the text directly unless the hash already contains attributes. + if element.has_text? + text_content = element.get_text.value.strip + if !text_content.empty? + if element.elements.empty? && hash.empty? + # If it's pure text (no attributes, no children), return string value + return text_content + elsif element.elements.empty? + # If it has attributes but no children, the text is the '_content' + hash["#text"] = text_content + end + end + end + + # 3. Handle Child Elements + element.elements.each do |child| + child_key = child.name + child_hash = xml2json_recursive(child) + + if hash.key?(child_key) + # If key already exists, convert to array or append to array (standard convention) + if hash[child_key].is_a?(Array) + hash[child_key] << child_hash + else + hash[child_key] = [hash[child_key], child_hash] + end + else + # New key + hash[child_key] = child_hash + end + end + + # Special handling to match the user's specific array format for the 'assets' node. + # This makes the output match the input test case, but is non-standard for general XML-JSON mappers. + if hash.key?("assets") && hash["assets"].is_a?(Hash) && hash["assets"].key?("asset") + asset_content = hash["assets"].delete("asset") + # Force the structure to be: [ {"asset" => asset_content} ] + hash["assets"] = [ {"asset" => asset_content} ] + end + + hash + end + + # Converts an XML string into a corresponding Ruby Hash (JSON-like) structure. + # + # @param xmlstr [String] The XML string. + # @return [Hash] The resulting Ruby hash structure. + def self.xml2json(xmlstr) + doc = REXML::Document.new(xmlstr) + root_element = doc.root + return {} unless root_element + + # The final output is a hash containing the single root element and its content. + { root_element.name => xml2json_recursive(root_element) } + end + end