/******************************************************************************* * Copyright (c) 2000, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.equinox.internal.p2.updatesite; import java.io.*; import java.net.URI; import java.util.*; import javax.xml.parsers.*; import org.eclipse.core.runtime.*; import org.eclipse.equinox.internal.p2.core.helpers.LogHelper; import org.eclipse.equinox.internal.p2.core.helpers.Tracing; import org.eclipse.equinox.p2.publisher.eclipse.URLEntry; import org.eclipse.osgi.util.NLS; import org.w3c.dom.*; import org.xml.sax.*; import org.xml.sax.helpers.DefaultHandler; /** * Parses a site.xml file. * This class was initially copied from org.eclipse.update.core.model.DefaultSiteParser. */ public class DefaultSiteParser extends DefaultHandler { private static final String ARCHIVE = "archive"; //$NON-NLS-1$ private static final String CATEGORY = "category"; //$NON-NLS-1$ private static final String CATEGORY_DEF = "category-def"; //$NON-NLS-1$ private static final String ASSOCIATE_SITES_URL = "associateSitesURL"; //$NON-NLS-1$ private static final String ASSOCIATE_SITE = "associateSite"; //$NON-NLS-1$ private static final String DEFAULT_INFO_URL = "index.html"; //$NON-NLS-1$ private static final String DESCRIPTION = "description"; //$NON-NLS-1$ private static final String FEATURE = "feature"; //$NON-NLS-1$ private static final String FEATURES = "features/"; //$NON-NLS-1$ private final static SAXParserFactory parserFactory = SAXParserFactory.newInstance(); private static final String PLUGIN_ID = Activator.ID; private static final String SITE = "site"; //$NON-NLS-1$ private static final int STATE_ARCHIVE = 3; private static final int STATE_CATEGORY = 4; private static final int STATE_CATEGORY_DEF = 5; private static final int STATE_DESCRIPTION_CATEGORY_DEF = 7; private static final int STATE_DESCRIPTION_SITE = 6; private static final int STATE_FEATURE = 2; private static final int STATE_IGNORED_ELEMENT = -1; private static final int STATE_INITIAL = 0; private static final int STATE_SITE = 1; private int currentState; private boolean DESCRIPTION_SITE_ALREADY_SEEN = false; // Current object stack (used to hold the current object we are // populating in this plugin descriptor Stack<Object> objectStack = new Stack<Object>(); private SAXParser parser; // Current State Information Stack<Integer> stateStack = new Stack<Integer>(); // List of string keys for translated strings private final List<String> messageKeys = new ArrayList<String>(4); private MultiStatus status; private final URI siteLocation; /* * */ private static void debug(String s) { Tracing.debug("DefaultSiteParser: " + s); //$NON-NLS-1$ } private static URLEntry[] getAssociateSites(String associateSitesURL) { try { DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = domFactory.newDocumentBuilder(); Document document = builder.parse(associateSitesURL); if (document == null) return null; NodeList mirrorNodes = document.getElementsByTagName(ASSOCIATE_SITE); URLEntry[] mirrors = new URLEntry[mirrorNodes.getLength()]; for (int i = 0; i < mirrorNodes.getLength(); i++) { Element mirrorNode = (Element) mirrorNodes.item(i); mirrors[i] = new URLEntry(); String infoURL = mirrorNode.getAttribute("url"); //$NON-NLS-1$ String label = mirrorNode.getAttribute("label"); //$NON-NLS-1$ mirrors[i].setURL(infoURL); mirrors[i].setAnnotation(label); if (Tracing.DEBUG_GENERATOR_PARSING) debug("Processed mirror: url:" + infoURL + " label:" + label); //$NON-NLS-1$ //$NON-NLS-2$ } return mirrors; } catch (Exception e) { // log if absolute url if (associateSitesURL != null && (associateSitesURL.startsWith("http://") //$NON-NLS-1$ || associateSitesURL.startsWith("https://") //$NON-NLS-1$ || associateSitesURL.startsWith("file://") //$NON-NLS-1$ || associateSitesURL.startsWith("ftp://") //$NON-NLS-1$ || associateSitesURL.startsWith("jar://"))) //$NON-NLS-1$ log(Messages.DefaultSiteParser_mirrors, e); return null; } } static void log(Exception e) { LogHelper.log(new Status(IStatus.ERROR, Activator.ID, "Internal Error", e)); //$NON-NLS-1$ } static void log(String message) { LogHelper.log(new Status(IStatus.WARNING, Activator.ID, message, null)); } static void log(String message, Exception e) { LogHelper.log(new Status(IStatus.WARNING, Activator.ID, message, e)); } /** * Constructs a site parser. */ public DefaultSiteParser(URI siteLocation) { super(); this.siteLocation = siteLocation; stateStack = new Stack<Integer>(); objectStack = new Stack<Object>(); status = null; DESCRIPTION_SITE_ALREADY_SEEN = false; try { parserFactory.setNamespaceAware(true); this.parser = parserFactory.newSAXParser(); } catch (ParserConfigurationException e) { log(e); } catch (SAXException e) { log(e); } if (Tracing.DEBUG_GENERATOR_PARSING) debug("Created"); //$NON-NLS-1$ } /** * Handle character text * @see DefaultHandler#characters(char[], int, int) * @since 2.0 */ public void characters(char[] ch, int start, int length) { String text = new String(ch, start, length); //only push if description int state = stateStack.peek().intValue(); if (state == STATE_DESCRIPTION_SITE || state == STATE_DESCRIPTION_CATEGORY_DEF) objectStack.push(text); } /** * Handle end of element tags * @see DefaultHandler#endElement(String, String, String) * @since 2.0 */ public void endElement(String uri, String localName, String qName) { String text = null; URLEntry info = null; int state = stateStack.peek().intValue(); switch (state) { case STATE_IGNORED_ELEMENT : case STATE_ARCHIVE : case STATE_CATEGORY : stateStack.pop(); break; case STATE_INITIAL : internalError(Messages.DefaultSiteParser_ParsingStackBackToInitialState); break; case STATE_SITE : stateStack.pop(); if (objectStack.peek() instanceof String) { text = (String) objectStack.pop(); SiteModel site = (SiteModel) objectStack.peek(); site.getDescription().setAnnotation(text); } //do not pop the object break; case STATE_FEATURE : stateStack.pop(); objectStack.pop(); break; case STATE_CATEGORY_DEF : stateStack.pop(); if (objectStack.peek() instanceof String) { text = (String) objectStack.pop(); SiteCategory category = (SiteCategory) objectStack.peek(); category.setDescription(text); } objectStack.pop(); break; case STATE_DESCRIPTION_SITE : stateStack.pop(); text = ""; //$NON-NLS-1$ while (objectStack.peek() instanceof String) { // add text, preserving at most one space between text fragments String newText = (String) objectStack.pop(); if (trailingSpace(newText) && !leadingSpace(text)) { text = " " + text; //$NON-NLS-1$ } text = newText.trim() + text; if (leadingSpace(newText) && !leadingSpace(text)) { text = " " + text; //$NON-NLS-1$ } } text = text.trim(); info = (URLEntry) objectStack.pop(); if (text != null) info.setAnnotation(text); SiteModel siteModel = (SiteModel) objectStack.peek(); // override description. // do not raise error as previous description may be default one // when parsing site tag if (DESCRIPTION_SITE_ALREADY_SEEN) debug(NLS.bind(Messages.DefaultSiteParser_ElementAlreadySet, (new String[] {getState(state)}))); siteModel.setDescription(info); DESCRIPTION_SITE_ALREADY_SEEN = true; break; case STATE_DESCRIPTION_CATEGORY_DEF : stateStack.pop(); text = ""; //$NON-NLS-1$ while (objectStack.peek() instanceof String) { // add text, preserving at most one space between text fragments String newText = (String) objectStack.pop(); if (trailingSpace(newText) && !leadingSpace(text)) { text = " " + text; //$NON-NLS-1$ } text = newText.trim() + text; if (leadingSpace(newText) && !leadingSpace(text)) { text = " " + text; //$NON-NLS-1$ } } text = text.trim(); info = (URLEntry) objectStack.pop(); if (text != null) info.setAnnotation(text); SiteCategory category = (SiteCategory) objectStack.peek(); if (category.getDescription() != null) internalError(NLS.bind(Messages.DefaultSiteParser_ElementAlreadySet, (new String[] {getState(state), category.getLabel()}))); else { checkTranslated(info.getAnnotation()); category.setDescription(info.getAnnotation()); } break; default : internalError(NLS.bind(Messages.DefaultSiteParser_UnknownEndState, (new String[] {getState(state)}))); break; } if (Tracing.DEBUG_GENERATOR_PARSING) debug("End Element:" + uri + ":" + localName + ":" + qName);//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } /* * Handles an error state specified by the status. The collection of all logged status * objects can be accessed using <code>getStatus()</code>. * * @param error a status detailing the error condition */ private void error(IStatus error) { if (status == null) { status = new MultiStatus(PLUGIN_ID, 0, Messages.DefaultSiteParser_ErrorParsingSite, null); } status.add(error); if (Tracing.DEBUG_GENERATOR_PARSING) LogHelper.log(error); } /** * Handle errors * @see DefaultHandler#error(SAXParseException) * @since 2.0 */ public void error(SAXParseException ex) { logStatus(ex); } /** * Handle fatal errors * @see DefaultHandler#fatalError(SAXParseException) * @exception SAXException * @since 2.0 */ public void fatalError(SAXParseException ex) throws SAXException { logStatus(ex); throw ex; } /* * return the state as String */ private String getState(int state) { switch (state) { case STATE_IGNORED_ELEMENT : return "Ignored"; //$NON-NLS-1$ case STATE_INITIAL : return "Initial"; //$NON-NLS-1$ case STATE_SITE : return "Site"; //$NON-NLS-1$ case STATE_FEATURE : return "Feature"; //$NON-NLS-1$ case STATE_ARCHIVE : return "Archive"; //$NON-NLS-1$ case STATE_CATEGORY : return "Category"; //$NON-NLS-1$ case STATE_CATEGORY_DEF : return "Category Def"; //$NON-NLS-1$ case STATE_DESCRIPTION_CATEGORY_DEF : return "Description / Category Def"; //$NON-NLS-1$ case STATE_DESCRIPTION_SITE : return "Description / Site"; //$NON-NLS-1$ default : return Messages.DefaultSiteParser_UnknownState; } } /** * Returns all status objects accumulated by the parser. * * @return multi-status containing accumulated status, or <code>null</code>. * @since 2.0 */ public MultiStatus getStatus() { return status; } private void handleCategoryDefState(String elementName, Attributes attributes) { if (elementName.equals(FEATURE)) { stateStack.push(new Integer(STATE_FEATURE)); processFeature(attributes); } else if (elementName.equals(ARCHIVE)) { stateStack.push(new Integer(STATE_ARCHIVE)); processArchive(attributes); } else if (elementName.equals(CATEGORY_DEF)) { stateStack.push(new Integer(STATE_CATEGORY_DEF)); processCategoryDef(attributes); } else if (elementName.equals(DESCRIPTION)) { stateStack.push(new Integer(STATE_DESCRIPTION_CATEGORY_DEF)); processInfo(attributes); } else internalErrorUnknownTag(NLS.bind(Messages.DefaultSiteParser_UnknownElement, (new String[] {elementName, getState(currentState)}))); } private void handleCategoryState(String elementName, Attributes attributes) { if (elementName.equals(DESCRIPTION)) { stateStack.push(new Integer(STATE_DESCRIPTION_SITE)); processInfo(attributes); } else if (elementName.equals(FEATURE)) { stateStack.push(new Integer(STATE_FEATURE)); processFeature(attributes); } else if (elementName.equals(ARCHIVE)) { stateStack.push(new Integer(STATE_ARCHIVE)); processArchive(attributes); } else if (elementName.equals(CATEGORY_DEF)) { stateStack.push(new Integer(STATE_CATEGORY_DEF)); processCategoryDef(attributes); } else if (elementName.equals(CATEGORY)) { stateStack.push(new Integer(STATE_CATEGORY)); processCategory(attributes); } else internalErrorUnknownTag(NLS.bind(Messages.DefaultSiteParser_UnknownElement, (new String[] {elementName, getState(currentState)}))); } private void handleFeatureState(String elementName, Attributes attributes) { if (elementName.equals(DESCRIPTION)) { stateStack.push(new Integer(STATE_DESCRIPTION_SITE)); processInfo(attributes); } else if (elementName.equals(FEATURE)) { stateStack.push(new Integer(STATE_FEATURE)); processFeature(attributes); } else if (elementName.equals(ARCHIVE)) { stateStack.push(new Integer(STATE_ARCHIVE)); processArchive(attributes); } else if (elementName.equals(CATEGORY_DEF)) { stateStack.push(new Integer(STATE_CATEGORY_DEF)); processCategoryDef(attributes); } else if (elementName.equals(CATEGORY)) { stateStack.push(new Integer(STATE_CATEGORY)); processCategory(attributes); } else internalErrorUnknownTag(NLS.bind(Messages.DefaultSiteParser_UnknownElement, (new String[] {elementName, getState(currentState)}))); } private void handleInitialState(String elementName, Attributes attributes) throws SAXException { if (elementName.equals(SITE)) { stateStack.push(new Integer(STATE_SITE)); processSite(attributes); } else { internalErrorUnknownTag(NLS.bind(Messages.DefaultSiteParser_UnknownElement, (new String[] {elementName, getState(currentState)}))); // what we received was not a site.xml, no need to continue throw new SAXException(Messages.DefaultSiteParser_InvalidXMLStream); } } private void handleSiteState(String elementName, Attributes attributes) { if (elementName.equals(DESCRIPTION)) { stateStack.push(new Integer(STATE_DESCRIPTION_SITE)); processInfo(attributes); } else if (elementName.equals(FEATURE)) { stateStack.push(new Integer(STATE_FEATURE)); processFeature(attributes); } else if (elementName.equals(ARCHIVE)) { stateStack.push(new Integer(STATE_ARCHIVE)); processArchive(attributes); } else if (elementName.equals(CATEGORY_DEF)) { stateStack.push(new Integer(STATE_CATEGORY_DEF)); processCategoryDef(attributes); } else internalErrorUnknownTag(NLS.bind(Messages.DefaultSiteParser_UnknownElement, (new String[] {elementName, getState(currentState)}))); } /* * */ private void internalError(String message) { error(new Status(IStatus.ERROR, PLUGIN_ID, IStatus.OK, message, null)); } /* * */ private void internalErrorUnknownTag(String msg) { stateStack.push(new Integer(STATE_IGNORED_ELEMENT)); internalError(msg); } private boolean leadingSpace(String str) { if (str.length() <= 0) { return false; } return Character.isWhitespace(str.charAt(0)); } /* * */ private void logStatus(SAXParseException ex) { String name = ex.getSystemId(); if (name == null) name = ""; //$NON-NLS-1$ else name = name.substring(1 + name.lastIndexOf("/")); //$NON-NLS-1$ String msg; if (name.equals("")) //$NON-NLS-1$ name = siteLocation.toString(); String[] values = new String[] {name, Integer.toString(ex.getLineNumber()), Integer.toString(ex.getColumnNumber()), ex.getMessage()}; msg = NLS.bind(Messages.DefaultSiteParser_ErrorlineColumnMessage, values); error(new Status(IStatus.ERROR, PLUGIN_ID, msg, ex)); } /** * Parses the specified input steam and constructs a site model. * The input stream is not closed as part of this operation. * * @param in input stream * @return site model * @exception SAXException * @exception IOException * @since 2.0 */ public SiteModel parse(InputStream in) throws SAXException, IOException { stateStack.push(new Integer(STATE_INITIAL)); currentState = stateStack.peek().intValue(); parser.parse(new InputSource(in), this); if (objectStack.isEmpty()) throw new SAXException(Messages.DefaultSiteParser_NoSiteTag); if (objectStack.peek() instanceof SiteModel) { SiteModel site = (SiteModel) objectStack.pop(); site.setMessageKeys(messageKeys); return site; } String stack = ""; //$NON-NLS-1$ Iterator<Object> iter = objectStack.iterator(); while (iter.hasNext()) { stack = stack + iter.next().toString() + "\r\n"; //$NON-NLS-1$ } throw new SAXException(NLS.bind(Messages.DefaultSiteParser_WrongParsingStack, (new String[] {stack}))); } /* * process archive info */ private void processArchive(Attributes attributes) { URLEntry archive = new URLEntry(); String id = attributes.getValue("path"); //$NON-NLS-1$ if (id == null || id.trim().equals("")) { //$NON-NLS-1$ internalError(NLS.bind(Messages.DefaultSiteParser_Missing, (new String[] {"path", getState(currentState)}))); //$NON-NLS-1$ } archive.setAnnotation(id); String url = attributes.getValue("url"); //$NON-NLS-1$ if (url == null || url.trim().equals("")) { //$NON-NLS-1$ internalError(NLS.bind(Messages.DefaultSiteParser_Missing, (new String[] {"archive", getState(currentState)}))); //$NON-NLS-1$ } else { archive.setURL(url); SiteModel site = (SiteModel) objectStack.peek(); site.addArchive(archive); } if (Tracing.DEBUG_GENERATOR_PARSING) debug("End processing Archive: path:" + id + " url:" + url);//$NON-NLS-1$ //$NON-NLS-2$ } /* * process the Category info */ private void processCategory(Attributes attributes) { String category = attributes.getValue("name"); //$NON-NLS-1$ SiteFeature feature = (SiteFeature) objectStack.peek(); feature.addCategoryName(category); if (Tracing.DEBUG_GENERATOR_PARSING) debug("End processing Category: name:" + category); //$NON-NLS-1$ } /* * process category def info */ private void processCategoryDef(Attributes attributes) { SiteCategory category = new SiteCategory(); String name = attributes.getValue("name"); //$NON-NLS-1$ String label = attributes.getValue("label"); //$NON-NLS-1$ checkTranslated(label); category.setName(name); category.setLabel(label); SiteModel site = (SiteModel) objectStack.peek(); site.addCategory(category); objectStack.push(category); if (Tracing.DEBUG_GENERATOR_PARSING) debug("End processing CategoryDef: name:" + name + " label:" + label); //$NON-NLS-1$ //$NON-NLS-2$ } /* * process feature info */ private void processFeature(Attributes attributes) { SiteFeature feature = new SiteFeature(); // feature location on the site String urlInfo = attributes.getValue("url"); //$NON-NLS-1$ // identifier and version String id = attributes.getValue("id"); //$NON-NLS-1$ String ver = attributes.getValue("version"); //$NON-NLS-1$ boolean noURL = (urlInfo == null || urlInfo.trim().equals("")); //$NON-NLS-1$ boolean noId = (id == null || id.trim().equals("")); //$NON-NLS-1$ boolean noVersion = (ver == null || ver.trim().equals("")); //$NON-NLS-1$ // We need to have id and version, or the url, or both. if (noURL) { if (noId || noVersion) internalError(NLS.bind(Messages.DefaultSiteParser_Missing, (new String[] {"url", getState(currentState)}))); //$NON-NLS-1$ else // default url urlInfo = FEATURES + id + '_' + ver; // } feature.setURLString(urlInfo); String type = attributes.getValue("type"); //$NON-NLS-1$ feature.setType(type); // if one is null, and not the other if (noId ^ noVersion) { String[] values = new String[] {id, ver, getState(currentState)}; log(NLS.bind(Messages.DefaultFeatureParser_IdOrVersionInvalid, values)); } else { feature.setFeatureIdentifier(id); feature.setFeatureVersion(ver); } // get label if it exists String label = attributes.getValue("label"); //$NON-NLS-1$ if (label != null) { if ("".equals(label.trim())) //$NON-NLS-1$ label = null; checkTranslated(label); } feature.setLabel(label); // OS String os = attributes.getValue("os"); //$NON-NLS-1$ feature.setOS(os); // WS String ws = attributes.getValue("ws"); //$NON-NLS-1$ feature.setWS(ws); // NL String nl = attributes.getValue("nl"); //$NON-NLS-1$ feature.setNL(nl); // arch String arch = attributes.getValue("arch"); //$NON-NLS-1$ feature.setArch(arch); //patch String patch = attributes.getValue("patch"); //$NON-NLS-1$ feature.setPatch(patch); SiteModel site = (SiteModel) objectStack.peek(); site.addFeature(feature); feature.setSiteModel(site); objectStack.push(feature); if (Tracing.DEBUG_GENERATOR_PARSING) debug("End Processing DefaultFeature Tag: url:" + urlInfo + " type:" + type); //$NON-NLS-1$ //$NON-NLS-2$ } /* * process URL info with element text */ private void processInfo(Attributes attributes) { URLEntry inf = new URLEntry(); String infoURL = attributes.getValue("url"); //$NON-NLS-1$ inf.setURL(infoURL); if (Tracing.DEBUG_GENERATOR_PARSING) debug("Processed Info: url:" + infoURL); //$NON-NLS-1$ objectStack.push(inf); } /* * process site info */ private void processSite(Attributes attributes) { // create site map SiteModel site = new SiteModel(); // if URL is specified, it replaces the URL of the site // used to calculate the location of features and archives String siteURL = attributes.getValue("url"); //$NON-NLS-1$ if (siteURL != null && !("".equals(siteURL.trim()))) { //$NON-NLS-1$ if (!siteURL.endsWith("/") && !siteURL.endsWith(File.separator)) { //$NON-NLS-1$ siteURL += "/"; //$NON-NLS-1$ } site.setLocationURIString(siteURL); } // provide default description URL // If <description> is specified, for the site, it takes precedence URLEntry description = new URLEntry(); description.setURL(DEFAULT_INFO_URL); site.setDescription(description); // verify we can parse the site ...if the site has // a different type throw an exception to force reparsing // with the matching parser String type = attributes.getValue("type"); //$NON-NLS-1$ site.setType(type); // get mirrors, if any String mirrorsURL = attributes.getValue("mirrorsURL"); //$NON-NLS-1$ if (mirrorsURL != null && mirrorsURL.trim().length() > 0) { // URLEntry[] mirrors = getMirrors(mirrorsURL); // if (mirrors != null) // site.setMirrors(mirrors); // else //Since we are parsing the site at p2 generation time and the //mirrors may change, there is no point doing the mirror expansion now site.setMirrorsURIString(mirrorsURL); } String pack200 = attributes.getValue("pack200"); //$NON-NLS-1$ if (pack200 != null && new Boolean(pack200).booleanValue()) { site.setSupportsPack200(true); } String digestURL = attributes.getValue("digestURL"); //$NON-NLS-1$ if (digestURL != null) site.setDigestURIString(digestURL); // TODO: Digest locales // if ((attributes.getValue("availableLocales") != null) && (!attributes.getValue("availableLocales").trim().equals(""))) { //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ // StringTokenizer locals = new StringTokenizer(attributes.getValue("availableLocales"), ","); //$NON-NLS-1$//$NON-NLS-2$ // String[] availableLocals = new String[locals.countTokens()]; // int i = 0; // while (locals.hasMoreTokens()) { // availableLocals[i++] = locals.nextToken(); // } // extendedSite.setAvailableLocals(availableLocals); // } // } // final String associateURL = attributes.getValue(ASSOCIATE_SITES_URL); if (associateURL != null) { //resolve the URI relative to the site location URI resolvedLocation = siteLocation.resolve(associateURL); site.setAssociateSites(getAssociateSites(resolvedLocation.toString())); } objectStack.push(site); if (Tracing.DEBUG_GENERATOR_PARSING) debug("End process Site tag: siteURL:" + siteURL + " type:" + type);//$NON-NLS-1$ //$NON-NLS-2$ } /** * Handle start of element tags * @see DefaultHandler#startElement(String, String, String, Attributes) * @since 2.0 */ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (Tracing.DEBUG_GENERATOR_PARSING) { debug("State: " + currentState); //$NON-NLS-1$ debug("Start Element: uri:" + uri + " local Name:" + localName + " qName:" + qName);//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } switch (currentState) { case STATE_IGNORED_ELEMENT : internalErrorUnknownTag(NLS.bind(Messages.DefaultSiteParser_UnknownElement, (new String[] {localName, getState(currentState)}))); break; case STATE_INITIAL : handleInitialState(localName, attributes); break; case STATE_SITE : handleSiteState(localName, attributes); break; case STATE_FEATURE : handleFeatureState(localName, attributes); break; case STATE_ARCHIVE : handleSiteState(localName, attributes); break; case STATE_CATEGORY : handleCategoryState(localName, attributes); break; case STATE_CATEGORY_DEF : handleCategoryDefState(localName, attributes); break; case STATE_DESCRIPTION_SITE : handleSiteState(localName, attributes); break; case STATE_DESCRIPTION_CATEGORY_DEF : handleSiteState(localName, attributes); break; default : internalErrorUnknownTag(NLS.bind(Messages.DefaultSiteParser_UnknownStartState, (new String[] {getState(currentState)}))); break; } int newState = stateStack.peek().intValue(); if (newState != STATE_IGNORED_ELEMENT) currentState = newState; } private boolean trailingSpace(String str) { if (str.length() <= 0) { return false; } return Character.isWhitespace(str.charAt(str.length() - 1)); } // Add translatable strings from the site.xml // to the list of message keys. private void checkTranslated(String value) { if (value != null && value.length() > 1 && value.startsWith("%")) //$NON-NLS-1$ messageKeys.add(value.substring(1)); } }