1 module deepmagic.dom.xml.element;
2 
3 import deepmagic.dom;
4 
5 class Element : Item{
6 	Tag tag; /// The start tag of the element
7 	Item[] items; /// The element's items
8 	Text[] texts; /// The element's text items
9 	CData[] cdatas; /// The element's CData items
10 	Comment[] comments; /// The element's comments
11 	ProcessingInstruction[] pis; /// The element's processing instructions
12 	Element[] elements; /// The element's child elements
13 	bool[string] _classes = null;
14 	Sass[] _sasses = null;
15 
16 	this(string name, string interior=null){
17 		this(new Tag(name));
18 		if (interior.length != 0) opCatAssign(new Text(interior));
19 	}
20 
21 	this(const(Tag) tag_){
22 		this.tag = new Tag(tag_.name);
23 		tag.type = TagType.EMPTY;
24 		foreach(k,v;tag_.attr) tag.attr[k] = v;
25 		tag.tagString = tag_.tagString;
26 	}
27 
28 	void opCatAssign(Text item){
29 		texts ~= item;
30 		appendItem(item);
31 	}
32 
33 	void opCatAssign(CData item){
34 		cdatas ~= item;
35 		appendItem(item);
36 	}
37 
38 	void opCatAssign(Comment item){
39 		comments ~= item;
40 		appendItem(item);
41 	}
42 
43 	void opCatAssign(ProcessingInstruction item){
44 		pis ~= item;
45 		appendItem(item);
46 	}
47 
48 	void opCatAssign(Element item){
49 		elements ~= item;
50 		appendItem(item);
51 	}
52 
53 	void opCatAssign(string item){
54 		this ~= new Text(item);
55 	}
56 
57 	import deepmagic.sass;
58 	typeof(this) add_sass(Sass sass){
59 		this._sasses ~= sass;
60 		this.refresh_tag();
61 		return this;
62 	}
63 	
64 	typeof(this) add_class(string class_name, bool value = true){
65 		this._classes[class_name] = value;
66 		this.refresh_tag();
67 		return this;
68 	}
69 
70 	typeof(this) add_class(string[] class_names){
71 		foreach(int key, string class_name; class_names){
72 			this.add_class(class_name);
73 		}
74 		return this;
75 	}
76 
77 	typeof(this) remove_class(string class_name){
78 		if(class_name in this._classes){
79 			this._classes.remove(class_name);
80 		}
81 		this.refresh_tag();
82 		return this;
83 	}
84 
85 	typeof(this) add_sass(Sass[] sasses){
86 		foreach(int key, Sass sass; sasses){
87 			this.add_sass(sass);
88 		}
89 		return this;
90 	}
91 
92 	void opCatAssign(Sass sass){
93 		this.add_sass(sass);
94 	}
95 
96 	protected void refresh_tag(){
97 		string c = "";
98 		foreach(int i, Sass sass; this._sasses){
99 			c ~= sass.name ~ " "; //TODO: This leaves a trailing " " at the end of the string, which is sloppy. Clean it up.
100 		}
101 		foreach(string s, bool v; this._classes){
102 			c ~= s ~ " "; //TODO: This leaves a trailing " " at the end of the string, which is sloppy. Clean it up.
103 		}
104 		this.tag.attr["class"] = c;
105 	}
106 
107 	private void appendItem(Item item){
108 		items ~= item;
109 		if (tag.type == TagType.EMPTY && !item.isEmptyXML)
110 			tag.type = TagType.START;
111 	}
112 
113 	void parse(ElementParser xml){
114 		xml.onText = (string s) { opCatAssign(new Text(s)); };
115 		xml.onCData = (string s) { opCatAssign(new CData(s)); };
116 		xml.onComment = (string s) { opCatAssign(new Comment(s)); };
117 		xml.onPI = (string s) { opCatAssign(new ProcessingInstruction(s)); };
118 
119 		xml.onStartTag[null] = (ElementParser xml){
120 			auto e = new Element(xml.tag);
121 			e.parse(xml);
122 			opCatAssign(e);
123 		};
124 
125 		xml.parse();
126 	}
127 
128 	override bool opEquals(Object o){
129 		const element = toType!(const Element)(o);
130 		auto len = items.length;
131 		if (len != element.items.length) return false;
132 		foreach (i; 0 .. len){
133 			if (!items[i].opEquals(cast()element.items[i])) return false;
134 		}
135 		return true;
136 	}
137 
138 	override int opCmp(Object o){
139 		const element = toType!(const Element)(o);
140 		for (uint i=0; ; ++i){
141 			if (i == items.length && i == element.items.length) return 0;
142 			if (i == items.length) return -1;
143 			if (i == element.items.length) return 1;
144 			if (items[i] != element.items[i])
145 				return items[i].opCmp(cast()element.items[i]);
146 		}
147 	}
148 
149 	override size_t toHash() const{
150 		size_t hash = tag.toHash();
151 		foreach(item;items) hash += item.toHash();
152 		return hash;
153 	}
154 
155 	const{
156 		string text(DecodeMode mode=DecodeMode.LOOSE){
157 			string buffer;
158 			foreach(item;items){
159 				Text t = cast(Text)item;
160 				if (t is null) throw new DecodeException(item.toString());
161 				buffer ~= decode(t.toString(),mode);
162 			}
163 			return buffer;
164 		}
165 
166 		override string[] pretty(uint indent=2){
167 
168 			if (isEmptyXML) return [ tag.toEmptyString() ];
169 
170 			if (items.length == 1){
171 				Text t = cast(Text)(items[0]);
172 				if (t !is null)
173 				{
174 					return [tag.toStartString() ~ t.toString() ~ tag.toEndString()];
175 				}
176 			}
177 
178 			string[] a = [ tag.toStartString() ];
179 			foreach(item;items){
180 				string[] b = item.pretty(indent);
181 				foreach(s;b)
182 				{
183 					a ~= rightJustify(s,count(s) + indent);
184 				}
185 			}
186 			a ~= tag.toEndString();
187 			return a;
188 		}
189 
190 		override string toString(){
191 			if (isEmptyXML) return tag.toEmptyString();
192 
193 			string buffer = tag.toStartString();
194 			foreach (item;items) { buffer ~= item.toString(); }
195 			buffer ~= tag.toEndString();
196 			return buffer;
197 		}
198 
199 		override @property bool isEmptyXML() { return items.length == 0; }
200 	}
201 }