The function below takes in XML input, parses it, and returns an ordinary string which is simply displayed in the caller function. So objects in the context are internal to the function below.
But this function has a strange problem that, it remembers the input which means that the objects are not released properly. Output string has a part resulting from the previous call even if the input is checked to be different.
Before assigning to nil each of XMLDoc, IXMLNodeList, I also tried to release, in a loop, each IXMLNode directly referred from the Item array by assigning nil to it but that assignment statement resulted in a syntax error so settled for the below.
function tform1.nodeToSentence(nodeXml: string): string ;
var
tempXmlDoc : IXMLDocument;
resultWordPuncNodes : IXMLNodeList;
i: Integer;
begin
tempXmlDoc := CreateXMLDoc;
tempXmlDoc.LoadXML(nodeXml);
resultWordPuncNodes := XPathSelect(tempXmlDoc.DocumentElement,'//*');
for i:= 0 to resultWordPuncNodes.Length-1 do
begin
if resultWordPuncNodes.Item[i].NodeName = 'name' then
begin
if resultWordPuncNodes.Item[i].Attributes['attrib'] = 'v_attrib' then
begin
Result := TrimRight(Result);
Result := Result + resultWordPuncNodes.Item[i].Text + ' ';
end
else Result := Result + resultWordPuncNodes.Item[i].Text + ' ';
end;
end;
resultWordPuncNodes:=nil;
tempXmlDoc := nil; //interface based objects are freed this way
end;
calling code
//iterating over i
nodeXML := mugList.Strings[i];
readableSentence := nodeToSentence(mugList.Strings[i]);
//examplesList.Append(readableSentence);
//for debugging
showMessage(readableSentence);
//iteration ends
It does not have to do with interfaces. It only has with string implementation in Delphi.
You have to clear the Result variable as the first line of your function.
Here below is your function fixed:
function tform1.nodeToSentence(nodeXml: string): string ;
var
tempXmlDoc : IXMLDocument;
resultWordPuncNodes : IXMLNodeList;
i: Integer;
begin
// Change #1 - added the line
Result := '';
// Variable Result is shared here before by both the function and the caller.
// You DO HAVE to clear the shared variable to make the function FORGET the previous result.
// You may do it by the 'function' or by the calling site, but it should have be done.
// Usually it is better to do it inside the function.
tempXmlDoc := CreateXMLDoc;
tempXmlDoc.LoadXML(nodeXml);
resultWordPuncNodes := XPathSelect(tempXmlDoc.DocumentElement,'//*');
for i:= 0 to resultWordPuncNodes.Length-1 do
begin
if resultWordPuncNodes.Item[i].NodeName = 'name' then
begin
if resultWordPuncNodes.Item[i].Attributes['attrib'] = 'v_attrib' then
begin
/// REMEMBER this line, it is important, I would explain later.
Result := TrimRight(Result);
Result := Result + resultWordPuncNodes.Item[i].Text + ' ';
end
else Result := Result + resultWordPuncNodes.Item[i].Text + ' ';
end;
end;
resultWordPuncNodes:=nil;
// Change #2 - removed the line - it is redundant
(* tempXmlDoc := nil; //interface based objects are freed this way *)
// Yes, they are. But Delphi automatically clears local ARC-variables
// when destroying them where the function exits.
// Variable should both be local and should be one of ARC types foe that.
end;
1) about auto-clearing local and ARC-kind variables see http://docwiki.embarcadero.com/Libraries/XE8/en/System.Finalize
2) Your real function is not a function at all.
// function tform1.nodeToSentence(nodeXml: string): string ; // only an illusion
procedure tform1.nodeToSentence(nodeXml: string; var Result: string); // real thing
You may say that you was writing a function, not the procedure.
However that was merely a syntactic sugar.
It is like TDateTime
and double
are different terms, but those terms are synonyms for the same implementation;
All the functions returning String/AnsiString/UnicodeString variables in Delphi are procedures. They are merely disguised as function, and the disguise is thin.
3) there is an old Delphi kōan about it. Without OmniXML and all the complex stuff that only blurs the truth. Run this code and see how it behaves.
Function Impossible: String;
begin
ShowMessage( 'Empty string is equal to: ' + Result );
end;
Procedure ShowMe; Var spell: string;
begin
spell := Impossible();
spell := 'ABCDE';
spell := Impossible();
spell := '12345';
spell := Impossible();
end;
4) Now, could you know it ? Yes you could, it only takes a little bit of attention. Let us make a little change.
Function ImpossibleS: String;
begin
ShowMessage( 'Unknown string today is equal to: ' + Result );
end;
Function ImpossibleI: Integer;
begin
ShowMessage( 'Unknown integer today is equal to: ' + IntToStr(Result) );
end;
Procedure ShowMe;
Var spell: string; chant: integer;
begin
spell := ImpossibleS();
spell := 'ABCDE';
spell := ImpossibleS();
spell := '12345';
spell := ImpossibleS();
chant := ImpossibleI();
chant := 100;
chant := ImpossibleI();
chant := 54321;
chant := ImpossibleI();
end;
Be attentive and read compilation warnings. You did fixed your original code that it compiles with NO COMPILER WARNINGS there, didn't you ?
Now compile my second kōan. Read the warnings. Integer-function does generate "Non-initialized variable" warning. String-function does not. Is it so ?
Why the difference? Because string is ARC-type. That means string-function is really a procedure, not a function. And that means the Result variable is initialized, by the caller, outside of the looks-like-function. You may also observe that chant
variables does change after the call, because the integer-function is a real function and the assignment-after-call is a real one. Conversely a string-function is an illusionary one and so is the illusionary assignment-after-call. It looks as being assigned, but it is not.
5) Last thing. Why did I put that mark in your code.
/// REMEMBER this line, it is important, I would explain later.
Result := TrimRight(Result);
Exactly because of that kōan above. You must have been reading the garbage here. You must have been using "Result" variable that you did not initialized nowhere before. You must have expected your compiler giving warning to you at this line. Like it does in ImpossibleI
real function above. But it does not. Just like it does not with ImpossibleS
illusionary function. This lack of the warning is a dead giveaway of the illusion Delphi creates here. Would you notice that there SHOULD be a 'non-initialized var' warning but it got missing you would ask yourself "who initialized variable if it was not my function" and you would understand what had happened yourself. ;-)
6) Okay, one more last thing.
procedure StringVar( const S1: string; var S2: string );
begin
ShowMessage ( S1 + S2 );
end;
procedure StringOut( const S1: string; out S2: string );
begin
ShowMessage ( S1 + S2 );
end;
Those two procedures looks similar. The difference is S2 kind. It should be an OUT-parameter, not the VAR (IN+OUT) parameter. But Delphi fails here just with your function. Delphi can not do string-type OUT-parameters. To compare, FreePascal(Lazarus,CodeTyphon) knows the difference and would show the legit 'non-initialized variable' warning for the StringOut
procedure.