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}