diff --git a/libxisf.cpp b/libxisf.cpp index 46177b4..b03d7f2 100644 --- a/libxisf.cpp +++ b/libxisf.cpp @@ -949,12 +949,12 @@ public: void save(ByteArray &data); void save(std::ostream &io); void writeImage(const Image &image); + static void writeFITSKeyword(pugi::xml_node &node, const FITSKeyword &keyword); private: void writeHeader(); void writeImageElement(pugi::xml_node &node, const Image &image); void writeDataBlockAttributes(pugi::xml_node &image_node, const DataBlock &dataBlock); void writePropertyElement(pugi::xml_node &node, const Property &property); - void writeFITSKeyword(pugi::xml_node &node, const FITSKeyword &keyword); void writeMetadata(pugi::xml_node &node); void updateImageAttachmentPos(pugi::xml_node &root, size_t offset); ByteArray _xisfHeader; @@ -1270,6 +1270,321 @@ void XISFWriter::writeImage(const Image &image) p->writeImage(image); } +class XISFModifyPrivate +{ +public: + void open(const String &name); + void open(const ByteArray &data); + /** Open image from istream. This method takes ownership of *io pointer */ + void open(std::istream *io); + /** Close opended file release all data. */ + void close(); + + void save(const String &name); + void save(ByteArray &data); + void save(std::ostream &io); + + void addFITSKeyword(uint32_t image, const FITSKeyword &keyword); + void updateFITSKeyword(uint32_t image, const FITSKeyword &keyword, bool add); + void removeFITSKeyword(u_int32_t image, const String &name); +private: + void readXISFHeader(); + void parseAttachmentPos(pugi::xml_node &root); + void updateAttachmentPos(pugi::xml_node &root, size_t offset); + + std::unique_ptr _io; + std::unique_ptr _buffer; + + pugi::xml_document _doc; + pugi::xml_node _root; + std::map> _attachmentPos;// pair contain position and size + std::map> _attachmentPosNew; +}; + + +void XISFModifyPrivate::open(const String &name) +{ + close(); + _io = std::make_unique(name.c_str(), std::ios_base::in | std::ios_base::binary); + readXISFHeader(); +} + +void XISFModifyPrivate::open(const ByteArray &data) +{ + close(); + _buffer = std::make_unique(data); + _io = std::make_unique(_buffer.get()); + readXISFHeader(); +} + +void XISFModifyPrivate::open(std::istream *io) +{ + close(); + _io.reset(io); + readXISFHeader(); +} + +void XISFModifyPrivate::close() +{ + _io.reset(); + _buffer.reset(); + _root = pugi::xml_node(); + _doc.reset(); +} + +void XISFModifyPrivate::save(const String &name) +{ + std::ofstream fw(name.c_str(), std::ios_base::out | std::ios_base::binary); + + if(fw.fail()) + throw Error("Failed to open file"); + + save(fw); +} + +void XISFModifyPrivate::save(ByteArray &data) +{ + StreamBuffer buffer; + std::ostream oss(&buffer); + save(oss); + data = buffer.byteArray(); +} + +void XISFModifyPrivate::save(std::ostream &io) +{ + if(!_io || !_root) + throw Error("No input file opened"); + + const char signature[16] = {'X', 'I', 'S', 'F', '0', '1', '0', '0', 0, 0, 0, 0, 0, 0, 0, 0}; + + pugi::xml_document doc; + doc.append_child(pugi::node_comment).set_value("\nExtensible Image Serialization Format - XISF version 1.0\nCreated with libXISF - https://nouspiro.space\n"); + pugi::xml_node root_copy = doc.append_copy(_root); + + uint32_t size = 0; + std::string header; + while(true) + { + std::stringstream xml; + xml.write(signature, sizeof(signature)); + doc.save(xml, "", pugi::format_raw); + header = xml.str(); + if(size != header.size()) + { + size = header.size(); + updateAttachmentPos(root_copy, size); + } + else + { + break; + } + } + + uint32_t headerSize = header.size() - sizeof(signature); + header.replace(8, sizeof(uint32_t), (const char*)&headerSize, sizeof(uint32_t)); + + io.write(header.c_str(), header.size()); + const uint64_t BLOCK_SIZE = 1024*1024*4; + std::vector data(BLOCK_SIZE); + for(auto &pos : _attachmentPos) + { + uint64_t oldPos = pos.second.first; + uint64_t size = pos.second.second; + + _io->seekg(oldPos); + while(size) + { + _io->read(&data[0], std::min(size, BLOCK_SIZE)); + io.write(&data[0], std::min(size, BLOCK_SIZE)); + size -= std::min(size, BLOCK_SIZE); + } + } +} + +void XISFModifyPrivate::addFITSKeyword(uint32_t image, const FITSKeyword &keyword) +{ + if(!_root) + throw Error("No input file opened"); + + pugi::xpath_node_set images = _root.select_nodes("//Image"); + + if(image >= images.size()) + throw Error("Out of bounds"); + + pugi::xml_node imageNode = images[image].node(); + XISFWriterPrivate::writeFITSKeyword(imageNode, keyword); +} + +void XISFModifyPrivate::updateFITSKeyword(uint32_t image, const FITSKeyword &keyword, bool add) +{ + if(!_root) + throw Error("No input file opened"); + + pugi::xpath_node_set images = _root.select_nodes("//Image"); + + if(image >= images.size()) + throw Error("Out of bounds"); + + pugi::xpath_variable_set variables; + variables.set("name", keyword.name.c_str()); + pugi::xml_node imageNode = images[image].node(); + pugi::xml_node keywordNode = imageNode.select_node("FITSKeyword[@name=string($name)]", &variables).node(); + + if(keywordNode) + { + if(keywordNode.attribute("value")) + keywordNode.attribute("value").set_value(keyword.value.c_str()); + else + keywordNode.append_attribute("value").set_value(keyword.value.c_str()); + + if(keywordNode.attribute("comment")) + keywordNode.attribute("comment").set_value(keyword.comment.c_str()); + else + keywordNode.append_attribute("comment").set_value(keyword.comment.c_str()); + } + else if(add) + { + XISFWriterPrivate::writeFITSKeyword(imageNode, keyword); + } +} + +void XISFModifyPrivate::removeFITSKeyword(u_int32_t image, const String &name) +{ + if(!_root) + throw Error("No input file opened"); + + pugi::xpath_node_set images = _root.select_nodes("//Image"); + + if(image >= images.size()) + throw Error("Out of bounds"); + + pugi::xpath_variable_set variables; + variables.set("name", name.c_str()); + pugi::xml_node imageNode = images[image].node(); + pugi::xml_node keywordNode = imageNode.select_node("FITSKeyword[@name=string($name)]", &variables).node(); + + if(keywordNode) + imageNode.remove_child(keywordNode); +} + +void XISFModifyPrivate::readXISFHeader() +{ + char signature[8]; + _io->read(signature, sizeof(signature)); + if(_io->fail()) + throw Error("Failed to read from file"); + + if(memcmp(signature, "XISF0100", sizeof(signature)) != 0) + throw Error("Not valid XISF 1.0 file"); + + uint32_t headerLen[2] = {0}; + _io->read((char*)&headerLen, sizeof(headerLen)); + + ByteArray xisfHeader(headerLen[0]); + _io->read(xisfHeader.data(), headerLen[0]); + + _doc.load_buffer(xisfHeader.data(), xisfHeader.size()); + + _root = _doc.child("xisf"); + + parseAttachmentPos(_root); + + if(!_root || _root.attribute("version").as_string() != std::string("1.0")) + throw Error("Unknown root XML element"); +} + +void XISFModifyPrivate::parseAttachmentPos(pugi::xml_node &root) +{ + pugi::xpath_node_set locationAttributes = root.select_nodes("//@location"); + int i = 0; + for(auto &attr : locationAttributes) + { + std::string locationStr = attr.attribute().as_string(); + std::vector location = splitString(locationStr, ':'); + if(location.size() >= 3 && location[0] == "attachment") + { + uint64_t attachmentPos = std::stoull(location[1]); + uint64_t attachmentSize = std::stoull(location[2]); + _attachmentPos[i] = {attachmentPos, attachmentSize}; + } + i++; + } +} + +void XISFModifyPrivate::updateAttachmentPos(pugi::xml_node &root, size_t offset) +{ + pugi::xpath_node_set locationAttributes = root.select_nodes("//@location"); + for(auto &pos : _attachmentPos) + { + pugi::xml_attribute attr = locationAttributes[pos.first].attribute(); + uint64_t attachmentSize = pos.second.second; + std::string locationStr = "attachment:" + std::to_string(offset) + ":" + std::to_string(attachmentSize); + _attachmentPosNew[pos.first] = pos.second; + offset += attachmentSize; + attr.set_value(locationStr.c_str()); + } +} + +XISFModify::XISFModify() +{ + p = new XISFModifyPrivate(); +} + +XISFModify::~XISFModify() +{ + delete p; +} + +void XISFModify::open(const String &name) +{ + p->open(name); +} + +void XISFModify::open(const ByteArray &data) +{ + p->open(data); +} + +void XISFModify::open(std::istream *io) +{ + p->open(io); +} + +void XISFModify::close() +{ + p->close(); +} + +void XISFModify::save(const String &name) +{ + p->save(name); +} + +void XISFModify::save(ByteArray &data) +{ + p->save(data); +} + +void XISFModify::save(std::ostream &io) +{ + p->save(io); +} + +void XISFModify::addFITSKeyword(uint32_t image, const FITSKeyword &keyword) +{ + p->addFITSKeyword(image, keyword); +} + +void XISFModify::updateFITSKeyword(uint32_t image, const FITSKeyword &keyword, bool add) +{ + p->updateFITSKeyword(image, keyword, add); +} + +void XISFModify::removeFITSKeyword(uint32_t image, const String &name) +{ + p->removeFITSKeyword(image, name); +} + #define STRING_ENUM(map, map2, c, e) { map.insert({#e, c::e}); map2.insert({c::e, #e}); } struct Init diff --git a/libxisf.h b/libxisf.h index 83cdde3..089de20 100644 --- a/libxisf.h +++ b/libxisf.h @@ -35,6 +35,7 @@ namespace LibXISF class XISFReaderPrivate; class XISFWriterPrivate; +class XISFModifyPrivate; class LIBXISF_EXPORT ByteArray { @@ -405,6 +406,42 @@ public: const char* what() const noexcept { return _msg.c_str(); } }; +class LIBXISF_EXPORT XISFModify +{ +public: + XISFModify(); + virtual ~XISFModify(); + void open(const String &name); + void open(const ByteArray &data); + void open(std::istream *io); + void close(); + void save(const String &name); + void save(ByteArray &data); + void save(std::ostream &io); + + /** + * @brief addFITSKeyword append new keyword to image + * @param image index of image to update + * @param keyword that will be added + */ + void addFITSKeyword(uint32_t image, const FITSKeyword &keyword); + /** + * @brief updateFITSKeyword update keyword with same name + * @param image index of image to update + * @param keyword that will be updated + * @param add if true then it will add new keyword in case that it doesn't exists + */ + void updateFITSKeyword(uint32_t image, const FITSKeyword &keyword, bool add); + /** + * @brief removeFITSKeyword remove keyword with name from image + * @param image index of image to update + * @param name of keyword that will be removed + */ + void removeFITSKeyword(uint32_t image, const String &name); +private: + XISFModifyPrivate *p; +}; + template constexpr Image::SampleFormat Image::sampleFormatEnum() {