1// © 2016 and later: Unicode, Inc. and others.
2// License & terms of use: http://www.unicode.org/copyright.html#License
3/*
4 ******************************************************************************
5 * Copyright (C) 2007-2010, International Business Machines Corporation and   *
6 * others. All Rights Reserved.                                               *
7 ******************************************************************************
8 */
9
10package com.ibm.icu.impl.duration.impl;
11
12import java.io.IOException;
13import java.io.Writer;
14import java.util.ArrayList;
15import java.util.List;
16
17import com.ibm.icu.lang.UCharacter;
18
19public class XMLRecordWriter implements RecordWriter {
20    private Writer w;
21    private List<String> nameStack;
22
23    public XMLRecordWriter(Writer w) {
24        this.w = w;
25        this.nameStack = new ArrayList<String>();
26    }
27
28    @Override
29    public boolean open(String title) {
30        newline();
31        writeString("<" + title + ">");
32        nameStack.add(title);
33        return true;
34    }
35
36    @Override
37    public boolean close() {
38        int ix = nameStack.size() - 1;
39        if (ix >= 0) {
40            String name = nameStack.remove(ix);
41            newline();
42            writeString("</" + name + ">");
43            return true;
44        }
45        return false;
46    }
47
48    public void flush() {
49        try {
50            w.flush();
51        } catch (IOException e) {
52        }
53    }
54
55    @Override
56    public void bool(String name, boolean value) {
57        internalString(name, String.valueOf(value));
58    }
59
60    @Override
61    public void boolArray(String name, boolean[] values) {
62        if (values != null) {
63            String[] stringValues = new String[values.length];
64            for (int i = 0; i < values.length; ++i) {
65                stringValues[i] = String.valueOf(values[i]);
66            }
67            stringArray(name, stringValues);
68        }
69    }
70
71    private static String ctos(char value) {
72        if (value == '<') {
73            return "&lt;";
74        }
75        if (value == '&') {
76            return "&amp;";
77        }
78        return String.valueOf(value);
79    }
80
81    @Override
82    public void character(String name, char value) {
83        if (value != '\uffff') {
84            internalString(name, ctos(value));
85        }
86    }
87
88    @Override
89    public void characterArray(String name, char[] values) {
90        if (values != null) {
91            String[] stringValues = new String[values.length];
92            for (int i = 0; i < values.length; ++i) {
93                char value = values[i];
94                if (value == '\uffff') {
95                    stringValues[i] = NULL_NAME;
96                } else {
97                    stringValues[i] = ctos(value);
98                }
99            }
100            internalStringArray(name, stringValues);
101        }
102    }
103
104    @Override
105    public void namedIndex(String name, String[] names, int value) {
106        if (value >= 0) {
107            internalString(name, names[value]);
108        }
109    }
110
111    @Override
112    public void namedIndexArray(String name, String[] names, byte[] values) {
113        if (values != null) {
114            String[] stringValues = new String[values.length];
115            for (int i = 0; i < values.length; ++i) {
116                int value = values[i];
117                if (value < 0) {
118                    stringValues[i] = NULL_NAME;
119                } else {
120                    stringValues[i] = names[value];
121                }
122            }
123            internalStringArray(name, stringValues);
124        }
125    }
126
127    public static String normalize(String str) {
128        if (str == null) {
129            return null;
130        }
131        StringBuilder sb = null;
132        boolean inWhitespace = false;
133        char c = '\0';
134        boolean special = false;
135        for (int i = 0; i < str.length(); ++i) {
136            c = str.charAt(i);
137            if (UCharacter.isWhitespace(c)) {
138                if (sb == null && (inWhitespace || c != ' ')) {
139                    sb = new StringBuilder(str.substring(0, i));
140                }
141                if (inWhitespace) {
142                    continue;
143                }
144                inWhitespace = true;
145                special = false;
146                c = ' ';
147            } else {
148                inWhitespace = false;
149                special = c == '<' || c == '&';
150                if (special && sb == null) {
151                    sb = new StringBuilder(str.substring(0, i));
152                }
153            }
154            if (sb != null) {
155                if (special) {
156                    sb.append(c == '<' ? "&lt;" : "&amp;");
157                } else {
158                    sb.append(c);
159                }
160            }
161        }
162        if (sb != null) {
163            /*
164             * if (c == ' ') { int len = sb.length(); if (len == 0) { return
165             * " "; } if (len > 1 && c == ' ') { sb.deleteCharAt(len - 1); } }
166             */
167            return sb.toString();
168        }
169        return str;
170    }
171
172    private void internalString(String name, String normalizedValue) {
173        if (normalizedValue != null) {
174            newline();
175            writeString("<" + name + ">" + normalizedValue + "</" + name + ">");
176        }
177    }
178
179    private void internalStringArray(String name, String[] normalizedValues) {
180        if (normalizedValues != null) {
181            push(name + "List");
182            for (int i = 0; i < normalizedValues.length; ++i) {
183                String value = normalizedValues[i];
184                if (value == null) {
185                    value = NULL_NAME;
186                }
187                string(name, value);
188            }
189            pop();
190        }
191    }
192
193    @Override
194    public void string(String name, String value) {
195        internalString(name, normalize(value));
196    }
197
198    @Override
199    public void stringArray(String name, String[] values) {
200        if (values != null) {
201            push(name + "List");
202            for (int i = 0; i < values.length; ++i) {
203                String value = normalize(values[i]);
204                if (value == null) {
205                    value = NULL_NAME;
206                }
207                internalString(name, value);
208            }
209            pop();
210        }
211    }
212
213    @Override
214    public void stringTable(String name, String[][] values) {
215        if (values != null) {
216            push(name + "Table");
217            for (int i = 0; i < values.length; ++i) {
218                String[] rowValues = values[i];
219                if (rowValues == null) {
220                    internalString(name + "List", NULL_NAME);
221                } else {
222                    stringArray(name, rowValues);
223                }
224            }
225            pop();
226        }
227    }
228
229    private void push(String name) {
230        newline();
231        writeString("<" + name + ">");
232        nameStack.add(name);
233    }
234
235    private void pop() {
236        int ix = nameStack.size() - 1;
237        String name = nameStack.remove(ix);
238        newline();
239        writeString("</" + name + ">");
240    }
241
242    private void newline() {
243        writeString("\n");
244        for (int i = 0; i < nameStack.size(); ++i) {
245            writeString(INDENT);
246        }
247    }
248
249    private void writeString(String str) {
250        if (w != null) {
251            try {
252                w.write(str);
253            } catch (IOException e) {
254                // if there's a problem, record it and stop writing
255                System.err.println(e.getMessage());
256                w = null;
257            }
258        }
259    }
260
261    static final String NULL_NAME = "Null";
262    private static final String INDENT = "    ";
263}
264