public FormMoveQuestion(Office.CustomXMLPart answersPart,
            List<Word.ContentControl> relevantRepeats,
            questionnaire questionnaire, 
            string questionID,
            XPathsPartEntry xppe)
        {
            InitializeComponent();

            this.textBoxQuestion.Text = questionnaire.getQuestion(questionID).text;

            this.controlQuestionVaryWhichRepeat1.init(answersPart,
                relevantRepeats,
                questionnaire, questionID,
                xppe);
        }
        public FormRepeat(Office.CustomXMLPart questionsPart,
            Office.CustomXMLPart answersPart,
            Model model,
            Word.ContentControl cc)
        {
            InitializeComponent();

            this.model = model;
            this.cc = cc;

            this.questionsPart = questionsPart;
            questionnaire = new questionnaire();
            questionnaire.Deserialize(questionsPart.XML, out questionnaire);

            this.answersPart = answersPart;

            Office.CustomXMLNodes answers = answersPart.SelectNodes("//oda:repeat");
            foreach (Office.CustomXMLNode answer in answers)
            {
                this.answerID.Add(CustomXMLNodeHelper.getAttribute(answer, "qref")); // ID
            }

            // Suggest ID .. the idea is that
            // the question ID = answer ID.
               // this.ID = generateId();

            xppe = new XPathsPartEntry(model);
            // List of repeat names, for re-use purposes
            // (we need a map from repeat name (shown in the UI) to repeat id,
            //  which is xppe.xpathId).
            // What is it that distinguishes a repeat from any other question?
            // Answer: The fact that the XPath pointing to it ends with oda:row

            // Only show repeats which are in scope (since this repeat is not allowed elsewhere)
            // - if no ancestor repeat, then top level repeats.
            // - if there is an ancestor repeat, then just those which are direct children of it.
            Word.ContentControl rptAncestor = RepeatHelper.getYoungestRepeatAncestor(cc);
            String scope = "/oda:answers";
            if (rptAncestor != null)
            {
                // find its XPath
                scope = xppe.getXPathByID(  (new TagData(rptAncestor.Tag)).getRepeatID() ).dataBinding.xpath;

            }

            repeatNameToIdMap = new Dictionary<string, string>();
            foreach (xpathsXpath xpath in xppe.getXPaths().xpath)
            {
                if (xpath.dataBinding.xpath.EndsWith("oda:row"))
                {
                    if (isRepeatInScope(scope, xpath.dataBinding.xpath)) {
                        // the repeat "name" is its question text.
                        // Get that.
                        question q = questionnaire.getQuestion(xpath.questionID);
                        repeatNameToIdMap.Add(q.text, xpath.id);
                    }
                }
            }
            // Populate the comboBox
            foreach(KeyValuePair<String,String> entry in repeatNameToIdMap)
            {
                this.comboBoxRepeatNames.Items.Add(entry.Key);
            }
        }
        public void init(
            Office.CustomXMLPart answersPart,
            List<Word.ContentControl> relevantRepeats,
            questionnaire questionnaire, 
            string questionID,
            XPathsPartEntry xppe)
        {
            List<Office.CustomXMLNode> commonAncestors =null;
            foreach (Word.ContentControl repeat in relevantRepeats)
            {
                log.Info("considering relevantRepeat cc " + repeat.ID + " " + repeat.Tag);
                TagData repeatTD = new TagData(repeat.Tag);
                string repeatXPathID = repeatTD.getRepeatID();
                xpathsXpath repeatXP = xppe.getXPathByID(repeatXPathID);
                Office.CustomXMLNode repeatNode = answersPart.SelectSingleNode(repeatXP.dataBinding.xpath).ParentNode;

                if (commonAncestors == null)
                {
                    // First entry, so init
                    // Make a list of the ancestors of the
                    // first repeat.
                    commonAncestors = new List<Microsoft.Office.Core.CustomXMLNode>();
                    commonAncestors.Add(repeatNode);
                    log.Info("Added to common ancestors " + CustomXMLNodeHelper.getAttribute(repeatNode, "qref"));
                    addAncestors(commonAncestors, repeatNode);
                }
                else
                {
                    // cross off that list anything
                    // which isn't an ancestor of the other repeats.
                    List<Microsoft.Office.Core.CustomXMLNode> whitelist = new List<Microsoft.Office.Core.CustomXMLNode>();
                    whitelist.Add(repeatNode);
                    addAncestors(whitelist, repeatNode);
                    removeNonCommonAncestor(commonAncestors, whitelist);
                }

                if (commonAncestors.Count == 0) break;
            }
            if (commonAncestors == null)
            {
                    commonAncestors = new List<Microsoft.Office.Core.CustomXMLNode>();
            }

            // Is it OK where it is?
            // Yes - if it is top level
            log.Debug(questionID + " --> " + xppe.getXPathByQuestionID(questionID).dataBinding.xpath);
            // eg /oda:answers/oda:repeat[@qref='rpt1']/oda:row[1]/oda:answer[@id='qa_2']
            OkAsis = (xppe.getXPathByQuestionID(questionID).dataBinding.xpath.IndexOf("oda:repeat") < 0);
            Microsoft.Office.Core.CustomXMLNode currentPos = null; // so we can highlight existing choice
            // Yes - if it is a child of common ancestors
            if (OkAsis)
            {
                log.Debug("its top level");
            }
            else
            {
                foreach (Microsoft.Office.Core.CustomXMLNode currentNode in commonAncestors)
                {
                    Microsoft.Office.Core.CustomXMLNode selection =
                        currentNode.SelectSingleNode("oda:row[1]/oda:answer[@id='" + questionID + "']");
                    if (selection != null)
                    {
                        log.Debug("found it");
                        OkAsis = true;
                        currentPos = currentNode;
                        break;
                    }
                }
            }

            // Now make the tree from what is left in commonAncestors

            root = new TreeNode("Ask only once");
            this.treeViewRepeat.Nodes.Add(root);

            TreeNode thisNode = null;
            TreeNode previousNode = null;
            treeViewRepeat.HideSelection = false; // keep selection when focus is lost

            TreeNode nodeToSelect = null;

            foreach (Microsoft.Office.Core.CustomXMLNode currentNode in commonAncestors)
            {
                // Find the question associated with this repeat
                string rptQRef = CustomXMLNodeHelper.getAttribute(currentNode, "qref");
                    //currentNode.Attributes[1].NodeValue;
                question q = questionnaire.getQuestion(rptQRef);
                if (q == null)
                {
                    log.Error("no question with id " + rptQRef);
                }
                thisNode = new TreeNode(q.text);
                thisNode.Tag = rptQRef;

                if (currentNode == currentPos)
                {
                    nodeToSelect = thisNode;
                }

                if (previousNode == null)
                {
                    // Check the innermost (may be overridden below by what level user already had, if possible)
                    this.treeViewRepeat.SelectedNode = thisNode;
                }
                else
                {
                    thisNode.Nodes.Add(previousNode);
                }
                previousNode = thisNode;

            }

            if (thisNode != null)
            {
                root.Nodes.Add(thisNode);
            }
            treeViewRepeat.ExpandAll();

            if (nodeToSelect != null)
            {
                this.treeViewRepeat.SelectedNode = nodeToSelect;
                originalValue = thisNode;
            }
            else if (OkAsis)
            {
                originalValue = root;
                this.treeViewRepeat.SelectedNode = root;
            }
        }
        /// <summary>
        /// When question is first being added, it is being added to a particular CC,
        /// so we only need to consider that CC's ancestors.
        /// 
        /// When a question is being edited, it could be in several content controls,
        /// so that case is handled by the more generic method.
        /// </summary>
        /// <param name="cc"></param>
        /// <param name="questionnaire"></param>
        /// <param name="xppe"></param>
        public void init(Word.ContentControl cc, questionnaire questionnaire, XPathsPartEntry xppe)
        {
            root = new TreeNode("Ask only once");

            Word.ContentControl currentCC = cc.ParentContentControl;

            TreeNode thisNode = null;
            TreeNode previousNode = null;
            treeViewRepeat.HideSelection = false; // keep selection when focus is lost

            while (currentCC != null)
            {
                if (currentCC.Tag.Contains("od:repeat"))
                {
                    TagData td = new TagData(currentCC.Tag);
                    string ancestorRepeatXPathID = td.getRepeatID();

                    // Find associated question
                    xpathsXpath xp = xppe.getXPathByID(ancestorRepeatXPathID);
                    question q = questionnaire.getQuestion(xp.questionID);

                    thisNode = new TreeNode(q.text);
                    thisNode.Tag = ancestorRepeatXPathID;

                    if (previousNode == null)
                    {
                        // Check the innermost
                        treeViewRepeat.SelectedNode = thisNode;
                    }
                    else
                    {
                        thisNode.Nodes.Add(previousNode);
                    }
                    previousNode = thisNode;
                }
                currentCC = currentCC.ParentContentControl;
            }

            if (thisNode == null)
            {
                // Hide the control
            //                this.groupBoxRepeat.Visible = false;
                root = null;
            }
            else
            {
                root.Nodes.Add(thisNode);
                this.treeViewRepeat.Nodes.Add(root);
                treeViewRepeat.ExpandAll();
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="target"></param>
        /// <param name="setSourceAttr">When saving a building block, we want to write the ID of the source part</param>
        /// <param name="setBindingStore">When re-using a building block, storeItemID to write to the xpath; otherwise null</param>
        /// <param name="overwriteExisting">If re-using a building block back into original source, we want to skip silently.
        /// When going the other way, we want to overwrite the logic in any existing building block (since
        /// it may have been updated).</param>
        public void injectLogic(Model targetModel, bool setSourceAttr, bool setBindingStore, bool overwriteExisting)
        {
            //Model targetModel = Model.ModelFactory(target);

            // XPaths
            XPathsPartEntry targetXppe = new XPathsPartEntry(targetModel);
            string sourceAttr = null;
            if (setSourceAttr)
            {
                sourceAttr = srcXPathsPart.Id;
            }
            string answersPartStoreID = null;
            if (setBindingStore)
            {
                answersPartStoreID = targetModel.answersPart.Id;
            }

            // .. add em
            foreach (xpathsXpath xp in BBxpaths)
            {
                xpathsXpath existing = targetXppe.getXPathByID(xp.id);
                if (existing == null)
                {
                    injectLogicXPath(targetXppe, xp, sourceAttr, answersPartStoreID);
                } else
                {
                    // Does it come from this doc?
                    //log.Debug("xp.source: " + xp.source);
                    //log.Debug("existing.source: " + existing.source);
                    //log.Debug("targetModel.xpathsPart.Id: " + targetModel.xpathsPart.Id);
                    if (xp.source != null && xp.source.Equals(targetModel.xpathsPart.Id))
                    {
                        // yes ..
                        if (overwriteExisting)
                        {
                            injectLogicXPath(targetXppe, xp, sourceAttr, answersPartStoreID);
                        }
                        else
                        {
                            continue;
                        }
                    }
                    else if (xp.source != null &&
                        existing.source != null &&
                        xp.source.Equals(existing.source))
                    {
                        // It has already been copied in.
                        // so don't do it again, whether we're copying to template
                        // (could go either way, but for now, only update from original source),
                        // or into docx
                        continue;

                    } else
                    {
                        // Yikes! ID collision
                        throw new BuildingBlockLogicException("XPath with ID " + xp.id + " is already present.");
                    }
                }
            }

            // Questions
            questionnaire targetQuestionnaire = new questionnaire();
            questionnaire.Deserialize(targetModel.questionsPart.XML, out targetQuestionnaire);
            // .. add em
            foreach (question q in BBquestions)
            {
                question existing = targetQuestionnaire.getQuestion(q.id);
                if (existing == null)
                {
                    targetQuestionnaire.questions.Add(q);
                    if (setSourceAttr)
                    {
                        q.source = srcQuestionsPart.Id;
                    }
                }
                else
                {
                    // Does it come from this doc?
                    //log.Debug("q.source: " + q.source);
                    //log.Debug("existing.source: " + existing.source);
                    //log.Debug("targetModel.questionsPart.Id: " + targetModel.questionsPart.Id);
                    if (q.source != null && q.source.Equals(targetModel.questionsPart.Id))
                    {
                        // yes ..
                        if (overwriteExisting)
                        {
                            targetQuestionnaire.questions.Add(q); // this is a HashSet, so we're overrwriting, not adding :-)
                            if (setSourceAttr)
                            {
                                q.source = targetModel.questionsPart.Id;
                            }
                        }
                        else
                        {
                            continue;
                        }
                    }
                    else if (q.source != null &&
                        existing.source != null &&
                        q.source.Equals(existing.source))
                    {
                        // It has already been copied in.
                        continue;
                    }
                    else
                    {
                        // Yikes! ID collision
                        throw new BuildingBlockLogicException("Question with ID " + q.id + " is already present.");
                    }
                }
            }

            // Answers
            answers targetAnswers = new answers();
            answers.Deserialize(targetModel.answersPart.XML, out targetAnswers);
            foreach (answer a in BBanswers)
            {
                answer existing = getAnswer(targetAnswers, a.id);
                if (existing == null)
                {
                    targetAnswers.Items.Add(a);
                    if (setSourceAttr)
                    {
                        a.source = srcAnswersPart.Id;
                    }
                }
                else
                {
                    // Does it come from this doc?
                    if (a.source != null && a.source.Equals(targetModel.answersPart.Id))
                    {
                        log.Debug("source is this part");
                        // yes ..
                        if (overwriteExisting)
                        {
                            log.Debug(".. and overwriting..");
                            targetAnswers.Items.Add(a); // this is a HashSet, so we're overrwriting, not adding :-)
                            if (setSourceAttr)
                            {
                                a.source = srcAnswersPart.Id;
                            }
                        }
                        else
                        {
                            continue;
                        }
                    }
                    else if (a.source != null &&
                        existing.source != null &&
                        a.source.Equals(existing.source))
                    {
                        // It has already been copied in.
                        log.Debug("this logic already present");
                        continue;
                    }
                    else
                    {
                        // Yikes! ID collision
                        throw new BuildingBlockLogicException("Answer with ID " + a.id + " from different source is already present.");
                    }
                }
            }
            foreach (repeat r in BBrepeats)
            {
                repeat existing = getRepeat(targetAnswers, r.qref);
                if (existing == null)
                {
                    targetAnswers.Items.Add(r);
                    if (setSourceAttr)
                    {
                        r.source = srcAnswersPart.Id;
                    }
                }
                else
                {
                    // Does it come from this doc?
                    if (r.source != null && r.source.Equals(targetModel.answersPart.Id))
                    {
                        // yes ..
                        if (overwriteExisting)
                        {
                            targetAnswers.Items.Add(r); // this is a HashSet, so we're overrwriting, not adding :-)
                            if (setSourceAttr)
                            {
                                r.source = srcAnswersPart.Id;
                            }
                        }
                        else
                        {
                            continue;
                        }
                    }
                    else if (r.source != null &&
                        existing.source != null &&
                        r.source.Equals(existing.source))
                    {
                        // It has already been copied in.
                        continue;
                    }
                    else
                    {
                        // Yikes! ID collision
                        throw new BuildingBlockLogicException("Answer with ID " + r.qref + " is already present.");
                    }
                }
            }

            // Conditions
            conditions targetConditions = new conditions();
            conditions.Deserialize(targetModel.conditionsPart.XML, out targetConditions);
            foreach (condition c in BBconditions)
            {
                condition existing = getCondition(targetConditions, c.id);
                if (existing == null)
                {
                    targetConditions.condition.Add(c);
                    if (setSourceAttr)
                    {
                        c.source = srcConditionsPart.Id;
                    }
                }
                else
                {
                    // Does it come from this doc?
                    if (c.source != null && c.source.Equals(targetModel.conditionsPart.Id))
                    {
                        // yes ..
                        if (overwriteExisting)
                        {
                            targetConditions.condition.Add(c);  // this is a HashSet, so we're overrwriting, not adding :-)
                            if (setSourceAttr)
                            {
                                c.source = targetModel.conditionsPart.Id;
                            }
                        }
                        else
                        {
                            continue;
                        }
                    }
                    else if (c.source != null &&
                        existing.source != null &&
                        c.source.Equals(existing.source))
                    {
                        // It has already been copied in.
                        continue;
                    }
                    else
                    {
                        // Yikes! ID collision
                        throw new BuildingBlockLogicException("Condition with ID " + c.id + " is already present.");
                    }
                }
            }

            // .. save: we only save if there have been no ID collisions.
            // Otherwise, we will have aborted with a BuildingBlockLogicException
            targetXppe.save();
            CustomXmlUtilities.replaceXmlDoc(targetModel.questionsPart, targetQuestionnaire.Serialize());
            CustomXmlUtilities.replaceXmlDoc(targetModel.conditionsPart, targetConditions.Serialize());
            CustomXmlUtilities.replaceXmlDoc(targetModel.answersPart, targetAnswers.Serialize());
        }
        public void buttonRepeat_Click(Office.IRibbonControl control)
        {
            Word.Document document = Globals.ThisAddIn.Application.ActiveDocument;

            FabDocxState fabDocxState = (FabDocxState)Globals.ThisAddIn.Application.ActiveDocument.GetVstoObject(Globals.Factory).Tag;

            Model model = fabDocxState.model;
            Office.CustomXMLPart answersPart = model.answersPart; //.userParts[0]; // TODO: make this better
            XPathsPartEntry xppe = new XPathsPartEntry(model); // used to get entries

            questionnaire questionnaire = new questionnaire();
            questionnaire.Deserialize(model.questionsPart.XML, out questionnaire);

            Microsoft.Office.Interop.Word.Range rng = document.ActiveWindow.Selection.Range;

            // Are there any content controls in the selection?
            Word.ContentControls ccs = rng.ContentControls;

            // First identify nested repeats
            List<Word.ContentControl> nestedRepeats = new List<Word.ContentControl>();
            foreach (Word.ContentControl desc in ccs)
            {
                if (desc.Tag.Contains("od:repeat"))
                {
                    nestedRepeats.Add(desc);
                }
            }

            // questions contains questions wrapped by repeat,
            // but not nested inside another repeat
            List<question> questions = new List<question>();

            foreach (Word.ContentControl desc in ccs)
            {
                if (desc.Tag.Contains("od:repeat"))
                {
                    continue; // will handle repeats later
                }

                // exclude if in nested repeat, since
                // this question will have previously been dealt with
                // (ie it already varies with that repeat, or the
                // use has said they don't want it to)
                if (isInside(nestedRepeats, desc))
                {
                    continue;
                }

                //log.Warn("got a desc with tag " + desc.Tag);
                // Get the tag
                if (desc.Tag.Contains("od:xpath"))
                {
                    TagData td = new TagData(desc.Tag);
                    string xpathID = td.getXPathID();
                    //log.Warn("xpath is  " + xpathID);
                    xpathsXpath xp = xppe.getXPathByID(xpathID);
                    log.Warn("qid is  " + xp.questionID);
                    question q = questionnaire.getQuestion(xp.questionID);
                    if (q == null)
                    {
                        log.Error("Consistency issue: couldn't find question {0} used in xpath {1}", xp.questionID, xpathID);
                    }
                    else
                    {
                        questions.Add(q);
                    }
                }
                else if (desc.Tag.Contains("od:condition"))
                {
                    // TODO: find questions inside conditions
                }
            }

            if (questions.Count > 0)
            {
                // Rule: Only questions which aren't used elsewhere can vary in a repeat.
                // Check that none of the questions that will be
                // inside the repeat are also used outside of it.
                List<question> questionsUsedOutside = new List<question>();
                foreach (Word.ContentControl ccx in document.ContentControls)
                {
                    if (isListed(ccs, ccx))
                    {
                        // this control is inside the repeat
                    }
                    else
                    {
                        // its outside, so look at its question
                        // TODO: conditions, repeats
                        if (ccx.Tag.Contains("od:xpath"))
                        {
                            TagData td = new TagData(ccx.Tag);
                            string xpathID = td.getXPathID();
                            //log.Warn("xpath is  " + xpathID);
                            xpathsXpath xp = xppe.getXPathByID(xpathID);
                            //log.Warn("qid is  " + xp.questionID);
                            question q = questionnaire.getQuestion(xp.questionID);
                            if (q == null)
                            {
                                log.Error("Consistency issue: couldn't find question {0} used in xpath {1}", xp.questionID, xpathID);
                            }
                            else
                            {
                                if (questions.Contains(q))
                                {
                                    questionsUsedOutside.Add(q);
                                }
                            }
                        }
                    }
                } // foreach

                // If they are, they can't vary in repeat.  Get the user to OK this.
                if (questionsUsedOutside.Count == 0)
                {
                    log.Info("None of the questions in wrapping repeat are used elsewhere");
                }
                else
                {
                    log.Info(questionsUsedOutside.Count + " of the questions in wrapping repeat are used elsewhere");
                    DialogResult dresult = MessageBox.Show(
                        questionsUsedOutside.Count + " of the questions here are also used elsewhere. If you continue, these won't vary in each repeat.",
                        "Questions used elsewhere", MessageBoxButtons.OKCancel);
                    if (dresult == DialogResult.OK)
                    {
                        // Just remove them from the list
                        foreach (question qx in questionsUsedOutside)
                        {
                            questions.Remove(qx);
                        }
                    }
                    else
                    {
                        log.Info("User cancelled wrapping repeat coz questions used elsewhere");
                        return;
                    }
                }
            }

            // Create control
            Word.ContentControl wrappingRepeatCC = null;
            object oRng = rng;
            try
            {
                fabDocxState.inPlutextAdd = true;
                wrappingRepeatCC = document.ContentControls.Add(Word.WdContentControlType.wdContentControlRichText, ref oRng);
                //cc.MultiLine = true; // Causes error for RichText
            }
            catch (System.Exception ex)
            {
                log.Warn(ex);
                MessageBox.Show("Selection must be either part of a single paragraph, or one or more whole paragraphs");
                fabDocxState.inPlutextAdd = false;
                return;
            }

            FormRepeat formRepeat = new FormRepeat(model.questionsPart, answersPart, model, wrappingRepeatCC);
            formRepeat.ShowDialog();
            string repeatId = formRepeat.ID;
            formRepeat.Dispose();

            // necessary here? shouldn't be..
            //answersPart.NamespaceManager.AddNamespace("answers", "http://opendope.org/answers");

            // Destination for moves
            Office.CustomXMLNode destination = answersPart.SelectSingleNode("//oda:repeat[@qref='" + repeatId + "']/oda:row");
            if (destination == null)
            {
                log.Error("no rpt node " + repeatId);
            }

            Dictionary<string, string> xpathChanges = new Dictionary<string, string>();

            // Questions, Conditions
            // ^^^^^^^^^^^^^^^^^^^^^
            // If so, the associated questions may need to be moved into the repeat in the answers XML.
            // Present a table of questions, where the user can say yes/no to each,
            // then move.. table excludes:
            // 1. any that are used outside the repeat, since these can't be made to vary (see above)
            // 2. any that are in a nested repeat
            if (questions.Count > 0)
            {
                FormRepeatWhichVariables formRepeatWhichVariables = new FormRepeatWhichVariables(questions);
                formRepeatWhichVariables.ShowDialog();

                List<question> questionsWhichRepeat = formRepeatWhichVariables.getVars();

                formRepeatWhichVariables.Dispose();

                log.Info(answersPart.XML);

                foreach (question q in questionsWhichRepeat)
                {
                    // Find the relevant answer (by ID)
                    // (easiest to do using XPath on XML document
                    Office.CustomXMLNode node = answersPart.SelectSingleNode("//oda:answer[@id='" + q.id + "']");
                    if (node == null)
                    {
                        log.Error("no node " + q.id);
                    }
                    string fromXPath = NodeToXPath.getXPath(node);
                    log.Info("from: " + fromXPath);

                    // Move it
                    String nodeXML = node.XML; // No API to add a node!
                    node.ParentNode.RemoveChild(node);
                    destination.AppendChildSubtree(nodeXML);

                    // So we'll have to change its xpath in XPaths part
                    // eg from:
                    //   "/oda:answers/oda:answer[@id='qa_2']"
                    // to:
                    //   "/oda:answers/oda:repeat[@qref='rpt1"]/oda:row[1]/oda:answer[@id='qa_2']"
                    //
                    // CustomXMLNode's Xpath produces something like: /ns2:answers[1]/ns2:answer[1]
                    // which we don't want

                    string toXPath = NodeToXPath.getXPath(destination.LastChild);
                    log.Info("to: " + toXPath);

                    xpathChanges.Add(fromXPath, toXPath);
                }

            }
            // nested repeats
            // ^^^^^^^^^^^^^^
            // 1. move the nested repeat answer structure
            // 2. change the xpath for all questions re anything in the nested repeat

            // Note: if wrapping repeat r0 around r1 which in turn contains r2,
            // must avoid explicitly processing  r2, since doing stuff to r1 here is
            // enough to take care of r2.

            // So, step 0. Find top level nested repeats
            // Already have nestedRepeats list, so just remove from it
            // those which aren't top level.
            foreach (Word.ContentControl desc in nestedRepeats)
            {
                if (!desc.ParentContentControl.ID.Equals(wrappingRepeatCC.ID) )
                {
                    // not top level, so remove
                    nestedRepeats.Remove(desc);
                }
            }

            if (nestedRepeats.Count > 0)
            {
                foreach (Word.ContentControl desc in nestedRepeats)
                {
                    TagData td = new TagData(desc.Tag);
                    string nestedRepeatXPathID = td.getRepeatID();

                    // Get the XPath, to find the question ID,
                    // which is what we use to find the repeat answer.
                    xpathsXpath xp = xppe.getXPathByID(nestedRepeatXPathID);

                    // 1. move the nested repeat answer structure
                    Office.CustomXMLNode node = answersPart.SelectSingleNode("//oda:repeat[@qref='" + xp.questionID + "']");
                    if (node == null)
                    {
                        log.Error("no node for nested repeat " + xp.questionID);
                    }
                    string fromXPath = NodeToXPath.getXPath(node);
                    log.Info("from: " + fromXPath);

                    // Move it
                    String nodeXML = node.XML; // No API to add a node!
                    node.ParentNode.RemoveChild(node);
                    destination.AppendChildSubtree(nodeXML);

                    // 2. change the xpath for all questions re anything in the nested repeat
                    // With a bit of luck, this will just work!
                    string toXPath = NodeToXPath.getXPath(destination.LastChild);
                    log.Info("to: " + toXPath);

                    xpathChanges.Add(fromXPath, toXPath);
                }
            }

            // Now do the substitutions in the XPaths part - for all
            string xpaths = model.xpathsPart.XML;
            foreach (KeyValuePair<string, string> entry in xpathChanges)
            {
                xpaths = xpaths.Replace(entry.Key, entry.Value);
            }
            CustomXmlUtilities.replaceXmlDoc(model.xpathsPart, xpaths);
            //log.Info(model.xpathsPart.XML);
            //log.Info(answersPart.XML);

            // Now do the substitutions in the content control databindings
            // (Added 2012 12 16, since docx4j relies on the databinding element to do its bit)
            foreach (Word.ContentControl cc in wrappingRepeatCC.Range.ContentControls)
            //foreach (Word.ContentControl cc in Globals.ThisAddIn.Application.ActiveDocument.ContentControls)
            {
                // XMLMapping.IsMapped returns false here,
                // in cases where it is mapped! So avoid using that.
                // (Could be a defect elsewhere .. check for usage)
                if (cc.XMLMapping!=null
                    && cc.XMLMapping.XPath!=null
                    && cc.XMLMapping.PrefixMappings!=null)
                {
                    foreach (KeyValuePair<string, string> entry in xpathChanges)
                    {
                        //log.Info("Comparing " + cc.XMLMapping.XPath + " with " + entry.Key);

                        if (cc.XMLMapping.XPath.Equals(entry.Key))
                        {
                            // matched, so replace
                            cc.XMLMapping.SetMapping(entry.Value, cc.XMLMapping.PrefixMappings, answersPart);
                            break;
                        }
                    }
                }
            }
        }