1require "rexml/namespace" 2require 'rexml/text' 3 4module REXML 5 # Defines an Element Attribute; IE, a attribute=value pair, as in: 6 # <element attribute="value"/>. Attributes can be in their own 7 # namespaces. General users of REXML will not interact with the 8 # Attribute class much. 9 class Attribute 10 include Node 11 include Namespace 12 13 # The element to which this attribute belongs 14 attr_reader :element 15 # The normalized value of this attribute. That is, the attribute with 16 # entities intact. 17 attr_writer :normalized 18 PATTERN = /\s*(#{NAME_STR})\s*=\s*(["'])(.*?)\2/um 19 20 NEEDS_A_SECOND_CHECK = /(<|&((#{Entity::NAME});|(#0*((?:\d+)|(?:x[a-fA-F0-9]+)));)?)/um 21 22 # Constructor. 23 # FIXME: The parser doesn't catch illegal characters in attributes 24 # 25 # first:: 26 # Either: an Attribute, which this new attribute will become a 27 # clone of; or a String, which is the name of this attribute 28 # second:: 29 # If +first+ is an Attribute, then this may be an Element, or nil. 30 # If nil, then the Element parent of this attribute is the parent 31 # of the +first+ Attribute. If the first argument is a String, 32 # then this must also be a String, and is the content of the attribute. 33 # If this is the content, it must be fully normalized (contain no 34 # illegal characters). 35 # parent:: 36 # Ignored unless +first+ is a String; otherwise, may be the Element 37 # parent of this attribute, or nil. 38 # 39 # 40 # Attribute.new( attribute_to_clone ) 41 # Attribute.new( attribute_to_clone, parent_element ) 42 # Attribute.new( "attr", "attr_value" ) 43 # Attribute.new( "attr", "attr_value", parent_element ) 44 def initialize( first, second=nil, parent=nil ) 45 @normalized = @unnormalized = @element = nil 46 if first.kind_of? Attribute 47 self.name = first.expanded_name 48 @unnormalized = first.value 49 if second.kind_of? Element 50 @element = second 51 else 52 @element = first.element 53 end 54 elsif first.kind_of? String 55 @element = parent 56 self.name = first 57 @normalized = second.to_s 58 else 59 raise "illegal argument #{first.class.name} to Attribute constructor" 60 end 61 end 62 63 # Returns the namespace of the attribute. 64 # 65 # e = Element.new( "elns:myelement" ) 66 # e.add_attribute( "nsa:a", "aval" ) 67 # e.add_attribute( "b", "bval" ) 68 # e.attributes.get_attribute( "a" ).prefix # -> "nsa" 69 # e.attributes.get_attribute( "b" ).prefix # -> "elns" 70 # a = Attribute.new( "x", "y" ) 71 # a.prefix # -> "" 72 def prefix 73 pf = super 74 if pf == "" 75 pf = @element.prefix if @element 76 end 77 pf 78 end 79 80 # Returns the namespace URL, if defined, or nil otherwise 81 # 82 # e = Element.new("el") 83 # e.add_attributes({"xmlns:ns", "http://url"}) 84 # e.namespace( "ns" ) # -> "http://url" 85 def namespace arg=nil 86 arg = prefix if arg.nil? 87 @element.namespace arg 88 end 89 90 # Returns true if other is an Attribute and has the same name and value, 91 # false otherwise. 92 def ==( other ) 93 other.kind_of?(Attribute) and other.name==name and other.value==value 94 end 95 96 # Creates (and returns) a hash from both the name and value 97 def hash 98 name.hash + value.hash 99 end 100 101 # Returns this attribute out as XML source, expanding the name 102 # 103 # a = Attribute.new( "x", "y" ) 104 # a.to_string # -> "x='y'" 105 # b = Attribute.new( "ns:x", "y" ) 106 # b.to_string # -> "ns:x='y'" 107 def to_string 108 if @element and @element.context and @element.context[:attribute_quote] == :quote 109 %Q^#@expanded_name="#{to_s().gsub(/"/, '"e;')}"^ 110 else 111 "#@expanded_name='#{to_s().gsub(/'/, ''')}'" 112 end 113 end 114 115 def doctype 116 if @element 117 doc = @element.document 118 doc.doctype if doc 119 end 120 end 121 122 # Returns the attribute value, with entities replaced 123 def to_s 124 return @normalized if @normalized 125 126 @normalized = Text::normalize( @unnormalized, doctype ) 127 @unnormalized = nil 128 @normalized 129 end 130 131 # Returns the UNNORMALIZED value of this attribute. That is, entities 132 # have been expanded to their values 133 def value 134 return @unnormalized if @unnormalized 135 @unnormalized = Text::unnormalize( @normalized, doctype ) 136 @normalized = nil 137 @unnormalized 138 end 139 140 # Returns a copy of this attribute 141 def clone 142 Attribute.new self 143 end 144 145 # Sets the element of which this object is an attribute. Normally, this 146 # is not directly called. 147 # 148 # Returns this attribute 149 def element=( element ) 150 @element = element 151 152 if @normalized 153 Text.check( @normalized, NEEDS_A_SECOND_CHECK, doctype ) 154 end 155 156 self 157 end 158 159 # Removes this Attribute from the tree, and returns true if successfull 160 # 161 # This method is usually not called directly. 162 def remove 163 @element.attributes.delete self.name unless @element.nil? 164 end 165 166 # Writes this attribute (EG, puts 'key="value"' to the output) 167 def write( output, indent=-1 ) 168 output << to_string 169 end 170 171 def node_type 172 :attribute 173 end 174 175 def inspect 176 rv = "" 177 write( rv ) 178 rv 179 end 180 181 def xpath 182 path = @element.xpath 183 path += "/@#{self.expanded_name}" 184 return path 185 end 186 end 187end 188#vim:ts=2 sw=2 noexpandtab: 189