使用Go语言实现xmind文件转换为markdown

Written by

in

文章目录
  • 将XMind转为结构化Markdown 你是否曾遇到过这些场景? 精心设计的XMind思维导图需要分享给只支持Markdown的协作者 想将思维导图发布到支持Markdown的博客平台 需要版本化管理思维导图内容 今天我们将深入探讨如何用Go语言构建一个强大的命令行工具,实现XMind到Markdown的无损转换。
  • 和docx等格式一样,xmind本质上来说是一个压缩包,将节点信息压缩在文件内。 ├── Thumbnails/      # 缩略图├── content.json     # 核心内容├── content.xml     ├── manifest.json├── metadata.json    # 元数据├── Revisions/       # 修订历史└── resources/       # 附件资源 其中最关键的是content.json文件,它用JSON格式存储了完整的思维导图数据结构。我们的转换工具需要精准解析这个文件。
  • /* 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, “# %snn”, sheet.Title) // Root topic title as H2 fmt.Fprintf(outputFile, “## %sn”, 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, “### %sn”, 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 %sn”, inputFile, outputFile) err := Convert(inputFile, outputFile) if err != nil { fmt.Printf(“Error: %vn”, err) } else { fmt.Printf(“Successfully converted to %sn”, outputFile) } }, } func init() { xmind2mdCmd.Flags().StringP(“output”, “o”, “”, “output file”) rootCmd.AddCommand(xmind2mdCmd) } 到此这篇关于使用Go语言实现xmind文件转换为markdown的文章就介绍到这了,更多相关Go xmind转markdown内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客! 您可能感兴趣的文章: go语言实现markdown解析库的方法示例 漂亮的Django Markdown富文本app插件的实现 Django渲染Markdown文章目录的方法示例
  • 目录
    • 解锁思维导图新姿势
    • 一、认识Xmind结构
    • 二、核心转换流程详解
      • 1.解压XMind文件(ZIP处理)
      • 2.解析JSON数据结构
      • 3:递归转换树形结构
      • 4:Markdown层级生成逻辑
    • 三、完整代码

      将XMind转为结构化Markdown

      你是否曾遇到过这些场景?

      • 精心设计的XMind思维导图需要分享给只支持Markdown的协作者
      • 想将思维导图发布到支持Markdown的博客平台
      • 需要版本化管理思维导图内容

      今天我们将深入探讨如何用Go语言构建一个强大的命令行工具,实现XMind到Markdown的无损转换。

      和docx等格式一样,xmind本质上来说是一个压缩包,将节点信息压缩在文件内。

      ├── Thumbnails/      # 缩略图
      ├── content.json     # 核心内容
      ├── content.xml     
      ├── manifest.json
      ├── metadata.json    # 元数据
      ├── Revisions/       # 修订历史
      └── resources/       # 附件资源

      其中最关键的是content.json文件,它用JSON格式存储了完整的思维导图数据结构。我们的转换工具需要精准解析这个文件。

      r, err := zip.OpenReader(inputPath)
      defer r.Close()
      
      for _, f := range r.File {
          if f.Name == "content.json" {
              // 读取文件内容
          }
      }
      

      这里使用标准库archive/zip读取压缩包,精准定位核心JSON文件。异常处理是关键点:

      • 检查是否为有效ZIP文件
      • 确保content.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 {
                  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)解析

      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)
          }
      }
      

      递归策略:

      • 每个节点根据层级生成对应缩进
      • 动态处理超链接和标记
      • 深度优先遍历确保结构正确性

      采用清晰的标题层级映射:

      # 思维导图名称        // 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:"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, "# %snn", sheet.Title)
      
      		// Root topic title as H2
      		fmt.Fprintf(outputFile, "## %sn", 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, "### %sn", 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 %sn", inputFile, outputFile)
      		err := Convert(inputFile, outputFile)
      		if err != nil {
      			fmt.Printf("Error: %vn", err)
      		} else {
      			fmt.Printf("Successfully converted to %sn", outputFile)
      		}
      	},
      }
      
      func init() {
      	xmind2mdCmd.Flags().StringP("output", "o", "", "output file")
      	rootCmd.AddCommand(xmind2mdCmd)
      }
      

      到此这篇关于使用Go语言实现xmind文件转换为markdown的文章就介绍到这了,更多相关Go xmind转markdown内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!

      您可能感兴趣的文章:

      • go语言实现markdown解析库的方法示例
      • 漂亮的Django Markdown富文本app插件的实现
      • Django渲染Markdown文章目录的方法示例

      站内搜索