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 }