Mercurial Hosting > nabble
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/html/HtmlTag.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,335 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + + +package fschmidt.html; + +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Iterator; +import java.util.Collection; +import java.util.Collections; + + +public class HtmlTag { + + static final class BadTag extends RuntimeException { + private BadTag(String msg) { + super(msg); + } + } + + public static final class Attribute { + private final String spaceBeforeName; + private final String name; + private final String spaceAfterName; + private final String spaceBeforeValue; + private final String value; + + protected Attribute(String spaceBeforeName,String name,String spaceAfterName,String spaceBeforeValue,String value) { + this.spaceBeforeName = spaceBeforeName; + this.name = name; + this.spaceAfterName = spaceAfterName; + this.spaceBeforeValue = spaceBeforeValue; + this.value = value; + } + + public String getSpaceBeforeName() { + return spaceBeforeName; + } + + public String getName() { + return name; + } + + public String getSpaceAfterName() { + return spaceAfterName; + } + + public String getSpaceBeforeValue() { + return spaceBeforeValue; + } + + public String getValue() { + return value; + } + + @Override public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(name); + if( value != null ) { + buf.append(spaceAfterName); + buf.append('='); + buf.append(spaceBeforeValue); + buf.append(value); + } + return buf.toString(); + } + } + + private String name; + private final Map<String,Attribute> attrMap; + private boolean isEmpty; + int lineNumber = -1; + private final String spaceAtEnd; + + private static int toEndName(String text,int i,int len) { + if( i==len ) + return i; + char c = text.charAt(i); + switch(c) { + case '"': + case '\'': + i = text.indexOf(c,i+1); + return i==-1 ? len : i+1; + default: + if( Character.isWhitespace(c) ) { + throw new RuntimeException("text="+text+" i="+i); + } + do { + i++; + } while( i<len && (c=text.charAt(i))!='=' && !Character.isWhitespace(c) ); + return i; + } + } + + private static int toEndValue(String text,int i,int len) { + if( i==len ) + return i; + char c = text.charAt(i); + switch(c) { + case '"': + case '\'': + i = text.indexOf(c,i+1); + return i==-1 ? len : i+1; + default: + if( Character.isWhitespace(c) ) { + throw new RuntimeException("text="+text+" i="+i); + } + do { + i++; + } while( i<len && !Character.isWhitespace(text.charAt(i)) ); + return i; + } + } + + public HtmlTag(String text) throws BadTag { + attrMap = new LinkedHashMap<String,Attribute>(); + if( text.endsWith("/") ) { + text = text.substring(0,text.length()-1); + isEmpty = true; + } else { + isEmpty = false; + } + int len = text.length(); + int i = 0; + int i2 = i; + if( i2<len && text.charAt(i2)=='/' ) + i2++; + while( i2<len ) { + char c = text.charAt(i2); + if( Character.isWhitespace(c) ) + break; + if( !( Character.isLetterOrDigit(c) || c=='_' || c=='.' || c=='-' || c==':' ) ) + throw new BadTag("invalid tag name for <"+text+">"); + i2++; + } + name = text.substring(i,i2); + i = i2; + while( i<len && Character.isWhitespace(text.charAt(i)) ) i++; + while( i<len ) { + String attrSpaceBeforeName = text.substring(i2,i); + i2 = toEndName(text,i,len); + String attrName = text.substring(i,i2); + i = i2; + while( i<len && Character.isWhitespace(text.charAt(i)) ) i++; + String attrSpaceAfterName = ""; + String attrSpaceBeforeValue = ""; + String attrValue = null; + if( i<len && text.charAt(i) == '=' ) { + attrSpaceAfterName = text.substring(i2,i); + i++; + i2 = i; + while( i<len && Character.isWhitespace(text.charAt(i)) ) i++; + attrSpaceBeforeValue = text.substring(i2,i); + i2 = toEndValue(text,i,len); + attrValue = text.substring(i,i2); + if( attrValue.indexOf('<') != -1 || attrValue.indexOf('>') != -1 ) + throw new BadTag("invalid attribute value: "+attrValue); + i = i2; + while( i<len && Character.isWhitespace(text.charAt(i)) ) i++; + } + if( setAttribute(attrSpaceBeforeName,attrName,attrSpaceAfterName,attrSpaceBeforeValue,attrValue) != null ) + throw new BadTag("duplicate attribute: "+attrName); + } + spaceAtEnd = text.substring(i2,i); + } + + public HtmlTag(HtmlTag tag) { + this.name = tag.name; + this.attrMap = new LinkedHashMap<String,Attribute>(tag.attrMap); + this.isEmpty = tag.isEmpty; + this.spaceAtEnd = tag.spaceAtEnd; + } + + public HtmlTag cloneTag() { + HtmlTag tag = new HtmlTag(this); + tag.lineNumber = lineNumber; + return tag; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isEmpty() { + return isEmpty; + } + + public void setEmpty(boolean isEmpty) { + this.isEmpty = isEmpty; + } + + public String getAttributeName(String name) { + String key = unquote(name).toLowerCase(); + Attribute attr = attrMap.get(key); + return attr==null ? null : attr.name; + } + + public String getAttributeValue(String name) { + String key = unquote(name).toLowerCase(); + Attribute attr = attrMap.get(key); + return attr==null ? null : attr.value; + } + + public boolean hasAttribute(String name) { + String key = unquote(name).toLowerCase(); + return attrMap.containsKey(key); + } + + public void setAttribute(String name,String value) { + setAttribute(" ",name,"","",value); + } + + protected Attribute setAttribute(String spaceBeforeName,String name,String spaceAfterName,String spaceBeforeValue,String value) { + String key = unquote(name).toLowerCase(); + Attribute attr = new Attribute(spaceBeforeName,name,spaceAfterName,spaceBeforeValue,value); + return attrMap.put(key,attr); + } + + public void removeAttribute(String name) { + String key = unquote(name).toLowerCase(); + attrMap.remove(key); + } + + public String[] getAttributeNames() { + String[] a = new String[attrMap.size()]; + int i = 0; + for( Attribute attr : attrMap.values() ) { + a[i++] = attr.name; + } + return a; + } + + @Override public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append('<'); + buf.append(name); + for( Attribute attr : attrMap.values() ) { + buf.append(attr.spaceBeforeName); + buf.append(attr); + } + buf.append(spaceAtEnd); + if(isEmpty) + buf.append('/'); + buf.append('>'); + return buf.toString(); + } + + public static String unquote(String s) { + if( s==null || s.length()<=1 ) + return s; + char c = s.charAt(0); + return (c=='"' || c=='\'') && s.charAt(s.length()-1)==c + ? s.substring(1,s.length()-1) : s; + } + + public static String quote(String s) { + if( s==null ) + return null; + StringBuilder buf = new StringBuilder(); + buf.append('"'); + int i = 0; + while(true) { + int i2 = s.indexOf('"',i); + if( i2 == -1 ) { + buf.append(s.substring(i)); + break; + } else { + buf.append(s.substring(i,i2)); + buf.append("""); + i = i2 + 1; + } + } + buf.append('"'); + return buf.toString(); + } + + @Override public boolean equals(Object obj) { + if( obj == this ) + return true; + if( !(obj instanceof HtmlTag) ) + return false; + HtmlTag tag = (HtmlTag)obj; + return getName().equalsIgnoreCase(tag.getName()) + && isEmpty() == tag.isEmpty() + && getAttributeMap().equals(tag.getAttributeMap()) + ; + } + + public Map<String,String> getAttributeMap() { + Map<String,String> map = new HashMap<String,String>(); + for( Attribute attr : attrMap.values() ) { + map.put(unquote(attr.name),unquote(attr.value)); + } + return map; + } + + public Collection<Attribute> getAttributes() { + return Collections.unmodifiableCollection(attrMap.values()); + } + + public int getLineNumber() { + return lineNumber; + } + + public String getSpaceAtEnd() { + return spaceAtEnd; + } +}