Created
September 24, 2023 19:58
-
-
Save goerz/3b1eb0fdd161abb84f592149d0ec7659 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Pkg | |
Pkg.activate(temp=true) | |
Pkg.add("MarkdownAST") | |
import MarkdownAST | |
""" | |
replace(f::Function, root::Node) | |
Creates a copy of the tree where all child nodes of `root` are recursively | |
replaced by the result of `f(child)`. | |
The function `f(child::Node)` must return either a new `Node` to replace | |
`child` or a Vector of nodes that will be inserted as siblings, replacing | |
`child`. | |
Note that `replace` does not allow the construction of invalid trees, and | |
element replacements that require invalid parent-child relationships (e.g., a | |
block element as a child to an element expecting inlines) will throw an error. | |
# Example | |
The following snippet removes links from the given AST. That is, it replaces | |
`Link` nodes with their link text (which may contain nested inline markdown | |
elements): | |
```julia | |
new_mdast = replace(mdast) do node | |
if node.element isa MarkdownAST.Link | |
return [MarkdownAST.copy_tree(child) for child in node.children] | |
else | |
return node | |
end | |
end | |
``` | |
""" | |
function Base.replace(f::Function, root::MarkdownAST.Node{M}) where M | |
new_root = MarkdownAST.Node{M}(root.element, deepcopy(root.meta)) | |
for child in root.children | |
replaced_child = replace(f, child) | |
transformed = f(replaced_child) | |
if transformed isa MarkdownAST.Node | |
push!(new_root.children, transformed) | |
elseif transformed isa Vector | |
append!(new_root.children, transformed) | |
else | |
error("Function `f` in `replace(f, root::MarkdownAST.Node)` must return either a Node or a Vector of nodes, not $(repr(typeof(transformed)))") | |
end | |
end | |
return new_root | |
end | |
""" | |
replace!(f::Function, root::Node) | |
Acts like `replace(f, root)`, but modifies `root` in-place. | |
""" | |
function Base.replace!(f::Function, root::MarkdownAST.Node{M}) where M | |
new_root = replace(f, root) | |
while !isempty(root.children) | |
# `Base.empty!(root.children)` would be nice! | |
MarkdownAST.unlink!(first(root.children)) | |
end | |
append!(root.children, new_root.children) | |
return root | |
end | |
## TEST ###################################################################### | |
# | |
# As a test, we're resolving simple citation links in a format similar to | |
# https://juliadocs.org/DocumenterCitations.jl/stable/gallery/#Custom-style:-Citation-key-labels | |
# | |
# That test replaces a single Link node with a list of new inline nodes that | |
# mix text and links to a `references.md` page. | |
# | |
# Also, to test the simpler transformation of a node with a single new node, we | |
# replace Strong (bold) nodes with Emph (italic) nodes – This could also be | |
# donw with MarkdownAST.copy_tree directly, but it's just a test. | |
Pkg.add(url="https://github.com/JuliaDocs/Documenter.jl", rev="master") | |
import Markdown | |
import Documenter | |
MD = raw""" | |
# Quantum Control | |
**[Quantum optimal control](https://qutip.org/docs/latest/guide/guide-control.html)** | |
[BrumerShapiro2003;BrifNJP2010;KochJPCM2016;SolaAAMOP2018;MorzhinRMS2019; | |
Wilhelm2003.10132;KochEPJQT2022](@cite) attempts to steer a quantum system in | |
some desired way. | |
## Methods used | |
We use the following methods: | |
* *[Krotov's method](https://github.com/JuliaQuantumControl/Krotov.jl)* | |
[Krotov1996](@cite), and | |
* [**GRAPE** (*Gradient Ascent Pulse Engineering*)](https://github.com/JuliaQuantumControl/GRAPE.jl) | |
[KhanejaJMR2005;FouquieresJMR2011](@cite). | |
This concludes the document. | |
""" | |
function parse_md_string(mdsrc) | |
mdpage = Markdown.parse(mdsrc) | |
return convert(MarkdownAST.Node, mdpage) | |
end | |
mdast = parse_md_string(MD) | |
println("====== IN =======") | |
println("AS AST:") | |
@show mdast | |
println("AS TEXT:") | |
print(string(convert(Markdown.MD, mdast))) | |
println("=== TRANSFORM ===") | |
replace!(mdast) do node | |
if node.element == MarkdownAST.Link("@cite", "") | |
text = first(node.children).element.text # assume no nested markdown | |
keys = [strip(key) for key in split(text, ";")] | |
n = length(keys) | |
if n == 1 | |
k = keys[1] | |
new_md = "[[$k]](references.md#$k)" | |
else | |
k1 = keys[1] | |
k2 = keys[end] | |
if n > 2 | |
new_md = "[[$k1](references.md#$k1)-[$k2](references.md#$k2)]" | |
else | |
new_md = "[[$k1](references.md#$k1), [$k2](references.md#$k2)]" | |
end | |
end | |
return Documenter.mdparse(new_md; mode=:span) | |
# We probably wouldn't want to use `Documenter`, but it shouldn't be | |
# hard to copy in a stripped-down version of `mdparse` here. | |
elseif node.element == MarkdownAST.Strong() | |
# Not sure if `copy_tree(f, node)` is really the most elegant way to do | |
# this, but I wanted to try out how `copy_tree` can modify a node's | |
# `element`. | |
return MarkdownAST.copy_tree(node) do node, element | |
element == MarkdownAST.Strong() ? MarkdownAST.Emph() : element | |
end | |
else | |
return node | |
end | |
end | |
println("====== OUT =======") | |
println("AS AST:") | |
@show mdast | |
println("AS TEXT:") | |
print(string(convert(Markdown.MD, mdast))) | |
println("====== END =======") | |
# TEST 2: delete links (example from the docstring) ########################## | |
println("\n\n=====================================") | |
println("TEST2: ORIGINAL MD WITH LINKS REMOVED") | |
mdast = parse_md_string(MD) | |
replace!(mdast) do node | |
if node.element isa MarkdownAST.Link | |
return [MarkdownAST.copy_tree(child) for child in node.children] | |
else | |
return node | |
end | |
end | |
print(string(convert(Markdown.MD, mdast))) | |
println("====== END =======") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Output: