【问题标题】:string with quotes from database, cannot do interpolation带有数据库引号的字符串,不能进行插值
【发布时间】:2020-02-11 12:52:42
【问题描述】:

我在 C# 中遇到了字符串问题。我从数据库中得到这个字符串,它包含引号 ("),所以我的程序没有正确读取它。

string attributes = databaseFunctions.GetVariationAttributes(produc_id);

我在数据库中的字符串是

a:3:{s:9:"variation";a:6:{s:4:"name";s:9:"Variation";s:5:"value";s:24:"type a | type b | type c";s:8:"position";s:1:"0";s:10:"is_visible";s:1:"1";s:12:"is_variation";s:1:"1";s:11:"is_taxonomy";s:1:"0";}s:5:"color";a:6:{s:4:"name";s:5:"Color";s:5:"value";s:27:"RED | BLUE | WHITE | ORANGE";s:8:"position";s:1:"1";s:10:"is_visible";s:1:"1";s:12:"is_variation";s:1:"1";s:11:"is_taxonomy";s:1:"0";}s:4:"test";a:6:{s:4:"name";s:4:"TEST";s:5:"value";s:15:"120 | 140 | 160";s:8:"position";s:1:"2";s:10:"is_visible";s:1:"1";s:12:"is_variation";s:1:"0";s:11:"is_taxonomy";s:1:"0";}}

这实际上是 Woocommerce 产品变体属性。我需要获取每个属性,检查它是否用于变体,如果是,获取它的名称和可能的值。

也许你知道怎么做?我正在尝试使用子字符串和 IndexOf 函数(获取第一个和第二个冒号的索引,然后从它们之间获取值并在循环中使用它)

我会感谢您的任何建议

[编辑]

好的,我做到了。这不是最漂亮的解决方案,但它有效。我把它贴在这里,所以其他人可能会在类似的情况下使用它

if(databaseFunctions.CheckVariations(variations))
        {
            string attributes = databaseFunctions.GetVariationAttributes(produc_id);

            List<List<string>> parameters = new List<List<string>>();
            List<List<string>> values = new List<List<string>>();

            int i_1 = 0;
            int i_2 = 0;

            //First ':' 
            int c_1 = attributes.IndexOf(':');

            //Second ':'
            int c_2 = attributes.IndexOf(':', c_1 + 1);

            //First 'a' - number of attributes
            int a_1 = Convert.ToInt32(attributes.Substring(c_1 + 1, c_2-c_1 -1));

            //For each attribute
            for (int i = 0; i < a_1; i++)
            {

                List<string> parameters_of_attribute = new List<string>();
                List<string> values_of_attribute = new List<string>();

                //First ':'
                int ac_1 = attributes.IndexOf(':', c_2 + 1 + i_1);

                //Second ':'
                int ac_2 = attributes.IndexOf(':', ac_1 + 1);

                //First ':' of a 
                int kc_1 = attributes.IndexOf(':', ac_2 + 1);

                //Second ':' of a
                int kc_2 = attributes.IndexOf(':', kc_1 + 1);

                //Number of parameter-value pairs
                int p_v = Convert.ToInt32(attributes.Substring(kc_1 + 1, kc_2 - kc_1 - 1));


                //For each parameter-value pair
                for (int j = 0; j < p_v; j++)
                {
                    //First '"' of parameter
                    int pq_1 = attributes.IndexOf('"', kc_2 + 1 + i_2);

                    //Second '"' of parameter
                    int pq_2 = attributes.IndexOf('"', pq_1 + 1);

                    //Name of parameter
                    string par = attributes.Substring(pq_1 + 1, pq_2 - pq_1 - 1);

                    //First '"' of value
                    int vq_1 = attributes.IndexOf('"', pq_2 + 1);

                    //Second '"' of value
                    int vq_2 = attributes.IndexOf('"', vq_1 + 1);

                    //Value of parameter
                    string val = attributes.Substring(vq_1 + 1, vq_2 - vq_1 - 1);

                    parameters_of_attribute.Add(par);
                    values_of_attribute.Add(val);

                    i_2 = vq_2 - kc_2;
                }
                parameters.Add(parameters_of_attribute);
                values.Add(values_of_attribute);
                i_1 = i_2 + kc_2 - c_2;
                i_2 = 0;
            }
        }

【问题讨论】:

  • 该数据应该是一个单独的表或 5 个,而不是一个字符串。但我会假设改变不是那么容易?
  • 不可能,这是标准的 WordPress 格式。而且我不是只为一个基础制作应用程序,而是为通用基础制作应用程序,因此它必须使用本机格式
  • 编写一个词法分析器和一个解析器。这样做并不难,而且您将有一个正确的解决方案,而不是一些乱七八糟的子字符串和索引。
  • 这是 PHP 序列化的输出。格式描述为here。可能已经有 C# 代码浮动在那里专门反序列化它,尽管推荐外部库对于 SO 来说是题外话。

标签: c#


【解决方案1】:

Gordon Breuer 在https://gordon-breuer.de/unknown/2011/05/04/php-un-serialize-with-c.html 发布了使用 C# 读取 PHP 的序列化数据格式的代码 - 不幸的是,这还不能作为 NuGet 包提供(还没有!) - it's based off dinosaur code written for .NET Framework 1.0 back in 2001

为了保存,我将重复以下代码并提供额外的版权/来源信息(博客不会永远存在)。根据原始 SourceForge 项目的页面,代码在 OSI 批准的 IBM CPL 下获得许可。

/*
 * Serializer.cs
 * This is the Serializer class for the PHPSerializationLibrary
 * 
 * https://sourceforge.net/projects/csphpserial/
 *  
 * Copyright 2004 Conversive, Inc. Licensed under Common Public License 1.0
 * 
 * Modified for WP7-Silverlight by Gordon Breuer
 * https://gordon-breuer.de/unknown/2011/05/04/php-un-serialize-with-c.html
 *
 * Updated to be thread-safe and correctly reentrant and with C# 7.0 features added for https://stackoverflow.com/questions/58384540/c-sharp-string-with-from-database-cannot-do-interpolation/
 * 
 */

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;

namespace Conversive.PHPSerializationLibrary {


    public class PhpSerializer
    {

        //types:
        // N = null
        // s = string
        // i = int
        // d = double
        // a = array (hashtable)

        private static readonly NumberFormatInfo _nfi = new NumberFormatInfo { NumberGroupSeparator = "", NumberDecimalSeparator = "." };

        public Encoding StringEncoding { get; }

        /// <summary>Whether or not to strip carriage returns from strings when serializing and adding them back in when deserializing</summary>
        public Boolean XmlSafe { get; }

        public PhpSerializer()
            : this( encoding: new UTF8Encoding(), xmlSafe: true )
        {
        }

        public PhpSerializer( Encoding encoding, Boolean xmlSafe )
        {
            this.StringEncoding = encoding ?? throw new ArgumentNullException(nameof(encoding));
            this.XmlSafe        = xmlSafe;
        }

        private class SerializeState
        {
            public readonly StringBuilder sb = new StringBuilder();

            // These collections used for cycle-detection.
            public readonly HashSet<Object> seenCollections = new HashSet<Object>();
        }

        public String Serialize(object obj)
        {
            SerializeState state = new SerializeState();

            this.Serialize(obj, state );

            return state.sb.ToString();
        }

        //Serialize(object obj)

        private void Serialize( Object obj, SerializeState state )
        {
            StringBuilder sb = state.sb;

            if (obj == null)
            {
                sb.Append("N;");
            }
            else if (obj is string str)
            {
                if (XmlSafe)
                {
                    str = str.Replace("rn", "n"); //replace rn with n
                    str = str.Replace("r", "n"); //replace r not followed by n with a single n  Should we do this?
                }
                sb.Append("s:" + StringEncoding.GetByteCount(str) + ":" + str + ";");
            }
            else if (obj is bool b)
            {
                sb.Append("b:" + ( b ? "1" : "0") + ";");       
            }
            else if (obj is int objInt)
            {
                sb.Append("i:" + objInt.ToString(_nfi) + ";");
            }
            else if (obj is double objDbl)
            {
                sb.Append("d:" + objDbl.ToString(_nfi) + ";");
            }
            else if ( (obj is IReadOnlyDictionary<object, object> ) || ( obj is IDictionary<object, object> ) )
            {
                if (state.seenCollections.Contains(obj))
                {
                    sb.Append("N;");
                }
                else
                {
                    state.seenCollections.Add(obj);

                    IEnumerable<KeyValuePair<Object,Object>> asDictEnum = (IEnumerable<KeyValuePair<Object,Object>>)obj;

                    sb.Append("a:" + asDictEnum.Count() + ":{");
                    foreach (var entry in asDictEnum)
                    {
                        this.Serialize(entry.Key, state);
                        this.Serialize(entry.Value, state);
                    }
                    sb.Append("}");
                }
            }
            else if (obj is IEnumerable<object> enumerable)
            {
                if (state.seenCollections.Contains( enumerable ) )
                {
                    sb.Append("N;");
                }
                else
                {
                    state.seenCollections.Add(enumerable);

                    IReadOnlyList<Object> asList = enumerable as IReadOnlyList<Object>; // T[] / Array<T> implements IReadOnlyList<T> already.
                    if( asList == null ) asList = enumerable.ToList();

                    sb.Append("a:" + asList.Count + ":{");

                    Int32 i = 0;
                    foreach( Object item in asList )
                    {
                        this.Serialize(i, state);
                        this.Serialize( item, state);

                        i++;
                    }

                    sb.Append("}");
                }
            }
            else
            {
                throw new SerializationException( "Value could not be serialized." );
            }
        }

        public Object Deserialize(String str)
        {
            Int32 pos = 0;
            return this.Deserialize(str, ref pos ); 
        }

        private Object Deserialize( String str, ref Int32 pos )
        {
            if( String.IsNullOrWhiteSpace( str ) )
                return new Object();

            int start, end, length;
            string stLen;
            switch( str[pos] )
            {
            case 'N':
                pos += 2;
                return null;
            case 'b':
                char chBool = str[pos + 2];
                pos += 4;
                return chBool == '1';
            case 'i':
                start = str.IndexOf(":", pos) + 1;
                end = str.IndexOf(";", start);
                var stInt = str.Substring(start, end - start);
                pos += 3 + stInt.Length;
                return Int32.Parse(stInt, _nfi);
            case 'd':
                start = str.IndexOf(":", pos) + 1;
                end = str.IndexOf(";", start);
                var stDouble = str.Substring(start, end - start);
                pos += 3 + stDouble.Length;
                return Double.Parse(stDouble, _nfi);
            case 's':
                start = str.IndexOf(":", pos) + 1;
                end = str.IndexOf(":", start);
                stLen = str.Substring(start, end - start);
                var bytelen = Int32.Parse(stLen);
                length = bytelen;
                if ((end + 2 + length) >= str.Length) length = str.Length - 2 - end;
                var stRet = str.Substring(end + 2, length);
                while (StringEncoding.GetByteCount(stRet) > bytelen)
                {
                    length--;
                    stRet = str.Substring(end + 2, length);
                }
                pos += 6 + stLen.Length + length;
                if (XmlSafe) stRet = stRet.Replace("n", "rn");
                return stRet;
            case 'a':
                start = str.IndexOf(":", pos) + 1;
                end = str.IndexOf(":", start);
                stLen = str.Substring(start, end - start);
                length = Int32.Parse(stLen);
                var htRet = new Dictionary<object, object>(length);
                var alRet = new List<object>(length);
                pos += 4 + stLen.Length; //a:Len:{
                for (int i = 0; i < length; i++)
                {
                    var key = this.Deserialize(str, ref pos);
                    var val = this.Deserialize(str, ref pos);

                    if (alRet != null)
                    {
                        if (key is int && (int)key == alRet.Count)
                            alRet.Add(val);
                        else
                            alRet = null;
                    }
                    htRet[key] = val;
                }
                pos++;
                if( pos < str.Length && str[pos] == ';')
                {
                    pos++;
                }

                return alRet != null ? (object)alRet : htRet;
            default:
                return "";
            }
        }
    }

}

当我使用您的输入在 Linqpad 中运行它时,我会得到以下结果输出:

void Main()
{
    const String attribs = @"a:3:{s:9:""variation"";a:6:{s:4:""name"";s:9:""Variation"";s:5:""value"";s:24:""type a | type b | type c"";s:8:""position"";s:1:""0"";s:10:""is_visible"";s:1:""1"";s:12:""is_variation"";s:1:""1"";s:11:""is_taxonomy"";s:1:""0"";}s:5:""color"";a:6:{s:4:""name"";s:5:""Color"";s:5:""value"";s:27:""RED | BLUE | WHITE | ORANGE"";s:8:""position"";s:1:""1"";s:10:""is_visible"";s:1:""1"";s:12:""is_variation"";s:1:""1"";s:11:""is_taxonomy"";s:1:""0"";}s:4:""test"";a:6:{s:4:""name"";s:4:""TEST"";s:5:""value"";s:15:""120 | 140 | 160"";s:8:""position"";s:1:""2"";s:10:""is_visible"";s:1:""1"";s:12:""is_variation"";s:1:""0"";s:11:""is_taxonomy"";s:1:""0"";}}";

    PhpSerializer s = new PhpSerializer();
    Object result = s.Deserialize( attribs ); 
    result.Dump();
}

可视化输出:

【讨论】:

    猜你喜欢
    • 2013-04-08
    • 1970-01-01
    • 2015-07-01
    • 1970-01-01
    • 2019-02-04
    • 2020-03-30
    • 1970-01-01
    • 2015-02-15
    • 2022-07-06
    相关资源
    最近更新 更多