0001: /*******************************************************************************
0002: * Copyright (c) 2002, 2007 IBM Corporation and others.
0003: * All rights reserved. This program and the accompanying materials
0004: * are made available under the terms of the Eclipse Public License v1.0
0005: * which accompanies this distribution, and is available at
0006: * http://www.eclipse.org/legal/epl-v10.html
0007: *
0008: * Contributors:
0009: * IBM Corporation - initial API and implementation
0010: *******************************************************************************/package org.eclipse.ui.internal.cheatsheets.data;
0011:
0012: import java.io.IOException;
0013: import java.io.InputStream;
0014: import java.io.StringReader;
0015: import java.net.URL;
0016: import java.util.ArrayList;
0017:
0018: import javax.xml.parsers.DocumentBuilder;
0019:
0020: import org.eclipse.core.runtime.Assert;
0021: import org.eclipse.core.runtime.IStatus;
0022: import org.eclipse.core.runtime.Platform;
0023: import org.eclipse.core.runtime.Status;
0024: import org.eclipse.help.internal.UAElement;
0025: import org.eclipse.help.internal.UAElementFactory;
0026: import org.eclipse.help.internal.dynamic.DocumentProcessor;
0027: import org.eclipse.help.internal.dynamic.DocumentReader;
0028: import org.eclipse.help.internal.dynamic.ExtensionHandler;
0029: import org.eclipse.help.internal.dynamic.FilterHandler;
0030: import org.eclipse.help.internal.dynamic.IncludeHandler;
0031: import org.eclipse.help.internal.dynamic.ProcessorHandler;
0032: import org.eclipse.osgi.util.NLS;
0033: import org.eclipse.ui.cheatsheets.AbstractItemExtensionElement;
0034: import org.eclipse.ui.internal.cheatsheets.CheatSheetEvaluationContext;
0035: import org.eclipse.ui.internal.cheatsheets.CheatSheetPlugin;
0036: import org.eclipse.ui.internal.cheatsheets.ICheatSheetResource;
0037: import org.eclipse.ui.internal.cheatsheets.Messages;
0038: import org.eclipse.ui.internal.cheatsheets.composite.model.CompositeCheatSheetModel;
0039: import org.eclipse.ui.internal.cheatsheets.composite.parser.CompositeCheatSheetParser;
0040: import org.eclipse.ui.internal.cheatsheets.composite.parser.ICompositeCheatsheetTags;
0041: import org.eclipse.ui.internal.cheatsheets.composite.parser.IStatusContainer;
0042: import org.eclipse.ui.internal.cheatsheets.registry.CheatSheetItemExtensionElement;
0043: import org.eclipse.ui.internal.cheatsheets.registry.CheatSheetRegistryReader;
0044: import org.w3c.dom.Document;
0045: import org.w3c.dom.NamedNodeMap;
0046: import org.w3c.dom.Node;
0047: import org.w3c.dom.NodeList;
0048: import org.xml.sax.InputSource;
0049: import org.xml.sax.SAXException;
0050: import org.xml.sax.SAXParseException;
0051:
0052: /**
0053: * Parser for the cheatsheet content files.
0054: *
0055: * Construct an intance of the CheatSheetDomParser.
0056: * Call <code>parse()</code>.
0057: * Then get the content items by calling
0058: * <code>getIntroItem()</code> and <code>getItemsList()</code>.
0059: * The title of the cheatsheet can be retrieved by calling
0060: * <code>getTitle()</code>.
0061: *
0062: */
0063: public class CheatSheetParser implements IStatusContainer {
0064:
0065: private static final String TRUE_STRING = "true"; //$NON-NLS-1$
0066:
0067: private DocumentBuilder documentBuilder;
0068: private DocumentProcessor processor;
0069: private ArrayList itemExtensionContainerList;
0070:
0071: // Cheatsheet kinds that can be parsed
0072: public static final int COMPOSITE_ONLY = 1;
0073: public static final int SIMPLE_ONLY = 2;
0074: public static final int ANY = 3;
0075:
0076: private IStatus status;
0077:
0078: private int commandCount;
0079:
0080: private int actionCount;
0081:
0082: /**
0083: * Java constructor comment.
0084: */
0085: public CheatSheetParser() {
0086: super ();
0087: documentBuilder = CheatSheetPlugin.getPlugin()
0088: .getDocumentBuilder();
0089: }
0090:
0091: /**
0092: * Gets the status of the last call to parse()
0093: */
0094: public IStatus getStatus() {
0095: return status;
0096: }
0097:
0098: public void addStatus(int severity, String message,
0099: Throwable exception) {
0100: status = ParserStatusUtility.addStatus(status, severity,
0101: message, exception);
0102: }
0103:
0104: /**
0105: * Converts any characters required to escaped by an XML parser to
0106: * their escaped counterpart.
0107: *
0108: * Characters XML escaped counterpart
0109: * < -> <
0110: * > -> >
0111: * & -> &
0112: * ' -> '
0113: * " -> "
0114: *
0115: * Tags that will be ignored <b>, </b> and <br/>.
0116: *
0117: * @param text the string buffer to have its characters escaped
0118: * @return string buffer with any of the characters requiring XML escaping escaped
0119: */
0120: private StringBuffer escapeXMLCharacters(StringBuffer text) {
0121: // Set the maximum length of the tags to ignore
0122: final int MAXIMUM_TAG_LENGTH = 5;
0123:
0124: // Keep a local variable for the orignal string's length
0125: int length = text.length();
0126:
0127: // Create the buffer to store the resulting string
0128: StringBuffer result = new StringBuffer(length);
0129:
0130: // Loop for the characters of the original string
0131: for (int i = 0; i < length; i++) {
0132: // Grab the next character and determine how to handle it
0133: char c = text.charAt(i);
0134: switch (c) {
0135: case '<': {
0136: // We have a less than, grab the maximum tag length of characters
0137: // or the remaining characters which follow and determine if it
0138: // is the start of a tag to ignore.
0139: String tmp = ICheatSheetResource.EMPTY_STRING;
0140: if (i + MAXIMUM_TAG_LENGTH < length)
0141: tmp = text.substring(i, i + MAXIMUM_TAG_LENGTH)
0142: .toLowerCase();
0143: else {
0144: tmp = text.substring(i, length).toLowerCase();
0145: }
0146: if (tmp.startsWith(IParserTags.BOLD_START_TAG)
0147: || tmp.startsWith(IParserTags.BOLD_END_TAG)
0148: || tmp.startsWith(IParserTags.BREAK_TAG)) {
0149: // We have a tag to ignore so just emit the character
0150: result.append(c);
0151: } else {
0152: // We have detemined that it is just a less than
0153: // so emit the XML escaped counterpart
0154: result.append(IParserTags.LESS_THAN);
0155: }
0156: break;
0157: }
0158: case '>': {
0159: // We have a greater than, grab the maximum tag length of characters
0160: // or the starting characters which come before and determine if it
0161: // is the end of a tag to ignore.
0162: String tmp = ICheatSheetResource.EMPTY_STRING;
0163: if (i >= MAXIMUM_TAG_LENGTH) {
0164: tmp = text.substring(i - MAXIMUM_TAG_LENGTH, i + 1)
0165: .toLowerCase();
0166: } else {
0167: tmp = text.substring(0, i + 1).toLowerCase();
0168: }
0169: if (tmp.endsWith(IParserTags.BOLD_START_TAG)
0170: || tmp.endsWith(IParserTags.BOLD_END_TAG)
0171: || tmp.endsWith(IParserTags.BREAK_TAG)) {
0172: // We have a tag to ignore so just emit the character
0173: result.append(c);
0174: } else {
0175: // We have detemined that it is just a greater than
0176: // so emit the XML escaped counterpart
0177: result.append(IParserTags.GREATER_THAN);
0178: }
0179: break;
0180: }
0181: case '&':
0182: // We have an ampersand so emit the XML escaped counterpart
0183: result.append(IParserTags.AMPERSAND);
0184: break;
0185: case '\'':
0186: // We have an apostrophe so emit the XML escaped counterpart
0187: result.append(IParserTags.APOSTROPHE);
0188: break;
0189: case '"':
0190: // We have a quote so emit the XML escaped counterpart
0191: result.append(IParserTags.QUOTE);
0192: break;
0193: case '\t':
0194: // We have a tab, replace with a space
0195: result.append(' ');
0196: break;
0197: default:
0198: // We have a character that does not require escaping
0199: result.append(c);
0200: break;
0201: }
0202: }
0203: return result;
0204: }
0205:
0206: private Node findNode(Node startNode, String nodeName) {
0207: if (startNode == null) {
0208: return null;
0209: }
0210:
0211: if (startNode.getNodeName().equals(nodeName)) {
0212: return startNode;
0213: }
0214:
0215: NodeList nodes = startNode.getChildNodes();
0216: for (int i = 0; i < nodes.getLength(); i++) {
0217: Node node = nodes.item(i);
0218: if (node.getNodeName().equals(nodeName)) {
0219: return node;
0220: }
0221: }
0222:
0223: return null;
0224: }
0225:
0226: private void handleExecutable(IExecutableItem item,
0227: Node executableNode, AbstractExecutable executable)
0228: throws CheatSheetParserException {
0229: Assert.isNotNull(item);
0230: Assert.isNotNull(executableNode);
0231:
0232: String[] params = null;
0233:
0234: if (executable instanceof CheatSheetCommand) {
0235: commandCount++;
0236: }
0237: if (executable instanceof Action) {
0238: actionCount++;
0239: }
0240:
0241: NamedNodeMap attributes = executableNode.getAttributes();
0242: if (attributes != null) {
0243: for (int x = 0; x < attributes.getLength(); x++) {
0244: Node attribute = attributes.item(x);
0245: String attributeName = attribute.getNodeName();
0246: if (attribute == null || attributeName == null)
0247: continue;
0248: if (attributeName.equals(IParserTags.CONFIRM)) {
0249: executable.setConfirm(attribute.getNodeValue()
0250: .equals(TRUE_STRING));
0251: } else if (attributeName.equals(IParserTags.WHEN)) {
0252: executable.setWhen(attribute.getNodeValue());
0253: } else if (attributeName.equals(IParserTags.REQUIRED)) {
0254: executable.setRequired(attribute.getNodeValue()
0255: .equals(TRUE_STRING));
0256: } else if (attributeName.equals(IParserTags.TRANSLATE)) {
0257: // Translation hint, no semantic effect
0258: } else if (executable.hasParams()
0259: && attributeName.startsWith(IParserTags.PARAM)) {
0260: try {
0261: if (params == null) {
0262: params = new String[9];
0263: }
0264: String paramNum = attributeName
0265: .substring(IParserTags.PARAM.length());
0266: int num = Integer.parseInt(paramNum) - 1;
0267:
0268: if (num > -1 && num < 9) {
0269: params[num] = attribute.getNodeValue();
0270: } else {
0271: String message = NLS
0272: .bind(
0273: Messages.ERROR_PARSING_PARAM_INVALIDRANGE,
0274: (new Object[] {
0275: attributeName,
0276: paramNum }));
0277: addStatus(IStatus.ERROR, message, null);
0278: }
0279: } catch (NumberFormatException e) {
0280: String message = Messages.ERROR_PARSING_PARAM_INVALIDNUMBER;
0281: addStatus(IStatus.ERROR, message, e);
0282: }
0283: } else if (!executable.handleAttribute(attribute)) {
0284: String message = NLS.bind(
0285: Messages.WARNING_PARSING_UNKNOWN_ATTRIBUTE,
0286: (new Object[] { attributeName,
0287: executableNode.getNodeName() }));
0288: addStatus(IStatus.WARNING, message, null);
0289: }
0290: }
0291: String errorMessage = executable
0292: .checkAttributes(executableNode);
0293: if (errorMessage != null) {
0294: throw new CheatSheetParserException(errorMessage);
0295: }
0296: }
0297: checkForNoChildren(executableNode);
0298: executable.setParams(params);
0299: item.setExecutable(executable);
0300: }
0301:
0302: private void checkForNoChildren(Node parentNode) {
0303: NodeList nodes = parentNode.getChildNodes();
0304: for (int i = 0; i < nodes.getLength(); i++) {
0305: Node node = nodes.item(i);
0306: if (node.getNodeType() != Node.TEXT_NODE
0307: && node.getNodeType() != Node.COMMENT_NODE) {
0308: String message = NLS.bind(
0309: Messages.WARNING_PARSING_UNKNOWN_ELEMENT,
0310: (new Object[] { node.getNodeName(),
0311: parentNode.getNodeName() }));
0312: addStatus(IStatus.WARNING, message, null);
0313: }
0314: }
0315: }
0316:
0317: private void handleCheatSheetAttributes(CheatSheet cheatSheet,
0318: Node cheatSheetNode) throws CheatSheetParserException {
0319: Assert.isNotNull(cheatSheet);
0320: Assert.isNotNull(cheatSheetNode);
0321: Assert.isTrue(cheatSheetNode.getNodeName().equals(
0322: IParserTags.CHEATSHEET));
0323:
0324: boolean title = false;
0325:
0326: NamedNodeMap attributes = cheatSheetNode.getAttributes();
0327: if (attributes != null) {
0328: for (int x = 0; x < attributes.getLength(); x++) {
0329: Node attribute = attributes.item(x);
0330: String attributeName = attribute.getNodeName();
0331: if (attribute == null || attributeName == null)
0332: continue;
0333:
0334: if (attributeName.equals(IParserTags.TITLE)) {
0335: title = true;
0336: cheatSheet.setTitle(attribute.getNodeValue());
0337: } else {
0338: String message = NLS.bind(
0339: Messages.WARNING_PARSING_UNKNOWN_ATTRIBUTE,
0340: (new Object[] { attributeName,
0341: cheatSheetNode.getNodeName() }));
0342: addStatus(IStatus.WARNING, message, null);
0343: }
0344: }
0345: }
0346:
0347: if (!title) {
0348: String message = NLS.bind(Messages.ERROR_PARSING_NO_TITLE,
0349: (new Object[] { cheatSheetNode.getNodeName() }));
0350: throw new CheatSheetParserException(message);
0351: }
0352: }
0353:
0354: private void handleConditionalSubItem(Item item,
0355: Node conditionalSubItemNode)
0356: throws CheatSheetParserException {
0357: Assert.isNotNull(item);
0358: Assert.isNotNull(conditionalSubItemNode);
0359: Assert.isTrue(conditionalSubItemNode.getNodeName().equals(
0360: IParserTags.CONDITIONALSUBITEM));
0361:
0362: ConditionalSubItem conditionalSubItem = new ConditionalSubItem();
0363:
0364: boolean condition = false;
0365:
0366: // Handle attributes
0367: NamedNodeMap attributes = conditionalSubItemNode
0368: .getAttributes();
0369: if (attributes != null) {
0370: for (int x = 0; x < attributes.getLength(); x++) {
0371: Node attribute = attributes.item(x);
0372: String attributeName = attribute.getNodeName();
0373: if (attribute == null || attributeName == null)
0374: continue;
0375:
0376: if (attributeName.equals(IParserTags.CONDITION)) {
0377: condition = true;
0378: conditionalSubItem.setCondition(attribute
0379: .getNodeValue());
0380: } else {
0381: String message = NLS.bind(
0382: Messages.WARNING_PARSING_UNKNOWN_ATTRIBUTE,
0383: (new Object[] {
0384: attributeName,
0385: conditionalSubItemNode
0386: .getNodeName() }));
0387: addStatus(IStatus.WARNING, message, null);
0388: }
0389: }
0390: }
0391:
0392: if (!condition) {
0393: String message = NLS.bind(
0394: Messages.ERROR_PARSING_NO_CONDITION,
0395: (new Object[] { conditionalSubItemNode
0396: .getNodeName() }));
0397: throw new CheatSheetParserException(message);
0398: }
0399:
0400: boolean subitem = false;
0401:
0402: // Handle nodes
0403: NodeList nodes = conditionalSubItemNode.getChildNodes();
0404: for (int i = 0; i < nodes.getLength(); i++) {
0405: Node node = nodes.item(i);
0406:
0407: if (node.getNodeName().equals(IParserTags.SUBITEM)) {
0408: subitem = true;
0409: handleSubItem(conditionalSubItem, node);
0410: } else {
0411: if (node.getNodeType() != Node.TEXT_NODE
0412: && node.getNodeType() != Node.COMMENT_NODE) {
0413: String message = NLS.bind(
0414: Messages.WARNING_PARSING_UNKNOWN_ELEMENT,
0415: (new Object[] {
0416: node.getNodeName(),
0417: conditionalSubItemNode
0418: .getNodeName() }));
0419: addStatus(IStatus.WARNING, message, null);
0420: }
0421: }
0422: }
0423:
0424: if (!subitem) {
0425: String message = NLS.bind(
0426: Messages.ERROR_PARSING_NO_SUBITEM,
0427: (new Object[] { conditionalSubItemNode
0428: .getNodeName() }));
0429: throw new CheatSheetParserException(message);
0430: }
0431:
0432: item.addSubItem(conditionalSubItem);
0433: }
0434:
0435: private void handleDescription(Item item, Node startNode)
0436: throws CheatSheetParserException {
0437: Assert.isNotNull(item);
0438: Assert.isNotNull(startNode);
0439:
0440: Node descriptionNode = findNode(startNode,
0441: IParserTags.DESCRIPTION);
0442:
0443: if (descriptionNode != null) {
0444: String text = handleMarkedUpText(descriptionNode,
0445: startNode, IParserTags.DESCRIPTION);
0446: item.setDescription(text);
0447: } else {
0448: Node parentNode = startNode;
0449: if (startNode.getNodeName().equals(IParserTags.DESCRIPTION)) {
0450: parentNode = startNode.getParentNode();
0451: }
0452: String message = NLS.bind(
0453: Messages.ERROR_PARSING_NO_DESCRIPTION,
0454: (new Object[] { parentNode.getNodeName() }));
0455: throw new CheatSheetParserException(message);
0456: }
0457: }
0458:
0459: private String handleMarkedUpText(Node nodeContainingText,
0460: Node startNode, String nodeName) {
0461: NodeList nodes = nodeContainingText.getChildNodes();
0462: StringBuffer text = new StringBuffer();
0463:
0464: boolean containsMarkup = false;
0465:
0466: // The documentation for the content file specifies
0467: // that leading whitespace should be ignored at the
0468: // beginning of a description or after a <br/>. This
0469: // applies also to <onCompletion> elements.
0470: // See Bug 129208 and Bug 131185
0471: boolean isLeadingTrimRequired = true;
0472:
0473: for (int i = 0; i < nodes.getLength(); i++) {
0474: Node node = nodes.item(i);
0475: if (node.getNodeType() == Node.TEXT_NODE) {
0476: String nodeValue = node.getNodeValue();
0477: if (isLeadingTrimRequired) {
0478: nodeValue = trimLeadingWhitespace(nodeValue);
0479: }
0480: text.append(nodeValue);
0481: isLeadingTrimRequired = false;
0482: } else if (node.getNodeType() == Node.ELEMENT_NODE) {
0483: // handle <b></b> and <br/>
0484: if (node.getNodeName().equals(IParserTags.BOLD)) {
0485: containsMarkup = true;
0486: text.append(IParserTags.BOLD_START_TAG);
0487: text.append(node.getFirstChild().getNodeValue());
0488: text.append(IParserTags.BOLD_END_TAG);
0489: isLeadingTrimRequired = false;
0490: } else if (node.getNodeName().equals(IParserTags.BREAK)) {
0491: containsMarkup = true;
0492: text.append(IParserTags.BREAK_TAG);
0493: isLeadingTrimRequired = true;
0494: } else {
0495: warnUnknownMarkupElement(startNode, nodeName, node);
0496: }
0497: }
0498: }
0499:
0500: if (containsMarkup) {
0501: text = escapeXMLCharacters(text);
0502: text.insert(0, IParserTags.FORM_START_TAG);
0503: text.append(IParserTags.FORM_END_TAG);
0504: } else {
0505: deTab(text);
0506: }
0507:
0508: // Remove the new line, form feed and tab chars
0509: return text.toString().trim();
0510: }
0511:
0512: // Replace any tabs with spaces
0513:
0514: private void deTab(StringBuffer text) {
0515: for (int i = 0; i < text.length(); i++) {
0516: if (text.charAt(i) == '\t') {
0517: text.setCharAt(i, ' ');
0518: }
0519: }
0520: }
0521:
0522: private String trimLeadingWhitespace(String nodeValue) {
0523: int firstNonWhitespaceIndex = 0;
0524: while (firstNonWhitespaceIndex < nodeValue.length()
0525: && Character.isWhitespace(nodeValue
0526: .charAt(firstNonWhitespaceIndex))) {
0527: firstNonWhitespaceIndex++;
0528: }
0529: if (firstNonWhitespaceIndex > 0) {
0530: return nodeValue.substring(firstNonWhitespaceIndex,
0531: nodeValue.length());
0532: }
0533: return nodeValue;
0534: }
0535:
0536: /*
0537: * Write a warning to the log
0538: */
0539: private void warnUnknownMarkupElement(Node startNode,
0540: String nodeName, Node node) {
0541: Node parentNode = startNode;
0542: if (startNode.getNodeName().equals(nodeName)) {
0543: parentNode = startNode.getParentNode();
0544: }
0545: String message;
0546: if (IParserTags.DESCRIPTION.equals(nodeName)) {
0547: message = NLS
0548: .bind(
0549: Messages.WARNING_PARSING_DESCRIPTION_UNKNOWN_ELEMENT,
0550: (new Object[] { parentNode.getNodeName(),
0551: node.getNodeName() }));
0552: } else {
0553: message = NLS
0554: .bind(
0555: Messages.WARNING_PARSING_ON_COMPLETION_UNKNOWN_ELEMENT,
0556: (new Object[] { parentNode.getNodeName(),
0557: node.getNodeName() }));
0558: }
0559: addStatus(IStatus.WARNING, message, null);
0560:
0561: }
0562:
0563: private void handleOnCompletion(Item item, Node onCompletionNode) {
0564: String text = handleMarkedUpText(onCompletionNode,
0565: onCompletionNode, IParserTags.ON_COMPLETION);
0566: item.setCompletionMessage(text);
0567: }
0568:
0569: private void handleIntroNode(CheatSheet cheatSheet, Node introNode)
0570: throws CheatSheetParserException {
0571: Item introItem = new Item();
0572: introItem.setTitle(Messages.CHEAT_SHEET_INTRO_TITLE);
0573:
0574: handleIntroAttributes(introItem, introNode);
0575:
0576: boolean hasDescription = false;
0577:
0578: NodeList nodes = introNode.getChildNodes();
0579: for (int i = 0; i < nodes.getLength(); i++) {
0580: Node node = nodes.item(i);
0581:
0582: if (node.getNodeName().equals(IParserTags.DESCRIPTION)) {
0583: if (hasDescription) {
0584: String message = NLS
0585: .bind(
0586: Messages.ERROR_PARSING_MULTIPLE_DESCRIPTION,
0587: (new Object[] { introNode
0588: .getNodeName() }));
0589: addStatus(IStatus.ERROR, message, null);
0590: } else {
0591: hasDescription = true;
0592: handleDescription(introItem, node);
0593: }
0594: } else {
0595: if (node.getNodeType() != Node.TEXT_NODE
0596: && node.getNodeType() != Node.COMMENT_NODE) {
0597: String message = NLS.bind(
0598: Messages.WARNING_PARSING_UNKNOWN_ELEMENT,
0599: (new Object[] { node.getNodeName(),
0600: introNode.getNodeName() }));
0601: addStatus(IStatus.WARNING, message, null);
0602: }
0603: }
0604: }
0605:
0606: if (!hasDescription) {
0607: String message = NLS.bind(
0608: Messages.ERROR_PARSING_NO_DESCRIPTION,
0609: (new Object[] { introNode.getNodeName() }));
0610: addStatus(IStatus.ERROR, message, null);
0611: }
0612:
0613: cheatSheet.setIntroItem(introItem);
0614: }
0615:
0616: private void handleIntroAttributes(Item item, Node introNode) {
0617: Assert.isNotNull(item);
0618: Assert.isNotNull(introNode);
0619:
0620: NamedNodeMap attributes = introNode.getAttributes();
0621: if (attributes != null) {
0622: for (int x = 0; x < attributes.getLength(); x++) {
0623: Node attribute = attributes.item(x);
0624: String attributeName = attribute.getNodeName();
0625: if (attribute == null || attributeName == null)
0626: continue;
0627:
0628: if (attributeName.equals(IParserTags.CONTEXTID)) {
0629: item.setContextId(attribute.getNodeValue());
0630: } else if (attributeName.equals(IParserTags.HREF)) {
0631: item.setHref(attribute.getNodeValue());
0632: } else {
0633: String message = NLS.bind(
0634: Messages.WARNING_PARSING_UNKNOWN_ATTRIBUTE,
0635: (new Object[] { attributeName,
0636: introNode.getNodeName() }));
0637: addStatus(IStatus.WARNING, message, null);
0638: }
0639: }
0640: }
0641: }
0642:
0643: private Item handleItem(Node itemNode)
0644: throws CheatSheetParserException {
0645: Assert.isNotNull(itemNode);
0646: Assert.isTrue(itemNode.getNodeName().equals(IParserTags.ITEM));
0647:
0648: Item item = new Item();
0649:
0650: handleItemAttributes(item, itemNode);
0651:
0652: boolean hasDescription = false;
0653:
0654: NodeList nodes = itemNode.getChildNodes();
0655:
0656: IncompatibleSiblingChecker checker = new IncompatibleSiblingChecker(
0657: this , itemNode);
0658: for (int i = 0; i < nodes.getLength(); i++) {
0659: Node node = nodes.item(i);
0660: checker.checkElement(node.getNodeName());
0661: if (node.getNodeName().equals(IParserTags.ACTION)) {
0662: handleExecutable(item, node, new Action());
0663: } else if (node.getNodeName().equals(IParserTags.COMMAND)) {
0664: handleExecutable(item, node, new CheatSheetCommand());
0665: } else if (node.getNodeName().equals(
0666: IParserTags.DESCRIPTION)) {
0667: if (hasDescription) {
0668: String message = NLS
0669: .bind(
0670: Messages.ERROR_PARSING_MULTIPLE_DESCRIPTION,
0671: (new Object[] { itemNode
0672: .getNodeName() }));
0673: addStatus(IStatus.ERROR, message, null);
0674: } else {
0675: hasDescription = true;
0676: handleDescription(item, node);
0677: }
0678: } else if (node.getNodeName().equals(
0679: IParserTags.ON_COMPLETION)) {
0680: handleOnCompletion(item, node);
0681: } else if (node.getNodeName().equals(IParserTags.SUBITEM)) {
0682: handleSubItem(item, node);
0683: } else if (node.getNodeName().equals(
0684: IParserTags.CONDITIONALSUBITEM)) {
0685: handleConditionalSubItem(item, node);
0686: } else if (node.getNodeName().equals(
0687: IParserTags.REPEATEDSUBITM)) {
0688: handleRepeatedSubItem(item, node);
0689: } else if (node.getNodeName().equals(
0690: IParserTags.PERFORMWHEN)) {
0691: handlePerformWhen(item, node);
0692: } else {
0693: if (node.getNodeType() != Node.TEXT_NODE
0694: && node.getNodeType() != Node.COMMENT_NODE) {
0695: String message = NLS.bind(
0696: Messages.WARNING_PARSING_UNKNOWN_ELEMENT,
0697: (new Object[] { node.getNodeName(),
0698: itemNode.getNodeName() }));
0699: addStatus(IStatus.WARNING, message, null);
0700: }
0701: }
0702: }
0703:
0704: if (!hasDescription) {
0705: String message = NLS.bind(
0706: Messages.ERROR_PARSING_NO_DESCRIPTION,
0707: (new Object[] { itemNode.getNodeName() }));
0708: addStatus(IStatus.ERROR, message, null);
0709: }
0710:
0711: return item;
0712: }
0713:
0714: private void handleItemAttributes(Item item, Node itemNode)
0715: throws CheatSheetParserException {
0716: Assert.isNotNull(item);
0717: Assert.isNotNull(itemNode);
0718:
0719: ArrayList itemExtensionElements = new ArrayList();
0720:
0721: boolean title = false;
0722:
0723: NamedNodeMap attributes = itemNode.getAttributes();
0724: if (attributes != null) {
0725: for (int x = 0; x < attributes.getLength(); x++) {
0726: Node attribute = attributes.item(x);
0727: String attributeName = attribute.getNodeName();
0728: if (attribute == null || attributeName == null)
0729: continue;
0730:
0731: if (attributeName.equals(IParserTags.TITLE)) {
0732: title = true;
0733: item.setTitle(attribute.getNodeValue());
0734: } else if (attributeName.equals(IParserTags.CONTEXTID)) {
0735: item.setContextId(attribute.getNodeValue());
0736: } else if (attributeName.equals(IParserTags.HREF)) {
0737: item.setHref(attribute.getNodeValue());
0738: } else if (attributeName.equals(IParserTags.SKIP)) {
0739: item.setSkip(attribute.getNodeValue().equals(
0740: TRUE_STRING));
0741: } else if (attributeName.equals(IParserTags.DIALOG)) {
0742: item.setDialog(attribute.getNodeValue().equals(
0743: TRUE_STRING));
0744: } else {
0745: AbstractItemExtensionElement[] ie = handleUnknownItemAttribute(
0746: attribute, itemNode);
0747: if (ie != null) {
0748: itemExtensionElements.add(ie);
0749: } else {
0750: String message = NLS
0751: .bind(
0752: Messages.WARNING_PARSING_UNKNOWN_ATTRIBUTE,
0753: (new Object[] { attributeName,
0754: itemNode.getNodeName() }));
0755: addStatus(IStatus.WARNING, message, null);
0756: }
0757: }
0758: }
0759: }
0760:
0761: if (!title) {
0762: String message = NLS.bind(Messages.ERROR_PARSING_NO_TITLE,
0763: (new Object[] { itemNode.getNodeName() }));
0764: throw new CheatSheetParserException(message);
0765: }
0766:
0767: if (itemExtensionElements != null)
0768: item.setItemExtensions(itemExtensionElements);
0769: }
0770:
0771: private void handlePerformWhen(IPerformWhenItem item,
0772: Node performWhenNode) throws CheatSheetParserException {
0773: Assert.isNotNull(item);
0774: Assert.isNotNull(performWhenNode);
0775: Assert.isTrue(performWhenNode.getNodeName().equals(
0776: IParserTags.PERFORMWHEN));
0777:
0778: PerformWhen performWhen = new PerformWhen();
0779:
0780: boolean condition = false;
0781:
0782: // Handle attributes
0783: NamedNodeMap attributes = performWhenNode.getAttributes();
0784: if (attributes != null) {
0785: for (int x = 0; x < attributes.getLength(); x++) {
0786: Node attribute = attributes.item(x);
0787: String attributeName = attribute.getNodeName();
0788: if (attribute == null || attributeName == null)
0789: continue;
0790:
0791: if (attributeName.equals(IParserTags.CONDITION)) {
0792: condition = true;
0793: performWhen.setCondition(attribute.getNodeValue());
0794: } else {
0795: String message = NLS.bind(
0796: Messages.WARNING_PARSING_UNKNOWN_ATTRIBUTE,
0797: (new Object[] { attributeName,
0798: performWhenNode.getNodeName() }));
0799: addStatus(IStatus.WARNING, message, null);
0800: }
0801: }
0802: }
0803:
0804: if (!condition) {
0805: String message = NLS.bind(
0806: Messages.ERROR_PARSING_NO_CONDITION,
0807: (new Object[] { performWhenNode.getNodeName() }));
0808: throw new CheatSheetParserException(message);
0809: }
0810:
0811: boolean exeutable = false;
0812:
0813: // Handle nodes
0814: NodeList nodes = performWhenNode.getChildNodes();
0815: for (int i = 0; i < nodes.getLength(); i++) {
0816: Node node = nodes.item(i);
0817: if (node.getNodeName().equals(IParserTags.ACTION)) {
0818: exeutable = true;
0819: handleExecutable(performWhen, node, new Action());
0820: } else if (node.getNodeName().equals(IParserTags.COMMAND)) {
0821: exeutable = true;
0822: handleExecutable(performWhen, node,
0823: new CheatSheetCommand());
0824: } else {
0825: if (node.getNodeType() != Node.TEXT_NODE
0826: && node.getNodeType() != Node.COMMENT_NODE) {
0827: String message = NLS.bind(
0828: Messages.WARNING_PARSING_UNKNOWN_ELEMENT,
0829: (new Object[] { node.getNodeName(),
0830: performWhenNode.getNodeName() }));
0831: addStatus(IStatus.WARNING, message, null);
0832: }
0833: }
0834: }
0835:
0836: if (!exeutable) {
0837: String message = NLS.bind(Messages.ERROR_PARSING_NO_ACTION,
0838: (new Object[] { performWhenNode.getNodeName() }));
0839: throw new CheatSheetParserException(message);
0840: }
0841:
0842: item.setPerformWhen(performWhen);
0843: }
0844:
0845: private void handleRepeatedSubItem(Item item,
0846: Node repeatedSubItemNode) throws CheatSheetParserException {
0847: Assert.isNotNull(item);
0848: Assert.isNotNull(repeatedSubItemNode);
0849: Assert.isTrue(repeatedSubItemNode.getNodeName().equals(
0850: IParserTags.REPEATEDSUBITM));
0851:
0852: RepeatedSubItem repeatedSubItem = new RepeatedSubItem();
0853:
0854: boolean values = false;
0855:
0856: // Handle attributes
0857: NamedNodeMap attributes = repeatedSubItemNode.getAttributes();
0858: if (attributes != null) {
0859: for (int x = 0; x < attributes.getLength(); x++) {
0860: Node attribute = attributes.item(x);
0861: String attributeName = attribute.getNodeName();
0862: if (attribute == null || attributeName == null)
0863: continue;
0864:
0865: if (attributeName.equals(IParserTags.VALUES)) {
0866: values = true;
0867: repeatedSubItem.setValues(attribute.getNodeValue());
0868: } else {
0869: String message = NLS
0870: .bind(
0871: Messages.WARNING_PARSING_UNKNOWN_ATTRIBUTE,
0872: (new Object[] {
0873: attributeName,
0874: repeatedSubItemNode
0875: .getNodeName() }));
0876: addStatus(IStatus.WARNING, message, null);
0877: }
0878: }
0879: }
0880:
0881: if (!values) {
0882: String message = NLS
0883: .bind(Messages.ERROR_PARSING_NO_VALUES,
0884: (new Object[] { repeatedSubItemNode
0885: .getNodeName() }));
0886: throw new CheatSheetParserException(message);
0887: }
0888:
0889: boolean subitem = false;
0890:
0891: // Handle nodes
0892: NodeList nodes = repeatedSubItemNode.getChildNodes();
0893: for (int i = 0; i < nodes.getLength(); i++) {
0894: Node node = nodes.item(i);
0895:
0896: if (node.getNodeName().equals(IParserTags.SUBITEM)) {
0897: subitem = true;
0898: handleSubItem(repeatedSubItem, node);
0899: } else {
0900: if (node.getNodeType() != Node.TEXT_NODE
0901: && node.getNodeType() != Node.COMMENT_NODE) {
0902: String message = NLS
0903: .bind(
0904: Messages.WARNING_PARSING_UNKNOWN_ELEMENT,
0905: (new Object[] {
0906: node.getNodeName(),
0907: repeatedSubItemNode
0908: .getNodeName() }));
0909: addStatus(IStatus.WARNING, message, null);
0910: }
0911: }
0912: }
0913:
0914: if (!subitem) {
0915: String message = NLS
0916: .bind(Messages.ERROR_PARSING_NO_SUBITEM,
0917: (new Object[] { repeatedSubItemNode
0918: .getNodeName() }));
0919: throw new CheatSheetParserException(message);
0920: }
0921:
0922: item.addSubItem(repeatedSubItem);
0923: }
0924:
0925: private void handleSubItem(ISubItemItem item, Node subItemNode)
0926: throws CheatSheetParserException {
0927: Assert.isNotNull(item);
0928: Assert.isNotNull(subItemNode);
0929: Assert.isTrue(subItemNode.getNodeName().equals(
0930: IParserTags.SUBITEM));
0931:
0932: SubItem subItem = new SubItem();
0933:
0934: handleSubItemAttributes(subItem, subItemNode);
0935:
0936: IncompatibleSiblingChecker checker = new IncompatibleSiblingChecker(
0937: this , subItemNode);
0938:
0939: NodeList nodes = subItemNode.getChildNodes();
0940: for (int i = 0; i < nodes.getLength(); i++) {
0941: Node node = nodes.item(i);
0942: checker.checkElement(node.getNodeName());
0943:
0944: if (node.getNodeName().equals(IParserTags.ACTION)) {
0945: handleExecutable(subItem, node, new Action());
0946: } else if (node.getNodeName().equals(IParserTags.COMMAND)) {
0947: handleExecutable(subItem, node, new CheatSheetCommand());
0948: } else if (node.getNodeName().equals(
0949: IParserTags.PERFORMWHEN)) {
0950: handlePerformWhen(subItem, node);
0951: } else {
0952: if (node.getNodeType() != Node.TEXT_NODE
0953: && node.getNodeType() != Node.COMMENT_NODE) {
0954: String message = NLS.bind(
0955: Messages.WARNING_PARSING_UNKNOWN_ELEMENT,
0956: (new Object[] { node.getNodeName(),
0957: subItemNode.getNodeName() }));
0958: addStatus(IStatus.WARNING, message, null);
0959: }
0960: }
0961: }
0962: item.addSubItem(subItem);
0963: }
0964:
0965: private void handleSubItemAttributes(SubItem subItem,
0966: Node subItemNode) throws CheatSheetParserException {
0967: Assert.isNotNull(subItem);
0968: Assert.isNotNull(subItemNode);
0969:
0970: boolean label = false;
0971:
0972: NamedNodeMap attributes = subItemNode.getAttributes();
0973: if (attributes != null) {
0974: for (int x = 0; x < attributes.getLength(); x++) {
0975: Node attribute = attributes.item(x);
0976: String attributeName = attribute.getNodeName();
0977: if (attribute == null || attributeName == null)
0978: continue;
0979:
0980: if (attributeName.equals(IParserTags.LABEL)) {
0981: label = true;
0982: subItem.setLabel(attribute.getNodeValue());
0983: } else if (attributeName.equals(IParserTags.SKIP)) {
0984: subItem.setSkip(attribute.getNodeValue().equals(
0985: TRUE_STRING));
0986: } else if (attributeName.equals(IParserTags.WHEN)) {
0987: subItem.setWhen(attribute.getNodeValue());
0988: } else {
0989: String message = NLS.bind(
0990: Messages.WARNING_PARSING_UNKNOWN_ATTRIBUTE,
0991: (new Object[] { attributeName,
0992: subItemNode.getNodeName() }));
0993: addStatus(IStatus.WARNING, message, null);
0994: }
0995: }
0996: }
0997:
0998: if (!label) {
0999: String message = NLS.bind(Messages.ERROR_PARSING_NO_LABEL,
1000: (new Object[] { subItemNode.getNodeName() }));
1001: throw new CheatSheetParserException(message);
1002: }
1003: }
1004:
1005: private AbstractItemExtensionElement[] handleUnknownItemAttribute(
1006: Node item, Node node) {
1007: ArrayList al = new ArrayList();
1008: if (itemExtensionContainerList == null)
1009: return null;
1010:
1011: for (int i = 0; i < itemExtensionContainerList.size(); i++) {
1012: CheatSheetItemExtensionElement itemExtensionElement = (CheatSheetItemExtensionElement) itemExtensionContainerList
1013: .get(i);
1014:
1015: if (itemExtensionElement.getItemAttribute().equals(
1016: item.getNodeName())) {
1017: AbstractItemExtensionElement itemElement = itemExtensionElement
1018: .createInstance();
1019: if (itemElement != null) {
1020: itemElement.handleAttribute(item.getNodeValue());
1021: al.add(itemElement);
1022: }
1023: }
1024: }
1025:
1026: if (al.size() == 0) {
1027: String message = NLS.bind(
1028: Messages.WARNING_PARSING_UNKNOWN_ATTRIBUTE,
1029: (new Object[] { item.getNodeName(),
1030: node.getNodeName() }));
1031: addStatus(IStatus.WARNING, message, null);
1032: }
1033: return (AbstractItemExtensionElement[]) al
1034: .toArray(new AbstractItemExtensionElement[al.size()]);
1035: }
1036:
1037: public ICheatSheet parse(URL url, String pluginId,
1038: int cheatSheetKind) {
1039: return parse(new ParserInput(url, pluginId), cheatSheetKind);
1040: }
1041:
1042: public ICheatSheet parse(ParserInput input, int cheatSheetKind) {
1043: status = Status.OK_STATUS;
1044: commandCount = 0;
1045: actionCount = 0;
1046: if (input == null) {
1047: return null;
1048: }
1049:
1050: InputStream is = null;
1051: InputSource inputSource = null;
1052: String filename = ""; //$NON-NLS-1$
1053: URL url = input.getUrl();
1054:
1055: if (input.getXml() != null) {
1056: StringReader reader = new StringReader(input.getXml());
1057: inputSource = new InputSource(reader);
1058: } else if (input.getUrl() != null) {
1059: try {
1060: is = url.openStream();
1061:
1062: if (is != null) {
1063: inputSource = new InputSource(is);
1064: }
1065: } catch (Exception e) {
1066: String message = NLS.bind(Messages.ERROR_OPENING_FILE,
1067: (new Object[] { url.getFile() }));
1068: addStatus(IStatus.ERROR, message, e);
1069: return null;
1070: }
1071: } else {
1072: return null;
1073: }
1074:
1075: if (input.getUrl() != null) {
1076: filename = url.getFile();
1077: }
1078:
1079: Document document;
1080: try {
1081: if (documentBuilder == null) {
1082: addStatus(IStatus.ERROR,
1083: Messages.ERROR_DOCUMENT_BUILDER_NOT_INIT, null);
1084: return null;
1085: }
1086: document = documentBuilder.parse(inputSource);
1087: } catch (IOException e) {
1088: String message = NLS.bind(
1089: Messages.ERROR_OPENING_FILE_IN_PARSER,
1090: (new Object[] { filename }));
1091: addStatus(IStatus.ERROR, message, e);
1092: return null;
1093: } catch (SAXParseException spe) {
1094: String message = NLS.bind(
1095: Messages.ERROR_SAX_PARSING_WITH_LOCATION,
1096: (new Object[] { filename,
1097: new Integer(spe.getLineNumber()),
1098: new Integer(spe.getColumnNumber()) }));
1099: addStatus(IStatus.ERROR, message, spe);
1100: return null;
1101: } catch (SAXException se) {
1102: String message = NLS.bind(Messages.ERROR_SAX_PARSING,
1103: (new Object[] { filename }));
1104: addStatus(IStatus.ERROR, message, se);
1105: return null;
1106: } finally {
1107: try {
1108: is.close();
1109: } catch (Exception e) {
1110: }
1111: }
1112:
1113: // process dynamic content, normalize paths
1114: if (processor == null) {
1115: DocumentReader reader = new DocumentReader();
1116: processor = new DocumentProcessor(new ProcessorHandler[] {
1117: new FilterHandler(CheatSheetEvaluationContext
1118: .getContext()), new NormalizeHandler(),
1119: new IncludeHandler(reader, Platform.getNL()),
1120: new ExtensionHandler(reader, Platform.getNL()) });
1121: }
1122: String documentPath = null;
1123: if (input.getPluginId() != null) {
1124: documentPath = '/' + input.getPluginId()
1125: + input.getUrl().getPath();
1126: }
1127: processor.process(UAElementFactory.newElement(document
1128: .getDocumentElement()), documentPath);
1129:
1130: if (cheatSheetKind == COMPOSITE_ONLY
1131: || (cheatSheetKind == ANY && isComposite(document))) {
1132: CompositeCheatSheetParser compositeParser = new CompositeCheatSheetParser();
1133: CompositeCheatSheetModel result = compositeParser
1134: .parseCompositeCheatSheet(document, input.getUrl());
1135: status = compositeParser.getStatus();
1136: return result;
1137: }
1138: try {
1139: return parseCheatSheet(document);
1140: } catch (CheatSheetParserException e) {
1141: addStatus(IStatus.ERROR, e.getMessage(), e);
1142: }
1143: return null;
1144: }
1145:
1146: private boolean isComposite(Document document) {
1147: if (document != null) {
1148: Node rootnode = document.getDocumentElement();
1149: // Is the root node compositeCheatsheet?
1150: return rootnode.getNodeName().equals(
1151: ICompositeCheatsheetTags.COMPOSITE_CHEATSHEET);
1152: }
1153: return false;
1154: }
1155:
1156: private CheatSheet parseCheatSheet(Document document)
1157: throws CheatSheetParserException {
1158: // If the document passed is null return a null tree and update the status
1159: if (document != null) {
1160: Node rootnode = document.getDocumentElement();
1161:
1162: // Is the root node really <cheatsheet>?
1163: if (!rootnode.getNodeName().equals(IParserTags.CHEATSHEET)) {
1164: throw new CheatSheetParserException(
1165: Messages.ERROR_PARSING_CHEATSHEET_ELEMENT);
1166: }
1167:
1168: // Create the cheat sheet model object
1169: CheatSheet cheatSheet = new CheatSheet();
1170:
1171: handleCheatSheetAttributes(cheatSheet, rootnode);
1172:
1173: boolean hasItem = false;
1174: boolean hasIntro = false;
1175:
1176: CheatSheetRegistryReader reader = CheatSheetRegistryReader
1177: .getInstance();
1178: itemExtensionContainerList = reader.readItemExtensions();
1179:
1180: NodeList nodes = rootnode.getChildNodes();
1181: for (int i = 0; i < nodes.getLength(); i++) {
1182: Node node = nodes.item(i);
1183:
1184: if (node.getNodeName().equals(IParserTags.ITEM)) {
1185: hasItem = true;
1186: Item item = handleItem(node);
1187: cheatSheet.addItem(item);
1188: } else if (node.getNodeName().equals(IParserTags.INTRO)) {
1189: if (hasIntro) {
1190: addStatus(
1191: IStatus.ERROR,
1192: Messages.ERROR_PARSING_MORE_THAN_ONE_INTRO,
1193: null);
1194: } else {
1195: hasIntro = true;
1196: handleIntroNode(cheatSheet, node);
1197: }
1198: } else {
1199: if (node.getNodeType() != Node.TEXT_NODE
1200: && node.getNodeType() != Node.COMMENT_NODE) {
1201: String message = NLS
1202: .bind(
1203: Messages.WARNING_PARSING_UNKNOWN_ELEMENT,
1204: (new Object[] {
1205: node.getNodeName(),
1206: rootnode.getNodeName() }));
1207: addStatus(IStatus.WARNING, message, null);
1208: }
1209: }
1210: }
1211:
1212: if (!hasIntro) {
1213: addStatus(IStatus.ERROR,
1214: Messages.ERROR_PARSING_NO_INTRO, null);
1215: }
1216: if (!hasItem) {
1217: addStatus(IStatus.ERROR,
1218: Messages.ERROR_PARSING_NO_ITEM, null);
1219: }
1220:
1221: //handleIntro(cheatSheet, document);
1222:
1223: //handleItems(cheatSheet, document);
1224:
1225: if (status.getSeverity() == IStatus.ERROR) {
1226: return null;
1227: }
1228:
1229: cheatSheet.setContainsCommandOrAction(actionCount != 0
1230: || commandCount != 0);
1231: return cheatSheet;
1232: }
1233: throw new CheatSheetParserException(
1234: Messages.ERROR_PARSING_CHEATSHEET_CONTENTS);
1235: }
1236:
1237: /*
1238: * Normalizes composite cheat sheet-relative paths to simple cheat sheets into fully
1239: * qualified paths, e.g. for the path "tasks/mySimpleCheatSheet.xml" in composite cheat
1240: * sheet "/my.plugin/cheatsheets/myCompositeCheatSheet.xml", this normalizes to
1241: * "/my.plugin/cheatsheets/tasks/mySimpleCheatSheet.xml".
1242: *
1243: * This is necessary because with dynamic content we are pulling in tasks from other
1244: * plug-ins and those tasks have relative paths. It also only applies for cheat sheets
1245: * located in running plug-ins.
1246: */
1247: private class NormalizeHandler extends ProcessorHandler {
1248:
1249: private static final String ELEMENT_PARAM = "param"; //$NON-NLS-1$
1250: private static final String ATTRIBUTE_NAME = "name"; //$NON-NLS-1$
1251: private static final String ATTRIBUTE_VALUE = "value"; //$NON-NLS-1$
1252: private static final String NAME_PATH = "path"; //$NON-NLS-1$
1253:
1254: public short handle(UAElement element, String id) {
1255: if (id != null
1256: && ELEMENT_PARAM.equals(element.getElementName())) {
1257: String name = element.getAttribute(ATTRIBUTE_NAME);
1258: if (NAME_PATH.equals(name)) {
1259: String value = element
1260: .getAttribute(ATTRIBUTE_VALUE);
1261: if (value != null) {
1262: int index = id.lastIndexOf('/');
1263: element.setAttribute(ATTRIBUTE_VALUE, id
1264: .substring(0, index + 1)
1265: + value);
1266: }
1267: }
1268: return HANDLED_CONTINUE;
1269: }
1270: return UNHANDLED;
1271: }
1272: }
1273: }
|