1 module deepmagic.dom.xml.tag;
2 
3 import deepmagic.dom;
4 
5 enum TagType { START, END, EMPTY }
6 
7 class Tag{
8 	TagType type = TagType.START;   /// Type of tag
9 	string name;					/// Tag name
10 	string[string] attr;			/// Associative array of attributes
11 	string tagString;
12 
13 	invariant(){
14 		string s;
15 		string t;
16 
17 		assert(type == TagType.START
18 			|| type == TagType.END
19 			|| type == TagType.EMPTY);
20 
21 		s = name;
22 		try { checkName(s,t); }
23 		catch(Err e) { assert(false,"Invalid tag name:" ~ e.toString()); }
24 
25 		foreach(k,v;attr){
26 			s = k;
27 			try { checkName(s,t); }
28 			catch(Err e){ assert(false,"Invalid atrribute name:" ~ e.toString()); }
29 		}
30 	}
31 
32 	this(string name, TagType type=TagType.START){
33 		this.name = name;
34 		this.type = type;
35 	}
36 
37 	this(ref string s, bool dummy){
38 		tagString = s;
39 		try{
40 			reqc(s,'<');
41 			if (optc(s,'/')) type = TagType.END;
42 			name = munch(s,"^/>"~whitespace);
43 			munch(s,whitespace);
44 			while(s.length > 0 && s[0] != '>' && s[0] != '/'){
45 				string key = munch(s,"^="~whitespace);
46 				munch(s,whitespace);
47 				reqc(s,'=');
48 				munch(s,whitespace);
49 				reqc(s,'"');
50 				string val = decode(munch(s,"^\""), DecodeMode.LOOSE);
51 				reqc(s,'"');
52 				munch(s,whitespace);
53 				attr[key] = val;
54 			}
55 			if (optc(s,'/')){
56 				if (type == TagType.END) throw new TagException("");
57 				type = TagType.EMPTY;
58 			}
59 			reqc(s,'>');
60 			tagString.length = (s.ptr - tagString.ptr);
61 		}
62 		catch(XMLException e){
63 			tagString.length = (s.ptr - tagString.ptr);
64 			throw new TagException(tagString);
65 		}
66 	}
67 
68 	const{
69 		override bool opEquals(Object o){
70 			const tag = toType!(const Tag)(o);
71 			return
72 				(name != tag.name) ? false : (
73 				(attr != tag.attr) ? false : (
74 				(type != tag.type) ? false : (
75 			true )));
76 		}
77 
78 		override int opCmp(Object o){
79 			const tag = toType!(const Tag)(o);
80 			// Note that attr is an AA, so the comparison is nonsensical (bug 10381)
81 			return
82 				((name != tag.name) ? ( name < tag.name ? -1 : 1 ) :
83 				((attr != tag.attr) ? ( cast(void *)attr < cast(void*)tag.attr ? -1 : 1 ) :
84 				((type != tag.type) ? ( type < tag.type ? -1 : 1 ) :
85 			0 )));
86 		}
87 
88 		override size_t toHash(){
89 			return typeid(name).getHash(&name);
90 		}
91 
92 		override string toString(){
93 			if (isEmpty) return toEmptyString();
94 			return (isEnd) ? toEndString() : toStartString();
95 		}
96 
97 		public{
98 			string toNonEndString(){
99 				string s = "<" ~ name;
100 				foreach(key,val;attr)
101 					s ~= format(" %s=\"%s\"",key,encode(val));
102 				return s;
103 			}
104 
105 			string toStartString() { return toNonEndString() ~ ">"; }
106 			string toEndString() { return "</" ~ name ~ ">"; }
107 			string toEmptyString() { return toNonEndString() ~ " />"; }
108 		}
109 
110 		@property bool isStart() { return type == TagType.START; }
111 		@property bool isEnd()   { return type == TagType.END;   }
112 		@property bool isEmpty() { return type == TagType.EMPTY; }
113 	}
114 }