/** * Copyright (c) 2005-2006 Aptana, Inc. * * 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. If redistributing this code, * this entire header must remain intact. */ package com.aptana.ide.js.docgen; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import com.aptana.ide.core.FileUtils; import com.aptana.ide.core.IdeLog; import com.aptana.ide.core.StringUtils; import com.aptana.ide.editor.js.lexing.JSTokenTypes; import com.aptana.ide.editor.js.outline.JSContentProvider; import com.aptana.ide.editor.js.outline.JSOutlineItem; import com.aptana.ide.editor.js.outline.JSOutlineItemType; import com.aptana.ide.editor.js.parsing.JSParseState; import com.aptana.ide.editor.scriptdoc.parsing.FunctionDocumentation; import com.aptana.ide.editor.scriptdoc.parsing.PropertyDocumentation; import com.aptana.ide.editor.scriptdoc.parsing.TypedDescription; import com.aptana.ide.io.SourceWriter; import com.aptana.ide.lexer.IRange; import com.aptana.ide.lexer.Lexeme; import com.aptana.ide.lexer.LexemeList; import com.aptana.ide.metadata.IDocumentation; import com.aptana.ide.metadata.IDocumentationStore; import com.aptana.ide.parsing.nodes.IParseNode; /** * Utility class for generating documentation * @author Ingo Muschenetz * */ public final class GenerateDocs { /** * Private constructor * */ private GenerateDocs() { } /** * Generates XML from a JS parse state * @param parseState The current parse state of the file * @param fileName Optional parameter for name of the file being parsed * @return */ public static String generateXML(JSParseState parseState, String fileName) { String xml = null; JSContentProvider cp = new JSContentProvider(); try { IParseNode results = parseState.getParseResults(); Object[] nodes = cp.getElements(results); xml = getXML(cp, nodes, parseState, fileName); } catch (Exception ex) { IdeLog.logError(DocgenPlugin.getDefault(), Messages.GenerateDocs_ERR_GenerateXML, ex); } return xml; } /** * Generates HTML documents from a string of XML * @param xml * @param docRoot * @param fileName * @param schemaStream * @return */ public static String generateHTMLFromXML(String xml, String docRoot, String fileName, InputStream schemaStream) { StringReader sw = new StringReader(xml); try { String filePath = docRoot; Path p = new Path(filePath); String indexPath = p.append("index.html").toOSString(); //$NON-NLS-1$ dump(xml, indexPath + ".xml"); //$NON-NLS-1$ transform(sw, schemaStream, fileName, indexPath); return "file://" + indexPath; //$NON-NLS-1$ } catch (TransformerException ex) { IdeLog.logError(DocgenPlugin.getDefault(), Messages.GenerateDocs_ERR_TransformDoc, ex); } catch (IOException ex) { IdeLog.logError(DocgenPlugin.getDefault(), Messages.GenerateDocs_ERR_TransformDoc, ex); } return null; } /** * Retrieves the function documentation from the documentation store. * * @param lexeme * The lexeme for which to retrieve documentation * @return A new FunctionDocumentation object, or null if not found */ protected static PropertyDocumentation getFunctionDocumentation(JSParseState parseState, int offset) { if (parseState != null) { IDocumentationStore store = parseState.getDocumentationStore(); return (PropertyDocumentation) store.getDocumentationFromOffset(offset); } return null; } /** * Retrieves the property documentation from the documentation store. * * @param lexeme * The lexeme for which to retrieve documentation * @return A new PropertyDocumentation object, or null if not found */ protected static IDocumentation getPropertyDocumentation(JSParseState parseState, Lexeme lexeme) { if (lexeme == null) { return null; } if (parseState != null) { IDocumentationStore store = parseState.getDocumentationStore(); return (IDocumentation) store.getDocumentationFromOffset(lexeme.offset + lexeme.getLength()); } return null; } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getXML() */ public static String getXML(JSContentProvider cp, Object[] nodes, JSParseState ps, String fileName) { SourceWriter writer = new SourceWriter(); writer.println("<?xml-stylesheet type=\"text/xsl\" href=\"docs.xsl\"?><javascript fileName=\"" + fileName //$NON-NLS-1$ + "\">"); //$NON-NLS-1$ writer.increaseIndent(); getXML(writer, cp, nodes, ps, null); writer.println("</javascript>"); //$NON-NLS-1$ return writer.toString(); } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getXML(com.aptana.ide.io.SourceWriter) */ public static void getXML(SourceWriter writer, JSContentProvider cp, Object[] nodes, JSParseState ps, String prefix) { if (nodes == null) { return; } for (int i = 0; i < nodes.length; i++) { Object item = nodes[i]; if (item instanceof JSOutlineItem) { JSOutlineItem jsItem = (JSOutlineItem) item; PropertyDocumentation fd = getRelatedDocumentation(ps, jsItem); String newPrefix = ""; //$NON-NLS-1$ if (prefix == null) { newPrefix = fd.getName(); } else { newPrefix = prefix + "." + fd.getName(); //$NON-NLS-1$ } if (prefix != null && (newPrefix.endsWith(".prototype") || prefix.endsWith(".prototype"))) //$NON-NLS-1$ //$NON-NLS-2$ { fd.setIsInstance(true); } startOutlineItem(writer, (FunctionDocumentation) fd, jsItem); Object[] children = cp.getChildren(jsItem); getXML(writer, cp, children, ps, newPrefix); endOutlineItem(writer, jsItem); } } } /** * Change the item type code to a string * * @param type * @return */ private static String typeAsStringType(int type) { String returnString = null; switch (type) { case JSOutlineItemType.FUNCTION: { returnString = "Function"; //$NON-NLS-1$ break; } case JSOutlineItemType.ARRAY: { returnString = "Array"; //$NON-NLS-1$ break; } case JSOutlineItemType.BOOLEAN: { returnString = "Boolean"; //$NON-NLS-1$ break; } case JSOutlineItemType.NULL: { returnString = "null"; //$NON-NLS-1$ break; } case JSOutlineItemType.NUMBER: { returnString = "Number"; //$NON-NLS-1$ break; } case JSOutlineItemType.REGEX: { returnString = "Regex"; //$NON-NLS-1$ break; } case JSOutlineItemType.STRING: { returnString = "String"; //$NON-NLS-1$ break; } case JSOutlineItemType.PROPERTY: case JSOutlineItemType.OBJECT_LITERAL: default: { returnString = "Object"; //$NON-NLS-1$ break; } } return returnString; } /** * Change the item type code to a string * * @param type * @return */ private static String typeAsString(int type) { String returnString = null; switch (type) { case JSOutlineItemType.FUNCTION: { returnString = "function"; //$NON-NLS-1$ break; } case JSOutlineItemType.OBJECT_LITERAL: { returnString = "object_literal"; //$NON-NLS-1$ break; } case JSOutlineItemType.PROPERTY: case JSOutlineItemType.ARRAY: case JSOutlineItemType.BOOLEAN: case JSOutlineItemType.NULL: case JSOutlineItemType.NUMBER: case JSOutlineItemType.REGEX: case JSOutlineItemType.STRING: { returnString = "property"; //$NON-NLS-1$ break; } default: { returnString = "unknown"; //$NON-NLS-1$ break; } } return returnString; } /** * Ends an item in the outline * * @param writer * @param jsItem */ private static void endOutlineItem(SourceWriter writer, JSOutlineItem jsItem) { writer.decreaseIndent(); writer.printlnWithIndent("</" + typeAsString(jsItem.getType()) + ">"); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Starts an item in the outline * * @param writer * @param documentation * @param jsItem */ private static void startOutlineItem(SourceWriter writer, FunctionDocumentation documentation, JSOutlineItem jsItem) { writer.printWithIndent("<" + typeAsString(jsItem.getType())).print( //$NON-NLS-1$ " name=\"" + stripTags(documentation.getName()) + "\""); //$NON-NLS-1$ //$NON-NLS-2$ writeAttributes(writer, documentation, jsItem); writer.println(">"); //$NON-NLS-1$ writer.increaseIndent(); if (documentation != null) { writeDocumentation(writer, documentation); writeParameters(writer, documentation); writeExamples(writer, documentation); writeAliases(writer, documentation); writeSeeAlso(writer, documentation); if (jsItem.getType() == JSOutlineItemType.FUNCTION) { writeTypes(writer, documentation); } } } private static void writeTypes(SourceWriter writer, FunctionDocumentation documentation) { if (documentation.getReturn() != null && documentation.getReturn().getTypes().length > 0) { writer.printlnWithIndent("<return-types>"); //$NON-NLS-1$ writer.increaseIndent(); for (int i = 0; i < documentation.getReturn().getTypes().length; i++) { String array_element = documentation.getReturn().getTypes()[i]; writer.printWithIndent("<return-type").println(" type=\"" + array_element + "\" />"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } writer.decreaseIndent(); writer.printlnWithIndent("</return-types>"); //$NON-NLS-1$ } } /** * Write method attributes * * @param writer * @param documentation */ private static void writeAttributes(SourceWriter writer, FunctionDocumentation documentation, JSOutlineItem jsItem) { if (documentation.getIsInstance()) { writer.print(" scope=\"instance\""); //$NON-NLS-1$ } else { writer.print(" scope=\"static\""); //$NON-NLS-1$ } writer.print(" constructor=\"" + documentation.getIsConstructor() + "\""); //$NON-NLS-1$ //$NON-NLS-2$ writer.print(" deprecated=\"" + documentation.getIsDeprecated() + "\""); //$NON-NLS-1$ //$NON-NLS-2$ writer.print(" private=\"" + documentation.getIsPrivate() + "\""); //$NON-NLS-1$ //$NON-NLS-2$ writer.print(" protected=\"" + documentation.getIsProtected() + "\""); //$NON-NLS-1$ //$NON-NLS-2$ writer.print(" ignored=\"" + documentation.getIsIgnored() + "\""); //$NON-NLS-1$ //$NON-NLS-2$ writer.print(" internal=\"" + documentation.getIsInternal() + "\""); //$NON-NLS-1$ //$NON-NLS-2$ if (documentation.getReturn() != null && documentation.getReturn().getTypes().length > 0) { writer.print(" type=\"" + documentation.getReturn().getTypes()[0] + "\""); //$NON-NLS-1$ //$NON-NLS-2$ } else { writer.print(" type=\"" + typeAsStringType(jsItem.getType()) + "\""); //$NON-NLS-1$ //$NON-NLS-2$ } } /** * Write method parameters * * @param writer * @param documentation */ private static void writeParameters(SourceWriter writer, FunctionDocumentation documentation) { TypedDescription[] params = documentation.getParams(); if (documentation.getParams() != null && documentation.getParams().length > 0) { writer.printlnWithIndent("<parameters>"); //$NON-NLS-1$ writer.increaseIndent(); for (int i = 0; i < params.length; i++) { TypedDescription description = params[i]; writer.printWithIndent("<parameter").print(" name=\"" + stripTags(description.getName()) + "\""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ if (description.getTypes().length > 0) { writer.print(" type=\"" + description.getTypes()[0] + "\""); //$NON-NLS-1$ //$NON-NLS-2$ } else { writer.print(" type=\"Object\""); //$NON-NLS-1$ } writer.println(">"); //$NON-NLS-1$ if (description.getDescription() != null && !description.getDescription().trim().equals("")) //$NON-NLS-1$ { writer.increaseIndent(); writeDocumentation(writer, description); writer.decreaseIndent(); } writer.printlnWithIndent("</parameter>"); //$NON-NLS-1$ } writer.decreaseIndent(); writer.printlnWithIndent("</parameters>"); //$NON-NLS-1$ } } /** * Write method parameters * * @param writer * @param documentation */ private static void writeExamples(SourceWriter writer, FunctionDocumentation documentation) { String[] params = documentation.getExamples(); if (params != null && params.length > 0) { writer.printlnWithIndent("<examples>"); //$NON-NLS-1$ writer.increaseIndent(); for (int i = 0; i < params.length; i++) { String description = params[i]; description = StringUtils.replace(description, "<", "<"); //$NON-NLS-1$ //$NON-NLS-2$ description = StringUtils.replace(description, ">", ">"); //$NON-NLS-1$ //$NON-NLS-2$ writer.printWithIndent("<example>"); //$NON-NLS-1$ writer.increaseIndent(); writer.printWithIndent(description); writer.decreaseIndent(); writer.printWithIndent("</example>"); //$NON-NLS-1$ } writer.decreaseIndent(); writer.printlnWithIndent("</examples>"); //$NON-NLS-1$ } } /** * Write method parameters * * @param writer * @param documentation */ private static void writeSeeAlso(SourceWriter writer, FunctionDocumentation documentation) { String[] params = documentation.getSees(); if (params != null && params.length > 0) { writer.printlnWithIndent("<references>"); //$NON-NLS-1$ writer.increaseIndent(); for (int i = 0; i < params.length; i++) { String description = params[i]; writer.printWithIndent("<reference").print(" name=\"" + stripTags(description) + "\" />"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } writer.decreaseIndent(); writer.printlnWithIndent("</references>"); //$NON-NLS-1$ } } /** * Write method parameters * * @param writer * @param documentation */ private static void writeAliases(SourceWriter writer, FunctionDocumentation documentation) { TypedDescription params = documentation.getAliases(); if (params != null && params.getTypes().length > 0) { writer.printlnWithIndent("<aliases>"); //$NON-NLS-1$ writer.increaseIndent(); String[] types = params.getTypes(); for (int i = 0; i < types.length; i++) { String description = types[i]; writer.printWithIndent("<alias").print(" name=\"" + stripTags(description) + "\" />"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } writer.decreaseIndent(); writer.printlnWithIndent("</aliases>"); //$NON-NLS-1$ } } private static void writeDocumentation(SourceWriter writer, PropertyDocumentation fd) { if (fd.getDescription() != null && !fd.getDescription().trim().equals("")) //$NON-NLS-1$ { writer.printWithIndent("<description>").println(stripTags(fd.getDescription().trim()) + "</description>"); //$NON-NLS-1$ //$NON-NLS-2$ } } private static void writeDocumentation(SourceWriter writer, TypedDescription fd) { if (fd.getDescription() != null && !fd.getDescription().trim().equals("")) //$NON-NLS-1$ { writer.printWithIndent("<description>").println(stripTags(fd.getDescription().trim()) + "</description>"); //$NON-NLS-1$ //$NON-NLS-2$ } } /** * Strip < and > from the text * * @param text * @return */ private static String stripTags(String text) { String replaced = StringUtils.replace(text, "&", "&"); //$NON-NLS-1$ //$NON-NLS-2$ replaced = StringUtils.replace(replaced, "<", "<"); //$NON-NLS-1$ //$NON-NLS-2$ replaced = StringUtils.replace(replaced, ">", ">"); //$NON-NLS-1$ //$NON-NLS-2$ replaced = StringUtils.replace(replaced, "\"", """); //$NON-NLS-1$ //$NON-NLS-2$ return StringUtils.replace(replaced, "'", "'"); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Get the related documentation for the particular node * * @param ps * @param jsItem * @return */ private static PropertyDocumentation getRelatedDocumentation(JSParseState ps, JSOutlineItem jsItem) { int startOffset = jsItem.getStartingOffset(); LexemeList ll = ps.getLexemeList(); int startIndex = ll.getLexemeIndex(startOffset); Lexeme next = ll.get(startIndex + 1); PropertyDocumentation fd = null; if (!jsItem.getLabel().equals("prototype") //$NON-NLS-1$ && (next == null || next.getToken().getTypeIndex() != JSTokenTypes.DOT)) { fd = searchForDocumentation(ps, ll, startIndex); } if (fd == null) { fd = new FunctionDocumentation(); fd.setIsIgnored(true); } setDocumentationName(fd, jsItem); if (jsItem.getType() == JSOutlineItemType.FUNCTION) { FunctionDocumentation fd2 = (FunctionDocumentation) fd; fd2.setIsMethod(true); } return fd; } /** * Searches back through the lexeme list to find related documentation * @param ps * @param ll * @param startIndex * @return */ private static PropertyDocumentation searchForDocumentation(JSParseState ps, LexemeList ll, int startIndex) { PropertyDocumentation fd = null; for (int k = startIndex; k > startIndex - 10; k--) { if (k < 0) { break; } Lexeme l = ll.get(k); fd = getFunctionDocumentation(ps, l.getEndingOffset()); if (fd != null) { break; } } return fd; } /** * Set the name of the documentation function * * @param fd * @param jsItem */ private static void setDocumentationName(PropertyDocumentation fd, JSOutlineItem jsItem) { if (fd.getName() == null || fd.getName().equals("")) //$NON-NLS-1$ { if (jsItem.getType() == JSOutlineItemType.FUNCTION) { IRange range = null; // jsItem.getRange(); if (range instanceof IParseNode) { IParseNode pn = (IParseNode) range; fd.setName(pn.getAttribute("name")); //$NON-NLS-1$ } else { fd.setName(stripParens(jsItem.getLabel())); } } else { fd.setName(stripParens(jsItem.getLabel())); } } } /** * Strips the parenthesis * * @param label * @return */ public static String stripParens(String label) { if (label.indexOf('(') > 0) { return label.substring(0, label.indexOf('(')); } else { return label; } } /** * Pull the images outside of the jar and export to disk * * @param object * @param folderPath * @param fileName */ public static void exportResource(String folderPath, String fileName) { InputStream zipStream = DocgenPlugin.class .getResourceAsStream("/com/aptana/ide/js/docgen/resources/" + fileName); //$NON-NLS-1$ (new File(folderPath)).mkdirs(); FileUtils.writeStreamToFile(zipStream, folderPath + "/" + fileName); //$NON-NLS-1$ } /** * Pull the images outside of the jar and export to disk * * @param object * @param folderPath * @param fileName */ public static void exportImage(String folderPath, String fileName) { InputStream zipStream = DocgenPlugin.class .getResourceAsStream("/com/aptana/ide/js/docgen/resources/images/" + fileName); //$NON-NLS-1$ (new File(folderPath)).mkdirs(); FileUtils.writeStreamToFile(zipStream, folderPath + "/" + fileName); //$NON-NLS-1$ } /** * Returns the "prefix" of a file (which is the name of the file minus the ".js" at the end. * * @param file * The file to parse. * @return The file name minus the ".js" */ public static String getFilePrefix(IFile file) { if (file == null) { return StringUtils.EMPTY; } String extension = "." + file.getFileExtension(); //$NON-NLS-1$ return file.getName().replaceAll(extension, StringUtils.EMPTY); } /** * The parent folder of the file. If the file name has a trailing separator, return that, otherwise, return the * parent folder * * @param file * The file to parse * @return A string representing the parent folder name. */ public static String getParentFolder(IFile file) { IPath path = file.getLocation(); if (path.hasTrailingSeparator()) { return path.toString(); } else { IPath newPath = path.removeLastSegments(1); return newPath.toString(); } } /** * Transform the XML with an XSL file * * @param sw * The stream of XML to transform * @param inputStreamXsl * The XSL file * @param fileNamePrefix * The "prefix" of the file. * @param filePath * The path to the parent folder on disk * @throws TransformerException * @throws TransformerConfigurationException */ public static void transform(StringReader sw, InputStream inputStreamXsl, String fileNamePrefix, String filePath) throws TransformerException, TransformerConfigurationException { // Create a transform factory instance. TransformerFactory tfactory = TransformerFactory.newInstance(); // Create a transformer for the stylesheet. Transformer transformer = tfactory.newTransformer(new StreamSource(inputStreamXsl)); transformer.setParameter("fileNamePrefix", fileNamePrefix); //$NON-NLS-1$ // Transform the source XML to System.out. transformer.transform(new StreamSource(sw), new StreamResult(new File(filePath))); } /** * dump * * @param text * @param filePath * @throws IOException */ public static void dump(String text, String filePath) throws IOException { File outFile = new File(filePath); FileWriter out = new FileWriter(outFile); out.write(text); out.close(); } }