diff --git a/Go/WMarkDown.go b/Go/WMarkDown.go new file mode 100644 index 0000000..92653ea --- /dev/null +++ b/Go/WMarkDown.go @@ -0,0 +1,1684 @@ +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) +} diff --git a/version b/version index 9676eb0..30ac009 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.0.3.7 \ No newline at end of file +0.0.3.8 \ No newline at end of file