summaryrefslogtreecommitdiff
path: root/lib/rexml/attribute.rb
blob: ef9e54429434f57307cdedb67a216954e67fde30 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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
108
109
110
111
112
113
114
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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
require "rexml/namespace"
require 'rexml/text'

module REXML
  # Defines an Element Attribute; IE, a attribute=value pair, as in:
  # <element attribute="value"/>.  Attributes can be in their own
  # namespaces.  General users of REXML will not interact with the
  # Attribute class much.
  class Attribute
    include Node
    include Namespace

    # The element to which this attribute belongs
    attr_reader :element
    # The normalized value of this attribute.  That is, the attribute with
    # entities intact.
    attr_writer :normalized
    PATTERN = /\s*(#{NAME_STR})\s*=\s*(["'])(.*?)\2/um

    NEEDS_A_SECOND_CHECK = /(<|&((#{Entity::NAME});|(#0*((?:\d+)|(?:x[a-fA-F0-9]+)));)?)/um

    # Constructor.
    # FIXME: The parser doesn't catch illegal characters in attributes
    #
    # first::
    #   Either: an Attribute, which this new attribute will become a
    #   clone of; or a String, which is the name of this attribute
    # second::
    #   If +first+ is an Attribute, then this may be an Element, or nil.
    #   If nil, then the Element parent of this attribute is the parent
    #   of the +first+ Attribute.  If the first argument is a String,
    #   then this must also be a String, and is the content of the attribute.
    #   If this is the content, it must be fully normalized (contain no
    #   illegal characters).
    # parent::
    #   Ignored unless +first+ is a String; otherwise, may be the Element
    #   parent of this attribute, or nil.
    #
    #
    #  Attribute.new( attribute_to_clone )
    #  Attribute.new( attribute_to_clone, parent_element )
    #  Attribute.new( "attr", "attr_value" )
    #  Attribute.new( "attr", "attr_value", parent_element )
    def initialize( first, second=nil, parent=nil )
      @normalized = @unnormalized = @element = nil
      if first.kind_of? Attribute
        self.name = first.expanded_name
        @unnormalized = first.value
        if second.kind_of? Element
          @element = second
        else
          @element = first.element
        end
      elsif first.kind_of? String
        @element = parent
        self.name = first
        @normalized = second.to_s
      else
        raise "illegal argument #{first.class.name} to Attribute constructor"
      end
    end

    # Returns the namespace of the attribute.
    #
    #  e = Element.new( "elns:myelement" )
    #  e.add_attribute( "nsa:a", "aval" )
    #  e.add_attribute( "b", "bval" )
    #  e.attributes.get_attribute( "a" ).prefix   # -> "nsa"
    #  e.attributes.get_attribute( "b" ).prefix   # -> "elns"
    #  a = Attribute.new( "x", "y" )
    #  a.prefix                                   # -> ""
    def prefix
      pf = super
      if pf == ""
        pf = @element.prefix if @element
      end
      pf
    end

    # Returns the namespace URL, if defined, or nil otherwise
    #
    #  e = Element.new("el")
    #  e.add_namespace("ns", "http://url")
    #  e.add_attribute("ns:a", "b")
    #  e.add_attribute("nsx:a", "c")
    #  e.attribute("ns:a").namespace # => "http://url"
    #  e.attribute("nsx:a").namespace # => nil
    def namespace arg=nil
      arg = prefix if arg.nil?
      @element.namespace arg
    end

    # Returns true if other is an Attribute and has the same name and value,
    # false otherwise.
    def ==( other )
      other.kind_of?(Attribute) and other.name==name and other.value==value
    end

    # Creates (and returns) a hash from both the name and value
    def hash
      name.hash + value.hash
    end

    # Returns this attribute out as XML source, expanding the name
    #
    #  a = Attribute.new( "x", "y" )
    #  a.to_string     # -> "x='y'"
    #  b = Attribute.new( "ns:x", "y" )
    #  b.to_string     # -> "ns:x='y'"
    def to_string
      if @element and @element.context and @element.context[:attribute_quote] == :quote
        %Q^#@expanded_name="#{to_s().gsub(/"/, '&quote;')}"^
      else
        "#@expanded_name='#{to_s().gsub(/'/, '&apos;')}'"
      end
    end

    def doctype
      if @element
        doc = @element.document
        doc.doctype if doc
      end
    end

    # Returns the attribute value, with entities replaced
    def to_s
      return @normalized if @normalized

      @normalized = Text::normalize( @unnormalized, doctype )
      @unnormalized = nil
      @normalized
    end

    # Returns the UNNORMALIZED value of this attribute.  That is, entities
    # have been expanded to their values
    def value
      return @unnormalized if @unnormalized
      @unnormalized = Text::unnormalize( @normalized, doctype )
      @normalized = nil
      @unnormalized
    end

    # Returns a copy of this attribute
    def clone
      Attribute.new self
    end

    # Sets the element of which this object is an attribute.  Normally, this
    # is not directly called.
    #
    # Returns this attribute
    def element=( element )
      @element = element

      if @normalized
        Text.check( @normalized, NEEDS_A_SECOND_CHECK, doctype )
      end

      self
    end

    # Removes this Attribute from the tree, and returns true if successful
    #
    # This method is usually not called directly.
    def remove
      @element.attributes.delete self.name unless @element.nil?
    end

    # Writes this attribute (EG, puts 'key="value"' to the output)
    def write( output, indent=-1 )
      output << to_string
    end

    def node_type
      :attribute
    end

    def inspect
      rv = ""
      write( rv )
      rv
    end

    def xpath
      path = @element.xpath
      path += "/@#{self.expanded_name}"
      return path
    end
  end
end
#vim:ts=2 sw=2 noexpandtab: