PageRenderTime 48ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/bundles/plugins-trunk/XML/xml/parser/SchemaAutoLoader.java

#
Java | 343 lines | 186 code | 43 blank | 114 comment | 26 complexity | 4df017184d7f18c19ec4d7b87c4073cc MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
  1. /*
  2. * SchemaAutoLoader.java
  3. * :tabSize=4:indentSize=4:noTabs=false:
  4. * :folding=explicit:collapseFolds=1:
  5. *
  6. * Copyright (C) 2009 Eric Le Lay
  7. *
  8. * The XML plugin is licensed under the GNU General Public License, with
  9. * the following exception:
  10. *
  11. * "Permission is granted to link this code with software released under
  12. * the Apache license version 1.1, for example used by the Xerces XML
  13. * parser package."
  14. */
  15. package xml.parser;
  16. //{{{ Imports
  17. import org.xml.sax.XMLReader;
  18. import org.xml.sax.SAXException;
  19. import org.xml.sax.InputSource;
  20. import org.xml.sax.helpers.XMLFilterImpl;
  21. import org.xml.sax.helpers.NamespaceSupport;
  22. import org.xml.sax.ContentHandler;
  23. import org.xml.sax.EntityResolver;
  24. import org.xml.sax.ext.EntityResolver2;
  25. import org.xml.sax.Locator;
  26. import org.xml.sax.Attributes;
  27. import javax.xml.validation.ValidatorHandler;
  28. import javax.xml.validation.TypeInfoProvider;
  29. import java.util.Map;
  30. import java.util.Enumeration;
  31. import java.io.IOException;
  32. import java.net.URI;
  33. import java.net.URISyntaxException;
  34. import org.gjt.sp.util.Log;
  35. import org.gjt.sp.jedit.Buffer;
  36. import xml.Resolver;
  37. import xml.completion.CompletionInfo;
  38. import static xml.Debug.*;
  39. //}}}
  40. /**
  41. * XMLFilter inserting a Validator based upon rules defined in a SchemaMapping.
  42. * The schema is inserted between the parser and this SchemaAutoLoader in the chain.
  43. * If a schema is found, a RewindException is thrown, because it saves a tedious
  44. * replay mecanism (see the commented out Replay class).
  45. * If this is not acceptable, don't forget to call startElement with the document
  46. * element, and to filter out the startDocument(), startElement() events.
  47. *
  48. * This component is only interested in introducing Validators, not providing
  49. * completion.
  50. *
  51. * If the inserted VerifierFilter doesn't implement EntityResolver2,
  52. * entity resolution might fail. finding a test case is not easy,
  53. * since resolution is mainly used to load a schema...
  54. *
  55. * @author Eric Le Lay
  56. * @version $Id: SchemaAutoLoader.java 21318 2012-03-11 10:34:13Z kerik-sf $
  57. */
  58. public class SchemaAutoLoader extends XMLFilterImpl implements EntityResolver2
  59. {
  60. /** flag: are we still waiting for the document element ? */
  61. private boolean documentElement;
  62. /** document-schema mapping rules */
  63. private SchemaMapping mapping;
  64. /** requesting buffer, for caching */
  65. private Buffer requestingBuffer;
  66. /** saved publicId from parse() */
  67. private String publicId;
  68. /** saved systemId from parse() */
  69. private String systemId;
  70. /** saved locator from setLocator() */
  71. private Locator locator;
  72. /** saved namespaces before root element */
  73. private NamespaceSupport docElementNamespaces = new NamespaceSupport();
  74. /** URL of the installed schema, if any */
  75. private String schemaURL;
  76. /** CompletionInfo constructed from the installed schema, if any */
  77. private Map<String,CompletionInfo> completions;
  78. //{{{ Constructors
  79. /**
  80. * @param parent parent in the XML parsing chain
  81. * @param mapping schema-mapping rules or null if you plan to force the schema
  82. */
  83. public SchemaAutoLoader(XMLReader parent,SchemaMapping mapping, Buffer requestingBuffer)
  84. {
  85. super(parent);
  86. this.mapping=mapping;
  87. this.requestingBuffer = requestingBuffer;
  88. }
  89. //}}}
  90. /**
  91. * force the schema to use for validation and CompletionInfo.
  92. * It disables autodiscovery and doesn't cancel any existing verifier,
  93. * so it should be called before parsing.
  94. * @param baseURI baseURI to resolve the schemaURI against (may be null if schemaURI is absolute)
  95. * @param schemaURI URI of the schema to install
  96. */
  97. public void forceSchema(String baseURI, String schemaURI)
  98. throws SAXException, IOException, URISyntaxException
  99. {
  100. this.mapping = null;
  101. installJaxpGrammar(new URI(baseURI), schemaURI, false);
  102. }
  103. /**
  104. * this doesn't return the schema bound using xsi:schemalocation nor the
  105. * DTD file : only a schema discovered via the SchemaMapping instance.
  106. * @return URL of the schema used for validation or null if no schema was installed
  107. */
  108. public String getSchemaURL(){
  109. return schemaURL;
  110. }
  111. /**
  112. * only Relax NG schemas are supported for the moment
  113. * @return CompletionInfo constructed from the schema or null if no Relax NG schema was used
  114. */
  115. public Map<String,CompletionInfo> getCompletionInfo(){
  116. return completions;
  117. }
  118. /**
  119. * load a Validator from the url schema and install it after this SchemaAutoLoader
  120. * in the parsing chain.
  121. * @param baseURI URL to resolve schemaURL against (may be null if schemaURL is absolute)
  122. * @param schemaURL URL to the schema
  123. * @param needReplay is parsing started and we need to replay events to the verifier
  124. */
  125. private void installJaxpGrammar(final URI baseURI, final String schemaURL, boolean needReplay) throws SAXException,IOException
  126. {
  127. // schemas URLs are resolved against the schema mapping file
  128. final ValidatorHandler verifierFilter =
  129. SchemaLoader.instance().loadJaxpGrammar(baseURI.toString(),schemaURL,getErrorHandler(),requestingBuffer);
  130. if(baseURI == null)
  131. {
  132. this.schemaURL = schemaURL;
  133. }
  134. else
  135. {
  136. // this is stupid: new URI("jar:file:.....").resolve(schema4schemas.xsd") returns schema4schemas.xsd
  137. // instead of jar:file:...../schema4schemas.xsd because jar:file:.... URIs are opaque.
  138. // so use xml.Resolver
  139. String[] sids = Resolver.instance().resolveEntityToPathInternal(null, null, baseURI.toString(), schemaURL);
  140. if(sids==null){
  141. this.schemaURL = baseURI.resolve(schemaURL).toString();
  142. }else{
  143. this.schemaURL = sids[0];
  144. }
  145. }
  146. if(needReplay){
  147. // replay setDocumentLocator() and startDocument(), but only for the new
  148. // filter since other components have already received it
  149. verifierFilter.setContentHandler(new org.xml.sax.helpers.DefaultHandler());
  150. if(locator == null)throw new IllegalStateException("LOCATOR");
  151. verifierFilter.setDocumentLocator(locator);
  152. verifierFilter.startDocument();
  153. }
  154. verifierFilter.setContentHandler(getContentHandler());
  155. verifierFilter.setErrorHandler(getErrorHandler());
  156. verifierFilter.setResourceResolver(Resolver.instance());
  157. setContentHandler(verifierFilter);
  158. // very add-hoc, but who uses other extensions for one's schema ?
  159. if(schemaURL.endsWith("rng") || schemaURL.endsWith("rnc")){
  160. // take care of using the resolved schemaURL !
  161. Map<String,CompletionInfo> info = SchemaToCompletion.rngSchemaToCompletionInfo(baseURI.toString(),this.schemaURL,getErrorHandler(),requestingBuffer);
  162. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,SchemaAutoLoader.class,"constructed CompletionInfos : "+info);
  163. completions = info;
  164. }else if(schemaURL.endsWith("xsd")){
  165. // take care of using the resolved schemaURL !
  166. Map<String,CompletionInfo> infos = XSDSchemaToCompletion.getCompletionInfoFromSchema(this.schemaURL,null,null,getErrorHandler(),requestingBuffer);
  167. if(DEBUG_XSD_SCHEMA)Log.log(Log.DEBUG,SchemaAutoLoader.class,"constructed CompletionsInfos : "+infos);
  168. completions = infos;
  169. }
  170. }
  171. /**
  172. * capture system and public ID to find a matching schema mapping,
  173. * @param input input to parse
  174. */
  175. @Override
  176. public void parse(InputSource input)throws SAXException,IOException
  177. {
  178. if(DEBUG_SCHEMA_MAPPING)Log.log(Log.DEBUG,SchemaAutoLoader.this,"PARSE input ("+input.getPublicId()+","+input.getSystemId()+")");
  179. documentElement=true;
  180. publicId = input.getPublicId();
  181. systemId = input.getSystemId();
  182. docElementNamespaces.pushContext();
  183. super.parse(input);
  184. }
  185. /**
  186. * capture sytem ID to find a matching schema mapping
  187. * @param systemId systemId of the input to parse
  188. */
  189. @Override
  190. public void parse(String systemId)throws SAXException,IOException
  191. {
  192. if(DEBUG_SCHEMA_MAPPING)Log.log(Log.DEBUG,SchemaAutoLoader.this,"PARSE systemId "+systemId);
  193. documentElement=true;
  194. publicId = null;
  195. systemId = systemId;
  196. docElementNamespaces.pushContext();
  197. super.parse(systemId);
  198. }
  199. @Override
  200. public void startPrefixMapping(String prefix, String ns)throws SAXException {
  201. /* delay startPrefixMapping, to be able to retrieve the schema in XercesParser.startPrefixMapping
  202. and this is not until startElement("root element")*/
  203. if(documentElement && mapping!=null)
  204. {
  205. if(DEBUG_SCHEMA_MAPPING)Log.log(Log.DEBUG,SchemaAutoLoader.this,"Prefix Mapping ("+prefix+","+ns+")");
  206. docElementNamespaces.declarePrefix(prefix,ns);
  207. }
  208. else
  209. {
  210. super.startPrefixMapping(prefix,ns);
  211. }
  212. }
  213. /**
  214. * if this is the root element, try to find a matching schema,
  215. * instantiate it and insert it in the parsing chain.
  216. */
  217. @Override
  218. public void startElement(String uri, String localName, String qName, Attributes atts)throws SAXException
  219. {
  220. if(documentElement && mapping != null)
  221. {
  222. if(DEBUG_SCHEMA_MAPPING)Log.log(Log.DEBUG,SchemaAutoLoader.this,"DOC element ("+uri+","+localName+","+qName+")");
  223. String prefix;
  224. if("".equals(localName)){
  225. //namespaces are off
  226. prefix = "";
  227. }else{
  228. prefix = qName.equals(localName)? "" : qName.substring(0,qName.indexOf(":"));
  229. }
  230. String politeSystemId = xml.PathUtilities.pathToURL(systemId);
  231. SchemaMapping.Result schema = mapping.getSchemaForDocument(
  232. publicId, politeSystemId,
  233. uri,prefix,localName, true);
  234. if(schema!=null)
  235. {
  236. if(DEBUG_SCHEMA_MAPPING)Log.log(Log.DEBUG,SchemaAutoLoader.this,"FOUND SCHEMA: "+schema);
  237. try
  238. {
  239. installJaxpGrammar(schema.baseURI, schema.target,true);
  240. }
  241. catch(IOException ioe)
  242. {
  243. throw new SAXException("unable to install schema "+schema,ioe);
  244. }
  245. }
  246. //replay the namespace declarations
  247. for(Enumeration e = docElementNamespaces.getDeclaredPrefixes(); e.hasMoreElements();){
  248. String pre = (String)e.nextElement();
  249. super.startPrefixMapping(pre,
  250. docElementNamespaces.getURI(pre));
  251. }
  252. docElementNamespaces.reset();
  253. //root element has been seen
  254. documentElement=false;
  255. }
  256. super.startElement(uri,localName,qName,atts);
  257. }
  258. /**
  259. * manually implement EntityResolver2 because XMLFilterImpl only
  260. * implements EntityResolver, and we need EntityResolver2 for Resolver
  261. * to work properly
  262. * @throws UnsupportedOperationException if getEntityResolver() doesn't implement EntityResolver2
  263. */
  264. public InputSource resolveEntity(String name,String publicId,
  265. String baseURI,String systemId)
  266. throws SAXException, IOException
  267. {
  268. EntityResolver r = getEntityResolver();
  269. if(r instanceof EntityResolver2){
  270. return ((EntityResolver2)r).resolveEntity(name,publicId,baseURI,systemId);
  271. }else{
  272. throw new UnsupportedOperationException("SchemaAutoLoader needs EntityResolver2");
  273. }
  274. }
  275. /**
  276. * manually implement EntityResolver2 because XMLFilterImpl only
  277. * implements EntityResolver, and we need EntityResolver2 for Resolver
  278. * to work properly
  279. * @throws UnsupportedOperationException if getEntityResolver() doesn't implement EntityResolver2
  280. */
  281. public InputSource getExternalSubset(String name, String baseURI) throws SAXException,IOException
  282. {
  283. EntityResolver r = getEntityResolver();
  284. if(r instanceof EntityResolver2){
  285. return ((EntityResolver2)r).getExternalSubset(name,baseURI);
  286. }else{
  287. throw new UnsupportedOperationException("SchemaAutoLoader needs EntityResolver2");
  288. }
  289. }
  290. /**
  291. * capture the locator, in case we need to pass it to a schema
  292. * @see #installJaxpGrammar(java.net.URI,String,boolean)
  293. */
  294. @Override
  295. public void setDocumentLocator(Locator l)
  296. {
  297. this.locator = l;
  298. super.setDocumentLocator(l);
  299. }
  300. }