From a877083e96aed13360a0e60fb36222efac58124e Mon Sep 17 00:00:00 2001 From: Augists Date: Fri, 11 Feb 2022 11:10:58 +0800 Subject: [PATCH 01/10] add support for importing docx file --- controllers/BookController.go | 12 +- models/BookModel.go | 47 ++- utils/docx2md.go | 551 ++++++++++++++++++++++++++++++++++ views/book/index.tpl | 2 +- 4 files changed, 606 insertions(+), 6 deletions(-) create mode 100644 utils/docx2md.go diff --git a/controllers/BookController.go b/controllers/BookController.go index 6fd9d4af..2e26708d 100644 --- a/controllers/BookController.go +++ b/controllers/BookController.go @@ -340,7 +340,7 @@ func (c *BookController) UploadCover() { fileName := "cover_" + strconv.FormatInt(time.Now().UnixNano(), 16) //附件路径按照项目组织 -// filePath := filepath.Join("uploads", book.Identify, "images", fileName+ext) + // filePath := filepath.Join("uploads", book.Identify, "images", fileName+ext) filePath := filepath.Join(conf.WorkingDirectory, "uploads", book.Identify, "images", fileName+ext) path := filepath.Dir(filePath) @@ -571,7 +571,7 @@ func (c *BookController) Copy() { } } -//导入zip压缩包 +// 导入zip压缩包或docx func (c *BookController) Import() { file, moreFile, err := c.GetFile("import-file") @@ -608,7 +608,7 @@ func (c *BookController) Import() { ext := filepath.Ext(moreFile.Filename) - if !strings.EqualFold(ext, ".zip") { + if !strings.EqualFold(ext, ".zip") && !strings.EqualFold(ext, ".docx") { c.JsonResult(6004, "不支持的文件类型") } @@ -643,7 +643,11 @@ func (c *BookController) Import() { book.Editor = "markdown" book.Theme = "default" - go book.ImportBook(tempPath, c.Lang) + if strings.EqualFold(ext, ".zip") { + go book.ImportBook(tempPath, c.Lang) + } else if strings.EqualFold(ext, ".docx") { + go book.ImportWordBook(tempPath, c.Lang) + } logs.Info("用户[", c.Member.Account, "]导入了项目 ->", book) diff --git a/models/BookModel.go b/models/BookModel.go index d78f6ca2..77e441d2 100644 --- a/models/BookModel.go +++ b/models/BookModel.go @@ -680,7 +680,7 @@ func (book *Book) ResetDocumentNumber(bookId int) { } } -//导入项目 +// 导入zip项目 func (book *Book) ImportBook(zipPath string, lang string) error { if !filetil.FileExists(zipPath) { return errors.New("文件不存在 => " + zipPath) @@ -978,6 +978,51 @@ func (book *Book) ImportBook(zipPath string, lang string) error { return err } +// 导入docx项目 +func (book *Book) ImportWordBook(docxPath string, lang string) error { + if !filetil.FileExists(docxPath) { + return errors.New("文件不存在") + } + docxPath = strings.Replace(docxPath, "\\", "/", -1) + + o := orm.NewOrm() + + o.Insert(book) + relationship := NewRelationship() + relationship.BookId = book.BookId + relationship.RoldId = 0 + relationship.MemberId = book.MemberId + relationship.Insert() + + doc := NewDocument() + doc.BookId = book.BookId + doc.MemberId = book.MemberId + docIdentify := strings.Replace(strings.TrimPrefix(docxPath, os.TempDir()+"/"), "/", "-", -1) + + if ok, err := regexp.MatchString(`[a-z]+[a-zA-Z0-9_.\-]*$`, docIdentify); !ok || err != nil { + docIdentify = "import-" + docIdentify + } + + doc.Identify = docIdentify + + if doc.Markdown, err := util.Docx2md(docxPath, false); err != nil { + logs.Error("导入doc项目转换异常 => ", err) + } + + doc.Content = string(blackfriday.Run([]byte(doc.Markdown))) + + doc.Version = time.Now().Unix() + + for _, line := range strings.Split(doc.Markdown, "\n") { + if strings.HasPrefix(line, "#") { + docName := strings.TrimLeft(line, "#") + break + } + } + + doc.DocumentName = strings.TrimSpace(docName) +} + func (book *Book) FindForRoleId(bookId, memberId int) (conf.BookRole, error) { o := orm.NewOrm() diff --git a/utils/docx2md.go b/utils/docx2md.go new file mode 100644 index 00000000..be0d2e12 --- /dev/null +++ b/utils/docx2md.go @@ -0,0 +1,551 @@ +// https://github.com/mattn/docx2md +// License MIT +package util + +import ( + "archive/zip" + "bytes" + "encoding/base64" + "encoding/xml" + "errors" + "flag" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "path" + "path/filepath" + "runtime" + "strconv" + "strings" + + "github.com/mattn/go-runewidth" +) + +// Relationship is +type Relationship struct { + Text string `xml:",chardata"` + ID string `xml:"Id,attr"` + Type string `xml:"Type,attr"` + Target string `xml:"Target,attr"` + TargetMode string `xml:"TargetMode,attr"` +} + +// Relationships is +type Relationships struct { + XMLName xml.Name `xml:"Relationships"` + Text string `xml:",chardata"` + Xmlns string `xml:"xmlns,attr"` + Relationship []Relationship `xml:"Relationship"` +} + +// TextVal is +type TextVal struct { + Text string `xml:",chardata"` + Val string `xml:"val,attr"` +} + +// NumberingLvl is +type NumberingLvl struct { + Text string `xml:",chardata"` + Ilvl string `xml:"ilvl,attr"` + Tplc string `xml:"tplc,attr"` + Tentative string `xml:"tentative,attr"` + Start TextVal `xml:"start"` + NumFmt TextVal `xml:"numFmt"` + LvlText TextVal `xml:"lvlText"` + LvlJc TextVal `xml:"lvlJc"` + PPr struct { + Text string `xml:",chardata"` + Ind struct { + Text string `xml:",chardata"` + Left string `xml:"left,attr"` + Hanging string `xml:"hanging,attr"` + } `xml:"ind"` + } `xml:"pPr"` + RPr struct { + Text string `xml:",chardata"` + U struct { + Text string `xml:",chardata"` + Val string `xml:"val,attr"` + } `xml:"u"` + RFonts struct { + Text string `xml:",chardata"` + Hint string `xml:"hint,attr"` + } `xml:"rFonts"` + } `xml:"rPr"` +} + +// Numbering is +type Numbering struct { + XMLName xml.Name `xml:"numbering"` + Text string `xml:",chardata"` + Wpc string `xml:"wpc,attr"` + Cx string `xml:"cx,attr"` + Cx1 string `xml:"cx1,attr"` + Mc string `xml:"mc,attr"` + O string `xml:"o,attr"` + R string `xml:"r,attr"` + M string `xml:"m,attr"` + V string `xml:"v,attr"` + Wp14 string `xml:"wp14,attr"` + Wp string `xml:"wp,attr"` + W10 string `xml:"w10,attr"` + W string `xml:"w,attr"` + W14 string `xml:"w14,attr"` + W15 string `xml:"w15,attr"` + W16se string `xml:"w16se,attr"` + Wpg string `xml:"wpg,attr"` + Wpi string `xml:"wpi,attr"` + Wne string `xml:"wne,attr"` + Wps string `xml:"wps,attr"` + Ignorable string `xml:"Ignorable,attr"` + AbstractNum []struct { + Text string `xml:",chardata"` + AbstractNumID string `xml:"abstractNumId,attr"` + RestartNumberingAfterBreak string `xml:"restartNumberingAfterBreak,attr"` + Nsid TextVal `xml:"nsid"` + MultiLevelType TextVal `xml:"multiLevelType"` + Tmpl TextVal `xml:"tmpl"` + Lvl []NumberingLvl `xml:"lvl"` + } `xml:"abstractNum"` + Num []struct { + Text string `xml:",chardata"` + NumID string `xml:"numId,attr"` + AbstractNumID TextVal `xml:"abstractNumId"` + } `xml:"num"` +} + +type file struct { + rels Relationships + num Numbering + r *zip.ReadCloser + embed bool + list map[string]int +} + +// Node is +type Node struct { + XMLName xml.Name + Attrs []xml.Attr `xml:"-"` + Content []byte `xml:",innerxml"` + Nodes []Node `xml:",any"` +} + +// UnmarshalXML is +func (n *Node) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + n.Attrs = start.Attr + type node Node + + return d.DecodeElement((*node)(n), &start) +} + +func escape(s, set string) string { + replacer := []string{} + for _, r := range []rune(set) { + rs := string(r) + replacer = append(replacer, rs, `\`+rs) + } + return strings.NewReplacer(replacer...).Replace(s) +} + +func (zf *file) extract(rel *Relationship, w io.Writer) error { + err := os.MkdirAll(filepath.Dir(rel.Target), 0755) + if err != nil { + return err + } + for _, f := range zf.r.File { + if f.Name != "word/"+rel.Target { + continue + } + rc, err := f.Open() + if err != nil { + return err + } + defer rc.Close() + + b := make([]byte, f.UncompressedSize64) + n, err := rc.Read(b) + if err != nil && err != io.EOF { + return err + } + if zf.embed { + fmt.Fprintf(w, "![](data:image/png;base64,%s)", + base64.StdEncoding.EncodeToString(b[:n])) + } else { + err = ioutil.WriteFile(rel.Target, b, 0644) + if err != nil { + return err + } + fmt.Fprintf(w, "![](%s)", escape(rel.Target, "()")) + } + break + } + return nil +} + +func attr(attrs []xml.Attr, name string) (string, bool) { + for _, attr := range attrs { + if attr.Name.Local == name { + return attr.Value, true + } + } + return "", false +} + +func (zf *file) walk(node *Node, w io.Writer) error { + switch node.XMLName.Local { + case "hyperlink": + fmt.Fprint(w, "[") + var cbuf bytes.Buffer + for _, n := range node.Nodes { + if err := zf.walk(&n, &cbuf); err != nil { + return err + } + } + fmt.Fprint(w, escape(cbuf.String(), "[]")) + fmt.Fprint(w, "]") + + fmt.Fprint(w, "(") + if id, ok := attr(node.Attrs, "id"); ok { + for _, rel := range zf.rels.Relationship { + if id == rel.ID { + fmt.Fprint(w, escape(rel.Target, "()")) + break + } + } + } + fmt.Fprint(w, ")") + case "t": + fmt.Fprint(w, string(node.Content)) + case "pPr": + code := false + for _, n := range node.Nodes { + switch n.XMLName.Local { + case "ind": + if left, ok := attr(n.Attrs, "left"); ok { + if i, err := strconv.Atoi(left); err == nil && i > 0 { + fmt.Fprint(w, strings.Repeat(" ", i/360)) + } + } + case "pStyle": + if val, ok := attr(n.Attrs, "val"); ok { + if strings.HasPrefix(val, "Heading") { + if i, err := strconv.Atoi(val[7:]); err == nil && i > 0 { + fmt.Fprint(w, strings.Repeat("#", i)+" ") + } + } else if val == "Code" { + code = true + } else { + if i, err := strconv.Atoi(val); err == nil && i > 0 { + fmt.Fprint(w, strings.Repeat("#", i)+" ") + } + } + } + case "numPr": + numID := "" + ilvl := "" + numFmt := "" + start := 1 + ind := 0 + for _, nn := range n.Nodes { + if nn.XMLName.Local == "numId" { + if val, ok := attr(nn.Attrs, "val"); ok { + numID = val + } + } + if nn.XMLName.Local == "ilvl" { + if val, ok := attr(nn.Attrs, "val"); ok { + ilvl = val + } + } + } + for _, num := range zf.num.Num { + if numID != num.NumID { + continue + } + for _, abnum := range zf.num.AbstractNum { + if abnum.AbstractNumID != num.AbstractNumID.Val { + continue + } + for _, ablvl := range abnum.Lvl { + if ablvl.Ilvl != ilvl { + continue + } + if i, err := strconv.Atoi(ablvl.Start.Val); err == nil { + start = i + } + if i, err := strconv.Atoi(ablvl.PPr.Ind.Left); err == nil { + ind = i / 360 + } + numFmt = ablvl.NumFmt.Val + break + } + break + } + break + } + + fmt.Fprint(w, strings.Repeat(" ", ind)) + switch numFmt { + case "decimal", "aiueoFullWidth": + key := fmt.Sprintf("%s:%d", numID, ind) + cur, ok := zf.list[key] + if !ok { + zf.list[key] = start + } else { + zf.list[key] = cur + 1 + } + fmt.Fprintf(w, "%d. ", zf.list[key]) + case "bullet": + fmt.Fprint(w, "* ") + } + } + } + if code { + fmt.Fprint(w, "`") + } + for _, n := range node.Nodes { + if err := zf.walk(&n, w); err != nil { + return err + } + } + if code { + fmt.Fprint(w, "`") + } + case "tbl": + var rows [][]string + for _, tr := range node.Nodes { + if tr.XMLName.Local != "tr" { + continue + } + var cols []string + for _, tc := range tr.Nodes { + if tc.XMLName.Local != "tc" { + continue + } + var cbuf bytes.Buffer + if err := zf.walk(&tc, &cbuf); err != nil { + return err + } + cols = append(cols, strings.Replace(cbuf.String(), "\n", "", -1)) + } + rows = append(rows, cols) + } + maxcol := 0 + for _, cols := range rows { + if len(cols) > maxcol { + maxcol = len(cols) + } + } + widths := make([]int, maxcol) + for _, row := range rows { + for i := 0; i < maxcol; i++ { + if i < len(row) { + width := runewidth.StringWidth(row[i]) + if widths[i] < width { + widths[i] = width + } + } + } + } + for i, row := range rows { + if i == 0 { + for j := 0; j < maxcol; j++ { + fmt.Fprint(w, "|") + fmt.Fprint(w, strings.Repeat(" ", widths[j])) + } + fmt.Fprint(w, "|\n") + for j := 0; j < maxcol; j++ { + fmt.Fprint(w, "|") + fmt.Fprint(w, strings.Repeat("-", widths[j])) + } + fmt.Fprint(w, "|\n") + } + for j := 0; j < maxcol; j++ { + fmt.Fprint(w, "|") + if j < len(row) { + width := runewidth.StringWidth(row[j]) + fmt.Fprint(w, escape(row[j], "|")) + fmt.Fprint(w, strings.Repeat(" ", widths[j]-width)) + } else { + fmt.Fprint(w, strings.Repeat(" ", widths[j])) + } + } + fmt.Fprint(w, "|\n") + } + fmt.Fprint(w, "\n") + case "r": + bold := false + italic := false + strike := false + for _, n := range node.Nodes { + if n.XMLName.Local != "rPr" { + continue + } + for _, nn := range n.Nodes { + switch nn.XMLName.Local { + case "b": + bold = true + case "i": + italic = true + case "strike": + strike = true + } + } + } + if strike { + fmt.Fprint(w, "~~") + } + if bold { + fmt.Fprint(w, "**") + } + if italic { + fmt.Fprint(w, "*") + } + var cbuf bytes.Buffer + for _, n := range node.Nodes { + if err := zf.walk(&n, &cbuf); err != nil { + return err + } + } + fmt.Fprint(w, escape(cbuf.String(), `*~\`)) + if italic { + fmt.Fprint(w, "*") + } + if bold { + fmt.Fprint(w, "**") + } + if strike { + fmt.Fprint(w, "~~") + } + case "p": + for _, n := range node.Nodes { + if err := zf.walk(&n, w); err != nil { + return err + } + } + fmt.Fprintln(w) + case "blip": + if id, ok := attr(node.Attrs, "embed"); ok { + for _, rel := range zf.rels.Relationship { + if id != rel.ID { + continue + } + if err := zf.extract(&rel, w); err != nil { + return err + } + } + } + case "Fallback": + case "txbxContent": + var cbuf bytes.Buffer + for _, n := range node.Nodes { + if err := zf.walk(&n, &cbuf); err != nil { + return err + } + } + fmt.Fprintln(w, "\n```\n"+cbuf.String()+"```") + default: + for _, n := range node.Nodes { + if err := zf.walk(&n, w); err != nil { + return err + } + } + } + + return nil +} + +func readFile(f *zip.File) (*Node, error) { + rc, err := f.Open() + defer rc.Close() + + b, _ := ioutil.ReadAll(rc) + if err != nil { + return nil, err + } + + var node Node + err = xml.Unmarshal(b, &node) + if err != nil { + return nil, err + } + return &node, nil +} + +func findFile(files []*zip.File, target string) *zip.File { + for _, f := range files { + if ok, _ := path.Match(target, f.Name); ok { + return f + } + } + return nil +} + +func Docx2md(arg string, embed bool) (string, error) { + r, err := zip.OpenReader(arg) + if err != nil { + return err + } + defer r.Close() + + var rels Relationships + var num Numbering + + for _, f := range r.File { + switch f.Name { + case "word/_rels/document.xml.rels": + rc, err := f.Open() + defer rc.Close() + + b, _ := ioutil.ReadAll(rc) + if err != nil { + return err + } + + err = xml.Unmarshal(b, &rels) + if err != nil { + return err + } + case "word/numbering.xml": + rc, err := f.Open() + defer rc.Close() + + b, _ := ioutil.ReadAll(rc) + if err != nil { + return err + } + + err = xml.Unmarshal(b, &num) + if err != nil { + return err + } + } + } + + f := findFile(r.File, "word/document*.xml") + if f == nil { + return errors.New("incorrect document") + } + node, err := readFile(f) + if err != nil { + return err + } + + var buf bytes.Buffer + zf := &file{ + r: r, + rels: rels, + num: num, + embed: embed, + list: make(map[string]int), + } + err = zf.walk(node, &buf) + if err != nil { + return nil, err + } + + return buf.String(), nil +} diff --git a/views/book/index.tpl b/views/book/index.tpl index 6c6d819c..c04f8b08 100644 --- a/views/book/index.tpl +++ b/views/book/index.tpl @@ -465,7 +465,7 @@ 'required': true, 'validateInitialCount': true, "language" : "{{i18n $.Lang "common.upload_lang"}}", - 'allowedFileExtensions': ['zip'], + 'allowedFileExtensions': ['zip', 'docx'], 'msgPlaceholder' : '{{i18n $.Lang "message.file_type_placeholder"}}', 'elErrorContainer' : "#import-book-form-error-message", 'uploadExtraData' : function () { From 9bb4f5a5dbfe1e39a19ee11837c2377c7bcdcae0 Mon Sep 17 00:00:00 2001 From: Augists Date: Fri, 11 Feb 2022 11:29:12 +0800 Subject: [PATCH 02/10] util -> utils in docx2md --- utils/docx2md.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/docx2md.go b/utils/docx2md.go index be0d2e12..a8c44b59 100644 --- a/utils/docx2md.go +++ b/utils/docx2md.go @@ -1,6 +1,6 @@ // https://github.com/mattn/docx2md // License MIT -package util +package utils import ( "archive/zip" From 2733498933b2393464d2cd3c4b75c929c80f392c Mon Sep 17 00:00:00 2001 From: Augists Date: Fri, 11 Feb 2022 11:34:29 +0800 Subject: [PATCH 03/10] fix not enough return --- utils/docx2md.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/utils/docx2md.go b/utils/docx2md.go index a8c44b59..bee192d3 100644 --- a/utils/docx2md.go +++ b/utils/docx2md.go @@ -487,7 +487,7 @@ func findFile(files []*zip.File, target string) *zip.File { func Docx2md(arg string, embed bool) (string, error) { r, err := zip.OpenReader(arg) if err != nil { - return err + return nil, err } defer r.Close() @@ -502,12 +502,12 @@ func Docx2md(arg string, embed bool) (string, error) { b, _ := ioutil.ReadAll(rc) if err != nil { - return err + return nil, err } err = xml.Unmarshal(b, &rels) if err != nil { - return err + return nil, err } case "word/numbering.xml": rc, err := f.Open() @@ -515,23 +515,23 @@ func Docx2md(arg string, embed bool) (string, error) { b, _ := ioutil.ReadAll(rc) if err != nil { - return err + return nil, err } err = xml.Unmarshal(b, &num) if err != nil { - return err + return nil, err } } } f := findFile(r.File, "word/document*.xml") if f == nil { - return errors.New("incorrect document") + return nil, errors.New("incorrect document") } node, err := readFile(f) if err != nil { - return err + return nil, err } var buf bytes.Buffer From 7de992adb0af2ea66cee459021594b706046bc35 Mon Sep 17 00:00:00 2001 From: Augists Date: Fri, 11 Feb 2022 11:36:30 +0800 Subject: [PATCH 04/10] woshishazi --- models/BookModel.go | 1 + utils/docx2md.go | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/models/BookModel.go b/models/BookModel.go index 77e441d2..67fda8ce 100644 --- a/models/BookModel.go +++ b/models/BookModel.go @@ -1007,6 +1007,7 @@ func (book *Book) ImportWordBook(docxPath string, lang string) error { if doc.Markdown, err := util.Docx2md(docxPath, false); err != nil { logs.Error("导入doc项目转换异常 => ", err) + exit(1) } doc.Content = string(blackfriday.Run([]byte(doc.Markdown))) diff --git a/utils/docx2md.go b/utils/docx2md.go index bee192d3..142b3c86 100644 --- a/utils/docx2md.go +++ b/utils/docx2md.go @@ -487,7 +487,7 @@ func findFile(files []*zip.File, target string) *zip.File { func Docx2md(arg string, embed bool) (string, error) { r, err := zip.OpenReader(arg) if err != nil { - return nil, err + return "", err } defer r.Close() @@ -502,12 +502,12 @@ func Docx2md(arg string, embed bool) (string, error) { b, _ := ioutil.ReadAll(rc) if err != nil { - return nil, err + return "", err } err = xml.Unmarshal(b, &rels) if err != nil { - return nil, err + return "", err } case "word/numbering.xml": rc, err := f.Open() @@ -515,23 +515,23 @@ func Docx2md(arg string, embed bool) (string, error) { b, _ := ioutil.ReadAll(rc) if err != nil { - return nil, err + return "", err } err = xml.Unmarshal(b, &num) if err != nil { - return nil, err + return "", err } } } f := findFile(r.File, "word/document*.xml") if f == nil { - return nil, errors.New("incorrect document") + return "", errors.New("incorrect document") } node, err := readFile(f) if err != nil { - return nil, err + return "", err } var buf bytes.Buffer @@ -544,7 +544,7 @@ func Docx2md(arg string, embed bool) (string, error) { } err = zf.walk(node, &buf) if err != nil { - return nil, err + return "", err } return buf.String(), nil From 42c82450ed234fa5681adb54af6c990aed2134fe Mon Sep 17 00:00:00 2001 From: Augists Date: Fri, 11 Feb 2022 13:11:59 +0800 Subject: [PATCH 05/10] import but not use --- utils/docx2md.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/docx2md.go b/utils/docx2md.go index 142b3c86..af8a8dca 100644 --- a/utils/docx2md.go +++ b/utils/docx2md.go @@ -8,15 +8,15 @@ import ( "encoding/base64" "encoding/xml" "errors" - "flag" + _ "flag" "fmt" "io" "io/ioutil" - "log" + _ "log" "os" "path" "path/filepath" - "runtime" + _ "runtime" "strconv" "strings" From 4eec37e7c9cb532d9ef934b9477bb587f2860a7b Mon Sep 17 00:00:00 2001 From: Augists Date: Fri, 11 Feb 2022 13:31:03 +0800 Subject: [PATCH 06/10] fix bugs --- go.mod | 1 + go.sum | 4 ++++ models/BookModel.go | 50 +++++++++++++++++++++++---------------------- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index ad9a1f1b..7d2418c5 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/kardianos/service v1.1.0 github.com/lib/pq v1.7.0 // indirect github.com/lifei6671/gocaptcha v0.1.1 + github.com/mattn/go-runewidth v0.0.13 github.com/mattn/go-sqlite3 v2.0.3+incompatible github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/russross/blackfriday/v2 v2.1.0 diff --git a/go.sum b/go.sum index e74efd15..7140e3f5 100644 --- a/go.sum +++ b/go.sum @@ -126,6 +126,8 @@ github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lifei6671/gocaptcha v0.1.1 h1:5cvU3w0bK8eJm1P6AiQoPuicoZVAgKKpREBxXF9IaHo= github.com/lifei6671/gocaptcha v0.1.1/go.mod h1:6QlTU2WzFhzqylAJWSo3OANfKCraGccJwbK01P5fFmI= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= @@ -174,6 +176,8 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/models/BookModel.go b/models/BookModel.go index 67fda8ce..db0f0b94 100644 --- a/models/BookModel.go +++ b/models/BookModel.go @@ -979,49 +979,51 @@ func (book *Book) ImportBook(zipPath string, lang string) error { } // 导入docx项目 -func (book *Book) ImportWordBook(docxPath string, lang string) error { +func (book *Book) ImportWordBook(docxPath string, lang string) (err error) { if !filetil.FileExists(docxPath) { return errors.New("文件不存在") } - docxPath = strings.Replace(docxPath, "\\", "/", -1) + docxPath = strings.Replace(docxPath, "\\", "/", -1) o := orm.NewOrm() o.Insert(book) relationship := NewRelationship() relationship.BookId = book.BookId - relationship.RoldId = 0 + // relationship.RoldId = 0 relationship.MemberId = book.MemberId relationship.Insert() - doc := NewDocument() - doc.BookId = book.BookId - doc.MemberId = book.MemberId - docIdentify := strings.Replace(strings.TrimPrefix(docxPath, os.TempDir()+"/"), "/", "-", -1) + doc := NewDocument() + doc.BookId = book.BookId + doc.MemberId = book.MemberId + docIdentify := strings.Replace(strings.TrimPrefix(docxPath, os.TempDir()+"/"), "/", "-", -1) - if ok, err := regexp.MatchString(`[a-z]+[a-zA-Z0-9_.\-]*$`, docIdentify); !ok || err != nil { - docIdentify = "import-" + docIdentify - } + if ok, err := regexp.MatchString(`[a-z]+[a-zA-Z0-9_.\-]*$`, docIdentify); !ok || err != nil { + docIdentify = "import-" + docIdentify + } - doc.Identify = docIdentify + doc.Identify = docIdentify - if doc.Markdown, err := util.Docx2md(docxPath, false); err != nil { - logs.Error("导入doc项目转换异常 => ", err) - exit(1) - } + if doc.Markdown, err = utils.Docx2md(docxPath, false); err != nil { + logs.Error("导入doc项目转换异常 => ", err) + return err + } - doc.Content = string(blackfriday.Run([]byte(doc.Markdown))) + doc.Content = string(blackfriday.Run([]byte(doc.Markdown))) - doc.Version = time.Now().Unix() + doc.Version = time.Now().Unix() - for _, line := range strings.Split(doc.Markdown, "\n") { - if strings.HasPrefix(line, "#") { - docName := strings.TrimLeft(line, "#") - break - } - } + var docName string + for _, line := range strings.Split(doc.Markdown, "\n") { + if strings.HasPrefix(line, "#") { + docName = strings.TrimLeft(line, "#") + break + } + } - doc.DocumentName = strings.TrimSpace(docName) + doc.DocumentName = strings.TrimSpace(docName) + return err } func (book *Book) FindForRoleId(bookId, memberId int) (conf.BookRole, error) { From f3c1c4bd3a7fc0bbc632bfd8b350c22b01ddb9a9 Mon Sep 17 00:00:00 2001 From: Augists Date: Fri, 11 Feb 2022 13:42:26 +0800 Subject: [PATCH 07/10] html accept docx --- views/book/index.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/book/index.tpl b/views/book/index.tpl index c04f8b08..9cb1951e 100644 --- a/views/book/index.tpl +++ b/views/book/index.tpl @@ -248,7 +248,7 @@
- +
From 63c0ac0a1d90b0aa2a8496986c911e534149d1a5 Mon Sep 17 00:00:00 2001 From: Augists Date: Fri, 11 Feb 2022 16:37:42 +0800 Subject: [PATCH 08/10] add support for importing docx file as book --- conf/lang/zh-cn.ini | 2 +- models/BookModel.go | 28 ++++++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/conf/lang/zh-cn.ini b/conf/lang/zh-cn.ini index 93625896..75588975 100644 --- a/conf/lang/zh-cn.ini +++ b/conf/lang/zh-cn.ini @@ -127,7 +127,7 @@ project_id_existed = 文档标识已被使用 project_id_error = 项目标识有误 project_id_length = 项目标识必须小于50字符 import_file_empty = 请选择需要上传的文件 -file_type_placeholder = 请选择Zip文件 +file_type_placeholder = 请选择Zip或Docx文件 publish_to_queue = 发布任务已推送到任务队列,稍后将在后台执行。 team_name_empty = 团队名称不能为空 operate_failed = 操作失败 diff --git a/models/BookModel.go b/models/BookModel.go index db0f0b94..c91c1af7 100644 --- a/models/BookModel.go +++ b/models/BookModel.go @@ -990,9 +990,13 @@ func (book *Book) ImportWordBook(docxPath string, lang string) (err error) { o.Insert(book) relationship := NewRelationship() relationship.BookId = book.BookId - // relationship.RoldId = 0 + relationship.RoleId = 0 relationship.MemberId = book.MemberId - relationship.Insert() + err = relationship.Insert() + if err != nil { + logs.Error("插入项目与用户关联 -> ", err) + return err + } doc := NewDocument() doc.BookId = book.BookId @@ -1010,8 +1014,16 @@ func (book *Book) ImportWordBook(docxPath string, lang string) (err error) { return err } + // fmt.Println("===doc.Markdown===") + // fmt.Println(doc.Markdown) + // fmt.Println("==================") + doc.Content = string(blackfriday.Run([]byte(doc.Markdown))) + // fmt.Println("===doc.Content===") + // fmt.Println(doc.Content) + // fmt.Println("==================") + doc.Version = time.Now().Unix() var docName string @@ -1023,6 +1035,18 @@ func (book *Book) ImportWordBook(docxPath string, lang string) (err error) { } doc.DocumentName = strings.TrimSpace(docName) + + doc.DocumentId = book.MemberId + + if err := doc.InsertOrUpdate("document_name", "book_id", "markdown", "content"); err != nil { + logs.Error(doc.DocumentId, err) + } + if err != nil { + logs.Error("导入项目异常 => ", err) + book.Description = "【项目导入存在错误:" + err.Error() + "】" + } + logs.Info("项目导入完毕 => ", book.BookName) + book.ReleaseContent(book.BookId, lang) return err } From 247dbe8a2d1b6c99e0136bc96598b6d879e7e395 Mon Sep 17 00:00:00 2001 From: Augists Date: Tue, 15 Feb 2022 10:08:01 +0800 Subject: [PATCH 09/10] fix image path use /upload/filename/media/imagename.ext --- utils/docx2md.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/utils/docx2md.go b/utils/docx2md.go index af8a8dca..19a004ee 100644 --- a/utils/docx2md.go +++ b/utils/docx2md.go @@ -12,7 +12,7 @@ import ( "fmt" "io" "io/ioutil" - _ "log" + "log" "os" "path" "path/filepath" @@ -123,6 +123,7 @@ type file struct { r *zip.ReadCloser embed bool list map[string]int + name string } // Node is @@ -151,7 +152,7 @@ func escape(s, set string) string { } func (zf *file) extract(rel *Relationship, w io.Writer) error { - err := os.MkdirAll(filepath.Dir(rel.Target), 0755) + err := os.MkdirAll(filepath.Join("uploads", zf.name, filepath.Dir(rel.Target)), 0755) if err != nil { return err } @@ -174,11 +175,11 @@ func (zf *file) extract(rel *Relationship, w io.Writer) error { fmt.Fprintf(w, "![](data:image/png;base64,%s)", base64.StdEncoding.EncodeToString(b[:n])) } else { - err = ioutil.WriteFile(rel.Target, b, 0644) + err = ioutil.WriteFile(filepath.Join("uploads", zf.name, rel.Target), b, 0644) if err != nil { return err } - fmt.Fprintf(w, "![](%s)", escape(rel.Target, "()")) + fmt.Fprintf(w, "![](%s)", "/"+filepath.Join("uploads", zf.name, escape(rel.Target, "()"))) } break } @@ -534,6 +535,13 @@ func Docx2md(arg string, embed bool) (string, error) { return "", err } + fileNames := strings.Split(arg, "/") + fileName := fileNames[len(fileNames)-1] + // make sure the file name + if !strings.HasSuffix(fileName, ".docx") { + log.Fatal("File name must end with .docx") + } + var buf bytes.Buffer zf := &file{ r: r, @@ -541,6 +549,7 @@ func Docx2md(arg string, embed bool) (string, error) { num: num, embed: embed, list: make(map[string]int), + name: fileName, } err = zf.walk(node, &buf) if err != nil { From 6703b97a01f887c6c26e01c5809a2a85c6a31470 Mon Sep 17 00:00:00 2001 From: Augists Date: Tue, 15 Feb 2022 14:36:05 +0800 Subject: [PATCH 10/10] remove .docx in name of directory use /upload/filename(without .docx)/media/imagename.ext --- utils/docx2md.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/utils/docx2md.go b/utils/docx2md.go index 19a004ee..64676855 100644 --- a/utils/docx2md.go +++ b/utils/docx2md.go @@ -152,7 +152,11 @@ func escape(s, set string) string { } func (zf *file) extract(rel *Relationship, w io.Writer) error { - err := os.MkdirAll(filepath.Join("uploads", zf.name, filepath.Dir(rel.Target)), 0755) + err := os.MkdirAll( + filepath.Join("uploads", + strings.TrimSuffix(zf.name, ".docx"), + filepath.Dir(rel.Target)), + 0755) if err != nil { return err } @@ -175,11 +179,18 @@ func (zf *file) extract(rel *Relationship, w io.Writer) error { fmt.Fprintf(w, "![](data:image/png;base64,%s)", base64.StdEncoding.EncodeToString(b[:n])) } else { - err = ioutil.WriteFile(filepath.Join("uploads", zf.name, rel.Target), b, 0644) + err = ioutil.WriteFile( + filepath.Join("uploads", + strings.TrimSuffix(zf.name, ".docx"), + rel.Target), + b, 0644) if err != nil { return err } - fmt.Fprintf(w, "![](%s)", "/"+filepath.Join("uploads", zf.name, escape(rel.Target, "()"))) + fmt.Fprintf(w, "![](%s)", "/"+filepath.Join( + "uploads", + strings.TrimSuffix(zf.name, ".docx"), + escape(rel.Target, "()"))) } break }