/// <summary> /// Insert the children of the current source node under the node pointed to by the destination. /// </summary> /// <param name="dest"></param> /// <param name="source"></param> /// <returns></returns> public static bool CopyChildren(this BaseElementNavigator dest, ElementNavigator source) { if (dest.HasChildren) { return(false); // Protect children from being overwritten } if (!source.MoveToFirstChild()) { return(true); // Nothing to copy, but successful anyway } bool firstChild = true; do { var copiedChild = (ElementDefinition)source.Current.DeepCopy(); if (firstChild) { // The first time, create a new child in the destination dest.InsertFirstChild(copiedChild); firstChild = false; } else { // Then insert other childs after that dest.InsertAfter(copiedChild); } // If there are nested children in the source, insert them under // the newly inserted node in the destination if (source.HasChildren) { dest.CopyChildren(source); } }while (source.MoveToNext()); // Bring both source & destination back one step to the original parents source.MoveToParent(); dest.MoveToParent(); return(true); }
/// <summary> /// Insert the children of the source navigator under the node pointed to by this Navigator. /// </summary> /// <param name="dest"></param> /// <param name="source"></param> /// <returns></returns> public static bool CopyChildren(this BaseElementNavigator dest, ElementNavigator source) { if (dest.HasChildren) return false; // Protect children from being overwritten if (!source.MoveToFirstChild()) return true; // Nothing to copy, but successful anyway bool firstChild = true; do { var copiedChild = (ElementDefinition)source.Current.DeepCopy(); if (firstChild) { // The first time, create a new child in the destination dest.InsertFirstChild(copiedChild); firstChild = false; } else // Then insert other childs after that dest.InsertAfter(copiedChild); // If there are nested children in the source, insert them under // the newly inserted node in the destination if (source.HasChildren) dest.CopyChildren(source); } while (source.MoveToNext()); // Bring both source & destination back one step to the original parents source.MoveToParent(); dest.MoveToParent(); return true; }
private static int countChildNameRepeats(ElementNavigator diff) { var repeats = 1; var currentPath = diff.PathName; var bm = diff.Bookmark(); while(diff.MoveToNext()) { // check whether the next sibling in the differential has the same name, // that means we're looking at a slice if (diff.PathName == currentPath) repeats++; else break; } diff.ReturnToBookmark(bm); return repeats; }
//private static void mergeStructure(Profile.ConstraintComponent snapshot, Profile.ConstraintComponent differential) //{ // if (differential.Name != null) snapshot.Name = differential.Name; // if (differential.Publish != null) snapshot.Publish = differential.Publish; // if (differential.Purpose != null) snapshot.Purpose = differential.Purpose; //} private void merge(ElementNavigator snap, ElementNavigator diff) { mergeElementAttributes(snap.Current, diff.Current); // If there are children, move into them, and recursively merge them if (diff.MoveToFirstChild()) { if (!snap.HasChildren) { // The differential moves into an element that has no children in the base. // This is allowable if the base's element has a nameReference or a TypeRef, // in which case needs to be expanded before we can move to the path indicated // by the differential expandBaseElement(snap, diff); } // Due to how MoveToFirstChild() works, we have to move to the first matching *child* // when entering the loop for the first time, after that we can look for the next // matching *sibling*. bool firstEntry = true; do { if( (firstEntry && !snap.MoveToChild(diff.PathName)) || (!firstEntry && !snap.MoveToNext(diff.PathName)) ) throw Error.InvalidOperation("Differential has a constraint for path '{0}', which does not exist in its base", diff.PathName); firstEntry = false; // Child found in both, merge them if (countChildNameRepeats(diff) > 1 || diff.Current.IsExtension()) { // The child in the diff repeats or we recognize it as an extension slice -> we're on the first element of a slice! mergeSlice(snap, diff); } else merge(snap, diff); } while (diff.MoveToNext()); // After the merge, return the diff and snapho back to their original position diff.MoveToParent(); snap.MoveToParent(); } }
private void genElement(HierarchicalTableGenerator gen, List<Row> rows, ElementNavigator nav, Profile profile, bool showMissing) { var element = nav.Current; if(onlyInformationIsMapping(nav.Structure.Element, element)) return; // we don't even show it in this case Row row = new Row(); row.setAnchor(element.Path); String s = element.GetNameFromPath(); bool hasDef = element.Definition != null; bool ext = false; if (s == "extension" || s == "modifierExtension") { row.setIcon("icon_extension_simple.png"); ext = true; } else if (!hasDef || element.Definition.Type == null || element.Definition.Type.Count == 0) { row.setIcon("icon_element.gif"); } else if (hasDef && element.Definition.Type.Count > 1) { if (allTypesAre(element.Definition.Type, "ResourceReference")) row.setIcon("icon_reference.png"); else row.setIcon("icon_choice.gif"); } else if (hasDef && element.Definition.Type[0].Code.StartsWith("@")) { //TODO: That's not a legal code, will this ever appear? //I am pretty sure this depends on ElementDefn.NameReference row.setIcon("icon_reuse.png"); } else if (hasDef && _pkp.isPrimitive(element.Definition.Type[0].Code)) row.setIcon("icon_primitive.png"); else if (hasDef && _pkp.isReference(element.Definition.Type[0].Code)) row.setIcon("icon_reference.png"); else if (hasDef && _pkp.isDataType(element.Definition.Type[0].Code)) row.setIcon("icon_datatype.gif"); else row.setIcon("icon_resource.png"); var reference = _pkp.GetLinkForElementDefinition(nav.Structure, profile, element); //String reference = defPath == null ? null : defPath + makePathLink(element); UnusedTracker used = new UnusedTracker(); used.used = true; Cell left = new Cell(null, reference, s, !hasDef ? null : element.Definition.Formal, null); row.getCells().Add(left); if (ext) { // If this element (row) in the table is an extension... if (element.Definition != null && element.Definition.Type.Count == 1 && element.Definition.Type[0].Profile != null) { Profile.ProfileExtensionDefnComponent extDefn = _pkp.getExtensionDefinition(profile, element.Definition.Type[0].Profile); if (extDefn == null) { row.getCells().Add(new Cell(null, null, !hasDef ? null : describeCardinality(element.Definition, null, used), null, null)); row.getCells().Add(new Cell(null, null, "?? "+element.Definition.Type[0].Profile, null, null)); generateDescription(gen, row, element, null, used.used, element.Definition.Type[0].Profile, profile); } else { row.getCells().Add(new Cell(null, null, !hasDef ? null : describeCardinality(element.Definition, extDefn.Definition, used), null, null)); genTypes(gen, row, extDefn.Definition, profile); generateDescription(gen, row, element, extDefn.Definition, used.used, element.Definition.Type[0].Profile, profile); } } else if (element.Definition != null) { row.getCells().Add(new Cell(null, null, !hasDef ? null : describeCardinality(element.Definition, null, used), null, null)); genTypes(gen, row, element.Definition, profile); generateDescription(gen, row, element, null, used.used, null, profile); } else { row.getCells().Add(new Cell(null, null, !hasDef ? null : describeCardinality(element.Definition, null, used), null, null)); row.getCells().Add(new Cell()); generateDescription(gen, row, element, null, used.used, null, profile); } } else { row.getCells().Add(new Cell(null, null, !hasDef ? null : describeCardinality(element.Definition, null, used), null, null)); if (element.Definition != null) genTypes(gen, row, element.Definition, profile); else row.getCells().Add(new Cell()); generateDescription(gen, row, element, null, used.used, null, profile); } if (element.Slicing != null) { row.setIcon("icon_slice.png"); row.getCells()[2].getPieces().Clear(); foreach (Cell cell in row.getCells()) foreach (Piece p in cell.getPieces()) { p.addStyle("font-style: italic"); } } if (used.used || showMissing) rows.Add(row); if (!used.used) { foreach (Cell cell in row.getCells()) foreach (Piece p in cell.getPieces()) { p.setStyle("text-decoration:line-through"); p.setReference(null); } } else { if (nav.MoveToFirstChild()) { do { genElement(gen, row.getSubRows(), nav, profile, showMissing); } while (nav.MoveToNext()); nav.MoveToParent(); } } }
private void mergeSlice(ElementNavigator snap, ElementNavigator diff) { // diff is now located at the first repeat of a slice, which is (possibly) the slice entry // snap is located at the base definition of the element that will become sliced. But snap is not yet sliced. // Before we start, is the base element sliceable? if (!snap.Current.IsRepeating() && !isSlicedToOne(diff.Current)) throw Error.InvalidOperation("The slicing entry in the differential at {0} indicates an unbounded slice, but the base element is not a repeating element", diff.Current.Path); Profile.ElementComponent slicingEntry; // Yes, so, first, add the slicing entry to the snapshot. if (diff.Current.Slicing != null) { slicingEntry = createSliceEntry(snap.Current, diff.Current); snap.InsertBefore(slicingEntry); if (!diff.MoveToNext(diff.PathName)) throw Error.InvalidOperation("Slicing has no elements beyond the slicing entry"); // currently impossible to happen } else { // Mmmm....no slicing entry in the differential. This is only alloweable for extension slices, as a shorthand notation. if (!snap.Current.IsExtension()) throw Error.InvalidOperation("The slice group at {0} does not start with a slice entry element", diff.Current.Path); // In this case we insert a "prefab" extension slice. slicingEntry = createExtensionSlicingEntry(snap.Path, snap.Current); snap.InsertBefore(slicingEntry); } snap.MoveToNext(); // The differential and the snapshot are now both positioned on the first "real" slicing content element // Start by duplicating the current unsliced base definition as many times as we have slices, so we can // update these copies for each slice. var numSlices = countChildNameRepeats(diff); for (var count = 0; count < numSlices-1; count++) snap.Duplicate(); var slicingName = snap.PathName; do { merge(snap, diff); } while (diff.MoveToNext(slicingName) && snap.MoveToNext(slicingName)); //if (slicingEntry.Slicing.Rules != Profile.SlicingRules.Closed) //{ // // Slices that are open in some form need to repeat the original "base" definition, // // so that the open slices have a place to "fit in" // snap.InsertAfter((Profile.ElementComponent)slicingTemplate.DeepCopy()); //} //TODO: update/check the slice entry's min/max property to match what we've found in the slice group }
private static string nextChildName(ElementNavigator nav) { string result = null; if (nav.MoveToNext()) { result = nav.PathName; nav.MoveToPrevious(); } return result; }
/// <summary> /// Will match up the children of the current element in diffNav to the children of the element in snapNav. /// </summary> /// <param name="snapNav"></param> /// <param name="diffNav"></param> /// <returns>Returns a list of Bookmark combinations, the first bookmark pointing to an element in the base, /// the second a bookmark in the diff that matches the bookmark in the base.</returns> /// <remarks>Will match slices to base elements, re-sliced slices to slices and type-slice shorthands to choie elements. /// Note that this function may expand snapNav when it encounters paths in the differential that move into the complex types /// of one of snap's elements. (NO NEED, it just has to match direct children, not deeper) /// This function assumes the differential is not sparse: it must have parent nodes for all child constraint paths. /// </remarks> public List<MatchInfo> Match(ElementNavigator snapNav, ElementNavigator diffNav) { if (!snapNav.HasChildren) throw Error.Argument("snapNav", "Cannot match base to diff: element '{0}' in snap has no children".FormatWith(snapNav.PathName)); if (!diffNav.HasChildren) throw Error.Argument("diffNav", "Cannot match base to diff: element '{0}' in diff has no children".FormatWith(diffNav.PathName)); // These bookmarks are used only in the finally {} to make sure we don't alter the position of the navs when leaving the merger var baseStartBM = snapNav.Bookmark(); var diffStartBM = diffNav.Bookmark(); snapNav.MoveToFirstChild(); diffNav.MoveToFirstChild(); var choiceNames = listChoiceElements(snapNav); var result = new List<MatchInfo>(); try { do { // First, match directly -> try to find the child in base with the same name as the path in the diff if (snapNav.PathName != diffNav.PathName && !snapNav.MoveToNext(diffNav.PathName)) { // Not found, maybe this is a type slice shorthand, look if we have a matching choice prefix in snap var typeSliceShorthand = diffNav.PathName; // Try to match nameXXXXX to name[x] var matchingChoice = choiceNames.SingleOrDefault(prefix => isPossibleTypeSlice(prefix, typeSliceShorthand)); if (matchingChoice != null) snapNav.MoveToNext(matchingChoice); else throw Error.InvalidOperation("Differential has a constraint for path '{0}', which does not exist in its base".FormatWith(diffNav.Path)); } result.AddRange(constructMatch(snapNav, diffNav)); } while (diffNav.MoveToNext()); } finally { snapNav.ReturnToBookmark(baseStartBM); diffNav.ReturnToBookmark(diffStartBM); } return result; }
/// <summary> /// List all names of nodes in the current navigator that are choice ('[x]') elements /// </summary> /// <param name="snapNav"></param> /// <returns></returns> private List<string> listChoiceElements(ElementNavigator snapNav) { var bm = snapNav.Bookmark(); var result = new List<string>(); do { if (snapNav.Current.IsChoice()) result.Add(snapNav.PathName); } while (snapNav.MoveToNext()); snapNav.ReturnToBookmark(bm); return result; }
private static List<MatchInfo> constructSliceMatch(ElementNavigator snapNav, ElementNavigator diffNav) { var result = new List<MatchInfo>(); var bm = snapNav.Bookmark(); var diffName = diffNav.PathName; bool baseIsSliced = snapNav.Current.Slicing != null; bool diffIsSliced = diffNav.Current.IsExtension() || nextChildName(diffNav) == diffNav.PathName; if (baseIsSliced) throw Error.NotSupported("Cannot yet handle re-slicing found at diff {0}".FormatWith(diffNav.Path)); // For the first entries with explicit slicing information (or implicit if this is an extension), // generate a match between the base's unsliced element and the first entry in the diff if(diffNav.Current.Slicing != null || diffNav.Current.IsExtension() ) { // Differential has information for the slicing entry result.Add(new MatchInfo() { BaseBookmark = bm, DiffBookmark = diffNav.Bookmark(), Action = MatchAction.Slice }); if(!diffNav.Current.IsExtension()) { if (!diffNav.MoveToNext()) throw Error.InvalidOperation("Differential has a slicing entry {0}, but no first actual slice", diffNav.Path); } } // Then, generate a match between the base's unsliced element and the slicing entries in the diff // Note that the first entry may serve a double role and have to result matches (one for the constraints, one as a slicing entry) do { result.Add(new MatchInfo() { BaseBookmark = bm, DiffBookmark = diffNav.Bookmark(), Action = MatchAction.Add }); } while (nextChildName(diffNav) == diffName && diffNav.MoveToNext()); // Warning: Subtle use of short-cut evaluation return result; }
private void merge(ElementNavigator snapNav, ElementNavigator diffNav) { var snapPos = snapNav.Bookmark(); var diffPos = diffNav.Bookmark(); try { var matches = (new ElementMatcher()).Match(snapNav, diffNav); //Debug.WriteLine("Matches for children of {0}".FormatWith(snapNav.Path)); //matches.DumpMatches(snapNav, diffNav); foreach (var match in matches) { if (!snapNav.ReturnToBookmark(match.BaseBookmark)) throw Error.InvalidOperation("Internal merging error: bookmark {0} in snap is no longer available", match.BaseBookmark); if (!diffNav.ReturnToBookmark(match.DiffBookmark)) throw Error.InvalidOperation("Internal merging error: bookmark {0} in diff is no longer available", match.DiffBookmark); if (match.Action == ElementMatcher.MatchAction.Add) { // TODO: move this logic to matcher, the Add should point to the last slice where // the new slice will be added after. // Find last entry in slice to add to the end var current = snapNav.Path; while (snapNav.Current.Path == current && snapNav.MoveToNext()) ; snapNav.MoveToPrevious(); // take one step back... var dest = snapNav.Bookmark(); snapNav.ReturnToBookmark(match.BaseBookmark); snapNav.DuplicateAfter(dest); markChange(snapNav.Current); mergeElement(snapNav, diffNav); snapNav.Current.Slicing = null; // Probably not good enough... } else if (match.Action == ElementMatcher.MatchAction.Merge) { mergeElement(snapNav, diffNav); } else if (match.Action == ElementMatcher.MatchAction.Slice) { makeSlice(snapNav, diffNav); } } } finally { snapNav.ReturnToBookmark(snapPos); diffNav.ReturnToBookmark(diffPos); } }
private void merge(ElementNavigator snap, ElementNavigator diff) { (new ElementDefnMerger(_markChanges)).Merge(snap.Current, diff.Current); // If there are children, move into them, and recursively merge them if (diff.MoveToFirstChild()) { if (!snap.HasChildren) { // The differential moves into an element that has no children in the base. // This is allowable if the base's element has a nameReference or a TypeRef, // in which case needs to be expanded before we can move to the path indicated // by the differential if (snap.Current.Type.Count > 1) throw new NotSupportedException("Differential has a constraint on a choice element {0}, but does so without using a type slice".FormatWith(diff.Path)); expandBaseElement(snap, diff); } // Due to how MoveToFirstChild() works, we have to move to the first matching *child* // when entering the loop for the first time, after that we can look for the next // matching *sibling*. bool firstEntry = true; do { if ((firstEntry && !snap.MoveToChild(diff.PathName)) || (!firstEntry && !snap.MoveTo(diff.PathName)) ) // HACK: I don't think it should be allowed for a diff to list constraints in the wrong order... { throw Error.InvalidOperation("Differential has a constraint for path '{0}', which does not exist in its base", diff.Path); } firstEntry = false; // Child found in both, merge them if (countChildNameRepeats(diff) > 1 || diff.Current.IsExtension()) { // The child in the diff repeats or we recognize it as an extension slice -> we're on the first element of a slice! mergeSlice(snap, diff); } else merge(snap, diff); } while (diff.MoveToNext()); // After the merge, return the diff and snapho back to their original position diff.MoveToParent(); snap.MoveToParent(); } }
private static int countChildNameRepeats(ElementNavigator diff) { //TODO: We use this function to determine whether an element is sliced...doing this by counting repeats of elements //in the diff. However, when reslicing, the diff doesn't need to have repeating elements, and you have to derive from the //base (snapshot) that the element is sliced. var repeats = 1; var currentPath = diff.PathName; var bm = diff.Bookmark(); while (diff.MoveToNext()) { // check whether the next sibling in the differential has the same name, // that means we're looking at a slice if (diff.PathName == currentPath) repeats++; else break; } diff.ReturnToBookmark(bm); return repeats; }