Skip to content

Commit aa6cb0e

Browse files
committed
Add xml tag autocomplete
1 parent d216d75 commit aa6cb0e

File tree

1 file changed

+160
-1
lines changed

1 file changed

+160
-1
lines changed

StructuredXmlEditor/Tools/DataTransformerTool.cs

Lines changed: 160 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
using StructuredXmlEditor.View;
77
using System;
88
using System.Collections.Generic;
9+
using System.Diagnostics;
910
using System.IO;
1011
using System.Linq;
1112
using System.Reflection;
1213
using System.Text;
1314
using System.Threading.Tasks;
15+
using System.Windows.Input;
1416
using System.Windows.Media;
1517
using System.Xml;
1618
using System.Xml.Linq;
@@ -93,8 +95,10 @@ public DataTransformerTool(Workspace workspace) : base(workspace, "Data Transfor
9395
TextEditor.Text = "{el||}";
9496
TextEditor.Foreground = new SolidColorBrush(Color.FromRgb(255, 255, 255));
9597
TextEditor.Background = Brushes.Transparent;
96-
TextEditor.Document.TextChanged += (e, args) =>
98+
TextEditor.TextArea.TextEntered += (e, args) =>
9799
{
100+
Autocomplete(e, args);
101+
98102
UpdatePreview();
99103
};
100104

@@ -226,6 +230,161 @@ public void SaveAll()
226230
}
227231
Return();
228232
}
233+
234+
//-----------------------------------------------------------------------
235+
public void Autocomplete(object sender, TextCompositionEventArgs e)
236+
{
237+
if (e.Text == ">")
238+
{
239+
//auto-insert closing element
240+
int offset = TextEditor.CaretOffset;
241+
string s = GetElementAtCursor(TextEditor.Text, offset - 1);
242+
if (!string.IsNullOrWhiteSpace(s) && "!--" != s)
243+
{
244+
if (!IsClosingElement(TextEditor.Text, offset - 1, s))
245+
{
246+
string endElement = "</" + s + ">";
247+
var rightOfCursor = TextEditor.Text.Substring(offset, Math.Max(0, Math.Min(endElement.Length + 50, TextEditor.Text.Length) - offset - 1)).TrimStart();
248+
if (!rightOfCursor.StartsWith(endElement))
249+
{
250+
TextEditor.TextArea.Document.Insert(offset, endElement);
251+
TextEditor.CaretOffset = offset;
252+
}
253+
}
254+
}
255+
}
256+
else if (e.Text == "/")
257+
{
258+
int offset = TextEditor.CaretOffset;
259+
if (TextEditor.Text.Length > offset + 2 && TextEditor.Text[offset] == '>')
260+
{
261+
//remove closing tag if exist
262+
string s = GetElementAtCursor(TextEditor.Text, offset - 1);
263+
if (!string.IsNullOrWhiteSpace(s))
264+
{
265+
//search closing end tag. Element must be empty (whitespace allowed)
266+
//"<hallo> </hallo>" --> enter '/' --> "<hallo/> "
267+
string expectedEndTag = "</" + s + ">";
268+
for (int i = offset + 1; i < TextEditor.Text.Length - expectedEndTag.Length + 1; i++)
269+
{
270+
if (!char.IsWhiteSpace(TextEditor.Text[i]))
271+
{
272+
if (TextEditor.Text.Substring(i, expectedEndTag.Length) == expectedEndTag)
273+
{
274+
//remove already existing endTag
275+
TextEditor.Document.Remove(i, expectedEndTag.Length);
276+
}
277+
break;
278+
}
279+
}
280+
}
281+
}
282+
}
283+
else if (e.Text == "{")
284+
{
285+
int offset = TextEditor.CaretOffset;
286+
TextEditor.TextArea.Document.Insert(offset, "el||}");
287+
TextEditor.CaretOffset = offset+3;
288+
}
289+
}
290+
291+
//-----------------------------------------------------------------------
292+
/// <summary>
293+
/// Source: https://xsemmel.codeplex.com
294+
/// </summary>
295+
/// <param name="xml"></param>
296+
/// <param name="offset"></param>
297+
/// <returns></returns>
298+
public static string GetElementAtCursor(string xml, int offset)
299+
{
300+
if (offset == xml.Length)
301+
{
302+
offset--;
303+
}
304+
int startIdx = xml.LastIndexOf('<', offset);
305+
if (startIdx < 0) return null;
306+
307+
if (startIdx < xml.Length && xml[startIdx + 1] == '/')
308+
{
309+
startIdx = startIdx + 1;
310+
}
311+
312+
int endIdx1 = xml.IndexOf(' ', startIdx);
313+
if (endIdx1 == -1 /*|| endIdx1 > offset*/) endIdx1 = int.MaxValue;
314+
315+
int endIdx2 = xml.IndexOf('>', startIdx);
316+
if (endIdx2 == -1 /*|| endIdx2 > offset*/)
317+
{
318+
endIdx2 = int.MaxValue;
319+
}
320+
else
321+
{
322+
if (endIdx2 < xml.Length && xml[endIdx2 - 1] == '/')
323+
{
324+
endIdx2 = endIdx2 - 1;
325+
}
326+
}
327+
328+
int endIdx = Math.Min(endIdx1, endIdx2);
329+
if (endIdx2 > 0 && endIdx2 < int.MaxValue && endIdx > startIdx)
330+
{
331+
return xml.Substring(startIdx + 1, endIdx - startIdx - 1);
332+
}
333+
else
334+
{
335+
return null;
336+
}
337+
}
338+
339+
//-----------------------------------------------------------------------
340+
/// <summary>
341+
/// Source: https://xsemmel.codeplex.com
342+
/// Liefert true falls das Element beim offset ein schließendes Element ist,
343+
/// also &lt;/x&gt; oder &lt;x/&gt;
344+
/// </summary>
345+
/// <param name="xml"></param>
346+
/// <param name="offset"></param>
347+
/// <param name="elementName">optional, elementName = GetElementAtCursor(xml, offset)</param>
348+
/// <returns></returns>
349+
public static bool IsClosingElement(string xml, int offset, string elementName = null)
350+
{
351+
if (elementName == null)
352+
{
353+
elementName = GetElementAtCursor(xml, offset);
354+
}
355+
else
356+
{
357+
Debug.Assert(GetElementAtCursor(xml, offset) == elementName);
358+
}
359+
360+
if (offset >= xml.Length || offset < 0)
361+
{
362+
return false;
363+
}
364+
int idxOpen = xml.LastIndexOf('<', offset);
365+
if (idxOpen < 0)
366+
{
367+
return false;
368+
}
369+
370+
int idxClose = xml.LastIndexOf('>', offset);
371+
if (idxClose > 0)
372+
{
373+
if (idxClose > idxOpen && idxClose < offset - 1)
374+
{
375+
return false;
376+
}
377+
}
378+
379+
string prefix = xml.Substring(idxOpen, offset - idxOpen);
380+
if (prefix.Contains("/"))
381+
{
382+
return true;
383+
}
384+
385+
386+
return false;
387+
}
229388
}
230389

231390
//-----------------------------------------------------------------------

0 commit comments

Comments
 (0)