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 using System.Collections;
25 using System.Collections.Generic;
26 using System.Globalization;
28 using System.Reflection;
29 using System.Windows.Markup;
30 using System.Xaml.Schema;
35 internal class PrefixLookup : INamespacePrefixLookup
37 public PrefixLookup (XamlSchemaContext schemaContext)
40 Namespaces = new List<NamespaceDeclaration> ();
43 XamlSchemaContext sctx;
45 public bool IsCollectingNamespaces { get; set; }
47 public List<NamespaceDeclaration> Namespaces { get; private set; }
49 public string LookupPrefix (string ns)
51 var nd = Namespaces.FirstOrDefault (n => n.Namespace == ns);
52 if (nd == null && IsCollectingNamespaces)
53 return AddNamespace (ns);
55 return nd != null ? nd.Prefix : null;
58 public string AddNamespace (string ns)
62 if (ns == XamlLanguage.Xaml2006Namespace)
64 else if (!l.Any (i => i.Prefix == String.Empty))
65 prefix = String.Empty;
66 else if ((s = GetAcronym (ns)) != null && !l.Any (i => i.Prefix == s))
69 prefix = sctx.GetPreferredPrefix (ns);
70 l.Add (new NamespaceDeclaration (ns, prefix));
74 string GetAcronym (string ns)
76 int idx = ns.IndexOf (';');
79 string pre = "clr-namespace:";
80 if (!ns.StartsWith (pre, StringComparison.Ordinal))
82 ns = ns.Substring (pre.Length, idx - pre.Length);
84 foreach (string nsp in ns.Split ('.'))
87 return ac.Length > 0 ? ac.ToLower (CultureInfo.InvariantCulture) : null;
91 internal struct XamlNodeIterator
93 static readonly XamlObject null_object = new XamlObject (XamlLanguage.Null, null);
95 public XamlNodeIterator (object root, XamlSchemaContext schemaContext, PrefixLookup prefixLookup)
99 this.prefix_lookup = prefixLookup;
102 XamlSchemaContext ctx;
104 // FIXME: this will become IServiceProvider.
105 PrefixLookup prefix_lookup;
107 public XamlSchemaContext SchemaContext {
111 XamlType GetType (object obj)
113 return ctx.GetXamlType (new InstanceContext (obj).GetWrappedValue ().GetType ());
116 // returns StartObject, StartMember, Value, EndMember and EndObject. (NamespaceDeclaration is not included)
117 public IEnumerable<XamlNodeInfo> GetNodes ()
119 var xobj = new XamlObject (GetType (root), root);
120 foreach (var node in GetNodes (null, xobj))
124 IEnumerable<XamlNodeInfo> GetNodes (XamlMember xm, XamlObject xobj)
126 return GetNodes (xm, xobj, null);
129 IEnumerable<XamlNodeInfo> GetNodes (XamlMember xm, XamlObject xobj, XamlType overrideMemberType)
131 // collection items: each item is exposed as a standalone object that has StartObject, EndObject and contents.
132 if (xm == XamlLanguage.Items) {
133 foreach (var xn in GetItemsNodes (xm, xobj))
138 // Arguments: each argument is written as a standalone object
139 if (xm == XamlLanguage.Arguments) {
140 foreach (var argm in xobj.Type.GetSortedConstructorArguments ()) {
141 var argv = argm.Invoker.GetValue (xobj.GetRawValue ());
142 var xarg = new XamlObject (argm.Type, argv);
143 foreach (var cn in GetNodes (null, xarg))
149 // PositionalParameters: items are from constructor arguments, and are all in simple string value, written as Value node sequentially.
150 if (xm == XamlLanguage.PositionalParameters) {
151 foreach (var argm in xobj.Type.GetSortedConstructorArguments ()) {
152 // Unlike XamlLanguage.Items, it only outputs string value. So, convert values here.
153 var argv = argm.Type.GetStringValue (xobj.GetMemberValue (argm), prefix_lookup);
154 yield return new XamlNodeInfo ((string) argv);
159 if (xm == XamlLanguage.Initialization) {
160 yield return new XamlNodeInfo (xobj.Type.GetStringValue (xobj.GetRawValue (), prefix_lookup));
164 // Value - only for non-top-level node (thus xm != null)
166 // overrideMemberType is (so far) used for XamlLanguage.Key.
167 var xtt = overrideMemberType ?? xm.Type;
168 if (xtt.IsContentValue ()) {
169 // though null value is special: it is written as a standalone object.
170 var val = xobj.GetRawValue ();
172 foreach (var xn in GetNodes (null, null_object))
175 yield return new XamlNodeInfo (xtt.GetStringValue (val, prefix_lookup));
180 // collection items: return GetObject and Items.
181 if (xm != null && xm.Type.IsCollection && xm.IsReadOnly) {
182 yield return new XamlNodeInfo (XamlNodeType.GetObject, xobj);
183 // Write Items member only when there are items (i.e. do not write it if it is empty).
184 var xnm = new XamlNodeMember (xobj, XamlLanguage.Items);
185 var en = GetNodes (XamlLanguage.Items, xnm.Value).GetEnumerator ();
186 if (en.MoveNext ()) {
187 yield return new XamlNodeInfo (XamlNodeType.StartMember, xnm);
189 yield return en.Current;
190 } while (en.MoveNext ());
191 yield return new XamlNodeInfo (XamlNodeType.EndMember, xnm);
193 yield return new XamlNodeInfo (XamlNodeType.EndObject, xobj);
196 yield return new XamlNodeInfo (XamlNodeType.StartObject, xobj);
197 foreach (var xn in GetObjectMemberNodes (xobj))
199 yield return new XamlNodeInfo (XamlNodeType.EndObject, xobj);
203 IEnumerable<XamlNodeInfo> GetObjectMemberNodes (XamlObject xobj)
205 var xce = xobj.Children ().GetEnumerator ();
206 while (xce.MoveNext ()) {
207 // XamlLanguage.Items does not show up if the content is empty.
208 if (xce.Current.Member == XamlLanguage.Items)
209 if (!GetNodes (xce.Current.Member, xce.Current.Value).GetEnumerator ().MoveNext ())
212 // Other collections as well, but needs different iteration (as nodes contain GetObject and EndObject).
213 if (xce.Current.Member.IsReadOnly && xce.Current.Member.Type != null && xce.Current.Member.Type.IsCollection) {
214 var e = GetNodes (xce.Current.Member, xce.Current.Value).GetEnumerator ();
215 if (!(e.MoveNext () && e.MoveNext () && e.MoveNext ())) // GetObject, EndObject and more
219 yield return new XamlNodeInfo (XamlNodeType.StartMember, xce.Current);
220 foreach (var cn in GetNodes (xce.Current.Member, xce.Current.Value))
222 yield return new XamlNodeInfo (XamlNodeType.EndMember, xce.Current);
226 IEnumerable<XamlNodeInfo> GetItemsNodes (XamlMember xm, XamlObject xobj)
228 var ie = xobj.Type.Invoker.GetItems (xobj.GetRawValue ());
229 while (ie.MoveNext ()) {
230 var iobj = ie.Current;
231 // If it is dictionary, then retrieve the key, and rewrite the item as the Value part.
233 XamlNodeMember xknm = default (XamlNodeMember);
234 if (xobj.Type.IsDictionary) {
235 Type kvpType = iobj.GetType ();
236 bool isNonGeneric = kvpType == typeof (DictionaryEntry);
237 var kp = isNonGeneric ? null : kvpType.GetProperty ("Key");
238 var vp = isNonGeneric ? null : kvpType.GetProperty ("Value");
239 xknm = new XamlNodeMember (xobj, XamlLanguage.Key);
240 ikey = isNonGeneric ? ((DictionaryEntry) iobj).Key : kp.GetValue (iobj, null);
241 iobj = isNonGeneric ? ((DictionaryEntry) iobj).Value : vp.GetValue (iobj, null);
244 var xiobj = new XamlObject (GetType (iobj), iobj);
245 var en = GetNodes (null, xiobj).GetEnumerator ();
247 yield return en.Current;
249 // Key member is written *inside* the item object.
250 yield return new XamlNodeInfo (XamlNodeType.StartMember, xknm);
251 foreach (var xn in GetNodes (XamlLanguage.Key, new XamlObject (GetType (ikey), ikey), xobj.Type.KeyType))
253 yield return new XamlNodeInfo (XamlNodeType.EndMember, xknm);
255 while (en.MoveNext ())
256 yield return en.Current;
260 // Namespace retrieval.
261 // It is iterated before iterating the actual object nodes,
262 // and results are cached for use in XamlObjectReader.
263 public void CollectNamespaces ()
265 prefix_lookup.IsCollectingNamespaces = true;
266 foreach (var xn in GetNodes ()) {
267 if (xn.NodeType == XamlNodeType.GetObject)
268 continue; // it is out of consideration here.
269 if (xn.NodeType == XamlNodeType.StartObject) {
270 foreach (var ns in NamespacesInType (xn.Object.Type))
271 prefix_lookup.LookupPrefix (ns);
272 } else if (xn.NodeType == XamlNodeType.StartMember) {
273 if (xn.Member.Member is XamlDirective)
275 prefix_lookup.LookupPrefix (xn.Member.Member.PreferredXamlNamespace);
277 if (xn.NodeType == XamlNodeType.Value && xn.Value is Type)
278 // this tries to lookup existing prefix, and if there isn't any, then adds a new declaration.
279 XamlLanguage.Type.GetStringValue (xn.Value, prefix_lookup);
283 prefix_lookup.Namespaces.Sort ((nd1, nd2) => String.CompareOrdinal (nd1.Prefix, nd2.Prefix));
284 prefix_lookup.IsCollectingNamespaces = false;
287 IEnumerable<string> NamespacesInType (XamlType xt)
289 yield return xt.PreferredXamlNamespace;
290 if (xt.TypeArguments != null) {
291 // It is for x:TypeArguments
292 yield return XamlLanguage.Xaml2006Namespace;
293 foreach (var targ in xt.TypeArguments)
294 foreach (var ns in NamespacesInType (targ))
300 internal struct XamlNodeInfo
302 public XamlNodeInfo (XamlNodeType nodeType, XamlObject value)
304 node_type = nodeType;
306 member = default (XamlNodeMember);
309 public XamlNodeInfo (XamlNodeType nodeType, XamlNodeMember member)
311 node_type = nodeType;
312 this.value = default (XamlObject);
313 this.member = member;
316 public XamlNodeInfo (string value)
318 node_type = XamlNodeType.Value;
320 member = default (XamlNodeMember);
323 XamlNodeType node_type;
325 XamlNodeMember member;
327 public XamlNodeType NodeType {
328 get { return node_type; }
330 public XamlObject Object {
331 get { return (XamlObject) value; }
333 public XamlNodeMember Member {
334 get { return member; }
336 public object Value {
337 get { return value; }
341 internal struct XamlObject
343 public XamlObject (XamlType type, object instance)
344 : this (type, new InstanceContext (instance))
348 public XamlObject (XamlType type, InstanceContext context)
351 this.context = context;
354 readonly XamlType type;
355 readonly InstanceContext context;
357 public XamlType Type {
361 public InstanceContext Context {
362 get { return context; }
365 XamlType GetType (object obj)
367 return type.SchemaContext.GetXamlType (new InstanceContext (obj).GetWrappedValue ().GetType ());
370 public IEnumerable<XamlNodeMember> Children ()
372 // FIXME: consider XamlLanguage.Key
373 foreach (var xm in type.GetAllObjectReaderMembersByType (null))
374 yield return new XamlNodeMember (this, xm);
377 public object GetRawValue ()
379 return context.GetRawValue ();
382 public object GetWrappedValue ()
384 return context.GetWrappedValue ();
388 internal struct XamlNodeMember
390 public XamlNodeMember (XamlObject owner, XamlMember member)
393 this.member = member;
396 readonly XamlObject owner;
397 readonly XamlMember member;
399 public XamlObject Owner {
400 get { return owner; }
402 public XamlMember Member {
403 get { return member; }
405 public XamlObject Value {
407 var mv = Owner.GetMemberValue (Member);
408 return new XamlObject (GetType (mv), mv);
412 XamlType GetType (object obj)
414 return owner.Type.SchemaContext.GetXamlType (new InstanceContext (obj).GetWrappedValue ().GetType ());
418 // Its original purpose was to enable delayed reflection, but it's not supported yet.
419 internal struct InstanceContext
421 static readonly NullExtension null_value = new NullExtension ();
423 public InstanceContext (object value)
430 public object GetWrappedValue ()
432 var o = GetRawValue ();
434 // FIXME: should this manually checked, or is there any way to automate it?
438 return new ArrayExtension ((Array) o);
440 return new TypeExtension ((Type) o);
444 public object GetRawValue ()
446 return value; // so far.
450 internal static class TypeExtensionMethods2
452 static bool ExaminePositionalParametersApplicable (this XamlType type)
454 if (!type.IsMarkupExtension || type.UnderlyingType == null)
457 var args = type.GetSortedConstructorArguments ();
461 Type [] argTypes = (from arg in args select arg.Type.UnderlyingType).ToArray ();
462 if (argTypes.Any (at => at == null))
464 var ci = type.UnderlyingType.GetConstructor (argTypes);
468 // Note that this returns XamlMember which might not actually appear in XamlObjectReader. For example, XamlLanguage.Items won't be returned when there is no item in the collection.
469 public static IEnumerable<XamlMember> GetAllObjectReaderMembersByType (this XamlType type, object dictionaryKey)
471 // FIXME: find out why only TypeExtension and StaticExtension yield this directive. Seealso XamlObjectReaderTest.Read_CustomMarkupExtension*()
472 if (type == XamlLanguage.Type ||
473 type == XamlLanguage.Static ||
474 ExaminePositionalParametersApplicable (type) && type.ConstructionRequiresArguments) {
475 yield return XamlLanguage.PositionalParameters;
479 // Note that if the XamlType has the default constructor, we don't need "Arguments".
480 IEnumerable<XamlMember> args = type.ConstructionRequiresArguments ? type.GetSortedConstructorArguments () : null;
481 if (args != null && args.Any ())
482 yield return XamlLanguage.Arguments;
484 if (dictionaryKey != null)
485 yield return XamlLanguage.Key;
487 if (type.TypeConverter != null || type.IsContentValue ()) {
488 yield return XamlLanguage.Initialization;
492 if (type.IsDictionary) {
493 yield return XamlLanguage.Items;
497 foreach (var m in type.GetAllMembers ()) {
498 // do not read constructor arguments twice (they are written inside Arguments).
499 if (args != null && args.Contains (m))
501 // do not return non-public members. Not sure why .NET filters out them though.
508 if (type.IsCollection)
509 yield return XamlLanguage.Items;
513 internal static class XamlNodeExtensions
515 internal static object GetMemberValue (this XamlObject xobj, XamlMember xm)
519 // FIXME: this looks like an ugly hack. Is this really true? What if there's MarkupExtension that uses another MarkupExtension type as a member type.
520 var obj = xobj.Context.GetRawValue ();
521 if (xm == XamlLanguage.Initialization)
523 if (xm == XamlLanguage.Items) // collection itself.
525 if (xm == XamlLanguage.Arguments) // object itself
527 if (xm == XamlLanguage.PositionalParameters)
528 return xobj.GetWrappedValue (); // dummy value
529 return xm.Invoker.GetValue (xobj.GetWrappedValue ());
533 internal static ICustomAttributeProvider GetCustomAttributeProvider (this XamlType type)
535 return type.UnderlyingType;
538 internal static ICustomAttributeProvider GetCustomAttributeProvider (this XamlMember member)
540 return member.UnderlyingMember;