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