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