diff --git a/NEWS b/NEWS index 1bc6e6d8e9..f6480f75a8 100644 --- a/NEWS +++ b/NEWS @@ -453,6 +453,9 @@ sufficient information, see the ChangeLog file or Redmine [REXML] + * Upgrade to REXML 3.1.8 + https://github.com/ruby/rexml/blob/master/NEWS.md + [Improved some XPath implementations] * concat() function: Stringify all arguments before concatenating diff --git a/lib/rexml/doctype.rb b/lib/rexml/doctype.rb index ca44454dec..757b639639 100644 --- a/lib/rexml/doctype.rb +++ b/lib/rexml/doctype.rb @@ -108,13 +108,19 @@ module REXML # Ignored def write( output, indent=0, transitive=false, ie_hack=false ) f = REXML::Formatters::Default.new + c = context + if c and c[:prologue_quote] == :apostrophe + quote = "'" + else + quote = "\"" + end indent( output, indent ) output << START output << ' ' output << @name - output << " #@external_id" if @external_id - output << " #{@long_name.inspect}" if @long_name - output << " #{@uri.inspect}" if @uri + output << " #{@external_id}" if @external_id + output << " #{quote}#{@long_name}#{quote}" if @long_name + output << " #{quote}#{@uri}#{quote}" if @uri unless @children.empty? output << ' [' @children.each { |child| @@ -127,7 +133,11 @@ module REXML end def context - @parent.context + if @parent + @parent.context + else + nil + end end def entity( name ) @@ -249,9 +259,16 @@ module REXML end def to_s + c = nil + c = parent.context if parent + if c and c[:prologue_quote] == :apostrophe + quote = "'" + else + quote = "\"" + end notation = "" notation end diff --git a/lib/rexml/formatters/default.rb b/lib/rexml/formatters/default.rb index c375f1468b..811b2ff3d5 100644 --- a/lib/rexml/formatters/default.rb +++ b/lib/rexml/formatters/default.rb @@ -1,4 +1,5 @@ # frozen_string_literal: false + module REXML module Formatters class Default @@ -101,11 +102,14 @@ module REXML end def write_instruction( node, output ) - output << Instruction::START.sub(/\\/u, '') + output << Instruction::START output << node.target - output << ' ' - output << node.content - output << Instruction::STOP.sub(/\\/u, '') + content = node.content + if content + output << ' ' + output << content + end + output << Instruction::STOP end end end diff --git a/lib/rexml/functions.rb b/lib/rexml/functions.rb index 452426703c..219f9c8db5 100644 --- a/lib/rexml/functions.rb +++ b/lib/rexml/functions.rb @@ -423,7 +423,7 @@ module REXML number = number(number) begin neg = number.negative? - number = number.abs.round(half: :up) + number = number.abs.round neg ? -number : number rescue FloatDomainError number diff --git a/lib/rexml/instruction.rb b/lib/rexml/instruction.rb index 2552f3e442..318741f03b 100644 --- a/lib/rexml/instruction.rb +++ b/lib/rexml/instruction.rb @@ -1,4 +1,5 @@ # frozen_string_literal: false + require_relative "child" require_relative "source" @@ -6,8 +7,8 @@ module REXML # Represents an XML Instruction; IE, # TODO: Add parent arg (3rd arg) to constructor class Instruction < Child - START = '<\?' - STOP = '\?>' + START = "" # target is the "name" of the Instruction; IE, the "tag" in # content is everything else. @@ -17,20 +18,25 @@ module REXML # @param target can be one of a number of things. If String, then # the target of this instruction is set to this. If an Instruction, # then the Instruction is shallowly cloned (target and content are - # copied). If a Source, then the source is scanned and parsed for - # an Instruction declaration. + # copied). # @param content Must be either a String, or a Parent. Can only # be a Parent if the target argument is a Source. Otherwise, this # String is set as the content of this instruction. def initialize(target, content=nil) - if target.kind_of? String + case target + when String super() @target = target @content = content - elsif target.kind_of? Instruction + when Instruction super(content) @target = target.target @content = target.content + else + message = + "processing instruction target must be String or REXML::Instruction: " + message << "<#{target.inspect}>" + raise ArgumentError, message end @content.strip! if @content end @@ -45,11 +51,13 @@ module REXML def write writer, indent=-1, transitive=false, ie_hack=false Kernel.warn( "#{self.class.name}.write is deprecated", uplevel: 1) indent(writer, indent) - writer << START.sub(/\\/u, '') + writer << START writer << @target - writer << ' ' - writer << @content - writer << STOP.sub(/\\/u, '') + if @content + writer << ' ' + writer << @content + end + writer << STOP end # @return true if other is an Instruction, and the content and target diff --git a/lib/rexml/namespace.rb b/lib/rexml/namespace.rb index 4a7174eaeb..924edf9506 100644 --- a/lib/rexml/namespace.rb +++ b/lib/rexml/namespace.rb @@ -1,4 +1,5 @@ # frozen_string_literal: false + require_relative 'xmltokens' module REXML @@ -14,14 +15,24 @@ module REXML # Sets the name and the expanded name def name=( name ) @expanded_name = name - name =~ NAMESPLIT - if $1 - @prefix = $1 + case name + when NAMESPLIT + if $1 + @prefix = $1 + else + @prefix = "" + @namespace = "" + end + @name = $2 + when "" + @prefix = nil + @namespace = nil + @name = nil else - @prefix = "" - @namespace = "" + message = "name must be \#{PREFIX}:\#{LOCAL_NAME} or \#{LOCAL_NAME}: " + message += "<#{name.inspect}>" + raise ArgumentError, message end - @name = $2 end # Compares names optionally WITH namespaces diff --git a/lib/rexml/parsers/baseparser.rb b/lib/rexml/parsers/baseparser.rb index 4df1f57a05..de337a74d5 100644 --- a/lib/rexml/parsers/baseparser.rb +++ b/lib/rexml/parsers/baseparser.rb @@ -3,6 +3,7 @@ require_relative '../parseexception' require_relative '../undefinednamespaceexception' require_relative '../source' require 'set' +require "strscan" module REXML module Parsers @@ -32,9 +33,9 @@ module REXML COMBININGCHAR = '' # TODO EXTENDER = '' # TODO - NCNAME_STR= "[#{LETTER}_:][-[:alnum:]._:#{COMBININGCHAR}#{EXTENDER}]*" - NAME_STR= "(?:(#{NCNAME_STR}):)?(#{NCNAME_STR})" - UNAME_STR= "(?:#{NCNAME_STR}:)?#{NCNAME_STR}" + NCNAME_STR= "[#{LETTER}_][-[:alnum:]._#{COMBININGCHAR}#{EXTENDER}]*" + QNAME_STR= "(?:(#{NCNAME_STR}):)?(#{NCNAME_STR})" + QNAME = /(#{QNAME_STR})/ NAMECHAR = '[\-\w\.:]' NAME = "([\\w:]#{NAMECHAR}*)" @@ -46,7 +47,7 @@ module REXML DOCTYPE_START = /\A\s*/um DOCTYPE_PATTERN = /\s*)/um - ATTRIBUTE_PATTERN = /\s*(#{NAME_STR})\s*=\s*(["'])(.*?)\4/um + ATTRIBUTE_PATTERN = /\s*(#{QNAME_STR})\s*=\s*(["'])(.*?)\4/um COMMENT_START = /\A/um CDATA_START = /\A/um INSTRUCTION_START = /\A<\?/u - INSTRUCTION_PATTERN = /<\?(.*?)(\s+.*?)?\?>/um - TAG_MATCH = /^<((?>#{NAME_STR}))\s*((?>\s+#{UNAME_STR}\s*=\s*(["']).*?\5)*)\s*(\/)?>/um - CLOSE_MATCH = /^\s*<\/(#{NAME_STR})\s*>/um + INSTRUCTION_PATTERN = /<\?#{NAME}(\s+.*?)?\?>/um + TAG_MATCH = /^<((?>#{QNAME_STR}))/um + CLOSE_MATCH = /^\s*<\/(#{QNAME_STR})\s*>/um VERSION = /\bversion\s*=\s*["'](.*?)['"]/um ENCODING = /\bencoding\s*=\s*["'](.*?)['"]/um @@ -107,13 +108,6 @@ module REXML "apos" => [/'/, "'", "'", /'/] } - - ###################################################################### - # These are patterns to identify common markup errors, to make the - # error messages more informative. - ###################################################################### - MISSING_ATTRIBUTE_QUOTES = /^<#{NAME_STR}\s+#{NAME_STR}\s*=\s*[^"']/um - def initialize( source ) self.stream = source @listeners = [] @@ -224,7 +218,7 @@ module REXML standalone = standalone[1] unless standalone.nil? return [ :xmldecl, version, encoding, standalone ] when INSTRUCTION_START - return [ :processing_instruction, *@source.match(INSTRUCTION_PATTERN, true)[1,2] ] + return process_instruction when DOCTYPE_START md = @source.match( DOCTYPE_PATTERN, true ) @nsstack.unshift(curr_ns=Set.new) @@ -336,11 +330,12 @@ module REXML if @source.buffer[1] == ?/ @nsstack.shift last_tag = @tags.pop - #md = @source.match_to_consume( '>', CLOSE_MATCH) md = @source.match( CLOSE_MATCH, true ) - raise REXML::ParseException.new( "Missing end tag for "+ - "'#{last_tag}' (got \"#{md[1]}\")", - @source) unless last_tag == md[1] + if md.nil? or last_tag != md[1] + message = "Missing end tag for '#{last_tag}'" + message << " (got '#{md[1]}')" if md + raise REXML::ParseException.new(message, @source) + end return [ :end_element, last_tag ] elsif @source.buffer[1] == ?! md = @source.match(/\A(\s*[^>]*>)/um) @@ -362,52 +357,17 @@ module REXML raise REXML::ParseException.new( "Declarations can only occur "+ "in the doctype declaration.", @source) elsif @source.buffer[1] == ?? - md = @source.match( INSTRUCTION_PATTERN, true ) - return [ :processing_instruction, md[1], md[2] ] if md - raise REXML::ParseException.new( "Bad instruction declaration", - @source) + return process_instruction else # Get the next tag md = @source.match(TAG_MATCH, true) unless md - # Check for missing attribute quotes - raise REXML::ParseException.new("missing attribute quote", @source) if @source.match(MISSING_ATTRIBUTE_QUOTES ) raise REXML::ParseException.new("malformed XML: missing tag start", @source) end - attributes = {} prefixes = Set.new prefixes << md[2] if md[2] @nsstack.unshift(curr_ns=Set.new) - if md[4].size > 0 - attrs = md[4].scan( ATTRIBUTE_PATTERN ) - raise REXML::ParseException.new( "error parsing attributes: [#{attrs.join ', '}], excess = \"#$'\"", @source) if $' and $'.strip.size > 0 - attrs.each do |attr_name, prefix, local_part, quote, value| - if prefix == "xmlns" - if local_part == "xml" - if value != "http://www.w3.org/XML/1998/namespace" - msg = "The 'xml' prefix must not be bound to any other namespace "+ - "(http://www.w3.org/TR/REC-xml-names/#ns-decl)" - raise REXML::ParseException.new( msg, @source, self ) - end - elsif local_part == "xmlns" - msg = "The 'xmlns' prefix must not be declared "+ - "(http://www.w3.org/TR/REC-xml-names/#ns-decl)" - raise REXML::ParseException.new( msg, @source, self) - end - curr_ns << local_part - elsif prefix - prefixes << prefix unless prefix == "xml" - end - - if attributes.has_key?(attr_name) - msg = "Duplicate attribute #{attr_name.inspect}" - raise REXML::ParseException.new(msg, @source, self) - end - - attributes[attr_name] = value - end - end - + attributes, closed = parse_attributes(prefixes, curr_ns) # Verify that all of the prefixes have been defined for prefix in prefixes unless @nsstack.find{|k| k.member?(prefix)} @@ -415,7 +375,7 @@ module REXML end end - if md[6] + if closed @closed = md[1] @nsstack.shift else @@ -438,7 +398,7 @@ module REXML raise rescue REXML::ParseException raise - rescue Exception, NameError => error + rescue => error raise REXML::ParseException.new( "Exception parsing", @source, self, (error ? error : $!) ) end @@ -508,6 +468,99 @@ module REXML return false if /\AUTF-16\z/i =~ xml_declaration_encoding true end + + def process_instruction + match_data = @source.match(INSTRUCTION_PATTERN, true) + unless match_data + message = "Invalid processing instruction node" + raise REXML::ParseException.new(message, @source) + end + [:processing_instruction, match_data[1], match_data[2]] + end + + def parse_attributes(prefixes, curr_ns) + attributes = {} + closed = false + match_data = @source.match(/^(.*?)(\/)?>/um, true) + if match_data.nil? + message = "Start tag isn't ended" + raise REXML::ParseException.new(message, @source) + end + + raw_attributes = match_data[1] + closed = !match_data[2].nil? + return attributes, closed if raw_attributes.nil? + return attributes, closed if raw_attributes.empty? + + scanner = StringScanner.new(raw_attributes) + until scanner.eos? + if scanner.scan(/\s+/) + break if scanner.eos? + end + + pos = scanner.pos + loop do + break if scanner.scan(ATTRIBUTE_PATTERN) + unless scanner.scan(QNAME) + message = "Invalid attribute name: <#{scanner.rest}>" + raise REXML::ParseException.new(message, @source) + end + name = scanner[0] + unless scanner.scan(/\s*=\s*/um) + message = "Missing attribute equal: <#{name}>" + raise REXML::ParseException.new(message, @source) + end + quote = scanner.scan(/['"]/) + unless quote + message = "Missing attribute value start quote: <#{name}>" + raise REXML::ParseException.new(message, @source) + end + unless scanner.scan(/.*#{Regexp.escape(quote)}/um) + match_data = @source.match(/^(.*?)(\/)?>/um, true) + if match_data + scanner << "/" if closed + scanner << ">" + scanner << match_data[1] + scanner.pos = pos + closed = !match_data[2].nil? + next + end + message = + "Missing attribute value end quote: <#{name}>: <#{quote}>" + raise REXML::ParseException.new(message, @source) + end + end + name = scanner[1] + prefix = scanner[2] + local_part = scanner[3] + # quote = scanner[4] + value = scanner[5] + if prefix == "xmlns" + if local_part == "xml" + if value != "http://www.w3.org/XML/1998/namespace" + msg = "The 'xml' prefix must not be bound to any other namespace "+ + "(http://www.w3.org/TR/REC-xml-names/#ns-decl)" + raise REXML::ParseException.new( msg, @source, self ) + end + elsif local_part == "xmlns" + msg = "The 'xmlns' prefix must not be declared "+ + "(http://www.w3.org/TR/REC-xml-names/#ns-decl)" + raise REXML::ParseException.new( msg, @source, self) + end + curr_ns << local_part + elsif prefix + prefixes << prefix unless prefix == "xml" + end + + if attributes.has_key?(name) + msg = "Duplicate attribute #{name.inspect}" + raise REXML::ParseException.new(msg, @source, self) + end + + attributes[name] = value + end + return attributes, closed + end end end end diff --git a/lib/rexml/rexml.gemspec b/lib/rexml/rexml.gemspec index f9acda0698..8362ca3c3a 100644 --- a/lib/rexml/rexml.gemspec +++ b/lib/rexml/rexml.gemspec @@ -16,23 +16,65 @@ Gem::Specification.new do |spec| spec.homepage = "https://github.com/ruby/rexml" spec.license = "BSD-2-Clause" - spec.files = [".gitignore", ".travis.yml", "Gemfile", "LICENSE.txt", "README.md", "Rakefile", - "bin/console", "bin/setup", "lib/rexml/attlistdecl.rb", "lib/rexml/attribute.rb", "lib/rexml/cdata.rb", - "lib/rexml/child.rb", "lib/rexml/comment.rb", "lib/rexml/doctype.rb", "lib/rexml/document.rb", - "lib/rexml/dtd/attlistdecl.rb", "lib/rexml/dtd/dtd.rb", "lib/rexml/dtd/elementdecl.rb", - "lib/rexml/dtd/entitydecl.rb", "lib/rexml/dtd/notationdecl.rb", "lib/rexml/element.rb", - "lib/rexml/encoding.rb", "lib/rexml/entity.rb", "lib/rexml/formatters/default.rb", - "lib/rexml/formatters/pretty.rb", "lib/rexml/formatters/transitive.rb", "lib/rexml/functions.rb", - "lib/rexml/instruction.rb", "lib/rexml/light/node.rb", "lib/rexml/namespace.rb", "lib/rexml/node.rb", - "lib/rexml/output.rb", "lib/rexml/parent.rb", "lib/rexml/parseexception.rb", "lib/rexml/parsers/baseparser.rb", - "lib/rexml/parsers/lightparser.rb", "lib/rexml/parsers/pullparser.rb", "lib/rexml/parsers/sax2parser.rb", - "lib/rexml/parsers/streamparser.rb", "lib/rexml/parsers/treeparser.rb", - "lib/rexml/parsers/ultralightparser.rb", "lib/rexml/parsers/xpathparser.rb", "lib/rexml/quickpath.rb", - "lib/rexml/rexml.rb", "lib/rexml/sax2listener.rb", "lib/rexml/security.rb", "lib/rexml/source.rb", - "lib/rexml/streamlistener.rb", "lib/rexml/syncenumerator.rb", "lib/rexml/text.rb", - "lib/rexml/undefinednamespaceexception.rb", "lib/rexml/validation/relaxng.rb", - "lib/rexml/validation/validation.rb", "lib/rexml/validation/validationexception.rb", "lib/rexml/xmldecl.rb", - "lib/rexml/xmltokens.rb", "lib/rexml/xpath.rb", "lib/rexml/xpath_parser.rb", "rexml.gemspec"] + spec.files = [ + ".gitignore", + ".travis.yml", + "Gemfile", + "LICENSE.txt", + "README.md", + "Rakefile", + "lib/rexml/attlistdecl.rb", + "lib/rexml/attribute.rb", + "lib/rexml/cdata.rb", + "lib/rexml/child.rb", + "lib/rexml/comment.rb", + "lib/rexml/doctype.rb", + "lib/rexml/document.rb", + "lib/rexml/dtd/attlistdecl.rb", + "lib/rexml/dtd/dtd.rb", + "lib/rexml/dtd/elementdecl.rb", + "lib/rexml/dtd/entitydecl.rb", + "lib/rexml/dtd/notationdecl.rb", + "lib/rexml/element.rb", + "lib/rexml/encoding.rb", + "lib/rexml/entity.rb", + "lib/rexml/formatters/default.rb", + "lib/rexml/formatters/pretty.rb", + "lib/rexml/formatters/transitive.rb", + "lib/rexml/functions.rb", + "lib/rexml/instruction.rb", + "lib/rexml/light/node.rb", + "lib/rexml/namespace.rb", + "lib/rexml/node.rb", + "lib/rexml/output.rb", + "lib/rexml/parent.rb", + "lib/rexml/parseexception.rb", + "lib/rexml/parsers/baseparser.rb", + "lib/rexml/parsers/lightparser.rb", + "lib/rexml/parsers/pullparser.rb", + "lib/rexml/parsers/sax2parser.rb", + "lib/rexml/parsers/streamparser.rb", + "lib/rexml/parsers/treeparser.rb", + "lib/rexml/parsers/ultralightparser.rb", + "lib/rexml/parsers/xpathparser.rb", + "lib/rexml/quickpath.rb", + "lib/rexml/rexml.rb", + "lib/rexml/sax2listener.rb", + "lib/rexml/security.rb", + "lib/rexml/source.rb", + "lib/rexml/streamlistener.rb", + "lib/rexml/syncenumerator.rb", + "lib/rexml/text.rb", + "lib/rexml/undefinednamespaceexception.rb", + "lib/rexml/validation/relaxng.rb", + "lib/rexml/validation/validation.rb", + "lib/rexml/validation/validationexception.rb", + "lib/rexml/xmldecl.rb", + "lib/rexml/xmltokens.rb", + "lib/rexml/xpath.rb", + "lib/rexml/xpath_parser.rb", + "rexml.gemspec", + ] spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] diff --git a/lib/rexml/rexml.rb b/lib/rexml/rexml.rb index fbc0d339d8..654f54799f 100644 --- a/lib/rexml/rexml.rb +++ b/lib/rexml/rexml.rb @@ -1,4 +1,4 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- # frozen_string_literal: false # REXML is an XML toolkit for Ruby[http://www.ruby-lang.org], in Ruby. # @@ -24,8 +24,8 @@ module REXML COPYRIGHT = "Copyright © 2001-2008 Sean Russell " DATE = "2008/019" - VERSION = "3.1.7.3" - REVISION = %w$Revision$[1] || '' + VERSION = "3.1.8" + REVISION = "" Copyright = COPYRIGHT Version = VERSION diff --git a/lib/rexml/source.rb b/lib/rexml/source.rb index 8663e489a8..770aefc818 100644 --- a/lib/rexml/source.rb +++ b/lib/rexml/source.rb @@ -254,6 +254,7 @@ module REXML end rescue end + @er_source.seek(pos) rescue IOError pos = -1 line = -1 diff --git a/lib/rexml/text.rb b/lib/rexml/text.rb index 208febf5ee..6139caecd7 100644 --- a/lib/rexml/text.rb +++ b/lib/rexml/text.rb @@ -96,27 +96,28 @@ module REXML @raw = false @parent = nil + @entity_filter = nil if parent super( parent ) @raw = parent.raw end - @raw = raw unless raw.nil? - @entity_filter = entity_filter - clear_cache - if arg.kind_of? String @string = arg.dup - @string.squeeze!(" \n\t") unless respect_whitespace elsif arg.kind_of? Text - @string = arg.to_s + @string = arg.instance_variable_get(:@string).dup @raw = arg.raw + @entity_filter = arg.instance_variable_get(:@entity_filter) elsif raise "Illegal argument of type #{arg.type} for Text constructor (#{arg})" end - @string.gsub!( /\r\n?/, "\n" ) + @string.squeeze!(" \n\t") unless respect_whitespace + @string.gsub!(/\r\n?/, "\n") + @raw = raw unless raw.nil? + @entity_filter = entity_filter if entity_filter + clear_cache Text.check(@string, illegal, doctype) if @raw end @@ -181,7 +182,7 @@ module REXML def clone - return Text.new(self) + return Text.new(self, true) end @@ -226,9 +227,7 @@ module REXML # u.to_s #-> "sean russell" def to_s return @string if @raw - return @normalized if @normalized - - @normalized = Text::normalize( @string, doctype, @entity_filter ) + @normalized ||= Text::normalize( @string, doctype, @entity_filter ) end def inspect @@ -249,8 +248,7 @@ module REXML # u = Text.new( "sean russell", false, nil, true ) # u.value #-> "sean russell" def value - return @unnormalized if @unnormalized - @unnormalized = Text::unnormalize( @string, doctype ) + @unnormalized ||= Text::unnormalize( @string, doctype ) end # Sets the contents of this text node. This expects the text to be @@ -266,16 +264,16 @@ module REXML @raw = false end - def wrap(string, width, addnewline=false) - # Recursively wrap string at width. - return string if string.length <= width - place = string.rindex(' ', width) # Position in string with last ' ' before cutoff - if addnewline then - return "\n" + string[0,place] + "\n" + wrap(string[place+1..-1], width) - else - return string[0,place] + "\n" + wrap(string[place+1..-1], width) - end - end + def wrap(string, width, addnewline=false) + # Recursively wrap string at width. + return string if string.length <= width + place = string.rindex(' ', width) # Position in string with last ' ' before cutoff + if addnewline then + return "\n" + string[0,place] + "\n" + wrap(string[place+1..-1], width) + else + return string[0,place] + "\n" + wrap(string[place+1..-1], width) + end + end def indent_text(string, level=1, style="\t", indentfirstline=true) return string if level < 0 diff --git a/lib/rexml/xmldecl.rb b/lib/rexml/xmldecl.rb index d02204931c..89c0747d49 100644 --- a/lib/rexml/xmldecl.rb +++ b/lib/rexml/xmldecl.rb @@ -1,4 +1,5 @@ # frozen_string_literal: false + require_relative 'encoding' require_relative 'source' @@ -7,11 +8,11 @@ module REXML class XMLDecl < Child include Encoding - DEFAULT_VERSION = "1.0"; - DEFAULT_ENCODING = "UTF-8"; - DEFAULT_STANDALONE = "no"; - START = '<\?xml'; - STOP = '\?>'; + DEFAULT_VERSION = "1.0" + DEFAULT_ENCODING = "UTF-8" + DEFAULT_STANDALONE = "no" + START = "" attr_accessor :version, :standalone attr_reader :writeencoding, :writethis @@ -46,9 +47,9 @@ module REXML # Ignored def write(writer, indent=-1, transitive=false, ie_hack=false) return nil unless @writethis or writer.kind_of? Output - writer << START.sub(/\\/u, '') + writer << START writer << " #{content encoding}" - writer << STOP.sub(/\\/u, '') + writer << STOP end def ==( other ) @@ -102,14 +103,26 @@ module REXML end def inspect - START.sub(/\\/u, '') + " ... " + STOP.sub(/\\/u, '') + "#{START} ... #{STOP}" end private def content(enc) - rv = "version='#@version'" - rv << " encoding='#{enc}'" if @writeencoding || enc !~ /\Autf-8\z/i - rv << " standalone='#@standalone'" if @standalone + context = nil + context = parent.context if parent + if context and context[:prologue_quote] == :quote + quote = "\"" + else + quote = "'" + end + + rv = "version=#{quote}#{@version}#{quote}" + if @writeencoding or enc !~ /\Autf-8\z/i + rv << " encoding=#{quote}#{enc}#{quote}" + end + if @standalone + rv << " standalone=#{quote}#{@standalone}#{quote}" + end rv end end diff --git a/spec/ruby/library/rexml/attribute/inspect_spec.rb b/spec/ruby/library/rexml/attribute/inspect_spec.rb index 86a437ec74..632b477cca 100644 --- a/spec/ruby/library/rexml/attribute/inspect_spec.rb +++ b/spec/ruby/library/rexml/attribute/inspect_spec.rb @@ -13,7 +13,7 @@ describe "REXML::Attribute#inspect" do end it "does not escape text" do - a = REXML::Attribute.new("&&", "<>") - a.inspect.should == "&&='<>'" + a = REXML::Attribute.new("name", "<>") + a.inspect.should == "name='<>'" end end diff --git a/test/rexml/data/t75.xml b/test/rexml/data/t75.xml index 0911fb1b1a..eb3cccee4b 100644 --- a/test/rexml/data/t75.xml +++ b/test/rexml/data/t75.xml @@ -1,4 +1,4 @@ - + ", format(instruction)) + end + end + end +end diff --git a/test/rexml/parse/test_element.rb b/test/rexml/parse/test_element.rb new file mode 100644 index 0000000000..aad915fe7b --- /dev/null +++ b/test/rexml/parse/test_element.rb @@ -0,0 +1,38 @@ +require "test/unit" +require "rexml/document" + +module REXMLTests + class TestParseElement < Test::Unit::TestCase + def parse(xml) + REXML::Document.new(xml) + end + + class TestInvalid < self + def test_no_end_tag + exception = assert_raise(REXML::ParseException) do + parse("") + end + assert_equal(<<-DETAIL.chomp, exception.to_s) +Invalid attribute name: <:a=""> +Line: 1 +Position: 9 +Last 80 unconsumed characters: + + DETAIL + end + end + end +end diff --git a/test/rexml/parse/test_processing_instruction.rb b/test/rexml/parse/test_processing_instruction.rb new file mode 100644 index 0000000000..a23513fc6e --- /dev/null +++ b/test/rexml/parse/test_processing_instruction.rb @@ -0,0 +1,25 @@ +require "test/unit" +require "rexml/document" + +module REXMLTests + class TestParseProcessinInstruction < Test::Unit::TestCase + def parse(xml) + REXML::Document.new(xml) + end + + class TestInvalid < self + def test_no_name + exception = assert_raise(REXML::ParseException) do + parse("") + end + assert_equal(<<-DETAIL.chomp, exception.to_s) +Invalid processing instruction node +Line: 1 +Position: 4 +Last 80 unconsumed characters: + + DETAIL + end + end + end +end diff --git a/test/rexml/parser/test_tree.rb b/test/rexml/parser/test_tree.rb index 7ab0addca1..8a5d9d1223 100644 --- a/test/rexml/parser/test_tree.rb +++ b/test/rexml/parser/test_tree.rb @@ -12,7 +12,7 @@ class TestTreeParser < Test::Unit::TestCase parse(xml) end assert_equal(<<-MESSAGE, exception.to_s) -Missing end tag for 'root' (got "not-root") +Missing end tag for 'root' (got 'not-root') Line: 1 Position: #{xml.bytesize} Last 80 unconsumed characters: diff --git a/test/rexml/parser/test_ultra_light.rb b/test/rexml/parser/test_ultra_light.rb index c48a13d311..8f4a3980d5 100644 --- a/test/rexml/parser/test_ultra_light.rb +++ b/test/rexml/parser/test_ultra_light.rb @@ -55,7 +55,7 @@ class TestUltraLightParser < Test::Unit::TestCase normalized_doctype[1] = normalized_parent normalized_doctype when :start_element - tag, parent, name, attributes, *children = child + tag, _parent, name, attributes, *children = child normalized_parent = :parent normalized_children = children.collect do |sub_child| normalize_child(sub_child) diff --git a/test/rexml/test_attribute.rb b/test/rexml/test_attribute.rb new file mode 100644 index 0000000000..5175bd4454 --- /dev/null +++ b/test/rexml/test_attribute.rb @@ -0,0 +1,14 @@ +require_relative "rexml_test_utils" + +module REXMLTests + class AttributeTest < Test::Unit::TestCase + def test_empty_prefix + error = assert_raise(ArgumentError) do + REXML::Attribute.new(":x") + end + assert_equal("name must be " + + "\#{PREFIX}:\#{LOCAL_NAME} or \#{LOCAL_NAME}: <\":x\">", + error.message) + end + end +end diff --git a/test/rexml/test_core.rb b/test/rexml/test_core.rb index b2e5299f39..46036d7f12 100644 --- a/test/rexml/test_core.rb +++ b/test/rexml/test_core.rb @@ -1274,14 +1274,15 @@ EOL def test_ticket_21 src = "" - assert_raise( ParseException, "invalid XML should be caught" ) { + exception = assert_raise(ParseException) do Document.new(src) - } - begin - Document.new(src) - rescue - assert_match( /missing attribute quote/, $!.message ) end + assert_equal(<<-DETAIL, exception.to_s) +Missing attribute value start quote: +Line: 1 +Position: 16 +Last 80 unconsumed characters: + DETAIL end def test_ticket_63 diff --git a/test/rexml/test_doctype.rb b/test/rexml/test_doctype.rb index 91de05b05f..7f42669170 100644 --- a/test/rexml/test_doctype.rb +++ b/test/rexml/test_doctype.rb @@ -1,68 +1,92 @@ # frozen_string_literal: false -require 'test/unit' -require 'rexml/document' + +require_relative "rexml_test_utils" module REXMLTests class TestDocTypeAccessor < Test::Unit::TestCase - def setup @sysid = "urn:x-test:sysid1" - @notid1 = "urn:x-test:notation1" - @notid2 = "urn:x-test:notation2" - document_string1 = <<-"XMLEND" - - + @notation_id1 = "urn:x-test:notation1" + @notation_id2 = "urn:x-test:notation2" + xml_system = <<-XML + + ]> - - XMLEND - @doctype1 = REXML::Document.new(document_string1).doctype + + XML + @doc_type_system = REXML::Document.new(xml_system).doctype @pubid = "TEST_ID" - document_string2 = <<-"XMLEND" - - - XMLEND - @doctype2 = REXML::Document.new(document_string2).doctype - - document_string3 = <<-"XMLEND" - - - XMLEND - @doctype3 = REXML::Document.new(document_string3).doctype + xml_public = <<-XML + + + XML + @doc_type_public = REXML::Document.new(xml_public).doctype + xml_public_system = <<-XML + + + XML + @doc_type_public_system = REXML::Document.new(xml_public_system).doctype end def test_public - assert_equal(nil, @doctype1.public) - assert_equal(@pubid, @doctype2.public) - assert_equal(@pubid, @doctype3.public) + assert_equal([ + nil, + @pubid, + @pubid, + ], + [ + @doc_type_system.public, + @doc_type_public.public, + @doc_type_public_system.public, + ]) + end + + def test_to_s + assert_equal("", + @doc_type_public_system.to_s) + end + + def test_to_s_apostrophe + @doc_type_public_system.parent.context[:prologue_quote] = :apostrophe + assert_equal("", + @doc_type_public_system.to_s) end def test_system - assert_equal(@sysid, @doctype1.system) - assert_equal(nil, @doctype2.system) - assert_equal(@sysid, @doctype3.system) + assert_equal([ + @sysid, + nil, + @sysid, + ], + [ + @doc_type_system.system, + @doc_type_public.system, + @doc_type_public_system.system, + ]) end def test_notation - assert_equal(@notid1, @doctype1.notation("n1").system) - assert_equal(@notid2, @doctype1.notation("n2").system) + assert_equal([ + @notation_id1, + @notation_id2, + ], + [ + @doc_type_system.notation("n1").system, + @doc_type_system.notation("n2").system, + ]) end def test_notations - notations = @doctype1.notations - assert_equal(2, notations.length) - assert_equal(@notid1, find_notation(notations, "n1").system) - assert_equal(@notid2, find_notation(notations, "n2").system) + notations = @doc_type_system.notations + assert_equal([ + @notation_id1, + @notation_id2, + ], + notations.collect(&:system)) end - - def find_notation(notations, name) - notations.find { |notation| - name == notation.name - } - end - end class TestNotationDeclPublic < Test::Unit::TestCase @@ -82,6 +106,19 @@ module REXMLTests decl(@id, @uri).to_s) end + def test_to_s_apostrophe + document = REXML::Document.new(<<-XML) + + + XML + document.context[:prologue_quote] = :apostrophe + notation = document.doctype.notations[0] + assert_equal("", + notation.to_s) + end + private def decl(id, uri) REXML::NotationDecl.new(@name, "PUBLIC", id, uri) @@ -99,6 +136,19 @@ module REXMLTests decl(@id).to_s) end + def test_to_s_apostrophe + document = REXML::Document.new(<<-XML) + + + XML + document.context[:prologue_quote] = :apostrophe + notation = document.doctype.notations[0] + assert_equal("", + notation.to_s) + end + private def decl(id) REXML::NotationDecl.new(@name, "SYSTEM", id, nil) diff --git a/test/rexml/test_instruction.rb b/test/rexml/test_instruction.rb new file mode 100644 index 0000000000..96fa909e17 --- /dev/null +++ b/test/rexml/test_instruction.rb @@ -0,0 +1,14 @@ +require_relative "rexml_test_utils" + +module REXMLTests + class InstructionTest < Test::Unit::TestCase + def test_target_nil + error = assert_raise(ArgumentError) do + REXML::Instruction.new(nil) + end + assert_equal("processing instruction target must be String or " + + "REXML::Instruction: ", + error.message) + end + end +end diff --git a/test/rexml/test_stream.rb b/test/rexml/test_stream.rb index d7ceedc70e..08d4462ef9 100644 --- a/test/rexml/test_stream.rb +++ b/test/rexml/test_stream.rb @@ -15,8 +15,8 @@ module REXMLTests def test_listener data = %Q{\n} - b = RequestReader.new( data ) - b = RequestReader.new( data ) + RequestReader.new( data ) + RequestReader.new( data ) end def test_ticket_49 diff --git a/test/rexml/test_text.rb b/test/rexml/test_text.rb index 3f8036eee3..a27701fce5 100644 --- a/test/rexml/test_text.rb +++ b/test/rexml/test_text.rb @@ -5,6 +5,52 @@ module REXMLTests class TextTester < Test::Unit::TestCase include REXML + def test_new_text_response_whitespace_default + text = Text.new("a b\t\tc", true) + assert_equal("a b\tc", Text.new(text).to_s) + end + + def test_new_text_response_whitespace_true + text = Text.new("a b\t\tc", true) + assert_equal("a b\t\tc", Text.new(text, true).to_s) + end + + def test_new_text_raw_default + text = Text.new("&lt;", false, nil, true) + assert_equal("&lt;", Text.new(text).to_s) + end + + def test_new_text_raw_false + text = Text.new("&lt;", false, nil, true) + assert_equal("&amp;lt;", Text.new(text, false, nil, false).to_s) + end + + def test_new_text_entity_filter_default + document = REXML::Document.new(<<-XML) + + +]> + + XML + text = Text.new("aaa bbb", false, document.root, nil, ["a"]) + assert_equal("aaa &b;", + Text.new(text, false, document.root).to_s) + end + + def test_new_text_entity_filter_custom + document = REXML::Document.new(<<-XML) + + +]> + + XML + text = Text.new("aaa bbb", false, document.root, nil, ["a"]) + assert_equal("&a; bbb", + Text.new(text, false, document.root, nil, ["b"]).to_s) + end + def test_shift_operator_chain text = Text.new("original\r\n") text << "append1\r\n" << "append2\r\n" @@ -18,5 +64,11 @@ module REXMLTests text << "append3\r\n" << "append4\r\n" assert_equal("original\nappend1\nappend2\nappend3\nappend4\n", text.to_s) end + + def test_clone + text = Text.new("&lt; <") + assert_equal(text.to_s, + text.clone.to_s) + end end end diff --git a/test/rexml/test_xml_declaration.rb b/test/rexml/test_xml_declaration.rb index a4d97c41d0..1d5a6d312f 100644 --- a/test/rexml/test_xml_declaration.rb +++ b/test/rexml/test_xml_declaration.rb @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # frozen_string_literal: false # # Created by Henrik Mårtensson on 2007-02-18. @@ -10,11 +9,11 @@ require "test/unit" module REXMLTests class TestXmlDeclaration < Test::Unit::TestCase def setup - xml = <<-'END_XML' + xml = <<-XML - END_XML + XML @doc = REXML::Document.new xml @root = @doc.root @xml_declaration = @doc.children[0] @@ -32,5 +31,12 @@ module REXMLTests assert_kind_of(REXML::XMLDecl, @root.previous_sibling.previous_sibling) assert_kind_of(REXML::Element, @xml_declaration.next_sibling.next_sibling) end + + def test_write_prologue_quote + @doc.context[:prologue_quote] = :quote + assert_equal("", + @xml_declaration.to_s) + end end end