void onInsertHyperlink() { //get the document fragment which the user has currently selected using mouse and/or cursor IWindowSelection windowSelection = modelEdit.windowSelection; //verify that there's only one selection if (windowSelection.rangeCount != 1) { //this can happen when the user has selected several cell in a table, //in which case each cell is a separate selection/range MessageBox.Show("Can't insert a hyperlink when more than one range in the document is selected"); return; } using (IDomRange domRange = windowSelection.getRangeAt(0)) { //verify that only one node is selected if (!domRange.startContainer.isSameNode(domRange.endContainer)) { //this can happen for example when the selection spans multiple paragraphs MessageBox.Show("Can't insert a hyperlink when more than one node in the document is selected"); return; } IDomNode container = domRange.startContainer; //already just checked that this is the same as domRange.endContainer //read existing values from the current selection string url; string visibleText; IDomElement existingHyperlink; switch (container.nodeType) { case DomNodeType.Text: //selection is a text fragment visibleText = container.nodeValue.Substring(domRange.startOffset, domRange.endOffset - domRange.startOffset); IDomNode parentNode = container.parentNode; if ((parentNode.nodeType == DomNodeType.Element) && (parentNode.nodeName == "a")) { //parent of this text node is a <a> element existingHyperlink = parentNode as IDomElement; url = existingHyperlink.getAttribute("href"); visibleText = container.nodeValue; if ((existingHyperlink.childNodes.count != 1) || (existingHyperlink.childNodes.itemAt(0).nodeType != DomNodeType.Text)) { //this can happen when an anchor tag wraps more than just a single, simple text node //for example when it contains inline elements like <strong> MessageBox.Show("Can't edit a complex hyperlink"); return; } } else { existingHyperlink = null; url = null; } break; default: //unexpected MessageBox.Show("Can't insert a hyperlink when more than one node in the document is selected"); return; } //display the modal dialog box using (FormInsertHyperlink formInsertHyperlink = new FormInsertHyperlink()) { formInsertHyperlink.url = url; formInsertHyperlink.visibleText = visibleText; DialogResult dialogResult = formInsertHyperlink.ShowDialog(); if (dialogResult != DialogResult.OK) { //user cancelled return; } //get new values from the dialog box //the FormInsertHyperlink.onEditTextChanged method assures that both strings are non-empty url = formInsertHyperlink.url; visibleText = formInsertHyperlink.visibleText; } //need to change href, change text, and possibly delete existing text; //do this within the scope of a single IEditorTransaction instance so //that if the user does 'undo' then it will undo all these operations at once, instead of one at a time using (IEditorTransaction editorTransaction = modelEdit.createEditorTransaction()) { if (existingHyperlink != null) { //changing an existing hyperlink ... //... change the href attribute value existingHyperlink.setAttribute("href", url); //... change the text, by removing the old text node and inserting a new text node IDomText newDomText = modelEdit.domDocument.createTextNode(visibleText); IDomNode oldDomText = existingHyperlink.childNodes.itemAt(0); existingHyperlink.removeChild(oldDomText); existingHyperlink.insertBefore(newDomText, null); } else { //creating a new hyperlink IDomElement newHyperlink = modelEdit.domDocument.createElement("a"); IDomText newDomText = modelEdit.domDocument.createTextNode(visibleText); newHyperlink.insertBefore(newDomText, null); newHyperlink.setAttribute("href", url); //remove whatever was previously selected, if anything if (!domRange.collapsed) { domRange.deleteContents(); } //insert the new hyperlink domRange.insertNode(newHyperlink); } } } }