summaryrefslogtreecommitdiff
path: root/lib/rexml/entity.rb
blob: 4b88a3c55338d974531804d9664f9e3bfefc9067 (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
require 'rexml/child'
require 'rexml/source'
require 'rexml/xmltokens'

module REXML
	# God, I hate DTDs.  I really do.  Why this idiot standard still
	# plagues us is beyond me.
	class Entity < Child
		include XMLTokens
		PUBIDCHAR = "\x20\x0D\x0Aa-zA-Z0-9\\-()+,./:=?;!*@$_%#"
		SYSTEMLITERAL = %Q{((?:"[^"]*")|(?:'[^']*'))}
		PUBIDLITERAL = %Q{("[#{PUBIDCHAR}']*"|'[#{PUBIDCHAR}]*')}
		EXTERNALID = "(?:(?:(SYSTEM)\\s+#{SYSTEMLITERAL})|(?:(PUBLIC)\\s+#{PUBIDLITERAL}\\s+#{SYSTEMLITERAL}))"
		NDATADECL = "\\s+NDATA\\s+#{NAME}"
		PEREFERENCE = "%#{NAME};"
		ENTITYVALUE = %Q{((?:"(?:[^%&"]|#{PEREFERENCE}|#{REFERENCE})*")|(?:'([^%&']|#{PEREFERENCE}|#{REFERENCE})*'))}
		PEDEF = "(?:#{ENTITYVALUE}|#{EXTERNALID})"
		ENTITYDEF = "(?:#{ENTITYVALUE}|(?:#{EXTERNALID}(#{NDATADECL})?))"
		PEDECL = "<!ENTITY\\s+(%)\\s+#{NAME}\\s+#{PEDEF}\\s*>"
		GEDECL = "<!ENTITY\\s+#{NAME}\\s+#{ENTITYDEF}\\s*>"
		ENTITYDECL = /\s*(?:#{GEDECL})|(?:#{PEDECL})/um

		attr_reader :name, :external, :ref, :ndata, :pubid

		# Create a new entity.  Simple entities can be constructed by passing a
		# name, value to the constructor; this creates a generic, plain entity
		# reference. For anything more complicated, you have to pass a Source to
		# the constructor with the entity definiton, or use the accessor methods.
		# +WARNING+: There is no validation of entity state except when the entity
		# is read from a stream.  If you start poking around with the accessors,
		# you can easily create a non-conformant Entity.  The best thing to do is
		# dump the stupid DTDs and use XMLSchema instead.
		# 
		#  e = Entity.new( 'amp', '&' )
		def initialize stream, value=nil, parent=nil, reference=false
			super(parent)
			@ndata = @pubid = @value = @external = nil
			if stream.kind_of? Array
				@name = stream[1]
				if stream[-1] == '%'
					@reference = true 
					stream.pop
				else
					@reference = false
				end
				if stream[2] =~ /SYSTEM|PUBLIC/
					@external = stream[2]
					if @external == 'SYSTEM'
						@ref = stream[3]
						@ndata = stream[4] if stream.size == 5
					else
						@pubid = stream[3]
						@ref = stream[4]
					end
				else
					@value = stream[2]
				end
			else
				@reference = reference
				@external = nil
				@name = stream
				@value = value
			end
		end

		# Evaluates whether the given string matchs an entity definition,
		# returning true if so, and false otherwise.
		def Entity::matches? string
			(ENTITYDECL =~ string) == 0
		end

		# Evaluates to the unnormalized value of this entity; that is, replacing
		# all entities -- both %ent; and &ent; entities.  This differs from
		# +value()+ in that +value+ only replaces %ent; entities.
		def unnormalized
			v = value()
			return nil if v.nil?
			@unnormalized = Text::unnormalize(v, parent)
			@unnormalized
		end

		#once :unnormalized

		# Returns the value of this entity unprocessed -- raw.  This is the
		# normalized value; that is, with all %ent; and &ent; entities intact
		def normalized
			@value
		end

		# Write out a fully formed, correct entity definition (assuming the Entity
		# object itself is valid.)
		def write out, indent=-1
			out << '<!ENTITY '
			out << '% ' if @reference
			out << @name
			out << ' '
			if @external
				out << @external << ' '
				if @pubid
					q = @pubid.include?('"')?"'":'"'
					out << q << @pubid << q << ' '
				end
				q = @ref.include?('"')?"'":'"'
				out << q << @ref << q
				out << ' NDATA ' << @ndata if @ndata
			else
				q = @value.include?('"')?"'":'"'
				out << q << @value << q
			end
			out << '>'
		end

		# Returns this entity as a string.  See write().
		def to_s
			rv = ''
			write rv
			rv
		end

		PEREFERENCE_RE = /#{PEREFERENCE}/um
		# Returns the value of this entity.  At the moment, only internal entities
		# are processed.  If the value contains internal references (IE,
		# %blah;), those are replaced with their values.  IE, if the doctype
		# contains:
		#  <!ENTITY % foo "bar">
		#  <!ENTITY yada "nanoo %foo; nanoo>
		# then:
		#  doctype.entity('yada').value   #-> "nanoo bar nanoo"
		def value
			if @value
				matches = @value.scan(PEREFERENCE_RE)
				rv = @value.clone
				if @parent
					matches.each do |entity_reference|
						entity_value = @parent.entity( entity_reference[0] )
						rv.gsub!( /%#{entity_reference};/um, entity_value )
					end
				end
				return rv
			end
			nil
		end
	end

	# This is a set of entity constants -- the ones defined in the XML
	# specification.  These are +gt+, +lt+, +amp+, +quot+ and +apos+.
	module EntityConst
		# +>+
		GT = Entity.new( 'gt', '>' )
		# +<+
		LT = Entity.new( 'lt', '<' )
		# +&+
		AMP = Entity.new( 'amp', '&' )
		# +"+
		QUOT = Entity.new( 'quot', '"' )
		# +'+
		APOS = Entity.new( 'apos', "'" )
	end
end