blob: 8762cce3d90e675afd1b24c0e81f71902eb03288 [file] [log] [blame]
Stuart McCulloch26e7a5a2011-10-17 10:31:43 +00001package aQute.lib.tag;
2
3import java.io.*;
4import java.text.*;
5import java.util.*;
6
7/**
8 * The Tag class represents a minimal XML tree. It consist of a named element
9 * with a hashtable of named attributes. Methods are provided to walk the tree
10 * and get its constituents. The content of a Tag is a list that contains String
11 * objects or other Tag objects.
12 */
13public class Tag {
14 Tag parent; // Parent
15 String name; // Name
16 final Map<String, String> attributes = new LinkedHashMap<String, String>();
17 final List<Object> content = new ArrayList<Object>(); // Content
18 static SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss.SSS");
19 boolean cdata;
20
21 /**
22 * Construct a new Tag with a name.
23 */
24 public Tag(String name, Object... contents) {
25 this.name = name;
26 for (Object c : contents)
27 content.add(c);
28 }
29
30 public Tag(Tag parent, String name, Object... contents) {
31 this(name,contents);
32 parent.addContent(this);
33 }
34
35 /**
36 * Construct a new Tag with a name.
37 */
38 public Tag(String name, Map<String, String> attributes, Object... contents) {
39 this(name,contents);
40 this.attributes.putAll(attributes);
41
42 }
43 public Tag(String name, Map<String, String> attributes) {
44 this(name, attributes, new Object[0]);
45 }
46
47 /**
48 * Construct a new Tag with a name and a set of attributes. The attributes
49 * are given as ( name, value ) ...
50 */
51 public Tag(String name, String[] attributes, Object... contents) {
52 this(name,contents);
53 for (int i = 0; i < attributes.length; i += 2)
54 addAttribute(attributes[i], attributes[i + 1]);
55 }
56
57 public Tag(String name, String[] attributes) {
58 this(name, attributes, new Object[0]);
59 }
60
61 /**
62 * Add a new attribute.
63 */
64 public Tag addAttribute(String key, String value) {
65 if (value != null)
66 attributes.put(key, value);
67 return this;
68 }
69
70 /**
71 * Add a new attribute.
72 */
73 public Tag addAttribute(String key, Object value) {
74 if (value == null)
75 return this;
76 attributes.put(key, value.toString());
77 return this;
78 }
79
80 /**
81 * Add a new attribute.
82 */
83 public Tag addAttribute(String key, int value) {
84 attributes.put(key, Integer.toString(value));
85 return this;
86 }
87
88 /**
89 * Add a new date attribute. The date is formatted as the SimpleDateFormat
90 * describes at the top of this class.
91 */
92 public Tag addAttribute(String key, Date value) {
93 if (value != null)
94 attributes.put(key, format.format(value));
95 return this;
96 }
97
98 /**
99 * Add a new content string.
100 */
101 public Tag addContent(String string) {
102 if (string != null)
103 content.add(string);
104 return this;
105 }
106
107 /**
108 * Add a new content tag.
109 */
110 public Tag addContent(Tag tag) {
111 content.add(tag);
112 tag.parent = this;
113 return this;
114 }
115
116 /**
117 * Return the name of the tag.
118 */
119 public String getName() {
120 return name;
121 }
122
123 /**
124 * Return the attribute value.
125 */
126 public String getAttribute(String key) {
127 return (String) attributes.get(key);
128 }
129
130 /**
131 * Return the attribute value or a default if not defined.
132 */
133 public String getAttribute(String key, String deflt) {
134 String answer = getAttribute(key);
135 return answer == null ? deflt : answer;
136 }
137
138 /**
139 * Answer the attributes as a Dictionary object.
140 */
141 public Map<String, String> getAttributes() {
142 return attributes;
143 }
144
145 /**
146 * Return the contents.
147 */
148 public List<Object> getContents() {
149 return content;
150 }
151
152 /**
153 * Return a string representation of this Tag and all its children
154 * recursively.
155 */
156 public String toString() {
157 StringWriter sw = new StringWriter();
158 print(0, new PrintWriter(sw));
159 return sw.toString();
160 }
161
162 /**
163 * Return only the tags of the first level of descendants that match the
164 * name.
165 */
166 public List<Object> getContents(String tag) {
167 List<Object> out = new ArrayList<Object>();
168 for (Object o : out) {
169 if (o instanceof Tag && ((Tag) o).getName().equals(tag))
170 out.add(o);
171 }
172 return out;
173 }
174
175 /**
176 * Return the whole contents as a String (no tag info and attributes).
177 */
178 public String getContentsAsString() {
179 StringBuffer sb = new StringBuffer();
180 getContentsAsString(sb);
181 return sb.toString();
182 }
183
184 /**
185 * convenient method to get the contents in a StringBuffer.
186 */
187 public void getContentsAsString(StringBuffer sb) {
188 for (Object o : content) {
189 if (o instanceof Tag)
190 ((Tag) o).getContentsAsString(sb);
191 else
192 sb.append(o.toString());
193 }
194 }
195
196 /**
197 * Print the tag formatted to a PrintWriter.
198 */
199 public Tag print(int indent, PrintWriter pw) {
200 pw.print("\n");
201 spaces(pw, indent);
202 pw.print('<');
203 pw.print(name);
204
205 for (String key : attributes.keySet()) {
206 String value = escape(attributes.get(key));
207 pw.print(' ');
208 pw.print(key);
209 pw.print("=");
210 String quote = "'";
211 if (value.indexOf(quote) >= 0)
212 quote = "\"";
213 pw.print(quote);
214 pw.print(value);
215 pw.print(quote);
216 }
217
218 if (content.size() == 0)
219 pw.print('/');
220 else {
221 pw.print('>');
222 for (Object c : content) {
223 if (c instanceof String) {
224 formatted(pw, indent + 2, 60, escape((String) c));
225 } else if (c instanceof Tag) {
226 Tag tag = (Tag) c;
227 tag.print(indent + 2, pw);
228 }
229 }
230 pw.print("\n");
231 spaces(pw, indent);
232 pw.print("</");
233 pw.print(name);
234 }
235 pw.print('>');
236 return this;
237 }
238
239 /**
240 * Convenience method to print a string nicely and does character conversion
241 * to entities.
242 */
243 void formatted(PrintWriter pw, int left, int width, String s) {
244 int pos = width + 1;
245 s = s.trim();
246
247 for (int i = 0; i < s.length(); i++) {
248 char c = s.charAt(i);
249 if (i == 0 || (Character.isWhitespace(c) && pos > width - 3)) {
250 pw.print("\n");
251 spaces(pw, left);
252 pos = 0;
253 }
254 switch (c) {
255 case '<':
256 pw.print("&lt;");
257 pos += 4;
258 break;
259 case '>':
260 pw.print("&gt;");
261 pos += 4;
262 break;
263 case '&':
264 pw.print("&amp;");
265 pos += 5;
266 break;
267 default:
268 pw.print(c);
269 pos++;
270 break;
271 }
272
273 }
274 }
275
276 /**
277 * Escape a string, do entity conversion.
278 */
279 String escape(String s) {
280 StringBuffer sb = new StringBuffer();
281 for (int i = 0; i < s.length(); i++) {
282 char c = s.charAt(i);
283 switch (c) {
284 case '<':
285 sb.append("&lt;");
286 break;
287 case '>':
288 sb.append("&gt;");
289 break;
290 case '&':
291 sb.append("&amp;");
292 break;
293 default:
294 sb.append(c);
295 break;
296 }
297 }
298 return sb.toString();
299 }
300
301 /**
302 * Make spaces.
303 */
304 void spaces(PrintWriter pw, int n) {
305 while (n-- > 0)
306 pw.print(' ');
307 }
308
309 /**
310 * root/preferences/native/os
311 */
312 public Collection<Tag> select(String path) {
313 return select(path, (Tag) null);
314 }
315
316 public Collection<Tag> select(String path, Tag mapping) {
317 List<Tag> v = new ArrayList<Tag>();
318 select(path, v, mapping);
319 return v;
320 }
321
322 void select(String path, List<Tag> results, Tag mapping) {
323 if (path.startsWith("//")) {
324 int i = path.indexOf('/', 2);
325 String name = path.substring(2, i < 0 ? path.length() : i);
326
327 for (Object o : content) {
328 if (o instanceof Tag) {
329 Tag child = (Tag) o;
330 if (match(name, child, mapping))
331 results.add(child);
332 child.select(path, results, mapping);
333 }
334
335 }
336 return;
337 }
338
339 if (path.length() == 0) {
340 results.add(this);
341 return;
342 }
343
344 int i = path.indexOf("/");
345 String elementName = path;
346 String remainder = "";
347 if (i > 0) {
348 elementName = path.substring(0, i);
349 remainder = path.substring(i + 1);
350 }
351
352 for (Object o : content) {
353 if (o instanceof Tag) {
354 Tag child = (Tag) o;
355 if (child.getName().equals(elementName) || elementName.equals("*"))
356 child.select(remainder, results, mapping);
357 }
358 }
359 }
360
361 public boolean match(String search, Tag child, Tag mapping) {
362 String target = child.getName();
363 String sn = null;
364 String tn = null;
365
366 if (search.equals("*"))
367 return true;
368
369 int s = search.indexOf(':');
370 if (s > 0) {
371 sn = search.substring(0, s);
372 search = search.substring(s + 1);
373 }
374 int t = target.indexOf(':');
375 if (t > 0) {
376 tn = target.substring(0, t);
377 target = target.substring(t + 1);
378 }
379
380 if (!search.equals(target)) // different tag names
381 return false;
382
383 if (mapping == null) {
384 return tn == sn || (sn != null && sn.equals(tn));
385 } else {
386 String suri = sn == null ? mapping.getAttribute("xmlns") : mapping
387 .getAttribute("xmlns:" + sn);
388 String turi = tn == null ? child.findRecursiveAttribute("xmlns") : child
389 .findRecursiveAttribute("xmlns:" + tn);
390 return turi == suri || (turi != null && suri != null && turi.equals(suri));
391 }
392 }
393
394 public String getString(String path) {
395 String attribute = null;
396 int index = path.indexOf("@");
397 if (index >= 0) {
398 // attribute
399 attribute = path.substring(index + 1);
400
401 if (index > 0) {
402 // prefix path
403 path = path.substring(index - 1); // skip -1
404 } else
405 path = "";
406 }
407 Collection<Tag> tags = select(path);
408 StringBuffer sb = new StringBuffer();
409 for (Tag tag : tags) {
410 if (attribute == null)
411 tag.getContentsAsString(sb);
412 else
413 sb.append(tag.getAttribute(attribute));
414 }
415 return sb.toString();
416 }
417
418 public String getStringContent() {
419 StringBuffer sb = new StringBuffer();
420 for (Object c : content) {
421 if (!(c instanceof Tag))
422 sb.append(c);
423 }
424 return sb.toString();
425 }
426
427 public String getNameSpace() {
428 return getNameSpace(name);
429 }
430
431 public String getNameSpace(String name) {
432 int index = name.indexOf(':');
433 if (index > 0) {
434 String ns = name.substring(0, index);
435 return findRecursiveAttribute("xmlns:" + ns);
436 } else
437 return findRecursiveAttribute("xmlns");
438 }
439
440 public String findRecursiveAttribute(String name) {
441 String value = getAttribute(name);
442 if (value != null)
443 return value;
444 if (parent != null)
445 return parent.findRecursiveAttribute(name);
446 return null;
447 }
448
449 public String getLocalName() {
450 int index = name.indexOf(':');
451 if (index <= 0)
452 return name;
453
454 return name.substring(index + 1);
455 }
456
457 public void rename(String string) {
458 name = string;
459 }
460
461 public void setCDATA() {
462 cdata = true;
463 }
464
465}