/// <summary>
        // RULE: A repeat can be moved wherever under
        // the same repeat ancestor cc.
        // It can be moved elsewhere, provided it is
        // the only cc using that repeat.  (change
        // AF and XPaths accordingly).
        // In principle, all n x cc using that repeat
        // could legally be moved to the same destination,
        // but we have no way to facilitate this!
        /// </summary>
        private void handleRepeat(Word.ContentControl copiedCC)
        {
            td = new TagData(copiedCC.Tag);
            string repeatID = td.getRepeatID();

            suppressDescendantEvents(copiedCC);

            // Look in the answer file to find this repeats
            // closest ancestor repeat (if any).
            // It is enough to look at the XPath, so get that.
            // (Actually, either way you get a qref, which is
            //  NOT a repeat id. )
            string xpathCurrent = xppe.getXPathByID(repeatID).dataBinding.xpath;
            // something like /oda:answers/oda:repeat[@qref='rpt1']/oda:row[1]/oda:repeat[@qref='rpt2']"
            int index = xpathCurrent.LastIndexOf("oda:repeat");
            if (index < 0)
            {
                log.Error("Couldn't find repeat in " + xpathCurrent);
            }
            string xpathSubstring = xpathCurrent.Substring(0, index);
            log.Debug("xpath substring: " + xpathSubstring); //eg /oda:answers/oda:repeat[@qref='rpt1']/oda:row[1]/
            // OK, find last remaining qref
            int qrefIndex = xpathSubstring.LastIndexOf("qref");

            Word.ContentControl rptAncestor = RepeatHelper.getYoungestRepeatAncestor(copiedCC);
            if (qrefIndex < 0)
            {
                // No repeat ancestor, so this repeat cc can be moved anywhere
                log.Debug("No repeat ancestor, so this repeat cc can be moved anywhere");

                // Has it been moved into a repeat?
                if (rptAncestor == null)
                {
                    // Destination has no repeat ancestor,
                    // so nothing to do
                    return;
                }

                // Now change AF and XPath structure.
                string destRptID = (new TagData(rptAncestor.Tag)).getRepeatID();
                string destRptXPath = xppe.getXPathByID(destRptID).dataBinding.xpath;

                //Office.CustomXMLPart answersPart = model.userParts[0]; // TODO: make this better
                //Office.CustomXMLNode node = answersPart.SelectSingleNode(destRptXPath);
                //if (node == null)
                //{
                //    log.Error("no answer for repeat " + destRptXPath);
                //}
                //string toRepeat = NodeToXPath.getXPath(node);
                NodeMover nm = new NodeMover();
                nm.Move(xpathCurrent, destRptXPath);

                return;
                    // TODO: Need to figure out whether to do this just for
                    // the repeat (and let descendants take care of themselves),
                    // or do it for all (and suppress action on descendant event)
                    // NodeMover does it for all XPaths, so unless that is changed, we're looking at suppression!
                    // - suppression is difficult?
                    // - easy to do just a single xpath, but what about answers? we move these as a tree!! leave desc behind?
                // BIG QUESTION: are there things we need to do to the descendants in response to their events,
                // or does this repeat logic take care of everything?
                // What about in the copy case?
            }
            else
            {
                // Had a repeat ancestor, with ancestorRepeatID

                string ancestorQref = xpathSubstring.Substring(qrefIndex + 6);
                //log.Debug("ancestorQref: " + ancestorQref); //  rpt1']/oda:row[1]/
                ancestorQref = ancestorQref.Substring(0, ancestorQref.IndexOf("'"));
                log.Debug("ancestorQref: " + ancestorQref);

                string ancestorRepeatID = xppe.getXPathByQuestionID(ancestorQref).id;
                log.Debug("Had a repeat ancestor, with ancestorRepeatID: " + ancestorRepeatID);

                List<Word.ContentControl> controlsThisRepeat = getRepeatCCsUsingRepeatID(copiedCC, repeatID);

                // Find the new repeat ancestor, if any
                if (rptAncestor == null)
                {
                    // It can be moved elsewhere, provided it is
                    // the only cc using that repeat

                    // Is it the only repeat cc using this repeat id?
                    if (controlsThisRepeat.Count == 0) // since this cc excluded from count
                    {
                        // Yes
                        NodeMover nm = new NodeMover();
                        nm.Move(xpathCurrent, "/oda:answers");
                    }
                    else
                    {
                        // This is a problem
                        MessageBox.Show("Your move includes a repeat content control which can't go here. Removing that repeat.");
                        removeButKeepContents(copiedCC);
                        // and enable descendant events
                        fabDocxState.suppressEventsForSdtID.Clear();
                        return;
                        // ie there were >1 cc using this repeat ID, and we just removed one of them!
                    }
                }
                else
                {
                    string destRptID = (new TagData(rptAncestor.Tag)).getRepeatID();
                    string destRptXPath = xppe.getXPathByID(destRptID).dataBinding.xpath;

                    if (destRptID.Equals(ancestorRepeatID))
                    {
                        // OK, no change in ancestor.
                        // Nothing to do.
                        return;
                    }
                    else if (controlsThisRepeat.Count == 1)
                    {
                        // OK, only used once
                        NodeMover nm = new NodeMover();
                        nm.Move(xpathCurrent, destRptXPath);
                    }
                    else
                    {
                        // Not allowed
                        MessageBox.Show("Your move includes a repeat content control which can't go here. Removing that repeat.");
                        removeButKeepContents(copiedCC);
                        // and enable descendant events
                        fabDocxState.suppressEventsForSdtID.Clear();
                        return;
                        // ie there were >1 cc using this repeat ID, and we just removed one of them!
                    }

                }

            }
        }
        /// <summary>
        // RULE: A variable cc can copied wherever.
        // If its repeat ancestors change, its "vary in repeat"
        // will need to change (to lowest common denominator).
        // If this cc is not in any repeat,
        // make the answer top level and we're done
        // Otherwise, could assume its position is OK wrt
        // existing repeats.
        // So if anything, we just need to move it
        // up the tree until
        // we reach a node which contains this additional repeat.
        // But we'd like this code to
        // be used for both moves and copy. (Move case
        // needs this constraint relaxed)
        // So our algorithm finds viable positions
        // (ie ancestors common to all
        // repeats).
        // That node and higher are candidates for new position
        // Ask user to choose.
        /// </summary>
        protected void handleXPath(string xpathID, bool dontAskIfOkAsis)
        {
            xpathsXpath xp = xppe.getXPathByID(xpathID);

            string questionID = xp.questionID;

            // If this cc is not in any repeat,
            // make the answer top level and we're done

            // Otherwise, we can assume its position is OK wrt
            // existing repeats.

            // So if anything, we just need to move it
            // up the tree until
            // we reach a node which contains this additional repeat.

            // But for the variable move case (handled in MoveHandler)
            // an existing repeat will no longer be a constraint.
            // So better to just have a single algorithm which
            // finds viable positions (ie ancestors common to all
            // repeats).

            // 2 algorithms for doing this.
            // The first:  Make a list of the ancestors of the
            // first repeat.  Then cross off that list anything
            // which isn't an ancestor of the other repeats.

            // The second: Get XPath for each repeat.
            // Find the shortest XPath.
            // Then find the shortest substring common to each
            // repeat. Then make a list out of that.

            // I like the first better.

            // Find all cc's in which this question is used.
            QuestionHelper qh = new QuestionHelper(xppe, cpe);
            List<Word.ContentControl> thisQuestionControls = qh.getControlsUsingQuestion(questionID, xpathID);

            // For each such cc, find closest repeat ancestor cc (if any).
            // With each such repeat, do the above algorithm.

            // Find all cc's in which this question is used.
            List<Word.ContentControl> relevantRepeats = new List<Word.ContentControl>();
            foreach (Word.ContentControl ccx in thisQuestionControls)
            {
                Word.ContentControl rpt = RepeatHelper.getYoungestRepeatAncestor(ccx);
                if (rpt == null)
                {
                    // make the answer top level and we're done.
                    // That means moving it in AF, and XPaths part.
                    log.Info("question " + questionID + " used at top level, so moving it there.");
                    NodeMover nm = new NodeMover();
                    nm.Move(xp.dataBinding.xpath, "/oda:answers");
                    nm.adjustBinding(thisQuestionControls, "/oda:answers", questionID);
                    return;

                }
                else
                {
                    relevantRepeats.Add(rpt);
                }
            }

            Office.CustomXMLPart answersPart = model.answersPart; // userParts[0]; // TODO: make this better

            // That node and higher are candidates for new position
            // Ask user to choose, or cancel (and remove this cc,
            // but what about any impending child add events??  Maybe
            // need a cancel state)

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

            FormMoveQuestion formMoveQuestion = new FormMoveQuestion(answersPart, relevantRepeats, questionnaire, questionID, xppe);
            if (dontAskIfOkAsis
                && formMoveQuestion.OkAsis() )
            {
                // Do nothing
            }
            else
            {
                formMoveQuestion.ShowDialog();
                formMoveQuestion.moveIfNecessary(questionID, xp, answersPart);

                //string varyInRepeat = formMoveQuestion.getVaryingRepeat();
                //if (varyInRepeat == null)
                //{
                //    // make the answer top level and we're done.
                //    NodeMover nm = new NodeMover();
                //    nm.Move(xp.dataBinding.xpath, "/oda:answers");
                //    nm.adjustBinding(thisQuestionControls, "/oda:answers", questionID);
                //}
                //else
                //{
                //    // Move it to the selected repeat
                //    // get the node corresponding to the repeat's row
                //    Office.CustomXMLNode node = answersPart.SelectSingleNode("//oda:repeat[@qref='" + varyInRepeat + "']/oda:row[1]");
                //    if (node == null)
                //    {
                //        log.Error("no node for nested repeat " + varyInRepeat);
                //    }
                //    string toRepeat = NodeToXPath.getXPath(node);
                //    NodeMover nm = new NodeMover();
                //    nm.Move(xp.dataBinding.xpath, toRepeat);
                //    nm.adjustBinding(thisQuestionControls, toRepeat, questionID);

                //}
            }
            formMoveQuestion.Dispose();
        }