package Modules import ( "bytes" "encoding/base64" "encoding/json" "maps" "os" "path/filepath" "regexp" "runtime/debug" "slices" "strconv" "strings" ) type ItemMark struct { from string to string pattern *regexp.Regexp } type WMDConstants struct { // Languages HTML int // Analyse modes RAW int SUBITEM int LINKED int html_special_characters map[string]string quote_special map[string]string item_mark ItemMark } type PatternMatch struct { String string Groups []string Span []int Ok bool } type WMDModule struct { mode int patterns []*regexp.Regexp pattern_method func(data string, patterns []*regexp.Regexp) *PatternMatch method func(matches *PatternMatch, language int, path string) string } type WMDModulePattern struct { patterns []string method func(data string, patterns []*regexp.Regexp) *PatternMatch } type WMarkDown struct { keys []string modules map[string]*WMDModule ids_cache []string } type WMDAnalyseItem struct { exists bool from int to int matches *PatternMatch } type PatternsModel struct { RE_BLOCK_HTML *regexp.Regexp RE_BLOCK_MARK *regexp.Regexp RE_NEW_LINES *regexp.Regexp RE_NEW_LINE *regexp.Regexp RE_STARTED_WHITE_SPACES *regexp.Regexp RE_LINES *regexp.Regexp RE_LIST_LINE *regexp.Regexp RE_TABLE_LINE *regexp.Regexp RE_TABLE_ITEM *regexp.Regexp RE_OPTIONS *regexp.Regexp RE_OPTION *regexp.Regexp RE_EXTENSION *regexp.Regexp RE_CHARACTERS_NO_ID *regexp.Regexp RE_PHONE_NUMBER *regexp.Regexp RE_EMAIL_ADDRESS *regexp.Regexp RE_CLASS_ATTRIBUTE *regexp.Regexp RE_WHITE_SPACES *regexp.Regexp RE_LINE_MARKS *regexp.Regexp RE_INTEGER *regexp.Regexp RE_CODE_DOC *regexp.Regexp RE_CODE_DOC_ARGUMENTS *regexp.Regexp RE_CODE_DOC_SUBARGUMENTS *regexp.Regexp RE_START_WITH_WHITE_SPACES *regexp.Regexp RE_DOMAIN *regexp.Regexp RE_PROTOCOL *regexp.Regexp RE_TRACE_STACK *regexp.Regexp } type WMDListLevel struct { separator int _type byte subtype string } type ModulePresentationLinkItem struct { title string avatars []string links []string } var RE_BLOCK_HTML_PATTERN string = `^[ \t]*<\!\-{2}[ \t]*\[{2}[ \t]*(wmd|markdown)[ \t]*\]{2}[ \t]*\-{2}>` var WMD WMDConstants = WMDConstants{ // Languages HTML: 1 << 0, // Analyse modes RAW: 0, SUBITEM: 1 << 0, LINKED: 1 << 1, html_special_characters: map[string]string{ "<": "lt", ">": "gt", "&": "amp", }, quote_special: map[string]string{ "?": "ask", "!": "warning", ">": "answer", "Q": "comment", "#": "note", }, item_mark: ItemMark{ from: "###@&&_", to: "_&&@###", pattern: regexp.MustCompile(`\#{3}\@\&{2}_([0-9]+)_\&{2}\@\#{3}`), }, } var Patterns PatternsModel = PatternsModel{ RE_BLOCK_HTML: regexp.MustCompile(`(?mi)` + RE_BLOCK_HTML_PATTERN), RE_BLOCK_MARK: regexp.MustCompile(`(?mi)^[ \t]*((" ?){3}|(\' ?){3}|(` + "`" + ` ?){3})[ \t]*(wmd|wmarkdown)|` + RE_BLOCK_HTML_PATTERN), RE_NEW_LINES: regexp.MustCompile(`[\r\n]+`), RE_NEW_LINE: regexp.MustCompile(`\n|\r\n|\r`), RE_STARTED_WHITE_SPACES: regexp.MustCompile(`^[ \t]*`), RE_LINES: regexp.MustCompile(`(?m)^[^\r\n]+`), RE_LIST_LINE: regexp.MustCompile(`(?mi)^([ \t]*)(([\-\*#\+]+)|([0-9]+|[a-z]+)\.)?(.*)$`), RE_TABLE_LINE: regexp.MustCompile(`(?m)^\|([^\r\n"\']+|"([^"\\\\]+|\\\\(.|[\r\n])|[\r\n]+)*"|\'([^\'\\\\]+|\\\\(.|[\r\n])|[\r\n]+)*\')*[\r\n]*`), RE_TABLE_ITEM: regexp.MustCompile(`(\|+)(([^\|\'\"]+|"([^"\\\\]+|\\\\(.|[\r\n])|[\r\n]+)*"|\'([^\'\\\\]+|\\\\(.|[\r\n])|[\r\n]+)*\')*)`), RE_OPTIONS: regexp.MustCompile("(?im)^`" + `{3}[ \t]*w(?:md|markdown)-options[ \t]*(?:\n|\r\n|\r)((?:[^\r\n` + "`" + `]+|[\r\n]+|` + "`{1,2}[^`])*)`{3}"), RE_OPTION: regexp.MustCompile(`(?m)^([^=\r\n]+)=([^\r\n]*)$`), RE_EXTENSION: regexp.MustCompile(`(?:([^\.\/\\\\]+)\.)?([^\.\/\\\\]+)$`), RE_CHARACTERS_NO_ID: regexp.MustCompile(`(?i)[^a-z0-9]+`), RE_PHONE_NUMBER: regexp.MustCompile(`^\+?[0-9 ]{5,22}$`), RE_EMAIL_ADDRESS: regexp.MustCompile(`^[a-z\.0-9_\-]+@[a-z\.0-9_\-]+\.[a-z0-9]+$`), RE_CLASS_ATTRIBUTE: regexp.MustCompile(`(?i)([^a-z0-9_\-]|^)(class=")([^"]+)(")`), RE_WHITE_SPACES: regexp.MustCompile(`[ \t]+`), RE_LINE_MARKS: regexp.MustCompile(`(?i)[\*#~\-_]+|\[[ \-x]\]|\([ \-x]\)`), RE_INTEGER: regexp.MustCompile(`^[0-9]+$`), RE_CODE_DOC: regexp.MustCompile(`^(\[([^\]]+)\][ \t]+)?([\!\?=\&\*]{0,3})(([^\(\[\]\?\!]+)([\:\.]))([^\(\.\:\[\]\=]+)(\(((.+|[\r\n]+)*)\)|[ \t]*=[ \t]*((.+|[\r\n]+)*))?`), RE_CODE_DOC_ARGUMENTS: regexp.MustCompile(`([\!\?\&\*]{0,2})([^=]+)[ \t]+([^=]+)(=(.+))?`), RE_CODE_DOC_SUBARGUMENTS: regexp.MustCompile(`<([^<>]+)>|("[^"]*"|\'[^\']*\')`), RE_START_WITH_WHITE_SPACES: regexp.MustCompile(`(?m)^[ \t]`), RE_DOMAIN: regexp.MustCompile(`^[^\:]+\:\/{2}[^\/]+`), RE_PROTOCOL: regexp.MustCompile(`(?i)^([a-z0-9_\-]+)\:`), RE_TRACE_STACK: regexp.MustCompile(`[\n\r](([^\.\(\s]+|\..)+)\([^\n\r]+[\r\n]+\s+([^\:\s]+)\:([0-9]+)`), } var MAXIMUM_INTEGER int = 2<<31 - 1 var LIST_MARKS []byte = []byte{'-', '*', '+'} var LIST_LATIN_MARKS []byte = []byte{'a', 'A', 'i', 'I'} var TABLE_MARKS []byte = []byte{'^', '_', '='} func (_self *PatternMatch) GetByGroup(is []int, _default string) string { var l int = len(_self.Groups) for _, i := range is { if i < l && _self.Groups[i] != "" { return _self.Groups[i] } } return _default } func (_self *PatternsModel) GetMatches(_string string, groups [][]byte) *PatternMatch { var binary []byte = []byte(_string) var matches PatternMatch if groups != nil { var index int = bytes.Index(binary, groups[0]) matches.String = _string matches.Groups = []string{} matches.Span = []int{index, index + len(groups[0])} matches.Ok = true for _, group := range groups { matches.Groups = append(matches.Groups, string(group)) } } return &matches } func NewDebugStackLineModel(line int, file string, method string) DebugStackLineModel { return DebugStackLineModel{ Line: line, File: file, Method: method, } } func NewDebugStackLineFromMatch(match *PatternMatch) DebugStackLineModel { line, _ := strconv.ParseInt(match.Groups[4], 10, 32) return NewDebugStackLineModel(int(line), match.Groups[3], match.Groups[1]) } func GetTraceStack(i int) []DebugStackLineModel { var stack_map []DebugStackLineModel = []DebugStackLineModel{} Patterns.Replace(string(debug.Stack()), Patterns.RE_TRACE_STACK, func(matches *PatternMatch) string { stack_map = append(stack_map, NewDebugStackLineFromMatch(matches)) return "" }) return stack_map[i+2:] } func DebugPrint(items ...any) { var stack DebugStackLineModel = GetTraceStack(1)[0] var json_data []byte json_data, _ = json.Marshal(items) println(">[DEV]>[" + strconv.FormatInt(int64(stack.Line), 10) + "]" + stack.File + "(" + stack.Method + "): " + string(json_data)) } func (_self *PatternsModel) Match(_string string, pattern *regexp.Regexp) *PatternMatch { return _self.GetMatches(_string, pattern.FindSubmatch([]byte(_string))) } func (_self *PatternsModel) Replace(_string string, pattern *regexp.Regexp, new_value any) string { var callback func(matches *PatternMatch) string var processed string = "" var binary []byte = []byte(_string) switch value := new_value.(type) { case func(matches *PatternMatch) string: callback = value case string: callback = func(matches *PatternMatch) string { return value } } for { var matches *PatternMatch = _self.Match(string(binary), pattern) if !matches.Ok { break } processed += string(binary[:matches.Span[0]]) + callback(matches) binary = binary[matches.Span[1]:] } return processed + string(binary) } func NewWMDModule(method func(matches *PatternMatch, language int, path string) string, mode int, pattern any) *WMDModule { var wmdmodule WMDModule = WMDModule{ mode: mode, method: method, } switch value := pattern.(type) { case string: wmdmodule.patterns = []*regexp.Regexp{regexp.MustCompile(value)} wmdmodule.pattern_method = func(data string, patterns []*regexp.Regexp) *PatternMatch { return Patterns.Match(data, patterns[0]) } case WMDModulePattern: wmdmodule.patterns = []*regexp.Regexp{} for _, pattern := range value.patterns { wmdmodule.patterns = append(wmdmodule.patterns, regexp.MustCompile(pattern)) } wmdmodule.pattern_method = value.method } return &wmdmodule } func NewWMDModulePattern(patterns []string, method func(data string, patterns []*regexp.Regexp) *PatternMatch) WMDModulePattern { return WMDModulePattern{ patterns: patterns, method: method, } } func BoldOrItalicModelMethod(data string, patterns []*regexp.Regexp) *PatternMatch { var matches *PatternMatch = Patterns.Match(data, patterns[0]) if matches.Ok { if matches.Groups[1] != "" { matches.String = matches.String[1:] matches.Groups[0] = matches.Groups[0][1:] matches.Span[0]++ } matches.Groups = []string{matches.Groups[0], matches.Groups[2]} } return matches } func check_html_module(_type string, matches *PatternMatch) string { var class_type string = _type var icon_name string = _type var input_type string = _type var disabled string = "" var checked string = "" switch _type { case "radio": class_type = "radio-button" icon_name = "radio_button" case "tick": input_type = "checkbox" } if matches.Groups[1] != "" { disabled = " disabled" } if matches.Groups[2] != "" { checked = " checked" } return `` } func ModuleCheckbox(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { return check_html_module("checkbox", matches) } return "" } func ModuleRadio(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { return check_html_module("radio", matches) } return "" } func ModuleTick(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { return check_html_module("tick", matches) } return "" } func ModuleExclude(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { return `` + matches.Groups[1] + `` } return "" } func ModuleSpecialCharacters(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { return matches.Groups[1] } return "" } func GetDirectory(path string) string { stat, _ := os.Stat(path) if stat.IsDir() { return path } return filepath.Dir(path) } func PathExists(path string) bool { _, error := os.Stat(path) return error == nil } func LoadFile(path string) string { var data string = "" if stat, error := os.Stat(path); error == nil && !stat.IsDir() { bytes, _ := os.ReadFile(path) data = string(bytes) } return string(data) } func Base64Encode(_string string) string { return base64.StdEncoding.EncodeToString([]byte(_string)) } func JSONEncode(data any) string { json, _ := json.Marshal(data) return string(json) } func ModuleColorSample(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { var color = matches.GetByGroup([]int{2, 3}, "") return `` + `` + `` + color + `` + `` } return "" } func BuildHTMLImage(links []string, _type string, title string) string { var attributes string = `` if title != "" { attributes = ` title="` + title + `" alt="` + title + `"` } var html string = `` + `` + `` + `` if title != "" { html += `` + title + `` } return html + `` } func ModulePresentationLinks(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { var items []ModulePresentationLinkItem = []ModulePresentationLinkItem{} var i int = -1 var html_avatars string = `` var html_list string = `` for _, line := range Patterns.RE_NEW_LINES.Split(matches.Groups[1], -1) { var data string = Trim(line) if data == "" { continue } var has_spaces bool = Patterns.Match(line, Patterns.RE_STARTED_WHITE_SPACES).Ok if has_spaces { if i == -1 { continue } if data[0] == '*' { items[i].avatars = append(items[i].avatars, data[1:]) if strings.Contains(data, ".k3y.pw/") { items[i].avatars = append(items[i].avatars, strings.ReplaceAll(data[1:], ".k3y.pw/", ".local/")) } } else { items[i].links = append(items[i].links, data) } } else { i += 1 items = append(items, ModulePresentationLinkItem{ title: Trim(line), avatars: []string{}, links: []string{}, }) } } for i, item := range items { html_avatars += `
  • ` if len(item.links) != 0 { html_avatars += `` + BuildHTMLImage(item.avatars, "image", "") + `` } else { html_avatars += BuildHTMLImage(item.avatars, "image", "") } html_list += `
  • ` + `` + item.title + `` + `` + `
  • ` } return `` } return "" } func NewWMarkDown() WMarkDown { var wmarkdown WMarkDown = WMarkDown{ ids_cache: []string{}, keys: []string{ "special_characters", "title", "list", "code_block", "table", "quote", "include", "media", "code_doc", "presentation_links", "code_sample_block", "paragraph", "bold", "italic", "underline", "strike", "code_item", "checkbox", "radio", "tick", "color_sample", "exclude", "link", }, } var code_sample_block_module_pattern WMDModulePattern = NewWMDModulePattern([]string{ `(?i)]*)>`, `(?i)<\/code\-sample\-block>`, }, func(data string, patterns []*regexp.Regexp) *PatternMatch { var pattern *PatternMatch = &PatternMatch{} var matches *PatternMatch = Patterns.Match(data, patterns[0]) if matches.Ok { var from int = matches.Span[0] var internal_string string = data[matches.Span[0]:matches.Span[1]] var attributes string = matches.Groups[1] var fill int = matches.Span[1] data = data[fill:] matches = Patterns.Match(data, patterns[1]) if matches.Ok { internal_string += data[:matches.Span[1]] pattern.Ok = true pattern.Groups = []string{internal_string, attributes, data[:matches.Span[0]]} pattern.Span = []int{from, fill + matches.Span[1]} pattern.String = internal_string } } return pattern }) wmarkdown.modules = map[string]*WMDModule{ "special_characters": NewWMDModule(ModuleSpecialCharacters, WMD.SUBITEM, `\\([\(\{\[\*\\])`), "title": NewWMDModule(wmarkdown.ModuleTitle, WMD.RAW, `(?m)^(?:(#{1,6})[\t ]*([^\r\n]+)|(={1,6})[\t ]*([^\r\n=]+)={1,6})(?:\n|\r\n|\r){2,}`), "list": NewWMDModule(wmarkdown.ModuleList, WMD.RAW, `(?mi)^([\-\*#\+]+|([0-9]+|[a-z]+)\.)[^\r\n]*(\n|\r\n|\r)([\t ]*(([\-\*#\+]+|([0-9]+|[a-z]+)\.)[^\r\n]*)(\n|\r\n|\r)?)*`), "code_block": NewWMDModule(wmarkdown.ModuleCodeBlock, WMD.RAW, `(?m)^(?:"{3}([^\r\n]*)((?:[^"]+|[\r\n]+|"{1,2}[^"])*)"{3}|" " "([^\r\n]*)((?:[^"]+|[\r\n]|"[^ ]|(" ){1,2}[^"])*)" " "|'{3}([^\r\n]*)((?:[^']+|[\r\n]+|'{1,2}[^'])*)'{3}|' ' '([^\r\n]*)((?:[^']+|[\r\n]|'[^ ]|(' ){1,2}[^'])*)' ' '|`+"`{3}([^\\r\\n]*)((?:[^`]+|[\\r\\n]+|`{1,2}[^`])*)`{3}|` ` `([^\\r\\n]*)((?:[^`]+|[\\r\\n]|`[^ ]|(` ){1,2}[^`])*)` ` `)"), "table": NewWMDModule(wmarkdown.ModuleTable, WMD.RAW, `(?m)^\[\|([^\r\n]*)[\r\n]+((^\|([^\]]([^\r\n"\']|"([^"\\\\]+|\\\\(.|[\r\n])|[\r\n]+)*"|\'([^\'\\\\]+|\\\\(.|[\r\n])|[\r\n]+)*\')*)?[\r\n]+)*)^\|\]`), "quote": NewWMDModule(wmarkdown.ModuleQuote, WMD.RAW, `(?m)^>[ \t]*(\[\![\t ]*([^\] \t]+)([ \t]+([^\]]+))?\])?(([^\r\n]+(\r\n|[\r\n])?)*)`), "include": NewWMDModule(wmarkdown.ModuleInclude, WMD.RAW, `(?m)^\[{2}include[ \t]+([^\]]+)\]{2}`), "media": NewWMDModule(wmarkdown.ModuleMedia, WMD.SUBITEM|WMD.LINKED, `(?i)\({2}\!(image|icon|video|audio|sound|picture)[ \t]+("(([^"]+|[\r\n]+))"|\'(([^\']+|[\r\n]+))\'|([^ \t\)]+))([ \t]*|[ \t]+("(([^\\\\"]+|\\\\.|[\r\n]+)*)"|\'(([^\\\\\']+|\\\\.|[\r\n]+)*)\'|([^\)]+)))\){2}`), "code_doc": NewWMDModule(wmarkdown.ModuleCodeDoc, WMD.RAW, `(?m)^\[{2}@(([^\]]+|\][^\]])+)\]{2}`), "presentation_links": NewWMDModule(ModulePresentationLinks, WMD.RAW, `(?m)^\[{2}"{3}(([^"]+|[\r\n]+|"{1,2}[^"])*)"{3}\]{2}`), "code_sample_block": NewWMDModule(wmarkdown.ModuleCodeSampleBlock, WMD.RAW, code_sample_block_module_pattern), "paragraph": NewWMDModule(wmarkdown.ModuleParagraph, WMD.RAW, `(?m)^(([^\r\n]+(\n|\r\n|\r)?)+)`), "bold": NewWMDModule(wmarkdown.ModuleBold, WMD.SUBITEM|WMD.LINKED, NewWMDModulePattern([]string{`(^|[^\\])\*{2}((?:[^\*]+|[\n\r]+|\*[^\*])+\*?)\*{2}`}, BoldOrItalicModelMethod)), "italic": NewWMDModule(wmarkdown.ModuleItalic, WMD.SUBITEM|WMD.LINKED, NewWMDModulePattern([]string{`(^|[^\\])\*((?:[^\*]+|[\r\n]+|\*{2})+(?:\*{2})?)\*`}, BoldOrItalicModelMethod)), "underline": NewWMDModule(wmarkdown.ModuleUnderline, WMD.SUBITEM|WMD.LINKED, `__(([^_]+|_[^_])+)__`), "strike": NewWMDModule(wmarkdown.ModuleStrike, WMD.SUBITEM|WMD.LINKED, `~~(([^~]+|~[^~])+)~~`), "code_item": NewWMDModule(wmarkdown.ModuleCodeItem, WMD.SUBITEM|WMD.LINKED, "``(([^`]+|`[^`])+)``"), "checkbox": NewWMDModule(ModuleCheckbox, WMD.SUBITEM|WMD.LINKED, `(?i)\[(?:(\-)|(x)|( ))\]`), "radio": NewWMDModule(ModuleRadio, WMD.SUBITEM|WMD.LINKED, `(?i)\((?:(\-)|(x)|( ))\)`), "tick": NewWMDModule(ModuleTick, WMD.SUBITEM|WMD.LINKED, `(?i)\{(?:(w)|(v)|(x))\}`), "color_sample": NewWMDModule(ModuleColorSample, WMD.SUBITEM|WMD.LINKED, `(?i)\[{2}((\#[a-f0-9]{3,8})|color[ \t]+([^\[\]\t\r\n]+))\]{2}`), "exclude": NewWMDModule(ModuleExclude, WMD.SUBITEM, `\[{2}\!(([^\]]+|\][^\]]|[\r\n])*)\]{2}`), "link": NewWMDModule(wmarkdown.ModuleLink, WMD.SUBITEM, `(?i)(\[([^\[\]]+)\])?\((([^\(\) \t]+)[ \t]+("(([^\\\\"]+|\\\\.|[\r\n]+)*)"|\'(([^\\\\\']+|\\\\.|[\r\n]+)*)\'|([^\(\)]+)?))[ \t]*\)|\[(\+?[0-9 ]{5,22}|[^ \]]+)([\t ]+([^\]]*))?\]|([a-z0-9]{3,8}\:\/{2}[^ \(\[\)\]>]+|[a-z\.0-9_\-]+@[a-z\.0-9_\-]+\.[a-z0-9]+)`), } return wmarkdown } func (_self *WMarkDown) ModuleDefault(matches *PatternMatch, language int, path string) string { return matches.Groups[0] } func (_self *WMarkDown) Id(_string string) string { var id string = Patterns.Replace(_string, Patterns.RE_CHARACTERS_NO_ID, "-") if id[0] == '-' { id = id[1:] } else if id[len(id)-1] == '-' { id = id[:len(id)-1] } if slices.Contains(_self.ids_cache, id) { var i int = 2 for ; slices.Contains(_self.ids_cache, id+"-"+strconv.FormatInt(int64(i), 10)); i++ { } id += "-" + strconv.FormatInt(int64(i), 10) } _self.ids_cache = append(_self.ids_cache, id) return id } func (_self *WMarkDown) ResetIds() { _self.ids_cache = []string{} } func (_self *WMarkDown) ModuleTitle(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { var level string var content string if matches.Groups[1] != "" { level = matches.Groups[1] } else { level = matches.Groups[3] } level = strconv.FormatInt(int64(len(level)), 10) if matches.Groups[2] != "" { content = strings.ToLower(matches.Groups[2]) } else { content = strings.ToLower(matches.Groups[4]) } return `` + `` + _self.Analyse(content, language, WMD.SUBITEM, path) + `` + `` } return "" } func Trim(_string string) string { return strings.Trim(_string, " \r\t\n") } func (_self *WMarkDown) ModuleParagraph(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { return `

    ` + _self.Analyse(Trim(matches.Groups[0]), language, WMD.SUBITEM, path) + `

    ` } return "" } func (_self *WMarkDown) ModuleBold(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { return `` + _self.Analyse(matches.Groups[1], language, WMD.SUBITEM, path) + `` } return "" } func (_self *WMarkDown) ModuleItalic(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { return `` + _self.Analyse(matches.Groups[1], language, WMD.SUBITEM, path) + `` } return "" } func (_self *WMarkDown) ModuleStrike(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { return `` + _self.Analyse(matches.Groups[1], language, WMD.SUBITEM, path) + `` } return "" } func (_self *WMarkDown) ModuleUnderline(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { return `` + _self.Analyse(matches.Groups[1], language, WMD.SUBITEM, path) + `` } return "" } func (_self *WMarkDown) ModuleCodeItem(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { return `` + _self.Analyse(matches.Groups[1], language, WMD.SUBITEM, path) + `` } return "" } func NewWMDListLevel(separator int, _type byte, subtype string) *WMDListLevel { return &WMDListLevel{ separator: separator, _type: _type, subtype: subtype, } } func list_deployed(level *WMDListLevel, last_mark string, l int) string { var _type string = level.subtype var deployable bool = l != 0 && strings.Contains("+-", _type) var deployed string = "true" var processed string = "" if l != 0 && last_mark != "" { _type = last_mark } if !deployable { deployed = "null" } else { processed = ` data-list-unprocessed="true"` if _type == "-" { deployed = "false" } } return ` data-deployed="` + deployed + `"` + processed } func list_start(unordered bool, _type string) string { if !unordered { _type = _type[:len(_type)-1] if Patterns.Match(_type, Patterns.RE_INTEGER).Ok { return ` start="` + _type + `"` } } return `` } func (_self *WMarkDown) ModuleList(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { var html string = `` var levels []*WMDListLevel = []*WMDListLevel{NewWMDListLevel(0, 'u', "")} var l int = 0 var last_mark string = "" var subtype string = "" for _, line := range Patterns.RE_NEW_LINE.Split(matches.Groups[0], -1) { var matches *PatternMatch = Patterns.Match(line, Patterns.RE_LIST_LINE) if !matches.Ok || matches.String == "" { continue } var separator_by_marks int = len(matches.Groups[3]) var separator int = separator_by_marks var _type string = matches.Groups[2] var key string = matches.Groups[4] var _string string = Trim(_self.Analyse(matches.Groups[5], language, WMD.SUBITEM, path)) var type_character byte = _type[len(_type)-1] var unordered bool = _type != "" && slices.Contains(LIST_MARKS, type_character) if separator_by_marks <= 1 { separator = len(matches.Groups[1]) } if !unordered { levels[0]._type = 'o' } subtype = _type if _type != "" { if levels[l].separator == separator { levels[l].subtype = subtype if html != "" { html += `` } else { html += `<` + string(levels[l]._type) + `l class="wmd-list"` + list_start(unordered, _type) + list_deployed(levels[l], last_mark, l) if key != "" && !unordered && slices.Contains(LIST_LATIN_MARKS, key[0]) { html += ` type="` + string(key[0]) + `"` } html += `>` } html += `
  • ` + _string } else if levels[l].separator < separator { var child_type byte = 'u' if unordered { child_type = 'o' } l++ levels = append(levels, NewWMDListLevel(separator, child_type, subtype)) html += `<` + string(levels[l]._type) + `l class="wmd-list"` + list_start(unordered, _type) + list_deployed(levels[l-1], last_mark, l) if key != "" && !unordered && slices.Contains(LIST_LATIN_MARKS, key[0]) { html += ` type="` + string(key[0]) + `"` } html += `>
  • ` + _string } else { for levels[l].separator > separator { html += `
  • ` levels = levels[:l] l-- if l == 0 { break } } html += `
  • ` + _string } } else { html += ` ` + _string } last_mark = subtype } for l >= 0 { html += `
  • ` levels = levels[:l] l-- } return html } return "" } func (_self *WMarkDown) ModuleTable(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { var html map[string]string = map[string]string{ "thead": ``, "tbody": ``, "tfoot": ``, } var attributes string = Trim(matches.Groups[1]) var data string = Trim(matches.Groups[2]) for { var line_matches *PatternMatch = Patterns.Match(data, Patterns.RE_TABLE_LINE) if !line_matches.Ok || line_matches.Groups[0] == "" { break } var line string = line_matches.Groups[0] var cell_type string = "d" var tags []string var row string = `` if slices.Contains(TABLE_MARKS, line[1]) { cell_type = "h" switch line[1] { case '^': tags = []string{"thead"} case '_': tags = []string{"tfoot"} case '=': tags = []string{"thead", "tfoot"} } } else { tags = []string{"tbody"} } if cell_type == "h" { line = line[:1] + line[2:] } for { var cell_matches *PatternMatch = Patterns.Match(line, Patterns.RE_TABLE_ITEM) if !cell_matches.Ok || cell_matches.Groups[0] == "" { break } var column_span_i int = len(cell_matches.Groups[1]) var column_span string = `` var content string = Trim(cell_matches.Groups[2]) if column_span_i > 1 { column_span = ` colspan="` + strconv.FormatInt(int64(column_span_i), 10) + `"` } if len(content) > 1 && slices.Contains([]string{`''`, `""`}, content[:1]+content[len(content)-1:]) { content = content[1 : len(content)-1] } row += `` + _self.Analyse(content, WMD.HTML, WMD.SUBITEM, path) + `` line = line[cell_matches.Span[1]:] } row += `` for i, tag := range tags { if i == 0 { html[tag] = row + html[tag] } else { html[tag] += row } } data = data[line_matches.Span[1]:] } return `
    ` + `` + `` + html["thead"] + `` + `` + html["tbody"] + `` + `` + html["tfoot"] + `` + `
    ` + `
    ` } return "" } func (_self *WMarkDown) ModuleLink(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { var raw_url string = Trim(matches.GetByGroup([]int{4, 11, 14}, "")) var url string = raw_url var text string = Trim(matches.GetByGroup([]int{2, 13}, raw_url)) var protocol_matches *PatternMatch = Patterns.Match(url, Patterns.RE_PROTOCOL) var html string if Patterns.Match(raw_url, Patterns.RE_PHONE_NUMBER).Ok { url = "tel:" + strings.ReplaceAll(raw_url, " ", "") } else if Patterns.Match(raw_url, Patterns.RE_EMAIL_ADDRESS).Ok { url = "mailto:" + raw_url } html += `` + _self.Analyse(text, language, WMD.SUBITEM|WMD.LINKED, path) + `` } return "" } func (_self *WMarkDown) ModuleQuote(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { var _type string = matches.Groups[2] var type_text string = matches.Groups[4] var html string if value, ok := WMD.quote_special[_type]; ok { _type = value } html = `
    ` + `
    ` if _type[0] == '@' { html += `` + `` + strings.ToLower(_type[1:]) + `` } else { html += `` if type_text != "" { html += `` + type_text + `` } } html += `
    ` } else { html += `>` } return html + `
    ` + _self.Analyse(matches.Groups[5], language, WMD.SUBITEM, path) + `
    ` + `
    ` } return "" } func code_block_data(key string, value string, language int) string { if language&WMD.HTML != 0 { return `
  • ` + `` + `` + key + `` + `` + value + `` + `
  • ` } return "" } func FilterHTMLSpecialCharacters(_string string) string { var l int = len(WMD.html_special_characters) var z int = -(1 << 28) var index []int = []int{} var response string = `` var characters []string = slices.Collect(maps.Keys(WMD.html_special_characters)) for j := 0; j < l; j++ { index = append(index, -1) } for { var i int = -1 for j, character := range characters { if index[j] < z { continue } if index[j] < 0 { index[j] = strings.Index(_string, character) if index[j] == -1 { index[j] = z continue } } if i == -1 || index[i] > index[j] { i = j } } if i == -1 { response += _string break } var length int = index[i] response += _string[:length] + "&" + WMD.html_special_characters[characters[i]] + ";" _string = _string[length+1:] for j := 0; j < l; j++ { if index[j] != z { index[j] -= length + 1 } } } return response } func GetCodeBlockData(matches *PatternMatch) (string, string) { var _type string var content string for i := 0; i < 6; i++ { var j int = 1 + i*2 _type = matches.Groups[j] content = matches.Groups[j+1] if _type != "" || content != "" { if _type != "" { _type = strings.ToLower(_type) } else { _type = "unamed" } break } } return _type, content } func (_self *WMarkDown) ModuleCodeBlock(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { _type, content := GetCodeBlockData(matches) if _type != "" { var lines []string = Patterns.RE_NEW_LINE.Split(content, -1)[2:] var html string = `
    ` + `` + `
    ` + `
      ` for range lines { html += `
    1. ` } html += `
      ` + FilterHTMLSpecialCharacters(content) + `
      ` + `
    ` + `
    ` return html } return "UNKNOWN_BLOCK" } return "" } func (_self *WMarkDown) ModuleInclude(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { var relative_path string = matches.Groups[1] var directory_origin string = GetDirectory(path) var new_path string = directory_origin + "/" + relative_path var extension []string = Patterns.Match(new_path, Patterns.RE_EXTENSION).Groups[1:] var directory string = GetDirectory(new_path) var content string = LoadFile(new_path) var results string = content var included string = "false" if content != "" { if strings.Join(extension, ".") == "w.md" { results, _ = _self.Process(content, language, directory) } else if extension[len(extension)-1] == "md" { results = _self.Analyse(content, language, WMD.RAW, directory) } } if PathExists(new_path) { included = "true" } return `
    ` + `` + results + `
    ` } return "" } func (_self *WMarkDown) ModuleMedia(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { var _type string = strings.ToLower(matches.Groups[1]) var links []string = Patterns.RE_WHITE_SPACES.Split(matches.GetByGroup([]int{3, 5, 7}, ""), -1) var text string = Trim(matches.GetByGroup([]int{10, 12, 14}, "")) if slices.Contains([]string{"image", "picture", "icon"}, _type) { return BuildHTMLImage(links, _type, text) } if slices.Contains([]string{"video"}, _type) { var html string if strings.Contains(links[0], "youtube.com") || strings.Contains(links[0], "youtu.be") { _type = "youtube" } else if strings.Contains(links[0], "vimeo.com") { _type = "vimeo" } html = `` + `` switch _type { case "youtube": html += `YOUTUBE` case "vimeo": html += `VIMEO` default: var poster string = "" if len(links) > 1 { poster = ` poster="` + links[1] + `"` } html += `` } return html + `` } if slices.Contains([]string{"sound", "audio"}, _type) { var html string if strings.Contains(links[0], "soundcloud.com") { _type = "soundcloud" } html = `` + `` switch _type { case "soundcloud": html += `SOUNDCLOUD` default: html += `` } return html + `` } return "" } return "" } func MarkReplace(_string string, matches *PatternMatch, fragments *[]string) string { if matches.Ok { _string = _string[:matches.Span[0]] + WMD.item_mark.from + strconv.FormatInt(int64(len(*fragments)), 10) + WMD.item_mark.to + _string[matches.Span[1]:] *fragments = append(*fragments, matches.Groups[0]) } return _string } func RestoreMarks(_string string, fragments *[]string) string { var l int = len(*fragments) for { var matches *PatternMatch = Patterns.Match(_string, WMD.item_mark.pattern) if !matches.Ok { break } power_i, _ := strconv.ParseInt(matches.Groups[1], 10, 32) var i int = int(power_i) var subdata string = "" if i < l { subdata = (*fragments)[i] } _string = _string[:matches.Span[0]] + subdata + _string[matches.Span[1]:] } return _string } func doc_type_format(typed string) string { return FilterHTMLSpecialCharacters(strings.ReplaceAll(strings.ReplaceAll(typed, " ", ""), ",", ", ")) } func IsReference(scope string) string { if strings.Contains(scope, "&") { return "reference" } if strings.Contains(scope, "*") { return "pointer" } return "" } func (_self *WMarkDown) ModuleCodeDoc(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { var data string = Trim(matches.Groups[1]) var base PatternMatch = *Patterns.Match(data, Patterns.RE_CODE_DOC) var return_type string = doc_type_format(base.GetByGroup([]int{2}, "void")) var access string = base.GetByGroup([]int{3}, "public") var constant string = "" var reference string = IsReference(access) var name_space string = base.Groups[5] var environment string = base.GetByGroup([]int{6}, "global") var parameters string = base.Groups[9] var has_parameters bool = parameters != "" var default_value string = base.Groups[11] var full_method string = "" var arguments string = "" var arguments_definition string = "" var header string = `` + `Name` + `Required` + `Nullable` + `Typed` + `Default value` + `` var fragments *[]string = &[]string{} var arguments_l int = 0 if name_space != "" { full_method = name_space + "." } full_method += Trim(base.Groups[7]) if strings.Contains(access, "=") { constant = "constant" } if strings.Contains(access, "?") { access = "protected" } else if strings.Contains(access, "!") { access = "private" } else { access = "global" } switch environment { case ":": environment = "static" case ".": environment = "object" } if has_parameters { for { var matches *PatternMatch = Patterns.Match(parameters, Patterns.RE_CODE_DOC_SUBARGUMENTS) if !matches.Ok { break } parameters = MarkReplace(parameters, matches, fragments) } } for parameter := range strings.SplitSeq(parameters, ",") { var matches *PatternMatch = Patterns.Match(Trim(parameter), Patterns.RE_CODE_DOC_ARGUMENTS) if matches.Ok { var scopes string = matches.Groups[1] var required bool = strings.Contains(scopes, "!") var nullish bool = strings.Contains(scopes, "?") var subreference string = IsReference(access) var typed string = doc_type_format(RestoreMarks(matches.Groups[2], fragments)) var name string = Trim(matches.Groups[3]) var default_value string = FilterHTMLSpecialCharacters(RestoreMarks(Trim(matches.Groups[5]), fragments)) arguments += `` if required { arguments += `Required` } if nullish { arguments += `Nullish` } if subreference != "" { arguments += `` + subreference + `` } arguments += `` + typed + `` + `` + name + `` if default_value != "" { arguments += `` + default_value + `` } arguments += `` arguments_definition += `` + `` + name + `` + `` if required { arguments_definition += `True` } else { arguments_definition += `False` } arguments_definition += `` + `` if nullish { arguments_definition += `True` } else { arguments_definition += `False` } arguments_definition += `` + `` + typed + `` + `` + default_value + `` + `` arguments_l++ } } var html string = `
    ` + `` + full_method + `` + `
    ` if reference != "" { html += `` + reference + `` } html += `` + return_type + `` if constant != "" { html += `` + constant + `` } html += `` + environment + `` + `` + access + `` + `` + full_method + `` if has_parameters { html += `` + arguments + `` } if default_value != "" { html += `` + default_value + `` } return html + `
    ` + `
    ` + `` + `` + header + `` + `` + arguments_definition + `` + `` + header + `` + `
    ` + `
    ` + `
    ` } return "" } func (_self *WMarkDown) ModuleCodeSampleBlock(matches *PatternMatch, language int, path string) string { if language&WMD.HTML != 0 { var attributes string = matches.Groups[1] var submatches *PatternMatch = Patterns.Match(attributes, Patterns.RE_CLASS_ATTRIBUTE) var classes []string = []string{} var content string = matches.Groups[2] var languages string = `` var html string = `` var i int = 0 if submatches.Ok { classes = Patterns.RE_WHITE_SPACES.Split(Trim(submatches.Groups[1]), -1) } for { var submatches *PatternMatch = Patterns.Match(content, _self.modules["code_block"].patterns[0]) if !submatches.Ok { break } _type, _ := GetCodeBlockData(submatches) languages += `
  • ` + _type + `
  • ` html += _self.ModuleCodeBlock(submatches, language, path) content = content[submatches.Span[1]:] } if len(classes) == 0 { classes = append(classes, "wmd-code-sample-block") } return `
    ` + `` + `
    ` + html + `
    ` + `
    ` } return "" } func WMDRemoveDefaultTabulations(data string) string { var spaces int = len(data) for _, line := range Patterns.RE_NEW_LINES.Split(data, -1) { if line == "" { continue } var white_spaces int = len(Patterns.Match(line, Patterns.RE_STARTED_WHITE_SPACES).Groups[0]) if white_spaces < spaces { spaces = white_spaces } } return Patterns.Replace(data, Patterns.RE_LINES, func(matches *PatternMatch) string { return matches.Groups[0][spaces:] }) } func (_self WMarkDown) build(data string, language int, path string) string { if language&WMD.HTML != 0 { return `
    ` + _self.Analyse(WMDRemoveDefaultTabulations(data), language, WMD.RAW, path) + `
    ` + `
    ` } return "" } func (_self *WMarkDown) Process(data string, language int, path string) (string, map[string]string) { var results string = "" var variables map[string]string = map[string]string{} var options *PatternMatch = Patterns.Match(data, Patterns.RE_OPTIONS) if options.Ok { var options_data string = options.Groups[1] for { var option *PatternMatch = Patterns.Match(options_data, Patterns.RE_OPTION) if !option.Ok { break } variables[Trim(option.Groups[1])] = Trim(option.Groups[2]) options_data = options_data[option.Span[1]:] } data = data[:options.Span[0]] + data[options.Span[1]:] } for { var matches *PatternMatch = Patterns.Match(data, Patterns.RE_BLOCK_MARK) if !matches.Ok { results += data break } var mark string = matches.Groups[1] var RE_CLOSE *regexp.Regexp = Patterns.RE_BLOCK_MARK if mark != "" { RE_CLOSE = regexp.MustCompile(`(?m)^\s*` + mark) } results += data[:matches.Span[0]] data = data[matches.Span[1]:] var close_matches *PatternMatch = Patterns.Match(data, RE_CLOSE) if close_matches.Ok { results += _self.build(data[:close_matches.Span[0]], language, path) data = data[close_matches.Span[1]:] } else { results += _self.build(data, language, path) break } } return results, variables } func (_self *WMarkDown) Analyse(data string, language int, mode int, path string) string { var response string = "" var items map[string]*WMDAnalyseItem = map[string]*WMDAnalyseItem{} var current_keys []string = []string{} for _, key := range _self.keys { if mode == WMD.RAW || mode|_self.modules[key].mode == _self.modules[key].mode { items[key] = &WMDAnalyseItem{ exists: true, from: -1, to: -1, matches: &PatternMatch{ Ok: false, String: "", Groups: []string{}, Span: []int{-1, -1}, }, } current_keys = append(current_keys, key) } } for { var l int = len(data) var selected string = "" for _, key := range current_keys { if !items[key].exists { continue } var module WMDModule = *_self.modules[key] if items[key].from < 0 { items[key].matches = module.pattern_method(data, module.patterns) if items[key].matches.Ok { items[key].from = items[key].matches.Span[0] items[key].to = items[key].matches.Span[1] } else { items[key].exists = false continue } } if items[key].from < l { l = items[key].from selected = key } } if selected == "" { response += data break } var to int = items[selected].to response += data[:items[selected].from] + _self.modules[selected].method(items[selected].matches, language, path) data = data[to:] for _, item := range items { if item.matches.Ok { item.from -= to item.to -= to } } } return response } type DebugStackLineModel struct { Line int Method string File string } func (_self *WMarkDown) ToHTML(data string, path string) string { return _self.Analyse(data, WMD.HTML, WMD.RAW, path) }