1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef INCLUDED_OOX_MATHML_IMPORTUTILS_HXX
#define INCLUDED_OOX_MATHML_IMPORTUTILS_HXX
#include <map>
#include <string_view>
#include <vector>
#include <com/sun/star/uno/Reference.hxx>
#include <oox/dllapi.h>
#include <oox/token/tokens.hxx>
#include <rtl/ustring.hxx>
#include <sal/types.h>
namespace com::sun::star
{
namespace xml::sax
{
class XFastAttributeList;
}
}
namespace oox::formulaimport
{
// used to differentiate between tags that opening or closing
const int TAG_OPENING = 1 << 29;
const int TAG_CLOSING = 1 << 30;
// you probably want to #define these to something shorter in the .cxx file,
// but they must be done as macros, otherwise they wouldn't be usable for case values,
// and macros cannot be namespaced
#define XML_STREAM_OPENING(token) (TAG_OPENING | token)
#define XML_STREAM_CLOSING(token) (TAG_CLOSING | token)
/**
Class for storing a stream of xml tokens.
A part of an XML file can be parsed and stored in this stream, from which it can be read
as if parsed linearly. The purpose of this class is to allow simpler handling of XML
files, unlike the usual LO way of using callbacks, context handlers and similar needlessly
complicated stuff (YMMV).
The advantages of this approach is easy to read and debug code (as it is just functions
reading tokens one by one and calling other functions, compared to having to use callbacks
and temporary storage). The disadvantage is that the XML structure needs to be handled
manually by the code.
Note that tag identifiers are simply int values and the API does not care besides matching
their values to XML stream contents and requiring that the values are not as high as TAG_OPENING.
Be prepared for the fact that some of the functions may throw exceptions if the input
stream does not match the required token (TBD).
The API tries to make the common idioms as simple as possible, see the following examples.
Parse <tagone attr="value"><tagtwo>text</tagtwo></tagone> , where tagtwo is optional:
@code
XmlStream::Tag tagoneTag = stream.ensureOpeningTag( tagone );
if( attributeTag.hasAttribute( attr ))
... = attributeTag.attribute( attr, defaultValueOfTheRightType );
if( XmlStream::Tag tagtwoTag = stream.checkOpeningTag( tagtwo ))
{
... = tagtwoTag.text;
stream.ensureClosingTag( tagtwo );
}
stream.ensureClosingTag( tagone );
@endcode
Parse an element that may contain several sub-elements of different types in random order:
@code
stream.ensureOpeningTag( element );
while( !stream.atEnd() && stream.currentToken() != CLOSING( element ))
{
switch( stream.currentToken())
{
case OPENING( subelement1 ):
handleSubElement1();
break;
case OPENING( subelement2 ):
... process subelement2;
break;
default:
stream.handleUnexpectedTag();
break;
}
stream.ensureClosingTag( element );
@endcode
If there may not be a zero number of sub-elements, use a helper bool variable or use a do-while loop.
Parse an element that may contain an unknown number of sub-elements of the same type:
@code
stream.ensureOpeningTag( element );
while( !stream.atEnd() && stream.findTag( OPENING( subelement )))
{
handleSubelement();
}
stream.ensureClosingTag( element );
@endcode
If there may not be a zero number of sub-elements, use a helper bool variable or use a do-while loop.
@since 3.5
*/
class OOX_DLLPUBLIC XmlStream
{
public:
XmlStream();
/**
Structure representing a list of attributes.
*/
// One could theoretically use oox::AttributeList, but that complains if the passed reference is empty,
// which would be complicated to avoid here. Also, parsers apparently reuse the same instance of XFastAttributeList,
// which means using oox::AttributeList would make them all point to the one instance.
struct OOX_DLLPUBLIC AttributeList
{
OUString& operator[](int token);
OUString attribute(int token, const OUString& def) const;
bool attribute(int token, bool def) const;
sal_Unicode attribute(int token, sal_Unicode def) const;
// when adding more attribute() overloads, add also to XmlStream itself
protected:
std::map<int, OUString> attrs;
};
/**
Structure representing a tag, including its attributes and content text immediately following it.
*/
struct OOX_DLLPUBLIC Tag
{
Tag(int token = XML_TOKEN_INVALID,
const css::uno::Reference<css::xml::sax::XFastAttributeList>& attributes
= css::uno::Reference<css::xml::sax::XFastAttributeList>());
Tag(int token, AttributeList attribs);
int token; ///< tag type, or XML_TOKEN_INVALID
AttributeList attributes;
OUString text;
/**
This function returns value of the given attribute, or the passed default value if not found.
The type of the default value selects the return type (OUString here).
*/
OUString attribute(int token, const OUString& def = OUString()) const;
/**
@overload
*/
bool attribute(int token, bool def) const;
/**
@overload
*/
sal_Unicode attribute(int token, sal_Unicode def) const;
// when adding more attribute() overloads, add also to XmlStream::AttributeList and inline below
/**
Converts to true if the tag has a valid token, false otherwise. Allows simple
usage in if(), for example 'if( XmlStream::Tag foo = stream.checkOpeningTag( footoken ))'.
*/
operator bool() const;
};
/**
@return true if current position is at the end of the XML stream
*/
bool atEnd() const;
/**
@return data about the current tag
*/
Tag currentTag() const;
/**
@return the token for the current tag
*/
int currentToken() const;
/**
Moves position to the next tag.
*/
void moveToNextTag();
/**
Ensures that an opening tag with the given token is read. If the current tag does not match,
writes out a warning and tries to recover by skipping tags until found (or until the current element would end).
If found, the position in the stream is afterwards moved to the next tag.
@return the matching found opening tag, or empty tag if not found
*/
Tag ensureOpeningTag(int token);
/**
Tries to find an opening tag with the given token. Works similarly like ensureOpeningTag(),
but if a matching tag is not found, the position in the stream is not altered. The primary
use of this function is to check for optional elements.
@return the matching found opening tag, or empty tag if not found
*/
Tag checkOpeningTag(int token);
/**
Ensures that a closing tag with the given token is read. Like ensureOpeningTag(),
if not, writes out a warning and tries to recover by skipping tags until found (or until the current element would end).
If found, the position in the stream is afterwards moved to the next tag.
*/
void ensureClosingTag(int token);
/**
Tries to find the given token, until either found (returns true) or end of current element.
Position in the stream is set to make the tag current (i.e. it will be the next one read).
*/
bool findTag(int token);
/**
Handle the current (unexpected) tag.
*/
void handleUnexpectedTag();
protected:
Tag checkTag(int token, bool optional);
bool findTagInternal(int token, bool silent);
void skipElementInternal(int token, bool silent);
std::vector<Tag> tags;
unsigned int pos;
};
/**
This class is used for creating XmlStream.
Simply use this class and then pass it as XmlStream to the consumer.
@since 3.5.0
*/
class OOX_DLLPUBLIC XmlStreamBuilder : public XmlStream
{
public:
void appendOpeningTag(int token,
const css::uno::Reference<css::xml::sax::XFastAttributeList>& attributes
= css::uno::Reference<css::xml::sax::XFastAttributeList>());
void appendOpeningTag(int token, const AttributeList& attribs);
void appendClosingTag(int token);
// appends the characters after the last appended token
void appendCharacters(std::u16string_view characters);
};
inline OUString XmlStream::Tag::attribute(int t, const OUString& def) const
{
return attributes.attribute(t, def);
}
inline bool XmlStream::Tag::attribute(int t, bool def) const
{
return attributes.attribute(t, def);
}
inline sal_Unicode XmlStream::Tag::attribute(int t, sal_Unicode def) const
{
return attributes.attribute(t, def);
}
} // namespace
#endif
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|