Mercurial Hosting > nabble
comparison src/fschmidt/html/HtmlTag.java @ 68:00520880ad02
add fschmidt source
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Sun, 05 Oct 2025 17:24:15 -0600 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
67:9d0fefce6985 | 68:00520880ad02 |
---|---|
1 /* | |
2 Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> | |
3 | |
4 Permission is hereby granted, free of charge, to any person obtaining a copy | |
5 of this software and associated documentation files (the "Software"), to deal | |
6 in the Software without restriction, including without limitation the rights | |
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
8 copies of the Software, and to permit persons to whom the Software is | |
9 furnished to do so, subject to the following conditions: | |
10 | |
11 The above copyright notice and this permission notice shall be included in | |
12 all copies or substantial portions of the Software. | |
13 | |
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
20 THE SOFTWARE. | |
21 */ | |
22 | |
23 | |
24 package fschmidt.html; | |
25 | |
26 import java.util.List; | |
27 import java.util.ArrayList; | |
28 import java.util.Map; | |
29 import java.util.HashMap; | |
30 import java.util.LinkedHashMap; | |
31 import java.util.Iterator; | |
32 import java.util.Collection; | |
33 import java.util.Collections; | |
34 | |
35 | |
36 public class HtmlTag { | |
37 | |
38 static final class BadTag extends RuntimeException { | |
39 private BadTag(String msg) { | |
40 super(msg); | |
41 } | |
42 } | |
43 | |
44 public static final class Attribute { | |
45 private final String spaceBeforeName; | |
46 private final String name; | |
47 private final String spaceAfterName; | |
48 private final String spaceBeforeValue; | |
49 private final String value; | |
50 | |
51 protected Attribute(String spaceBeforeName,String name,String spaceAfterName,String spaceBeforeValue,String value) { | |
52 this.spaceBeforeName = spaceBeforeName; | |
53 this.name = name; | |
54 this.spaceAfterName = spaceAfterName; | |
55 this.spaceBeforeValue = spaceBeforeValue; | |
56 this.value = value; | |
57 } | |
58 | |
59 public String getSpaceBeforeName() { | |
60 return spaceBeforeName; | |
61 } | |
62 | |
63 public String getName() { | |
64 return name; | |
65 } | |
66 | |
67 public String getSpaceAfterName() { | |
68 return spaceAfterName; | |
69 } | |
70 | |
71 public String getSpaceBeforeValue() { | |
72 return spaceBeforeValue; | |
73 } | |
74 | |
75 public String getValue() { | |
76 return value; | |
77 } | |
78 | |
79 @Override public String toString() { | |
80 StringBuilder buf = new StringBuilder(); | |
81 buf.append(name); | |
82 if( value != null ) { | |
83 buf.append(spaceAfterName); | |
84 buf.append('='); | |
85 buf.append(spaceBeforeValue); | |
86 buf.append(value); | |
87 } | |
88 return buf.toString(); | |
89 } | |
90 } | |
91 | |
92 private String name; | |
93 private final Map<String,Attribute> attrMap; | |
94 private boolean isEmpty; | |
95 int lineNumber = -1; | |
96 private final String spaceAtEnd; | |
97 | |
98 private static int toEndName(String text,int i,int len) { | |
99 if( i==len ) | |
100 return i; | |
101 char c = text.charAt(i); | |
102 switch(c) { | |
103 case '"': | |
104 case '\'': | |
105 i = text.indexOf(c,i+1); | |
106 return i==-1 ? len : i+1; | |
107 default: | |
108 if( Character.isWhitespace(c) ) { | |
109 throw new RuntimeException("text="+text+" i="+i); | |
110 } | |
111 do { | |
112 i++; | |
113 } while( i<len && (c=text.charAt(i))!='=' && !Character.isWhitespace(c) ); | |
114 return i; | |
115 } | |
116 } | |
117 | |
118 private static int toEndValue(String text,int i,int len) { | |
119 if( i==len ) | |
120 return i; | |
121 char c = text.charAt(i); | |
122 switch(c) { | |
123 case '"': | |
124 case '\'': | |
125 i = text.indexOf(c,i+1); | |
126 return i==-1 ? len : i+1; | |
127 default: | |
128 if( Character.isWhitespace(c) ) { | |
129 throw new RuntimeException("text="+text+" i="+i); | |
130 } | |
131 do { | |
132 i++; | |
133 } while( i<len && !Character.isWhitespace(text.charAt(i)) ); | |
134 return i; | |
135 } | |
136 } | |
137 | |
138 public HtmlTag(String text) throws BadTag { | |
139 attrMap = new LinkedHashMap<String,Attribute>(); | |
140 if( text.endsWith("/") ) { | |
141 text = text.substring(0,text.length()-1); | |
142 isEmpty = true; | |
143 } else { | |
144 isEmpty = false; | |
145 } | |
146 int len = text.length(); | |
147 int i = 0; | |
148 int i2 = i; | |
149 if( i2<len && text.charAt(i2)=='/' ) | |
150 i2++; | |
151 while( i2<len ) { | |
152 char c = text.charAt(i2); | |
153 if( Character.isWhitespace(c) ) | |
154 break; | |
155 if( !( Character.isLetterOrDigit(c) || c=='_' || c=='.' || c=='-' || c==':' ) ) | |
156 throw new BadTag("invalid tag name for <"+text+">"); | |
157 i2++; | |
158 } | |
159 name = text.substring(i,i2); | |
160 i = i2; | |
161 while( i<len && Character.isWhitespace(text.charAt(i)) ) i++; | |
162 while( i<len ) { | |
163 String attrSpaceBeforeName = text.substring(i2,i); | |
164 i2 = toEndName(text,i,len); | |
165 String attrName = text.substring(i,i2); | |
166 i = i2; | |
167 while( i<len && Character.isWhitespace(text.charAt(i)) ) i++; | |
168 String attrSpaceAfterName = ""; | |
169 String attrSpaceBeforeValue = ""; | |
170 String attrValue = null; | |
171 if( i<len && text.charAt(i) == '=' ) { | |
172 attrSpaceAfterName = text.substring(i2,i); | |
173 i++; | |
174 i2 = i; | |
175 while( i<len && Character.isWhitespace(text.charAt(i)) ) i++; | |
176 attrSpaceBeforeValue = text.substring(i2,i); | |
177 i2 = toEndValue(text,i,len); | |
178 attrValue = text.substring(i,i2); | |
179 if( attrValue.indexOf('<') != -1 || attrValue.indexOf('>') != -1 ) | |
180 throw new BadTag("invalid attribute value: "+attrValue); | |
181 i = i2; | |
182 while( i<len && Character.isWhitespace(text.charAt(i)) ) i++; | |
183 } | |
184 if( setAttribute(attrSpaceBeforeName,attrName,attrSpaceAfterName,attrSpaceBeforeValue,attrValue) != null ) | |
185 throw new BadTag("duplicate attribute: "+attrName); | |
186 } | |
187 spaceAtEnd = text.substring(i2,i); | |
188 } | |
189 | |
190 public HtmlTag(HtmlTag tag) { | |
191 this.name = tag.name; | |
192 this.attrMap = new LinkedHashMap<String,Attribute>(tag.attrMap); | |
193 this.isEmpty = tag.isEmpty; | |
194 this.spaceAtEnd = tag.spaceAtEnd; | |
195 } | |
196 | |
197 public HtmlTag cloneTag() { | |
198 HtmlTag tag = new HtmlTag(this); | |
199 tag.lineNumber = lineNumber; | |
200 return tag; | |
201 } | |
202 | |
203 public String getName() { | |
204 return name; | |
205 } | |
206 | |
207 public void setName(String name) { | |
208 this.name = name; | |
209 } | |
210 | |
211 public boolean isEmpty() { | |
212 return isEmpty; | |
213 } | |
214 | |
215 public void setEmpty(boolean isEmpty) { | |
216 this.isEmpty = isEmpty; | |
217 } | |
218 | |
219 public String getAttributeName(String name) { | |
220 String key = unquote(name).toLowerCase(); | |
221 Attribute attr = attrMap.get(key); | |
222 return attr==null ? null : attr.name; | |
223 } | |
224 | |
225 public String getAttributeValue(String name) { | |
226 String key = unquote(name).toLowerCase(); | |
227 Attribute attr = attrMap.get(key); | |
228 return attr==null ? null : attr.value; | |
229 } | |
230 | |
231 public boolean hasAttribute(String name) { | |
232 String key = unquote(name).toLowerCase(); | |
233 return attrMap.containsKey(key); | |
234 } | |
235 | |
236 public void setAttribute(String name,String value) { | |
237 setAttribute(" ",name,"","",value); | |
238 } | |
239 | |
240 protected Attribute setAttribute(String spaceBeforeName,String name,String spaceAfterName,String spaceBeforeValue,String value) { | |
241 String key = unquote(name).toLowerCase(); | |
242 Attribute attr = new Attribute(spaceBeforeName,name,spaceAfterName,spaceBeforeValue,value); | |
243 return attrMap.put(key,attr); | |
244 } | |
245 | |
246 public void removeAttribute(String name) { | |
247 String key = unquote(name).toLowerCase(); | |
248 attrMap.remove(key); | |
249 } | |
250 | |
251 public String[] getAttributeNames() { | |
252 String[] a = new String[attrMap.size()]; | |
253 int i = 0; | |
254 for( Attribute attr : attrMap.values() ) { | |
255 a[i++] = attr.name; | |
256 } | |
257 return a; | |
258 } | |
259 | |
260 @Override public String toString() { | |
261 StringBuilder buf = new StringBuilder(); | |
262 buf.append('<'); | |
263 buf.append(name); | |
264 for( Attribute attr : attrMap.values() ) { | |
265 buf.append(attr.spaceBeforeName); | |
266 buf.append(attr); | |
267 } | |
268 buf.append(spaceAtEnd); | |
269 if(isEmpty) | |
270 buf.append('/'); | |
271 buf.append('>'); | |
272 return buf.toString(); | |
273 } | |
274 | |
275 public static String unquote(String s) { | |
276 if( s==null || s.length()<=1 ) | |
277 return s; | |
278 char c = s.charAt(0); | |
279 return (c=='"' || c=='\'') && s.charAt(s.length()-1)==c | |
280 ? s.substring(1,s.length()-1) : s; | |
281 } | |
282 | |
283 public static String quote(String s) { | |
284 if( s==null ) | |
285 return null; | |
286 StringBuilder buf = new StringBuilder(); | |
287 buf.append('"'); | |
288 int i = 0; | |
289 while(true) { | |
290 int i2 = s.indexOf('"',i); | |
291 if( i2 == -1 ) { | |
292 buf.append(s.substring(i)); | |
293 break; | |
294 } else { | |
295 buf.append(s.substring(i,i2)); | |
296 buf.append("""); | |
297 i = i2 + 1; | |
298 } | |
299 } | |
300 buf.append('"'); | |
301 return buf.toString(); | |
302 } | |
303 | |
304 @Override public boolean equals(Object obj) { | |
305 if( obj == this ) | |
306 return true; | |
307 if( !(obj instanceof HtmlTag) ) | |
308 return false; | |
309 HtmlTag tag = (HtmlTag)obj; | |
310 return getName().equalsIgnoreCase(tag.getName()) | |
311 && isEmpty() == tag.isEmpty() | |
312 && getAttributeMap().equals(tag.getAttributeMap()) | |
313 ; | |
314 } | |
315 | |
316 public Map<String,String> getAttributeMap() { | |
317 Map<String,String> map = new HashMap<String,String>(); | |
318 for( Attribute attr : attrMap.values() ) { | |
319 map.put(unquote(attr.name),unquote(attr.value)); | |
320 } | |
321 return map; | |
322 } | |
323 | |
324 public Collection<Attribute> getAttributes() { | |
325 return Collections.unmodifiableCollection(attrMap.values()); | |
326 } | |
327 | |
328 public int getLineNumber() { | |
329 return lineNumber; | |
330 } | |
331 | |
332 public String getSpaceAtEnd() { | |
333 return spaceAtEnd; | |
334 } | |
335 } |