ParseTopicEntriesHTML extracts structured entry data from the HTML page served by /topics/{id}/entries. The JSON API does not return full entry bodies, so this HTML-based extraction is required.
(html string)
| 68 | // served by /topics/{id}/entries. The JSON API does not return full entry |
| 69 | // bodies, so this HTML-based extraction is required. |
| 70 | func ParseTopicEntriesHTML(html string) []models.Entry { |
| 71 | // Find unique entry IDs in order |
| 72 | idMatches := entryBlockRe.FindAllStringSubmatch(html, -1) |
| 73 | seen := map[string]bool{} |
| 74 | var entryIDs []string |
| 75 | for _, m := range idMatches { |
| 76 | if !seen[m[1]] { |
| 77 | seen[m[1]] = true |
| 78 | entryIDs = append(entryIDs, m[1]) |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | // Build lookup maps |
| 83 | senders := map[string]string{} |
| 84 | for _, m := range senderRe.FindAllStringSubmatch(html, -1) { |
| 85 | if _, exists := senders[m[1]]; !exists { |
| 86 | senders[m[1]] = m[2] |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | // Associate times with entries by finding the first <time> after each entry anchor |
| 91 | entryTimes := map[string]string{} |
| 92 | for _, eid := range entryIDs { |
| 93 | anchor := fmt.Sprintf(`id="entry_%s"`, eid) |
| 94 | idx := strings.Index(html, anchor) |
| 95 | if idx < 0 { |
| 96 | continue |
| 97 | } |
| 98 | if m := timeRe.FindStringSubmatch(html[idx:]); m != nil { |
| 99 | entryTimes[eid] = m[1] |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | // Extract bodies from srcdoc iframes - they appear in entry order |
| 104 | type body struct{ html, text string } |
| 105 | bodyMatches := srcdocRe.FindAllStringSubmatch(html, -1) |
| 106 | bodies := make([]body, 0, len(bodyMatches)) |
| 107 | for _, m := range bodyMatches { |
| 108 | raw := m[1] |
| 109 | raw = strings.ReplaceAll(raw, "<", "<") |
| 110 | raw = strings.ReplaceAll(raw, ">", ">") |
| 111 | raw = strings.ReplaceAll(raw, """, "\"") |
| 112 | raw = strings.ReplaceAll(raw, "&", "&") |
| 113 | raw = strings.ReplaceAll(raw, "'", "'") |
| 114 | bodies = append(bodies, body{html: raw, text: ToText(raw)}) |
| 115 | } |
| 116 | |
| 117 | entries := make([]models.Entry, 0, len(entryIDs)) |
| 118 | for i, eid := range entryIDs { |
| 119 | id, _ := strconv.ParseInt(eid, 10, 64) |
| 120 | e := models.Entry{ |
| 121 | ID: id, |
| 122 | CreatedAt: entryTimes[eid], |
| 123 | } |
| 124 | if name, ok := senders[eid]; ok { |
| 125 | e.Creator = models.Contact{Name: name} |
| 126 | } |
| 127 | if i < len(bodies) { |
no test coverage detected