Imported File#ftype spec from rubyspecs.
[rbx.git] / lib / rexml / doctype.rb
blob05cd4ab33199ddeafa01d625f7500315d3e2ae9f
1 require "rexml/parent"
2 require "rexml/parseexception"
3 require "rexml/namespace"
4 require 'rexml/entity'
5 require 'rexml/attlistdecl'
6 require 'rexml/xmltokens'
8 module 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     }
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
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
70     def node_type
71       :doctype
72     end
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
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
94     def clone
95       DocType.new self
96     end
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         next_indent = indent + 1
119         output << ' ['
120         child = nil    # speed
121         @children.each { |child|
122           output << "\n"
123           f.write( child, output )
124         }
125         output << "\n]"
126       end
127       output << STOP
128     end
130     def context
131       @parent.context
132     end
134     def entity( name )
135       @entities[name].unnormalized if @entities[name]
136     end
138     def add child
139       super(child)
140       @entities = DEFAULT_ENTITIES.clone if @entities == DEFAULT_ENTITIES
141       @entities[ child.name ] = child if child.kind_of? Entity
142     end
143     
144     # This method retrieves the public identifier identifying the document's 
145     # DTD.
146     #
147     # Method contributed by Henrik Martensson
148     def public
149       case @external_id
150       when "SYSTEM"
151         nil
152       when "PUBLIC"
153         strip_quotes(@long_name)
154       end
155     end
156     
157     # This method retrieves the system identifier identifying the document's DTD
158     #
159     # Method contributed by Henrik Martensson
160     def system
161       case @external_id
162       when "SYSTEM"
163         strip_quotes(@long_name)
164       when "PUBLIC"
165         @uri.kind_of?(String) ? strip_quotes(@uri) : nil
166       end
167     end
168     
169     # This method returns a list of notations that have been declared in the
170     # _internal_ DTD subset. Notations in the external DTD subset are not 
171     # listed.
172     #
173     # Method contributed by Henrik Martensson
174     def notations
175       children().select {|node| node.kind_of?(REXML::NotationDecl)}
176     end
177     
178     # Retrieves a named notation. Only notations declared in the internal
179     # DTD subset can be retrieved.
180     #
181     # Method contributed by Henrik Martensson
182     def notation(name)
183       notations.find { |notation_decl|
184         notation_decl.name == name
185       }
186     end
187     
188     private
189     
190     # Method contributed by Henrik Martensson
191     def strip_quotes(quoted_string)
192       quoted_string =~ /^[\'\"].*[\ยด\"]$/ ?
193         quoted_string[1, quoted_string.length-2] :
194         quoted_string
195     end
196   end
198   # We don't really handle any of these since we're not a validating
199   # parser, so we can be pretty dumb about them.  All we need to be able
200   # to do is spew them back out on a write()
202   # This is an abstract class.  You never use this directly; it serves as a
203   # parent class for the specific declarations.
204   class Declaration < Child
205     def initialize src
206       super()
207       @string = src
208     end
210     def to_s
211       @string+'>'
212     end
214     # == DEPRECATED
215     # See REXML::Formatters
216     #
217     def write( output, indent )
218       output << to_s
219     end
220   end
221   
222   public
223   class ElementDecl < Declaration
224     def initialize( src )
225       super
226     end
227   end
229   class ExternalEntity < Child
230     def initialize( src )
231       super()
232       @entity = src
233     end
234     def to_s
235       @entity
236     end
237     def write( output, indent )
238       output << @entity
239     end
240   end
242   class NotationDecl < Child
243     attr_accessor :public, :system
244     def initialize name, middle, pub, sys
245       super(nil)
246       @name = name
247       @middle = middle
248       @public = pub
249       @system = sys
250     end
252     def to_s
253       "<!NOTATION #@name #@middle#{
254         @public ? ' ' + public.inspect : '' 
255       }#{
256         @system ? ' ' +@system.inspect : ''
257       }>"
258     end
260     def write( output, indent=-1 )
261       output << to_s
262     end
263     
264     # This method retrieves the name of the notation.
265     #
266     # Method contributed by Henrik Martensson
267     def name
268       @name
269     end
270   end