1 module pixelperfectengine.system.lang.textparser;
2 
3 public import pixelperfectengine.graphics.fontsets;
4 public import pixelperfectengine.graphics.bitmap;
5 
6 public import pixelperfectengine.graphics.text;
7 public import pixelperfectengine.system.exc;
8 
9 import xml = undead.xml;
10 import std.utf : toUTF32, toUTF8;
11 import std.conv : to;
12 import std.algorithm : countUntil;
13 
14 /**
15  * Parses text from XML/ETML
16  *
17  * See "ETML.md" for info.
18  *
19  * Constraints:
20  * * Due to the poor documentation of the replacement XML libraries, I have to use Phobos's own and outdated library.
21  * * <text> chunks are mandatory with ID.
22  * * Certain functions may not be fully implemented.
23  */
24 /+
25 public class TextParserTempl(BitmapType = Bitmap8Bit)
26 		 {
27 	//private Text!BitmapType	 		current;	///Current element
28 	//private Text!BitmapType			_output;	///Root/output element
29 	//private Text!BitmapType[] 		stack;		///Mostly to refer back to previous elements
30 	public TextTempl!BitmapType[string]	output;		///All texts found within the ETML file
31 	private TextTempl!BitmapType chunkRoot;
32 	private TextTempl!BitmapType currTextBlock;
33 	private CharacterFormattingInfo!BitmapType	currFrmt;///currently parsed formatting
34 	public CharacterFormattingInfo!BitmapType[]	chrFrmt;///All character format, that has been parsed so far
35 	public Fontset!BitmapType[]		fontStack;	///Fonttype formatting stack. Used for referring back to previous font types.
36 	public uint[]					colorStack;
37 	private CharacterFormattingInfo!BitmapType	defFrmt;///Default character formatting. Must be set before parsing
38 	public Fontset!BitmapType[string]	fontsets;///Fontset name association table
39 	public BitmapType[string]		icons;		///Icon name association table
40 	private string					_input;		///The source XML/ETML document
41 	///Constructor with no arguments
42 	public this() @safe pure nothrow {
43 		reset();
44 	}
45 	///Creates a new instance with a select string input.
46 	public this(string _input) @safe pure nothrow {
47 		this._input = _input;
48 		__ctor();
49 	}
50 	///Resets the output, but keeps the public parameters.
51 	///Input must be set to default or to new target separately.
52 	public void reset() @safe pure nothrow {
53 		current = new Text!BitmapType("", null);
54 		//_output = current;
55 		stack ~= current;
56 	}
57 	///Sets the default formatting
58 	public CharacterFormattingInfo!BitmapType defaultFormatting(CharacterFormattingInfo!BitmapType val) @property @safe
59 			pure nothrow {
60 		if (stack[0].formatting is null) {
61 			stack[0].formatting = val;
62 		}
63 		defFrmt = val;
64 		return defFrmt;
65 	}
66 	///Gets the default formatting
67 	public CharacterFormattingInfo!BitmapType defaultFormatting() @property @safe pure nothrow @nogc {
68 		return defFrmt;
69 	}
70 	///Returns the root/output element
71 	
72 	///Sets/gets the input
73 	public string input() @property @safe @nogc pure nothrow inout {
74 		return _input;
75 	}
76 	/**
77 	 * Parses the formatted text, then returns the output.
78 	 */
79 	public void parse() @trusted {
80 		xml.check(_input);
81 		string currTextChunkID;
82 		currFrmt = new CharacterFormattingInfo!BitmapType(defFrmt);
83 		fontStack ~= currFrmt.fontType;
84 		colorStack ~= currFrmt.color;
85 		auto parser = new xml.DocumentParser(_input);
86 		parser.onStartTag["text"] = (xml.ElementParser parser) {
87 			currTextChunkID = parser.tag.attr["id"];
88 			chunkRoot = new TextTempl!BitmapType();
89 			currTextBlock = chunkRoot;
90 			
91 			output[currTextChunkID] = chunkRoot;
92 			parseRecursively(parser);
93 		};
94 		parser.parse;
95 	}
96 	//block for parsing
97 	private void onText(string s) {
98 		currTextBlock.text ~= toUTF32(s);
99 	}
100 	private void onEndTag_br(xml.Element e) {
101 		currTextBlock.text ~= FormattingCharacters.newLine;
102 	}
103 	private void onStartTag_p(xml.ElementParser parser) {
104 		currTextBlock.text ~= FormattingCharacters.newParagraph;
105 		if(parser.tag.attr.length) {
106 			currFrmt.paragraphSpace = parser.tag.attr.get("paragraphSpace", defFrmt.paragraphSpace);
107 			currFrmt.rowHeight = parser.tag.attr.get("rowHeight", defFrmt.paragraphSpace);
108 			createNextTextChunk();
109 		}
110 		parseRecursively (parser);
111 	}
112 	private void onStartTag_u(xml.ElementParser parser) {
113 		currFrmt.formatFlags |= FormattingFlags.underline;
114 		createNextTextChunk();
115 		parseRecursively (parser);
116 	}
117 	private void onStartTag_s(xml.ElementParser parser) {
118 		currFrmt.formatFlags |= FormattingFlags.strikeThrough;
119 		createNextTextChunk();
120 		parseRecursively (parser);
121 	}
122 	private void onStartTag_o(xml.ElementParser parser) {
123 		currFrmt.formatFlags |= FormattingFlags.overline;
124 		createNextTextChunk();
125 		parseRecursively (parser);
126 	}
127 	private void onStartTag_i(xml.ElementParser parser) {
128 		const string amount = parser.tag.attr.get("amount", "0");
129 		currFrmt.formatFlags &= !FormattingFlags.forceItalicsMask;
130 		switch(amount) {
131 			case "1/2":
132 				currFrmt.formatFlags |= FormattingFlags.forceItalics1per2;
133 				break;
134 			case "1/3":
135 				currFrmt.formatFlags |= FormattingFlags.forceItalics1per3;
136 				break;
137 			case "1/4":
138 				currFrmt.formatFlags |= FormattingFlags.forceItalics1per4;
139 				break;
140 			default:
141 				currFrmt.formatFlags |= defFrmt.formatFlags & FormattingFlags.forceItalicsMask;
142 				break;
143 		}
144 		createNextTextChunk();
145 		parseRecursively (parser);
146 	}
147 	private void onStartTag_font(xml.ElementParser parser) {
148 		const string fontName = parser.tag.attr.get("type", "");
149 		const uint color = to!uint(parser.tag.attr.get(color, colorStack[$-1]));
150 		Fontset!BitmapType fontType;
151 		if(fontType.length) {
152 			fontType = fontsets.get(fontName, null);
153 			if (fontType is null)
154 				throw new XMLTextParsingException("Unknown fonttype!");
155 		} else {
156 			fontType = fontStack[$-1];
157 		}
158 		fontStack ~= fontType;
159 		colorStack ~= color;
160 		currFrmt.color = cast(ubyte)color;
161 		currFrmt.fontType = fontType;
162 		createNextTextChunk();
163 		parseRecursively (parser);
164 	}
165 	private void onEndTag_u(xml.Element e) {
166 		currFrmt.formatFlags &= !FormattingFlags.underline;
167 		createNextTextChunk();
168 	}
169 	private void onEndTag_s(xml.Element e) {
170 		currFrmt.formatFlags &= !FormattingFlags.strikeThrough;
171 		createNextTextChunk();
172 	}
173 	private void onEndTag_o(xml.Element e) {
174 		currFrmt.formatFlags &= !FormattingFlags.overline;
175 		createNextTextChunk();
176 	}
177 	private void onEndTag_i(xml.Element e) {
178 		currFrmt.formatFlags &= !FormattingFlags.forceItalicsMask;
179 		createNextTextChunk();
180 	}
181 	private void onEndTag_font(xml.Element e) {
182 		currFrmt.color = cast(ubyte)colorStack[$-2];
183 		currFrmt.fontType = fontStack[$-2];
184 		colorStack.length--;
185 		fontStack.length--;
186 		createNextTextChunk();
187 	}
188 	private void onStartTag_frontTab(xml.ElementParser parser) {
189 		parseRecursively (parser);
190 	}
191 	private void onStartTag_image(xml.ElementParser parser) {
192 		parseRecursively (parser);
193 	}
194 	///Creates the next text chunk if needed, and also checks the formatting stack.
195 	///Called by the appropriate functions.
196 	///Also handles the character formatting stack
197 	private void createNextTextChunk() {
198 		if(currTextBlock.text.length || currTextBlock.icon !is null){
199 			currTextBlock = new TextTempl!BitmapType(null, null, currTextBlock);
200 			const ptrdiff_t frmtPos = countUntil(chrFrmt, currFrmt);
201 			if (frmtPos == -1){ 
202 				chrFrmt ~= new CharacterFormattingInfo(currFrmt);
203 				currTextBlock.formatting = chrFrmt[$-1];
204 			} else {
205 				currTextBlock.formatting = chrFrmt[frmtPos];
206 			}
207 		}
208 	}
209 	///Called for every instance when there might be additional tags to be parsed.
210 	private void parseRecursively(xml.ElementParser parser) {
211 		parser.onText = &onText;
212 		parser.onEndTag["br"] = &onEndTag_br;
213 		parser.onStartTag["p"] = &onStartTag_p;
214 		parser.parse;
215 	}
216 }+/
217 //alias TextParser = TextParserTempl!Bitmap8Bit;
218 public class XMLTextParsingException : PPEException {
219 	@nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null)
220 			{
221 		super(msg, file, line, nextInChain);
222 	}
223 
224 	@nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) {
225 		super(msg, file, line, nextInChain);
226 	}
227 }