Doing transformation on XML (XSLT?)

I have an XML document containing attributes. This has to be sent to a legacy system that cannot handle attributes properly. It needs the value of one element’s attribute appended to the name of the element.

Example:
Out of

120

I must make something like

120

Moving the attribute to an additional element does not work.

Instead of changing my record structure I want to create an XML file and apply the changes there. Has anybody done something like this before? Any hints appreciated.

I forgot:Working on Integration Server 4.6

Just my opinion, but I don’t think you’ll be able to use any XML/XSLT tools to help you do this. The most likely solution is to generate this pseudo-XML with your own custom code. The coding do to this would be pretty straight forward.

You could customize some DOM or SAX source code that is in the public domain to help speed coding. There is a SAX filter at http://www.megginson.com/Software/ that you can use. I used it for a project and adjusted it so that I could easily write XML without needing a SAX parser engine. You could do the same and change how it outputs tags and attributes.

Hope this helps.

Hi Rob,

I agree with you that just using “external” XSLT tools is probably not a very good idea. Instead I will most likely go for installing the respective Java packages and call the translation from a Java service. With regard to this approach I was actually posting my question. From what I have learned so far, this is feasible way to go and I was hoping that someone could give me some hints on how to approach the task.

Regards,
Christoph

Check out the open source library JDOM (www.jdom.org). It will allow you to dynamically add and remove the elements you need to a DOM object.

A couple of approaches come to mind. Both approaches assume that all other transformations are complete and that you have an XML string on which to do the work. Also, both solutions would be a Java service, expecting a valid incoming XML string and an outgoing pseudo-XML string.

  • Parse the incoming string yourself. You only need to look for tags. For each tag (search for ‘<’ and then ‘>’) search for ‘=’. If ‘=’ is present, reformat the tag, jamming all the attributes together and write it to the output string. Transfer everything else over to the output string directly.

  • Use a SAX or DOM parser to parse the incoming string. Then output the XML, outputting tags/attributes in the way that you need. The SAX parser would probably be the way to go as you can create a SAX ContentHandler interface to write the target string in the format you want. DOM will create a complete parse tree of the entire document in memory, which may be okay if your doc isn’t huge. Whichever one you are comfortable with will work fine. If you’ve not used either, I would suggest SAX.

Hope this helps. I could probably throw a solution together, using either approach, if you’d like. Let me know which one you’d like to use and the details of the attribute munging (i.e. what to do if there are multiple attributes).

Rob
reamon@sbiandcompany.com

Assuming I am in an Integration Server, I would call WmPublic pub.web:stringToDocument and WmPublic pub.web:documentToRecord to get an IData representation of the XML dcoument and write a Java service that takes the price record and does something like this:

IDataCursor ic = pipeline.getCursor();
IData price = IDataUtil.getIData(ic, “price”);
if (price == null) {
ic.destroy();
throw new ServiceException(“missing required parameter ‘price’”);
}

IDataCursor icp = price.getCursor();

String priceType = IDataUtil.getString(icp, “@priceType”);
String newKey = “pricePriceType”+priceType;

// May need to generically loop through
// child elements and copy them to
// newRecord with the exception of @priceType
String amount = IDataUtil.getString(icp, “amount”);

Object[][] o = {
{“amount”,amount}
};
IData newRecord = IDataFactory.create(o);
IDataUtil.put(ic, newKey, newRecord);
ic.destroy();

and after calling this service, drop the original price record.

Depending on the bigger picture of other required transformations, the structure of the document and where the document is going the Java service may need to do a little more (like take an array of price Records) and there may need to be a Record input to documentToRecord to get the IData structure formatted that way you want.

Rob,

I have also thought about the two solutions you outlined in your post and decided to go for the latter (using XSLT and SAX) as it gives much more flexibility for the future.

The assumptions you have made -all other processing finished and XML strings as input and output- are just what I did as well.

So, if you could provide me with some code and hints how best to load it into the Integration Server (library issues are my special concern), that would be just great.

Thanks in advance and best regards,
Christoph

I’m working on a working example and should be able to share that in the next day or two (I’ll post the package to wmusers.com).

In the meantime, here are random thoughts that might help:

  1. Make any external classes you want to use available on the server and its classpath.

  2. I assume you’ll write a class that implements ContentHandler. Add a method that accepts your original XML and passes it to the SAX parser. The parser will invoke your ContentHandler methods. You will write these to generate the output XML in the format you want. When the parser returns control to your method, your ContentHandler should have a ready-to-use String or StringBuffer that can be passed back to the caller.

  3. In the Java service you write, add the class you use to the import section of the shared tab.

  4. In the Java service, instantiate the ContentHandler and make the method call you need to do the work.

Piece of cake! :wink:

You might try this crude, brute-force approach. This simply strips certain characters (space, =, comma, ") out of all tags. Maybe this is enough to do what you need?

[begin]
String sourceXML = “”;

// pipeline
IDataHashCursor pipelineCursor = pipeline.getHashCursor();
if ( pipelineCursor.first( “sourceXML” ) )
{
sourceXML = (String) pipelineCursor.getValue();
}
pipelineCursor.destroy();

if(sourceXML.length() < 1)
throw new ServiceException(“sourceXML paramter is empty”);

StringBuffer buf = new StringBuffer(sourceXML.length());
String skipSet = " ,="";
boolean inTag = false;

for(int i=0; i<sourceXML.length(); i++)
{
char c = sourceXML.charAt(i);
if(c == ‘<’)
inTag = true;
else if(c == ‘>’)
inTag = false;

if(inTag)
{
    if(skipSet.indexOf(c) == -1) // If current char not in skip set
        buf.append(c);           // write it
}
else
{
    buf.append(c);
}

}

// pipeline
IDataHashCursor pipelineCursor_1 = pipeline.getHashCursor();
pipelineCursor_1.last();
pipelineCursor_1.insertAfter( “legacyXML”, buf.toString());
pipelineCursor_1.destroy();
[end]

I’ve uploaded an example package on www.wmusers.com that behaves as you describe. Of course you can easily modify it to do what you need.

The funky thing about this is that you have to be careful about trying to use a SAX library–the B2B Server/Integration Server already has a SAX1 implementation along with a Sun XML parser in client.jar. Trying to put another SAX library on the classpath can be a little tricky, especially if you try to put a SAX2 library up–it isn’t compatible with SAX1, which is what is in client.jar. To address this situation, I took the easy way out–I avoided it! :wink:

The example I uploaded uses SAX1 from client.jar, since it is readily available on any wM IS box. SAX2 is more robust and a smidgen easier to work with but SAX1 will do the job.

I hope this is what you were after. Hopefully you don’t need namespace support or something that requires a bit more sophistication than what I put together. Feel free to follow-up with any questions.

Rob

The 4.6 release of the Integration Server includes the newer versions of the SAX libraries.

The client.jar now includes the SAX2 and SAX2 Ext classes, so you don’t have to worry about work arounds now.

Hmm. I haven’t done an install yet but looking through the files that are in IntegrationServer46_en_US.exe and Developer46_en_US.exe, all I see are SAX1 files and no SAX2 files (did a quick browse for XMLFilterImpl.class and didn’t see it). Guess I need to do an install to see the real thing…

Thanks, Rob, this thing does the job for me.

Nevertheless I am curious whether you have found a way to easily run an XSLT processor on an XML document.

Sorry, I just saw that you have already provided this information to the community. I will have a close look at it. Thanks.

As you’ll see, the solution I posted doesn’t use an XSLT engine. Rather, it just uses the XML parser built-in to Integration Server along with a custom writer. Using an XSLT engine would involve doing something similar though–put the XSLT jar/directory on the classpath and then do your thing in the Java service.

Post your results back to the forum so we see how things go…