PageRenderTime 225ms CodeModel.GetById 148ms app.highlight 66ms RepoModel.GetById 1ms app.codeStats 1ms

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

#
Java | 1068 lines | 774 code | 151 blank | 143 comment | 153 complexity | 34113c15bb143bdb4565a97e35afe063 MD5 | raw file
   1/*
   2 * SchemaToCompletion.java
   3 * :folding=explicit:collapseFolds=1:
   4 *
   5 * Copyright (C) 2009 Eric Le Lay
   6 *
   7 * The XML plugin is licensed under the GNU General Public License, with
   8 * the following exception:
   9 *
  10 * "Permission is granted to link this code with software released under
  11 * the Apache license version 1.1, for example used by the Xerces XML
  12 * parser package."
  13 */
  14package xml.parser;
  15
  16import java.util.*;
  17import java.io.IOException;
  18
  19import org.xml.sax.ErrorHandler;
  20import org.xml.sax.InputSource;
  21
  22import com.thaiopensource.relaxng.edit.*;
  23import com.thaiopensource.xml.util.Name;
  24
  25
  26import org.gjt.sp.util.Log;
  27import org.gjt.sp.jedit.Buffer;
  28
  29import xml.cache.Cache;
  30import xml.cache.CacheEntry;
  31import xml.completion.CompletionInfo;
  32import xml.completion.ElementDecl;
  33import static xml.completion.ElementDecl.AttributeDecl;
  34import static xml.Debug.*;
  35
  36/**
  37 * converts the RNG object model to a list of CompletionInfo
  38 */
  39@SuppressWarnings({"rawtypes", "unchecked"}) // whole class is littered with lists of AttributeDecl,ElementDecl which do not share common super type
  40public class SchemaToCompletion
  41{
  42	private static final String RNG_DTD_COMPATIBILITY_NS =
  43		"http://relaxng.org/ns/compatibility/annotations/1.0";
  44		
  45	/** the empty namespace */
  46	private static final String INHERIT = "#inherit";
  47	/** any local name... */
  48	private static final String ANY = "__WHAT_YOU_WANT_IN_NS__";
  49	/**
  50	* @param	current	systemId of the mapping document, to resolve the schema URL
  51	* @param	schemaFileNameOrURL	identifier of the schema to load
  52	* @param	handler	channel to report errors
  53	* @param	requestingBuffer	Buffer requesting the CompletionInfo (for caching)
  54	*/
  55	public static Map<String,CompletionInfo> rngSchemaToCompletionInfo(String current, String schemaFileNameOrURL, ErrorHandler handler, Buffer requestingBuffer){
  56		Map<String,CompletionInfo> infos = new HashMap<String,CompletionInfo>();
  57
  58		String realLocation = null;
  59		
  60		try{
  61			realLocation = xml.Resolver.instance().resolveEntityToPath(
  62				/*name*/null,
  63				/*publicId*/null,
  64				current,
  65				schemaFileNameOrURL);
  66		}catch(IOException ioe){
  67			Log.log(Log.ERROR,SchemaToCompletion.class,"error resolving schema location",ioe);
  68			return infos;
  69		}
  70		
  71		CacheEntry en = Cache.instance().get(realLocation,"CompletionInfo");
  72		if(en == null){
  73			if(DEBUG_CACHE)Log.log(Log.DEBUG, SchemaToCompletion.class,"CompletionInfo not found in cache for "+schemaFileNameOrURL);
  74
  75			com.thaiopensource.relaxng.input.InputFormat pif;
  76			
  77			if(schemaFileNameOrURL.endsWith(".rnc")){
  78				pif = new xml.translate.BufferCompactParseInputFormat();
  79			}else{
  80				pif = new xml.translate.BufferSAXParseInputFormat();
  81			}
  82			
  83			try{
  84				InputSource is = xml.Resolver.instance().resolveEntity(
  85					/*name*/null,
  86					/*publicId*/null,
  87					current,
  88					schemaFileNameOrURL);
  89				
  90				SchemaCollection schemas = pif.load(
  91					is.getSystemId(),
  92					new String[]{},
  93					"unused",
  94					handler,
  95					new xml.translate.EntityResolverWrapper(xml.Resolver.instance(),false));
  96				
  97				SchemaDocument mainSchema = schemas.getSchemaDocumentMap().get(schemas.getMainUri());
  98				
  99				Pattern p = mainSchema.getPattern();
 100				
 101				MyPatternVisitor v = new MyPatternVisitor(infos,schemas);
 102				List roots  = p.accept(v);
 103				
 104				v.addRootsToCompletionInfos(roots);
 105				v.resolveRefs();
 106				// use a more intuitive '' key for the completion info
 107				// of the no-namespace elements
 108				if(infos.containsKey(INHERIT)){
 109					CompletionInfo nons = infos.get(INHERIT);
 110					nons.namespace = "";
 111					infos.put("",nons);
 112					infos.remove(INHERIT);
 113				}
 114				//{{{ put everything in cache
 115				List<CacheEntry> related = new ArrayList<CacheEntry>(schemas.getSchemaDocumentMap().size());
 116				
 117				for(String url : schemas.getSchemaDocumentMap().keySet()){
 118					if(DEBUG_XSD_SCHEMA)Log.log(Log.DEBUG,SchemaToCompletion.class,"grammar is composed of "+url);
 119					try{
 120						String realLoc = xml.Resolver.instance().resolveEntityToPath(null, null, current, url);
 121						CacheEntry ce = Cache.instance().put(realLoc,"GrammarComponent","Dummy");
 122						related.add(ce);
 123					}catch(IOException ioe){
 124						Log.log(Log.ERROR, SchemaToCompletion.class, "error resolving path for "+url, ioe);
 125					}
 126				}
 127				
 128				related.add(Cache.instance().put(realLocation,"CompletionInfo",infos));
 129				// mark all components related and requested by the buffer
 130				for(CacheEntry ce : related){
 131					ce.getRelated().addAll(related);
 132					ce.getRelated().remove(ce);
 133					ce.getRequestingBuffers().add(requestingBuffer);
 134				}
 135				//}}}
 136				
 137			}catch(Exception e){
 138				// FIXME: handle exceptions
 139				Log.log(Log.ERROR, SchemaToCompletion.class, e);
 140			}
 141		}else{
 142			if(DEBUG_CACHE)Log.log(Log.DEBUG, SchemaToCompletion.class,"found CompletionInfo in cache for "+schemaFileNameOrURL);
 143			infos = (Map<String,CompletionInfo>)en.getCachedItem();
 144		}
 145		return infos;
 146	}
 147	
 148	private static class GrabDefinesVisitor
 149	extends AbstractPatternVisitor< Map<String,List<DefineComponent>> >
 150	implements ComponentVisitor < Map<String,List<DefineComponent>> >
 151	{
 152		private Map<String,List<DefineComponent>> comps = new HashMap<String,List<DefineComponent>>();
 153		public Map<String,List<DefineComponent>> visitGrammar(GrammarPattern p){
 154			p.componentsAccept(this);
 155			return comps;
 156		}
 157		
 158		public Map<String,List<DefineComponent>> visitDefine(DefineComponent c){
 159			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,GrabDefinesVisitor.class,"visitDefine("+c.getName()+")");
 160			if(!comps.containsKey(c.getName())){
 161				comps.put(c.getName(),new ArrayList<DefineComponent>());
 162			}
 163			comps.get(c.getName()).add(c);
 164			return comps;
 165		}
 166		
 167		public Map<String,List<DefineComponent>> visitDiv(DivComponent c){
 168			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,GrabDefinesVisitor.class,"visitDiv()");
 169			c.componentsAccept(this);
 170			return comps;
 171		}
 172		
 173		public Map<String,List<DefineComponent>> visitInclude(IncludeComponent c){
 174			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,GrabDefinesVisitor.class,"visitInclude("+c.getHref()+")");
 175			// do not grab definitions inside the include element
 176			return comps;
 177		}
 178		
 179		public Map<String,List<DefineComponent>> visitPattern(Pattern p){
 180			throw new UnsupportedOperationException("visitPattern("+p+")");
 181		}
 182	}
 183	/** add-hock collection to gather definitions in nested grammars.
 184	 */
 185	private static class StackableMap<K,V>
 186	{
 187		private List<Map<K,V>> contents;
 188		
 189		StackableMap(int initialSize)
 190		{
 191			contents = new ArrayList<Map<K,V>>(initialSize);
 192		}
 193		
 194		public void push(Map<K,V> step)
 195		{
 196			contents.add(0,step);
 197		}
 198
 199		public Map<K,V> pop()
 200		{
 201			return contents.remove(0);
 202		}
 203		
 204		public V get(K key)
 205		{
 206			if(contents.isEmpty())return null;
 207			return contents.get(0).get(key);
 208		}
 209		
 210		public V getFromParent(K key)
 211		{
 212			if(contents.size()<2)return null;
 213			return contents.get(1).get(key);
 214		}
 215
 216		public boolean containsKey(K key)
 217		{
 218			if(contents.isEmpty())return false;
 219			return contents.get(0).containsKey(key);
 220		}
 221		
 222		public boolean parentContainsKey(K key)
 223		{
 224			if(contents.size()<2)return false;
 225			return contents.get(1).containsKey(key);
 226		}
 227		
 228		public String toString(){
 229			return "StackableMap{"+contents+"}";
 230		}
 231	}
 232	
 233	/**
 234	 * only visitAttribute returns a non empty list of AttributeDecl
 235	 */
 236	private static class MyAttributeVisitor extends AbstractPatternVisitor< List<AttributeDecl> >{
 237		private Map<String,String> values;
 238		private List<String> data;
 239		private StackableMap<String,List<DefineComponent>> defined;
 240		private boolean required;
 241		MyAttributeVisitor(StackableMap<String,List<DefineComponent>> defined,boolean required){
 242			this.defined=defined;
 243			this.required = required;
 244		}
 245		
 246		public List<AttributeDecl> visitAttribute(AttributePattern p){
 247			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyAttributeVisitor.class,"visitAttribute(req="+required+")");
 248			if(values!=null)throw new IllegalArgumentException("attribute//attribute isn't allowed");
 249			values = new HashMap<String,String>();
 250			data = new ArrayList<String>();
 251			
 252			List<Name> names = p.getNameClass().accept(new MyNameClassVisitor());
 253
 254			p.getChild().accept(this);
 255
 256			
 257			List<AttributeDecl> attrs = new ArrayList<AttributeDecl>(names.size());
 258			
 259			String value;
 260			String type;
 261			
 262			
 263			if(values.isEmpty())
 264			{
 265				if(data.isEmpty()) type = "";
 266				else type = data.get(0);
 267				
 268				value = "";
 269			}
 270			else
 271			{
 272				value = values.keySet().iterator().next();
 273				type = values.get(value);
 274			}
 275			
 276			if(p.getAttributeAnnotation(RNG_DTD_COMPATIBILITY_NS,"defaultValue")!=null){
 277				value = p.getAttributeAnnotation(RNG_DTD_COMPATIBILITY_NS,"defaultValue");
 278			}
 279			
 280			// as soon as there are more than one name required, can't mark each attributeDecl as required !
 281			required &= names.size() < 2;
 282			
 283			for(Name name:names){
 284				attrs.add(new AttributeDecl(name.getLocalName(),
 285					name.getNamespaceUri(),
 286					value,
 287					new ArrayList<String>(values.keySet()),
 288					type,
 289					required));
 290			}
 291			return attrs;
 292		}
 293				   
 294		public List<AttributeDecl> visitChoice(ChoicePattern p){
 295			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyAttributeVisitor.class,"visitChoice()");
 296			// here is only the choice of values, so it doesn't change the requiredness of the attribute
 297			for(Pattern c: p.getChildren())
 298			{
 299				c.accept(this);
 300			}
 301			return Collections.emptyList();
 302		}
 303				   
 304		public List<AttributeDecl> visitElement(ElementPattern p){
 305			throw new IllegalArgumentException("attribute//element isn't allowed");
 306		}
 307				   
 308		public List<AttributeDecl> visitEmpty(EmptyPattern p){
 309			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyAttributeVisitor.class,"visitEmpty()");
 310			return Collections.emptyList();
 311		}
 312				   
 313		public List<AttributeDecl> visitExternalRef(ExternalRefPattern p){
 314			throw new IllegalArgumentException("attribute//externalRef isn't allowed");
 315		}
 316				   
 317		public List<AttributeDecl> visitGrammar(GrammarPattern p){
 318			throw new IllegalArgumentException("attribute//grammar isn't allowed");
 319		}
 320				   
 321		public List<AttributeDecl> visitGroup(GroupPattern p){
 322			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyAttributeVisitor.class,"visitGroup()");
 323			
 324			for(Pattern c: p.getChildren())
 325			{
 326				c.accept(this);
 327			}
 328			return Collections.emptyList();
 329		}
 330				   
 331		public List<AttributeDecl> visitInterleave(InterleavePattern p){
 332			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyAttributeVisitor.class,"visitInterleave()");
 333			for(Pattern c: p.getChildren())
 334			{
 335				c.accept(this);
 336			}
 337			return Collections.emptyList();
 338		}
 339				   
 340		public List<AttributeDecl> visitList(ListPattern p){
 341			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyAttributeVisitor.class,"visitList()");
 342			return p.getChild().accept(this);
 343		}
 344				   
 345		public List<AttributeDecl> visitMixed(MixedPattern p){
 346			throw new IllegalArgumentException("attribute//mixed doesn't make sense");
 347		}
 348				   
 349		public List<AttributeDecl> visitNotAllowed(NotAllowedPattern p){
 350			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyAttributeVisitor.class,"visitNotAllowed()");
 351			return Collections.emptyList();
 352		}
 353				   
 354		public List<AttributeDecl> visitOneOrMore(OneOrMorePattern p){
 355			throw new IllegalArgumentException("attribute//oneOrMore doesn't make sense to me");
 356		}
 357				   
 358		public List<AttributeDecl> visitOptional(OptionalPattern p){
 359			throw new IllegalArgumentException("attribute//optional doesn't make sense to me");
 360		}
 361				   
 362		public List<AttributeDecl> visitParentRef(ParentRefPattern p){
 363			throw new IllegalArgumentException("attribute//parentRef isn't allowed");
 364		}
 365				 
 366		/** 
 367		 *  no endless loop here, since definitions in attributes can't be recursive
 368		 */
 369		public List<AttributeDecl> visitRef(RefPattern p){
 370			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyAttributeVisitor.class,"visitRef()");
 371			if(defined.containsKey(p.getName()))
 372			{
 373				for(DefineComponent dc: defined.get(p.getName())){
 374					dc.getBody().accept(this);
 375				}
 376				return Collections.emptyList();
 377			}
 378			else throw new IllegalArgumentException("unknown define : "+p.getName());
 379		}
 380				   
 381		public List<AttributeDecl>	visitText(TextPattern p){
 382			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyAttributeVisitor.class,"visitText()");
 383			return Collections.emptyList();
 384		}
 385				   
 386		public List<AttributeDecl> visitValue(ValuePattern p){
 387			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyAttributeVisitor.class,"visitValue("+p.getPrefixMap()+")");
 388			values.put(p.getValue(),p.getType());
 389			return Collections.emptyList();
 390		}
 391
 392		public List<AttributeDecl> visitData(DataPattern p){
 393			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyAttributeVisitor.class,"visitData()");
 394			data.add(p.getType());
 395			return Collections.emptyList();
 396		}
 397				   
 398				   
 399		public List<AttributeDecl> visitZeroOrMore(ZeroOrMorePattern p){
 400			throw new IllegalArgumentException("attribute//zeroOrMore doesn't make sense to me");
 401		}
 402
 403		public List<AttributeDecl> visitPattern(Pattern p){
 404			throw new IllegalArgumentException("which pattern did I forget ? "+p);
 405		}
 406
 407	}
 408	
 409	private static class MyNameClassVisitor implements NameClassVisitor<List<Name> >
 410	{
 411		private boolean any = false;
 412		private List<Name> names = new ArrayList<Name>();
 413		
 414		public List<Name> visitName(NameNameClass nc)
 415		{
 416			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,SchemaToCompletion.class,"visitName("+nc.getNamespaceUri()+","+nc.getPrefix()+":"+nc.getLocalName()+")");
 417			names.add(new Name(nc.getNamespaceUri(),nc.getLocalName()));
 418			return names;
 419		}
 420		
 421		public List<Name> visitNsName(NsNameNameClass nc)
 422		{
 423			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyNameClassVisitor.class,"visitNsName("+nc.getNs()+")");
 424			names.add(new Name(nc.getNs(),ANY));
 425			Log.log(Log.WARNING,MyNameClassVisitor.class,
 426					"doesn't handle \"any element\" in namespace in RNG schema (namespace is "+nc.getNs()+")");
 427			if(nc.getExcept()!=null)
 428			{
 429				Log.log(Log.WARNING,MyNameClassVisitor.class,
 430					"doesn't handle except clause in RNG schema");
 431			}
 432			return names;
 433		}
 434		
 435		public List<Name> visitAnyName(AnyNameNameClass nc)
 436		{
 437			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyNameClassVisitor.class,"visitAnyName()");
 438			any = true;
 439			return names;
 440		}
 441		
 442		public List<Name> visitChoice(ChoiceNameClass nc)
 443		{
 444			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyNameClassVisitor.class,"visitChoiceNameClass()");
 445			nc.childrenAccept(this);
 446			return names;
 447		}
 448	}
 449	
 450	/**
 451	 * main visitor of the RNG grammar.
 452	 * Most of the complexity comes from handling defines/ref patterns
 453	 * the list returned is used to cache the translation of the content of definitions.
 454	 * @see MyPatternVisitor#handleRef(List) 
 455	 * */
 456	private static class MyPatternVisitor extends AbstractPatternVisitor<List>
 457	implements ComponentVisitor<List>
 458	{
 459		/** all the CompletionInfo discovered so far,
 460		 *  one by target namespace.
 461		 * */
 462		private Map<String,CompletionInfo> info;
 463		/**
 464		 * for grammars defined in multiple files
 465		 * */
 466		private SchemaCollection schemas;
 467		/**
 468		 * all the defines pattern curently visible (StackeableMap because of parentRef)
 469		 **/
 470		private StackableMap<String,List<DefineComponent>> defines;
 471		/**
 472		 * the content of all defines patterns so far.
 473		 * for each DefineComponent, list of ElementDecl and AttributeDecl (ElementDecl can be ElementRefDecl) 
 474		 * */
 475		private Map<DefineComponent,List> definesContents;
 476		/**
 477		 * current element must be empty
 478		 * */
 479		private boolean empty = false;
 480		/**
 481		 * current attribute is required
 482		 * */
 483		private boolean required = true;
 484		/**
 485		 * current parent
 486		 * */
 487		private ElementDecl parent;
 488		
 489		MyPatternVisitor(Map<String,CompletionInfo> info,SchemaCollection schemas){
 490			this.info = info;
 491			this.schemas = schemas;
 492			parent = null;
 493			defines = new StackableMap<String,List<DefineComponent>>(2);
 494			definesContents = new HashMap<DefineComponent,List>(); 
 495		}
 496		
 497		
 498		public List visitAttribute(AttributeAnnotation a)
 499		{
 500			throw new UnsupportedOperationException("visitAttribute(Annotation)");
 501		}
 502		
 503		public List visitAttribute(AttributePattern p)
 504		{
 505			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitAttribute()");
 506
 507			MyAttributeVisitor visitor = new MyAttributeVisitor(defines,required);
 508			
 509			List<AttributeDecl> attrs = p.accept(visitor);
 510			if(parent!=null)
 511			{
 512				for(AttributeDecl d:attrs)
 513				{
 514					parent.addAttribute(d);
 515				}
 516			}
 517			return attrs;
 518		}
 519		
 520		
 521		public List visitChoice(ChoicePattern p)
 522		{
 523			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitChoice()");
 524			boolean savedRequired = required;
 525			required = false;
 526			List res = new ArrayList<Object>();
 527			for(Pattern c: p.getChildren())
 528			{
 529				res.addAll(c.accept(this));
 530			}
 531			required = savedRequired;
 532			return res;
 533		}
 534		
 535		public List visitComment(Comment c)
 536		{
 537			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitComment()");
 538			return Collections.emptyList();
 539		}
 540		
 541		public List visitComponent(Component c)
 542		{
 543			throw new UnsupportedOperationException(" visitComponent() should not be called");
 544		}
 545		
 546		public List visitData(DataPattern p)
 547		{
 548			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitData()");
 549			// TODO: display the data type of an element
 550			return Collections.emptyList();
 551		}
 552		
 553		/**
 554		 * define components are ignored by this visitor; GrabDefinesVisitor has collected them already
 555		 * @see GrabDefinesVisitor
 556		 * @see MyPatternVisitor#visitGrammar(GrammarPattern) 
 557		 */
 558		public List visitDefine(DefineComponent c)
 559		{
 560			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitDefine("+c.getName()+","+c.getCombine()+")");
 561			return Collections.emptyList();
 562		}
 563		
 564		public List visitDiv(DivComponent p)
 565		{
 566			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitDiv()");
 567			// may be used to add documentation to a group of elements.
 568			List res = new ArrayList<Object>();
 569			for(Component c: p.getComponents())
 570			{
 571				res.addAll(c.accept(this));
 572			}
 573			return res;
 574		}
 575		
 576		public List visitElement(ElementAnnotation ea)
 577		{
 578			throw new UnsupportedOperationException("visitElement(Annotation)");
 579		}
 580		
 581		@SuppressWarnings("unchecked")
 582		public List visitElement(ElementPattern p)
 583		{
 584			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitElement()");
 585			
 586			ElementDecl myParent = parent;
 587			empty = false;
 588			
 589			MyNameClassVisitor nameVisitor = new MyNameClassVisitor();
 590			List<Name> myNames = p.getNameClass().accept(nameVisitor);
 591			
 592			boolean isEmpty = empty;
 593			boolean isRequired = required;
 594			required = true;// by default, attributes are required
 595			
 596			if(parent != null && parent.content == null)
 597				parent.content = new HashSet<String>();
 598			if(parent != null && parent.elementHash == null)
 599				parent.elementHash = new HashMap<String,ElementDecl>();
 600			
 601			// grab all variants of this element and return them
 602			List res = new ArrayList();
 603			for(Name name:myNames)
 604			{
 605				CompletionInfo myInfo = info.get(name.getNamespaceUri());
 606				
 607				if(myInfo==null)
 608				{
 609					myInfo = new CompletionInfo();
 610					myInfo.namespace = name.getNamespaceUri();
 611					info.put(name.getNamespaceUri(),myInfo);
 612					if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"setting completionInfo for "+myInfo.namespace);
 613				}
 614				
 615				ElementDecl me = myInfo.elementHash.get(name.getLocalName());
 616				
 617
 618				if(me == null){
 619					me = new ElementDecl(myInfo,name.getLocalName(),"");
 620					
 621					// only start elements are added to the elementHash of the info,
 622					// so that only start elements show up before root node
 623					if(parent == null)
 624					{
 625						myInfo.addElement(me);
 626					}
 627					else
 628					{
 629						myInfo.elements.add(me);
 630					}
 631					parent = me;
 632					p.getChild().accept(this);
 633					parent = myParent;
 634				}
 635				
 636				res.add(me);
 637				if(myParent!=null)
 638				{
 639					myParent.content.add(name.getLocalName());
 640					myParent.elementHash.put(name.getLocalName(),me);
 641				}
 642			}
 643			if(myParent!=null){
 644				myParent.any   = nameVisitor.any;
 645				myParent.empty = isEmpty;
 646			}
 647			required=isRequired;
 648			return res;
 649		}
 650		
 651		public List visitEmpty(EmptyPattern p)
 652		{
 653			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitEmpty()");
 654			empty=true;
 655			return Collections.emptyList();
 656		}
 657
 658		public List visitExternalRef(ExternalRefPattern p)
 659		{
 660			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitExternalRef("+p.getUri()+")");
 661			// "The externalRef matches if the pattern contained in the specified URL matches" [RNG tutorial]
 662			SchemaDocument sc = schemas.getSchemaDocumentMap().get(p.getUri());
 663			// no risk of endless loop since externalRefs are not allowed to be recursive
 664			return sc.getPattern().accept(this);
 665		}
 666		
 667		public List visitGrammar(GrammarPattern p)
 668		{
 669			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitGrammar()");
 670			defines.push(p.accept(new GrabDefinesVisitor()));
 671			//for the include
 672			p.componentsAccept(this);
 673			
 674			List res = new ArrayList();
 675			// explore the tree from <start>
 676			if(defines.containsKey(DefineComponent.START)){
 677				for(DefineComponent dc: defines.get(DefineComponent.START)){
 678					res.addAll(dc.getBody().accept(this));
 679				}
 680			}else{
 681				throw new IllegalArgumentException("Grammar without a start element !");
 682			}
 683			defines.pop();
 684			return res;
 685		}
 686		
 687		public List visitGroup(GroupPattern p)
 688		{
 689			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitGroup()");
 690
 691			List res = new ArrayList<Object>();
 692			for(Pattern c: p.getChildren())
 693			{
 694				res.addAll(c.accept(this));
 695			}
 696			return res;
 697		}
 698		
 699		public List visitInclude(IncludeComponent c)
 700		{
 701			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitInclude("+c.getUri()+")");
 702			SchemaDocument sc = schemas.getSchemaDocumentMap().get(c.getUri());
 703
 704			// the included element MUST be a grammar per the spec
 705			GrammarPattern g = (GrammarPattern)sc.getPattern();
 706			
 707			// a grammar contains only start, define, div, include elements 
 708			Map<String,List<DefineComponent>> grammarDefinitions = g.accept(new GrabDefinesVisitor());
 709			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"grammar definitions: "+grammarDefinitions.keySet());
 710			// an include contains only start, define, div, elements 
 711			GrabDefinesVisitor v = new GrabDefinesVisitor();
 712			c.componentsAccept(v);
 713			Map<String,List<DefineComponent>> includeDefinitions = v.comps;
 714			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"include definitions: "+includeDefinitions.keySet());
 715			// override each define and the start in the grammar with the include's contents 
 716			grammarDefinitions.putAll(includeDefinitions);
 717			
 718			// the included grammar is then like a div : definitions should be merged
 719			Map<String,List<DefineComponent>> parentDefinitions = defines.pop();
 720			for(Map.Entry<String,List<DefineComponent>> e:grammarDefinitions.entrySet()){
 721				if(!parentDefinitions.containsKey(e.getKey())){
 722					parentDefinitions.put(e.getKey(),new ArrayList<DefineComponent>());
 723				}
 724				parentDefinitions.get(e.getKey()).addAll(e.getValue());
 725			}
 726			defines.push(parentDefinitions);
 727			
 728			// proceed with the grammar's content
 729			if(defines.containsKey(DefineComponent.START)){
 730				List res = new ArrayList();
 731				for(DefineComponent dc: defines.get(DefineComponent.START)){
 732					res.addAll(dc.getBody().accept(this));
 733				}
 734				return res;
 735			}else{
 736				throw new UnsupportedOperationException("included grammar without a start element !");
 737			}
 738		}
 739		
 740		public List visitInterleave(InterleavePattern p)
 741		{
 742			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitInterleave()");
 743
 744			List res = new ArrayList<Object>();
 745			for(Pattern c: p.getChildren())
 746			{
 747				res.addAll(c.accept(this));
 748			}
 749			return res;
 750		}
 751		
 752		public List visitZeroOrMore(ZeroOrMorePattern p)
 753		{
 754			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitZeroOrMore()");
 755			boolean savedRequired = required;
 756			required = false;
 757			List res =  p.getChild().accept(this);
 758			savedRequired = required;
 759			return res;
 760		}
 761
 762		
 763		public List visitList(ListPattern p)
 764		{
 765			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitList()");
 766			//TODO: text completion inside this element
 767			return Collections.emptyList();
 768		}
 769		
 770		public List visitMixed(MixedPattern p)
 771		{
 772			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitMixed()");
 773			//indicates that the element may contain text
 774			return Collections.emptyList();
 775		}
 776		
 777		
 778		public List visitNameClass(NameClass nc)
 779		{
 780			throw new UnsupportedOperationException("visitNameClass() shouldn't be called");
 781			
 782		}
 783		
 784		public List visitNotAllowed(NotAllowedPattern p)
 785		{
 786			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitNotAllowed()");
 787			// not interesting
 788			return Collections.emptyList();
 789		}
 790		
 791		
 792		public List visitOneOrMore(OneOrMorePattern p)
 793		{
 794			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitOneOrMore()");
 795			return p.getChild().accept(this);
 796		}
 797		
 798		public List visitOptional(OptionalPattern p)
 799		{
 800			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitOptional()");
 801			boolean savedRequired = required;
 802			required = false;
 803			List res =  p.getChild().accept(this);
 804			required = savedRequired;
 805			return res;
 806		}
 807		
 808		public List visitParentRef(ParentRefPattern p)
 809		{
 810			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitParentRef("+p.getName()+")");
 811			if(!defines.parentContainsKey(p.getName()))
 812				throw new IllegalArgumentException("Undefined reference :"+p.getName());
 813
 814			List<DefineComponent> parentRef = defines.getFromParent(p.getName());
 815			// must pop the definition to be in the context of the parent grammar
 816			// if the referenced contents also reference sthing
 817			Map<String,List<DefineComponent>> myDefinitions = defines.pop();
 818			
 819			List res = handleRef(parentRef);
 820			
 821			// restore the current grammar"s context
 822			defines.push(myDefinitions);
 823			return res;
 824		}
 825		
 826		/** handle ref and parentRef all the same.
 827		 * 	The first time that a reference to a define is visited,
 828		 *    visit its body (so the current parent gets its content),
 829		 *    save the content (return of the visitXXX methods) in definesContents; 
 830		 *  Otherwise, don't do it.
 831		 *  Always return an ElementRefDecl anyway.
 832		 *  */
 833		private List handleRef(List<DefineComponent> refs){
 834			List res = new ArrayList();
 835			for(DefineComponent dc : refs){
 836				// will return it anyway !
 837				ElementDecl e = new ElementRefDecl(dc,required);
 838				res.add(e);
 839
 840				if(!definesContents.containsKey(dc)){
 841					
 842					//first time we see a reference to this define
 843					// we will not add the ElementRefDecl to the parent, since it will get the real content
 844					// via dc.getBody.accept(this)     (see bellow)
 845
 846					// let the attributes get their required flag from the body
 847					// of the definition only
 848					boolean savedRequired = required;
 849					required = true;
 850					
 851					//don't recurse indefinitely :
 852					//  - mark that this reference is explored
 853					definesContents.put(dc,null);
 854					//  - explore it
 855					List r = dc.getBody().accept(this);
 856					if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,dc.getName()+" defined as "+r);
 857					//  - set the actual value
 858					definesContents.put(dc,r);
 859					
 860					// correct the requiredness of attributes
 861					if(parent!=null && !savedRequired){
 862						for(Object o: r){
 863							if(o instanceof AttributeDecl){
 864								AttributeDecl newO = ((AttributeDecl)o).copy();
 865								newO.required = false;
 866								parent.attributes.remove((AttributeDecl)o);
 867								parent.addAttribute(newO);
 868							}
 869						}
 870					}
 871					required = savedRequired;
 872				}else{
 873					if(parent != null){
 874						// add the reference to the element as a normal child element
 875						if(parent.content == null)
 876							parent.content = new HashSet<String>();
 877						if(parent.elementHash == null)
 878							parent.elementHash = new HashMap<String,ElementDecl>();
 879						parent.elementHash.put(e.name,e);
 880						parent.content.add(e.name);
 881					}
 882				}
 883			}
 884			return res;
 885		}
 886		
 887		public List visitPattern(Pattern p)
 888		{
 889			// see http://code.google.com/p/jing-trang/issues/detail?id=102
 890			if(p instanceof ZeroOrMorePattern)
 891			{
 892				return visitZeroOrMore((ZeroOrMorePattern)p);
 893			}
 894			else
 895			{
 896				// we visit everything, so it shouldn't be called
 897				throw new UnsupportedOperationException("visitPattern("+p+")");
 898			}
 899		}
 900		
 901		public List visitRef(RefPattern p)
 902		{
 903			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitRef("+p.getName()+")");
 904			if(!defines.containsKey(p.getName())){
 905				throw new IllegalArgumentException("Undefined reference :"+p.getName());
 906			}
 907			return handleRef(defines.get(p.getName()));
 908		}
 909		
 910		public List visitText(TextAnnotation ta)
 911		{
 912			throw new UnsupportedOperationException("visitText(Annotation)");
 913		}	
 914		
 915		public List visitText(TextPattern p)
 916		{
 917			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitText("+p+")");
 918			//throwing away : there is no 'mixed' information in the ElementDecl
 919			return Collections.emptyList();
 920		}
 921		
 922		public List visitValue(ValuePattern p)
 923		{
 924			if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitValue()");
 925			//TODO : text completion inside an element
 926			return Collections.emptyList();
 927		}
 928		
 929	/** replace all ElementRefDecls by their value,
 930	 *  going through all elements of all CompletionInfo */
 931	void resolveRefs(){
 932		if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"resolving references...");
 933		for(CompletionInfo i : info.values()){
 934			for(ElementDecl e:i.elements){
 935				resolveRefs(e);
 936			}
 937		}
 938		for(CompletionInfo i : info.values()){
 939			for(ElementDecl e:i.elements){
 940				if(e.elementHash!=null){
 941					for(ElementDecl d:e.elementHash.values()){
 942						if(d instanceof ElementRefDecl){
 943							throw new IllegalStateException("still some undereferenced ElementRefDecl: "+d.name);
 944						}
 945					}
 946				}
 947			}
 948		}
 949	}
 950	
 951	/**
 952	 * if start elements are define components (like article, book in Docbook 5 grammar),
 953	 * their content must be added to CompletionInfo.elementHash because they are really top level elements.
 954	 * and were not visited as such before.
 955	 * Beware: unbounded recursion here, so defines may not be mutually recursive !
 956	 * @param	roots	list returned by visitGrammar
 957	 * */
 958	void addRootsToCompletionInfos(List roots){
 959		for(Object o: roots){
 960			if(o instanceof ElementRefDecl){
 961				ElementRefDecl e = (ElementRefDecl)o;
 962				List res = definesContents.get(e.ref);
 963				if(res == null)throw new IllegalArgumentException("can't find definitions for "+e.ref.getName()+"="+e.ref);
 964				else {
 965					for(Object r:res){
 966						if(r instanceof ElementDecl){
 967							if(r instanceof ElementRefDecl){
 968								addRootsToCompletionInfos(Collections.singletonList(r));
 969							}else{
 970								ElementDecl oo = (ElementDecl)r;
 971								if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"adding root "+oo);
 972								if(oo.name != null){
 973									oo.completionInfo.elementHash.put(oo.name, oo);
 974								}
 975							}
 976						}
 977					}
 978				}
 979			}
 980		}
 981		
 982	}
 983
 984	/** replace all ElementRefDecls children in this ElementDecl by their content,
 985	 *  recursively dereferencing if necessary
 986	 *  @see MyPatternVisitor#resolveRefs(List, boolean)
 987	 */
 988	private void resolveRefs(ElementDecl e){
 989		if(e.elementHash != null){
 990			List res = resolveRefs(new ArrayList(e.elementHash.values()), true);
 991			e.elementHash.clear();
 992			e.content.clear();
 993			for(Object o:res){
 994				if(o instanceof ElementDecl){
 995					ElementDecl oo = (ElementDecl)o;
 996					if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"adding "+oo);
 997					if(oo.name != null){
 998						e.elementHash.put(oo.name,oo);
 999						e.content.add(oo.name);
1000					}
1001				}else if(o instanceof AttributeDecl){
1002					AttributeDecl oo = (AttributeDecl)o;
1003					if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"adding "+oo);
1004					//requiredness has already been take care of in resolveRefs(List,boolean)
1005					e.addAttribute(oo);
1006				}else{
1007					throw new IllegalArgumentException("what's this : "+o);
1008				}
1009			}
1010		}
1011	}
1012	
1013	/**
1014	 * recursively resolve references in res
1015	 * @param	required	are referenced attributes required.
1016	 * 						If only 1 reference in the chain of references marks attributes as non required (field ElementRefDecl.required),
1017	 *						resulting attributes will be non required  
1018	 * @return	list of ElementDecl or AttributeDecl
1019	 */
1020	private List resolveRefs(List res, boolean required){
1021		List l = new ArrayList(res.size());
1022		for(Object o:res){
1023			if(o instanceof ElementRefDecl){
1024				ElementRefDecl oref = (ElementRefDecl)o;
1025				DefineComponent odc = oref.ref;
1026				List ores = definesContents.get(odc);
1027				if(ores == null)throw new IllegalArgumentException("can't find definitions for "+odc.getName()+"="+odc);
1028				// it only takes 1 ref in <optional> to make attributes non required
1029				l.addAll(resolveRefs(ores,required && oref.required));
1030				 
1031			} else if(o instanceof ElementDecl){
1032				l.add(o);
1033			}else if(o instanceof AttributeDecl){
1034				AttributeDecl oo = (AttributeDecl)o;
1035				if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"adding "+oo);
1036				// must make a copy with correct requiredness because attribute could be referenced somewhere else
1037				// where it is required
1038				if(!required){
1039					oo = oo.copy();
1040					oo.required = false;
1041				}
1042				l.add(oo);
1043			}else{
1044				throw new IllegalArgumentException("what's this : "+o);
1045			}
1046		}
1047		return l;
1048	}
1049
1050	/** intermediate placeholder for a reference */
1051	private static class ElementRefDecl extends ElementDecl{
1052		DefineComponent ref;
1053		static int i = 0;
1054		boolean required;
1055		
1056		ElementRefDecl(DefineComponent ref, boolean required){
1057			super(null,"_:"+ref.getName()+"_"+(i++),null);
1058			this.ref = ref;
1059			this.required = required;
1060		}
1061		
1062		public String toString(){
1063			return "ref to "+ref.getName()+"="+ref.toString();
1064		}
1065	}
1066	
1067	}
1068}