diff --git a/MultiTerm.Core/Model/MultiFormatString.cs b/MultiTerm.Core/Model/MultiFormatString.cs index 630a59b..829b568 100644 --- a/MultiTerm.Core/Model/MultiFormatString.cs +++ b/MultiTerm.Core/Model/MultiFormatString.cs @@ -197,14 +197,15 @@ public class MultiFormatString: ObservableCollection } } - #region ToString + #region GetBytes, Conversions /// - /// Converts the to a string that only contains Unicode (UTF-16) characters. + /// Converts this to a byte array. Associated bundles of are converted to bytes. + /// Characters of Format are are ASCII Encoded. /// - /// converted string - public override string ToString() // TODO Implement Unit Tests! + /// byte array of this + public byte[] GetBytes() { - StringBuilder stringBuilder = new(); + List bytes = new(); string hexConversionCharacters = String.Empty, binaryConversionCharacters = String.Empty; // Internal function to check and finalize a Hexadecimal Conversion @@ -219,8 +220,8 @@ public class MultiFormatString: ObservableCollection formattedChar.Format != FormatType.Hexadecimal || hexConversionCharacters.Length == charsPerHexBundle) { - // finalize conversion - stringBuilder.Append(FromHexString(hexConversionCharacters)); + // finalize conversion, expects one byte only => therefore use first + bytes.Add(GetBytesFromString(hexConversionCharacters, 16, charsPerHexBundle).First()); // reset ongoing conversion hexConversionCharacters = String.Empty; } @@ -239,19 +240,18 @@ public class MultiFormatString: ObservableCollection formattedChar.Format != FormatType.Binary || binaryConversionCharacters.Length == charsPerBinBundle) { - // finalize conversion - stringBuilder.Append(FromBinaryString(binaryConversionCharacters)); + // finalize conversion, expects one byte only => therefore use first + bytes.Add(GetBytesFromString(hexConversionCharacters, 2, charsPerBinBundle).First()); // reset ongoing conversion binaryConversionCharacters = String.Empty; } } } - // go through every character - foreach (var character in this.Items) + foreach (IFormattedCharacter character in this.Items) { - // can only handle FormattedCharacters - if (character is not FormattedCharacter formattedCharacter) { continue; } + // skip all characters that are not of type FormattedCharacter + if(character is not FormattedCharacter formattedCharacter) { continue; } // finalize ongoing conversions if there are any finalizeHexConversion(formattedCharacter); @@ -260,7 +260,7 @@ public class MultiFormatString: ObservableCollection switch (formattedCharacter.Format) // switch by format { case FormatType.Character: - stringBuilder.Append(formattedCharacter.Character); + bytes.Add(Encoding.ASCII.GetBytes(formattedCharacter.Character).First()); break; case FormatType.Hexadecimal: @@ -272,44 +272,74 @@ public class MultiFormatString: ObservableCollection break; default: - throw new NotImplementedException($"'{nameof(ToString)}()' does not implement conversion for format {formattedCharacter.Format}"); + throw new NotImplementedException($"'{nameof(GetBytes)}()' does not implement conversion for format {formattedCharacter.Format}"); } + } // fully finalize after the loop, finish all unfinished conversions finalizeHexConversion(null); finalizeBinaryConversion(null); - return stringBuilder.ToString(); + return bytes.ToArray(); } - // from: https://stackoverflow.com/questions/724862/converting-from-hex-to-string - // test with: returns: "Hello world" for "48656C6C6F20776F726C64" - // see https://www.fileformat.info/info/charset/UTF-16/list.htm - private static string FromHexString(string hexString) + // TODO Implement Unit Tests! + /// + /// Converts the to a string that only contains ASCII characters. + /// Therefore this method converts all parts of other formats (e.g. or ) to ASCII characters. + /// + /// converted string + public string ToAsciiEncodedString() { - // Added PadLeft so strings with one character do not get ignored - string internalHexString = hexString.PadLeft(charsPerHexBundle, '0'); - var bytes = new byte[internalHexString.Length / 2]; - for (var i = 0; i < bytes.Length; i++) - { - bytes[i] = Convert.ToByte(internalHexString.Substring(i * 2, 2), 16); - } - - return Encoding.ASCII.GetString(bytes); + return Encoding.ASCII.GetString(this.GetBytes()); } - private static string FromBinaryString(string binaryString) + // TODO Unit Tests + /// + /// Converts an input string to a bytes array with the given base () and bundle size (). + /// Input string gets padded with zeroes left. + /// Idea from: https://stackoverflow.com/questions/724862/converting-from-hex-to-string + /// + /// input string, without separators between individual bytes! + /// number base of the input string (e.g. 2 for binary) + /// amount of characters required for a complete byte in the format (e.g. 8 for a binary byte) + /// byte array of the given input string + private static byte[] GetBytesFromString(string str, int fromBase, int byteWidth) { - string internalBinaryString = binaryString.PadLeft(charsPerBinBundle, '0'); - var bytes = new byte[internalBinaryString.Length / 8]; + // Added left padded zeroes (total width + spill) so the block size matches the number base + string paddedString = str.PadLeft((str.Length + (str.Length % byteWidth)), '0'); + // creates bytes array of correct size + var bytes = new byte[paddedString.Length / byteWidth]; + // convert every part of the string to for (var i = 0; i < bytes.Length; i++) { - bytes[i] = Convert.ToByte(internalBinaryString.Substring(i * 8, 8), 2); + bytes[i] = Convert.ToByte(paddedString.Substring(i * byteWidth, byteWidth), fromBase); } + return bytes; + } + + /// + /// Returns ASCII encoded string from a hex string of amount of hexadecimal characters. + /// + /// hex string with maximum amount of hex characters according to . hex characters must be without any separator (no : or - inbetween bytes)! + /// ASCII Encoded string of the + private static string GetAsciiEncodedStringFromHexString(string hexString) + { + var bytes = GetBytesFromString(hexString, 16, charsPerHexBundle); return Encoding.ASCII.GetString(bytes); } + /// + /// Returns ASCII encoded string from a binary string of amount of binary characters. + /// + /// hex string with maximum amount of binaryString characters according to . binary characters must be without any separator (no space or - inbetween bytes)! + /// ASCII Encoded string of the + private static string GetAsciiEncodedStringFromBinaryString(string binaryString) + { + var bytes = GetBytesFromString(binaryString, 2, charsPerBinBundle); + return Encoding.ASCII.GetString(bytes); + } #endregion } diff --git a/MultiTerm.Core/ViewModel/SendReceiveViewModel.cs b/MultiTerm.Core/ViewModel/SendReceiveViewModel.cs index f92b123..4a37290 100644 --- a/MultiTerm.Core/ViewModel/SendReceiveViewModel.cs +++ b/MultiTerm.Core/ViewModel/SendReceiveViewModel.cs @@ -40,9 +40,9 @@ public partial class SendReceiveViewModel : TerminalViewModel // Temp //var items = this.CommunicationData.SelectedReceivedData; //Debugger.Break(); - this.TempSentDataString = this.SendableData.ToString(); + this.TempSentDataString = this.SendableData.ToAsciiEncodedString(); // send data - this.SendToCommunicationProtocol(this.SendableData.ToString()); + this.SendToCommunicationProtocol(this.SendableData.GetBytes()); // clear textbox this.SendableData.Clear(); diff --git a/MultiTerm.Core/ViewModel/TerminalViewModel.cs b/MultiTerm.Core/ViewModel/TerminalViewModel.cs index 0942c45..105fa0f 100644 --- a/MultiTerm.Core/ViewModel/TerminalViewModel.cs +++ b/MultiTerm.Core/ViewModel/TerminalViewModel.cs @@ -151,11 +151,11 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie #endregion /// - /// Sends the given string to this instances , UTF-16 encoded. + /// Sends the given data to this instances . /// Appends configured to the end of string. /// - /// data string - protected void SendToCommunicationProtocol(string data) + /// data in form of enumerable (e.g. array) + protected void SendToCommunicationProtocol(IEnumerable data) { // guard null values if(this.CommunicationProtocol == null) { throw new NullReferenceException($"'{nameof(SendToCommunicationProtocol)}()' was called but {nameof(CommunicationProtocol)} is null."); } @@ -168,22 +168,21 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie } // add newline sequence to end of data - string newlineSequence = this.SendNewlineSeparatorType switch + byte[] newlineSequence = this.SendNewlineSeparatorType switch { - NewlineSeparatorType.None => String.Empty, - NewlineSeparatorType.CR => "\r", - NewlineSeparatorType.LF => "\n", - NewlineSeparatorType.CR_LF => "\r\n", + NewlineSeparatorType.None => Array.Empty(), + NewlineSeparatorType.CR => new byte[] { (byte)'\r' }, + NewlineSeparatorType.LF => new byte[] { (byte)'\n' }, + NewlineSeparatorType.CR_LF => new byte[] { (byte)'\r', (byte)'\n' }, _ => throw new NotImplementedException($"'{nameof(SendToCommunicationProtocol)}()' " + $"does not implement handling for {nameof(NewlineSeparatorType)} {this.SendNewlineSeparatorType}.") }; - string modifiedData = data + newlineSequence; - // extract unicode byte array - var byteArray = Encoding.Unicode.GetBytes(modifiedData); + // join data and newline sequence + var dataWithNewlineSequence = data.Concat(newlineSequence); // send - this.CommunicationProtocol.SendBytes(byteArray); + this.CommunicationProtocol.SendBytes(dataWithNewlineSequence.ToArray()); } #region Protocol Settings diff --git a/MultiTerm.Wpf.CustomControl/MultiFormatTextBox/MultiFormatTextBox.cs b/MultiTerm.Wpf.CustomControl/MultiFormatTextBox/MultiFormatTextBox.cs index 6fd75a1..e8880ac 100644 --- a/MultiTerm.Wpf.CustomControl/MultiFormatTextBox/MultiFormatTextBox.cs +++ b/MultiTerm.Wpf.CustomControl/MultiFormatTextBox/MultiFormatTextBox.cs @@ -217,13 +217,18 @@ public class MultiFormatTextBox : Control } // if this is the first element to enter in the paragraph => simply add it - if(paragraph!.Inlines.Count == 0) + if (paragraph!.Inlines.Count == 0) { paragraph.Inlines.Add(run); } + // if the previous item is outside of the range => insert at first position + else if(indexCounter - 1 < 0) + { + paragraph.Inlines.InsertBefore(paragraph.Inlines.FirstInline, run); + } else // add to paragraph of richtextbox after previous inline { - var previousInline = paragraph.Inlines.ElementAt(new Index(indexCounter-1)); // paragraph.Inlines = zero based counting + var previousInline = paragraph.Inlines.ElementAt(new Index(indexCounter - 1)); // paragraph.Inlines = zero based counting paragraph.Inlines.InsertAfter(previousInline, run); }