//Word.ContentControl copiedCC) public ContentControlHandlerAbstract() { fabDocxState = (FabDocxState)Globals.ThisAddIn.Application.ActiveDocument.GetVstoObject(Globals.Factory).Tag; //this.copiedCC = copiedCC; model = fabDocxState.model; xppe = new XPathsPartEntry(model); // used to get entries cpe = new ConditionsPartEntry(model); }
public QuestionListHelper(Model model, XPathsPartEntry xppe, questionnaire questionnaire, Word.ContentControl cc) { this.model = model; this.xppe = xppe; this.questionnaire = questionnaire; this.cc = cc; }
public FormMoveQuestion(Office.CustomXMLPart answersPart, List<Word.ContentControl> relevantRepeats, questionnaire questionnaire, string questionID, XPathsPartEntry xppe) { InitializeComponent(); this.textBoxQuestion.Text = questionnaire.getQuestion(questionID).text; this.controlQuestionVaryWhichRepeat1.init(answersPart, relevantRepeats, questionnaire, questionID, xppe); }
public static bool doesConditionUseQuestion(XPathsPartEntry xppe, conditions conditions, condition c, string questionID) { //List<xpathsXpath> xpaths = ConditionsPartEntry.getXPathsUsedInCondition(c, xppe); List<xpathsXpath> xpaths = new List<xpathsXpath>(); c.listXPaths(xpaths, conditions, xppe.getXPaths()); foreach (xpathsXpath xpathObj in xpaths) { String xpathVal = xpathObj.dataBinding.xpath; if (xpathVal.StartsWith("/")) { // simple //System.out.println("question " + xpathObj.getQuestionID() // + " is in use via boolean condition " + conditionId); if (xpathObj.questionID.Equals(questionID)) { return true; } } else if (xpathVal.Contains("position")) { continue; } else { //System.out.println(xpathVal); String qid = xpathVal.Substring( xpathVal.LastIndexOf("@id") + 5); // System.out.println("Got qid: " + qid); qid = qid.Substring(0, qid.IndexOf("'")); // System.out.println("Got qid: " + qid); //System.out.println("question " + qid // + " is in use via condition " + conditionId); if (qid.Equals(questionID)) { return true; } } } return false; }
/// <summary> /// Add xpath to xpaths part, and a condition to the conditions part. /// </summary> /// <param name="model"></param> /// <param name="cxpId"></param> /// <param name="strXPath"></param> /// <param name="prefixMappings"></param> public condition setup(string cxpId, string strXPath, string prefixMappings, bool setupQuestionNow) { ////////////////////////// // First, add the new XPath // Drop any trailing "/" from a Condition XPath if (strXPath.EndsWith("/")) { strXPath = strXPath.Substring(0, strXPath.Length - 1); log.Debug("truncated to " + strXPath); } XPathsPartEntry xppe = new XPathsPartEntry(model); xpathsXpath xpath = xppe.setup("cond", cxpId, strXPath, prefixMappings, setupQuestionNow); xppe.save(); return(setup(xpath)); }
public FormConditionBuilder(Word.ContentControl cc, ConditionsPartEntry cpe, condition existingCondition) { InitializeComponent(); // NET 4 way; see http://msdn.microsoft.com/en-us/library/microsoft.office.tools.word.extensions.aspx FabDocxState fabDocxState = (FabDocxState)Globals.Factory.GetVstoObject(Globals.ThisAddIn.Application.ActiveDocument).Tag; // NET 3.5 way, which requires using Microsoft.Office.Tools.Word.Extensions //FabDocxState fabDocxState = (FabDocxState)Globals.ThisAddIn.Application.ActiveDocument.GetVstoObject(Globals.Factory).Tag; this.model = fabDocxState.model; xppe = new XPathsPartEntry(model); this.cc = cc; this.cpe = cpe; this.existingCondition = existingCondition; this.questionsPart = model.questionsPart; questionnaire qtmp = new questionnaire(); questionnaire.Deserialize(questionsPart.XML, out qtmp); questionnaire = qtmp; conditions ctmp = new conditions(); conditions.Deserialize(model.conditionsPart.XML, out ctmp); conditions = ctmp; log.Debug("conditions: " + conditions.Serialize()); this.listBoxGovernor.Items.Add("all"); this.listBoxGovernor.Items.Add("any"); this.listBoxGovernor.Items.Add("none"); this.listBoxGovernor.SelectedItem = "all"; rowHelper = new Helpers.ConditionsFormRowHelper(model, xppe, questionnaire, cc, this); rowHelper.init(this.dataGridView); DataGridViewRow row = this.dataGridView.Rows[0]; rowHelper.populateRow(row, null, null); }
public void buttonFormat_Click(Office.IRibbonControl control) { /* You can only format a date or a number here. * * For a date, we use cc date stuff: * <w:sdt> <w:sdtPr> <w:date w:fullDate="2012-07-13T00:00:00Z"> <w:dateFormat w:val="dddd, d MMMM yyyy"/> <w:lid w:val="en-AU"/> <w:storeMappedDataAs w:val="date"/> <w:calendar w:val="gregorian"/> </w:date> </w:sdtPr> * * For a number, ... */ if (currentCC.Tag != null) { String XPathID = (new TagData(currentCC.Tag)).getXPathID(); if (XPathID != null) { // Get the xpath obj FabDocxState fabDocxState = getState(); XPathsPartEntry xppe = new XPathsPartEntry(fabDocxState.model); xpathsXpath xpo = xppe.getXPathByID(XPathID); if (xpo.type != null) { if (xpo.type.Equals("date")) { Forms.FormFormatDate formDate = new Forms.FormFormatDate(currentCC); formDate.ShowDialog(); formDate.Dispose(); } //else if (xpo.type.Equals("decimal") // || xpo.type.Equals("integer") // || xpo.type.Equals("positiveInteger") // || xpo.type.Equals("nonPositiveInteger") // || xpo.type.Equals("negativeInteger") // || xpo.type.Equals("nonNegativeInteger") // ) //{ // return true; //} } } } }
private void injectLogicXPath(XPathsPartEntry targetXppe, xpathsXpath xp, string sourceAttr, string answersPartStoreID) { if (sourceAttr != null) xp.source = sourceAttr; if (answersPartStoreID != null) { xp.dataBinding.storeItemID = answersPartStoreID; } targetXppe.getXPaths().xpath.Add(xp); // this is a HashSet, so we're overrwriting, not adding :-) }
private void QuestionCreateNew() { FormQA formQA = new FormQA(cc, false); formQA.ShowDialog(); question q = formQA.getQuestion(); formQA.Dispose(); // Refresh these xppe = new XPathsPartEntry(model); fcb.xppe = xppe; questionnaire.Deserialize(model.questionsPart.XML, out questionnaire); fcb.questionnaire = questionnaire; // How do we know what the new question was? Just refresh.. //listBoxTypeFilter.Value = "ALL"; //populateQuestions("ALL"); //dataGridView.RefreshEdit(); //critical listBoxQuestions.Items.Add(q); listBoxQuestions.Value = q; //myCachedComboBox.SelectedItem = q; dataGridView.RefreshEdit(); //critical log.Debug("set question " + q); //log.Debug("Repopulating row " + rowIndex); //DataGridViewRow row = dataGridView.Rows[rowIndex]; //populateRow(row, null, null); //dataGridView.RefreshEdit(); }
private void init1() { FabDocxState fabDocxState = (FabDocxState)Globals.ThisAddIn.Application.ActiveDocument.GetVstoObject(Globals.Factory).Tag; this.model = fabDocxState.model; xppe = new XPathsPartEntry(model); this.questionsPart = model.questionsPart; questionnaire = new questionnaire(); questionnaire.Deserialize(questionsPart.XML, out questionnaire); this.answersPart = model.answersPart; }
public FormRepeat(Office.CustomXMLPart questionsPart, Office.CustomXMLPart answersPart, Model model, Word.ContentControl cc) { InitializeComponent(); this.model = model; this.cc = cc; this.questionsPart = questionsPart; questionnaire = new questionnaire(); questionnaire.Deserialize(questionsPart.XML, out questionnaire); this.answersPart = answersPart; Office.CustomXMLNodes answers = answersPart.SelectNodes("//oda:repeat"); foreach (Office.CustomXMLNode answer in answers) { this.answerID.Add(CustomXMLNodeHelper.getAttribute(answer, "qref")); // ID } // Suggest ID .. the idea is that // the question ID = answer ID. // this.ID = generateId(); xppe = new XPathsPartEntry(model); // List of repeat names, for re-use purposes // (we need a map from repeat name (shown in the UI) to repeat id, // which is xppe.xpathId). // What is it that distinguishes a repeat from any other question? // Answer: The fact that the XPath pointing to it ends with oda:row // Only show repeats which are in scope (since this repeat is not allowed elsewhere) // - if no ancestor repeat, then top level repeats. // - if there is an ancestor repeat, then just those which are direct children of it. Word.ContentControl rptAncestor = RepeatHelper.getYoungestRepeatAncestor(cc); String scope = "/oda:answers"; if (rptAncestor != null) { // find its XPath scope = xppe.getXPathByID( (new TagData(rptAncestor.Tag)).getRepeatID() ).dataBinding.xpath; } repeatNameToIdMap = new Dictionary<string, string>(); foreach (xpathsXpath xpath in xppe.getXPaths().xpath) { if (xpath.dataBinding.xpath.EndsWith("oda:row")) { if (isRepeatInScope(scope, xpath.dataBinding.xpath)) { // the repeat "name" is its question text. // Get that. question q = questionnaire.getQuestion(xpath.questionID); repeatNameToIdMap.Add(q.text, xpath.id); } } } // Populate the comboBox foreach(KeyValuePair<String,String> entry in repeatNameToIdMap) { this.comboBoxRepeatNames.Items.Add(entry.Key); } }
public static void editXPath(Word.ContentControl cc) { Word.Document document = Globals.ThisAddIn.Application.ActiveDocument; // First, work out whether this is a condition or a repeat or a plain bind bool isCondition = false; bool isRepeat = false; bool isBind = false; if ( (cc.Title!=null && cc.Title.StartsWith("Condition") ) || (cc.Tag!=null && cc.Tag.Contains("od:condition") )) { isCondition = true; } else if ( (cc.Title!=null && cc.Title.StartsWith("Repeat")) || (cc.Tag!=null && cc.Tag.Contains("od:repeat") )) { isRepeat = true; } else if ((cc.Title != null && cc.Title.StartsWith("Data")) || (cc.Tag != null && cc.Tag.Contains("od:xpath")) || cc.XMLMapping.IsMapped ) { isBind = true; } else { // Ask user using (Forms.ConditionOrRepeat cor = new Forms.ConditionOrRepeat()) { if (cor.ShowDialog() == DialogResult.OK) { isCondition = cor.radioButtonCondition.Checked; isRepeat = cor.radioButtonRepeat.Checked; isBind = cor.radioButtonBind.Checked; } else { // They cancelled return; } } } // OK, now we know whether its a condition or a repeat or a bind // Is it already mapped to something? TagData td = new TagData(cc.Tag); Model model = Model.ModelFactory(document); string strXPath = ""; // In order to get Id and prefix mappings for current part CustomTaskPane ctpPaneForThisWindow = Utilities.FindTaskPaneForCurrentWindow(); Controls.ControlMain ccm = (Controls.ControlMain)ctpPaneForThisWindow.Control; string cxpId = ccm.CurrentPart.Id; string prefixMappings = ""; // TODO GetPrefixMappings(ccm.CurrentPart.NamespaceManager); log.Debug("default prefixMappings: " + prefixMappings); XPathsPartEntry xppe = null; ConditionsPartEntry cpe = null; if (isCondition && td.get("od:condition") != null) { string conditionId = td.get("od:condition"); cpe = new ConditionsPartEntry(model); condition c = cpe.getConditionByID(conditionId); string xpathid = null; if (c!=null && c.Item is xpathref) { xpathref ex = (xpathref)c.Item; xpathid = ex.id; // Now fetch the XPath xppe = new XPathsPartEntry(model); xpathsXpath xx = xppe.getXPathByID(xpathid); if (xx != null) { strXPath = xx.dataBinding.xpath; cxpId = xx.dataBinding.storeItemID; prefixMappings = xx.dataBinding.prefixMappings; } } } else if (isRepeat && td.get("od:repeat") != null) { string repeatId = td.get("od:repeat"); // Now fetch the XPath xppe = new XPathsPartEntry(model); xpathsXpath xx = xppe.getXPathByID(repeatId); if (xx != null) { strXPath = xx.dataBinding.xpath; cxpId = xx.dataBinding.storeItemID; prefixMappings = xx.dataBinding.prefixMappings; } } else if (isBind) { if (cc.XMLMapping.IsMapped) { // Prefer this, if for some reason it contradicts od:xpath strXPath = cc.XMLMapping.XPath; cxpId = cc.XMLMapping.CustomXMLPart.Id; prefixMappings = cc.XMLMapping.PrefixMappings; } else if( td.get("od:xpath") != null) { string xpathId = td.get("od:xpath"); // Now fetch the XPath xppe = new XPathsPartEntry(model); xpathsXpath xx = xppe.getXPathByID(xpathId); if (xx != null) { strXPath = xx.dataBinding.xpath; cxpId = xx.dataBinding.storeItemID; prefixMappings = xx.dataBinding.prefixMappings; } } } // Now we can present the form using (Forms.XPathEditor xpe = new Forms.XPathEditor()) { xpe.textBox1.Text = strXPath; if (xpe.ShowDialog() == DialogResult.OK) { strXPath = xpe.textBox1.Text; } else { // They cancelled return; } } // Now give effect to it td = new TagData(""); if (isCondition) { // Create the new condition. Doesn't attempt to delete // the old one (if any) if (cpe == null) { cpe = new ConditionsPartEntry(model); } cpe.setup(cxpId, strXPath, prefixMappings, true); cpe.save(); cc.Title = "Conditional: " + cpe.conditionId; // Write tag td.set("od:condition", cpe.conditionId); cc.Tag = td.asQueryString(); } else if (isRepeat) { // Create the new repeat. Doesn't attempt to delete // the old one (if any) if (xppe == null) { xppe = new XPathsPartEntry(model); } xppe.setup("rpt", cxpId, strXPath, prefixMappings, false); xppe.save(); cc.Title = "Repeat: " + xppe.xpathId; // Write tag td.set("od:repeat", xppe.xpathId); cc.Tag = td.asQueryString(); } else if (isBind) { // Create the new bind. Doesn't attempt to delete // the old one (if any) if (xppe == null) { xppe = new XPathsPartEntry(model); } Word.XMLMapping bind = cc.XMLMapping; bool mappable = bind.SetMapping(strXPath, prefixMappings, CustomXmlUtilities.getPartById(document, cxpId) ); if (mappable) { // What does the XPath point to? string val = cc.XMLMapping.CustomXMLNode.Text; cc.Title = "Data value: " + xppe.xpathId; if (ContentDetection.IsBase64Encoded(val)) { // Force picture content control ... // cc.Type = Word.WdContentControlType.wdContentControlPicture; // from wdContentControlText (or wdContentControlRichText for that matter) // doesn't work (you get "inappropriate range for applying this // content control type"). cc.Delete(true); // Now add a new cc object missing = System.Type.Missing; Globals.ThisAddIn.Application.Selection.Collapse(ref missing); cc = document.ContentControls.Add( Word.WdContentControlType.wdContentControlPicture, ref missing); cc.XMLMapping.SetMapping(strXPath, prefixMappings, CustomXmlUtilities.getPartById(document, cxpId)); } else if (ContentDetection.IsXHTMLContent(val) ) { td.set("od:ContentType", "application/xhtml+xml"); cc.Tag = td.asQueryString(); cc.XMLMapping.Delete(); cc.Type = Word.WdContentControlType.wdContentControlRichText; cc.Title = "XHTML: " + xppe.xpathId; if (Inline2Block.containsBlockLevelContent(val)) { Inline2Block i2b = new Inline2Block(); cc = i2b.convertToBlockLevel(cc, true); if (cc == null) { MessageBox.Show("Problems inserting block level XHTML at this location."); return; } } } xppe.setup(null, cxpId, strXPath, prefixMappings, true); xppe.save(); td.set("od:xpath", xppe.xpathId); cc.Tag = td.asQueryString(); } else { xppe.setup(null, cxpId, strXPath, prefixMappings, true); xppe.save(); td.set("od:xpath", xppe.xpathId); cc.Title = "Data value: " + xppe.xpathId; cc.Tag = td.asQueryString(); log.Warn(" XPath \n\r " + strXPath + "\n\r does not return an element. The OpenDoPE pre-processor will attempt to evaluate it, but Word will never update the result. "); bind.Delete(); MessageBox.Show(" XPath \n\r " + strXPath + "\n\r does not return an element. Check this is what you want? "); } } }
public QuestionHelper(XPathsPartEntry xppe, ConditionsPartEntry cpe) { this.xppe = xppe; this.cpe = cpe; }
/// <summary> /// Add xpath to xpaths part, and a condition to the conditions part. /// </summary> /// <param name="model"></param> /// <param name="cxpId"></param> /// <param name="strXPath"></param> /// <param name="prefixMappings"></param> public condition setup(string cxpId, string strXPath, string prefixMappings, bool setupQuestionNow) { ////////////////////////// // First, add the new XPath // Drop any trailing "/" from a Condition XPath if (strXPath.EndsWith("/")) { strXPath = strXPath.Substring(0, strXPath.Length - 1); log.Debug("truncated to " + strXPath); } XPathsPartEntry xppe = new XPathsPartEntry(model); xpathsXpath xpath = xppe.setup("cond", cxpId, strXPath, prefixMappings, setupQuestionNow); xppe.save(); return setup(xpath); }
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; } }
public void init( Office.CustomXMLPart answersPart, questionnaire questionnaire, question q, XPathsPartEntry xppe, ConditionsPartEntry cpe) { QuestionHelper qh = new QuestionHelper(xppe, cpe); thisQuestionControls = qh.getControlsUsingQuestion(q); List<Word.ContentControl> relevantRepeats = new List<Word.ContentControl>(); foreach (Word.ContentControl ccx in thisQuestionControls) { Word.ContentControl rpt = RepeatHelper.getYoungestRepeatAncestor(ccx); if (rpt == null) { // will have to make the answer top level and we're done. break; } else { relevantRepeats.Add(rpt); } } init( answersPart, relevantRepeats, questionnaire, q.id, xppe); }
/// <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 LibraryHelper(Model srcModel) { srcXppe = new XPathsPartEntry(srcModel); // used to get entries this.srcXPathsPart = srcModel.xpathsPart; srcCpe = new ConditionsPartEntry(srcModel); this.srcConditionsPart = srcModel.conditionsPart; this.srcQuestionsPart = srcModel.questionsPart; srcQuestionnaire = new questionnaire(); questionnaire.Deserialize(srcQuestionsPart.XML, out srcQuestionnaire); srcAnswersPart = srcModel.answersPart; srcAnswers = new answers(); answers.Deserialize(srcAnswersPart.XML, out srcAnswers); }
/// <summary> /// Create a content control mapped to the selected XML node. /// </summary> /// <param name="CCType">A WdContentControlType value specifying the type of control to create.</param> public void CreateMappedControl(Word.WdContentControlType CCType, ControlTreeView.OpenDopeType odType, ControlTreeView controlTreeView, ControlMain controlMain, Word.Document CurrentDocument, Office.CustomXMLPart CurrentPart, //XmlDocument OwnerDocument, bool _PictureContentControlsReplace ) { OpenDoPEModel.DesignMode designMode = new OpenDoPEModel.DesignMode(CurrentDocument); designMode.Off(); try { object missing = Type.Missing; TreeNode tn = controlTreeView.treeView.SelectedNode; if (((XmlNode)tn.Tag).NodeType == XmlNodeType.Text) { tn = tn.Parent; } //get an nsmgr NameTable nt = new NameTable(); //generate the xpath and the ns manager XmlNamespaceManager xmlnsMgr = new XmlNamespaceManager(nt); string strXPath = Utilities.XpathFromXn(CurrentPart.NamespaceManager, (XmlNode)tn.Tag, true, xmlnsMgr); log.Info("Right click for XPath: " + strXPath); string prefixMappings = Utilities.GetPrefixMappings(xmlnsMgr); // Insert bind | condition | repeat // depending on which mode button is pressed. TagData td = new TagData(""); if ((controlMain.modeControlEnabled == false && odType == ControlTreeView.OpenDopeType.Unspecified) // ie always mode bind || (controlMain.modeControlEnabled == true && controlMain.controlMode1.isModeBind()) || odType == ControlTreeView.OpenDopeType.Bind) { log.Debug("In bind mode"); String val = ((XmlNode)tn.Tag).InnerText; //bool isXHTML = HasXHTMLContent(tn); bool isPicture = false; bool isXHTML = false; bool isFlatOPC = ContentDetection.IsFlatOPCContent(val); Word.ContentControl cc = null; if (isFlatOPC) { // <?mso-application progid="Word.Document"?> // <pkg:package xmlns:pkg="http://schemas.microsoft.com/office/2006/xmlPackage"> log.Debug(".. contains block content "); cc = CurrentDocument.Application.Selection.ContentControls.Add( Word.WdContentControlType.wdContentControlRichText, ref missing); // Ensure block level Inline2Block i2b = new Inline2Block(); cc = i2b.convertToBlockLevel(cc, false, true); if (cc == null) { MessageBox.Show("Problems inserting block level WordML at this location."); return; } } else { isXHTML = ContentDetection.IsXHTMLContent(val); } if (isXHTML) { cc = CurrentDocument.Application.Selection.ContentControls.Add( Word.WdContentControlType.wdContentControlRichText, ref missing); if (Inline2Block.containsBlockLevelContent(val)) { Inline2Block i2b = new Inline2Block(); cc = i2b.convertToBlockLevel(cc, true, true); if (cc == null) { MessageBox.Show("Problems inserting block level XHTML at this location."); designMode.restoreState(); return; } } } else if (ContentDetection.IsBase64Encoded(val)) { isPicture = true; if (_PictureContentControlsReplace) { // Use a rich text control instead cc = CurrentDocument.ContentControls.Add( Word.WdContentControlType.wdContentControlRichText, ref missing); PictureUtils.pastePictureIntoCC(cc, Convert.FromBase64String(val)); } else { // Force picture content control log.Debug("Detected picture"); cc = CurrentDocument.Application.Selection.ContentControls.Add(Word.WdContentControlType.wdContentControlPicture, ref missing); } } else if (!isFlatOPC) { log.Debug("Not picture or XHTML; " + CCType.ToString()); // This formulation seems more susceptible to "locked for editing" //object rng = CurrentDocument.Application.Selection.Range; //cc = CurrentDocument.ContentControls.Add(Word.WdContentControlType.wdContentControlText, ref rng); // so prefer: cc = CurrentDocument.Application.Selection.ContentControls.Add(CCType, ref missing); } XPathsPartEntry xppe = new XPathsPartEntry(controlMain.model); xppe.setup(null, CurrentPart.Id, strXPath, prefixMappings, true); xppe.save(); td.set("od:xpath", xppe.xpathId); if (isFlatOPC) { td.set("od:progid", "Word.Document"); cc.Title = "Word: " + xppe.xpathId; //cc.Range.Text = val; // don't escape it cc.Range.InsertXML(val, ref missing); } else if (isXHTML) { td.set("od:ContentType", "application/xhtml+xml"); cc.Title = "XHTML: " + xppe.xpathId; cc.Range.Text = val; } else if (isPicture) { PictureUtils.setPictureHandler(td); cc.Title = "Image: " + xppe.xpathId; } else { cc.Title = "Data value: " + xppe.xpathId; } cc.Tag = td.asQueryString(); if (cc.Type == Word.WdContentControlType.wdContentControlText) { cc.MultiLine = true; } if (cc.Type != Word.WdContentControlType.wdContentControlRichText) { cc.XMLMapping.SetMappingByNode(Utilities.MxnFromTn(tn, CurrentPart, true)); } designMode.restoreState(); } else if ((controlMain.modeControlEnabled == true && controlMain.controlMode1.isModeCondition()) || odType == ControlTreeView.OpenDopeType.Condition) { log.Debug("In condition mode"); // User can make a condition whatever type they like, // but if they make it text, change it to RichText. if (CCType == Word.WdContentControlType.wdContentControlText) { CCType = Word.WdContentControlType.wdContentControlRichText; } Word.ContentControl cc = CurrentDocument.Application.Selection.ContentControls.Add(CCType, ref missing); ConditionsPartEntry cpe = new ConditionsPartEntry(controlMain.model); cpe.setup(CurrentPart.Id, strXPath, prefixMappings, true); cpe.save(); cc.Title = "Conditional: " + cpe.conditionId; // Write tag td.set("od:condition", cpe.conditionId); cc.Tag = td.asQueryString(); // We want to be in Design Mode, so user can see their gesture take effect designMode.On(); Ribbon.editXPath(cc); } else if ((controlMain.modeControlEnabled == true && controlMain.controlMode1.isModeRepeat()) || odType == ControlTreeView.OpenDopeType.Repeat) { log.Debug("In repeat mode"); // User can make a repeat whatever type they like // (though does it ever make sense for it to be other than RichText?), // but if they make it text, change it to RichText. if (CCType == Word.WdContentControlType.wdContentControlText) { CCType = Word.WdContentControlType.wdContentControlRichText; } Word.ContentControl cc = CurrentDocument.Application.Selection.ContentControls.Add(CCType, ref missing); // Need to drop eg [1] (if any), so BetterForm-based interactive processing works if (strXPath.EndsWith("]")) { strXPath = strXPath.Substring(0, strXPath.LastIndexOf("[")); log.Debug("Having dropped '[]': " + strXPath); } XPathsPartEntry xppe = new XPathsPartEntry(controlMain.model); xppe.setup("rpt", CurrentPart.Id, strXPath, prefixMappings, false); xppe.save(); cc.Title = "Data value: " + xppe.xpathId; // Write tag td.set("od:repeat", xppe.xpathId); cc.Tag = td.asQueryString(); // We want to be in Design Mode, so user can see their gesture take effect designMode.On(); } } catch (COMException cex) { controlTreeView.ShowErrorMessage(cex.Message); designMode.restoreState(); } }
/// <summary> /// used when they right click then select "map to" /// </summary> /// <param name="odType"></param> public void mapToSelectedControl(ControlTreeView.OpenDopeType odType, ControlTreeView controlTreeView, ControlMain controlMain, Word.Document CurrentDocument, Office.CustomXMLPart CurrentPart, //XmlDocument OwnerDocument, bool _PictureContentControlsReplace ) { object missing = System.Type.Missing; DesignMode designMode = new OpenDoPEModel.DesignMode(CurrentDocument); // In this method, we're usually not creating a control, // so we don't need to turn off try { //create a binding Word.ContentControl cc = null; if (CurrentDocument.Application.Selection.ContentControls.Count == 1) { log.Debug("CurrentDocument.Application.Selection.ContentControls.Count == 1"); object objOne = 1; cc = CurrentDocument.Application.Selection.ContentControls.get_Item(ref objOne); log.Info("Mapped content control to tree view node " + controlTreeView.treeView.SelectedNode.Name); } else if (CurrentDocument.Application.Selection.ParentContentControl != null) { log.Debug("ParentContentControl != null"); cc = CurrentDocument.Application.Selection.ParentContentControl; } if (cc != null) { TreeNode tn = controlTreeView.treeView.SelectedNode; //get an nsmgr NameTable nt = new NameTable(); //generate the xpath and the ns manager XmlNamespaceManager xmlnsMgr = new XmlNamespaceManager(nt); string strXPath = Utilities.XpathFromXn(CurrentPart.NamespaceManager, (XmlNode)tn.Tag, true, xmlnsMgr); log.Info("Right clicked with XPath: " + strXPath); string prefixMappings = Utilities.GetPrefixMappings(xmlnsMgr); // Insert bind | condition | repeat // depending on which mode button is pressed. TagData td = new TagData(""); if ((controlMain.modeControlEnabled == false && odType == ControlTreeView.OpenDopeType.Unspecified) // ie always mode bind || (controlMain.modeControlEnabled == true && controlMain.controlMode1.isModeBind()) || odType == ControlTreeView.OpenDopeType.Bind) { log.Debug("In bind mode"); XPathsPartEntry xppe = new XPathsPartEntry(controlMain.model); xppe.setup(null, CurrentPart.Id, strXPath, prefixMappings, true); xppe.save(); td.set("od:xpath", xppe.xpathId); String val = ((XmlNode)tn.Tag).InnerText; bool isXHTML = false; bool isFlatOPC = ContentDetection.IsFlatOPCContent(val); if (isFlatOPC) { // <?mso-application progid="Word.Document"?> // <pkg:package xmlns:pkg="http://schemas.microsoft.com/office/2006/xmlPackage"> log.Debug(".. contains Flat OPC content "); cc.Type = Word.WdContentControlType.wdContentControlRichText; // Ensure block level Inline2Block i2b = new Inline2Block(); cc = i2b.convertToBlockLevel(cc, false, true); if (cc == null) { MessageBox.Show("Problems inserting block level WordML at this location."); return; } td.set("od:progid", "Word.Document"); cc.Title = "Word: " + xppe.xpathId; //cc.Range.Text = val; // don't escape it cc.Range.InsertXML(val, ref missing); } else if (ContentDetection.IsBase64Encoded(val)) { // Force picture content control ... // cc.Type = Word.WdContentControlType.wdContentControlPicture; // from wdContentControlText (or wdContentControlRichText for that matter) // doesn't work (you get "inappropriate range for applying this // content control type"). // They've said map, so delete existing, and replace it. designMode.Off(); cc.Delete(true); // Now add a new cc Globals.ThisAddIn.Application.Selection.Collapse(ref missing); if (_PictureContentControlsReplace) { // Use a rich text control instead cc = CurrentDocument.ContentControls.Add( Word.WdContentControlType.wdContentControlRichText, ref missing); PictureUtils.setPictureHandler(td); cc.Title = "Image: " + xppe.xpathId; PictureUtils.pastePictureIntoCC(cc, Convert.FromBase64String(val)); } else { cc = CurrentDocument.ContentControls.Add( Word.WdContentControlType.wdContentControlPicture, ref missing); } designMode.restoreState(); } else { isXHTML = ContentDetection.IsXHTMLContent(val); } if (cc.Type == Word.WdContentControlType.wdContentControlText) { // cc.Type = Word.WdContentControlType.wdContentControlText; // ??? cc.MultiLine = true; } //if (HasXHTMLContent(tn)) if (isXHTML) { log.Info("detected XHTML.. "); td.set("od:ContentType", "application/xhtml+xml"); cc.Title = "XHTML: " + xppe.xpathId; cc.Type = Word.WdContentControlType.wdContentControlRichText; cc.Range.Text = val; // don't escape it if (Inline2Block.containsBlockLevelContent(val)) { Inline2Block i2b = new Inline2Block(); cc = i2b.convertToBlockLevel(cc, true, true); if (cc == null) { MessageBox.Show("Problems inserting block level XHTML at this location."); return; } } } else if (!isFlatOPC) { cc.Title = "Data value: " + xppe.xpathId; } cc.Tag = td.asQueryString(); if (cc.Type != Word.WdContentControlType.wdContentControlRichText) { cc.XMLMapping.SetMappingByNode( Utilities.MxnFromTn(controlTreeView.treeView.SelectedNode, CurrentPart, true)); } } else if ((controlMain.modeControlEnabled == true && controlMain.controlMode1.isModeCondition()) || odType == ControlTreeView.OpenDopeType.Condition) { log.Debug("In condition mode"); // We want to be in Design Mode, so user can see their gesture take effect designMode.On(); ConditionsPartEntry cpe = new ConditionsPartEntry(controlMain.model); cpe.setup(CurrentPart.Id, strXPath, prefixMappings, true); cpe.save(); cc.Title = "Conditional: " + cpe.conditionId; // Write tag td.set("od:condition", cpe.conditionId); cc.Tag = td.asQueryString(); // Make it RichText; remove any pre-existing bind if (cc.XMLMapping.IsMapped) { cc.XMLMapping.Delete(); } if (cc.Type == Word.WdContentControlType.wdContentControlText) { cc.Type = Word.WdContentControlType.wdContentControlRichText; } } else if ((controlMain.modeControlEnabled == true && controlMain.controlMode1.isModeRepeat()) || odType == ControlTreeView.OpenDopeType.Repeat) { log.Debug("In repeat mode"); // We want to be in Design Mode, so user can see their gesture take effect designMode.On(); // Need to drop eg [1] (if any), so BetterForm-based interactive processing works if (strXPath.EndsWith("]")) { strXPath = strXPath.Substring(0, strXPath.LastIndexOf("[")); log.Debug("Having dropped '[]': " + strXPath); } XPathsPartEntry xppe = new XPathsPartEntry(controlMain.model); xppe.setup("rpt", CurrentPart.Id, strXPath, prefixMappings, false); xppe.save(); cc.Title = "Data value: " + xppe.xpathId; // Write tag td.set("od:repeat", xppe.xpathId); cc.Tag = td.asQueryString(); // Make it RichText; remove any pre-existing bind if (cc.XMLMapping.IsMapped) { cc.XMLMapping.Delete(); } if (cc.Type == Word.WdContentControlType.wdContentControlText) { cc.Type = Word.WdContentControlType.wdContentControlRichText; } } //ensure it's checked controlTreeView.mapToSelectedControlToolStripMenuItem.Checked = true; } } catch (COMException cex) { controlTreeView.ShowErrorMessage(cex.Message); designMode.restoreState(); } }
private void buttonOK_Click(object sender, EventArgs e) { string titleText = ""; String newXPath = null; string pred; TagData td; if (listBoxTypeFilter.SelectedItem != null && listBoxTypeFilter.SelectedItem.ToString().Equals(Helpers.QuestionListHelper.REPEAT_POS)) { // Special case newXPath = null; pred = (string)this.listBoxPredicate.SelectedItem; if (pred.Equals("first")) { newXPath = "position()=1"; titleText = "If first entry in repeat"; } else if (pred.Equals("not first")) { newXPath = "position()>1"; titleText = "If not the first entry in repeat"; } else if (pred.Equals("second")) { newXPath = "position()=2"; titleText = "If second entry in repeat"; } else if (pred.Equals("second last")) { newXPath = "position()=last()-1"; titleText = "If second last entry in repeat"; } else if (pred.Equals("last")) { newXPath = "position()=last()"; titleText = "If last entry in repeat"; } else if (pred.Equals("not last")) { newXPath = "position()!=last()"; titleText = "If not the last entry in repeat"; } else { log.Error("unexpected predicate " + pred); } // No point making this a condition //condition result = conditionsHelper.setup(xpathExisting.dataBinding.storeItemID, // newXPath, xpathExisting.dataBinding.prefixMappings, false); xpathsXpath xpathEntry = xppe.setup("", model.answersPart.Id, newXPath, null, false); //xpathEntry.questionID = q.id; xpathEntry.dataBinding.prefixMappings = "xmlns:oda='http://opendope.org/answers'"; //xpathEntry.type = dataType; xppe.save(); td = new TagData(""); td.set("od:RptPosCon", xpathEntry.id); cc.Tag = td.asQueryString(); cc.Title = titleText; cc.SetPlaceholderText(null, null, "Type the text that'll appear between repeated items."); // that'll only be displayed if the cc is not being wrapped around existing content :-) // Don't : // ContentControlNewConditionCheck variableRelocator = new ContentControlNewConditionCheck(); // variableRelocator.checkAnswerAncestry(xpathExisting.id); postconditionsMet = true; return; } xpathsXpath xpathExisting = null; string val = null; // Validation question q = (question)listBoxQuestions.SelectedItem; if (q == null) { MessageBox.Show("You must select a question!"); 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; } else { // Get the XPath for the selected question xpathExisting = xppe.getXPathByQuestionID(q.id); } //if (listBoxTypeFilter.SelectedItem != null // && listBoxTypeFilter.SelectedItem.ToString().Equals("repeat")) { // // Special case // } //else //{ //More validation object o = this.comboBoxValues.SelectedItem; if (o==null) { if (comboBoxValues.Text == null) { MessageBox.Show("You must specify a value!"); DialogResult = DialogResult.None; return; } else { o = comboBoxValues.Text; } } if (o is string) { val = (string)o; } else { //responseFixed val = ((responseFixedItem)o).value; } //} ConditionsPartEntry conditionsHelper = new ConditionsPartEntry(model); //if (xpathExisting!=null && xpathExisting.type.Equals("boolean") // && (val.ToLower().Equals("true") // || val.ToLower().Equals("false"))) { // // if its boolean true, all we need to do is create a condition pointing to that // // if its boolean false, all we need to do is create a condition not pointing to that // TagData td = new TagData(""); // if (val.ToLower().Equals("true") ) // { // // if its boolean true, all we need to do is create a condition pointing to that // log.Info("boolean true - just need a condition"); // condition c = conditionsHelper.setup(xpathExisting); // td.set("od:condition", c.id); // cc.Tag = td.asQueryString(); // titleText = "If '" + val + "' for Q: " + q.text; // } // else if (val.ToLower().Equals("false") ) // { // // if its boolean false, all we need to do is create a condition not pointing to that // log.Info("boolean true - just need a NOT condition"); // condition c = new condition(); // xpathref xpathref = new xpathref(); // xpathref.id = xpathExisting.id; // not n = new not(); // n.Item = xpathref; // c.Item = n; // conditionsHelper.add(c, "not" + xpathref.id); // td.set("od:condition", c.id); // cc.Tag = td.asQueryString(); // titleText = "If '" + val + "' for Q: " + q.text; // } // else // { // MessageBox.Show("Only true/yes or false/no are allowed for this question"); // return; // } //} else { // otherwise, create a new xpath object, and a condition pointing to it pred = (string)this.listBoxPredicate.SelectedItem; if (pred == null) { MessageBox.Show("For " + xpathExisting.type + ", you must select a relation!"); DialogResult = DialogResult.None; return; } log.Info("create a new xpath object, and a condition pointing to it. Predicate is " + pred); newXPath = null; if (listBoxTypeFilter.SelectedItem != null && listBoxTypeFilter.SelectedItem.ToString().Equals(Helpers.QuestionListHelper.REPEAT_COUNT)) { if (pred.Equals("=")) { newXPath = "count(" + xpathExisting.dataBinding.xpath + ")=" + val; titleText = "If Repeat " + q.text + " has " + val; } else if (pred.Equals(">")) { newXPath = "count(" + xpathExisting.dataBinding.xpath + ")>" + val; titleText = "If Repeat " + q.text + " > " + val; } else if (pred.Equals(">=")) { newXPath = "count(" + xpathExisting.dataBinding.xpath + ")>=" + val; titleText = "If Repeat " + q.text + " >= " + val; } else if (pred.Equals("<")) { newXPath = "count(" + xpathExisting.dataBinding.xpath + ")<" + val; titleText = "If Repeat " + q.text + " < " + val; } else if (pred.Equals("<=")) { newXPath = "count(" + xpathExisting.dataBinding.xpath + ")<=" + val; titleText = "If Repeat " + q.text + " <= " + val; } else { log.Error("unexpected predicate " + pred); } } else if (xpathExisting.type.Equals("boolean")) { // done this way, since XPath spec says the boolean value of a string is true, // if it is not empty! newXPath = "string(" + xpathExisting.dataBinding.xpath + ")='" + val + "'"; titleText = "If '" + val + "' for Q: " + q.text; } else if (xpathExisting.type.Equals("string")) { if (pred.Equals("equals")) { newXPath = "string(" + xpathExisting.dataBinding.xpath + ")='" + val + "'"; titleText = "If '" + val + "' for Q: " + q.text; } else if (pred.Equals("is not")) { newXPath = "string(" + xpathExisting.dataBinding.xpath + ")!='" + val + "'"; titleText = "If NOT '" + val + "' for Q: " + q.text; } else if (pred.Equals("starts-with")) { newXPath = "starts-with(string(" + xpathExisting.dataBinding.xpath + "), '" + val + "')"; titleText = "If starts-with '" + val + "' for Q: " + q.text; } else if (pred.Equals("contains")) { newXPath = "contains(string(" + xpathExisting.dataBinding.xpath + "), '" + val + "')"; titleText = "If contains '" + val + "' for Q: " + q.text; } else { log.Error("unexpected predicate " + pred); } } else if (xpathExisting.type.Equals("decimal") || xpathExisting.type.Equals("integer") || xpathExisting.type.Equals("positiveInteger") || xpathExisting.type.Equals("nonPositiveInteger") || xpathExisting.type.Equals("negativeInteger") || xpathExisting.type.Equals("nonNegativeInteger") ) { if (pred.Equals("=")) { newXPath = "number(" + xpathExisting.dataBinding.xpath + ")=" + val; titleText = "If '" + val + "' for Q: " + q.text; } else if (pred.Equals(">")) { newXPath = "number(" + xpathExisting.dataBinding.xpath + ")>" + val; titleText = "If >" + val + " for Q: " + q.text; } else if (pred.Equals(">=")) { newXPath = "number(" + xpathExisting.dataBinding.xpath + ")>=" + val; titleText = "If >=" + val + " for Q: " + q.text; } else if (pred.Equals("<")) { newXPath = "number(" + xpathExisting.dataBinding.xpath + ")<" + val; titleText = "If <" + val + " for Q: " + q.text; } else if (pred.Equals("<=")) { newXPath = "number(" + xpathExisting.dataBinding.xpath + ")<=" + val; titleText = "If <=" + val + " for Q: " + q.text; } else { log.Error("unexpected predicate " + pred); } } else if (xpathExisting.type.Equals("date")) { // Requires XPath 2.0 if (pred.Equals("equals")) { newXPath = "xs:date(" + xpathExisting.dataBinding.xpath + ") = xs:date('" + val + "')"; titleText = "If '" + val + "' for Q: " + q.text; } else if (pred.Equals("is before")) { newXPath = "xs:date(" + xpathExisting.dataBinding.xpath + ") < xs:date('" + val + "')"; titleText = "If before '" + val + "' for Q: " + q.text; } else if (pred.Equals("is after")) { newXPath = "xs:date(" + xpathExisting.dataBinding.xpath + ") > xs:date('" + val + "')"; titleText = "If after '" + val + "' for Q: " + q.text; } else { log.Error("unexpected predicate " + pred); } } else { log.Error("Unexpected data type " + xpathExisting.type); } if (existingCondition == null) { // Create new condition condition result = conditionsHelper.setup(xpathExisting.dataBinding.storeItemID, newXPath, xpathExisting.dataBinding.prefixMappings, false); td = new TagData(""); td.set("od:condition", result.id); cc.Tag = td.asQueryString(); //} cc.SetPlaceholderText(null, null, "Type the text for when this condition is satisfied."); // that'll only be displayed if the cc is not being wrapped around existing content :-) } else { // Update existing condition // Drop any trailing "/" from a Condition XPath if (newXPath.EndsWith("/")) { newXPath = newXPath.Substring(0, newXPath.Length - 1); } log.Debug("Creating condition using XPath:" + newXPath); XPathsPartEntry xppe = new XPathsPartEntry(model); xpathsXpath xpath = xppe.setup("cond", xpathExisting.dataBinding.storeItemID, newXPath, xpathExisting.dataBinding.prefixMappings, false); xppe.save(); xpathref xpathref = new xpathref(); xpathref.id = xpath.id; // NB no attempt is made here to delete the old xpathref // TODO existingCondition.Item = xpathref; // Save the conditions in docx cpe.save(); } cc.Title = titleText; if (listBoxTypeFilter.SelectedItem != null && listBoxTypeFilter.SelectedItem.ToString().Equals(Helpers.QuestionListHelper.REPEAT_COUNT)) { // Skip ContentControlNewConditionCheck } else { // Make sure this question is allowed here // ie the it is top level or in a common repeat ancestor. // We do this last, so this cc has od:condition on it, // in which case we can re-use existing code to do the check // TODO: when we support and/or, will need to do this // for each variable. ContentControlNewConditionCheck variableRelocator = new ContentControlNewConditionCheck(); variableRelocator.checkAnswerAncestry(xpathExisting.id); } postconditionsMet = true; }
// We can't add a tag for a repeat or a condition, // unless we know the XPath. And we can't know that // if there is no suitable child to deduce it from. // But if they later press the edit button, we // would prefer not to have to ask them whether it // is a repeat or a condition. We could track the // content controls by their ID, but it seems // better to just use the Title. /// <summary> /// Wrap the selection in a Repeat. /// The XPath is deduced from plain binds in the contents. /// If there are none of these, just insert an empty content control. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void buttonRepeat_Click(object sender, RibbonControlEventArgs e) { Word.Document document = Globals.ThisAddIn.Application.ActiveDocument; // Workaround for reported Word crash. // Can't reproduce with Word 2010 sp1: 14.0.6129.500 Word.ContentControl currentCC = ContentControlMaker.getActiveContentControl(document, Globals.ThisAddIn.Application.Selection); if (currentCC != null && currentCC.Type != Word.WdContentControlType.wdContentControlRichText) { MessageBox.Show("You can't add a repeat here."); return; } OpenDoPEModel.DesignMode designMode = new OpenDoPEModel.DesignMode(document); designMode.Off(); // Find a content control within the selection List<Word.ContentControl> shallowChildren = ContentControlUtilities.getShallowestSelectedContentControls(document, Globals.ThisAddIn.Application.Selection); log.Debug(shallowChildren.Count + " shallowChildren found."); Word.ContentControl repeatCC = null; object missing = System.Type.Missing; try { repeatCC = document.ContentControls.Add(Word.WdContentControlType.wdContentControlRichText, ref missing); // Limitation here: you can't make your content control of eg type picture log.Debug("New content control added, with id: " + repeatCC.ID); designMode.On(); } catch (System.Exception) { MessageBox.Show("Selection must be either part of a single paragraph, or one or more whole paragraphs"); designMode.restoreState(); return; } repeatCC.Title = "Repeat [unbound]"; // This used if they later click edit if (shallowChildren.Count == 0) { log.Debug("No child control found. So Repeat not set on our new CC"); //MessageBox.Show("Unbound content control only added. Click the edit button to setup the repeat."); editXPath(repeatCC); return; } // For now, just use the tag on the first simple bind we find. // Later, we could try parsing a condition or repeat Word.ContentControl usableChild = null; foreach (Word.ContentControl child in shallowChildren) { //if (child.Tag.Contains("od:xpath")) if (child.XMLMapping.IsMapped) { usableChild = child; break; } } if (usableChild == null) { log.Debug("No usable child found. So Repeat not set on our new CC"); //MessageBox.Show("Naked content control only added. Click the edit button to setup the repeat."); editXPath(repeatCC); return; } string strXPath = null; // Need to work out what repeats. Could be this child, // the parent, etc. If its obvious from this exemplar xml doc, // we can do it automatically. Otherwise, we'll ask user. // Currently the logic supports repeating ., .., or grandparent. // See whether this child repeats in this exemplar. Office.CustomXMLNode thisNode = usableChild.XMLMapping.CustomXMLNode; Office.CustomXMLNode thisNodeSibling = usableChild.XMLMapping.CustomXMLNode.NextSibling; Office.CustomXMLNode parent = usableChild.XMLMapping.CustomXMLNode.ParentNode; Office.CustomXMLNode parentSibling = usableChild.XMLMapping.CustomXMLNode.ParentNode.NextSibling; if (thisNodeSibling!=null && thisNodeSibling.BaseName.Equals(thisNode.BaseName) ) { // Looks like this node repeats :-) strXPath = usableChild.XMLMapping.XPath; // Get XPath. Could use the od xpaths part, but // easier here to work with the binding log.Debug("Using . as repeat: " + strXPath); } // If it doesn't, test parent. else if (parentSibling != null && parentSibling.BaseName.Equals(parent.BaseName)) { strXPath = usableChild.XMLMapping.XPath; log.Debug("Using parent for repeat "); strXPath = strXPath.Substring(0, strXPath.LastIndexOf("/")); log.Debug("Using: " + strXPath); } else // If that doesn't either, ask user. { Office.CustomXMLNode grandparent = null; if (parent != null) { grandparent = parent.ParentNode; } using (Forms.FormSelectRepeatedElement sr = new Forms.FormSelectRepeatedElement()) { sr.labelXPath.Text = usableChild.XMLMapping.XPath; sr.listElementNames.Items.Add(thisNode.BaseName); sr.listElementNames.Items.Add(parent.BaseName); if (grandparent != null) { sr.listElementNames.Items.Add(grandparent.BaseName); } sr.listElementNames.SelectedIndex = 0; sr.ShowDialog(); if (sr.listElementNames.SelectedIndex == 0) { strXPath = usableChild.XMLMapping.XPath; } else if (sr.listElementNames.SelectedIndex == 1) { strXPath = parent.XPath; log.Debug("Using parent for repeat: " + strXPath); } else { // Grandparent strXPath = grandparent.XPath; log.Debug("Using grandparent for repeat: " + strXPath); } } } // Need to drop eg [1] (if any), so BetterForm-based interactive processing works if (strXPath.EndsWith("]")) { strXPath = strXPath.Substring(0, strXPath.LastIndexOf("[")); log.Debug("Having dropped '[]': " + strXPath); } XPathsPartEntry xppe = new XPathsPartEntry(Model.ModelFactory(document)); // TODO fix usableChild.XMLMapping.PrefixMappings xppe.setup("rpt", usableChild.XMLMapping.CustomXMLPart.Id, strXPath, "", false); // No Q for repeat xppe.save(); repeatCC.Title = "Repeat: " + xppe.xpathId; // Write tag TagData td = new TagData(""); td.set("od:repeat", xppe.xpathId); repeatCC.Tag = td.asQueryString(); }
private void buttonQuestionAdd_Click(object sender, EventArgs e) { FormQA formQA = new FormQA(cc, false); formQA.ShowDialog(); formQA.Dispose(); // Refresh these xppe = new XPathsPartEntry(model); questionnaire.Deserialize(questionsPart.XML, out questionnaire); questionListHelper.filterAction(); }
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 FormCondition(Word.ContentControl cc, ConditionsPartEntry cpe, condition existingCondition) { InitializeComponent(); // NET 4 way; see http://msdn.microsoft.com/en-us/library/microsoft.office.tools.word.extensions.aspx FabDocxState fabDocxState = (FabDocxState)Globals.Factory.GetVstoObject(Globals.ThisAddIn.Application.ActiveDocument).Tag; // NET 3.5 way, which requires using Microsoft.Office.Tools.Word.Extensions //FabDocxState fabDocxState = (FabDocxState)Globals.ThisAddIn.Application.ActiveDocument.GetVstoObject(Globals.Factory).Tag; this.model = fabDocxState.model; xppe = new XPathsPartEntry(model); this.cc = cc; this.cpe = cpe; this.existingCondition = existingCondition; this.questionsPart = model.questionsPart; questionnaire = new questionnaire(); questionnaire.Deserialize(questionsPart.XML, out questionnaire); questionListHelper = new Helpers.QuestionListHelperForConditionsForm(model, xppe, questionnaire, cc); questionListHelper.listBoxTypeFilter = listBoxTypeFilter; questionListHelper.listBoxQuestions = listBoxQuestions; questionListHelper.checkBoxScope = checkBoxScope; questionListHelper.comboBoxValues = comboBoxValues; questionListHelper.listBoxPredicate = listBoxPredicate; this.listBoxQuestions.SelectedIndexChanged += new System.EventHandler(questionListHelper.listBoxQuestions_SelectedIndexChanged); this.listBoxTypeFilter.SelectedIndexChanged += new System.EventHandler(questionListHelper.listBoxTypeFilter_SelectedIndexChanged); question existingQuestion = null; string matchResponse = null; if (existingCondition != null) { // Use the question associated with it, to pre-select // the correct entries in the dialog. // Re-label the window, so user can see what the condition was about this.Text = "Editing Condition: " + cc.Title; //List<xpathsXpath> xpaths = ConditionsPartEntry.getXPathsUsedInCondition(existingCondition, xppe); List<xpathsXpath> xpaths = new List<xpathsXpath>(); existingCondition.listXPaths(xpaths, cpe.conditions, xppe.getXPaths()); if (xpaths.Count > 1) { // TODO: use complex conditions editor } xpathsXpath xpathObj = xpaths[0]; String xpathVal = xpathObj.dataBinding.xpath; if (xpathVal.StartsWith("/")) { // simple //System.out.println("question " + xpathObj.getQuestionID() // + " is in use via boolean condition " + conditionId); existingQuestion = this.questionnaire.getQuestion(xpathObj.questionID); matchResponse = xpathVal; } else if (xpathVal.Contains("position")) { // TODO } else { //System.out.println(xpathVal); String qid = xpathVal.Substring( xpathVal.LastIndexOf("@id") + 5); // System.out.println("Got qid: " + qid); qid = qid.Substring(0, qid.IndexOf("'")); // System.out.println("Got qid: " + qid); //System.out.println("question " + qid // + " is in use via condition " + conditionId); existingQuestion = this.questionnaire.getQuestion(qid); matchResponse = xpathVal; } } questionListHelper.populateTypeFilter(true); if (existingQuestion == null) { // for init, populate with all questions questionListHelper.populateQuestions(null); } else { // Just show the existing question listBoxQuestions.Items.Add(existingQuestion); } if (this.listBoxQuestions.Items.Count == 0) // Never happens if in a repeat, and nor do we want it to, since user might just want to use "repeat pos" stuff { // Try including out of scope this.checkBoxScope.Checked = true; questionListHelper.populateQuestions(null); if (this.listBoxQuestions.Items.Count == 0) { MessageBox.Show("You can't define a condition until you have set up at least one question. Let's do that now. "); FormQA formQA = new FormQA(cc, false); formQA.ShowDialog(); formQA.Dispose(); // Refresh these xppe = new XPathsPartEntry(model); questionnaire.Deserialize(questionsPart.XML, out questionnaire); questionListHelper.filterAction(); return; } } // value question q; if (existingQuestion == null) { // for init, populate with all questions q = (question)this.listBoxQuestions.Items[0]; } else { q = existingQuestion; } this.listBoxQuestions.SelectedItem = q; if (q.response.Item is responseFixed) { questionListHelper.populateValues((responseFixed)q.response.Item, matchResponse); } // predicate = questionListHelper.populatePredicates(q); // TODO: set this correctly in editing mode }
/* * For each question, I need to keep track of the last value * it had on a given row. This I can store in DataGridViewBand.Tag * so a Dictionary<Q, object> is OK for this. * * Actually, the last value seems to be retained. Without looking * to see why/how, I don't need to do anything. * */ public ConditionsFormRowHelper(Model model, XPathsPartEntry xppe, questionnaire questionnaire, Word.ContentControl cc, Forms.FormConditionBuilder fcb) { this.model = model; this.xppe = xppe; this.questionnaire = questionnaire; this.fcb = fcb; this.cc = cc; }
public Boolean IsFormatEnabled(Office.IRibbonControl control) { if (getState() == null) return false; // Enable for date and (TODO) numbers if (currentCC!=null && currentCC.Tag != null) { String XPathID = (new TagData(currentCC.Tag)).getXPathID(); if (XPathID != null) { // Get the xpath obj FabDocxState fabDocxState = getState(); XPathsPartEntry xppe = new XPathsPartEntry(fabDocxState.model); xpathsXpath xpo = xppe.getXPathByID(XPathID); if (xpo.type != null) { if (xpo.type.Equals("date")) { return true; } //else if (xpo.type.Equals("decimal") // || xpo.type.Equals("integer") // || xpo.type.Equals("positiveInteger") // || xpo.type.Equals("nonPositiveInteger") // || xpo.type.Equals("negativeInteger") // || xpo.type.Equals("nonNegativeInteger") // ) //{ // return true; //} } } } return false; }
/// <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 buttonEditQuestion_Click(Office.IRibbonControl control) { FabDocxState fabDocxState = getState(); if (currentCC.Tag != null) { String XPathID = (new TagData(currentCC.Tag)).getXPathID(); if (XPathID != null) { // Get the xpath obj XPathsPartEntry xppe = new XPathsPartEntry(fabDocxState.model); xpathsXpath xpo = xppe.getXPathByID(XPathID); if (xpo.questionID != null) { Forms.FormQuestionEdit fqe = new Forms.FormQuestionEdit(xpo.questionID); fqe.ShowDialog(); fqe.Dispose(); } else { log.Debug("No question associated with xpath " + xpo.dataBinding.xpath); } } } if (fabDocxState.TaskPane.Visible) { Controls.LogicTaskPaneUserControl ltp = (Controls.LogicTaskPaneUserControl)fabDocxState.TaskPane.Control; ltp.populateLogicInUse(); } }
public QuestionListHelperForConditionsForm(Model model, XPathsPartEntry xppe, questionnaire questionnaire, Word.ContentControl cc) : base(model, xppe, questionnaire, cc) { }
/// <summary> /// /// </summary> /// <param name="target"></param> /// <param name="setSourceAttr">When saving a building block, we want to write the ID of the source part</param> /// <param name="setBindingStore">When re-using a building block, storeItemID to write to the xpath; otherwise null</param> /// <param name="overwriteExisting">If re-using a building block back into original source, we want to skip silently. /// When going the other way, we want to overwrite the logic in any existing building block (since /// it may have been updated).</param> public void injectLogic(Model targetModel, bool setSourceAttr, bool setBindingStore, bool overwriteExisting) { //Model targetModel = Model.ModelFactory(target); // XPaths XPathsPartEntry targetXppe = new XPathsPartEntry(targetModel); string sourceAttr = null; if (setSourceAttr) { sourceAttr = srcXPathsPart.Id; } string answersPartStoreID = null; if (setBindingStore) { answersPartStoreID = targetModel.answersPart.Id; } // .. add em foreach (xpathsXpath xp in BBxpaths) { xpathsXpath existing = targetXppe.getXPathByID(xp.id); if (existing == null) { injectLogicXPath(targetXppe, xp, sourceAttr, answersPartStoreID); } else { // Does it come from this doc? //log.Debug("xp.source: " + xp.source); //log.Debug("existing.source: " + existing.source); //log.Debug("targetModel.xpathsPart.Id: " + targetModel.xpathsPart.Id); if (xp.source != null && xp.source.Equals(targetModel.xpathsPart.Id)) { // yes .. if (overwriteExisting) { injectLogicXPath(targetXppe, xp, sourceAttr, answersPartStoreID); } else { continue; } } else if (xp.source != null && existing.source != null && xp.source.Equals(existing.source)) { // It has already been copied in. // so don't do it again, whether we're copying to template // (could go either way, but for now, only update from original source), // or into docx continue; } else { // Yikes! ID collision throw new BuildingBlockLogicException("XPath with ID " + xp.id + " is already present."); } } } // Questions questionnaire targetQuestionnaire = new questionnaire(); questionnaire.Deserialize(targetModel.questionsPart.XML, out targetQuestionnaire); // .. add em foreach (question q in BBquestions) { question existing = targetQuestionnaire.getQuestion(q.id); if (existing == null) { targetQuestionnaire.questions.Add(q); if (setSourceAttr) { q.source = srcQuestionsPart.Id; } } else { // Does it come from this doc? //log.Debug("q.source: " + q.source); //log.Debug("existing.source: " + existing.source); //log.Debug("targetModel.questionsPart.Id: " + targetModel.questionsPart.Id); if (q.source != null && q.source.Equals(targetModel.questionsPart.Id)) { // yes .. if (overwriteExisting) { targetQuestionnaire.questions.Add(q); // this is a HashSet, so we're overrwriting, not adding :-) if (setSourceAttr) { q.source = targetModel.questionsPart.Id; } } else { continue; } } else if (q.source != null && existing.source != null && q.source.Equals(existing.source)) { // It has already been copied in. continue; } else { // Yikes! ID collision throw new BuildingBlockLogicException("Question with ID " + q.id + " is already present."); } } } // Answers answers targetAnswers = new answers(); answers.Deserialize(targetModel.answersPart.XML, out targetAnswers); foreach (answer a in BBanswers) { answer existing = getAnswer(targetAnswers, a.id); if (existing == null) { targetAnswers.Items.Add(a); if (setSourceAttr) { a.source = srcAnswersPart.Id; } } else { // Does it come from this doc? if (a.source != null && a.source.Equals(targetModel.answersPart.Id)) { log.Debug("source is this part"); // yes .. if (overwriteExisting) { log.Debug(".. and overwriting.."); targetAnswers.Items.Add(a); // this is a HashSet, so we're overrwriting, not adding :-) if (setSourceAttr) { a.source = srcAnswersPart.Id; } } else { continue; } } else if (a.source != null && existing.source != null && a.source.Equals(existing.source)) { // It has already been copied in. log.Debug("this logic already present"); continue; } else { // Yikes! ID collision throw new BuildingBlockLogicException("Answer with ID " + a.id + " from different source is already present."); } } } foreach (repeat r in BBrepeats) { repeat existing = getRepeat(targetAnswers, r.qref); if (existing == null) { targetAnswers.Items.Add(r); if (setSourceAttr) { r.source = srcAnswersPart.Id; } } else { // Does it come from this doc? if (r.source != null && r.source.Equals(targetModel.answersPart.Id)) { // yes .. if (overwriteExisting) { targetAnswers.Items.Add(r); // this is a HashSet, so we're overrwriting, not adding :-) if (setSourceAttr) { r.source = srcAnswersPart.Id; } } else { continue; } } else if (r.source != null && existing.source != null && r.source.Equals(existing.source)) { // It has already been copied in. continue; } else { // Yikes! ID collision throw new BuildingBlockLogicException("Answer with ID " + r.qref + " is already present."); } } } // Conditions conditions targetConditions = new conditions(); conditions.Deserialize(targetModel.conditionsPart.XML, out targetConditions); foreach (condition c in BBconditions) { condition existing = getCondition(targetConditions, c.id); if (existing == null) { targetConditions.condition.Add(c); if (setSourceAttr) { c.source = srcConditionsPart.Id; } } else { // Does it come from this doc? if (c.source != null && c.source.Equals(targetModel.conditionsPart.Id)) { // yes .. if (overwriteExisting) { targetConditions.condition.Add(c); // this is a HashSet, so we're overrwriting, not adding :-) if (setSourceAttr) { c.source = targetModel.conditionsPart.Id; } } else { continue; } } else if (c.source != null && existing.source != null && c.source.Equals(existing.source)) { // It has already been copied in. continue; } else { // Yikes! ID collision throw new BuildingBlockLogicException("Condition with ID " + c.id + " is already present."); } } } // .. save: we only save if there have been no ID collisions. // Otherwise, we will have aborted with a BuildingBlockLogicException targetXppe.save(); CustomXmlUtilities.replaceXmlDoc(targetModel.questionsPart, targetQuestionnaire.Serialize()); CustomXmlUtilities.replaceXmlDoc(targetModel.conditionsPart, targetConditions.Serialize()); CustomXmlUtilities.replaceXmlDoc(targetModel.answersPart, targetAnswers.Serialize()); }