package drawio import ( "bufio" "errors" "io" "io/fs" "os" "os/exec" "path" "strconv" "strings" xmlparser "github.com/tamerh/xml-stream-parser" ) // Создать нового экспортёра диаграмм 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 { 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 = os.ReadDir } else { exp.readDir = opts.readDir } return exp } // Экспортёр диаграмм type Exporter struct { opts *Options // Параметры экспортёра диаграмм openFile OpenFileFunc // Открыть файл readDir ReadDirFunc // Прочитать папку на диске } // Экспорт диаграмм из указанный файлов или папок func (exp Exporter) Export(fileOrDir ...string) error { var ( commands = []*exec.Cmd{} errs = []error{} args = exp.opts.Args() info fs.FileInfo ) for _, item := range fileOrDir { var err error if info, err = os.Stat(item); err != nil { errs = append(errs, err) continue } var addCommands []*exec.Cmd if info.IsDir() { addCommands, err = exp.ExportDir(item, args) } else { addCommands, err = exp.ExportFile(item, args) } commands = append(commands, addCommands...) if err != nil { errs = append(errs, err) } } if err := RunSequence(commands...); err != nil { errs = append(errs, err) } return errors.Join(errs...) } // Экспорт диаграмм из всех файлов в папке func (exp Exporter) ExportDir( dirPath string, args []string, subDir ...string, ) ([]*exec.Cmd, error) { var ( commands = []*exec.Cmd{} err error entries []fs.DirEntry errs []error ) if entries, err = exp.readDir(dirPath); err != nil { return commands, err } for _, entry := range entries { name := entry.Name() 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 } } else { addCommands, err = exp.ExportFile(path.Join(dirPath, name), args, subDir...) } commands = append(commands, addCommands...) if err != nil { errs = append(errs, err) } } return commands, errors.Join(errs...) } // Экспорт файла с диаграммами func (exp Exporter) ExportFile( filePath string, args []string, subDir ...string, ) ([]*exec.Cmd, error) { const AdditionalArgsNumber = 10 var ( commands = []*exec.Cmd{} err error file io.ReadCloser diagrams []string ) if file, err = exp.openFile(filePath); err != nil { return commands, err } defer func() { _ = file.Close() }() if diagrams, err = Diagrams(file); err != nil { return commands, err } outDirs := make([]string, 1, len(subDir)+1) outDirs[0] = exp.opts.OutDir() outDirs = append(outDirs, subDir...) output := path.Join(outDirs...) for index, 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() drawioArgs := make([]string, len(args), len(args)+AdditionalArgsNumber) copy(drawioArgs, args) drawioArgs = append(drawioArgs, "--page-index", strconv.Itoa(index), "--output", path.Join(output, outName), "--export", filePath, ) if exp.opts.EnableXvfb { drawioArgs = append(drawioArgs, "--disable-gpu", "--headless", "--no-sandbox") } cmd := exec.Command(exp.opts.App(), drawioArgs...) //nolint:gosec 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"]) } } } return result, nil }