Resolving DTD URI when XML doc is transformed?
Hi all.
Thanks to your help I have managed to validate and parse an XML file when the external DTD is not referenced with an exact path in the <!DOCTYPE declaration (problem solved using setEntityResolver()).
Now I am trying to do the same with a transformation. The XML doc contains a reference to an external DTD. If I try to transform the XML using my XSL stylesheet, and simply remove the ><!DOCTYPE line from the stylesheet, all is well. However, with the DTD reference in place, I get an exception saying that the dtd file cannot be found in my servlet server config directory (something that I could solve by copying the dtd in there, but would rather not, as I would like to keep the dtds in another location).
I have tried implementing the following code to get around this:
//The transform factory has been successfully created and works, as I have tested it with the xml doc, omitting the DTD reference
resolver u = new resolver();
u.resolve(null,null); //null because I have hard coded the values in my implementation of u.resolve(String,String)
tran.setURIResolver(u);
StreamSource xmlsource = new StreamSource(ClassLoader.getSystemResourceAsStream("xmltestservlet/myxml.xml"));
tran.transform(xmlsource,new StreamResult(resp.getOutputStream()));
class resolver implements URIResolver {
public Source resolve(String href, String basePath) throws TransformerException {
try{
return new StreamSource(ClassLoader.getSystemResourceAsStream("xmltestservlet/mydtd.dtd"));
}catch(Exception e){
return null;
}
}
}
This does not appear to do anything whatsoever. What am I doing wrong. I just want to inform the transformer that if it finds a reference to an external file (ie. DTD file), to look for it in the specified directory/resource as implemented by the URIResolver....
Andy ideas?
Many thanks once more!
Andy>
[1989 byte] By [
andymoose] at [2007-9-26 3:48:40]

Nevermind folks....did some rooting around the API docs and found that this solves the problem, incase in the future anyone else encounters this...:
StreamSource xmls = new StreamSource(ClassLoader.getSystemResourceAsStream("xmltestservlet/myxml.xml"));
xmls.setSystemId("d:/inet/mucci/approot/classes/xmltestservlet/"); //set the base path for relative path dtd in the <!DOCTYPE xxx SYSTEM "mydtd.dtd"> line in the XML doc.
tran.transform(xmls,new StreamResult(resp.getOutputStream()));
Andy
****....I was wrong. All this does is get round the exception raised that the transformer cannot locate the DTD file. It doesn't actually validate the XML at all. I added an invalid tag that went against the DTD definition of what is valid, and no exception was raised. I have since my first post implemented an ErrorListener, but this does nothing.
StreamSource xmls = new StreamSource(ClassLoader.getSystemResourceAsStream("xmltestservlet/myxml.xml"));
xmls.setSystemId(dtd); //dtd is a File() of d:/etc etc etc/mydtd.dtd
tran.setErrorListener(new ErrorListener(){
public void error(TransformerException e) throws TransformerException {
throw new TransformerException(e);
}
public void fatalError(TransformerException e) throws TransformerException {
throw new TransformerException(e);
}
public void warning(TransformerException e) throws TransformerException {
throw new TransformerException(e);
}
});
tran.transform(xmls,new StreamResult(resp.getOutputStream()));
Andy
Andy,
when taking a look at the URIResolver.resolve() documentation you will see that it will only be called to resolve URIs in the XSL file, namely when the document() function is called or when encountering <xsl:include> or <xsl:import> elements. I think the basic idea is that you do the validation of the document yourself if you deem it necessary and pass the resulting DOM tree to the Transformer as a javax.xml.tansform.Source.
Especially, since you've solved the problem for parsing already, you could do (- assuming doc is the DOM tree resulting from the validating parser):
tran.transform(new DOMSource(doc),new StreamResult(resp.getOutputStream()));
Good luck.
lk555 at 2007-6-29 12:32:24 >

Thanks! That worked.Do you know how I can setup the URIResolver so that any of my includes/imports work?Andy
When instantiating a transformer you need to provide an XSL source (unless you want the indentity transformation). The stylesheet specified in the XML source is not automatically chosen as the XSL source for the transformer (you'd need to use getAssociatedStylesheet() for that).
Thus, you can always submit your own XSL file, located at your preferred location, including or importing stylesheets from your preferred location, etc. I think, this should solve your location problem, as I understand it.
But then again, I might not understand your problem. So here's the answer to your question. You'd need to implement a URIResolver similar to the EntityResolver earlier.
I checked the following with Xalan-2.1.0, which shows something peculiar: when I called setURIResolver() on the instance of a Transformer obtained by t_factory.newTransformer(xslSource), my resolve function was never called (maybe this is a bug, I didn't investigate it any further). However, when I used t_factory.setURIResolver, and after that obtained the transformer instance by t_factory.newTransformer(xslSource) everything worked as expected.
.....
TransformerFactory t_factory = TransformerFactory.newInstance();
t_factory.setURIResolver(new MyURIResolver);
Transformer transformer = t_factory.newTransformer(xslSource);
transformer.transform(xmlSource,outputResult);
.....
}
class MyURIResolver{
public Source resolve(String href,String base) throws TransformerException
{
Source src=null;
System.out.println("href="+href);
System.out.println("base="+base);
System.out.println("-");
if(href.endsWith("myImport.xsl"))
src= new StreamSource("test/myTestImport.xsl");
return src;
}
}
xslSource contains the import statement <xsl-import href="myImport.xsl"/>.
Hope that helps,
Good luck.
lk555 at 2007-6-29 12:32:24 >

Thanks again. Enjoy the points!Andy
Hi lk55/andymoose,
Just wondering how this solution would help in my case.
I have a XSL file abc.xsl which includes other XSL files.. A sample of abc.xsl is:
...
...
...
<xsl:include href='main.xsl'/>
<xsl:include href='/default/body.xsl'/>
<xsl:include href='/default/common.xsl'/>
<xsl:include href='/default/header.xsl'/>
<xsl:include href='/default/footer.xsl'/>
....
...
...
How would the logic of using URIResolver in such a case, help?
I have tried the option that lk55 suggested earlier, but it doesnt seem to work.
Thanks in advance!
Thanks.... the following resolved the issue
For those who need it:::
class MyURIResolver
{
public Source resolve(String href,String base) throws TransformerException
{
Source src=null;
System.out.println("href="+href);
System.out.println("base="+base);
System.out.println("-");
if(href.endsWith("main.xsl"))
src= new StreamSource("/usr/local/xsl/main.xsl");
if(href.endsWith("body.xsl"))
src= new StreamSource("/usr/local/xsl/default/body.xsl");
if(href.endsWith("common.xsl"))
src= new StreamSource("/usr/local/xsl/default/common.xsl");
if(href.endsWith("header.xsl"))
src= new StreamSource("/usr/local/xsl/default/header.xsl");
if(href.endsWith("footer.xsl"))
src= new StreamSource("/usr/local/xsl/default/footer.xsl");
return src;
}
}