Want to protect your cyber security and still get fast solutions? Ask a secure question today.Go Premium

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 243
  • Last Modified:

SAX parsing file using multiple classes

Dear Experts
In an XML file I would like to parse some elements in one class and some elements in another class.

I currently have two classes:
public class ProgramDescription implements ContentHandler {..}
class FileType implements ContentHandler {..}

The file gets first parsed in ProgramDescription and when it reaches a certain element:
        if(qName.equalsIgnoreCase("File")) {
            FileType ft = new FileType(parser);
            parser.setContentHandler(ft);
            acceptedInputFiles.add(ft);
        }
the handling should be done by the FileType class.
In the FileType class the following code is reached:
    FileType(XMLReader xr) {
        parent = xr.getContentHandler();
        parser = xr;
        try {
            parser.parse("File");
        } catch(Exception e) {
            parser.setContentHandler(parent);
        }
    }
The problem seems to be parser.parse("File").
The parsing never reaches startElement in FileType although it seems that endElement is reached.
Kind of strange.

Anyway, this may not be the way to do such a thing.
If there is a better way to handle this situation feel free to share.
I would also be happy to just get this to work.
Thanks,
Jens
0
allmer
Asked:
allmer
  • 13
  • 10
  • 4
5 Solutions
 
CEHJCommented:
What you need to do is delegate the ContentHandler methods to the other class where you want it to happen. Only the first class should attempt to read the file and create the parser
0
 
CEHJCommented:
e.g.
if(qName.equalsIgnoreCase("File")) {
	fileHandler.startElement(uri, localName, qName, atts);
}

Open in new window

0
 
CEHJCommented:
You can also use a SAX filter pattern to do this:

http://www.ibm.com/developerworks/xml/library/x-tipsaxfilter/
0
VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

 
allmerAuthor Commented:
Thanks,
for one element that would be fine.
How about a hierarchy?
There will be elements contained in <File> which I would also like to parse in FileType.

How can I handle that situation?
Didn't seem like filtering will solve the problem.

Basically I would like to have as little code as possible in ProgramDescription::startElement
And delegate the handling of elements and their dependent elements to other classes.

How can I achieve that?
0
 
CEHJCommented:
You could use any number of handlers at any level of nesting. Just create a Map<String, ContentHandler>, where the key is element name. You can add your first ContentHandler to the Map for default handling. You can pass the reference to the Map to each handler
0
 
allmerAuthor Commented:
That sounds great CEHJ
do you have a small example for that?
BTW if a hashmap does the trick, I would prefer that (less methods to override ;)

0
 
allmerAuthor Commented:
Maybe the problem is somewhere else
Since endElement is called correctly,
I believe that the contenthandler is also set correctly.
Therefore I seem to loose the information in startElement and would need to reset the reader.
But then again, although endElement is reached in the correct class,
startElement is also not called for elements contained in File so there may be something different going on.

0
 
allmerAuthor Commented:
According to the JavaDocs:
Applications may register a new or different handler in the middle of a parse, and the SAX parser must begin using the new handler immediately.

So what am I missing?
0
 
CEHJCommented:
First of all a question (so i don't send you down the wrong track): do you need to distinguish, say, between

/a/b/file
/c/d/file

?
0
 
allmerAuthor Commented:
A section of the code is shown in the picture.
The first breakpoint is never reached.
The second breakpoint is reached first
and the third breakpoint returns handling.
contentHandler.jpg
0
 
CEHJCommented:
>>... and the SAX parser must begin using the new handler immediately.

But it looks like you might be beginning the parse again, not merely continuing with handling it
0
 
allmerAuthor Commented:
Nonetheless is should eventually come to startElement for the associated element.

I also changed the code refering the contenthandler:
ProgramDescription::startElement(..) {
        ContentHandler ct = nodeMap.get(qName);
        if(ct != null)
            parser.setContentHandler(ct);  //e.g. FileType s.o.
        else
            parser.setContentHandler(this);
}
So that the parser is not explicitly called anymore as mentioned in the question.
0
 
CEHJCommented:
No - you shouldn't allow it to reparse the source. If you're switching handlers, make sure you're switching in and out at the right time. Personally i would use the factory pattern i mentioned earlier. Something like
import java.util.*;
import org.xml.sax.ContentHandler;
 
public class ContentHandlerFactory {
    private static Map<String, ContentHandler> handlerMap = new HashMap<String, ContentHandler>();
 
    public static ContentHandler getHandlerInstance(String name) {
	if (handlerMap.containsKey(name)) {
	    return handlerMap.get(name);
	}
	else {
	    ContentHandler handler = getHandlerTypeForElement(name);
	    handlerMap.put(name, handler);
	    return handler;
	}
    }
 
    public static ContentHandler getHandlerTypeForElement(String name) {
	// Perhaps get this from a Properties file
	// and load via reflction
	return null;
    }
}

Open in new window

0
 
CEHJCommented:
btw, there are libraries available that will marshall various parts of your xml as Java objects. One such API is Commons Digester
0
 
objectsCommented:
you should be using a delegate instead of changingmthe parsers content handler and switch to using it as required. the following outlines what I mean:

  // define your deleagte as a member, initially null

  ContentHandler delegate = null;


if (delegate!=null) (
   // then use it
   delegate.startElement(....

} else {

        if(qName.equalsIgnoreCase("File")) {
            delegate = new FileType();
        }
......

0
 
allmerAuthor Commented:
Thanks CEHJ,
it's fixed now. The problem actually was an if statement that was not visible without horizontal scrolling
that's what I get for copying and pasting from one class to the other.

Anyhow, I believe the map improved my situation a lot.
I won't use reflection however .. no need here.

Finally, I got around making my SAX handler code modular.

Thnaks objects,
I think that is what I am doing although delegate is called parser (see below).
And I do not want to call startElement so I implement ContentHandler and just
redirect the complete handling



public class ProgramDescription implements ContentHandler {
    private HashMap<String,ContentHandler> nodeMap = new HashMap<String,ContentHandler>();
    ProgramDescription(File path) {
        nodeMap.put("AcceptedInput", ft);
    }
    public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
        ContentHandler ct = nodeMap.get(qName);
        if(ct != null)
            parser.setContentHandler(ct);
        else
            parser.setContentHandler(this);
    }
}
//Excerpt from ProgramDescription
 
class FileType implements ContentHandler {
    public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {        if(qName.equalsIgnoreCase("ProgramDescription")) {       //Hidden if statement here//
        if(qName.equalsIgnoreCase("File")) {
            String tmp = atts.getValue("type");
            if(tmp != null) {
                type = tmp;
            }
            tmp = atts.getValue("switchPrefix");
            if(tmp != null) {
                precedeWith = tmp;
            }
            tmp = atts.getValue("number");
            if(tmp != null) {
                number = Integer.parseInt(tmp);
            }
            tmp = atts.getValue("description");
            if(tmp != null) {
                description = tmp;
            }
        }
    }
    public void endElement(String uri, String localName, String qName) throws SAXException {
        if(qName.equalsIgnoreCase("File")) {
            System.out.print("Test");
        }
        if(qName.equalsIgnoreCase("AcceptedInput")) {
            parser.setContentHandler(parent);
        }
    }
}

Open in new window

0
 
allmerAuthor Commented:
Almost there ;)
There is just one issue left.
How do I give control back to the initial ContentHandler (ProgramDescription)?
Line 42 above gives me errors.

0
 
objectsCommented:
your other content handler would need a reference to the other one.

You can avoid that by having one content handler and it delegating to the other as required (instead of switching the content handler of the parser)

A map is unnecessary in your case, and doesn't allow control to be passed back.

    public void endElement(String uri, String localName, String qName) throws SAXException {
        if(qName.equalsIgnoreCase("AcceptedInput")) {
            delegate = programDescriptionHandler;
        }
        delegate.endElement(...


So what you have is one hanler that simply controls which handler to calls.
You program description and file type handlers do not then need to worry about switching the content handler of the parser.

0
 
CEHJCommented:
>>parser.setContentHandler(parent);

Won't work ,i don't think, for that endElement - unless you mean it to start with the next element - is that what you mean?
0
 
CEHJCommented:
>>Anyhow, I believe the map improved my situation a lot.

Yes, it means that the hard-coded delegation that i suggested earlier (which is now being suggested again) is more flexible and could even be changed declaratively.
0
 
objectsCommented:
Swicth the content handler as you are doing would require your code to be more like the following.
Using a delegate handler would simplify it a fair bit so let me know if you need a hand getting it implemented.


public class ProgramDescription implements ContentHandler {
    private FileType ft = new FileType(this);

    public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
        if (qName.equals("AcceptedInput")) {
            parser.setContentHandler(ft);
        else
            parser.setContentHandler(this);
    }
}
//Excerpt from ProgramDescription
 
class FileType implements ContentHandler {
    public ProgramDescription pd = null;

    public FileType(ProgramDescription pd) { this.pd = pd; }

    public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {        if(qName.equalsIgnoreCase("ProgramDescription")) {       //Hidden if statement here//
        if(qName.equalsIgnoreCase("File")) {
            String tmp = atts.getValue("type");
            if(tmp != null) {
                type = tmp;
            }
            tmp = atts.getValue("switchPrefix");
            if(tmp != null) {
                precedeWith = tmp;
            }
            tmp = atts.getValue("number");
            if(tmp != null) {
                number = Integer.parseInt(tmp);
            }
            tmp = atts.getValue("description");
            if(tmp != null) {
                description = tmp;
            }
        }
    }
    public void endElement(String uri, String localName, String qName) throws SAXException {
        if(qName.equalsIgnoreCase("File")) {
            System.out.print("Test");
        }
        if(qName.equalsIgnoreCase("AcceptedInput")) {
            parser.setContentHandler(pd);
        }
    }
}
0
 
allmerAuthor Commented:
good point Objects,
I added the reference to the 'parent'

@CEHJ
yes, the next startElement should be handled again by ProgramDescription
although it would also be nice to handle the endElement in ProgramDescription.




ProgramDescription added to ctor:
        ft = new FileType(this);
        nodeMap.put("AcceptedInput", ft);
 
FileType added:
    ContentHandler parent = null;
ctor:
    FileType(ContentHandler xr) {
        parent = xr;
    }
 
//Earlier I tried the below in ProgramDescription::startElement 
//which doesn't seem to work
        if(qName.equalsIgnoreCase("AcceptedInput")) {
            ft = new FileType(this);
            parser.setContentHandler(ft);
        }

Open in new window

0
 
CEHJCommented:
I don't think you can be using the Map correctly - otherwise you wouldn't need to set the parent explicitly - the Map would set that as the handler automatically
0
 
objectsCommented:
>         nodeMap.put("AcceptedInput", ft);

the map is redundant, you can get rid of it
see the code I posted above

though still reckon it'll be a lot simpler if you create a 3rd content handler which just manages which content handler calls should be forwarded to. That way your existing content handler can just parse and not worry about switching content handlers.

0
 
CEHJCommented:
If you want to switch content handler entirely then all you need is the following, the values in the Map being ContentHandler:
public void endElement(String namespaceURI, String localName, String qName) {
    setNextContentHandler(qName); // Look up ContentHandler in Map
}

Open in new window

0
 
allmerAuthor Commented:
Sorry Objects, I overlooked your code.
Map will come in handy when the rest of the classes are setup.
I would not go to this extent if there was only one class handling elements ;)

Apparently my parser in FileType was not set correctly.
I need to pass that to FileType as well then it returns handling to ProgramDescription.
Unfortunately, it does not handle endElement for 'AcceptedInput' no problem in this context, though.

All done,
Thanks
        if(qName.equalsIgnoreCase("AcceptedInput")) {
            ft = new FileType(this,parser);
            parser.setContentHandler(ft);
        }
 
    FileType(ProgramDescription xr, XMLReader parser) {
        parent = xr;
        this.parser = parser;
    }

Open in new window

0
 
CEHJCommented:
>> Map will come in handy when the rest of the classes are setup.

You need to start now ;-) Or you'll just end up with a large hard-coded succession of if statements that will require changing when you need to change your parsing logic
0

Featured Post

Get your problem seen by more experts

Be seen. Boost your question’s priority for more expert views and faster solutions

  • 13
  • 10
  • 4
Tackle projects and never again get stuck behind a technical roadblock.
Join Now