mirror of
https://github.com/ArvinLovegood/go-stock.git
synced 2025-07-19 00:00:09 +08:00
- 添加 CheckStockBaseInfo 方法,用于更新股票基础信息 - 修改 domReady 方法,移除初始化股票数据的逻辑 - 更新 StockBasic、StockInfoHK 和 StockInfoUS 模型,添加行业代码和名称字段 - 修改 getDCStockInfo 方法,支持获取更详细的股票信息 - 添加 DCToTsCode 函数,用于将东财代码转换为 TS 代码 - 优化行业报告信息获取功能
222 lines
4.8 KiB
Go
222 lines
4.8 KiB
Go
package util
|
||
|
||
// @Author spark
|
||
// @Date 2025/7/15 14:08
|
||
// @Desc
|
||
//-----------------------------------------------------------------------------------
|
||
|
||
import (
|
||
"bytes"
|
||
"fmt"
|
||
"golang.org/x/net/html"
|
||
"strings"
|
||
)
|
||
|
||
// HTMLNode 表示HTML文档中的一个节点
|
||
type HTMLNode struct {
|
||
Type html.NodeType
|
||
Data string
|
||
Attr []html.Attribute
|
||
Children []*HTMLNode
|
||
}
|
||
|
||
// HTMLToMarkdown 将HTML转换为Markdown
|
||
func HTMLToMarkdown(htmlContent string) (string, error) {
|
||
doc, err := html.Parse(strings.NewReader(htmlContent))
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
root := parseHTMLNode(doc)
|
||
var buf bytes.Buffer
|
||
convertNode(&buf, root, 0)
|
||
|
||
return buf.String(), nil
|
||
}
|
||
|
||
// parseHTMLNode 递归解析HTML节点
|
||
func parseHTMLNode(n *html.Node) *HTMLNode {
|
||
node := &HTMLNode{
|
||
Type: n.Type,
|
||
Data: n.Data,
|
||
Attr: n.Attr,
|
||
}
|
||
|
||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||
node.Children = append(node.Children, parseHTMLNode(c))
|
||
}
|
||
|
||
return node
|
||
}
|
||
|
||
// convertNode 递归转换节点为Markdown
|
||
func convertNode(buf *bytes.Buffer, node *HTMLNode, depth int) {
|
||
switch node.Type {
|
||
case html.ElementNode:
|
||
convertElementNode(buf, node, depth)
|
||
case html.TextNode:
|
||
// 处理文本节点,去除多余的空白
|
||
text := strings.TrimSpace(node.Data)
|
||
if text != "" {
|
||
buf.WriteString(text)
|
||
}
|
||
}
|
||
|
||
// 递归处理子节点
|
||
for _, child := range node.Children {
|
||
convertNode(buf, child, depth+1)
|
||
}
|
||
|
||
// 处理需要在结束标签后添加内容的元素
|
||
switch node.Data {
|
||
case "p", "h1", "h2", "h3", "h4", "h5", "h6", "li":
|
||
buf.WriteString("\n\n")
|
||
case "blockquote":
|
||
buf.WriteString("\n")
|
||
}
|
||
}
|
||
|
||
// convertElementNode 转换元素节点为Markdown
|
||
func convertElementNode(buf *bytes.Buffer, node *HTMLNode, depth int) {
|
||
switch node.Data {
|
||
case "h1":
|
||
buf.WriteString("# ")
|
||
case "h2":
|
||
buf.WriteString("## ")
|
||
case "h3":
|
||
buf.WriteString("### ")
|
||
case "h4":
|
||
buf.WriteString("#### ")
|
||
case "h5":
|
||
buf.WriteString("##### ")
|
||
case "h6":
|
||
buf.WriteString("###### ")
|
||
case "p":
|
||
// 段落标签不需要特殊标记,直接处理内容
|
||
case "strong", "b":
|
||
buf.WriteString("**")
|
||
case "em", "i":
|
||
buf.WriteString("*")
|
||
case "u":
|
||
buf.WriteString("<u>")
|
||
case "s", "del":
|
||
buf.WriteString("~~")
|
||
case "a":
|
||
//href := getAttrValue(node.Attr, "href")
|
||
buf.WriteString("[")
|
||
case "img":
|
||
src := getAttrValue(node.Attr, "src")
|
||
alt := getAttrValue(node.Attr, "alt")
|
||
buf.WriteString(fmt.Sprintf("", alt, src))
|
||
case "ul":
|
||
// 无序列表不需要特殊标记,子项会处理
|
||
case "ol":
|
||
// 有序列表不需要特殊标记,子项会处理
|
||
case "li":
|
||
if isParentListType(node, "ul") {
|
||
buf.WriteString("- ")
|
||
} else {
|
||
// 计算当前列表项的序号
|
||
index := 1
|
||
if parent := findParentList(node); parent != nil {
|
||
for i, sibling := range parent.Children {
|
||
if sibling == node {
|
||
index = i + 1
|
||
break
|
||
}
|
||
}
|
||
}
|
||
buf.WriteString(fmt.Sprintf("%d. ", index))
|
||
}
|
||
case "blockquote":
|
||
buf.WriteString("> ")
|
||
case "code":
|
||
if isParentPre(node) {
|
||
// 父节点是pre,使用代码块
|
||
buf.WriteString("\n```\n")
|
||
} else {
|
||
// 行内代码
|
||
buf.WriteString("`")
|
||
}
|
||
case "pre":
|
||
// 前置代码块由子节点code处理
|
||
case "br":
|
||
buf.WriteString("\n")
|
||
case "hr":
|
||
buf.WriteString("\n---\n")
|
||
}
|
||
|
||
// 处理闭合标签
|
||
if needsClosingTag(node.Data) {
|
||
defer func() {
|
||
switch node.Data {
|
||
case "strong", "b":
|
||
buf.WriteString("**")
|
||
case "em", "i":
|
||
buf.WriteString("*")
|
||
case "u":
|
||
buf.WriteString("</u>")
|
||
case "s", "del":
|
||
buf.WriteString("~~")
|
||
case "a":
|
||
href := getAttrValue(node.Attr, "href")
|
||
buf.WriteString(fmt.Sprintf("](%s)", href))
|
||
case "code":
|
||
if isParentPre(node) {
|
||
buf.WriteString("\n```\n")
|
||
} else {
|
||
buf.WriteString("`")
|
||
}
|
||
}
|
||
}()
|
||
}
|
||
}
|
||
|
||
// getAttrValue 获取属性值
|
||
func getAttrValue(attrs []html.Attribute, key string) string {
|
||
for _, attr := range attrs {
|
||
if attr.Key == key {
|
||
return attr.Val
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// isParentListType 检查父节点是否为指定类型的列表
|
||
func isParentListType(node *HTMLNode, listType string) bool {
|
||
parent := findParentList(node)
|
||
return parent != nil && parent.Data == listType
|
||
}
|
||
|
||
// findParentList 查找父列表节点
|
||
func findParentList(node *HTMLNode) *HTMLNode {
|
||
// 简化实现,实际应该递归查找父节点
|
||
if node.Type == html.ElementNode && (node.Data == "ul" || node.Data == "ol") {
|
||
return node
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// isParentPre 检查父节点是否为pre
|
||
func isParentPre(node *HTMLNode) bool {
|
||
if len(node.Children) == 0 {
|
||
return false
|
||
}
|
||
for _, child := range node.Children {
|
||
if child.Type == html.ElementNode && child.Data == "pre" {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// needsClosingTag 判断元素是否需要闭合标签
|
||
func needsClosingTag(tag string) bool {
|
||
switch tag {
|
||
case "img", "br", "hr", "input", "meta", "link":
|
||
return false
|
||
default:
|
||
return true
|
||
}
|
||
}
|