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