使用Go语言实现xmind文件转换为markdown
目录
- 解锁思维导图新姿势
- 一、认识Xmind结构
- 二、核心转换流程详解
- 1.解压XMind文件(ZIP处理)
- 2.解析jsON数据结构
- 3:递归转换树形结构
- 4:Markdown层级生成逻辑
- 三、完整代码
解锁思维导图新姿势
将XMind转为结构化Markdown
你是否曾遇到过这些场景?
- 精心设计的XMind思维导图需要分享给只支持Markdown的协作者
- 想将思维导图发布到支持Markdown的博客平台
- 需要版本化管理思维导图内容
今天我们将深入探讨如何用Go语言构建一个强大的命令行工具,实现XMind到Markdown的无损转换。
一、认识Xmind结构
和docx等格式一样,xmind本质上来说是一个压缩包,将节点信息压缩在文件内。
├── Thumbnails/ # 缩略图
├── content.json # 核心内容├── content.XML ├── manifest.json├── metadata.json # 元数据├── Revisions/ # 修订历史└── resources/ # 附件资源
其中最关键的是content.json文件,它用JSON格式存储了完整的思维导图数据结构。我们的转换工具需要精准解析这个文件。
二、核心转换流程详解
1.解压XMind文件(ZIP处理)
r, err := zip.OpenReader(inputPath) defer r.Close() for _, f := range r.File { if f.Name == "content.json" { // 读取文件内容 } }
这里使用标准库archive/zip读取压缩包,精准定位核心JSON文件。异常处理是关键点:
- 检查是否为有效ZIP文件
- 确保content.json存在
- 处理文件读取错误
2.解析JSON数据结构
我们定义了精准映射JSON的Go结构体:
// XMindContent represents the structure of content.json type XMindContent []struct { ID string `json:"id"` Class string `json:"class"` Title string `json:"title"` RootTopic struct { ID string `json:"id"` Class string `json:"class"` Title string `json:"title"` Href string `json:"href"` StructureClass string `json:"structureClass"` Children struct {http://www.devze.com Attached []Topic `json:"attached"` } `json:"children"` } `json:"rootTopic"` } type Topic struct { Title string `json:"title"` ID string `json:"id"` Href string `json:"href"` Position struct { X float64 `json:"x"` Y float64 `json:"y"` } `json:"position"` Children struct { Attached []Topic `json:"attached"` } `json:"children"` Branch string `json:"branch"` Markers []struct { MarkerID string `json:"markerId"` } `json:"markers"` Summaries []struct { Range string `json:"range"` TopicID string `json:"topicId"` } `json:"summaries"` Image struct { Src string `json:"src"` Align string `json:"align"` } `json:"image"` AttributedTitle []struct { Text string `json:"text"` } `json:"attributedTitle"` }
核心:
- 嵌套结构匹配XMind的树形数据
- Attached字段处理多分支结构
- 支持标记(markers)和超链接(href)解析
3:递归转换树形结构
func printTopic(topic Topic, level int, output *os.File) { // 动态计算缩进 fmt.Fprintf(output, "%s- ", strings.Repeat(" ", level)) // 处理超链接 if topic.Href != "" { fmt.Fprintf(output, "[%s](%s)", topic.Title, topic.Href) } else { fmt.Fprint(output, topic.Title) } // 添加标记图标 if len(topic.Markers) > 0 { fmt.Fprint(output, " [") for i, m := range topic.Markers { if i > 0 { fmt.Print(", ") } fmt.Fprint(output, m.MarkerID) } fmt.Print("]") } fmt.Println() // 递归处理子节点 for _, child := range topic.Children.Attached { printTopic(child, level+1, output) } }
递归策略:
- 每个节点根据层级生成对应缩进
- 动态处理超链接和标记
- 深度优先遍历确保结构正确性
4:Markdown层级生成逻辑
采用清晰的标题层级映射:
# 思维导图名称 // H1
## 中心主题 // H2### 主要分支 // H3- 子主题1 // 无序列表 - 子子主题 // 缩进列表
这种结构完美保留了:
- 原始信息的层次关系
- 超链接资源
- 优先级标记(旗帜/星标等)
三、完整代码
/* Copyright 2025 NAME HERE <EMAIL ADDRESS> */ package cmd import ( "archive/zip" "encoding/json" "fmt" "io/ioutil" "os" "github.com/spf13/cobra" ) // XMindContent represents the structure of content.json type XMindContent []struct { ID string `json:"id"` Class string `json:"class"` Title string `json:"title"` RootTopic struct { ID string `json:"id"` Class string `json:"class"` Title string `json:"title"` Href string `json:"href"` StructureClass string `json:"structureClass"` Children struct { Attached []Topic `json:"attached"` } `json:"children"` } `json:"rootTopic"` } type Topic struct { Title string `json:"title"` ID string `json:"id"` Href string `json:"href"` Position struct { X float64 `json:"x"` Y float64 `json:"y"` } `json:"posiSjKKution"` Children struct { Attached []Topic `jsohttp://www.devze.comn:"attached"` } `json:"children"` Branch string `json:"branch"` Markers []struct { MarkerID string `json:"markerId"` } `json:"markers"` Summaries []struct { Range string `jsonSjKKu:"range"` TopicID string `json:"topicId"` } `json:"summaries"` } func generateMarkdown(sheets XMindContent, outputPath string) error { // Create output file outputFile, err := os.Create(outputPath) if err != nil { return fmt.Errorf("failed to create output file: %v", err) } defer outputFile.Close() // Generate Markdown for each sheet for _, sheet := range sheets { // Sheet title as H1 fmt.Fprintf(outputFile, "# %s\n\n", sheet.Title) // Root topic title as H2 fmt.Fprintf(outputFile, "## %s\n", sheet.RootTopic.Title) if sheet.RootTopic.Href != "" { fmt.Fprintf(outputFile, "[%s](%s)\n", sheet.RootTopic.Title, sheet.RootTopic.Href) } fmt.Fprintln(outputFile) // First level topics as H3 for _, topic := range sheet.RootTopic.Children.Attached { fmt.Fprintf(outputFile, "### %s\n", topic.Title) if topic.Href != "" { fmt.Fprintf(outputFile, "[%s](%s)\n", topic.Title, topic.Href) } // Print markers if present if len(topic.Markers) > 0 { fmt.Fprint(outputFile, "Markers: ") for i, marker := range topic.Markers { if i > 0 { fmt.Fprint(outputFile, ", ") } fmt.Fprint(outputFile, marker.MarkerID) } fmt.Fprintln(outputFile) } // Deeper levels as lists for _, child := range topic.Children.Attached { printTopic(child, 0, outputFile) } fmt.Fprintln(outputFile) // Add extra space between topics } } return nil } func printTopic(topic Topic, level int, output *os.File) { // Print topic title with indentation fmt.Fprintf(output, "%s- ", getIndent(level)) // Handle title with or without href if topic.Href != "" { fmt.Fprintf(output, "[%s](%s)", topic.Title, topic.Href) } else { fmt.Fprint(output, topic.Title) } // Show markers if present if len(topic.Markers) > 0 { fmt.Fprint(output, " [") for i, marker := range topic.Markers { if i > 0 { fmt.Fprint(output, ", ") } fmt.Fprint(output, marker.MarkerID) } fmt.Fprint(output, "]") } fmt.Fprintln(output) // Recursively print subtopics for _, child := range topic.Children.Attached { printTopic(child, level+1, output) } } func getIndent(level int) string { indent := "" for i := 0; i < level; i++ { indent += " " } return indent } func Convert(inputPath, outputPath string) errjavascriptor { // 1. Unzip XMind file r, err := zip.OpenReader(inputPath) if err != nil { return fmt.Errorf("failed to open XMind file: %v", err) } defer r.Close() // 2. Read content.json var content []byte for _, f := range r.File { if f.Name == "content.json" { rc, err := f.Open() if err != nil { return fmt.Errorf("failed to open content.json: %v", err) } defer rc.Close() content, err = ioutil.ReadAll(rc) if err != nil { return fmt.Errorf("failed to read content.json: %v", err) } break } } if content == nil { return fmt.Errorf("content.json not found in XMind file") } // 3. Parse content.json var xmindContent XMindContent err = json.Unmarshal(content, &xmindContent) if err != nil { return fmt.Errorf("failed to parse JSON: %v", err) } // 4. Generate Markdown return generateMarkdown(xmindContent, outputPath) } // xmind2mdCmd represents the xmind2md command var xmind2mdCmd = &cobra.Command{ Use: "xmind2md", Short: "Convert XMind to Markdown", Long: `Transform XMind mind maps to Markdown format`, Run: func(cmd *cobra.Command, args []string) { if len(args) == 0 { fmt.Println("Please provide an input XMind file") return } inputFile := args[0] outputFile, _ := cmd.Flags().GetString("output") if outputFile == "" { // 去除.xmind后缀 if len(inputFile) > 6 && inputFile[len(inputFile)-6:] == ".xmind" { outputFile = inputFile[:len(inputFile)-6] + ".md" } else { outputFile = inputFile + ".md" } } fmt.Printf("Converting %s to %s\n", inputFile, outputFile) err := Convert(inputFile, outputFile) if err != nil { fmt.Printf("Error: %v\n", err) } else { fmt.Printf("Successfully converted to %s\n", outputFile) } }, } func init() { xmind2mdCmd.Flags().StringP("output", "o", "", "output file") rootCmd.AddCommand(xmind2mdCmd) }
到此这篇关于使用Go语言实现xmind文件转换为markdown的文章就介绍到这了,更多相关Go xmind转markdown内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论