1 /*
2  * @(#)HelpSet.java	1.108 06/10/30
3  *
4  * Copyright (c) 2006 Sun Microsystems, Inc.  All Rights Reserved.
5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
6  *
7  * This code is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License version 2 only, as
9  * published by the Free Software Foundation.  Sun designates this
10  * particular file as subject to the "Classpath" exception as provided
11  * by Sun in the LICENSE file that accompanied this code.
12  *
13  * This code is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16  * version 2 for more details (a copy is included in the LICENSE file that
17  * accompanied this code).
18  *
19  * You should have received a copy of the GNU General Public License version
20  * 2 along with this work; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
22  *
23  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
24  * CA 95054 USA or visit www.sun.com if you need additional information or
25  * have any questions.
26  */
27 
28 package javax.help;
29 
30 import java.net.URL;
31 import java.net.URLConnection;
32 import java.net.MalformedURLException;
33 import java.util.*;
34 import java.io.*;
35 import java.awt.Dimension;
36 import java.awt.Point;
37 import javax.help.event.EventListenerList;
38 import javax.help.DefaultHelpBroker;
39 import javax.help.event.HelpSetListener;
40 import javax.help.event.HelpSetEvent;
41 import javax.help.Map.ID;
42 
43 // implementation-specific
44 import com.sun.java.help.impl.Parser;
45 import com.sun.java.help.impl.ParserListener;
46 import com.sun.java.help.impl.ParserEvent;
47 import com.sun.java.help.impl.Tag;
48 import com.sun.java.help.impl.TagProperties;
49 import com.sun.java.help.impl.XmlReader;
50 import com.sun.java.help.impl.LangElement;
51 import javax.help.Map.ID;
52 import java.beans.PropertyChangeSupport;
53 import java.lang.reflect.Constructor;
54 /**
55  * A HelpSet  is a collection of help information consisting of a HelpSet
56  * file, table of contents (TOC), index, topic files, and Map file.
57  * The HelpSet file is the portal to the HelpSet.
58  *
59  * @author Roger D. Brinkley
60  * @author Eduardo Pelegri-Llopart
61  * @author Stepan Marek
62  * @version	1.108	10/30/06
63  */
64 
65 public class HelpSet implements Serializable{
66     private static String errorMsg = null;
67     /*
68      * Event listeners for adding to the HelpSet
69      */
70 
71     protected EventListenerList listenerList = new EventListenerList();
72 
73     /**
74      * PublicID (known to this XML processor) to the DTD for version 1.0 of the HelpSet
75      */
76     public static final String publicIDString =
77         "-//Sun Microsystems Inc.//DTD JavaHelp HelpSet Version 1.0//EN";
78 
79     /**
80      * PublicID (known to this XML processor) to the DTD for version 2.0 of the HelpSet
81      */
82     public static final String publicIDString_V2 =
83         "-//Sun Microsystems Inc.//DTD JavaHelp HelpSet Version 2.0//EN";
84 
85     /**
86      * Information for implementation customization.
87      *
88      * helpBroker/class is used to locate the class for a HelpBroker.
89      * helpBroker/loader is used to determine the ClassLoader to use.
90      */
91 
92     public static final Object implRegistry =
93         new StringBuffer("HelpSet.implRegistry");
94     public static final String helpBrokerClass = "helpBroker/class";
95     public static final String helpBrokerLoader = "helpBroker/loader";
96 
97     /**
98      * HelpSet context information.
99      *
100      * A HelpSet can map between keys (String) and values (Strings).
101      * There is a per-HelpSet value and a default value.
102      * The per-HelpSet value is specified in the appropriate section of the
103      * HelpSet file.
104      * The default value is global and only specified at class initialization time.
105      */
106 
107     public static final Object kitTypeRegistry =
108         new StringBuffer("JHelpViewer.kitTypeRegistry");
109     public static final Object kitLoaderRegistry =
110         new StringBuffer("JHelpViewer.kitLoaderRegistry");
111 
112     /**
113      * Creates an empty HelpSet that one can parse into.
114      * @param loader The ClassLoader to use. If loader is null, the default
115      * ClassLoader is used.
116      */
HelpSet(ClassLoader loader)117     public HelpSet(ClassLoader loader) {
118 	this.helpsets = new Vector();
119 	this.loader = loader;
120     }
121 
122     /**
123      * Creates an empty HelpSet. Uses the default ClassLoader
124      */
HelpSet()125     public HelpSet() {
126 	this.helpsets = new Vector();
127 	this.loader = null;
128     }
129 
130     /**
131      * Creates a HelpSet.  The locale for the data is either that indicated in
132      * the <tt>lang</tt> attribute of the <tt>helpset</tt> tag, or
133      * <tt>Locale.getDefault()</tt> if the <tt>lang</tt> attribute is not present.
134      *
135      * @param loader The class loader to use to locate any classes
136      * required by the navigators in the Helpset
137      * If loader is null, the default ClassLoader is used.
138      * @param helpset The URL to the HelpSet "file"
139      *
140      * @exception HelpSetException if there are problems parsing the helpset
141      */
HelpSet(ClassLoader loader, URL helpset)142     public HelpSet(ClassLoader loader, URL helpset) throws HelpSetException {
143 	this(loader);
144 	this.helpset = helpset;	// so it can be used in parseInto()
145 
146 	HelpSetFactory factory = new DefaultHelpSetFactory();
147 
148 	parseInto(helpset, factory);
149 	HelpSet x = factory.parsingEnded(this); // use the error reporting
150 
151 	if (x == null) {
152 	    // We had trouble parsing
153 	    // May need to revisit this...
154             throw new HelpSetException("Could not parse\n"+errorMsg);
155 	}
156     }
157 
158 
159     /**
160      * Locates a HelpSet file and return its URL.
161      * Applies localization conventions.
162      *
163      * @param cl The classloader to use when searching for the resource
164      * with the appropriate name. If cl is null the default
165      * ClassLoader is used.
166      * @param shortName The shortname of the resource.
167      * @param extension The extension of the resource.
168      * @param locale The desired Locale
169      * @see javax.help.HelpUtilities
170      */
171 
findHelpSet(ClassLoader cl, String shortName, String extension, Locale locale)172     public static URL findHelpSet(ClassLoader cl,
173 				  String shortName,
174 				  String extension,
175 				  Locale locale) {
176 	// Test for whether URL can be opened! - workaround Browser bugs
177 	return HelpUtilities.getLocalizedResource(cl,
178 						  shortName,
179 						  extension,
180 						  locale,
181 						  true);
182     }
183 
184     /**
185      * Locates a HelpSet file and return its URL.
186      *
187      * If the name does not end with the ".hs" extension, the
188      * ".hs" extension is appended and localization rules
189      * are applied to it.
190      *
191      * @param cl The classloader to use. If cl is null the default
192      * ClassLoader is used.
193      * @param name The name of the resource.
194      * @param locale The desired locale.
195      */
findHelpSet(ClassLoader cl, String name, Locale locale)196     public static URL findHelpSet(ClassLoader cl,
197                                  String name,
198                                  Locale locale) {
199 	String shortName;
200 	String extension;
201 	if (name.endsWith(".hs")) {
202 	    shortName = name.substring(0, name.length()-3);
203 	    extension = ".hs";
204 	} else {
205 	    shortName = name;
206 	    extension = ".hs";
207 	}
208 	return findHelpSet(cl, shortName, extension, locale);
209     }
210 
211     /**
212      * As above but default on locale to Locale.getDefault()
213      *
214      * @param cl The ClassLoader to use. If cl is null the default
215      * ClassLoader is used.
216      * @param name The name of the resource.
217      * @return Null if not found.
218      */
findHelpSet(ClassLoader cl, String name)219     public static URL findHelpSet(ClassLoader cl,
220                                  String name) {
221 	return findHelpSet(cl, name, Locale.getDefault());
222     }
223 
224 
225     /**
226      * Creates a presentation object for this HelpSet.
227      * Consults the <tt>implRegistry</tt> of <tt>KeyData</tt> for
228      * the class name (as helpBrokerClass) and for the ClassLoader
229      * instance (as helpBrokerLoader) and then tries to instantiate
230      * that class.  It then invokes <tt>setHelpSet()</tt> with
231      * this instance of HelpSet as the argument.  The resulting object is
232      * returned.
233      * @see createHelpBroker(String)
234      */
createHelpBroker()235     public HelpBroker createHelpBroker() {
236 	return createHelpBroker(null);
237     }
238 
239     /**
240      * Creates a presentation object for this HelpSet.
241      * Consults the <tt>implRegistry</tt> of <tt>KeyData</tt> for
242      * the class name (as helpBrokerClass) and for the ClassLoader
243      * instance (as helpBrokerLoader) and then tries to instantiate
244      * that class.  It then invokes <tt>setHelpSet()</tt> with
245      * this instance of HelpSet as the argument.  The resulting object is
246      * returned.
247      * @param presenationName A presentation name defined in the HelpSet
248      *                        that will dictate the presentation.
249      * @return HelpBroker The created HelpBroker
250      * @since 2.0
251      * @see createHelpBroker()
252      */
createHelpBroker(String presentationName)253     public HelpBroker createHelpBroker(String presentationName) {
254         HelpBroker back = null;
255 
256 	String classname =
257 	    (String) getKeyData(implRegistry, helpBrokerClass);
258 	ClassLoader loader =
259 	    (ClassLoader) getKeyData(implRegistry, helpBrokerLoader);
260 
261 	if (loader == null) {
262 	    loader = getLoader();
263 	}
264 	try {
265 	    Class c;
266 	    if (loader != null) {
267 		c = loader.loadClass(classname);
268 	    } else {
269 		c = Class.forName(classname);
270 	    }
271 	    back = (HelpBroker) c.newInstance();
272 	} catch (Throwable e) {
273 	    back = null;
274 	}
275 
276         if (back != null) {
277             back.setHelpSet(this);
278 	    HelpSet.Presentation hsPres = null;
279 	    // If there is a presentation name find it otherwise get the
280 	    // default if one exists.
281 	    if (presentationName != null) {
282 		hsPres = getPresentation(presentationName);
283 	    } else {
284 		hsPres = getDefaultPresentation();
285 	    }
286 	    if (hsPres != null) {
287 		back.setHelpSetPresentation(hsPres);
288 	    }
289 	}
290         return back;
291     }
292 
293     /**
294      * Adds a HelpSet, HelpSetEvents are generated.
295      * Adding a composed HelpSet to another is equivalent to
296      * adding all the HelpSets individually.
297      *
298      * @param hs The HelpSet to add.
299      */
add(HelpSet hs)300     public void add(HelpSet hs) {
301 	debug("add("+hs+")");
302 	helpsets.addElement(hs);
303 	fireHelpSetAdded(this, hs);
304 	combinedMap = null;	// invalidate the map
305     }
306 
307     /**
308      * Removes a HelpSet from this HelpSet; HelpSetEvents are generated
309      * Return True if it is found, otherwise false.
310      *
311      * @param hs The HelpSet to remove.
312      * @return False if the hs is null or was not in this HelpSet
313      */
remove(HelpSet hs)314     public boolean remove(HelpSet hs) {
315 	if (helpsets.removeElement(hs)) {
316 	    fireHelpSetRemoved(this, hs);
317 	    combinedMap = null;	// HERE - invalidate it - epll
318 	    return true;
319 	} else {
320 	    return false;
321 	}
322     }
323 
324     /**
325      * Enumerates all the HelpSets that have been added to this one.
326      *
327      * @return An enumeration of the HelpSets that have been added to
328      * this HelpSet.
329      */
getHelpSets()330     public Enumeration getHelpSets() {
331 	return helpsets.elements();
332     }
333 
334     /**
335      * Determines if a HelpSet is a sub-HelpSet of this object.
336      *
337      * @param hs The HelpSet to check
338      * @return true If <tt>hs</tt> is contained in this HelpSet or in one of its children.
339      */
contains(HelpSet hs)340     public boolean contains(HelpSet hs) {
341 	if (hs == this) {
342 	    return true;
343 	}
344 	for (Enumeration e = helpsets.elements();
345 	     e.hasMoreElements();) {
346 	    HelpSet child = (HelpSet) e.nextElement();
347 	    if (child.contains(hs)) {
348 		return true;
349 	    }
350 	}
351 	return false;
352     }
353 
354     /**
355      * Adds a listener for the HelpSetEvent posted after the model has
356      * changed.
357      *
358      * @param l - The listener to add.
359      * @see javax.help.HelpSet#removeHelpSetListener.
360      * @throws IllegalArgumentException if l is null.
361      */
addHelpSetListener(HelpSetListener l)362     public void addHelpSetListener(HelpSetListener l) {
363 	debug("addHelpSetListener("+l+")");
364 	listenerList.add(HelpSetListener.class, l);
365     }
366 
367     /**
368      * Removes a listener previously added with <tt>addHelpSetListener</tt>
369      *
370      * @param l - The listener to remove.
371      * @see javax.help.HelpSet#addHelpSetListener.
372      * @throws IllegalArgumentException if l is null.
373      */
removeHelpSetListener(HelpSetListener l)374     public void removeHelpSetListener(HelpSetListener l) {
375 	listenerList.remove(HelpSetListener.class, l);
376     }
377 
378     /**
379      * Fires a helpSetAdded event.
380      */
fireHelpSetAdded(Object source, HelpSet helpset)381     protected void fireHelpSetAdded(Object source, HelpSet helpset){
382 	Object[] listeners = listenerList.getListenerList();
383 	HelpSetEvent e = null;
384 
385 	for (int i = listeners.length - 2; i >= 0; i -= 2) {
386 	    if (listeners[i] == HelpSetListener.class) {
387 		if (e == null) {
388 		    e = new HelpSetEvent(this, helpset,
389 					 HelpSetEvent.HELPSET_ADDED);
390 		}
391 		((HelpSetListener)listeners[i+1]).helpSetAdded(e);
392 	    }
393 	}
394     }
395 
396     /**
397      * Fires a helpSetRemoved event.
398      */
fireHelpSetRemoved(Object source, HelpSet helpset)399     protected void fireHelpSetRemoved(Object source, HelpSet helpset){
400 	Object[] listeners = listenerList.getListenerList();
401 	HelpSetEvent e = null;
402 
403 	for (int i = listeners.length - 2; i >= 0; i -= 2) {
404 	    if (listeners[i] == HelpSetListener.class) {
405 		if (e == null) {
406 		    e = new HelpSetEvent(this, helpset,
407 					 HelpSetEvent.HELPSET_REMOVED);
408 		}
409 		((HelpSetListener)listeners[i+1]).helpSetRemoved(e);
410 	    }
411 	}
412     }
413 
414     // ======= Labels, etc =======
415     /**
416      * Gets the title of this HelpSet.
417      *
418      * @return the title
419      */
getTitle()420     public String getTitle() {
421 	if (title == null) {
422 	    return "";
423 	} else {
424 	    return title;
425 	}
426     }
427 
428     /**
429      * Sest the title for this HelpSet.  This is a bound property.
430      *
431      * @param title The title to set.
432      */
433 
setTitle(String title)434     public void setTitle(String title) {
435 	String oldTitle = this.title;
436 	this.title = title;
437 	changes.firePropertyChange("title", oldTitle, title);
438     }
439 
440     /**
441      * Gets the locale for this HelpSet.
442      *
443      * @return The locale.
444      */
getLocale()445     public Locale getLocale() {
446 	return locale;
447     }
448 
449     /**
450      * Sets the locale for this HelpSet.
451      * Strictly a private routine but the read-only property is bound.
452      *
453      * @param locale The locale to set.
454      */
setLocale(Locale l)455     private void setLocale(Locale l) {
456 	Locale oldLocale = locale;
457 	locale = l;
458 	changes.firePropertyChange("locale", oldLocale, locale);
459     }
460 
461     /**
462      * Returns
463      * the ID to visit when the user makes a "go home" gesture.
464      * This can be identified in the project file, but may also be changed
465      * programmatically or (possibly) via the UI.
466      *
467      * @return The ID of home. A null is returned if homeID is null
468      * or if an ID cannot be created for the homeID.
469      */
getHomeID()470     public ID getHomeID() {
471 	if (homeID == null) {
472 	    return null;
473 	} else {
474 	    try {
475 		return ID.create(homeID, this);
476 	    } catch (Exception ex) {
477 		return null;
478 	    }
479 	}
480     }
481 
482     /**
483      * Sets the Home ID for a HelpSet.  This is a bound property.
484      *
485      * @param The ID (in the Map) that identifies the default topic for this HelpSet. Null is valid homeID.
486      */
setHomeID(String homeID)487     public void setHomeID(String homeID) {
488 	String oldID = homeID;
489 	this.homeID = homeID;
490 	changes.firePropertyChange("homeID", oldID, homeID);
491     }
492 
493     // WARNING/HERE: In this implementation, the Map is not updated automatically
494     // so you need to come get a new one - epll
495 
496     // Warning.  This is not handling recursive aggregation
497 
498     /**
499      * The map for this HelpSet.  This map involves the closure of
500      * this HelpSet's children HelpSets.
501      *
502      * @return The map
503      */
getCombinedMap()504     public Map getCombinedMap() {
505  	if (combinedMap == null) {
506  	    combinedMap = new TryMap();
507 	    if (map != null) {
508 		combinedMap.add(map); // the local map
509 	    }
510 	    for (Enumeration e = helpsets.elements();
511 		 e.hasMoreElements(); ) {
512 		HelpSet hs = (HelpSet) e.nextElement();
513 		combinedMap.add(hs.getCombinedMap());
514 	    }
515 	}
516 	return combinedMap;
517     }
518 
519     /**
520      * Get the local (i.e.<!-- --> non-recursive) Map for this HelpSet.
521      * This Map does not include the Maps for its children.
522      *
523      * @return The Map object that associates ID->URL. A null map is valid.
524      */
getLocalMap()525     public Map getLocalMap() {
526 	return this.map;
527     }
528 
529     /**
530      * Set the Map for this HelpSet.  This Map object is not recursive; for example,
531      * it does not include the Maps for its children.
532      *
533      * @param The Map object that associates ID->URL. A null map is a valid.
534      */
setLocalMap(Map map)535     public void setLocalMap(Map map) {
536 	this.map = map;
537     }
538 
539     /**
540      * The URL that is the base for this HelpSet.
541      *
542      * @return The URL that is base to this HelpSet.
543      */
getHelpSetURL()544     public URL getHelpSetURL() {
545 	return helpset;
546     }
547 
548     /**
549      * A classloader to use when locating classes.
550      *
551      * @return The ClassLoader to use when locating classes mentioned
552      * in this HelpSet.
553      */
554 
getLoader()555     public ClassLoader getLoader() {
556 	return loader;
557     }
558 
559     /**
560      * NavigatorView describes the navigator views that are requested
561      * by this HelpSet.
562      *
563      * @return The array of NavigatorView.
564      */
565 
getNavigatorViews()566     public NavigatorView[] getNavigatorViews() {
567 	NavigatorView back[] = new NavigatorView[views.size()];
568 	views.copyInto(back);
569 	return back;
570     }
571 
572     /**
573      * Gets the NavigatorView with a specific name.
574      *
575      * @param The name of the desired navigator view.
576      */
getNavigatorView(String name)577     public NavigatorView getNavigatorView(String name) {
578 	debug("getNavigatorView("+name+")");
579 	for (int i=0; i<views.size(); i++) {
580 	    NavigatorView view = (NavigatorView) views.elementAt(i);
581 	    if (view.getName().equals(name)) {
582 		debug("  = "+view);
583 		return view;
584 	    }
585 	}
586 	debug("  = null");
587 	return null;
588     }
589 
590     /**
591      * HelpSet.Presentation describes the presentations that are defined
592      * by this HelpSet.
593      *
594      * @return The array of HelpSet.Presentations.
595      */
596 
getPresentations()597     public HelpSet.Presentation [] getPresentations() {
598 	HelpSet.Presentation back[] = new HelpSet.Presentation[presentations.size()];
599 	presentations.copyInto(back);
600 	return back;
601     }
602 
603     /**
604      * Gets the HelpSet.Presentation with a specific name.
605      *
606      * @param The name of the desired HelpSet.Presentation.
607      */
getPresentation(String name)608     public HelpSet.Presentation getPresentation(String name) {
609 	debug("getPresentation("+name+")");
610 	for (int i=0; i<presentations.size(); i++) {
611 	    HelpSet.Presentation pres = (HelpSet.Presentation) presentations.elementAt(i);
612 	    if (pres.getName().equals(name)) {
613 		debug("  = "+pres);
614 		return pres;
615 	    }
616 	}
617 	debug("  = null");
618 	return null;
619     }
620 
getDefaultPresentation()621     public HelpSet.Presentation getDefaultPresentation() {
622 	return defaultPresentation;
623     }
624 
625     /**
626      * Prints Name for this HelpSet.
627      */
toString()628     public String toString() {
629 	return getTitle();
630     }
631 
632     // ===== Public interfaces to parsing
633 
634     /**
635      * Parsed a HelpSet file.
636      */
parse(URL url, ClassLoader loader, HelpSetFactory factory)637     public static HelpSet parse(URL url,
638 				ClassLoader loader,
639 				HelpSetFactory factory) {
640 	HelpSet hs = new HelpSet(loader); // an empty HelpSet
641 	hs.helpset = url;
642 	hs.parseInto(url, factory);
643 	return factory.parsingEnded(hs);
644     }
645 
646     /**
647      * Parses into this HelpSet.
648      */
parseInto(URL url, HelpSetFactory factory)649     public void parseInto(URL url,
650 			  HelpSetFactory factory) {
651 	Reader src;
652 	try {
653 	    URLConnection uc = url.openConnection();
654 	    src = XmlReader.createReader(uc);
655 	    factory.parsingStarted(url);
656 	    (new HelpSetParser(factory)).parseInto(src, this);
657 	    src.close();
658 	} catch (Exception ex) {
659 	    factory.reportMessage("Got an IOException ("+
660 				       ex.getMessage()+
661 				       ")", false);
662 	    if(debug)
663                 ex.printStackTrace();
664 	}
665 
666 	// Now add any subhelpsets
667 	for (int i=0; i<subHelpSets.size(); i++) {
668 	    HelpSet subHS = (HelpSet) subHelpSets.elementAt(i);
669 	    add(subHS);
670 	}
671     }
672 
673     /**
674      * The default HelpSetFactory that processes HelpSets.
675      */
676 
677     public static class DefaultHelpSetFactory implements HelpSetFactory {
678 	private Vector messages = new Vector();
679 	private URL source;
680 	private boolean validParse = true;
681 
682 	/**
683 	 * Parsing starts.
684 	 */
parsingStarted(URL source)685 	public void parsingStarted(URL source) {
686 	    if (source == null) {
687 		throw new NullPointerException("source");
688 	    }
689 	    this.source = source;
690 	}
691 
692 	/**
693 	 * Process a DOCTYPE
694 	 * @param publicID the document. If null or is not valid a parsingError
695 	 * will be generated.
696 	 */
processDOCTYPE(String root, String publicID, String systemID)697 	public void processDOCTYPE(String root,
698 				   String publicID,
699 				   String systemID) {
700 	    if (publicID == null ||
701 		(publicID.compareTo(publicIDString) != 0 &&
702 		 publicID.compareTo(publicIDString_V2) != 0)) {
703 		parsingError("helpset.wrongPublicID", publicID);
704 	    }
705 	}
706 
707 	/**
708 	 * Processes a PI
709 	 */
processPI(HelpSet hs, String target, String data)710 	public void processPI(HelpSet hs,
711 			      String target,
712 			      String data) {
713 	    // ignore for now
714 	}
715 
716 	/**
717 	 * A title is found
718 	 */
processTitle(HelpSet hs, String value)719 	public void processTitle(HelpSet hs,
720 				 String value) {
721 	    String title = hs.getTitle();
722 	    if ((title != null) && !title.equals("")) {
723 		parsingWarning("helpset.wrongTitle", value, title);
724 	    }
725 	    hs.setTitle(value);
726 	}
727 
728 	/**
729 	 * A HomeID is found.
730 	 */
731 
processHomeID(HelpSet hs, String value)732 	public void processHomeID(HelpSet hs,
733 				  String value) {
734 	    ID homeID = hs.getHomeID();
735 	    if ((homeID == null) || homeID.equals("")) {
736 		//parsingError("helpset.wrongHomeID", value, homeID.id);
737                 hs.setHomeID(value);
738             }else{
739                 parsingError("helpset.wrongHomeID", value, homeID.id);
740             }
741 
742 	}
743 
744 	/**
745 	 * process a &lt;mapref&gt;
746 	 *
747 	 * @param Spec to the URL
748 	 * @param Attributes for the tag
749 	 */
processMapRef(HelpSet hs, Hashtable attributes)750 	public void processMapRef(HelpSet hs,
751 				  Hashtable attributes) {
752 	    String spec = (String) attributes.get("location");
753 	    URL hsURL = hs.getHelpSetURL();
754 	    try {
755 		Map map = new FlatMap(new URL(hsURL, spec), hs);
756 		Map omap = hs.getLocalMap();
757 		if (omap == null) {
758                     debug("map is null");
759 		    hs.setLocalMap(map);
760 		} else {
761 		    // to implement multiple Maps add this code:
762 		    //
763  		    if (omap instanceof TryMap) {
764                         debug("map is TryMap");
765 		        ((TryMap)omap).add(map);
766                         ///
767                         ///what about hs.setLocalMap////
768                         ///
769                         hs.setLocalMap(omap);
770 		    } else {
771                         debug("map is not TryMap");
772 		        TryMap tmap = new TryMap();
773                         tmap.add(omap);
774 		        tmap.add(map);
775 		        hs.setLocalMap(tmap);
776 		    }
777 
778 		}
779 	    } catch (MalformedURLException ee) {
780 		parsingError("helpset.malformedURL", spec);
781 	    } catch (IOException ee) {
782 		parsingError("helpset.incorrectURL", spec);
783 	    } catch (Exception ex) {
784 		// parsing error...
785 	    }
786 	}
787 
788 	/*
789 	 * Called per &lt;view&gt;
790 	 */
processView(HelpSet hs, String name, String label, String type, Hashtable viewAttributes, String data, Hashtable dataAttributes, Locale locale)791 	public void processView(HelpSet hs,
792 				String name,
793 				String label,
794 				String type,
795 				Hashtable viewAttributes,
796 				String data,
797 				Hashtable dataAttributes,
798 				Locale locale) {
799 
800 	     try {
801 		 NavigatorView view;
802 		 if (data != null) {
803 		     if (dataAttributes == null) {
804 			 dataAttributes = new Hashtable();
805 		     }
806 		     dataAttributes.put("data", data);
807 
808 		 }
809 
810 		 view = NavigatorView.create(hs,
811 					     name, label,
812 					     locale,
813 					     type,
814 					     dataAttributes);
815 
816 		 if (view == null) {
817 		     // ignore ??
818 		 } else {
819 
820 		     hs.addView(view);
821 		 }
822 	     } catch (Exception ex) {
823 		 // ignore this view...
824 	     }
825 	}
826 
827 	/*
828 	 * Called per &lt;presentation&gt;
829 	 */
processPresentation(HelpSet hs, String name, boolean defaultPresentation, boolean displayViews, boolean displayViewImages, Dimension size, Point location, String title, String imageID, boolean toolbar, Vector helpActions)830 	public void processPresentation(HelpSet hs,
831 					String name,
832 					boolean defaultPresentation,
833 					boolean displayViews,
834 					boolean displayViewImages,
835 					Dimension size,
836 					Point location,
837 					String title,
838 					String imageID,
839 					boolean toolbar,
840 					Vector helpActions) {
841 
842 	    Map.ID imageMapID = null;
843 	    try {
844 		imageMapID = ID.create(imageID, hs);
845 	    } catch (BadIDException bex2) {
846 	    }
847 	    try {
848 		HelpSet.Presentation presentation =
849 		    new HelpSet.Presentation(name, displayViews,
850 					     displayViewImages, size,
851 					     location, title, imageMapID,
852 					     toolbar, helpActions);
853 
854 		if (presentation == null) {
855 		    // ignore ??
856 		} else {
857 		    hs.addPresentation(presentation, defaultPresentation);
858 		}
859 	    } catch (Exception ex) {
860 		// ignore this presentation...
861 	    }
862 	}
863 
864 	/**
865 	 * Called when a sub-HelpSet is found.
866 	 */
processSubHelpSet(HelpSet hs, Hashtable attributes)867 	public void processSubHelpSet(HelpSet hs,
868 				      Hashtable attributes) {
869 	    debug("createSubHelpSet");
870 
871 	    String spec = (String) attributes.get("location");
872 	    URL base = hs.getHelpSetURL();
873 
874 	    debug("  location: "+spec);
875 	    debug("  base helpset: "+base);
876 
877 	    URL u = null;
878 	    HelpSet subHS = null;
879 	    try {
880 		u = new URL(base, spec);
881 		// test and see if the file is there
882 		// if it doesnt' through an exception all is ok
883 		InputStream is = u.openStream();
884 		if (is != null) {
885 		    subHS = new HelpSet(hs.getLoader(), u);
886 		    if (subHS != null) {
887 			hs.addSubHelpSet(subHS);
888 		    }
889 		}
890 	    } catch (MalformedURLException ex) {
891 		// ignore a malformed URL
892 		// The subhelpset is just ignored
893 	    } catch (IOException ex) {
894 		// ignore an IOException
895 		// The subhelpset is just ignored
896 	    } catch (HelpSetException ex) {
897 		parsingError("helpset.subHelpSetTrouble", spec);
898 	    }
899 	}
900 
901 	/**
902 	 * Reports an error message.
903 	 */
reportMessage(String msg, boolean validParse)904 	public void reportMessage(String msg, boolean validParse) {
905 	    messages.addElement(msg);
906 	    this.validParse = this.validParse && validParse;
907 	}
908 
909 	/**
910 	 * Enumerates all the error messages.
911 	 */
listMessages()912 	public Enumeration listMessages() {
913 	    return messages.elements();
914 	}
915 
916 	/**
917 	 * Parsing has ended. Last chance to do something
918 	 * to the HelpSet.
919 	 * @param hs The HelpSet the parsing ended on. A null hs is valid.
920 	 */
parsingEnded(HelpSet hs)921 	public HelpSet parsingEnded(HelpSet hs) {
922 	    HelpSet back = hs;
923 	    if (! validParse) {
924 		// A parse with problems...
925 		back = null;
926 
927                 String errMsg = "Parsing failed for "+source;
928                 //System.err.println(errMsg);
929                 messages.addElement(errMsg);
930 
931 		for (Enumeration e = messages.elements();
932 		     e.hasMoreElements();) {
933 		    String msg = (String) e.nextElement();
934                     if(debug)
935                         System.err.println(msg);
936                     if(HelpSet.errorMsg == null)
937                         HelpSet.errorMsg = msg;
938                     else{
939                        HelpSet.errorMsg = HelpSet.errorMsg+"\n";
940                        HelpSet.errorMsg = HelpSet.errorMsg + msg;
941                     }
942 
943 		}
944 	    }
945 	    return back;
946 	}
947 
948 	// Convenience methods
parsingError(String key)949 	private void parsingError(String key) {
950 	    String s = HelpUtilities.getText(key);
951 	    reportMessage(s, false); // tree will be wrong
952 	}
953 
954 	/**
955 	 * @throws Error if key is invalid.
956 	 */
parsingError(String key, String s)957 	private void parsingError(String key, String s) {
958 	    String msg = HelpUtilities.getText(key, s);
959 	    reportMessage(msg, false); // tree will be wrong
960 	}
961 
962 	/**
963 	 * @throws Error if key is invalid.
964 	 */
parsingError(String key, String s1, String s2)965 	private void parsingError(String key, String s1, String s2) {
966 	    String msg = HelpUtilities.getText(key, s1, s2);
967 	    reportMessage(msg, false); // tree will be wrong
968 	}
969 
parsingWarning(String key, String s1, String s2)970 	private void parsingWarning(String key, String s1, String s2) {
971 	    String msg = HelpUtilities.getText(key, s1, s2);
972 	    reportMessage(msg, true); // warning only
973 	}
974 
975     } // End of DefaultHelpSetFactory
976 
977 
978     /**
979      * HelpSet Presentation class. Contains information concerning a
980      * presentation in a HelpSet file
981      * @since 2.0
982      */
983     public static class Presentation {
984 
985 	private String name;
986 	private boolean displayViews;
987 	private boolean displayViewImages;
988 	private Dimension size;
989 	private Point location;
990 	private String title;
991 	private boolean toolbar;
992 	private Vector helpActions;
993 	private Map.ID imageID;
994 
Presentation(String name, boolean displayViews, boolean displayViewImages, Dimension size, Point location, String title, Map.ID imageID, boolean toolbar, Vector helpActions)995 	public Presentation (String name,
996 			     boolean displayViews,
997 			     boolean displayViewImages,
998 			     Dimension size,
999 			     Point location,
1000 			     String title,
1001 			     Map.ID imageID,
1002 			     boolean toolbar,
1003 			     Vector helpActions) {
1004 	    this.name = name;
1005 	    this.displayViews = displayViews;
1006 	    this.displayViewImages = displayViewImages;
1007 	    this.size = size;
1008 	    this.location = location;
1009 	    this.title = title;
1010 	    this.imageID = imageID;
1011 	    this.toolbar = toolbar;
1012 	    this.helpActions = helpActions;
1013 	}
1014 
getName()1015 	public String getName() {
1016 	    return name;
1017 	}
1018 
getTitle()1019 	public String getTitle() {
1020 	    return title;
1021 	}
1022 
getImageID()1023 	public Map.ID getImageID() {
1024 	    return imageID;
1025 	}
1026 
isViewDisplayed()1027 	public boolean isViewDisplayed() {
1028 	    return displayViews;
1029 	}
1030 
isViewImagesDisplayed()1031 	public boolean isViewImagesDisplayed() {
1032 	    return displayViewImages;
1033 	}
1034 
getSize()1035 	public Dimension getSize() {
1036 	    return size;
1037 	}
1038 
getLocation()1039 	public Point getLocation() {
1040 	    return location;
1041 	}
1042 
isToolbar()1043 	public boolean isToolbar() {
1044 	    return toolbar;
1045 	}
1046 
1047 	/**
1048 	 * Returns an Enumeration HelpActions created from the
1049 	 * list of Actions in the Presentation.
1050 	 *
1051 	 * @see HelpAction
1052 	 */
getHelpActions(HelpSet hs, Object control)1053 	public Enumeration getHelpActions(HelpSet hs, Object control) {
1054 	    Vector actions = new Vector();
1055 	    ClassLoader loader = hs.getLoader();
1056 	    Class klass;
1057 	    Constructor konstructor;
1058 	    HelpAction action;
1059 
1060 	    if (helpActions == null) {
1061 		// got a nutziod who didn't check the isToolbar
1062 		// just return back the empty vector elements
1063 		return actions.elements();
1064 	    }
1065 	    Enumeration actionEnum = helpActions.elements();
1066 	    while (actionEnum.hasMoreElements()) {
1067 		HelpSetFactory.HelpAction act =
1068 		    (HelpSetFactory.HelpAction)actionEnum.nextElement();
1069 
1070 		try {
1071 		    Class types[] = { Object.class };
1072 		    Object args[] = { control };
1073 		    if (loader == null) {
1074 			klass = Class.forName(act.className);
1075 		    } else {
1076 			klass = loader.loadClass(act.className);
1077 		    }
1078 		    konstructor = klass.getConstructor(types);
1079 		    action = (HelpAction) konstructor.newInstance(args);
1080 
1081 		    // The HelpAction has been added now add any known
1082 		    // attributes
1083 		    if (act.attr.containsKey("image")) {
1084 			String imageID = (String) act.attr.get("image");
1085 			try {
1086 			    Map.ID id = Map.ID.create(imageID, hs);
1087 			    javax.swing.ImageIcon icon = null;
1088 			    Map map = hs.getCombinedMap();
1089 			    URL url = map.getURLFromID(id);
1090 			    icon = new javax.swing.ImageIcon(url);
1091 			    action.putValue("icon", icon);
1092 			} catch (Exception ex) {
1093 			}
1094 		    }
1095 		    actions.add(action);
1096 		} catch (Exception ex) {
1097 		    throw new RuntimeException("Could not create HelpAction " +
1098 					       act.className);
1099 		}
1100 	    }
1101 
1102 	    return actions.elements();
1103 	}
1104 
1105     }
1106 
1107     // =========== Protected Methods for use by subclasses ==========
1108 
1109     /**
1110      * Adds a NavigatorView to the current list.
1111      */
addView(NavigatorView view)1112     protected void addView(NavigatorView view) {
1113 	views.addElement(view);
1114     }
1115 
1116     /**
1117      * Adds a SubHelpSet to the current list.
1118      */
1119 
addSubHelpSet(HelpSet hs)1120     protected void addSubHelpSet(HelpSet hs) {
1121 	subHelpSets.addElement(hs);
1122     }
1123 
1124     /**
1125      * Adds a HelpSet.Presentation to the current list.
1126      */
addPresentation(HelpSet.Presentation presentation, boolean defaultPres)1127     protected void addPresentation(HelpSet.Presentation presentation,
1128 				   boolean defaultPres) {
1129 	presentations.addElement(presentation);
1130 	if (defaultPres) {
1131 	    defaultPresentation = presentation;
1132 	}
1133     }
1134 
1135     // ============= Simple registry ============
1136 
1137     /**
1138      * Gets some Data for a Key in a given context.
1139      * Local (per HelpSet instance) data is searched first, then defaults.
1140      */
1141 
getKeyData(Object context, String key)1142     public Object getKeyData(Object context,
1143 			     String key) {
1144 	Object back = null;
1145 	Hashtable h = (Hashtable) localKeys.get(context);
1146 	if (h != null) {
1147 	    back = h.get(key);
1148 	}
1149 	if (back == null) {
1150 	    h = (Hashtable) defaultKeys.get(context);
1151 	    if (h != null) {
1152 		back = h.get(key);
1153 	    }
1154 	}
1155 	return back;
1156     }
1157 
1158     /**
1159      * Sets some local KeyData on a given context.  The information is set on
1160      * a per-HelpSet basis.
1161      */
setKeyData(Object context, String key, Object data)1162     public void setKeyData(Object context,
1163 			   String key,
1164 			   Object data) {
1165 	Hashtable h = (Hashtable) localKeys.get(context);
1166 	if (h == null) {
1167 	    h = new Hashtable();
1168 	    localKeys.put(context, h);
1169 	}
1170 	h.put(key, data);
1171     }
1172 
1173     /**
1174      * Default initialization.  This can only be done from within this class.
1175      */
setDefaultKeyData(Object context, String key, Object data)1176     private static void setDefaultKeyData(Object context,
1177 					  String key,
1178 					  Object data) {
1179 	if (defaultKeys == null) {
1180 	    defaultKeys = new Hashtable();
1181 	}
1182 	Hashtable h = (Hashtable) defaultKeys.get(context);
1183 	if (h == null) {
1184 	    h = new Hashtable();
1185 	    defaultKeys.put(context, h);
1186 	}
1187 	h.put(key, data);
1188     }
1189 
1190 
1191     /**
1192      * Initializes the default registries.
1193      */
1194     static {
setDefaultKeyData(implRegistry, helpBrokerClass, R)1195         setDefaultKeyData(implRegistry,
1196 			  helpBrokerClass, "javax.help.DefaultHelpBroker");
setDefaultKeyData(kitTypeRegistry, R, R)1197 	setDefaultKeyData(kitTypeRegistry,
1198 			  "text/html", "com.sun.java.help.impl.CustomKit");
1199 
1200 	ClassLoader cl = HelpSet.class.getClassLoader();
1201 	if (cl != null) {
setDefaultKeyData(implRegistry, helpBrokerLoader, cl)1202 	    setDefaultKeyData(implRegistry,
1203 			      helpBrokerLoader, cl);
setDefaultKeyData(kitLoaderRegistry, R, cl)1204 	    setDefaultKeyData(kitLoaderRegistry,
1205 			      "text/html", cl);
1206 	}
1207     }
1208 
1209     //======== Private members =========
1210 
1211     private String title;	// the title for this helpset
1212     private Map map;		// the local map
1213     private TryMap combinedMap;	// the combined ID<->URL map
1214     private URL helpset;	// the HelpSet from where to search
1215     private String homeID;
1216     private Locale locale = Locale.getDefault();
1217 
1218     private transient ClassLoader loader;	// encapsulates loading...
1219 
1220     private Vector views = new Vector();
1221 
1222     private Vector presentations = new Vector();
1223 
1224     private HelpSet.Presentation defaultPresentation = null;
1225 
1226     private Vector helpsets;	// All the helpsets added to this one
1227 
1228     private static HelpBroker defaultHelpBroker = null; // the default HelpBroker
1229 
1230     private Vector subHelpSets = new Vector();
1231 
1232     // Default and Local Hashtables for keys
1233 
1234     private static Hashtable defaultKeys;
1235     private Hashtable localKeys = new Hashtable();
1236 
1237     private PropertyChangeSupport changes = new PropertyChangeSupport(this);
1238 
1239     // ============= PRIVATE Parsing Class ========
1240 
1241     /**
1242      * Inner class for parsing a TOC stream.
1243      *
1244      * WARNING!! This class is an interim solution until when we move to a
1245      * real XML parser.  This is not a public class.  Clients should only use
1246      * the parse method in the enclosing class.
1247      */
1248 
1249     private static class HelpSetParser implements ParserListener {
1250 	private Stack tagStack;	// the collection of active Parse tags
1251 	private Locale defaultLocale;
1252 	private Locale lastLocale;
1253 	private HelpSet myHS;	// my HelpSet
1254 	private Locale myHSLocale; // its locale
1255 	private HelpSetFactory factory;	// the factory
1256 
1257 
1258 	private String tagName;	  // genericly used for views and presentation
1259 	private String viewLabel;
1260 	private String viewType;
1261 	private String viewEngine;
1262         private String tagImage;
1263         private String helpActionImage;
1264 	private String viewData;
1265         private String viewMergeType;
1266 	private Hashtable htData;
1267 
1268 	private boolean defaultPresentation = false;
1269 	private boolean displayViews = true;
1270 	private boolean displayViewImages = true;
1271 	private Dimension size;
1272 	private Point location;
1273 	private String presentationTitle;
1274 	private boolean toolbar;
1275 	private Vector helpActions;
1276 	private String helpAction;
1277 
1278 	/**
1279 	 * Creates a Parser (Listener) instance.
1280 	 */
HelpSetParser(HelpSetFactory factory)1281 	HelpSetParser (HelpSetFactory factory) {
1282 	    this.factory = factory;
1283 	}
1284 
1285 	/**
1286 	 * Parses a reader into a HelpSet.
1287 	 */
parseInto(Reader src, HelpSet hs)1288 	synchronized void parseInto(Reader src,
1289 				    HelpSet hs)
1290 	    throws IOException
1291         {
1292 	    tagStack = new Stack();
1293 	    defaultLocale = hs.getLocale();
1294 	    lastLocale = defaultLocale;
1295 	    myHS = hs;
1296 	    myHSLocale = hs.getLocale();
1297 	    Parser parser = new Parser(src); // the XML parser instance
1298 	    parser.addParserListener(this);
1299 	    parser.parse();
1300 	}
1301 
tagFound(ParserEvent e)1302 	public void tagFound(ParserEvent e) {
1303             debug("tagFound " + e.getTag().name);
1304 	    Locale locale = null;
1305 	    LangElement le;
1306 	    Tag tag = e.getTag();
1307 	    String name = tag.name;
1308 	    int x=0, y=0, width=0, height=0;
1309 	    TagProperties attr = tag.atts;
1310 	    Hashtable ht = (attr == null) ? null : attr.getHashtable();
1311 
1312 	    if (attr != null) {
1313 		String lang = attr.getProperty("xml:lang");
1314 		locale = HelpUtilities.localeFromLang(lang);
1315                 viewMergeType = attr.getProperty("mergetype");
1316 		helpActionImage = attr.getProperty("image");
1317 		String value = null;
1318 		value = attr.getProperty("width");
1319 		if (value != null) {
1320 		    width = Integer.parseInt(value);
1321 		}
1322 		value = null;
1323 		value = attr.getProperty("height");
1324 		if (value != null) {
1325 		    height = Integer.parseInt(value);
1326 		}
1327 		value = null;
1328 		value = attr.getProperty("x");
1329 		if (value != null) {
1330 		    x = Integer.parseInt(value);
1331 		}
1332 		value = null;
1333 		value = attr.getProperty("y");
1334 		if (value != null) {
1335 		    y = Integer.parseInt(value);
1336 		}
1337 		value = null;
1338 		value = attr.getProperty("default");
1339 		if (value != null && value.equals("true")) {
1340 		    defaultPresentation = true;
1341 		}
1342 		value = null;
1343 		value = attr.getProperty("displayviews");
1344 		if (value != null && value.equals("false")) {
1345 		    displayViews = false;
1346 		}
1347 		value = null;
1348 		value = attr.getProperty("displayviewimages");
1349 		if (value != null && value.equals("false")) {
1350 		    displayViewImages = false;
1351 		}
1352 	    }
1353 	    if (locale == null) {
1354 		locale = lastLocale;
1355 	    }
1356 
1357 	    if (name.equals("helpset")) {
1358 		if (tag.isEnd) {
1359 		    removeTag(tag);
1360 		} else {
1361 		    // Check and see if the locale is different from the
1362 		    // defaultLocale. If it is then reset the locale.
1363  		    if (! locale.equals(defaultLocale) &&
1364 			! locale.equals(myHSLocale)) {
1365 			if (locale != null) {
1366 			    myHS.setLocale(locale);
1367 			    defaultLocale = locale;
1368 			}
1369 		    }
1370 		    if (attr != null) {
1371 		        String version = attr.getProperty("version");
1372 			if (version != null &&
1373 			    (version.compareTo("1.0") != 0 &&
1374 			     version.compareTo("2.0") != 0)) {
1375 			    parsingError("helpset.unknownVersion", version);
1376 			}
1377 		    }
1378 		    addTag(tag, locale);
1379 		}
1380 		return;
1381 	    }
1382 
1383 	    if (tagStack.empty()) {
1384 		parsingError("helpset.wrongTopLevel", name);
1385 	    }
1386 
1387 	    // Get the parents name
1388 	    le = (LangElement) tagStack.peek();
1389 	    String pname = ((Tag) le.getTag()).name; // the parent
1390 
1391 	    if (name.equals("title")) {
1392 		// TITLE tag
1393 		if (tag.isEnd) {
1394 		    removeTag(tag);		// processing was done in textFound()
1395 		} else {
1396 		    if ((! pname.equals("helpset")) &&
1397 			(! pname.equals("presentation"))){
1398 			wrongParent(name, pname);
1399 		    }
1400  		    if (! locale.equals(defaultLocale) &&
1401 			! locale.equals(myHSLocale)) {
1402 			wrongLocale(locale, defaultLocale, myHSLocale);
1403 		    }
1404 		    addTag(tag, locale);
1405 		}
1406 	    } else if (name.equals("homeID")) {
1407 		// HOMEID tags
1408 		if (tag.isEnd) {
1409 		    removeTag(tag);		// processing was done in textFound()
1410 		} else {
1411 		    if (! pname.equals("maps")) {
1412 			wrongParent(name, pname);
1413 		    }
1414 		    addTag(tag, locale);
1415 		}
1416 	    } else if (name.equals("mapref")) {
1417 		// MAPREF tags
1418 
1419 		// Remove from stack if an empty tag
1420 		if (tag.isEnd && !tag.isEmpty) {
1421 		    removeTag(tag);
1422 		} else {
1423 		    if (! pname.equals("maps")) {
1424 			wrongParent(name, pname);
1425 		    }
1426 		    // add the tag if not an empty tag
1427 		    if (! tag.isEmpty) {
1428 			addTag(tag, locale);
1429 		    }
1430 		    // Process the tag
1431 		    factory.processMapRef(myHS,
1432 					  ht);
1433 		}
1434 	    } else if (name.equals("data")) {
1435 		// DATA tag
1436 		if (tag.isEnd) {
1437 		    removeTag(tag);
1438 		} else {
1439 		    if (! pname.equals("view")) {
1440 			wrongParent(name, pname);
1441 		    } else {
1442 			addTag(tag, locale);
1443 		    }
1444 		    htData = ht;
1445 		}
1446 	    } else if (name.equals("name") ||
1447 		       name.equals("type") ||
1448 		       name.equals("image")){
1449 		// NAME, TYPE, IMAGE tag
1450 		if (tag.isEnd) {
1451 		    removeTag(tag);
1452 		} else {
1453 		    if ((! pname.equals("view")) &&
1454 			(! pname.equals("presentation"))) {
1455 			wrongParent(name, pname);
1456 		    } else {
1457 			addTag(tag, locale);
1458 		    }
1459 		}
1460 	    } else if (name.equals("label")) {
1461 		// LABEL tag
1462 		// Special processing to check the locale attribute.
1463 		if (tag.isEnd) {
1464 		    removeTag(tag);
1465 		} else {
1466 		    if (! pname.equals("view")) {
1467 			wrongParent(name, pname);
1468 		    } else {
1469 			if (! locale.equals(defaultLocale) &&
1470 			    ! locale.equals(myHSLocale)) {
1471 			    wrongLocale(locale, defaultLocale, myHSLocale);
1472 			}
1473 			addTag(tag, locale);
1474 		    }
1475 		}
1476 	    } else if (name.equals("view")) {
1477 		// VIEW tag
1478 		if (tag.isEnd) {
1479 		    removeTag(tag);
1480 
1481                     if (tagImage != null) {
1482                         if (htData == null) {
1483                             htData = new Hashtable();
1484 			}
1485                         htData.put("imageID", tagImage);
1486                     }
1487 
1488                     if (viewMergeType != null) {
1489                         if(htData == null) {
1490                             htData = new Hashtable();
1491 			}
1492                         htData.put("mergetype",viewMergeType);
1493                     }
1494 
1495 		    factory.processView(myHS,
1496 					tagName,
1497 					viewLabel,
1498 					viewType,
1499 					ht,
1500 					viewData,
1501 					htData,
1502 					locale);
1503                     tagName = null;
1504                     viewLabel = null;
1505                     viewType = null;
1506                     tagImage = null;
1507                     viewData = null;
1508                     htData = null;
1509                     viewMergeType = null;
1510 
1511 		} else {
1512 		    if (! pname.equals("helpset")) {
1513 			wrongParent(name, pname);
1514 		    } else {
1515 			addTag(tag, locale);
1516 		    }
1517 		}
1518 	    } else if (name.equals("presentation")) {
1519 		// Presentation tag
1520 		if (tag.isEnd) {
1521 		    removeTag(tag);
1522 
1523 		    factory.processPresentation(myHS,
1524 						tagName,
1525 						defaultPresentation,
1526 						displayViews,
1527 						displayViewImages,
1528 						size,
1529 						location,
1530 						presentationTitle,
1531 						tagImage,
1532 						toolbar,
1533 						helpActions);
1534 
1535                     tagName = null;
1536                     defaultPresentation = false;
1537                     displayViews = true;
1538                     displayViewImages = true;
1539                     size = null;
1540                     location = null;
1541                     presentationTitle = null;
1542 		    tagImage = null;
1543 		    toolbar = false;
1544 		    helpActions = null;
1545 
1546 		} else {
1547 		    if (! pname.equals("helpset")) {
1548 			wrongParent(name, pname);
1549 		    } else {
1550 			addTag(tag, locale);
1551 		    }
1552 		}
1553 	    } else if (name.equals("size")) {
1554 		// DATA tag
1555 		if (tag.isEnd) {
1556 		    if (size == null) {
1557 			size = new Dimension(width, height);
1558 		    } else {
1559 			size.setSize(width, height);
1560 		    }
1561 		    width = 0;
1562 		    height = 0;
1563 		    if (!tag.isEmpty) {
1564 			removeTag(tag);
1565 		    }
1566 		} else {
1567 		    if (! pname.equals("presentation")) {
1568 			wrongParent(name, pname);
1569 		    } else {
1570 			addTag(tag, locale);
1571 			size = new Dimension();
1572 		    }
1573 		}
1574 	    } else if (name.equals("location")) {
1575 		// DATA tag
1576 		if (tag.isEnd) {
1577 		    if (location == null) {
1578 			location = new Point(x, y);
1579 		    } else {
1580 			location.setLocation(x, y);
1581 		    }
1582 		    x = 0;
1583 		    y = 0;
1584 		    if (!tag.isEmpty) {
1585 			removeTag(tag);
1586 		    }
1587 		} else {
1588 		    if (! pname.equals("presentation")) {
1589 			wrongParent(name, pname);
1590 		    } else {
1591 			addTag(tag, locale);
1592 			location = new Point();
1593 		    }
1594 		}
1595 	    } else if (name.equals("toolbar")) {
1596 		// DATA tag
1597 		if (tag.isEnd) {
1598 		    removeTag(tag);
1599 		} else {
1600 		    if (! pname.equals("presentation")) {
1601 			wrongParent(name, pname);
1602 		    } else {
1603 			addTag(tag, locale);
1604 			helpActions = new Vector();
1605 			toolbar = true;
1606 		    }
1607 		}
1608 	    } else if (name.equals("helpaction")) {
1609 		// DATA tag
1610 		if (tag.isEnd) {
1611 		    removeTag(tag);
1612 		    if (helpAction != null) {
1613 			Hashtable tmp = new Hashtable();
1614 			helpActions.add(new HelpSetFactory.HelpAction(helpAction, tmp));
1615 			if (helpActionImage != null) {
1616 			    tmp.put("image", helpActionImage);
1617 			    helpActionImage = null;
1618 			}
1619 			helpAction = null;
1620 		    }
1621 		} else {
1622 		    if (! pname.equals("toolbar")) {
1623 			wrongParent(name, pname);
1624 		    } else {
1625 			addTag(tag, locale);
1626 		    }
1627 		}
1628 	    } else if (name.equals("maps")) {
1629 		// MAPS tag
1630 		if (tag.isEnd) {
1631 		    removeTag(tag);
1632 		} else {
1633 		    if (! pname.equals("helpset")) {
1634 			wrongParent(name, pname);
1635 		    } else {
1636 			addTag(tag, locale);
1637 		    }
1638 		}
1639 	    } else if (name.equals("subhelpset")) {
1640 		// SUBHELPSET tag
1641 
1642 		// Remove from stack if an empty tag
1643 		if (tag.isEnd && !tag.isEmpty) {
1644 		    removeTag(tag);
1645 		} else {
1646 		    // Add the tag if it isn't an inline tag
1647 		    if (!tag.isEmpty) {
1648 			addTag(tag, locale);
1649 		    }
1650 		    // Process the tag
1651 		    factory.processSubHelpSet(myHS, ht);
1652 		}
1653 	    } else if (name.equals("impl")) {
1654 		// Presentation tag
1655 		if (tag.isEnd) {
1656 		    removeTag(tag);
1657 		    // Nothing to do here. Everything is done while
1658 		    // processing the sub tags
1659 		} else {
1660 		    if (! pname.equals("helpset")) {
1661 			wrongParent(name, pname);
1662 		    } else {
1663 			addTag(tag, locale);
1664 		    }
1665 		}
1666 	    } else if (name.equals("helpsetregistry")) {
1667 		if (tag.isEnd && !tag.isEmpty) {
1668 		    removeTag(tag);
1669 		} else {
1670 		    if (! pname.equals("impl")) {
1671 			wrongParent(name, pname);
1672 		    } else {
1673 			if (!tag.isEnd) {
1674 			    addTag(tag, locale);
1675 			}
1676 			if (attr != null) {
1677 			    String hbClass = attr.getProperty("helpbrokerclass");
1678 			    if (hbClass != null) {
1679 				myHS.setKeyData(implRegistry,
1680 						helpBrokerClass,
1681 						hbClass);
1682 			    }
1683 			}
1684 		    }
1685 		}
1686 	    } else if (name.equals("viewerregistry")) {
1687 		if (tag.isEnd && !tag.isEmpty) {
1688 		    removeTag(tag);
1689 		} else {
1690 		    if (! pname.equals("impl")) {
1691 			wrongParent(name, pname);
1692 		    } else {
1693 			if (!tag.isEnd) {
1694 			    addTag(tag, locale);
1695 			}
1696 			if (attr != null) {
1697 			    String viewerType = attr.getProperty("viewertype");
1698 			    String viewerClass = attr.getProperty("viewerclass");
1699 			    if (viewerType != null && viewerClass != null) {
1700 				ClassLoader cl = HelpSet.class.getClassLoader();
1701 				myHS.setKeyData(kitTypeRegistry,
1702 						viewerType, viewerClass);
1703 				myHS.setKeyData(kitLoaderRegistry,
1704 						viewerType, cl);
1705 			    }
1706 			}
1707 		    }
1708 		}
1709 	    }
1710 	}
1711 
piFound(ParserEvent e)1712 	public void piFound(ParserEvent e) {
1713 	    factory.processPI(myHS, e.getTarget(), e.getData());
1714 	}
1715 
doctypeFound(ParserEvent e)1716 	public void doctypeFound(ParserEvent e) {
1717 	    factory.processDOCTYPE(e.getRoot(), e.getPublicId(), e.getSystemId());
1718 	}
1719 
checkNull(String name, String t)1720 	private void checkNull(String name, String t) {
1721 	    if (! t.equals("")) {
1722 		parsingError("helpset.wrongText", name, t);
1723 	    }
1724 	}
1725 
textFound(ParserEvent e)1726 	public void textFound(ParserEvent e) {
1727 	    debug("textFound: ");
1728 	    debug("  text: "+e.getText());
1729 
1730 	    if (tagStack.empty()) {
1731 		return;		// ignore
1732 	    }
1733 	    LangElement le = (LangElement) tagStack.peek();
1734 	    Tag tag = le.getTag();
1735 	    TagProperties attr = tag.atts;
1736 	    Hashtable ht = (attr == null) ? null : attr.getHashtable();
1737 	    String text = e.getText().trim();
1738 	    String name = tag.name;
1739 
1740 	    if (name.equals("helpset")) {
1741 		// HELPSET tag
1742 		checkNull("helpset", text);
1743 		return;
1744 	    }
1745 	    int depth = tagStack.size();
1746 	    String pname = "";
1747 	    if (depth >= 2) {
1748 		le = (LangElement) tagStack.elementAt(depth-2);
1749 		pname = ((Tag) le.getTag()).name; // the parent
1750 	    }
1751 
1752 	    if (name.equals("title")) {
1753 		// TITLE tag
1754 		if (pname.equals("helpset")) {
1755 		    factory.processTitle(myHS, text);
1756 		} else {
1757 		    presentationTitle = text.trim();
1758 		}
1759 	    } else if (name.equals("homeID")) {
1760 		// HOMEID tag
1761 		factory.processHomeID(myHS, text);
1762 	    } else if (name.equals("mapref")) {
1763 		checkNull("mapref", text);
1764 	    } else if (name.equals("subhelpset")) {
1765 		checkNull("subhelpset", text);
1766 	    } else if (name.equals("data")) {
1767 		// DATA tag -- this is in a view
1768 		viewData = text.trim();
1769 	    } else if (name.equals("label")) {
1770 		// LABEL tag -- this is in a view
1771 		viewLabel = text.trim();
1772 	    } else if (name.equals("name")) {
1773 		// NAME tag -- this is in a view
1774 		tagName = text.trim();
1775 	    } else if (name.equals("helpaction")) {
1776 		// NAME tag -- this is in a view
1777 		helpAction = text.trim();
1778 	    } else if (name.equals("type")) {
1779 		// TYPE tag -- this is in a view
1780 		viewType = text.trim();
1781             } else if (name.equals("image")) {
1782                 // IMAGE tag -- this is in the view
1783                 tagImage = text.trim();
1784 	    } else if (name.equals("view")) {
1785 		// VIEW tag
1786 		checkNull("view", text);
1787 	    } else if (name.equals("maps")) {
1788 		// MAP tag
1789 		checkNull("maps", text);
1790             } else if (name.equals("mergetype")) {
1791                  checkNull("mergetype",text);
1792             }
1793 	}
1794 
1795 	/**
1796 	 * Method used to parse a HelpSet.
1797 	 */
errorFound(ParserEvent e)1798 	public void errorFound(ParserEvent e) {
1799 	    // Ignore it for now
1800 	}
1801 
1802 	/**
1803 	 * Method used to parse a HelpSet.
1804 	 */
commentFound(ParserEvent e)1805 	public void commentFound(ParserEvent e) {
1806 	    // Ignore it for now
1807 	}
1808 
1809     	/**
1810 	 * addTag keeps track of tags and their locale attributes.
1811 	 */
addTag(Tag tag, Locale locale)1812 	protected void addTag(Tag tag, Locale locale) {
1813 	    LangElement el = new LangElement(tag, locale);
1814 	    tagStack.push(el);
1815 	    // It's possible for lastLocale not be specified ergo null.
1816 	    // If it is then set lastLocale to null even if locale is null.
1817 	    // It is impossible for locale to be null
1818 	    if (lastLocale == null) {
1819 		lastLocale = locale;
1820 		return;
1821 	    }
1822 	    if (locale == null) {
1823 		lastLocale = locale;
1824 		return;
1825 	    }
1826 	    if (! lastLocale.equals(locale)) {
1827 		lastLocale = locale;
1828 	    }
1829 	}
1830 
1831 	/**
1832 	 * removeTag removes a tag from the tagStack. The tagStack is
1833 	 * used to keep track of tags and locales.
1834 	 */
removeTag(Tag tag)1835 	protected void removeTag(Tag tag) {
1836 	    LangElement el;
1837 	    String name = tag.name;
1838 	    Locale newLocale = null;
1839 
1840 	    for (;;) {
1841 		if (tagStack.empty())
1842 		    unbalanced(name);
1843 		el = (LangElement) tagStack.pop();
1844 		if (el.getTag().name.equals(name)) {
1845 		    if (tagStack.empty()) {
1846 			newLocale = defaultLocale;
1847 		    } else {
1848 			el = (LangElement) tagStack.peek();
1849 			newLocale = el.getLocale();
1850 		    }
1851 		    break;
1852 		}
1853 	    }
1854 	    // It's possible for lastLocale not be specified ergo null.
1855 	    // If it is then set lastLocale to null even if locale is null.
1856 	    // It also possible for locale to be null so if lastLocale is set
1857 	    // then reset lastLocale to null;
1858 	    // Otherwise if lastLocale doesn't equal locale reset lastLocale to locale
1859 	    if (lastLocale == null) {
1860 		lastLocale = newLocale;
1861 		return;
1862 	    }
1863 	    if (newLocale == null) {
1864 		lastLocale = newLocale;
1865 		return;
1866 	    }
1867 	    if (! lastLocale.equals(newLocale)) {
1868 		lastLocale = newLocale;
1869 	    }
1870 	}
1871 
1872 	/**
1873 	 * Handy error message methods.
1874 	 */
1875 
parsingError(String key)1876 	private void parsingError(String key) {
1877 	    String s = HelpUtilities.getText(key);
1878 	    factory.reportMessage(s, false); // tree will be wrong
1879 	}
1880 
parsingError(String key, String s)1881 	private void parsingError(String key, String s) {
1882 	    String msg = HelpUtilities.getText(key, s);
1883 	    factory.reportMessage(msg, false); // tree will be wrong
1884 	}
1885 
parsingError(String key, String s1, String s2)1886 	private void parsingError(String key, String s1, String s2) {
1887 	    String msg = HelpUtilities.getText(key, s1, s2);
1888 	    factory.reportMessage(msg, false); // tree will be wrong
1889 	}
1890 
wrongParent(String name, String pname)1891 	private void wrongParent(String name, String pname) {
1892 	    parsingError("helpset.wrongParent", name, pname);
1893 	}
1894 
unbalanced(String name)1895 	private void unbalanced(String name) {
1896 	    parsingError("helpset.unbalanced", name);
1897 	}
1898 
wrongLocale(Locale found, Locale l1, Locale l2)1899 	private void wrongLocale(Locale found, Locale l1, Locale l2) {
1900 	    String msg = HelpUtilities.getText("helpset.wrongLocale",
1901 					     found.toString(),
1902 					     l1.toString(),
1903 					     l2.toString());
1904 	    factory.reportMessage(msg, true); // will continue
1905 	}
1906     } // End of HelpSetParser
1907 
1908     /**
1909      * For printf debugging.
1910      */
1911     private final static boolean debug = false;
debug(String str)1912     private static void debug(String str) {
1913         if (debug) {
1914             System.out.println("HelpSet: " + str);
1915         }
1916     }
1917 }
1918