public InstallScript ConvertManifestXml(XmlDocument doc, string name) { ExportProcessor.EnsureSystemData(_conn, ref _itemTypes); foreach (var elem in doc.ElementsByXPath("//Item[@action='add']").ToList()) { elem.SetAttribute("action", "merge"); } ItemType itemType; foreach (var elem in doc.ElementsByXPath("//Item[@type and @id]").ToList()) { if (_itemTypes.TryGetValue(elem.Attribute("type", "").ToLowerInvariant(), out itemType) && itemType.IsVersionable) { elem.SetAttribute(XmlFlags.Attr_ConfigId, elem.Attribute("id")); elem.SetAttribute("where", string.Format("[{0}].[config_id] = '{1}'", itemType.Name.Replace(' ', '_'), elem.Attribute("id"))); elem.RemoveAttribute("id"); } } var result = new InstallScript(); result.Title = name; _exportTools.Export(result, doc); return result; }
/// <summary> /// Convert references to poly itemtypes to the correct itemtype so that dependency checking works properly /// </summary> private void FixPolyItemReferences(XmlDocument doc) { var polyQuery = "//*[" + (from i in _itemTypesByName where i.Value.IsPolymorphic select "@type='" + i.Value.Name + "'") .Aggregate((p, c) => p + " or " + c) + "]"; var elements = doc.ElementsByXPath(polyQuery); var elementsByRef = (from e in elements group e by ItemReference.FromElement(e) into newGroup select newGroup) .ToDictionary(g => g.Key, g => g.ToList()); // Fix items referenced by ID var queries = (from i in elementsByRef.Keys where i.Unique.IsGuid() group i by i.Type into newGroup select "<Item type=\"" + newGroup.Key + "\" idlist=\"" + newGroup.Select(r => r.Unique).Aggregate((p, c) => p + "," + c) + "\" select=\"itemtype\" action=\"get\" />"); IEnumerable<XmlElement> items; List<XmlElement> fixElems; ItemType fullType; foreach (var query in queries) { items = _conn.GetItems("ApplyItem", query); foreach (var item in items) { if (elementsByRef.TryGetValue(ItemReference.FromFullItem(item, false), out fixElems)) { fullType = _itemTypesById[item.Element("itemtype", "")]; if (fullType != null) { foreach (var elem in fixElems) { elem.SetAttribute("type", fullType.Name); } } } } } // Fix items referenced with a where clause var whereQueries = (from i in elementsByRef.Keys where !i.Unique.IsGuid() && !string.IsNullOrEmpty(i.Unique) select new { Ref = i, Query = "<Item type=\"" + i.Type + "\" where=\"" + i.Unique + "\" select=\"itemtype\" action=\"get\" />" }); foreach (var whereQuery in whereQueries) { var item = _conn.GetItems("ApplyItem", whereQuery.Query).FirstOrDefault(); if (elementsByRef.TryGetValue(whereQuery.Ref, out fixElems)) { fullType = _itemTypesById[item.Element("itemtype", "")]; if (fullType != null) { foreach (var elem in fixElems) { elem.SetAttribute("type", fullType.Name); } } } } }
/// <summary> /// Move all foreign properties to a script. This is because the other properties must be created first before these can /// be added. /// </summary> private void FixForeignProperties(XmlDocument doc) { var itemTypes = doc.ElementsByXPath("//Item[@type='ItemType' and Relationships/Item[@type = 'Property' and data_type = 'foreign']]").ToList(); XmlElement fix = null; foreach (var itemType in itemTypes) { fix = (XmlElement)itemType.CloneNode(false); fix.SetAttribute("action", "edit"); fix.IsEmpty = true; fix = (XmlElement)fix.AppendChild(doc.CreateElement("Relationships")); var uppermostItem = itemType.Parents().LastOrDefault(e => e.LocalName == "Item" && !string.IsNullOrEmpty(e.Attribute("type", ""))) ?? itemType; uppermostItem.ParentNode.InsertAfter(fix.ParentNode, uppermostItem); foreach (var foreignProp in itemType.ElementsByXPath(".//Relationships/Item[@type = 'Property' and data_type = 'foreign']").ToList()) { foreignProp.Detatch(); fix.AppendChild(foreignProp); } } }
/// <summary> /// Convert form fields pointing to a system property from an ID reference to a search /// </summary> private void FixFormFieldsPointingToSystemProperties(XmlDocument doc) { const string sysProps = "|behavior|classification|config_id|created_by_id|created_on|css|current_state|generation|history_id|id|is_current|is_released|keyed_name|release_date|effective_date|locked_by_id|major_rev|managed_by_id|minor_rev|modified_by_id|modified_on|new_version|not_lockable|owned_by_id|permission_id|related_id|sort_order|source_id|state|itemtype|superseded_date|team_id|"; var fields = doc.ElementsByXPath("//Item[@type='Field'][contains($p0,concat('|',propertytype_id/@keyed_name,'|'))]", sysProps).ToList(); var query = "<Item type=\"Property\" action=\"get\" idlist=\"" + fields.GroupConcat(",", f => f.Element("propertytype_id", "")) + "\" select=\"name,source_id\" />"; // Get all the property information from the database var results = _conn.GetItems("ApplyItem", query).ToDictionary(e => e.Attribute("id")); // Reformat the results as queries foreach (var result in results.Values) { foreach (var attr in result.Attributes.OfType<XmlAttribute>().Where(a => a.LocalName != "type").ToList()) { result.RemoveAttributeNode(attr); } foreach (var child in result.Elements().Where(e => e.LocalName != "name" && e.LocalName != "source_id").ToList()) { child.Detatch(); } result.Attr("action", "get").Attr("select", "id"); } // Update the export the the proper edit scripts XmlElement propData; XmlElement propType; XmlElement parentItem; XmlElement script; foreach (var field in fields) { if (results.TryGetValue(field.Element("propertytype_id", ""), out propData)) { propType = field.Element("propertytype_id"); propType.RemoveAll(); propType.AppendChild(propType.OwnerDocument.ImportNode(propData, true)); propType.Detatch(); parentItem = field.Parents().Last(e => e.LocalName == "Item"); script = parentItem.OwnerDocument.CreateElement("Item").Attr("type", field.Attribute("type")).Attr("id", field.Attribute("id")).Attr("action", "edit"); script.AppendChild(propType); parentItem.Parent().InsertAfter(script, parentItem); } } }
/// <summary> /// Fix cyclical workflow-itemtype references by creating an edit script /// </summary> private void FixCyclicalWorkflowItemTypeRefs(XmlDocument doc) { var workflowRefs = doc.ElementsByXPath("//Item/Relationships/Item[@type='Allowed Workflow' and (@action='add' or @action='merge' or @action='create')]").ToList(); XmlElement type; XmlElement sourceId; foreach (var workflowRef in workflowRefs) { if (!workflowRef.Elements("source_id").Any()) { type = (XmlElement)workflowRef.ParentNode.ParentNode; sourceId = workflowRef.Elem("source_id"); sourceId.SetAttribute("type", type.Attribute("type")); sourceId.SetAttribute("keyed_name", type.Element("id").Attribute("keyed_name", "")); sourceId.InnerText = type.Attribute("id"); } type = workflowRef.Parents().Last(e => e.LocalName == "Item"); while (type.NextSibling.Attribute("action") == "edit") type = (XmlElement)type.NextSibling; workflowRef.ParentNode.RemoveChild(workflowRef); type.ParentNode.InsertAfter(workflowRef, type); workflowRef.Attr(XmlFlags.Attr_IsScript, "1"); } }
/// <summary> /// Fix cyclical workflow-life cycle references by creating an edit script /// </summary> private void FixCyclicalWorkflowLifeCycleRefs(XmlDocument doc) { var workflowRefs = doc.ElementsByXPath("//Item[@type='Life Cycle State' and (@action='add' or @action='merge' or @action='create')]/workflow").ToList(); XmlElement map; XmlElement fix; foreach (var workflowRef in workflowRefs) { fix = (XmlElement)workflowRef.ParentNode.CloneNode(false); fix.SetAttribute("action", "edit"); fix.IsEmpty = true; fix.AppendChild(workflowRef.CloneNode(true)); map = workflowRef.Parents().First(e => e.LocalName == "Item" && e.Attribute("type", "") == "Life Cycle Map"); map.ParentNode.InsertAfter(fix, map); workflowRef.ParentNode.RemoveChild(workflowRef); } }
/// <summary> /// Remove related items from relationships to non-dependent itemtypes (they should be exported separately). /// </summary> /// <remarks> /// Floating relationships to versionable items are flagged to float. /// </remarks> private void RemoveRelatedItems(XmlDocument doc, IEnumerable<ItemReference> items) { var itemDict = items.ToDictionary(i => i); ItemReference itemRefOpts; ItemType itemType; List<XmlElement> parents; int levels; foreach (var elem in doc.ElementsByXPath("//Relationships/Item/related_id[Item/@id!='']").ToList()) { parents = elem.Parents().Where(e => e.LocalName == "Item").Skip(1).ToList(); levels = 1; if (itemDict.TryGetValue(ItemReference.FromFullItem(parents.Last(), false), out itemRefOpts)) { levels = Math.Min(itemRefOpts.Levels, 1); } Debug.Print(parents.GroupConcat(" > ", p => ItemReference.FromFullItem(p, true).ToString()) + " > " + ItemReference.FromFullItem(elem.Element("Item"), true).ToString()); if (parents.Count >= levels && _itemTypesByName.TryGetValue(elem.Element("Item").Attribute("type").ToLowerInvariant(), out itemType) && !itemType.IsDependent) { Debug.Print("Removing"); if (itemType.IsVersionable && elem.Parent().Element("behavior", "").IndexOf("float") >= 0) { elem.Attr(XmlFlags.Attr_Float, "1"); elem.InnerXml = elem.Element("Item").Element("config_id", elem.Element("Item").Attribute("id")); } else { elem.InnerXml = elem.Element("Item").Attribute("id"); } } } }
/// <summary> /// Match system identities by name instead of ID /// </summary> private void ExpandSystemIdentities(XmlDocument doc) { ItemReference ident; foreach (var elem in doc.ElementsByXPath("//self::node()[local-name() != 'Item' and local-name() != 'id' and local-name() != 'config_id' and local-name() != 'source_id' and @type='Identity' and not(Item)]").ToList()) { ident = _dependAnalyzer.GetSystemIdentity(elem.InnerText); if (ident != null) { elem.InnerXml = "<Item type=\"Identity\" action=\"get\" select=\"id\"><name>" + ident.KeyedName + "</name></Item>"; } } }
/// <summary> /// Move the form nodes inline with the itemtype create script /// </summary> private void MoveFormRefsInline(XmlDocument doc, IEnumerable<InstallItem> lines) { ItemReference formItemRef = null; foreach (var form in doc.ElementsByXPath("//Item[@type='Form' and @action and @id]").ToList()) { var references = doc.ElementsByXPath(".//self::node()[local-name() != 'Item' and local-name() != 'id' and local-name() != 'config_id' and @type='Form' and not(Item) and text() = $p0]", form.Attribute("id", "")) //.Concat(otherDocs.SelectMany(d => d.ElementsByXPath("//self::node()[local-name() != 'Item' and local-name() != 'id' and local-name() != 'config_id' and @type='Form' and not(Item) and text() = $p0]", form.Attribute("id", "")))).ToList(); .ToList(); if (references.Any()) { foreach (var formRef in references) { formRef.InnerXml = form.OuterXml; foreach (var relTag in formRef.Elements("Item").Elements("Relationships").ToList()) { relTag.Detatch(); } } } foreach (var line in lines) { references = line.Script.ElementsByXPath(".//self::node()[local-name() != 'Item' and local-name() != 'id' and local-name() != 'config_id' and @type='Form' and not(Item) and text() = $p0]", form.Attribute("id", "")).ToList(); if (references.Any()) { if (formItemRef == null) formItemRef = ItemReference.FromFullItem(form, false); foreach (var formRef in references) { formRef.InnerXml = form.OuterXml; foreach (var relTag in formRef.Elements("Item").Elements("Relationships").ToList()) { relTag.Detatch(); } } _dependAnalyzer.RemoveDependency(line.Reference, formItemRef); } } } }
/// <summary> /// Normalize the formatting of class structure nodes to aid in manual line-based diff-ing /// </summary> private void NormalizeClassStructure(XmlDocument doc) { ReportProgress(97, "Transforming the results"); var settings = new XmlWriterSettings(); settings.OmitXmlDeclaration = true; settings.Indent = true; settings.IndentChars = " "; var classStructs = (from e in doc.ElementsByXPath("//Item[@type='ItemType']/class_structure") where !string.IsNullOrEmpty(e.InnerText) select e); XmlDocument classDoc; foreach (var classStruct in classStructs) { classDoc = new XmlDocument(); classDoc.LoadXml(classStruct.InnerText); using (var output = new StringWriter()) { using (var writer = XmlTextWriter.Create(output, settings)) { WriteClassStruct(Enumerable.Repeat((XmlNode)classDoc.DocumentElement, 1), writer); } classStruct.InnerXml = "<![CDATA[" + output.ToString() + "]]>"; } } }
/// <summary> /// Move all foreign properties to a script. This is because the other properties must be created first before these can /// be added. /// </summary> private void FixForeignProperties(XmlDocument doc) { var foreignProps = doc.ElementsByXPath("//Relationships/Item[@type = 'Property' and data_type = 'foreign']").ToList(); XmlElement fix = null; foreach (var foreignProp in foreignProps) { if (fix == null) { var itemType = foreignProp.Parents().First(e => e.LocalName == "Item" && e.Attribute("type", "") == "ItemType"); fix = (XmlElement)itemType.CloneNode(false); fix.SetAttribute("action", "edit"); fix.IsEmpty = true; fix = (XmlElement)fix.AppendChild(doc.CreateElement("Relationships")); var uppermostItem = foreignProp.Parents().Last(e => e.LocalName == "Item" && !string.IsNullOrEmpty(e.Attribute("type", ""))); uppermostItem.ParentNode.InsertAfter(fix.ParentNode, uppermostItem); } foreignProp.ParentNode.RemoveChild(foreignProp); fix.AppendChild(foreignProp); } }
/// <summary> /// Convert form fields pointing to a system property from an ID reference to a search /// </summary> private void FixFormFieldsPointingToSystemProperties(XmlDocument doc) { const string sysProps = "|behavior|classification|config_id|created_by_id|created_on|css|current_state|generation|history_id|id|is_current|is_released|keyed_name|release_date|effective_date|locked_by_id|major_rev|managed_by_id|minor_rev|modified_by_id|modified_on|new_version|not_lockable|owned_by_id|permission_id|related_id|sort_order|source_id|state|itemtype|superseded_date|team_id|"; var fields = doc.ElementsByXPath("//Item[@type='Field'][contains($p0,concat('|',propertytype_id/@keyed_name,'|'))]", sysProps).ToList(); if (fields.Count < 1) return; var query = "<Item type=\"Property\" action=\"get\" idlist=\"" + fields.GroupConcat(",", f => f.Element("propertytype_id", "")) + "\" select=\"name,source_id\" />"; // Get all the property information from the database var results = _conn.Apply(query).Items().ToDictionary(e => e.Id(), e => new Command( @"<Item type='@0' action='get' select='id'> <name>@1</name> <source_id>@2</source_id> </Item>", e.Type().Value, e.Property("name").Value, e.SourceId().Value) .ToNormalizedAml(_conn.AmlContext.LocalizationContext)); // Update the export the the proper edit scripts string propData; XmlDocument tempDoc; XmlElement propType; XmlElement parentItem; XmlElement script; foreach (var field in fields) { if (results.TryGetValue(field.Element("propertytype_id", ""), out propData)) { propType = field.Element("propertytype_id"); propType.RemoveAll(); tempDoc = doc.NewDoc(); tempDoc.LoadXml(propData); propType.AppendChild(propType.OwnerDocument.ImportNode(tempDoc.DocumentElement, true)); propType.Detatch(); parentItem = field.Parents().Last(e => e.LocalName == "Item"); script = parentItem.OwnerDocument.CreateElement("Item") .Attr("type", field.Attribute("type")) .Attr("id", field.Attribute("id")) .Attr("action", "edit") .Attr(XmlFlags.Attr_ScriptType, "6"); script.AppendChild(propType); parentItem.Parent().InsertAfter(script, parentItem); } } }
/// <summary> /// Remove the ID attribute from relationships from versionable items. This will improve the /// compare process and the import process should handle the import of these items without an /// ID. /// </summary> private void RemoveVersionableRelIds(XmlDocument doc) { var query = "//Item[" + (from i in _metadata.ItemTypes where i.IsVersionable select "@type='" + i.Name + "'") .Aggregate((p, c) => p + " or " + c) + "]/Relationships/Item[@id]"; var elements = doc.ElementsByXPath(query).ToList(); foreach (var elem in elements) { elem.RemoveAttribute("id"); } }
/// <summary> /// Normalize the formatting of class structure nodes to aid in manual line-based diff-ing /// </summary> private void NormalizeClassStructure(XmlDocument doc) { ReportProgress(97, "Transforming the results"); var settings = new XmlWriterSettings(); settings.OmitXmlDeclaration = true; settings.Indent = true; settings.IndentChars = " "; var classStructs = (from e in doc.ElementsByXPath("//Item[@type='ItemType']/class_structure") where !string.IsNullOrEmpty(e.InnerText) select e); XmlDocument classDoc; foreach (var classStruct in classStructs) { classDoc = doc.NewDoc(); var text = classStruct.InnerText; classDoc.LoadXml(text); var sb = new StringBuilder((int)(text.Length * 1.4)); using (var output = new StringWriter(sb)) { using (var writer = XmlTextWriter.Create(output, settings)) { WriteClassStruct(Enumerable.Repeat((XmlNode)classDoc.DocumentElement, 1), writer); } classStruct.IsEmpty = true; classStruct.AppendChild(doc.CreateCDataSection(output.ToString())); } } }
/// <summary> /// Convert references to polyitem lists (which are not exported as they are auto-generated) /// to AML gets /// </summary> private void FixPolyItemListReferences(XmlDocument doc) { var query = "//*[" + (from i in _metadata.PolyItemLists select "text()='" + i.Unique + "'") .Aggregate((p, c) => p + " or " + c) + "]"; var elements = doc.ElementsByXPath(query); ItemReference itemRef; foreach (var elem in elements) { itemRef = _metadata.PolyItemLists.Single(i => i.Unique == elem.InnerText); elem.InnerXml = "<Item type='List' action='get'><name>" + itemRef.KeyedName + "</name></Item>"; } }