XmlUtils.java revision 2269d1572e5fcfb725ea55f5764d8c3280d69f6d
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.util;
18
19
20import org.xmlpull.v1.XmlPullParser;
21import org.xmlpull.v1.XmlPullParserException;
22import org.xmlpull.v1.XmlSerializer;
23
24import java.io.IOException;
25import java.io.InputStream;
26import java.io.OutputStream;
27import java.util.ArrayList;
28import java.util.HashMap;
29import java.util.Iterator;
30import java.util.List;
31import java.util.Map;
32import java.util.Set;
33
34import android.util.Xml;
35
36/** {@hide} */
37public class XmlUtils
38{
39
40    public static void skipCurrentTag(XmlPullParser parser)
41            throws XmlPullParserException, IOException {
42        int outerDepth = parser.getDepth();
43        int type;
44        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
45               && (type != XmlPullParser.END_TAG
46                       || parser.getDepth() > outerDepth)) {
47        }
48    }
49
50    public static final int
51    convertValueToList(CharSequence value, String[] options, int defaultValue)
52    {
53        if (null != value) {
54            for (int i = 0; i < options.length; i++) {
55                if (value.equals(options[i]))
56                    return i;
57            }
58        }
59
60        return defaultValue;
61    }
62
63    public static final boolean
64    convertValueToBoolean(CharSequence value, boolean defaultValue)
65    {
66        boolean result = false;
67
68        if (null == value)
69            return defaultValue;
70
71        if (value.equals("1")
72        ||  value.equals("true")
73        ||  value.equals("TRUE"))
74            result = true;
75
76        return result;
77    }
78
79    public static final int
80    convertValueToInt(CharSequence charSeq, int defaultValue)
81    {
82        if (null == charSeq)
83            return defaultValue;
84
85        String nm = charSeq.toString();
86
87        // XXX This code is copied from Integer.decode() so we don't
88        // have to instantiate an Integer!
89
90        int value;
91        int sign = 1;
92        int index = 0;
93        int len = nm.length();
94        int base = 10;
95
96        if ('-' == nm.charAt(0)) {
97            sign = -1;
98            index++;
99        }
100
101        if ('0' == nm.charAt(index)) {
102            //  Quick check for a zero by itself
103            if (index == (len - 1))
104                return 0;
105
106            char    c = nm.charAt(index + 1);
107
108            if ('x' == c || 'X' == c) {
109                index += 2;
110                base = 16;
111            } else {
112                index++;
113                base = 8;
114            }
115        }
116        else if ('#' == nm.charAt(index))
117        {
118            index++;
119            base = 16;
120        }
121
122        return Integer.parseInt(nm.substring(index), base) * sign;
123    }
124
125    public static final int
126    convertValueToUnsignedInt(String value, int defaultValue)
127    {
128        if (null == value)
129            return defaultValue;
130
131        return parseUnsignedIntAttribute(value);
132    }
133
134    public static final int
135    parseUnsignedIntAttribute(CharSequence charSeq)
136    {
137        String  value = charSeq.toString();
138
139        long    bits;
140        int     index = 0;
141        int     len = value.length();
142        int     base = 10;
143
144        if ('0' == value.charAt(index)) {
145            //  Quick check for zero by itself
146            if (index == (len - 1))
147                return 0;
148
149            char    c = value.charAt(index + 1);
150
151            if ('x' == c || 'X' == c) {     //  check for hex
152                index += 2;
153                base = 16;
154            } else {                        //  check for octal
155                index++;
156                base = 8;
157            }
158        } else if ('#' == value.charAt(index)) {
159            index++;
160            base = 16;
161        }
162
163        return (int) Long.parseLong(value.substring(index), base);
164    }
165
166    /**
167     * Flatten a Map into an output stream as XML.  The map can later be
168     * read back with readMapXml().
169     *
170     * @param val The map to be flattened.
171     * @param out Where to write the XML data.
172     *
173     * @see #writeMapXml(Map, String, XmlSerializer)
174     * @see #writeListXml
175     * @see #writeValueXml
176     * @see #readMapXml
177     */
178    public static final void writeMapXml(Map val, OutputStream out)
179            throws XmlPullParserException, java.io.IOException {
180        XmlSerializer serializer = new FastXmlSerializer();
181        serializer.setOutput(out, "utf-8");
182        serializer.startDocument(null, true);
183        serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
184        writeMapXml(val, null, serializer);
185        serializer.endDocument();
186    }
187
188    /**
189     * Flatten a List into an output stream as XML.  The list can later be
190     * read back with readListXml().
191     *
192     * @param val The list to be flattened.
193     * @param out Where to write the XML data.
194     *
195     * @see #writeListXml(List, String, XmlSerializer)
196     * @see #writeMapXml
197     * @see #writeValueXml
198     * @see #readListXml
199     */
200    public static final void writeListXml(List val, OutputStream out)
201    throws XmlPullParserException, java.io.IOException
202    {
203        XmlSerializer serializer = Xml.newSerializer();
204        serializer.setOutput(out, "utf-8");
205        serializer.startDocument(null, true);
206        serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
207        writeListXml(val, null, serializer);
208        serializer.endDocument();
209    }
210
211    /**
212     * Flatten a Map into an XmlSerializer.  The map can later be read back
213     * with readThisMapXml().
214     *
215     * @param val The map to be flattened.
216     * @param name Name attribute to include with this list's tag, or null for
217     *             none.
218     * @param out XmlSerializer to write the map into.
219     *
220     * @see #writeMapXml(Map, OutputStream)
221     * @see #writeListXml
222     * @see #writeValueXml
223     * @see #readMapXml
224     */
225    public static final void writeMapXml(Map val, String name, XmlSerializer out)
226    throws XmlPullParserException, java.io.IOException
227    {
228        if (val == null) {
229            out.startTag(null, "null");
230            out.endTag(null, "null");
231            return;
232        }
233
234        Set s = val.entrySet();
235        Iterator i = s.iterator();
236
237        out.startTag(null, "map");
238        if (name != null) {
239            out.attribute(null, "name", name);
240        }
241
242        while (i.hasNext()) {
243            Map.Entry e = (Map.Entry)i.next();
244            writeValueXml(e.getValue(), (String)e.getKey(), out);
245        }
246
247        out.endTag(null, "map");
248    }
249
250    /**
251     * Flatten a List into an XmlSerializer.  The list can later be read back
252     * with readThisListXml().
253     *
254     * @param val The list to be flattened.
255     * @param name Name attribute to include with this list's tag, or null for
256     *             none.
257     * @param out XmlSerializer to write the list into.
258     *
259     * @see #writeListXml(List, OutputStream)
260     * @see #writeMapXml
261     * @see #writeValueXml
262     * @see #readListXml
263     */
264    public static final void writeListXml(List val, String name, XmlSerializer out)
265    throws XmlPullParserException, java.io.IOException
266    {
267        if (val == null) {
268            out.startTag(null, "null");
269            out.endTag(null, "null");
270            return;
271        }
272
273        out.startTag(null, "list");
274        if (name != null) {
275            out.attribute(null, "name", name);
276        }
277
278        int N = val.size();
279        int i=0;
280        while (i < N) {
281            writeValueXml(val.get(i), null, out);
282            i++;
283        }
284
285        out.endTag(null, "list");
286    }
287
288    /**
289     * Flatten a byte[] into an XmlSerializer.  The list can later be read back
290     * with readThisByteArrayXml().
291     *
292     * @param val The byte array to be flattened.
293     * @param name Name attribute to include with this array's tag, or null for
294     *             none.
295     * @param out XmlSerializer to write the array into.
296     *
297     * @see #writeMapXml
298     * @see #writeValueXml
299     */
300    public static final void writeByteArrayXml(byte[] val, String name,
301            XmlSerializer out)
302            throws XmlPullParserException, java.io.IOException {
303
304        if (val == null) {
305            out.startTag(null, "null");
306            out.endTag(null, "null");
307            return;
308        }
309
310        out.startTag(null, "byte-array");
311        if (name != null) {
312            out.attribute(null, "name", name);
313        }
314
315        final int N = val.length;
316        out.attribute(null, "num", Integer.toString(N));
317
318        StringBuilder sb = new StringBuilder(val.length*2);
319        for (int i=0; i<N; i++) {
320            int b = val[i];
321            int h = b>>4;
322            sb.append(h >= 10 ? ('a'+h-10) : ('0'+h));
323            h = b&0xff;
324            sb.append(h >= 10 ? ('a'+h-10) : ('0'+h));
325        }
326
327        out.text(sb.toString());
328
329        out.endTag(null, "byte-array");
330    }
331
332    /**
333     * Flatten an int[] into an XmlSerializer.  The list can later be read back
334     * with readThisIntArrayXml().
335     *
336     * @param val The int array to be flattened.
337     * @param name Name attribute to include with this array's tag, or null for
338     *             none.
339     * @param out XmlSerializer to write the array into.
340     *
341     * @see #writeMapXml
342     * @see #writeValueXml
343     * @see #readThisIntArrayXml
344     */
345    public static final void writeIntArrayXml(int[] val, String name,
346            XmlSerializer out)
347            throws XmlPullParserException, java.io.IOException {
348
349        if (val == null) {
350            out.startTag(null, "null");
351            out.endTag(null, "null");
352            return;
353        }
354
355        out.startTag(null, "int-array");
356        if (name != null) {
357            out.attribute(null, "name", name);
358        }
359
360        final int N = val.length;
361        out.attribute(null, "num", Integer.toString(N));
362
363        for (int i=0; i<N; i++) {
364            out.startTag(null, "item");
365            out.attribute(null, "value", Integer.toString(val[i]));
366            out.endTag(null, "item");
367        }
368
369        out.endTag(null, "int-array");
370    }
371
372    /**
373     * Flatten an object's value into an XmlSerializer.  The value can later
374     * be read back with readThisValueXml().
375     *
376     * Currently supported value types are: null, String, Integer, Long,
377     * Float, Double Boolean, Map, List.
378     *
379     * @param v The object to be flattened.
380     * @param name Name attribute to include with this value's tag, or null
381     *             for none.
382     * @param out XmlSerializer to write the object into.
383     *
384     * @see #writeMapXml
385     * @see #writeListXml
386     * @see #readValueXml
387     */
388    public static final void writeValueXml(Object v, String name, XmlSerializer out)
389    throws XmlPullParserException, java.io.IOException
390    {
391        String typeStr;
392        if (v == null) {
393            out.startTag(null, "null");
394            if (name != null) {
395                out.attribute(null, "name", name);
396            }
397            out.endTag(null, "null");
398            return;
399        } else if (v instanceof String) {
400            out.startTag(null, "string");
401            if (name != null) {
402                out.attribute(null, "name", name);
403            }
404            out.text(v.toString());
405            out.endTag(null, "string");
406            return;
407        } else if (v instanceof Integer) {
408            typeStr = "int";
409        } else if (v instanceof Long) {
410            typeStr = "long";
411        } else if (v instanceof Float) {
412            typeStr = "float";
413        } else if (v instanceof Double) {
414            typeStr = "double";
415        } else if (v instanceof Boolean) {
416            typeStr = "boolean";
417        } else if (v instanceof byte[]) {
418            writeByteArrayXml((byte[])v, name, out);
419            return;
420        } else if (v instanceof int[]) {
421            writeIntArrayXml((int[])v, name, out);
422            return;
423        } else if (v instanceof Map) {
424            writeMapXml((Map)v, name, out);
425            return;
426        } else if (v instanceof List) {
427            writeListXml((List)v, name, out);
428            return;
429        } else if (v instanceof CharSequence) {
430            // XXX This is to allow us to at least write something if
431            // we encounter styled text...  but it means we will drop all
432            // of the styling information. :(
433            out.startTag(null, "string");
434            if (name != null) {
435                out.attribute(null, "name", name);
436            }
437            out.text(v.toString());
438            out.endTag(null, "string");
439            return;
440        } else {
441            throw new RuntimeException("writeValueXml: unable to write value " + v);
442        }
443
444        out.startTag(null, typeStr);
445        if (name != null) {
446            out.attribute(null, "name", name);
447        }
448        out.attribute(null, "value", v.toString());
449        out.endTag(null, typeStr);
450    }
451
452    /**
453     * Read a HashMap from an InputStream containing XML.  The stream can
454     * previously have been written by writeMapXml().
455     *
456     * @param in The InputStream from which to read.
457     *
458     * @return HashMap The resulting map.
459     *
460     * @see #readListXml
461     * @see #readValueXml
462     * @see #readThisMapXml
463     * #see #writeMapXml
464     */
465    public static final HashMap readMapXml(InputStream in)
466    throws XmlPullParserException, java.io.IOException
467    {
468        XmlPullParser   parser = Xml.newPullParser();
469        parser.setInput(in, null);
470        return (HashMap)readValueXml(parser, new String[1]);
471    }
472
473    /**
474     * Read an ArrayList from an InputStream containing XML.  The stream can
475     * previously have been written by writeListXml().
476     *
477     * @param in The InputStream from which to read.
478     *
479     * @return HashMap The resulting list.
480     *
481     * @see #readMapXml
482     * @see #readValueXml
483     * @see #readThisListXml
484     * @see #writeListXml
485     */
486    public static final ArrayList readListXml(InputStream in)
487    throws XmlPullParserException, java.io.IOException
488    {
489        XmlPullParser   parser = Xml.newPullParser();
490        parser.setInput(in, null);
491        return (ArrayList)readValueXml(parser, new String[1]);
492    }
493
494    /**
495     * Read a HashMap object from an XmlPullParser.  The XML data could
496     * previously have been generated by writeMapXml().  The XmlPullParser
497     * must be positioned <em>after</em> the tag that begins the map.
498     *
499     * @param parser The XmlPullParser from which to read the map data.
500     * @param endTag Name of the tag that will end the map, usually "map".
501     * @param name An array of one string, used to return the name attribute
502     *             of the map's tag.
503     *
504     * @return HashMap The newly generated map.
505     *
506     * @see #readMapXml
507     */
508    public static final HashMap readThisMapXml(XmlPullParser parser, String endTag, String[] name)
509    throws XmlPullParserException, java.io.IOException
510    {
511        HashMap map = new HashMap();
512
513        int eventType = parser.getEventType();
514        do {
515            if (eventType == parser.START_TAG) {
516                Object val = readThisValueXml(parser, name);
517                if (name[0] != null) {
518                    //System.out.println("Adding to map: " + name + " -> " + val);
519                    map.put(name[0], val);
520                } else {
521                    throw new XmlPullParserException(
522                        "Map value without name attribute: " + parser.getName());
523                }
524            } else if (eventType == parser.END_TAG) {
525                if (parser.getName().equals(endTag)) {
526                    return map;
527                }
528                throw new XmlPullParserException(
529                    "Expected " + endTag + " end tag at: " + parser.getName());
530            }
531            eventType = parser.next();
532        } while (eventType != parser.END_DOCUMENT);
533
534        throw new XmlPullParserException(
535            "Document ended before " + endTag + " end tag");
536    }
537
538    /**
539     * Read an ArrayList object from an XmlPullParser.  The XML data could
540     * previously have been generated by writeListXml().  The XmlPullParser
541     * must be positioned <em>after</em> the tag that begins the list.
542     *
543     * @param parser The XmlPullParser from which to read the list data.
544     * @param endTag Name of the tag that will end the list, usually "list".
545     * @param name An array of one string, used to return the name attribute
546     *             of the list's tag.
547     *
548     * @return HashMap The newly generated list.
549     *
550     * @see #readListXml
551     */
552    public static final ArrayList readThisListXml(XmlPullParser parser, String endTag, String[] name)
553    throws XmlPullParserException, java.io.IOException
554    {
555        ArrayList list = new ArrayList();
556
557        int eventType = parser.getEventType();
558        do {
559            if (eventType == parser.START_TAG) {
560                Object val = readThisValueXml(parser, name);
561                list.add(val);
562                //System.out.println("Adding to list: " + val);
563            } else if (eventType == parser.END_TAG) {
564                if (parser.getName().equals(endTag)) {
565                    return list;
566                }
567                throw new XmlPullParserException(
568                    "Expected " + endTag + " end tag at: " + parser.getName());
569            }
570            eventType = parser.next();
571        } while (eventType != parser.END_DOCUMENT);
572
573        throw new XmlPullParserException(
574            "Document ended before " + endTag + " end tag");
575    }
576
577    /**
578     * Read an int[] object from an XmlPullParser.  The XML data could
579     * previously have been generated by writeIntArrayXml().  The XmlPullParser
580     * must be positioned <em>after</em> the tag that begins the list.
581     *
582     * @param parser The XmlPullParser from which to read the list data.
583     * @param endTag Name of the tag that will end the list, usually "list".
584     * @param name An array of one string, used to return the name attribute
585     *             of the list's tag.
586     *
587     * @return Returns a newly generated int[].
588     *
589     * @see #readListXml
590     */
591    public static final int[] readThisIntArrayXml(XmlPullParser parser,
592            String endTag, String[] name)
593            throws XmlPullParserException, java.io.IOException {
594
595        int num;
596        try {
597            num = Integer.parseInt(parser.getAttributeValue(null, "num"));
598        } catch (NullPointerException e) {
599            throw new XmlPullParserException(
600                    "Need num attribute in byte-array");
601        } catch (NumberFormatException e) {
602            throw new XmlPullParserException(
603                    "Not a number in num attribute in byte-array");
604        }
605
606        int[] array = new int[num];
607        int i = 0;
608
609        int eventType = parser.getEventType();
610        do {
611            if (eventType == parser.START_TAG) {
612                if (parser.getName().equals("item")) {
613                    try {
614                        array[i] = Integer.parseInt(
615                                parser.getAttributeValue(null, "value"));
616                    } catch (NullPointerException e) {
617                        throw new XmlPullParserException(
618                                "Need value attribute in item");
619                    } catch (NumberFormatException e) {
620                        throw new XmlPullParserException(
621                                "Not a number in value attribute in item");
622                    }
623                } else {
624                    throw new XmlPullParserException(
625                            "Expected item tag at: " + parser.getName());
626                }
627            } else if (eventType == parser.END_TAG) {
628                if (parser.getName().equals(endTag)) {
629                    return array;
630                } else if (parser.getName().equals("item")) {
631                    i++;
632                } else {
633                    throw new XmlPullParserException(
634                        "Expected " + endTag + " end tag at: "
635                        + parser.getName());
636                }
637            }
638            eventType = parser.next();
639        } while (eventType != parser.END_DOCUMENT);
640
641        throw new XmlPullParserException(
642            "Document ended before " + endTag + " end tag");
643    }
644
645    /**
646     * Read a flattened object from an XmlPullParser.  The XML data could
647     * previously have been written with writeMapXml(), writeListXml(), or
648     * writeValueXml().  The XmlPullParser must be positioned <em>at</em> the
649     * tag that defines the value.
650     *
651     * @param parser The XmlPullParser from which to read the object.
652     * @param name An array of one string, used to return the name attribute
653     *             of the value's tag.
654     *
655     * @return Object The newly generated value object.
656     *
657     * @see #readMapXml
658     * @see #readListXml
659     * @see #writeValueXml
660     */
661    public static final Object readValueXml(XmlPullParser parser, String[] name)
662    throws XmlPullParserException, java.io.IOException
663    {
664        int eventType = parser.getEventType();
665        do {
666            if (eventType == parser.START_TAG) {
667                return readThisValueXml(parser, name);
668            } else if (eventType == parser.END_TAG) {
669                throw new XmlPullParserException(
670                    "Unexpected end tag at: " + parser.getName());
671            } else if (eventType == parser.TEXT) {
672                throw new XmlPullParserException(
673                    "Unexpected text: " + parser.getText());
674            }
675            eventType = parser.next();
676        } while (eventType != parser.END_DOCUMENT);
677
678        throw new XmlPullParserException(
679            "Unexpected end of document");
680    }
681
682    private static final Object readThisValueXml(XmlPullParser parser, String[] name)
683    throws XmlPullParserException, java.io.IOException
684    {
685        final String valueName = parser.getAttributeValue(null, "name");
686        final String tagName = parser.getName();
687
688        //System.out.println("Reading this value tag: " + tagName + ", name=" + valueName);
689
690        Object res;
691
692        if (tagName.equals("null")) {
693            res = null;
694        } else if (tagName.equals("string")) {
695            String value = "";
696            int eventType;
697            while ((eventType = parser.next()) != parser.END_DOCUMENT) {
698                if (eventType == parser.END_TAG) {
699                    if (parser.getName().equals("string")) {
700                        name[0] = valueName;
701                        //System.out.println("Returning value for " + valueName + ": " + value);
702                        return value;
703                    }
704                    throw new XmlPullParserException(
705                        "Unexpected end tag in <string>: " + parser.getName());
706                } else if (eventType == parser.TEXT) {
707                    value += parser.getText();
708                } else if (eventType == parser.START_TAG) {
709                    throw new XmlPullParserException(
710                        "Unexpected start tag in <string>: " + parser.getName());
711                }
712            }
713            throw new XmlPullParserException(
714                "Unexpected end of document in <string>");
715        } else if (tagName.equals("int")) {
716            res = Integer.parseInt(parser.getAttributeValue(null, "value"));
717        } else if (tagName.equals("long")) {
718            res = Long.valueOf(parser.getAttributeValue(null, "value"));
719        } else if (tagName.equals("float")) {
720            res = new Float(parser.getAttributeValue(null, "value"));
721        } else if (tagName.equals("double")) {
722            res = new Double(parser.getAttributeValue(null, "value"));
723        } else if (tagName.equals("boolean")) {
724            res = Boolean.valueOf(parser.getAttributeValue(null, "value"));
725        } else if (tagName.equals("int-array")) {
726            parser.next();
727            res = readThisIntArrayXml(parser, "int-array", name);
728            name[0] = valueName;
729            //System.out.println("Returning value for " + valueName + ": " + res);
730            return res;
731        } else if (tagName.equals("map")) {
732            parser.next();
733            res = readThisMapXml(parser, "map", name);
734            name[0] = valueName;
735            //System.out.println("Returning value for " + valueName + ": " + res);
736            return res;
737        } else if (tagName.equals("list")) {
738            parser.next();
739            res = readThisListXml(parser, "list", name);
740            name[0] = valueName;
741            //System.out.println("Returning value for " + valueName + ": " + res);
742            return res;
743        } else {
744            throw new XmlPullParserException(
745                "Unknown tag: " + tagName);
746        }
747
748        // Skip through to end tag.
749        int eventType;
750        while ((eventType = parser.next()) != parser.END_DOCUMENT) {
751            if (eventType == parser.END_TAG) {
752                if (parser.getName().equals(tagName)) {
753                    name[0] = valueName;
754                    //System.out.println("Returning value for " + valueName + ": " + res);
755                    return res;
756                }
757                throw new XmlPullParserException(
758                    "Unexpected end tag in <" + tagName + ">: " + parser.getName());
759            } else if (eventType == parser.TEXT) {
760                throw new XmlPullParserException(
761                "Unexpected text in <" + tagName + ">: " + parser.getName());
762            } else if (eventType == parser.START_TAG) {
763                throw new XmlPullParserException(
764                    "Unexpected start tag in <" + tagName + ">: " + parser.getName());
765            }
766        }
767        throw new XmlPullParserException(
768            "Unexpected end of document in <" + tagName + ">");
769    }
770
771    public static final void beginDocument(XmlPullParser parser, String firstElementName) throws XmlPullParserException, IOException
772    {
773        int type;
774        while ((type=parser.next()) != parser.START_TAG
775                   && type != parser.END_DOCUMENT) {
776            ;
777        }
778
779        if (type != parser.START_TAG) {
780            throw new XmlPullParserException("No start tag found");
781        }
782
783        if (!parser.getName().equals(firstElementName)) {
784            throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
785                    ", expected " + firstElementName);
786        }
787    }
788
789    public static final void nextElement(XmlPullParser parser) throws XmlPullParserException, IOException
790    {
791        int type;
792        while ((type=parser.next()) != parser.START_TAG
793                   && type != parser.END_DOCUMENT) {
794            ;
795        }
796    }
797}
798