class Moxml::Adapter::CustomizedRexml::Formatter
Custom REXML formatter that fixes indentation and wrapping issues
def escape_attribute_value(value)
def escape_attribute_value(value) value.to_s.gsub(/[<>&"]/) do |match| case match when "<" then "<" when ">" then ">" when "&" then "&" when '"' then """ # when "'" then ''' end end end
def escape_text(text)
def escape_text(text) text.to_s.gsub(/[<>&]/) do |match| case match when "<" then "<" when ">" then ">" when "&" then "&" end end end
def find_significant_sibling(node, direction)
def find_significant_sibling(node, direction) method = direction == :next ? :next_sibling : :previous_sibling sibling = node.send(method) sibling = sibling.send(method) while sibling && sibling.is_a?(::REXML::Text) && sibling.to_s.strip.empty? sibling end
def initialize(indentation: 2, self_close_empty: false)
def initialize(indentation: 2, self_close_empty: false) @indentation = " " * indentation @level = 0 @compact = true @width = -1 # Disable line wrapping @self_close_empty = self_close_empty end
def write(node, output)
def write(node, output) case node when ::REXML::XMLDecl write_declaration(node, output) else super end end
def write_attributes(node, output)
def write_attributes(node, output) # First write namespace declarations node.attributes.each do |name, attr| next unless name.to_s.start_with?("xmlns:") || name.to_s == "xmlns" name = "xmlns" if name.to_s == "xmlns:" # convert the default namespace value = attr.respond_to?(:value) ? attr.value : attr output << " #{name}=\"#{value}\"" end # Then write regular attributes node.attributes.each do |name, attr| next if name.to_s.start_with?("xmlns:") || name.to_s == "xmlns" output << " " output << if attr.respond_to?(:prefix) && attr.prefix "#{attr.prefix}:#{attr.name}" else name.to_s end output << "=\"" value = attr.respond_to?(:value) ? attr.value : attr output << escape_attribute_value(value.to_s) output << "\"" end end
def write_cdata(node, output)
def write_cdata(node, output) # output << ' ' * @level output << "<![CDATA[" output << node.to_s.gsub("]]>", "]]]]><![CDATA[>") output << "]]>" # output << "\n" end
def write_comment(node, output)
def write_comment(node, output) # output << ' ' * @level output << "<!--" output << node.to_s output << "-->" # output << "\n" end
def write_declaration(node, output)
def write_declaration(node, output) output << "<?xml" output << %( version="#{node.version}") if node.version output << %( encoding="#{node.encoding.to_s.upcase}") if node.writeencoding output << %( standalone="#{node.standalone}") if node.standalone output << "?>" # output << "\n" end
def write_doctype(node, output)
def write_doctype(node, output) output << "<!DOCTYPE " output << node.name output << " " output << node.external_id if node.external_id output << ">" # output << "\n" end
def write_document(node, output)
def write_document(node, output) node.children.each do |child| write(child, output) # output << "\n" unless child == node.children.last end end
def write_element(node, output)
def write_element(node, output) # output << ' ' * @level output << "<#{node.expanded_name}" write_attributes(node, output) if node.children.empty? && @self_close_empty output << "/>" return end output << ">" # Check for mixed content has_text = node.children.any? { |c| c.is_a?(::REXML::Text) && !c.to_s.strip.empty? } has_elements = node.children.any? { |c| c.is_a?(::REXML::Element) } mixed = has_text && has_elements # Handle children based on content type unless node.children.empty? @level += @indentation.length unless mixed node.children.each_with_index do |child, _index| # Skip insignificant whitespace next if child.is_a?(::REXML::Text) && child.to_s.strip.empty? && !(child.next_sibling.nil? && child.previous_sibling.nil?) # Indent non-text nodes in non-mixed content # if !mixed && !child.is_a?(::REXML::Text) # output << ' ' * @level # end write(child, output) # Add newlines between elements in non-mixed content # if !mixed && !child.is_a?(::REXML::Text) && index < node.children.size - 1 # output << "\n" # end end # Reset indentation for closing tag in non-mixed content unless mixed @level -= @indentation.length # output << ' ' * @level end end output << "</#{node.expanded_name}>" # output << "\n" unless mixed end
def write_instruction(node, output)
def write_instruction(node, output) # output << ' ' * @level output << "<?" output << node.target output << " " output << node.content if node.content output << "?>" # output << "\n" end
def write_text(node, output)
def write_text(node, output) text = node.value return if text.empty? output << escape_text(text) end