Pārlūkot izejas kodu

qdr.fnd.core: Date:__25.06.2025__

- Add extension StringExt.SplitQuoted to split string with quoted values
- Add Tests for StringExt
Dalibor Votruba 1 gadu atpakaļ
vecāks
revīzija
5128c32a40

+ 100 - 0
qdr.fnd.core.test/Value/StringExtensionTest.cs

@@ -0,0 +1,100 @@
+using Quadarax.Foundation.Core.Value.Extensions;
+
+namespace qdr.fnd.core.test.Value
+{
+    internal class StringExtensionTest
+    {
+        [Test(TestOf = typeof(StringExt))]
+        public void SplitQuotedSingleBracketOk()
+        {
+            var sep = ",";
+            var quote = "|";
+            var input = "a,b,|c,d|,,e";
+
+            var result = input.SplitQuoted(sep, quote);
+            Assert.That(result, Is.Not.Null);
+            Assert.That(result.Length, Is.EqualTo(4));
+            Assert.That(result[0], Is.EqualTo("a"));
+            Assert.That(result[1], Is.EqualTo("b"));
+            Assert.That(result[2], Is.EqualTo("c,d"));
+            Assert.That(result[3], Is.EqualTo("e"));
+
+        }
+        [Test(TestOf = typeof(StringExt))]
+        public void SplitQuotedDiffBrackedOk()
+        {
+            var sep = ",";
+            var quote = "|";
+            var input = "a,b,|c,d-,,e";
+
+            var result = input.SplitQuoted(sep, quote, "-");
+            Assert.That(result, Is.Not.Null);
+            Assert.That(result.Length, Is.EqualTo(4));
+            Assert.That(result[0], Is.EqualTo("a"));
+            Assert.That(result[1], Is.EqualTo("b"));
+            Assert.That(result[2], Is.EqualTo("c,d"));
+            Assert.That(result[3], Is.EqualTo("e"));
+
+        }
+
+        [Test(TestOf = typeof(StringExt))]
+        public void SplitQuotedKeepBrackedOk()
+        {
+            var sep = ",";
+            var quote = "|";
+            var input = "a,b,|c,d-,,e";
+
+            var result = input.SplitQuoted(sep, quote, "-", keepBracket: true);
+            Assert.That(result, Is.Not.Null);
+            Assert.That(result.Length, Is.EqualTo(4));
+            Assert.That(result[0], Is.EqualTo("a"));
+            Assert.That(result[1], Is.EqualTo("b"));
+            Assert.That(result[2], Is.EqualTo(quote + "c,d" + "-"));
+            Assert.That(result[3], Is.EqualTo("e"));
+
+        }
+
+        [Test(TestOf = typeof(StringExt))]
+        public void SplitQuotedBackCompatibilityOk()
+        {
+            var sep = ",";
+            var quote = "|";
+            var input = "a,b,";
+
+            var result = input.SplitQuoted(sep, quote);
+            Assert.That(result, Is.Not.Null);
+            Assert.That(result.Length, Is.EqualTo(2));
+            Assert.That(result[0], Is.EqualTo("a"));
+            Assert.That(result[1], Is.EqualTo("b"));
+        }
+
+        [Test(TestOf = typeof(StringExt))]
+        public void SplitQuotedBackCompatibilityTrimmedOk()
+        {
+            var sep = ",";
+            var quote = "|";
+            var input = "a, b ,";
+
+            var result = input.SplitQuoted(sep, quote);
+            Assert.That(result, Is.Not.Null);
+            Assert.That(result.Length, Is.EqualTo(2));
+            Assert.That(result[0], Is.EqualTo("a"));
+            Assert.That(result[1], Is.EqualTo("b"));
+        }
+
+        [Test(TestOf = typeof(StringExt))]
+        public void SplitQuotedBackCompatibilityRemoveEmptyOk()
+        {
+            var sep = ",";
+            var quote = "|";
+            var input = "a,b,";
+
+            var result = input.SplitQuoted(sep, quote, removeEmptyItems: false);
+            Assert.That(result, Is.Not.Null);
+            Assert.That(result.Length, Is.EqualTo(3));
+            Assert.That(result[0], Is.EqualTo("a"));
+            Assert.That(result[1], Is.EqualTo("b"));
+            Assert.That(result[2], Is.EqualTo(""));
+        }
+    }
+}

+ 270 - 157
qdr.fnd.core/Value/Extensions/StringExt.cs

@@ -1,165 +1,278 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-
-namespace Quadarax.Foundation.Core.Value.Extensions
-{
-    public static class StringExt
-    {
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace Quadarax.Foundation.Core.Value.Extensions
+{
+    public static class StringExt
+    {
         /// <summary>
         /// Default string comparer for like operator.
-        /// </summary>
-        public static IEqualityComparer<string> DefaultLikeComparer { get; set; } = new StringPatternComparer();
-
-        public static string Align(this string value, int size)
-        {
-            if (string.IsNullOrEmpty(value)) return new string(' ', size);
-            if (value.Length < size)
-                return value + new string(' ',  size - value.Length);
-            return Truncate(value, size);
-        }
-        public static string Truncate(this string value, int maxLength)
-        {
-            if (string.IsNullOrEmpty(value)) return value;
-            return value.Length <= maxLength ? value : value.Substring(0, maxLength); 
-        }
-        
-        public static int SeparateValueCount(this string value, string separator)
-        {
-            var nSepLen = separator.Length;
-            var nStrLen = value.Length;
-            if (nStrLen == 0 || nSepLen == 0)
-                return nSepLen == 0 && nStrLen > 0 ? 1 : 0;
-            var nCnt = 0;
-            var nPos = value.IndexOf(separator, StringComparison.Ordinal);
-            while (nPos >= 0)
-            {
-                nCnt++;
-                nPos = value.IndexOf(separator, nPos + nSepLen, StringComparison.Ordinal);
-            }
-            return nCnt + 1;
-
-        }
-        public static string SeparateValue(this string value, string separator, int zeroBasedIndex)
-        {
-            var nSepLen = separator.Length;
-            var nStrLen = value.Length;
-            if (nStrLen == 0 || nSepLen == 0 || nSepLen > nStrLen)
-                throw new IndexOutOfRangeException("SeparateValue method has index out of range.");
-            var nLastPos = 0;
-            var nPos = value.IndexOf(separator, StringComparison.Ordinal);
-            while (nPos >= 0)
-            {
-                if (zeroBasedIndex == 0)
-                    break;
-                nLastPos = nPos + nSepLen;
-                nPos = value.IndexOf(separator, nPos + nSepLen, StringComparison.Ordinal);
-                zeroBasedIndex--;
-            }
-            if (zeroBasedIndex > 0)
-                throw new IndexOutOfRangeException("SeparateValue method has index out of range.");
-            return value.Substring(nLastPos, nPos == -1 ? nStrLen - nLastPos : nPos - nLastPos);
-        }
-        public static string SeparateValueLast(this string value, string separator)
-        {
-            var nSepLen = separator.Length;
-            var nStrLen = value.Length;
-            if (nStrLen == 0 || nSepLen == 0 || nSepLen > nStrLen)
-                throw new IndexOutOfRangeException("SeparateValue method has index out of range.");
-            return value.SeparateValue(separator, value.SeparateValueCount(separator) - 1);
-        }
-        public static string RemoveLast(this string value)
-        {
-            return value.Length == 0 ? string.Empty : value.Substring(0, value.Length - 1);
-        }
-        public static string RemoveFirst(this string value)
-        {
-            return value.Length == 0 ? string.Empty : value.Substring(1, value.Length - 1);
-        }
-
-        public static int MaxLineChars(this string value)
-        {
-            var aParts = value.Split("/n");
-            return aParts.Length > 0 ? aParts.Max(x => x.Length) : value.Length;
-        }
-
-        public static string[] Split(this string value, string separator)
-        {
-            return value.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries);
-        }
-
-        public static Tuple<string, string>[] Extract(this string value, params string[] hashes) //hashtype, tashname
-        {
-
-            var oResult = new List<Tuple<string, string>>();
-            if (hashes.Length == 0)
-                return new Tuple<string, string>[0];
-            var nLenIndex = value.Length + 1;
-            foreach (var sHash in hashes)
-            {
-                var bBeginBlock = true;
-                var nPos = value.IndexOf(sHash, StringComparison.InvariantCulture);
-                var nLenHash = sHash.Length;
-                if (nPos < 0)
-                    continue;
-                while (nPos >= 0)
-                {
-                    if (nPos + nLenHash > nLenIndex)
-                        break;
-                    var nEndPos = value.IndexOf(sHash, nPos + nLenHash, StringComparison.InvariantCulture);
-                    if (nEndPos < 0)
-                        break;
-                    if (bBeginBlock)
-                    {
-                        var oEntry = new Tuple<string, string>(sHash, value.Substring(nPos + nLenHash, nEndPos - nPos - nLenHash));
-                        if (!oResult.Contains(oEntry))
-                            oResult.Add(oEntry);
-                    }
-                    nPos = nEndPos;
-                    bBeginBlock = !bBeginBlock;
-                }
-            }
-            return oResult.ToArray();
-        }
-
-        public static string EnsurePathBackslash(this string value)
-        {
-            if (string.IsNullOrEmpty(value))
-                return value;
-            value = value.Trim();
-            if (value.EndsWith(Path.DirectorySeparatorChar))
-                return value;
-            return value + Path.DirectorySeparatorChar;
-        }
-        public static string EnsurePathNonBackslash(this string value)
-        {
-            if (string.IsNullOrEmpty(value))
-                return value;
-            value = value.Trim();
-            if (!value.EndsWith(Path.DirectorySeparatorChar))
-                return value;
-            return value.RemoveLast();
-        }
-
-        public static string RemoveWhitespace(this string input)
-        {
-            return new string(input.ToCharArray()
-                .Where(c => !Char.IsWhiteSpace(c))
-                .ToArray());
-        }
-
+        /// </summary>
+        public static IEqualityComparer<string> DefaultLikeComparer { get; set; } = new StringPatternComparer();
+
+        public static string Align(this string value, int size)
+        {
+            if (string.IsNullOrEmpty(value)) return new string(' ', size);
+            if (value.Length < size)
+                return value + new string(' ', size - value.Length);
+            return Truncate(value, size);
+        }
+        public static string Truncate(this string value, int maxLength)
+        {
+            if (string.IsNullOrEmpty(value)) return value;
+            return value.Length <= maxLength ? value : value.Substring(0, maxLength);
+        }
+
+        public static int SeparateValueCount(this string value, string separator)
+        {
+            var nSepLen = separator.Length;
+            var nStrLen = value.Length;
+            if (nStrLen == 0 || nSepLen == 0)
+                return nSepLen == 0 && nStrLen > 0 ? 1 : 0;
+            var nCnt = 0;
+            var nPos = value.IndexOf(separator, StringComparison.Ordinal);
+            while (nPos >= 0)
+            {
+                nCnt++;
+                nPos = value.IndexOf(separator, nPos + nSepLen, StringComparison.Ordinal);
+            }
+            return nCnt + 1;
+
+        }
+        public static string SeparateValue(this string value, string separator, int zeroBasedIndex)
+        {
+            var nSepLen = separator.Length;
+            var nStrLen = value.Length;
+            if (nStrLen == 0 || nSepLen == 0 || nSepLen > nStrLen)
+                throw new IndexOutOfRangeException("SeparateValue method has index out of range.");
+            var nLastPos = 0;
+            var nPos = value.IndexOf(separator, StringComparison.Ordinal);
+            while (nPos >= 0)
+            {
+                if (zeroBasedIndex == 0)
+                    break;
+                nLastPos = nPos + nSepLen;
+                nPos = value.IndexOf(separator, nPos + nSepLen, StringComparison.Ordinal);
+                zeroBasedIndex--;
+            }
+            if (zeroBasedIndex > 0)
+                throw new IndexOutOfRangeException("SeparateValue method has index out of range.");
+            return value.Substring(nLastPos, nPos == -1 ? nStrLen - nLastPos : nPos - nLastPos);
+        }
+        public static string SeparateValueLast(this string value, string separator)
+        {
+            var nSepLen = separator.Length;
+            var nStrLen = value.Length;
+            if (nStrLen == 0 || nSepLen == 0 || nSepLen > nStrLen)
+                throw new IndexOutOfRangeException("SeparateValue method has index out of range.");
+            return value.SeparateValue(separator, value.SeparateValueCount(separator) - 1);
+        }
+        public static string RemoveLast(this string value)
+        {
+            return value.Length == 0 ? string.Empty : value.Substring(0, value.Length - 1);
+        }
+        public static string RemoveFirst(this string value)
+        {
+            return value.Length == 0 ? string.Empty : value.Substring(1, value.Length - 1);
+        }
+
+        public static int MaxLineChars(this string value)
+        {
+            var aParts = value.Split("/n");
+            return aParts.Length > 0 ? aParts.Max(x => x.Length) : value.Length;
+        }
+
+        public static string[] Split(this string value, string separator)
+        {
+            return value.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries);
+        }
+
+        public static Tuple<string, string>[] Extract(this string value, params string[] hashes) //hashtype, tashname
+        {
+
+            var oResult = new List<Tuple<string, string>>();
+            if (hashes.Length == 0)
+                return new Tuple<string, string>[0];
+            var nLenIndex = value.Length + 1;
+            foreach (var sHash in hashes)
+            {
+                var bBeginBlock = true;
+                var nPos = value.IndexOf(sHash, StringComparison.InvariantCulture);
+                var nLenHash = sHash.Length;
+                if (nPos < 0)
+                    continue;
+                while (nPos >= 0)
+                {
+                    if (nPos + nLenHash > nLenIndex)
+                        break;
+                    var nEndPos = value.IndexOf(sHash, nPos + nLenHash, StringComparison.InvariantCulture);
+                    if (nEndPos < 0)
+                        break;
+                    if (bBeginBlock)
+                    {
+                        var oEntry = new Tuple<string, string>(sHash, value.Substring(nPos + nLenHash, nEndPos - nPos - nLenHash));
+                        if (!oResult.Contains(oEntry))
+                            oResult.Add(oEntry);
+                    }
+                    nPos = nEndPos;
+                    bBeginBlock = !bBeginBlock;
+                }
+            }
+            return oResult.ToArray();
+        }
+
+        public static string EnsurePathBackslash(this string value)
+        {
+            if (string.IsNullOrEmpty(value))
+                return value;
+            value = value.Trim();
+            if (value.EndsWith(Path.DirectorySeparatorChar))
+                return value;
+            return value + Path.DirectorySeparatorChar;
+        }
+        public static string EnsurePathNonBackslash(this string value)
+        {
+            if (string.IsNullOrEmpty(value))
+                return value;
+            value = value.Trim();
+            if (!value.EndsWith(Path.DirectorySeparatorChar))
+                return value;
+            return value.RemoveLast();
+        }
+
+        public static string RemoveWhitespace(this string input)
+        {
+            return new string(input.ToCharArray()
+                .Where(c => !Char.IsWhiteSpace(c))
+                .ToArray());
+        }
+
         /// <summary>
         /// Performs like operator on input string. customComparer can be provides to override default comparer.
         /// </summary>
         /// <param name="input">Input string to compare</param>
         /// <param name="searchPattern">Search pattern corresponds with comparer implementation.</param>
         /// <param name="customComparer">Custom comparer. If not set, use default comparer (defined in <see cref="DefaultLikeComparer"/>)</param>
-        /// <returns></returns>
-        public static bool Like(this string input,string searchPattern, IEqualityComparer<string>? customComparer = null)
-        {
-            customComparer ??= DefaultLikeComparer;
-            return customComparer.Equals(input, searchPattern);
-        }
-    }
-}
+        /// <returns></returns>
+        public static bool Like(this string input, string searchPattern, IEqualityComparer<string>? customComparer = null)
+        {
+            customComparer ??= DefaultLikeComparer;
+            return customComparer.Equals(input, searchPattern);
+        }
+
+
+        /// <summary>
+        /// Splits a string by the specified separator, but ignores separators that are within brackets/quotes.
+        /// </summary>
+        /// <param name="value">The string to split</param>
+        /// <param name="separator">The separator string to split on</param>
+        /// <param name="beginBracket">The opening bracket/quote character(s)</param>
+        /// <param name="endBracket">The closing bracket/quote character(s). If null, uses the same as beginBracket</param>
+        /// <param name="removeEmptyItems">If true, removes empty entries from the result</param>
+        /// <param name="trimValues">If true, trims whitespace from each result value</param>
+        /// <param name="keepBracket">If true, keeps the begin and end brackets in the result</param>
+        /// <returns>Array of split string parts</returns>
+        public static string[] SplitQuoted(this string value, string separator, string beginBracket, string? endBracket = null, bool removeEmptyItems = true, bool trimValues = true, bool keepBracket = false)
+        {
+            if (string.IsNullOrEmpty(value))
+                return new string[] { value ?? string.Empty };
+            
+            if (string.IsNullOrEmpty(separator))
+                throw new ArgumentException("Separator cannot be null or empty", nameof(separator));
+            
+            if (string.IsNullOrEmpty(beginBracket))
+                throw new ArgumentException("Begin bracket cannot be null or empty", nameof(beginBracket));
+            
+            // If endBracket is not specified, use the same as beginBracket
+            endBracket ??= beginBracket;
+            
+            var result = new List<string>();
+            var currentPart = new StringBuilder();
+            var i = 0;
+            
+            while (i < value.Length)
+            {
+                // Check if we're at the start of a separator
+                if (i <= value.Length - separator.Length && 
+                    value.Substring(i, separator.Length) == separator)
+                {
+                    // Found separator outside brackets, so add current part to result
+                    result.Add(currentPart.ToString());
+                    currentPart.Clear();
+                    i += separator.Length;
+                    continue;
+                }
+                
+                // Check if we're at the start of a begin bracket
+                if (i <= value.Length - beginBracket.Length && 
+                    value.Substring(i, beginBracket.Length) == beginBracket)
+                {
+                    // Found begin bracket, add it to current part if keepBracket is true
+                    if (keepBracket)
+                    {
+                        currentPart.Append(beginBracket);
+                    }
+                    i += beginBracket.Length;
+                    
+                    // Now find the matching end bracket
+                    var bracketDepth = 1;
+                    while (i < value.Length && bracketDepth > 0)
+                    {
+                        // Check for end brackets first (important for single-character brackets like quotes)
+                        if (i <= value.Length - endBracket.Length && 
+                            value.Substring(i, endBracket.Length) == endBracket)
+                        {
+                            i += endBracket.Length;
+                            bracketDepth--;
+                            // Add the end bracket if we're still nested or if keepBracket is true for outermost
+                            if (bracketDepth > 0 || keepBracket)
+                            {
+                                currentPart.Append(endBracket);
+                            }
+                        }
+                        // Check for nested begin brackets (only if different from end bracket or if we haven't found end bracket)
+                        else if (beginBracket != endBracket && 
+                                 i <= value.Length - beginBracket.Length && 
+                                 value.Substring(i, beginBracket.Length) == beginBracket)
+                        {
+                            currentPart.Append(beginBracket);
+                            i += beginBracket.Length;
+                            bracketDepth++;
+                        }
+                        else
+                        {
+                            // Regular character inside brackets
+                            currentPart.Append(value[i]);
+                            i++;
+                        }
+                    }
+                    continue;
+                }
+                
+                // Regular character outside brackets
+                currentPart.Append(value[i]);
+                i++;
+            }
+            
+            // Add the last part
+            result.Add(currentPart.ToString());
+            
+            // Process the result based on options
+            var finalResult = new List<string>();
+            foreach (var part in result)
+            {
+                var processedPart = trimValues ? part.Trim() : part;
+                
+                if (!removeEmptyItems || !string.IsNullOrEmpty(processedPart))
+                {
+                    finalResult.Add(processedPart);
+                }
+            }
+            
+            return finalResult.ToArray();
+        }
+    }
+}

+ 5 - 6
qdr.fnd.core/qdr.fnd.core.csproj

@@ -15,12 +15,11 @@
     <Copyright>Copyright © 2023,2024,2025 Quadarax</Copyright>
     <PackageIcon>quadarax_dll.png</PackageIcon>
     <PackageTags>QDR;FND;CORE</PackageTags>
-    <AssemblyVersion>0.0.9.0</AssemblyVersion>
-    <FileVersion>0.0.9.0</FileVersion>
-    <Version>0.0.9.0-alpha</Version>
-    <PackageReleaseNotes>Date:24.6.2025
-		- Add extension Exception.ToAggregateException to convert exception to AggregateException
-		- update dependencies packages (System.IO.Abstractions)
+    <AssemblyVersion>0.0.10.0</AssemblyVersion>
+    <FileVersion>0.0.10.0</FileVersion>
+    <Version>0.0.10.0-alpha</Version>
+    <PackageReleaseNotes>Date:25.06.2025
+		- Add extension StringExt.SplitQuoted to split string with quoted values
     </PackageReleaseNotes>
     <PackageReadmeFile>releasenotes.md</PackageReadmeFile>
     <Nullable>enable</Nullable>

+ 4 - 0
qdr.fnd.core/releasenotes.md

@@ -1,5 +1,9 @@
 # Quadarax.Foundation.Core * Release notes
 Ordered by version descending
+## 0.0.10.0
+Date:__25.06.2025__
+- Add extension StringExt.SplitQuoted to split string with quoted values
+---
 
 ## 0.0.9.0
 Date:__24.06.2025__