【问题标题】:What is a more unique delimiter than comma for separating strings?有什么比逗号更独特的分隔符来分隔字符串?
【发布时间】:2010-10-23 09:08:16
【问题描述】:

我有几个文本框,用户可以在其中输入信息。这可以包含逗号,所以我不能使用标准的逗号分隔字符串。

什么是一个好的分隔符来表示字符串应该基于用户在他们的作品中通常不使用的字符来分隔?我将把这些字段组合成一个字符串,并将它们传递给我的加密方法。在我解密它们之后,我需要能够可靠地分离它们。

如果重要的话,我会使用 C#。

【问题讨论】:

  • 使用贝尔角色! ASCII码7。:p
  • 只是不要将文件添加到标准输出。哎哟。
  • 使用标签。用户无法输入选项卡,因为它退出了文本框。 (我想 \n 也可能有用。)
  • 他们无法通过键入来输入选项卡(至少在大多数文本字段实现中),但他们可以轻松地将选项卡粘贴到文本框中。
  • @tabbers,他没有指定它的 web ui,如果 asp.net TextBox 中的 multiline=true,它应该被转义/编码标签是允许的

标签: c# csv


【解决方案1】:

|将是我列表中的下一个,并且通常用作 CSV 的替代品。 google "pipe delimited" 你会发现很多例子。

string[] items = new string[] {"Uno","Dos","Tres"};

string toEncrypt = String.Join("|", items);

items = toEncrypt.Split(new char[] {'|'}, StringSplitOptions.RemoveEmptyEntries);

foreach(string s in items)
  Console.WriteLine(s);

而且由于每个人都喜欢批评编码而不是提供代码,所以这里有一种编码文本的方法,以便您的 | delim 不会碰撞。

string[] items = new string[] {"Uno","Dos","Tres"};

for (int i = 0; i < items.Length; i++)
    items[i] = Convert.ToBase64String(Encoding.UTF8.GetBytes(items[i]));

string toEncrypt = String.Join("|", items);

items = toEncrypt.Split(new char[] {'|'}, StringSplitOptions.RemoveEmptyEntries);

foreach (string s in items)
     Console.WriteLine(Encoding.UTF8.GetString(Convert.FromBase64String(s)));

【讨论】:

  • 这仍然依赖于导致安全错误的那种一厢情愿。希望没有用户会输入 |只是没有削减它。无论分隔符如何,都必须支持转义。那个,或者分隔符必须被阻止,所以用户根本无法输入它们。
  • @Emil 指出。请参阅我对 mP 答案的评论。有人应该提供转义代码。也许你是游戏?
  • 没什么私人的。只是想为路过的人添加一个注释。 :)
  • 你的代码比不给出答案更糟糕,它对新手来说令人费解和困惑。考虑到它的错误,我不敢相信它得到了如此多的选票。现实世界中的许多格式,例如 XML 和 HTML 都转义了它们的特殊字符,它们从不会对整个内容进行 base64 编码,只是为了在这里和那里转义少数字符。
  • 例子就是这样,一个例子。当锤子更合适时,它可能会使用大锤。但是,至少该线程中的人试图帮助解决他的问题,而您却挑剔并且没有破坏一行代码。 Base64 是合适的,并且在 web 开发中使用了很多......所以希望他能比你从中学到更多。在这个线程中还有另外两个人提供了 foreach(char),所以我做了第三个选择。教学选择很重要。你不能用 List 解决所有问题
【解决方案2】:

我已经看到不常用的字符用作分隔符,甚至是不常用的字符组合,如 -|::|-,但即使它们不太可能出现,它们仍然会出现。

如果你想让它防水,基本上有两种选择:

1:使用无法输入的字符,例如'\0'字符:

加入:

string combined = string.Join("\0", inputArray);

拆分:

string[] result = combined.Split('\0');

2:转义字符串并使用转义字符作为分隔符,如 url 编码值并使用 & 作为分隔符:

加入:

string combined = string.Join("&", inputArray.Select<string,string>(System.Web.HttpUtility.UrlEncode).ToArray());

拆分:

string[] result = combined.Split('&').Select<string,string>(System.Web.HttpUtility.UrlDecode).ToArray();

【讨论】:

    【解决方案3】:

    自从我停止使用 C 以来,我不认为我愿意对字符串集合进行自定界。“现代”语言中不需要它,而且 - 虽然微不足道 - 边缘情况的数量就足够了把你烦死了。

    将它们存储在 List 或 string[] 中并序列化/反序列化它们。如果您想要人类可读性或互操作性,请使用 XML - 如果您不想要,则使用二进制序列化它们。无论哪种方式,您都可以轻松地加密输出,并且没有歧义或创建您自己需要的转义例程。

    在 C# 中,与此答案相比,它的 LOC 更少,编写时间也更少。没有理由推出自己的解决方案。

    【讨论】:

    • 数据库访问仍然存在,并且需要在 Unix 脚本中正确分隔输出。
    【解决方案4】:

    最好的解决方案是坚持使用逗号并引入对字符转义的支持。您选择的任何字符最终都需要输入,因此您也可以为此提供支持。

    在双引号字符串中考虑反斜杠 + 双引号。

    不要选择反引号之类的字符,因为有些用户可能不知道如何输入...

    【讨论】:

    • 指出转义的好主意,请发布一些他可能如何实现的代码
    • 根据我从问题中了解到的情况,用户不必输入分隔符,因此可以使用反引号。不过,转义仍然是一个更好的解决方案。
    • 同意。不管你怎么做,逃避都是必经之路。鉴于此,选择分隔符是一种优化。
    • @DeadHead 不管数据来自哪里,当用户输入您用作分隔符的字符时,您仍然会遇到问题。
    • @mP:“不要选择反引号之类的字符,因为有些用户可能不知道如何输入...”请在批评之前阅读该帖子。
    【解决方案5】:

    用户将在文本框中输入分隔字符串,还是将输入单独的字符串,然后您的代码将这些字符串构建为分隔字符串?

    在第一种情况下,最好重新考虑您的 UI。例如,用户可以一次在文本框中输入一个字符串,然后在每个字符串之后单击“添加到列表”按钮。

    在第二种情况下,使用什么分隔符并不重要。选择您喜欢的任何角色,只要确保您避开该角色的任何其他出现。

    编辑

    由于其他答案上的几个 cmets 都在询问代码,所以这里有一种创建逗号分隔字符串的方法,使用反斜杠作为转义字符:

    public static string CreateDelimitedString(IEnumerable<string> items)
    {
        StringBuilder sb = new StringBuilder();
    
        foreach (string item in items)
        {
            sb.Append(item.Replace("\\", "\\\\").Replace(",", "\\,"));
            sb.Append(",");
        }
    
        return (sb.Length > 0) ? sb.ToString(0, sb.Length - 1) : string.Empty;
    }
    

    以下是将逗号分隔的字符串转换回单个字符串集合的方法:

    public static IEnumerable<string> GetItemsFromDelimitedString(string s)
    {
        bool escaped = false;
        StringBuilder sb = new StringBuilder();
    
        foreach (char c in s)
        {
            if ((c == '\\') && !escaped)
            {
                escaped = true;
            }
            else if ((c == ',') && !escaped)
            {
                yield return sb.ToString();
                sb.Remove(0, sb.Length);
            }
            else
            {
                sb.Append(c);
                escaped = false;
            }
        }
    
        yield return sb.ToString();
    }
    

    下面是一些用法示例:

    string[] test =
        {
            "no commas or backslashes",
            "just one, comma",
            @"a comma, and a\ backslash",
            @"lots, of\ commas,\ and\, backslashes",
            @"even\\ more,, commas\\ and,, backslashes"
        };
    
        string delimited = CreateDelimitedString(test);
        Console.WriteLine(delimited);
    
        foreach (string item in GetItemsFromDelimitedString(delimited))
        {
            Console.WriteLine(item);
        }
    

    【讨论】:

      【解决方案6】:

      如果您想使用真正的唯一分隔符,我会推荐 或唯一字符串\u2561

      【讨论】:

        【解决方案7】:

        任何非标准字符管道|、反引号`、波浪号~、砰!或分号;可能会工作。然而,如果你走这条路,你真的冒险远离可用性。要求他们用反斜杠或其他东西来转义逗号是在乞求他们错过。

        如果无法使用 CSV,那么您应该考虑更改您的 UI。 (哎呀,对于用户输入,无论如何你都应该远离 CSV!)你说文本框,所以我假设你在 web 或某种赢表单或 WPF 中(绝对不是控制台)。所有这些都为您提供了比单个文本框更好的 UI 控制,并迫使用户遵循您困难的 UI 设计。

        更多信息肯定有助于更好地指导答案。

        但是,作为使用反斜杠转义逗号的示例。请注意,您不能在逗号前转义反斜杠。所以@"uno, dos, tr\\,es" 会以 {"uno", "dos", "tr\es"} 结尾。

        string data = @"uno, dos, tr\,es";
        string[] items = data.Split(','); // {"uno", " dos", @"tr\", "es"}
        List<string> realitems = new List<string>();
        for (int i=items.Length-1; i >= 0; i--)
        {
            string item = items[i];
            if (item.Length == 0) { realitems.Insert(0, ""); continue; }
        
            if (realitems.Count == 0) { realitems.Insert(0, item); }
            else
            {
                if (item[item.Length - 1] == '\\') { realitems[0] = item + "," + realitems[0]; }
                else { realitems.Insert(0, item); }
            }
        }
        
        // Should end up with {"uno", " dos", "tr,es"}
        

        【讨论】:

        • 字符串数据 = @"uno,,,,,,, dos, tr\,es"; = 轰轰烈烈
        • Added Length == 0 检查将 "" 添加到列表中。固定?
        【解决方案8】:

        我想最终,每个角色都会被某人使用。用户总能找到破解我们的 HL7 解析器的方法。

        也许可以尝试一个足够随机的字符串,而不是单个字符,这样没人会使用它。类似“#!@!#”。

        【讨论】:

        • 我在解析 HL7 时做了完全相同的事情。
        【解决方案9】:

        检测一个未使用的字符,然后使用它。您的最终组合字符串可以从用作分隔符的那个点开始。

        示例:您的用户输入“pants”“,;,;,;,;,;”和“|~~|” 您遍历一组字符,直到找到一个未使用的字符。可以是,比如说,“$” 然后,您的最终连接字符串是 "$pants$,;,;,;,;,;$|~~|" 初始字符告诉您的程序将使用哪个字符作为分隔符。 这样,就没有禁止字符,句号。

        【讨论】:

        • 如果用户设法使用编码中的每个字符,他们将赢得无奖 :-)
        • 如果这种可能性足够严重,那么它可以很容易地扩展到搜索未使用的 2 甚至 3 个字符模式,从而成倍增加用户必须输入的组合数量。
        【解决方案10】:

        根据您的说法,我假设用户将数据输入到单独的字段中,然后您将其组合起来。所以用户永远不需要知道或关心分隔符是什么。

        不要只是尝试选择一个“没人使用过”的字符,因为无论是出于偶然还是为了尝试破坏您的代码,最终都会有一些用户使用它。

        所以,我会:

        • 在用户输入中插入反斜杠以转义逗号和反斜杠,然后将字符串与逗号组合。要进行分隔,请使用未转义的逗号(这是状态机的一项工作)进行拆分,然后对每个组件进行转义。

        • 使用现成的方法来序列化字符串列表。可用的内容取决于您的环境,我不太了解 C#/.NET,无法提供建议。在 Java 中,您可以只序列化一个向量或其他任何东西。

        • 使用控制字符(如 ASCII-BEL 或 ASCII-VT(或 ASCII-NUL,如果您的字符串从未被视为 nul 终止))分隔数据,并拒绝包含该字符的用户输入。

        如果必须允许用户输入他们喜欢的任何字符值,则第一个选项很好。如果您不关心显着膨胀数据,则第二个选项很好。如果您不介意拒绝尝试插入有趣数据的 smart-alec 用户(或有不寻常要求的用户),则第三个选项很好。

        【讨论】:

          【解决方案11】:

          如前所述,您选择的任何字符都有可能出现在输入中,因此您必须处理转义。 XML 可能是一种很好的序列化格式,因为我相信 .NET 具有良好的 XML 创建和删除支持。这可能比尝试实现自己的字符转义更强大,并且在未来也将更具可扩展性。

          【讨论】:

            【解决方案12】:

            没有人说TAB?制表符分隔很棒,但在 GUI 中键入制表符并不容易(它往往会将您移动到下一个屏幕元素)。但是对于计算机生成的文件,TAB 是完美的,因为它真的不应该出现在用户生成的文本中。

            【讨论】:

              【解决方案13】:

              你为什么不把每个输入都用引号括起来?

              这样你最终会得到这个:

              "Aaron","Johnson","25","I like cats, and dogs"
              

              不要忘记在输入时转义引号...

              【讨论】:

                【解决方案14】:

                Mark Brackett 给出了正确答案。我只会补充一点,这个简单问题的答案数量应该会让你永远不再使用分隔字符串。让这成为“智者的话”。

                【讨论】:

                  【解决方案15】:

                  反引号。没有人使用反引号。

                  【讨论】:

                  • 同意,但是 windows 复制/粘贴有时会用反引号替换单引号,不知道为什么,但这令人沮丧
                  • 或者认为“聪明的报价”真的很漂亮,但喜欢低技术、自己动手的解决方案的人。
                  • 我以前也使用反引号,然后发现 shift backtick 输出一个波浪号:〜我没有看到在我处理的任何值中使用波浪号。看起来很独特。
                  【解决方案16】:

                  也许是竖线字符 (|)?如果您的用户群对 IT 很敏感,那么这种方法(要求他们划定文本)可能不是最好的方法;你可以试试别的,例如提供一些动态添加文本框的方法,该文本框接受另一个字符串等。

                  如果您提供更多关于您正在做什么以及为谁做的信息,有人可能会建议另一种方法。

                  【讨论】:

                    【解决方案17】:

                    换行符? (即使用多行文本框)

                    【讨论】:

                    • 如果您的字符串代表任意“描述”类型字段,我不建议这样做。
                    【解决方案18】:

                    我建议使用“;”

                    【讨论】:

                    • ;我会说,在文本中相当常见。当然,如果没有关于被分隔的字符串性质的更多信息,很难说。
                    【解决方案19】:

                    如果可能,我更喜欢使用普通人不太可能输入的字符组合作为分隔符。例如,我使用了 ")^&^(" 并在我的代码中将其设置为 const "cDelimiter";然后将我的所有字段与它连接起来。通过使用一个小的唯一字符串,我大大减少了可能的引擎盖用户不小心输入我的分隔符。用户输入 | 或 ~ 的可能性诚然不太可能,但这并不意味着它不会发生。

                    【讨论】:

                    • Ewww,我绝对不会很乐意使用强制我在值之间键入“)^&^(”的 UI。使用它来分隔您自己的数据,但为用户输入走这条路线是一条糟糕的路。
                    • 我的回答假设分隔是由代码本身完成的,而不是留给用户来分隔字段。我的回答的重点是永远不会期望用户输入我的示例中提供的字符串。
                    【解决方案20】:

                    使用选项卡(或者可能是 \n) - 如果用户输入它会导致文本框退出。

                    【讨论】:

                      【解决方案21】:

                      我还支持选择 TAB (\t) 和某种程度上的 PIPE (|) 符号。

                      但在我的经验中,最常用的是分号 (;) 以及带引号的字段以及 \ 和 \" 的转义符,这非常完美。只需要一个解析器来保持状态。实际的分隔字符变得不重要了。

                      如果您不使用转义,明智的做法是计算每行的“字段”并将它们与您的预期结果进行比较。由于此类文件的大多数应用程序都使用某种固定数量的字段,因此您可以在条目中捕获错误并获得如果它不触发,一切都很好。

                      【讨论】:

                        【解决方案22】:

                        我知道这个回复已经很晚了,但我不久前就遇到了这个问题,并且很好地解决了这个问题(恕我直言)。希望将来,这将有助于其他人寻找类似问题的答案。

                        虽然我通常会将自己置于类似于 Mike Ottum、John Saunders 和 Mark Brackett 的阵营中,但简单的事实是,有时我们的开发人员不得不做一些我们不愿意做的事情。我的特殊情况需要提供一个(大部分)人类可读的“id”,以在从对象的有机复合键派生的 RESTful URI 中使用。二进制或 XML 序列化并不是一个真正的选择。所以?我选择尽可能少地重新发明轮子。 System.Text.RegularExpressions.Regex 类具有对那些疯狂的正则表达式模式进行操作的转义/取消转义方法。有一些可逃避的字符可供选择。我选择了管道('|')字符。

                        这是我的实现(分类为可重用,但如果你喜欢滚动,你可以为 7 行“内联”解决方案开辟出好的部分):

                        using System;
                        using System.Collections.Generic;
                        using System.Text.RegularExpressions;
                        
                        namespace RPlus.DTO
                        {
                            /// <summary>
                            /// Provide safe string un/concatenating
                            /// </summary>
                            static class Glob
                            {
                                // a Regex Split param that basically says:
                                // Split on the pipe char unless the preceeding char is a backslash
                                private const string _splitterer = @"(?<!\\)\|";
                                // no explanation needed (hopefully)
                                private const char _delimiter = '|';
                        
                                /// <summary>
                                /// Produce a properly escaped concatenation
                                /// from some number of strings
                                /// </summary>
                                /// <param name="items">strings to escape/concate</param>
                                /// <returns>an escaped concatenation of items</returns>
                                public static string To(IEnumerable<string> items)
                                {
                                    var escapedItems = new List<string>();
                                    foreach (var s in items) escapedItems.Add(Regex.Escape(s));
                                    return string.Join(_delimiter.ToString(), escapedItems);
                                }
                        
                                /// <summary>
                                /// Unconcatenate/unescape a string into its original strings
                                /// </summary>
                                /// <param name="globbedValue">
                                /// A value returned from Glob.To()
                                /// </param>
                                /// <returns>
                                /// The orignal strings used to construct the globbedValue
                                /// </returns>
                                public static List<string> From(string globbedValue)
                                {
                                    return From(globbedValue, default(int?));
                                }
                        
                                /// <summary>
                                /// Unconcatenate/unescape a string into its original strings
                                /// </summary>
                                /// <param name="globbedValue">
                                /// A value returned from Glob.To()
                                /// </param>
                                /// <param name="expectedTokens">
                                /// The number of string tokens that 
                                /// should be found in the concatenation
                                /// </param>
                                /// <returns>
                                /// The orignal strings used to construct the globbedValue
                                /// </returns>
                                public static List<string> From(string value, int? expectedTokens)
                                {
                                    var nugs = Regex.Split(value, _splitterer);
                                    if (expectedTokens.HasValue && nugs.Length != expectedTokens.Value)
                                        throw new ArgumentException("Unexpected number of tokens");
                                    var unescapedItems = new List<string>();
                                    foreach (var s in nugs) unescapedItems.Add(Regex.Unescape(s));
                                    return unescapedItems;
                                }
                        
                            }
                        }
                        

                        下面是一些示例用法:

                        var glob = Glob.To(new string[] { "Foo|Bar", "Bar|Baz", "Baz|Qux" });
                        var orig = Glob.From(glob);
                        

                        CAVEAT:请不要试图寻找“用户永远不会输入的字符”作为连接字符串的分隔符。用户最终会输入它。那里已经有足够多的“神奇数字”代码等待爆炸。对于这个问题,有多种久经考验的解决方案。

                        【讨论】:

                          【解决方案23】:

                          为什么不使用 ASCII 代码 31(单位分隔符),它专门用于分隔字符串中的数据元素?

                          普通人不可能输入该字符,使用程序员工具的人很难意外输入它,因此您不必担心在极端情况下转义。

                          【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 2018-12-21
                            • 1970-01-01
                            相关资源
                            最近更新 更多