Right, I suppose I knew that was the correct answer when I posted: you can’t safely iterate and mutate at the same time ![]()
At least on paper/pseudocode, building a mutated copy of the original AST is relatively straightforward. What I was getting hung up on – why I didn’t start with that in the first place – was how to “copy” a node without its children. Looking at the source code of copy_tree really clarified how to do that. So I think using the recursive rewrite_links function below (modeled after copy_tree) is a clean way to implement this:
using Pkg
Pkg.activate(temp=true)
Pkg.add(url="https://github.com/JuliaDocs/Documenter.jl", rev="master")
Pkg.add("MarkdownAST")
Pkg.add("AbstractTrees")
import Documenter
import MarkdownAST
import Markdown
import AbstractTrees
MD_MINIMAL = raw"""
Text with [rabiner_tutorial_1989](@cite).
"""
function parse_md_string(mdsrc)
mdpage = Markdown.parse(mdsrc)
return convert(MarkdownAST.Node, mdpage)
end
function rewrite_links(node::MarkdownAST.Node{M}) where M
new_node = MarkdownAST.Node{M}(node.element, deepcopy(node.meta))
for child in node.children
if child.element == MarkdownAST.Link("@cite", "")
new_text = "[Citation](https://github.com/JuliaDocs/DocumenterCitations.jl) for `key`"
# In reality, new_text would be derived from the link node. Might
# contain multiple links and arbitrary inline formatting.
println("-> Doing transform to new text=$new_text")
expanded = Documenter.mdparse(new_text; mode=:span)
append!(new_node.children, expanded)
else
push!(new_node.children, rewrite_links(child))
end
end
return new_node
end
mdast = parse_md_string(MD_MINIMAL)
println("====== IN =======")
println("AS AST:")
@show mdast
println("AS TEXT:")
print(string(convert(Markdown.MD, mdast)))
println("=== TRANSFORM ===")
mdast = rewrite_links(mdast)
println("====== OUT =======")
println("AS AST:")
@show mdast
println("AS TEXT:")
print(string(convert(Markdown.MD, mdast)))
println("====== END =======")
It doesn’t technically mutate the original mdast, but I can always replace the original root’s children with the new root’s children at the end.