5

I have a Delphi XE app that consumes a web service written in Cold Fusion (I have no control over the service's output format). I used the WSDL Importer in Delphi to create my unit for the calls to the web service. I am running into situations where I get an exception in Delphi that says "Element "data" does not contain a single text node".

The relevant portion of the XML coming back from the web service when I get the exception is this:

<data soapenc:arrayType="xsd:anyType[][1]" xsi:type="soapenc:Array">
  <data soapenc:arrayType="xsd:anyType[2]" xsi:type="soapenc:Array">
    <data xsi:type="soapenc:string">6490</data>
    <data xsi:type="soapenc:string">Other Expense</data>
  </data>
</data>

If the XML from the web service contains more than one <data> child, no exception occurs.

<data soapenc:arrayType="xsd:anyType[][3]" xsi:type="soapenc:Array">
  <data soapenc:arrayType="xsd:anyType[2]" xsi:type="soapenc:Array">
    <data xsi:type="soapenc:string">2600</data>
    <data xsi:type="soapenc:string">Deferred Revenue</data>
  </data>
  <data soapenc:arrayType="xsd:anyType[2]" xsi:type="soapenc:Array">
    <data xsi:type="soapenc:string">4120</data>
    <data xsi:type="soapenc:string">Non-Credit Income</data>
  </data>
  <data soapenc:arrayType="xsd:anyType[2]" xsi:type="soapenc:Array">
    <data xsi:type="soapenc:string">6490</data>
    <data xsi:type="soapenc:string">Other Expense</data>
  </data>
</data>

What causes this exception and is there a way around it without being able to change the web service itself?

Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
Sam M
  • 4,136
  • 4
  • 29
  • 42
  • please post the delphi xml handling code – Sam Aug 25 '11 at 06:47
  • 1
    That is some ugly XML. Everything "data" with no real naming? Someone didn't know what XML & SOAP are about! My suspicion is that the Delphi code can't handle all the nodes being the same name, so gets confused, but heck! – mj2008 Aug 25 '11 at 08:00
  • @mj2008: I agree about the XML being ugly. It's one of the "features" of Cold Fusion to simplify writing web services; the developer doesn't have to think about the XML format. If they had written it in Delphi it would have been much cleaner. – Sam M Aug 25 '11 at 17:37

3 Answers3

4

I don't know what is causing the error, but yes, there is a way around it. You can use the RIO_AfterExecute() handler to modify the SOAPResponse, to alter the XML to "make it fit". It's an ugly, "bigger hammer" approach, but it ultimately lets you fiddle with the data to get around all sorts of problems.
Looking at your two examples, I'd try using stringreplace to replace 'xsd:anyType[][1]' with 'xsd:anyType[][3]'. If that doesn't work, try injecting another set of data with empty values, to make it seem like there's not just one.

You'll need a RIO object, and then you hook it up to a handler like this:

MyRIO.OnAfterExecute := self.RIO_AfterExecute;

In my case, "self" refers to a class that I've written around my SOAP stuff.

Be sure to set your position back to 0 when you're done fiddling with the request.

Here is some untested code:

procedure MyWrapper.RIO_AfterExecute(const MethodName: string; SOAPResponse: TStream);
var 
  SL : TStringList;   
begin
  // do stuff with the SOAPResponse here. 
  // It's a stream, so I like to load it into a stringlist
  // ex: 
    SL := TStringList.Create;
    try
      SOAPResponse.Position := 0;
      SL.LoadFromSTream(SOAPREsponse);
      // fiddle with stringreplace here, to doctor up the SL.text.
      SOAPResponse.Position := 0;
      SOAPResponse.size := length(SL.Text);
      SL.SaveToStream(SOAPResponse);
    finally
      SL.free;
    end;
end;
Chris Thornton
  • 15,620
  • 5
  • 37
  • 62
  • I was hoping to avoid the hammer approach but I think that's my only option at this point. Thanks for the suggestion. – Sam M Aug 25 '11 at 18:52
2

Just for reference, I encountered the same problem today and after a few hours of searching I found the problem. The fact is that the WSDL importer maps certain types wrongly to string which results in the fact that TXMLDocument is instructed to read a textnode while there is none! So any type defined as string (or array of string) could be wrong...

To the OP: check the definition for the soapenc:Array type in your imported unit.

whosrdaddy
  • 11,720
  • 4
  • 50
  • 99
1

There must be a bug in your Delphi xml reading code. The fact it works sometimes is co-incidental. Navigating through XML is different depending on the component you're using.

I believe these will help you

Libraries and tutorials for XML in Delphi

Where is a tutorial for using XML with Delphi?

If you post the Delphi XML handling code, we could delve further.

Community
  • 1
  • 1
Sam
  • 2,663
  • 10
  • 41
  • 60
  • I didn't write any code to parse the XML. It's all handled by the unit created by the WSDL importer. I don't know enough about how all that works, just that I instantiate the object, call the appropriate method, and go through the array of variants returned. Cold Fusion, which is what the web service is written in, doesn't return nice objects like I accustomed to. It maps to arrays of variants. – Sam M Aug 25 '11 at 17:30
  • The WSDL importer creates this function for the web service call.`function GetAdjustmentAccountList(const OperatingUnit: string; const RequestedDate: TXSDateTime): Map; stdcall;` `Map = array of mapItem;` `mapItem = class(TRemotable) private Fkey: Variant; Fvalue: Variant; published property key: Variant read Fkey write Fkey; property value: Variant read Fvalue write Fvalue; end;` – Sam M Aug 25 '11 at 17:33
  • Ok, fair enough that's how Cold Fusion works... but maybe you could still capture that xml from one of the variants returned and process it yourself. You may have uncovered a deficiency in the WSDL Importer's ability to import a Cold Fusion generated WSDL. That's another point actually, did you import a WSDL file or point the WSDL importer to the webservice URI? It might be of interest to you to consider writing a .Net client to the Cold Fusion webservice and have the .Net client act as a service for the Delphi app. Then you can take control of the webservice interface exposed to Delphi. – Sam Aug 26 '11 at 01:31