使用Go語言實(shí)現(xiàn)xmind文件轉(zhuǎn)換為markdown
解鎖思維導(dǎo)圖新姿勢(shì)
將XMind轉(zhuǎn)為結(jié)構(gòu)化Markdown
你是否曾遇到過這些場(chǎng)景?
- 精心設(shè)計(jì)的XMind思維導(dǎo)圖需要分享給只支持Markdown的協(xié)作者
- 想將思維導(dǎo)圖發(fā)布到支持Markdown的博客平臺(tái)
- 需要版本化管理思維導(dǎo)圖內(nèi)容
今天我們將深入探討如何用Go語言構(gòu)建一個(gè)強(qiáng)大的命令行工具,實(shí)現(xiàn)XMind到Markdown的無損轉(zhuǎn)換。
一、認(rèn)識(shí)Xmind結(jié)構(gòu)
和docx等格式一樣,xmind本質(zhì)上來說是一個(gè)壓縮包,將節(jié)點(diǎn)信息壓縮在文件內(nèi)。
├── Thumbnails/ # 縮略圖
├── content.json # 核心內(nèi)容
├── content.xml
├── manifest.json
├── metadata.json # 元數(shù)據(jù)
├── Revisions/ # 修訂歷史
└── resources/ # 附件資源
其中最關(guān)鍵的是content.json文件,它用JSON格式存儲(chǔ)了完整的思維導(dǎo)圖數(shù)據(jù)結(jié)構(gòu)。我們的轉(zhuǎn)換工具需要精準(zhǔn)解析這個(gè)文件。
二、核心轉(zhuǎn)換流程詳解
1.解壓XMind文件(ZIP處理)
r, err := zip.OpenReader(inputPath) defer r.Close() for _, f := range r.File { if f.Name == "content.json" { // 讀取文件內(nèi)容 } }
這里使用標(biāo)準(zhǔn)庫(kù)archive/zip讀取壓縮包,精準(zhǔn)定位核心JSON文件。異常處理是關(guān)鍵點(diǎn):
- 檢查是否為有效ZIP文件
- 確保content.json存在
- 處理文件讀取錯(cuò)誤
2.解析JSON數(shù)據(jù)結(jié)構(gòu)
我們定義了精準(zhǔn)映射JSON的Go結(jié)構(gòu)體:
// 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:"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"` }
核心:
- 嵌套結(jié)構(gòu)匹配XMind的樹形數(shù)據(jù)
- Attached字段處理多分支結(jié)構(gòu)
- 支持標(biāo)記(markers)和超鏈接(href)解析
3:遞歸轉(zhuǎn)換樹形結(jié)構(gòu)
func printTopic(topic Topic, level int, output *os.File) { // 動(dòng)態(tài)計(jì)算縮進(jìn) 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) } // 添加標(biāo)記圖標(biāo) 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() // 遞歸處理子節(jié)點(diǎn) for _, child := range topic.Children.Attached { printTopic(child, level+1, output) } }
遞歸策略:
- 每個(gè)節(jié)點(diǎn)根據(jù)層級(jí)生成對(duì)應(yīng)縮進(jìn)
- 動(dòng)態(tài)處理超鏈接和標(biāo)記
- 深度優(yōu)先遍歷確保結(jié)構(gòu)正確性
4:Markdown層級(jí)生成邏輯
采用清晰的標(biāo)題層級(jí)映射:
# 思維導(dǎo)圖名稱 // H1
## 中心主題 // H2
### 主要分支 // H3
- 子主題1 // 無序列表
- 子子主題 // 縮進(jìn)列表
這種結(jié)構(gòu)完美保留了:
- 原始信息的層次關(guān)系
- 超鏈接資源
- 優(yōu)先級(jí)標(biāo)記(旗幟/星標(biāo)等)
三、完整代碼
/* 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:"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"` } 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) error { // 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) }
到此這篇關(guān)于使用Go語言實(shí)現(xiàn)xmind文件轉(zhuǎn)換為markdown的文章就介紹到這了,更多相關(guān)Go xmind轉(zhuǎn)markdown內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go defer使用時(shí)的兩個(gè)常見陷阱與避免方法
這篇文章主要將帶大家一起深入探討 Go 1.20 中 defer 的優(yōu)化機(jī)制,并揭示在使用 defer 時(shí)需要避免的兩個(gè)常見陷阱,有需要的可以了解下2025-03-03Golang語言中的Prometheus的日志模塊使用案例代碼編寫
這篇文章主要介紹了Golang語言中的Prometheus的日志模塊使用案例,本文給大家分享源代碼編寫方法,感興趣的朋友跟隨小編一起看看吧2024-08-08golang之?dāng)?shù)據(jù)校驗(yàn)的實(shí)現(xiàn)代碼示例
這篇文章主要介紹了golang之?dāng)?shù)據(jù)校檢的實(shí)現(xiàn)代碼示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-107分鐘讀懂Go的臨時(shí)對(duì)象池pool以及其應(yīng)用場(chǎng)景
這篇文章主要給大家介紹了關(guān)于如何通過7分鐘讀懂Go的臨時(shí)對(duì)象池pool以及其應(yīng)用場(chǎng)景的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或使用Go具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧2018-11-11Go到底能不能實(shí)現(xiàn)安全的雙檢鎖(推薦)
這篇文章主要介紹了Go到底能不能實(shí)現(xiàn)安全的雙檢鎖,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05Go?gRPC進(jìn)階教程服務(wù)超時(shí)設(shè)置
這篇文章主要為大家介紹了Go?gRPC進(jìn)階,gRPC請(qǐng)求的超時(shí)時(shí)間設(shè)置,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06