webservices: Add initial support for binary XML in the reader.

Signed-off-by: Hans Leidekker <hans@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Hans Leidekker 2017-05-25 12:54:40 +02:00 committed by Alexandre Julliard
parent 2ac3492537
commit 505d60f988
2 changed files with 400 additions and 105 deletions

View File

@ -357,26 +357,27 @@ struct prefix
struct reader
{
ULONG magic;
CRITICAL_SECTION cs;
ULONG read_size;
ULONG read_pos;
const unsigned char *read_bufptr;
enum reader_state state;
struct node *root;
struct node *current;
ULONG current_attr;
struct node *last;
struct prefix *prefixes;
ULONG nb_prefixes;
ULONG nb_prefixes_allocated;
WS_XML_READER_INPUT_TYPE input_type;
struct xmlbuf *input_buf;
const unsigned char *input_data;
ULONG input_size;
ULONG text_conv_offset;
ULONG prop_count;
struct prop prop[sizeof(reader_props)/sizeof(reader_props[0])];
ULONG magic;
CRITICAL_SECTION cs;
ULONG read_size;
ULONG read_pos;
const unsigned char *read_bufptr;
enum reader_state state;
struct node *root;
struct node *current;
ULONG current_attr;
struct node *last;
struct prefix *prefixes;
ULONG nb_prefixes;
ULONG nb_prefixes_allocated;
WS_XML_READER_ENCODING_TYPE input_enc;
WS_XML_READER_INPUT_TYPE input_type;
struct xmlbuf *input_buf;
const unsigned char *input_data;
ULONG input_size;
ULONG text_conv_offset;
ULONG prop_count;
struct prop prop[sizeof(reader_props)/sizeof(reader_props[0])];
};
#define READER_MAGIC (('R' << 24) | ('E' << 16) | ('A' << 8) | 'D')
@ -519,6 +520,7 @@ static HRESULT init_reader( struct reader *reader )
reader->nb_prefixes = 1;
if (!(node = alloc_node( WS_XML_NODE_TYPE_EOF ))) return E_OUTOFMEMORY;
read_insert_eof( reader, node );
reader->input_enc = WS_XML_READER_ENCODING_TYPE_TEXT;
return S_OK;
}
@ -819,6 +821,28 @@ static inline const unsigned char *read_current_ptr( struct reader *reader )
return &reader->read_bufptr[reader->read_pos];
}
static inline HRESULT read_peek( struct reader *reader, unsigned char *byte )
{
if (reader->read_pos >= reader->read_size) return WS_E_INVALID_FORMAT;
*byte = reader->read_bufptr[reader->read_pos];
return S_OK;
}
static inline HRESULT read_byte( struct reader *reader, unsigned char *byte )
{
if (reader->read_pos >= reader->read_size) return WS_E_INVALID_FORMAT;
*byte = reader->read_bufptr[reader->read_pos++];
return S_OK;
}
static inline HRESULT read_bytes( struct reader *reader, unsigned char *bytes, unsigned int len )
{
if (reader->read_pos + len > reader->read_size) return WS_E_INVALID_FORMAT;
memcpy( bytes, reader->read_bufptr + reader->read_pos, len );
reader->read_pos += len;
return S_OK;
}
/* UTF-8 support based on libs/wine/utf8.c */
/* number of following bytes in sequence based on first byte value (for bytes above 0x7f) */
@ -1145,12 +1169,100 @@ static HRESULT decode_text( const unsigned char *str, ULONG len, unsigned char *
return S_OK;
}
static HRESULT read_attribute( struct reader *reader, WS_XML_ATTRIBUTE **ret )
static HRESULT read_attribute_value_text( struct reader *reader, WS_XML_ATTRIBUTE *attr )
{
WS_XML_UTF8_TEXT *utf8 = NULL;
unsigned int len, ch, skip, quote;
const unsigned char *start;
HRESULT hr = E_OUTOFMEMORY;
read_skip_whitespace( reader );
if (read_cmp( reader, "=", 1 )) return WS_E_INVALID_FORMAT;
read_skip( reader, 1 );
read_skip_whitespace( reader );
if (read_cmp( reader, "\"", 1 ) && read_cmp( reader, "'", 1 )) return WS_E_INVALID_FORMAT;
quote = read_utf8_char( reader, &skip );
read_skip( reader, 1 );
len = 0;
start = read_current_ptr( reader );
for (;;)
{
if (!(ch = read_utf8_char( reader, &skip ))) return WS_E_INVALID_FORMAT;
if (ch == quote) break;
read_skip( reader, skip );
len += skip;
}
read_skip( reader, 1 );
if (attr->isXmlNs)
{
if (!(attr->ns = alloc_xml_string( start, len ))) goto error;
if ((hr = bind_prefix( reader, attr->prefix, attr->ns )) != S_OK) goto error;
if (!(utf8 = alloc_utf8_text( NULL, 0 )))
{
hr = E_OUTOFMEMORY;
goto error;
}
}
else
{
if (!(utf8 = alloc_utf8_text( NULL, len ))) goto error;
if ((hr = decode_text( start, len, utf8->value.bytes, &utf8->value.length )) != S_OK) goto error;
}
attr->value = &utf8->text;
attr->singleQuote = (quote == '\'');
return S_OK;
error:
heap_free( utf8 );
return hr;
}
static HRESULT read_int31( struct reader *reader, ULONG *len )
{
unsigned char byte;
HRESULT hr;
if ((hr = read_byte( reader, &byte )) != S_OK) return hr;
*len = byte & 0x7f;
if (!(byte & 0x80)) return S_OK;
if ((hr = read_byte( reader, &byte )) != S_OK) return hr;
*len += (byte & 0x7f) << 7;
if (!(byte & 0x80)) return S_OK;
if ((hr = read_byte( reader, &byte )) != S_OK) return hr;
*len += (byte & 0x7f) << 14;
if (!(byte & 0x80)) return S_OK;
if ((hr = read_byte( reader, &byte )) != S_OK) return hr;
*len += (byte & 0x7f) << 21;
if (!(byte & 0x80)) return S_OK;
if ((hr = read_byte( reader, &byte )) != S_OK) return hr;
*len += (byte & 0x07) << 28;
return S_OK;
}
static HRESULT read_string( struct reader *reader, WS_XML_STRING **str )
{
ULONG len;
HRESULT hr;
if ((hr = read_int31( reader, &len )) != S_OK) return hr;
if (!(*str = alloc_xml_string( NULL, len ))) return E_OUTOFMEMORY;
if ((hr = read_bytes( reader, (*str)->bytes, len )) == S_OK) return S_OK;
heap_free( *str );
return hr;
}
static HRESULT read_attribute_text( struct reader *reader, WS_XML_ATTRIBUTE **ret )
{
static const WS_XML_STRING xmlns = {5, (BYTE *)"xmlns"};
WS_XML_ATTRIBUTE *attr;
WS_XML_UTF8_TEXT *text = NULL;
unsigned int len = 0, ch, skip, quote;
unsigned int len = 0, ch, skip;
const unsigned char *start;
WS_XML_STRING *prefix, *localname;
HRESULT hr = WS_E_INVALID_FORMAT;
@ -1168,7 +1280,6 @@ static HRESULT read_attribute( struct reader *reader, WS_XML_ATTRIBUTE **ret )
if (!len) goto error;
if ((hr = parse_name( start, len, &prefix, &localname )) != S_OK) goto error;
hr = E_OUTOFMEMORY;
if (WsXmlStringEquals( prefix, &xmlns, NULL ) == S_OK)
{
heap_free( prefix );
@ -1176,6 +1287,7 @@ static HRESULT read_attribute( struct reader *reader, WS_XML_ATTRIBUTE **ret )
if (!(attr->prefix = alloc_xml_string( localname->bytes, localname->length )))
{
heap_free( localname );
hr = E_OUTOFMEMORY;
goto error;
}
attr->localName = localname;
@ -1192,61 +1304,25 @@ static HRESULT read_attribute( struct reader *reader, WS_XML_ATTRIBUTE **ret )
attr->localName = localname;
}
hr = WS_E_INVALID_FORMAT;
read_skip_whitespace( reader );
if (read_cmp( reader, "=", 1 )) goto error;
read_skip( reader, 1 );
read_skip_whitespace( reader );
if (read_cmp( reader, "\"", 1 ) && read_cmp( reader, "'", 1 )) goto error;
quote = read_utf8_char( reader, &skip );
read_skip( reader, 1 );
len = 0;
start = read_current_ptr( reader );
for (;;)
{
if (!(ch = read_utf8_char( reader, &skip ))) goto error;
if (ch == quote) break;
read_skip( reader, skip );
len += skip;
}
read_skip( reader, 1 );
hr = E_OUTOFMEMORY;
if (attr->isXmlNs)
{
if (!(attr->ns = alloc_xml_string( start, len ))) goto error;
if ((hr = bind_prefix( reader, attr->prefix, attr->ns )) != S_OK) goto error;
if (!(text = alloc_utf8_text( NULL, 0 ))) goto error;
}
else
{
if (!(text = alloc_utf8_text( NULL, len ))) goto error;
if ((hr = decode_text( start, len, text->value.bytes, &text->value.length )) != S_OK) goto error;
}
attr->value = &text->text;
attr->singleQuote = (quote == '\'');
if ((hr = read_attribute_value_text( reader, attr )) != S_OK) goto error;
*ret = attr;
return S_OK;
error:
heap_free( text );
free_attribute( attr );
return hr;
}
static struct node *find_parent( struct reader *reader )
static inline struct node *find_parent( struct reader *reader )
{
if (node_type( reader->current ) == WS_XML_NODE_TYPE_END_ELEMENT)
{
if (is_valid_parent( reader->current->parent->parent )) return reader->current->parent->parent;
return NULL;
}
else if (is_valid_parent( reader->current )) return reader->current;
else if (is_valid_parent( reader->current->parent )) return reader->current->parent;
if (is_valid_parent( reader->current )) return reader->current;
if (is_valid_parent( reader->current->parent )) return reader->current->parent;
return NULL;
}
@ -1270,13 +1346,47 @@ static HRESULT set_namespaces( struct reader *reader, WS_XML_ELEMENT_NODE *elem
return S_OK;
}
static HRESULT read_element( struct reader *reader )
static WS_XML_ELEMENT_NODE *alloc_element_pair(void)
{
struct node *node, *end;
if (!(node = alloc_node( WS_XML_NODE_TYPE_ELEMENT ))) return NULL;
if (!(end = alloc_node( WS_XML_NODE_TYPE_END_ELEMENT )))
{
free_node( node );
return NULL;
}
list_add_tail( &node->children, &end->entry );
end->parent = node;
return &node->hdr;
}
static HRESULT read_attributes_text( struct reader *reader, WS_XML_ELEMENT_NODE *elem )
{
WS_XML_ATTRIBUTE *attr;
HRESULT hr;
reader->current_attr = 0;
for (;;)
{
read_skip_whitespace( reader );
if (!read_cmp( reader, ">", 1 ) || !read_cmp( reader, "/>", 2 )) break;
if ((hr = read_attribute_text( reader, &attr )) != S_OK) return hr;
if ((hr = append_attribute( elem, attr )) != S_OK)
{
free_attribute( attr );
return hr;
}
reader->current_attr++;
}
return S_OK;
}
static HRESULT read_element_text( struct reader *reader )
{
unsigned int len = 0, ch, skip;
const unsigned char *start;
struct node *node = NULL, *endnode, *parent;
struct node *node = NULL, *parent;
WS_XML_ELEMENT_NODE *elem;
WS_XML_ATTRIBUTE *attr = NULL;
HRESULT hr = WS_E_INVALID_FORMAT;
if (read_end_of_data( reader ))
@ -1287,14 +1397,17 @@ static HRESULT read_element( struct reader *reader )
return S_OK;
}
if (read_cmp( reader, "<", 1 )) goto error;
if (read_cmp( reader, "<", 1 )) return WS_E_INVALID_FORMAT;
read_skip( reader, 1 );
if (!read_isnamechar( read_utf8_char( reader, &skip )))
{
read_rewind( reader, 1 );
goto error;
return WS_E_INVALID_FORMAT;
}
if (!(elem = alloc_element_pair())) return E_OUTOFMEMORY;
node = (struct node *)elem;
start = read_current_ptr( reader );
for (;;)
{
@ -1306,29 +1419,78 @@ static HRESULT read_element( struct reader *reader )
if (!len) goto error;
if (!(parent = find_parent( reader ))) goto error;
hr = E_OUTOFMEMORY;
if (!(node = alloc_node( WS_XML_NODE_TYPE_ELEMENT ))) goto error;
if (!(endnode = alloc_node( WS_XML_NODE_TYPE_END_ELEMENT ))) goto error;
list_add_tail( &node->children, &endnode->entry );
endnode->parent = node;
elem = (WS_XML_ELEMENT_NODE *)node;
if ((hr = parse_name( start, len, &elem->prefix, &elem->localName )) != S_OK) goto error;
reader->current_attr = 0;
for (;;)
if ((hr = read_attributes_text( reader, elem )) != S_OK) goto error;
if ((hr = set_namespaces( reader, elem )) != S_OK) goto error;
read_insert_node( reader, parent, node );
reader->state = READER_STATE_STARTELEMENT;
return S_OK;
error:
destroy_nodes( node );
return hr;
}
static inline BOOL is_element_type( unsigned char type )
{
return (type >= RECORD_SHORT_ELEMENT && type <= RECORD_PREFIX_ELEMENT_Z);
}
static HRESULT read_element_bin( struct reader *reader )
{
struct node *node = NULL, *parent;
WS_XML_ELEMENT_NODE *elem;
unsigned char type;
HRESULT hr;
if ((hr = read_byte( reader, &type )) != S_OK) return hr;
if (!is_element_type( type )) return WS_E_INVALID_FORMAT;
if (!(elem = alloc_element_pair())) return E_OUTOFMEMORY;
node = (struct node *)elem;
if (type >= RECORD_PREFIX_ELEMENT_A && type <= RECORD_PREFIX_ELEMENT_Z)
{
read_skip_whitespace( reader );
if (!read_cmp( reader, ">", 1 ) || !read_cmp( reader, "/>", 2 )) break;
if ((hr = read_attribute( reader, &attr )) != S_OK) goto error;
if ((hr = append_attribute( elem, attr )) != S_OK)
unsigned char ch = type - RECORD_PREFIX_ELEMENT_A + 'a';
if (!(elem->prefix = alloc_xml_string( &ch, 1 )))
{
free_attribute( attr );
hr = E_OUTOFMEMORY;
goto error;
}
reader->current_attr++;
if ((hr = read_string( reader, &elem->localName )) != S_OK) goto error;
}
else
{
switch (type)
{
case RECORD_SHORT_ELEMENT:
if (!(elem->prefix = alloc_xml_string( NULL, 0 )))
{
hr = E_OUTOFMEMORY;
goto error;
}
if ((hr = read_string( reader, &elem->localName )) != S_OK) goto error;
break;
case RECORD_ELEMENT:
if ((hr = read_string( reader, &elem->prefix )) != S_OK) goto error;
if ((hr = read_string( reader, &elem->localName )) != S_OK) goto error;
break;
default:
ERR( "unhandled record type %02x\n", type );
return WS_E_NOT_SUPPORTED;
}
}
if (!(parent = find_parent( reader )))
{
hr = WS_E_INVALID_FORMAT;
goto error;
}
if ((hr = set_namespaces( reader, elem )) != S_OK) goto error;
read_insert_node( reader, parent, node );
@ -1382,7 +1544,7 @@ static HRESULT read_text( struct reader *reader )
return S_OK;
}
static HRESULT read_node( struct reader * );
static HRESULT read_node_text( struct reader * );
static HRESULT read_startelement( struct reader *reader )
{
@ -1398,7 +1560,7 @@ static HRESULT read_startelement( struct reader *reader )
else if (!read_cmp( reader, ">", 1 ))
{
read_skip( reader, 1 );
return read_node( reader );
return read_node_text( reader );
}
return WS_E_INVALID_FORMAT;
}
@ -1422,7 +1584,7 @@ static HRESULT read_to_startelement( struct reader *reader, BOOL *found )
}
read_skip_whitespace( reader );
if ((hr = read_element( reader )) == S_OK && found)
if ((hr = read_element_text( reader )) == S_OK && found)
{
if (reader->state == READER_STATE_STARTELEMENT)
*found = TRUE;
@ -1441,8 +1603,8 @@ static int cmp_name( const unsigned char *name1, ULONG len1, const unsigned char
return 0;
}
static struct node *read_find_startelement( struct reader *reader, const WS_XML_STRING *prefix,
const WS_XML_STRING *localname )
static struct node *find_startelement( struct reader *reader, const WS_XML_STRING *prefix,
const WS_XML_STRING *localname )
{
struct node *parent;
const WS_XML_STRING *str;
@ -1461,7 +1623,7 @@ static struct node *read_find_startelement( struct reader *reader, const WS_XML_
return NULL;
}
static HRESULT read_endelement( struct reader *reader )
static HRESULT read_endelement_text( struct reader *reader )
{
struct node *parent;
unsigned int len = 0, ch, skip;
@ -1469,16 +1631,6 @@ static HRESULT read_endelement( struct reader *reader )
WS_XML_STRING *prefix, *localname;
HRESULT hr;
if (reader->state == READER_STATE_EOF) return WS_E_INVALID_FORMAT;
if (read_end_of_data( reader ))
{
reader->current = LIST_ENTRY( list_tail( &reader->root->children ), struct node, entry );
reader->last = reader->current;
reader->state = READER_STATE_EOF;
return S_OK;
}
if (read_cmp( reader, "</", 2 )) return WS_E_INVALID_FORMAT;
read_skip( reader, 2 );
@ -1497,7 +1649,7 @@ static HRESULT read_endelement( struct reader *reader )
}
if ((hr = parse_name( start, len, &prefix, &localname )) != S_OK) return hr;
parent = read_find_startelement( reader, prefix, localname );
parent = find_startelement( reader, prefix, localname );
heap_free( prefix );
heap_free( localname );
if (!parent) return WS_E_INVALID_FORMAT;
@ -1508,6 +1660,45 @@ static HRESULT read_endelement( struct reader *reader )
return S_OK;
}
static HRESULT read_endelement_bin( struct reader *reader )
{
struct node *parent;
unsigned char type;
HRESULT hr;
if ((hr = read_byte( reader, &type )) != S_OK) return hr;
if (type != RECORD_ENDELEMENT) return WS_E_INVALID_FORMAT;
if (!(parent = find_parent( reader ))) return WS_E_INVALID_FORMAT;
reader->current = LIST_ENTRY( list_tail( &parent->children ), struct node, entry );
reader->last = reader->current;
reader->state = READER_STATE_ENDELEMENT;
return S_OK;
}
static HRESULT read_endelement( struct reader *reader )
{
if (reader->state == READER_STATE_EOF) return WS_E_INVALID_FORMAT;
if (read_end_of_data( reader ))
{
reader->current = LIST_ENTRY( list_tail( &reader->root->children ), struct node, entry );
reader->last = reader->current;
reader->state = READER_STATE_EOF;
return S_OK;
}
switch (reader->input_enc)
{
case WS_XML_READER_ENCODING_TYPE_TEXT: return read_endelement_text( reader );
case WS_XML_READER_ENCODING_TYPE_BINARY: return read_endelement_bin( reader );
default:
ERR( "unhandled encoding %u\n", reader->input_enc );
return WS_E_NOT_SUPPORTED;
}
}
static HRESULT read_comment( struct reader *reader )
{
unsigned int len = 0, ch, skip;
@ -1618,7 +1809,7 @@ static HRESULT read_endcdata( struct reader *reader )
return S_OK;
}
static HRESULT read_node( struct reader *reader )
static HRESULT read_node_text( struct reader *reader )
{
HRESULT hr;
@ -1638,15 +1829,61 @@ static HRESULT read_node( struct reader *reader )
hr = read_xmldecl( reader );
if (FAILED( hr )) return hr;
}
else if (!read_cmp( reader, "</", 2 )) return read_endelement( reader );
else if (!read_cmp( reader, "</", 2 )) return read_endelement_text( reader );
else if (!read_cmp( reader, "<![CDATA[", 9 )) return read_startcdata( reader );
else if (!read_cmp( reader, "<!--", 4 )) return read_comment( reader );
else if (!read_cmp( reader, "<", 1 )) return read_element( reader );
else if (!read_cmp( reader, "<", 1 )) return read_element_text( reader );
else if (!read_cmp( reader, "/>", 2 ) || !read_cmp( reader, ">", 1 )) return read_startelement( reader );
else return read_text( reader );
}
}
static HRESULT read_node_bin( struct reader *reader )
{
unsigned char type;
HRESULT hr;
if (node_type( reader->current ) == WS_XML_NODE_TYPE_TEXT)
{
reader->current = LIST_ENTRY( list_tail( &reader->current->parent->children ), struct node, entry );
reader->last = reader->current;
reader->state = READER_STATE_ENDELEMENT;
return S_OK;
}
if (read_end_of_data( reader ))
{
reader->current = LIST_ENTRY( list_tail( &reader->root->children ), struct node, entry );
reader->last = reader->current;
reader->state = READER_STATE_EOF;
return S_OK;
}
if ((hr = read_peek( reader, &type )) != S_OK) return hr;
if (type == RECORD_ENDELEMENT)
{
return read_endelement_bin( reader );
}
else if (type >= RECORD_SHORT_ELEMENT && type <= RECORD_PREFIX_ELEMENT_Z)
{
return read_element_bin( reader );
}
FIXME( "unhandled record type %02x\n", type );
return WS_E_NOT_SUPPORTED;
}
static HRESULT read_node( struct reader *reader )
{
switch (reader->input_enc)
{
case WS_XML_READER_ENCODING_TYPE_TEXT: return read_node_text( reader );
case WS_XML_READER_ENCODING_TYPE_BINARY: return read_node_bin( reader );
default:
ERR( "unhandled encoding %u\n", reader->input_enc );
return WS_E_NOT_SUPPORTED;
}
}
HRESULT copy_node( WS_XML_READER *handle, struct node **node )
{
struct reader *reader = (struct reader *)handle;
@ -4641,8 +4878,14 @@ HRESULT WINAPI WsSetInput( WS_XML_READER *handle, const WS_XML_READER_ENCODING *
hr = prop_set( reader->prop, reader->prop_count, WS_XML_READER_PROPERTY_CHARSET,
&charset, sizeof(charset) );
if (hr != S_OK) goto done;
reader->input_enc = WS_XML_READER_ENCODING_TYPE_TEXT;
break;
}
case WS_XML_READER_ENCODING_TYPE_BINARY:
reader->input_enc = WS_XML_READER_ENCODING_TYPE_BINARY;
break;
default:
FIXME( "encoding type %u not supported\n", encoding->encodingType );
hr = E_NOTIMPL;

View File

@ -4495,6 +4495,57 @@ static void test_WsSkipNode(void)
WsFreeReader( reader );
}
static HRESULT set_input_bin( WS_XML_READER *reader, const char *data, ULONG size )
{
WS_XML_READER_BINARY_ENCODING bin = {{WS_XML_READER_ENCODING_TYPE_BINARY}};
WS_XML_READER_BUFFER_INPUT buf;
buf.input.inputType = WS_XML_READER_INPUT_TYPE_BUFFER;
buf.encodedData = (void *)data;
buf.encodedDataSize = size;
return WsSetInput( reader, &bin.encoding, &buf.input, NULL, 0, NULL );
}
static void test_binary_encoding(void)
{
static const char res[] =
{0x40,0x01,'t',0x01};
const WS_XML_NODE *node;
const WS_XML_ELEMENT_NODE *elem;
WS_XML_READER *reader;
HRESULT hr;
hr = WsCreateReader( NULL, 0, &reader, NULL );
ok( hr == S_OK, "got %08x\n", hr );
/* short element */
hr = set_input_bin( reader, res, sizeof(res) );
ok( hr == S_OK, "got %08x\n", hr );
hr = WsReadNode( reader, NULL );
ok( hr == S_OK, "got %08x\n", hr );
hr = WsGetReaderNode( reader, &node, NULL );
ok( hr == S_OK, "got %08x\n", hr );
ok( node->nodeType == WS_XML_NODE_TYPE_ELEMENT, "got %u\n", node->nodeType );
elem = (const WS_XML_ELEMENT_NODE *)node;
ok( !elem->prefix->length, "got %u\n", elem->prefix->length );
ok( elem->prefix->bytes == NULL, "bytes set\n" );
ok( elem->localName->length == 1, "got %u\n", elem->localName->length );
ok( !memcmp( elem->localName->bytes, "t", 1 ), "wrong name\n" );
ok( !elem->ns->length, "got %u\n", elem->ns->length );
ok( elem->ns->bytes != NULL, "bytes not set\n" );
ok( !elem->attributeCount, "got %u\n", elem->attributeCount );
ok( !elem->isEmpty, "empty\n" );
hr = WsReadNode( reader, NULL );
ok( hr == S_OK, "got %08x\n", hr );
hr = WsGetReaderNode( reader, &node, NULL );
ok( hr == S_OK, "got %08x\n", hr );
ok( node->nodeType == WS_XML_NODE_TYPE_END_ELEMENT, "got %u\n", node->nodeType );
WsFreeReader( reader );
}
START_TEST(reader)
{
test_WsCreateError();
@ -4537,4 +4588,5 @@ START_TEST(reader)
test_WsReadQualifiedName();
test_WsReadAttribute();
test_WsSkipNode();
test_binary_encoding();
}