blob: 99ab06415b2c15bc6327b5350f8ef98e65ff32ac [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2002, 2017 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.data;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.URL;
import java.util.ArrayList;
import javax.xml.parsers.DocumentBuilder;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.help.internal.UAElement;
import org.eclipse.help.internal.UAElementFactory;
import org.eclipse.help.internal.dynamic.DocumentProcessor;
import org.eclipse.help.internal.dynamic.DocumentReader;
import org.eclipse.help.internal.dynamic.ExtensionHandler;
import org.eclipse.help.internal.dynamic.FilterHandler;
import org.eclipse.help.internal.dynamic.IncludeHandler;
import org.eclipse.help.internal.dynamic.ProcessorHandler;
import org.eclipse.osgi.util.NLS;
import org.eclipse.ui.cheatsheets.AbstractItemExtensionElement;
import org.eclipse.ui.internal.cheatsheets.CheatSheetEvaluationContext;
import org.eclipse.ui.internal.cheatsheets.CheatSheetPlugin;
import org.eclipse.ui.internal.cheatsheets.ICheatSheetResource;
import org.eclipse.ui.internal.cheatsheets.Messages;
import org.eclipse.ui.internal.cheatsheets.composite.model.CompositeCheatSheetModel;
import org.eclipse.ui.internal.cheatsheets.composite.parser.CompositeCheatSheetParser;
import org.eclipse.ui.internal.cheatsheets.composite.parser.ICompositeCheatsheetTags;
import org.eclipse.ui.internal.cheatsheets.composite.parser.IStatusContainer;
import org.eclipse.ui.internal.cheatsheets.registry.CheatSheetItemExtensionElement;
import org.eclipse.ui.internal.cheatsheets.registry.CheatSheetRegistryReader;
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;
/**
* Parser for the cheatsheet content files.
*
* Construct an intance of the CheatSheetDomParser.
* Call <code>parse()</code>.
* Then get the content items by calling
* <code>getIntroItem()</code> and <code>getItemsList()</code>.
* The title of the cheatsheet can be retrieved by calling
* <code>getTitle()</code>.
*
*/
public class CheatSheetParser implements IStatusContainer {
private static final String TRUE_STRING = "true"; //$NON-NLS-1$
private DocumentBuilder documentBuilder;
private DocumentProcessor processor;
private ArrayList<CheatSheetItemExtensionElement> itemExtensionContainerList;
// Cheatsheet kinds that can be parsed
public static final int COMPOSITE_ONLY = 1;
public static final int SIMPLE_ONLY = 2;
public static final int ANY = 3;
private IStatus status;
private int commandCount;
private int actionCount;
/**
* Java constructor comment.
*/
public CheatSheetParser() {
super();
documentBuilder = CheatSheetPlugin.getPlugin().getDocumentBuilder();
}
/**
* Gets the status of the last call to parse()
*/
public IStatus getStatus() {
return status;
}
@Override
public void addStatus(int severity, String message, Throwable exception) {
status = ParserStatusUtility.addStatus(status, severity, message, exception);
}
/**
* Converts any characters required to escaped by an XML parser to
* their escaped counterpart.
*
* Characters XML escaped counterpart
* < -> &lt;
* > -> &gt;
* & -> &amp;
* ' -> &apos;
* " -> &quot;
*
* Tags that will be ignored <b>, </b> and <br/>.
*
* @param text the string buffer to have its characters escaped
* @return string buffer with any of the characters requiring XML escaping escaped
*/
private StringBuilder escapeXMLCharacters(StringBuilder text) {
// Set the maximum length of the tags to ignore
final int MAXIMUM_TAG_LENGTH = 5;
// Keep a local variable for the orignal string's length
int length = text.length();
// Create the buffer to store the resulting string
StringBuilder result = new StringBuilder(length);
// Loop for the characters of the original string
for(int i=0; i<length; i++) {
// Grab the next character and determine how to handle it
char c = text.charAt(i);
switch (c) {
case '<': {
// We have a less than, grab the maximum tag length of characters
// or the remaining characters which follow and determine if it
// is the start of a tag to ignore.
String tmp = ICheatSheetResource.EMPTY_STRING;
if(i+MAXIMUM_TAG_LENGTH < length)
tmp = text.substring(i, i+MAXIMUM_TAG_LENGTH).toLowerCase();
else {
tmp = text.substring(i, length).toLowerCase();
}
if(tmp.startsWith(IParserTags.BOLD_START_TAG) || tmp.startsWith(IParserTags.BOLD_END_TAG) || tmp.startsWith(IParserTags.BREAK_TAG)) {
// We have a tag to ignore so just emit the character
result.append(c);
} else {
// We have detemined that it is just a less than
// so emit the XML escaped counterpart
result.append(IParserTags.LESS_THAN);
}
break; }
case '>': {
// We have a greater than, grab the maximum tag length of characters
// or the starting characters which come before and determine if it
// is the end of a tag to ignore.
String tmp = ICheatSheetResource.EMPTY_STRING;
if(i>=MAXIMUM_TAG_LENGTH) {
tmp = text.substring(i-MAXIMUM_TAG_LENGTH, i+1).toLowerCase();
} else {
tmp = text.substring(0, i+1).toLowerCase();
}
if(tmp.endsWith(IParserTags.BOLD_START_TAG) || tmp.endsWith(IParserTags.BOLD_END_TAG) || tmp.endsWith(IParserTags.BREAK_TAG)) {
// We have a tag to ignore so just emit the character
result.append(c);
} else {
// We have detemined that it is just a greater than
// so emit the XML escaped counterpart
result.append(IParserTags.GREATER_THAN);
}
break; }
case '&':
// We have an ampersand so emit the XML escaped counterpart
result.append(IParserTags.AMPERSAND);
break;
case '\'':
// We have an apostrophe so emit the XML escaped counterpart
result.append(IParserTags.APOSTROPHE);
break;
case '"':
// We have a quote so emit the XML escaped counterpart
result.append(IParserTags.QUOTE);
break;
case '\t':
// We have a tab, replace with a space
result.append(' ');
break;
default:
// We have a character that does not require escaping
result.append(c);
break;
}
}
return result;
}
private Node findNode(Node startNode, String nodeName) {
if(startNode == null) {
return null;
}
if(startNode.getNodeName().equals(nodeName)) {
return startNode;
}
NodeList nodes = startNode.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if(node.getNodeName().equals(nodeName)) {
return node;
}
}
return null;
}
private void handleExecutable(IExecutableItem item, Node executableNode, AbstractExecutable executable) throws CheatSheetParserException {
Assert.isNotNull(item);
Assert.isNotNull(executableNode);
String[] params = null;
if (executable instanceof CheatSheetCommand) {
commandCount++;
}
if (executable instanceof Action) {
actionCount++;
}
NamedNodeMap attributes = executableNode.getAttributes();
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(IParserTags.CONFIRM)) {
executable.setConfirm(attribute.getNodeValue().equals(TRUE_STRING));}
else if (attributeName.equals(IParserTags.WHEN)) {
executable.setWhen(attribute.getNodeValue());
} else if (attributeName.equals(IParserTags.REQUIRED)) {
executable.setRequired(attribute.getNodeValue().equals(TRUE_STRING));
} else if (attributeName.equals(IParserTags.TRANSLATE)) {
// Translation hint, no semantic effect
} else if (executable.hasParams() && attributeName.startsWith(IParserTags.PARAM)) {
try {
if(params == null) {
params = new String[9];
}
String paramNum = attributeName.substring(IParserTags.PARAM.length());
int num = Integer.parseInt(paramNum)-1;
if(num>-1 && num<9){
params[num] = attribute.getNodeValue();
} else {
String message = NLS.bind(Messages.ERROR_PARSING_PARAM_INVALIDRANGE, (new Object[] {attributeName, paramNum}));
addStatus(IStatus.ERROR, message, null);
}
} catch(NumberFormatException e) {
String message = Messages.ERROR_PARSING_PARAM_INVALIDNUMBER;
addStatus(IStatus.ERROR, message, e);
}
} else if (!executable.handleAttribute(attribute)) {
String message = NLS.bind(Messages.WARNING_PARSING_UNKNOWN_ATTRIBUTE, (new Object[] {attributeName, executableNode.getNodeName()}));
addStatus(IStatus.WARNING, message, null);
}
}
String errorMessage = executable.checkAttributes(executableNode);
if (errorMessage != null) {
throw new CheatSheetParserException(errorMessage);
}
}
checkForNoChildren(executableNode);
executable.setParams(params);
item.setExecutable(executable);
}
private void checkForNoChildren(Node parentNode) {
NodeList nodes = parentNode.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if(node.getNodeType() != Node.TEXT_NODE && node.getNodeType() != Node.COMMENT_NODE ) {
String message = NLS.bind(Messages.WARNING_PARSING_UNKNOWN_ELEMENT, (new Object[] {node.getNodeName(), parentNode.getNodeName()}));
addStatus(IStatus.WARNING, message, null);
}
}
}
private void handleCheatSheetAttributes(CheatSheet cheatSheet, Node cheatSheetNode) throws CheatSheetParserException {
Assert.isNotNull(cheatSheet);
Assert.isNotNull(cheatSheetNode);
Assert.isTrue(cheatSheetNode.getNodeName().equals(IParserTags.CHEATSHEET));
boolean title = false;
NamedNodeMap attributes = cheatSheetNode.getAttributes();
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(IParserTags.TITLE)) {
title = true;
cheatSheet.setTitle(attribute.getNodeValue());
} else {
String message = NLS.bind(Messages.WARNING_PARSING_UNKNOWN_ATTRIBUTE, (new Object[] {attributeName, cheatSheetNode.getNodeName()}));
addStatus(IStatus.WARNING, message, null);
}
}
}
if(!title) {
String message = NLS.bind(Messages.ERROR_PARSING_NO_TITLE, (new Object[] {cheatSheetNode.getNodeName()}));
throw new CheatSheetParserException(message);
}
}
private void handleConditionalSubItem(Item item, Node conditionalSubItemNode) throws CheatSheetParserException {
Assert.isNotNull(item);
Assert.isNotNull(conditionalSubItemNode);
Assert.isTrue(conditionalSubItemNode.getNodeName().equals(IParserTags.CONDITIONALSUBITEM));
ConditionalSubItem conditionalSubItem = new ConditionalSubItem();
boolean condition = false;
// Handle attributes
NamedNodeMap attributes = conditionalSubItemNode.getAttributes();
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(IParserTags.CONDITION)) {
condition = true;
conditionalSubItem.setCondition(attribute.getNodeValue());
} else {
String message = NLS.bind(Messages.WARNING_PARSING_UNKNOWN_ATTRIBUTE, (new Object[] {attributeName, conditionalSubItemNode.getNodeName()}));
addStatus(IStatus.WARNING, message, null);
}
}
}
if(!condition) {
String message = NLS.bind(Messages.ERROR_PARSING_NO_CONDITION, (new Object[] {conditionalSubItemNode.getNodeName()}));
throw new CheatSheetParserException(message);
}
boolean subitem = false;
// Handle nodes
NodeList nodes = conditionalSubItemNode.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if(node.getNodeName().equals(IParserTags.SUBITEM)) {
subitem = true;
handleSubItem(conditionalSubItem, node);
} else {
if(node.getNodeType() != Node.TEXT_NODE && node.getNodeType() != Node.COMMENT_NODE ) {
String message = NLS.bind(Messages.WARNING_PARSING_UNKNOWN_ELEMENT, (new Object[] {node.getNodeName(), conditionalSubItemNode.getNodeName()}));
addStatus(IStatus.WARNING, message, null);
}
}
}
if(!subitem) {
String message = NLS.bind(Messages.ERROR_PARSING_NO_SUBITEM, (new Object[] {conditionalSubItemNode.getNodeName()}));
throw new CheatSheetParserException(message);
}
item.addSubItem(conditionalSubItem);
}
private void handleDescription(Item item, Node startNode) throws CheatSheetParserException {
Assert.isNotNull(item);
Assert.isNotNull(startNode);
Node descriptionNode = findNode(startNode, IParserTags.DESCRIPTION);
if(descriptionNode != null) {
String text = handleMarkedUpText(descriptionNode, startNode, IParserTags.DESCRIPTION);
item.setDescription(text);
} else {
Node parentNode = startNode;
if( startNode.getNodeName().equals(IParserTags.DESCRIPTION) ) {
parentNode = startNode.getParentNode();
}
String message = NLS.bind(Messages.ERROR_PARSING_NO_DESCRIPTION, (new Object[] {parentNode.getNodeName()}));
throw new CheatSheetParserException(message);
}
}
private void handleSubItemDescription(SubItem subItem, Node startNode) throws CheatSheetParserException {
Assert.isNotNull(subItem);
Assert.isNotNull(startNode);
Node descriptionNode = findNode(startNode, IParserTags.DESCRIPTION);
if(descriptionNode != null) {
String text = handleMarkedUpText(descriptionNode, startNode, IParserTags.DESCRIPTION);
subItem.setLabel(text);
subItem.setFormatted(true);
}
}
private String handleMarkedUpText(Node nodeContainingText, Node startNode, String nodeName ) {
NodeList nodes = nodeContainingText.getChildNodes();
StringBuilder text = new StringBuilder();
boolean containsMarkup = false;
// The documentation for the content file specifies
// that leading whitespace should be ignored at the
// beginning of a description or after a <br/>. This
// applies also to <onCompletion> elements.
// See Bug 129208 and Bug 131185
boolean isLeadingTrimRequired = true;
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if(node.getNodeType() == Node.TEXT_NODE) {
String nodeValue = node.getNodeValue();
if (isLeadingTrimRequired) {
nodeValue = trimLeadingWhitespace(nodeValue);
}
text.append(nodeValue);
isLeadingTrimRequired = false;
} else if(node.getNodeType() == Node.ELEMENT_NODE) {
// handle <b></b> and <br/>
if(node.getNodeName().equals(IParserTags.BOLD)) {
containsMarkup = true;
text.append(IParserTags.BOLD_START_TAG);
text.append(node.getFirstChild().getNodeValue());
text.append(IParserTags.BOLD_END_TAG);
isLeadingTrimRequired = false;
} else if(node.getNodeName().equals(IParserTags.BREAK)) {
containsMarkup = true;
text.append(IParserTags.BREAK_TAG);
isLeadingTrimRequired = true;
} else {
warnUnknownMarkupElement(startNode, nodeName, node);
}
}
}
if(containsMarkup) {
text = escapeXMLCharacters(text);
text.insert(0, IParserTags.FORM_START_TAG);
text.append(IParserTags.FORM_END_TAG);
} else {
deTab(text);
}
// Remove the new line, form feed and tab chars
return text.toString().trim();
}
// Replace any tabs with spaces
private void deTab(StringBuilder text) {
for (int i = 0; i < text.length(); i++) {
if (text.charAt(i) == '\t') {
text.setCharAt(i, ' ');
}
}
}
private String trimLeadingWhitespace(String nodeValue) {
int firstNonWhitespaceIndex = 0;
while (firstNonWhitespaceIndex < nodeValue.length() &&
Character.isWhitespace(nodeValue.charAt(firstNonWhitespaceIndex))) {
firstNonWhitespaceIndex++;
}
if (firstNonWhitespaceIndex > 0) {
return nodeValue.substring(firstNonWhitespaceIndex, nodeValue.length());
}
return nodeValue;
}
/*
* Write a warning to the log
*/
private void warnUnknownMarkupElement(Node startNode, String nodeName, Node node) {
Node parentNode = startNode;
if( startNode.getNodeName().equals(nodeName) ) {
parentNode = startNode.getParentNode();
}
String message;
if (IParserTags.DESCRIPTION.equals(nodeName)) {
message = NLS.bind(Messages.WARNING_PARSING_DESCRIPTION_UNKNOWN_ELEMENT, (new Object[] {parentNode.getNodeName(), node.getNodeName()}));
} else {
message = NLS.bind(Messages.WARNING_PARSING_ON_COMPLETION_UNKNOWN_ELEMENT, (new Object[] {parentNode.getNodeName(), node.getNodeName()}));
}
addStatus(IStatus.WARNING, message, null);
}
private void handleOnCompletion(Item item, Node onCompletionNode) {
String text = handleMarkedUpText(onCompletionNode, onCompletionNode, IParserTags.ON_COMPLETION);
item.setCompletionMessage(text);
}
private void handleIntroNode(CheatSheet cheatSheet, Node introNode)
throws CheatSheetParserException {
Item introItem = new Item();
introItem.setTitle(Messages.CHEAT_SHEET_INTRO_TITLE);
handleIntroAttributes(introItem, introNode);
boolean hasDescription = false;
NodeList nodes = introNode.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if(node.getNodeName().equals(IParserTags.DESCRIPTION)) {
if (hasDescription) {
String message = NLS.bind(Messages.ERROR_PARSING_MULTIPLE_DESCRIPTION, (new Object[] {introNode.getNodeName()}));
addStatus(IStatus.ERROR, message, null);
} else {
hasDescription = true;
handleDescription(introItem, node);
}
} else {
if(node.getNodeType() != Node.TEXT_NODE && node.getNodeType() != Node.COMMENT_NODE ) {
String message = NLS.bind(Messages.WARNING_PARSING_UNKNOWN_ELEMENT, (new Object[] {node.getNodeName(), introNode.getNodeName()}));
addStatus(IStatus.WARNING, message, null);
}
}
}
if(!hasDescription) {
String message = NLS.bind(Messages.ERROR_PARSING_NO_DESCRIPTION, (new Object[] {introNode.getNodeName()}));
addStatus(IStatus.ERROR, message, null);
}
cheatSheet.setIntroItem(introItem);
}
private void handleIntroAttributes(Item item, Node introNode) {
Assert.isNotNull(item);
Assert.isNotNull(introNode);
NamedNodeMap attributes = introNode.getAttributes();
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(IParserTags.CONTEXTID)) {
item.setContextId(attribute.getNodeValue());
} else if (attributeName.equals(IParserTags.HREF)) {
item.setHref(attribute.getNodeValue());
} else {
String message = NLS.bind(Messages.WARNING_PARSING_UNKNOWN_ATTRIBUTE, (new Object[] {attributeName, introNode.getNodeName()}));
addStatus(IStatus.WARNING, message, null);
}
}
}
}
private Item handleItem(Node itemNode) throws CheatSheetParserException {
Assert.isNotNull(itemNode);
Assert.isTrue(itemNode.getNodeName().equals(IParserTags.ITEM));
Item item = new Item();
handleItemAttributes(item, itemNode);
boolean hasDescription = false;
NodeList nodes = itemNode.getChildNodes();
IncompatibleSiblingChecker checker = new IncompatibleSiblingChecker(this, itemNode);
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
checker.checkElement(node.getNodeName());
if(node.getNodeName().equals(IParserTags.ACTION)) {
handleExecutable(item, node, new Action());
} else if(node.getNodeName().equals(IParserTags.COMMAND)) {
handleExecutable(item, node, new CheatSheetCommand());
} else if(node.getNodeName().equals(IParserTags.DESCRIPTION)) {
if (hasDescription) {
String message = NLS.bind(Messages.ERROR_PARSING_MULTIPLE_DESCRIPTION, (new Object[] {itemNode.getNodeName()}));
addStatus(IStatus.ERROR, message, null);
} else {
hasDescription = true;
handleDescription(item, node);
}
} else if(node.getNodeName().equals(IParserTags.ON_COMPLETION)) {
handleOnCompletion(item, node);
} else if(node.getNodeName().equals(IParserTags.SUBITEM)) {
handleSubItem(item, node);
} else if(node.getNodeName().equals(IParserTags.CONDITIONALSUBITEM)) {
handleConditionalSubItem(item, node);
} else if(node.getNodeName().equals(IParserTags.REPEATEDSUBITM)) {
handleRepeatedSubItem(item, node);
} else if(node.getNodeName().equals(IParserTags.PERFORMWHEN)) {
handlePerformWhen(item, node);
} else {
if(node.getNodeType() != Node.TEXT_NODE && node.getNodeType() != Node.COMMENT_NODE ) {
String message = NLS.bind(Messages.WARNING_PARSING_UNKNOWN_ELEMENT, (new Object[] {node.getNodeName(), itemNode.getNodeName()}));
addStatus(IStatus.WARNING, message, null);
}
}
}
if(!hasDescription) {
String message = NLS.bind(Messages.ERROR_PARSING_NO_DESCRIPTION, (new Object[] {itemNode.getNodeName()}));
addStatus(IStatus.ERROR, message, null);
}
return item;
}
private void handleItemAttributes(Item item, Node itemNode) throws CheatSheetParserException {
Assert.isNotNull(item);
Assert.isNotNull(itemNode);
ArrayList<AbstractItemExtensionElement[]> itemExtensionElements = new ArrayList<>();
boolean title = false;
NamedNodeMap attributes = itemNode.getAttributes();
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(IParserTags.TITLE)) {
title = true;
item.setTitle(attribute.getNodeValue());
} else if (attributeName.equals(IParserTags.CONTEXTID)) {
item.setContextId(attribute.getNodeValue());
} else if (attributeName.equals(IParserTags.HREF)) {
item.setHref(attribute.getNodeValue());
} else if (attributeName.equals(IParserTags.SKIP)) {
item.setSkip(attribute.getNodeValue().equals(TRUE_STRING));
} else if (attributeName.equals(IParserTags.DIALOG)) {
item.setDialog(attribute.getNodeValue().equals(TRUE_STRING));
} else {
AbstractItemExtensionElement[] ie = handleUnknownItemAttribute(attribute, itemNode);
if (ie != null) {
itemExtensionElements.add(ie);
} else {
String message = NLS.bind(Messages.WARNING_PARSING_UNKNOWN_ATTRIBUTE, (new Object[] {attributeName, itemNode.getNodeName()}));
addStatus(IStatus.WARNING, message, null);
}
}
}
}
if(!title) {
String message = NLS.bind(Messages.ERROR_PARSING_NO_TITLE, (new Object[] {itemNode.getNodeName()}));
throw new CheatSheetParserException(message);
}
if (itemExtensionElements != null)
item.setItemExtensions(itemExtensionElements);
}
private void handlePerformWhen(IPerformWhenItem item, Node performWhenNode) throws CheatSheetParserException {
Assert.isNotNull(item);
Assert.isNotNull(performWhenNode);
Assert.isTrue(performWhenNode.getNodeName().equals(IParserTags.PERFORMWHEN));
PerformWhen performWhen = new PerformWhen();
boolean condition = false;
// Handle attributes
NamedNodeMap attributes = performWhenNode.getAttributes();
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(IParserTags.CONDITION)) {
condition = true;
performWhen.setCondition(attribute.getNodeValue());
} else {
String message = NLS.bind(Messages.WARNING_PARSING_UNKNOWN_ATTRIBUTE, (new Object[] {attributeName, performWhenNode.getNodeName()}));
addStatus(IStatus.WARNING,message, null);
}
}
}
if(!condition) {
String message = NLS.bind(Messages.ERROR_PARSING_NO_CONDITION, (new Object[] {performWhenNode.getNodeName()}));
throw new CheatSheetParserException(message);
}
boolean exeutable = false;
// Handle nodes
NodeList nodes = performWhenNode.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if(node.getNodeName().equals(IParserTags.ACTION)) {
exeutable = true;
handleExecutable(performWhen, node, new Action());
} else if(node.getNodeName().equals(IParserTags.COMMAND)) {
exeutable = true;
handleExecutable(performWhen, node, new CheatSheetCommand());
} else {
if(node.getNodeType() != Node.TEXT_NODE && node.getNodeType() != Node.COMMENT_NODE ) {
String message = NLS.bind(Messages.WARNING_PARSING_UNKNOWN_ELEMENT, (new Object[] {node.getNodeName(), performWhenNode .getNodeName()}));
addStatus(IStatus.WARNING, message, null);
}
}
}
if(!exeutable) {
String message = NLS.bind(Messages.ERROR_PARSING_NO_ACTION, (new Object[] {performWhenNode.getNodeName()}));
throw new CheatSheetParserException(message);
}
item.setPerformWhen(performWhen);
}
private void handleRepeatedSubItem(Item item, Node repeatedSubItemNode) throws CheatSheetParserException {
Assert.isNotNull(item);
Assert.isNotNull(repeatedSubItemNode);
Assert.isTrue(repeatedSubItemNode.getNodeName().equals(IParserTags.REPEATEDSUBITM));
RepeatedSubItem repeatedSubItem = new RepeatedSubItem();
boolean values = false;
// Handle attributes
NamedNodeMap attributes = repeatedSubItemNode.getAttributes();
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(IParserTags.VALUES)) {
values = true;
repeatedSubItem.setValues(attribute.getNodeValue());
} else {
String message = NLS.bind(Messages.WARNING_PARSING_UNKNOWN_ATTRIBUTE, (new Object[] {attributeName, repeatedSubItemNode.getNodeName()}));
addStatus(IStatus.WARNING, message, null);
}
}
}
if(!values) {
String message = NLS.bind(Messages.ERROR_PARSING_NO_VALUES, (new Object[] {repeatedSubItemNode.getNodeName()}));
throw new CheatSheetParserException(message);
}
boolean subitem = false;
// Handle nodes
NodeList nodes = repeatedSubItemNode.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if(node.getNodeName().equals(IParserTags.SUBITEM)) {
subitem = true;
handleSubItem(repeatedSubItem, node);
} else {
if(node.getNodeType() != Node.TEXT_NODE && node.getNodeType() != Node.COMMENT_NODE ) {
String message = NLS.bind(Messages.WARNING_PARSING_UNKNOWN_ELEMENT, (new Object[] {node.getNodeName(), repeatedSubItemNode.getNodeName()}));
addStatus(IStatus.WARNING, message, null);
}
}
}
if(!subitem) {
String message = NLS.bind(Messages.ERROR_PARSING_NO_SUBITEM, (new Object[] {repeatedSubItemNode.getNodeName()}));
throw new CheatSheetParserException(message);
}
item.addSubItem(repeatedSubItem);
}
private void handleSubItem(ISubItemItem item, Node subItemNode) throws CheatSheetParserException {
Assert.isNotNull(item);
Assert.isNotNull(subItemNode);
Assert.isTrue(subItemNode.getNodeName().equals(IParserTags.SUBITEM));
SubItem subItem = new SubItem();
IncompatibleSiblingChecker checker = new IncompatibleSiblingChecker(this, subItemNode);
NodeList nodes = subItemNode.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
checker.checkElement(node.getNodeName());
if(node.getNodeName().equals(IParserTags.ACTION)) {
handleExecutable(subItem, node, new Action());
} else if(node.getNodeName().equals(IParserTags.COMMAND)) {
handleExecutable(subItem, node, new CheatSheetCommand());
} else if(node.getNodeName().equals(IParserTags.PERFORMWHEN)) {
handlePerformWhen(subItem, node);
} else if (node.getNodeName().equals(IParserTags.DESCRIPTION)) {
if (subItem.isFormatted()) {
String message = NLS.bind(
Messages.ERROR_PARSING_MULTIPLE_DESCRIPTION,
(new Object[] { node.getNodeName() }));
addStatus(IStatus.ERROR, message, null);
} else {
handleSubItemDescription(subItem, node);
}
} else {
if(node.getNodeType() != Node.TEXT_NODE && node.getNodeType() != Node.COMMENT_NODE ) {
String message = NLS.bind(Messages.WARNING_PARSING_UNKNOWN_ELEMENT, (new Object[] {node.getNodeName(), subItemNode.getNodeName()}));
addStatus(IStatus.WARNING, message, null);
}
}
}
handleSubItemAttributes(subItem, subItemNode);
item.addSubItem(subItem);
}
private void handleSubItemAttributes(SubItem subItem, Node subItemNode) throws CheatSheetParserException {
Assert.isNotNull(subItem);
Assert.isNotNull(subItemNode);
NamedNodeMap attributes = subItemNode.getAttributes();
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(IParserTags.LABEL)) {
subItem.setLabel(attribute.getNodeValue());
} else if (attributeName.equals(IParserTags.SKIP)) {
subItem.setSkip(attribute.getNodeValue().equals(TRUE_STRING));
} else if (attributeName.equals(IParserTags.WHEN)) {
subItem.setWhen(attribute.getNodeValue());
} else {
String message = NLS.bind(Messages.WARNING_PARSING_UNKNOWN_ATTRIBUTE, (new Object[] {attributeName, subItemNode.getNodeName()}));
addStatus(IStatus.WARNING, message, null);
}
}
}
if(subItem.getLabel() == null) {
String message = NLS.bind(Messages.ERROR_PARSING_NO_LABEL, (new Object[] {subItemNode.getNodeName()}));
throw new CheatSheetParserException(message);
}
}
private AbstractItemExtensionElement[] handleUnknownItemAttribute(Node item, Node node) {
ArrayList<AbstractItemExtensionElement> al = new ArrayList<>();
if (itemExtensionContainerList == null)
return null;
for (int i = 0; i < itemExtensionContainerList.size(); i++) {
CheatSheetItemExtensionElement itemExtensionElement = itemExtensionContainerList.get(i);
if (itemExtensionElement.getItemAttribute().equals(item.getNodeName())) {
AbstractItemExtensionElement itemElement = itemExtensionElement.createInstance();
if(itemElement != null) {
itemElement.handleAttribute(item.getNodeValue());
al.add(itemElement);
}
}
}
if(al.size() == 0) {
String message = NLS.bind(Messages.WARNING_PARSING_UNKNOWN_ATTRIBUTE, (new Object[] {item.getNodeName(), node.getNodeName()}));
addStatus(IStatus.WARNING, message, null);
}
return al.toArray(new AbstractItemExtensionElement[al.size()]);
}
public ICheatSheet parse(URL url, String pluginId, int cheatSheetKind) {
return parse(new ParserInput(url, pluginId, null), cheatSheetKind);
}
public ICheatSheet parse(ParserInput input, int cheatSheetKind) {
status = Status.OK_STATUS;
commandCount = 0;
actionCount = 0;
if(input == null) {
return null;
}
if (input.getErrorMessage() != null) {
addStatus(IStatus.ERROR, input.getErrorMessage(), null);
}
InputStream is = null;
InputSource inputSource = null;
String filename = ""; //$NON-NLS-1$
URL url = input.getUrl();
if (input.getXml() != null) {
StringReader reader = new StringReader(input.getXml());
inputSource = new InputSource(reader);
} else if (input.getUrl() != null){
try {
is = url.openStream();
if (is != null) {
inputSource = new InputSource(is);
}
} catch (Exception e) {
String message = NLS.bind(Messages.ERROR_OPENING_FILE, (new Object[] {url.getFile()}));
addStatus(IStatus.ERROR, message, e);
return null;
}
} else {
return null;
}
if (input.getUrl() != null){
filename = url.getFile();
}
Document document;
try {
if(documentBuilder == null) {
addStatus(IStatus.ERROR, Messages.ERROR_DOCUMENT_BUILDER_NOT_INIT, null);
return null;
}
document = documentBuilder.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, Integer.valueOf(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) {
}
}
// process dynamic content, normalize paths
if (processor == null) {
DocumentReader reader = new DocumentReader();
processor = new DocumentProcessor(new ProcessorHandler[] {
new FilterHandler(CheatSheetEvaluationContext.getContext()),
new NormalizeHandler(),
new IncludeHandler(reader, Platform.getNL()),
new ExtensionHandler(reader, Platform.getNL())
});
}
String documentPath = null;
if (input.getPluginId() != null) {
documentPath = '/' + input.getPluginId() + input.getUrl().getPath();
}
processor.process(UAElementFactory.newElement(document.getDocumentElement()), documentPath);
if ( cheatSheetKind == COMPOSITE_ONLY || (cheatSheetKind == ANY && isComposite(document))) {
CompositeCheatSheetParser compositeParser = new CompositeCheatSheetParser();
CompositeCheatSheetModel result = compositeParser.parseCompositeCheatSheet(document, input.getUrl());
status = compositeParser.getStatus();
return result;
}
try {
return parseCheatSheet(document);
} catch(CheatSheetParserException e) {
addStatus(IStatus.ERROR, e.getMessage(), e);
}
return null;
}
private boolean isComposite(Document document) {
if (document != null) {
Node rootnode = document.getDocumentElement();
// Is the root node compositeCheatsheet?
return rootnode.getNodeName().equals(ICompositeCheatsheetTags.COMPOSITE_CHEATSHEET) ;
}
return false;
}
private CheatSheet parseCheatSheet(Document document) throws CheatSheetParserException {
// 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 really <cheatsheet>?
if( !rootnode.getNodeName().equals(IParserTags.CHEATSHEET) ) {
throw new CheatSheetParserException(Messages.ERROR_PARSING_CHEATSHEET_ELEMENT);
}
// Create the cheat sheet model object
CheatSheet cheatSheet = new CheatSheet();
handleCheatSheetAttributes(cheatSheet, rootnode);
boolean hasItem = false;
boolean hasIntro = false;
CheatSheetRegistryReader reader = CheatSheetRegistryReader.getInstance();
itemExtensionContainerList = reader.readItemExtensions();
NodeList nodes = rootnode.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if(node.getNodeName().equals(IParserTags.ITEM)) {
hasItem = true;
Item item = handleItem(node);
cheatSheet.addItem(item);
} else if(node.getNodeName().equals(IParserTags.INTRO)) {
if (hasIntro) {
addStatus(IStatus.ERROR, Messages.ERROR_PARSING_MORE_THAN_ONE_INTRO, null);
} else {
hasIntro = true;
handleIntroNode(cheatSheet, node);
}
} else {
if(node.getNodeType() != Node.TEXT_NODE && node.getNodeType() != Node.COMMENT_NODE ) {
String message = NLS.bind(Messages.WARNING_PARSING_UNKNOWN_ELEMENT, (new Object[] {node.getNodeName(), rootnode.getNodeName()}));
addStatus(IStatus.WARNING, message, null);
}
}
}
if(!hasIntro) {
addStatus(IStatus.ERROR, Messages.ERROR_PARSING_NO_INTRO, null);
}
if(!hasItem) {
addStatus(IStatus.ERROR, Messages.ERROR_PARSING_NO_ITEM, null);
}
//handleIntro(cheatSheet, document);
//handleItems(cheatSheet, document);
if (status.getSeverity() == IStatus.ERROR) {
return null;
}
cheatSheet.setContainsCommandOrAction(actionCount != 0 || commandCount != 0);
return cheatSheet;
}
throw new CheatSheetParserException(Messages.ERROR_PARSING_CHEATSHEET_CONTENTS);
}
/*
* Normalizes composite cheat sheet-relative paths to simple cheat sheets into fully
* qualified paths, e.g. for the path "tasks/mySimpleCheatSheet.xml" in composite cheat
* sheet "/my.plugin/cheatsheets/myCompositeCheatSheet.xml", this normalizes to
* "/my.plugin/cheatsheets/tasks/mySimpleCheatSheet.xml".
*
* This is necessary because with dynamic content we are pulling in tasks from other
* plug-ins and those tasks have relative paths. It also only applies for cheat sheets
* located in running plug-ins.
*/
private class NormalizeHandler extends ProcessorHandler {
private static final String ELEMENT_PARAM = "param"; //$NON-NLS-1$
private static final String ATTRIBUTE_NAME = "name"; //$NON-NLS-1$
private static final String ATTRIBUTE_VALUE = "value"; //$NON-NLS-1$
private static final String NAME_PATH = "path"; //$NON-NLS-1$
@Override
public short handle(UAElement element, String id) {
if (id != null && ELEMENT_PARAM.equals(element.getElementName())) {
String name = element.getAttribute(ATTRIBUTE_NAME);
if (NAME_PATH.equals(name)) {
String value = element.getAttribute(ATTRIBUTE_VALUE);
if (value != null) {
int index = id.lastIndexOf('/');
element.setAttribute(ATTRIBUTE_VALUE, id.substring(0, index + 1) + value);
}
}
return HANDLED_CONTINUE;
}
return UNHANDLED;
}
}
}