Class: REXML::Text

Inherits:
Child
  • Object
show all
Includes:
Comparable
Defined in:
lib/compat/opal/rexml/text.rb

Overview

Represents text nodes in an XML document

Constant Summary collapse

SPECIALS =

The order in which the substitutions occur

[ /&(?!#?[\w-]+;)/u, /</u, />/u, /"/u, /'/u, /\r/u ]
SUBSTITUTES =
['&amp;', '&lt;', '&gt;', '&quot;', '&apos;', '&#13;']
SLAICEPS =

Characters which are substituted in written strings

[ '<', '>', '"', "'", '&' ]
SETUTITSBUS =
[ /&lt;/u, /&gt;/u, /&quot;/u, /&apos;/u, /&amp;/u ]
NEEDS_A_SECOND_CHECK =
/(<|&((#{Entity::NAME});|(#0*((?:\d+)|(?:x[a-fA-F0-9]+)));)?)/um
NUMERICENTITY =
/&#0*((?:\d+)|(?:x[a-fA-F0-9]+));/
VALID_CHAR =

BMP-only: above-BMP ranges removed for JavaScript regex compatibility.

[
  0x9, 0xA, 0xD,
  (0x20..0xD7FF),
  (0xE000..0xFFFD),
]
VALID_XML_CHARS =
Regexp.new('^['+
  VALID_CHAR.map { |item|
    case item
    when Integer
      [item].pack('U')
    when Range
      [item.first, '-'.ord, item.last].pack('UUU')
    end
  }.join +
']*$')
REFERENCE =
/#{Entity::REFERENCE}/

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(arg, respect_whitespace = false, parent = nil, raw = nil, entity_filter = nil, illegal = NEEDS_A_SECOND_CHECK) ⇒ Text

Constructor arg if a String, the content is set to the String. If a Text, the object is shallowly cloned.

respect_whitespace (boolean, false) if true, whitespace is respected

parent (nil) if this is a Parent object, the parent will be set to this.

raw (nil) This argument can be given three values. If true, then the value of used to construct this object is expected to contain no unescaped XML markup, and REXML will not change the text. If this value is false, the string may contain any characters, and REXML will escape any and all defined entities whose values are contained in the text. If this value is nil (the default), then the raw value of the parent will be used as the raw value for this node. If there is no raw value for the parent, and no value is supplied, the default is false. Use this field if you have entities defined for some text, and you don’t want REXML to escape that text in output.

Text.new( "<&", false, nil, false ) #-> "&lt;&amp;"
Text.new( "&lt;&amp;", false, nil, false ) #-> "&amp;lt;&amp;amp;"
Text.new( "<&", false, nil, true )  #-> Parse exception
Text.new( "&lt;&amp;", false, nil, true )  #-> "&lt;&amp;"
# Assume that the entity "s" is defined to be "sean"
# and that the entity    "r" is defined to be "russell"
Text.new( "sean russell" )          #-> "&s; &r;"
Text.new( "sean russell", false, nil, true ) #-> "sean russell"

entity_filter (nil) This can be an array of entities to match in the supplied text. This argument is only useful if raw is set to false.

Text.new( "sean russell", false, nil, false, ["s"] ) #-> "&s; russell"
Text.new( "sean russell", false, nil, true, ["s"] ) #-> "sean russell"

In the last example, the entity_filter argument is ignored.

illegal INTERNAL USE ONLY



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/compat/opal/rexml/text.rb', line 78

def initialize(arg, respect_whitespace=false, parent=nil, raw=nil,
  entity_filter=nil, illegal=NEEDS_A_SECOND_CHECK )

  @raw = false
  @parent = nil
  @entity_filter = nil

  if parent
    super( parent )
    @raw = parent.raw
  end

  if arg.kind_of? String
    @string = arg.dup
  elsif arg.kind_of? Text
    @string = arg.instance_variable_get(:@string).dup
    @raw = arg.raw
    @entity_filter = arg.instance_variable_get(:@entity_filter)
  else
    raise "Illegal argument of type #{arg.type} for Text constructor (#{arg})"
  end

  @string = @string.squeeze(" \n\t") unless respect_whitespace
  @string = @string.gsub(/\r\n?/, "\n")
  @raw = raw unless raw.nil?
  @entity_filter = entity_filter if entity_filter
  clear_cache

  Text.check(@string, illegal) if @raw
end

Instance Attribute Details

#rawObject

If raw is true, then REXML leaves the value alone



20
21
22
# File 'lib/compat/opal/rexml/text.rb', line 20

def raw
  @raw
end

Class Method Details

.check(string, pattern, doctype = nil) ⇒ Object

check for illegal characters



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/compat/opal/rexml/text.rb', line 115

def Text.check string, pattern, doctype = nil

  # illegal anywhere — avoid VALID_XML_CHARS regex on uncontrolled data
  string.each_char do |c|
    code = c.ord
    unless (code == 0x9 || code == 0xA || code == 0xD ||
            (code >= 0x20 && code <= 0xD7FF) ||
            (code >= 0xE000 && code <= 0xFFFD))
      raise "Illegal character #{c.inspect} in raw string #{string.inspect}"
    end
  end

  pos = 0
  while (index = string.index(/<|&/, pos))
    if string[index] == "<"
      raise "Illegal character \"#{string[index]}\" in raw string #{string.inspect}"
    end

    unless (end_index = string.index(/[^\s];/, index + 1))
      raise "Illegal character \"#{string[index]}\" in raw string #{string.inspect}"
    end

    value = string[(index + 1)..end_index]
    if /\s/.match?(value)
      raise "Illegal character \"#{string[index]}\" in raw string #{string.inspect}"
    end

    if value[0] == "#"
      character_reference = value[1..-1]

      unless (/^(\d+|x[0-9a-fA-F]+)$/.match?(character_reference))
        if character_reference[0] == "x" || character_reference[-1] == "x"
          raise "Illegal character \"#{string[index]}\" in raw string #{string.inspect}"
        else
          raise "Illegal character #{string.inspect} in raw string #{string.inspect}"
        end
      end

      case (character_reference[0] == "x" ? character_reference[1..-1].to_i(16) : character_reference[0..-1].to_i)
      when *VALID_CHAR
      else
        raise "Illegal character #{string.inspect} in raw string #{string.inspect}"
      end
    elsif !(/^#{Entity::NAME}$/um.match?(value))
      raise "Illegal character \"#{string[index]}\" in raw string #{string.inspect}"
    end

    pos = end_index + 1
  end

  string
end

.expand(ref, doctype, filter) ⇒ Object



399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
# File 'lib/compat/opal/rexml/text.rb', line 399

def Text.expand(ref, doctype, filter)
  if ref[1] == ?#
    if ref[2] == ?x
      [ref[3...-1].to_i(16)].pack('U*')
    else
      [ref[2...-1].to_i].pack('U*')
    end
  elsif ref == '&amp;'
    '&'
  elsif filter and filter.include?( ref[1...-1] )
    ref
  elsif doctype
    doctype.entity( ref[1...-1] ) or ref
  else
    entity_value = DocType::DEFAULT_ENTITIES[ ref[1...-1] ]
    entity_value ? entity_value.value : ref
  end
end

.normalize(input, doctype = nil, entity_filter = nil) ⇒ Object

Escapes all possible entities



361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
# File 'lib/compat/opal/rexml/text.rb', line 361

def Text::normalize( input, doctype=nil, entity_filter=nil )
  copy = input.to_s
  # Doing it like this rather than in a loop improves the speed
  #copy = copy.gsub( EREFERENCE, '&amp;' )
  copy = copy.gsub( "&", "&amp;" ) if copy.include?("&")
  if doctype
    # Replace all ampersands that aren't part of an entity
    doctype.entities.each_value do |entity|
      copy = copy.gsub( entity.value,
        "&#{entity.name};" ) if entity.value and
          not( entity_filter and entity_filter.include?(entity.name) )
    end
  else
    # Replace all ampersands that aren't part of an entity
    DocType::DEFAULT_ENTITIES.each_value do |entity|
      if copy.include?(entity.value)
        copy = copy.gsub(entity.value, "&#{entity.name};" )
      end
    end
  end
  copy
end

.read_with_substitution(input, illegal = nil) ⇒ Object

Reads text, substituting entities



335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'lib/compat/opal/rexml/text.rb', line 335

def Text::read_with_substitution( input, illegal=nil )
  copy = input.clone

  if copy =~ illegal
    raise ParseException.new( "malformed text: Illegal character #$& in \"#{copy}\"" )
  end if illegal

  copy.gsub!( /\r\n?/, "\n" )
  if copy.include? ?&
    copy.gsub!( SETUTITSBUS[0], SLAICEPS[0] )
    copy.gsub!( SETUTITSBUS[1], SLAICEPS[1] )
    copy.gsub!( SETUTITSBUS[2], SLAICEPS[2] )
    copy.gsub!( SETUTITSBUS[3], SLAICEPS[3] )
    copy.gsub!( SETUTITSBUS[4], SLAICEPS[4] )
    copy.gsub!( /&#0*((?:\d+)|(?:x[a-f0-9]+));/ ) {
      m=$1
      #m='0' if m==''
      m = "0#{m}" if m[0] == ?x
      [Integer(m)].pack('U*')
    }
  end
  copy
end

.unnormalize(string, doctype = nil, filter = nil, illegal = nil, entity_expansion_text_limit: nil) ⇒ Object

Unescapes all possible entities



385
386
387
388
389
390
391
392
393
394
395
396
397
# File 'lib/compat/opal/rexml/text.rb', line 385

def Text::unnormalize( string, doctype=nil, filter=nil, illegal=nil, entity_expansion_text_limit: nil )
  entity_expansion_text_limit ||= Security.entity_expansion_text_limit
  sum = 0
  string.gsub( /\r\n?/, "\n" ).gsub( REFERENCE ) {
    s = Text.expand($&, doctype, filter)
    if sum + s.bytesize > entity_expansion_text_limit
      raise "entity expansion has grown too large"
    else
      sum += s.bytesize
    end
    s
  }
end

Instance Method Details

#<<(to_append) ⇒ Object

Appends text to this text node. The text is appended in the raw mode of this text node.

returns the text itself to enable method chain like ‘text << “XXX” << “YYY”’.



187
188
189
190
191
# File 'lib/compat/opal/rexml/text.rb', line 187

def <<( to_append )
  @string << to_append.gsub( /\r\n?/, "\n" )
  clear_cache
  self
end

#<=>(other) ⇒ Object

other a String or a Text returns the result of (to_s <=> arg.to_s)



196
197
198
# File 'lib/compat/opal/rexml/text.rb', line 196

def <=>( other )
  to_s() <=> other.to_s
end

#cloneObject



177
178
179
# File 'lib/compat/opal/rexml/text.rb', line 177

def clone
  Text.new(self, true)
end

#doctypeObject



200
201
202
# File 'lib/compat/opal/rexml/text.rb', line 200

def doctype
  @parent&.document&.doctype
end

#empty?Boolean

Returns:

  • (Boolean)


172
173
174
# File 'lib/compat/opal/rexml/text.rb', line 172

def empty?
  @string.size==0
end

#indent_text(string, level = 1, style = "\t", indentfirstline = true) ⇒ Object



269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/compat/opal/rexml/text.rb', line 269

def indent_text(string, level=1, style="\t", indentfirstline=true)
  Kernel.warn("#{self.class.name}#indent_text is deprecated. See REXML::Formatters", uplevel: 1)
  return string if level < 0

  new_string = +''
  string.each_line { |line|
    indent_string = style * level
    new_line = (indent_string + line).rstrip
    new_string << new_line
  }
  new_string.strip! unless indentfirstline
  new_string
end

#inspectObject



223
224
225
# File 'lib/compat/opal/rexml/text.rb', line 223

def inspect
  @string.inspect
end

#node_typeObject



168
169
170
# File 'lib/compat/opal/rexml/text.rb', line 168

def node_type
  :text
end

#parent=(parent) ⇒ Object



109
110
111
112
# File 'lib/compat/opal/rexml/text.rb', line 109

def parent= parent
  super(parent)
  Text.check(@string, NEEDS_A_SECOND_CHECK) if @raw and @parent
end

#to_sObject

Returns the string value of this text node. This string is always escaped, meaning that it is a valid XML text node string, and all entities that can be escaped, have been inserted. This method respects the entity filter set in the constructor.

# Assume that the entity "s" is defined to be "sean", and that the
# entity "r" is defined to be "russell"
t = Text.new( "< & sean russell", false, nil, false, ['s'] )
t.to_s   #-> "&lt; &amp; &s; russell"
t = Text.new( "< & &s; russell", false, nil, false )
t.to_s   #-> "&lt; &amp; &s; russell"
u = Text.new( "sean russell", false, nil, true )
u.to_s   #-> "sean russell"


218
219
220
221
# File 'lib/compat/opal/rexml/text.rb', line 218

def to_s
  return @string if @raw
  @normalized ||= Text::normalize( @string, doctype, @entity_filter )
end

#valueObject

Returns the string value of this text. This is the text without entities, as it might be used programmatically, or printed to the console. This ignores the ‘raw’ attribute setting, and any entity_filter.

# Assume that the entity "s" is defined to be "sean", and that the
# entity "r" is defined to be "russell"
t = Text.new( "< & sean russell", false, nil, false, ['s'] )
t.value   #-> "< & sean russell"
t = Text.new( "< & &s; russell", false, nil, false )
t.value   #-> "< & sean russell"
u = Text.new( "sean russell", false, nil, true )
u.value   #-> "sean russell"


240
241
242
243
# File 'lib/compat/opal/rexml/text.rb', line 240

def value
  @unnormalized ||= Text::unnormalize(@string, doctype,
                                      entity_expansion_text_limit: document&.entity_expansion_text_limit)
end

#value=(val) ⇒ Object

Sets the contents of this text node. This expects the text to be unnormalized. It returns self.

e = Element.new( "a" )
e.add_text( "foo" )   # <a>foo</a>
e[0].value = "bar"    # <a>bar</a>
e[0].value = "<a>"    # <a>&lt;a&gt;</a>


252
253
254
255
256
# File 'lib/compat/opal/rexml/text.rb', line 252

def value=( val )
  @string = val.gsub( /\r\n?/, "\n" )
  clear_cache
  @raw = false
end

#wrap(string, width, addnewline = false) ⇒ Object



258
259
260
261
262
263
264
265
266
267
# File 'lib/compat/opal/rexml/text.rb', line 258

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
    "\n" + string[0,place] + "\n" + wrap(string[place+1..-1], width)
  else
    string[0,place] + "\n" + wrap(string[place+1..-1], width)
  end
end

#write(writer, indent = -1,, transitive = false, ie_hack = false) ⇒ Object

DEPRECATED

See REXML::Formatters



286
287
288
289
290
291
292
293
294
# File 'lib/compat/opal/rexml/text.rb', line 286

def write( writer, indent=-1, transitive=false, ie_hack=false )
  Kernel.warn("#{self.class.name}#write is deprecated.  See REXML::Formatters", uplevel: 1)
  formatter = if indent > -1
      REXML::Formatters::Pretty.new( indent )
    else
      REXML::Formatters::Default.new
    end
  formatter.write( self, writer )
end

#write_with_substitution(out, input) ⇒ Object

Writes out text, substituting special characters beforehand. out A String, IO, or any other object supporting <<( String ) input the text to substitute and the write out

z=utf8.unpack("U*")
ascOut=""
z.each{|r|
  if r <  0x100
    ascOut.concat(r.chr)
  else
    ascOut.concat(sprintf("&#x%x;", r))
  end
}
puts ascOut


316
317
318
319
320
321
322
323
324
325
326
# File 'lib/compat/opal/rexml/text.rb', line 316

def write_with_substitution out, input
  copy = input.clone
  # Doing it like this rather than in a loop improves the speed
  copy.gsub!( SPECIALS[0], SUBSTITUTES[0] )
  copy.gsub!( SPECIALS[1], SUBSTITUTES[1] )
  copy.gsub!( SPECIALS[2], SUBSTITUTES[2] )
  copy.gsub!( SPECIALS[3], SUBSTITUTES[3] )
  copy.gsub!( SPECIALS[4], SUBSTITUTES[4] )
  copy.gsub!( SPECIALS[5], SUBSTITUTES[5] )
  out << copy
end

#xpathObject

FIXME This probably won’t work properly



298
299
300
# File 'lib/compat/opal/rexml/text.rb', line 298

def xpath
  @parent.xpath + "/text()"
end