blob: a9bd61e56805db45e45a1f2dd11b5ed307720317 [file] [log] [blame]
/*******************************************************************************
* 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$
}
}