Skip to content

Commit

Permalink
vue parsing: support flexgrid kwargs for row, column and cell; add co…
Browse files Browse the repository at this point in the history
…ntext
  • Loading branch information
Helmut Hänsel committed Dec 11, 2023
1 parent 2d11c46 commit 49a0044
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 26 deletions.
198 changes: 174 additions & 24 deletions src/StippleUIParser.jl
Original file line number Diff line number Diff line change
Expand Up @@ -106,23 +106,29 @@ end

k => v
end
function function_parser(tag, attrs, context = @__MODULE__)

function function_parser(tag, attrs; context = @__MODULE__)
tag_str = replace(String(typeof(tag).parameters[1]), "-" => "__")
julia_str = String(typeof(tag).parameters[1])
julia_fn = Symbol(replace(startswith(julia_str, "q-") ? julia_str[3:end] : julia_str, "-" => ""))
M = if isdefined(Stipple, julia_fn)
Stipple
elseif isdefined(StippleUI, julia_fn)
M = if isdefined(StippleUI, julia_fn)
StippleUI
elseif isdefined(Stipple, julia_fn)
Stipple
elseif isdefined(context, julia_fn)
context
else
nothing
end

# if tag_str == "q__layout"
# julia_str = Symbol("StippleUI.layout")
# M = StippleUI
# end

arg_str = ""
is_html_tag = false
f = nothing
if M !== nothing && tag_str != "q__input"
f = getfield(M, julia_fn)
attr_names = rstrip.(keys(attrs), '!')
Expand All @@ -140,6 +146,34 @@ function function_parser(tag, attrs, context = @__MODULE__)
end
end

# parse flexgrid attributes
if !is_html_tag || tag_str in ("div", "col", "row", "cell")
kk = String.(collect(keys(attrs)))
pos = findfirst(startswith(r":?class$"), kk)
if pos !== nothing
k = kk[pos]
v = attrs[k]
v_new, removed = remove_class(v, r"^col(-..)?(-(\d+|auto))?$")

if ! isempty(removed)
if v_new in ("", "''", "R\"''\"")
delete!(attrs, k)
else
attrs[k] = v_new
end
for r in removed
xx = split(r, '-')
(length(xx) == 1 || length(xx) == 2 && xx[2][1] 'a':'z' ) && push!(xx, "0")
if length(xx) == 2
attrs[:col] = xx[2]
else
attrs[Symbol(xx[2])] = xx[3]
end
end
end
end
end

name_dict = LittleDict(zip(attr_names, keys(attrs)))
# if variable accepts Symbols but no Strings convert to Symbol
arg_str = join([!(String <: type_dict[k]) && Symbol <: type_dict[k] ? symrepr(attrs[name_dict[k]]) : attrs[name_dict[k]] for k in args], ", ")
Expand All @@ -153,7 +187,7 @@ function function_parser(tag, attrs, context = @__MODULE__)
index = startswith.(values(attrs), '"')
if ! all(index)
attrs = LittleDict(startswith(v, '"') ? k => v :
(endswith(k, '"') ? string(k[1:end-1], "!\"") : string(k, '!')) => rawrepr(startswith(v, 'R') ? v[3:end-1] : v[2:end])
(endswith("$k", '"') ? string(k[1:end-1], "!\"") : string(k, '!')) => rawrepr(startswith(v, 'R') ? v[3:end-1] : v[2:end])
for (k, v) in attrs
)
end
Expand All @@ -163,7 +197,20 @@ function function_parser(tag, attrs, context = @__MODULE__)
fn_str = if M === nothing || tag_str == "q__input"
startswith(tag_str, "q__") ? "quasar(:$(tag_str[4:end]), " : startswith(tag_str, "vue__") ? "vue(:$(tag_str[6:end]), " : "xelem(:$tag_str, "
else
julia_fn == :div && (julia_fn = :htmldiv)
if julia_fn == :div
julia_fn = :htmldiv
elseif M == Stipple
# if a function is defined in both Stipple and context and if they are not identical, prefix it with `Stipple.`
if isdefined(context, julia_fn) && getfield(context, julia_fn) != f
julia_fn = Symbol("Stipple.$julia_fn")
end
elseif M == StippleUI
# if a function is defined in both StippleUI and Stipple or context and if they are not identical, prefix it with `StippleUI.`
if isdefined(Stipple, julia_fn) && getfield(Stipple, julia_fn) != f ||
isdefined(context, julia_fn) && getfield(context, julia_fn) != f
julia_fn = Symbol("StippleUI.$julia_fn")
end
end
"$julia_fn("
end

Expand All @@ -178,7 +225,7 @@ function attr_to_paramstring(attr::Pair)
"$(attr[1])=\"$(attr[2])\""
end

function node_to_stipple(el::EzXML.Node, level = 0; @nospecialize(indent::Union{Int, String} = 4), pre::Bool = false, vec_sep::String = ",\n")
function node_to_stipple(el::EzXML.Node, level = 0; @nospecialize(indent::Union{Int, String} = 4), pre::Bool = false, vec_sep::String = ",\n", context = @__MODULE__)
startlevel = level
level < 0 && (level = 0)

Expand All @@ -205,11 +252,11 @@ function node_to_stipple(el::EzXML.Node, level = 0; @nospecialize(indent::Union{
arg_str = ""
attrs = attr_dict(stipple_attr, el)

fn_str, arg_str, new_attrs = function_parser(Val(Symbol(el.name)), attrs)
fn_str, arg_str, new_attrs = function_parser(Val(Symbol(el.name)), attrs; context)

attr_str = join(attr_to_kwargstring.(collect(new_attrs)), ", ")

children = node_to_stipple.(nodes(el), startlevel + 1; indent, pre, vec_sep)
children = node_to_stipple.(nodes(el), startlevel + 1; indent, pre, vec_sep, context)
children = children[length.(children) .> 0]
children_str = join(children, vec_sep)

Expand Down Expand Up @@ -283,14 +330,15 @@ function node_to_html(el::EzXML.Node, level = 0; @nospecialize(indent::Union{Int
end

"""
parse_vue_html(html, level = 0; indent::Union{String, Int} = 4, vec_sep::String = ",\n")
parse_vue_html(html, level = 0; indent::Union{String, Int} = 4, vec_sep::String = ",\n", context = @__MODULE__)
Parse html code to Julia/StippleUI code with automatic line breaks and indenting. Indenting can be determined by
- `level: starting level for formatting; negative values are allowed, negative levels are not indented
- `indent`: either Integer for number of ' ' characters per level or a string value
- `vec_sep`: separator in array listings, reasonable values are `",\\n"`, `"\\n\\n"`, `",\\n\\n"`
- `context`: context for evaluation
"""
function parse_vue_html(html; level::Integer = 0, indent::Union{String, Int} = 4, vec_sep::String = ",\n")
function parse_vue_html(html; level::Integer = 0, indent::Union{String, Int} = 4, vec_sep::String = ",\n", context = @__MODULE__)
startlevel = level
level < 0 && (level = 0)

Expand Down Expand Up @@ -320,12 +368,12 @@ function parse_vue_html(html; level::Integer = 0, indent::Union{String, Int} = 4
# remove the html -> body levels

if root == :html
node_to_stipple(root_node, startlevel; indent, vec_sep)
node_to_stipple(root_node, startlevel; indent, vec_sep, context)
else
# remove the html / body levels
children = nodes(root == :no_root ? root_node.firstelement : root_node)
is_single = length(children) <= 1
children_str = node_to_stipple.(children, is_single ? startlevel : startlevel + 1; indent, vec_sep)
children_str = node_to_stipple.(children, is_single ? startlevel : startlevel + 1; indent, vec_sep, context)
replace(is_single ? children_str[1] : "$indent_str[\n$(join(filter(!isempty, children_str), vec_sep))$indent_str\n]", AT_MASK => "@")
end |> ParsedHTMLString
end
Expand Down Expand Up @@ -376,11 +424,11 @@ prettify(doc::EzXML.Document; level::Int = 0, indent::Union{String, Int} = 4) =

prettify(v::Vector; level::Int = 0, indent::Union{String, Int} = 4) = prettify(join(v); level, indent)

function function_parser(tag::Val{Symbol("q-input")}, attrs, context = @__MODULE__)
function function_parser(tag::Val{Symbol("q-input")}, attrs; context = @__MODULE__)
kk = String.(collect(keys(attrs)))
pos = findfirst(startswith(r"fieldname$|var\"v-model."), kk)
if pos === nothing
function_parser(Val(:q__input), attrs)
function_parser(Val(:q__input), attrs; context)
else
haskey(attrs, "label") || (attrs["label"] = "\"\"")
k = kk[pos]
Expand All @@ -389,32 +437,134 @@ function function_parser(tag::Val{Symbol("q-input")}, attrs, context = @__MODULE
attrs["fieldname"] = v

if k == "var\"v-model.number\""
function_parser(Val(:numberfield), attrs)
function_parser(Val(:numberfield), attrs; context)
else
function_parser(Val(:textfield), attrs)
function_parser(Val(:textfield), attrs; context)
end
end
end

function contains_class(class::String, subclass::Union{String, Regex})
# removes any quotation ("<...>", R"<...>") and stores the captured parts for concatentation at the end
m = match(r"^(R?\"+)?(.*?)(\"+)?$", class)
class = m.captures[2]
isreactive = m.captures[1] !== nothing && contains(m.captures[1], "R")

classes = isreactive ? split(class, r"\s*\+\s*") : [class]
for (n, class) in enumerate(classes)
isreactive && first(class) != ''' && continue
# only evaluate explicit terms when they are at the beginning or when their first character is a whitespace
n > 1 && length(class) > 1 && !contains(class[2:2], r"\s") && continue
cc = split(strip(class, '''))
for c in cc
(subclass isa String ? subclass == c : contains(c, subclass)) && return true
end
end
false
end

function remove_class(class::String, subclass::Union{String, Regex})
removed = String[]
# removes any quotation ("<...>", R"<...>") and stores the captured parts for concatentation at the end
m = match(r"^(R?\"+)?(.*?)(\"+)?$", class)
class = m.captures[2]
isquoted = m.captures[1] !== nothing

isreactive = m.captures[1] !== nothing && contains(m.captures[1], "R")
classes = isreactive ? split(class, r"\s*\+\s*") : [class]
for n in length(classes):-1:1
class = classes[n]
isreactive && first(class) != ''' && continue
# only evaluate explicit terms when they are at the beginning or when their first character is a whitespace
n > 1 && length(class) > 1 && !contains(class[2:2], r"\s") && continue
cc = split(strip(class, '''))
remove_outer = false
for i in length(cc):-1:1
remove = false
c = cc[i]
if subclass isa String ? subclass == c : contains(c, subclass)
pushfirst!(removed, c) # because of inverted
deleteat!(cc, i)
remove_outer = remove = true
end
end

if remove_outer
classes[n] = join(cc, ' ')
first(class) == ''' && (classes[n] = "' " * classes[n] * ''')
end
classes[n] in ("", "''", "' '") && deleteat!(classes, n)
end

c = join(classes, " + ")
c == "" && isreactive && (c = "''")

if isquoted
c = m.captures[1] * c * m.captures[3]
if c[1] == 'R'
x = repr(Symbol(c[3:end-1]))
startswith(x, ":") && (c = x)
end
end

return c, removed
end

function function_parser(tag::Val{:div}, attrs, context = @__MODULE__)
kk = String.(collect(keys(attrs)))
pos = findfirst(startswith(r":?class$"), kk)
if pos === nothing
function_parser(Val(:htmldiv), attrs; context)
else
k = kk[pos]
v = attrs[k]
if k == "class"
for c in ("row", "column", r"^col(-..)?(-(\d+|auto))?$")
tagname = c isa Regex ? :cell : Symbol(c)
v_new, removed = remove_class(v, c)
isempty(removed) && continue

if c isa Regex && ! isempty(removed)
# if there is at least one col element check if "st-col" is part of the class
v_new, removed = remove_class(v, "st-col")
isempty(removed) && continue
# remove "col" from the class
v_new, removed = remove_class(v_new, "col")
end

if v_new[2:end-1] in ("", "''")
delete!(attrs, k)
else
attrs[k] = v_new
end

return function_parser(Val(tagname), attrs; context)
end
end
function_parser(Val(:htmldiv), attrs; context)
end
end

"""
test_vue_parsing(html_string; prettify::Bool = true, level = 0, indent = 4)
test_vue_parsing(html_string; prettify::Bool = true, level = 0, indent = 4, context = @__MODULE__)
Parse html code with automatic line breaks and indentingto Julia/StippleUI code, execute the code and prettify the result.
Indenting can be determined by
- level: starting level for formatting; negative values are allowed, negative levels are not indented
- indent: either Integer for number of ' ' characters per level or a string value
- `level`: starting level for formatting; negative values are allowed, negative levels are not indented
- `indent`: either Integer for number of ' ' characters per level or a string value
- `context`: context for evaluation
"""
function test_vue_parsing(html_string; prettify::Bool = true, level = 0, indent = 4)
function test_vue_parsing(html_string; prettify::Bool = true, level = 0, indent = 4, context = @__MODULE__)
println("\nOriginal HTML string:")
printstyled(html_string, "\n\n", color = :light_red)

julia_code = parse_vue_html(html_string; level, indent)
julia_code = parse_vue_html(html_string; level, indent, context)

println("Julia code:")
printstyled(julia_code, "\n\n", color = :blue)

println("Produced HTML:")
new_html = eval(Meta.parse(julia_code))
new_html = Core.eval(context, Meta.parse(julia_code))
printstyled(prettify ? StippleUIParser.prettify(new_html; level, indent) : new_html, "\n", color = :green)
end

Expand Down
4 changes: 2 additions & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ end
<p> Vowel count: {{vowels}} vowels.</p>
</div>"""

result = """Stipple.Html.div([
result = """htmldiv([
textfield("", :message, ),
br(),
p(
Expand All @@ -37,7 +37,7 @@ end

@test parse_vue_html(doc_string) == result

result = """Stipple.Html.div([
result = """htmldiv([
textfield("", :message, )
br()
Expand Down

0 comments on commit 49a0044

Please sign in to comment.