summaryrefslogtreecommitdiff
path: root/support/latex-dependency-grapher/source/main/java/ch/bfh/lpdg/DependencyScanner.java
diff options
context:
space:
mode:
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.java193
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];
+ }
+}