Kaydet (Commit) 88b9e7f3 authored tarafından Rohit Deshmukh's avatar Rohit Deshmukh Kaydeden (comit) Miklos Vajna

fdo#71786 : Implemented Glossary folder

   1. Glosary folder is imported as Intrograb and exported.
   2. Added unit test case

Reviewed on:
	https://gerrit.libreoffice.org/6825

Conflicts:
	sw/qa/extras/ooxmlexport/ooxmlexport.cxx
	writerfilter/source/filter/ImportFilter.cxx

Change-Id: Ifd51a75a65e030d44d30e02cd7ab51fb088186b3
üst e9c08cfe
...@@ -3104,6 +3104,10 @@ void SAL_CALL OWriteStream::setPropertyValue( const OUString& aPropertyName, con ...@@ -3104,6 +3104,10 @@ void SAL_CALL OWriteStream::setPropertyValue( const OUString& aPropertyName, con
else if ( m_pData->m_nStorageType == embed::StorageFormats::PACKAGE else if ( m_pData->m_nStorageType == embed::StorageFormats::PACKAGE
&& ( aPropertyName == "IsEncrypted" || aPropertyName == "Encrypted" ) ) && ( aPropertyName == "IsEncrypted" || aPropertyName == "Encrypted" ) )
throw beans::PropertyVetoException(); // TODO throw beans::PropertyVetoException(); // TODO
else if ( aPropertyName == "RelId" )
{
aValue >>= m_pImpl->m_nRelId;
}
else else
throw beans::UnknownPropertyException(); // TODO throw beans::UnknownPropertyException(); // TODO
......
...@@ -2115,6 +2115,14 @@ DECLARE_OOXMLEXPORT_TEST(testcolumnbreak, "columnbreak.docx") ...@@ -2115,6 +2115,14 @@ DECLARE_OOXMLEXPORT_TEST(testcolumnbreak, "columnbreak.docx")
assertXPath(pXmlDoc, "/w:document/w:body/w:p[5]/w:r[1]/w:br", "type", "column"); assertXPath(pXmlDoc, "/w:document/w:body/w:p[5]/w:r[1]/w:br", "type", "column");
} }
DECLARE_OOXMLEXPORT_TEST(testGlossary, "testGlossary.docx")
{
xmlDocPtr pXmlDoc = parseExport("word/glossary/document.xml");
if (!pXmlDoc)
return;
assertXPath(pXmlDoc, "/w:glossaryDocument", "Ignorable", "w14 wp14");
}
#endif #endif
CPPUNIT_PLUGIN_IMPLEMENT(); CPPUNIT_PLUGIN_IMPLEMENT();
......
...@@ -64,7 +64,7 @@ ...@@ -64,7 +64,7 @@
#include "ww8par.hxx" #include "ww8par.hxx"
#include "ww8scan.hxx" #include "ww8scan.hxx"
#include <oox/token/properties.hxx>
#include <comphelper/string.hxx> #include <comphelper/string.hxx>
#include <rtl/ustrbuf.hxx> #include <rtl/ustrbuf.hxx>
#include <vcl/font.hxx> #include <vcl/font.hxx>
...@@ -361,6 +361,8 @@ void DocxExport::ExportDocument_Impl() ...@@ -361,6 +361,8 @@ void DocxExport::ExportDocument_Impl()
WriteTheme(); WriteTheme();
WriteGlossary();
WriteCustomXml(); WriteCustomXml();
WriteActiveX(); WriteActiveX();
...@@ -831,6 +833,78 @@ void DocxExport::WriteTheme() ...@@ -831,6 +833,78 @@ void DocxExport::WriteTheme()
uno::Sequence< beans::StringPair >() ); uno::Sequence< beans::StringPair >() );
} }
void DocxExport::WriteGlossary()
{
uno::Reference< beans::XPropertySet > xPropSet( pDoc->GetDocShell()->GetBaseModel(), uno::UNO_QUERY_THROW );
uno::Reference< beans::XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo();
OUString pName = UNO_NAME_MISC_OBJ_INTEROPGRABBAG;
if ( !xPropSetInfo->hasPropertyByName( pName ) )
return;
uno::Reference<xml::dom::XDocument> glossaryDocDom;
uno::Sequence< uno::Sequence< uno::Any> > glossaryDomList;
uno::Sequence< beans::PropertyValue > propList;
xPropSet->getPropertyValue( pName ) >>= propList;
sal_Int32 collectedProperties = 0;
for ( sal_Int32 nProp=0; nProp < propList.getLength(); ++nProp )
{
OUString propName = propList[nProp].Name;
if ( propName == "OOXGlossary" )
{
propList[nProp].Value >>= glossaryDocDom;
collectedProperties++;
}
if (propName == "OOXGlossaryDom")
{
propList[nProp].Value >>= glossaryDomList;
collectedProperties++;
}
if (collectedProperties == 2)
break;
}
// no glossary dom to write
if ( !glossaryDocDom.is() )
return;
m_pFilter->addRelation( m_pDocumentFS->getOutputStream(),
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/glossaryDocument",
"glossary/document.xml" );
uno::Reference< io::XOutputStream > xOutputStream = GetFilter().openFragmentStream( "word/glossary/document.xml",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml" );
uno::Reference< xml::sax::XSAXSerializable > serializer( glossaryDocDom, uno::UNO_QUERY );
uno::Reference< xml::sax::XWriter > writer = xml::sax::Writer::create( comphelper::getProcessComponentContext() );
writer->setOutputStream( xOutputStream );
serializer->serialize( uno::Reference< xml::sax::XDocumentHandler >( writer, uno::UNO_QUERY_THROW ),
uno::Sequence< beans::StringPair >() );
sal_Int32 length = glossaryDomList.getLength();
for ( int i =0; i < length; i++)
{
uno::Sequence< uno::Any> glossaryElement = glossaryDomList[i];
OUString gTarget, gType, gId, contentType;
uno::Reference<xml::dom::XDocument> xDom;
glossaryElement[0] >>= xDom;
glossaryElement[1] >>= gId;
glossaryElement[2] >>= gType;
glossaryElement[3] >>= gTarget;
glossaryElement[4] >>= contentType;
gId = gId.copy(3); //"rId" only save the numeric value
PropertySet aProps(xOutputStream);
aProps.setAnyProperty( PROP_RelId, uno::makeAny( sal_Int32( gId.toInt32() )));
m_pFilter->addRelation( xOutputStream, gType, gTarget);
uno::Reference< xml::sax::XSAXSerializable > gserializer( xDom, uno::UNO_QUERY );
writer->setOutputStream(GetFilter().openFragmentStream( "word/glossary/" + gTarget, contentType ) );
gserializer->serialize( uno::Reference< xml::sax::XDocumentHandler >( writer, uno::UNO_QUERY_THROW ),
uno::Sequence< beans::StringPair >() );
}
}
void DocxExport::WriteCustomXml() void DocxExport::WriteCustomXml()
{ {
uno::Reference< beans::XPropertySet > xPropSet( pDoc->GetDocShell()->GetBaseModel(), uno::UNO_QUERY_THROW ); uno::Reference< beans::XPropertySet > xPropSet( pDoc->GetDocShell()->GetBaseModel(), uno::UNO_QUERY_THROW );
......
...@@ -210,6 +210,8 @@ private: ...@@ -210,6 +210,8 @@ private:
/// Write word/theme/theme1.xml /// Write word/theme/theme1.xml
void WriteTheme(); void WriteTheme();
void WriteGlossary();
/// Write customXml/item[n].xml and customXml/itemProps[n].xml /// Write customXml/item[n].xml and customXml/itemProps[n].xml
void WriteCustomXml(); void WriteCustomXml();
......
...@@ -75,8 +75,8 @@ using namespace com::sun::star; ...@@ -75,8 +75,8 @@ using namespace com::sun::star;
class WRITERFILTER_OOXML_DLLPUBLIC OOXMLStream class WRITERFILTER_OOXML_DLLPUBLIC OOXMLStream
{ {
public: public:
enum StreamType_t { UNKNOWN, DOCUMENT, STYLES, FONTTABLE, NUMBERING, enum StreamType_t { UNKNOWN, DOCUMENT, STYLES, WEBSETTINGS, FONTTABLE, NUMBERING,
FOOTNOTES, ENDNOTES, COMMENTS, THEME, CUSTOMXML, CUSTOMXMLPROPS, ACTIVEX, ACTIVEXBIN, SETTINGS, VBAPROJECT }; FOOTNOTES, ENDNOTES, COMMENTS, THEME, CUSTOMXML, CUSTOMXMLPROPS, ACTIVEX, ACTIVEXBIN, GLOSSARY, SETTINGS, VBAPROJECT };
typedef boost::shared_ptr<OOXMLStream> Pointer_t; typedef boost::shared_ptr<OOXMLStream> Pointer_t;
virtual ~OOXMLStream() {} virtual ~OOXMLStream() {}
...@@ -242,6 +242,8 @@ public: ...@@ -242,6 +242,8 @@ public:
virtual void setShapeContext( uno::Reference<xml::sax::XFastShapeContextHandler> xContext ) = 0; virtual void setShapeContext( uno::Reference<xml::sax::XFastShapeContextHandler> xContext ) = 0;
virtual uno::Reference<xml::dom::XDocument> getThemeDom( ) = 0; virtual uno::Reference<xml::dom::XDocument> getThemeDom( ) = 0;
virtual void setThemeDom( uno::Reference<xml::dom::XDocument> xThemeDom ) = 0; virtual void setThemeDom( uno::Reference<xml::dom::XDocument> xThemeDom ) = 0;
virtual uno::Reference<xml::dom::XDocument> getGlossaryDocDom( ) = 0;
virtual uno::Sequence<uno::Sequence< uno::Any> > getGlossaryDomList() = 0;
virtual uno::Sequence<uno::Reference<xml::dom::XDocument> > getCustomXmlDomList( ) = 0; virtual uno::Sequence<uno::Reference<xml::dom::XDocument> > getCustomXmlDomList( ) = 0;
virtual uno::Sequence<uno::Reference<xml::dom::XDocument> > getCustomXmlDomPropsList( ) = 0; virtual uno::Sequence<uno::Reference<xml::dom::XDocument> > getCustomXmlDomPropsList( ) = 0;
virtual uno::Sequence<uno::Reference<xml::dom::XDocument> > getActiveXDomList( ) = 0; virtual uno::Sequence<uno::Reference<xml::dom::XDocument> > getActiveXDomList( ) = 0;
......
...@@ -120,7 +120,7 @@ sal_Bool WriterFilter::filter( const uno::Sequence< beans::PropertyValue >& aDes ...@@ -120,7 +120,7 @@ sal_Bool WriterFilter::filter( const uno::Sequence< beans::PropertyValue >& aDes
pDocument->resolve(*pStream); pDocument->resolve(*pStream);
// Adding some properties to the document's grab bag for interoperability purposes: // Adding some properties to the document's grab bag for interoperability purposes:
uno::Sequence<beans::PropertyValue> aGrabBagProperties(6); uno::Sequence<beans::PropertyValue> aGrabBagProperties(8);
// Adding the saved Theme DOM // Adding the saved Theme DOM
aGrabBagProperties[0].Name = "OOXTheme"; aGrabBagProperties[0].Name = "OOXTheme";
...@@ -142,6 +142,12 @@ sal_Bool WriterFilter::filter( const uno::Sequence< beans::PropertyValue >& aDes ...@@ -142,6 +142,12 @@ sal_Bool WriterFilter::filter( const uno::Sequence< beans::PropertyValue >& aDes
aGrabBagProperties[5].Name = "ThemeFontLangProps"; aGrabBagProperties[5].Name = "ThemeFontLangProps";
aGrabBagProperties[5].Value = uno::makeAny( aDomainMapper->GetThemeFontLangProperties() ); aGrabBagProperties[5].Value = uno::makeAny( aDomainMapper->GetThemeFontLangProperties() );
// Adding the saved Glossary Documnet DOM to the document's grab bag
aGrabBagProperties[6].Name = "OOXGlossary";
aGrabBagProperties[6].Value = uno::makeAny( pDocument->getGlossaryDocDom() );
aGrabBagProperties[7].Name = "OOXGlossaryDom";
aGrabBagProperties[7].Value = uno::makeAny( pDocument->getGlossaryDomList() );
putPropertiesToDocumentGrabBag( aGrabBagProperties ); putPropertiesToDocumentGrabBag( aGrabBagProperties );
writerfilter::ooxml::OOXMLStream::Pointer_t pVBAProjectStream(writerfilter::ooxml::OOXMLDocumentFactory::createStream( pDocStream, writerfilter::ooxml::OOXMLStream::VBAPROJECT )); writerfilter::ooxml::OOXMLStream::Pointer_t pVBAProjectStream(writerfilter::ooxml::OOXMLDocumentFactory::createStream( pDocStream, writerfilter::ooxml::OOXMLStream::VBAPROJECT ));
......
...@@ -438,6 +438,9 @@ void OOXMLDocumentImpl::resolve(Stream & rStream) ...@@ -438,6 +438,9 @@ void OOXMLDocumentImpl::resolve(Stream & rStream)
resolveFastSubStream(rStream, OOXMLStream::SETTINGS); resolveFastSubStream(rStream, OOXMLStream::SETTINGS);
mxThemeDom = importSubStream(OOXMLStream::THEME); mxThemeDom = importSubStream(OOXMLStream::THEME);
resolveFastSubStream(rStream, OOXMLStream::THEME); resolveFastSubStream(rStream, OOXMLStream::THEME);
mxGlossaryDocDom = importSubStream(OOXMLStream::GLOSSARY);
if (mxGlossaryDocDom.is())
resolveGlossaryStream(rStream);
// Custom xml's are handled as part of grab bag. // Custom xml's are handled as part of grab bag.
resolveCustomXmlStream(rStream); resolveCustomXmlStream(rStream);
...@@ -523,6 +526,109 @@ void OOXMLDocumentImpl::resolveCustomXmlStream(Stream & rStream) ...@@ -523,6 +526,109 @@ void OOXMLDocumentImpl::resolveCustomXmlStream(Stream & rStream)
} }
} }
void OOXMLDocumentImpl::resolveGlossaryStream(Stream & /*rStream*/)
{
static OUString sSettingsType("http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings");
static OUString sStylesWithEffects("http://schemas.microsoft.com/office/2007/relationships/stylesWithEffects");
static OUString sStylesType("http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles");
static OUString sFonttableType("http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable");
static OUString sWebSettings("http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings");
OOXMLStream::Pointer_t pStream;
try
{
pStream = OOXMLDocumentFactory::createStream(mpStream, OOXMLStream::GLOSSARY);
}
catch (uno::Exception const& e)
{
SAL_INFO("writerfilter", "resolveGlossaryStream: exception while "
"createStream for glossary" << OOXMLStream::GLOSSARY << " : " << e.Message);
return;
}
uno::Reference<embed::XRelationshipAccess> mxRelationshipAccess;
mxRelationshipAccess.set((*dynamic_cast<OOXMLStreamImpl *>(pStream.get())).accessDocumentStream(), uno::UNO_QUERY_THROW);
if (mxRelationshipAccess.is())
{
uno::Sequence< uno::Sequence< beans::StringPair > >aSeqs =
mxRelationshipAccess->getAllRelationships();
uno::Sequence<uno::Sequence< uno::Any> > mxGlossaryDomListTemp(aSeqs.getLength());
sal_Int32 counter = 0;
for (sal_Int32 j = 0; j < aSeqs.getLength(); j++)
{
OOXMLStream::Pointer_t gStream;
uno::Sequence< beans::StringPair > aSeq = aSeqs[j];
//Follows following aSeq[0] is Id, aSeq[1] is Type, aSeq[2] is Target
OUString gId(aSeq[0].Second);
OUString gType(aSeq[1].Second);
OUString gTarget(aSeq[2].Second);
OUString contentType;
OOXMLStream::StreamType_t nType;
bool bFound = true;
if(gType.compareTo(sSettingsType) == 0)
{
nType = OOXMLStream::SETTINGS;
contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml";
}
else if(gType.compareTo(sStylesType) == 0)
{
nType = OOXMLStream::STYLES;
contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml";
}
else if(gType.compareTo(sWebSettings) == 0)
{
nType = OOXMLStream::WEBSETTINGS;
contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml";
}
else if(gType.compareTo(sFonttableType) == 0)
{
nType = OOXMLStream::FONTTABLE;
contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml";
}
else
{
bFound = false;
//"Unhandled content-type while grab bagging Glossary Folder");
}
if (bFound)
{
uno::Reference<xml::dom::XDocument> xDom;
try
{
gStream = OOXMLDocumentFactory::createStream(pStream, nType);
uno::Reference<io::XInputStream> xInputStream = gStream->getDocumentStream();
uno::Reference<uno::XComponentContext> xContext(pStream->getContext());
uno::Reference<xml::dom::XDocumentBuilder> xDomBuilder(xml::dom::DocumentBuilder::create(xContext));
xDom = xDomBuilder->parse(xInputStream);
}
catch (uno::Exception const& e)
{
SAL_INFO("writerfilter glossary grab bag", "importSubStream: exception while "
"parsing stream of Type" << nType << " : " << e.Message);
return;
}
if (xDom.is())
{
uno::Sequence< uno::Any > glossaryTuple (5);
glossaryTuple[0] = uno::makeAny(xDom);
glossaryTuple[1] = uno::makeAny(gId);
glossaryTuple[2] = uno::makeAny(gType);
glossaryTuple[3] = uno::makeAny(gTarget);
glossaryTuple[4] = uno::makeAny(contentType);
mxGlossaryDomListTemp[counter] = glossaryTuple;
counter++;
}
}
}
mxGlossaryDomListTemp.realloc(counter);
mxGlossaryDomList = mxGlossaryDomListTemp;
}
}
void OOXMLDocumentImpl::resolveActiveXStream(Stream & rStream) void OOXMLDocumentImpl::resolveActiveXStream(Stream & rStream)
{ {
// Resolving all ActiveX[n].xml files from ActiveX folder. // Resolving all ActiveX[n].xml files from ActiveX folder.
...@@ -579,6 +685,16 @@ void OOXMLDocumentImpl::resolveActiveXStream(Stream & rStream) ...@@ -579,6 +685,16 @@ void OOXMLDocumentImpl::resolveActiveXStream(Stream & rStream)
} }
} }
uno::Reference<xml::dom::XDocument> OOXMLDocumentImpl::getGlossaryDocDom( )
{
return mxGlossaryDocDom;
}
uno::Sequence<uno::Sequence< uno::Any> > OOXMLDocumentImpl::getGlossaryDomList()
{
return mxGlossaryDomList;
}
uno::Reference<io::XInputStream> OOXMLDocumentImpl::getInputStreamForId(const OUString & rId) uno::Reference<io::XInputStream> OOXMLDocumentImpl::getInputStreamForId(const OUString & rId)
{ {
OOXMLStream::Pointer_t pStream(OOXMLDocumentFactory::createStream(mpStream, rId)); OOXMLStream::Pointer_t pStream(OOXMLDocumentFactory::createStream(mpStream, rId));
......
...@@ -40,6 +40,8 @@ class OOXMLDocumentImpl : public OOXMLDocument ...@@ -40,6 +40,8 @@ class OOXMLDocumentImpl : public OOXMLDocument
uno::Reference<frame::XModel> mxModel; uno::Reference<frame::XModel> mxModel;
uno::Reference<drawing::XDrawPage> mxDrawPage; uno::Reference<drawing::XDrawPage> mxDrawPage;
uno::Reference<xml::dom::XDocument> mxGlossaryDocDom;
uno::Sequence < uno::Sequence< uno::Any > > mxGlossaryDomList;
uno::Reference<xml::sax::XFastShapeContextHandler> mxShapeContext; uno::Reference<xml::sax::XFastShapeContextHandler> mxShapeContext;
uno::Reference<xml::dom::XDocument> mxThemeDom; uno::Reference<xml::dom::XDocument> mxThemeDom;
uno::Sequence<uno::Reference<xml::dom::XDocument> > mxCustomXmlDomList; uno::Sequence<uno::Reference<xml::dom::XDocument> > mxCustomXmlDomList;
...@@ -73,7 +75,7 @@ protected: ...@@ -73,7 +75,7 @@ protected:
void setIsSubstream( bool bSubstream ) { mbIsSubstream = bSubstream; }; void setIsSubstream( bool bSubstream ) { mbIsSubstream = bSubstream; };
void resolveCustomXmlStream(Stream & rStream); void resolveCustomXmlStream(Stream & rStream);
void resolveActiveXStream(Stream & rStream); void resolveActiveXStream(Stream & rStream);
void resolveGlossaryStream(Stream & rStream);
public: public:
OOXMLDocumentImpl(OOXMLStream::Pointer_t pStream); OOXMLDocumentImpl(OOXMLStream::Pointer_t pStream);
virtual ~OOXMLDocumentImpl(); virtual ~OOXMLDocumentImpl();
...@@ -123,6 +125,8 @@ public: ...@@ -123,6 +125,8 @@ public:
virtual uno::Sequence<uno::Reference<xml::dom::XDocument> > getCustomXmlDomPropsList(); virtual uno::Sequence<uno::Reference<xml::dom::XDocument> > getCustomXmlDomPropsList();
virtual uno::Sequence<uno::Reference<xml::dom::XDocument> > getActiveXDomList(); virtual uno::Sequence<uno::Reference<xml::dom::XDocument> > getActiveXDomList();
virtual uno::Sequence<uno::Reference<io::XInputStream> > getActiveXBinList(); virtual uno::Sequence<uno::Reference<io::XInputStream> > getActiveXBinList();
virtual uno::Reference<xml::dom::XDocument> getGlossaryDocDom();
virtual uno::Sequence<uno::Sequence< uno::Any> > getGlossaryDomList();
}; };
}} }}
#endif // OOXML_DOCUMENT_IMPL_HXX #endif // OOXML_DOCUMENT_IMPL_HXX
......
...@@ -114,6 +114,8 @@ bool OOXMLStreamImpl::lcl_getTarget(uno::Reference<embed::XRelationshipAccess> ...@@ -114,6 +114,8 @@ bool OOXMLStreamImpl::lcl_getTarget(uno::Reference<embed::XRelationshipAccess>
static OUString sCustomPropsType("http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXmlProps"); static OUString sCustomPropsType("http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXmlProps");
static OUString sActiveXType("http://schemas.openxmlformats.org/officeDocument/2006/relationships/control"); static OUString sActiveXType("http://schemas.openxmlformats.org/officeDocument/2006/relationships/control");
static OUString sActiveXBinType("http://schemas.microsoft.com/office/2006/relationships/activeXControlBinary"); static OUString sActiveXBinType("http://schemas.microsoft.com/office/2006/relationships/activeXControlBinary");
static OUString sGlossaryType("http://schemas.openxmlformats.org/officeDocument/2006/relationships/glossaryDocument");
static OUString sWebSettings("http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings");
static OUString sSettingsType("http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings"); static OUString sSettingsType("http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings");
static OUString sTarget("Target"); static OUString sTarget("Target");
static OUString sTargetMode("TargetMode"); static OUString sTargetMode("TargetMode");
...@@ -166,6 +168,12 @@ bool OOXMLStreamImpl::lcl_getTarget(uno::Reference<embed::XRelationshipAccess> ...@@ -166,6 +168,12 @@ bool OOXMLStreamImpl::lcl_getTarget(uno::Reference<embed::XRelationshipAccess>
case SETTINGS: case SETTINGS:
sStreamType = sSettingsType; sStreamType = sSettingsType;
break; break;
case GLOSSARY:
sStreamType = sGlossaryType;
break;
case WEBSETTINGS:
sStreamType = sWebSettings;
break;
default: default:
break; break;
} }
......
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