~michael-rynn-500/d2-xml/d2-xml-dev

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
/**
	This module provides an ElementParser behaviour that loosely resembles that of the original std.xml
	A difference is that this ElementParser provides HandlerSet, which can be set, exchanged, pushed and
	popped during a parse.


===


===

*/
module std.xml1;

public import std.xmlp.parseitem;
public import std.xmlp.arraydom;
public import std.xmlp.sliceparse;

import std.xmlp.subparse;
import std.string, std.stdint, std.variant;

import alt.buffer, alt.gcstats;

class ElementParser 
{
	alias string XmlString;
	alias void delegate(XmlReturn r) Handler; // event object
	alias void delegate(in Element) ElementHandler; // end tag constructed element tree
	alias void delegate(ElementParser) ParserHandler; // event object is property of parser

	class HandlerSet {
		Handler					  onText;
		Handler					  onPI;
		Handler					  onCDATA;
		Handler					  onComment;
		ParserHandler[XmlString]  onStartTag;
		ElementHandler[XmlString] onEndTag; // A start match starts an Element tree
	}
	private {
		XmlString			src_;
		XmlStringParser		parser_;
		Buffer!HandlerSet	stack_;
		Buffer!Element		elemStack_; 
		// track Element parents, because ArrayDom has no parent field
		// working set
		Handler					  onText_;
		Handler					  onPI_;
		Handler					  onCDATA_;
		Handler					  onComment_;
		Handler					  onXmlDec_;

		bool called_;
	}
public:	
	ParserHandler[XmlString]  onStartTag;
	ElementHandler[XmlString] onEndTag; // A start match starts an Element tree
	@property {
		void onPI(Handler handler) { onPI_ = handler; }
		void onText(Handler handler) { onText_ = handler; }
		void onCDATA(Handler handler) { onCDATA_ = handler; }
		void onComment(Handler handler) { onComment_ = handler; }
		void onXI(Handler handler) { onXmlDec_ = handler; }
	}

	XmlReturn	tag;

	version (GC_STATS)
	{
		mixin GC_statistics;
	}

	this(XmlString s)
	{
		version(GC_STATS)
			gcStatsSum.inc();


		src_ = s;
		parser_ = new XmlStringParser(s);
		tag = new XmlReturn();
		stack_.reserve(10);
	}

	version(GC_STATS)
	{
		~this()
		{
		gcStatsSum.inc();
		}
	}
	// Save existing handlers on stack, employ a new set
	void pushHandlerSet(HandlerSet hs)
	{
		auto hset = new HandlerSet();
		getHandlers(hset);
		stack_.put(hset);
		setHandlers(hs);	
	}

	/// replace existing handlers with set saved on stack. Return popped set, if any
	void popHandlerSet()
	{
		if (stack_.length > 0)
		{
			auto hset = stack_.movePopBack();
			setHandlers(hset);
		}
	}
	/// Get current handlers as a set, leaving values unchanged. Uninitialised AA ambiguity may apply. 
	void getHandlers(HandlerSet hset)
	{
		hset.onText = onText_;
		hset.onPI = onPI_;
		hset.onCDATA = onCDATA_;
		hset.onComment = onComment_;
		hset.onStartTag = this.onStartTag;
		hset.onEndTag = this.onEndTag;
	}
	/// Apply set to overwrite existing handlers.
	void setHandlers(HandlerSet hset)
	{
		onText_ = hset.onText;
		onPI_ = hset.onPI;
		onCDATA_ = hset.onCDATA;
		onComment_ = hset.onComment;
		this.onStartTag = hset.onStartTag;
		this.onEndTag = hset.onEndTag;
	}
	void setupRaw()
	{
		parser_.setParameter(xmlCharFilter,Variant(false));
		parser_.setParameter(xmlAttributeNormalize,Variant(false));
		parser_.initSource(src_);
	}
	void setupNormalize()
	{
		parser_.setParameter(xmlAttributeNormalize,Variant(true));
		parser_.initSource(src_);
	}

	/** 
	Loop each parse event, until current Element endtag 

	*/
	void parse(intptr_t relativeAdjust = 0)
	{
		auto   startLevel = parser_.tagDepth() + relativeAdjust;

		while(true)
		{
			parser_.parse(tag);
			called_ = false;
			switch(tag.type)
			{
				case XmlResult.TAG_START:
					// a new tag.
					if (onStartTag !is null)
					{
						auto callMyStart = onStartTag.get(tag.name,null);
						if (callMyStart !is null)
						{
							callMyStart(this);
						}
					}
					auto parent = (elemStack_.length > 0) ? elemStack_.back() : null;
					if (parent !is null)
					{	// building already
						auto e = createElement(tag);
						parent.appendChild(e);
						parent = e;
					}
					else if ((onEndTag !is null) && (tag.data in onEndTag))
					{
						// start building here
						parent = createElement(tag);
					}	
					elemStack_.put(parent); // null or not
					break;
				case XmlResult.TAG_SINGLE:
					// no push required, but check after
					auto parent = (elemStack_.length > 0) ? elemStack_.back() : null;
					if (parent !is null)
					{	// building already
						auto e = createElement(tag);
						parent.appendChild(e);
					}
					if (onStartTag !is null)
					{
						auto callMyStart = onStartTag.get(tag.name,null);
						if (callMyStart !is null)
						{
							callMyStart(this);
						}
					}   
					// solo tag is an start + endTag with no content.
					if (onEndTag !is null)
					{
						auto p = onEndTag.get(tag.name,null);

						if (p !is null)
						{
							if (parent is null)
								// make isolated Element
								parent = createElement(tag);
							p(parent);
						}
					}
					break;
				case XmlResult.TAG_END:
					if (onEndTag !is null)
					{
						auto p = onEndTag.get(tag.name,null);
						auto e = (elemStack_.length > 0) ? elemStack_.movePopBack() : null;
						if ((p !is null) && ( e !is null))
							p(e);
					}
					debug(VERBOSE)
						writefln("Start level %s, tag = %s %s", startLevel, parser_.tagDepth, tag.name);
					auto depth = parser_.tagDepth();

					if ((startLevel == depth) || (depth == 0))
						return;

					break;
				case XmlResult.STR_TEXT:
					auto parent = (elemStack_.length > 0) ? elemStack_.back() : null;
					if (parent !is null)
						parent.addText(tag.data);
					if (onText_ !is null)
						onText_(tag);
					break;
				default:

					break;
			}
		}
	}
	void explode()
	{

	}
}

alias ElementParser DocumentParser;