public static IBDocument Deserialize (XDocument doc, Dictionary<string, Func<IBObject>> constructors) { var resolver = new ReferencePool (); var ibDoc = new IBDocument (); var dataEl = doc.Root.Element ("data"); ibDoc.DeserializeContents (dataEl.Elements (), constructors, resolver); resolver.ResolveAll (); if (resolver.UnresolvedReferences.Count > 0) { var sb = new StringBuilder ("Unresolved references in XIB document: "); foreach (var r in resolver.UnresolvedReferences) sb.Append (r.Id + " "); throw new InvalidOperationException (sb.ToString ()); } return ibDoc; }
public static IBDocument Deserialize(XDocument doc, Dictionary <string, Func <IBObject> > constructors) { var resolver = new ReferencePool(); var ibDoc = new IBDocument(); var dataEl = doc.Root.Element("data"); ibDoc.DeserializeContents(dataEl.Elements(), constructors, resolver); resolver.ResolveAll(); if (resolver.UnresolvedReferences.Count > 0) { var sb = new StringBuilder("Unresolved references in XIB document: "); foreach (var r in resolver.UnresolvedReferences) { sb.Append(r.Id + " "); } throw new InvalidOperationException(sb.ToString()); } return(ibDoc); }
IEnumerable<CodeTypeDeclaration> GetTypes (IBDocument doc, CodeDomProvider provider, CodeGeneratorOptions options) { object outVar; UnknownIBObject objects; if (!doc.Properties.TryGetValue ("IBDocument.Objects", out outVar) || (objects = outVar as UnknownIBObject) == null) return new CodeTypeDeclaration[0]; //process the connection records NSMutableArray connectionRecords; if (!objects.Properties.TryGetValue ("connectionRecords", out outVar) || (connectionRecords = outVar as NSMutableArray) == null) return new CodeTypeDeclaration[0]; //group connection records by type ref ID var typeRecords = new Dictionary<int,List<IBConnectionRecord>> (); foreach (var record in connectionRecords.Values.OfType<IBConnectionRecord> ()) { //get the type this member belongs in var ev = record.Connection as IBActionConnection; var outlet = record.Connection as IBOutletConnection; if (outlet == null && ev == null) { //not a recognised connection type. probably a desktop xib continue; } int? typeIndex = ((IBObject)(ev != null ? ev.Destination.Reference : outlet.Source.Reference)).Id; if (typeIndex == null) throw new InvalidOperationException ("Connection " + record.ConnectionId + " references null object ID"); List<IBConnectionRecord> records; if (!typeRecords.TryGetValue (typeIndex.Value, out records)) typeRecords[typeIndex.Value] = records = new List<IBConnectionRecord> (); records.Add (record); } //grab the custom class names, keyed by object ID var classNames = new Dictionary<int, string> (); var flattenedProperties = (NSMutableDictionary) objects.Properties ["flattenedProperties"]; foreach (var pair in flattenedProperties.Values) { string keyStr = (string)pair.Key; if (!keyStr.EndsWith (".CustomClassName")) continue; int key = int.Parse (keyStr.Substring (0, keyStr.IndexOf ('.'))); string name = (string)pair.Value; //HACK: why does IB not generate partial classes for UIApplication or UIResponder? I guess we should suppress them too if (name == "UIApplication" || name == "UIResponder") continue; classNames[key] = (string)pair.Value; } // it seems to be hard to figure out which objects we should generate classes for, // so take the list of classes that xcode would generate var ibApprovedPartialClassNames = new HashSet<string> (); UnknownIBObject classDescriber; if (doc.Properties.TryGetValue ("IBDocument.Classes", out outVar) && (classDescriber = outVar as UnknownIBObject) != null) { NSMutableArray arr; if (classDescriber.Properties.TryGetValue ("referencedPartialClassDescriptions", out outVar) && (arr = outVar as NSMutableArray) != null) { foreach (var cls in arr.Values.OfType<IBPartialClassDescription> ()) if (!String.IsNullOrEmpty (cls.ClassName)) ibApprovedPartialClassNames.Add (cls.ClassName); } } // construct the type objects, keyed by ref ID var objectRecords = (IBMutableOrderedSet) objects.Properties ["objectRecords"]; var customTypeNames = new Dictionary<int,string> (); var types = new Dictionary<int,CodeTypeDeclaration> (); foreach (IBObjectRecord record in objectRecords.OrderedObjects.OfType<IBObjectRecord> ()) { string name; int? objId = ((IBObject)ResolveIfReference (record.Object)).Id; if (objId != null && classNames.TryGetValue (record.ObjectId, out name)) { customTypeNames[objId.Value] = name; if (!ibApprovedPartialClassNames.Contains (name)) continue; //HACK to avoid duplicate class definitions, which is not compilable ibApprovedPartialClassNames.Remove (name); var type = new CodeTypeDeclaration (name) { IsPartial = true }; type.CustomAttributes.Add ( new CodeAttributeDeclaration ("MonoTouch.Foundation.Register", new CodeAttributeArgument (new CodePrimitiveExpression (name)))); //FIXME: implement proper base class resolution. I'm not sure where the info is - it might need some // inference rules var obj = ResolveIfReference (record.Object); if (obj != null) { string baseType = "MonoTouch.Foundation.NSObject"; if (obj is IBProxyObject) { baseType = "MonoTouch.UIKit.UIViewController"; } else if (obj is UnknownIBObject) { var uobj = (UnknownIBObject)obj; //if the item comes from another nib, don't generate the partial class in this xib's codebehind if (uobj.Properties.ContainsKey ("IBUINibName") && !String.IsNullOrEmpty (uobj.Properties["IBUINibName"] as string)) continue; baseType = GetTypeName (null, uobj) ?? "MonoTouch.Foundation.NSObject"; } type.Comments.Add (new CodeCommentStatement (String.Format ("Base type probably should be {0} or subclass", baseType))); } types.Add (objId.Value, type); } } foreach (KeyValuePair<int,List<IBConnectionRecord>> typeRecord in typeRecords) { CodeTypeDeclaration type; if (!types.TryGetValue (typeRecord.Key, out type)) continue; //separate out the actions and outlets var actions = new List<IBActionConnection> (); var outlets = new List<IBOutletConnection> (); foreach (var record in typeRecord.Value) { if (record.Connection is IBActionConnection) actions.Add ((IBActionConnection)record.Connection); else if (record.Connection is IBOutletConnection) outlets.Add ((IBOutletConnection)record.Connection); } //process the actions, grouping ones with the same name foreach (var actionGroup in actions.GroupBy (a => a.Label)) { //find a common sender type for all the items in the grouping CodeTypeReference senderType = null; foreach (IBActionConnection ev in actionGroup) { var sender = ResolveIfReference (ev.Source) as IBObject; var newType = new CodeTypeReference (GetTypeName (customTypeNames, sender) ?? "MonoTouch.Foundation.NSObject"); if (senderType == null) { senderType = newType; continue; } else if (senderType == newType) { continue; } else { //FIXME: resolve common type newType = new CodeTypeReference ("MonoTouch.Foundation.NSObject"); break; } } if (type.Members.Count == 0) AddWarningDisablePragmas (type, provider); //create the action method and add it StringWriter actionStubWriter = null; GenerateAction (type, actionGroup.Key, senderType, provider, options, ref actionStubWriter); if (actionStubWriter != null) { type.Comments.Add (new CodeCommentStatement (actionStubWriter.ToString ())); actionStubWriter.Dispose (); } } foreach (var outlet in outlets) { CodeTypeReference outletType; //destination is widget, so get type var widget = ResolveIfReference (outlet.Destination.Reference) as IBObject; outletType = new CodeTypeReference (GetTypeName (customTypeNames, widget) ?? "System.Object"); if (type.Members.Count == 0) AddWarningDisablePragmas (type, provider); AddOutletProperty (type, outlet.Label, outletType); } } return types.Values; }