package converters import ( "fmt" "html" "sort" "strings" "unicode/utf16" "github.com/medium.rip/pkg/entities" ) type RangeWithMarkup struct { Range []int Markups []entities.Markup } func unique(intSlice []int) []int { keys := make(map[int]bool) list := []int{} for _, entry := range intSlice { if _, value := keys[entry]; !value { keys[entry] = true list = append(list, entry) } } return list } func ranges(text string, markups []entities.Markup) []RangeWithMarkup { ranges := make([]RangeWithMarkup, 0) // first, get all the borders of the markups markupBoundaries := make([]int, 0) for _, m := range markups { markupBoundaries = append(markupBoundaries, []int{int(m.Start), int(m.End)}...) } // include the start and end indexes of the text markupBoundaries = append([]int{0}, markupBoundaries...) markupBoundaries = append(markupBoundaries, len(utf16.Encode([]rune(text)))) // remove duplicates markupBoundaries = unique(markupBoundaries) // sort slice sort.Slice(markupBoundaries, func(i, j int) bool { return markupBoundaries[i] < markupBoundaries[j] }) // attach markup to every range for i := 0; i < len(markupBoundaries)-1; i++ { start := markupBoundaries[i] end := markupBoundaries[i+1] // check if this markup is covered by the range coveredMarkups := make([]entities.Markup, 0) for _, m := range markups { if (int(m.Start) >= start && int(m.Start) < end) || (int(m.End-1) >= start && int(m.End-1) < end) { coveredMarkups = append(coveredMarkups, m) } } // append the range ranges = append(ranges, RangeWithMarkup{ Range: []int{start, end}, Markups: coveredMarkups, }) } return ranges } func ConvertMarkup(text string, markups []entities.Markup) string { if len(markups) == 0 { return html.EscapeString(text) } var markedUp strings.Builder for _, r := range ranges(text, markups) { // handle utf-16 utf16Text := utf16.Encode([]rune(text)) ranged := utf16Text[r.Range[0]:r.Range[1]] textToWrap := string(utf16.Decode(ranged)) markedUp.WriteString(wrapInMarkups(textToWrap, r.Markups)) } return markedUp.String() } func wrapInMarkups(child string, markups []entities.Markup) string { if len(markups) == 0 { return child } markedUp := markupNodeInContainer(child, markups[0]) return wrapInMarkups(markedUp, markups[1:]) } func markupNodeInContainer(child string, markup entities.Markup) string { switch markup.Type { case "A": if markup.Href != nil { return fmt.Sprintf(`%s`, *markup.Href, html.EscapeString(child)) } else if markup.UserID != nil { return fmt.Sprintf(`%s`, markup.UserID, html.EscapeString(child)) } case "CODE": return fmt.Sprintf(`%s`, html.EscapeString(child)) case "EM": return fmt.Sprintf(`%s`, html.EscapeString(child)) case "STRONG": return fmt.Sprintf(`%s`, html.EscapeString(child)) default: return fmt.Sprintf(`%s`, html.EscapeString(child)) } return html.EscapeString(child) }