Commit 1b953d68 authored by konovalovsergey's avatar konovalovsergey

open/save oleObject

parent 7f928766
......@@ -1258,7 +1258,7 @@ rIns=\"91440\" bIns=\"45720\" numCol=\"1\" spcCol=\"0\" rtlCol=\"0\" fromWordArt
LONG lChildsCount = oChilds.GetCount();
bool bIsFound = false;
PPTX::Logic::SpTreeElem* pElem = NULL;
OOX::VmlOffice::COLEObject* pOle = NULL;
PPTX::Logic::COLEObject* pOle = NULL;
for (LONG k = 0; k < lChildsCount; k++)
{
XmlUtils::CXmlNode oNodeP;
......@@ -1287,7 +1287,8 @@ rIns=\"91440\" bIns=\"45720\" numCol=\"1\" spcCol=\"0\" rtlCol=\"0\" fromWordArt
}
else if (_T("OLEObject") == strNameP)
{
pOle = new OOX::VmlOffice::COLEObject( oNodeP );
pOle = new PPTX::Logic::COLEObject();
pOle->fromXML(oNodeP);
}
else if (_T("group") == strNameP)
{
......@@ -1326,22 +1327,20 @@ rIns=\"91440\" bIns=\"45720\" numCol=\"1\" spcCol=\"0\" rtlCol=\"0\" fromWordArt
const PPTX::Logic::BlipFill& oBlipFill = pShape->spPr.Fill.Fill.as<PPTX::Logic::BlipFill>();
if(oBlipFill.blip.IsInit())
{
int dxaOrig = 0;
int dyaOrig = 0;
oBlipFill.blip->oleRid = pOle->m_oId.get().ToString();
if(strName == _T("object"))
{
dxaOrig = oParseNode.ReadAttributeInt(_T("w:dxaOrig"));
dyaOrig = oParseNode.ReadAttributeInt(_T("w:dyaOrig"));
pOle->m_oDxaOrig = oParseNode.ReadAttributeInt(_T("w:dxaOrig"));
pOle->m_oDyaOrig = oParseNode.ReadAttributeInt(_T("w:dyaOrig"));
}
oBlipFill.blip->oleInfo.Init();
oBlipFill.blip->oleInfo->m_sOleProperty.Format(_T("%d|%d|%s"), dxaOrig, dyaOrig, pOle->m_sProgId.get());
oBlipFill.blip->oleInfo->m_sRid = pOle->m_oId->GetValue();
PPTX::Logic::Pic *newElem = new PPTX::Logic::Pic();
newElem->blipFill = oBlipFill;
newElem->spPr = pShape->spPr;
newElem->style = pShape->style;
newElem->oleObject.reset(pOle);
pOle = NULL;
pElem->InitElem(newElem);
}
......@@ -4158,24 +4157,12 @@ HRESULT CDrawingConverter::SaveObject(LONG lStart, LONG lLength, const CString&
oElem.fromPPTY(m_pReader);
bool bOle = false;
if (oElem.is<PPTX::Logic::Shape>())
{
PPTX::Logic::Shape& oShape = oElem.as<PPTX::Logic::Shape>();
if(oShape.spPr.Fill.Fill.is<PPTX::Logic::BlipFill>())
{
PPTX::Logic::BlipFill& oBlipFill = oShape.spPr.Fill.Fill.as<PPTX::Logic::BlipFill>();
if(oBlipFill.blip.IsInit() && oBlipFill.blip->oleInfo.IsInit())
bOle = true;
}
}
else if (oElem.is<PPTX::Logic::Pic>())
if (oElem.is<PPTX::Logic::Pic>())
{
PPTX::Logic::Pic& oPic = oElem.as<PPTX::Logic::Pic>();
if(oPic.spPr.Fill.Fill.is<PPTX::Logic::BlipFill>())
if(oPic.oleObject.IsInit())
{
PPTX::Logic::BlipFill& oBlipFill = oPic.spPr.Fill.Fill.as<PPTX::Logic::BlipFill>();
if(oBlipFill.blip.IsInit() && oBlipFill.blip->oleInfo.IsInit())
bOle = true;
bOle = oPic.oleObject->isValid();
}
}
......@@ -4197,14 +4184,7 @@ HRESULT CDrawingConverter::SaveObject(LONG lStart, LONG lLength, const CString&
#endif
if(bOle)
{
if (oElem.is<PPTX::Logic::Shape>())
{
ConvertShapeVML(oElem, bsMainProps, oXmlWriter);
}
else
{
ConvertPicVML(oElem, bsMainProps, oXmlWriter);
}
ConvertPicVML(oElem, bsMainProps, oXmlWriter);
}
else
{
......
......@@ -149,6 +149,7 @@ namespace NSBinPptxRW
#define SPTREE_TYPE_CXNSP 3
#define SPTREE_TYPE_SPTREE 4
#define SPTREE_TYPE_GRFRAME 5
#define SPTREE_TYPE_OLE 6
static BYTE SchemeClr_GetBYTECode(const CString& sValue)
{
......
......@@ -34,6 +34,9 @@
namespace NSBinPptxRW
{
template <typename T,unsigned S>
inline unsigned arraysize(const T (&v)[S]) { return S; }
inline _INT32 __strlen(const char* str)
{
const char* s = str;
......@@ -142,7 +145,7 @@ namespace NSBinPptxRW
}
return nRes;
}
CImageManager2Info CImageManager2::GenerateImage(const CString& strInput, CString strBase64Image)
CImageManager2Info CImageManager2::GenerateImage(const CString& strInput, const CString& oleData, CString strBase64Image)
{
if (IsNeedDownload(strInput))
return DownloadImage(strInput);
......@@ -158,7 +161,6 @@ namespace NSBinPptxRW
strExts = strInput.Mid(nIndexExt);
CString strOleImage = _T("");
CString strOleImageProperty = _T("");
CString strImage = strInput;
int nDisplayType = IsDisplayedImage(strInput);
if (0 != nDisplayType)
......@@ -192,12 +194,9 @@ namespace NSBinPptxRW
CString strOle = strFolder + strFileName + _T(".bin");
if (OOX::CSystemUtility::IsFileExist(strOle))
strOleImage = strOle;
CString strOleProperty = strFolder + strFileName + _T(".txt");
if (OOX::CSystemUtility::IsFileExist(strOleProperty))
strOleImageProperty = strOleProperty;
}
}
CImageManager2Info oImageManagerInfo = GenerateImageExec(strImage, strExts, strOleImage, strOleImageProperty);
CImageManager2Info oImageManagerInfo = GenerateImageExec(strImage, strExts, strOleImage, oleData);
if (_T("") == strBase64Image)
m_mapImages[strInput] = oImageManagerInfo;
......@@ -205,7 +204,41 @@ namespace NSBinPptxRW
m_mapImages [strBase64Image] = oImageManagerInfo;
return oImageManagerInfo;
}
CImageManager2Info CImageManager2::GenerateImageExec(const CString& strInput, const CString& sExts, const CString& strOleImage, const CString& strOleImageProperty)
bool CImageManager2::WriteOleData(const std::wstring& sFilePath, const std::wstring& sData)
{
bool bRes = false;
//EncodingMode.unparsed https://github.com/tonyqus/npoi/blob/master/main/POIFS/FileSystem/Ole10Native.cs
POLE::Storage oStorage(sFilePath.c_str());
if(oStorage.open(true, true))
{
//CompObj Stream
BYTE dataCompObj[] = {0x01,0x00,0xfe,0xff,0x03,0x0a,0x00,0x00,0xff,0xff,0xff,0xff,0x0c,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0x0c,0x00,0x00,0x00,0x4f,0x4c,0x45,0x20,0x50,0x61,0x63,0x6b,0x61,0x67,0x65,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x50,0x61,0x63,0x6b,0x61,0x67,0x65,0x00,0xf4,0x39,0xb2,0x71,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
POLE::Stream oStream1(&oStorage, "\001CompObj", true, arraysize(dataCompObj));
oStream1.write(dataCompObj, arraysize(dataCompObj));
oStream1.flush();
//ObjInfo Stream
BYTE dataObjInfo[] = {0x00,0x00,0x03,0x00,0x0d,0x00};
POLE::Stream oStream2(&oStorage, "\003ObjInfo", true, arraysize(dataObjInfo));
oStream2.write(dataObjInfo, arraysize(dataObjInfo));
oStream2.flush();
//Ole10Native Stream
std::string sDataUtf8 = NSFile::CUtf8Converter::GetUtf8StringFromUnicode2(sData.c_str(), sData.size());
BYTE head[] = {0x00,0x00,0x00,0x00};
//LittleEndian
unsigned char* aData = (unsigned char*)sDataUtf8.c_str();
uint32_t nDataSize = sDataUtf8.size();
memcpy(head, &nDataSize, sizeof(uint32_t));
POLE::Stream oStream(&oStorage, "\001Ole10Native", true, arraysize(head) + nDataSize);
oStream.write(head, arraysize(head));
oStream.write(aData, nDataSize);
oStream.flush();
oStorage.close();
bRes = true;
}
return bRes;
}
CImageManager2Info CImageManager2::GenerateImageExec(const CString& strInput, const CString& sExts, const CString& strOleImage, const CString& oleData)
{
CImageManager2Info oImageManagerInfo;
CString strExts = sExts;
......@@ -230,26 +263,26 @@ namespace NSBinPptxRW
else
strImage = _T("media/") + strImage + strExts;
if (_T("") != strOleImage)
if (!strOleImage.IsEmpty() || !oleData.IsEmpty() )
{
CString strImageOle;
strImageOle.Format(_T("oleObject%d.bin"), m_lIndexNextOle++);
OOX::CPath pathOutputOle = m_strDstEmbed + FILE_SEPARATOR_STR + strImageOle;
CString strOleImageOut = pathOutputOle.GetPath();
CDirectory::CopyFile(strOleImage, strOleImageOut, NULL, NULL);
if(!oleData.IsEmpty())
{
WriteOleData(string2std_string(strOleImageOut), string2std_string(oleData));
}
else
{
CDirectory::CopyFile(strOleImage, strOleImageOut, NULL, NULL);
}
if (!m_bIsWord)
strImageOle = _T("../embeddings/") + strImageOle;
else
strImageOle = _T("embeddings/") + strImageOle;
oImageManagerInfo.m_sOlePath = strImageOle;
if(!strOleImageProperty.IsEmpty())
{
std::wstring sOleProperty;
NSFile::CFileBinary::ReadAllTextUtf8(string2std_string(strOleImageProperty), sOleProperty);
oImageManagerInfo.m_sOleProperty = std_string2string(sOleProperty);
}
}
oImageManagerInfo.m_sImagePath = strImage;
......@@ -296,7 +329,6 @@ namespace NSBinPptxRW
CString strImage;
CString strOleImage;
CString strOleImageProperty;
int nDisplayType = IsDisplayedImage(strUrl);
if(0 != nDisplayType)
{
......@@ -306,7 +338,6 @@ namespace NSBinPptxRW
if(0 != (nDisplayType & 4))
{
strOleImage = DownloadImageExec(strInputMetafile + _T(".bin"));
strOleImageProperty = DownloadImageExec(strInputMetafile + _T(".txt"));
}
if(0 != (nDisplayType & 1))
......@@ -327,13 +358,11 @@ namespace NSBinPptxRW
CImageManager2Info oImageManagerInfo;
if (!strImage.IsEmpty())
{
oImageManagerInfo = GenerateImageExec(strImage, strExts, strOleImage, strOleImageProperty);
oImageManagerInfo = GenerateImageExec(strImage, strExts, strOleImage, L"");
CDirectory::DeleteFile(strImage);
}
if (!strOleImage.IsEmpty())
CDirectory::DeleteFile(strOleImage);
if (!strOleImageProperty.IsEmpty())
CDirectory::DeleteFile(strOleImageProperty);
m_mapImages[strUrl] = oImageManagerInfo;
return oImageManagerInfo;
......@@ -1312,9 +1341,9 @@ namespace NSBinPptxRW
oFile.CloseFile();
}
CRelsGeneratorInfo CRelsGenerator::WriteImage(const CString& strImagePath, CString strBase64Image = _T(""))
CRelsGeneratorInfo CRelsGenerator::WriteImage(const CString& strImagePath, const CString& oleData, CString strBase64Image = _T(""))
{
CImageManager2Info oImageManagerInfo = m_pManager->GenerateImage(strImagePath, strBase64Image);
CImageManager2Info oImageManagerInfo = m_pManager->GenerateImage(strImagePath, oleData, strBase64Image);
CString strImage = oImageManagerInfo.m_sImagePath;
std::map<CString, CRelsGeneratorInfo>::iterator pPair = m_mapImages.find(strImage);
......@@ -1335,7 +1364,6 @@ namespace NSBinPptxRW
if(!oImageManagerInfo.m_sOlePath.IsEmpty())
{
oRelsGeneratorInfo.m_nOleRId = m_lNextRelsID++;
oRelsGeneratorInfo.m_sOleProperty = oImageManagerInfo.m_sOleProperty;
CString strRid = _T("");
strRid.Format(_T("rId%d"), oRelsGeneratorInfo.m_nOleRId);
......
......@@ -55,14 +55,12 @@ namespace NSBinPptxRW
public:
CString m_sImagePath;
CString m_sOlePath;
CString m_sOleProperty;
};
class CRelsGeneratorInfo
{
public:
int m_nImageRId;
int m_nOleRId;
CString m_sOleProperty;
CRelsGeneratorInfo()
{
m_nImageRId = -1;
......@@ -173,8 +171,8 @@ namespace NSBinPptxRW
}
public:
CImageManager2Info GenerateImage(const CString& strInput, CString strBase64Image = _T(""));
CImageManager2Info GenerateImageExec(const CString& strInput, const CString& strExts, const CString& strOleImage, const CString& strOleImageProperty);
CImageManager2Info GenerateImage(const CString& strInput, const CString& oleData, CString strBase64Image);
CImageManager2Info GenerateImageExec(const CString& strInput, const CString& strExts, const CString& strOleImage, const CString& oleData);
void SaveImageAsPng(const CString& strFileSrc, const CString& strFileDst);
......@@ -183,6 +181,7 @@ namespace NSBinPptxRW
bool IsNeedDownload(const CString& strFile);
CImageManager2Info DownloadImage(const CString& strFile);
CString DownloadImageExec(const CString& strFile);
bool WriteOleData(const std::wstring& sFilePath, const std::wstring& sData);
};
class CBinaryFileWriter
......@@ -412,7 +411,7 @@ namespace NSBinPptxRW
void AddRels(const CString& strRels);
void SaveRels(const CString& strFile);
CRelsGeneratorInfo WriteImage(const CString& strImagePath, CString strBase64Image);
CRelsGeneratorInfo WriteImage(const CString& strImagePath, const CString& oleData, CString strBase64Image);
int WriteChart(int nChartNumber, _INT32 lDocType);
int WriteRels(const CString& bsType, const CString& bsTarget, const CString& bsTargetMode);
......
......@@ -123,14 +123,6 @@ namespace NSShapeImageGen
}
};
class COleInfo
{
public:
CString m_sRid;
CString m_sFilename;
CString m_sOleProperty;
};
class CImageManager
{
public:
......@@ -259,7 +251,7 @@ namespace NSShapeImageGen
return GenerateImageID(punkImage, (std::max)(1.0, width), (std::max)(1.0, height));
}
CImageInfo WriteImage(const CString& strFile, COleInfo* pOle, double& x, double& y, double& width, double& height)
CImageInfo WriteImage(const CString& strFile, const CString& strOleFile, double& x, double& y, double& width, double& height)
{
bool bIsDownload = false;
int n1 = strFile.Find(_T("www"));
......@@ -300,7 +292,7 @@ namespace NSShapeImageGen
#endif
return GenerateImageID(strDownload, strFile1, pOle, (std::max)(1.0, width), (std::max)(1.0, height));
return GenerateImageID(strDownload, strFile1, strOleFile, (std::max)(1.0, width), (std::max)(1.0, height));
}
......@@ -313,8 +305,8 @@ namespace NSShapeImageGen
oFile.CloseFile();
if (-1 == width && -1 == height)
return GenerateImageID(strFile, CString(L""), pOle, width, height);
return GenerateImageID(strFile, CString(L""), pOle, (std::max)(1.0, width), (std::max)(1.0, height));
return GenerateImageID(strFile, CString(L""), strOleFile, width, height);
return GenerateImageID(strFile, CString(L""), strOleFile, (std::max)(1.0, width), (std::max)(1.0, height));
}
void SetFontManager(CFontManager* pFontManager)
{
......@@ -472,13 +464,13 @@ namespace NSShapeImageGen
return oInfo;
}
CImageInfo GenerateImageID(const CString& strFileName, const CString & strUrl, COleInfo* pOle, double dWidth, double dHeight)
CImageInfo GenerateImageID(const CString& strFileName, const CString & strUrl, const CString& strOleFile, double dWidth, double dHeight)
{
CString sMapKey = strFileName;
if(!strUrl.IsEmpty())
sMapKey = strUrl;
if(NULL != pOle)
sMapKey += pOle->m_sFilename;
if(!strOleFile.IsEmpty())
sMapKey += strOleFile;
CImageInfo oInfo;
std::map<CString, CImageInfo>::iterator pPair = m_mapImagesFile.find(sMapKey);
......@@ -492,7 +484,7 @@ namespace NSShapeImageGen
LONG lImageType = m_oImageExt.GetImageType(strFileName);
bool bVector = (1 == lImageType || 2 == lImageType);
bool bOle = NULL != pOle;
bool bOle = !strOleFile.IsEmpty();
if(bVector)
oInfo.m_eType = (1 == lImageType) ? itWMF : itEMF;
oInfo.SetNameModificator(oInfo.m_eType, bOle);
......@@ -504,9 +496,7 @@ namespace NSShapeImageGen
if(bOle)
{
CString sCopyOlePath = std_string2string(strSaveItemWE) + _T(".bin");
CDirectory::CopyFile(pOle->m_sFilename, sCopyOlePath, NULL, NULL);
std::wstring sCopyOleProgPath = strSaveItemWE + _T(".txt");
NSFile::CFileBinary::SaveToFile(sCopyOleProgPath, string2std_string(pOle->m_sOleProperty), false);
CDirectory::CopyFile(strOleFile, sCopyOlePath, NULL, NULL);
}
if (bVector)
......
......@@ -28,7 +28,7 @@ namespace PPTX
link = oSrc.link;
m_namespace = oSrc.m_namespace;
oleInfo = oSrc.oleInfo;
oleRid = oSrc.oleRid;
return *this;
}
......@@ -99,14 +99,14 @@ namespace PPTX
if (pWriter->m_pCommonRels->is_init())
pRels = pWriter->m_pCommonRels->operator ->();
NSShapeImageGen::COleInfo* pOleInfo = NULL;
if(oleInfo.IsInit())
CString olePath;
if(!oleRid.IsEmpty())
{
pOleInfo = oleInfo.GetPointer();
pOleInfo->m_sFilename = this->GetFullOleName(PPTX::RId(oleInfo->m_sRid), pRels);
olePath= this->GetFullOleName(PPTX::RId(oleRid), pRels);
}
NSShapeImageGen::CImageInfo oId = pWriter->m_pCommon->m_pImageManager->WriteImage(this->GetFullPicName(pRels), pOleInfo, dX, dY, dW, dH);
NSShapeImageGen::CImageInfo oId = pWriter->m_pCommon->m_pImageManager->WriteImage(this->GetFullPicName(pRels), olePath, dX, dY, dW, dH);
CString s = oId.GetPath2();
pWriter->StartRecord(3);
......@@ -120,65 +120,6 @@ namespace PPTX
public:
virtual CString GetFullPicName(FileContainer* pRels = NULL)const;
virtual CString GetFullOleName(const PPTX::RId& pRId, FileContainer* pRels = NULL)const;
void writeOleStart(NSBinPptxRW::CXmlWriter *pWriter, NSShapeImageGen::COleInfo& oOleInfo, bool& bOle, CString& sOleProgID, CString& sOleNodeName)
{
if(oleInfo.IsInit())
oOleInfo = oleInfo.get();
if(!oOleInfo.m_sRid.IsEmpty() && !oOleInfo.m_sOleProperty.IsEmpty())
{
std::vector<CString> aOleProp;
int nTokenPos = 0;
CString strToken = oOleInfo.m_sOleProperty.Tokenize(_T("|"), nTokenPos);
while (!strToken.IsEmpty())
{
aOleProp.push_back(strToken);
strToken = oOleInfo.m_sOleProperty.Tokenize(_T("|"), nTokenPos);
}
if(3 == aOleProp.size())
{
bOle = true;
CString dxaOrig = aOleProp[0];
CString dyaOrig = aOleProp[1];
sOleProgID = aOleProp[2];
if(_T("0") != dxaOrig && _T("0") != dyaOrig)
{
sOleNodeName = _T("w:object");
pWriter->StartNode(sOleNodeName);
pWriter->StartAttributes();
pWriter->WriteAttribute(_T("w:dxaOrig"), dxaOrig);
pWriter->WriteAttribute(_T("w:dyaOrig"), dyaOrig);
pWriter->EndAttributes();
}
else
{
sOleNodeName = _T("w:pict");
pWriter->StartNode(sOleNodeName);
pWriter->StartAttributes();
pWriter->EndAttributes();
}
}
}
}
void writeOleEnd(NSBinPptxRW::CXmlWriter *pWriter, const NSShapeImageGen::COleInfo& oOleInfo, const CString& strId, const CString& sOleProgID, const CString& sOleNodeName)
{
if(!sOleProgID.IsEmpty())
{
pWriter->StartNode(_T("o:OLEObject"));
pWriter->StartAttributes();
pWriter->WriteAttribute(_T("Type"), CString(_T("Embed")));
pWriter->WriteAttribute(_T("ProgID"), sOleProgID);
pWriter->WriteAttribute(_T("ShapeID"), strId);
pWriter->WriteAttribute(_T("DrawAspect"), CString(_T("Content")));
CString sObjectID;
sObjectID.Format(_T("_%010d"), pWriter->m_lObjectIdOle++);
pWriter->WriteAttribute(_T("ObjectID"), sObjectID);
pWriter->WriteAttribute(_T("r:id"), oOleInfo.m_sRid);
pWriter->EndAttributes();
pWriter->EndNode(_T("o:OLEObject"));
pWriter->EndNode(sOleNodeName);
}
}
public:
std::vector<UniEffect> Effects;
......@@ -189,11 +130,12 @@ namespace PPTX
//private:
public:
CString m_namespace;
nullable<NSShapeImageGen::COleInfo> oleInfo;
//internal
CString oleRid;
protected:
virtual void FillParentPointersForChilds();
};
} // namespace Logic
} // namespace PPTX
#endif // PPTX_LOGIC_BLIP_INCLUDE_H_
\ No newline at end of file
#endif // PPTX_LOGIC_BLIP_INCLUDE_H_
......@@ -365,7 +365,7 @@ namespace PPTX
strUrl = pathUrl.GetPath();
}
NSBinPptxRW::CRelsGeneratorInfo oRelsGeneratorInfo = pReader->m_pRels->WriteImage(strUrl, strOrigBase64);
NSBinPptxRW::CRelsGeneratorInfo oRelsGeneratorInfo = pReader->m_pRels->WriteImage(strUrl, oleData, strOrigBase64);
// -------------------
if (strTempFile != _T(""))
......@@ -381,9 +381,7 @@ namespace PPTX
if(oRelsGeneratorInfo.m_nOleRId > 0)
{
blip->oleInfo.Init();
blip->oleInfo->m_sOleProperty = oRelsGeneratorInfo.m_sOleProperty;
blip->oleInfo->m_sRid = PPTX::RId((size_t)oRelsGeneratorInfo.m_nOleRId).get();
blip->oleRid = PPTX::RId((size_t)oRelsGeneratorInfo.m_nOleRId).get();
}
pReader->Skip(1); // end attribute
......@@ -439,6 +437,8 @@ namespace PPTX
nullable_bool rotWithShape;
mutable CString m_namespace;
//internal
CString oleData;
protected:
virtual void FillParentPointersForChilds()
{
......
//#include "./stdafx.h"
//#include "./stdafx.h"
#include "GraphicFrame.h"
#include "SpTree.h"
......@@ -62,7 +62,14 @@ namespace PPTX
XmlUtils::CXmlNode oNode2 = oNodeData.ReadNodeNoNS(_T("oleObj"));
if (oNode2.IsValid())
{
fromXMLOle(oNode2);
oNode2.ReadAttributeBase(L"spid", spid);
pic = oNode2.ReadNode(_T("p:pic"));
if (pic.is_init())
{
xfrm.Merge(pic->spPr.xfrm);
}
return;
}
XmlUtils::CXmlNode oNode3 = oNodeData.ReadNodeNoNS(_T("AlternateContent"));
......@@ -84,6 +91,7 @@ namespace PPTX
XmlUtils::CXmlNode oNodeO;
if (oNodeFallback.GetNode(_T("p:oleObj"), oNodeO))
{
fromXMLOle(oNodeO);
pic = oNodeO.ReadNode(_T("p:pic"));
if (pic.is_init())
......@@ -112,9 +120,30 @@ namespace PPTX
}
}
}
if(pic.IsInit() && oleObject.IsInit())
{
pic->oleObject = oleObject;
pic->blipFill.blip->oleRid = oleObject->m_oId.get().ToString();
}
FillParentPointersForChilds();
}
void GraphicFrame::fromXMLOle(XmlUtils::CXmlNode& node)
{
oleObject.Init();
node.ReadAttributeBase(L"progId", oleObject->m_sProgId);
node.ReadAttributeBase(L"r:id", oleObject->m_oId);
int imgW = node.GetAttributeInt(L"imgW", 0);
if(imgW > 0)
{
oleObject->m_oDxaOrig = Emu_To_Twips(imgW);
}
int imgH = node.GetAttributeInt(L"imgH", 0);
if(imgH > 0)
{
oleObject->m_oDyaOrig = Emu_To_Twips(imgH);
}
}
void GraphicFrame::toXmlWriter(NSBinPptxRW::CXmlWriter* pWriter) const
{
......@@ -215,7 +244,7 @@ namespace PPTX
dst->flipV = src->flipV;
dst->rot = src->rot;
// (p:nvPr) -
//удалим индекс плейсхолдера если он есть(p:nvPr) - он будет лишний так как будет имплементация объекта
if (smartArt->m_diag->nvGrpSpPr.nvPr.ph.IsInit())
{
if(smartArt->m_diag->nvGrpSpPr.nvPr.ph->idx.IsInit())
......@@ -354,7 +383,7 @@ namespace PPTX
// XML::Write(nvGraphicFramePr) +
// XML::Write(xfrm) +
// XML::XElement(ns.a + "graphic",
// XML::XElement(ns.a + "graphicData", //,
// XML::XElement(ns.a + "graphicData", //Возможно, здесь надо добавить ури
// XML::XElement(ns.dgm + "relIds",
// XML::XNamespace(ns.dgm) +
// XML::XNamespace(ns.r) +
......
......@@ -26,6 +26,7 @@ namespace PPTX
public:
virtual void fromXML(XmlUtils::CXmlNode& node);
void fromXMLOle(XmlUtils::CXmlNode& node);
virtual CString toXML() const;
virtual void GetRect(Aggplus::RECT& pRect)const;
......@@ -47,6 +48,7 @@ namespace PPTX
nullable<SmartArt> smartArt;
nullable<ChartRec> chartRec;
nullable<Pic> pic;
nullable<COLEObject> oleObject;
CString GetVmlXmlBySpid(CString & rels) const;
protected:
......
......@@ -12,6 +12,177 @@ namespace PPTX
{
namespace Logic
{
void COLEObject::fromXML(XmlUtils::CXmlNode& node)
{
node.ReadAttributeBase(L"DrawAspect", m_oDrawAspect);
node.ReadAttributeBase(L"r:id", m_oId);
node.ReadAttributeBase(L"ObjectID", m_sObjectId);
node.ReadAttributeBase(L"ProgID", m_sProgId);
node.ReadAttributeBase(L"ShapeID", m_sShapeId);
node.ReadAttributeBase(L"Type", m_oType);
node.ReadAttributeBase(L"UpdateMode", m_oUpdateMode);
}
CString COLEObject::toXML() const
{
XmlUtils::CAttribute oAttribute;
oAttribute.WriteLimitNullable(L"DrawAspect", m_oDrawAspect);
oAttribute.WriteNullable(L"r:id", m_oId);
oAttribute.WriteNullable(L"ObjectID", m_sObjectId);
oAttribute.WriteNullable(L"ProgID", m_sProgId);
oAttribute.WriteNullable(L"ShapeID", m_sShapeId);
oAttribute.WriteLimitNullable(L"Type", m_oType);
oAttribute.WriteLimitNullable(L"UpdateMode", m_oUpdateMode);
return XmlUtils::CreateNode(_T("o:OLEObject"), oAttribute);
}
void COLEObject::toXmlWriter(NSBinPptxRW::CXmlWriter* pWriter) const
{
CString strName = L"o:OLEObject";
pWriter->StartNode(strName);
pWriter->StartAttributes();
pWriter->WriteAttribute(L"DrawAspect", m_oDrawAspect);
if(m_oId.IsInit())
{
pWriter->WriteAttribute(L"r:id", m_oId->ToString());
}
pWriter->WriteAttribute(L"ObjectID", m_sObjectId);
pWriter->WriteAttribute(L"ProgID", m_sProgId);
pWriter->WriteAttribute(L"ShapeID", m_sShapeId);
pWriter->WriteAttribute(L"Type", m_oType);
pWriter->WriteAttribute(L"UpdateMode", m_oUpdateMode);
pWriter->EndAttributes();
pWriter->EndNode(strName);
}
void COLEObject::toPPTY(NSBinPptxRW::CBinaryFileWriter* pWriter) const
{
std::wstring sData;
if(m_oId.IsInit() && m_sProgId.IsInit() && 0 == m_sProgId.get().Find(L"asc."))
{
FileContainer* pRels = NULL;
if (pWriter->m_pCommonRels->is_init())
pRels = pWriter->m_pCommonRels->operator ->();
CString sFilePath = this->GetFullOleName(PPTX::RId(m_oId.get()), pRels);
if(!sFilePath.IsEmpty())
{
sData = GetOleData(string2std_string(sFilePath));
}
}
pWriter->WriteBYTE(NSBinPptxRW::g_nodeAttributeStart);
pWriter->WriteString2(0, m_sProgId);
if(!sData.empty())
{
pWriter->WriteString1Data(1, sData.c_str(), sData.length());
}
pWriter->WriteInt2(2, m_oDxaOrig);
pWriter->WriteInt2(3, m_oDyaOrig);
pWriter->WriteLimit2(4, m_oDrawAspect);
pWriter->WriteLimit2(5, m_oType);
pWriter->WriteLimit2(6, m_oUpdateMode);
pWriter->WriteBYTE(NSBinPptxRW::g_nodeAttributeEnd);
}
void COLEObject::fromPPTY(NSBinPptxRW::CBinaryFileReader* pReader)
{
LONG _end_rec = pReader->GetPos() + pReader->GetLong() + 4;
pReader->Skip(1); // start attributes
while (true)
{
BYTE _at = pReader->GetUChar_TypeNode();
if (_at == NSBinPptxRW::g_nodeAttributeEnd)
break;
if (0 == _at)
{
m_sProgId = pReader->GetString2();
}
else if (1 == _at)
{
m_sData = pReader->GetString2();
}
else if (2 == _at)
{
m_oDxaOrig = pReader->GetLong();
}
else if (3 == _at)
{
m_oDyaOrig = pReader->GetLong();
}
else if (4 == _at)
{
m_oDrawAspect = new Limit::OLEDrawAspectType();
m_oDrawAspect->SetBYTECode(pReader->GetUChar());
}
else if (5 == _at)
{
m_oType = new Limit::OLEType();
m_oType->SetBYTECode(pReader->GetUChar());
}
else if (6 == _at)
{
m_oUpdateMode = new Limit::OLEUpdateMode();
m_oUpdateMode->SetBYTECode(pReader->GetUChar());
}
else
break;
}
pReader->Seek(_end_rec);
}
void COLEObject::FillParentPointersForChilds()
{
}
bool COLEObject::isValid()
{
return m_sProgId.IsInit() && (m_sData.IsInit() || m_oId.IsInit());
}
CString COLEObject::GetFullOleName(const PPTX::RId& oRId, FileContainer* pRels)const
{
if (pRels != NULL)
{
smart_ptr<PPTX::OleObject> p = pRels->oleObject(oRId);
if (p.is_init())
return p->filename().m_strFilename;
}
if(parentFileIs<Slide>())
return parentFileAs<Slide>().GetOleFromRId(oRId);
else if(parentFileIs<SlideLayout>())
return parentFileAs<SlideLayout>().GetOleFromRId(oRId);
else if(parentFileIs<SlideMaster>())
return parentFileAs<SlideMaster>().GetOleFromRId(oRId);
else if(parentFileIs<Theme>())
return parentFileAs<Theme>().GetOleFromRId(oRId);
return _T("");
}
std::wstring COLEObject::GetOleData(const std::wstring& sFilePath)const
{
std::wstring sRes;
//EncodingMode.unparsed https://github.com/tonyqus/npoi/blob/master/main/POIFS/FileSystem/Ole10Native.cs
POLE::Storage oStorage(sFilePath.c_str());
if(oStorage.open(false, false))
{
POLE::Stream oStream(&oStorage, "Ole10Native");
if(oStream.size() > 4)
{
BYTE head[] = {0x00, 0x00, 0x00, 0x00};
oStream.read(head, 4);
uint32_t nDataSize = (uint32_t)((head[0]<< 0) | ((head[1]) << 8) | ((head[2]) << 16) | ((head[3]) << 24));
BYTE* aData = new BYTE[nDataSize];
oStream.read(aData, nDataSize);
sRes = NSFile::CUtf8Converter::GetUnicodeStringFromUTF8(aData, nDataSize);
RELEASEARRAYOBJECTS(aData);
}
}
return sRes;
}
Pic::Pic()
{
......@@ -87,7 +258,7 @@ namespace PPTX
oValue.Write(blipFill);
oValue.Write(spPr);
oValue.WriteNullable(style);
return XmlUtils::CreateNode(_T("p:pic"), oValue);
}
......@@ -287,19 +458,24 @@ namespace PPTX
smart_ptr<PPTX::Theme> oTheme = _oTheme.smart_dynamic_cast<PPTX::Theme>();
smart_ptr<PPTX::Logic::ClrMap> oClrMap = oTheme.smart_dynamic_cast<PPTX::Logic::ClrMap>();
NSShapeImageGen::COleInfo oOleInfo;
bool bOle = false;
CString sOleProgID;
bool bOle = oleObject.IsInit() && oleObject->isValid();
CString sOleNodeName;
Blip* pBlip = NULL;
if(this->spPr.Fill.Fill.is<PPTX::Logic::BlipFill>())
if(bOle && oleObject->m_oDxaOrig.IsInit() && oleObject->m_oDyaOrig.IsInit())
{
PPTX::Logic::BlipFill& oBlipFill = this->spPr.Fill.Fill.as<PPTX::Logic::BlipFill>();
if(oBlipFill.blip.IsInit())
pBlip = oBlipFill.blip.GetPointer();
sOleNodeName = L"w:object";
pWriter->StartNode(sOleNodeName);
pWriter->StartAttributes();
pWriter->WriteAttribute(_T("w:dxaOrig"), oleObject->m_oDxaOrig);
pWriter->WriteAttribute(_T("w:dyaOrig"), oleObject->m_oDyaOrig);
pWriter->EndAttributes();
}
else
{
sOleNodeName = L"w:pict";
pWriter->StartNode(sOleNodeName);
pWriter->StartAttributes();
pWriter->EndAttributes();
}
if(NULL != pBlip)
pBlip->writeOleStart(pWriter, oOleInfo, bOle, sOleProgID, sOleNodeName);
int dL = 0;
int dT = 0;
......@@ -307,9 +483,11 @@ namespace PPTX
int dH = 0;
CString strId = _T("");
strId.Format(_T("picture %d"), pWriter->m_lObjectIdVML);
strId.Format(_T("_x0000_i%04d"), pWriter->m_lObjectIdVML);
CString strSpid = _T("");
strSpid.Format(_T("_x%04d_s%04d"), 0xFFFF & (pWriter->m_lObjectIdVML >> 16), 0xFFFF & pWriter->m_lObjectIdVML);
CString strObjectid = _T("");
strObjectid.Format(_T("_152504%04d"), pWriter->m_lObjectIdVML);
pWriter->m_lObjectIdVML++;
NSBinPptxRW::CXmlWriter oStylesWriter;
......@@ -401,7 +579,10 @@ namespace PPTX
pWriter->WriteString(pWriter->m_strAttributesMain);
pWriter->m_strAttributesMain = _T("");
}
if(bOle)
{
pWriter->WriteAttribute(_T("filled"), (CString)L"f");
}
CString strNodeVal = _T("");
if (!spPr.ln.is_init())
{
......@@ -411,7 +592,7 @@ namespace PPTX
{
CString strPenAttr = _T("");
nullable<ShapeStyle> pShapeStyle;
CalculateLine(spPr, pShapeStyle, _oTheme, _oClrMap, strPenAttr, strNodeVal);
CalculateLine(spPr, pShapeStyle, _oTheme, _oClrMap, strPenAttr, strNodeVal, bOle);
pWriter->WriteString(strPenAttr);
}
......@@ -452,6 +633,11 @@ namespace PPTX
{
pWriter->WriteAttribute(_T("style"), pWriter->m_strStyleMain + oStylesWriter.GetXmlString());
}
if(bOle)
{
pWriter->WriteAttribute(_T("filled"), L"f");
pWriter->WriteAttribute(_T("stroked"), L"f");
}
pWriter->EndAttributes();
......@@ -469,8 +655,13 @@ namespace PPTX
}
pWriter->m_strStyleMain = _T("");
if(NULL != pBlip)
pBlip->writeOleEnd(pWriter, oOleInfo, strId, sOleProgID, sOleNodeName);
if(bOle)
{
oleObject->m_sShapeId = strId;
oleObject->m_sObjectId = strObjectid;
oleObject->toXmlWriter(pWriter);
}
pWriter->EndNode(sOleNodeName);
}
} // namespace Logic
} // namespace PPTX
\ No newline at end of file
} // namespace PPTX
......@@ -8,10 +8,174 @@
#include "SpPr.h"
#include "ShapeStyle.h"
namespace PPTX
{
namespace Limit
{
class OLEDrawAspectType : public BaseLimit
{
public:
OLEDrawAspectType()
{
m_strValue = _T("Content");
}
_USE_STRING_OPERATOR
virtual void set(const CString& strValue)
{
if ((_T("Content") == strValue) ||
(_T("Icon") == strValue))
{
m_strValue = strValue;
}
}
virtual BYTE GetBYTECode() const
{
if (_T("Content") == m_strValue)
return 0;
if (_T("Icon") == m_strValue)
return 1;
return 0;
}
virtual void SetBYTECode(const BYTE& src)
{
switch (src)
{
case 0:
m_strValue = _T("Content");
break;
case 1:
m_strValue = _T("Icon");
break;
default:
break;
}
}
};
class OLEType : public BaseLimit
{
public:
OLEType()
{
m_strValue = _T("Embed");
}
_USE_STRING_OPERATOR
virtual void set(const CString& strValue)
{
if ((_T("Embed") == strValue) ||
(_T("Link") == strValue))
{
m_strValue = strValue;
}
}
virtual BYTE GetBYTECode() const
{
if (_T("Embed") == m_strValue)
return 0;
if (_T("Link") == m_strValue)
return 1;
return 0;
}
virtual void SetBYTECode(const BYTE& src)
{
switch (src)
{
case 0:
m_strValue = _T("Embed");
break;
case 1:
m_strValue = _T("Link");
break;
default:
break;
}
}
};
class OLEUpdateMode : public BaseLimit
{
public:
OLEUpdateMode()
{
m_strValue = _T("Always");
}
_USE_STRING_OPERATOR
virtual void set(const CString& strValue)
{
if ((_T("Always") == strValue) ||
(_T("OnCall") == strValue))
{
m_strValue = strValue;
}
}
virtual BYTE GetBYTECode() const
{
if (_T("Always") == m_strValue)
return 0;
if (_T("OnCall") == m_strValue)
return 1;
return 0;
}
virtual void SetBYTECode(const BYTE& src)
{
switch (src)
{
case 0:
m_strValue = _T("Always");
break;
case 1:
m_strValue = _T("OnCall");
break;
default:
break;
}
}
};
} // namespace Limit
} // namespace PPTX
namespace PPTX
{
namespace Logic
{
class COLEObject : public WrapperWritingElement
{
public:
virtual void fromXML(XmlUtils::CXmlNode& node);
virtual CString toXML() const;
virtual void toXmlWriter(NSBinPptxRW::CXmlWriter* pWriter) const;
virtual void toPPTY(NSBinPptxRW::CBinaryFileWriter* pWriter) const;
virtual void fromPPTY(NSBinPptxRW::CBinaryFileReader* pReader);
virtual void FillParentPointersForChilds();
bool isValid();
CString COLEObject::GetFullOleName(const PPTX::RId& oRId, FileContainer* pRels)const;
std::wstring COLEObject::GetOleData(const std::wstring& sFilePath)const;
public:
// Attributes
nullable_limit<Limit::OLEDrawAspectType> m_oDrawAspect;
nullable<PPTX::RId> m_oId;
nullable_string m_sObjectId;
nullable_string m_sProgId;
nullable_string m_sShapeId;
nullable_limit<Limit::OLEType> m_oType;
nullable_limit<Limit::OLEUpdateMode> m_oUpdateMode;
//internal
nullable_string m_sData;
nullable_int m_oDxaOrig;
nullable_int m_oDyaOrig;
};
class Pic : public WrapperWritingElement
{
......@@ -36,8 +200,16 @@ namespace PPTX
virtual void toPPTY(NSBinPptxRW::CBinaryFileWriter* pWriter) const
{
pWriter->StartRecord(SPTREE_TYPE_PIC);
if(oleObject.IsInit())
{
pWriter->StartRecord(SPTREE_TYPE_OLE);
}
else
{
pWriter->StartRecord(SPTREE_TYPE_PIC);
}
pWriter->WriteRecord2(4, oleObject);
pWriter->WriteRecord1(0, nvPicPr);
pWriter->WriteRecord1(1, blipFill);
pWriter->WriteRecord1(2, spPr);
......@@ -48,6 +220,7 @@ namespace PPTX
virtual void toXmlWriter(NSBinPptxRW::CXmlWriter* pWriter) const
{
bool bOle = false;
if (pWriter->m_lDocType == XMLWRITER_DOC_TYPE_XLSX)
pWriter->StartNode(_T("xdr:pic"));
else if (pWriter->m_lDocType == XMLWRITER_DOC_TYPE_DOCX)
......@@ -57,7 +230,44 @@ namespace PPTX
pWriter->WriteAttribute(_T("xmlns:pic"), (CString)_T("http://schemas.openxmlformats.org/drawingml/2006/picture"));
}
else
{
if(oleObject.IsInit() && oleObject->isValid())
{
bOle = true;
pWriter->WriteString(L"<p:graphicFrame><p:nvGraphicFramePr><p:cNvPr id=\"0\" name=\"\"/><p:cNvGraphicFramePr><a:graphicFrameLocks noChangeAspect=\"1\"/></p:cNvGraphicFramePr><p:nvPr><p:extLst><p:ext uri=\"{D42A27DB-BD31-4B8C-83A1-F6EECF244321}\"><p14:modId xmlns:p14=\"http://schemas.microsoft.com/office/powerpoint/2010/main\" val=\"2157879785\"/></p:ext></p:extLst></p:nvPr></p:nvGraphicFramePr>");
if(spPr.xfrm.IsInit())
{
CString oldNamespace = spPr.xfrm->m_ns;
spPr.xfrm->m_ns = _T("p");
spPr.xfrm->toXmlWriter(pWriter);
spPr.xfrm->m_ns = oldNamespace;
}
pWriter->WriteString(L"<a:graphic><a:graphicData uri=\"http://schemas.openxmlformats.org/presentationml/2006/ole\">");
pWriter->StartNode(_T("p:oleObj"));
pWriter->WriteAttribute(L"name", (CString)L"oleObj");
pWriter->WriteAttribute(L"showAsIcon", (CString)L"1");
if(oleObject->m_oId.IsInit())
{
pWriter->WriteAttribute2(L"r:id", oleObject->m_oId->get());
}
if(oleObject->m_oDxaOrig.IsInit())
{
int nDxaOrig = oleObject->m_oDxaOrig.get();
pWriter->WriteAttribute(L"imgW", 635 * nDxaOrig); //twips to emu
}
if(oleObject->m_oDyaOrig.IsInit())
{
int nDyaOrig = oleObject->m_oDyaOrig.get();
pWriter->WriteAttribute(L"imgH", 635 * nDyaOrig); //twips to emu
}
pWriter->WriteAttribute2(L"progId", oleObject->m_sProgId);
pWriter->EndAttributes();
pWriter->WriteString(L"<p:embed/>");
}
pWriter->StartNode(_T("p:pic"));
}
pWriter->EndAttributes();
......@@ -85,7 +295,13 @@ namespace PPTX
else if (pWriter->m_lDocType == XMLWRITER_DOC_TYPE_DOCX)
pWriter->EndNode(_T("pic:pic"));
else
{
pWriter->EndNode(_T("p:pic"));
if(bOle)
{
pWriter->WriteString(L"</p:oleObj></a:graphicData></a:graphic></p:graphicFrame>");
}
}
}
......@@ -121,12 +337,26 @@ namespace PPTX
style->fromPPTY(pReader);
break;
}
case 4:
{
oleObject = new COLEObject();
oleObject->fromPPTY(pReader);
if(oleObject->m_sData.IsInit())
{
blipFill.oleData = oleObject->m_sData.get();
}
break;
}
default:
{
break;
}
}
}
if(blipFill.blip.IsInit() && !blipFill.blip->oleRid.IsEmpty() && oleObject.IsInit())
{
oleObject->m_oId = PPTX::RId(blipFill.blip->oleRid);
}
pReader->Seek(_end_rec);
}
......@@ -139,6 +369,8 @@ namespace PPTX
BlipFill blipFill;
SpPr spPr;
nullable<ShapeStyle> style;
//internal
nullable<COLEObject> oleObject;
protected:
virtual void FillParentPointersForChilds();
};
......
......@@ -374,20 +374,7 @@ namespace PPTX
lW = spPr.xfrm->extX.get_value_or(43200);
lH = spPr.xfrm->extY.get_value_or(43200);
}
NSShapeImageGen::COleInfo oOleInfo;
bool bOle = false;
CString sOleProgID;
CString sOleNodeName;
Blip* pBlip = NULL;
if(this->spPr.Fill.Fill.is<PPTX::Logic::BlipFill>())
{
PPTX::Logic::BlipFill& oBlipFill = this->spPr.Fill.Fill.as<PPTX::Logic::BlipFill>();
if(oBlipFill.blip.IsInit())
pBlip = oBlipFill.blip.GetPointer();
}
if(NULL != pBlip)
pBlip->writeOleStart(pWriter, oOleInfo, bOle, sOleProgID, sOleNodeName);
#ifdef AVS_USE_CONVERT_PPTX_TOCUSTOM_VML
spPr.Geometry.ConvertToCustomVML(pWriter->m_pOOXToVMLRenderer, strPath, strTextRect, lW, lH);
#endif
......@@ -633,10 +620,6 @@ namespace PPTX
pWriter->EndNode(_T("v:shape"));
}
if(NULL != pBlip)
pBlip->writeOleEnd(pWriter, oOleInfo, strId, sOleProgID, sOleNodeName);
}
} // namespace Logic
} // namespace PPTX
......@@ -266,6 +266,7 @@ namespace PPTX
m_elem.reset(p);
break;
}
case SPTREE_TYPE_OLE:
case SPTREE_TYPE_PIC:
{
Logic::Pic* p = new Logic::Pic();
......@@ -314,4 +315,4 @@ namespace PPTX
}
} // namespace Logic
} // namespace PPTX
\ No newline at end of file
} // namespace PPTX
......@@ -352,7 +352,7 @@ namespace PPTX
}
// -------------------
NSBinPptxRW::CRelsGeneratorInfo oRelsGeneratorInfo = pReader->m_pRels->WriteImage(strUrl, strOrigBase64);
NSBinPptxRW::CRelsGeneratorInfo oRelsGeneratorInfo = pReader->m_pRels->WriteImage(strUrl, pFill->oleData, strOrigBase64);
// -------------------
if (strTempFile != _T(""))
......@@ -370,9 +370,7 @@ namespace PPTX
if(oRelsGeneratorInfo.m_nOleRId > 0)
{
pFill->blip->oleInfo.Init();
pFill->blip->oleInfo->m_sOleProperty = oRelsGeneratorInfo.m_sOleProperty;
pFill->blip->oleInfo->m_sRid = PPTX::RId((size_t)oRelsGeneratorInfo.m_nOleRId).get();
pFill->blip->oleRid = PPTX::RId((size_t)oRelsGeneratorInfo.m_nOleRId).get();
}
pReader->Skip(1); // end attribute
......
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