1 //
2 // Copyright (C) 2010 Novell Inc. http://novell.com
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 //
23 using System;
24 using System.Collections.Generic;
25 using System.IO;
26 using System.Linq;
27 using System.Xml;
28 
29 /*
30 
31 * State transition
32 
33 Unlike XmlWriter, XAML nodes are not immediately writable because object
34 output has to be delayed to be determined whether it should write
35 an attribute or an element.
36 
37 ** NamespaceDeclarations
38 
39 NamespaceDeclaration does not immediately participate in the state transition
40 but some write methods reject stored namespaces (e.g. WriteEndObject cannot
41 handle them). In such cases, they throw InvalidOperationException, while the
42 writer throws XamlXmlWriterException for usual state transition.
43 
44 Though they still seems to affect some outputs. If a member with simple
45 value is written after a namespace, then it becomes an element, not attribute.
46 
47 ** state transition
48 
49 states are: Initial, ObjectStarted, MemberStarted, ValueWritten, MemberDone, End
50 
51 Initial + StartObject -> ObjectStarted : push(xt)
52 ObjectStarted + StartMember -> MemberStarted : push(xm)
53 ObjectStarted + EndObject -> ObjectWritten or End : pop()
54 MemberStarted + StartObject -> ObjectStarted : push(xt)
55 MemberStarted + Value -> ValueWritten
56 MemberStarted + GetObject -> MemberDone : pop()
57 ObjectWritten + StartObject -> ObjectStarted : push(x)
58 ObjectWritten + Value -> ValueWritten : pop()
59 ObjectWritten + EndMember -> MemberDone : pop()
60 ValueWritten + StartObject -> invalid - or - ObjectStarted : push(x)
61 ValueWritten + Value -> invalid - or - ValueWritten
62 ValueWritten + EndMember -> MemberDone : pop()
63 MemberDone + EndObject -> ObjectWritten or End : pop() // xt
64 MemberDone + StartMember -> MemberStarted : push(xm)
65 
66 (in XamlObjectWriter, Value must be followed by EndMember.)
67 
68 */
69 
70 namespace System.Xaml
71 {
72 	internal class XamlWriterStateManager<TError,TNSError> : XamlWriterStateManager
73 		where TError : Exception
74 		where TNSError : Exception
75 	{
XamlWriterStateManager(bool isXmlWriter)76 		public XamlWriterStateManager (bool isXmlWriter)
77 			: base (isXmlWriter)
78 		{
79 		}
80 
CreateError(string msg)81 		public override Exception CreateError (string msg)
82 		{
83 			return (Exception) Activator.CreateInstance (typeof (TError), new object [] {msg});
84 		}
85 
CreateNamespaceError(string msg)86 		public override Exception CreateNamespaceError (string msg)
87 		{
88 			return (Exception) Activator.CreateInstance (typeof (TNSError), new object [] {msg});
89 		}
90 	}
91 
92 	internal enum XamlWriteState
93 	{
94 		Initial,
95 		ObjectStarted,
96 		MemberStarted,
97 		ObjectWritten,
98 		ValueWritten,
99 		MemberDone,
100 		End
101 	}
102 
103 	internal abstract class XamlWriterStateManager
104 	{
XamlWriterStateManager(bool isXmlWriter)105 		public XamlWriterStateManager (bool isXmlWriter)
106 		{
107 			allow_ns_at_value = isXmlWriter;
108 			allow_object_after_value = isXmlWriter;
109 			allow_parallel_values = !isXmlWriter;
110 			allow_empty_member = !isXmlWriter;
111 			allow_multiple_results = !isXmlWriter;
112 		}
113 
114 		// configuration
115 		bool allow_ns_at_value, allow_object_after_value, allow_parallel_values, allow_empty_member, allow_multiple_results;
116 
117 		// state
118 		XamlWriteState state = XamlWriteState.Initial;
119 		bool ns_pushed;
120 		bool accept_multiple_values; // It is PositionalParameters-specific state.
121 
122 		public XamlWriteState State {
123 			get { return state; }
124 		}
125 
126 		// FIXME: actually this property is a hack. It should preserve stacked flag values for each nested member in current tree state.
127 		public bool AcceptMultipleValues {
128 			get { return accept_multiple_values; }
129 			set { accept_multiple_values = value; }
130 		}
131 
OnClosingItem()132 		public void OnClosingItem ()
133 		{
134 			// somewhat hacky state change to not reject StartMember->EndMember.
135 			if (state == XamlWriteState.MemberStarted)
136 				state = XamlWriteState.ValueWritten;
137 		}
138 
EndMember()139 		public void EndMember ()
140 		{
141 			RejectNamespaces (XamlNodeType.EndMember);
142 			CheckState (XamlNodeType.EndMember);
143 			state = XamlWriteState.MemberDone;
144 		}
145 
EndObject(bool hasMoreNodes)146 		public void EndObject (bool hasMoreNodes)
147 		{
148 			RejectNamespaces (XamlNodeType.EndObject);
149 			CheckState (XamlNodeType.EndObject);
150 			state = hasMoreNodes ? XamlWriteState.ObjectWritten : allow_multiple_results ? XamlWriteState.Initial : XamlWriteState.End;
151 		}
152 
GetObject()153 		public void GetObject ()
154 		{
155 			CheckState (XamlNodeType.GetObject);
156 			RejectNamespaces (XamlNodeType.GetObject);
157 			state = XamlWriteState.MemberDone;
158 		}
159 
StartMember()160 		public void StartMember ()
161 		{
162 			CheckState (XamlNodeType.StartMember);
163 			state = XamlWriteState.MemberStarted;
164 			ns_pushed = false;
165 		}
166 
StartObject()167 		public void StartObject ()
168 		{
169 			CheckState (XamlNodeType.StartObject);
170 			state = XamlWriteState.ObjectStarted;
171 			ns_pushed = false;
172 		}
173 
Value()174 		public void Value ()
175 		{
176 			CheckState (XamlNodeType.Value);
177 			RejectNamespaces (XamlNodeType.Value);
178 			state = XamlWriteState.ValueWritten;
179 		}
180 
Namespace()181 		public void Namespace ()
182 		{
183 			if (!allow_ns_at_value && (state == XamlWriteState.ValueWritten || state == XamlWriteState.ObjectStarted))
184 				throw CreateError (String.Format ("Namespace declarations cannot be written at {0} state", state));
185 			ns_pushed = true;
186 		}
187 
NamespaceCleanedUp()188 		public void NamespaceCleanedUp ()
189 		{
190 			ns_pushed = false;
191 		}
192 
CheckState(XamlNodeType next)193 		void CheckState (XamlNodeType next)
194 		{
195 			switch (state) {
196 			case XamlWriteState.Initial:
197 				switch (next) {
198 				case XamlNodeType.StartObject:
199 					return;
200 				}
201 				break;
202 			case XamlWriteState.ObjectStarted:
203 				switch (next) {
204 				case XamlNodeType.StartMember:
205 				case XamlNodeType.EndObject:
206 					return;
207 				}
208 				break;
209 			case XamlWriteState.MemberStarted:
210 				switch (next) {
211 				case XamlNodeType.StartObject:
212 				case XamlNodeType.Value:
213 				case XamlNodeType.GetObject:
214 					return;
215 				case XamlNodeType.EndMember:
216 					if (allow_empty_member)
217 						return;
218 					break;
219 				}
220 				break;
221 			case XamlWriteState.ObjectWritten:
222 				switch (next) {
223 				case XamlNodeType.StartObject:
224 				case XamlNodeType.Value:
225 				case XamlNodeType.EndMember:
226 					return;
227 				}
228 				break;
229 			case XamlWriteState.ValueWritten:
230 				switch (next) {
231 				case XamlNodeType.Value:
232 					if (allow_parallel_values | accept_multiple_values)
233 						return;
234 					break;
235 				case XamlNodeType.StartObject:
236 					if (allow_object_after_value)
237 						return;
238 					break;
239 				case XamlNodeType.EndMember:
240 					return;
241 				}
242 				break;
243 			case XamlWriteState.MemberDone:
244 				switch (next) {
245 				case XamlNodeType.StartMember:
246 				case XamlNodeType.EndObject:
247 					return;
248 				}
249 				break;
250 			}
251 			throw CreateError (String.Format ("{0} is not allowed at current state {1}", next, state));
252 		}
253 
RejectNamespaces(XamlNodeType next)254 		void RejectNamespaces (XamlNodeType next)
255 		{
256 			if (ns_pushed) {
257 				// strange, but on WriteEndMember it throws XamlXmlWriterException, while for other nodes it throws IOE.
258 				string msg = String.Format ("Namespace declarations cannot be written before {0}", next);
259 				if (next == XamlNodeType.EndMember)
260 					throw CreateError (msg);
261 				else
262 					throw CreateNamespaceError (msg);
263 			}
264 		}
265 
CreateError(string msg)266 		public abstract Exception CreateError (string msg);
CreateNamespaceError(string msg)267 		public abstract Exception CreateNamespaceError (string msg);
268 	}
269 }
270