1

I have a problem with string in C#. I get this string from database, and it contains quote signs ("), so my program doesn't read it correctly.

string attributes = databaseFunctions.GetVariationAttributes(produc_id);

My string in the database is

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";}}

This is actually the Woocommerce product variation attributes. I need to get each attribute, check if it is used for variations, and if yes, get its name and possible values.

Maybe you have any idea how to do it? I'm trying to use substrings and IndexOf functions (Get index of the first and second colon, then get value from between them and use it in a loop)

I will appreciate any of your advice

[EDIT]

Ok, I did it. It's not the prettiest solution, but it works. I post it here, so others may use it in a similar situation

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;
            }
        }
Erik Philips
  • 53,428
  • 11
  • 128
  • 150
  • That data should be a seperate table or 5, not a string. But I am going to asume it is not that easy to change? – Christopher Oct 14 '19 at 22:01
  • It's impossible, that's the standard WordPress format. And I'm not making app for only one base, but for generic ones, so it has to use the native format – Maciek Nawrocki Oct 14 '19 at 22:04
  • 3
    **Write a lexer and a parser**. It's not hard to do so, and you'll have a correct solution rather than some hacked up mess of substrings and indexes. – Eric Lippert Oct 14 '19 at 22:10
  • 1
    This is output from PHP serialization. The format is described [here](https://stackoverflow.com/a/14298662/4137916). There's likely already C# code floating out there to deserialize this specifically, though recommending external libraries is off-topic for SO. – Jeroen Mostert Oct 14 '19 at 22:11

1 Answers1

0

Gordon Breuer published code to read PHP's serialized data format using C# at https://gordon-breuer.de/unknown/2011/05/04/php-un-serialize-with-c.html - unfortunately this is not available as a NuGet package (yet!) - it's based off dinosaur code written for .NET Framework 1.0 back in 2001.

I'm repeating the code below for preservation's sake with extra copyright/origin info (Blogs aren't around forever). According to the original SourceForge project's page the code is licensed under the OSI-approved 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 "";
            }
        }
    }

}

When I run this in Linqpad with your input I get this result output:

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();
}

Visualized output:

enter image description here

Dai
  • 141,631
  • 28
  • 261
  • 374