1require "rexml/parent"
2require "rexml/parseexception"
3require "rexml/namespace"
4require 'rexml/entity'
5require 'rexml/attlistdecl'
6require 'rexml/xmltokens'
7
8module REXML
9  # Represents an XML DOCTYPE declaration; that is, the contents of <!DOCTYPE
10  # ... >.  DOCTYPES can be used to declare the DTD of a document, as well as
11  # being used to declare entities used in the document.
12  class DocType < Parent
13    include XMLTokens
14    START = "<!DOCTYPE"
15    STOP = ">"
16    SYSTEM = "SYSTEM"
17    PUBLIC = "PUBLIC"
18    DEFAULT_ENTITIES = {
19      'gt'=>EntityConst::GT,
20      'lt'=>EntityConst::LT,
21      'quot'=>EntityConst::QUOT,
22      "apos"=>EntityConst::APOS
23    }
24
25    # name is the name of the doctype
26    # external_id is the referenced DTD, if given
27    attr_reader :name, :external_id, :entities, :namespaces
28
29    # Constructor
30    #
31    #   dt = DocType.new( 'foo', '-//I/Hate/External/IDs' )
32    #   # <!DOCTYPE foo '-//I/Hate/External/IDs'>
33    #   dt = DocType.new( doctype_to_clone )
34    #   # Incomplete.  Shallow clone of doctype
35    #
36    # +Note+ that the constructor:
37    #
38    #  Doctype.new( Source.new( "<!DOCTYPE foo 'bar'>" ) )
39    #
40    # is _deprecated_.  Do not use it.  It will probably disappear.
41    def initialize( first, parent=nil )
42      @entities = DEFAULT_ENTITIES
43      @long_name = @uri = nil
44      if first.kind_of? String
45        super()
46        @name = first
47        @external_id = parent
48      elsif first.kind_of? DocType
49        super( parent )
50        @name = first.name
51        @external_id = first.external_id
52      elsif first.kind_of? Array
53        super( parent )
54        @name = first[0]
55        @external_id = first[1]
56        @long_name = first[2]
57        @uri = first[3]
58      elsif first.kind_of? Source
59        super( parent )
60        parser = Parsers::BaseParser.new( first )
61        event = parser.pull
62        if event[0] == :start_doctype
63          @name, @external_id, @long_name, @uri, = event[1..-1]
64        end
65      else
66        super()
67      end
68    end
69
70    def node_type
71      :doctype
72    end
73
74    def attributes_of element
75      rv = []
76      each do |child|
77        child.each do |key,val|
78          rv << Attribute.new(key,val)
79        end if child.kind_of? AttlistDecl and child.element_name == element
80      end
81      rv
82    end
83
84    def attribute_of element, attribute
85      att_decl = find do |child|
86        child.kind_of? AttlistDecl and
87        child.element_name == element and
88        child.include? attribute
89      end
90      return nil unless att_decl
91      att_decl[attribute]
92    end
93
94    def clone
95      DocType.new self
96    end
97
98    # output::
99    #   Where to write the string
100    # indent::
101    #   An integer.  If -1, no indentation will be used; otherwise, the
102    #   indentation will be this number of spaces, and children will be
103    #   indented an additional amount.
104    # transitive::
105    #   Ignored
106    # ie_hack::
107    #   Ignored
108    def write( output, indent=0, transitive=false, ie_hack=false )
109      f = REXML::Formatters::Default.new
110      indent( output, indent )
111      output << START
112      output << ' '
113      output << @name
114      output << " #@external_id" if @external_id
115      output << " #{@long_name.inspect}" if @long_name
116      output << " #{@uri.inspect}" if @uri
117      unless @children.empty?
118        output << ' ['
119        @children.each { |child|
120          output << "\n"
121          f.write( child, output )
122        }
123        output << "\n]"
124      end
125      output << STOP
126    end
127
128    def context
129      @parent.context
130    end
131
132    def entity( name )
133      @entities[name].unnormalized if @entities[name]
134    end
135
136    def add child
137      super(child)
138      @entities = DEFAULT_ENTITIES.clone if @entities == DEFAULT_ENTITIES
139      @entities[ child.name ] = child if child.kind_of? Entity
140    end
141
142    # This method retrieves the public identifier identifying the document's
143    # DTD.
144    #
145    # Method contributed by Henrik Martensson
146    def public
147      case @external_id
148      when "SYSTEM"
149        nil
150      when "PUBLIC"
151        strip_quotes(@long_name)
152      end
153    end
154
155    # This method retrieves the system identifier identifying the document's DTD
156    #
157    # Method contributed by Henrik Martensson
158    def system
159      case @external_id
160      when "SYSTEM"
161        strip_quotes(@long_name)
162      when "PUBLIC"
163        @uri.kind_of?(String) ? strip_quotes(@uri) : nil
164      end
165    end
166
167    # This method returns a list of notations that have been declared in the
168    # _internal_ DTD subset. Notations in the external DTD subset are not
169    # listed.
170    #
171    # Method contributed by Henrik Martensson
172    def notations
173      children().select {|node| node.kind_of?(REXML::NotationDecl)}
174    end
175
176    # Retrieves a named notation. Only notations declared in the internal
177    # DTD subset can be retrieved.
178    #
179    # Method contributed by Henrik Martensson
180    def notation(name)
181      notations.find { |notation_decl|
182        notation_decl.name == name
183      }
184    end
185
186    private
187
188    # Method contributed by Henrik Martensson
189    def strip_quotes(quoted_string)
190      quoted_string =~ /^[\'\"].*[\'\"]$/ ?
191        quoted_string[1, quoted_string.length-2] :
192        quoted_string
193    end
194  end
195
196  # We don't really handle any of these since we're not a validating
197  # parser, so we can be pretty dumb about them.  All we need to be able
198  # to do is spew them back out on a write()
199
200  # This is an abstract class.  You never use this directly; it serves as a
201  # parent class for the specific declarations.
202  class Declaration < Child
203    def initialize src
204      super()
205      @string = src
206    end
207
208    def to_s
209      @string+'>'
210    end
211
212    # == DEPRECATED
213    # See REXML::Formatters
214    #
215    def write( output, indent )
216      output << to_s
217    end
218  end
219
220  public
221  class ElementDecl < Declaration
222    def initialize( src )
223      super
224    end
225  end
226
227  class ExternalEntity < Child
228    def initialize( src )
229      super()
230      @entity = src
231    end
232    def to_s
233      @entity
234    end
235    def write( output, indent )
236      output << @entity
237    end
238  end
239
240  class NotationDecl < Child
241    attr_accessor :public, :system
242    def initialize name, middle, pub, sys
243      super(nil)
244      @name = name
245      @middle = middle
246      @public = pub
247      @system = sys
248    end
249
250    def to_s
251      notation = "<!NOTATION #{@name} #{@middle}"
252      notation << " #{@public.inspect}" if @public
253      notation << " #{@system.inspect}" if @system
254      notation << ">"
255      notation
256    end
257
258    def write( output, indent=-1 )
259      output << to_s
260    end
261
262    # This method retrieves the name of the notation.
263    #
264    # Method contributed by Henrik Martensson
265    def name
266      @name
267    end
268  end
269end
270