Kaydet (Commit) 31c6f22a authored tarafından sb's avatar sb

#i101955# obtain external values on demand in PropertyNode::getValue instead of…

#i101955# obtain external values on demand in PropertyNode::getValue instead of up-front in XcuParser
üst e5739255
......@@ -271,7 +271,8 @@ css::uno::Any ChildAccess::asValue() {
}
switch (node_->kind()) {
case Node::KIND_PROPERTY:
return dynamic_cast< PropertyNode * >(node_.get())->getValue();
return dynamic_cast< PropertyNode * >(node_.get())->getValue(
getComponents());
case Node::KIND_LOCALIZED_PROPERTY:
{
rtl::OUString locale(getRootAccess()->getLocale());
......
......@@ -33,7 +33,14 @@
#include <algorithm>
#include <list>
#include "com/sun/star/beans/Optional.hpp"
#include "com/sun/star/beans/UnknownPropertyException.hpp"
#include "com/sun/star/beans/XPropertySet.hpp"
#include "com/sun/star/container/NoSuchElementException.hpp"
#include "com/sun/star/lang/WrappedTargetException.hpp"
#include "com/sun/star/lang/XMultiComponentFactory.hpp"
#include "com/sun/star/uno/Any.hxx"
#include "com/sun/star/uno/Exception.hpp"
#include "com/sun/star/uno/Reference.hxx"
#include "com/sun/star/uno/RuntimeException.hpp"
#include "com/sun/star/uno/XComponentContext.hpp"
......@@ -43,6 +50,7 @@
#include "rtl/bootstrap.hxx"
#include "rtl/ref.hxx"
#include "rtl/string.h"
#include "rtl/textenc.h"
#include "rtl/ustring.h"
#include "rtl/ustring.hxx"
#include "sal/types.h"
......@@ -80,9 +88,7 @@ void parseSystemLayer() {
//TODO
}
void parseXcsFile(
css::uno::Reference< css::uno::XComponentContext > const &,
rtl::OUString const & url, int layer, Data * data)
void parseXcsFile(rtl::OUString const & url, int layer, Data * data)
SAL_THROW((
css::container::NoSuchElementException, css::uno::RuntimeException))
{
......@@ -91,16 +97,13 @@ void parseXcsFile(
new ParseManager(url, new XcsParser(layer, data)))->parse());
}
void parseXcuFile(
css::uno::Reference< css::uno::XComponentContext > const & context,
rtl::OUString const & url, int layer, Data * data)
void parseXcuFile(rtl::OUString const & url, int layer, Data * data)
SAL_THROW((
css::container::NoSuchElementException, css::uno::RuntimeException))
{
OSL_VERIFY(
rtl::Reference< ParseManager >(
new ParseManager(url, new XcuParser(context, layer, data)))->
parse());
new ParseManager(url, new XcuParser(layer, data)))->parse());
}
rtl::OUString expand(rtl::OUString const & str) {
......@@ -202,14 +205,12 @@ void Components::addModification(Path const & path) {
}
void Components::writeModifications() {
writeModFile(getModificationFileUrl(), data_);
writeModFile(*this, getModificationFileUrl(), data_);
}
void Components::insertXcsFile(int layer, rtl::OUString const & fileUri) {
try {
parseXcsFile(
css::uno::Reference< css::uno::XComponentContext >(), fileUri,
layer, &data_);
parseXcsFile(fileUri, layer, &data_);
} catch (css::container::NoSuchElementException & e) {
throw css::uno::RuntimeException(
(rtl::OUString(
......@@ -221,7 +222,7 @@ void Components::insertXcsFile(int layer, rtl::OUString const & fileUri) {
void Components::insertXcuFile(int layer, rtl::OUString const & fileUri) {
try {
parseXcuFile(context_, fileUri, layer + 1, &data_);
parseXcuFile(fileUri, layer + 1, &data_);
} catch (css::container::NoSuchElementException & e) {
throw css::uno::RuntimeException(
(rtl::OUString(
......@@ -231,6 +232,68 @@ void Components::insertXcuFile(int layer, rtl::OUString const & fileUri) {
}
}
css::beans::Optional< css::uno::Any > Components::getExternalValue(
rtl::OUString const & descriptor) const
{
sal_Int32 i = descriptor.indexOf(' ');
if (i <= 0) {
throw css::uno::RuntimeException(
(rtl::OUString(
RTL_CONSTASCII_USTRINGPARAM("bad external value descriptor ")) +
descriptor),
css::uno::Reference< css::uno::XInterface >());
}
//TODO: Do not make calls with mutex locked:
css::uno::Reference< css::uno::XInterface > service;
try {
service = css::uno::Reference< css::lang::XMultiComponentFactory >(
context_->getServiceManager(), css::uno::UNO_SET_THROW)->
createInstanceWithContext(descriptor.copy(0, i), context_);
} catch (css::uno::RuntimeException &) {
// Assuming these exceptions are real errors:
throw;
} catch (css::uno::Exception & e) {
// Assuming these exceptions indicate that the service is not installed:
OSL_TRACE(
"createInstance(%s) failed with %s",
rtl::OUStringToOString(
descriptor.copy(0, i), RTL_TEXTENCODING_UTF8).getStr(),
rtl::OUStringToOString(e.Message, RTL_TEXTENCODING_UTF8).getStr());
}
css::beans::Optional< css::uno::Any > value;
if (service.is()) {
try {
if (!((css::uno::Reference< css::beans::XPropertySet >(
service, css::uno::UNO_QUERY_THROW)->
getPropertyValue(descriptor.copy(i + 1))) >>=
value))
{
throw css::uno::RuntimeException(
(rtl::OUString(
RTL_CONSTASCII_USTRINGPARAM(
"cannot obtain external value through ")) +
descriptor),
css::uno::Reference< css::uno::XInterface >());
}
} catch (css::beans::UnknownPropertyException & e) {
throw css::uno::RuntimeException(
(rtl::OUString(
RTL_CONSTASCII_USTRINGPARAM(
"unknwon external value descriptor ID: ")) +
e.Message),
css::uno::Reference< css::uno::XInterface >());
} catch (css::lang::WrappedTargetException & e) {
throw css::uno::RuntimeException(
(rtl::OUString(
RTL_CONSTASCII_USTRINGPARAM(
"cannot obtain external value: ")) +
e.Message),
css::uno::Reference< css::uno::XInterface >());
}
}
return value;
}
Components::Components(
css::uno::Reference< css::uno::XComponentContext > const & context):
context_(context)
......@@ -313,9 +376,7 @@ Components::~Components() {}
void Components::parseFiles(
int layer, rtl::OUString const & extension,
void (* parseFile)(
css::uno::Reference< css::uno::XComponentContext > const &,
rtl::OUString const &, int, Data *),
void (* parseFile)(rtl::OUString const &, int, Data *),
rtl::OUString const & url, bool recursive)
{
osl::Directory dir(url);
......@@ -365,7 +426,7 @@ void Components::parseFiles(
file.match(extension, file.getLength() - extension.getLength()))
{
try {
(*parseFile)(context_, stat.getFileURL(), layer, &data_);
(*parseFile)(stat.getFileURL(), layer, &data_);
} catch (css::container::NoSuchElementException & e) {
throw css::uno::RuntimeException(
(rtl::OUString(
......@@ -380,10 +441,7 @@ void Components::parseFiles(
}
void Components::parseFileList(
int layer,
void (* parseFile)(
css::uno::Reference< css::uno::XComponentContext > const &,
rtl::OUString const &, int, Data *),
int layer, void (* parseFile)(rtl::OUString const &, int, Data *),
rtl::OUString const & urls, rtl::Bootstrap const & ini)
{
for (sal_Int32 i = 0;;) {
......@@ -391,7 +449,7 @@ void Components::parseFileList(
if (url.getLength() != 0) {
ini.expandMacrosFrom(url); //TODO: detect failure
try {
(*parseFile)(context_, url, layer, &data_);
(*parseFile)(url, layer, &data_);
} catch (css::container::NoSuchElementException & e) {
throw css::uno::RuntimeException(
(rtl::OUString(
......@@ -459,8 +517,7 @@ void Components::parseXcdFiles(int layer, rtl::OUString const & url) {
rtl::Reference< ParseManager > manager;
try {
manager = new ParseManager(
stat.getFileURL(),
new XcdParser(context_, layer, deps, &data_));
stat.getFileURL(), new XcdParser(layer, deps, &data_));
} catch (css::container::NoSuchElementException & e) {
throw css::uno::RuntimeException(
(rtl::OUString(
......@@ -551,8 +608,7 @@ rtl::OUString Components::getModificationFileUrl() const {
void Components::parseModificationLayer() {
try {
parseXcuFile(
context_, getModificationFileUrl(), Data::NO_LAYER, &data_);
parseXcuFile(getModificationFileUrl(), Data::NO_LAYER, &data_);
} catch (css::container::NoSuchElementException &) {
OSL_TRACE(
"configmgr user registrymodifications.xcu does not (yet) exist");
......
......@@ -35,6 +35,7 @@
#include <set>
#include "boost/noncopyable.hpp"
#include "com/sun/star/beans/Optional.hpp"
#include "com/sun/star/uno/Reference.hxx"
#include "rtl/ref.hxx"
......@@ -42,6 +43,7 @@
#include "path.hxx"
namespace com { namespace sun { namespace star { namespace uno {
class Any;
class XComponentContext;
} } } }
namespace rtl {
......@@ -90,6 +92,9 @@ public:
void insertXcuFile(int layer, rtl::OUString const & fileUri);
com::sun::star::beans::Optional< com::sun::star::uno::Any >
getExternalValue(rtl::OUString const & descriptor) const;
private:
Components(
com::sun::star::uno::Reference< com::sun::star::uno::XComponentContext >
......@@ -99,18 +104,12 @@ private:
void parseFiles(
int layer, rtl::OUString const & extension,
void (* parseFile)(
com::sun::star::uno::Reference<
com::sun::star::uno::XComponentContext > const &,
rtl::OUString const &, int, Data *),
void (* parseFile)(rtl::OUString const &, int, Data *),
rtl::OUString const & url, bool recursive);
void parseFileList(
int layer,
void (* parseFile)(
com::sun::star::uno::Reference<
com::sun::star::uno::XComponentContext > const &,
rtl::OUString const &, int, Data *),
void (* parseFile)(rtl::OUString const &, int, Data *),
rtl::OUString const & urls, rtl::Bootstrap const & ini);
void parseXcdFiles(int layer, rtl::OUString const & url);
......
......@@ -30,11 +30,14 @@
#include "precompiled_configmgr.hxx"
#include "sal/config.h"
#include "com/sun/star/beans/Optional.hpp"
#include "com/sun/star/uno/Any.hxx"
#include "osl/diagnose.h"
#include "rtl/ref.hxx"
#include "rtl/ustring.h"
#include "rtl/ustring.hxx"
#include "components.hxx"
#include "node.hxx"
#include "propertynode.hxx"
#include "type.hxx"
......@@ -66,13 +69,28 @@ bool PropertyNode::isNillable() const {
return nillable_;
}
css::uno::Any PropertyNode::getValue() const {
css::uno::Any PropertyNode::getValue(Components const & components) {
if (externalDescriptor_.getLength() != 0) {
css::beans::Optional< css::uno::Any > val(
components.getExternalValue(externalDescriptor_));
if (val.IsPresent) {
value_ = val.Value; //TODO: check value type
}
externalDescriptor_ = rtl::OUString(); // must not throw
}
return value_;
}
void PropertyNode::setValue(int layer, css::uno::Any const & value) {
setLayer(layer);
value_ = value;
externalDescriptor_ = rtl::OUString();
}
void PropertyNode::setExternal(int layer, rtl::OUString const & descriptor) {
OSL_ASSERT(descriptor.getLength() != 0);
setLayer(layer);
externalDescriptor_ = descriptor;
}
bool PropertyNode::isExtension() const {
......@@ -81,7 +99,8 @@ bool PropertyNode::isExtension() const {
PropertyNode::PropertyNode(PropertyNode const & other):
Node(other), type_(other.type_), nillable_(other.nillable_),
value_(other.value_), extension_(other.extension_)
value_(other.value_), externalDescriptor_(other.externalDescriptor_),
extension_(other.extension_)
{}
PropertyNode::~PropertyNode() {}
......
......@@ -42,6 +42,8 @@ namespace rtl { class OUString; }
namespace configmgr {
class Components;
class PropertyNode: public Node {
public:
PropertyNode(
......@@ -54,10 +56,12 @@ public:
bool isNillable() const;
com::sun::star::uno::Any getValue() const;
com::sun::star::uno::Any getValue(Components const & components);
void setValue(int layer, com::sun::star::uno::Any const & value);
void setExternal(int layer, rtl::OUString const & descriptor);
bool isExtension() const;
private:
......@@ -70,6 +74,7 @@ private:
Type type_;
bool nillable_;
com::sun::star::uno::Any value_;
rtl::OUString externalDescriptor_;
bool extension_;
};
......
......@@ -62,6 +62,8 @@
namespace configmgr {
class Components;
namespace {
namespace css = com::sun::star;
......@@ -340,8 +342,9 @@ void writeValue(oslFileHandle handle, Type type, css::uno::Any const & value) {
}
void writeNode(
oslFileHandle handle, rtl::Reference< Node > const & parent,
rtl::OUString const & name, rtl::Reference< Node > const & node)
Components const & components, oslFileHandle handle,
rtl::Reference< Node > const & parent, rtl::OUString const & name,
rtl::Reference< Node > const & node)
{
static Span const typeNames[] = {
Span(), Span(), Span(), // TYPE_ERROR, TYPE_NIL, TYPE_ANY
......@@ -368,7 +371,7 @@ void writeNode(
writeData(handle, RTL_CONSTASCII_STRINGPARAM("\" oor:op=\"fuse\""));
Type type = prop->getType();
if (type == TYPE_ANY) {
type = mapType(prop->getValue());
type = mapType(prop->getValue(components));
if (type != TYPE_ERROR) { //TODO
writeData(
handle, RTL_CONSTASCII_STRINGPARAM(" oor:type=\""));
......@@ -378,7 +381,7 @@ void writeNode(
}
}
writeData(handle, "><value");
writeValue(handle, type, prop->getValue());
writeValue(handle, type, prop->getValue(components));
writeData(handle, "</prop>");
}
break;
......@@ -389,7 +392,7 @@ void writeNode(
for (NodeMap::iterator i(node->getMembers().begin());
i != node->getMembers().end(); ++i)
{
writeNode(handle, node, i->first, i->second);
writeNode(components, handle, node, i->first, i->second);
}
writeData(handle, RTL_CONSTASCII_STRINGPARAM("</prop>"));
break;
......@@ -430,7 +433,7 @@ void writeNode(
for (NodeMap::iterator i(node->getMembers().begin());
i != node->getMembers().end(); ++i)
{
writeNode(handle, node, i->first, i->second);
writeNode(components, handle, node, i->first, i->second);
}
writeData(handle, RTL_CONSTASCII_STRINGPARAM("</node>"));
break;
......@@ -438,7 +441,8 @@ void writeNode(
}
void writeModifications(
oslFileHandle handle, rtl::OUString const & grandparentPathRepresentation,
Components const & components, oslFileHandle handle,
rtl::OUString const & grandparentPathRepresentation,
rtl::OUString const & parentName, rtl::Reference< Node > const & parent,
rtl::OUString const & nodeName, rtl::Reference< Node > const & node,
Modifications::Node const & modifications)
......@@ -454,7 +458,7 @@ void writeModifications(
rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("/")) +
Data::createSegment(parent->getTemplateName(), parentName)));
writeData(handle, RTL_CONSTASCII_STRINGPARAM("\">"));
writeNode(handle, parent, nodeName, node);
writeNode(components, handle, parent, nodeName, node);
writeData(handle, RTL_CONSTASCII_STRINGPARAM("</item>"));
// It is never necessary to write the oor:mandatory attribute, as it
// cannot be set via the UNO API.
......@@ -530,7 +534,7 @@ void writeModifications(
i != modifications.children.end(); ++i)
{
writeModifications(
handle, parentPathRep, nodeName, node, i->first,
components, handle, parentPathRep, nodeName, node, i->first,
node->getMember(i->first), i->second);
}
}
......@@ -538,7 +542,9 @@ void writeModifications(
}
void writeModFile(rtl::OUString const & url, Data const & data) {
void writeModFile(
Components const & components, rtl::OUString const & url, Data const & data)
{
sal_Int32 i = url.lastIndexOf('/');
OSL_ASSERT(i != -1);
rtl::OUString dir(url.copy(0, i));
......@@ -579,7 +585,7 @@ void writeModFile(rtl::OUString const & url, Data const & data) {
j != data.modifications.getRoot().children.end(); ++j)
{
writeModifications(
tmp.handle, rtl::OUString(), rtl::OUString(),
components, tmp.handle, rtl::OUString(), rtl::OUString(),
rtl::Reference< Node >(), j->first,
Data::findNode(Data::NO_LAYER, data.components, j->first),
j->second);
......
......@@ -36,9 +36,12 @@ namespace rtl { class OUString; }
namespace configmgr {
class Components;
struct Data;
void writeModFile(rtl::OUString const & url, Data const & data);
void writeModFile(
Components const & components, rtl::OUString const & url,
Data const & data);
}
......
......@@ -34,7 +34,6 @@
#include "com/sun/star/uno/Reference.hxx"
#include "com/sun/star/uno/RuntimeException.hpp"
#include "com/sun/star/uno/XComponentContext.hpp"
#include "com/sun/star/uno/XInterface.hpp"
#include "osl/diagnose.hxx"
#include "rtl/string.h"
......@@ -56,14 +55,9 @@ namespace css = com::sun::star;
}
XcdParser::XcdParser(
css::uno::Reference< css::uno::XComponentContext > const & context,
int layer, Dependencies const & dependencies, Data * data):
context_(context), layer_(layer), dependencies_(dependencies), data_(data),
state_(STATE_START)
{
OSL_ASSERT(context.is());
}
XcdParser::XcdParser(int layer, Dependencies const & dependencies, Data * data):
layer_(layer), dependencies_(dependencies), data_(data), state_(STATE_START)
{}
XcdParser::~XcdParser() {}
......@@ -145,7 +139,7 @@ bool XcdParser::startElement(
if (ns == XmlReader::NAMESPACE_OOR &&
name.equals(RTL_CONSTASCII_STRINGPARAM("component-data")))
{
nestedParser_ = new XcuParser(context_, layer_ + 1, data_);
nestedParser_ = new XcuParser(layer_ + 1, data_);
nesting_ = 1;
return nestedParser_->startElement(reader, ns, name);
}
......
......@@ -34,17 +34,12 @@
#include <set>
#include "com/sun/star/uno/Reference.hxx"
#include "rtl/ref.hxx"
#include "rtl/ustring.hxx"
#include "parser.hxx"
#include "xmlreader.hxx"
namespace com { namespace sun { namespace star { namespace uno {
class XComponentContext;
} } } }
namespace configmgr {
struct Data;
......@@ -54,10 +49,7 @@ class XcdParser: public Parser {
public:
typedef std::set< rtl::OUString > Dependencies;
XcdParser(
com::sun::star::uno::Reference< com::sun::star::uno::XComponentContext >
const & context,
int layer, Dependencies const & dependencies, Data * data);
XcdParser(int layer, Dependencies const & dependencies, Data * data);
private:
virtual ~XcdParser();
......@@ -74,8 +66,6 @@ private:
enum State {
STATE_START, STATE_DEPENDENCIES, STATE_DEPENDENCY, STATE_COMPONENTS };
com::sun::star::uno::Reference< com::sun::star::uno::XComponentContext >
context_;
int layer_;
Dependencies const & dependencies_;
Data * data_;
......
......@@ -32,25 +32,16 @@
#include <algorithm>
#include "com/sun/star/beans/Optional.hpp"
#include "com/sun/star/beans/UnknownPropertyException.hpp"
#include "com/sun/star/beans/XPropertySet.hpp"
#include "com/sun/star/lang/WrappedTargetException.hpp"
#include "com/sun/star/lang/XMultiComponentFactory.hpp"
#include "com/sun/star/uno/Any.hxx"
#include "com/sun/star/uno/Exception.hpp"
#include "com/sun/star/uno/Reference.hxx"
#include "com/sun/star/uno/RuntimeException.hpp"
#include "com/sun/star/uno/XComponentContext.hpp"
#include "com/sun/star/uno/XInterface.hpp"
#include "osl/diagnose.h"
#include "rtl/ref.hxx"
#include "rtl/strbuf.hxx"
#include "rtl/string.h"
#include "rtl/textenc.h"
#include "rtl/ustring.h"
#include "rtl/ustring.hxx"
#include "sal/types.h"
#include "data.hxx"
#include "localizedpropertynode.hxx"
......@@ -75,13 +66,8 @@ namespace css = com::sun::star;
}
XcuParser::XcuParser(
css::uno::Reference< css::uno::XComponentContext > const & context,
int layer, Data * data):
context_(context), valueParser_(layer), data_(data)
{
OSL_ASSERT(context.is());
}
XcuParser::XcuParser(int layer, Data * data): valueParser_(layer), data_(data)
{}
XcuParser::~XcuParser() {}
......@@ -426,8 +412,7 @@ void XcuParser::handlePropValue(XmlReader & reader, PropertyNode * prop) {
state_.push(State());
} else if (attrExternal.is()) {
rtl::OUString external(xmldata::convertFromUtf8(attrExternal));
sal_Int32 i = external.indexOf(' ');
if (i <= 0) {
if (external.getLength() == 0) {
throw css::uno::RuntimeException(
(rtl::OUString(
RTL_CONSTASCII_USTRINGPARAM(
......@@ -435,58 +420,7 @@ void XcuParser::handlePropValue(XmlReader & reader, PropertyNode * prop) {
reader.getUrl()),
css::uno::Reference< css::uno::XInterface >());
}
css::uno::Reference< css::uno::XInterface > service;
try {
service = css::uno::Reference< css::lang::XMultiComponentFactory >(
context_->getServiceManager(), css::uno::UNO_SET_THROW)->
createInstanceWithContext(external.copy(0, i), context_);
} catch (css::uno::RuntimeException &) {
// Assuming these exceptions are real errors:
throw;
} catch (css::uno::Exception & e) {
// Assuming these exceptions indicate that the service is not
// installed:
OSL_TRACE(
"createInstance(%s) failed with %s",
rtl::OUStringToOString(
external.copy(0, i), RTL_TEXTENCODING_UTF8).getStr(),
rtl::OUStringToOString(
e.Message, RTL_TEXTENCODING_UTF8).getStr());
}
if (service.is()) {
css::beans::Optional< css::uno::Any > value;
try {
if (!((css::uno::Reference< css::beans::XPropertySet >(
service, css::uno::UNO_QUERY_THROW)->
getPropertyValue(external.copy(i + 1))) >>=
value))
{
throw css::uno::RuntimeException(
rtl::OUString(
RTL_CONSTASCII_USTRINGPARAM(
"cannot obtain oor:external value")),
css::uno::Reference< css::uno::XInterface >());
}
} catch (css::beans::UnknownPropertyException & e) {
throw css::uno::RuntimeException(
(rtl::OUString(
RTL_CONSTASCII_USTRINGPARAM(
"unknwon oor:external ID: ")) +
e.Message),
css::uno::Reference< css::uno::XInterface >());
} catch (css::lang::WrappedTargetException & e) {
throw css::uno::RuntimeException(
(rtl::OUString(
RTL_CONSTASCII_USTRINGPARAM(
"cannot obtain oor:external value: ")) +
e.Message),
css::uno::Reference< css::uno::XInterface >());
}
if (value.IsPresent) {
//TODO: check value type
prop->setValue(valueParser_.getLayer(), value.Value);
}
}
prop->setExternal(valueParser_.getLayer(), external);
state_.push(State());
} else {
valueParser_.separator_ = attrSeparator;
......
......@@ -34,7 +34,6 @@
#include <stack>
#include "com/sun/star/uno/Reference.hxx"
#include "rtl/ref.hxx"
#include "rtl/ustring.hxx"
......@@ -47,10 +46,6 @@
#include "xmldata.hxx"
#include "xmlreader.hxx"
namespace com { namespace sun { namespace star { namespace uno {
class XComponentContext;
} } } }
namespace configmgr {
class GroupNode;
......@@ -62,10 +57,7 @@ struct Span;
class XcuParser: public Parser {
public:
XcuParser(
com::sun::star::uno::Reference< com::sun::star::uno::XComponentContext >
const & context,
int layer, Data * data);
XcuParser(int layer, Data * data);
private:
virtual ~XcuParser();
......@@ -140,8 +132,6 @@ private:
typedef std::stack< State > StateStack;
com::sun::star::uno::Reference< com::sun::star::uno::XComponentContext >
context_;
ValueParser valueParser_;
Data * data_;
rtl::OUString componentName_;
......
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