2020-10-30 13:25:48 +00:00
|
|
|
|
package markup
|
2020-08-05 15:08:59 +00:00
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"path"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const xclError = -9
|
|
|
|
|
|
2020-10-30 13:25:48 +00:00
|
|
|
|
// Transclusion is used by markup parser to remember what hyphae shall be transcluded.
|
2020-08-05 15:08:59 +00:00
|
|
|
|
type Transclusion struct {
|
|
|
|
|
name string
|
|
|
|
|
from int // inclusive
|
|
|
|
|
to int // inclusive
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Transclude transcludes `xcl` and returns html representation.
|
|
|
|
|
func Transclude(xcl Transclusion, state GemParserState) (html string) {
|
|
|
|
|
state.recursionLevel++
|
|
|
|
|
tmptOk := `<section class="transclusion transclusion_ok">
|
|
|
|
|
<a class="transclusion__link" href="/page/%s">%s</a>
|
|
|
|
|
<div class="transclusion__content">%s</div>
|
|
|
|
|
</section>`
|
|
|
|
|
tmptFailed := `<section class="transclusion transclusion_failed">
|
|
|
|
|
<p>Failed to transclude <a href="/page/%s">%s</a></p>
|
|
|
|
|
</section>`
|
|
|
|
|
if xcl.from == xclError || xcl.to == xclError || xcl.from > xcl.to {
|
|
|
|
|
return fmt.Sprintf(tmptFailed, xcl.name, xcl.name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rawText, binaryHtml, err := HyphaAccess(xcl.name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Sprintf(tmptFailed, xcl.name, xcl.name)
|
|
|
|
|
}
|
|
|
|
|
xclText := Parse(lex(xcl.name, rawText), xcl.from, xcl.to, state)
|
|
|
|
|
return fmt.Sprintf(tmptOk, xcl.name, xcl.name, binaryHtml+xclText)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Grammar from hypha ‘transclusion’:
|
|
|
|
|
transclusion_line ::= transclusion_token hypha_name LWS* [":" LWS* range LWS*]
|
|
|
|
|
transclusion_token ::= "<=" LWS+
|
|
|
|
|
hypha_name ::= canonical_name | noncanonical_name
|
|
|
|
|
range ::= id | (from_id two_dots to_id) | (from_id two_dots) | (two_dots to_id)
|
|
|
|
|
two_dots ::= ".."
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
func parseTransclusion(line, hyphaName string) (xclusion Transclusion) {
|
|
|
|
|
line = strings.TrimSpace(remover("<=")(line))
|
|
|
|
|
if line == "" {
|
|
|
|
|
return Transclusion{"", xclError, xclError}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if strings.ContainsRune(line, ':') {
|
|
|
|
|
parts := strings.SplitN(line, ":", 2)
|
|
|
|
|
xclusion.name = xclCanonicalName(hyphaName, strings.TrimSpace(parts[0]))
|
|
|
|
|
selector := strings.TrimSpace(parts[1])
|
|
|
|
|
xclusion.from, xclusion.to = parseSelector(selector)
|
|
|
|
|
} else {
|
|
|
|
|
xclusion.name = xclCanonicalName(hyphaName, strings.TrimSpace(line))
|
|
|
|
|
}
|
|
|
|
|
return xclusion
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func xclCanonicalName(hyphaName, xclName string) string {
|
|
|
|
|
switch {
|
|
|
|
|
case strings.HasPrefix(xclName, "./"):
|
|
|
|
|
return canonicalName(path.Join(hyphaName, strings.TrimPrefix(xclName, "./")))
|
|
|
|
|
case strings.HasPrefix(xclName, "../"):
|
|
|
|
|
return canonicalName(path.Join(path.Dir(hyphaName), strings.TrimPrefix(xclName, "../")))
|
|
|
|
|
default:
|
|
|
|
|
return canonicalName(xclName)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// At this point:
|
|
|
|
|
// selector ::= id
|
|
|
|
|
// | from ".."
|
|
|
|
|
// | from ".." to
|
|
|
|
|
// | ".." to
|
|
|
|
|
// If it is not, return (xclError, xclError).
|
|
|
|
|
func parseSelector(selector string) (from, to int) {
|
|
|
|
|
if selector == "" {
|
|
|
|
|
return 0, 0
|
|
|
|
|
}
|
|
|
|
|
if strings.Contains(selector, "..") {
|
|
|
|
|
parts := strings.Split(selector, "..")
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
fromStr = strings.TrimSpace(parts[0])
|
|
|
|
|
from, fromErr = strconv.Atoi(fromStr)
|
|
|
|
|
toStr = strings.TrimSpace(parts[1])
|
|
|
|
|
to, toErr = strconv.Atoi(toStr)
|
|
|
|
|
)
|
|
|
|
|
if fromStr == "" && toStr == "" {
|
|
|
|
|
return 0, 0
|
|
|
|
|
}
|
|
|
|
|
if fromErr == nil || toErr == nil {
|
|
|
|
|
return from, to
|
|
|
|
|
}
|
|
|
|
|
} else if id, err := strconv.Atoi(selector); err == nil {
|
|
|
|
|
return id, id
|
|
|
|
|
}
|
|
|
|
|
return xclError, xclError
|
|
|
|
|
}
|