Merge branch 'xml-fixes' of https://github.com/myeisha/mono into myeisha-xml-fixes
[mono.git] / mcs / class / System.Xaml / System.Xaml / XamlWriterStateManager.cs
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         {
76                 public XamlWriterStateManager (bool isXmlWriter)
77                         : base (isXmlWriter)
78                 {
79                 }
80
81                 public override Exception CreateError (string msg)
82                 {
83                         return (Exception) Activator.CreateInstance (typeof (TError), new object [] {msg});
84                 }
85
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         {
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                 }
112
113                 // configuration
114                 bool allow_ns_at_value, allow_object_after_value, allow_parallel_values, allow_empty_member;
115
116                 // state
117                 XamlWriteState state = XamlWriteState.Initial;
118                 bool ns_pushed;
119                 bool accept_multiple_values; // It is PositionalParameters-specific state.
120
121                 public XamlWriteState State {
122                         get { return state; }
123                 }
124                 
125                 // FIXME: actually this property is a hack. It should preserve stacked flag values for each nested member in current tree state.
126                 public bool AcceptMultipleValues {
127                         get { return accept_multiple_values; }
128                         set { accept_multiple_values = value; }
129                 }
130
131                 public void OnClosingItem ()
132                 {
133                         // somewhat hacky state change to not reject StartMember->EndMember.
134                         if (state == XamlWriteState.MemberStarted)
135                                 state = XamlWriteState.ValueWritten;
136                 }
137
138                 public void EndMember ()
139                 {
140                         RejectNamespaces (XamlNodeType.EndMember);
141                         CheckState (XamlNodeType.EndMember);
142                         state = XamlWriteState.MemberDone;
143                 }
144
145                 public void EndObject (bool hasMoreNodes)
146                 {
147                         RejectNamespaces (XamlNodeType.EndObject);
148                         CheckState (XamlNodeType.EndObject);
149                         state = hasMoreNodes ? XamlWriteState.ObjectWritten : XamlWriteState.End;
150                 }
151
152                 public void GetObject ()
153                 {
154                         CheckState (XamlNodeType.GetObject);
155                         RejectNamespaces (XamlNodeType.GetObject);
156                         state = XamlWriteState.MemberDone;
157                 }
158
159                 public void StartMember ()
160                 {
161                         CheckState (XamlNodeType.StartMember);
162                         state = XamlWriteState.MemberStarted;
163                         ns_pushed = false;
164                 }
165
166                 public void StartObject ()
167                 {
168                         CheckState (XamlNodeType.StartObject);
169                         state = XamlWriteState.ObjectStarted;
170                         ns_pushed = false;
171                 }
172
173                 public void Value ()
174                 {
175                         CheckState (XamlNodeType.Value);
176                         RejectNamespaces (XamlNodeType.Value);
177                         state = XamlWriteState.ValueWritten;
178                 }
179
180                 public void Namespace ()
181                 {
182                         if (!allow_ns_at_value && (state == XamlWriteState.ValueWritten || state == XamlWriteState.ObjectStarted))
183                                 throw CreateError (String.Format ("Namespace declarations cannot be written at {0} state", state));
184                         ns_pushed = true;
185                 }
186
187                 public void NamespaceCleanedUp ()
188                 {
189                         ns_pushed = false;
190                 }
191
192                 void CheckState (XamlNodeType next)
193                 {
194                         switch (state) {
195                         case XamlWriteState.Initial:
196                                 switch (next) {
197                                 case XamlNodeType.StartObject:
198                                         return;
199                                 }
200                                 break;
201                         case XamlWriteState.ObjectStarted:
202                                 switch (next) {
203                                 case XamlNodeType.StartMember:
204                                 case XamlNodeType.EndObject:
205                                         return;
206                                 }
207                                 break;
208                         case XamlWriteState.MemberStarted:
209                                 switch (next) {
210                                 case XamlNodeType.StartObject:
211                                 case XamlNodeType.Value:
212                                 case XamlNodeType.GetObject:
213                                         return;
214                                 case XamlNodeType.EndMember:
215                                         if (allow_empty_member)
216                                                 return;
217                                         break;
218                                 }
219                                 break;
220                         case XamlWriteState.ObjectWritten:
221                                 switch (next) {
222                                 case XamlNodeType.StartObject:
223                                 case XamlNodeType.Value:
224                                 case XamlNodeType.EndMember:
225                                         return;
226                                 }
227                                 break;
228                         case XamlWriteState.ValueWritten:
229                                 switch (next) {
230                                 case XamlNodeType.Value:
231                                         if (allow_parallel_values | accept_multiple_values)
232                                                 return;
233                                         break;
234                                 case XamlNodeType.StartObject:
235                                         if (allow_object_after_value)
236                                                 return;
237                                         break;
238                                 case XamlNodeType.EndMember:
239                                         return;
240                                 }
241                                 break;
242                         case XamlWriteState.MemberDone:
243                                 switch (next) {
244                                 case XamlNodeType.StartMember:
245                                 case XamlNodeType.EndObject:
246                                         return;
247                                 }
248                                 break;
249                         }
250                         throw CreateError (String.Format ("{0} is not allowed at current state {1}", next, state));
251                 }
252                 
253                 void RejectNamespaces (XamlNodeType next)
254                 {
255                         if (ns_pushed) {
256                                 // strange, but on WriteEndMember it throws XamlXmlWriterException, while for other nodes it throws IOE.
257                                 string msg = String.Format ("Namespace declarations cannot be written before {0}", next);
258                                 if (next == XamlNodeType.EndMember)
259                                         throw CreateError (msg);
260                                 else
261                                         throw CreateNamespaceError (msg);
262                         }
263                 }
264
265                 public abstract Exception CreateError (string msg);
266                 public abstract Exception CreateNamespaceError (string msg);
267         }
268 }