Friday, October 29, 2010

Recursion of the mummy


Recursion of the mummy

It’s Halloween, and its recursion which often makes me curse but it is the only realistic way to parse an xmldocument. It is critical for us, using Infopath, to get the functionality of the form libraries along with the aggregation type of goodies I can grab out of a traditional DBMS. So our submit logic often starts with a submit to DBMS and then save the form to the library, while the associated onopen event for the form updates the fields from a secondary datasource grabbed from the DBMS by a webservice.

So, to parse a whole InfoPath form I first create an object for each repeating group in the form like so…
TLocationRecord = class(System.Object)
private
function isNewRecord(thisNode : xmlNode) : Boolean;
function isComplete : Boolean;
procedure ParseNode(thisNode : xmlNode;
thisRecord : TLocationRecord);
procedure RecurseChildNodes(ThisNode : xmlNode;
thisRecord : TLocationRecord);
public
Location_ID : Integer;
Name : String;
Address : String;
City : String;
Zip : String;
Phone : String;
Fax : String;
isActive : String;

Next_Location : TLocationRecord;

procedure LoadFromXML(RootNode : TLocationRecord;
xml : xmlDocument);
end;

The only tricky part is the recursion of course, so in this case after tossing the whole document to the LoadFromXML procedure I grab the first element of the form in a xmlNode object and feed into the recursion.

The recursion looks for the specific part of the form I want to parse, in this case the locations repeating table named my:EditLocations, which will return true on the NewRecord function, at which point I add another node to then end of my own object and recurse. Otherwise I just toss it to the parser until I run out of document.

procedure TLocationRecord.RecurseChildNodes(ThisNode : xmlNode;
thisRecord : TLocationRecord);
var
wrkNode : xmlNode;
NextRecord : TLocationRecord;
wrkRecord : TLocationRecord;
begin
while ThisNode <> nil do
begin
if isNewRecord(thisNode) then
begin
NextRecord := TLocationRecord.Create;
NextRecord.Next_Location := nil;

wrkRecord := thisRecord;
wrkRecord.IsWellFormed := self.isComplete;

while wrkrecord.Next_Location <> nil do
wrkRecord := wrkRecord.Next_Location;

wrkRecord.Next_Location := NextRecord;
RecurseChildNodes(ThisNode.FirstChild,NextRecord);
end
else
if ThisNode.HasChildNodes then
begin
wrkNode := thisNode.FirstChild;
ParseNode(thisNode,thisRecord);
RecurseChildNodes(wrkNode,thisRecord);
end;
thisNode := thisNode.NextSibling;
end;
end;

procedure TLocationRecord.ParseNode(thisNode : xmlNode;
thisRecord : TLocationRecord);
var
wrkNode : xmlNode;
begin
If ThisNode.Name = 'my:Name' then
begin
wrkNode := ThisNode.FirstChild;
if wrkNode.NodeType = xmlNodeType.Text then
thisRecord.Name := wrkNode.Value;
end
else
If ThisNode.Name = 'my:Address' then
begin
wrkNode := ThisNode.FirstChild;
if wrkNode.NodeType = xmlNodeType.Text then
thisRecord.Address := wrkNode.Value;
end
else
If ThisNode.Name = 'my:City' then
begin
wrkNode := ThisNode.FirstChild;
if wrkNode.NodeType = xmlNodeType.Text then
thisRecord.City := wrkNode.Value;
end
else
If ThisNode.Name = 'my:Zip' then
begin
wrkNode := ThisNode.FirstChild;
if wrkNode.NodeType = xmlNodeType.Text then
thisRecord.Zip := wrkNode.Value;
end
else
If ThisNode.Name = 'my:Phone' then
begin
wrkNode := ThisNode.FirstChild;
if wrkNode.NodeType = xmlNodeType.Text then
thisRecord.Phone := wrkNode.Value;
end
else
If ThisNode.Name = 'my:Fax' then
begin
wrkNode := ThisNode.FirstChild;
if wrkNode.NodeType = xmlNodeType.Text then
thisRecord.Fax := wrkNode.Value;
end
else
If ThisNode.Name = 'my:isActive' then
begin
wrkNode := ThisNode.FirstChild;
if wrkNode.NodeType = xmlNodeType.Text then
thisRecord.isActive := wrkNode.Value;
end;
end;

function TWebService1.maintUpdateLocations(IIRForm : xmlDocument) : integer;
var
insstr : String;
editStr : String;
inscmd : sqlCommand;
editcmd : sqlcommand;
Root : TLocationRecord;
wrkRecord : TLocationRecord;
RVal : Integer;
begin
Root := TLocationRecord.Create;
Root.Next_Location := nil;

Root.LoadFromXML(Root,IIRForm);

IIR_SQL_CONNECTION.Open;

insstr := 'INSERT INTO LOCATIONS(NAME,ADDRESS,CITY,ZIP,PHONE,FAX) '+
'VALUES (@NAMEPARAM,@ADDRPARAM,@CITYPARAM,@ZIPPARAM,@PHONEPARAM,@FAXPARAM)';

inscmd := sqlCommand.Create;
inscmd.Connection := IIR_SQL_CONNECTION;
inscmd.CommandText := insstr;

editStr := 'UPDATE LOCATIONS SET(NAME,ADDRESS,CITY,ZIP,PHONE,FAX) '+
'VALUES (@NAMEPARAM,@ADDRPARAM,@CITYPARAM,@ZIPPARAM,@PHONEPARAM,@FAXPARAM) '+
'WHERE LOCATION_ID = @LOCPARAM';

editcmd := sqlCommand.Create;
editcmd.Connection := IIR_SQL_CONNECTION;
editcmd.CommandText := editstr;

inscmd.Parameters.Add('@NAMEPARAM',sqldbType.NVarChar,50);
inscmd.Parameters.Add('@ADDRPARAM',sqldbType.NVarChar,75);
inscmd.Parameters.Add('@CITYPARAM',sqldbType.NVarChar,50);
inscmd.Parameters.Add('@ZIPPARAM',sqldbType.NVarChar,10);
inscmd.Parameters.Add('@PHONEPARAM',sqldbType.NVarChar,14);
inscmd.Parameters.Add('@FAXPARAM',sqldbType.NVarChar,14);


// Walk the list and either update or insert.....
wrkRecord := Root.Next_Location;

while wrkRecord <> nil do
begin
if (wrkRecord.Location_ID > 0) then
begin
// edit existing record
if wrkRecord.isComplete then
begin
editcmd.Parameters.Item['@NAMEPARAM'].Value := wrkRecord.Name;
editcmd.Parameters.Item['@ADDRPARAM'].Value := (wrkRecord.Address as TObject);
editcmd.Parameters.Item['@CITYPARAM'].Value := (wrkRecord.City as TObject);
editcmd.Parameters.Item['@ZIPPARAM'].Value := (wrkRecord.Zip as TObject);
editcmd.Parameters.Item['@PHONEPARAM'].Value := (wrkRecord.Phone as TObject);
editcmd.Parameters.Item['@FAXPARAM'].Value := (wrkRecord.Fax as TObject);
editcmd.Parameters.Item['@ISACTIVE'].Value := (wrkRecord.IsActive as TObject);
editcmd.Parameters.Item['@LOCPARAM'].Value := (wrkRecord.Location_ID as TObject);

RVal := RVal + inscmd.ExecuteNonQuery;
end;
end
else
begin
// Insert new record
// Validate Record for completeness...
If wrkRecord.isComplete then
begin
inscmd.Parameters.Item['@NAMEPARAM'].Value := wrkRecord.Name;
inscmd.Parameters.Item['@ADDRPARAM'].Value := (wrkRecord.Address as TObject);
inscmd.Parameters.Item['@CITYPARAM'].Value := (wrkRecord.City as TObject);
inscmd.Parameters.Item['@ZIPPARAM'].Value := (wrkRecord.Zip as TObject);
inscmd.Parameters.Item['@PHONEPARAM'].Value := (wrkRecord.Phone as TObject);
inscmd.Parameters.Item['@FAXPARAM'].Value := (wrkRecord.Fax as TObject);

RVal := RVal + inscmd.ExecuteNonQuery;
end;
end;
wrkRecord := wrkRecord.Next_Location;
end;
Result := RVal;
end;

Then it is just a matter of walking the TLocation Object and editing or inserting the rows.