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("&quot;");
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 }