001/**
002 *
003 * Copyright © 2014-2023 Florian Schmaus
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smack.util;
018
019import java.io.IOException;
020import java.net.URI;
021import java.net.URISyntaxException;
022import java.text.ParseException;
023import java.util.Date;
024import java.util.Locale;
025
026import javax.xml.namespace.QName;
027
028import org.jivesoftware.smack.datatypes.UInt16;
029import org.jivesoftware.smack.datatypes.UInt32;
030import org.jivesoftware.smack.packet.XmlEnvironment;
031import org.jivesoftware.smack.parsing.SmackParsingException;
032import org.jivesoftware.smack.parsing.SmackParsingException.RequiredAttributeMissingException;
033import org.jivesoftware.smack.parsing.SmackParsingException.SmackUriSyntaxParsingException;
034import org.jivesoftware.smack.xml.XmlPullParser;
035import org.jivesoftware.smack.xml.XmlPullParserException;
036
037import org.jxmpp.jid.EntityBareJid;
038import org.jxmpp.jid.EntityFullJid;
039import org.jxmpp.jid.EntityJid;
040import org.jxmpp.jid.Jid;
041import org.jxmpp.jid.impl.JidCreate;
042import org.jxmpp.jid.parts.Resourcepart;
043import org.jxmpp.stringprep.XmppStringprepException;
044import org.jxmpp.util.XmppDateTime;
045
046public class ParserUtils {
047
048    /**
049     * The constant String "jid".
050     */
051    public static final String JID = "jid";
052
053    public static void assertAtStartTag(XmlPullParser parser) throws XmlPullParserException {
054        assert parser.getEventType() == XmlPullParser.Event.START_ELEMENT;
055    }
056
057    public static void assertAtStartTag(XmlPullParser parser, String name) throws XmlPullParserException {
058        assertAtStartTag(parser);
059        assert name.equals(parser.getName());
060    }
061
062    public static void assertAtEndTag(XmlPullParser parser) throws XmlPullParserException {
063        assert parser.getEventType() == XmlPullParser.Event.END_ELEMENT;
064    }
065
066    public static void forwardToStartElement(XmlPullParser parser) throws XmlPullParserException, IOException {
067         // Wind the parser forward to the first start tag
068        XmlPullParser.Event event = parser.getEventType();
069        while (event != XmlPullParser.Event.START_ELEMENT) {
070            if (event == XmlPullParser.Event.END_DOCUMENT) {
071                throw new IllegalArgumentException("Document contains no start tag");
072            }
073            event = parser.next();
074        }
075    }
076
077    public static void forwardToEndTagOfDepth(XmlPullParser parser, int depth)
078                    throws XmlPullParserException, IOException {
079        XmlPullParser.Event event = parser.getEventType();
080        while (!(event == XmlPullParser.Event.END_ELEMENT && parser.getDepth() == depth)) {
081            assert event != XmlPullParser.Event.END_DOCUMENT;
082            event = parser.next();
083        }
084    }
085
086    public static Jid getJidAttribute(XmlPullParser parser) throws XmppStringprepException {
087        return getJidAttribute(parser, JID);
088    }
089
090    public static Jid getJidAttribute(XmlPullParser parser, String name) throws XmppStringprepException {
091        final String jidString = parser.getAttributeValue("", name);
092        if (jidString == null) {
093            return null;
094        }
095        return JidCreate.from(jidString);
096    }
097
098    public static EntityBareJid getBareJidAttribute(XmlPullParser parser) throws XmppStringprepException {
099        return getBareJidAttribute(parser, JID);
100    }
101
102    public static EntityBareJid getBareJidAttribute(XmlPullParser parser, String name) throws XmppStringprepException {
103        final String jidString = parser.getAttributeValue("", name);
104        if (jidString == null) {
105            return null;
106        }
107        return JidCreate.entityBareFrom(jidString);
108    }
109
110    public static EntityFullJid getFullJidAttribute(XmlPullParser parser) throws XmppStringprepException {
111        return getFullJidAttribute(parser, JID);
112    }
113
114    public static EntityFullJid getFullJidAttribute(XmlPullParser parser, String name) throws XmppStringprepException {
115        final String jidString = parser.getAttributeValue("", name);
116        if (jidString == null) {
117            return null;
118        }
119        return JidCreate.entityFullFrom(jidString);
120    }
121
122    public static EntityJid getEntityJidAttribute(XmlPullParser parser, String name) throws XmppStringprepException {
123        final String jidString = parser.getAttributeValue("", name);
124        if (jidString == null) {
125            return null;
126        }
127        Jid jid = JidCreate.from(jidString);
128
129        if (!jid.hasLocalpart()) return null;
130
131        EntityFullJid fullJid = jid.asEntityFullJidIfPossible();
132        if (fullJid != null) {
133            return fullJid;
134        }
135
136        EntityBareJid bareJid = jid.asEntityBareJidIfPossible();
137        return bareJid;
138    }
139
140    public static Resourcepart getResourcepartAttribute(XmlPullParser parser, String name) throws XmppStringprepException {
141        final String resourcepartString = parser.getAttributeValue("", name);
142        if (resourcepartString == null) {
143            return null;
144        }
145        return Resourcepart.from(resourcepartString);
146    }
147
148    /**
149     * Prase a string to a boolean value as per "xs:boolean". Valid input strings are "true", "1" for true, and "false", "0" for false.
150     *
151     * @param booleanString the input string.
152     * @return the boolean representation of the input string
153     * @throws IllegalArgumentException if the input string is not valid.
154     * @since 4.3.2
155     */
156    public static boolean parseXmlBoolean(String booleanString) {
157        switch (booleanString) {
158        case "true":
159        case "1":
160            return true;
161        case "false":
162        case "0":
163            return false;
164        default:
165            throw new IllegalArgumentException(booleanString + " is not a valid boolean string");
166        }
167    }
168
169    /**
170     * Get the boolean value of an argument.
171     *
172     * @param parser TODO javadoc me please
173     * @param name TODO javadoc me please
174     * @return the boolean value or null of no argument of the given name exists
175     */
176    public static Boolean getBooleanAttribute(XmlPullParser parser, String name) {
177        String valueString = parser.getAttributeValue("", name);
178        if (valueString == null)
179            return null;
180        valueString = valueString.toLowerCase(Locale.US);
181        return parseXmlBoolean(valueString);
182    }
183
184    public static boolean getBooleanAttribute(XmlPullParser parser, String name,
185                    boolean defaultValue) {
186        Boolean bool = getBooleanAttribute(parser, name);
187        if (bool == null) {
188            return defaultValue;
189        }
190        else {
191            return bool;
192        }
193    }
194
195    public static Byte getByteAttributeFromNextText(XmlPullParser parser) throws IOException, XmlPullParserException {
196        String nextText = parser.nextText();
197        return Byte.valueOf(nextText);
198    }
199
200    public static int getIntegerAttributeOrThrow(XmlPullParser parser, String name, String throwMessage)
201                    throws IOException {
202        Integer res = getIntegerAttribute(parser, name);
203        if (res == null) {
204            // TODO Should be SmackParseException.
205            throw new IOException(throwMessage);
206        }
207        return res;
208    }
209
210    public static Integer getIntegerAttribute(XmlPullParser parser, String name) {
211        String valueString = parser.getAttributeValue("", name);
212        if (valueString == null)
213            return null;
214        return Integer.valueOf(valueString);
215    }
216
217    public static int getIntegerAttribute(XmlPullParser parser, String name, int defaultValue) {
218        Integer integer = getIntegerAttribute(parser, name);
219        if (integer == null) {
220            return defaultValue;
221        }
222        else {
223            return integer;
224        }
225    }
226
227    public static UInt16 getUInt16Attribute(XmlPullParser parser, String name) {
228        Integer integer = getIntegerAttribute(parser, name);
229        if (integer == null) {
230            return null;
231        }
232        return UInt16.from(integer);
233    }
234
235    public static UInt16 getRequiredUInt16Attribute(XmlPullParser parser, String name) throws RequiredAttributeMissingException {
236        UInt16 uint16 = getUInt16Attribute(parser, name);
237        if (uint16 == null) {
238            throw new SmackParsingException.RequiredAttributeMissingException(name);
239        }
240        return uint16;
241    }
242
243    public static int getIntegerFromNextText(XmlPullParser parser) throws XmlPullParserException, IOException {
244        String intString = parser.nextText();
245        return Integer.valueOf(intString);
246    }
247
248    public static Long getLongAttribute(XmlPullParser parser, String name) {
249        String valueString = parser.getAttributeValue("", name);
250        if (valueString == null)
251            return null;
252        return Long.valueOf(valueString);
253    }
254
255    public static long getLongAttribute(XmlPullParser parser, String name, long defaultValue) {
256        Long l = getLongAttribute(parser, name);
257        if (l == null) {
258            return defaultValue;
259        }
260        else {
261            return l;
262        }
263    }
264
265    public static UInt32 getUInt32Attribute(XmlPullParser parser, String name) {
266        Long l = getLongAttribute(parser, name);
267        if (l == null) {
268            return null;
269        }
270        return UInt32.from(l);
271    }
272
273    public static double getDoubleFromNextText(XmlPullParser parser) throws XmlPullParserException, IOException {
274        String doubleString = parser.nextText();
275        return Double.valueOf(doubleString);
276    }
277
278    public static Double getDoubleAttribute(XmlPullParser parser, String name) {
279        String valueString = parser.getAttributeValue("", name);
280        if (valueString == null)
281            return null;
282        return Double.valueOf(valueString);
283    }
284
285    public static double getDoubleAttribute(XmlPullParser parser, String name, long defaultValue) {
286        Double d = getDoubleAttribute(parser, name);
287        if (d == null) {
288            return defaultValue;
289        }
290        else {
291            return d;
292        }
293    }
294
295    public static Short getShortAttribute(XmlPullParser parser, String name) {
296        String valueString = parser.getAttributeValue("", name);
297        if (valueString == null) {
298            return null;
299        }
300        return Short.valueOf(valueString);
301    }
302
303    public static short getShortAttribute(XmlPullParser parser, String name, short defaultValue) {
304        Short s = getShortAttribute(parser, name);
305        if (s == null) {
306            return defaultValue;
307        }
308        return s;
309    }
310
311    public static Date getDateFromOptionalXep82String(String dateString) throws ParseException {
312        if (dateString == null) {
313            return null;
314        }
315        return getDateFromXep82String(dateString);
316    }
317
318    public static Date getDateFromXep82String(String dateString) throws ParseException {
319        return XmppDateTime.parseXEP0082Date(dateString);
320    }
321
322    public static Date getDateFromString(String dateString) throws ParseException {
323        return XmppDateTime.parseDate(dateString);
324    }
325
326    public static Date getDateFromNextText(XmlPullParser parser)
327                    throws XmlPullParserException, IOException, ParseException {
328        String dateString = parser.nextText();
329        return getDateFromString(dateString);
330    }
331
332    public static URI getUriFromNextText(XmlPullParser parser) throws XmlPullParserException, IOException, SmackUriSyntaxParsingException  {
333        String uriString = parser.nextText();
334        try {
335            return new URI(uriString);
336        }
337        catch (URISyntaxException e) {
338            throw new SmackParsingException.SmackUriSyntaxParsingException(e);
339        }
340    }
341
342    public static String getRequiredAttribute(XmlPullParser parser, String name) throws IOException {
343        String value = parser.getAttributeValue("", name);
344        if (StringUtils.isNullOrEmpty(value)) {
345            throw new IOException("Attribute " + name + " is null or empty (" + value + ')');
346        }
347        return value;
348    }
349
350    public static String getRequiredNextText(XmlPullParser parser) throws XmlPullParserException, IOException {
351        String text = parser.nextText();
352        if (StringUtils.isNullOrEmpty(text)) {
353            throw new IOException("Next text is null or empty (" + text + ')');
354        }
355        return text;
356    }
357
358    public static String getXmlLang(XmlPullParser parser, XmlEnvironment xmlEnvironment) {
359        String currentXmlLang = getXmlLang(parser);
360        if (currentXmlLang != null) {
361            return currentXmlLang;
362        }
363        return xmlEnvironment.getEffectiveLanguage();
364    }
365
366    public static String getXmlLang(XmlPullParser parser) {
367        return parser.getAttributeValue("http://www.w3.org/XML/1998/namespace", "lang");
368    }
369
370    /**
371     * Get the QName of the current element.
372     *
373     * @param parser the parser.
374     * @return the Qname.
375     * @deprecated use {@link XmlPullParser#getQName()} instead.
376     */
377    @Deprecated
378    // TODO: Remove in Smack 4.5
379    public static QName getQName(XmlPullParser parser) {
380        return parser.getQName();
381    }
382
383    public static InternetAddress getInternetAddressIngoringZoneIdAttribute(XmlPullParser parser, String attribute) {
384        String inetAddressString = parser.getAttributeValue(attribute);
385        if (inetAddressString == null) {
386            return null;
387        }
388        return InternetAddress.fromIgnoringZoneId(inetAddressString);
389    }
390}