package cmd import ( "accorder/pkg/calibre" "encoding/xml" "fmt" "log" "os" "strconv" "github.com/araddon/dateparse" "github.com/beevik/etree" "github.com/spf13/cobra" "github.com/spf13/viper" ) // best zotero rdf documentation: // https://github.com/zotero/translators/blob/master/Zotero%20RDF.js // type ZoteroItem struct { XMLName xml.Name `xml:"zoteroItem"` FilePath string `xml:"filePath"` MimeType string `xml:"mimeType"` Publisher string `xml:"publisher,omitempty"` Authors []ZoteroAuthor `xml:"authors"` Title string `xml:"title"` Description string `xml:"description,omitempty"` } type ZoteroAuthor struct { XMLName xml.Name `xml:"authors"` Author string `xml:"author"` } var buildCmd = &cobra.Command{ Use: "build", Short: "Build standalone, portable webapp from Calibre library.", Long: `Build searchable, standalone, portable webapp against the local Calibre library including all the metadata needed. It creates BROWSE_LIBRARY.html in root directory of the local Calibre library. For search (authors, titles, tags...) it uses rendered metadata from static/data{1-8}.js files. Every time the directory path and/or librarian is provided it is saved in the configuration file for the future use (therefore: 'accorder build SESSION' should be enough for the next successful build).`, Args: cobra.ExactArgs(1), PreRun: func(cmd *cobra.Command, args []string) { session := args[0] for vipFlag, cliFlag := range map[string]string{ "librarian_name": "librarian", "local_upload": "directory", "library_uuid": "library-uuid", "library_secret": "library-secret", "server_upload": "server", "bucket_upload": "bucket", } { viper.BindPFlag(fmt.Sprintf("%s.%s", session, vipFlag), cmd.Flags().Lookup(cliFlag)) } }, Run: func(cmd *cobra.Command, args []string) { session := args[0] MissingRequiredFlags( map[string]string{ "librarian_name": "librarian", "local_upload": "directory", }, session, cmd, ) libraryUUID := ViperValue(session, "library_uuid") librarySecret := ViperValue(session, "library_secret") calibrePath := ViperValue(session, "local_upload") librarianName := ViperValue(session, "librarian_name") jsonPath := CliFlagValue(cmd, "jsonpath") bibtexPath := CliFlagValue(cmd, "import-bibtex") doc := etree.NewDocument() if err := doc.ReadFromFile(bibtexPath); err != nil { panic(err) } root := doc.SelectElement("rdf:RDF") for _, attachmentNode := range root.FindElements("[name()='link:type']") { var zoteroItem ZoteroItem var bookOpf calibre.BookOpfW if attachmentNode.Text() != "application/pdf" { continue } newDoc := etree.NewDocument() zoteroUnion := newDoc.CreateElement("zoteroItem") filePathElement := zoteroUnion.CreateElement("filePath") filePathQuery := attachmentNode.Parent().FindElement("[name()='rdf:resource']").SelectAttr("rdf:resource").Value filePathElement.CreateText(filePathQuery) mimeTypeElement := zoteroUnion.CreateElement("mimeType") mimeType := attachmentNode.Text() mimeTypeElement.CreateText(mimeType) bibliographyNode := root.FindElement(fmt.Sprintf("[@rdf:resource='%s']", attachmentNode.Parent().SelectAttr("rdf:about").Value)).Parent().Copy() // newDoc.AddChild(bibliographyNode) // newDoc.WriteTo(os.Stdout) dateQuery := bibliographyNode.FindElement("[name()='dc:date']") if dateQuery != nil { dateElement := zoteroUnion.CreateElement("date") date, err := dateparse.ParseAny(dateQuery.Text()) if err == nil { formattedDate := date.Format("2006-01-02") dateElement.CreateText(formattedDate) bookOpf.Metadata.Published = formattedDate } else { newDateQuery := fmt.Sprintf("1 %s", dateQuery.Text()) newDate, err := dateparse.ParseAny(newDateQuery) if err == nil { newFormattedDate := newDate.Format("2006-01-02") dateElement.CreateText(newFormattedDate) bookOpf.Metadata.Published = newFormattedDate } else { lastChanceDate := dateQuery.Text()[len(dateQuery.Text())-4:] year, err := strconv.Atoi(lastChanceDate) if err == nil { justYear := fmt.Sprintf("%d-01-01", year) dateElement.CreateText(justYear) bookOpf.Metadata.Published = justYear } else { fmt.Println("ERROR parsing date...", err) } } } } publisherQuery := bibliographyNode.FindElement("[name()='foaf:name']") if publisherQuery != nil { publisherElement := zoteroUnion.CreateElement("publisher") publisher := publisherQuery.Text() publisherElement.CreateText(publisher) bookOpf.Metadata.Publisher = publisher } authorsQuery := bibliographyNode.FindElements("[name()='foaf:Person']") authors := zoteroUnion.CreateElement("authors") for _, authorNode := range authorsQuery { var firstName, surName string author := authors.CreateElement("author") firstNameNode := authorNode.FindElement("[name()='foaf:givenName']") if firstNameNode != nil { firstName = firstNameNode.Text() } surNameNode := authorNode.FindElement("[name()='foaf:surname']") if surNameNode != nil { surName = surNameNode.Text() } fullName := fmt.Sprintf("%s %s", firstName, surName) author.CreateText(fullName) bookOpf.Metadata.Creators = append(bookOpf.Metadata.Creators, calibre.Creator{ Role: "aut", Name: fullName, }) } titleQuery := bibliographyNode.FindElement("[name()='dc:title']") if titleQuery != nil { titleNode := zoteroUnion.CreateElement("title") title := titleQuery.Text() titleNode.CreateText(title) bookOpf.Metadata.Title = title } descriptionQuery := bibliographyNode.FindElement("[name()='dcterms:abstract']") if descriptionQuery != nil { descriptionNode := zoteroUnion.CreateElement("description") description := descriptionQuery.Text() descriptionNode.CreateText(description) bookOpf.Metadata.Description = description } // newDoc.WriteTo(os.Stdout) // fmt.Println("") b, err := newDoc.WriteToBytes() if err != nil { log.Fatal(err) } if err := xml.Unmarshal(b, &zoteroItem); err != nil { log.Fatalln(err) } // fmt.Printf("\nZoteroItem: %#v\n", zoteroItem) bookOpf.Version = "2.0" bookOpf.Xmlns = "http://www.idpf.org/2007/opf" bookOpf.UniqueIdentifier = "uuid_id" bookOpf.Metadata.DC = "http://purl.org/dc/elements/1.1/" bookOpf.Metadata.OPF = "http://www.idpf.org/2007/opf" bookOpf.Metadata.Identifiers = append(bookOpf.Metadata.Identifiers, calibre.Identifier{ Scheme: "calibre", Id: "calibre_id", Value: "-1", }) bookOpfOutput, err := xml.MarshalIndent(bookOpf, " ", " ") if err != nil { log.Fatalln(err) } // _ = bookOpfOutput os.Stdout.Write(bookOpfOutput) fmt.Println("") } calibre.RenderStandaloneApp(calibrePath, librarianName, libraryUUID, librarySecret, jsonPath) }, } func init() { rootCmd.AddCommand(buildCmd) buildCmd.Flags().StringP("directory", "d", "", "A local Calibre directory.") buildCmd.Flags().StringP("librarian", "l", "", "Librarian's name.") buildCmd.Flags().StringP("export-json", "e", "", "Path where to render all metadata into JSON.") buildCmd.Flags().StringP("import-bibtex", "i", "", "Import books from BibTex file into Calibre.") buildCmd.Flags().StringP("library-uuid", "u", "", "A library's UUID used if part of MotW.") buildCmd.Flags().StringP("library-secret", "p", "", "A password used if part of MotW.") buildCmd.Flags().StringP("server", "s", "minio.memoryoftheworld.org", "Server.") buildCmd.Flags().StringP("bucket", "b", "", "Server.") CustomHelpOutput(buildCmd) buildCmd.Flags().MarkHidden("library-uuid") buildCmd.Flags().MarkHidden("library-secret") buildCmd.Flags().MarkHidden("server") buildCmd.Flags().MarkHidden("bucket") }