public void handle(Word.ContentControl copiedCC) { // In the move case, we know that if it is a FabDocx content control, it is valid // in this docx if (copiedCC.Tag == null) { // Its just some content control we don't care about fabDocxState.registerKnownSdt(copiedCC); return; // this is OK, since if it contains a repeat or something, that event will fire } else if (copiedCC.Tag.Contains("od:xpath")) { td = new TagData(copiedCC.Tag); // RULE: A variable cc can copied wherever. // If its repeat ancestors change, its "vary in repeat" // will need to change (to lowest common denominator). handleXPath(td.getXPathID(), true); } else if (copiedCC.Tag.Contains("od:repeat")) { // RULE: A repeat can be moved wherever under // the same repeat ancestor cc. // It can be moved elsewhere, provided it is // the only cc using that repeat. (change // AF and XPaths accordingly). handleRepeat(copiedCC); } else if (copiedCC.Tag.Contains("od:condition")) { // Identify child content controls Microsoft.Office.Interop.Word.Range rng = copiedCC.Range; Word.ContentControls ccs = rng.ContentControls; foreach (Word.ContentControl desc in ccs) { if (desc.ParentContentControl.ID.Equals(copiedCC.ID)) { handle(desc); } } } }
/// <summary> /// 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(); } }
/// <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(); } }
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); } } } } } }
/// <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(); } }
private void buttonReuseOK_Click(object sender, EventArgs e) { if (this.comboBoxRepeatNames.SelectedItem == null) { // do nothing for now. // TODO: grey out the OK button unless there is a selected item DialogResult = DialogResult.None; return; } string repeatName = (string)this.comboBoxRepeatNames.SelectedItem; // Get the ID we need to use String repeatId = repeatNameToIdMap[repeatName]; TagData td = new TagData(""); td.set("od:repeat", repeatId); cc.Tag = td.asQueryString(); cc.Title = "REPEAT " + repeatName; cc.SetPlaceholderText(null, null, "Repeating content goes here."); // They could be trying to re-usw this repeat somewhere // outside its ancestral repeat scope. This should handle that. ContentControlCopyHandler handler = new ContentControlCopyHandler(); handler.handle(cc); }
// 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(); }
/// <summary> /// We've already written the correct binding part id, in our xpaths. We need to do it in the cc'sas well. /// </summary> /// <param name="range"></param> /// <param name="answersPart"></param> public void updateBindings(Word.Range range, Office.CustomXMLPart answersPart) { foreach (Word.ContentControl cc in range.ContentControls) { if (cc.Tag == null) { // Its just some content control we don't care about continue; } TagData td = new TagData(cc.Tag); if (cc.Tag.Contains("od:xpath")) { xpathsXpath xp = srcXppe.getXPathByID(td.getXPathID()); cc.XMLMapping.SetMapping(xp.dataBinding.xpath, "xmlns:oda='http://opendope.org/answers'", answersPart); } } }
public void TagsSourceRemove(Word.Range range) { foreach (Word.ContentControl cc in range.ContentControls) { if (cc.Tag == null) continue; TagData td = new TagData(cc.Tag); td.remove("od:source"); cc.Tag = td.asQueryString(); } }
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; } } } } }
private void setTag(Word.ContentControl cc, condition result) { TagData 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 :-) log.Info("Created condition " + result.Serialize()); }
private void buildCondition() { ConditionsPartEntry cpe = new ConditionsPartEntry(model); TagData td; if (this.dataGridView.Rows.Count == 2 // auto last row && !this.listBoxGovernor.SelectedItem.ToString().Equals("none") ) // none handled separately { // a simple condition if (this.dataGridView.Rows[0].Cells["Questions"].Value is condition) { // this is just condition re-use! condition cReused = (condition)this.dataGridView.Rows[0].Cells["Questions"].Value; setTag(cc, cReused); cc.Title = cReused.description; // that'll do for now return; } // Usual case Pairing pair = buildXPathRef(this.dataGridView.Rows[0]); cc.Title = restrict64chars(pair.titleText); if (pair.xpathEntry.dataBinding.xpath.Contains("position()")) { // special case. TODO: make this a normal condition! // since this approach won't work if it is in complex condition td = new TagData(""); td.set("od:RptPosCon", pair.xpathEntry.id); cc.Tag = td.asQueryString(); 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 :-) return; } condition result = cpe.setup(pair.xpathEntry); result.name = this.textBoxName.Text; if (string.IsNullOrWhiteSpace(this.textBoxDescription.Text)) { result.description = pair.titleText; } else { result.description = this.textBoxDescription.Text; } cpe.save(); setTag(cc, result); return; } // multi-row int last = this.dataGridView.Rows.Count - 1; condition outer = new condition(); cc.Title = null; if (this.listBoxGovernor.SelectedItem.ToString().Equals("all")) { // = and and and = new and(); outer.Item = and; foreach (DataGridViewRow row in this.dataGridView.Rows) { // Last row is added automatically if (row == this.dataGridView.Rows[last]) continue; if (row.Cells["Questions"].Value is condition) { // this is just condition re-use! condition cReused = (condition)row.Cells["Questions"].Value; if (cc.Title == null) { cc.Title = this.restrict64chars(cReused.description); // that'll do for now } else { cc.Title = this.restrict64chars(cc.Title + " and " + cReused.description); // that'll do for now } conditionref conditionref = new conditionref(); conditionref.id = cReused.id; and.Items.Add(conditionref); } else { // xpathref Pairing pair = buildXPathRef(row); if (cc.Title == null) { cc.Title = this.restrict64chars(pair.titleText); } else { cc.Title = this.restrict64chars(cc.Title + " and " + pair.titleText); } xpathref xpathref = new xpathref(); xpathref.id = pair.xpathEntry.id; and.Items.Add(xpathref); } } outer.name = this.textBoxName.Text; if (string.IsNullOrWhiteSpace(this.textBoxDescription.Text)) { outer.description = cc.Title; } else { outer.description = this.textBoxDescription.Text; } cpe.add(outer, null); cpe.save(); setTag(cc, outer); return; } if (this.listBoxGovernor.SelectedItem.ToString().Equals("any") ) { // = or or or = new or(); outer.Item = or; foreach (DataGridViewRow row in this.dataGridView.Rows) { // Last row is added automatically if (row == this.dataGridView.Rows[last]) continue; if (row.Cells["Questions"].Value is condition) { // this is just condition re-use! condition cReused = (condition)row.Cells["Questions"].Value; if (cc.Title == null) { cc.Title = this.restrict64chars(cReused.description); // that'll do for now } else { cc.Title = this.restrict64chars(cc.Title + " and " + cReused.description); // that'll do for now } conditionref conditionref = new conditionref(); conditionref.id = cReused.id; or.Items.Add(conditionref); } else { Pairing pair = buildXPathRef(row); if (cc.Title == null) { cc.Title = this.restrict64chars(pair.titleText); } else { cc.Title = this.restrict64chars(cc.Title + " or " + pair.titleText); } xpathref xpathref = new xpathref(); xpathref.id = pair.xpathEntry.id; or.Items.Add(xpathref); } } outer.name = this.textBoxName.Text; if (string.IsNullOrWhiteSpace(this.textBoxDescription.Text)) { outer.description = cc.Title; } else { outer.description = this.textBoxDescription.Text; } cpe.add(outer, null); cpe.save(); setTag(cc, outer); return; } if (this.listBoxGovernor.SelectedItem.ToString().Equals("none")) { // none: not(A || B) = !A && !B not not = new not(); outer.Item = not; or or = new or(); not.Item = or; cc.Title = "NONE OF "; foreach (DataGridViewRow row in this.dataGridView.Rows) { // Last row is added automatically if (row == this.dataGridView.Rows[last]) continue; if (row.Cells["Questions"].Value is condition) { // this is just condition re-use! condition cReused = (condition)row.Cells["Questions"].Value; if (cc.Title == null) { cc.Title = this.restrict64chars(cReused.description); // that'll do for now } else { cc.Title = this.restrict64chars(cc.Title + " and " + cReused.description); // that'll do for now } conditionref conditionref = new conditionref(); conditionref.id = cReused.id; or.Items.Add(conditionref); } else { Pairing pair = buildXPathRef(row); if (cc.Title.Equals("NONE OF ")) { cc.Title = this.restrict64chars(cc.Title + pair.titleText); } else { cc.Title = this.restrict64chars(cc.Title + " or " + pair.titleText); } xpathref xpathref = new xpathref(); xpathref.id = pair.xpathEntry.id; or.Items.Add(xpathref); } } outer.name = this.textBoxName.Text; if (string.IsNullOrWhiteSpace(this.textBoxDescription.Text)) { outer.description = cc.Title; } else { outer.description = this.textBoxDescription.Text; } cpe.add(outer, null); cpe.save(); setTag(cc, outer); return; } //// 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); }
public void treeView_ItemDrag(object sender, ItemDragEventArgs e, ControlTreeView controlTreeView, ControlMain controlMain, Word.Document CurrentDocument, Office.CustomXMLPart CurrentPart, XmlDocument OwnerDocument, bool _PictureContentControlsReplace) { object missing = System.Type.Missing; TreeNode tn = (TreeNode)e.Item; if (tn == null) { Debug.Fail("no tn"); return; } //check if this is something we can drag if (((XmlNode)tn.Tag).NodeType == XmlNodeType.ProcessingInstruction || ((XmlNode)tn.Tag).NodeType == XmlNodeType.Comment) return; if (controlMain.modeControlEnabled == false // ie always mode bind || controlMain.controlMode1.isModeBind()) { if (!ControlTreeView.IsLeafNode(tn) || ((XmlNode)tn.Tag).NodeType == XmlNodeType.Text && !ControlTreeView.IsLeafNode(tn.Parent)) return; } // repeats and conditions; let them drag any node //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("Dragging XPath: " + strXPath); string prefixMappings = Utilities.GetPrefixMappings(xmlnsMgr); // OpenDoPE TagData td = new TagData(""); String val = ((XmlNode)tn.Tag).InnerText; DesignMode designMode = new OpenDoPEModel.DesignMode(CurrentDocument); // Special case for pictures, since drag/drop does not seem // to work properly (the XHTML pasted doesn't do what it should?) bool isPicture = ContentDetection.IsBase64Encoded(val); if (isPicture && !_PictureContentControlsReplace) { designMode.Off(); log.Debug("Special case handling for pictures.."); // Selection can't be textual content, so ensure it isn't. // It is allowed to be a picture, so in the future we could // leave the selection alone if it is just a picture. Globals.ThisAddIn.Application.Selection.Collapse(ref missing); // Are they dragging to an existing picture content control Word.ContentControl picCC = ContentControlMaker.getActiveContentControl(CurrentDocument, Globals.ThisAddIn.Application.Selection); try { if (picCC == null || (picCC.Type != Word.WdContentControlType.wdContentControlPicture)) { picCC = CurrentDocument.ContentControls.Add( Word.WdContentControlType.wdContentControlPicture, ref missing); designMode.restoreState(); } } catch (COMException ce) { // Will happen if you try to drag a text node onto an existing image content control log.Debug("Ignoring " + ce.Message); return; } XPathsPartEntry xppe = new XPathsPartEntry(controlMain.model); xppe.setup(null, CurrentPart.Id, strXPath, prefixMappings, false); xppe.save(); td.set("od:xpath", xppe.xpathId); picCC.Tag = td.asQueryString(); picCC.Title = "Data value: " + xppe.xpathId; picCC.XMLMapping.SetMappingByNode(Utilities.MxnFromTn(tn, CurrentPart, true)); return; } log.Debug("\n\ntreeView_ItemDrag for WdSelectionType " + Globals.ThisAddIn.Application.Selection.Type.ToString()); bool isXHTML = false; bool isFlatOPC = ContentDetection.IsFlatOPCContent(val); if (!isFlatOPC) isXHTML = ContentDetection.IsXHTMLContent(val); if (Globals.ThisAddIn.Application.Selection.Type != Microsoft.Office.Interop.Word.WdSelectionType.wdSelectionIP) { // ie something is selected, since "inline paragraph selection" // just means the cursor is somewhere inside // a paragraph, but with nothing selected. designMode.Off(); // Selection types: http://msdn.microsoft.com/en-us/library/microsoft.office.interop.word.wdselectiontype(v=office.11).aspx log.Debug("treeView_ItemDrag fired, but interpreted as gesture for WdSelectionType " + Globals.ThisAddIn.Application.Selection.Type.ToString()); Word.ContentControl parentCC = ContentControlMaker.getActiveContentControl(CurrentDocument, Globals.ThisAddIn.Application.Selection); // Insert bind | condition | repeat // depending on which mode button is pressed. if (controlMain.modeControlEnabled == false // ie always mode bind || controlMain.controlMode1.isModeBind()) { log.Debug("In bind mode"); Word.ContentControl cc = null; try { if (isFlatOPC || isXHTML || (isPicture && _PictureContentControlsReplace)) { // Rich text if (parentCC != null && ContentControlOpenDoPEType.isBound(parentCC)) { // Reuse existing cc cc = ContentControlMaker.MakeOrReuse(true, Word.WdContentControlType.wdContentControlRichText, CurrentDocument, Globals.ThisAddIn.Application.Selection); } else { // Make new cc cc = ContentControlMaker.MakeOrReuse(true, Word.WdContentControlType.wdContentControlRichText, CurrentDocument, Globals.ThisAddIn.Application.Selection); } if (isFlatOPC) { log.Debug(".. contains block content "); // 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 if (isXHTML // and thus not picture && Inline2Block.containsBlockLevelContent(val)) { log.Debug(".. contains block content "); // Ensure block level Inline2Block i2b = new Inline2Block(); cc = i2b.convertToBlockLevel(cc, false, true); if (cc == null) { MessageBox.Show("Problems inserting block level XHTML at this location."); return; } } } else { // Plain text if (parentCC != null && ContentControlOpenDoPEType.isBound(parentCC)) { // Reuse existing cc cc = ContentControlMaker.MakeOrReuse(true, Word.WdContentControlType.wdContentControlText, CurrentDocument, Globals.ThisAddIn.Application.Selection); } else { // Make new cc cc = ContentControlMaker.MakeOrReuse(false, Word.WdContentControlType.wdContentControlText, CurrentDocument, Globals.ThisAddIn.Application.Selection); } cc.MultiLine = true; // Is a text content control always run-level? // No, not if you have a single para selected and you do drag gesture // (or if you remap a rich text control) } } catch (Exception ex) { log.Error("Couldn't add content control: " + ex.Message); return; } XPathsPartEntry xppe = new XPathsPartEntry(controlMain.model); xppe.setup(null, CurrentPart.Id, strXPath, prefixMappings, true); xppe.save(); td.set("od:xpath", xppe.xpathId); if (isFlatOPC) { // <?mso-application progid="Word.Document"?> // <pkg:package xmlns:pkg="http://schemas.microsoft.com/office/2006/xmlPackage"> 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; // don't escape it } else if (isPicture) { PictureUtils.setPictureHandler(td); cc.Title = "Image: " + xppe.xpathId; string picContent = CurrentPart.SelectSingleNode(strXPath).Text; PictureUtils.pastePictureIntoCC(cc, Convert.FromBase64String(picContent)); } else { cc.XMLMapping.SetMappingByNode(Utilities.MxnFromTn(tn, CurrentPart, true)); string nodeXML = cc.XMLMapping.CustomXMLNode.XML; log.Info(nodeXML); cc.Title = "Data value: " + xppe.xpathId; } cc.Tag = td.asQueryString(); designMode.restoreState(); } else if (controlMain.controlMode1.isModeCondition()) { log.Debug("In condition mode"); Word.ContentControl cc = null; try { // always make new cc = ContentControlMaker.MakeOrReuse(false, Word.WdContentControlType.wdContentControlRichText, CurrentDocument, Globals.ThisAddIn.Application.Selection); } catch (Exception ex) { log.Error("Couldn't add content control: " + ex.Message); return; } 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(); designMode.On(); } else if (controlMain.controlMode1.isModeRepeat()) { log.Debug("In repeat mode"); Word.ContentControl cc = null; try { // always make new cc = ContentControlMaker.MakeOrReuse(false, Word.WdContentControlType.wdContentControlRichText, CurrentDocument, Globals.ThisAddIn.Application.Selection); } catch (Exception ex) { log.Error("Couldn't add content control: " + ex.Message); return; } // 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); // no Q for repeat xppe.save(); cc.Title = "Repeat: " + xppe.xpathId; // Write tag td.set("od:repeat", xppe.xpathId); cc.Tag = td.asQueryString(); designMode.On(); } return; } // end if (Globals.ThisAddIn.Application.Selection.Type != Microsoft.Office.Interop.Word.WdSelectionType.wdSelectionIP) // Selection.Type: Microsoft.Office.Interop.Word.WdSelectionType.wdSelectionIP // ie cursor is somewhere inside a paragraph, but with nothing selected. log.Info("In wdSelectionIP specific code."); // leave designMode alone here // Following processing uses clipboard HTML to implement drag/drop processing // Could avoid dealing with that (what's the problem anyway?) if they are dragging onto an existing content control, with: //Word.ContentControl existingCC = ContentControlMaker.getActiveContentControl(CurrentDocument, Globals.ThisAddIn.Application.Selection); //if (existingCC != null) return; // But that stops them from dragging any more content into a repeat. string title = ""; string tag = ""; bool needBind = false; log.Debug(strXPath); Office.CustomXMLNode targetNode = CurrentPart.SelectSingleNode(strXPath); string nodeContent = targetNode.Text; // or ((XmlNode)tn.Tag).InnerXml // Insert bind | condition | repeat // depending on which mode button is pressed. if (controlMain.modeControlEnabled == false // ie always mode bind || controlMain.controlMode1.isModeBind()) { log.Debug("In bind mode"); // OpenDoPE: create w:tag=od:xpath=x1 // and add XPath to xpaths part XPathsPartEntry xppe = new XPathsPartEntry(controlMain.model); xppe.setup(null, CurrentPart.Id, strXPath, prefixMappings, false); // Don't setup Q until after drop xppe.save(); // Write tag td.set("od:xpath", xppe.xpathId); // Does this node contain XHTML? // TODO: error handling log.Info(nodeContent); if (isFlatOPC) { // <?mso-application progid="Word.Document"?> // <pkg:package xmlns:pkg="http://schemas.microsoft.com/office/2006/xmlPackage"> td.set("od:progid", "Word.Document"); title = "Word: " + xppe.xpathId; needBind = false; // make it a rich text control } else if (isXHTML) { td.set("od:ContentType", "application/xhtml+xml"); // TODO since this is a run-level sdt, // the XHTML content will need to be run-level. // Help the user with this? // Or in a run-level context, docx4j could convert // p to soft-enter? But what to do about tables? title = "XHTML: " + xppe.xpathId; needBind = false; // make it a rich text control // Word will only replace our HTML-imported-to-docx with the raw HTML // if we have the bind. // Without this, giving the user visual feedback in Word is a TODO } else if (isPicture) { designMode.Off(); log.Debug("NEW Special case handling for pictures.."); //object missing = System.Type.Missing; Globals.ThisAddIn.Application.Selection.Collapse(ref missing); // Are they dragging to an existing picture content control Word.ContentControl picCC = ContentControlMaker.getActiveContentControl(CurrentDocument, Globals.ThisAddIn.Application.Selection); try { if (picCC == null || (picCC.Type != Word.WdContentControlType.wdContentControlPicture)) { picCC = CurrentDocument.ContentControls.Add( Word.WdContentControlType.wdContentControlRichText, ref missing); designMode.restoreState(); } } catch (COMException ce) { // Will happen if you try to drag a text node onto an existing image content control log.Debug("Ignoring " + ce.Message); return; } PictureUtils.setPictureHandler(td); picCC.Title = "Image: " + xppe.xpathId; picCC.Tag = td.asQueryString(); PictureUtils.pastePictureIntoCC(picCC, Convert.FromBase64String(nodeContent)); return; } else { title = "Data value: " + xppe.xpathId; needBind = true; } tag = td.asQueryString(); } else if (controlMain.controlMode1.isModeCondition()) { log.Debug("In condition mode"); ConditionsPartEntry cpe = new ConditionsPartEntry(controlMain.model); cpe.setup(CurrentPart.Id, strXPath, prefixMappings, false); cpe.save(); title = "Conditional: " + cpe.conditionId; // Write tag td.set("od:condition", cpe.conditionId); tag = td.asQueryString(); } else if (controlMain.controlMode1.isModeRepeat()) { log.Debug("In repeat mode"); // 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(); title = "Data value: " + xppe.xpathId; // Write tag td.set("od:repeat", xppe.xpathId); tag = td.asQueryString(); } //create the HTML string strHTML = string.Empty; if (isFlatOPC) { // <?mso-application progid="Word.Document"?> // <pkg:package xmlns:pkg="http://schemas.microsoft.com/office/2006/xmlPackage"> nodeContent = ControlTreeView.EscapeXHTML(nodeContent); strHTML = ClipboardUtilities.GenerateClipboardHTML(needBind, strXPath, Utilities.GetPrefixMappings(xmlnsMgr), CurrentPart.Id, Utilities.MappingType.RichText, title, tag, nodeContent); } else if (isXHTML) { // need to escape eg <span> for it to get through the Clipboard nodeContent = ControlTreeView.EscapeXHTML(nodeContent); // use a RichText control, and set nodeContent strHTML = ClipboardUtilities.GenerateClipboardHTML(needBind, strXPath, Utilities.GetPrefixMappings(xmlnsMgr), CurrentPart.Id, Utilities.MappingType.RichText, title, tag, nodeContent); // alternatively, this could be done in DocumentEvents.doc_ContentControlAfterAdd // but to do it there, we'd need to manually resolve the XPath to // find the value of the CustomXMLNode it pointed to. } else if (!needBind) { // For conditions & repeats, we use a RichText control strHTML = ClipboardUtilities.GenerateClipboardHTML(needBind, strXPath, Utilities.GetPrefixMappings(xmlnsMgr), CurrentPart.Id, Utilities.MappingType.RichText, title, tag); } else { // Normal bind if (OwnerDocument.Schemas.Count > 0) { switch (Utilities.CheckNodeType((XmlNode)tn.Tag)) { case Utilities.MappingType.Date: strHTML = ClipboardUtilities.GenerateClipboardHTML(needBind, strXPath, prefixMappings, CurrentPart.Id, Utilities.MappingType.Date, title, tag); break; case Utilities.MappingType.DropDown: strHTML = ClipboardUtilities.GenerateClipboardHTML(needBind, strXPath, prefixMappings, CurrentPart.Id, Utilities.MappingType.DropDown, title, tag); break; case Utilities.MappingType.Picture: strHTML = ClipboardUtilities.GenerateClipboardHTML(needBind, strXPath, prefixMappings, CurrentPart.Id, Utilities.MappingType.Picture, title, tag); break; default: strHTML = ClipboardUtilities.GenerateClipboardHTML(needBind, strXPath, prefixMappings, CurrentPart.Id, Utilities.MappingType.Text, title, tag); break; } } else { //String val = ((XmlNode)tn.Tag).InnerText; if (ContentDetection.IsBase64Encoded(val)) { // Force picture content control strHTML = ClipboardUtilities.GenerateClipboardHTML(needBind, strXPath, Utilities.GetPrefixMappings(xmlnsMgr), CurrentPart.Id, Utilities.MappingType.Picture, title, tag); } else { strHTML = ClipboardUtilities.GenerateClipboardHTML(needBind, strXPath, Utilities.GetPrefixMappings(xmlnsMgr), CurrentPart.Id, Utilities.MappingType.Text, title, tag); } } } // All cases:- //notify ourselves of a pending drag/drop controlMain.NotifyDragDrop(true); //throw it on the clipboard to drag DataObject dobj = new DataObject(); dobj.SetData(DataFormats.Html, strHTML); dobj.SetData(DataFormats.Text, tn.Text); controlTreeView.DoDragDrop(dobj, DragDropEffects.Move); //notify ourselves of a completed drag/drop controlMain.NotifyDragDrop(false); Clipboard.SetData(DataFormats.Text, ((XmlNode)tn.Tag).InnerXml); }
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? "); } } }
private void buttonCondition_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 condition 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 conditionCC = null; object missing = System.Type.Missing; try { if (Globals.ThisAddIn.Application.Selection.Type == Microsoft.Office.Interop.Word.WdSelectionType.wdSelectionIP) { // Nothing is selected, so type "condition" document.Windows[1].Selection.Text="condition"; } object range = Globals.ThisAddIn.Application.Selection.Range; conditionCC = document.ContentControls.Add(Word.WdContentControlType.wdContentControlRichText, ref range); // Limitation here: you can't make your content control of eg type picture 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; } conditionCC.Title = "Condition [unbound]"; // // This used if they later click edit if (shallowChildren.Count == 0) { log.Debug("No child control found. So Condition not set on our new CC"); //MessageBox.Show("Unbound content control only added. Click the edit button to setup the condition."); editXPath(conditionCC); 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 Condition not set on our new CC"); //MessageBox.Show("Naked content control only added. Click the edit button to setup the condition."); editXPath(conditionCC); return; } // Get XPath. Could use the od xpaths part, but // easier here to get it from the binding string strXPath = usableChild.XMLMapping.XPath; log.Debug("Getting count condition from " + strXPath); strXPath = "count(" + strXPath + ")>0"; log.Debug(strXPath); ConditionsPartEntry cpe = new ConditionsPartEntry(Model.ModelFactory(document)); // TODO fix usableChild.XMLMapping.PrefixMappings cpe.setup(usableChild.XMLMapping.CustomXMLPart.Id, strXPath, "", true); cpe.save(); conditionCC.Title = "Conditional: " + cpe.conditionId; // Write tag TagData td = new TagData(""); td.set("od:condition", cpe.conditionId); conditionCC.Tag = td.asQueryString(); editXPath(conditionCC); }
/// <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(); } }
///// <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 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 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; }
/// <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(); }
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; }
public void handle(Word.ContentControl copiedCC) { if (copiedCC.Tag == null) { // Its just some content control we don't care about fabDocxState.registerKnownSdt(copiedCC); return; // this is OK, since if it contains a repeat or something, that event will fire } log.Info("copy handler invoked on CC: " + copiedCC.Tag); if (copiedCC.Tag.Contains("od:xpath")) { td = new TagData(copiedCC.Tag); string xpathID = td.getXPathID(); // Check it is known to this docx if (xppe.getXPathByID(xpathID) == null) { removeButKeepContents(copiedCC); return; } // RULE: A variable cc can copied wherever. // If its repeat ancestors change, its "vary in repeat" // will need to change (to lowest common denominator). handleXPath(td.getXPathID(), true); } else if (copiedCC.Tag.Contains("od:repeat")) { td = new TagData(copiedCC.Tag); string xpathID = td.getRepeatID(); // Check it is known to this docx if (xppe.getXPathByID(xpathID) == null) { removeButKeepContents(copiedCC); return; } // RULE: A repeat can only be copied if destination // has same repeat ancestors (in which case no change // to answer file is required). handleRepeat(copiedCC); } else if (copiedCC.Tag.Contains("od:condition")) { // Find child CC Microsoft.Office.Interop.Word.Range rng = copiedCC.Range; Word.ContentControls ccs = rng.ContentControls; foreach (Word.ContentControl desc in ccs) { if (desc.ParentContentControl.ID.Equals(copiedCC.ID)) { handle(desc); } } } else { // Its just some content control we don't care about fabDocxState.registerKnownSdt(copiedCC); return; } }
/// <summary> /// Create OpenDoPE parts, including optionally, question part. /// </summary> public void process() { Microsoft.Office.Interop.Word.Document document = null; try { document = Globals.ThisAddIn.Application.ActiveDocument; } catch (Exception ex) { Mbox.ShowSimpleMsgBoxError("No document is open/active. Create or open a docx first."); return; } Model model = Model.ModelFactory(document); // Button shouldn't be available if this exists, // but .. if (model.conditionsPart == null) { conditions conditions = new conditions(); string conditionsXml = conditions.Serialize(); model.conditionsPart = addCustomXmlPart(document, conditionsXml); } if (model.componentsPart == null) { components components = new components(); string componentsXml = components.Serialize(); model.componentsPart = addCustomXmlPart(document, componentsXml); } // Add XPath xpaths xpaths = new xpaths(); // Button shouldn't be available if this exists, // but .. if (model.xpathsPart != null) { xpaths.Deserialize(model.xpathsPart.XML, out xpaths); } int idInt = 1; foreach (Word.ContentControl cc in Globals.ThisAddIn.Application.ActiveDocument.ContentControls) { if (cc.XMLMapping.IsMapped) { log.Debug("Adding xpath for " + cc.ID); // then we need to add an XPath string xmXpath = cc.XMLMapping.XPath; xpathsXpath item = new xpathsXpath(); // I make no effort here to check whether the xpath // already exists, since the part shouldn't already exist! item.id = "x" + idInt; xpathsXpathDataBinding db = new xpathsXpathDataBinding(); db.xpath = xmXpath; db.storeItemID = cc.XMLMapping.CustomXMLPart.Id; if (!string.IsNullOrWhiteSpace(cc.XMLMapping.PrefixMappings)) db.prefixMappings = cc.XMLMapping.PrefixMappings; item.dataBinding = db; xpaths.xpath.Add(item); // Write tag TagData td = new TagData(cc.Tag); td.set("od:xpath", item.id); cc.Tag = td.asQueryString(); log.Debug(".. added for " + cc.ID); idInt++; } } string xpathsXml = xpaths.Serialize(); if (model.xpathsPart == null) { model.xpathsPart = addCustomXmlPart(document, xpathsXml); } else { CustomXmlUtilities.replaceXmlDoc(model.xpathsPart, xpathsXml); } Microsoft.Office.Tools.Word.Document extendedDocument = Globals.ThisAddIn.Application.ActiveDocument.GetVstoObject(Globals.Factory); //Microsoft.Office.Tools.CustomTaskPane ctp // = Globals.ThisAddIn.createCTP(document, cxp, xpathsPart, conditionsPart, questionsPart, componentsPart); //extendedDocument.Tag = ctp; //// Want a 2 way association //WedTaskPane wedTaskPane = (WedTaskPane)ctp.Control; //wedTaskPane.associatedDocument = document; //extendedDocument.Shutdown += new EventHandler( // Globals.ThisAddIn.extendedDocument_Shutdown); //taskPane.setupCcEvents(document); log.Debug("Done. Task pane now also open."); }