diff options
Diffstat (limited to 'support/latex-dependency-grapher/source/main/java/ch/bfh/lpdg/DependencyScanner.java')
-rw-r--r-- | support/latex-dependency-grapher/source/main/java/ch/bfh/lpdg/DependencyScanner.java | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/support/latex-dependency-grapher/source/main/java/ch/bfh/lpdg/DependencyScanner.java b/support/latex-dependency-grapher/source/main/java/ch/bfh/lpdg/DependencyScanner.java new file mode 100644 index 0000000000..fa635b9532 --- /dev/null +++ b/support/latex-dependency-grapher/source/main/java/ch/bfh/lpdg/DependencyScanner.java @@ -0,0 +1,193 @@ +package ch.bfh.lpdg; + +import ch.bfh.lpdg.datastructure.Dependency; +import ch.bfh.lpdg.datastructure.DependencyType; + +import java.io.*; +import java.nio.file.Paths; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import static java.util.Collections.emptyList; + +/** + * This class is responsible for scanning dependencies for a file input. + */ +public class DependencyScanner { + + public static final String REGEX_DEPENDENCIES = "\\u005Cusepackage\\{(.*?)}|\\u005CRequirePackage\\{(.*?)}|\\u005Cinput\\{(.*?)}|\\u005Cinclude\\{(.*?)}"; + public final static DependencyScanner INSTANCE = new DependencyScanner(); + private String filePathWithoutFileName; + + private DependencyScanner() { + } + + public static DependencyScanner getInstance() { + return INSTANCE; + } + + /** + * This method can be called recursively, it loops through all dependencies of the given file. + * This is done via a regex pattern -> it scans for all types in the DependencyType.java class. + * It scans each line of the file individually and if the line matches one of the patterns in the regex the extractDependency() + * method is called and is then added to the list of dependencies. + * + * @param filePath is needed to know the initial location of the document that has to be scanned + * @param type describes what type the currently scanned document has + * @param source describes what type of dependency the currently scanned dependency has + * @param alreadyAddedDependencies is needed to avoid dependency loops + * @param scanDepth limits how deep the scan looks into the dependencies + * @return returns a Dependency containing a list of all its dependencies itself + */ + public Dependency findDependencies(final String filePath, final DependencyType type, final String source, final List<String> alreadyAddedDependencies, final int scanDepth) { + final String fileName = Paths.get(filePath).getFileName().toString(); + final String fileNameWithoutExt = this.resolveDependencyName(fileName); + filePathWithoutFileName = String.copyValueOf(filePath.toCharArray()); + filePathWithoutFileName = filePathWithoutFileName.replace(fileName, ""); + + final Dependency dependency = new Dependency(type, fileNameWithoutExt, source); + + if(scanDepth > 0) { + try { + final BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath))); + final Pattern pattern = Pattern.compile(REGEX_DEPENDENCIES); + + String line; + while ((line = br.readLine()) != null) { + final Matcher matcher = pattern.matcher(line); + + while (matcher.find()) { + final String usepackage = matcher.group(1); + final String requirepackage = matcher.group(2); + final String input = matcher.group(3); + final String include = matcher.group(4); + + // Create dependencies for dependency + final List<Dependency> extractedUP = this.extractDependencies(usepackage, DependencyType.USE_PACKAGE, matcher.group(0), alreadyAddedDependencies, scanDepth); + final List<Dependency> extractedRP = this.extractDependencies(requirepackage, DependencyType.REQUIRE_PACKAGE, matcher.group(0), alreadyAddedDependencies, scanDepth); + final List<Dependency> extractedINP = this.extractDependencies(input, DependencyType.INPUT, matcher.group(0), alreadyAddedDependencies, scanDepth); + final List<Dependency> extractedINC = this.extractDependencies(include, DependencyType.INCLUDE, matcher.group(0), alreadyAddedDependencies, scanDepth); + + // Add dependency to tree + final List<Dependency> extractedDependencies = Stream.of(extractedUP, extractedRP, extractedINP, extractedINC).filter(Objects::nonNull).flatMap(Collection::stream).toList(); + extractedDependencies.forEach(dep -> dependency.getDependencyList().add(dep)); + } + } + br.close(); + } catch (FileNotFoundException ex) { + InteractionHandler.getInstance().printDebugMessage("File: " + fileName + " was not found."); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + var cleanDependencies = removeInvalidDependencies(dependency); + dependency.setDependencyList(cleanDependencies); + + return dependency; + } + + /** + * This method extracts all dependencies for a Dependency scanned in the findDependencies() method. + * + * @return a list of dependencies, this is required for a format like requirePackage{array, color} + */ + private List<Dependency> extractDependencies(final String dependencyName, final DependencyType type, final String source, final List<String> alreadyAddedDependencies, final int scanDepth) { + final List<String> currentDependencies = new ArrayList<>(alreadyAddedDependencies); + if (dependencyName != null && !dependencyName.isEmpty()) { + // Resolve multiline usePackage dependencies, ex. usePackage{array,color} + if (dependencyName.contains(",")) { + final String[] packages = dependencyName.split(","); + final List<Dependency> foundDependencies = new ArrayList<>(); + Arrays.stream(packages).forEach(p -> { + if (!currentDependencies.contains(p)) { + // Try to resolve the path for the found dependency, if a path is found, the file has to be scanned recursively. + final String filePath = this.resolvePath(p, type); + if (filePath == null) { + foundDependencies.add(new Dependency(type, p, source)); + } else { + currentDependencies.add(p); + final Dependency dependency = this.findDependencies(filePath, type, source, currentDependencies, scanDepth-1); + if (dependency != null) { + foundDependencies.add(dependency); + } + } + } + }); + return foundDependencies; + } else { + if (!alreadyAddedDependencies.contains(dependencyName)) { + // Try to resolve the path for the found dependency, if a path is found, the file has to be scanned recursively. + final String filePath = this.resolvePath(dependencyName, type); + if (filePath == null) { + return Collections.singletonList(new Dependency(type, dependencyName, source)); + } else { + currentDependencies.add(dependencyName); + return List.of(this.findDependencies(filePath, type, source, currentDependencies, scanDepth -1)); + } + } + } + } + return emptyList(); + } + + /** + * This method resolves a path for a LaTeX dependency + * @param packageName the name of the package that should be searched for with kpsewhich + * @param type the type of the package, this is needed to search for project dependencies for input and include + * @return returns the path of the dependency location + */ + private String resolvePath(final String packageName, final DependencyType type) { + //Execute kpsewhich tool to find path of package + Process process; + try { + boolean hasExtension = packageName.lastIndexOf('.') != -1; + + //If the package doesn't have an extension add the standard one + process = Runtime.getRuntime().exec(String.format("kpsewhich %s%s", packageName, hasExtension ? "" : ".sty")); + } catch (IOException e) { + System.err.println("Couldn't execute kpsewhich command."); + throw new RuntimeException(e); + } + + StringBuilder result = new StringBuilder(); + BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); + String s; + while (true) { + try { + if ((s = stdInput.readLine()) == null) break; //Path not found + } catch (IOException e) { + System.err.println("Couldn't read output of kpsewhich command."); + throw new RuntimeException(e); + } + + result.append(s); + } + + if (result.isEmpty()) { + if (type.equals(DependencyType.INCLUDE) || type.equals(DependencyType.INPUT)) { + return filePathWithoutFileName + packageName + ".tex"; + } + return null; + } + + return Paths.get(result.toString()).toAbsolutePath().toString(); + } + + /** + * Removes invalid dependencies like variable names. + * Example: #1.def, \gin@driver + * + * @param d The List of all found dependencies + * @return A clean list without variable names and other invalid dependencies + */ + private List<Dependency> removeInvalidDependencies(Dependency d) { + return d.getDependencyList().stream().filter(e -> !e.getName().contains("#") && !e.getName().contains("@")).toList(); + } + + private String resolveDependencyName(final String fileName) { + return fileName.split("\\.")[0]; + } +} |