diff --git a/source/formats/CMakeLists.txt b/source/formats/CMakeLists.txt index d5a88e47..85d7c2ed 100644 --- a/source/formats/CMakeLists.txt +++ b/source/formats/CMakeLists.txt @@ -7,7 +7,8 @@ set( srcs gp7/converter.cpp gp7/gp7exporter.cpp gp7/gp7importer.cpp - gp7/parser.cpp + gp7/from_xml.cpp + gp7/to_xml.cpp gpx/bitstream.cpp gpx/filesystem.cpp @@ -63,9 +64,9 @@ set( headers fileformatmanager.h gp7/converter.h + gp7/document.h gp7/gp7exporter.h gp7/gp7importer.h - gp7/parser.h gpx/bitstream.h gpx/filesystem.h diff --git a/source/formats/gp7/converter.cpp b/source/formats/gp7/converter.cpp index 4061f33a..4584460f 100644 --- a/source/formats/gp7/converter.cpp +++ b/source/formats/gp7/converter.cpp @@ -16,7 +16,7 @@ */ #include "converter.h" -#include "parser.h" +#include "document.h" #include #include diff --git a/source/formats/gp7/parser.h b/source/formats/gp7/document.h similarity index 97% rename from source/formats/gp7/parser.h rename to source/formats/gp7/document.h index 8aa728f6..339e4461 100644 --- a/source/formats/gp7/parser.h +++ b/source/formats/gp7/document.h @@ -15,10 +15,9 @@ * along with this program. If not, see . */ -#ifndef FORMATS_GP7_PARSER_H -#define FORMATS_GP7_PARSER_H +#ifndef FORMATS_GP7_DOCUMENT_H +#define FORMATS_GP7_DOCUMENT_H -#include "score/timesignature.h" #include #include #include @@ -391,7 +390,10 @@ struct Document }; /// Parses the score.gpif XML file. -Document parse(const pugi::xml_document &root, Version version); +Document from_xml(const pugi::xml_document &root, Version version); + +/// Creates the score.gpif XML file from the intermediate representation. +pugi::xml_document to_xml(const Document &doc); } // namespace Gp7 diff --git a/source/formats/gp7/parser.cpp b/source/formats/gp7/from_xml.cpp similarity index 99% rename from source/formats/gp7/parser.cpp rename to source/formats/gp7/from_xml.cpp index d97a239a..fe5c8074 100644 --- a/source/formats/gp7/parser.cpp +++ b/source/formats/gp7/from_xml.cpp @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -#include "parser.h" +#include "document.h" #include #include @@ -871,7 +871,7 @@ Gp7::Document::addRhythm(Beat &beat, Rhythm rhythm) } Gp7::Document -Gp7::parse(const pugi::xml_document &root, Version version) +Gp7::from_xml(const pugi::xml_document &root, Version version) { Gp7::Document doc; diff --git a/source/formats/gp7/gp7exporter.cpp b/source/formats/gp7/gp7exporter.cpp index d06e1b4c..5972e312 100644 --- a/source/formats/gp7/gp7exporter.cpp +++ b/source/formats/gp7/gp7exporter.cpp @@ -18,14 +18,125 @@ #include "gp7exporter.h" #include "gp7importer.h" +#include "document.h" + +#include +#include +#ifdef _WIN32 +#include +#endif + +#include Gp7Exporter::Gp7Exporter() : FileFormatExporter(Gp7Importer::getFileFormat()) { } +namespace +{ +/// Custom deleter for use with std::unique_ptr. +struct ZipFileCloser +{ + using pointer = zipFile; + + void operator()(zipFile f) + { + if (f) + zipClose(f, nullptr); + } +}; + +/// Handle to a zipFile. Calls zipClose() when it goes out of scope. +using ZipFileHandle = std::unique_ptr; + +/// Opens a zip file for writing. +static ZipFileHandle +createZipFile(const std::filesystem::path &filename) +{ + zlib_filefunc64_def ffunc; +#ifdef _WIN32 + // Ensure wide strings are used, matching boost::filesystem::path. + fill_win32_filefunc64W(&ffunc); +#else + fill_fopen64_filefunc(&ffunc); +#endif + + ZipFileHandle zip_file; + zip_file.reset(zipOpen2_64(filename.c_str(), 0, nullptr, &ffunc)); + if (!zip_file) + throw FileFormatException("Failed to create zip file."); + + return zip_file; +} + +/// RAII wrapper for creating a file in the archive. +struct ZipFileEntry +{ + ZipFileEntry(zipFile file, const char *filename) : myFile(file) + { + if (zipOpenNewFileInZip64(myFile, filename, nullptr, nullptr, 0, + nullptr, 0, nullptr, Z_DEFLATED, + Z_DEFAULT_COMPRESSION, 1) != ZIP_OK) + { + throw FileFormatException("Failed to create file entry."); + } + } + + ~ZipFileEntry() + { + [[maybe_unused]] const int ret = zipCloseFileInZip(myFile); + assert(ret == ZIP_OK); + } + + zipFile myFile; +}; + +static void +writeVersionInfo(zipFile file) +{ + ZipFileEntry entry(file, "VERSION"); + + const char *contents = "7.0"; + zipWriteInFileInZip(file, contents, strlen(contents)); +} + +/// pugi::xml_writer implementation for writing chunks to a zip entry. +struct zip_entry_writer : public pugi::xml_writer +{ + zip_entry_writer(zipFile file) : myFile(file) + { + } + + void write(const void *data, size_t size) override + { + zipWriteInFileInZip(myFile, data, size); + } + + zipFile myFile; +}; + +static void +writeScore(zipFile file, const Score &score) +{ + ZipFileEntry entry(file, "Content/score.gpif"); + + // TODO - convert score to GP7 + Gp7::Document doc; + pugi::xml_document xml_doc = Gp7::to_xml(doc); + + zip_entry_writer writer(file); + xml_doc.save(writer); +} + +} // namespace + void Gp7Exporter::save(const std::filesystem::path &filename, const Score &score) { - // TODO + // The .gp file format is just a zip file with a different extension. + ZipFileHandle zip_file = createZipFile(filename); + + writeVersionInfo(zip_file.get()); + writeScore(zip_file.get(), score); } diff --git a/source/formats/gp7/gp7importer.cpp b/source/formats/gp7/gp7importer.cpp index 2f582db0..e722dcf4 100644 --- a/source/formats/gp7/gp7importer.cpp +++ b/source/formats/gp7/gp7importer.cpp @@ -18,7 +18,7 @@ #include "gp7importer.h" #include "converter.h" -#include "parser.h" +#include "document.h" #include #ifdef _WIN32 @@ -145,6 +145,6 @@ void Gp7Importer::load(const std::filesystem::path &filename, Score &score) if (!result) throw FileFormatException(result.description()); - Gp7::Document doc = Gp7::parse(xml_doc, Gp7::Version::V7); + Gp7::Document doc = Gp7::from_xml(xml_doc, Gp7::Version::V7); Gp7::convert(doc, score); } diff --git a/source/formats/gp7/to_xml.cpp b/source/formats/gp7/to_xml.cpp new file mode 100644 index 00000000..6c5b310e --- /dev/null +++ b/source/formats/gp7/to_xml.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022 Cameron White + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "document.h" + +namespace Gp7 +{ +pugi::xml_document +to_xml(const Document &doc) +{ + pugi::xml_document root; + + // TODO + + return root; +} +} // namespace Gp7 diff --git a/source/formats/gpx/gpximporter.cpp b/source/formats/gpx/gpximporter.cpp index 72d0a3ff..cf5b9a09 100644 --- a/source/formats/gpx/gpximporter.cpp +++ b/source/formats/gpx/gpximporter.cpp @@ -19,7 +19,7 @@ #include "filesystem.h" -#include +#include #include #include @@ -53,6 +53,6 @@ GpxImporter::load(const std::filesystem::path &filename, Score &score) xml_doc.print(std::cerr); #endif - Gp7::Document doc = Gp7::parse(xml_doc, Gp7::Version::V6); + Gp7::Document doc = Gp7::from_xml(xml_doc, Gp7::Version::V6); Gp7::convert(doc, score); } diff --git a/source/formats/guitar_pro/gp345to7converter.h b/source/formats/guitar_pro/gp345to7converter.h index 60ed8b2e..ecbced10 100644 --- a/source/formats/guitar_pro/gp345to7converter.h +++ b/source/formats/guitar_pro/gp345to7converter.h @@ -19,7 +19,7 @@ #define FORMATS_GP_GP345TO7CONVERTER_H #include "document.h" -#include +#include namespace Gp { diff --git a/source/formats/guitar_pro/guitarproimporter.cpp b/source/formats/guitar_pro/guitarproimporter.cpp index 1b1cec49..5a7a23b1 100644 --- a/source/formats/guitar_pro/guitarproimporter.cpp +++ b/source/formats/guitar_pro/guitarproimporter.cpp @@ -19,7 +19,7 @@ #include "gp345to7converter.h" #include -#include +#include #include #include #include