Class: TagTreeScanner::Tag

Inherits:
Object
  • Object
show all
Defined in:
lib/tagtreescanner.rb

Overview

Tags are the equivalent of a DOM Element. The majority of tags are created automatically by a TagFactory, but it may be necessary to create them directly in order to augment or replace information in the tag tree.

A Tag may have one or more attributes, which are pairs of key/value strings; attributes are output in the HTML or XML representation of the Tag.

Each tag also has an info hash, which may be used to keep track of extra bits of information about a tag. Example usages might be keeping track of the depth of a list item, or the associated section for a header. Information from the info hash is not output in the HTML or XML representations.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, attributes = {}) ⇒ Tag

name

A symbol with the name of this tag

attributes

A hash of key/value pairs to store with this tag



194
195
196
197
198
199
# File 'lib/tagtreescanner.rb', line 194

def initialize( name, attributes={} )
  @name = name
  @child_tags = [ ]
  @attributes = attributes
  @info = {}
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args) ⇒ Object

Allows for settings HTML or XML-like attributes directly without knowing about the attributes collection. For example:

tag.href  = 'http://www.google.com'
tag.class = 'external'

is the same as:

tag.attributes['href']  = 'http://www.google.com'
tag.attributes['class'] = 'external'

…for any attributes (like the above) that don’t have the same name as an existing method or attribute on the Tag class.



210
211
212
213
214
215
216
# File 'lib/tagtreescanner.rb', line 210

def method_missing( name, *args )
  if (name=name.to_s) =~ /=$/
    @attributes[ name[0...-1] ] = (args.size==1 ? args[0] : args )
  else
    @attributes[ name ]
  end
end

Instance Attribute Details

#attributesObject

A hash of key/value attributes to emit in the XML/HTML representation



173
174
175
# File 'lib/tagtreescanner.rb', line 173

def attributes
  @attributes
end

#child_tagsObject

An array of child Tag or TextNode instances



169
170
171
# File 'lib/tagtreescanner.rb', line 169

def child_tags
  @child_tags
end

#factoryObject

The TagFactory that created this tag (may be nil)



176
177
178
# File 'lib/tagtreescanner.rb', line 176

def factory
  @factory
end

#infoObject

A hash that may be used to store extra information about a Tag



179
180
181
# File 'lib/tagtreescanner.rb', line 179

def info
  @info
end

#nameObject

A symbol with the name of this tag



166
167
168
# File 'lib/tagtreescanner.rb', line 166

def name
  @name
end

#next_siblingObject

The Tag or TextNode which immediately follows this tag (may be nil if this is the last tag of its parent)



186
187
188
# File 'lib/tagtreescanner.rb', line 186

def next_sibling
  @next_sibling
end

#parent_tagObject

The Tag to which this tag is attached (may be nil)



182
183
184
# File 'lib/tagtreescanner.rb', line 182

def parent_tag
  @parent_tag
end

#previous_siblingObject

The Tag or TextNode which immediately precedes this tag (may be nil if this is the first tag of its parent)



190
191
192
# File 'lib/tagtreescanner.rb', line 190

def previous_sibling
  @previous_sibling
end

Instance Method Details

#<<(additional_text) ⇒ Object

additional_text

The text to add to this node.

Appends additional_text to this tag. If the last item in the child_tags collection is a TextNode, the text is added to that item; otherwise, a new TextNode is created with additional_text and added as the last child of this tag.



382
383
384
385
386
387
388
389
# File 'lib/tagtreescanner.rb', line 382

def << ( additional_text )
  last_child = @child_tags.last
  if last_child.is_a? TextNode
    last_child << additional_text
  else
    append_child( TextNode.new( additional_text ) )
  end
end

#allowed_genreObject

Returns the allowed_genre property of the owning TagFactory, or nil if this tag wasn’t created by a factory.



244
245
246
# File 'lib/tagtreescanner.rb', line 244

def allowed_genre
  @factory && @factory.allowed_genre
end

#allows_text?Boolean

Returns the allows_text property of the owning TagFactory, or true if this tag wasn’t created by a factory.

Returns:

  • (Boolean)


238
239
240
# File 'lib/tagtreescanner.rb', line 238

def allows_text?
  @factory ? @factory.allows_text : true
end

#append_child(new_child) ⇒ Object

new_child

The Tag or TextNode to add as the last child.

Adds new_child to the end of this tag’s +child_tags_ collection. Returns a reference to new_child.

If new_child is a child of another Tag, it is first removed from that tag.



255
256
257
258
# File 'lib/tagtreescanner.rb', line 255

def append_child( new_child )
  return if new_child == @child_tags.last
  insert_after( new_child, @child_tags.last )
end

#autoclose?Boolean

Returns the autoclose property of the owning TagFactory, or nil if this tag wasn’t created by a factory.

Returns:

  • (Boolean)


232
233
234
# File 'lib/tagtreescanner.rb', line 232

def autoclose?
  @factory && @factory.autoclose
end

#close_matchObject

Returns the close_match property of the owning TagFactory, or nil if this tag wasn’t created by a factory.



220
221
222
# File 'lib/tagtreescanner.rb', line 220

def close_match
  @factory && @factory.close_match
end

#close_requires_bol?Boolean

Returns the close_requires_bol property of the owning TagFactory, or nil if this tag wasn’t created by a factory.

Returns:

  • (Boolean)


226
227
228
# File 'lib/tagtreescanner.rb', line 226

def close_requires_bol?
  @factory && @factory.close_requires_bol
end

#dupObject

Returns a copy of this tag and its entire hierarchy. All descendant tags/text nodes are also cloned.

The info hash is not preserved.



498
499
500
501
502
# File 'lib/tagtreescanner.rb', line 498

def dup
  tag = self.class.new( self.name, self.attributes.dup )
  @child_tags.each{ |tag2| tag.append_child( tag2.dup ) }
  tag
end

#inner_textObject

Returns the text contents of this tag and its descendants.



450
451
452
453
454
# File 'lib/tagtreescanner.rb', line 450

def inner_text
  @child_tags.inject(''){ |out,tag|
    out << ( tag.is_a?( TextNode ) ? tag.text : tag.inner_text )
  }
end

#insert_after(new_child, reference_child = nil) ⇒ Object

new_child

The Tag or TextNode to add as a child of this tag.

reference_child

The child to place new_child after.

Adds new_child as a child of this tag, immediately after the location of reference_child. Returns a reference to new_child.

If reference_child is nil, the child is added as the first child of this tag. A RuntimeError is raised if reference_child is not a child of this tag.

If new_child is a child of another Tag, #remove_child is automatically invoked to remove it from that tag.



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/tagtreescanner.rb', line 289

def insert_after( new_child, reference_child=nil )
  #puts "#{self.inspect}#insert_after( #{new_child.inspect}, #{reference_child.inspect} )"
  return new_child if reference_child ? ( reference_child.next_sibling == new_child ) : ( new_child == @child_tags.first )

  #Ensure new_child is not not an ancestor of self
  walker = self
  while walker
    raise "#{new_child.inspect} cannot be added under #{self.inspect}, because it is an ancestor of it!" if walker==new_child
    walker = walker.parent_tag
  end

  new_child.parent_tag.remove_child( new_child ) if new_child.parent_tag
  if reference_child
    new_idx = @child_tags.index( reference_child )
    raise "#{reference_child.inspect} is not a child of #{self.inspect}" unless new_idx
    new_idx += 1
  else
    new_idx = 0
  end
  new_child.parent_tag = self
  succ = @child_tags[ new_idx ]
  @child_tags.insert( new_idx, new_child )
  new_child.previous_sibling = reference_child
  reference_child.next_sibling = new_child if reference_child
  new_child.next_sibling = succ
  succ.previous_sibling = new_child if succ
  new_child
end

#insert_before(new_child, reference_child = nil) ⇒ Object

new_child

The Tag or TextNode to add as a child of this tag.

reference_child

The child to place new_child before.

Adds new_child as a child of this tag, immediately before the location of reference_child. Returns a reference to new_child.

If reference_child is nil, the child is added as the last child of this tag. A RuntimeError is raised if reference_child is not a child of this tag.

If new_child is a child of another Tag, #remove_child is automatically invoked to remove it from that tag.



272
273
274
275
# File 'lib/tagtreescanner.rb', line 272

def insert_before( new_child, reference_child=nil )
  return new_child if reference_child ? ( reference_child.previous_sibling == new_child ) : ( new_child == @child_tags.last )
  insert_after( new_child, reference_child ? reference_child.previous_sibling : @child_tags.last )
end

#inspectObject

:nodoc:



456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
# File 'lib/tagtreescanner.rb', line 456

def inspect #:nodoc:
  out = "<#{@name}"
  #out << " @pops=#{@parent_tag ? @parent_tag.name.inspect : 'nil'}"
  #out << " @prev=#{@previous_sibling ? @previous_sibling.name.inspect : 'nil'}"
  #out << " @next=#{@next_sibling ? @next_sibling.name.inspect : 'nil'}"
  @attributes.each{ |k,v| out << " #{k}=\"#{v}\"" }
  @info.each{ |k,v| out << " @#{k}=>#{v.inspect}" }
  children = @child_tags.length
  if children == 1 && TextNode === @child_tags.first
    out << ">#{@child_tags.first}</#{@name}"
  elsif children == 0
    out << '>'
  else
    out << " (#{@child_tags.length} child#{@child_tags.length != 1 ? 'ren' : ''})>"
  end
end

#remove_child(existing_child) ⇒ Object

existing_child

The Tag or TextNode to remove.

Removes existing_child from being a child of this tag. Returns existing_child.

A RuntimeError is raised if existing_child is not a child of this tag.

If new_child is a child of another Tag, #remove_child is automatically invoked to remove it from that tag.



328
329
330
331
332
333
334
335
336
337
# File 'lib/tagtreescanner.rb', line 328

def remove_child( existing_child )
  idx = @child_tags.index( existing_child )
  raise "#{existing_child.inspect} is not a child of #{self.inspect}" unless idx
  prev, succ = existing_child.previous_sibling, existing_child.next_sibling
  prev.next_sibling = succ if prev
  succ.previous_sibling = prev if succ
  @child_tags.delete_at( idx )
  existing_child.previous_sibling = existing_child.next_sibling = existing_child.parent_tag = nil
  existing_child
end

#replace_child(old_child, new_child) ⇒ Object

old_child

The existing child Tag or TextNode to replace.

new_child

The Tag or TextNode to replace old_child.

Replaces old_child with new_child in this collection. Returns old_child.

A RuntimeError is raised if existing_child is not a child of this tag.

If new_child is a child of another Tag, #remove_child is automatically invoked to remove it from that tag.



350
351
352
353
354
355
356
357
358
359
# File 'lib/tagtreescanner.rb', line 350

def replace_child( old_child, new_child )
  if ( prev = old_child.previous_sibling ) == new_child || old_child.next_sibling == new_child
    remove_child( old_child )
  else
    new_child.parent_tag.remove_child( new_child ) if new_child.parent_tag
    remove_child( old_child )
    insert_after( new_child, prev )
  end
  old_child
end

#replace_with(new_child) ⇒ Object

new_child

The Tag or TextNode to replace this tag.

Replaces this tag with new_child. Returns new_child.

A RuntimeError is raised if this tag is not a child of another tag.

If new_child is a child of another Tag, #remove_child is automatically invoked to remove it from that tag.



369
370
371
372
373
374
# File 'lib/tagtreescanner.rb', line 369

def replace_with( new_child )
  return new_child if new_child == self
  raise "#{self.inspect} is not a child of another tag" unless @parent_tag
  @parent_tag.replace_child( self, new_child )
  new_child
end

#tags_by_name(name) ⇒ Object

Returns an array of all descendants of this tag whose #name matches the supplied name.



438
439
440
441
442
443
444
445
446
447
# File 'lib/tagtreescanner.rb', line 438

def tags_by_name( name )
  out = []
  @child_tags.each{ |tag|
    out << tag if tag.name == name
    unless tag.child_tags.empty?
      out.concat( tag.tags_by_name( name ) ) 
    end
  }
  out
end

#text=(new_contents) ⇒ Object Also known as: inner_text=

Set the text content of this element to new_contents. Removes any child tags (and their text).



393
394
395
396
# File 'lib/tagtreescanner.rb', line 393

def text=( new_contents )
  @child_tags.clear
  append_child( TextNode.new( new_contents ) )
end

#to_hier(level = 0) ⇒ Object

level

The indentation level (tabs) to start at.

Returns a full-hierarchical representation of this tag and its descendants. (Used for debugging.)



477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
# File 'lib/tagtreescanner.rb', line 477

def to_hier( level=0 ) #:nodoc:
  tabs = "\t" * level
  out = "#{tabs}<#{@name}"
  @attributes.each{ |k,v| out << " #{k}=\"#{v}\"" }
  @info.each{ |k,v| out << " @#{k}=>#{v.inspect}" }
  if @child_tags.empty?
    out << " />\n"
  elsif @child_tags.length == 1 && TextNode === @child_tags.first
    out << ">#{@child_tags.first}</#{@name}>\n"
  else
    out << ">\n"
    @child_tags.each{ |n| out << n.to_hier(level+1) }
    out << "#{tabs}</#{@name}>\n"
  end
  out
end

#to_htmlObject

Returns an HTML representation of this tag and all its descendants.

This method is the same as #to_xml except that tags without any child_tags use an explicit close tag, e.g. <div></div> instead of XML’s <div />



405
406
407
# File 'lib/tagtreescanner.rb', line 405

def to_html
  to_xml( false )
end

#to_xml(empty_tags_collapsed = true) ⇒ Object

Returns an XML representation of this tag and all its descendants.

If empty_tags_collapsed is true (the default) then this method is the same as #to_html except that tags without any child_tags use a single closed tag, e.g. <div /> instead of HTML’s <div></div>

If empty_tags_collapsed is false, this is the same as #to_html.



417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# File 'lib/tagtreescanner.rb', line 417

def to_xml( empty_tags_collapsed=true )
  out = "<#{@name}"
  @attributes.each{ |k,v| out << " #{k}=\"#{v.to_s.gsub( '""', '&quot;' )}\"" }
  if empty_tags_collapsed && @child_tags.empty?
    out << ' />'
  else
    out << '>'
    unless @child_tags.empty?
      out << "\n" unless self.allows_text?
      @child_tags.each{ |tag|
        out << tag.to_xml( empty_tags_collapsed )
      }
    end 
    out << "</#{@name}>"
  end
  out << "\n" if @parent_tag && !@parent_tag.allows_text?
  out
end