jan-bar/xmind

能增加转成 markdown 格式的功能么

Closed this issue · 7 comments

能增加转成 markdown 格式的功能么

这个做起来应该简单(直接用递归一行行输出结果,控制前面缩进就行),主要是markdown的格式要什么样,还是说格式可以用参数指定。这块需要研究,你也可以提pr额。后面有空可以搞搞。

你看看行不行,行的话, 我再试增加新版本xmind的支持

From 75a38894c07754ae1349c55fc8645578c957f82a Mon Sep 17 00:00:00 2001
From: meifakun <runner.mei@gmail.com>
Date: Mon, 21 Aug 2023 22:11:01 +0800
Subject: [PATCH] add markdown convert

---
 convert/markdown/markdown.go | 36 ++++++++++++++++++
 markdown.go                  | 72 ++++++++++++++++++++++++++++++++++++
 2 files changed, 108 insertions(+)
 create mode 100644 convert/markdown/markdown.go
 create mode 100644 markdown.go

diff --git a/convert/markdown/markdown.go b/convert/markdown/markdown.go
new file mode 100644
index 0000000..e8fef4c
--- /dev/null
+++ b/convert/markdown/markdown.go
@@ -0,0 +1,36 @@
+package main
+
+import (
+	"fmt"
+	"os"
+
+	"github.com/jan-bar/xmind"
+)
+
+// go run youdao.go test.mindmap test.xmind
+
+func main() {
+	if len(os.Args) != 3 {
+		return
+	}
+	book, err := xmind.LoadFile(os.Args[1])
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+
+	fw, err := os.Create(os.Args[2])
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+	//goland:noinspection GoUnhandledErrorResult
+	defer fw.Close()
+
+	err = book.SaveToMarkdown(fw)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+}
+
diff --git a/markdown.go b/markdown.go
new file mode 100644
index 0000000..fc2728b
--- /dev/null
+++ b/markdown.go
@@ -0,0 +1,72 @@
+package xmind
+
+import (
+	"io"
+	"strings"
+)
+
+func (wk *WorkBook) ForEach(cb func(parents []*Topic, current *Topic) error) error {
+	for _, topic := range wk.Topics {
+		if err := topic.ForEach(nil, cb); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (st *Topic) ForEach(parents []*Topic, cb func([]*Topic, *Topic) error) error {
+	if st != nil {
+		var loop func([]*Topic, *Topic) error
+		loop = func(parents []*Topic, tp *Topic) error {
+			if tp != nil {
+				if err := cb(parents, tp); err != nil { // 通过回调函数让调用者实现自己的逻辑
+					return err
+				}
+				if tp.Children != nil && len(tp.Children.Attached) > 0 {
+					for _, tc := range tp.Children.Attached {
+						err := loop(append(parents, tp), tc)
+						if err != nil {
+							return err
+						}
+					}
+				}
+			}
+			return nil
+		}
+		if st.RootTopic != nil {
+			return loop(parents, st.RootTopic) // 当前为根节点,从中心主题开始遍历
+		} else {
+			return loop(parents, st) // 其他情况从当前节点开始遍历
+		}
+	}
+	return nil
+}
+
+func (wk *WorkBook) SaveToMarkdown(w io.Writer) error {
+	return wk.ForEach(func(parents []*Topic, current *Topic) error {
+		_, err := io.WriteString(w, strings.Repeat("#", len(parents)))
+		if err != nil {
+			return err
+		}
+		io.WriteString(" ")
+		io.WriteString(w,current.Title)
+		io.WriteString(w,"\r\n")
+		io.WriteString(w,"\r\n")
+		if len(current.Labels) > 0 {
+			for _, label := range current.Labels {
+				io.WriteString(w,"> ")
+				io.WriteString(w,label)
+				io.WriteString(w,"\r\n")
+			}
+			io.WriteString(w,"\r\n")
+		}
+
+		if current.Notes != nil && current.Notes.Plain.Content != "" {
+			io.WriteString(w,"> ")
+			io.WriteString(w,current.Notes.Plain.Content)
+			io.WriteString(w,"\r\n")
+			io.WriteString(w,"\r\n")
+		}
+		return nil
+	})
+}
-- 
2.29.2.windows.2

@mei-rune 我按照你的思路实现了可以自定义样式的方案,https://github.com/jan-bar/xmind/blob/master/example/save_markdown_test.go

你可以先试试看好不好用,有问题说下。在测试时还发现了一些优化项和bug,没问题的话我可以发布一个版本额。

暂时发现一个小 bug, Notes 可能是多行的, 所以加下列补丁就可以了

diff --git a/markdowm.go b/markdowm.go
index 94354cd..9c54398 100644
--- a/markdowm.go
+++ b/markdowm.go
@@ -9,11 +9,21 @@ import (

 const (
        DefaultMarkdownName   = "default"
-       DefaultMarkdownFormat = "{{Repeat \"#\" .Deep}} {{.Title}}\n\n{{range $i,$v := .Labels}}> {{$v}}\n\n{{end}}{{if .Notes}}> {{.Notes}}\n\n{{end}}"
+       DefaultMarkdownFormat = "{{Repeat \"#\" .Deep}} {{.Title}}\n\n{{range $i,$v := .Labels}}> {{$v}}\n\n{{end}}{{if .Notes}}{{range $i,$v := (SplitLines .Notes)}}> {{$v}}\n\n{{end}}\n\n{{end}}"

        MarkdownKeyDeep = "Deep" // 所在层级,>=1
 )

+func splitLines(s string) []string {
+       ss := strings.Split(s, "\n")
+       for idx := range ss {
+               if strings.HasSuffix(ss[idx], "\r") {
+                       ss[idx] = strings.TrimSuffix(ss[idx], "\r")
+               }
+       }
+       return ss
+}
+
 func (wk *WorkBook) SaveToMarkdown(w io.Writer, format map[string]string) error {
        defText, ok := format[DefaultMarkdownName]
        if !ok {
@@ -22,6 +32,7 @@ func (wk *WorkBook) SaveToMarkdown(w io.Writer, format map[string]string) error

        tpl, err := template.New(DefaultMarkdownName).Funcs(template.FuncMap{
                "Repeat": strings.Repeat, // 注册用到的方法
+               "SplitLines": splitLines,
        }).Parse(defText)
        if err != nil {
                return err

转 markdown 的命令行小工具

package main

import (
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
	"strings"

	"github.com/jan-bar/xmind"
)

func main() {
	if len(os.Args) != 3 {
		return
	}

	from := os.Args[1]
	to := os.Args[2]

	st, err := os.Stat(from)
	if err != nil {
		fmt.Println(err)
		return
	}

	if st.IsDir() {
		err = convertDir(from, to)
	} else {
		err = convertFile(from, to)
	}
	if err != nil {
		fmt.Println(err)
		return
	}
}

func convertDir(from, to string) error {
	return filepath.WalkDir(from, fs.WalkDirFunc(func(pa string, info fs.DirEntry, err error) error {
		if info.IsDir() {
			return nil
		}

		ext := filepath.Ext(pa)
		if strings.ToLower(ext) != ".xmind" {
			return nil
		}

		rel, err := filepath.Rel(from, pa)
		if err != nil {
			return err
		}

    targetDir := filepath.Join(to, filepath.Dir(rel))
    if err = os.MkdirAll(targetDir, 0666); err != nil {
      return err
    }
		return convertFile(pa, filepath.Join(targetDir, info.Name()+".md"))
	}))
}

func convertFile(from, to string) error {
  fmt.Println("convert", from)
	book, err := xmind.LoadFile(from)
	if err != nil {
		return err
	}

	fw, err := os.Create(to)
	if err != nil {
		return err
	}
	//goland:noinspection GoUnhandledErrorResult
	defer fw.Close()

	err = book.SaveToMarkdown(fw, nil)
	if err != nil {
		return err
	}
	return nil
}

@mei-rune 添加了你想要的方法,我这里定义的方法可以指定分隔符,更加通用。

不过话说回来xmind的notes字段就是一个字符串,里面有回车换行符在xmind里面可以正常显示额。

@mei-rune 我加了个通用转换的命令行工具,可用满足本项目支持的xmind/custom/markdown之间的转换,没啥问题过几天我就发个版了。

安装命令: go install github.com/jan-bar/xmind/cmd/xmind@e09107f