সি # সহ সংক্ষেপণ / ডিকম্প্রেশন স্ট্রিং


144

আমি নেট মধ্যে নবাগত। আমি সি # তে সংক্ষেপণ এবং ডিকম্প্রেশন স্ট্রিং করছি। এখানে একটি এক্সএমএল আছে এবং আমি স্ট্রিংয়ে রূপান্তর করছি এবং এর পরে আমি সংক্ষেপণ এবং ডিকম্প্রেশন করছি T আমার কোডটিতে কোনও সংকলন ত্রুটি নেই যখন আমি আমার কোডটি সংক্ষেপণ করি এবং আমার স্ট্রিংটি ফিরিয়ে দিই, এটি এক্সএমএলের ফিরে আসা মাত্র অর্ধেক।

নীচে আমার কোডটি রয়েছে, দয়া করে যেখানে আমি ভুল তা সংশোধন করুন।

কোড:

class Program
{
    public static string Zip(string value)
    {
        //Transform string into byte[]  
        byte[] byteArray = new byte[value.Length];
        int indexBA = 0;
        foreach (char item in value.ToCharArray())
        {
            byteArray[indexBA++] = (byte)item;
        }

        //Prepare for compress
        System.IO.MemoryStream ms = new System.IO.MemoryStream();
        System.IO.Compression.GZipStream sw = new System.IO.Compression.GZipStream(ms, System.IO.Compression.CompressionMode.Compress);

        //Compress
        sw.Write(byteArray, 0, byteArray.Length);
        //Close, DO NOT FLUSH cause bytes will go missing...
        sw.Close();

        //Transform byte[] zip data to string
        byteArray = ms.ToArray();
        System.Text.StringBuilder sB = new System.Text.StringBuilder(byteArray.Length);
        foreach (byte item in byteArray)
        {
            sB.Append((char)item);
        }
        ms.Close();
        sw.Dispose();
        ms.Dispose();
        return sB.ToString();
    }

    public static string UnZip(string value)
    {
        //Transform string into byte[]
        byte[] byteArray = new byte[value.Length];
        int indexBA = 0;
        foreach (char item in value.ToCharArray())
        {
            byteArray[indexBA++] = (byte)item;
        }

        //Prepare for decompress
        System.IO.MemoryStream ms = new System.IO.MemoryStream(byteArray);
        System.IO.Compression.GZipStream sr = new System.IO.Compression.GZipStream(ms,
            System.IO.Compression.CompressionMode.Decompress);

        //Reset variable to collect uncompressed result
        byteArray = new byte[byteArray.Length];

        //Decompress
        int rByte = sr.Read(byteArray, 0, byteArray.Length);

        //Transform byte[] unzip data to string
        System.Text.StringBuilder sB = new System.Text.StringBuilder(rByte);
        //Read the number of bytes GZipStream red and do not a for each bytes in
        //resultByteArray;
        for (int i = 0; i < rByte; i++)
        {
            sB.Append((char)byteArray[i]);
        }
        sr.Close();
        ms.Close();
        sr.Dispose();
        ms.Dispose();
        return sB.ToString();
    }

    static void Main(string[] args)
    {
        XDocument doc = XDocument.Load(@"D:\RSP.xml");
        string val = doc.ToString(SaveOptions.DisableFormatting);
        val = Zip(val);
        val = UnZip(val);
    }
} 

আমার এক্সএমএল আকারটি 63 কেবি।


1
আমি সন্দেহ করি যে যদি ইউটিএফ 8 এনকোডিং (বা ইউটিএফ 16 বা হোয়াট নোট) এবং গেটবাইটস / গেটস্ট্রিং ব্যবহার না করা হয় তবে সমস্যাটি "নিজেই ঠিক হয়ে যাবে" । এটি কোডকে ব্যাপকভাবে সরল করবে। ব্যবহার করার পরামর্শ দিন using

আপনি চরকে বাইট এবং আপনার মতো বিপরীতে রূপান্তর করতে পারবেন না (একটি সাধারণ castালাই ব্যবহার করে)। সংক্ষেপণ / সংক্ষেপনের জন্য আপনাকে একটি এনকোডিং এবং একই এনকোডিং ব্যবহার করতে হবে। নীচে xanatos উত্তর দেখুন।
সাইমন মউরিয়ার

@pst না এটি করবে না; আপনি Encodingচারপাশে ভুল উপায় ব্যবহার করা হবে। Xanatos এর উত্তর অনুসারে আপনার এখানে বেস-64৪ প্রয়োজন
মার্ক গ্র্যাভেল

@ মার্ক গ্র্যাভেল ট্রু, স্বাক্ষর / অভিপ্রায়টির সেই অংশটি মিস করেছেন। অবশ্যই স্বাক্ষরগুলির আমার প্রথম পছন্দ নয়।

উত্তর:


257

একটি স্ট্রিং সংকোচন / সংক্ষেপণ করতে কোড

public static void CopyTo(Stream src, Stream dest) {
    byte[] bytes = new byte[4096];

    int cnt;

    while ((cnt = src.Read(bytes, 0, bytes.Length)) != 0) {
        dest.Write(bytes, 0, cnt);
    }
}

public static byte[] Zip(string str) {
    var bytes = Encoding.UTF8.GetBytes(str);

    using (var msi = new MemoryStream(bytes))
    using (var mso = new MemoryStream()) {
        using (var gs = new GZipStream(mso, CompressionMode.Compress)) {
            //msi.CopyTo(gs);
            CopyTo(msi, gs);
        }

        return mso.ToArray();
    }
}

public static string Unzip(byte[] bytes) {
    using (var msi = new MemoryStream(bytes))
    using (var mso = new MemoryStream()) {
        using (var gs = new GZipStream(msi, CompressionMode.Decompress)) {
            //gs.CopyTo(mso);
            CopyTo(gs, mso);
        }

        return Encoding.UTF8.GetString(mso.ToArray());
    }
}

static void Main(string[] args) {
    byte[] r1 = Zip("StringStringStringStringStringStringStringStringStringStringStringStringStringString");
    string r2 = Unzip(r1);
}

যে মনে রাখুন Zipআয় একটি byte[], যখন Unzipআয় একটি string। আপনি যদি স্ট্রিং চান তবে আপনার Zipবেস 64 এটি এনকোড করতে পারে (উদাহরণস্বরূপ ব্যবহার করে Convert.ToBase64String(r1)) (এর ফলাফলটি Zipখুব বাইনারি হয়! এটি আপনি স্ক্রিনে মুদ্রণ করতে পারেন বা সরাসরি এক্সএমএলে লিখতে পারেন)

প্রস্তাবিত সংস্করণটি .NET 2.0 এর জন্য। নেট 4.0 ব্যবহার করুন MemoryStream.CopyTo

গুরুত্বপূর্ণ: সংকুচিত সামগ্রীগুলি আউটপুট স্ট্রিমে লেখা যায় না যতক্ষণ না এটি GZipStreamজানে যে এটির সমস্ত ইনপুট রয়েছে (অর্থাত্ কার্যকরভাবে সংকুচিত করতে এর জন্য সমস্ত ডেটা প্রয়োজন)। আপনি নিশ্চিত যে করতে হবে Dispose()এর GZipStreamআউটপুট স্ট্রিম পরিদর্শন সামনে (যেমন, mso.ToArray())। using() { }উপরের ব্লকটি দিয়ে এটি করা হয় । মনে রাখবেন যে এটি GZipStreamহ'ল অভ্যন্তরীণ ব্লক এবং সামগ্রীর বাইরে এটি অ্যাক্সেস করা হয়েছে। একইভাবে ডিকম্প্রেসিংয়ের জন্য যায়: ডেটা অ্যাক্সেস করার চেষ্টা করার আগে Dispose()ofGZipStream


উত্তরের জন্য আপনাকে ধন্যবাদ W আমি যখন আপনার কোডটি ব্যবহার করি, এটি আমাকে সংকলন ত্রুটি দিচ্ছে " এর পরে আমি গুগলে অনুসন্ধান করেছি এবং এটিকে। নেট 4 ফ্রেমওয়ার্কের কপিটো () অংশটি জমা করি। তবে আমি। নেট 2.0 এবং 3.5 ফ্রেমওয়ার্কে কাজ করছি। দয়া করে আমাকে পরামর্শ দিন :) :)
মোহিত কুমার

আমি কেবল জোর দিয়ে বলতে চাই যে আউটপুট স্ট্রিমে ToArray () কল করার আগে GZipstream অবশ্যই নিষ্পত্তি করতে হবে। আমি সেই বিটটিকে অগ্রাহ্য করেছি, তবে এটি একটি পার্থক্য করে!
ভেজা নুডলস

1
.NET 4.5 এ জিপ করার এই সবচেয়ে কার্যকর উপায়?
মনস্টার এমএমআরপিজি

1
নোট করুন যে সরোগেট জোড় যুক্ত স্ট্রিংয়ের ক্ষেত্রে এটি ব্যর্থ (আনজিপড স্ট্রিং! = আসল) string s = "X\uD800Y"। আমি লক্ষ্য করেছি যে আমরা যদি এনকোডিংটিকে ইউটিএফ 7 তে পরিবর্তন করি তবে এটি কাজ করে ... তবে ইউটিএফ 7 এর সাথে আমরা কি নিশ্চিত যে সমস্ত অক্ষর উপস্থাপন করা যায়?
digEmAll

@ ডিগেম সমস্ত আমি বলব ইনভালাইড সারোগেট জোড় (আপনার ক্ষেত্রে যেমন রয়েছে) থাকলে এটি কাজ করে না। ইউটিএফ 8 গেটবাইজ রূপান্তরটি নিঃশব্দে 0xFFFD এর সাথে অবৈধ সারোগেট জুটি প্রতিস্থাপন করে।
xanatos

103

এই স্নিপেট অনুযায়ী আমি এই কোডটি ব্যবহার করি এবং এটি ভাল কাজ করছে:

using System;
using System.IO;
using System.IO.Compression;
using System.Text;

namespace CompressString
{
    internal static class StringCompressor
    {
        /// <summary>
        /// Compresses the string.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <returns></returns>
        public static string CompressString(string text)
        {
            byte[] buffer = Encoding.UTF8.GetBytes(text);
            var memoryStream = new MemoryStream();
            using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
            {
                gZipStream.Write(buffer, 0, buffer.Length);
            }

            memoryStream.Position = 0;

            var compressedData = new byte[memoryStream.Length];
            memoryStream.Read(compressedData, 0, compressedData.Length);

            var gZipBuffer = new byte[compressedData.Length + 4];
            Buffer.BlockCopy(compressedData, 0, gZipBuffer, 4, compressedData.Length);
            Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, gZipBuffer, 0, 4);
            return Convert.ToBase64String(gZipBuffer);
        }

        /// <summary>
        /// Decompresses the string.
        /// </summary>
        /// <param name="compressedText">The compressed text.</param>
        /// <returns></returns>
        public static string DecompressString(string compressedText)
        {
            byte[] gZipBuffer = Convert.FromBase64String(compressedText);
            using (var memoryStream = new MemoryStream())
            {
                int dataLength = BitConverter.ToInt32(gZipBuffer, 0);
                memoryStream.Write(gZipBuffer, 4, gZipBuffer.Length - 4);

                var buffer = new byte[dataLength];

                memoryStream.Position = 0;
                using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Decompress))
                {
                    gZipStream.Read(buffer, 0, buffer.Length);
                }

                return Encoding.UTF8.GetString(buffer);
            }
        }
    }
}

2
আমি এই কোডটি পোস্ট করার জন্য আপনাকে ধন্যবাদ জানাতে চাইছিলাম। আমি এটিকে আমার প্রকল্পে ফেলে দিয়েছি এবং এটি বাকী থেকে ঠিক কোনও সমস্যা ছাড়াই কাজ করেছে।
বোল্টবাইট

3
ইউপ বক্সের বাইরে কাজ করছে! আমি প্রথম চারটি বাইট হিসাবে দৈর্ঘ্য যুক্ত করার ধারণাটিও পছন্দ করেছি
জাস্টাডেভ

2
এটি সেরা উত্তর। এই উত্তর হিসাবে চিহ্নিত করা উচিত!
ইরিয়াওয়ান কুসুমাওয়ারধোন

1
@ ম্যাট এটি একটি জিপ ফাইল জিপ করার মতো - .png ইতিমধ্যে একটি সংকুচিত সামগ্রী
ফুবু

2
উত্তর হিসাবে চিহ্নিত হিসাবে চিহ্নিত উত্তর স্থিতিশীল নয়। এই এক সেরা উত্তর।
শাড়ি

38

স্ট্রিম.কপিটো () পদ্ধতিগুলির সাথে .NET 4.0 (এবং উচ্চতর) এর আবির্ভাবের সাথে আমি ভেবেছিলাম যে আমি একটি আপডেট পদ্ধতির পোস্ট করব।

আমি আরও মনে করি যে বেসওড 64 এনকোডযুক্ত স্ট্রিংগুলিতে নিয়মিত স্ট্রিং সংকোচনের জন্য স্ব-অন্তর্ভুক্ত শ্রেণীর সুস্পষ্ট উদাহরণ হিসাবে নীচের সংস্করণটি কার্যকর এবং এর বিপরীতে:

public static class StringCompression
{
    /// <summary>
    /// Compresses a string and returns a deflate compressed, Base64 encoded string.
    /// </summary>
    /// <param name="uncompressedString">String to compress</param>
    public static string Compress(string uncompressedString)
    {
        byte[] compressedBytes;

        using (var uncompressedStream = new MemoryStream(Encoding.UTF8.GetBytes(uncompressedString)))
        {
            using (var compressedStream = new MemoryStream())
            { 
                // setting the leaveOpen parameter to true to ensure that compressedStream will not be closed when compressorStream is disposed
                // this allows compressorStream to close and flush its buffers to compressedStream and guarantees that compressedStream.ToArray() can be called afterward
                // although MSDN documentation states that ToArray() can be called on a closed MemoryStream, I don't want to rely on that very odd behavior should it ever change
                using (var compressorStream = new DeflateStream(compressedStream, CompressionLevel.Fastest, true))
                {
                    uncompressedStream.CopyTo(compressorStream);
                }

                // call compressedStream.ToArray() after the enclosing DeflateStream has closed and flushed its buffer to compressedStream
                compressedBytes = compressedStream.ToArray();
            }
        }

        return Convert.ToBase64String(compressedBytes);
    }

    /// <summary>
    /// Decompresses a deflate compressed, Base64 encoded string and returns an uncompressed string.
    /// </summary>
    /// <param name="compressedString">String to decompress.</param>
    public static string Decompress(string compressedString)
    {
        byte[] decompressedBytes;

        var compressedStream = new MemoryStream(Convert.FromBase64String(compressedString));

        using (var decompressorStream = new DeflateStream(compressedStream, CompressionMode.Decompress))
        {
            using (var decompressedStream = new MemoryStream())
            {
                decompressorStream.CopyTo(decompressedStream);

                decompressedBytes = decompressedStream.ToArray();
            }
        }

        return Encoding.UTF8.GetString(decompressedBytes);
    }

স্ট্রিং কম্প্রেশন এবং ডিকম্প্রেশন যুক্ত করতে স্ট্রিং ক্লাসটি বাড়ানোর জন্য এক্সটেনশন পদ্ধতির কৌশলটি ব্যবহার করে এখানে আরেকটি পদ্ধতি অবলম্বন করা হল। আপনি কোনও বিদ্যমান প্রকল্পের নীচে ক্লাসটি ফেলে দিতে পারেন এবং এরপরে এভাবে ব্যবহার করতে পারেন:

var uncompressedString = "Hello World!";
var compressedString = uncompressedString.Compress();

এবং

var decompressedString = compressedString.Decompress();

বুদ্ধিমান:

public static class Extensions
{
    /// <summary>
    /// Compresses a string and returns a deflate compressed, Base64 encoded string.
    /// </summary>
    /// <param name="uncompressedString">String to compress</param>
    public static string Compress(this string uncompressedString)
    {
        byte[] compressedBytes;

        using (var uncompressedStream = new MemoryStream(Encoding.UTF8.GetBytes(uncompressedString)))
        {
            using (var compressedStream = new MemoryStream())
            { 
                // setting the leaveOpen parameter to true to ensure that compressedStream will not be closed when compressorStream is disposed
                // this allows compressorStream to close and flush its buffers to compressedStream and guarantees that compressedStream.ToArray() can be called afterward
                // although MSDN documentation states that ToArray() can be called on a closed MemoryStream, I don't want to rely on that very odd behavior should it ever change
                using (var compressorStream = new DeflateStream(compressedStream, CompressionLevel.Fastest, true))
                {
                    uncompressedStream.CopyTo(compressorStream);
                }

                // call compressedStream.ToArray() after the enclosing DeflateStream has closed and flushed its buffer to compressedStream
                compressedBytes = compressedStream.ToArray();
            }
        }

        return Convert.ToBase64String(compressedBytes);
    }

    /// <summary>
    /// Decompresses a deflate compressed, Base64 encoded string and returns an uncompressed string.
    /// </summary>
    /// <param name="compressedString">String to decompress.</param>
    public static string Decompress(this string compressedString)
    {
        byte[] decompressedBytes;

        var compressedStream = new MemoryStream(Convert.FromBase64String(compressedString));

        using (var decompressorStream = new DeflateStream(compressedStream, CompressionMode.Decompress))
        {
            using (var decompressedStream = new MemoryStream())
            {
                decompressorStream.CopyTo(decompressedStream);

                decompressedBytes = decompressedStream.ToArray();
            }
        }

        return Encoding.UTF8.GetString(decompressedBytes);
    }

2
জেস: আমি মনে করি আপনি usingমেমরি স্ট্রিমের উদাহরণগুলির জন্য বিবৃতি অনুপস্থিত । এবং সেখানে এফ # বিকাশকারীদের কাছে: useToArray()
সংক্ষেপক স্ট্রিম

1
এটি কিছু অতিরিক্ত বৈধতা যুক্ত করার সাথে সাথে কি জিজেপস্ট্রিম ব্যবহার করা আরও ভাল হবে? জিজেপস্ট্রিম বা ডিফলেট স্ট্রিম ক্লাস?
মাইকেল ফ্রেইজিম

2
@ মিশেল ফ্রেইজিম মেমরি স্ট্রিমগুলি সংকোচনের এবং সঙ্কোচিত করার জন্য আমি এটি মনে করি না। ফাইল, বা অবিশ্বাস্য পরিবহনের জন্য এটি অর্থবোধ করে। আমি বলব যে আমার নির্দিষ্ট ব্যবহারের ক্ষেত্রে উচ্চ গতিটি খুব আকাঙ্ক্ষিত তাই যে কোনও ওভারহেড আমি এড়াতে পারি তা আরও ভাল।
জেসে

সলিড। আমার JSON এর 20MB স্ট্রিংটি নিচে 4.5MB এ নিয়েছে। 🎉
জেমস এশ

1
দুর্দান্ত কাজ করে তবে আপনার ব্যবহারের পরে স্মৃতিপ্রবাহটি নিষ্পত্তি করা উচিত বা @knocte এর পরামর্শ অনুযায়ী প্রতিটি স্ট্রিমটি ব্যবহার করা উচিত
সেবাস্তিয়ান

8

এটি .NET 4.5 এর জন্য একটি আপডেট সংস্করণ এবং এসিএনসি / অপেক্ষার এবং আইনিউবারেবলগুলি ব্যবহার করে আরও নতুন:

public static class CompressionExtensions
{
    public static async Task<IEnumerable<byte>> Zip(this object obj)
    {
        byte[] bytes = obj.Serialize();

        using (MemoryStream msi = new MemoryStream(bytes))
        using (MemoryStream mso = new MemoryStream())
        {
            using (var gs = new GZipStream(mso, CompressionMode.Compress))
                await msi.CopyToAsync(gs);

            return mso.ToArray().AsEnumerable();
        }
    }

    public static async Task<object> Unzip(this byte[] bytes)
    {
        using (MemoryStream msi = new MemoryStream(bytes))
        using (MemoryStream mso = new MemoryStream())
        {
            using (var gs = new GZipStream(msi, CompressionMode.Decompress))
            {
                // Sync example:
                //gs.CopyTo(mso);

                // Async way (take care of using async keyword on the method definition)
                await gs.CopyToAsync(mso);
            }

            return mso.ToArray().Deserialize();
        }
    }
}

public static class SerializerExtensions
{
    public static byte[] Serialize<T>(this T objectToWrite)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            binaryFormatter.Serialize(stream, objectToWrite);

            return stream.GetBuffer();
        }
    }

    public static async Task<T> _Deserialize<T>(this byte[] arr)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            await stream.WriteAsync(arr, 0, arr.Length);
            stream.Position = 0;

            return (T)binaryFormatter.Deserialize(stream);
        }
    }

    public static async Task<object> Deserialize(this byte[] arr)
    {
        object obj = await arr._Deserialize<object>();
        return obj;
    }
}

এটির সাহায্যে আপনি BinaryFormatterকেবল স্ট্রিংয়ের পরিবর্তে সবকিছু সমর্থন করে সিরিয়ালাইজ করতে পারেন ।

সম্পাদনা:

সেক্ষেত্রেEncoding আপনার যত্ন নেওয়া দরকার , আপনি কেবল কনভার্ট.টোবেস 64 স্ট্রিং (বাইট []] ব্যবহার করতে পারবেন ...

আপনার যদি উদাহরণের প্রয়োজন হয় তবে এই উত্তরটি একবার দেখুন!


আপনার নমুনা সম্পাদনা করে ডি-ইরিয়রিয়ালিংয়ের আগে আপনাকে অবশ্যই স্ট্রিম অবস্থানটি পুনরায় সেট করতে হবে। এছাড়াও, আপনার এক্সএমএল মন্তব্যগুলি সম্পর্কিত নয়।
ম্যাগনাস জোহানসন

এটি কেবলমাত্র ইউটিএফ 8-ভিত্তিক জিনিসগুলির জন্য লক্ষণীয়। যদি আপনি যোগ করেন তবে বলুন, সুইডিশ অক্ষরগুলি åäö এর মতো স্ট্রিংয়ের মান হিসাবে আপনি সিরিয়ালাইজ করছেন / ডিজিট্রাইজ করছেন এটি একটি রাউন্ড-ট্রিপ পরীক্ষায় ব্যর্থ হবে: /
বিসি

এই ক্ষেত্রে আপনি ব্যবহার করতে পারে Convert.ToBase64String(byte[])। দয়া করে এই উত্তরটি দেখুন ( স্ট্যাকওভারফ্লো . com / a / 23908465 / 3286975 )। আশা করি এটা সাহায্য করবে!
z3nth10n

6

যারা এখনও জিজেপ হেডারে ম্যাজিক নম্বর পেয়েছেন তাদের পক্ষে সঠিক নয় not নিশ্চিত হয়ে নিন যে আপনি কোনও জিজেপ স্ট্রিমে যাচ্ছেন। ত্রুটি এবং আপনার স্ট্রিংটি পিএইচপি ব্যবহার করে জিপ করা থাকলে আপনাকে এর মতো কিছু করতে হবে:

       public static string decodeDecompress(string originalReceivedSrc) {
        byte[] bytes = Convert.FromBase64String(originalReceivedSrc);

        using (var mem = new MemoryStream()) {
            //the trick is here
            mem.Write(new byte[] { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0, 8);
            mem.Write(bytes, 0, bytes.Length);

            mem.Position = 0;

            using (var gzip = new GZipStream(mem, CompressionMode.Decompress))
            using (var reader = new StreamReader(gzip)) {
                return reader.ReadToEnd();
                }
            }
        }

আমি এই ব্যতিক্রমটি পেয়েছি: ছোঁড়া ব্যতিক্রম: সিস্টেম.ডিল্লায় 'System.IO.InuthorDataException' অতিরিক্ত তথ্য: জিজেপ পাদলেখের সিআরসি সঙ্কলিত ডেটা থেকে গণনা করা সিআরসি সাথে মেলে না।
ডেইনিয়াস ক্রেইভিস
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.