/// <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!"); } } } }
/// <summary> /// Defines which XML elements may be inserted at this position /// </summary> /// <param name="alsoListPpcData">if true, PCDATA is also listed as a node, if it is allowed</param> /// <returns>A list of the node names. Zero means, no elements are allowed. /// If the content is "", then the element must be entered freely </returns> public virtual string[] AllowedInsertElements(XmlCursorPos targetPos, bool alsoListPpcData, bool alsoListComments) { #warning evtl. Optimierungs-TODO: // Wahrscheinlich (allein schon durch die Nutzung von IstDiesesTagAnDieserStelleErlaubt() etc.) // wird diese Liste oft hintereinander identisch neu erzeugt. Es macht daher Sinn, wenn der // das letzte Ergebnis hier ggf. gebuffert würde. Dabei sollte aber ausgeschlossen werden, dass // sich der XML-Inhalt in der Zwischenzeit geändert hat! if (targetPos.ActualNode == null) { return new string[] { } } ; // If nothing is selected, nothing is allowed if (this.Dtd == null) { return new string[] { string.Empty } } ; // Free input allowed if (dtdNodeEditChecker == null) { dtdNodeEditChecker = new DtdNodeEditCheck(this.Dtd); } return(dtdNodeEditChecker.AtThisPosAllowedTags(targetPos, alsoListPpcData, alsoListComments)); }
/// <summary> /// Checks all test patterns for validity within the scope of the read DTD /// </summary> private void CheckAllTestPattern(List <DtdTestpattern> allPattern, XmlCursorPos cursorPos) { var node = cursorPos.ActualNode; DtdElement element_; if (cursorPos.PosOnNode == XmlCursorPositions.CursorInsideTheEmptyNode) { // Get the DTD element for the node of the cursor element_ = dtd.DTDElementByName(Dtd.GetElementNameFromNode(node), false); } else { if ((node.OwnerDocument == null) || (node.OwnerDocument.DocumentElement == null)) { Debug.Assert(false, "Beep!"); return; } else { if (node == node.OwnerDocument.DocumentElement) // The node is the root element { // Only the root element is allowed in place of the root element foreach (DtdTestpattern muster in allPattern) { if (muster.ElementName == node.Name) // if it is the root element { muster.Success = true; // Only the root element is allowed at the position of the root element } } return; } else // The node is not the root element { // Get the DTD element for the parent node of the cursor element_ = dtd.DTDElementByName(Dtd.GetElementNameFromNode(node.ParentNode), false); } } } // Check whether the current DTD run has led to one of the searched test patterns foreach (DtdTestpattern muster in allPattern) // run through all samples to be tested { if (element_ == null) { muster.Success = false; // This element is not known at all } else { if (!muster.Success) { #if DEBUGTRACE Trace.WriteLine(String.Format("Check für neues Ziel-Muster {0} > {1}", ElementName(muster.Element), muster.Zusammenfassung_)); #endif muster.Success = FitsPatternInElement(muster, element_); } } } }
/// <summary> /// Which nodes are allowed in XML at this point? /// </summary> public string[] AtThisPosAllowedTags(XmlCursorPos cursorPosToCheck, bool allowPcDATA, bool allowComments) { // To avoid accidentally returning some changes, first clone the CursorPos var cursorPos = cursorPosToCheck.Clone(); #if ThinkLogging _thinkLog = new StringBuilder(); #endif var testPattern = this.GetAllTestPattern(cursorPos); // Write elements of valid test patterns into the result var result = new List <string>(); foreach (var muster in testPattern) { if (muster.Success) { if (muster.ElementName == null) { result.Add(""); // the existing element may be deleted } else { switch (muster.ElementName.ToLower()) { case "#pcdata": if (allowPcDATA) { result.Add(muster.ElementName); // This element may be inserted } break; case "#comment": if (allowComments) { result.Add(muster.ElementName); // This element may be inserted } break; default: result.Add(muster.ElementName); // This element may be inserted break; } } } #if ThinkLogging _thinkLog.Append(muster.Summary + "\r\n"); #endif } return(result.ToArray()); }
/// <summary> /// Determines which elements are allowed at this position /// </summary> public override string[] AllowedInsertElements(XmlCursorPos targetPos, bool listPcDataToo, bool listCommentsToo) { if (targetPos.ActualNode != null) { if (targetPos.ActualNode.Name.ToLower() == "category") { // No other tags are offered as alternatives instead of the category tag. // Otherwise, the tags META and TOPIC would be displayed when editing the category, since they would be allowed at this point according to the DTD. return(new string[] { }); } } return(base.AllowedInsertElements(targetPos, listPcDataToo, listCommentsToo)); }
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> /// Delete the node or the character behind the cursor /// </summary> public async Task <bool> ActionDeleteNodeOrSignBehindCursorPos(XmlCursorPos position, SetUndoSnapshotOptions setUnDoSnapshot) { if (!this.ActionsAllowed) { return(false); } if (setUnDoSnapshot == SetUndoSnapshotOptions.Yes) { this.editorState.UndoHandler.SetSnapshot("delete", this.editorState.CursorRaw); } var deleteArea = new XmlCursor(); deleteArea.StartPos.SetPos(position.ActualNode, position.PosOnNode, position.PosInTextNode); var endPos = deleteArea.StartPos.Clone(); await CursorPosMoveHelper.MoveRight(endPos, this.editorState.RootNode, this.xmlRules); deleteArea.EndPos.SetPos(endPos.ActualNode, endPos.PosOnNode, endPos.PosInTextNode); await deleteArea.OptimizeSelection(); if (deleteArea.StartPos.ActualNode == this.editorState.RootNode) { return(false); // You must not delete the rootnot } var deleteResult = await XmlCursorSelectionHelper.DeleteSelection(deleteArea); if (deleteResult.Success) { // After successful deletion the new CursorPos is retrieved here await this.editorState.CursorRaw.SetPositions(deleteResult.NewCursorPosAfterDelete.ActualNode, deleteResult.NewCursorPosAfterDelete.PosOnNode, deleteResult.NewCursorPosAfterDelete.PosInTextNode, throwChangedEventWhenValuesChanged : false); await this.editorState.FireContentChangedEvent(needToSetFocusOnEditorWhenLost : false, forceFullRepaint : false); return(true); } return(false); }
public XmlCursor() { this.EndPos = new XmlCursorPos(); this.StartPos = new XmlCursorPos(); this.ChangedEvent = new XmlAsyncEvent <EventArgs>(); }
public async Task <bool> MoveLeft(XmlCursorPos cursorPos) { return(await CursorPosMoveHelper.MoveLeft(cursorPos, this.editorContext.EditorState.RootNode, this.editorContext.XmlRules)); }
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); }
/// <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); } }
/// <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)); } } }
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); }
/// <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 }); }
/// <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> /// Adds a test pattern /// </summary> private DtdTestpattern CreateTestPattern(string elementName, XmlCursorPos cursorPos) { DtdTestpattern testPattern; var node = cursorPos.ActualNode; System.Xml.XmlNode sibling; switch (cursorPos.PosOnNode) { case XmlCursorPositions.CursorInsideTheEmptyNode: // The parent node is empty, so we only have to test for the allowed elements in it and not expect any sibling elements on the same level testPattern = new DtdTestpattern(elementName, Dtd.GetElementNameFromNode(node)); testPattern.AddElement(elementName); break; default: // If the parent node is the XML document itself, then abort here if (node.ParentNode is System.Xml.XmlDocument) { throw new ApplicationException("No test pattern can be created for the root element. Its validity must be guaranteed by comparison with the DTD root element."); } testPattern = new DtdTestpattern(elementName, Dtd.GetElementNameFromNode(node.ParentNode)); // Traverse all elements within the parent element sibling = node.ParentNode.FirstChild; while (sibling != null) { if (sibling is System.Xml.XmlWhitespace) { // Whitespace tags can be ignored during the check } else { if (sibling == node) // at this point the node must be inserted { if (sibling is System.Xml.XmlComment) { testPattern.AddElement("#COMMENT"); } else { if (this.dtd.DTDElementByName(Dtd.GetElementNameFromNode(node), false) == null) { // This element is not known at all, therefore the element is sometimes not included // throw new ApplicationException(String.Format("unknown Node-Element '{0}'", DTD.GetElementNameFromNode(node))); } else { switch (cursorPos.PosOnNode) { case XmlCursorPositions.CursorOnNodeStartTag: // If the node itself is selected and should be replaced case XmlCursorPositions.CursorOnNodeEndTag: if (elementName == null) // check delete { // Omit element } else // check insert/replace { // Instead of the element present at this position, the element to be tested is inserted here testPattern.AddElement(elementName); } break; case XmlCursorPositions.CursorBehindTheNode: if (elementName == null) // check delete { throw new ApplicationException("CreateTestPattern: Delete must not be checked for XmlCursorPositions.CursorBehindTheNode!"); } else { // element is inserted behind the element to be tested testPattern.AddElement(Dtd.GetElementNameFromNode(node)); testPattern.AddElement(elementName); } break; case XmlCursorPositions.CursorInsideTheEmptyNode: if (elementName == null) // check delete { throw new ApplicationException("CreateTestPattern: Delete must not be checked for XmlCursorPositions.CursorInsideTheEmptyNode!"); } else { throw new ApplicationException("CreateTestPattern: CursorInsideTheEmptyNode can´t be handled at this place!"); } case XmlCursorPositions.CursorInFrontOfNode: if (elementName == null) // check delete { throw new ApplicationException("CreateTestPattern: Delete must not be checked for XmlCursorPositions.CursorInFrontOfNode!"); } else { // Element is inserted before the element to be tested testPattern.AddElement(elementName); testPattern.AddElement(Dtd.GetElementNameFromNode(node)); } break; case XmlCursorPositions.CursorInsideTextNode: if (elementName == null) // check delete { throw new ApplicationException("CreateTestPattern: Delete must not be checked for XmlCursorPositions.CursorInsideTextNode!"); } else { if (Dtd.GetElementNameFromNode(node) != "#PCDATA") { throw new ApplicationException("CreateTestPattern: CursorInsideTextNode, but node.name=" + Dtd.GetElementNameFromNode(node)); } else { // The element to be tested is placed between two text nodes testPattern.AddElement("#PCDATA"); testPattern.AddElement(elementName); testPattern.AddElement("#PCDATA"); } } break; default: throw new ApplicationException("Unknown XmlCursorPositions value:" + cursorPos.PosOnNode); } } } } else // just continue enumerating the elements as usual { testPattern.AddElement(Dtd.GetElementNameFromNode(sibling)); } } sibling = sibling.NextSibling; // to the next element } break; } return(testPattern); }
/// <summary> /// Generates all test patterns including the results whether they are allowed /// </summary> private List <DtdTestpattern> GetAllTestPattern(XmlCursorPos cursorPos) { var patternToTest = new List <DtdTestpattern>(); DtdTestpattern singlePattern; if (cursorPos.ActualNode == null) { // How to check what is allowed for a non-existent node? throw new ApplicationException("GetAllTestPattern: cursorPos.AktNode=NULL!"); } // Check deletion (register deletion test pattern) switch (cursorPos.PosOnNode) { case XmlCursorPositions.CursorInsideTheEmptyNode: case XmlCursorPositions.CursorInFrontOfNode: case XmlCursorPositions.CursorBehindTheNode: case XmlCursorPositions.CursorInsideTextNode: // Here no deletion must be tested, because no node is selected break; case XmlCursorPositions.CursorOnNodeStartTag: case XmlCursorPositions.CursorOnNodeEndTag: // Provide delete pattern for testing if the selected node can be deleted //einMuster = CreateTestMuster(null,cursorPos); //zuTestendeMuster.Add(einMuster); break; default: throw new ApplicationException(String.Format("unknown cursorPos.StartPos.PosAmNode '{0}' detected.", cursorPos.PosOnNode)); } if (cursorPos.ActualNode is System.Xml.XmlComment) { // No tags can be inserted in a comment } else // Is no comment { string[] childrenAllowedAtThisPos; if (cursorPos.PosOnNode == XmlCursorPositions.CursorInsideTheEmptyNode) { // All children of this node are allowed in the node var element = dtd.DTDElementByName(cursorPos.ActualNode.Name, false); if (element == null) { // An element with this name is not known childrenAllowedAtThisPos = new string[] { }; } else { childrenAllowedAtThisPos = element.AllChildNamesAllowedAsDirectChild; } } else { // Which elements are *next to* the element allowed? if (cursorPos.ActualNode.OwnerDocument == null) { // The actual node does not hang in any document? Hm, maybe we are in the middle of an insert process... #warning Insert another correct message or sound Debug.Assert(false, "Beep!"); childrenAllowedAtThisPos = new string[] { }; } else { if (cursorPos.ActualNode == cursorPos.ActualNode.OwnerDocument.DocumentElement) { // This node is the document tag itself. This is exclusive on the root, so there can be no other elements besides it childrenAllowedAtThisPos = new string[] { }; } else { // All children of the parent are allowed next to or in the place of the node. // First find out which is the parent element of the node to check for var parentElement = dtd.DTDElementByName(cursorPos.ActualNode.ParentNode.Name, false); if (parentElement == null) { // An element with this name is not known childrenAllowedAtThisPos = new string[] { }; } else { childrenAllowedAtThisPos = parentElement.AllChildNamesAllowedAsDirectChild; } } } } // Create test patterns to insert for all allowed elements foreach (string elementName in childrenAllowedAtThisPos) { //if (element.Name =="pattern") // || element.Name =="template") //if (element.Name == "#PCDATA") //if (element.Name == "meta") { singlePattern = CreateTestPattern(elementName, cursorPos); patternToTest.Add(singlePattern); } } } // check all collected test samples for validity this.CheckAllTestPattern(patternToTest, cursorPos); return(patternToTest); }
/// <summary> /// Converts / formats a text which is to be inserted into a specific location as required by that location. /// In an AIML DTD, for example, this can mean that the text is converted to uppercase for insertion into the PATTERN tag. /// </summary> /// <param name="replacementNode">If a node is to be inserted instead of the text. Example: In the AIML template we press *, then a STAR tag is inserted.</param> public override string InsertTextTextPreProcessing(string insertText, XmlCursorPos insertWhere, out XmlNode replacementNode) { XmlNode node; if (insertWhere.ActualNode is XmlText) { // Pos id a Textnode // Node is the parent of the text node node = insertWhere.ActualNode.ParentNode; } else { // Pos is not a Textnode // The Pos itself is the node node = insertWhere.ActualNode; } string ausgabe; // In certain places when pressing * use SRAI instead of it if (insertText == "*") { switch (node.Name) { case "pattern": case "that": case "script": // Here is the normal star allowed break; default: replacementNode = insertWhere.ActualNode.OwnerDocument.CreateElement("star"); return(""); // Empty the insert-text, because it was already returned as a star-node } } // Allow / filter out different inputs depending on the node switch (node.Name) { case "srai": // In Srai tag always capital letters and no special characters ausgabe = insertText; ausgabe = ausgabe.Replace("*", ""); // No * allowed in SRAI ausgabe = ausgabe.Replace("_", ""); // No _ allowed in SRAI replacementNode = null !; return(ausgabe); case "pattern": // In the pattern tag always capital letters and no special characters StringBuilder sauber = new StringBuilder(insertText); // einfuegeText.ToUpper()); // Already write out "german Umlaute" when entering sauber.Replace("Ä", "AE"); sauber.Replace("Ö", "OE"); sauber.Replace("Ü", "UE"); sauber.Replace("ß", "SS"); // convert to a char array char[] tempArray = sauber.ToString().ToCharArray(); ArrayList doneCharacters = new ArrayList(); // iterate through the char array for (int i = 0; i < tempArray.Length; i++) { if (((tempArray[i] == '*') || (tempArray[i] == '_')) && (node.Name == "pattern")) { doneCharacters.Add((char)tempArray[i]); // * and _ are only allowed in patterns, not in SRAI } else { // check its a valid character... // valid in this case means: // " "(space), "0-9", "a-z" and "A-Z" if ((tempArray[i] > 64) & (tempArray[i] < 91) || // A-Z (tempArray[i] > 96) & (tempArray[i] < 123) || // a-z (tempArray[i] > 47) & (tempArray[i] < 58) || // 0-9 (tempArray[i] == 32)) // space { doneCharacters.Add((char)tempArray[i]); } } } // turn the arraylist into a char array char[] done = new char[doneCharacters.Count]; for (int i = 0; i < doneCharacters.Count; i++) { done[i] = (char)doneCharacters[i]; } ausgabe = new string(done); // et voila! replacementNode = null; return(ausgabe); default: return(base.InsertTextTextPreProcessing(insertText, insertWhere, out replacementNode)); } }
/// <summary> /// Returns if the specified tag is allowed at this position /// </summary> public bool IsThisTagAllowedAtThisPos(string tagName, XmlCursorPos targetPos) { return(this.AllowedInsertElements(targetPos, true, true).Contains(tagName)); }
/// <summary> /// Converts / formats text to be inserted in a specific location as required by that location. /// In an AIML DTD, for example, this can mean that the text is converted to upper case for insertion into the PATTERN tag. /// </summary> /// <param name="replacementNode">If a node is to be inserted instead of the text. Example: In the AIML-Template we press *, then a STAR-Tag is inserted</param> public virtual string InsertTextTextPreProcessing(string textToInsert, XmlCursorPos insertWhere, out System.Xml.XmlNode replacementNode) { replacementNode = null; return(textToInsert); // In the standard form the text always goes through }