Skip to content

Instantly share code, notes, and snippets.

@ronaldtse
Created June 26, 2025 03:08
Show Gist options
  • Select an option

  • Save ronaldtse/f878ebca1b9a4c4e52851fc00509c75c to your computer and use it in GitHub Desktop.

Select an option

Save ronaldtse/f878ebca1b9a4c4e52851fc00509c75c to your computer and use it in GitHub Desktop.
Metanorma script that fixes Lucid / LucidChart SVG files that have `<use>` elements reference invalid IDs, see https://community.lucid.co/lucid-for-engineering-19/svg-export-results-in-invalid-idref-on-a-use-element-8050
#!/usr/bin/env ruby
# This script is developed for Metanorma users that need to fix Lucid-generated
# SVGs. Lucid-generated SVGs have `<use>` elements reference invalid IDs, which
# result in invalid SVG XML. Metanorma PDFs and Word outputs rely on valid SVG
# XML files otherwise the PDF output will omit broken SVGs, and the Word output
# will have EMF files that have areas of the figures appear in black.
#
# The issue is described at:
# https://community.lucid.co/lucid-for-engineering-19/svg-export-results-in-invalid-idref-on-a-use-element-8050
#
# As of 2025-06-26, this issue is not fixed.
require 'thor'
require 'nokogiri'
require 'fileutils'
require 'set'
class SvgLucidFix < Thor
# Fix Thor deprecation warning
def self.exit_on_failure?
true
end
# Make fix the default command
default_command :fix
desc "fix PATH", "Fix SVG files by removing use elements with invalid IDREF"
method_option :inplace, type: :boolean, default: false, aliases: "-i",
desc: "Modify files in place"
method_option :output, type: :string, aliases: "-o",
desc: "Output directory for modified files (required if not using --inplace)"
def fix(path_pattern)
files = Dir.glob(path_pattern)
if files.empty?
puts "No files found matching pattern: #{path_pattern}"
exit 1
end
unless options[:inplace] || options[:output]
puts "Error: Either --inplace or --output must be specified"
exit 1
end
if options[:output] && !options[:inplace]
unless Dir.exist?(options[:output])
begin
FileUtils.mkdir_p(options[:output])
puts "Created output directory: #{options[:output]}"
rescue => e
puts "Error creating output directory: #{e.message}"
exit 1
end
end
end
files.each do |file|
process_svg_file(file)
end
end
private
def process_svg_file(file)
begin
# Parse the SVG file
doc = File.open(file) { |f| Nokogiri::XML(f) }
# Find all elements with ID attributes
existing_ids = Set.new
doc.xpath('//*[@id]').each do |element|
existing_ids.add(element['id'])
end
# Find all use elements with xlink:href attributes
invalid_use_elements = []
doc.xpath('//svg:use', 'svg' => 'http://www.w3.org/2000/svg').each do |use_element|
href = use_element['xlink:href'] || use_element['href']
next unless href
# Extract the ID from the href (removing the # prefix)
id_ref = href.sub(/^#/, '')
# Check if the ID exists in the document
unless existing_ids.include?(id_ref)
invalid_use_elements << use_element
end
end
# Remove invalid use elements
if invalid_use_elements.any?
invalid_use_elements.each(&:remove)
puts "Fixed #{file}: Removed #{invalid_use_elements.size} invalid use elements"
# Save the modified SVG
if options[:inplace]
output_path = file
else
output_path = File.join(options[:output], File.basename(file))
end
File.write(output_path, doc.to_xml)
else
puts "No invalid use elements found in #{file}"
end
rescue => e
puts "Error processing #{file}: #{e.message}"
end
end
end
# Allow the script to be run with bundle exec
if $0 == __FILE__
SvgLucidFix.start(ARGV)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment