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 ( [$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, (, " ", , $todo)) else if (local-name($item) = "ENCORE" and $item = "") then b:break-lines($done, (, " ", , $todo)) else b:break-lines ( $done, ( , let $data := replace(data($item), "^#line [0-9]+ ""[^""]*""\s+", "") return if (not(contains($data, " "))) then concat("") else ( concat(", b:re-indent($data, $b:t2 + 2), , "?>" ), , $todo ) ) default return error(xs:QName("b:break-lines"), concat("invalid input type: ", string($item))) }; b:break-lines((), /root/node())