/// <summary>
        /// Inserts a text between two nodes
        /// </summary>
        internal static void InsertTextBetweenTwoNodes(XmlCursorPos cursorPos, System.Xml.XmlNode nodeBefore, System.Xml.XmlNode nodeAfter, string text, XmlRules xmlRules)
        {
            if (ToolboxXml.IsTextOrCommentNode(nodeBefore))  // if the node is already text before, then simply append to it
            {
                nodeBefore.InnerText += text;
                cursorPos.SetPos(nodeBefore, XmlCursorPositions.CursorInsideTextNode, nodeBefore.InnerText.Length);
            }
            else  // the node before is no text
            {
                if (ToolboxXml.IsTextOrCommentNode(nodeAfter))  // if the node behind it is already text then just paste it into
                {
                    nodeAfter.InnerText = $"{text}{nodeAfter.InnerText}";
                    cursorPos.SetPos(nodeAfter, XmlCursorPositions.CursorInsideTextNode, text.Length);
                }
                else // the node behind is also no text
                {
                    // Insert between two non-text nodes
                    if (xmlRules.IsThisTagAllowedAtThisPos("#PCDATA", cursorPos))
                    {
                        System.Xml.XmlText newTextNode = cursorPos.ActualNode.OwnerDocument.CreateTextNode(text);
                        InsertXmlNode(cursorPos, newTextNode, xmlRules, false);
                    }
                    else
                    {
#warning Insert another correct message or sound
                        Debug.Assert(false, "Error - Beep!");
                    }
                }
            }
        }
示例#2
0
        /// <summary>
        /// Sets the cursor positions for corresponding mouse actions: For MouseDown StartAndEndpos, for Move and Up only the endpos
        /// </summary>
        public async Task SetCursorByMouseAction(XmlNode xmlNode, XmlCursorPositions cursorPos, int posInLine, MouseClickActions action)
        {
            switch (action)
            {
            case MouseClickActions.MouseDown:
                // move the cursor to the new position
                await SetPositions(xmlNode, cursorPos, posInLine, throwChangedEventWhenValuesChanged : true);

                break;

            case MouseClickActions.MouseDownMove:
            case MouseClickActions.MouseUp:
                // Set end of the select cursor
                if (EndPos.SetPos(xmlNode, cursorPos, posInLine))
                {
                    await this.ForceChangedEvent();
                }
                break;
            }
        }
示例#3
0
        private bool CheckNodePos(System.Xml.XmlNode node)
        {
            // Comment is always ok
            //if (node is System.Xml.XmlComment) return true;
            // Whitespace is always ok
            if (node is System.Xml.XmlWhitespace)
            {
                return(true);
            }

            if (dtd.IsDtdElementKnown(Dtd.GetElementNameFromNode(node))) // The element of this node is known in the DTD
            {
                try
                {
                    if (this.NodeChecker.IsTheNodeAllowedAtThisPos(node))
                    {
                        return(true);
                    }
                    else
                    {
                        errorMessages.AppendFormat($"Tag '{node.Name}' not allowed here.");
                        var pos = new XmlCursorPos();
                        pos.SetPos(node, XmlCursorPositions.CursorOnNodeStartTag);
                        var allowedTags = this.NodeChecker.AtThisPosAllowedTags(pos, false, false); // what is allowed at this position?
                        if (allowedTags.Length > 0)
                        {
                            errorMessages.Append("At this position allowed:");
                            foreach (string tag in allowedTags)
                            {
                                errorMessages.AppendFormat("{0} ", tag);
                            }
                        }
                        else
                        {
                            errorMessages.Append("No tags are allowed at this point. Probably the parent tag is already invalid.");
                        }
                        return(false);
                    }
                }
                catch (Dtd.XMLUnknownElementException e)
                {
                    errorMessages.AppendFormat($"unknown element '{e.ElementName}'");
                    return(false);
                }
            }
            else // The element of this node is not known in the DTD
            {
                errorMessages.AppendFormat($"unknown element '{Dtd.GetElementNameFromNode(node)}'");
                return(false);
            }
        }
        /// <summary>
        ///  Is the specified element allowed at this point in the XML?
        /// </summary>
        public bool IsTheNodeAllowedAtThisPos(System.Xml.XmlNode node)
        {
            if (node.ParentNode is System.Xml.XmlDocument)
            {   // It is the root element, this cannot be checked against the parent node, but must be compared separately. If it is the root element allowed in the DTD, then ok, otherwise not
                // Implementation: TO DO!
                return(true);
            }
            else
            {
                var cursorPos = new XmlCursorPos();
                cursorPos.SetPos(node, XmlCursorPositions.CursorOnNodeStartTag);

#if ThinkLogging
                _thinkLog = new StringBuilder();
#endif

                // Create the test patterns to insert for all available elements
                var elementName = Dtd.GetElementNameFromNode(node);
                var pattern     = this.CreateTestPattern(elementName, cursorPos);

                // Pack into a test sample list and send the list for testing
                var list = new List <DtdTestpattern>();
                list.Add(pattern);
                this.CheckAllTestPattern(list, cursorPos);

                if (pattern.Success)
                {
#if ThinkLogging
                    _thinkLog = new StringBuilder();
                    _thinkLog.Append(pattern.Summary + "\r\n");
#endif
                    return(true);
                }
                else
                {
                    return(false);
                }
            }
        }
        /// <summary>
        /// Replaces the root node of the editor with the content of the clipboard
        /// </summary>
        private async Task <bool> ActionReplaceRootNodeByClipboardContent(SetUndoSnapshotOptions setUnDoSnapshot)
        {
            if (!ActionsAllowed)
            {
                return(false);
            }

            try
            {
                var text = await this.nativePlatform.Clipboard.GetText();

                using (var reader = new XmlTextReader(text, XmlNodeType.Element, null))
                {
                    reader.MoveToContent(); // Move to the cd element node.

                    // Create a new root node from the clipboard, from which we can then steal the children
                    var pasteNode = this.editorState.RootNode.OwnerDocument.ReadNode(reader);

                    if (pasteNode.Name != this.editorState.RootNode.Name)
                    {
                        // The node in the clipboard and the current root node do not have the same name
                        return(false); // not allowed
                    }

                    if (setUnDoSnapshot == SetUndoSnapshotOptions.Yes)
                    {
                        this.editorState.UndoHandler.SetSnapshot("replace root node by clipboard content", this.editorState.CursorRaw);
                    }

                    // Delete all children + attributes of the previous root node
                    this.editorState.RootNode.RemoveAll();

                    // Copy all attributes of the clipboard root node to the correct root node
                    while (pasteNode.Attributes.Count > 0)
                    {
                        var attrib = pasteNode.Attributes.Remove(pasteNode.Attributes[0]); // Remove from clipboard root node
                        this.editorState.RootNode.Attributes.Append(attrib);               // put to the right root node
                    }

                    var startPos = new XmlCursorPos();
                    startPos.SetPos(this.editorState.RootNode, XmlCursorPositions.CursorInsideTheEmptyNode);
                    XmlCursorPos endPos;

                    // Now insert all children of the virtual root node one after the other at the CursorPos
                    endPos = startPos.Clone(); // Before inserting start- and endPos are equal
                    while (pasteNode.ChildNodes.Count > 0)
                    {
                        var child = pasteNode.RemoveChild(pasteNode.FirstChild);
                        this.editorState.RootNode.AppendChild(child);
                    }
                    await this.editorState.CursorRaw.SetPositions(this.editorState.RootNode, XmlCursorPositions.CursorOnNodeStartTag, 0, throwChangedEventWhenValuesChanged : false);

                    await this.editorState.FireContentChangedEvent(needToSetFocusOnEditorWhenLost : false, forceFullRepaint : false);

                    return(true);
                }
            }
            catch (Exception e)
            {
                this.nativePlatform.LogError($"ActionReplaceRootNodeByClipboardContent: error for insert text 'text': {e.Message}");
                return(false);
            }
        }
示例#6
0
        internal static async Task <bool> MoveLeft(XmlCursorPos cursorPos, System.Xml.XmlNode rootnode, XmlRules xmlRules)
        {
            var actualNode = cursorPos.ActualNode;

            switch (cursorPos.PosOnNode)
            {
            case XmlCursorPositions.CursorOnNodeStartTag:
            case XmlCursorPositions.CursorOnNodeEndTag:
                cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorInFrontOfNode);
                break;

            case XmlCursorPositions.CursorInFrontOfNode:
                if (actualNode != rootnode)
                {
                    if (actualNode.PreviousSibling != null)
                    {
                        cursorPos.SetPos(actualNode.PreviousSibling, XmlCursorPositions.CursorBehindTheNode);
                        await MoveLeft(cursorPos, rootnode, xmlRules);
                    }
                    else     // no previous sibling node available
                    {
                        cursorPos.SetPos(actualNode.ParentNode, XmlCursorPositions.CursorInFrontOfNode);
                    }
                }
                else
                {
                    return(false);
                }
                break;

            case XmlCursorPositions.CursorBehindTheNode:
                if (ToolboxXml.IsTextOrCommentNode(actualNode))     // With a text node the cursor is placed behind the last character
                {
                    cursorPos.SetPos(actualNode, XmlCursorPositions.CursorInsideTextNode, Math.Max(0, ToolboxXml.TextFromNodeCleaned(actualNode).Length - 1));
                }
                else
                {
                    if (actualNode.ChildNodes.Count == 0)
                    {
                        if (xmlRules.HasEndTag(actualNode))
                        {
                            //  If the cursor shows a close tag, place it in the empty node
                            cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorInsideTheEmptyNode);
                        }
                        else
                        {
                            // If the cursor does *not* show a close tag, place it before the empty node
                            cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorInFrontOfNode);
                        }
                    }
                    else     // there are children in the node
                    {
                        cursorPos.SetPos(actualNode.LastChild, XmlCursorPositions.CursorBehindTheNode);
                    }
                }
                break;

            case XmlCursorPositions.CursorInsideTheEmptyNode:
                cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorInFrontOfNode);
                break;

            case XmlCursorPositions.CursorInsideTextNode:
                if (ToolboxXml.IsTextOrCommentNode(actualNode))     // Node is text node
                {
                    if (cursorPos.PosInTextNode > 1)
                    {      // Cursor one character to the left
                        cursorPos.SetPos(cursorPos.ActualNode, cursorPos.PosOnNode, cursorPos.PosInTextNode - 1);
                    }
                    else
                    {
                        // Put in front of the node
                        cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorInFrontOfNode);
                    }
                }
                else     // not a text node
                {
                    throw new ApplicationException(string.Format("XMLCursorPos.MoveLeft: CursorPos is XMLCursorPositions.CursorInsideTextNodes, but no text node is selected, but the node {0}", actualNode.OuterXml));
                }
                break;

            default:
                throw new ApplicationException(String.Format("XMLCursorPos.MoveLeft: unknown CursorPos {0}", cursorPos.PosOnNode));
            }
            return(true);
        }
示例#7
0
        internal static async Task <bool> MoveRight(XmlCursorPos cursorPos, System.Xml.XmlNode rootnode, XmlRules xmlRules)
        {
            System.Xml.XmlNode node = cursorPos.ActualNode;

            switch (cursorPos.PosOnNode)
            {
            case XmlCursorPositions.CursorOnNodeStartTag:
            case XmlCursorPositions.CursorOnNodeEndTag:
                cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorBehindTheNode);
                break;

            case XmlCursorPositions.CursorBehindTheNode:
                if (node.NextSibling != null)
                {
                    // Place in front of the next sibling
                    cursorPos.SetPos(node.NextSibling, XmlCursorPositions.CursorInFrontOfNode);
                    // Since "behind the first" looks the same as "before the second", move one more step to the right
                    await MoveRight(cursorPos, rootnode, xmlRules);
                }
                else     // No following siblings available, then set behind the parent node
                {
                    if (node.ParentNode != rootnode)
                    {
                        cursorPos.SetPos(node.ParentNode, XmlCursorPositions.CursorBehindTheNode);
                        if (!xmlRules.HasEndTag(node.ParentNode))
                        {     // If no closed tag is displayed for the parent, then one more to the right
                            await MoveRight(cursorPos, rootnode, xmlRules);
                        }
                    }
                    else
                    {
                        return(false);
                    }
                }
                break;

            case XmlCursorPositions.CursorInsideTheEmptyNode:
                cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorBehindTheNode);
                break;

            case XmlCursorPositions.CursorInFrontOfNode:
                if (ToolboxXml.IsTextOrCommentNode(node))                                                   // The node itself is text node
                {
                    if (ToolboxXml.TextFromNodeCleaned(node).Length > 1)                                    // Text node is not empty
                    {
                        cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorInsideTextNode, 1); // one character forward, i.e. after the first character
                    }
                    else                                                                                    // Text node is empty
                    {
                        cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorBehindTheNode);
                    }
                }
                else      // Node is not a text node
                {
                    if (node.ChildNodes.Count == 0)
                    {
                        if (!xmlRules.HasEndTag(node))     // If no closed tag is displayed for this node, then directly behind the node
                        {
                            cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorBehindTheNode);
                        }
                        else      // Node has closing tag, so put it in between
                        {
                            // Set to the empty node
                            cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorInsideTheEmptyNode);
                        }
                    }
                    else     // Children available
                    {
                        cursorPos.SetPos(node.FirstChild, XmlCursorPositions.CursorInFrontOfNode);
                    }
                }
                break;

            case XmlCursorPositions.CursorInsideTextNode:
                if (ToolboxXml.IsTextOrCommentNode(node))                                          // Node is text node
                {
                    if (ToolboxXml.TextFromNodeCleaned(node).Length > cursorPos.PosInTextNode + 1) // there is text in the text node on the right
                    {
                        // one character forward, i.e. after the first character
                        cursorPos.SetPos(cursorPos.ActualNode, cursorPos.PosOnNode, cursorPos.PosInTextNode + 1);

                        /*if ((XMLEditor.TextAusTextNodeBereinigt(node).Length == cursor.PosInNode) && (node.NextSibling != null))
                         * {
                         *  // If after the last drawing of the text node and following sibling exists, then before the following sibling node
                         * }*/
                    }
                    else      // no text follows in the text node
                    {
                        cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorBehindTheNode);
                    }
                }
                else     // Node is not a text node
                {
                    throw new ApplicationException(String.Format("XMLCurorPos.MoveRight: CursorPos is XMLCursorPositions.CursorInsideTextNodes, but no text node is selected, but the node {0}", node.OuterXml));
                }
                break;

            default:
                throw new ApplicationException(String.Format("XMLCurorPos.MoveRight: unknown CursorPos {0}", cursorPos.PosOnNode));
            }
            return(true);
        }
示例#8
0
        /// <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));
                }
            }
        }
        /// <summary>
        /// Inserts the specified XML node at the specified position
        /// </summary>
        internal static bool InsertXmlNode(XmlCursorPos cursorPos, System.Xml.XmlNode node, XmlRules xmlRules, bool setNewCursorPosBehindNewInsertedNode)
        {
            System.Xml.XmlNode parentNode = cursorPos.ActualNode.ParentNode;

            switch (cursorPos.PosOnNode)
            {
            case XmlCursorPositions.CursorOnNodeStartTag:     // replace the acual node
            case XmlCursorPositions.CursorOnNodeEndTag:
                parentNode.ReplaceChild(node, cursorPos.ActualNode);
                break;

            case XmlCursorPositions.CursorInFrontOfNode:     // insert before actual node
                parentNode.InsertBefore(node, cursorPos.ActualNode);
                break;

            case XmlCursorPositions.CursorBehindTheNode:     // insert after actual node
                parentNode.InsertAfter(node, cursorPos.ActualNode);
                break;

            case XmlCursorPositions.CursorInsideTheEmptyNode:     // insert into empty node
                cursorPos.ActualNode.AppendChild(node);
                break;

            case XmlCursorPositions.CursorInsideTextNode:     // insert into textnode

                // Make the text available as a node before the insertion position
                string             textDavor     = cursorPos.ActualNode.InnerText.Substring(0, cursorPos.PosInTextNode);
                System.Xml.XmlNode textDavorNode = parentNode.OwnerDocument.CreateTextNode(textDavor);

                // Provide the text behind the insert position as a node
                string             textAfter     = cursorPos.ActualNode.InnerText.Substring(cursorPos.PosInTextNode, cursorPos.ActualNode.InnerText.Length - cursorPos.PosInTextNode);
                System.Xml.XmlNode textAfterNode = parentNode.OwnerDocument.CreateTextNode(textAfter);

                // Insert the node to be inserted between the new before and after text node
                // -> so replace the old text node with
                // textbefore - newNode - textafter
                parentNode.ReplaceChild(textDavorNode, cursorPos.ActualNode);
                parentNode.InsertAfter(node, textDavorNode);
                parentNode.InsertAfter(textAfterNode, node);
                break;

            default:
                throw new ApplicationException(String.Format("InsertXmlNode: unknown PosOnNode {0}", cursorPos.PosOnNode));
            }

            // set cursor
            if (setNewCursorPosBehindNewInsertedNode)
            {
                // Place cursor behind the new node
                cursorPos.SetPos(node, XmlCursorPositions.CursorBehindTheNode);
            }
            else
            {
                if (xmlRules.HasEndTag(node))
                {
                    // Place cursor in the new node
                    cursorPos.SetPos(node, XmlCursorPositions.CursorInsideTheEmptyNode);
                }
                else
                {
                    // Place cursor behind the new node
                    cursorPos.SetPos(node, XmlCursorPositions.CursorBehindTheNode);
                }
            }
            return(true);
        }
        /// <summary>
        /// Inserts the specified text at the current cursor position, if possible
        /// </summary>
        internal static InsertTextResult InsertText(XmlCursorPos cursorPos, string rawText, XmlRules xmlRules)
        {
            // Revise the entered text in preprocessing if necessary.
            // In an AIML 1.1 DTD this can mean, for example, that the text for insertion into the PATTERN tag is changed to upper case
            string text = xmlRules.InsertTextTextPreProcessing(rawText, cursorPos, out System.Xml.XmlNode replacementNode);

            if (replacementNode != null)
            {
                // If the entered text has become a node instead, e.g. with AIML,
                // if you press * in a template and then want to insert a <star> instead
                return(new InsertTextResult {
                    ReplaceNode = replacementNode
                });
            }

            switch (cursorPos.PosOnNode)
            {
            case XmlCursorPositions.CursorOnNodeStartTag:
            case XmlCursorPositions.CursorOnNodeEndTag:
                // First check whether this node may be replaced by a text node
                if (xmlRules.IsThisTagAllowedAtThisPos("#PCDATA", cursorPos))
                {
                    // The selected node by a newly created text node
                    System.Xml.XmlText neuerTextNode = cursorPos.ActualNode.OwnerDocument.CreateTextNode(text);
                    cursorPos.ActualNode.ParentNode.ReplaceChild(cursorPos.ActualNode, neuerTextNode);
                    cursorPos.SetPos(neuerTextNode, XmlCursorPositions.CursorBehindTheNode);
                }
                throw new ApplicationException(String.Format("InsertText: unknown CursorPos {0}", cursorPos.PosOnNode));

            case XmlCursorPositions.CursorBehindTheNode:
                InsertTextBetweenTwoNodes(cursorPos, cursorPos.ActualNode, cursorPos.ActualNode.NextSibling, text, xmlRules);
                break;

            case XmlCursorPositions.CursorInFrontOfNode:
                InsertTextBetweenTwoNodes(cursorPos, cursorPos.ActualNode.PreviousSibling, cursorPos.ActualNode, text, xmlRules);
                break;

            case XmlCursorPositions.CursorInsideTheEmptyNode:
                // First check if text is allowed inside the empty node
                if (xmlRules.IsThisTagAllowedAtThisPos("#PCDATA", cursorPos))
                {
                    // Then create a text node within the empty node with the desired text content
                    System.Xml.XmlText newTextNode = cursorPos.ActualNode.OwnerDocument.CreateTextNode(text);
                    cursorPos.ActualNode.AppendChild(newTextNode);
                    cursorPos.SetPos(newTextNode, XmlCursorPositions.CursorBehindTheNode);
                }
                else
                {
                    // Error BEEEEP!
                }
                break;

            case XmlCursorPositions.CursorInsideTextNode:
                string textBeforeNode  = cursorPos.ActualNode.InnerText.Substring(0, cursorPos.PosInTextNode);
                string textAfterCursor = cursorPos.ActualNode.InnerText.Substring(cursorPos.PosInTextNode, cursorPos.ActualNode.InnerText.Length - cursorPos.PosInTextNode);
                // Insert the character of the pressed keys after the cursor
                cursorPos.ActualNode.InnerText = $"{textBeforeNode}{text}{textAfterCursor}";
                cursorPos.SetPos(cursorPos.ActualNode, cursorPos.PosOnNode, cursorPos.PosInTextNode + text.Length);
                break;

            default:
                throw new ApplicationException(String.Format("InsertText: unknown CursorPos {0}", cursorPos.PosOnNode));
            }
            return(new InsertTextResult {
                ReplaceNode = replacementNode
            });
        }