I have a Base64 encoded String that contains PDF data. Using the EncdDecd unit I can decode the String to a Byte Array.
This is where I am having trouble: I tried saving the characters to a String but once it hits a zero value (ASCII = 0 or #0 or $00) the String no longer appends. Example:
uses
EncdDecd;
var
EncodedString : String;
Report : String;
Base64Bytes: TBytes; // contains the binary data
begin
Base64Bytes := DecodeBase64(EncodedString);
for I := 0 to Length(Base64Bytes) - 1 do
begin
Report := Report + Chr(Base64Bytes[I]);
end;
Writing to a text file seems to work better but after renaming it to .PDF the file does not open correctly.
How can I write to a binary file in Delphi? Or even save the data to a stream? Basically I am just trying to take the encoded String and save it to a PDF/binary file, or display the PDF in Delphi.
I have looked around quite a bit and found a possible solution in
Saving a Base64 string to disk as a binary using Delphi 2007 but is there another way?
This should do it:
procedure DecodeBaseToFile(const FileName: string;
const EncodedString: AnsiString);
var
bytes: TBytes;
Stream: TFileStream;
begin
bytes := DecodeBase64(EncodedString);
Stream := TFileStream.Create(FileName, fmCreate);
try
if bytes<>nil then
Stream.WriteBuffer(bytes[0], Length(bytes));
finally
Stream.Free;
end;
end;
Note: I have only compiled this in my head.
Related
I currently have a TMemoryStream that gets filled via reading a varbinary field, which is originally encoded by UTF8 standard from a MS SQL Server DB table.
The varbinary field contains a header part which needs to be removed before saving the remainder of the stream as a file.
I have successfully pulled out the header information required, which includes the file name, but haven’t
found a solution for then saving the remainder of the stream as a valid file.
The data is initially read into a TdxMemData dataset.
procedure TBaseDataPump.SaveAttachments(mtblAttachmentInfo: TdxMemData);
var
AFile: TMemoryStream;
ANewFile: TStringStream;
sFullPath: String;
wsImageBlob, wsHeader, wsBody: WideString;
iposition: Integer;
begin
if mtblAttachmentInfo.Active then // TdxMemData filled with data
begin
with mtblAttachmentInfo do
begin
First;
while NOT EOF do
begin
// Below code works fine to save the file without removing the data
AFile := TMemoryStream.Create; // Create the memory stream
TBlobField(FieldByName('ImageBlob')).SaveToStream(AFile);
// load the file image from the data to the memory strem
// Get the header content
ANewFile := TStringStream.Create('');
TBlobField(FieldByName('ImageBlob')).SaveToStream(ANewFile);
// read the filed again to a TStringStream
wsImageBlob := UTF8ToString(ANewFile.DataString);
// I can see the data as wide string and can find the header bit and its ending position on the
// its ending position on the string
iposition := AnsiPos(#13#13#10, wsImageBlob); // find the header ending position
// getting the header to another widestring
wsHeader := AnsiLeftStr(wsImageBlob,position);
wsHeader := AnsiLeftStr(wsImageBlob,POS(#9,wsHeader)-1);
wsHeader := StringReplace(wsHeader,'''','',[rfReplaceall]);
// need to save the stream from the position of the end of header as file
end;
end;
end;
end;
Found the solution with the copyfrom method. and its is as follows :
// Create the TStringStream
ANewFile := TStringStream.Create('');
try
TBlobField(FieldByName('ImageBlob')).SaveToStream(ANewFile);
// get the blob as a string so we can extract the header information
wsImageBlob := UTF8ToString(ANewFile.DataString);
position := AnsiPos(#13#13#10, wsImageBlob);
if position <> 0 then begin
// have header information to extract
position := position + 2;
wsHeader := AnsiLeftStr(wsImageBlob, position);
wsHeader := AnsiLeftStr(wsImageBlob, POS(#9, wsHeader) - 1);
wsHeader := StringReplace(wsHeader, '''', '', [rfReplaceall]);
sFilePath := sDirectorypath + wsHeader;
ANewFile.Position := position;
AFile := TMemoryStream.Create;
try
AFile.CopyFrom(ANewFile, ANewFile.Size - position);
AFile.SaveToFile(sFilePath);
finally
FreeAndNil(AFile);
end;
end
finally
FreeAndNil(ANewFile);
end;
What is the correct code that I need to use to send one or more files of any type along with other parameters using Indy's idHTTP.post? (using Delphi 2009 and Indy 10)
The post in question calls a function in a commercial company's API (ElasticEmail) that sends out emails to the recipients held in one of the parameters. (A link to the documentation on the function I am calling is here.
I have example code in C# and other languages from the company here and I have tried to replicate that code in my Delphi code below.
If, in Procedure btnSendbyElastic, I comment out the line Filenames.add(Afilename); so that the function Upload makes no attempt to attach a file,then the correct call seems to be made as the email gets sent successfully by the API.
However, if I leave that line in so that the lines in function UpLoad
MimeStr := GetMIMETypeFromFile(filenames[i]);
FormData.Addfile('file'+inttostr(i), filenames[i],MIMEStr);
do get executed, then no email is sent and the response from the server is
{"success":false,"error":"One of files has invalid characters in file name."}
(The file Afilename does exist at that location and I have tried with single and double backslashes)
Reading other SO posts on this topic I also tried replacing the file processing loop in Function UpLoad with the following loop instead
for i := 0 to filenames.Count - 1 do
begin
MimeStr := GetMIMETypeFromFile(filenames[i]);
FormData.AddFile('file'+inttostr(i), filenames[i],MIMEStr);
AttachmentContent := TFileStream.Create(filenames[i],fmOpenRead);
try
FormData.AddFormField(AttachmentContent.ToString,filenames[i]);
finally
AttachmentContent.free;
end;
end;
This time, even with a filename specified in Filenames.add(Afilename);, the email is sent correctly but the recipient sees no attachment.
Among many others, I have read these possible duplicate SO questions
Http Post with indy
Post a file through https using indy / delphi components
posting a file as part of a form
Nodejs POST request multipart/form-data
and in particular
Using the Indy TidHttp component to send email file attachments through sendgrid
(which is almost exactly what I am trying to do) but I still cannot see what I am doing wrong in my code and what I need to do to correct it.
Here is the code I am using (UPPER_CASE identifiers are constants defined elsewhere)
PS I'm in the UK so apologies for the time delay in responding to US comments/answers
function TForm1.Upload(url: string; params, filenames: Tstringlist): string;
var
FormData : TIdMultiPartFormDataStream;
MIMEStr, ResponseText : string;
i : integer;
begin
try
FormData := TIdMultiPartFormDataStream.Create;
for i := 0 to params.Count - 1 do
FormData.AddFormField(params.Names[i],params.values[params.Names[i]]);
for i := 0 to filenames.Count - 1 do
begin
MimeStr := GetMIMETypeFromFile(filenames[i]);
FormData.Addfile(filenames[i], filenames[i],MIMEStr);
end;
ResponseText :=IdHTTP1.Post(url, FormData);
Memo1.Text := ResponseText; //debug
finally
FormData.free;
end;
end;
procedure TForm1.btnSendbyElastic(Sender: TObject);
var
Params, Filenames : Tstringlist;
url, Afilename : string;
begin
Afilename := 'C:\\Users\\Admin\\Documents\\arrival and departure small.pdf';
Params := Tstringlist.Create;
Filenames := Tstringlist.Create;
try
Params.add('apikey=' + ELASTIC_MAIL_API_KEY) ;
Params.add('from=' + ELASTIC_EMAIL_FROM_EMAIL) ;
Params.add('fromname=' + ELASTIC_EMAIL_FROM_NAME) ;
Params.add('Subject=' + 'The Subject') ;
Params.add('bodyHtml=' + '<h1>Html Body</h1>') ;
Params.add('bodyText=' + 'Text Body') ;
Params.add('to=' + THE_RECIPIENT_ADDRESS) ;
Filenames.add(Afilename); //*** comment out this line and an email is sent correctly
url := ELASTIC_EMAIL_EMAIL_SEND ;
Upload (url , params, filenames );
finally
Params.free;
Filenames.free;
end;
The function GetMIMETypeFromFile is defined in the Indy unit idGlobalProtocols. I didn't write it, I just call it. But I have reproduced it here as requested
function GetMIMETypeFromFile(const AFile: TIdFileName): string;
var
MIMEMap: TIdMIMETable;
begin
MIMEMap := TIdMimeTable.Create(True);
try
Result := MIMEMap.GetFileMIMEType(AFile);
finally
MIMEMap.Free;
end;
end;
I see a few problems with your code.
You are erroneously escaping \ characters in your file paths. That is necessary in languages like C and C++, but is not needed in Delphi at all, so get rid of it.
Change this:
Afilename := 'C:\\Users\\Admin\\Documents\\arrival and departure small.pdf';
To this:
Afilename := 'C:\Users\Admin\Documents\arrival and departure small.pdf';
The next problem I see is you are not naming the file attachment fields correctly when adding them to the TIdMultipartFormDataStream.
When calling AddFile(), you are passing the complete file path as-is to the AFieldName parameter, instead of using names like file0, file1, etc like shown in Elastic's examples.
Change this:
FormData.Addfile(filenames[i], filenames[i],MIMEStr);
To this 1:
FormData.AddFile('file'+IntToStr(i), filenames[i], MIMEStr);
1: FYI, there is no need to call GetMIMETypeForFile() manually, AddFile() calls GetMIMETypeForFile() internally for you if you do not provide a string for the AContentType parameter, eg FormData.AddFile('file'+IntToStr(i), filenames[i]);
You made a similar mistake when you tried to use AddFormField() instead of AddFile() to add attachments. You used each file's actual data content for the AFieldName parameter, instead of using the content for the AFieldValue parameter.
In that case, change this:
FormData.AddFormField(AttachmentContent.ToString,filenames[i]);
To this:
FormData.AddFormField('file'+IntToStr(i), AttachmentContent.ToString, '', MIMEStr, filenames[i]);
Or, since you were opening TFileStream objects yourself, you could use the overloaded AddFormField() method that takes a TStream as input (just be sure NOT to free the TStream objects until after you are done using the TIdMultipartFormDataStream!):
AttachmentContent := TFileStream.Create(filenames[i], fmOpenRead);
FormData.AddFormField('file'+IntToStr(i), MIMEStr, '', AttachmentContent, filenames[i]);
With that said, try something more like this:
function TForm1.Upload(url: string; params, filenames: TStrings): string;
var
FormData : TIdMultiPartFormDataStream;
ResponseText : string;
i : integer;
begin
FormData := TIdMultiPartFormDataStream.Create;
try
for i := 0 to params.Count - 1 do
FormData.AddFormField(params.Names[i], params.ValueFromIndex[i]);
for i := 0 to filenames.Count - 1 do
FormData.AddFile('file'+IntToStr(i), filenames[i]);
ResponseText := IdHTTP1.Post(url, FormData);
Memo1.Text := ResponseText; //debug
finally
FormData.Free;
end;
end;
procedure TForm1.btnSendbyElastic(Sender: TObject);
var
Params, Filenames : TStringList;
url, Afilename : string;
begin
Afilename := 'C:\Users\Admin\Documents\arrival and departure small.pdf';
Params := TStringList.Create;
try
Params.Add('apikey=' + ELASTIC_MAIL_API_KEY);
Params.Add('from=' + ELASTIC_EMAIL_FROM_EMAIL);
Params.Add('fromname=' + ELASTIC_EMAIL_FROM_NAME);
Params.Add('Subject=' + 'The Subject');
Params.Add('bodyHtml=' + '<h1>Html Body</h1>');
Params.Add('bodyText=' + 'Text Body');
Params.Add('to=' + THE_RECIPIENT_ADDRESS);
Filenames := TStringList.Create;
try
Filenames.Add(Afilename);
url := ELASTIC_EMAIL_EMAIL_SEND;
Upload(url, params, filenames);
finally
Filenames.Free;
end;
finally
Params.Free;
end;
end;
Lastly, Elastic's documentation does not say anything about the encoding needed for filenames that contain non-ASCII/reserved characters in it. And there are conflicting standards as to how such filenames should be encoded when transmitted over HTTP. By default, TIdMultipartFormDataStream encodes filenames according to RFC 2047. If that turns out to be a problem for Elastic to handle (your example filename has space characters in it, I forget whether TIdMultipartFormDataStream RFC-encodes a filename due to spaces or not, hopefully not), you can disable TIdMultipartFormDataStream's default encoding by setting an affected file's TIdFormDataField.HeaderEncoding property to '8' (for 8-bit) and then you can set the TIdFormDataField.FileName property to whatever encoding you want:
with FormData.AddFile('file'+IntToStr(i), filenames[i]) do
begin
HeaderEncoding := '8';
FileName := EncodeFilenameMyWay(ExtractFileName(filenames[i]));
end;
Can anybody help? I'm keeping an old Delphi7 project and have next trouble. How can I store BLOB field value if it contains Unicode string?
I tried:
var
str: WideString;
begin
...
str := WideString(Fields[1].AsString); - but I get empty string
...
...
str := VarToWideStr(Fields[1].AsVariant); - but I get "(BLOB)" result in str varible.
...
end;
My solution:
Code usage:
...
stream := TMemoryStream.Create;
try
Fields[1].SaveToStream(stream);
ss := MemStreamToWStr(stream);
finally
stream.Destroy;
end;
...
And function:
function TSnsFrame.MemStreamToWStr(Mstream: TMemoryStream): WideString;
begin
Mstream.Seek(0, soFromBeginning);
SetLength(Result, Mstream.size div 2);
MStream.ReadBuffer(Result[1], Mstream.size);
end;
Have a look at TDataSet.CreateBlobStream(). It returns a TStream that can be used to read/write the raw data of a blob field.
I need to implement in Delphi 2006 an algoritm that work in .net
Basicaly I need to do the next steps:
Create an XML and validate aganist the XSD
Serialize the XML string into an array of bytes UTF-8 encoded and compress it with zip
The compressed info must be stored again into a array of bytes using base256 format
Create an image using Datamatrix 2D BarCode from this array of bytes and put this image on a report
For step 1, I create the XML using NativeXML library that work ok. In this library exist a metod SaveToBinaryFile but don't work ok. In my tests I used this function to create a binary file.
I was forced to use a binary file becouse my Zip component work only with files not with strings or aray of bytes from memory.
I compressed this binary file with the Zip component and loaded this compresed file into a blob file.
At the moment when I need to create the DataMatrix image I load this blob file into an ansistring and I create the image.
After many tests I found that my fault is when I save my XML into the binary file.
Now I need to found another way to save my xml (utf-8) string to a binarry file.
Please sorry for my english.
Can anyone help me?
procedure CreateXMLBarcodeș
var
BinarySize: Integer;
InputString: utf8string;
StringAsBytes: array of Byte;
F : FIle of Byte;
pTemp : Pointer;
begin
InputString := '';
ADoc := TNativeXml.CreateName(rootName);
try
... //create the XML
InputString := ADoc.WriteToLocalString; //utf8string
if InputString > '' then begin
//serialize the XML string to array of bytes
BinarySize := (Length(InputString) + 1) * SizeOf(Char);
SetLength(StringAsBytes, BinarySize);
Move(InputString[1], StringAsBytes[0], BinarySize);
//save the array of bytes to file
AssignFile( F, xmlFilename );
Rewrite(F);
try
BinarySize := Length( StringAsBytes );
pTemp := #StringAsBytes[0];
BlockWrite(F, pTemp^, BinarySize );
finally
CloseFile( F );
end;
end;
finally
ADoc.Free;
end;
end
...
//in other procedure:
DataSet1XMLBarCode.LoadFromFile(CreateXMLBarcode);
...
//when I want to create the report
//create the DataMatrix image
procedure xyz(Sender: TfrxReportComponent);
var AWidth, AHeight: Integer;
function GetStringFromBlob: AnsiString;
var BlobStream: TStream;
begin
Result := '';
DataSet.Open;
BlobStream := DataSet1.
CreateBlobStream(DataSet1XMLBarCode, bmRead);
try
SetLength(Result, BlobStream.Size);
BlobStream.Position := 0;
BlobStream.Read(Result[1], BlobStream.Size);
finally
BlobStream.Free;
end;
DataSet.Close;
end;
begin
Barcode2D_DataMatrixEcc2001.Locked := True;
Barcode2D_DataMatrixEcc2001.Barcode := GetStringFromBlob;
Barcode2D_DataMatrixEcc2001.Module := 1;
Barcode2D_DataMatrixEcc2001.DrawToSize(AWidth, AHeight);
with TfrxPictureView(frxReport1.FindObject('Picture1')).Picture.Bitmap do
begin
Width := AWidth;
Height := AHeight;
Barcode2D_DataMatrixEcc2001.DrawTo(Canvas, 0, 0);
end;
Barcode2D_DataMatrixEcc2001.Locked := False;
end;
//the code was 'çopied'and adapted from the internet
I need to go through a ton of data that is stored in a paradox table within a Memo field. I need to process this data line by line and process each line.
How can I tell Delphi to fetch each line in the memo field one by one?
Could I use #13#10 as a delimiter?
Assuming that what is in the memo field uses #13#10 as the line separator then I would use a TStringList, and the very useful Text property to split the memo field text into separate lines:
var
StringList: TStringList;
Line: string;
.....
StringList.Text := MemoFieldText;
for Line in StringList do
Process(Line);
Even if your memo field uses Unix linefeeds then this code will interpret the memo field correctly.
It depends on how the field is actually declared in Paradox. If it's a TMemoField, it's pretty easy:
var
SL: TStringList;
Line: string;
begin
SL := TStringList.Create;
try
SL.Text := YourMemoField.GetAsString;
for Line in SL do
// Process each line of text using `Line`
finally
SL.Free;
end;
end;
If it's a TBlobField, it's a little more complicated. You need to read the memo field using a TBlobStream, and load the content of that stream into a TStringList:
// For Delphi versions that support it:
procedure LoadBlobToStringList(const DS: TDataSet; const FieldName: string;
const SL: TStringList);
var
Stream: TStream;
begin
Assert(Assigned(SL), 'Create the stringlist for LoadBlobToStringList!');
SL.Clear;
Stream := DS.CreateBlobStream(DS.FieldByName(FieldName), bmRead);
try
SL.LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
// For older Delphi versions that do not have TDataSet.CreateBlobStream
procedure LoadBlobToStringList(const DS: TDataSet; const TheField: TField;
const SL: TStringList);
var
BlobStr: TBlobStream;
begin
Assert(Assigned(SL), 'Create the stringlist for LoadBlobToStringList!');
SL.Clear;
BlobStr := TBlobStream.Create(DS.FieldByName(TheField), bmRead);
try
SL.LoadFromStream(BlobStr);
finally
BlobStr.Free;
end;
end;
// Use it
var
SL: TStringList;
Line: string;
begin
SL := TStringList.Create;
LoadBlobToStringList(YourTable, YourMemoFieldName, SL);
for Line in SL do
// Process each Line, which will be the individual line in the blob field
// Alternatively, for earlier Delphi versions that don't support for..in
// declare an integer variable `i`
for i := 0 to SL.Count - 1 do
begin
Line := SL[i];
// process line of text using Line
end;
end;