diff --git a/lib/moxml/adapter/nokogiri.rb b/lib/moxml/adapter/nokogiri.rb index 3c8f48b..b9beef7 100644 --- a/lib/moxml/adapter/nokogiri.rb +++ b/lib/moxml/adapter/nokogiri.rb @@ -46,17 +46,12 @@ def create_native_processing_instruction(target, content) end def create_native_declaration(version, encoding, standalone) - doc = ::Nokogiri::XML::Document.new - doc.create_internal_subset("xml", nil, nil) - declaration = ::Nokogiri::XML::ProcessingInstruction.new(doc, "xml", declaration_content(version, encoding, standalone)) - declaration - end - - def declaration_content(version, encoding, standalone) - content = "version=\"#{version}\"" - content << " encoding=\"#{encoding}\"" if encoding - content << " standalone=\"#{standalone}\"" if standalone - content + decl = ::Nokogiri::XML::ProcessingInstruction.new( + ::Nokogiri::XML::Document.new, + "xml", + build_declaration_attrs(version, encoding, standalone) + ) + decl end def set_namespace(element, ns) @@ -227,6 +222,15 @@ def serialize(node, options = {}) save_with: save_options, ) end + + private + + def build_declaration_attrs(version, encoding, standalone) + attrs = { "version" => version } + attrs["encoding"] = encoding if encoding + attrs["standalone"] = standalone if standalone + attrs.map { |k, v| %{#{k}="#{v}"} }.join(" ") + end end end end diff --git a/lib/moxml/adapter/oga.rb b/lib/moxml/adapter/oga.rb index fba8149..7fcb8da 100644 --- a/lib/moxml/adapter/oga.rb +++ b/lib/moxml/adapter/oga.rb @@ -39,13 +39,10 @@ def create_native_processing_instruction(target, content) end def create_native_declaration(version, encoding, standalone) - doc = ::Oga::XML::Document.new - doc.xml_declaration = ::Oga::XML::XmlDeclaration.new( - version: version, - encoding: encoding, - standalone: standalone, + ::Oga::XML::ProcessingInstruction.new( + name: "xml", + text: build_declaration_attrs(version, encoding, standalone), ) - doc end def create_native_namespace(element, prefix, uri) diff --git a/lib/moxml/adapter/ox.rb b/lib/moxml/adapter/ox.rb index 5c72a70..5807efa 100644 --- a/lib/moxml/adapter/ox.rb +++ b/lib/moxml/adapter/ox.rb @@ -24,6 +24,12 @@ def create_document ::Ox::Document.new end + def create_native_declaration(version, encoding, standalone) + inst = ::Ox::Instruction.new("xml") + inst.value = build_declaration_attrs(version, encoding, standalone) + inst + end + def create_native_element(name) element = ::Ox::Element.new(name) element.instance_variable_set(:@attributes, {}) diff --git a/lib/moxml/declaration.rb b/lib/moxml/declaration.rb index f39177d..beba1e8 100644 --- a/lib/moxml/declaration.rb +++ b/lib/moxml/declaration.rb @@ -1,12 +1,18 @@ # lib/moxml/declaration.rb module Moxml class Declaration < Node + ALLOWED_VERSIONS = ["1.0", "1.1"].freeze + ALLOWED_STANDALONE = ["yes", "no"].freeze + def version extract_attribute("version") end def version=(new_version) - update_content("version", new_version) + unless ALLOWED_VERSIONS.include?(new_version) + raise ValidationError, "Invalid XML version: #{new_version}" + end + set_attribute("version", new_version) end def encoding @@ -14,7 +20,14 @@ def encoding end def encoding=(new_encoding) - update_content("encoding", new_encoding) + if new_encoding + begin + Encoding.find(new_encoding) + rescue ArgumentError + raise ValidationError, "Invalid encoding: #{new_encoding}" + end + end + set_attribute("encoding", new_encoding) end def standalone @@ -22,7 +35,10 @@ def standalone end def standalone=(new_standalone) - update_content("standalone", new_standalone) + unless new_standalone.nil? || ALLOWED_STANDALONE.include?(new_standalone) + raise ValidationError, "Invalid standalone value: #{new_standalone}" + end + set_attribute("standalone", new_standalone) end def declaration? @@ -37,19 +53,24 @@ def extract_attribute(name) match && match[1] end - def update_content(name, value) - content = @native.content || "" + def set_attribute(name, value) + attrs = current_attributes if value.nil? - content.gsub!(/\s*#{name}="[^"]*"/, "") + attrs.delete(name) else - if content.include?("#{name}=\"") - content.gsub!(/#{name}="[^"]*"/, "#{name}=\"#{value}\"") - else - content << " #{name}=\"#{value}\"" - end + attrs[name] = value end - @native.content = content.strip - self + update_content(attrs) + end + + def current_attributes + @native.content.to_s.scan(/(\w+)="([^"]*)"/).each_with_object({}) do |(name, value), hash| + hash[name] = value + end + end + + def update_content(attrs) + @native.content = attrs.map { |k, v| %{#{k}="#{v}"} }.join(" ") end end end diff --git a/spec/moxml/comment_spec.rb b/spec/moxml/comment_spec.rb index 87658de..a8ec933 100644 --- a/spec/moxml/comment_spec.rb +++ b/spec/moxml/comment_spec.rb @@ -26,7 +26,7 @@ describe "serialization" do it "wraps content in comment markers" do - expect(comment.to_xml(pretty: false)).to eq("") + expect(comment.to_xml(pretty: false)).to eq("") end it "escapes double hyphens" do @@ -38,7 +38,7 @@ it "handles special characters" do comment.content = "< > & \" '" - expect(comment.to_xml(pretty: false)).to eq("") + expect(comment.to_xml(pretty: false)).to eq("") end end @@ -47,7 +47,7 @@ it "adds to element" do element.add_child(comment) - expect(element.to_xml).to include("") + expect(element.to_xml).to include("") end it "removes from element" do