msxml3: Fix node_get_text() whitespace handling.
This commit is contained in:
parent
e95122162a
commit
85ced167bd
|
@ -159,6 +159,8 @@ extern void wineXmlCallbackLog(char const* caller, xmlErrorLevel lvl, char const
|
||||||
#define LIBXML2_CALLBACK_SERROR(caller, err) \
|
#define LIBXML2_CALLBACK_SERROR(caller, err) \
|
||||||
wineXmlCallbackLog(#caller, err->level, err->message, NULL)
|
wineXmlCallbackLog(#caller, err->level, err->message, NULL)
|
||||||
|
|
||||||
|
extern BOOL is_preserving_whitespace(xmlNodePtr node);
|
||||||
|
|
||||||
/* IXMLDOMNode Internal Structure */
|
/* IXMLDOMNode Internal Structure */
|
||||||
typedef struct _xmlnode
|
typedef struct _xmlnode
|
||||||
{
|
{
|
||||||
|
|
|
@ -543,6 +543,90 @@ static HRESULT WINAPI xmlnode_get_nodeTypeString(
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline xmlChar* trim_whitespace(xmlChar* str)
|
||||||
|
{
|
||||||
|
xmlChar* ret = str;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
if (!str)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
while (*ret && isspace(*ret))
|
||||||
|
++ret;
|
||||||
|
len = xmlStrlen(ret);
|
||||||
|
while (isspace(ret[len-1]))
|
||||||
|
--len;
|
||||||
|
|
||||||
|
ret = xmlStrndup(ret, len);
|
||||||
|
xmlFree(str);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static xmlChar* do_get_text(xmlNodePtr node)
|
||||||
|
{
|
||||||
|
xmlNodePtr child;
|
||||||
|
xmlChar* str;
|
||||||
|
BOOL preserving = is_preserving_whitespace(node);
|
||||||
|
|
||||||
|
if (!node->children)
|
||||||
|
{
|
||||||
|
str = xmlNodeGetContent(node);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
xmlElementType prev_type = XML_TEXT_NODE;
|
||||||
|
xmlChar* tmp;
|
||||||
|
str = xmlStrdup(BAD_CAST "");
|
||||||
|
for (child = node->children; child != NULL; child = child->next)
|
||||||
|
{
|
||||||
|
switch (child->type)
|
||||||
|
{
|
||||||
|
case XML_ELEMENT_NODE:
|
||||||
|
tmp = do_get_text(child);
|
||||||
|
break;
|
||||||
|
case XML_TEXT_NODE:
|
||||||
|
case XML_CDATA_SECTION_NODE:
|
||||||
|
case XML_ENTITY_REF_NODE:
|
||||||
|
case XML_ENTITY_NODE:
|
||||||
|
tmp = xmlNodeGetContent(child);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
tmp = NULL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tmp)
|
||||||
|
{
|
||||||
|
if (*tmp)
|
||||||
|
{
|
||||||
|
if (prev_type == XML_ELEMENT_NODE && child->type == XML_ELEMENT_NODE)
|
||||||
|
str = xmlStrcat(str, BAD_CAST " ");
|
||||||
|
str = xmlStrcat(str, tmp);
|
||||||
|
prev_type = child->type;
|
||||||
|
}
|
||||||
|
xmlFree(tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (node->type)
|
||||||
|
{
|
||||||
|
case XML_ELEMENT_NODE:
|
||||||
|
case XML_TEXT_NODE:
|
||||||
|
case XML_ENTITY_REF_NODE:
|
||||||
|
case XML_ENTITY_NODE:
|
||||||
|
case XML_DOCUMENT_NODE:
|
||||||
|
case XML_DOCUMENT_FRAG_NODE:
|
||||||
|
if (!preserving)
|
||||||
|
str = trim_whitespace(str);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
static HRESULT WINAPI xmlnode_get_text(
|
static HRESULT WINAPI xmlnode_get_text(
|
||||||
IXMLDOMNode *iface,
|
IXMLDOMNode *iface,
|
||||||
BSTR* text)
|
BSTR* text)
|
||||||
|
@ -556,7 +640,7 @@ static HRESULT WINAPI xmlnode_get_text(
|
||||||
if ( !text )
|
if ( !text )
|
||||||
return E_INVALIDARG;
|
return E_INVALIDARG;
|
||||||
|
|
||||||
pContent = xmlNodeGetContent((xmlNodePtr)This->node);
|
pContent = do_get_text((xmlNodePtr)This->node);
|
||||||
if(pContent)
|
if(pContent)
|
||||||
{
|
{
|
||||||
str = bstr_from_xmlChar(pContent);
|
str = bstr_from_xmlChar(pContent);
|
||||||
|
|
|
@ -674,20 +674,6 @@ static BOOL compareIgnoreReturns(BSTR sLeft, BSTR sRight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static BOOL compareIgnoreReturnsWhitespace(BSTR sLeft, BSTR sRight)
|
|
||||||
{
|
|
||||||
/* MSXML3 inserts whitespace where as libxml doesn't. */
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
while (*sLeft == '\r' || *sLeft == '\n' || *sLeft == ' ') sLeft++;
|
|
||||||
while (*sRight == '\r' || *sRight == '\n' || *sRight == ' ') sRight++;
|
|
||||||
if (*sLeft != *sRight) return FALSE;
|
|
||||||
if (!*sLeft) return TRUE;
|
|
||||||
sLeft++;
|
|
||||||
sRight++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void get_str_for_type(DOMNodeType type, char *buf)
|
static void get_str_for_type(DOMNodeType type, char *buf)
|
||||||
{
|
{
|
||||||
switch (type)
|
switch (type)
|
||||||
|
@ -1291,8 +1277,7 @@ if (0)
|
||||||
|
|
||||||
r = IXMLDOMText_get_text(nodetext, &str);
|
r = IXMLDOMText_get_text(nodetext, &str);
|
||||||
ok(r == S_OK, "ret %08x\n", r );
|
ok(r == S_OK, "ret %08x\n", r );
|
||||||
/* whitespace preserving needs to be handled here */
|
ok( !lstrcmpW( str, _bstr_("This &is a Middle; test <>\\Append End") ), "incorrect get_text string, got '%s'\n", wine_dbgstr_w(str) );
|
||||||
todo_wine ok( !lstrcmpW( str, _bstr_("This &is a Middle; test <>\\Append End") ), "incorrect get_text string, got '%s'\n", wine_dbgstr_w(str) );
|
|
||||||
SysFreeString(str);
|
SysFreeString(str);
|
||||||
|
|
||||||
/* delete from end */
|
/* delete from end */
|
||||||
|
@ -1305,7 +1290,7 @@ if (0)
|
||||||
|
|
||||||
r = IXMLDOMText_get_text(nodetext, &str);
|
r = IXMLDOMText_get_text(nodetext, &str);
|
||||||
ok(r == S_OK, "ret %08x\n", r );
|
ok(r == S_OK, "ret %08x\n", r );
|
||||||
todo_wine ok( !lstrcmpW( str, _bstr_("This &is a Middle; test <>\\Append") ), "incorrect get_text string, got '%s'\n", wine_dbgstr_w(str) );
|
ok( !lstrcmpW( str, _bstr_("This &is a Middle; test <>\\Append") ), "incorrect get_text string, got '%s'\n", wine_dbgstr_w(str) );
|
||||||
SysFreeString(str);
|
SysFreeString(str);
|
||||||
|
|
||||||
/* delete from inside */
|
/* delete from inside */
|
||||||
|
@ -1318,7 +1303,7 @@ if (0)
|
||||||
|
|
||||||
r = IXMLDOMText_get_text(nodetext, &str);
|
r = IXMLDOMText_get_text(nodetext, &str);
|
||||||
ok(r == S_OK, "ret %08x\n", r );
|
ok(r == S_OK, "ret %08x\n", r );
|
||||||
todo_wine ok( !lstrcmpW( str, _bstr_("") ), "incorrect get_text string, got '%s'\n", wine_dbgstr_w(str) );
|
ok( !lstrcmpW( str, _bstr_("") ), "incorrect get_text string, got '%s'\n", wine_dbgstr_w(str) );
|
||||||
SysFreeString(str);
|
SysFreeString(str);
|
||||||
|
|
||||||
/* delete whole data ... */
|
/* delete whole data ... */
|
||||||
|
@ -2741,7 +2726,7 @@ static void test_get_text(void)
|
||||||
{
|
{
|
||||||
r = IXMLDOMNode_get_text( nodeRoot, &str );
|
r = IXMLDOMNode_get_text( nodeRoot, &str );
|
||||||
ok( r == S_OK, "ret %08x\n", r );
|
ok( r == S_OK, "ret %08x\n", r );
|
||||||
ok( compareIgnoreReturnsWhitespace(str, _bstr_("fn1.txt\n\n fn2.txt \n\nf1\n")), "wrong get_text\n");
|
ok( compareIgnoreReturns(str, _bstr_("fn1.txt\n\n fn2.txt \n\nf1\n")), "wrong get_text: %s\n", wine_dbgstr_w(str));
|
||||||
SysFreeString(str);
|
SysFreeString(str);
|
||||||
|
|
||||||
IXMLDOMNode_Release(nodeRoot);
|
IXMLDOMNode_Release(nodeRoot);
|
||||||
|
@ -3724,41 +3709,86 @@ static void test_IXMLDOMDocument2(void)
|
||||||
free_bstrs();
|
free_bstrs();
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void check_ws_ignored(IXMLDOMDocument2* doc)
|
#define helper_ole_check(expr) { \
|
||||||
|
HRESULT r = expr; \
|
||||||
|
ok_(__FILE__, line)(r == S_OK, "=> %i: " #expr " returned %08x\n", __LINE__, r); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define helper_expect_list_and_release(list, expstr) { \
|
||||||
|
char *str = list_to_string(list); \
|
||||||
|
ok_(__FILE__, line)(strcmp(str, expstr)==0, "=> %i: Invalid node list: %s, expected %s\n", __LINE__, str, expstr); \
|
||||||
|
if (list) IXMLDOMNodeList_Release(list); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define helper_expect_bstr_and_release(bstr, str) { \
|
||||||
|
ok_(__FILE__, line)(lstrcmpW(bstr, _bstr_(str)) == 0, \
|
||||||
|
"=> %i: got %s\n", __LINE__, wine_dbgstr_w(bstr)); \
|
||||||
|
SysFreeString(bstr); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define check_ws_ignored(doc, str) _check_ws_ignored(__LINE__, doc, str)
|
||||||
|
static inline void _check_ws_ignored(int line, IXMLDOMDocument2* doc, char const* str)
|
||||||
{
|
{
|
||||||
IXMLDOMNode *node1, *node2;
|
IXMLDOMNode *node1, *node2;
|
||||||
IXMLDOMNodeList *list;
|
IXMLDOMNodeList *list;
|
||||||
|
BSTR bstr;
|
||||||
|
|
||||||
ole_check(IXMLDOMDocument2_selectNodes(doc, _bstr_("//*[local-name()='html']"), &list));
|
helper_ole_check(IXMLDOMDocument2_selectNodes(doc, _bstr_("//*[local-name()='html']"), &list));
|
||||||
ole_check(IXMLDOMNodeList_get_item(list, 0, &node1));
|
helper_ole_check(IXMLDOMNodeList_get_item(list, 0, &node1));
|
||||||
ole_check(IXMLDOMNodeList_get_item(list, 1, &node2));
|
helper_ole_check(IXMLDOMNodeList_get_item(list, 1, &node2));
|
||||||
ole_check(IXMLDOMNodeList_reset(list));
|
helper_ole_check(IXMLDOMNodeList_reset(list));
|
||||||
expect_list_and_release(list, "E1.E4.E1.E2.D1 E2.E4.E1.E2.D1");
|
helper_expect_list_and_release(list, "E1.E4.E1.E2.D1 E2.E4.E1.E2.D1");
|
||||||
|
|
||||||
ole_check(IXMLDOMNode_get_childNodes(node1, &list));
|
helper_ole_check(IXMLDOMNode_get_childNodes(node1, &list));
|
||||||
expect_list_and_release(list, "T1.E1.E4.E1.E2.D1 E2.E1.E4.E1.E2.D1 E3.E1.E4.E1.E2.D1 T4.E1.E4.E1.E2.D1 E5.E1.E4.E1.E2.D1");
|
helper_expect_list_and_release(list, "T1.E1.E4.E1.E2.D1 E2.E1.E4.E1.E2.D1 E3.E1.E4.E1.E2.D1 T4.E1.E4.E1.E2.D1 E5.E1.E4.E1.E2.D1");
|
||||||
|
helper_ole_check(IXMLDOMNode_get_text(node1, &bstr));
|
||||||
|
if (str)
|
||||||
|
{
|
||||||
|
helper_expect_bstr_and_release(bstr, str);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
helper_expect_bstr_and_release(bstr, "This is a description.");
|
||||||
|
}
|
||||||
IXMLDOMNode_Release(node1);
|
IXMLDOMNode_Release(node1);
|
||||||
ole_check(IXMLDOMNode_get_childNodes(node2, &list));
|
|
||||||
expect_list_and_release(list, "T1.E2.E4.E1.E2.D1 E2.E2.E4.E1.E2.D1 T3.E2.E4.E1.E2.D1 E4.E2.E4.E1.E2.D1 T5.E2.E4.E1.E2.D1 E6.E2.E4.E1.E2.D1 T7.E2.E4.E1.E2.D1");
|
helper_ole_check(IXMLDOMNode_get_childNodes(node2, &list));
|
||||||
|
helper_expect_list_and_release(list, "T1.E2.E4.E1.E2.D1 E2.E2.E4.E1.E2.D1 T3.E2.E4.E1.E2.D1 E4.E2.E4.E1.E2.D1 T5.E2.E4.E1.E2.D1 E6.E2.E4.E1.E2.D1 T7.E2.E4.E1.E2.D1");
|
||||||
|
helper_ole_check(IXMLDOMNode_get_text(node2, &bstr));
|
||||||
|
helper_expect_bstr_and_release(bstr, "\n This is a description with preserved whitespace. \n ");
|
||||||
IXMLDOMNode_Release(node2);
|
IXMLDOMNode_Release(node2);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void check_ws_preserved(IXMLDOMDocument2* doc)
|
#define check_ws_preserved(doc, str) _check_ws_preserved(__LINE__, doc, str)
|
||||||
|
static inline void _check_ws_preserved(int line, IXMLDOMDocument2* doc, char const* str)
|
||||||
{
|
{
|
||||||
IXMLDOMNode *node1, *node2;
|
IXMLDOMNode *node1, *node2;
|
||||||
IXMLDOMNodeList *list;
|
IXMLDOMNodeList *list;
|
||||||
|
BSTR bstr;
|
||||||
|
|
||||||
ole_check(IXMLDOMDocument2_selectNodes(doc, _bstr_("//*[local-name()='html']"), &list));
|
helper_ole_check(IXMLDOMDocument2_selectNodes(doc, _bstr_("//*[local-name()='html']"), &list));
|
||||||
ole_check(IXMLDOMNodeList_get_item(list, 0, &node1));
|
helper_ole_check(IXMLDOMNodeList_get_item(list, 0, &node1));
|
||||||
ole_check(IXMLDOMNodeList_get_item(list, 1, &node2));
|
helper_ole_check(IXMLDOMNodeList_get_item(list, 1, &node2));
|
||||||
ole_check(IXMLDOMNodeList_reset(list));
|
helper_ole_check(IXMLDOMNodeList_reset(list));
|
||||||
expect_list_and_release(list, "E2.E8.E2.E2.D1 E4.E8.E2.E2.D1");
|
helper_expect_list_and_release(list, "E2.E8.E2.E2.D1 E4.E8.E2.E2.D1");
|
||||||
|
|
||||||
ole_check(IXMLDOMNode_get_childNodes(node1, &list));
|
helper_ole_check(IXMLDOMNode_get_childNodes(node1, &list));
|
||||||
expect_list_and_release(list, "T1.E2.E8.E2.E2.D1 E2.E2.E8.E2.E2.D1 T3.E2.E8.E2.E2.D1 E4.E2.E8.E2.E2.D1 T5.E2.E8.E2.E2.D1 E6.E2.E8.E2.E2.D1 T7.E2.E8.E2.E2.D1");
|
helper_expect_list_and_release(list, "T1.E2.E8.E2.E2.D1 E2.E2.E8.E2.E2.D1 T3.E2.E8.E2.E2.D1 E4.E2.E8.E2.E2.D1 T5.E2.E8.E2.E2.D1 E6.E2.E8.E2.E2.D1 T7.E2.E8.E2.E2.D1");
|
||||||
|
helper_ole_check(IXMLDOMNode_get_text(node1, &bstr));
|
||||||
|
if (str)
|
||||||
|
{
|
||||||
|
helper_expect_bstr_and_release(bstr, str);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
helper_expect_bstr_and_release(bstr, "\n This is a description. \n ");
|
||||||
|
}
|
||||||
IXMLDOMNode_Release(node1);
|
IXMLDOMNode_Release(node1);
|
||||||
ole_check(IXMLDOMNode_get_childNodes(node2, &list));
|
|
||||||
expect_list_and_release(list, "T1.E4.E8.E2.E2.D1 E2.E4.E8.E2.E2.D1 T3.E4.E8.E2.E2.D1 E4.E4.E8.E2.E2.D1 T5.E4.E8.E2.E2.D1 E6.E4.E8.E2.E2.D1 T7.E4.E8.E2.E2.D1");
|
helper_ole_check(IXMLDOMNode_get_childNodes(node2, &list));
|
||||||
|
helper_expect_list_and_release(list, "T1.E4.E8.E2.E2.D1 E2.E4.E8.E2.E2.D1 T3.E4.E8.E2.E2.D1 E4.E4.E8.E2.E2.D1 T5.E4.E8.E2.E2.D1 E6.E4.E8.E2.E2.D1 T7.E4.E8.E2.E2.D1");
|
||||||
|
helper_ole_check(IXMLDOMNode_get_text(node2, &bstr));
|
||||||
|
helper_expect_bstr_and_release(bstr, "\n This is a description with preserved whitespace. \n ");
|
||||||
IXMLDOMNode_Release(node2);
|
IXMLDOMNode_Release(node2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3786,8 +3816,8 @@ static void test_whitespace(void)
|
||||||
ole_check(IXMLDOMDocument2_setProperty(doc1, _bstr_("SelectionLanguage"), _variantbstr_("XPath")));
|
ole_check(IXMLDOMDocument2_setProperty(doc1, _bstr_("SelectionLanguage"), _variantbstr_("XPath")));
|
||||||
ole_check(IXMLDOMDocument2_setProperty(doc2, _bstr_("SelectionLanguage"), _variantbstr_("XPath")));
|
ole_check(IXMLDOMDocument2_setProperty(doc2, _bstr_("SelectionLanguage"), _variantbstr_("XPath")));
|
||||||
|
|
||||||
check_ws_ignored(doc1);
|
check_ws_ignored(doc1, NULL);
|
||||||
check_ws_preserved(doc2);
|
check_ws_preserved(doc2, NULL);
|
||||||
|
|
||||||
/* new instances copy the property */
|
/* new instances copy the property */
|
||||||
ole_check(IXMLDOMDocument2_QueryInterface(doc1, &IID_IXMLDOMDocument2, (void**) &doc3));
|
ole_check(IXMLDOMDocument2_QueryInterface(doc1, &IID_IXMLDOMDocument2, (void**) &doc3));
|
||||||
|
@ -3798,15 +3828,16 @@ static void test_whitespace(void)
|
||||||
ole_check(IXMLDOMDocument2_get_preserveWhiteSpace(doc4, &b));
|
ole_check(IXMLDOMDocument2_get_preserveWhiteSpace(doc4, &b));
|
||||||
ok(b == VARIANT_TRUE, "expected true\n");
|
ok(b == VARIANT_TRUE, "expected true\n");
|
||||||
|
|
||||||
check_ws_ignored(doc3);
|
check_ws_ignored(doc3, NULL);
|
||||||
check_ws_preserved(doc4);
|
check_ws_preserved(doc4, NULL);
|
||||||
|
|
||||||
/* setting after loading xml does nothing */
|
/* setting after loading xml affects trimming of leading/trailing ws only */
|
||||||
ole_check(IXMLDOMDocument2_put_preserveWhiteSpace(doc1, VARIANT_TRUE));
|
ole_check(IXMLDOMDocument2_put_preserveWhiteSpace(doc1, VARIANT_TRUE));
|
||||||
ole_check(IXMLDOMDocument2_put_preserveWhiteSpace(doc2, VARIANT_FALSE));
|
ole_check(IXMLDOMDocument2_put_preserveWhiteSpace(doc2, VARIANT_FALSE));
|
||||||
|
|
||||||
check_ws_ignored(doc1);
|
/* the trailing "\n " isn't there, because it was ws-only node */
|
||||||
check_ws_preserved(doc2);
|
check_ws_ignored(doc1, "\n This is a description. ");
|
||||||
|
check_ws_preserved(doc2, "This is a description.");
|
||||||
|
|
||||||
/* it takes effect on reload */
|
/* it takes effect on reload */
|
||||||
ole_check(IXMLDOMDocument2_get_preserveWhiteSpace(doc1, &b));
|
ole_check(IXMLDOMDocument2_get_preserveWhiteSpace(doc1, &b));
|
||||||
|
@ -3819,8 +3850,8 @@ static void test_whitespace(void)
|
||||||
ole_check(IXMLDOMDocument2_loadXML(doc2, _bstr_(szExampleXML), &b));
|
ole_check(IXMLDOMDocument2_loadXML(doc2, _bstr_(szExampleXML), &b));
|
||||||
ok(b == VARIANT_TRUE, "failed to load XML string\n");
|
ok(b == VARIANT_TRUE, "failed to load XML string\n");
|
||||||
|
|
||||||
check_ws_preserved(doc1);
|
check_ws_preserved(doc1, NULL);
|
||||||
check_ws_ignored(doc2);
|
check_ws_ignored(doc2, NULL);
|
||||||
|
|
||||||
/* other instances follow suit */
|
/* other instances follow suit */
|
||||||
ole_check(IXMLDOMDocument2_get_preserveWhiteSpace(doc3, &b));
|
ole_check(IXMLDOMDocument2_get_preserveWhiteSpace(doc3, &b));
|
||||||
|
@ -3828,8 +3859,8 @@ static void test_whitespace(void)
|
||||||
ole_check(IXMLDOMDocument2_get_preserveWhiteSpace(doc4, &b));
|
ole_check(IXMLDOMDocument2_get_preserveWhiteSpace(doc4, &b));
|
||||||
ok(b == VARIANT_FALSE, "expected false\n");
|
ok(b == VARIANT_FALSE, "expected false\n");
|
||||||
|
|
||||||
check_ws_preserved(doc3);
|
check_ws_preserved(doc3, NULL);
|
||||||
check_ws_ignored(doc4);
|
check_ws_ignored(doc4, NULL);
|
||||||
|
|
||||||
IXMLDOMDocument_Release(doc1);
|
IXMLDOMDocument_Release(doc1);
|
||||||
IXMLDOMDocument_Release(doc2);
|
IXMLDOMDocument_Release(doc2);
|
||||||
|
|
Loading…
Reference in New Issue