Project

Profile

Help

Bug #4502 » ast-to-ebnf.xq

Gunther Rademacher, 2020-03-26 20:41

 
declare namespace b="de/bottlecaps/railroad/xq/ast-to-ebnf.xq";

(:~
: The indentation in front of continuation line content.
:)
declare variable $b:t1 := 12;
declare variable $b:t2 := 40;

(:~
: Replace indentation of a block of text by a fixed number of spaces.
:
: @param $text
: @param $indent
:)
declare function b:re-indent($text as xs:string, $indent as xs:integer) as xs:string?
{
let $trimmed := replace($text, "(^\s+)|(\s+$)", "")
let $lines := tokenize($trimmed, "
")
where exists($lines)
return
let $min-indent :=
min
(
for $line in $lines[position() > 1]
where normalize-space($line) != ""
return string-length($line) - string-length(replace($line, "^ +", ""))
)
let $old-indent := string-join(("^", for $x in 1 to $min-indent return " "), "")
let $new-indent := string-join(for $x in 1 to $indent return " ", "")
return
string-join
(
(
$lines[1],
for $line in $lines[position() > 1]
return
(
if (normalize-space($line) = "") then
""
else if ($old-indent = "^") then
concat($new-indent, $line)
else
replace($line, $old-indent, $new-indent)
)
),
"
"
)
};

(:~
: Render a sequence of nodes, returning a single string. Recursively pass
: grammar fragments from the todo list to the done list.
:
: @param $done the sequence of lines (strings) already rendered.
: @param $todo the sequence of nodes to be rendered.
: @return the rendered result.
:)
declare function b:break-lines($done, $todo) as element(ebnf)
{
if (empty($todo)) then
element ebnf
{
for $d at $i in $done
return (<lf>&#xA;</lf>[$i > 1], $d)
}
else
let $item := $todo[1]
let $todo := $todo[position() > 1]
return
typeswitch ($item)
case element(tab) return
let $n := xs:integer($item/@col - string-length($done[last()]) - 1)
return
if ($n = 0) then
b:break-lines($done, $todo)
else if ($n > 0) then
let $spaces := string-join(for $i in (1 to $n) return " ", "")
return b:break-lines(($done[position() < last()], element line {$done[last()]/node(), text{$spaces}}), $todo)
else if (normalize-space($done[last()]) != "") then
let $spaces := string-join(for $i in (1 to xs:integer($item/@col) - 1) return " ", "")
return b:break-lines(($done, element line {$spaces}), $todo)
else
let $spaces := string-join(for $i in (1 to xs:integer($item/@col) - 1) return " ", "")
return b:break-lines(($done[position() < last()], element line {$spaces}), $todo)
case element(fragment) return
b:break-lines
(
(
$done[position() < last()],
element line{$done[last()]/node(), text{" "}[$done[last()] != ""], $item/node()}
),
$todo
)
case element(name) return
b:break-lines
(
(
$done[position() < last()],
element line{$done[last()]/node(), text{" "}[$done[last()] != ""], $item}
),
$todo
)
case text() return
b:break-lines
(
(
$done[position() < last()],
element line{$done[last()]/node(), text{" "}[$done[last()] != ""], $item}
),
$todo
)
case xs:string return
b:break-lines
(
(
$done[position() < last()],
element line{$done[last()]/node(), text{" "}[$done[last()] != ""], text{$item}}
),
$todo
)
case processing-instruction() return
if (local-name($item) = "TOKENS" and $item = "") then
b:break-lines($done, (<tab col="1"/>, "&#xa;<?TOKENS?>&#xa;", <tab col="1"/>, $todo))
else if (local-name($item) = "ENCORE" and $item = "") then
b:break-lines($done, (<tab col="1"/>, "&#xa;<?ENCORE?>", <tab col="1"/>, $todo))
else
b:break-lines
(
$done,
(
<tab col="{$b:t2}"/>,
let $data := replace(data($item), "^#line [0-9]+ ""[^""]*""\s+", "")
return
if (not(contains($data, "&#xa;"))) then
concat("<?", local-name($item), " "[$data], $data, "?>")
else
(
concat("<?", local-name($item)),
<tab col="{$b:t2 + 2}"/>,
b:re-indent($data, $b:t2 + 2),
<tab col="{$b:t2}"/>,
"?>"
),
<tab col="{$b:t1 + 1}"/>,
$todo
)
)
default return
error(xs:QName("b:break-lines"), concat("invalid input type: ", string($item)))
};

b:break-lines((), /root/node())
(2-2/3)