2023-04-08 22:04:55 +07:00
|
|
|
|
package drawio
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bufio"
|
2023-04-09 21:08:35 +07:00
|
|
|
|
"errors"
|
2023-04-08 22:04:55 +07:00
|
|
|
|
"io"
|
2023-04-09 21:08:35 +07:00
|
|
|
|
"io/fs"
|
|
|
|
|
"os"
|
|
|
|
|
"os/exec"
|
|
|
|
|
"path"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
2023-04-08 22:04:55 +07:00
|
|
|
|
|
|
|
|
|
xmlparser "github.com/tamerh/xml-stream-parser"
|
|
|
|
|
)
|
|
|
|
|
|
2023-04-09 21:08:35 +07:00
|
|
|
|
// Создать нового экспортёра диаграмм
|
|
|
|
|
func New(opt ...Option) Exporter {
|
|
|
|
|
options := &Options{
|
|
|
|
|
Application: "drawio",
|
|
|
|
|
Output: "export",
|
|
|
|
|
Format: Format("pdf"),
|
|
|
|
|
}
|
|
|
|
|
for _, opt := range opt {
|
|
|
|
|
opt.apply(options)
|
|
|
|
|
}
|
|
|
|
|
return NewWithOptions(options)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Создать нового экспортёра с параметрами
|
|
|
|
|
func NewWithOptions(opts *Options) Exporter {
|
2023-04-12 22:37:54 +07:00
|
|
|
|
exp := Exporter{opts: opts}
|
|
|
|
|
if opts.openFile == nil {
|
|
|
|
|
exp.openFile = func(name string) (io.ReadCloser, error) {
|
|
|
|
|
return os.Open(name)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
exp.openFile = opts.openFile
|
|
|
|
|
}
|
|
|
|
|
if opts.readDir == nil {
|
|
|
|
|
exp.readDir = func(name string) ([]os.DirEntry, error) {
|
|
|
|
|
return os.ReadDir(name)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
exp.readDir = opts.readDir
|
|
|
|
|
}
|
|
|
|
|
return exp
|
2023-04-09 21:08:35 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Экспортёр диаграмм
|
|
|
|
|
type Exporter struct {
|
2023-04-12 22:37:54 +07:00
|
|
|
|
opts *Options // Параметры экспортёра диаграмм
|
|
|
|
|
openFile OpenFileFunc // Открыть файл
|
|
|
|
|
readDir ReadDirFunc // Прочитать папку на диске
|
2023-04-09 21:08:35 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Экспорт диаграмм из указанный файлов или папок
|
|
|
|
|
func (exp Exporter) Export(fileOrDir ...string) error {
|
|
|
|
|
var (
|
2023-04-12 22:37:54 +07:00
|
|
|
|
commands = []*exec.Cmd{}
|
|
|
|
|
err error
|
|
|
|
|
errs = []error{}
|
|
|
|
|
args = exp.opts.Args()
|
|
|
|
|
info fs.FileInfo
|
2023-04-09 21:08:35 +07:00
|
|
|
|
)
|
|
|
|
|
for _, item := range fileOrDir {
|
|
|
|
|
if info, err = os.Stat(item); err != nil {
|
|
|
|
|
errs = append(errs, err)
|
|
|
|
|
continue
|
|
|
|
|
}
|
2023-04-12 22:37:54 +07:00
|
|
|
|
var addCommands []*exec.Cmd
|
2023-04-09 21:08:35 +07:00
|
|
|
|
if info.IsDir() {
|
2023-04-12 22:37:54 +07:00
|
|
|
|
addCommands, err = exp.ExportDir(item, args)
|
2023-04-09 21:08:35 +07:00
|
|
|
|
} else {
|
2023-04-12 22:37:54 +07:00
|
|
|
|
addCommands, err = exp.ExportFile(item, args)
|
2023-04-09 21:08:35 +07:00
|
|
|
|
}
|
2023-04-12 22:37:54 +07:00
|
|
|
|
commands = append(commands, addCommands...)
|
2023-04-09 21:08:35 +07:00
|
|
|
|
if err != nil {
|
|
|
|
|
errs = append(errs, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-04-12 22:37:54 +07:00
|
|
|
|
if err = RunSequence(commands...); err != nil {
|
|
|
|
|
errs = append(errs, err)
|
2023-04-08 22:04:55 +07:00
|
|
|
|
}
|
2023-04-12 22:37:54 +07:00
|
|
|
|
return errors.Join(errs...)
|
2023-04-08 22:04:55 +07:00
|
|
|
|
}
|
2023-04-09 21:08:35 +07:00
|
|
|
|
|
|
|
|
|
// Экспорт диаграмм из всех файлов в папке
|
2023-04-12 22:37:54 +07:00
|
|
|
|
func (exp Exporter) ExportDir(
|
|
|
|
|
dirPath string, args []string, subDir ...string,
|
|
|
|
|
) ([]*exec.Cmd, error) {
|
2023-04-09 21:08:35 +07:00
|
|
|
|
var (
|
2023-04-12 22:37:54 +07:00
|
|
|
|
commands = []*exec.Cmd{}
|
|
|
|
|
err error
|
|
|
|
|
entries []fs.DirEntry
|
|
|
|
|
errs []error
|
2023-04-09 21:08:35 +07:00
|
|
|
|
)
|
2023-04-12 22:37:54 +07:00
|
|
|
|
if entries, err = exp.readDir(dirPath); err != nil {
|
|
|
|
|
return commands, err
|
2023-04-09 21:08:35 +07:00
|
|
|
|
}
|
|
|
|
|
for _, entry := range entries {
|
|
|
|
|
name := entry.Name()
|
2023-04-12 22:37:54 +07:00
|
|
|
|
var addCommands []*exec.Cmd
|
|
|
|
|
if entry.IsDir() {
|
|
|
|
|
if exp.opts.Recursive {
|
|
|
|
|
deep := len(subDir)
|
|
|
|
|
outDir := make([]string, deep+1)
|
|
|
|
|
copy(outDir, subDir)
|
|
|
|
|
outDir[deep] = name
|
|
|
|
|
addCommands, err = exp.ExportDir(path.Join(dirPath, name), args, outDir...)
|
|
|
|
|
} else {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2023-04-09 21:08:35 +07:00
|
|
|
|
} else {
|
2023-04-12 22:37:54 +07:00
|
|
|
|
addCommands, err = exp.ExportFile(path.Join(dirPath, name), args, subDir...)
|
2023-04-09 21:08:35 +07:00
|
|
|
|
}
|
2023-04-12 22:37:54 +07:00
|
|
|
|
commands = append(commands, addCommands...)
|
2023-04-09 21:08:35 +07:00
|
|
|
|
if err != nil {
|
|
|
|
|
errs = append(errs, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-04-12 22:37:54 +07:00
|
|
|
|
return commands, errors.Join(errs...)
|
2023-04-09 21:08:35 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Экспорт файла с диаграммами
|
2023-04-12 22:37:54 +07:00
|
|
|
|
func (exp Exporter) ExportFile(
|
|
|
|
|
filePath string, args []string, subDir ...string,
|
|
|
|
|
) ([]*exec.Cmd, error) {
|
2023-04-09 21:08:35 +07:00
|
|
|
|
var (
|
2023-04-12 22:37:54 +07:00
|
|
|
|
commands = []*exec.Cmd{}
|
2023-04-09 21:08:35 +07:00
|
|
|
|
err error
|
2023-04-12 22:37:54 +07:00
|
|
|
|
file io.ReadCloser
|
|
|
|
|
diagrams []string
|
2023-04-09 21:08:35 +07:00
|
|
|
|
)
|
2023-04-12 22:37:54 +07:00
|
|
|
|
if file, err = exp.openFile(filePath); err != nil {
|
|
|
|
|
return commands, err
|
2023-04-09 21:08:35 +07:00
|
|
|
|
}
|
|
|
|
|
defer file.Close()
|
|
|
|
|
if diagrams, err = Diagrams(file); err != nil {
|
2023-04-12 22:37:54 +07:00
|
|
|
|
return commands, err
|
2023-04-09 21:08:35 +07:00
|
|
|
|
}
|
|
|
|
|
outDirs := make([]string, 1, len(subDir)+1)
|
2023-04-12 22:37:54 +07:00
|
|
|
|
outDirs[0] = exp.opts.OutDir()
|
2023-04-09 21:08:35 +07:00
|
|
|
|
outDirs = append(outDirs, subDir...)
|
|
|
|
|
output := path.Join(outDirs...)
|
|
|
|
|
for i, name := range diagrams {
|
|
|
|
|
outName := strings.TrimSuffix(path.Base(filePath), path.Ext(filePath))
|
|
|
|
|
if !exp.opts.RemovePageSuffix || len(diagrams) > 1 {
|
|
|
|
|
outName += "-" + name
|
|
|
|
|
}
|
|
|
|
|
outName += exp.opts.OutExt()
|
2023-04-13 19:34:27 +07:00
|
|
|
|
drawioArgs := make([]string, len(args), len(args)+10)
|
2023-04-09 21:08:35 +07:00
|
|
|
|
copy(drawioArgs, args)
|
|
|
|
|
drawioArgs = append(drawioArgs,
|
|
|
|
|
"--page-index", strconv.Itoa(i),
|
|
|
|
|
"--output", path.Join(output, outName),
|
|
|
|
|
"--export", filePath,
|
|
|
|
|
)
|
2023-04-13 19:34:27 +07:00
|
|
|
|
if exp.opts.EnableXvfb {
|
|
|
|
|
drawioArgs = append(drawioArgs,
|
|
|
|
|
"--disable-gpu", "--headless", "--no-sandbox")
|
|
|
|
|
}
|
2023-04-09 21:08:35 +07:00
|
|
|
|
cmd := exec.Command(exp.opts.App(), drawioArgs...)
|
2023-04-12 22:37:54 +07:00
|
|
|
|
commands = append(commands, cmd)
|
|
|
|
|
}
|
|
|
|
|
return commands, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Разбирает данные и возвращает срез имён диаграмм в них
|
|
|
|
|
func Diagrams(reader io.Reader) ([]string, error) {
|
|
|
|
|
var (
|
|
|
|
|
result = []string{}
|
|
|
|
|
br = bufio.NewReader(reader)
|
|
|
|
|
parser = xmlparser.NewXMLParser(
|
|
|
|
|
br, "mxfile", "diagram",
|
|
|
|
|
).ParseAttributesOnly("diagram")
|
|
|
|
|
)
|
|
|
|
|
for xml := range parser.Stream() {
|
|
|
|
|
if xml.Err != nil {
|
|
|
|
|
return result, xml.Err
|
|
|
|
|
}
|
|
|
|
|
if items, ok := xml.Childs["diagram"]; ok {
|
|
|
|
|
for _, item := range items {
|
|
|
|
|
result = append(result, item.Attrs["name"])
|
|
|
|
|
}
|
2023-04-09 21:08:35 +07:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-04-12 22:37:54 +07:00
|
|
|
|
return result, nil
|
2023-04-09 21:08:35 +07:00
|
|
|
|
}
|