diff --git a/lib/rexml/cdata.rb b/lib/rexml/cdata.rb index 264ad642..d7a55cb0 100644 --- a/lib/rexml/cdata.rb +++ b/lib/rexml/cdata.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative "text" module REXML diff --git a/lib/rexml/comment.rb b/lib/rexml/comment.rb index e7e104d4..62aff77a 100644 --- a/lib/rexml/comment.rb +++ b/lib/rexml/comment.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative "child" module REXML diff --git a/lib/rexml/doctype.rb b/lib/rexml/doctype.rb index a52ac057..332dc91d 100644 --- a/lib/rexml/doctype.rb +++ b/lib/rexml/doctype.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative "parent" require_relative "parseexception" require_relative "namespace" @@ -59,7 +59,7 @@ class DocType < Parent 'lt'=>EntityConst::LT, 'quot'=>EntityConst::QUOT, "apos"=>EntityConst::APOS - } + }.freeze # name is the name of the doctype # external_id is the referenced DTD, if given @@ -183,7 +183,7 @@ def entity( name, expanding: nil ) def add child super(child) - @entities = DEFAULT_ENTITIES.clone if @entities == DEFAULT_ENTITIES + @entities = DEFAULT_ENTITIES.dup if @entities == DEFAULT_ENTITIES @entities[ child.name ] = child if child.kind_of? Entity end @@ -288,7 +288,7 @@ def initialize name, middle, pub, sys def to_s context = parent&.context - notation = "" diff --git a/lib/rexml/element.rb b/lib/rexml/element.rb index 7ce03df6..bb0b922f 100644 --- a/lib/rexml/element.rb +++ b/lib/rexml/element.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative "parent" require_relative "namespace" require_relative "attribute" @@ -356,7 +356,7 @@ def initialize( arg = UNDEFINED, parent=nil, context=nil ) # e.inspect # => " ... " # def inspect - rv = "<#@expanded_name" + rv = +"<#@expanded_name" @attributes.each_attribute do |attr| rv << " " @@ -1524,7 +1524,7 @@ def _namespace_internal(namespaces = _calculate_namespaces) # :nodoc: def _namespace_lookup_internal(prefix, namespaces = _calculate_namespaces) # :nodoc: prefix = (prefix == '') ? 'xmlns' : prefix.delete_prefix("xmlns:") ns = namespaces[prefix] - ns = '' if ns.nil? and prefix == 'xmlns' + ns = +'' if ns.nil? and prefix == 'xmlns' ns end diff --git a/lib/rexml/entity.rb b/lib/rexml/entity.rb index f55088fc..b939253b 100644 --- a/lib/rexml/entity.rb +++ b/lib/rexml/entity.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require 'set' require_relative 'child' require_relative 'parseexception' @@ -127,7 +127,7 @@ def write out, indent=-1 # Returns this entity as a string. See write(). def to_s - rv = '' + rv = +'' write rv rv end @@ -138,14 +138,14 @@ def to_s # CAUTION: these entities does not have parent and document module EntityConst # +>+ - GT = Entity.new( 'gt', '>' ) + GT = Entity.new( 'gt', '>' ).freeze # +<+ - LT = Entity.new( 'lt', '<' ) + LT = Entity.new( 'lt', '<' ).freeze # +&+ - AMP = Entity.new( 'amp', '&' ) + AMP = Entity.new( 'amp', '&' ).freeze # +"+ - QUOT = Entity.new( 'quot', '"' ) + QUOT = Entity.new( 'quot', '"' ).freeze # +'+ - APOS = Entity.new( 'apos', "'" ) + APOS = Entity.new( 'apos', "'" ).freeze end end diff --git a/lib/rexml/functions.rb b/lib/rexml/functions.rb index f84b846b..5ebd9456 100644 --- a/lib/rexml/functions.rb +++ b/lib/rexml/functions.rb @@ -8,8 +8,6 @@ module REXML # Therefore, in XML, "local-name()" is identical (and actually becomes) # "local_name()" class FunctionsClass # :nodoc: - @@available_functions = {} - def initialize @context = nil @namespace_context = {} @@ -26,14 +24,7 @@ def initialize :send, :compare_language, :string_value, - ] - class << self - def method_added(name) - unless INTERNAL_METHODS.include?(name) - @@available_functions[name] = true - end - end - end + ].freeze def namespace_context=(x) ; @namespace_context=x ; end def variables=(x) ; @variables=x ; end @@ -415,7 +406,9 @@ def round( number ) end def send(name, *args) - if @@available_functions[name.to_sym] + name = name.to_sym + if self.class.method_defined?(name, false) and + !INTERNAL_METHODS.include?(name) super else # TODO: Maybe, this is not XPath spec behavior. diff --git a/lib/rexml/instruction.rb b/lib/rexml/instruction.rb index a3dfbbec..1beb10b6 100644 --- a/lib/rexml/instruction.rb +++ b/lib/rexml/instruction.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative "child" require_relative "source" @@ -34,8 +34,8 @@ def initialize(target, content=nil) @content = target.content else message = - "processing instruction target must be String or REXML::Instruction: " - message << "<#{target.inspect}>" + "processing instruction target must be String or REXML::Instruction: " \ + "<#{target.inspect}>" raise ArgumentError, message end @content.strip! if @content diff --git a/lib/rexml/parsers/baseparser.rb b/lib/rexml/parsers/baseparser.rb index af8887e2..5498b711 100644 --- a/lib/rexml/parsers/baseparser.rb +++ b/lib/rexml/parsers/baseparser.rb @@ -134,11 +134,11 @@ class BaseParser EREFERENCE = /&(?!#{NAME};)/ DEFAULT_ENTITIES = { - 'gt' => [/>/, '>', '>', />/], - 'lt' => [/</, '<', '<', / [/"/, '"', '"', /"/], - "apos" => [/'/, "'", "'", /'/] - } + 'gt' => [/>/, '>', '>', />/].freeze, + 'lt' => [/</, '<', '<', / [/"/, '"', '"', /"/].freeze, + "apos" => [/'/, "'", "'", /'/].freeze + }.freeze module Private PEREFERENCE_PATTERN = /#{PEREFERENCE}/um @@ -157,6 +157,7 @@ module Private default_entities.each do |term| DEFAULT_ENTITIES_PATTERNS[term] = /&#{term};/ end + DEFAULT_ENTITIES_PATTERNS.freeze XML_PREFIXED_NAMESPACE = "http://www.w3.org/XML/1998/namespace" EXTERNAL_ID_PUBLIC_PATTERN = /\s+#{PUBIDLITERAL}\s+#{SYSTEMLITERAL}/um EXTERNAL_ID_SYSTEM_PATTERN = /\s+#{SYSTEMLITERAL}/um diff --git a/lib/rexml/security.rb b/lib/rexml/security.rb index e8e8c6b4..486be198 100644 --- a/lib/rexml/security.rb +++ b/lib/rexml/security.rb @@ -1,28 +1,28 @@ # frozen_string_literal: false module REXML module Security - @@entity_expansion_limit = 10_000 + @entity_expansion_limit = 10_000 # Set the entity expansion limit. By default the limit is set to 10000. def self.entity_expansion_limit=( val ) - @@entity_expansion_limit = val + @entity_expansion_limit = val end # Get the entity expansion limit. By default the limit is set to 10000. def self.entity_expansion_limit - @@entity_expansion_limit + @entity_expansion_limit end - @@entity_expansion_text_limit = 10_240 + @entity_expansion_text_limit = 10_240 # Set the entity expansion limit. By default the limit is set to 10240. def self.entity_expansion_text_limit=( val ) - @@entity_expansion_text_limit = val + @entity_expansion_text_limit = val end # Get the entity expansion limit. By default the limit is set to 10240. def self.entity_expansion_text_limit - @@entity_expansion_text_limit + @entity_expansion_text_limit end end end diff --git a/lib/rexml/source.rb b/lib/rexml/source.rb index 11ca5aad..06d23acd 100644 --- a/lib/rexml/source.rb +++ b/lib/rexml/source.rb @@ -1,5 +1,5 @@ # coding: US-ASCII -# frozen_string_literal: false +# frozen_string_literal: true require "stringio" require "strscan" @@ -78,6 +78,7 @@ module Private PRE_DEFINED_TERM_PATTERNS[term] = term end end + PRE_DEFINED_TERM_PATTERNS.freeze end private_constant :Private @@ -227,9 +228,9 @@ def initialize(arg, block_size=500, encoding=nil) @pending_buffer = nil if encoding - super("", encoding) + super(+"", encoding) else - super(@source.read(3) || "") + super(@source.read(3) || +"") end if !@to_utf and @@ -380,7 +381,7 @@ def encoding_updated @source.set_encoding(@encoding, @encoding) end @line_break = encode(">") - @pending_buffer, @scanner.string = @scanner.rest, "" + @pending_buffer, @scanner.string = @scanner.rest, +"" @pending_buffer.force_encoding(@encoding) super end diff --git a/lib/rexml/text.rb b/lib/rexml/text.rb index 2319cef8..193db27f 100644 --- a/lib/rexml/text.rb +++ b/lib/rexml/text.rb @@ -11,11 +11,11 @@ module REXML class Text < Child include Comparable # The order in which the substitutions occur - SPECIALS = [ /&(?!#?[\w-]+;)/u, //u, /"/u, /'/u, /\r/u ] - SUBSTITUTES = ['&', '<', '>', '"', ''', ' '] + SPECIALS = [ /&(?!#?[\w-]+;)/u, //u, /"/u, /'/u, /\r/u ].freeze + SUBSTITUTES = ['&', '<', '>', '"', ''', ' '].freeze # Characters which are substituted in written strings - SLAICEPS = [ '<', '>', '"', "'", '&' ] - SETUTITSBUS = [ /</u, />/u, /"/u, /'/u, /&/u ] + SLAICEPS = [ '<', '>', '"', "'", '&' ].freeze + SETUTITSBUS = [ /</u, />/u, /"/u, /'/u, /&/u ].freeze # If +raw+ is true, then REXML leaves the value alone attr_accessor :raw @@ -27,7 +27,7 @@ class Text < Child (0x20..0xD7FF), (0xE000..0xFFFD), (0x10000..0x10FFFF) - ] + ].freeze VALID_XML_CHARS = Regexp.new('^['+ VALID_CHAR.map { |item| @@ -38,7 +38,7 @@ class Text < Child [item.first, '-'.ord, item.last].pack('UUU').force_encoding('utf-8') end }.join + - ']*$') + ']*$').freeze # Constructor # +arg+ if a String, the content is set to the String. If a Text, diff --git a/lib/rexml/xmldecl.rb b/lib/rexml/xmldecl.rb index d19407ce..a3cfb351 100644 --- a/lib/rexml/xmldecl.rb +++ b/lib/rexml/xmldecl.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative 'encoding' require_relative 'source' @@ -117,7 +117,7 @@ def content(enc) quote = "'" end - rv = "version=#{quote}#{@version}#{quote}" + rv = +"version=#{quote}#{@version}#{quote}" if @writeencoding or enc !~ /\Autf-8\z/i rv << " encoding=#{quote}#{enc}#{quote}" end diff --git a/lib/rexml/xmltokens.rb b/lib/rexml/xmltokens.rb index 392b47b1..b74ad048 100644 --- a/lib/rexml/xmltokens.rb +++ b/lib/rexml/xmltokens.rb @@ -58,8 +58,8 @@ module XMLTokens "\\u0300-\\u036F", "\\u203F-\\u2040", ] - NAME_START_CHAR = "[#{name_start_chars.join('')}]" - NAME_CHAR = "[#{name_chars.join('')}]" + NAME_START_CHAR = "[#{name_start_chars.join('')}]".freeze + NAME_CHAR = "[#{name_chars.join('')}]".freeze NAMECHAR = NAME_CHAR # deprecated. Use NAME_CHAR instead. # From http://www.w3.org/TR/xml-names11/#NT-NCName @@ -70,13 +70,13 @@ module XMLTokens # # [5] NCNameChar ::= NameChar - ':' ncname_chars = name_chars - [":"] - NCNAME_STR = "[#{ncname_start_chars.join('')}][#{ncname_chars.join('')}]*" - NAME_STR = "(?:#{NCNAME_STR}:)?#{NCNAME_STR}" + NCNAME_STR = "[#{ncname_start_chars.join('')}][#{ncname_chars.join('')}]*".freeze + NAME_STR = "(?:#{NCNAME_STR}:)?#{NCNAME_STR}".freeze - NAME = "(#{NAME_START_CHAR}#{NAME_CHAR}*)" - NMTOKEN = "(?:#{NAME_CHAR})+" - NMTOKENS = "#{NMTOKEN}(\\s+#{NMTOKEN})*" - REFERENCE = "(?:&#{NAME};|&#\\d+;|&#x[0-9a-fA-F]+;)" + NAME = "(#{NAME_START_CHAR}#{NAME_CHAR}*)".freeze + NMTOKEN = "(?:#{NAME_CHAR})+".freeze + NMTOKENS = "#{NMTOKEN}(\\s+#{NMTOKEN})*".freeze + REFERENCE = "(?:&#{NAME};|&#\\d+;|&#x[0-9a-fA-F]+;)".freeze #REFERENCE = "(?:#{ENTITYREF}|#{CHARREF})" #ENTITYREF = "&#{NAME};" diff --git a/test/functions/test_base.rb b/test/functions/test_base.rb index b63f3d5a..b65bc7d3 100644 --- a/test/functions/test_base.rb +++ b/test/functions/test_base.rb @@ -19,7 +19,8 @@ def test_available_functions name namespace-uri normalize-space not number position round starts-with string string-length substring substring-after substring-before sum translate true ] - methods = REXML::FunctionsClass.class_variable_get(:@@available_functions).keys.sort + methods = REXML::FunctionsClass.instance_methods(false) - + REXML::FunctionsClass::INTERNAL_METHODS assert_equal expected_functions, methods.map { |m| m.to_s.tr('_', '-') }.sort end diff --git a/test/test_ractor.rb b/test/test_ractor.rb new file mode 100644 index 00000000..dd825e80 --- /dev/null +++ b/test/test_ractor.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true +require "test/unit" +require "core_assertions" + +require "rexml/document" + +module REXMLTests + class TestRactor < Test::Unit::TestCase + include Test::Unit::CoreAssertions + + def setup + if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("4.0") + omit("Ractor is unreliable before Ruby 4.0") + end + end + + def test_document + assert_ractor(<<~'RUBY', require: "rexml/document") + xml = "]>" + + "&g; & more" + result = Ractor.new(xml) do |source| + document = REXML::Document.new(source) + output = +"" + document.write(output) + [document.root.attributes["name"], + document.root.elements["child"].text, + output] + end.value + assert_equal(["value", + "Hello & more", + "\n]>" + + "&g; & more"], + result) + RUBY + end + + def test_xpath + assert_ractor(<<~'RUBY', require: ["rexml/document", "rexml/xpath"]) + xml = "AcesBandits" + result = Ractor.new(xml) do |source| + document = REXML::Document.new(source) + [REXML::XPath.match(document, "//team/@id").collect(&:value), + REXML::XPath.first(document, "count(//team)"), + REXML::XPath.first(document, "//team[@id=$id]", nil, {"id" => "2"}).text] + end.value + assert_equal([["1", "2"], 2, "Bandits"], result) + RUBY + end + + def test_node_types + assert_ractor(<<~'RUBY', require: "rexml/document") + xml = "" + + " & markup]]>" + result = Ractor.new(xml) do |source| + output = +"" + REXML::Document.new(source).write(output) + output + end.value + assert_equal(xml, result) + RUBY + end + + def test_stream_parsers + requires = ["rexml/parsers/pullparser", + "rexml/parsers/sax2parser", + "rexml/parsers/streamparser", + "rexml/streamlistener"] + assert_ractor(<<~'RUBY', require: requires) + class Listener + include REXML::StreamListener + attr_reader :names + def initialize + @names = [] + end + def tag_start(name, attributes) + @names << name + end + end + + xml = "text" + result = Ractor.new(xml) do |source| + pull = REXML::Parsers::PullParser.new(source) + pull_names = [] + while pull.has_next? + event = pull.pull + pull_names << event[0] if event.start_element? + end + + sax_names = [] + sax = REXML::Parsers::SAX2Parser.new(source) + sax.listen(:start_element) do |uri, local_name, qname, attributes| + sax_names << local_name + end + sax.parse + + listener = Listener.new + REXML::Parsers::StreamParser.new(source, listener).parse + + [pull_names, sax_names, listener.names] + end.value + assert_equal([["root", "child"]] * 3, result) + RUBY + end + + def test_parallel + assert_ractor(<<~'RUBY', require: ["rexml/document", "rexml/xpath"]) + xml = "AcesBandits" + ractors = 4.times.collect do + Ractor.new(xml) do |source| + 10.times.collect do + document = REXML::Document.new(source) + REXML::XPath.match(document, "//team").collect(&:text) + end.uniq + end + end + assert_equal([[["Aces", "Bandits"]]] * 4, ractors.collect(&:value)) + RUBY + end + end +end