/// <summary> /// Inserts the specified text at the current cursor position, if possible /// </summary> private async Task InsertText(XmlCursor cursor, string text, XmlRules xmlRules) { XmlCursorPos insertPos; // If something is selected, then delete it first, because it will be replaced by the new text XmlCursor deleteArea = cursor.Clone(); await deleteArea.OptimizeSelection(); var deleteResult = await XmlCursorSelectionHelper.DeleteSelection(deleteArea); if (deleteResult.Success) { insertPos = deleteResult.NewCursorPosAfterDelete; } else { insertPos = cursor.StartPos.Clone(); } // insert the specified text at the cursor position var replacementNode = InsertAtCursorPosHelper.InsertText(insertPos, text, xmlRules).ReplaceNode; if (replacementNode != null) { // Text could not be inserted because a node input was converted from text input. // Example: In the AIML template, * is pressed, and a <star> is inserted there instead InsertAtCursorPosHelper.InsertXmlNode(insertPos, replacementNode, xmlRules, false); } // then the cursor is only one line behind the inserted text await cursor.SetPositions(insertPos.ActualNode, insertPos.PosOnNode, insertPos.PosInTextNode, throwChangedEventWhenValuesChanged : false); }
public static LastPaintingDataText CalculateActualPaintData(PaintContext paintContext, bool cursorBlinkOn, XmlNode node, string actualText, int fontHeight, XmlCursor cursor, int selectionStart, int selectionLength) { return(new LastPaintingDataText { LastCursor = cursor.Clone(), LastPaintPosY = paintContext.PaintPosY, LastPaintPosX = paintContext.PaintPosX, LastPaintLimitRight = paintContext.LimitRight, LastPaintContent = actualText, LastPaintTextFontHeight = fontHeight, SelectionStart = selectionStart, SelectionLength = selectionLength, CursorInNode = cursor.StartPos.ActualNode == node, CursorPosInNode = cursor.StartPos.PosInTextNode, CursorBlinkOn = cursorBlinkOn, LastCursorPos = cursor.StartPos.PosOnNode, }); }
/// <summary> /// FInserts the specified node at the current cursor position, if possible /// </summary> private async Task XMLNodeEinfuegen(XmlCursor cursor, XmlNode node, XmlRules xmlRules, bool setNewCursorPosBehindNewInsertedNode) { // If something is selected, then delete it first, because it will be replaced by the new text XmlCursor deleteArea = cursor.Clone(); await deleteArea.OptimizeSelection(); var deleteResult = await XmlCursorSelectionHelper.DeleteSelection(deleteArea); if (deleteResult.Success) { await cursor.SetPositions(deleteResult.NewCursorPosAfterDelete.ActualNode, deleteResult.NewCursorPosAfterDelete.PosOnNode, deleteResult.NewCursorPosAfterDelete.PosInTextNode, throwChangedEventWhenValuesChanged : false); } // insert the specified node at the cursor position if (InsertAtCursorPosHelper.InsertXmlNode(cursor.StartPos, node, xmlRules, setNewCursorPosBehindNewInsertedNode)) { // then the cursor is only one line behind the inserted cursor.EndPos.SetPos(cursor.StartPos.ActualNode, cursor.StartPos.PosOnNode, cursor.StartPos.PosInTextNode); } }
/// <summary> /// Returns the selected XML content as string /// </summary> public static async Task <string> GetSelectionAsString(XmlCursor cursor) { if (cursor.IsSomethingSelected) { var result = new StringBuilder(); var optimized = cursor.Clone(); await optimized.OptimizeSelection(); System.Xml.XmlNode node = optimized.StartPos.ActualNode; // begin at the start node // Include the start node in the result switch (optimized.StartPos.PosOnNode) { case XmlCursorPositions.CursorOnNodeEndTag: case XmlCursorPositions.CursorOnNodeStartTag: case XmlCursorPositions.CursorInFrontOfNode: result.Append(node.OuterXml); // take the entire start node break; case XmlCursorPositions.CursorBehindTheNode: case XmlCursorPositions.CursorInsideTheEmptyNode: break; case XmlCursorPositions.CursorInsideTextNode: // take only a part of the text string textPart = node.InnerText; int start = optimized.StartPos.PosInTextNode; int length = textPart.Length - start; if (node == optimized.EndPos.ActualNode) // If this text node is both start and end node { switch (optimized.EndPos.PosOnNode) { case XmlCursorPositions.CursorOnNodeEndTag: case XmlCursorPositions.CursorBehindTheNode: // Length stays to the end of the node break; case XmlCursorPositions.CursorOnNodeStartTag: case XmlCursorPositions.CursorInsideTheEmptyNode: case XmlCursorPositions.CursorInFrontOfNode: throw new ApplicationException($"XMLCursor.GetSelectionAsString: implausible EndPos.PosOnNode '{optimized.EndPos.PosOnNode}' for StartPos.CursorInsideTextNode"); case XmlCursorPositions.CursorInsideTextNode: // Not quite to the end of the text if (optimized.StartPos.PosInTextNode > optimized.EndPos.PosInTextNode) { throw new ApplicationException("XMLCursor.GetSelectionAsString: optimized.StartPos.PosInTextNode > optimized.EndPos.PosInTextNode"); } else { // Subtract the text from the length after selection length -= (textPart.Length - optimized.EndPos.PosInTextNode); } break; default: throw new ApplicationException($"XMLCursor.GetSelectionAsString: unhandled optimized.EndPos.PosOnNode'{optimized.EndPos.PosOnNode}' for StartPos.CursorInsideTextNode"); } } textPart = textPart.Substring(start, length); result.Append(textPart); break; default: throw new ApplicationException($"XMLCursor.GetSelectionAsStringg: unhandled optimized.StartPos.PosOnNode'{optimized.StartPos.PosOnNode}'"); } if (optimized.StartPos.ActualNode != optimized.EndPos.ActualNode) // If more nodes are selected after the start node { do { node = node.NextSibling; // to the next node... if (node != null) { // Include the node in the result if (node == optimized.EndPos.ActualNode) // This node is the EndNode { switch (optimized.EndPos.PosOnNode) { case XmlCursorPositions.CursorOnNodeEndTag: case XmlCursorPositions.CursorOnNodeStartTag: case XmlCursorPositions.CursorBehindTheNode: result.Append(node.OuterXml); // Include node 1:1 in result break; case XmlCursorPositions.CursorInsideTextNode: // TRake the beginning of the text node string textPart = node.InnerText; result.Append(textPart.Substring(0, optimized.EndPos.PosInTextNode + 1)); break; case XmlCursorPositions.CursorInsideTheEmptyNode: throw new ApplicationException($"XMLCursor.GetSelectionAsString: implausible optimized.EndPos.PosOnNode '{optimized.EndPos.PosOnNode}' for StartPos.Node != EndPos.Node"); default: throw new ApplicationException($"XMLCursor.GetSelectionAsString: implausible optimized.StartPos.PosOnNode'{optimized.StartPos.PosOnNode}' for StartPos.Node != EndPos.Node"); } } else // Include node 1:1 in result { result.Append(node.OuterXml); } } } while ((node != optimized.EndPos.ActualNode) && (node != null)); // ... until the end node is reached if (node == null) { throw new ApplicationException("Endnode was not reachable as NextSibling from Startnode"); } } return(result.ToString()); } else { return(string.Empty); // nothing is selected at all } }
/// <summary> /// Deletes the characters and nodes between StartPos and EndPos of the cursor /// </summary> internal static async Task <DeleteSelectionResult> DeleteSelection(XmlCursor cursor) { // If the cursor contains no selection at all if (!cursor.IsSomethingSelected) { return(new DeleteSelectionResult { NewCursorPosAfterDelete = cursor.StartPos.Clone(), // Cursor is not changed Success = false // nothing deleted }); } else { if (cursor.StartPos.ActualNode == cursor.EndPos.ActualNode) // If both nodes are identical { switch (cursor.StartPos.PosOnNode) { case XmlCursorPositions.CursorOnNodeStartTag: case XmlCursorPositions.CursorOnNodeEndTag: // a single node is selected and should be deleted System.Xml.XmlNode nodeDelete = cursor.StartPos.ActualNode; // This node should be deleted System.Xml.XmlNode nodeBefore = nodeDelete.PreviousSibling; // This node is before the node to be deleted System.Xml.XmlNode nodeAfter = nodeDelete.NextSibling; // This node lies behind the node to be deleted var newPosAfterDelete = new XmlCursorPos(); // This neighboring node will get the cursor after deletion // If the node to be deleted is located between two text nodes, then these two text nodes are combined into one if (nodeBefore != null && nodeAfter != null) { if (nodeBefore is System.Xml.XmlText && nodeAfter is System.Xml.XmlText) { // the node to be deleted lies between two text nodes, therefore these two text nodes are combined into one // Afterwards, the cursor is positioned at the insertion point between the two text modules newPosAfterDelete.SetPos(nodeBefore, XmlCursorPositions.CursorInsideTextNode, nodeBefore.InnerText.Length); nodeBefore.InnerText += nodeAfter.InnerText; // Append the text from after node to the before node // Delete node to be deleted nodeDelete.ParentNode.RemoveChild(nodeDelete); // delete after node nodeAfter.ParentNode.RemoveChild(nodeAfter); return(new DeleteSelectionResult { NewCursorPosAfterDelete = newPosAfterDelete, Success = true }); } } // The node to be deleted is *not* between two text nodes // Determine what should be selected after deletion if (nodeBefore != null) { // After deletion, the cursor is positioned behind the previous node newPosAfterDelete.SetPos(nodeBefore, XmlCursorPositions.CursorBehindTheNode); } else { if (nodeAfter != null) { // After deletion, the cursor is positioned before the following node newPosAfterDelete.SetPos(nodeAfter, XmlCursorPositions.CursorInFrontOfNode); } else { // After deletion, the cursor is in the parent node newPosAfterDelete.SetPos(nodeDelete.ParentNode, XmlCursorPositions.CursorInsideTheEmptyNode); } } // delete the node nodeDelete.ParentNode.RemoveChild(nodeDelete); return(new DeleteSelectionResult { NewCursorPosAfterDelete = newPosAfterDelete, Success = true }); case XmlCursorPositions.CursorInFrontOfNode: // Start and end of the deletion area point to the same node and the start is before the node: This only makes sense with a text node! if (ToolboxXml.IsTextOrCommentNode(cursor.StartPos.ActualNode)) { // Place the cursor in the text node before the first character and then resend cursor.StartPos.SetPos(cursor.StartPos.ActualNode, XmlCursorPositions.CursorInsideTextNode, 0); return(await DeleteSelection(cursor)); // resend to delete } else { // if it is not a text node, then select the whole node and send it again await cursor.SetBothPositionsAndFireChangedEventIfChanged(cursor.StartPos.ActualNode, XmlCursorPositions.CursorOnNodeStartTag); return(await DeleteSelection(cursor)); // resend to delete } case XmlCursorPositions.CursorBehindTheNode: // Start and end of the deletion area point to the same node and the start is behind the node if (ToolboxXml.IsTextOrCommentNode(cursor.StartPos.ActualNode)) { // Place the cursor in the text node before the first character and then resend cursor.StartPos.SetPos(cursor.StartPos.ActualNode, XmlCursorPositions.CursorInsideTextNode, cursor.StartPos.ActualNode.InnerText.Length); return(await DeleteSelection(cursor)); // resend to delete } else { // if it is not a text node, then select the whole node and send it again await cursor.SetBothPositionsAndFireChangedEventIfChanged(cursor.StartPos.ActualNode, XmlCursorPositions.CursorOnNodeStartTag); return(await DeleteSelection(cursor)); // resend to delete } case XmlCursorPositions.CursorInsideTextNode: // a part of a text node is to be deleted // Determine the part of the text to be deleted int startpos = cursor.StartPos.PosInTextNode; int endpos = cursor.EndPos.PosInTextNode; if (cursor.EndPos.PosOnNode == XmlCursorPositions.CursorBehindTheNode) { // If the end of the selection is behind the text node, then all remaining text is selected endpos = cursor.StartPos.ActualNode.InnerText.Length; } // If all text is selected, then delete the entire text node if (startpos == 0 && endpos >= cursor.StartPos.ActualNode.InnerText.Length) { // The whole text node is to be deleted, this is passed on to the method for deleting individually selected nodes XmlCursor nodeSelectedCursor = new XmlCursor(); await nodeSelectedCursor.SetBothPositionsAndFireChangedEventIfChanged(cursor.StartPos.ActualNode, XmlCursorPositions.CursorOnNodeStartTag); return(await DeleteSelection(nodeSelectedCursor)); } else { // Only a part of the text is to be deleted string restText = cursor.StartPos.ActualNode.InnerText; restText = restText.Remove(startpos, endpos - startpos); cursor.StartPos.ActualNode.InnerText = restText; // determine where the cursor is after deletion newPosAfterDelete = new XmlCursorPos(); if (startpos == 0) // The cursor is positioned before the first character { // then it can better be placed before the text node itself newPosAfterDelete.SetPos(cursor.StartPos.ActualNode, XmlCursorPositions.CursorInFrontOfNode); } else { newPosAfterDelete.SetPos(cursor.StartPos.ActualNode, XmlCursorPositions.CursorInsideTextNode, startpos); } return(new DeleteSelectionResult { NewCursorPosAfterDelete = newPosAfterDelete, Success = true }); } case XmlCursorPositions.CursorInsideTheEmptyNode: if (cursor.EndPos.PosOnNode == XmlCursorPositions.CursorBehindTheNode || cursor.EndPos.PosOnNode == XmlCursorPositions.CursorInFrontOfNode) { XmlCursor newCursor = new XmlCursor(); await newCursor.SetBothPositionsAndFireChangedEventIfChanged(cursor.StartPos.ActualNode, XmlCursorPositions.CursorOnNodeStartTag, 0); return(await DeleteSelection(newCursor)); } else { throw new ApplicationException($"DeleteSelection:#6363S undefined Endpos '{cursor.EndPos.PosOnNode}'!"); } default: // what else should be selected besides text and the node itself, if start node and end node are identical? throw new ApplicationException($"DeleteSelection:#63346 StartPos.PosAmNode '{cursor.StartPos.PosOnNode}' not allowed!"); } } else // Both nodes are not identical { // If both nodes are not identical, then remove all nodes in between until the two nodes are behind each other while (cursor.StartPos.ActualNode.NextSibling != cursor.EndPos.ActualNode) { cursor.StartPos.ActualNode.ParentNode.RemoveChild(cursor.StartPos.ActualNode.NextSibling); } // delete the endnode or a part of it XmlCursor temp = cursor.Clone(); temp.StartPos.SetPos(cursor.EndPos.ActualNode, XmlCursorPositions.CursorInFrontOfNode); await DeleteSelection(temp); // delete the start node, or a part of it // -> Done by recursion in the selection delete method cursor.EndPos.SetPos(cursor.StartPos.ActualNode, XmlCursorPositions.CursorBehindTheNode); return(await DeleteSelection(cursor)); } } }