Mercurial > projects > mde
view mde/file/mergetag/MTTagReader.d @ 151:e785e98d3b78
Updated for compatibility with tango 0.99.8.
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Sat, 04 Apr 2009 17:32:18 +0200 |
parents | 9f035cd139c6 |
children |
line wrap: on
line source
/* LICENSE BLOCK Part of mde: a Modular D game-oriented Engine Copyright © 2007-2008 Diggory Hardy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ /****************************************************************************** * This module contains a simpler, easier to use, mergetag reader. *****************************************************************************/ module mde.file.mergetag.MTTagReader; import mde.file.mergetag.internal; import mde.file.mergetag.exception; import tango.io.UnicodeFile; import tango.io.FilePath; import Util = tango.text.Util; import tango.util.log.Log : Log, Logger; private Logger logger; static this() { logger = Log.getLogger ("mde.file.mergetag.MTTagReader"); } /** Make an TagReader class. * * Create an appropriate reader: MTTTagReader or MTBTagReader. * * Throws: * $(TABLE * $(TR $(TH Exception) $(TH Thrown when)) * $(TR $(TD MTFileIOException) $(TD When extension given is neither mtt nor mtb)) * ) * */ MTTagReader getMTTagReader (FilePath path) { if (path.ext == "mtb") return new MTBTagReader (path.toString); else if (path.ext == "mtt") return new MTTTagReader (path.toString); else throw new MTFileIOException ("Invalid mergetag extension"); } abstract class MTTagReader { /** The current section (null for header), tag type, tag id, and tag data. * * If the last call to readTag returned true, these are valid. */ char[] section, tagType, tagID, tagData; /** Read the next tag. * * The variables section, tagType, tagID and tagData are updated, dependant * on the type of tag read. * * Params: * sectionTag = If sectionTag is true, the read tag is a section marker, * not a data tag. * * Returns: * True if a tag has been read, false at EOF. * * Example usage: * ------ * TagReader reader = ...; * while (reader.readTag (sectionTag)) { * if (sectionTag) { * // section has changed, potentially do something... * } else { * // do something with the tag data... * } * } * ------ */ abstract bool readTag (out bool sectionTag); } class MTTTagReader : MTTagReader { private: // Non-static symbols: final char[] ErrInFile; // something like "in \"path/file.mtt\"" final char[] fbuf; // file is read into this size_t pos; // position within fbuf MTFormat fileVer = MTFormat.INVALID; // Remains INVALID until set otherwise by CTOR. bool fatal = false; // a fatal file error occured; don't try to recover //END DATA /** Tries to read file path and checks its header. * * Params: * path = The name or FilePath of the file to open. * Standard extensions are .mtt and .mtb for text and binary files respectively. * * No need to close later; the whole file is read within this(). */ public this (char[] path) { scope (failure) logger.warn ("Failure reading {}", path); // Open & read the file try { // Supports unicode files with a BOM; defaults to UTF8 when there isn't a BOM: scope file = new UnicodeFile!(char) (path, Encoding.Unknown); fbuf = cast(char[]) file.read(); } catch (Exception e) { throwMTErr ("Error reading file: " ~ e.msg, new MTFileIOException); } ErrInFile = " in \"" ~ path ~ '"'; // Version checking & matching header section tag: if (checkHeader(fbuf) != MTFormat.MT01) throwMTErr("Not a valid (known) MergeTag text file" ~ ErrInFile, new MTFileFormatException); pos = 6; section = CurrentVersionString; } public bool readTag (out bool sectionTag) { debug scope (failure) logger.trace ("MTTTagReader.readTag: failure"); if (fatal) return false; /* Searches fbuf starting from start to find one of <=>| and stops at its index. If quotable then be quote-aware for single and double quotes. Note: there's no length restriction for the content of the quote since it could be a single non-ascii UTF-8 char which would look like several chars. */ void fbufLocateDataTagChar (ref size_t pos, bool quotable) { while (true) { fbufIncrement (pos); if ((fbuf[pos] >= '<' && fbuf[pos] <= '>') || fbuf[pos] == '|') return; else if (quotable) { char c = fbuf[pos]; if (c == '\'' || c == '"') { fbufIncrement(pos); while (fbuf[pos] != c) { if (fbuf[pos] == '\\') ++pos; // escape seq. fbufIncrement(pos); } } } } } // Used to ignore a tag (if it starts !< or !{ or should otherwise be ignored): bool comment = false; while (pos < fbuf.length) { if (Util.isSpace(fbuf[pos])) { ++pos; continue; } else if (fbuf[pos] == '<') { // data tag char[] ErrDTAG = "Bad data tag format: not <type|id=data>" ~ ErrInFile; // Type section of tag: size_t pos_s = pos + 1; fbufLocateDataTagChar (pos, false); // find end of type section if (fbuf[pos] != '|') throwMTErr (ErrDTAG, new MTSyntaxException); tagType = Util.trim (fbuf[pos_s..pos]); // char[] section of tag: pos_s = pos + 1; fbufLocateDataTagChar (pos, false); // find end of type section if (fbuf[pos] != '=') throwMTErr (ErrDTAG, new MTSyntaxException); tagID = cast(char[]) fbuf[pos_s..pos]; // Data section of tag: pos_s = pos + 1; fbufLocateDataTagChar (pos, true); // find end of data section if (fbuf[pos] != '>') throwMTErr (ErrDTAG, new MTSyntaxException); tagData = fbuf[pos_s..pos]; ++pos; if (!comment) { return true; // got a tag } else comment = false; // cancel comment status now } else if (fbuf[pos] == '{') { fbufIncrement (pos); size_t start = pos; uint depth = 0; // depth of embedded {} blocks while (true) { if (fbuf[pos] == '}') { if (depth == 0) break; else --depth; } else if (fbuf[pos] == '{') ++depth; fbufIncrement (pos); } fbufIncrement(pos); if (comment) { // simple block comment comment = false; // end of this comment } else { section = cast(char[]) fbuf[start..pos-1]; sectionTag = true; return true; } } else if (fbuf[pos] == '!') { // possibly a comment; check next char comment = true; // starting a comment ++pos; } else // must be an error throwMTErr ("Invalid character '"~fbuf[pos..pos+1]~"' (or sequence starting \"!\") outside of tag" ~ ErrInFile, new MTSyntaxException); } return false; // EOF } /* Increments pos and checks it hasn't hit fbuf.length . */ private void fbufIncrement(ref size_t pos) { ++pos; if (pos >= fbuf.length) throwMTErr("Unexpected EOF" ~ ErrInFile, new MTSyntaxException); } private void throwMTErr (char[] msg, MTException exc = new MTException) { fatal = true; // if anyone catches the error and tries to do anything --- we're dead now logger.error (msg); // report the error throw exc; // and signal our error } } /** * Class for reading a mergetag text file. * * Currently only a dummy class: a MTNotImplementedException will be thrown if created. */ class MTBTagReader : MTTagReader { public this (char[] path) { throw new MTNotImplementedException; } bool readTag (out bool sectionTag) { return false; } } /** A special adapter for reading from multiple mergetag files. */ class MTMultiTagReader : MTTagReader { this (FilePath[] files) in { assert (files !is null, "MTMultiTagReader.this: files is null"); } body { Exception exc; foreach (file; files) { try { // try reading each file MTTagReader r = getMTTagReader (file); readers ~= r; } catch (Exception e) { exc = e; } } if (readers.length == 0) // no files have valid headers throw exc; // fail: re-throw last exception } bool readTag (out bool sectionTag) { while (readers.length) { // something left to read MTTagReader r = readers[0]; bool ret = r.readTag (sectionTag); if (ret) { section = r.section; tagType = r.tagType; tagID = r.tagID; tagData = r.tagData; return true; } delete r; readers = readers[1..$]; // done with that reader } return false; } private: MTTagReader[] readers; }