/// <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!
                    }

                }

            }
        }
        protected void populateConditions()
        {
            // Get list of repeat ancestors
            List<String> repeatXPaths = new List<String>();
            Word.ContentControl currentCC = cc.ParentContentControl;
            while (currentCC != null)
            {
                if (currentCC.Tag.Contains("od:repeat"))
                {
                    TagData td = new TagData(currentCC.Tag);
                    string rXPathID = td.getRepeatID();
                    xpathsXpath xp = xppe.getXPathByID(rXPathID);
                    repeatXPaths.Add(xp.dataBinding.xpath);
                }
                currentCC = currentCC.ParentContentControl;
            }

            foreach (condition c in fcb.conditions.condition)
            {
                log.Debug(c.id);

                // Limit to what is in scope. A condition is
                // in scope of it does not use a question which varies
                // in some repeat which is out of scope.
                List<xpathsXpath> xpathsUsedInCondition = new List<xpathsXpath>();
                c.listXPaths(xpathsUsedInCondition, fcb.conditions, xppe.xpaths);

                //foreach(xpathsXpath xp in theList) {
                //    log.Info("condition " + c.id + " uses " + xp.dataBinding.xpath);
                //}
                if (isConditionInScope(repeatXPaths, xpathsUsedInCondition))
                {
                    listBoxQuestions.Items.Add(c);
                }
                else
                {
                    log.Warn("Condition " + c.id + " is out of scope");
                }

            }

            if (this.listBoxQuestions.Items.Count > 0)
            {
                object o = this.listBoxQuestions.Items[0];
                this.listBoxQuestions.Value = o;

                // Freeze predicate and value columns since these aren't relevant
                freezeAsNoQuestions();

            }
            else
            {
                // None of this type
                string message = "No conditions available for re-use";
                this.listBoxQuestions.Items.Add(message);
                this.listBoxQuestions.Value = message;

                freezeAsNoQuestions();
            }
        }
        /// <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();
        }
        ///// <summary>
        ///// Generate a simple ID
        ///// </summary>
        ///// <returns></returns>
        //private string generateId()
        //{
        //    int i = this.answerID.Count;
        //    do
        //    {
        //        i++;
        //    } while (this.answerID.Contains("rpt" + i));
        //    return "rpt" + i;
        //}
        private void buttonNext_Click(object sender, EventArgs e)
        {
            int min = 1;
            int defautlVal = 2;
            int max = 4;
            int step = 1;

            //log.Info(".Text:" + answerID.Text);
            //log.Info(".SelectedText:" + answerID.SelectedText);

            //log.Info(".textBoxQuestionText:" + textBoxQuestionText.Text);

            if (//string.IsNullOrWhiteSpace(this.answerID.Text) ||
                 string.IsNullOrWhiteSpace(this.textBoxQuestionText.Text))
            {
                Mbox.ShowSimpleMsgBoxError("Required data missing!");
                DialogResult = DialogResult.None; // or use on closing event; see http://stackoverflow.com/questions/2499644/preventing-a-dialog-from-closing-in-the-buttons-click-event-handler
                return;
            }
            String repeatName = this.textBoxQuestionText.Text.Trim();

            try
            {
                string foo = repeatNameToIdMap[repeatName];

                Mbox.ShowSimpleMsgBoxError("You already have a repeat named '" + repeatName
                    + "' . Click the 're-use' tab to re-use it, or choose another name.");
                DialogResult = DialogResult.None;
                return;
            }
            catch (KeyNotFoundException knfe)
            {
                // Good
            }

            // Basic data validation
            try
            {
                min = int.Parse(this.textBoxMin.Text);
                defautlVal = int.Parse(this.textBoxDefault.Text);
                max = int.Parse(this.textBoxMax.Text);
                step = int.Parse(this.textBoxRangeStep.Text);
            } catch (Exception) {
                log.Warn("Repeat range val didn't parse to int properly");
            }
            if (min < 0) min = 0;
            if (min > 20) min = 20;
            if (max < min) max = min + 4;
            if (max > 30) max = 30;
            int av = (int)Math.Round((min + max) / 2.0);
            if (defautlVal < min || defautlVal > max)
                defautlVal = av;
            if (step > av) step = 1;

            TagData td;

            // Work out where to put this repeat
            // It will just go in /answers, unless it
            // has a repeat ancestor.
            // So look for one...
            Word.ContentControl repeatAncestor = null;
            Word.ContentControl currentCC = cc.ParentContentControl;
            while (repeatAncestor == null && currentCC != null)
            {
                if (currentCC.Tag.Contains("od:repeat"))
                {
                    repeatAncestor = currentCC;
                    break;
                }
                currentCC = currentCC.ParentContentControl;
            }

            // Generate a nice ID
            // .. first we need a list of existing IDs
            List<string> reserved = new List<string>();
            foreach (question qx in questionnaire.questions)
            {
                reserved.Add(qx.id);
            }
            ID = IdHelper.SuggestID(repeatName, reserved) + "_" + IdHelper.GenerateShortID(2);

            string xpath;
            if (repeatAncestor == null)
            {
                // create it in /answers
                Office.CustomXMLNode node = answersPart.SelectSingleNode("/oda:answers");
                //string xml = "<repeat qref=\"" + ID + "\" xmlns=\"http://opendope.org/answers\"><row/></repeat>";
                string xml = "<oda:repeat qref=\"" + ID + "\" xmlns:oda=\"http://opendope.org/answers\" ><oda:row/></oda:repeat>";

                node.AppendChildSubtree(xml);
                xpath = "/oda:answers/oda:repeat[@qref='" + ID + "']/oda:row"; // avoid trailing [1]

            } else
            {
                td = new TagData(repeatAncestor.Tag);
                string ancestorRepeatXPathID = td.getRepeatID();

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

                Office.CustomXMLNode node = answersPart.SelectSingleNode("//oda:repeat[@qref='" + xp.questionID + "']/oda:row");

                string parentXPath = NodeToXPath.getXPath(node);

                //string xml = "<repeat qref=\"" + ID + "\" xmlns=\"http://opendope.org/answers\"><row/></repeat>";
                string xml = "<oda:repeat qref=\"" + ID + "\"  xmlns:oda=\"http://opendope.org/answers\" ><oda:row/></oda:repeat>";

                node.AppendChildSubtree(xml);

                xpath = parentXPath + "/oda:repeat[@qref='" + ID + "']/oda:row"; // avoid trailing [1]
            }

            log.Info(answersPart.XML);

            // Question
            q = new question();
            //q.id = this.answerID.Text; // not SelectedText
            q.id = ID;
            q.text = repeatName;

            if (!string.IsNullOrWhiteSpace(this.textBoxHelp.Text))
            {
                q.help = this.textBoxHelp.Text;
            }
            if (!string.IsNullOrWhiteSpace(this.textBoxHint.Text))
            {
                q.hint = this.textBoxHint.Text;
            }

            if (this.isAppearanceCompact())
            {
                q.appearance = appearanceType.compact;
                q.appearanceSpecified = true;
            }
            else
            {
                q.appearanceSpecified = false;
            }

            questionnaire.questions.Add(q);

            // Bind to answer XPath
            xpathsXpath xpathEntry = xppe.setup("", answersPart.Id, xpath, null, false);
            xpathEntry.questionID = q.id;
            xpathEntry.dataBinding.prefixMappings = "xmlns:oda='http://opendope.org/answers'";
            xpathEntry.type = "nonNegativeInteger";
            xppe.save();

            // Repeat
            td = new TagData("");
            td.set("od:repeat", xppe.xpathId);
            cc.Tag = td.asQueryString();

            // At this point, answer should be present.  Sanity check!

            cc.Title = "REPEAT " + repeatName;
            cc.SetPlaceholderText(null, null, "Repeating content goes here.");

            //Not for wdContentControlRichText!
            //cc.XMLMapping.SetMapping(xpath, null, model.userParts[0]);

            // Responses
            response responses = q.response;
            if (responses == null)
            {
                responses = new response();
                q.response = responses;
            }

            // MCQ: display response form
            responseFixed responseFixed = new responseFixed();
            responses.Item = responseFixed;

            //for (int i = min; i<=max; i=i+step)
            //{
            //    responseFixedItem item = new OpenDoPEModel.responseFixedItem();

            //    item.value = ""+i;
            //    item.label = "" + i;

            //    responseFixed.item.Add(item);
            //}

            responseFixed.canSelectMany = false;

            // Finally, add to part
            updateQuestionsPart();

            this.Close();
        }
        public void populateQuestions(string type)
        {
            log.Debug("populateQuestions of type " + type);
            listBoxQuestions.Value = null;
            listBoxQuestions.Items.Clear();

            if (type != null && type.Equals("repeats"))
            {
                populateRepeats();
                return;
            }

            if (type != null && type.Equals("conditions"))
            {
                populateConditions();
                return;
            }

            HashSet<question> questions = null;

            // Find questions which are in scope:
            questions = new HashSet<question>();

            // Add their questions.  Could do this by finding
            // the repeat answer via XPath, then getting
            // variables in it, but in this case its easier
            // just to string match in the XPaths part.
            xpaths xpaths = xppe.xpaths;

            // Get list of repeat ancestors
            List<Word.ContentControl> relevantRepeats = new List<Word.ContentControl>();
            Word.ContentControl currentCC = cc.ParentContentControl;
            while (currentCC != null)
            {
                if (currentCC.Tag.Contains("od:repeat"))
                {
                    relevantRepeats.Add(currentCC);
                }
                currentCC = currentCC.ParentContentControl;
            }

            foreach (Word.ContentControl rcc in relevantRepeats)
            {
                TagData td = new TagData(rcc.Tag);
                string rXPathID = td.getRepeatID();
                xpathsXpath xp = xppe.getXPathByID(rXPathID);
                string rXPath = xp.dataBinding.xpath;
                int rXPathLength = rXPath.Length;

                // we want xpaths containing this xpath,
                // and no extra repeat
                foreach (xpathsXpath xx in xpaths.xpath)
                {
                    if (xx.questionID != null) // includes repeats. Shouldn't if can add/remove row on XForm?
                    {
                        string thisXPath = xx.dataBinding.xpath;
                        if (thisXPath.Contains(rXPath))
                        {
                            if (thisXPath.LastIndexOf("oda:repeat") < rXPathLength)
                            {
                                questions.Add(
                                    questionnaire.getQuestion(xx.questionID));
                            }
                        }
                    }
                }
            }

            // Finally, add top level questions
            foreach (xpathsXpath xx in xpaths.xpath)
            {
                if (xx.questionID != null)
                {
                    string thisXPath = xx.dataBinding.xpath;
                    if (thisXPath.IndexOf("oda:repeat") < 0)
                    {
                        questions.Add(
                            questionnaire.getQuestion(xx.questionID));
                    }
                }
            }

            if (questions.Count == 0)
            {
                // None of any type
                string message = "No questions in scope!";
                this.listBoxQuestions.Items.Add(message);
                this.listBoxQuestions.Value = message;

                freezeAsNoQuestions();
            }
            else
            {
                // filter and add
                foreach (question q in questions)
                {

                    xpathsXpath xpath = xppe.getXPathByQuestionID(q.id);
                    if (xpath.dataBinding.xpath.EndsWith("oda:row"))
                    {
                        // Ignore raw repeats

                    } else if (type == null
                       || type.Equals(ALL_QUESTION_TYPES))
                    {
                        // all questions
                        listBoxQuestions.Items.Add(q);
                    }
                    else
                    {
                        if (xpath.type != null
                            && xpath.type.Equals(type))
                        {
                            listBoxQuestions.Items.Add(q);
                            log.Debug("Added to listbox " + q.id);

                        }
                    }
                }

                if (this.listBoxQuestions.Items.Count > 0)
                {
                    object o = this.listBoxQuestions.Items[0];
                    this.listBoxQuestions.Value = o;

                    if (o is question)
                    {
                        currentQuestion = (question)this.listBoxQuestions.Items[0];
                        populateValues(augmentResponses(currentQuestion), null);
                    }
                    else
                    {
                        currentQuestion = null;
                    }
                    populatePredicates(o);
                }
                else
                {
                    // None of this type
                    string message = "No questions of type " + type;
                    this.listBoxQuestions.Items.Add(message);
                    this.listBoxQuestions.Value = message;

                    freezeAsNoQuestions();
                }
            }

            this.listBoxQuestions.Items.Add(NEW_QUESTION);
            //if (this.listBoxQuestions.Items.Count == 2)
            //{
            //    this.listBoxQuestions.Value = NEW_QUESTION;
            //}
        }
        public Word.ContentControl getRepeatAncestor(Word.ContentControl cc)
        {
            Word.ContentControl repeatAncestor = null;

            if (root != null
                && !treeViewRepeat.SelectedNode.Equals(root)) // this varies with some repeat
            {
                // Which one is checked?
                string variesInRepeatId = (string)treeViewRepeat.SelectedNode.Tag;

                Word.ContentControl currentCC = cc.ParentContentControl;
                while (repeatAncestor == null && currentCC != null)
                {
                    if (currentCC.Tag.Contains("od:repeat"))
                    {
                        TagData td = new TagData(currentCC.Tag);
                        string variesXPathID = td.getRepeatID();

                        if (variesXPathID.Equals(variesInRepeatId))
                        {
                            return currentCC;
                        }
                    }
                    currentCC = currentCC.ParentContentControl;
                }
            }

            return null;
        }
        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;
            }
        }
        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();
            }
        }
        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;
                        }
                    }
                }
            }
        }
        public void populateQuestions(string type)
        {
            listBoxQuestions.Items.Clear();

            if (type != null && type.Equals(REPEAT_COUNT))
            {
                populateRepeats();
                return;
            }

            if (type != null && type.Equals(REPEAT_POS))
            {
                // This special type does not relate to any specific repeat question
                return;
            }

            HashSet<question> questions = null;

            bool limitScope = !checkBoxScope.Checked;
            if (limitScope)
            {
                // Find questions which are in scope:
                questions = new HashSet<question>();

                // Add their questions.  Could do this by finding
                // the repeat answer via XPath, then getting
                // variables in it, but in this case its easier
                // just to string match in the XPaths part.
                xpaths xpaths = xppe.xpaths;

                // Get list of repeat ancestors
                List<Word.ContentControl> relevantRepeats = new List<Word.ContentControl>();
                Word.ContentControl currentCC = cc.ParentContentControl;
                while (currentCC != null)
                {
                    if (currentCC.Tag.Contains("od:repeat"))
                    {
                        relevantRepeats.Add(currentCC);
                    }
                    currentCC = currentCC.ParentContentControl;
                }

                foreach (Word.ContentControl rcc in relevantRepeats)
                {
                    TagData td = new TagData(rcc.Tag);
                    string rXPathID = td.getRepeatID();
                    xpathsXpath xp = xppe.getXPathByID(rXPathID);
                    string rXPath = xp.dataBinding.xpath;
                    int rXPathLength = rXPath.Length;

                    // we want xpaths containing this xpath,
                    // and no extra repeat
                    foreach (xpathsXpath xx in xpaths.xpath)
                    {
                        if (xx.questionID != null) // includes repeats. Shouldn't if can add/remove row on XForm?
                        {
                            string thisXPath = xx.dataBinding.xpath;
                            if (thisXPath.Contains(rXPath))
                            {
                                if (thisXPath.LastIndexOf("oda:repeat") < rXPathLength)
                                {
                                    questions.Add(
                                        questionnaire.getQuestion(xx.questionID));
                                }
                            }
                        }
                    }
                }

                // Finally, add top level questions
                foreach (xpathsXpath xx in xpaths.xpath)
                {
                    if (xx.questionID != null)
                    {
                        string thisXPath = xx.dataBinding.xpath;
                        if (thisXPath.IndexOf("oda:repeat") < 0)
                        {
                            questions.Add(
                                questionnaire.getQuestion(xx.questionID));
                        }
                    }
                }

            }
            else
            {
                questions = questionnaire.questions;
            }

            foreach (question q in questions)
            {
                if (type == null)
                //   || type.Equals("ALL"))
                {
                    // all questions
                    listBoxQuestions.Items.Add(q);
                }
                else
                {
                    xpathsXpath xpath = xppe.getXPathByQuestionID(q.id);

                    if (xpath.type != null)
                    {
                        if (type.Equals("card number"))
                        {
                            type = "card-number"; // the real date type name
                        }

                        if (type.Equals("number"))
                        {
                            // special case
                            if (xpath.type.Equals("decimal")
                                || xpath.type.Contains("Integer"))
                            {
                                listBoxQuestions.Items.Add(q);
                                log.Debug("Added to listbox " + q.id);
                            }
                        }
                        else
                        {
                            if (xpath.type.Equals(type))
                            {
                                listBoxQuestions.Items.Add(q);
                                log.Debug("Added to listbox " + q.id);

                            }
                        }
                    }
                }
            }
        }