blob: 566846a67da80a7db742ae06fca4cd546e1ce96a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011, 2018 Red Hat Inc. and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - Initial implementation
*******************************************************************************/
package org.eclipse.linuxtools.internal.cdt.libhover.devhelp;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.StringReader;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.xerces.parsers.AbstractSAXParser;
import org.apache.xerces.xni.Augmentations;
import org.apache.xerces.xni.QName;
import org.apache.xerces.xni.XMLAttributes;
import org.apache.xerces.xni.XMLString;
import org.cyberneko.html.HTMLConfiguration;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.filesystem.IFileSystem;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.linuxtools.cdt.libhover.FunctionInfo;
import org.eclipse.linuxtools.cdt.libhover.LibHoverInfo;
import org.eclipse.linuxtools.internal.cdt.libhover.devhelp.preferences.FuncFoundSaxException;
import org.eclipse.linuxtools.internal.cdt.libhover.devhelp.preferences.LibHoverMessages;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class ParseDevHelp {
private final static String PARSING_MSG = "Libhover.Devhelp.Parsing.msg"; //$NON-NLS-1$
private final static String PARSING_FMT_MSG = "Libhover.Devhelp.Parsing.fmt.msg"; //$NON-NLS-1$
private static class HTMLSaxParser extends AbstractSAXParser {
private boolean begin;
private boolean refsect2;
private boolean returnType;
private boolean protoStart;
private boolean parmStart;
private boolean descStart;
private boolean rowIgnore;
private boolean valid = true;
private HashMap<String, String> funcs;
private String returnValue;
private String funcName;
private String rowTag;
private StringBuilder prototype = new StringBuilder();
private StringBuilder description = new StringBuilder();
private int divCounter;
private int rowItemCount;
private TreeMap<String, FunctionInfo> infos = new TreeMap<>();
public HTMLSaxParser(HashMap<String, String> funcs) {
super(new HTMLConfiguration());
this.funcs = funcs;
}
@Override
public void startElement(QName name, XMLAttributes a, Augmentations aug) {
// look for a tag that matches one of the functions we are trying to find
if ("A".equals(name.rawname)) { //$NON-NLS-1$
String fname = a.getValue("name"); //$NON-NLS-1$
String mapName = funcs.get(fname);
if (mapName != null) {
// We have found one of the functions we are looking for.
// Register the name for later and allow function parsing to begin.
funcName = mapName.trim();
if (funcName.endsWith("()")) { //$NON-NLS-1$
// Remove () at end of function name and remove all space chars which might be
// non-breaking space chars which unfortunately do not get caught by the trim() method.
funcName = this.funcName.replaceAll("\\(\\)", "").replaceAll("\\p{javaSpaceChar}",""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
begin = true;
funcs.remove(fname); // take name off our list to do
divCounter = refsect2 ? 1 : 0;
refsect2 = false;
}
}
if (begin) {
if ("DIV".equals(name.rawname)) { //$NON-NLS-1$
++divCounter;
}
if (!descStart) {
if ("SPAN".equals(name.rawname)) { //$NON-NLS-1$
String type = a.getValue("class"); //$NON-NLS-1$
if (returnValue == null && type != null &&
type.equals("returnvalue")) { //$NON-NLS-1$
returnType = true;
}
} else if ("PRE".equals(name.rawname)) { //$NON-NLS-1$
String type = a.getValue("class"); //$NON-NLS-1$
if (type != null && type.equals("programlisting")) { //$NON-NLS-1$
returnType = true;
}
}
}
if (protoStart) {
if ("P".equals(name.rawname)) { //$NON-NLS-1$
protoStart = false;
descStart = true;
description.append("<p>"); //$NON-NLS-1$
}
} else if (descStart) {
if ("P".equals(name.rawname)) { //$NON-NLS-1$
description.append("<p>"); //$NON-NLS-1$
} else if ("TABLE".equals(name.rawname)) { //$NON-NLS-1$
description.append("<dl>"); //$NON-NLS-1$
} else if ("TR".equals(name.rawname)) { //$NON-NLS-1$
rowItemCount = 0;
} else if ("TD".equals(name.rawname)) { //$NON-NLS-1$
String type = a.getValue("class"); //$NON-NLS-1$
if (type != null && type.equals("listing_lines")) { //$NON-NLS-1$
rowIgnore = true;
} else {
rowIgnore = false;
if (rowItemCount++ == 0) {
rowTag = "<dt>"; //$NON-NLS-1$
} else {
rowTag = "<dd>"; //$NON-NLS-1$
}
description.append(rowTag);
}
} else if ("H4".equals(name.rawname)) { //$NON-NLS-1$
description.append("<br><br>"); //$NON-NLS-1$
}
}
} else {
if ("DIV".equals(name.rawname)) { //$NON-NLS-1$
String className = a.getValue("class"); //$NON-NLS-1$
if ("refsect2".equals(className)) { //$NON-NLS-1$
refsect2 = true;
} else {
refsect2 = false;
}
}
}
}
@Override
public void endElement(QName name, Augmentations aug) {
if (begin) {
if ("DIV".equals(name.rawname)) { //$NON-NLS-1$
--divCounter;
if (divCounter <= 0) {
// We have finished parsing the current function, reset all flags
begin = false;
descStart = false;
parmStart = false;
protoStart = false;
// If valid, create and save the function info
if (valid && returnValue != null &&
!returnValue.startsWith("#") && //$NON-NLS-1$
!returnValue.startsWith("typedef ")) { //$NON-NLS-1$
FunctionInfo info = new FunctionInfo(funcName);
info.setReturnType(returnValue);
info.setPrototype(prototype.toString());
info.setDescription(description.toString());
infos.put(funcName, info);
// Clear the description and prototype strings for next function
description.setLength(0);
prototype.setLength(0);
if (funcs.isEmpty())
throw new FuncFoundSaxException(); // indicate we are done and stop parser
}
}
}
if (descStart) {
if ("P".equals(name.rawname)) {//$NON-NLS-1$
description.append("</p>"); //$NON-NLS-1$
} else if ("TABLE".equals(name.rawname)) { //$NON-NLS-1$
description.append("</dl>"); //$NON-NLS-1$
} else if ("TR".equals(name.rawname)) { //$NON-NLS-1$
rowItemCount = 0;
} else if ("TD".equals(name.rawname)) { //$NON-NLS-1$
if (!rowIgnore) {
if (rowTag != null && rowTag.equals("<dt>")) {//$NON-NLS-1$
description.append("</dt>"); //$NON-NLS-1$
} else {
description.append("</dd>"); //$NON-NLS-1$
}
}
rowIgnore = false;
} else if ("H4".equals(name.rawname)) { //$NON-NLS-1$
description.append("</br></br>"); //$NON-NLS-1$
}
}
}
}
@Override
public void characters(XMLString data, Augmentations aug) {
if (begin) {
if (returnType) {
returnValue = ""; //$NON-NLS-1$
String tmp = data.toString().trim();
boolean completed = false;
if (tmp.endsWith(");")) { //$NON-NLS-1$
completed = true;
tmp = tmp.substring(0, tmp.length() - 2);
}
String tokens[] = tmp.split("\\s+"); //$NON-NLS-1$
String separator = ""; //$NON-NLS-1$
protoStart = true;
for (int i = 0; i < tokens.length; ++i) {
String token = tokens[i];
if (!token.equals(funcName)) {
returnValue += separator + token;
separator = " "; //$NON-NLS-1$
} else {
separator = ""; //$NON-NLS-1$
for (int j = i + 1; j < tokens.length; ++j) {
String jtoken = tokens[j];
if (j == i + 1 && jtoken.charAt(0) == '(') {
jtoken = jtoken.substring(1);
parmStart = true;
protoStart = false;
}
prototype.append(separator).append(jtoken);
separator = " "; //$NON-NLS-1$
}
if (parmStart && completed) {
parmStart = false;
descStart = true;
}
break;
}
}
returnType = false;
} else if (protoStart) {
String temp = data.toString().trim();
boolean completed = false;
if (temp.endsWith(");")) { //$NON-NLS-1$
completed = true;
temp = temp.substring(0, temp.length() - 2);
}
String separator = " "; //$NON-NLS-1$
while (temp.startsWith("*") || temp.startsWith("const")) { //$NON-NLS-1$ //$NON-NLS-2$
if (temp.charAt(0) == '*') {
returnValue += separator + "*"; //$NON-NLS-1$
temp = temp.substring(1).trim();
separator = ""; //$NON-NLS-1$
} else {
returnValue += "const"; //$NON-NLS-1$
temp = temp.substring(5).trim();
separator = " "; //$NON-NLS-1$
}
}
int index = temp.lastIndexOf('(');
int index2 = temp.lastIndexOf(')');
if (index2 < index) {
if (index + 1 < temp.length()) {
temp = temp.substring(index + 1).trim();
prototype.append(temp);
}
parmStart = true;
protoStart = false;
}
if (parmStart && completed) {
parmStart = false;
descStart = true;
}
} else if (parmStart) {
String parmData = data.toString().trim();
int index = parmData.indexOf(')');
if (index >= 0) {
parmStart = false;
descStart = true;
parmData = parmData.substring(0, index);
}
if (prototype.length() == 0) {
if (!parmData.equals(",") && !parmData.isEmpty()) { //$NON-NLS-1$
parmData = " " + parmData; //$NON-NLS-1$
}
}
prototype.append(parmData);
} else if (descStart) {
if (!rowIgnore) {
description.append(String.valueOf(data));
}
}
}
}
private TreeMap<String, FunctionInfo> getFunctionInfos() {
return infos;
}
@Override
public String toString() {
return "funcName: <" + funcName + "> returnType: <" + returnValue + //$NON-NLS-1$ //$NON-NLS-2$
"> prototype: <" + prototype + "> description: " + description; //$NON-NLS-1$ //$NON-NLS-2$
}
}
public static class DevHelpParser {
private static final class NullEntityResolver implements EntityResolver {
@Override
public InputSource resolveEntity(String publicId, String systemId) {
return new InputSource(new StringReader("")); //$NON-NLS-1$
}
}
private static final class FilenameComparator implements
Comparator<IFileStore> {
@Override
public int compare(IFileStore arg0, IFileStore arg1) {
return (arg0.getName().compareToIgnoreCase(arg1.getName()));
}
}
private String dirName;
private LibHoverInfo libhover;
private boolean debug;
private FilenameComparator filenameComparator = new FilenameComparator();
private NullEntityResolver entityResolver = new NullEntityResolver();
private DocumentBuilderFactory factory;
public DevHelpParser(String dirName) {
this(dirName, false);
}
public DevHelpParser(String dirName, boolean debug) {
this.dirName = dirName;
this.libhover = new LibHoverInfo();
this.debug = debug;
factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
}
public LibHoverInfo getLibHoverInfo() {
return libhover;
}
public LibHoverInfo parse(IProgressMonitor monitor) {
try {
IFileSystem fs = EFS.getLocalFileSystem();
IPath dirPath = new Path(dirName);
IFileStore htmlDir = fs.getStore(dirPath);
IFileStore[] files = htmlDir.childStores(EFS.NONE, null);
monitor.beginTask(LibHoverMessages.getString(PARSING_MSG), files.length);
Arrays.sort(files, filenameComparator);
for (int i = 0; i < files.length; ++i) {
IFileStore file = files[i];
String name = file.fetchInfo().getName();
if (monitor.isCanceled()) {
return null;
}
monitor.setTaskName(LibHoverMessages.getFormattedString(PARSING_FMT_MSG,
new String[]{name}));
File f = new File(dirPath.append(name).append(name + ".devhelp2").toOSString()); //$NON-NLS-1$
if (f.exists()) {
parse(f.getAbsolutePath(),
monitor);
} else {
parse(dirPath.append(name)
.append(name + ".devhelp").toOSString(), //$NON-NLS-1$
monitor);
}
monitor.worked(1);
}
} catch (CoreException e) {
e.printStackTrace();
}
return libhover;
}
private void parseLinks(HashMap<String, String> funcMap, String fileName, IPath path, LibHoverInfo libhover) {
InputStream reader = null;
HTMLSaxParser parser = null;
try {
reader = new FileInputStream(path.removeLastSegments(1).toOSString()
+ "/" + fileName); //$NON-NLS-1$
parser = new HTMLSaxParser(funcMap);
try {
parser.parse(new InputSource(reader));
} catch (FuncFoundSaxException e) {
// ignore because this is just how we shorten parse time
}
reader.close();
TreeMap<String, FunctionInfo> finfos = parser.getFunctionInfos();
if (finfos != null) {
if (debug) {
System.out.println(parser.toString());
}
libhover.functions.putAll(finfos);
}
} catch (IOException e) {
// ignore
} catch (SAXException e) {
e.printStackTrace();
}
}
private void parse(String fileName, IProgressMonitor monitor) {
try {
HashMap<String, HashMap<String,String>> files = new HashMap<>();
Path path = new Path(fileName);
File f = new File(fileName);
FileInputStream stream = new FileInputStream(f);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
Document doc = builder.parse(stream);
NodeList bookNodes = doc.getElementsByTagName("book"); //$NON-NLS-1$
for (int x = 0; x < bookNodes.getLength(); ++x) {
Node n = bookNodes.item(x);
NamedNodeMap m = n.getAttributes();
Node language = m.getNamedItem("language"); //$NON-NLS-1$
if (language != null && !language.getNodeValue().equals("c")) { //$NON-NLS-1$
return;
}
}
if (path.getFileExtension().equals("devhelp")) { //$NON-NLS-1$
// Get all function nodes
NodeList nl = doc.getElementsByTagName("function"); // $NON-NLS-1$ //$NON-NLS-1$
for (int i = 0; i < nl.getLength(); ++i) {
if (monitor.isCanceled()) {
return;
}
Node n = nl.item(i);
NamedNodeMap m = n.getAttributes();
// For each function node, get its associated link
Node name = m.getNamedItem("name"); // $NON-NLS-1$ //$NON-NLS-1$
Node link = m.getNamedItem("link"); // $NON-NLS-1$ //$NON-NLS-1$
if (name != null && link != null) {
String nameValue = name.getNodeValue();
nameValue = nameValue.replaceAll("\\(.*\\);+", "").trim(); //$NON-NLS-1$ //$NON-NLS-2$
if (!nameValue.contains("::") && !nameValue.startsWith("enum ") //$NON-NLS-1$ //$NON-NLS-2$
&& !nameValue.contains("\"")) { //$NON-NLS-1$
String linkValue = link.getNodeValue();
String[] linkParts = linkValue.split("#"); //$NON-NLS-1$
// Check if the file referred to by the link has been seen before
// If not, create a new function list for it
HashMap<String, String> funcMap = files.get(linkParts[0]);
if (funcMap == null) {
funcMap = new HashMap<>();
files.put(linkParts[0], funcMap);
}
// Add the function to the function list for the link file
funcMap.put(linkParts[1], nameValue);
}
}
}
} else if (path.getFileExtension().equals("devhelp2")) { //$NON-NLS-1$
NodeList nl = doc.getElementsByTagName("keyword"); // $NON-NLS-1$ //$NON-NLS-1$
for (int i = 0; i < nl.getLength(); ++i) {
if (monitor.isCanceled())
return;
Node n = nl.item(i);
NamedNodeMap m = n.getAttributes();
Node type = m.getNamedItem("type"); // $NON-NLS-1$ //$NON-NLS-1$
if (type != null) {
String typeName = type.getNodeValue();
// Look for all function references in the devhelp file
if (typeName.equals("function")) { //$NON-NLS-1$
// Each function reference will have a link associated with it
Node name = m.getNamedItem("name"); // $NON-NLS-1$ //$NON-NLS-1$
Node link = m.getNamedItem("link"); // $NON-NLS-1$ //$NON-NLS-1$
if (name != null && link != null) {
// Clean up the name and make sure it isn't a non-C-function
String nameValue = name.getNodeValue();
nameValue = nameValue.replaceAll("\\(.*\\);+", "").trim(); //$NON-NLS-1$ //$NON-NLS-2$
if (!nameValue.contains("::") && !nameValue.startsWith("enum ") //$NON-NLS-1$ //$NON-NLS-2$
&& !nameValue.contains("\"")) { //$NON-NLS-1$
String linkValue = link.getNodeValue();
String[] linkParts = linkValue.split("#"); //$NON-NLS-1$
// Check to see if the file referred to by the link has been seen before
// If not, create a new function list for it
HashMap<String, String> funcMap = files.get(linkParts[0]);
if (funcMap == null) {
funcMap = new HashMap<>();
files.put(linkParts[0], funcMap);
}
// Add the function to the function list for the link file
funcMap.put(linkParts[1], nameValue);
}
}
}
}
}
}
// For each different file found in function links in the devhelp file,
// parse it and get all function info that is referred to
for (Map.Entry<String, HashMap<String, String>> entry : files.entrySet()) {
String fname = entry.getKey();
HashMap<String, String> funcMap = entry.getValue();
parseLinks(funcMap, fname, path, libhover);
}
} catch (FileNotFoundException e1) {
// ignore
} catch (ParserConfigurationException|SAXException|IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
long startParse = System.currentTimeMillis();
String devhelpHtmlDirectory = "/usr/share/gtk-doc/html"; //$NON-NLS-1$
DevHelpParser p = new DevHelpParser(devhelpHtmlDirectory, false);
File dir = new File(devhelpHtmlDirectory);
for (File f : dir.listFiles()) {
String name = f.getName();
p.parse(f.getAbsolutePath() + "/" + name + ".devhelp2", //$NON-NLS-1$ //$NON-NLS-2$
new NullProgressMonitor());
}
long endParse = System.currentTimeMillis();
System.out.println("Parse Complete:"+(endParse-startParse)); //$NON-NLS-1$
long startSerialize = System.currentTimeMillis();
LibHoverInfo hover = p.getLibHoverInfo();
try {
// Now, output the LibHoverInfo for caching later
IPath workspaceDir = new Path(args[1]);
IPath location = workspaceDir.append("org.eclipse.linuxtools.cdt.libhover/C"); //$NON-NLS-1$
File ldir = new File(location.toOSString());
ldir.mkdir();
location = location.append("devhelp.libhover"); //$NON-NLS-1$
try (FileOutputStream f = new FileOutputStream(
location.toOSString());
ObjectOutputStream out = new ObjectOutputStream(f)) {
out.writeObject(hover);
}
} catch(IOException e) {
}
long endSerialize = System.currentTimeMillis();
System.out.println("Parse Complete:"+(endSerialize-startSerialize)); //$NON-NLS-1$
}
}