001: /*******************************************************************************
002: * Copyright (c) 2005, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.internal.cheatsheets.composite.parser;
011:
012: import java.io.IOException;
013: import java.io.InputStream;
014: import java.net.URL;
015:
016: import javax.xml.parsers.DocumentBuilder;
017: import javax.xml.parsers.DocumentBuilderFactory;
018:
019: import org.eclipse.core.runtime.IStatus;
020: import org.eclipse.core.runtime.Status;
021: import org.eclipse.osgi.util.NLS;
022: import org.eclipse.ui.internal.cheatsheets.Messages;
023: import org.eclipse.ui.internal.cheatsheets.composite.model.AbstractTask;
024: import org.eclipse.ui.internal.cheatsheets.composite.model.CompositeCheatSheetModel;
025: import org.eclipse.ui.internal.cheatsheets.composite.model.EditableTask;
026: import org.eclipse.ui.internal.cheatsheets.composite.model.TaskGroup;
027: import org.eclipse.ui.internal.cheatsheets.data.CheatSheetParserException;
028: import org.eclipse.ui.internal.cheatsheets.data.IParserTags;
029: import org.eclipse.ui.internal.cheatsheets.data.ParserStatusUtility;
030: import org.w3c.dom.Document;
031: import org.w3c.dom.NamedNodeMap;
032: import org.w3c.dom.Node;
033: import org.w3c.dom.NodeList;
034: import org.xml.sax.InputSource;
035: import org.xml.sax.SAXException;
036: import org.xml.sax.SAXParseException;
037:
038: public class CompositeCheatSheetParser implements IStatusContainer {
039:
040: private DocumentBuilder documentBuilder;
041:
042: private IStatus status;
043:
044: private int nextTaskId = 0;
045:
046: /**
047: * Gets the status of the last call to parseGuide
048: */
049:
050: public IStatus getStatus() {
051: return status;
052: }
053:
054: /**
055: * Returns the DocumentBuilder to be used by composite cheat sheets.
056: */
057: public DocumentBuilder getDocumentBuilder() {
058: if (documentBuilder == null) {
059: try {
060: documentBuilder = DocumentBuilderFactory.newInstance()
061: .newDocumentBuilder();
062: } catch (Exception e) {
063: addStatus(IStatus.ERROR,
064: Messages.ERROR_CREATING_DOCUMENT_BUILDER, e);
065: }
066: }
067: return documentBuilder;
068: }
069:
070: public void addStatus(int severity, String message,
071: Throwable exception) {
072: status = ParserStatusUtility.addStatus(status, severity,
073: message, exception);
074: }
075:
076: /**
077: * Parse a composite cheat sheet from a url. The parser status will be set as a result
078: * of this operation, if the status is IStatus.ERROR the parser returns null
079: * @param url The url of the input
080: * @return A valid composite cheat sheet or null if there was an error
081: */
082: public CompositeCheatSheetModel parseGuide(URL url) {
083: status = Status.OK_STATUS;
084: if (url == null) {
085: String message = NLS.bind(Messages.ERROR_OPENING_FILE,
086: (new Object[] { "" })); //$NON-NLS-1$
087: addStatus(IStatus.ERROR, message, null);
088: return null;
089: }
090:
091: InputStream is = null;
092:
093: try {
094: is = url.openStream();
095:
096: if (is == null) {
097: String message = NLS.bind(Messages.ERROR_OPENING_FILE,
098: (new Object[] { url.getFile() }));
099: addStatus(IStatus.ERROR, message, null);
100: return null;
101: }
102: } catch (Exception e) {
103: String message = NLS.bind(Messages.ERROR_OPENING_FILE,
104: (new Object[] { url.getFile() }));
105: addStatus(IStatus.ERROR, message, e);
106: return null;
107: }
108:
109: Document document;
110: String filename = url.getFile();
111: try {
112: InputSource inputSource = new InputSource(is);
113: document = getDocumentBuilder().parse(inputSource);
114: } catch (IOException e) {
115: String message = NLS.bind(
116: Messages.ERROR_OPENING_FILE_IN_PARSER,
117: (new Object[] { filename }));
118: addStatus(IStatus.ERROR, message, e);
119: return null;
120: } catch (SAXParseException spe) {
121: String message = NLS.bind(
122: Messages.ERROR_SAX_PARSING_WITH_LOCATION,
123: (new Object[] { filename,
124: new Integer(spe.getLineNumber()),
125: new Integer(spe.getColumnNumber()) }));
126: addStatus(IStatus.ERROR, message, spe);
127: return null;
128: } catch (SAXException se) {
129: String message = NLS.bind(Messages.ERROR_SAX_PARSING,
130: (new Object[] { filename }));
131: addStatus(IStatus.ERROR, message, se);
132: return null;
133: } finally {
134: try {
135: is.close();
136: } catch (Exception e) {
137: }
138: }
139:
140: CompositeCheatSheetModel result = parseCompositeCheatSheet(
141: document, url);
142: return result;
143: }
144:
145: /**
146: * Parse a composite cheatsheet. The parser status will be set as a result
147: * of this operation, if the status is IStatus.ERROR the parser returns null
148: * @param url The url of the input. This is only used so the model can record
149: * its input location
150: * @param document the document to be parse
151: * @return A valid composite cheat sheet or null if there was an error
152: * @return
153: */
154: public CompositeCheatSheetModel parseCompositeCheatSheet(
155: Document document, URL url) {
156: status = Status.OK_STATUS;
157: try {
158: // If the document passed is null return a null tree and update the status
159: if (document != null) {
160: Node rootnode = document.getDocumentElement();
161:
162: // Is the root node correct?
163: if (!rootnode.getNodeName().equals(
164: ICompositeCheatsheetTags.COMPOSITE_CHEATSHEET)) {
165: String message = NLS
166: .bind(
167: Messages.ERROR_PARSING_ROOT_NODE_TYPE,
168: (new Object[] { ICompositeCheatsheetTags.COMPOSITE_CHEATSHEET }));
169: throw new CheatSheetParserException(message);
170: }
171:
172: String name = ""; //$NON-NLS-1$
173: boolean nameFound = false;
174: String explorerId = ICompositeCheatsheetTags.TREE;
175:
176: NamedNodeMap attributes = rootnode.getAttributes();
177: if (attributes != null) {
178: for (int x = 0; x < attributes.getLength(); x++) {
179: Node attribute = attributes.item(x);
180: String attributeName = attribute.getNodeName();
181: if (attributeName != null
182: && attributeName
183: .equals(ICompositeCheatsheetTags.NAME)) {
184: nameFound = true;
185: name = attribute.getNodeValue();
186: }
187: if (attributeName
188: .equals(ICompositeCheatsheetTags.EXPLORER)) {
189: explorerId = attribute.getNodeValue();
190: }
191: }
192: }
193: CompositeCheatSheetModel compositeCS = new CompositeCheatSheetModel(
194: name, name, explorerId);
195:
196: parseCompositeCheatSheetChildren(rootnode, compositeCS);
197:
198: compositeCS.getDependencies().resolveDependencies(this );
199:
200: if (compositeCS.getRootTask() == null) {
201: addStatus(IStatus.ERROR,
202: Messages.ERROR_PARSING_NO_ROOT, null);
203: }
204: if (!nameFound) {
205: addStatus(IStatus.ERROR,
206: Messages.ERROR_PARSING_CCS_NO_NAME, null);
207: }
208: if (status.getSeverity() != IStatus.ERROR) {
209: compositeCS.setContentUrl(url);
210: return compositeCS;
211: }
212: }
213: return null;
214: } catch (CheatSheetParserException e) {
215: addStatus(IStatus.ERROR, e.getMessage(), null);
216: return null;
217: }
218: }
219:
220: private void parseCompositeCheatSheetChildren(Node compositeCSNode,
221: CompositeCheatSheetModel model) {
222: nextTaskId = 0;
223: NodeList childNodes = compositeCSNode.getChildNodes();
224: for (int index = 0; index < childNodes.getLength(); index++) {
225: Node nextNode = childNodes.item(index);
226: if (isAbstractTask(nextNode.getNodeName())) {
227: AbstractTask task = parseAbstractTask(nextNode, model);
228: if (model.getRootTask() == null) {
229: model.setRootTask(task);
230: parseTaskChildren(nextNode, task, model);
231: } else {
232: addStatus(IStatus.ERROR,
233: Messages.ERROR_PARSING_MULTIPLE_ROOT, null);
234: }
235: }
236: }
237: }
238:
239: public static boolean isAbstractTask(String nodeName) {
240: return nodeName == ICompositeCheatsheetTags.TASK
241: || nodeName == ICompositeCheatsheetTags.TASK_GROUP;
242: }
243:
244: private void parseTaskChildren(Node parentNode,
245: AbstractTask parentTask, CompositeCheatSheetModel model) {
246: NodeList childNodes = parentNode.getChildNodes();
247: ITaskParseStrategy strategy = parentTask.getParserStrategy();
248: strategy.init();
249: for (int index = 0; index < childNodes.getLength(); index++) {
250: Node childNode = childNodes.item(index);
251: if (childNode.getNodeType() == Node.ELEMENT_NODE) {
252: String nodeName = childNode.getNodeName();
253: if (nodeName == IParserTags.PARAM) {
254: addParameter(parentTask, childNode.getAttributes());
255: } else if (nodeName == IParserTags.INTRO) {
256: parentTask.setDescription(MarkupParser
257: .parseAndTrimTextMarkup(childNode));
258: } else if (nodeName == ICompositeCheatsheetTags.ON_COMPLETION) {
259: parentTask.setCompletionMessage(MarkupParser
260: .parseAndTrimTextMarkup(childNode));
261: } else if (nodeName == ICompositeCheatsheetTags.DEPENDS_ON) {
262: parseDependency(childNode, parentTask, model);
263: } else if (CompositeCheatSheetParser
264: .isAbstractTask(nodeName)) {
265: if (parentTask instanceof TaskGroup) {
266: AbstractTask task = parseAbstractTask(
267: childNode, model);
268: ((TaskGroup) parentTask).addSubtask(task);
269: parseTaskChildren(childNode, task, model);
270: }
271: } else {
272: if (!strategy.parseElementNode(childNode,
273: parentNode, parentTask, this )) {
274: String message = NLS
275: .bind(
276: Messages.WARNING_PARSING_UNKNOWN_ELEMENT,
277: (new Object[] {
278: nodeName,
279: parentNode
280: .getNodeName() }));
281: addStatus(IStatus.WARNING, message, null);
282: }
283: }
284: }
285: }
286: // Check for missing attributes and add dependencies if this was a sequence
287: strategy.parsingComplete(parentTask, this );
288: }
289:
290: private void parseDependency(Node taskNode, AbstractTask task,
291: CompositeCheatSheetModel model) {
292: NamedNodeMap attributes = taskNode.getAttributes();
293: if (attributes != null) {
294: Node taskAttribute = attributes
295: .getNamedItem(ICompositeCheatsheetTags.TASK);
296: if (taskAttribute != null) {
297: String requiredTaskId = taskAttribute.getNodeValue();
298: model.getDependencies().addDependency(task,
299: requiredTaskId);
300: } else {
301: addStatus(IStatus.ERROR, Messages.ERROR_PARSING_NO_ID,
302: null);
303: }
304: }
305: }
306:
307: private void addParameter(AbstractTask parentTask,
308: NamedNodeMap attributes) {
309: String name = null;
310: String value = null;
311:
312: if (attributes != null) {
313: for (int x = 0; x < attributes.getLength(); x++) {
314: Node attribute = attributes.item(x);
315: String attributeName = attribute.getNodeName();
316: if (attribute == null || attributeName == null)
317: continue;
318: if (attributeName.equals(ICompositeCheatsheetTags.NAME)) {
319: name = attribute.getNodeValue();
320: }
321: if (attributeName
322: .equals(ICompositeCheatsheetTags.VALUE)) {
323: value = attribute.getNodeValue();
324: }
325: }
326: }
327: if (name == null) {
328: addStatus(IStatus.WARNING, Messages.ERROR_PARSING_NO_NAME,
329: null);
330: return;
331: } else if (value == null) {
332: addStatus(IStatus.WARNING, Messages.ERROR_PARSING_NO_VALUE,
333: null);
334: return;
335: } else {
336: parentTask.getParameters().put(name, value);
337: }
338:
339: }
340:
341: private AbstractTask parseAbstractTask(Node taskNode,
342: CompositeCheatSheetModel model) {
343: AbstractTask task;
344: NamedNodeMap attributes = taskNode.getAttributes();
345: String kind = null;
346: String name = null;
347: String id = null;
348: boolean skippable = false;
349: if (attributes != null) {
350: for (int x = 0; x < attributes.getLength(); x++) {
351: Node attribute = attributes.item(x);
352: String attributeName = attribute.getNodeName();
353: if (attribute == null || attributeName == null)
354: continue;
355: if (attributeName.equals(ICompositeCheatsheetTags.KIND)) {
356: kind = attribute.getNodeValue();
357: }
358: if (attributeName.equals(ICompositeCheatsheetTags.NAME)) {
359: name = attribute.getNodeValue();
360: }
361: if (attributeName.equals(IParserTags.ID)) {
362: id = attribute.getNodeValue();
363: }
364: if (attributeName.equals(IParserTags.SKIP)) {
365: skippable = "true".equalsIgnoreCase(attribute.getNodeValue()); //$NON-NLS-1$
366: }
367: }
368: }
369:
370: String nodeName = taskNode.getNodeName();
371: if (id == null) {
372: id = autoGenerateId();
373: }
374: if (name == null) {
375: String message = NLS.bind(
376: Messages.ERROR_PARSING_TASK_NO_NAME,
377: (new Object[] { nodeName }));
378: addStatus(IStatus.ERROR, message, null);
379: }
380: task = createTask(nodeName, model, kind, id, name);
381: task.setSkippable(skippable);
382:
383: if (model.getDependencies().getTask(id) != null) {
384: String message = NLS.bind(
385: Messages.ERROR_PARSING_DUPLICATE_TASK_ID,
386: (new Object[] { id, }));
387: addStatus(IStatus.ERROR, message, null);
388: } else {
389: model.getDependencies().saveId(task);
390: }
391:
392: return task;
393: }
394:
395: private AbstractTask createTask(String nodeKind,
396: CompositeCheatSheetModel model, String kind, String id,
397: String name) {
398: AbstractTask task;
399: if (ICompositeCheatsheetTags.TASK_GROUP.equals(nodeKind)) {
400: task = new TaskGroup(model, id, name, kind);
401: } else {
402: task = new EditableTask(model, id, name, kind);
403: }
404: task.setCompletionMessage(Messages.COMPLETED_TASK);
405: return task;
406: }
407:
408: private String autoGenerateId() {
409: return "TaskId_" + nextTaskId++; //$NON-NLS-1$
410: }
411:
412: }
|