public void handle(Word.ContentControl copiedCC)
        {
            // In the move case, we know that if it is a FabDocx content control, it is valid
            // in this docx
            if (copiedCC.Tag == null)
            {
                // Its just some content control we don't care about
                fabDocxState.registerKnownSdt(copiedCC);
                return; // this is OK, since if it contains a repeat or something, that event will fire
            }
            else if (copiedCC.Tag.Contains("od:xpath"))
            {
                td = new TagData(copiedCC.Tag);

                // RULE: A variable cc can copied wherever.
                // If its repeat ancestors change, its "vary in repeat"
                // will need to change (to lowest common denominator).
                handleXPath(td.getXPathID(), true);
            }
            else if (copiedCC.Tag.Contains("od:repeat"))
            {
                // 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).

                handleRepeat(copiedCC);

            }
            else if (copiedCC.Tag.Contains("od:condition"))
            {
                // Identify child content controls
                Microsoft.Office.Interop.Word.Range rng = copiedCC.Range;
                Word.ContentControls ccs = rng.ContentControls;
                foreach (Word.ContentControl desc in ccs)
                {
                    if (desc.ParentContentControl.ID.Equals(copiedCC.ID))
                    {
                        handle(desc);
                    }
                }

            }
        }
        /// <summary>
        /// Handle Word's OnEnter event for content controls, to set the selection in the pane (if the option is set).
        /// </summary>
        /// <param name="ccEntered">A ContentControl object specifying the control that was entered.</param>
        private void doc_ContentControlOnEnter(Word.ContentControl ccEntered)
        {
            log.Debug("Document.ContentControlOnEnter fired.");
            if (ccEntered.XMLMapping.IsMapped)
            {
                log.Debug("control mapped to " + ccEntered.XMLMapping.XPath);
                if (ccEntered.XMLMapping.CustomXMLNode != null)
                {
                    m_cmTaskPane.RefreshControls(Controls.ControlMain.ChangeReason.OnEnter, null, null, null, ccEntered.XMLMapping.CustomXMLNode, null);
                }
                else
                {
                    // Not mapped to anything; probably because the part was replaced?
                    log.Debug(".. but XMLMapping.CustomXMLNode is null");
                    m_cmTaskPane.controlTreeView.DeselectNode();
                    m_cmTaskPane.WarnViaProperties(ccEntered.XMLMapping.XPath);
                }
            }
            else if (ccEntered.Tag!=null)
            {

                TagData td = new TagData(ccEntered.Tag);
                string xpathid = td.getXPathID();
                if (xpathid==null) {
                    xpathid = td.getRepeatID();
                }

                if (xpathid == null)
                {
                    // Visually indicate in the task pane that we're no longer in a mapped control
                    m_cmTaskPane.controlTreeView.DeselectNode();
                    m_cmTaskPane.PropertiesClear();

                } else {
                    log.Debug("control mapped via tag to " + xpathid);

                    // Repeats, escaped XHTML; we don't show anything for conditions
                    XPathsPartEntry xppe = new XPathsPartEntry(m_cmTaskPane.model);
                    xpathsXpath xx = xppe.getXPathByID(xpathid);
                    Office.CustomXMLNode customXMLNode = null;
                    if (xx != null)
                    {
                        log.Debug(xx.dataBinding.xpath);
                        customXMLNode = m_currentPart.SelectSingleNode(xx.dataBinding.xpath);
                    }
                    if (customXMLNode == null)
                    {
                        // Not mapped to anything; probably because the part was replaced?
                        m_cmTaskPane.controlTreeView.DeselectNode();
                        if (xx == null)
                        {
                            log.Error("Couldn't find xpath for " + xpathid);
                            m_cmTaskPane.WarnViaProperties("Missing");
                        }
                        else
                        {
                            log.Warn("Couldn't find target node for " + xx.dataBinding.xpath);
                            m_cmTaskPane.WarnViaProperties(xx.dataBinding.xpath);
                        }
                    }
                    else
                    {
                        m_cmTaskPane.RefreshControls(Controls.ControlMain.ChangeReason.OnEnter, null, null, null, customXMLNode, null);
                    }
                }
            }
            Ribbon.buttonEditEnabled = true;
            Ribbon.buttonDeleteEnabled = true;

            Ribbon.myInvalidate();
        }
        public void handle(Word.ContentControl copiedCC)
        {
            if (copiedCC.Tag == null)
            {
                // Its just some content control we don't care about
                fabDocxState.registerKnownSdt(copiedCC);
                return; // this is OK, since if it contains a repeat or something, that event will fire
            }
            log.Info("copy handler invoked on CC: " + copiedCC.Tag);

            if (copiedCC.Tag.Contains("od:xpath"))
            {
                td = new TagData(copiedCC.Tag);
                string xpathID = td.getXPathID();

                // Check it is known to this docx
                if (xppe.getXPathByID(xpathID) == null)
                {
                    removeButKeepContents(copiedCC);
                    return;
                }
                // RULE: A variable cc can copied wherever.
                // If its repeat ancestors change, its "vary in repeat"
                // will need to change (to lowest common denominator).
                handleXPath(td.getXPathID(), true);
            }
            else if (copiedCC.Tag.Contains("od:repeat"))
            {
                td = new TagData(copiedCC.Tag);
                string xpathID = td.getRepeatID();

                // Check it is known to this docx
                if (xppe.getXPathByID(xpathID) == null)
                {
                    removeButKeepContents(copiedCC);
                    return;
                }
                // RULE: A repeat can only be copied if destination
                // has same repeat ancestors (in which case no change
                // to answer file is required).
                handleRepeat(copiedCC);
            }
            else if (copiedCC.Tag.Contains("od:condition"))
            {
                // Find child CC
                Microsoft.Office.Interop.Word.Range rng = copiedCC.Range;
                Word.ContentControls ccs = rng.ContentControls;
                foreach (Word.ContentControl desc in ccs)
                {
                    if (desc.ParentContentControl.ID.Equals(copiedCC.ID))
                    {
                        handle(desc);
                    }
                }
            }
            else
            {
                // Its just some content control we don't care about
                fabDocxState.registerKnownSdt(copiedCC);
                return;
            }
        }
        /// <summary>
        /// We've already written the correct binding part id, in our xpaths.  We need to do it in the cc'sas well.
        /// </summary>
        /// <param name="range"></param>
        /// <param name="answersPart"></param>
        public void updateBindings(Word.Range range, Office.CustomXMLPart answersPart)
        {
            foreach (Word.ContentControl cc in range.ContentControls)
            {
                if (cc.Tag == null)
                {
                    // Its just some content control we don't care about
                    continue;
                }

                TagData td = new TagData(cc.Tag);
                if (cc.Tag.Contains("od:xpath"))
                {
                    xpathsXpath xp = srcXppe.getXPathByID(td.getXPathID());

                    cc.XMLMapping.SetMapping(xp.dataBinding.xpath, "xmlns:oda='http://opendope.org/answers'",
                        answersPart);
                }
            }
        }
        public void identifyLogic(Word.Range range)
        {
            foreach (Word.ContentControl cc in range.ContentControls)
            {
                if (cc.Tag == null)
                {
                    // Its just some content control we don't care about
                    continue;
                }

                TagData td = new TagData(cc.Tag);
                if (cc.Tag.Contains("od:xpath"))
                {
                    xpathsXpath xp = srcXppe.getXPathByID( td.getXPathID() );
                    identifyLogicInXPath(xp, range, cc);
                }
                else if (cc.Tag.Contains("od:repeat"))
                {
                    testOldestRepeatIncluded(range, cc);

                    xpathsXpath xp = srcXppe.getXPathByID(td.getRepeatID());

                    // TODO: Clone, so we can write source to just the clone
                    //xp.Serialize
                    //xpathsXpath.Deserialize
                    // TODO consider cloning issue further.  I've implemented it
                    // below for xpaths, but since we're throwing the objects
                    // away, it doesn't matter that we're altering them?

                    BBxpaths.Add(xp);

                    string questionID = xp.questionID;
                    question q = srcQuestionnaire.getQuestion(questionID);
                    BBquestions.Add(q);

                    // Answer - just need to do this for the outermost repeat
                    Word.ContentControl oldestRepeat = RepeatHelper.getOldestRepeatAncestor(cc);
                    if (oldestRepeat.ID.Equals(cc.ID))
                    {
                        foreach (object o in srcAnswers.Items)
                        {
                            if (o is repeat)
                            {
                                repeat a = (repeat)o;
                                if (a.qref.Equals(questionID)) // question ID == answer ID
                                {
                                    BBrepeats.Add(a);
                                    log.Debug("Added outermost repeat!");
                                    break;
                                }
                            }
                        }
                    }
                }
                else if (cc.Tag.Contains("od:condition"))
                {
                    // Add the condition
                    condition c = srcCpe.getConditionByID(td.getConditionID());
                    BBconditions.Add(c);

                    // Find and add questions and xpaths used within it
                    //List<xpathsXpath> xpaths = ConditionsPartEntry.getXPathsUsedInCondition(c, srcXppe);
                    List<xpathsXpath> xpaths = new List<xpathsXpath>();
                    c.listXPaths(xpaths, srcCpe.conditions, srcXppe.getXPaths());

                    foreach (xpathsXpath xp in xpaths)
                    {
                        identifyLogicInXPath(xp, range, cc);
                    }
                }
            }
        }
        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;
                        }
                    }
                }
            }
        }