001package jmri.util;
002
003import java.io.File;
004import java.io.FileNotFoundException;
005import java.io.FileReader;
006import java.io.InputStream;
007import java.net.URI;
008import java.net.URISyntaxException;
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011import org.xml.sax.EntityResolver;
012import org.xml.sax.InputSource;
013
014/**
015 * Entity Resolver to locate JMRI DTDs in the local space.
016 * <p>
017 * For historical reasons, JMRI xml files might have DTD definitions of three
018 * forms:
019 * <ol>
020 * <li>SYSTEM "../DTD/decoder-config.dtd"
021 * <li>SYSTEM "layout-config.dtd"
022 * <li>SYSTEM "http://jmri.sourceforce.net/xml/DTD/layout-config.dtd"
023 * </ol>
024 * Only the last of these is preferred now. The first two refer to local files
025 * within the JMRI distributions in the xml/DTD directory.
026 *
027 * @author Bob Jacobsen Copyright 2007, 2009
028 */
029public class JmriLocalEntityResolver implements EntityResolver {
030
031    @Override
032    public InputSource resolveEntity(String publicId, String systemId) {
033        log.trace("-- got entity request {}", systemId);
034
035        // find local file first
036        try {
037            URI uri = new URI(systemId);
038            InputStream stream;
039            log.trace("systemId: {}", systemId);
040            String scheme = uri.getScheme();
041            String source = uri.getSchemeSpecificPart();
042            String path = uri.getPath();
043
044            log.trace("scheme: {}", scheme);
045            log.trace("source: {}", source);
046            log.trace("path: {}", path);
047
048            // figure out which form we have
049            if (scheme.equals("http")) {
050                if (systemId.equals("http://www.w3.org/2001/XInclude.xsd")) {
051                    path = "/xml/schema/xinclude.xsd";
052                }
053                // type 3 - find local file if we can
054                String filename = path.substring(1).trim();  // drop leading slash
055                log.trace("http finds filename: {}", filename);
056                stream = FileUtil.findInputStream(filename);
057                if (stream != null) {
058                    return new InputSource(stream);
059                } else {
060                    log.debug("did not find local type 3 DTD file: {}", filename);
061                    // try to find on web
062                    return null;  // tell parser to use default, which is to find on web
063                }
064            } else if (path != null && path.startsWith("../DTD")) {
065                // type 1
066                String filename = "xml" + File.separator + "DTD" + File.separator + path;
067                log.trace("starts with ../DTD finds filename: {}", filename);
068                stream = FileUtil.findInputStream(filename);
069                if (stream != null) {
070                    return new InputSource(stream);
071                } else {
072                    log.error("did not find type 1 DTD file: {}", filename);
073                    return null;
074                }
075            } else if (path != null && !path.contains("/")) {  // path doesn't contain "/", so is just name
076                // type 2
077                String filename = "xml" + File.separator + "DTD" + File.separator + path;
078                log.trace("doesn't contain / finds filename: {}", filename);
079                stream = FileUtil.findInputStream(filename);
080                if (stream != null) {
081                    return new InputSource(stream);
082                } else {
083                    log.error("did not find type 2 entity file: {}", filename);
084                    return null;
085                }
086            } else if (scheme.equals("file")) {
087                if (path != null) {
088                    // still looking for a local file, this must be absolute or full relative path
089                    log.trace("scheme file finds path: {}", path);
090                    // now we see if we've got a valid path
091                    stream = FileUtil.findInputStream(path);
092                    if (stream != null) {
093                        log.trace("file exists, used");
094                        return new InputSource(stream);
095                    } else { // file not exist
096                        // now do special case for Windows, which might use "/" or "\"
097                        // regardless of what File.separator says
098                        String realSeparator = File.separator;
099                        // guess! first form is right one
100                        if (SystemType.isWindows()) {
101                            int forIndex = path.indexOf("/");
102                            int backIndex = path.indexOf("\\");
103                            if (forIndex >= 0 && backIndex < 0) {
104                                realSeparator = "/";
105                            } else if (forIndex < 0 && backIndex >= 0) {
106                                realSeparator = "\\";
107                            } else if (forIndex > 0 && backIndex >= forIndex) {
108                                realSeparator = "\\";
109                            } else if (backIndex > 0 && forIndex >= backIndex) {
110                                realSeparator = "/";
111                            }
112                            log.trace(" forIndex {} backIndex {}", forIndex, backIndex);
113                        }
114                        log.trace("File.separator {} realSeparator {}", File.separator, realSeparator);
115                        // end special case
116                        if (path.lastIndexOf(realSeparator + "DTD" + realSeparator) >= 0) {
117                            log.trace("file not exist, DTD in name, insert xml directory");
118                            String modifiedPath = realSeparator + "xml"
119                                    + path.substring(path.lastIndexOf(realSeparator + "DTD" + realSeparator), path.length()).trim();
120                            path = modifiedPath;
121                        } else {
122                            log.trace("file not exist, no DTD, insert xml/DTD directory");
123                            String modifiedPath = realSeparator + "xml" + realSeparator + "DTD"
124                                    + path.substring(path.lastIndexOf(realSeparator), path.length());
125                            path = modifiedPath;
126                        }
127                        stream = FileUtil.findInputStream(path);
128                        log.trace("attempting : {}", path);
129                        if (stream != null) {
130                            return new InputSource(stream);
131                        } else {
132                            log.error("did not find direct entity path: {}", path);
133                            return null;
134                        }
135                    }
136                } else {
137                    log.trace("schema file with null path");
138                    try {
139                        return new InputSource(new FileReader(new File(source)));
140                    } catch (FileNotFoundException e2) {
141                        log.error("did not find direct entity file: {}", source);
142                        return null;
143                    }
144                }
145            } else {
146                // not recognized type, return null to use default
147                log.error("could not parse systemId: {}", systemId);
148                return null;
149            }
150        } catch (URISyntaxException e1) {
151            log.warn("Could not resolve Local Entity.", e1);
152            return null;
153        }
154    }
155
156    private static final Logger log = LoggerFactory.getLogger(JmriLocalEntityResolver.class);
157
158}