1 require "rexml/parent"
2 require "rexml/parseexception"
3 require "rexml/namespace"
4 require 'rexml/entity'
5 require 'rexml/attlistdecl'
6 require 'rexml/xmltokens'
7
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 }
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 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
129
130 def context
131 @parent.context
132 end
133
134 def entity( name )
135 @entities[name].unnormalized if @entities[name]
136 end
137
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
197
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()
201
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
209
210 def to_s
211 @string+'>'
212 end
213
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
228
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
241
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
251
252 def to_s
253 "<!NOTATION #@name #@middle#{
254 @public ? ' ' + public.inspect : ''
255 }#{
256 @system ? ' ' +@system.inspect : ''
257 }>"
258 end
259
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
271 end