2 // Copyright (C) 2010 Novell Inc. http://novell.com
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:
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
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.
24 // To use this under .NET, compile sources as:
26 // dmcs -d:DOTNET -r:System.Xaml -debug System.Xaml/XamlXmlWriter.cs System.Xaml/TypeExtensionMethods.cs System.Xaml/XamlWriterStateManager.cs System.Xaml/XamlNameResolver.cs System.Xaml/PrefixLookup.cs System.Xaml/ValueSerializerContext.cs ../../build/common/MonoTODOAttribute.cs Test/System.Xaml/TestedTypes.cs
29 using System.Collections.Generic;
30 using System.ComponentModel;
31 using System.Globalization;
34 using System.Reflection;
36 using System.Windows.Markup;
38 using System.Xaml.Schema;
42 // XamlWriter expects write operations in premised orders.
43 // The most basic one is:
45 // [NamespaceDeclaration]* -> StartObject -> [ StartMember -> Value | StartObject ... EndObject -> EndMember ]* -> EndObject
48 // [NamespaceDeclaration]* -> StartObject -> (members)* -> StartMember XamlLanguage.Items -> [ StartObject ... EndObject ]* -> EndMember -> EndObject
50 // For MarkupExtension with PositionalParameters:
52 // [NamespaceDeclaration]* -> StartObject -> StartMember XamlLanguage.PositionalParameters -> [Value]* -> EndMember -> ... -> EndObject
61 public class XamlXmlWriter : XamlWriter
63 public XamlXmlWriter (Stream stream, XamlSchemaContext schemaContext)
64 : this (stream, schemaContext, null)
68 public XamlXmlWriter (Stream stream, XamlSchemaContext schemaContext, XamlXmlWriterSettings settings)
69 : this (XmlWriter.Create (stream), schemaContext, null)
73 public XamlXmlWriter (TextWriter textWriter, XamlSchemaContext schemaContext)
74 : this (XmlWriter.Create (textWriter), schemaContext, null)
78 public XamlXmlWriter (TextWriter textWriter, XamlSchemaContext schemaContext, XamlXmlWriterSettings settings)
79 : this (XmlWriter.Create (textWriter), schemaContext, null)
83 public XamlXmlWriter (XmlWriter xmlWriter, XamlSchemaContext schemaContext)
84 : this (xmlWriter, schemaContext, null)
88 public XamlXmlWriter (XmlWriter xmlWriter, XamlSchemaContext schemaContext, XamlXmlWriterSettings settings)
90 if (xmlWriter == null)
91 throw new ArgumentNullException ("xmlWriter");
92 if (schemaContext == null)
93 throw new ArgumentNullException ("schemaContext");
95 this.sctx = schemaContext;
96 this.settings = settings ?? new XamlXmlWriterSettings ();
97 var manager = new XamlWriterStateManager<XamlXmlWriterException, InvalidOperationException> (true);
98 intl = new XamlXmlWriterInternal (xmlWriter, sctx, manager);
102 XamlSchemaContext sctx;
103 XamlXmlWriterSettings settings;
105 XamlXmlWriterInternal intl;
107 public override XamlSchemaContext SchemaContext {
111 public XamlXmlWriterSettings Settings {
112 get { return settings; }
115 protected override void Dispose (bool disposing)
122 if (settings.CloseOutput)
131 public override void WriteGetObject ()
133 intl.WriteGetObject ();
136 public override void WriteNamespace (NamespaceDeclaration namespaceDeclaration)
138 intl.WriteNamespace (namespaceDeclaration);
141 public override void WriteStartObject (XamlType xamlType)
143 intl.WriteStartObject (xamlType);
146 public override void WriteValue (object value)
148 if (value != null && !(value is string))
149 throw new ArgumentException ("Non-string value cannot be written.");
151 intl.WriteValue (value);
154 public override void WriteStartMember (XamlMember property)
156 intl.WriteStartMember (property);
159 public override void WriteEndObject ()
161 intl.WriteEndObject ();
164 public override void WriteEndMember ()
166 intl.WriteEndMember ();
170 internal abstract class XamlWriterInternalBase
172 public XamlWriterInternalBase (XamlSchemaContext schemaContext, XamlWriterStateManager manager)
174 this.sctx = schemaContext;
175 this.manager = manager;
176 var p = new PrefixLookup (sctx) { IsCollectingNamespaces = true }; // it does not raise unknown namespace error.
177 service_provider = new ValueSerializerContext (p, schemaContext);
180 XamlSchemaContext sctx;
181 XamlWriterStateManager manager;
183 internal IValueSerializerContext service_provider;
185 internal Stack<ObjectState> object_states = new Stack<ObjectState> ();
186 internal PrefixLookup prefix_lookup {
187 get { return (PrefixLookup) service_provider.GetService (typeof (INamespacePrefixLookup)); }
190 List<NamespaceDeclaration> namespaces {
191 get { return prefix_lookup.Namespaces; }
194 internal class ObjectState
196 public XamlType Type;
198 public List<object> Contents = new List<object> ();
199 public List<MemberAndValue> WrittenProperties = new List<MemberAndValue> ();
200 public bool IsInstantiated;
201 public bool IsGetObject;
202 public int PositionalParameterIndex = -1;
204 public string FactoryMethod;
205 public List<object> Arguments = new List<object> ();
208 internal class MemberAndValue
210 public MemberAndValue (XamlMember xm)
215 public XamlMember Member;
217 public object KeyValue;
218 public AllowedMemberLocations OccuredAs = AllowedMemberLocations.None;
221 public void CloseAll ()
223 while (object_states.Count > 0) {
224 switch (manager.State) {
225 case XamlWriteState.MemberDone:
226 case XamlWriteState.ObjectStarted: // StartObject without member
229 case XamlWriteState.ValueWritten:
230 case XamlWriteState.ObjectWritten:
231 case XamlWriteState.MemberStarted: // StartMember without content
232 manager.OnClosingItem ();
236 throw new NotImplementedException (manager.State.ToString ()); // there shouldn't be anything though
241 internal string GetPrefix (string ns)
243 foreach (var nd in namespaces)
244 if (nd.Namespace == ns)
249 protected MemberAndValue CurrentMemberState {
250 get { return object_states.Count > 0 ? object_states.Peek ().WrittenProperties.LastOrDefault () : null; }
253 protected XamlMember CurrentMember {
255 var mv = CurrentMemberState;
256 return mv != null ? mv.Member : null;
260 public void WriteGetObject ()
262 manager.GetObject ();
264 var xm = CurrentMember;
266 if (!xm.Type.IsCollection)
267 throw new InvalidOperationException (String.Format ("WriteGetObject method can be invoked only when current member '{0}' is of collection type", xm.Name));
269 var state = new ObjectState () {Type = xm.Type, IsGetObject = true};
271 object_states.Push (state);
276 public void WriteNamespace (NamespaceDeclaration namespaceDeclaration)
278 if (namespaceDeclaration == null)
279 throw new ArgumentNullException ("namespaceDeclaration");
281 manager.Namespace ();
283 namespaces.Add (namespaceDeclaration);
284 OnWriteNamespace (namespaceDeclaration);
287 public void WriteStartObject (XamlType xamlType)
289 if (xamlType == null)
290 throw new ArgumentNullException ("xamlType");
292 manager.StartObject ();
294 OnWriteStartObject (xamlType);
296 var cstate = new ObjectState () {Type = xamlType, IsInstantiated = false};
297 object_states.Push (cstate);
300 public void WriteValue (object value)
302 if (value != null && !(value is string))
303 throw new ArgumentException ("Non-string value cannot be written.");
307 OnWriteValue (value);
310 public void WriteStartMember (XamlMember property)
312 if (property == null)
313 throw new ArgumentNullException ("property");
315 manager.StartMember ();
316 if (property == XamlLanguage.PositionalParameters)
317 // this is an exception that indicates the state manager to accept more than values within this member.
318 manager.AcceptMultipleValues = true;
320 var state = object_states.Peek ();
321 var wpl = state.WrittenProperties;
322 if (wpl.Any (wp => wp.Member == property))
323 throw new XamlDuplicateMemberException (String.Format ("Property '{0}' is already set to this '{1}' object", property, object_states.Peek ().Type));
324 wpl.Add (new MemberAndValue (property));
325 if (property == XamlLanguage.PositionalParameters)
326 state.PositionalParameterIndex = 0;
328 OnWriteStartMember (property);
331 public void WriteEndObject ()
333 manager.EndObject (object_states.Count > 1);
337 object_states.Pop ();
340 public void WriteEndMember ()
342 manager.EndMember ();
346 var state = object_states.Peek ();
347 if (CurrentMember == XamlLanguage.PositionalParameters) {
348 manager.AcceptMultipleValues = false;
349 state.PositionalParameterIndex = -1;
351 var contents = state.Contents;
356 protected abstract void OnWriteEndObject ();
358 protected abstract void OnWriteEndMember ();
360 protected abstract void OnWriteStartObject (XamlType xamlType);
362 protected abstract void OnWriteGetObject ();
364 protected abstract void OnWriteStartMember (XamlMember xm);
366 protected abstract void OnWriteValue (object value);
368 protected abstract void OnWriteNamespace (NamespaceDeclaration nd);
370 bool IsAllowedType (XamlType xt, object value)
372 // FIXME: not sure if it is correct
377 xt.UnderlyingType == null ||
378 xt.UnderlyingType.IsInstanceOfType (value) ||
379 value == null && xt == XamlLanguage.Null ||
380 xt.IsMarkupExtension && IsAllowedType (xt.MarkupExtensionReturnType, value);
383 protected string GetValueString (XamlMember xm, object value)
385 var xt = value == null ? XamlLanguage.Null : sctx.GetXamlType (value.GetType ());
386 var vs = xm.ValueSerializer ?? xt.ValueSerializer;
388 return vs.ConverterInstance.ConvertToString (value, service_provider);
390 throw new XamlXmlWriterException (String.Format ("Value type is '{0}' but it must be either string or any type that is convertible to string indicated by TypeConverterAttribute.", value != null ? value.GetType () : null));
394 // specific implementation
395 class XamlXmlWriterInternal : XamlWriterInternalBase
397 const string Xmlns2000Namespace = "http://www.w3.org/2000/xmlns/";
399 public XamlXmlWriterInternal (XmlWriter w, XamlSchemaContext schemaContext, XamlWriterStateManager manager)
400 : base (schemaContext, manager)
403 this.sctx = schemaContext;
407 XamlSchemaContext sctx;
409 // Here's a complication.
410 // - local_nss holds namespace declarations that are written *before* current element.
411 // - local_nss2 holds namespace declarations that are wrtten *after* current element.
412 // (current element == StartObject or StartMember)
413 // - When the next element or content is being written, local_nss items are written *within* current element, BUT after all attribute members are written. Hence I had to preserve all those nsdecls at such late.
414 // - When current *start* element is closed, then copy local_nss2 items into local_nss.
415 // - When there was no children i.e. end element immediately occurs, local_nss should be written at this stage too, and local_nss2 are *ignored*.
416 List<NamespaceDeclaration> local_nss = new List<NamespaceDeclaration> ();
417 List<NamespaceDeclaration> local_nss2 = new List<NamespaceDeclaration> ();
418 bool inside_toplevel_positional_parameter;
419 bool inside_attribute_object;
421 protected override void OnWriteEndObject ()
423 WritePendingStartMember (XamlNodeType.EndObject);
425 var state = object_states.Count > 0 ? object_states.Peek () : null;
426 if (state != null && state.IsGetObject) {
428 state.IsGetObject = false;
429 } else if (w.WriteState == WriteState.Attribute) {
431 inside_attribute_object = false;
433 WritePendingNamespaces ();
434 w.WriteEndElement ();
438 protected override void OnWriteEndMember ()
440 WritePendingStartMember (XamlNodeType.EndMember);
442 var member = CurrentMember;
443 if (member == XamlLanguage.Initialization)
445 if (member == XamlLanguage.Items)
447 if (member.Type.IsCollection && member.IsReadOnly)
449 if (member.DeclaringType != null && member == member.DeclaringType.ContentProperty)
452 if (inside_toplevel_positional_parameter) {
453 w.WriteEndAttribute ();
454 inside_toplevel_positional_parameter = false;
455 } else if (inside_attribute_object) {
456 // do nothing. It didn't open this attribute.
458 switch (CurrentMemberState.OccuredAs) {
459 case AllowedMemberLocations.Attribute:
460 w.WriteEndAttribute ();
462 case AllowedMemberLocations.MemberElement:
463 WritePendingNamespaces ();
464 w.WriteEndElement ();
470 protected override void OnWriteStartObject (XamlType xamlType)
472 WritePendingStartMember (XamlNodeType.StartObject);
474 string ns = xamlType.PreferredXamlNamespace;
475 string prefix = GetPrefix (ns); // null prefix is not rejected...
477 if (w.WriteState == WriteState.Attribute) {
480 if (!String.IsNullOrEmpty (prefix)) {
481 w.WriteString (prefix);
484 string name = ns == XamlLanguage.Xaml2006Namespace ? xamlType.GetInternalXmlName () : xamlType.Name;
485 w.WriteString (name);
486 // space between type and first member (if any).
487 if (xamlType.IsMarkupExtension && xamlType.GetSortedConstructorArguments ().GetEnumerator ().MoveNext ())
490 WritePendingNamespaces ();
491 w.WriteStartElement (prefix, xamlType.GetInternalXmlName (), xamlType.PreferredXamlNamespace);
492 var l = xamlType.TypeArguments;
494 w.WriteStartAttribute ("x", "TypeArguments", XamlLanguage.Xaml2006Namespace);
495 for (int i = 0; i < l.Count; i++) {
497 w.WriteString (", ");
498 w.WriteString (new XamlTypeName (l [i]).ToString (prefix_lookup));
500 w.WriteEndAttribute ();
505 protected override void OnWriteGetObject ()
507 WritePendingStartMember (XamlNodeType.GetObject);
509 // Other than above, nothing to do.
512 void WritePendingStartMember (XamlNodeType nodeType)
514 var cm = CurrentMemberState;
515 if (cm == null || cm.OccuredAs != AllowedMemberLocations.Any)
518 var state = object_states.Peek ();
519 if (nodeType == XamlNodeType.Value)
520 OnWriteStartMemberAttribute (state.Type, CurrentMember);
522 OnWriteStartMemberElement (state.Type, CurrentMember);
525 protected override void OnWriteStartMember (XamlMember member)
527 if (member == XamlLanguage.Initialization)
529 if (member == XamlLanguage.Items)
531 if (member.Type.IsCollection && member.IsReadOnly)
533 if (member.DeclaringType != null && member == member.DeclaringType.ContentProperty)
536 var state = object_states.Peek ();
538 // Top-level positional parameters are somehow special.
539 // - If it has only one parameter, it is written as an
540 // attribute using the actual argument's member name.
541 // - If there are more than one, then it is an error at
542 // the second constructor argument.
543 // (Here "top-level" means an object that involves
544 // StartObject i.e. the root or a collection item.)
545 var posprms = member == XamlLanguage.PositionalParameters && IsAtTopLevelObject () && object_states.Peek ().Type.HasPositionalParameters (service_provider) ? state.Type.GetSortedConstructorArguments ().GetEnumerator () : null;
546 if (posprms != null) {
548 var arg = posprms.Current;
549 w.WriteStartAttribute (arg.Name);
550 inside_toplevel_positional_parameter = true;
552 else if (w.WriteState == WriteState.Attribute)
553 inside_attribute_object = true;
555 if (w.WriteState == WriteState.Attribute) {
556 if (state.PositionalParameterIndex < 0) {
558 w.WriteString (member.Name);
562 switch (IsAttribute (state.Type, member)) {
563 case AllowedMemberLocations.Attribute:
564 OnWriteStartMemberAttribute (state.Type, member);
566 case AllowedMemberLocations.MemberElement:
567 OnWriteStartMemberElement (state.Type, member);
569 default: // otherwise - pending output
570 CurrentMemberState.OccuredAs = AllowedMemberLocations.Any; // differentiate from .None
576 bool IsAtTopLevelObject ()
578 if (object_states.Count == 1)
580 var tmp = object_states.Pop ();
581 var parentMember = object_states.Peek ().WrittenProperties.LastOrDefault ().Member;
582 object_states.Push (tmp);
584 return parentMember == XamlLanguage.Items;
587 AllowedMemberLocations IsAttribute (XamlType ownerType, XamlMember xm)
591 if (xm == XamlLanguage.Key) {
592 var tmp = object_states.Pop ();
593 mt = object_states.Peek ().Type.KeyType;
594 object_states.Push (tmp);
597 if (xm == XamlLanguage.Initialization)
598 return AllowedMemberLocations.MemberElement;
599 if (mt.HasPositionalParameters (service_provider))
600 return AllowedMemberLocations.Attribute;
601 if (w.WriteState == WriteState.Content)
602 return AllowedMemberLocations.MemberElement;
603 if (xt.IsDictionary && xm != XamlLanguage.Key)
604 return AllowedMemberLocations.MemberElement; // as each item holds a key.
606 var xd = xm as XamlDirective;
607 if (xd != null && (xd.AllowedLocation & AllowedMemberLocations.Attribute) == 0)
608 return AllowedMemberLocations.MemberElement;
610 // surprisingly, WriteNamespace() can affect this.
611 if (local_nss2.Count > 0)
612 return AllowedMemberLocations.MemberElement;
614 // Somehow such a "stranger" is processed as an element.
615 if (xd == null && !xt.GetAllMembers ().Contains (xm))
616 return AllowedMemberLocations.None;
618 if (xm.IsContentValue (service_provider) || xt.IsContentValue (service_provider))
619 return AllowedMemberLocations.Attribute;
621 return AllowedMemberLocations.MemberElement;
624 void OnWriteStartMemberElement (XamlType xt, XamlMember xm)
626 CurrentMemberState.OccuredAs = AllowedMemberLocations.MemberElement;
627 string prefix = GetPrefix (xm.PreferredXamlNamespace);
628 string name = xm.IsDirective ? xm.Name : String.Concat (xt.GetInternalXmlName (), ".", xm.Name);
629 WritePendingNamespaces ();
630 w.WriteStartElement (prefix, name, xm.PreferredXamlNamespace);
633 void OnWriteStartMemberAttribute (XamlType xt, XamlMember xm)
635 CurrentMemberState.OccuredAs = AllowedMemberLocations.Attribute;
636 if (xt.PreferredXamlNamespace == xm.PreferredXamlNamespace &&
637 !(xm is XamlDirective)) // e.g. x:Key inside x:Int should not be written as Key.
638 w.WriteStartAttribute (xm.Name);
640 string prefix = GetPrefix (xm.PreferredXamlNamespace);
641 w.WriteStartAttribute (prefix, xm.Name, xm.PreferredXamlNamespace);
645 protected override void OnWriteValue (object value)
647 XamlMember xm = CurrentMember;
648 WritePendingStartMember (XamlNodeType.Value);
650 if (w.WriteState != WriteState.Attribute)
651 WritePendingNamespaces ();
653 string s = GetValueString (xm, value);
655 var state = object_states.Peek ();
656 switch (state.PositionalParameterIndex) {
660 state.PositionalParameterIndex++;
663 if (inside_toplevel_positional_parameter)
664 throw new XamlXmlWriterException (String.Format ("The XAML reader input has more than one positional parameter values within a top-level object {0} because it tries to write all of the argument values as an attribute value of the first argument. While XamlObjectReader can read such an object, XamlXmlWriter cannot write such an object to XML.", state.Type));
666 state.PositionalParameterIndex++;
667 w.WriteString (", ");
673 protected override void OnWriteNamespace (NamespaceDeclaration nd)
678 void WritePendingNamespaces ()
680 foreach (var nd in local_nss) {
681 if (String.IsNullOrEmpty (nd.Prefix))
682 w.WriteAttributeString ("xmlns", nd.Namespace);
684 w.WriteAttributeString ("xmlns", nd.Prefix, Xmlns2000Namespace, nd.Namespace);
688 local_nss.AddRange (local_nss2);
694 internal static class TypeExtensionMethods2
696 static TypeExtensionMethods2 ()
698 SpecialNames = new SpecialTypeNameList ();
701 public static string GetInternalXmlName (this XamlType type)
703 if (type.IsMarkupExtension && type.Name.EndsWith ("Extension", StringComparison.Ordinal))
704 return type.Name.Substring (0, type.Name.Length - 9);
705 var stn = SpecialNames.FirstOrDefault (s => s.Type == type);
706 return stn != null ? stn.Name : type.Name;
709 // FIXME: I'm not sure if these "special names" should be resolved like this. I couldn't find any rule so far.
710 internal static readonly SpecialTypeNameList SpecialNames;
712 internal class SpecialTypeNameList : List<SpecialTypeName>
714 internal SpecialTypeNameList ()
716 Add (new SpecialTypeName ("Member", XamlLanguage.Member));
717 Add (new SpecialTypeName ("Property", XamlLanguage.Property));
720 public XamlType Find (string name, string ns)
722 if (ns != XamlLanguage.Xaml2006Namespace)
724 var stn = this.FirstOrDefault (s => s.Name == name);
725 return stn != null ? stn.Type : null;
729 internal class SpecialTypeName
731 public SpecialTypeName (string name, XamlType type)
737 public string Name { get; private set; }
738 public XamlType Type { get; private set; }