PageRenderTime 174ms CodeModel.GetById 136ms app.highlight 29ms RepoModel.GetById 1ms app.codeStats 0ms

/bundles/plugins-trunk/XML/xml/XmlParsedData.java

#
Java | 858 lines | 596 code | 104 blank | 158 comment | 153 complexity | e208092a91b8622efc009141ebe6a955 MD5 | raw file
  1/*
  2 * XmlParsedData.java
  3 * :tabSize=8:indentSize=8:noTabs=false:
  4 * :folding=explicit:collapseFolds=1:
  5 *
  6 * Copyright (C) 2003 Slava Pestov
  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;
 17
 18//{{{ Imports
 19import java.util.ArrayList;
 20import java.util.Collections;
 21import java.util.Comparator;
 22import java.util.HashMap;
 23import java.util.Iterator;
 24import java.util.LinkedList;
 25import java.util.List;
 26import java.util.Map;
 27import java.util.TreeMap;
 28import javax.swing.SwingUtilities;
 29import javax.swing.tree.DefaultMutableTreeNode;
 30import javax.swing.tree.TreePath;
 31import javax.swing.tree.TreeNode;
 32
 33import org.gjt.sp.jedit.Buffer;
 34import org.gjt.sp.jedit.EditBus;
 35import org.gjt.sp.jedit.GUIUtilities;
 36import org.gjt.sp.util.Log;
 37import org.gjt.sp.util.StandardUtilities;
 38import org.gjt.sp.jedit.View;
 39
 40import sidekick.SideKickParsedData;
 41import sidekick.ExpansionModel;
 42import sidekick.SideKickUpdate;
 43import sidekick.IAsset;
 44
 45import xml.completion.CompletionInfo;
 46import xml.completion.ElementDecl;
 47import xml.completion.EntityDecl;
 48import xml.completion.IDDecl;
 49import xml.parser.TagParser;
 50import xml.parser.XmlTag;
 51
 52import com.thaiopensource.xml.util.Name;
 53
 54import java.util.Enumeration;
 55
 56
 57//}}}
 58
 59/**
 60 * Encapsulates the results of parsing a buffer, either using Xerces or the
 61 * Swing HTML parser.
 62 */
 63public class XmlParsedData extends SideKickParsedData
 64{
 65	// sorting values
 66	public static final int SORT_BY_NAME = 0;
 67	public static final int SORT_BY_LINE = 1;
 68	public static final int SORT_BY_TYPE = 2;
 69	
 70	private static int sortBy = SORT_BY_LINE;
 71	protected static boolean sortDown = true;
 72	
 73	public boolean html;
 74	
 75	/** indicate that all xmlns: attributes appear only on the root element
 76	 *  so there's no need to find the exact namespace context of the parent node.
 77	 *  the namespace context of the root element is sufficient
 78	 */
 79	public boolean allNamespacesBindingsAtTop;
 80
 81	/**
 82	 * A mapping of namespace to CompletionInfo objects.
 83	 *  namespace of "" is the default namespace.
 84	 */
 85	private Map<String, CompletionInfo> mappings;
 86	
 87	/**
 88	 *  A map of all identifiers encountered during the parse,
 89	 *  indexed by name.
 90	 *  Used in XMLInsert IDs pane and for Hyperlinks navigation
 91	 */
 92	public Map<String,IDDecl> ids;
 93
 94	public List<EntityDecl> entities;
 95	public Map entityHash;
 96	
 97	/** entities are added to the noNamespaceCompletionInfo, so if a schema is used
 98	 * on top of DTD, the entities are lost.
 99	 * To prevent this, the entities are copied into the parsed data
100	 */
101	public void setCompletionInfo(String namespace, CompletionInfo info) {
102		if(namespace == null)namespace = "";
103		mappings.put(namespace, info);
104
105		// if this append, should remove entities declared in this CompletionInfo
106		// and nowhere else.
107		if(info == null)throw new UnsupportedOperationException("setCompletionInfo("+namespace+",null");
108		for(EntityDecl en : info.entities)
109		{
110			// avoid duplicates of &lt;, &amp; etc.
111			if(!entityHash.containsKey(en.name)) addEntity(en);
112		}
113	}
114	
115	//{{{ XmlParsedData constructor
116	public XmlParsedData(String fileName, boolean html)
117	{
118		super(fileName);
119		this.html = html;
120		mappings = new HashMap<String, CompletionInfo>();
121		ids = new TreeMap<String, IDDecl>(new Comparator<String>(){
122			public int compare(String s1,String s2){
123				return StandardUtilities.compareStrings(s1,s2,false);
124			}
125		});
126		allNamespacesBindingsAtTop = true;
127		entities = new ArrayList<EntityDecl>();
128		entityHash = new HashMap();
129		setCompletionInfo("",getNoNamespaceCompletionInfo());//register NoNamespaceCompletionInfo at least once for the common entities
130	} //}}}
131
132	//{{{ getNoNamespaceCompletionInfo() method
133	public CompletionInfo getNoNamespaceCompletionInfo()
134	{
135		CompletionInfo info = mappings.get("");
136		if(info == null)
137		{
138			info = new CompletionInfo();
139			mappings.put("",info);
140		}
141
142		return info;
143	} //}}}
144
145	//{{{ getCompletionInfo(namespace) method
146	public CompletionInfo getCompletionInfo(String ns)
147	{
148		return mappings.get(ns);
149	} //}}}
150
151	//{{{ getElementDecl(String,int) method
152	public ElementDecl getElementDecl(String name, int pos)
153	{
154		if(Debug.DEBUG_COMPLETION)Log.log(Log.DEBUG,XmlParsedData.class,
155			"getElementDecl("+name+","+pos+")");
156		
157		ElementDecl decl = null;
158		String prefix = getElementNamePrefix(name);
159		String localName = "".equals(prefix) ? name : name.substring(prefix.length()+1);
160	
161	
162		if(html)
163		{
164			decl = getElementDeclHTML(name,pos);
165		}
166		else
167		{
168			TreePath path = null;
169			Map<String,String> bindings = null;
170			
171			if(allNamespacesBindingsAtTop){
172				if(Debug.DEBUG_COMPLETION)Log.log(Log.DEBUG,XmlParsedData.class,
173					"allNamespacesBindingsAtTop");
174				bindings = getRootNamespaceBindings();
175			}else {
176				path = getTreePathForPosition(pos);
177				bindings = getNamespaceBindings(path);
178			}
179	
180			// find parent's CompletionInfo
181			CompletionInfo info;
182			
183			if(mappings.isEmpty()){
184				if(Debug.DEBUG_COMPLETION)Log.log(Log.DEBUG,XmlParsedData.class,
185					"no completionInfo");
186				return null;
187			}else if(mappings.size() == 1){
188				info = mappings.values().iterator().next();
189				if(Debug.DEBUG_COMPLETION)Log.log(Log.DEBUG,XmlParsedData.class,
190					"only 1 completionInfo, for ns "+info.namespace);
191			}else{
192				String NS = getNS(bindings,prefix);
193				info = mappings.get(NS);
194				if(Debug.DEBUG_COMPLETION)Log.log(Log.DEBUG,XmlParsedData.class,
195					"many completionInfos ("+mappings.keySet()+"); getting for NS ="+NS);
196			}
197			
198			if(info == null){
199				if(Debug.DEBUG_COMPLETION)Log.log(Log.DEBUG,XmlParsedData.class,
200					"CompletionInfo not found");
201			}else {
202				if(Debug.DEBUG_COMPLETION)Log.log(Log.DEBUG,XmlParsedData.class,
203					"got CompletionInfo for "+info.namespace);
204				
205				// find elementdecl inside CompletionInfo
206				if(info.nameConflict){
207					if(path == null)path = getTreePathForPosition(pos);
208					
209					// find the elements tree leading to parent in the document
210	
211					DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)path.getLastPathComponent();
212					XmlTag parentXmlTag = (XmlTag)parentNode.getUserObject();
213
214					if(Debug.DEBUG_COMPLETION)Log.log(Log.DEBUG,XmlParsedData.class,
215						"parentXmlTag="+parentXmlTag.getName());
216					
217					// SideKick tree is in sync
218					if(name.equals(parentXmlTag.getName()))
219					{
220						if(Debug.DEBUG_COMPLETION)Log.log(Log.DEBUG,XmlParsedData.class,
221							"SideKick tree is in sync");
222						decl = getElementDeclFromSideKick(parentNode,parentXmlTag);
223					}
224					else 
225					{
226						// it's not the parent ; let's say it's an ancestor
227						/* wrong approach : don't know which one to return. 
228						   So either
229						    - keep the Sidekick tree in sync (ie Parse on Keystroke) !
230						    - parse backward to reconstruct ancestry to the last Sidekick node
231						   Don't do this :
232							ElementDecl ancestorDecl = getElementDecl(parentNode,parentXmlTag);
233							
234							String NS = getNS(bindings,prefix);
235							decl = findElementDeclInDescendants(ancestorDecl,NS,localName, new ArrayList<ElementDecl>());
236						*/
237						if(Debug.DEBUG_COMPLETION)Log.log(Log.DEBUG,XmlParsedData.class,
238							"Sidekick tree out of sync ; giving up");
239						decl = null;
240					}
241				}else{
242					if(Debug.DEBUG_COMPLETION)Log.log(Log.DEBUG,XmlParsedData.class,
243						"no nameConflict");
244					if(info.elementHash.containsKey(localName)){
245						if(Debug.DEBUG_COMPLETION)Log.log(Log.DEBUG,XmlParsedData.class,
246							"global declaration");
247						decl = info.elementHash.get(localName);
248					}else{
249						if(Debug.DEBUG_COMPLETION)Log.log(Log.DEBUG,XmlParsedData.class,
250							"local declaration");
251						decl = info.getElementDeclLocal(localName);
252					}
253				}
254			}
255		}
256		return decl;
257	}
258	//}}}
259	
260	//{{{ getElementDeclInternal(name, pos) method
261	/**
262	 * finds a global declaration of an element, returns it without prefix
263	 * @param	name	qualified name (with prefix) of an element
264	 * @param	pos	used to get the namespace bindings
265	 */
266	private ElementDecl getElementDeclHTML(String name,int pos)
267	{
268		if(html)
269			name = name.toLowerCase();
270
271		String prefix = getElementNamePrefix(name);
272		
273		CompletionInfo info;
274		
275		if(mappings.isEmpty())
276		{
277			return null;
278		}	
279		// simple case where we don't need to get the namespace
280		else if(mappings.size() == 1)
281		{
282			info = mappings.values().iterator().next();
283		}
284		else
285		{
286			String ns = getNamespaceForPrefix(prefix,pos);
287			info = mappings.get(ns);
288		}
289
290		if(info == null)
291			return null;
292		else
293		{
294			String lName = getElementLocalName(name);
295			ElementDecl decl = info.elementHash.get(lName);
296			if(decl == null)
297				return null;
298			else
299				return decl;
300		}
301	} //}}}
302
303	//{{{ getElementDecl() method
304	private ElementDecl getElementDeclFromSideKick(DefaultMutableTreeNode node,XmlTag tag)
305	{
306		if(Debug.DEBUG_COMPLETION)Log.log(Log.DEBUG,XmlParsedData.class,
307			"getElementDecl("+node+","+tag.getName()+")");
308		CompletionInfo info = mappings.get(tag.namespace);
309
310		if(info == null)
311			return null;
312		else
313		{
314			String prefix =  tag.getPrefix();
315			String lName = tag.getLocalName();
316
317			if(!info.nameConflict && info.elementHash != null) {
318				return info.elementHash.get(lName);
319			}
320			else
321			{
322				DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) node.getParent();
323				if(parentNode.getUserObject() instanceof XmlTag)
324				{
325					XmlTag parentTag = (XmlTag)parentNode.getUserObject();
326					ElementDecl parentDecl = getElementDeclFromSideKick(parentNode,parentTag);
327					if(parentDecl != null && parentDecl.elementHash!=null && parentDecl.elementHash.containsKey(lName))
328					{
329						return parentDecl.elementHash.get(lName);
330					}
331				}
332				else if(info.elementHash != null)
333				{
334					// at root node : allowed to use a global ElementDecl
335					return info.elementHash.get(lName);
336				}
337				return null;
338			}
339		}
340	} //}}}
341
342	//{{{ getXPathForPosition() method
343	public String getXPathForPosition(int pos)
344	{
345		TreePath path = getTreePathForPosition(pos);
346		if(path == null)return null;
347		
348		DefaultMutableTreeNode tn = (DefaultMutableTreeNode)path.getLastPathComponent();
349		TreeNode[]steps = tn.getPath();
350		DefaultMutableTreeNode parent = (DefaultMutableTreeNode)steps[0];
351		StringBuilder xpath = new StringBuilder();
352		if(steps.length == 1)
353		{
354			//there is only the node with the file name
355			xpath = null;
356		}
357		else
358		{
359			parent = (DefaultMutableTreeNode)steps[1];
360			if( ! (parent.getUserObject() instanceof XmlTag))
361			{
362				return null;
363			}
364			Map<String,String> prefixToNS = new HashMap<String,String>();
365			Map<String,String> nsToPrefix = new HashMap<String,String>();
366			
367			Name[] preXPath = new Name[steps.length-2];
368			int[]  preXPathIndexes = new int[steps.length-2];
369			
370			Name rootName;
371
372			XmlTag curTag = (XmlTag)parent.getUserObject();
373			String lname = curTag.getLocalName();
374			String ns = curTag.namespace;
375			StringBuilder prefix = new StringBuilder(curTag.getPrefix());
376			
377			assert(ns != null);
378			
379			rootName = new Name(ns,lname);
380			
381			prefixToNS.put(prefix.toString(), ns);
382			nsToPrefix.put(ns, prefix.toString());
383			
384			for(int i=2;i<steps.length;i++)
385			{
386				DefaultMutableTreeNode cur=(DefaultMutableTreeNode)steps[i];
387
388				curTag = (XmlTag)cur.getUserObject();
389				ns = curTag.namespace;
390				lname = curTag.getLocalName();
391				prefix = new StringBuilder(curTag.getPrefix());
392				
393				int jCur = parent.getIndex(cur);
394				int cntChild = 0;
395				for(int j=0;j<=jCur;j++)
396				{
397					DefaultMutableTreeNode aChild = (DefaultMutableTreeNode)parent.getChildAt(j);
398					XmlTag aTag = (XmlTag)aChild.getUserObject();
399					if(lname.equals(aTag.getLocalName())
400						  && ns.equals(aTag.namespace))
401					{
402						cntChild++;
403					}
404				}
405				preXPath[i-2] = new Name(ns,lname);
406				preXPathIndexes[i-2] = cntChild;
407				
408				/* implementation choice here :
409				   I think the XPath will be more usable
410				   if the same prefix is re-used, even if it's 
411				   not the one in the document.
412				*/
413				if(!nsToPrefix.containsKey(ns))
414				{
415					// same prefix, other ns
416					if(    prefixToNS.containsKey(prefix)
417					   && !prefixToNS.get(prefix).equals(ns))
418					{
419						/* keep the prefix close
420						   to what was in the document
421						   (only a suffixed number)
422						 */
423						int uniq=0;
424						// special case for default
425						// prefix, since a prefix must
426						// begin with a letter
427						if("".equals(prefix))
428						{
429							prefix.append(' ');
430						}
431						while(prefixToNS.containsKey(prefix.toString() + uniq))
432						{
433							uniq++;
434						}
435						prefix.append(uniq);
436					}
437					prefixToNS.put(prefix.toString(), ns);
438					nsToPrefix.put(ns, prefix.toString());
439				}
440				
441				parent = cur;
442			}
443			
444			prefix = new StringBuilder(nsToPrefix.get(rootName.getNamespaceUri()));
445			xpath.append('/') ;
446			if(prefix.length() > 0) 
447				xpath.append(prefix).append(':');
448			xpath.append(rootName.getLocalName());
449			
450			for(int i=0;i<preXPath.length;i++)
451			{
452				prefix = new StringBuilder(nsToPrefix.get(preXPath[i].getNamespaceUri()));
453				xpath.append('/');
454				if(prefix.length() > 0)
455					xpath.append(prefix).append(':');
456				xpath.append(preXPath[i].getLocalName());
457				xpath.append('[').append(preXPathIndexes[i]).append(']'); 
458			}
459		}
460		return xpath == null ? null : xpath.toString();
461	}
462	//}}}
463	
464	//{{{ getAllowedElements() method
465	/** @return a list containing Elements or Attributes */
466	public List<ElementDecl> getAllowedElements(Buffer buffer, int pos)
467	{
468		/*{{{ debug only */
469		IAsset asset = getAssetAtOffset(pos);
470		if(Debug.DEBUG_COMPLETION && asset != null)Log.log(Log.DEBUG, XmlParsedData.class,
471			("asset at "+pos+" is :"+asset+" ("+asset.getStart().getOffset()+","+asset.getEnd().getOffset()+")"));
472		/* }}} */
473		
474		List<ElementDecl> returnValue = new LinkedList<ElementDecl>();
475
476		TagParser.Tag parentTag = null;
477		try
478		{
479			parentTag = TagParser.findLastOpenTag(buffer.getText(0,pos),pos,this);
480		}
481		catch (Exception e) {}
482			
483		if(Debug.DEBUG_COMPLETION)Log.log(Log.DEBUG,XmlParsedData.class,
484			"parentTag="+parentTag);
485		if(parentTag == null)
486		{
487			// add everything
488			for(CompletionInfo info: mappings.values()) {
489				info.getAllElements(returnValue);
490			}
491		}
492		else
493		{
494			ElementDecl parentDecl = null;
495
496			parentDecl = getElementDecl(parentTag.tag,parentTag.start+1);
497			if(parentDecl != null){
498				returnValue.addAll(parentDecl.getChildElements());
499			}
500		}
501		Collections.sort(returnValue, new ElementDecl.Compare());
502		return returnValue;
503	} //}}}
504
505	//{{{ getAllowedElements() method
506	/** get allowed elements at startPos or endPos.
507	 *  called by updateTagList only.
508	 *  ensures:
509	 *  - that startPos and endPos are not inside a tag (then returns all global elements),
510	 *  - that both startPos and endPos have a parent (may be different) or none has
511	 *    (then return startPos parent declaration's children).
512	 * 	DO KEEP in sync with the other getAllowedElements() !!
513	 *  @see XmlParsedData#getAllowedElements(Buffer, int) 
514	 */
515	public List<ElementDecl> getAllowedElements(Buffer buffer, int startPos, int endPos)
516	{
517		ArrayList<ElementDecl> returnValue = new ArrayList<ElementDecl>();
518		CharSequence bufferText = buffer.getSegment(0, buffer.getLength());
519		
520		// make sure we are not inside a tag
521		if(TagParser.isInsideTag(bufferText,startPos)) {
522			return returnValue;
523		}
524
525		// make sure we are not inside a tag
526		if(TagParser.isInsideTag(bufferText,endPos)) {
527			return returnValue;
528		}
529
530		TagParser.Tag startParentTag = TagParser.findLastOpenTag(
531			bufferText,startPos,this);
532
533		TagParser.Tag endParentTag = TagParser.findLastOpenTag(
534			bufferText,endPos,this);
535
536		if(startParentTag == null) { 
537			if(endParentTag == null) {
538				// add everything
539				for(CompletionInfo info: mappings.values()) {
540					info.getAllElements(returnValue);
541				}
542			}
543			else
544				return returnValue;
545		}
546		else if(endParentTag == null) {
547			return returnValue;
548		}
549		else
550		{
551			ElementDecl startParentDecl = getElementDecl(startParentTag.tag,startParentTag.start+1);
552
553			ElementDecl endParentDecl = getElementDecl(endParentTag.tag,endParentTag.start+1);
554
555			if(startParentDecl == null)
556				return returnValue;
557			else if(endParentDecl == null)
558				return returnValue;
559			else
560			{
561				returnValue.addAll(startParentDecl.getChildElements());
562				// only return elements allowed both at startPos and endPos
563				returnValue.retainAll(endParentDecl.getChildElements());
564			}
565		}
566
567		Collections.sort(returnValue,new ElementDecl.Compare());
568		return returnValue;
569	} //}}}
570
571	//{{{ getNamespaceBindings() method
572	/** namespace to prefix
573	 *  (from sidekick) 
574	 **/
575	public Map<String,String> getNamespaceBindings(int pos){
576		
577		if(html) return new HashMap<String,String>();
578		
579		if(allNamespacesBindingsAtTop){
580			return getRootNamespaceBindings();
581		}else{
582			TreePath path = getTreePathForPosition(pos);
583			if(path == null)return null;
584			else return getNamespaceBindings(path);
585		}
586	}
587	
588	/** namespace to prefix */
589	private Map<String,String> getNamespaceBindings(TreePath path){
590		Map<String,String> bindings = new HashMap<String,String>();
591		
592		if(path == null)return bindings;
593		if(html)return bindings;
594		
595		Object[] pathObjs = ((DefaultMutableTreeNode)path.getLastPathComponent()).getUserObjectPath();
596		for(int i=1;i<pathObjs.length;i++){ // first step is the name of the file
597			XmlTag t = (XmlTag)pathObjs[i];
598			if(t.namespaceBindings != null){
599				bindings.putAll(t.namespaceBindings);
600			}
601		}
602		return bindings;
603	}
604	//}}}
605	
606	//{{{ getRootNamespaceBindings() method
607	private Map<String,String> getRootNamespaceBindings(){
608		Map<String,String> bindings;
609		
610		if(!html && root != null && root.getChildCount() > 0){
611			TreeNode node = root.getChildAt(0);
612			DefaultMutableTreeNode dmtn = (DefaultMutableTreeNode)node;
613			Object o = dmtn.getUserObject();
614			if(o instanceof XmlTag){
615				bindings = ((XmlTag)o).namespaceBindings;
616			}else {
617				bindings = Collections.emptyMap();
618			}
619		}else {
620			bindings = Collections.emptyMap();
621		}
622		if(Debug.DEBUG_COMPLETION)Log.log(Log.DEBUG,XmlParsedData.class,
623			"getRootNamespaceBindings()=>"+bindings);
624		return bindings;
625	}
626	//}}}
627
628	//{{{ getNamespaceForPrefix() method
629	private String getNamespaceForPrefix(String prefix, int pos){
630		
631		if(html)return null;
632		
633		if(allNamespacesBindingsAtTop){
634			Map<String,String> bindings=getRootNamespaceBindings();
635			if(bindings != null){
636				String ns = getNS(bindings,prefix);
637				if(ns != null)return ns;
638			}
639		}else{
640			TreePath path = getTreePathForPosition(pos);
641			if(path == null)return null;
642			
643			Object[] pathObjs = ((DefaultMutableTreeNode)path.getLastPathComponent()).getUserObjectPath();
644			for(int i=pathObjs.length-1;i>0;i--){  //first object (i==0) is a SourceAsset for the file
645				XmlTag t = (XmlTag)pathObjs[i];
646				if(t.namespaceBindings != null){
647					for(Map.Entry<String,String> binding: t.namespaceBindings.entrySet()){
648						if(binding.getValue().equals(prefix))return binding.getKey();
649					}
650				}
651			}
652		}
653		// no namespace is "" in the mappings
654		if("".equals(prefix))return "";
655		else return null;
656	}
657	//}}}
658	
659	//{{{ getNS() method
660	private String getNS(Map<String,String> nsToPrefix, String prefix){
661		for(Map.Entry<String,String> en:nsToPrefix.entrySet()){
662			if(prefix.equals(en.getValue())){
663				return en.getKey();
664			}
665		}
666		return "";
667	}
668	//}}}
669	
670	//{{{ done() method
671	/**
672 	 * Causes node sorting to be done.  Subclasse can override for their own 
673 	 * purposes, for example, the TldXmlParsedData class renames nodes based 
674 	 * on child nodes. 
675 	 */
676	public void done(View view) {
677		sort(view);	
678	}
679	//}}}
680	
681	//{{{ setSortBy(int) method
682	public void setSortBy(int by) {
683		switch (by) {
684		    case SORT_BY_NAME:
685		    case SORT_BY_LINE:
686		    case SORT_BY_TYPE:
687			sortBy = by;
688			break;
689		}
690	}
691	//}}}
692	
693	//{{{ getSortBy() method
694	public int getSortBy() {
695		return sortBy;
696	}
697	//}}}
698	
699	//{{{ setSortDirection(boolean) method
700	public void setSortDirection(boolean down) {
701		sortDown = down;	
702	}
703	//}}}
704	
705	//{{{ sort(view) method
706	public void sort(final View view) {
707		SwingUtilities.invokeLater(new Runnable() {
708			public void run() {
709				sortChildren((DefaultMutableTreeNode)root);
710				tree.reload();
711				expansionModel = createExpansionModel().getModel();
712				// commented out because it triggers infinite reparsing
713				//EditBus.send(new SideKickUpdate(view));
714			}
715		} );
716	}
717	//}}}
718	
719	//{{{ sortChildren(node) method
720	private void sortChildren(DefaultMutableTreeNode node) {
721		List<DefaultMutableTreeNode> children = new ArrayList<DefaultMutableTreeNode>();
722		Enumeration en = node.children();
723		while(en.hasMoreElements()) {
724			children.add((DefaultMutableTreeNode)en.nextElement());   
725		}
726		Collections.sort(children, getSorter());
727		node.removeAllChildren();
728		for (DefaultMutableTreeNode child : children) {
729			node.add(child);
730			sortChildren(child);
731		}
732	}
733	//}}}
734
735	//{{{ getSorter() method
736	protected Comparator<DefaultMutableTreeNode> getSorter() {
737		return new Comparator<DefaultMutableTreeNode>() {
738			public int compare(DefaultMutableTreeNode tna, DefaultMutableTreeNode tnb) {
739			    int sortBy = getSortBy();
740			    switch (sortBy) {                // NOPMD, no breaks are necessary here
741				case SORT_BY_LINE:
742				    Integer my_line = new Integer(((XmlTag)tna.getUserObject()).getStart().getOffset());
743				    Integer other_line = new Integer(((XmlTag)tnb.getUserObject()).getStart().getOffset());
744				    return my_line.compareTo(other_line) * (sortDown ? 1 : -1);
745				case SORT_BY_TYPE:
746				    String my_on = ((XmlTag)tna.getUserObject()).getName();
747				    String other_on = ((XmlTag)tnb.getUserObject()).getName();
748				    int comp = my_on.compareTo(other_on) * (sortDown ? 1 : -1);
749				    return comp == 0 ? compareNames(tna, tnb) : comp;
750				case SORT_BY_NAME:
751				default:
752				    return compareNames(tna, tnb);
753			    }
754			}
755			
756			private int compareNames(DefaultMutableTreeNode tna, DefaultMutableTreeNode tnb) {
757			    // sort by name
758			    String my_name = ((XmlTag)tna.getUserObject()).getLongString();
759			    String other_name = ((XmlTag)tnb.getUserObject()).getLongString();
760			    return my_name.compareTo(other_name) * (sortDown ? 1 : -1);
761			}
762		} ;
763	}//}}}
764	
765	//{{{ createExpansionModel() method
766	protected ExpansionModel createExpansionModel() {
767		ExpansionModel em = new ExpansionModel();
768		em.add();   // root (filename node)
769		em.add();   // document node
770		if (root.getChildCount() != 0) {
771			for (int i = 0; i < root.getChildAt(0).getChildCount(); i++) {
772			    em.inc();   // first level children.  Is this enough?
773			}
774		}
775		return em;
776	}
777	//}}}
778	
779	//{{{ addEntity() method
780	public void addEntity(EntityDecl entity)
781	{
782		entities.add(entity);
783		if(entity.type == EntityDecl.INTERNAL
784			&& entity.value.length() == 1)
785		{
786			Character ch = new Character(entity.value.charAt(0));
787			entityHash.put(entity.name, ch);
788			entityHash.put(ch, entity.name);
789		}
790	} //}}}
791
792	//{{{ getObjectsTo(pos) method
793	public Object[] getObjectsTo(int pos){
794			TreePath path = getTreePathForPosition(pos);
795			if(path == null)return null;
796			
797			Object[] pathObjs = ((DefaultMutableTreeNode)path.getLastPathComponent()).getUserObjectPath();
798			return pathObjs;
799	}//}}}
800	
801	//{{{ getIDDecl(id) method
802	/**
803	 * convenience method to find an IDDecl by name
804	 * @return	found IDDecl or null
805	 */
806	public IDDecl getIDDecl(String id){
807		return ids.get(id);
808	}//}}}
809	
810	//{{{ getSortedIds() method
811	public List<IDDecl> getSortedIds(){
812		List<IDDecl> idl = new ArrayList<IDDecl>(ids.values());
813		return idl;
814	}//}}}
815	
816	//{{{ getParsedData() method
817	/**
818	 * get parsed data as XmlParsedData.
819	 * @param	view 			current view
820	 * @param	signalError		shows an error dialog when not an XmlParsedData
821	 * @return	parsed data or null
822	 */
823	public static XmlParsedData getParsedData(View view, boolean signalError)
824	{
825		SideKickParsedData _data = SideKickParsedData.getParsedData(view);
826	
827		if(_data==null || !(_data instanceof XmlParsedData))
828		{
829			if(signalError)
830			{
831				GUIUtilities.error(view,"xml-no-data",null);
832			}
833			return null;
834		}
835	
836		return (XmlParsedData)_data;
837	}//}}}
838
839	//{{{ getElementNamePrefix() method
840	public static String getElementNamePrefix(String name)
841	{
842		int index = name.indexOf(':');
843		if(index == -1)
844			return "";
845		else
846			return name.substring(0,index);
847	} //}}}
848
849	//{{{ getElementLocalName() method
850	public static String getElementLocalName(String name)
851	{
852		int index = name.indexOf(':');
853		if(index == -1)
854			return name;
855		else
856			return name.substring(index+1);
857	} //}}}
858}