Декодирование данных в кодировке base64 из документа xml

Я получаю несколько xml-файлов со встроенными изображениями в кодировке base64, которые мне нужно декодировать и сохранить в виде файлов.

Немодифицированный (кроме заархивированного) пример такого файла можно скачать ниже:

20091123-125320.zip (60 КБ)

Однако я получаю такие ошибки, как недопустимая длина для массива символов Base-64 и недопустимый символ в строке Base-64. Я отметил строку в коде, где я получаю ошибку в коде.

Файл может выглядеть так:

<?xml version="1.0" encoding="windows-1252"?>
<mediafiles>
    <media media-type="image">
      <media-reference mime-type="image/jpeg"/>
      <media-object encoding="base64"><![CDATA[/9j/4AAQ[...snip...]P4Vm9zOR//Z=]]></media-object>
      <media.caption>What up</media.caption>
    </media>
</mediafiles>

И код для обработки такой:

var xd = new XmlDocument();
xd.Load(filename);
var nodes = xd.GetElementsByTagName("media");

foreach (XmlNode node in nodes)
        {
            var mediaObjectNode = node.SelectSingleNode("media-object");
            //The line below is where the errors occur
            byte[] imageBytes = Convert.FromBase64String(mediaObjectNode.InnerText);
            //Do stuff with the bytearray to save the image
        }

XML-данные взяты из корпоративной газетной системы, поэтому я почти уверен, что файлы в порядке - и должно быть что-то в том, как я их обрабатываю, это просто неправильно. Может проблема с кодировкой?

Я попытался записать содержимое mediaObjectNode.InnerText, и это данные, закодированные в base64, поэтому навигация по xml-doc не является проблемой.

Я гуглил, гуглил, переполнял стек и плакал - и не нашел решения... Помогите!

Редактировать:

Добавлен фактический файл примера (и награда). Обратите внимание, что загружаемый файл имеет немного другую схему, так как я упростил его в приведенном выше примере, удалив ненужные вещи...


person Kjensen    schedule 20.11.2009    source источник


Ответы (7)


Для первого выстрела я не использовал никакой язык программирования, только Notepad++.

Я открыл файл xml внутри, скопировал и вставил необработанное содержимое base64 в новый файл (без квадратных скобок).

После этого я выбрал все (Strg-A) и использовал опцию Extensions - Mime Tools - Base64 decode. Это вызвало ошибку о неправильной длине текста (должен быть мод 4). Поэтому я просто добавил два знака равенства ('=') в качестве заполнителя в конце, чтобы получить правильную длину.

Еще одна попытка, и он успешно декодируется в «что-то». Просто сохраните файл в формате .jpg, и он мгновенно откроется в любой программе просмотра изображений.

Поэтому я бы сказал, что что-то не так с данными, которые вы получите. У них просто нет нужного количества знаков равенства в конце, чтобы заполнить количество знаков, которое можно разбить на пакеты по 4.

«Простым» способом было бы добавить знак равенства до тех пор, пока декодирование не выдаст ошибку. Лучше всего было бы подсчитать количество символов (минус CR/LF!) и добавить нужные за один шаг.

Дальнейшие исследования

После некоторого кодирования и чтения функции преобразования, проблема в неправильной простановке знака равенства со стороны производителя. Notepad ++ не имеет проблем с множеством знаков равенства, но функция Convert из MS работает только с нулем, одним или двумя знаками. Поэтому, если вы заполните уже существующий дополнительными знаками равенства, вы также получите ошибку! Чтобы эта чертова штука заработала, приходится отрезать все существующие знаки, подсчитывать, сколько нужно, и добавлять заново.

Просто за щедрость, вот мой код (не совсем идеальный, но достаточный для хорошей отправной точки): ;-)

    static void Main(string[] args)
    {
        var elements = XElement
            .Load("test.xml")
            .XPathSelectElements("//media/media-object[@encoding='base64']");
        foreach (XElement element in elements)
        {
            var image = AnotherDecode64(element.Value);
        }
    }

    static byte[] AnotherDecode64(string base64Decoded)
    {
        string temp = base64Decoded.TrimEnd('=');
        int asciiChars = temp.Length - temp.Count(c => Char.IsWhiteSpace(c));
        switch (asciiChars % 4)
        {
            case 1:
                //This would always produce an exception!!
                //Regardless what (or what not) you attach to your string!
                //Better would be some kind of throw new Exception()
                return new byte[0];
            case 0:
                asciiChars = 0;
                break;
            case 2:
                asciiChars = 2;
                break;
            case 3:
                asciiChars = 1;
                break;
        }
        temp += new String('=', asciiChars);

        return Convert.FromBase64String(temp);
    }
person Oliver    schedule 23.11.2009
comment
@ Оливер, я знаю, что это старый ответ, но я боролся с этой проблемой. Оказывается, это потому, что там было три знака =. Кто знал? Спасибо! - person IanW; 28.04.2012

Строка base64 недействительна, как уже сказал Оливер, длина строки должна быть кратна 4 после удаления пробельных символов. Если вы посмотрите на конец строки base64 (см. ниже), вы увидите, что строка короче остальных.

RRRRRRRRRRRRRRRRRRRRRRRRRRRRX//Z=

Если вы удалите эту строку, ваша программа будет работать, но в полученном изображении будет отсутствовать часть в правом нижнем углу. Вам нужно дополнить эту строку, чтобы общая длина строки была правильной. По моим расчетам, если бы у вас было 3 символа, это должно сработать.

RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRX//Z=
person Andrew    schedule 23.11.2009

удалить последние 2 символа, пока изображение не станет правильным

public Image Base64ToImage(string base64String)
    {
        // Convert Base64 String to byte[]
        byte[] imageBytes=null;
        bool iscatch=true;
        while(iscatch)
        {
            try 
                {           
         imageBytes = Convert.FromBase64String(base64String);
         iscatch = false;

            }
            catch 
            {
                int length=base64String.Length;
                base64String=base64String.Substring(0,length-2);
            }
        }
        MemoryStream ms = new MemoryStream(imageBytes, 0,
          imageBytes.Length);

        // Convert byte[] to Image
        ms.Write(imageBytes, 0, imageBytes.Length);
        Image image = Image.FromStream(ms, true);
        pictureBox1.Image = image;
        return image;
    }
person swapnil malap    schedule 21.10.2013

Попробуйте использовать Linq to XML:

using System.Xml.XPath;

class Program
{
    static void Main(string[] args)
    {
        var elements = XElement
            .Load("test.xml")
            .XPathSelectElements("//media/media-object[@encoding='base64']");
        foreach (var element in elements)
        {
            byte[] image = Convert.FromBase64String(element.Value);
        }
    }
}

ОБНОВИТЬ:

После загрузки XML-файла и анализа значения узла media-object становится ясно, что это недопустимая строка base64:

string value = "PUT HERE THE BASE64 STRING FROM THE XML WITHOUT THE NEW LINES";
byte[] image = Convert.FromBase64String(value);

выдает System.FormatException, говоря, что длина не является допустимой строкой base 64. Событие, когда я удаляю \n из строки, не работает:

var elements = XElement
    .Load("20091123-125320.xml")
    .XPathSelectElements("//media/media-object[@encoding='base64']");
foreach (var element in elements)
{
    string value = element.Value.Replace("\n", "");
    byte[] image = Convert.FromBase64String(value);
}

также выдает System.FormatException.

person Darin Dimitrov    schedule 20.11.2009

У меня также была проблема с декодированием строки в кодировке Base64 из документа XML (в частности, документа пакета Office OpenXML).

Оказалось, что к строке была применена дополнительная кодировка: кодировка HTML, поэтому сначала было выполнено декодирование HTML, а затем декодирование Base64:

private static byte[] DecodeHtmlBase64String(string value)
{
    return System.Convert.FromBase64String(System.Net.WebUtility.HtmlDecode(value));
}

На всякий случай, если кто-то еще наткнется на ту же проблему.

person Stipo    schedule 06.12.2013

Ну тут все очень просто. CDATA сам является узлом, поэтому mediaObjectNode.InnerText фактически создает <![CDATA[/9j/4AAQ[...snip...]P4Vm9zOR//Z=]]>, что, очевидно, не является допустимыми данными в кодировке Base64.

Чтобы все заработало, используйте mediaObjectNode.ChildNodes[0].Value и передайте это значение Convert.FromBase64String'.

person Anton Gogolev    schedule 20.11.2009
comment
Я попытался сохранить содержимое mediaObjectNode.InnerText в text.file (после вывода его на консоль), и никакие cdata-материалы не включены. Я все равно попробовал ваше предложение, но это не имеет значения. - person Kjensen; 20.11.2009

Кодировка символов правильная? Ошибка звучит так, будто есть проблема, из-за которой в массиве появляются недопустимые символы. Попробуйте скопировать текст и расшифровать вручную, чтобы убедиться, что данные действительно верны.

(Для справки, windows-1252 не совсем то же самое, что iso-8859-1, так что это может быть причиной проблемы, за исключением других источников повреждения.)

person futureelite7    schedule 20.11.2009
comment
Ну - может там и ошибка, но вот так я получаю файл (с этой кодировкой). Как я могу проверить, правильный ли он? - person Kjensen; 23.11.2009