From eac55765c2479006dc0ff5e4c32b00a69c386089 Mon Sep 17 00:00:00 2001 From: Jonas Arnold Date: Tue, 2 May 2023 19:21:55 +0200 Subject: [PATCH] fixed many bugs in MultiFormatString, cleaned up IFormattedCharacter and subclasses, --- MultiTerm.Core/Model/FormattedCharacter.cs | 4 + MultiTerm.Core/Model/IFormattedCharacter.cs | 5 +- MultiTerm.Core/Model/MultiFormatString.cs | 299 ++++++++++-------- MultiTerm.Core/Model/SpacingCharacter.cs | 2 +- .../MultiFormatTextBox/MultiFormatTextBox.cs | 14 +- 5 files changed, 183 insertions(+), 141 deletions(-) diff --git a/MultiTerm.Core/Model/FormattedCharacter.cs b/MultiTerm.Core/Model/FormattedCharacter.cs index 50be437..c157904 100644 --- a/MultiTerm.Core/Model/FormattedCharacter.cs +++ b/MultiTerm.Core/Model/FormattedCharacter.cs @@ -4,6 +4,10 @@ namespace MultiTerm.Core.Model; public class FormattedCharacter : Tuple, IFormattedCharacter { + public FormatType Format => this.Item1; + + public string Character => this.Item2; + public FormattedCharacter(FormatType item1, string item2) : base(item1, item2) { } diff --git a/MultiTerm.Core/Model/IFormattedCharacter.cs b/MultiTerm.Core/Model/IFormattedCharacter.cs index 5d18503..1cf49e0 100644 --- a/MultiTerm.Core/Model/IFormattedCharacter.cs +++ b/MultiTerm.Core/Model/IFormattedCharacter.cs @@ -2,5 +2,8 @@ public interface IFormattedCharacter { - + /// + /// The instance of represents this character. + /// + string Character { get; } } \ No newline at end of file diff --git a/MultiTerm.Core/Model/MultiFormatString.cs b/MultiTerm.Core/Model/MultiFormatString.cs index 28cc076..630a59b 100644 --- a/MultiTerm.Core/Model/MultiFormatString.cs +++ b/MultiTerm.Core/Model/MultiFormatString.cs @@ -9,6 +9,10 @@ namespace MultiTerm.Core.Model; /// public class MultiFormatString: ObservableCollection { + private const int charsPerHexBundle = 2; + private const int charsPerBinBundle = 8; + + #region Insertion / Removal of Characters /// /// Inserts item at given index after checking if its valid using . /// @@ -16,25 +20,35 @@ public class MultiFormatString: ObservableCollection /// item to insert protected override void InsertItem(int index, IFormattedCharacter item) { + int offset = 0; + bool insertSpacingAfter = false; if(item is FormattedCharacter formattedChar) { //check if valid, only insert then - if(ValidateValue(formattedChar.Item1, formattedChar.Item2) == false) + if(ValidateValue(formattedChar.Format, formattedChar.Character) == false) { return; } // check if spacing is required and insert - if(IsSpacingCharacterRequiredBeforeNewCharacter(this, index, formattedChar)) + if(IsSpacingCharacterRequiredBeforeNewCharacter(this, index + offset, formattedChar)) { base.InsertItem(index, new SpacingCharacter()); - base.InsertItem(index + 1, item); + offset++; // insert item with offset of 1 + } - return; + if(IsSpacingCharacterRequiredAfterNewCharacter(this, index + offset, formattedChar)) + { + insertSpacingAfter = true; } } - base.InsertItem(index, item); + base.InsertItem(index + offset, item); + + if (insertSpacingAfter) + { + base.InsertItem(index + offset + 1, new SpacingCharacter()); + } } protected override void RemoveItem(int index) @@ -49,6 +63,141 @@ public class MultiFormatString: ObservableCollection } } + private static bool IsSpacingCharacterRequiredBeforeNewCharacter(IEnumerable collection, int indexOfInsertion, IFormattedCharacter newCharacter) + { + bool spacingCharRequired = false; + + // no spacing required if new character is a spacing character + if (newCharacter is SpacingCharacter) { return false; } + // no handling implemented for items that are not a FormattedCharacter + if (newCharacter is not FormattedCharacter newFormattedCharacter) + { throw new NotImplementedException($"'{nameof(IsSpacingCharacterRequiredBeforeNewCharacter)}()' does not implement handling for type {newCharacter.GetType()}"); } + + // switch spacing required by type of format + spacingCharRequired = newFormattedCharacter.Format switch + { + FormatType.Character => CheckSpacingRequiredBecauseChangedFormat(collection, indexOfInsertion, newFormattedCharacter.Format), + + FormatType.Hexadecimal => CheckSpacingRequiredBecauseChangedFormat(collection, indexOfInsertion, newFormattedCharacter.Format) || + CheckSpacingRequiredBecauseBundleMaxCountReached(collection, indexOfInsertion, charsPerHexBundle, newFormattedCharacter.Format), + + FormatType.Binary => CheckSpacingRequiredBecauseChangedFormat(collection, indexOfInsertion, newFormattedCharacter.Format) || + CheckSpacingRequiredBecauseBundleMaxCountReached(collection, indexOfInsertion, charsPerBinBundle, newFormattedCharacter.Format), + + _ => throw new NotImplementedException($"'{nameof(IsSpacingCharacterRequiredBeforeNewCharacter)}()' does not implement handling for format {newFormattedCharacter.Format}"), + }; + return spacingCharRequired; + + static bool CheckSpacingRequiredBecauseChangedFormat(IEnumerable collection, int indexOfInsertion, FormatType newCharFormat) + { + // check if previous item is of other type + if (collection.Any()) + { + int count = 1; + FormattedCharacter? prevChar = null; + while (prevChar is null) + { + try + { + prevChar = collection.ElementAt(indexOfInsertion - count++) as FormattedCharacter; + // format of previous is not equal to new => spacer required + if (prevChar != null && prevChar.Format != newCharFormat) + { + return true; + } + } + catch { break; } // end of collection => break while + } + } + return false; + } + + static bool CheckSpacingRequiredBecauseBundleMaxCountReached(IEnumerable collection, int indexOfInsertion, int charsPerBundle, FormatType newCharFormat) + { + // spacing required if previous x characters are of same format + if (collection.Count() >= charsPerBundle) + { + try + { + // get last x elements of collection + var lastXElements = collection.Take(new Range(new Index(indexOfInsertion - charsPerBundle), new Index(indexOfInsertion))); + // get elements of last x elements where the same format is used + var sameFormatElements = lastXElements.Where(x => x is FormattedCharacter formChar && formChar.Format == newCharFormat); + // elements of same Format are more (impossible) or equal to amount required for bundle + if (sameFormatElements.Count() >= charsPerBundle) + { + return true; + } + } + catch { } // catch any exception, ignore it and return default + } + return false; + } + } + + private static bool IsSpacingCharacterRequiredAfterNewCharacter(IEnumerable collection, int indexOfInsertion, IFormattedCharacter newCharacter) + { + bool spacingCharRequired = false; + + // no spacing required if new character is a spacing character + if (newCharacter is SpacingCharacter) { return false; } + // no handling implemented for items that are not a FormattedCharacter + if (newCharacter is not FormattedCharacter newFormattedCharacter) + { throw new NotImplementedException($"'{nameof(IsSpacingCharacterRequiredAfterNewCharacter)}()' does not implement handling for type {newCharacter.GetType()}"); } + + // if there is anything in the collection + if (collection.Any()) + { + // check if next character in collection is a FormattedCharacter + try + { + var nextChar = collection.ElementAt(indexOfInsertion) as FormattedCharacter; + // format of next is not equal to new => spacer required + if (nextChar != null && nextChar.Format != newFormattedCharacter.Format) + { + return true; + } + } + catch { } // catch any exception and return default result + } + + return spacingCharRequired; + } + #endregion + + /// + /// Validates wether a certain is valid for the given . + /// + /// true if valid + public static bool ValidateValue(FormatType format, string value) + { + // invalid if more than one character + if(value.Length > 1) + { return false; } + + // extract character + var character = value.First(); + + switch (format) + { + case FormatType.Character: + // accept only ascii, not extended ascii or unicode + return char.IsAscii(character); + + case FormatType.Hexadecimal: + return (character >= '0' && character <= '9' || + character >= 'A' && character <= 'F' || + character >= 'a' && character <= 'f'); + + case FormatType.Binary: + return (character == '0' || character == '1'); + + default: + throw new NotImplementedException($"'{nameof(ValidateValue)}()' does not implement validation for format {format}"); + } + } + + #region ToString /// /// Converts the to a string that only contains Unicode (UTF-16) characters. /// @@ -67,8 +216,8 @@ public class MultiFormatString: ObservableCollection { // ending conversion or reached limit of characters? if (formattedChar == null || - formattedChar.Item1 != FormatType.Hexadecimal || - hexConversionCharacters.Length == 4) + formattedChar.Format != FormatType.Hexadecimal || + hexConversionCharacters.Length == charsPerHexBundle) { // finalize conversion stringBuilder.Append(FromHexString(hexConversionCharacters)); @@ -87,8 +236,8 @@ public class MultiFormatString: ObservableCollection { // ending conversion or reached limit of characters? if (formattedChar == null || - formattedChar.Item1 != FormatType.Binary || - binaryConversionCharacters.Length == 16) + formattedChar.Format != FormatType.Binary || + binaryConversionCharacters.Length == charsPerBinBundle) { // finalize conversion stringBuilder.Append(FromBinaryString(binaryConversionCharacters)); @@ -102,28 +251,28 @@ public class MultiFormatString: ObservableCollection foreach (var character in this.Items) { // can only handle FormattedCharacters - if(character is not FormattedCharacter formattedCharacter) { continue; } + if (character is not FormattedCharacter formattedCharacter) { continue; } // finalize ongoing conversions if there are any finalizeHexConversion(formattedCharacter); finalizeBinaryConversion(formattedCharacter); - switch (formattedCharacter.Item1) // switch by format + switch (formattedCharacter.Format) // switch by format { case FormatType.Character: - stringBuilder.Append(formattedCharacter.Item2); + stringBuilder.Append(formattedCharacter.Character); break; case FormatType.Hexadecimal: - hexConversionCharacters += formattedCharacter.Item2; + hexConversionCharacters += formattedCharacter.Character; break; case FormatType.Binary: - binaryConversionCharacters += formattedCharacter.Item2; + binaryConversionCharacters += formattedCharacter.Character; break; default: - throw new NotImplementedException($"'{nameof(ToString)}()' does not implement conversion for format {formattedCharacter.Item1}"); + throw new NotImplementedException($"'{nameof(ToString)}()' does not implement conversion for format {formattedCharacter.Format}"); } } @@ -134,143 +283,33 @@ public class MultiFormatString: ObservableCollection return stringBuilder.ToString(); } - /// - /// Validates wether a certain is valid for the given . - /// - /// true if valid - public static bool ValidateValue(FormatType format, string value) - { - // invalid if more than one character - if(value.Length > 1) - { return false; } - - // extract character - var character = value.First(); - - switch (format) - { - case FormatType.Character: - return true; - - case FormatType.Hexadecimal: - if (character >= '0' && character <= '9' || - character >= 'A' && character <= 'F' || - character >= 'a' && character <= 'f') - { return true; } - else - { return false; }; - - case FormatType.Binary: - if (character == '0' || character == '1') - { return true; } - else - { return false; }; - - default: - throw new NotImplementedException($"'{nameof(ValidateValue)}()' does not implement validation for format {format}"); - } - } - - public static bool IsSpacingCharacterRequiredBeforeNewCharacter(IEnumerable collection, int indexOfInsertion, IFormattedCharacter newCharacter) - { - bool spacingCharRequired = false; - const int charsPerHexBundle = 2; - const int charsPerBinBundle = 8; - - // no spacing required if new character is a spacing character - if(newCharacter is SpacingCharacter) { return false; } - // no handling implemented for items that are not a FormattedCharacter - if(newCharacter is not FormattedCharacter formattedCharacter) - { throw new NotImplementedException($"'{nameof(IsSpacingCharacterRequiredBeforeNewCharacter)}()' does not implement handling for type {newCharacter.GetType()}"); } - - // switch spacing required by type of format - spacingCharRequired = formattedCharacter.Item1 switch - { - FormatType.Character => CheckSpacingRequiredBecauseChangedFormat(collection, indexOfInsertion, formattedCharacter.Item1), - - FormatType.Hexadecimal => CheckSpacingRequiredBecauseChangedFormat(collection, indexOfInsertion, formattedCharacter.Item1) || - CheckSpacingRequiredBecauseBundleMaxCountReached(collection, indexOfInsertion, charsPerHexBundle, formattedCharacter.Item1), - - FormatType.Binary => CheckSpacingRequiredBecauseChangedFormat(collection, indexOfInsertion, formattedCharacter.Item1) || - CheckSpacingRequiredBecauseBundleMaxCountReached(collection, indexOfInsertion, charsPerBinBundle, formattedCharacter.Item1), - - _ => throw new NotImplementedException($"'{nameof(IsSpacingCharacterRequiredBeforeNewCharacter)}()' does not implement handling for format {formattedCharacter.Item1}"), - }; - return spacingCharRequired; - - static bool CheckSpacingRequiredBecauseChangedFormat(IEnumerable collection, int indexOfInsertion, FormatType newCharFormat) - { - // check if previous item is of other type - if (collection.Any()) - { - int count = 1; - FormattedCharacter? prevChar = null; - while (prevChar is null) - { - try - { - prevChar = collection.ElementAt(indexOfInsertion - count++) as FormattedCharacter; - // format of previous is not equal to new => spacer required - if (prevChar != null && prevChar.Item1 != newCharFormat) - { - return true; - } - } - catch (Exception) - { - // end of collection => break while - break; - } - } - } - return false; - } - - static bool CheckSpacingRequiredBecauseBundleMaxCountReached(IEnumerable collection, int indexOfInsertion, int charsPerBundle, FormatType newCharFormat) - { - // spacing required if previous x characters are of same format - if (collection.Count() > charsPerBundle) - { - // get last x elements of collection - var lastXElements = collection.Take(new Range(new Index(indexOfInsertion - charsPerBundle), new Index(indexOfInsertion))); - // get elements of last x elements where the same format is used - var sameFormatElements = lastXElements.Where(x => x is FormattedCharacter formChar && formChar.Item1 == newCharFormat); - // elements of same Format are more (impossible) or equal to amount required for bundle - if (sameFormatElements.Count() >= charsPerBundle) - { - return true; - } - } - return false; - } - } - // 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) { // Added PadLeft so strings with one character do not get ignored - string internalHexString = hexString.PadLeft(4, '0'); + 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.BigEndianUnicode.GetString(bytes); + return Encoding.ASCII.GetString(bytes); } private static string FromBinaryString(string binaryString) { - string internalBinaryString = binaryString.PadLeft(16, '0'); + string internalBinaryString = binaryString.PadLeft(charsPerBinBundle, '0'); var bytes = new byte[internalBinaryString.Length / 8]; for (var i = 0; i < bytes.Length; i++) { bytes[i] = Convert.ToByte(internalBinaryString.Substring(i * 8, 8), 2); } - return Encoding.BigEndianUnicode.GetString(bytes); + return Encoding.ASCII.GetString(bytes); } + #endregion } diff --git a/MultiTerm.Core/Model/SpacingCharacter.cs b/MultiTerm.Core/Model/SpacingCharacter.cs index 3b7f5d4..163eda6 100644 --- a/MultiTerm.Core/Model/SpacingCharacter.cs +++ b/MultiTerm.Core/Model/SpacingCharacter.cs @@ -2,5 +2,5 @@ public class SpacingCharacter : IFormattedCharacter { - + public string Character => " "; } diff --git a/MultiTerm.Wpf.CustomControl/MultiFormatTextBox/MultiFormatTextBox.cs b/MultiTerm.Wpf.CustomControl/MultiFormatTextBox/MultiFormatTextBox.cs index 0c08525..6fd75a1 100644 --- a/MultiTerm.Wpf.CustomControl/MultiFormatTextBox/MultiFormatTextBox.cs +++ b/MultiTerm.Wpf.CustomControl/MultiFormatTextBox/MultiFormatTextBox.cs @@ -165,13 +165,9 @@ public class MultiFormatTextBox : Control } string toRemoveString = string.Empty; - if (items[0] is FormattedCharacter formattedCharacter) + if (items[0] is IFormattedCharacter character) { - toRemoveString = formattedCharacter.Item2; - } - else if(items[0] is SpacingCharacter spacingCharacter) - { - toRemoveString = " "; + toRemoveString = character.Character; } else { @@ -202,15 +198,15 @@ public class MultiFormatTextBox : Control if (item is FormattedCharacter formattedCharacter) { // text as run with correct background brush - run = new Run(formattedCharacter.Item2) + run = new Run(formattedCharacter.Character) { Background = this.currentlySelectedFormat!.BackgroundBrush }; } - else if(item is SpacingCharacter) + else if(item is SpacingCharacter spacingCharacter) { // text as run with default background brush - run = new Run(" ") + run = new Run(spacingCharacter.Character) { Background = defaultBackgroundBrush };