| /******************************************************************************* |
| * Copyright (c) 2005, 2015 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.ui.internal.cheatsheets.composite.parser; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.URL; |
| |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.help.internal.entityresolver.LocalEntityResolver; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.ui.internal.cheatsheets.Messages; |
| import org.eclipse.ui.internal.cheatsheets.composite.model.AbstractTask; |
| import org.eclipse.ui.internal.cheatsheets.composite.model.CompositeCheatSheetModel; |
| import org.eclipse.ui.internal.cheatsheets.composite.model.EditableTask; |
| import org.eclipse.ui.internal.cheatsheets.composite.model.TaskGroup; |
| import org.eclipse.ui.internal.cheatsheets.data.CheatSheetParserException; |
| import org.eclipse.ui.internal.cheatsheets.data.IParserTags; |
| import org.eclipse.ui.internal.cheatsheets.data.ParserStatusUtility; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.SAXParseException; |
| |
| public class CompositeCheatSheetParser implements IStatusContainer { |
| |
| private DocumentBuilder documentBuilder; |
| |
| private IStatus status; |
| |
| private int nextTaskId = 0; |
| |
| |
| /** |
| * Gets the status of the last call to parseGuide |
| */ |
| |
| public IStatus getStatus() { |
| return status; |
| } |
| |
| /** |
| * Returns the DocumentBuilder to be used by composite cheat sheets. |
| */ |
| public DocumentBuilder getDocumentBuilder() { |
| if(documentBuilder == null) { |
| try { |
| documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); |
| documentBuilder.setEntityResolver(new LocalEntityResolver()); |
| } catch (Exception e) { |
| addStatus(IStatus.ERROR, Messages.ERROR_CREATING_DOCUMENT_BUILDER, e); |
| } |
| } |
| return documentBuilder; |
| } |
| |
| @Override |
| public void addStatus(int severity, String message, Throwable exception) { |
| status = ParserStatusUtility.addStatus(status, severity, message, exception); |
| } |
| |
| /** |
| * Parse a composite cheat sheet from a url. The parser status will be set as a result |
| * of this operation, if the status is IStatus.ERROR the parser returns null |
| * @param url The url of the input |
| * @return A valid composite cheat sheet or null if there was an error |
| */ |
| public CompositeCheatSheetModel parseGuide(URL url) { |
| status = Status.OK_STATUS; |
| if(url == null) { |
| String message = NLS.bind(Messages.ERROR_OPENING_FILE, (new Object[] {""})); //$NON-NLS-1$ |
| addStatus(IStatus.ERROR, message, null); |
| return null; |
| } |
| |
| InputStream is = null; |
| |
| try { |
| is = url.openStream(); |
| |
| if (is == null) { |
| String message = NLS.bind(Messages.ERROR_OPENING_FILE, (new Object[] {url.getFile()})); |
| addStatus(IStatus.ERROR, message, null); |
| return null; |
| } |
| } catch (Exception e) { |
| String message = NLS.bind(Messages.ERROR_OPENING_FILE, (new Object[] {url.getFile()})); |
| addStatus(IStatus.ERROR, message, e); |
| return null; |
| } |
| |
| Document document; |
| String filename = url.getFile(); |
| try { |
| InputSource inputSource = new InputSource(is); |
| document = getDocumentBuilder().parse(inputSource); |
| } catch (IOException e) { |
| String message = NLS.bind(Messages.ERROR_OPENING_FILE_IN_PARSER, (new Object[] {filename})); |
| addStatus(IStatus.ERROR, message, e); |
| return null; |
| } catch (SAXParseException spe) { |
| String message = NLS.bind(Messages.ERROR_SAX_PARSING_WITH_LOCATION, (new Object[] {filename, new Integer(spe.getLineNumber()), new Integer(spe.getColumnNumber())})); |
| addStatus(IStatus.ERROR, message, spe); |
| return null; |
| } catch (SAXException se) { |
| String message = NLS.bind(Messages.ERROR_SAX_PARSING, (new Object[] {filename})); |
| addStatus(IStatus.ERROR, message, se); |
| return null; |
| } finally { |
| try { |
| is.close(); |
| } catch (Exception e) { |
| } |
| } |
| |
| CompositeCheatSheetModel result = parseCompositeCheatSheet(document, url); |
| return result; |
| } |
| |
| /** |
| * Parse a composite cheatsheet. The parser status will be set as a result |
| * of this operation, if the status is IStatus.ERROR the parser returns null |
| * @param url The url of the input. This is only used so the model can record |
| * its input location |
| * @param document the document to be parse |
| * @return A valid composite cheat sheet or null if there was an error |
| * @return |
| */ |
| public CompositeCheatSheetModel parseCompositeCheatSheet(Document document, URL url) { |
| status = Status.OK_STATUS; |
| try { |
| // If the document passed is null return a null tree and update the status |
| if (document != null) { |
| Node rootnode = document.getDocumentElement(); |
| |
| // Is the root node correct? |
| if( !rootnode.getNodeName().equals(ICompositeCheatsheetTags.COMPOSITE_CHEATSHEET )) { |
| String message = NLS.bind(Messages.ERROR_PARSING_ROOT_NODE_TYPE, ( |
| new Object[] {ICompositeCheatsheetTags.COMPOSITE_CHEATSHEET})); |
| throw new CheatSheetParserException(message); |
| } |
| |
| String name = ""; //$NON-NLS-1$ |
| boolean nameFound = false; |
| String explorerId = ICompositeCheatsheetTags.TREE; |
| |
| NamedNodeMap attributes = rootnode.getAttributes(); |
| if (attributes != null) { |
| for (int x = 0; x < attributes.getLength(); x++) { |
| Node attribute = attributes.item(x); |
| String attributeName = attribute.getNodeName(); |
| if ( attributeName != null && attributeName.equals(ICompositeCheatsheetTags.NAME)) { |
| nameFound = true; |
| name= attribute.getNodeValue(); |
| } |
| if (attributeName.equals(ICompositeCheatsheetTags.EXPLORER)) { |
| explorerId= attribute.getNodeValue(); |
| } |
| } |
| } |
| CompositeCheatSheetModel compositeCS = new CompositeCheatSheetModel(name, name, explorerId); |
| |
| parseCompositeCheatSheetChildren(rootnode, compositeCS); |
| |
| compositeCS.getDependencies().resolveDependencies(this); |
| |
| if (compositeCS.getRootTask() == null) { |
| addStatus(IStatus.ERROR, Messages.ERROR_PARSING_NO_ROOT, null); |
| } |
| if (!nameFound) { |
| addStatus(IStatus.ERROR, Messages.ERROR_PARSING_CCS_NO_NAME, null); |
| } |
| if (status.getSeverity() != IStatus.ERROR) { |
| compositeCS.setContentUrl(url); |
| return compositeCS; |
| } |
| } |
| return null; |
| } catch(CheatSheetParserException e) { |
| addStatus(IStatus.ERROR, e.getMessage(), null); |
| return null; |
| } |
| } |
| |
| private void parseCompositeCheatSheetChildren(Node compositeCSNode, CompositeCheatSheetModel model) { |
| nextTaskId = 0; |
| NodeList childNodes = compositeCSNode.getChildNodes(); |
| for (int index = 0; index < childNodes.getLength(); index++) { |
| Node nextNode = childNodes.item(index); |
| if (isAbstractTask(nextNode.getNodeName()) ) { |
| AbstractTask task = parseAbstractTask(nextNode, model); |
| if (model.getRootTask() == null ) { |
| model.setRootTask(task); |
| parseTaskChildren(nextNode, task, model); |
| } else { |
| addStatus(IStatus.ERROR, Messages.ERROR_PARSING_MULTIPLE_ROOT, null); |
| } |
| } |
| } |
| } |
| |
| public static boolean isAbstractTask(String nodeName) { |
| return nodeName == ICompositeCheatsheetTags.TASK || |
| nodeName == ICompositeCheatsheetTags.TASK_GROUP; |
| } |
| |
| private void parseTaskChildren(Node parentNode, AbstractTask parentTask, CompositeCheatSheetModel model) { |
| NodeList childNodes = parentNode.getChildNodes(); |
| ITaskParseStrategy strategy = parentTask.getParserStrategy(); |
| strategy.init(); |
| for (int index = 0; index < childNodes.getLength(); index++) { |
| Node childNode = childNodes.item(index); |
| if (childNode.getNodeType() == Node.ELEMENT_NODE) { |
| String nodeName = childNode.getNodeName(); |
| if (nodeName == IParserTags.PARAM) { |
| addParameter(parentTask, childNode.getAttributes()); |
| } else if (nodeName == IParserTags.INTRO) { |
| parentTask.setDescription(MarkupParser.parseAndTrimTextMarkup(childNode)); |
| } else if (nodeName == ICompositeCheatsheetTags.ON_COMPLETION) { |
| parentTask.setCompletionMessage(MarkupParser.parseAndTrimTextMarkup(childNode)); |
| } else if (nodeName == ICompositeCheatsheetTags.DEPENDS_ON) { |
| parseDependency(childNode, parentTask, model); |
| } else if (CompositeCheatSheetParser.isAbstractTask(nodeName)) { |
| if (parentTask instanceof TaskGroup) { |
| AbstractTask task = parseAbstractTask(childNode, model); |
| ((TaskGroup)parentTask).addSubtask(task); |
| parseTaskChildren(childNode, task, model); |
| } |
| } else { |
| if (!strategy.parseElementNode(childNode, parentNode, parentTask, this)) { |
| String message = NLS |
| .bind( |
| Messages.WARNING_PARSING_UNKNOWN_ELEMENT, |
| (new Object[] { nodeName, |
| parentNode.getNodeName() })); |
| addStatus(IStatus.WARNING, message, null); |
| } |
| } |
| } |
| } |
| // Check for missing attributes and add dependencies if this was a sequence |
| strategy.parsingComplete(parentTask, this); |
| } |
| |
| private void parseDependency(Node taskNode, AbstractTask task, CompositeCheatSheetModel model) { |
| NamedNodeMap attributes = taskNode.getAttributes(); |
| if (attributes != null) { |
| Node taskAttribute = attributes.getNamedItem(ICompositeCheatsheetTags.TASK); |
| if (taskAttribute != null) { |
| String requiredTaskId = taskAttribute.getNodeValue(); |
| model.getDependencies().addDependency(task, requiredTaskId); |
| } else { |
| addStatus(IStatus.ERROR, Messages.ERROR_PARSING_NO_ID, null); |
| } |
| } |
| } |
| |
| private void addParameter(AbstractTask parentTask, NamedNodeMap attributes) { |
| String name = null; |
| String value = null; |
| |
| if (attributes != null) { |
| for (int x = 0; x < attributes.getLength(); x++) { |
| Node attribute = attributes.item(x); |
| String attributeName = attribute.getNodeName(); |
| if (attribute == null || attributeName == null) |
| continue; |
| if (attributeName.equals(ICompositeCheatsheetTags.NAME)) { |
| name = attribute.getNodeValue(); |
| } |
| if (attributeName.equals(ICompositeCheatsheetTags.VALUE)) { |
| value= attribute.getNodeValue(); |
| } |
| } |
| } |
| if (name == null) { |
| addStatus(IStatus.WARNING, Messages.ERROR_PARSING_NO_NAME, null); |
| return; |
| } else if (value == null) { |
| addStatus(IStatus.WARNING, Messages.ERROR_PARSING_NO_VALUE, null); |
| return; |
| } else { |
| parentTask.getParameters().put(name, value); |
| } |
| |
| } |
| |
| private AbstractTask parseAbstractTask(Node taskNode, CompositeCheatSheetModel model) { |
| AbstractTask task; |
| NamedNodeMap attributes = taskNode.getAttributes(); |
| String kind = null; |
| String name = null; |
| String id = null; |
| boolean skippable = false; |
| if (attributes != null) { |
| for (int x = 0; x < attributes.getLength(); x++) { |
| Node attribute = attributes.item(x); |
| String attributeName = attribute.getNodeName(); |
| if (attribute == null || attributeName == null) |
| continue; |
| if (attributeName.equals(ICompositeCheatsheetTags.KIND)) { |
| kind = attribute.getNodeValue(); |
| } |
| if (attributeName.equals(ICompositeCheatsheetTags.NAME)) { |
| name= attribute.getNodeValue(); |
| } |
| if (attributeName.equals(IParserTags.ID)) { |
| id = attribute.getNodeValue(); |
| } |
| if (attributeName.equals(IParserTags.SKIP)) { |
| skippable = "true".equalsIgnoreCase(attribute.getNodeValue()); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| String nodeName = taskNode.getNodeName(); |
| if (id == null) { |
| id = autoGenerateId(); |
| } |
| if (name == null) { |
| String message = NLS.bind(Messages.ERROR_PARSING_TASK_NO_NAME, (new Object[] {nodeName})); |
| addStatus(IStatus.ERROR, message, null); |
| } |
| task = createTask(nodeName, model, kind, id, name); |
| task.setSkippable(skippable); |
| |
| if (model.getDependencies().getTask(id) != null) { |
| String message = NLS.bind(Messages.ERROR_PARSING_DUPLICATE_TASK_ID, (new Object[] {id, })); |
| addStatus(IStatus.ERROR, message, null); |
| } else { |
| model.getDependencies().saveId(task); |
| } |
| |
| return task; |
| } |
| |
| private AbstractTask createTask(String nodeKind, CompositeCheatSheetModel model, String kind, String id, String name) { |
| AbstractTask task; |
| if (ICompositeCheatsheetTags.TASK_GROUP.equals(nodeKind)) { |
| task = new TaskGroup(model, id, name, kind); |
| } else { |
| task = new EditableTask(model, id, name, kind); |
| } |
| task.setCompletionMessage(Messages.COMPLETED_TASK); |
| return task; |
| } |
| |
| private String autoGenerateId() { |
| return "TaskId_" + nextTaskId++; //$NON-NLS-1$ |
| } |
| |
| } |