views:

1039

answers:

3

I am trying to decode a base64 encoded EMF image from an XML document in my application and render it on screen, however, it never seems to appear.

If I copy/paste the data from the XML document into Notepad++ and use the Base64 Decode option and save the file as a .emf it opens fine in mspaint. So I think the issue is how I am decoding it.

I have tried the following decode methods described in these articles:

How to encode / decode Base 64 string
http://www.swissdelphicenter.ch/torry/showcode.php?id=1223

I have also tried the TIdDecoderMIME class to no avail.

Does anyone know the most reliable way of decoding a base64 encoded string from XML?

Example

procedure TXmlSerializer.SaveImageFromString(const AValue: string);
var
  StrStream: TStringStream;
  Decoder: TIdDecoderMIME;
begin
  // AValue is base64 encoded string from XML doc
  Decoder := TIdDecoderMIME.Create(nil);
  try
    StrStream := TStringStream.Create(Decoder.DecodeString(AValue));
    try
      StrStream.SaveToFile('MyPath\Image.emf');
    finally
      StrStream.Free;
    end;
  finally
    Decoder.Free;
  end;
end;

Why is it the above doesn't work but copying the raw data into Notepad++ and decoding & saving as .emf works?

+1  A: 

TIdDecoderMIME works just fine. Tried and tested. Just look at the code samples provided by Indy.

Also OmniXML has OmniXMLUtils.pas unit with various utilities functions for XML. Inside you can find standalone Base64 encode/decode function. You can copy paste them or use them as they are.

Otherwise post the code using TIdDecoderMIME. It must be an error in you code.

EDIT:

You must watch out that you write the string to stream as single byte order. Base64 works on bytes (ASCII chars) and will not work correctly if you supply it with the 2 byte unicode string. If you take an Base64 encoded sequence as string and write it to stream as it is you will have an error, because the lenghth of such stream will be twice as long as the original was.

You can take TStringStream or use AnsiStrings for the task. As long as you stick to that the encode / decode will work correctly.

You can also use my SimpleStorage which does all the work for you. Reading an image from XML comes to such simple task as:

  Jpeg := TJPEGImage.Create;
  try
    Jpeg.LoadFromStream(SrcStorage.Get('Image').Filter('gzip').AsBinary.Stream);
  finally
    Jpeg.Free;
  end;

Easy isn't it (and it is also decompressed in the process). :)

Runner
I have posted an example of what I was doing. Basically trying to take the raw image data and save it as an image.
James
@Runner, yeah looks good but I don't want to introduce any other 3rd party components.
James
Understood. I just posted it for reference in case somebody finds it usefull. Anyway check what I proposed. The error is probably in that arrea.
Runner
A: 

Ensure that you're decoding just the attribute value and not the entire XML.

In your code, be sure to set the position of the stream to 0 before read from it.

StrStream.Seek(0, soFromBeginning);

I'm not familiar with the SaveToFile() method of TStringStream. Are you sure about that?

Also, since Delphi 2010 supports unicode strings, you might want to use AnsiStrings when handling binary data. Be careful when mixing multi-byte character types with single byte data.

Marcus Adams
@Marcus, don't think this actually matters for this particular example. Did it anyway just before I wrote it to file and it didn't make a difference.
James
nit: that should be StrStream.Seek(0, soFromBeginning); :)
glob
@glob, thanks, I edited my answer.
Marcus Adams
+3  A: 

You're using a string to store binary data; Delphi 2010's unicode-ness could be to "blame" here.

Try using a base64 decoder which supports decoding to a stream, and decode directly to a TFileStream; I use JclMime (part of Jedi's JCL), but I'd be surprised if there wasn't an Indy MIME method which works on streams instead of strings.

As JclMime only supports stream-to-stream decoding, I use the following wrapper when decoding base64 data from XML payloads:

procedure MimeDecode(const inputString: string; const outputStream: Tstream);
var
  ss: TStringStream;
begin
  ss := TStringStream.Create(inputString);
  try
    JclMime.MimeDecodeStream(ss, outputStream);
  finally
    ss.free;
  end;
end;
glob
In Jame's original code, TIdDecoderMIME can be used to decode to a TStream. That is what TIdDecoder operates on natively. Use the TIdDecoder.DecodeBegin() and TIdDecoder.Decode() methods instead of the TIdDecoder.DecodeString() method (which is a wrapper that converts an internally decoded TStream into a String).
Remy Lebeau - TeamB
I was using JclMime and writing it to file and it was displaying perfectly, couldn't quite understand why it wasn't displaying properly....then I realised I wasn't resetting the position!
James