327 lines
8.8 KiB
Go
327 lines
8.8 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"text/template"
|
|
|
|
"github.com/alexflint/go-arg"
|
|
)
|
|
|
|
type NodeData struct {
|
|
Id string
|
|
Title string
|
|
Body string
|
|
}
|
|
|
|
type Node struct {
|
|
Children []*Node
|
|
Data NodeData
|
|
}
|
|
|
|
type Arguments struct {
|
|
Baseurl string `arg:"required" help:"Base URL of the Confluence instance (required)"`
|
|
Spacekey string `arg:"required" help:"Spacekey to export (required)"`
|
|
User string `arg:"required" help:"User used for authentication (required)"`
|
|
Token string `arg:"required" help:"Token used for authentication (required)"`
|
|
}
|
|
|
|
func (n *Node) writeIndex(w io.Writer) {
|
|
tmpl, err := template.New("root").Parse(`
|
|
{{ define "tree" }}
|
|
{{ if gt (len .Children) 0 }}
|
|
<ul>
|
|
{{ range $index, $element := .Children}}
|
|
<li>
|
|
<a href="{{$element.Data.Id}}.html">{{ $element.Data.Title }}</a>
|
|
{{ template "tree" $element }}
|
|
</li>
|
|
{{ end }}
|
|
</ul>
|
|
{{ end }}
|
|
{{ end }}
|
|
<html>
|
|
{{ template "tree" . }}
|
|
</html>
|
|
`)
|
|
if err != nil {
|
|
fmt.Println("ERROR creating template")
|
|
}
|
|
err = tmpl.Execute(w, n)
|
|
if err != nil {
|
|
fmt.Printf("ERROR executing template: %v", err)
|
|
}
|
|
}
|
|
|
|
func (n *Node) getAllChildren() []*Node {
|
|
var children []*Node
|
|
for _, child := range n.Children {
|
|
children = append(children, child)
|
|
if len(child.Children) > 0 {
|
|
children = append(children, child.getAllChildren()...)
|
|
}
|
|
}
|
|
return children
|
|
}
|
|
|
|
func (n *Node) getChildWithID(id string) (node *Node, hasChild bool) {
|
|
for _, c := range n.Children {
|
|
if c.Data.Id == id {
|
|
return c, true
|
|
}
|
|
}
|
|
empty := Node{}
|
|
return &empty, false
|
|
}
|
|
|
|
func (n *Node) addChild(child *Node) {
|
|
n.Children = append(n.Children, child)
|
|
}
|
|
|
|
func writeHTML(n *Node) {
|
|
f, err := os.Create(n.Data.Id + ".html")
|
|
if err != nil {
|
|
log.Fatalf("Error creating file for ID %v", n.Data.Id)
|
|
}
|
|
defer f.Close()
|
|
log.Printf("Writing file %v", f.Name())
|
|
f.Write([]byte(n.Data.Body))
|
|
}
|
|
|
|
type spaceResult struct {
|
|
Results []struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Status string `json:"status"`
|
|
Title string `json:"title"`
|
|
Ancestors []struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Status string `json:"status"`
|
|
Title string `json:"title"`
|
|
MacroRenderedOutput struct {
|
|
} `json:"macroRenderedOutput"`
|
|
Extensions struct {
|
|
Position int `json:"position"`
|
|
} `json:"extensions"`
|
|
Expandable struct {
|
|
Container string `json:"container"`
|
|
Metadata string `json:"metadata"`
|
|
Restrictions string `json:"restrictions"`
|
|
History string `json:"history"`
|
|
Body string `json:"body"`
|
|
Version string `json:"version"`
|
|
Descendants string `json:"descendants"`
|
|
Space string `json:"space"`
|
|
ChildTypes string `json:"childTypes"`
|
|
Operations string `json:"operations"`
|
|
SchedulePublishDate string `json:"schedulePublishDate"`
|
|
Children string `json:"children"`
|
|
Ancestors string `json:"ancestors"`
|
|
} `json:"_expandable"`
|
|
Links struct {
|
|
Self string `json:"self"`
|
|
Tinyui string `json:"tinyui"`
|
|
Editui string `json:"editui"`
|
|
Webui string `json:"webui"`
|
|
} `json:"_links"`
|
|
} `json:"ancestors"`
|
|
MacroRenderedOutput struct {
|
|
} `json:"macroRenderedOutput"`
|
|
Extensions struct {
|
|
Position int `json:"position"`
|
|
} `json:"extensions"`
|
|
Expandable struct {
|
|
ChildTypes string `json:"childTypes"`
|
|
Container string `json:"container"`
|
|
Metadata string `json:"metadata"`
|
|
Operations string `json:"operations"`
|
|
SchedulePublishDate string `json:"schedulePublishDate"`
|
|
Children string `json:"children"`
|
|
Restrictions string `json:"restrictions"`
|
|
History string `json:"history"`
|
|
Body string `json:"body"`
|
|
Version string `json:"version"`
|
|
Descendants string `json:"descendants"`
|
|
Space string `json:"space"`
|
|
} `json:"_expandable"`
|
|
Links struct {
|
|
Self string `json:"self"`
|
|
Tinyui string `json:"tinyui"`
|
|
Editui string `json:"editui"`
|
|
Webui string `json:"webui"`
|
|
} `json:"_links"`
|
|
} `json:"results"`
|
|
Start int `json:"start"`
|
|
Limit int `json:"limit"`
|
|
Size int `json:"size"`
|
|
Links struct {
|
|
Base string `json:"base"`
|
|
Context string `json:"context"`
|
|
Next string `json:"next"`
|
|
Self string `json:"self"`
|
|
} `json:"_links"`
|
|
}
|
|
|
|
type contentResult struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Status string `json:"status"`
|
|
Title string `json:"title"`
|
|
MacroRenderedOutput struct {
|
|
} `json:"macroRenderedOutput"`
|
|
Body struct {
|
|
View struct {
|
|
Value string `json:"value"`
|
|
Representation string `json:"representation"`
|
|
Expandable struct {
|
|
Webresource string `json:"webresource"`
|
|
EmbeddedContent string `json:"embeddedContent"`
|
|
MediaToken string `json:"mediaToken"`
|
|
Content string `json:"content"`
|
|
} `json:"_expandable"`
|
|
} `json:"view"`
|
|
Expandable struct {
|
|
Editor string `json:"editor"`
|
|
AtlasDocFormat string `json:"atlas_doc_format"`
|
|
ExportView string `json:"export_view"`
|
|
StyledView string `json:"styled_view"`
|
|
Dynamic string `json:"dynamic"`
|
|
Storage string `json:"storage"`
|
|
Editor2 string `json:"editor2"`
|
|
AnonymousExportView string `json:"anonymous_export_view"`
|
|
} `json:"_expandable"`
|
|
} `json:"body"`
|
|
Extensions struct {
|
|
Position int `json:"position"`
|
|
} `json:"extensions"`
|
|
Expandable struct {
|
|
ChildTypes string `json:"childTypes"`
|
|
Container string `json:"container"`
|
|
Metadata string `json:"metadata"`
|
|
Operations string `json:"operations"`
|
|
SchedulePublishDate string `json:"schedulePublishDate"`
|
|
Children string `json:"children"`
|
|
Restrictions string `json:"restrictions"`
|
|
History string `json:"history"`
|
|
Ancestors string `json:"ancestors"`
|
|
Version string `json:"version"`
|
|
Descendants string `json:"descendants"`
|
|
Space string `json:"space"`
|
|
} `json:"_expandable"`
|
|
Links struct {
|
|
Editui string `json:"editui"`
|
|
Webui string `json:"webui"`
|
|
Context string `json:"context"`
|
|
Self string `json:"self"`
|
|
Tinyui string `json:"tinyui"`
|
|
Collection string `json:"collection"`
|
|
Base string `json:"base"`
|
|
} `json:"_links"`
|
|
}
|
|
|
|
func getRequest(args Arguments, url string) ([]byte, error) {
|
|
b64Auth := base64.StdEncoding.EncodeToString([]byte(args.User + ":" + args.Token))
|
|
client := &http.Client{}
|
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Add("Authorization", "Basic "+b64Auth)
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, err
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return body, nil
|
|
}
|
|
|
|
func getSpaceChildren(args Arguments) spaceResult {
|
|
url := args.Baseurl + "/wiki/rest/api/space/" + args.Spacekey + "/content/page?expand=ancestors&limit=9999"
|
|
body, err := getRequest(args, url)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
var result spaceResult
|
|
json.Unmarshal(body, &result)
|
|
return result
|
|
}
|
|
|
|
func getContentFromID(args Arguments, id string) contentResult {
|
|
url := args.Baseurl + "/wiki/rest/api/content/" + id + "?expand=body.view"
|
|
body, err := getRequest(args, url)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
var result contentResult
|
|
json.Unmarshal(body, &result)
|
|
return result
|
|
}
|
|
|
|
func createPageTree(s *spaceResult) *Node {
|
|
var parent *Node
|
|
root := Node{Data: NodeData{Title: "root"}}
|
|
for _, page := range s.Results {
|
|
parent = &root
|
|
// ignore the root node
|
|
if len(page.Ancestors) == 0 {
|
|
continue
|
|
}
|
|
// create branches from ancestors
|
|
for i, ancestor := range page.Ancestors {
|
|
// skip root node
|
|
if i == 0 {
|
|
continue
|
|
}
|
|
node, hasChild := parent.getChildWithID(ancestor.ID)
|
|
if hasChild {
|
|
parent = node
|
|
continue
|
|
}
|
|
n := Node{Data: NodeData{Id: ancestor.ID, Title: ancestor.Title}}
|
|
parent.addChild(&n)
|
|
parent = &n
|
|
}
|
|
// create leaf node
|
|
n := Node{Data: NodeData{Id: page.ID, Title: page.Title}}
|
|
parent.addChild(&n)
|
|
}
|
|
return &root
|
|
}
|
|
|
|
func main() {
|
|
var args Arguments
|
|
arg.MustParse(&args)
|
|
result := getSpaceChildren(args)
|
|
root := createPageTree(&result)
|
|
|
|
fname := "index.html"
|
|
f, err := os.Create(fname)
|
|
if err != nil {
|
|
log.Fatalf("Failed to open %v", fname)
|
|
}
|
|
defer f.Close()
|
|
root.writeIndex(f)
|
|
|
|
pages := root.getAllChildren()
|
|
for _, page := range pages {
|
|
content := getContentFromID(args, page.Data.Id)
|
|
page.Data.Body = content.Body.View.Value
|
|
writeHTML(page)
|
|
}
|
|
}
|