
com.vladsch.flexmark.parser.internal.PostProcessorManager Maven / Gradle / Ivy
package com.vladsch.flexmark.parser.internal;
import com.vladsch.flexmark.parser.PostProcessor;
import com.vladsch.flexmark.parser.PostProcessorFactory;
import com.vladsch.flexmark.util.ast.ClassifyingNodeTracker;
import com.vladsch.flexmark.util.ast.Document;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.ast.NodeClassifierVisitor;
import com.vladsch.flexmark.util.collection.OrderedSet;
import com.vladsch.flexmark.util.collection.iteration.ReversibleIterable;
import com.vladsch.flexmark.util.data.DataHolder;
import com.vladsch.flexmark.util.dependency.DependencyResolver;
import com.vladsch.flexmark.util.dependency.DependentItem;
import com.vladsch.flexmark.util.dependency.DependentItemMap;
import java.util.*;
public class PostProcessorManager {
// @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
// final private static HashMap, PostProcessorFactory> CORE_POST_PROCESSORS = new HashMap<>();
// static {
// //CORE_POST_PROCESSORS.put(Parser.REFERENCE_PARAGRAPH_PRE_PROCESSOR, new ReferencePreProcessorFactory());
// }
final private List postProcessorDependencies;
final private OrderedSet allPostProcessNodes = new OrderedSet<>();
public PostProcessorManager(List postProcessorDependencies) {
this.postProcessorDependencies = postProcessorDependencies;
}
public static List calculatePostProcessors(DataHolder options, List postProcessorFactories) {
// By having the custom factories come first, extensions are able to change behavior of core syntax.
// List list = new ArrayList<>(postProcessorFactories);
//
// // add core block preprocessors
// for (DataKey processorDataKey : CORE_POST_PROCESSORS.keySet()) {
// if (processorDataKey.get(options)) {
// PostProcessorFactory preProcessorFactory = CORE_POST_PROCESSORS.get(processorDataKey);
// list.add(preProcessorFactory);
// }
// }
List> resolveDependencies = DependencyResolver.resolveDependencies(postProcessorFactories, PostProcessorManager::prioritizePostProcessors, null);
ArrayList dependencyStages = new ArrayList<>(resolveDependencies.size());
for (List dependencies : resolveDependencies) {
dependencyStages.add(new PostProcessorDependencyStage(dependencies));
}
return dependencyStages;
}
public static Document processDocument(Document document, List processorDependencies) {
if (!processorDependencies.isEmpty()) {
PostProcessorManager manager = new PostProcessorManager(processorDependencies);
document = manager.postProcess(document);
}
return document;
}
public Document postProcess(Document document) {
// first initialize node tracker if
ClassifyingNodeTracker classifyingNodeTracker;
classifyingNodeTracker = null;
for (PostProcessorDependencyStage stage : postProcessorDependencies) {
// idiosyncrasy of post processors the last dependency can be global, in which case it processes the whole document and no ancestry info is
// provided
//new ClassifyingNodeTracker()
boolean hadGlobal = false;
for (PostProcessorFactory dependent : stage.dependents) {
if (dependent.affectsGlobalScope()) {
document = dependent.apply(document).processDocument(document);
hadGlobal = true;
// assume it no longer reflects reality;
classifyingNodeTracker = null;
} else {
assert !hadGlobal;
if (classifyingNodeTracker == null) {
// build the node type information by traversing the document tree
classifyingNodeTracker = new NodeClassifierVisitor(stage.myNodeMap).classify(document);
}
Map, Set>> dependentNodeTypes = dependent.getNodeTypes();
PostProcessor postProcessor = dependent.apply(document);
BitSet exclusionSet = new BitSet();
if (dependentNodeTypes != null) {
for (Set> excluded : dependentNodeTypes.values()) {
BitSet mapped = classifyingNodeTracker.getExclusionSet().indexBitSet(excluded);
exclusionSet.or(mapped);
}
ReversibleIterable nodes = classifyingNodeTracker.getCategoryItems(Node.class, dependentNodeTypes.keySet());
for (Node node : nodes) {
if (node.getParent() == null) continue; // was already removed
// now we need to get the bitset for the excluded ancestors of the node, then intersect it with the actual ancestors of this factory
int index;
BitSet nodeAncestors;
BitSet nodeExclusions;
Set> excluded = dependentNodeTypes.get(node.getClass());
if (excluded != null) {
index = classifyingNodeTracker.getItems().indexOf(node);
if (index != -1) {
nodeAncestors = classifyingNodeTracker.getNodeAncestryMap().get(index);
if (nodeAncestors != null) {
nodeExclusions = classifyingNodeTracker.getExclusionSet().indexBitSet(excluded);
nodeExclusions.and(nodeAncestors);
if (!nodeExclusions.isEmpty()) {
// has excluded ancestor
continue;
}
}
}
}
postProcessor.process(classifyingNodeTracker, node);
}
}
}
}
}
return document;
}
static DependentItemMap prioritizePostProcessors(DependentItemMap dependentMap) {
// put globals last
List, DependentItem>> prioritized = dependentMap.entries();
prioritized.sort((e1, e2) -> {
int g1 = e1.getValue().isGlobalScope ? 1 : 0;
int g2 = e2.getValue().isGlobalScope ? 1 : 0;
return g1 - g2;
});
BitSet dependentMapSet = dependentMap.keySet().keyDifferenceBitSet(prioritized);
if (dependentMapSet.isEmpty()) {
return dependentMap;
}
DependentItemMap prioritizedMap = new DependentItemMap<>(prioritized.size());
prioritizedMap.addAll(prioritized);
return prioritizedMap;
}
public static class PostProcessorDependencyStage {
final Map, Set>> myNodeMap;
final List dependents;
public PostProcessorDependencyStage(List dependents) {
// compute mappings
HashMap, Set>> nodeMap = new HashMap<>();
for (PostProcessorFactory dependent : dependents) {
Map, Set>> types = dependent.getNodeTypes();
if ((types == null || types.isEmpty()) && !dependent.affectsGlobalScope()) {
throw new IllegalStateException("PostProcessorFactory " + dependent + " is not document post processor and has empty node map, does nothing, should not be registered.");
}
if (types != null) {
for (Map.Entry, Set>> entry : types.entrySet()) {
if (Node.class.isAssignableFrom(entry.getKey())) {
Set> classes = nodeMap.get(entry.getKey());
Set> value = entry.getValue();
if (classes == null) {
// copy so it is not modified by additional dependencies injecting other exclusions by mistake
classes = new HashSet<>(value);
//noinspection unchecked
nodeMap.put((Class extends Node>) entry.getKey(), classes);
} else {
try {
classes.addAll(value);
} catch (UnsupportedOperationException e) {
classes = new HashSet<>(classes);
classes.addAll(value);
}
}
}
}
}
}
this.dependents = dependents;
this.myNodeMap = nodeMap;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy