/// <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(); }