All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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) 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