A Golang Widget that Converting Xmind to Markdown

Introduction

An XMind file is actually an archived package of several XML files and other attachments compressed using general ZIP algorithm. An XMind archive may contain the following components:

  • content.xml: (required) main data and hierarchy
  • styles.xml: style information
  • meta.xml: meta information
  • META-INF/: meta folder
  • manifest.xml: (required) the manifest of this archive
  • attachments/: attachment folder, used to store attached files
  • markers/: markers folder, used to store custom markers
  • Thumbnails/: thumbnail folder, used to store preview image of this workbook
  • thumbnail.jpg: the preview image

Attribute Visiting Using DFS Algorithm

The content.xml contains main data and hierarchy of this xmind file. The struct of content.xml is as following,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<xmap-content version="1.0">
<sheet id="sheet1">
<topic id="level1">
<title></title>
<children>
<topics type="attached">
<topic id="level2">
<title></title>
<children>
nested topics...
</children>
</topic>
</topics>
</children>
</topic>
</sheet>
</xmap-content>

The major elements is topic elements. A topic element represents a topic in a mind map. A topic may have subtopics (or none), but has only one parent (or none). In view of this, the best method of visiting attribute is Depth First Search algorithm.

Get Hands Dirty

Save the following code into file. Now, you can run go run widget.go to convert xmind to markdown file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package main

import (
"archive/zip"
"encoding/xml"
"io"
"io/ioutil"
"os"
"path/filepath"
)

// XML struct starts

type Topic struct {
TagName xml.Name `xml:"topic"`
Title string `xml:"title"`

SubTopic []Topic `xml:"children>topics>topic"`
}

type Sheet struct {
TagName xml.Name `xml:"sheet"`
Topic Topic `xml:"topic"`
}

type XmapContent struct {
TagName xml.Name `xml:"xmap-content"`
Sheet Sheet `xml:"sheet"`
}

// XML struct ends

// Error handling
func HandleError(err error) {
if err != nil {
panic(err)
}
}

// Extracting specific file from xmind zip file, say content.xml
func ExtractFile(zipDir string, xmindFile string, fileName string) {
zipReader, err := zip.OpenReader(xmindFile)
HandleError(err)

for _, f := range zipReader.File {
if fileName != f.Name {
continue
}

fpath := filepath.Join(zipDir, f.Name)
err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm)
HandleError(err)

outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
HandleError(err)

rc, err := f.Open()
HandleError(err)

_, err = io.Copy(outFile, rc)
HandleError(err)

outFile.Close()
rc.Close()
}

zipReader.Close()
}

// Adopting Depth First Search algorithm to visit xmind topics
func DFSTopic(topic Topic, mdContent *string, depth int) {
var title = topic.Title

var titlePrefix = ""
for i := 0; i < depth; i++ {
titlePrefix += "#"
}

*mdContent += titlePrefix + " " + title + "\n\n"

for i := range topic.SubTopic {
DFSTopic(topic.SubTopic[i], mdContent, depth+1)
}
}

const tmpDir = "/tmp/"
const xmindContentFile = "content.xml"

func main() {
// add your xmind file here, then run this script
var xmindFile = "your-xmind-file.xmind"
var outputFile = "output-markdown.md"

// extract content.xml file
ExtractFile(tmpDir, xmindFile, xmindContentFile)

// read data from content.xml
var content, err = ioutil.ReadFile(tmpDir + xmindContentFile)
HandleError(err)

// parse the xml content
var xmapContent XmapContent
err = xml.Unmarshal(content, &xmapContent)
HandleError(err)

// extract xmind topics to markdown format
var mdContent = ""
DFSTopic(xmapContent.Sheet.Topic, &mdContent, 1)

// dump the markdown file
var byteF = []byte(mdContent)
err = ioutil.WriteFile(outputFile, byteF, 0644)
HandleError(err)
}

References