Commit db202320 authored by ElenaSubbotina's avatar ElenaSubbotina

OdfFormatReader - presentation - support convert ole objects (and other embedded)

parent 56ecea3f
...@@ -470,16 +470,16 @@ void _oox_drawing::serialize_xfrm(std::wostream & strm, const std::wstring & nam ...@@ -470,16 +470,16 @@ void _oox_drawing::serialize_xfrm(std::wostream & strm, const std::wstring & nam
CP_XML_NODE(L"a:off") CP_XML_NODE(L"a:off")
{ {
if (inGroup) //if (inGroup)
{ {
CP_XML_ATTR(L"x", x); CP_XML_ATTR(L"x", x);
CP_XML_ATTR(L"y", y); CP_XML_ATTR(L"y", y);
} }
else //else
{ //{
CP_XML_ATTR(L"x", 0); // CP_XML_ATTR(L"x", 0);
CP_XML_ATTR(L"y", 0); // CP_XML_ATTR(L"y", 0);
} //}
} }
if (cx > 0 || cy > 0) if (cx > 0 || cy > 0)
......
...@@ -74,6 +74,7 @@ static std::wstring get_mime_type(const std::wstring & extension) ...@@ -74,6 +74,7 @@ static std::wstring get_mime_type(const std::wstring & extension)
else if (L"wav" == extension) return L"audio/wav"; else if (L"wav" == extension) return L"audio/wav";
else if (L"bin" == extension) return L"application/vnd.openxmlformats-officedocument.oleObject"; else if (L"bin" == extension) return L"application/vnd.openxmlformats-officedocument.oleObject";
else if (L"xlsx" == extension) return L"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; else if (L"xlsx" == extension) return L"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
else if (L"doc" == extension) return L"application/msword";
else return L"application/octet-stream"; else return L"application/octet-stream";
...@@ -135,8 +136,8 @@ bool content_types_file::add_or_find_override(const std::wstring & fileName) ...@@ -135,8 +136,8 @@ bool content_types_file::add_or_find_override(const std::wstring & fileName)
} }
std::wstring content_type; std::wstring content_type;
int pos = fileName.rfind(L".");
int pos = fileName.rfind(L".");
std::wstring extension = pos >= 0 ? fileName.substr(pos + 1) : L""; std::wstring extension = pos >= 0 ? fileName.substr(pos + 1) : L"";
if (extension == L"xlsx") if (extension == L"xlsx")
...@@ -362,7 +363,10 @@ void embeddings::write(const std::wstring & RootPath) ...@@ -362,7 +363,10 @@ void embeddings::write(const std::wstring & RootPath)
if ( items[i].mediaInternal && items[i].valid && if ( items[i].mediaInternal && items[i].valid &&
(items[i].type == typeMsObject || items[i].type == typeOleObject)) (items[i].type == typeMsObject || items[i].type == typeOleObject))
{ {
content_types.add_or_find_override(std::wstring(L"/word/") + items[i].outputName); int pos = items[i].outputName.rfind(L".");
std::wstring extension = pos >= 0 ? items[i].outputName.substr(pos + 1) : L"";
content_types.add_or_find_default(extension);
std::wstring file_name_out = RootPath + FILE_SEPARATOR_STR + items[i].outputName; std::wstring file_name_out = RootPath + FILE_SEPARATOR_STR + items[i].outputName;
......
...@@ -514,7 +514,7 @@ void pptx_conversion_context::end_page() ...@@ -514,7 +514,7 @@ void pptx_conversion_context::end_page()
} }
get_slide_context().serialize_background(current_slide().Background()); get_slide_context().serialize_background(current_slide().Background());
get_slide_context().serialize_objects(current_slide().Data()); get_slide_context().serialize_objects (current_slide().Data());
get_slide_context().serialize_animations(current_slide().Timing()); get_slide_context().serialize_animations(current_slide().Timing());
get_slide_context().dump_rels(current_slide().Rels());//hyperlinks, mediaitems, ... get_slide_context().dump_rels(current_slide().Rels());//hyperlinks, mediaitems, ...
...@@ -543,9 +543,9 @@ std::pair<int,int> pptx_conversion_context::add_author_comments(std::wstring aut ...@@ -543,9 +543,9 @@ std::pair<int,int> pptx_conversion_context::add_author_comments(std::wstring aut
void pptx_conversion_context::end_master() void pptx_conversion_context::end_master()
{ {
get_slide_context().serialize_background(current_master().Background(),true); get_slide_context().serialize_background (current_master().Background(),true);
get_slide_context().serialize_objects(current_master().Data()); get_slide_context().serialize_objects (current_master().Data());
get_slide_context().serialize_HeaderFooter(current_master().DataExtra()); get_slide_context().serialize_HeaderFooter (current_master().DataExtra());
get_slide_context().dump_rels(current_master().Rels());//hyperlinks, mediaitems, ... get_slide_context().dump_rels(current_master().Rels());//hyperlinks, mediaitems, ...
......
...@@ -87,10 +87,10 @@ void pptx_serialize_image(std::wostream & strm, _pptx_drawing & val) ...@@ -87,10 +87,10 @@ void pptx_serialize_image(std::wostream & strm, _pptx_drawing & val)
{ {
CP_XML_NODE(L"p:cNvPr") CP_XML_NODE(L"p:cNvPr")
{ {
CP_XML_ATTR(L"id", val.id); CP_XML_ATTR(L"id", val.id);
CP_XML_ATTR(L"name", val.name); CP_XML_ATTR(L"name", val.name);
oox_serialize_hlink(CP_XML_STREAM(),val.hlinks); oox_serialize_hlink(CP_XML_STREAM(), val.hlinks);
} }
CP_XML_NODE(L"p:cNvPicPr") CP_XML_NODE(L"p:cNvPicPr")
...@@ -218,7 +218,6 @@ void pptx_serialize_chart(std::wostream & strm, _pptx_drawing & val) ...@@ -218,7 +218,6 @@ void pptx_serialize_chart(std::wostream & strm, _pptx_drawing & val)
} // p:graphicFrame } // p:graphicFrame
} // CP_XML_WRITER } // CP_XML_WRITER
} }
void pptx_serialize_table(std::wostream & strm, _pptx_drawing & val) void pptx_serialize_table(std::wostream & strm, _pptx_drawing & val)
{ {
CP_XML_WRITER(strm) CP_XML_WRITER(strm)
...@@ -260,11 +259,55 @@ void pptx_serialize_table(std::wostream & strm, _pptx_drawing & val) ...@@ -260,11 +259,55 @@ void pptx_serialize_table(std::wostream & strm, _pptx_drawing & val)
} }
void pptx_serialize_object(std::wostream & strm, _pptx_drawing & val)
{
CP_XML_WRITER(strm)
{
CP_XML_NODE(L"p:graphicFrame")
{
CP_XML_NODE(L"p:nvGraphicFramePr")
{
CP_XML_NODE(L"p:cNvPr")
{
CP_XML_ATTR(L"id", val.id);
CP_XML_ATTR(L"name", val.name);
}
CP_XML_NODE(L"p:cNvGraphicFramePr");
CP_XML_NODE(L"p:nvPr");
}
val.serialize_xfrm(CP_XML_STREAM(), L"p");
//oox_serialize_ln(CP_XML_STREAM(),val.additional);
CP_XML_NODE(L"a:graphic")
{
CP_XML_NODE(L"a:graphicData")
{
CP_XML_ATTR(L"uri", L"http://schemas.openxmlformats.org/presentationml/2006/ole");
CP_XML_NODE(L"p:oleObj")
{
CP_XML_ATTR(L"r:id", val.objectId);
CP_XML_ATTR(L"progId", val.objectProgId);
CP_XML_ATTR(L"imgW", val.cx );
CP_XML_ATTR(L"imgH", val.cy );
CP_XML_NODE(L"p:embed");
val.id = 0;
pptx_serialize_image(CP_XML_STREAM(), val);
}
}
}
} // p:graphicFrame
} // CP_XML_WRITER
}
void _pptx_drawing::serialize(std::wostream & strm) void _pptx_drawing::serialize(std::wostream & strm)
{ {
if (type == typeShape) if (type == typeShape)
{ {
serialize_shape(strm); pptx_serialize_shape(strm, *this);
} }
else if (type == typeImage) else if (type == typeImage)
{ {
...@@ -278,6 +321,11 @@ void _pptx_drawing::serialize(std::wostream & strm) ...@@ -278,6 +321,11 @@ void _pptx_drawing::serialize(std::wostream & strm)
{ {
pptx_serialize_table(strm, *this); pptx_serialize_table(strm, *this);
} }
else if (type == typeMsObject ||
type == typeOleObject)
{
pptx_serialize_object(strm, *this);
}
} }
......
This diff is collapsed.
...@@ -238,6 +238,7 @@ private: ...@@ -238,6 +238,7 @@ private:
element_ptr comments_; element_ptr comments_;
element_ptr media_; element_ptr media_;
element_ptr embeddings_;
}; };
// xlsx_document // xlsx_document
......
...@@ -80,6 +80,7 @@ public: ...@@ -80,6 +80,7 @@ public:
void start_frame(); void start_frame();
void set_image (std::wstring const & path); void set_image (std::wstring const & path);
void set_chart (std::wstring const & path); void set_chart (std::wstring const & path);
void set_object (const std::wstring & path, const std::wstring & progId);
void set_ole_object (const std::wstring & path, const std::wstring & progId); void set_ole_object (const std::wstring & path, const std::wstring & progId);
void set_text_box (); void set_text_box ();
void end_frame(); void end_frame();
...@@ -118,14 +119,7 @@ public: ...@@ -118,14 +119,7 @@ public:
void set_page_number(); void set_page_number();
void set_date_time(); void set_date_time();
private: private:
void process_common_properties(drawing_object_description& obj,_pptx_drawing & drawing);
void default_set(); void default_set();
void process_shape (drawing_object_description& obj);
void process_image (drawing_object_description& obj);
void process_chart (drawing_object_description& obj);
void process_table (drawing_object_description& obj);
void process_object (drawing_object_description& obj);
int hlinks_size_; int hlinks_size_;
......
...@@ -36,7 +36,6 @@ ...@@ -36,7 +36,6 @@
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <boost/foreach.hpp>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/regex.hpp> #include <boost/regex.hpp>
...@@ -48,16 +47,20 @@ ...@@ -48,16 +47,20 @@
#include "style_graphic_properties.h" #include "style_graphic_properties.h"
#include "odfcontext.h" #include "odfcontext.h"
#include "../docx/xlsx_package.h"
#include "../docx/docx_package.h"
#include "../docx/pptx_package.h"
#include "datatypes/length.h" #include "datatypes/length.h"
#include "datatypes/borderstyle.h" #include "datatypes/borderstyle.h"
#include "../../../OfficeUtils/src/OfficeUtils.h"
#include "../../../Common/3dParty/pole/pole.h"
namespace cpdoccore { namespace cpdoccore {
namespace odf_reader { namespace odf_reader {
// draw-image-attlist
/// draw-image-attlist
void draw_image_attlist::add_attributes( const xml::attributes_wc_ptr & Attributes ) void draw_image_attlist::add_attributes( const xml::attributes_wc_ptr & Attributes )
{ {
CP_APPLY_ATTR(L"draw:filter-name", draw_filter_name_); CP_APPLY_ATTR(L"draw:filter-name", draw_filter_name_);
...@@ -283,5 +286,89 @@ void draw_object_ole::add_child_element( xml::sax * Reader, const std::wstring & ...@@ -283,5 +286,89 @@ void draw_object_ole::add_child_element( xml::sax * Reader, const std::wstring &
CP_NOT_APPLICABLE_ELM(); CP_NOT_APPLICABLE_ELM();
} }
std::wstring draw_object_ole::detectObject(const std::wstring &fileName)
{
POLE::Storage *storage = new POLE::Storage(fileName.c_str());
if (storage == NULL) return L"";
if (storage->open(false, false) == false)
{
delete storage;
return L"";
}
std::wstring prog;
POLE::Stream* pStream = new POLE::Stream(storage, "CompObj");
if ((pStream) && (pStream->size() > 28))
{
//skip the CompObjHeader
pStream->seek(28);
int sz_obj = pStream->size() - 28;
std::vector<std::string> str;
while (sz_obj > 0)
{
_UINT32 sz = 0;
pStream->read((unsigned char*)&sz, 4); sz_obj-= 4;
if (sz > sz_obj)
break;
unsigned char *data = new unsigned char[sz];
pStream->read(data, sz);
str.push_back(std::string((char*)data, sz));
delete []data;
sz_obj-= sz;
}
if (!str.empty())
{
prog = std::wstring (str.back().begin(), str.back().end());
}
delete pStream;
}
delete storage;
return prog;
}
std::wstring draw_object::office_convert(odf_document * odfDocument, int type)
{
std::wstring href_result;
std::wstring folderPath = odfDocument->get_folder();
std::wstring objectOutPath = FileSystem::Directory::CreateDirectoryWithUniqueName(folderPath);
if (type == 2)
{
oox::package::xlsx_document outputXlsx;
oox::xlsx_conversion_context conversionXlsxContext ( odfDocument);
conversionXlsxContext.set_output_document (&outputXlsx);
//conversionContext.set_font_directory (fontsPath);
if (odfDocument->xlsx_convert(conversionXlsxContext))
{
outputXlsx.write(objectOutPath);
href_result = common_xlink_attlist_.href_.get_value_or(L"Object");
int pos = href_result.find(L"./");
if (pos >= 0) href_result = href_result.substr(2);
href_result = L"xlsx" + href_result + L".xlsx";
}
}
if (!href_result.empty())
{
std::wstring temp_file = folderPath + FILE_SEPARATOR_STR + href_result;
COfficeUtils oCOfficeUtils(NULL);
oCOfficeUtils.CompressFileOrDirectory(objectOutPath.c_str(), temp_file.c_str(), -1);
}
FileSystem::Directory::DeleteDirectory(objectOutPath);
return href_result;
}
} }
} }
...@@ -292,16 +292,18 @@ public: ...@@ -292,16 +292,18 @@ public:
static const ElementType type = typeDrawObject; static const ElementType type = typeDrawObject;
CPDOCCORE_DEFINE_VISITABLE(); CPDOCCORE_DEFINE_VISITABLE();
virtual void docx_convert(oox::docx_conversion_context & Context); virtual void docx_convert (oox::docx_conversion_context & Context);
virtual void xlsx_convert(oox::xlsx_conversion_context & Context); virtual void xlsx_convert (oox::xlsx_conversion_context & Context);
virtual void pptx_convert(oox::pptx_conversion_context & Context); virtual void pptx_convert (oox::pptx_conversion_context & Context);
draw_object_attlist draw_object_attlist_; draw_object_attlist draw_object_attlist_;
odf_types::common_xlink_attlist common_xlink_attlist_; odf_types::common_xlink_attlist common_xlink_attlist_;
private: private:
virtual void add_attributes( const xml::attributes_wc_ptr & Attributes ); virtual void add_attributes ( const xml::attributes_wc_ptr & Attributes );
virtual void add_child_element( xml::sax * Reader, const std::wstring & Ns, const std::wstring & Name); virtual void add_child_element ( xml::sax * Reader, const std::wstring & Ns, const std::wstring & Name);
std::wstring office_convert(odf_document * odfDocument, int type);
}; };
CP_REGISTER_OFFICE_ELEMENT2(draw_object); CP_REGISTER_OFFICE_ELEMENT2(draw_object);
......
...@@ -49,7 +49,6 @@ ...@@ -49,7 +49,6 @@
#include "draw_common.h" #include "draw_common.h"
#include "../docx/docx_drawing.h" #include "../docx/docx_drawing.h"
#include "../docx/xlsx_package.h"
#include "chart_build_oox.h" #include "chart_build_oox.h"
#include "calcs_styles.h" #include "calcs_styles.h"
...@@ -57,9 +56,6 @@ ...@@ -57,9 +56,6 @@
#include "datatypes/length.h" #include "datatypes/length.h"
#include "datatypes/borderstyle.h" #include "datatypes/borderstyle.h"
#include "../../../OfficeUtils/src/OfficeUtils.h"
#include "../../../Common/3dParty/pole/pole.h"
namespace cpdoccore { namespace cpdoccore {
using namespace odf_types; using namespace odf_types;
...@@ -1463,7 +1459,7 @@ void draw_object::docx_convert(oox::docx_conversion_context & Context) ...@@ -1463,7 +1459,7 @@ void draw_object::docx_convert(oox::docx_conversion_context & Context)
if (objectBuild.object_type_ == 1) //диаграмма if (objectBuild.object_type_ == 1) //диаграмма
{ {
drawing->type = oox::typeChart; drawing->type = oox::typeChart;
bool isMediaInternal = true; bool isMediaInternal = true;
drawing->objectId = Context.add_mediaitem(href, drawing->type, isMediaInternal, href); drawing->objectId = Context.add_mediaitem(href, drawing->type, isMediaInternal, href);
...@@ -1511,47 +1507,26 @@ void draw_object::docx_convert(oox::docx_conversion_context & Context) ...@@ -1511,47 +1507,26 @@ void draw_object::docx_convert(oox::docx_conversion_context & Context)
} }
Context.get_drawing_context().clear_stream_frame(); Context.get_drawing_context().clear_stream_frame();
} }
if (objectBuild.object_type_ == 4) //embedded sheet else if (objectBuild.object_type_ == 4) //embedded sheet
{ {
bool & use_image_replace = Context.get_drawing_context().get_use_image_replace(); bool & use_image_replace = Context.get_drawing_context().get_use_image_replace();
use_image_replace = true; use_image_replace = true;
oox::package::xlsx_document outputXlsx; std::wstring href_new = office_convert(&objectSubDoc, 2);
oox::xlsx_conversion_context conversionXlsxContext ( &objectSubDoc);
conversionXlsxContext.set_output_document (&outputXlsx);
//conversionContext.set_font_directory (fontsPath);
if (objectSubDoc.xlsx_convert(conversionXlsxContext))
{
drawing->type = oox::typeMsObject;
std::wstring objectXlsxPath = FileSystem::Directory::CreateDirectoryWithUniqueName(folderPath);
outputXlsx.write(objectXlsxPath);
href = Context.get_drawing_context().get_current_object_name() + L".xlsx";
std::wstring temp_file = folderPath + FILE_SEPARATOR_STR + href; if (!href_new.empty())
{
COfficeUtils oCOfficeUtils(NULL); drawing->type = oox::typeMsObject;
oCOfficeUtils.CompressFileOrDirectory(objectXlsxPath.c_str(), temp_file.c_str(), -1);
FileSystem::Directory::DeleteDirectory(objectXlsxPath);
bool isMediaInternal = true; bool isMediaInternal = true;
drawing->objectId = Context.add_mediaitem(href, drawing->type, isMediaInternal, temp_file);
href += FILE_SEPARATOR_STR + href_new;
drawing->objectId = Context.add_mediaitem(href, drawing->type, isMediaInternal, href);
drawing->objectProgId = L"Excel.Sheet.12"; drawing->objectProgId = L"Excel.Sheet.12";
} }
else
{
objectBuild.object_type_ = 0;
}
} }
else
if (objectBuild.object_type_ == 0)
{ {
//замещающая картинка(если она конечно присутствует) //замещающая картинка(если она конечно присутствует)
bool & use_image_replace = Context.get_drawing_context().get_use_image_replace(); bool & use_image_replace = Context.get_drawing_context().get_use_image_replace();
use_image_replace = true; use_image_replace = true;
} }
...@@ -1574,10 +1549,10 @@ void draw_object_ole::docx_convert(oox::docx_conversion_context & Context) ...@@ -1574,10 +1549,10 @@ void draw_object_ole::docx_convert(oox::docx_conversion_context & Context)
if (href.empty()) return; if (href.empty()) return;
draw_frame* frame = Context.get_drawing_context().get_current_frame(); //owner draw_frame* frame = Context.get_drawing_context().get_current_frame(); //owner
if (!frame) return; if (!frame) return;
oox::_docx_drawing * drawing = dynamic_cast<oox::_docx_drawing *>(frame->oox_drawing_.get()); oox::_docx_drawing * drawing = dynamic_cast<oox::_docx_drawing *>(frame->oox_drawing_.get());
if (!drawing) return; if (!drawing) return;
drawing->type = oox::typeOleObject; drawing->type = oox::typeOleObject;
...@@ -1588,51 +1563,5 @@ void draw_object_ole::docx_convert(oox::docx_conversion_context & Context) ...@@ -1588,51 +1563,5 @@ void draw_object_ole::docx_convert(oox::docx_conversion_context & Context)
drawing->objectProgId = detectObject(objectPath); drawing->objectProgId = detectObject(objectPath);
} }
std::wstring draw_object_ole::detectObject(const std::wstring &fileName)
{
POLE::Storage *storage = new POLE::Storage(fileName.c_str());
if (storage == NULL) return L"";
if (storage->open(false, false) == false)
{
delete storage;
return L"";
}
std::wstring prog;
POLE::Stream* pStream = new POLE::Stream(storage, "CompObj");
if ((pStream) && (pStream->size() > 28))
{
//skip the CompObjHeader
pStream->seek(28);
int sz_obj = pStream->size() - 28;
std::vector<std::string> str;
while (sz_obj > 0)
{
_UINT32 sz = 0;
pStream->read((unsigned char*)&sz, 4); sz_obj-= 4;
if (sz > sz_obj)
break;
unsigned char *data = new unsigned char[sz];
pStream->read(data, sz);
str.push_back(std::string((char*)data, sz));
delete []data;
sz_obj-= sz;
}
if (!str.empty())
{
prog = std::wstring (str.back().begin(), str.back().end());
}
delete pStream;
}
delete storage;
return prog;
}
} }
} }
...@@ -61,6 +61,8 @@ ...@@ -61,6 +61,8 @@
#include "datatypes/length.h" #include "datatypes/length.h"
#include "datatypes/borderstyle.h" #include "datatypes/borderstyle.h"
#include "../../../OfficeUtils/src/OfficeUtils.h"
namespace cpdoccore { namespace cpdoccore {
using namespace odf_types; using namespace odf_types;
...@@ -262,11 +264,9 @@ void draw_text_box::pptx_convert(oox::pptx_conversion_context & Context) ...@@ -262,11 +264,9 @@ void draw_text_box::pptx_convert(oox::pptx_conversion_context & Context)
void draw_object::pptx_convert(oox::pptx_conversion_context & Context) void draw_object::pptx_convert(oox::pptx_conversion_context & Context)
{ {
try { try {
const std::wstring href = common_xlink_attlist_.href_.get_value_or(L""); std::wstring href = common_xlink_attlist_.href_.get_value_or(L"");
odf_reader::odf_document * odf_reader = Context.root(); std::wstring folderPath = Context.root()->get_folder();
std::wstring folderPath = odf_reader->get_folder();
std::wstring objectPath = folderPath + FILE_SEPARATOR_STR + href; std::wstring objectPath = folderPath + FILE_SEPARATOR_STR + href;
//normalize path ??? todooo //normalize path ??? todooo
...@@ -339,6 +339,19 @@ void draw_object::pptx_convert(oox::pptx_conversion_context & Context) ...@@ -339,6 +339,19 @@ void draw_object::pptx_convert(oox::pptx_conversion_context & Context)
Context.get_slide_context().set_property(_property(L"text-content", text_content)); Context.get_slide_context().set_property(_property(L"text-content", text_content));
} }
} }
else if (objectBuild.object_type_ == 4) //embedded sheet
{
Context.get_slide_context().set_use_image_replacement();
std::wstring href_new = office_convert( &objectSubDoc, 2);
if (!href_new.empty())
{
bool isMediaInternal = true;
href += FILE_SEPARATOR_STR + href_new;
Context.get_slide_context().set_object(href, L"Excel.Sheet.12");
}
}
else else
{ {
//замещающая картинка(если она конечно присутствует) //замещающая картинка(если она конечно присутствует)
...@@ -354,7 +367,7 @@ void draw_object::pptx_convert(oox::pptx_conversion_context & Context) ...@@ -354,7 +367,7 @@ void draw_object::pptx_convert(oox::pptx_conversion_context & Context)
void draw_object_ole::pptx_convert(oox::pptx_conversion_context & Context) void draw_object_ole::pptx_convert(oox::pptx_conversion_context & Context)
{ {
Context.get_slide_context().set_use_image_replacement(); Context.get_slide_context().set_use_image_replacement();
std::wstring href = common_xlink_attlist_.href_.get_value_or(L""); std::wstring href = common_xlink_attlist_.href_.get_value_or(L"");
std::wstring folderPath = Context.root()->get_folder(); std::wstring folderPath = Context.root()->get_folder();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment