[dasher: 12/28] File searching w/ glob wildcards; RM SP_{USER,SYSTEM}_LOC; drop XML trainfiles



commit 8edc8186d07db99a1eedcf7e2a32be02dab667c2
Author: Alan Lawrence <acl33 inf phy cam ac uk>
Date:   Wed Sep 14 15:18:16 2011 +0100

    File searching w/ glob wildcards; RM SP_{USER,SYSTEM}_LOC; drop XML trainfiles
    
    AbstractParser superclass w/ Scan(istream&, bUser) for all input, not just XML
    Single method CDasherInterfaceBase::ScanFiles(AbstractParser*, string&)
      feeds all files in system/user location(s) matching pattern to parser.
    
    Common/Globber.h uses glob in <glob.h> for Mac, iPhone, Gtk2; Win32 separate.
    
    Messages.cpp adds FormatMessageWithString for common-case of exactly one %s.
    
    Note, this allows an alphabet to specify a wildcard for its game sentences file
      (=>will use last one) and/or training file (=>use all matches, but user text
       will be written to a filename containing literal '*'s - TODO sthg better?)

 Src/Common/Globber.cpp                       |   35 ++++++
 Src/Common/Globber.h                         |   17 +++
 Src/Common/Makefile.am                       |    2 +
 Src/DasherCore/AbstractXMLParser.cpp         |   55 +++++++---
 Src/DasherCore/AbstractXMLParser.h           |   40 ++++++-
 Src/DasherCore/Alphabet/AlphIO.cpp           |   19 +---
 Src/DasherCore/Alphabet/AlphIO.h             |    9 +-
 Src/DasherCore/AlphabetManager.cpp           |   21 ++--
 Src/DasherCore/ColourIO.cpp                  |   20 +---
 Src/DasherCore/ColourIO.h                    |   13 ++-
 Src/DasherCore/ControlManager.cpp            |  158 ++++++++++++--------------
 Src/DasherCore/ControlManager.h              |   24 +++-
 Src/DasherCore/DasherInterfaceBase.cpp       |   14 +--
 Src/DasherCore/DasherInterfaceBase.h         |   43 +++-----
 Src/DasherCore/FileWordGenerator.cpp         |   13 ++-
 Src/DasherCore/FileWordGenerator.h           |   29 ++---
 Src/DasherCore/Makefile.am                   |    1 +
 Src/DasherCore/Messages.cpp                  |   50 ++++++++
 Src/DasherCore/Messages.h                    |    4 +
 Src/DasherCore/NodeCreationManager.cpp       |   48 ++++----
 Src/DasherCore/Parameters.cpp                |    2 -
 Src/DasherCore/Parameters.h                  |    2 +-
 Src/DasherCore/Trainer.cpp                   |   90 +++------------
 Src/DasherCore/Trainer.h                     |   18 +--
 Src/Gtk2/DasherControl.cpp                   |   98 ++++------------
 Src/Gtk2/DasherControl.h                     |    7 +-
 Src/MacOSX/COSXDasherControl.h               |    8 +-
 Src/MacOSX/COSXDasherControl.mm              |   70 +++---------
 Src/MacOSX/Dasher.xcodeproj/project.pbxproj  |   12 ++
 Src/Win32/Dasher.cpp                         |   84 +++++---------
 Src/Win32/Dasher.h                           |    4 +-
 Src/iPhone/Classes/CDasherInterfaceBridge.h  |    6 +-
 Src/iPhone/Classes/CDasherInterfaceBridge.mm |   71 +++++-------
 Src/iPhone/Dasher.xcodeproj/project.pbxproj  |   10 ++
 34 files changed, 524 insertions(+), 573 deletions(-)
---
diff --git a/Src/Common/Globber.cpp b/Src/Common/Globber.cpp
new file mode 100644
index 0000000..9a4153b
--- /dev/null
+++ b/Src/Common/Globber.cpp
@@ -0,0 +1,35 @@
+//
+//  Globber.cpp
+//  Dasher
+//
+//  Created by Alan Lawrence on 21/9/11.
+//  Copyright 2011 Cambridge University. All rights reserved.
+//
+
+#include "Globber.h"
+#include <glob.h>
+#include <string.h>
+
+void globScan(AbstractParser *parser, const char **usrPaths, const char **sysPaths) {
+  int flags = GLOB_MARK | GLOB_NOSORT;
+  glob_t info;
+  int numUser = 0;
+  while (*usrPaths) {
+    glob(*usrPaths++, flags, NULL, &info); //NULL error function
+    flags |= GLOB_APPEND;
+  }
+  numUser = info.gl_pathc;
+  while (*sysPaths) {
+    glob(*sysPaths++, flags, NULL, &info);
+    flags |= GLOB_APPEND;
+  }
+  
+  if (info.gl_pathc) {
+    //user paths first
+    for (char **fname = info.gl_pathv; *fname; fname++, numUser=(numUser>0 ? numUser-1 : 0)) {
+      if ((*fname)[strlen(*fname)-1]=='/') continue; //directories were marked by GLOB_MARK
+      parser->ParseFile(*fname, numUser>0);
+    }
+  }
+  globfree(&info);
+}
diff --git a/Src/Common/Globber.h b/Src/Common/Globber.h
new file mode 100644
index 0000000..69ee08f
--- /dev/null
+++ b/Src/Common/Globber.h
@@ -0,0 +1,17 @@
+//
+//  Globber.h
+//  Dasher
+//
+//  Created by Alan Lawrence on 21/9/11.
+//  Copyright 2011 Cambridge University. All rights reserved.
+//
+
+#ifndef __GLOBBER_H__
+#define __GLOBBER_H__
+
+#include "../DasherCore/AbstractXMLParser.h"
+
+void globScan(AbstractParser *parser,
+              const char **userPaths,
+              const char **systemPaths);
+#endif
diff --git a/Src/Common/Makefile.am b/Src/Common/Makefile.am
index fe1dcff..6ecafdf 100644
--- a/Src/Common/Makefile.am
+++ b/Src/Common/Makefile.am
@@ -3,6 +3,8 @@ libdashermisc_a_SOURCES = \
                 AppSettingsData.h \
                 AppSettingsHeader.h \
 		Common.h \
+		Globber.h \
+		Globber.cpp \
 		Hash.h \
 		I18n.h \
 		MSVC_Unannoy.h \
diff --git a/Src/DasherCore/AbstractXMLParser.cpp b/Src/DasherCore/AbstractXMLParser.cpp
index 14a567d..6d5d8d0 100644
--- a/Src/DasherCore/AbstractXMLParser.cpp
+++ b/Src/DasherCore/AbstractXMLParser.cpp
@@ -9,17 +9,35 @@
 
 #include "AbstractXMLParser.h"
 
-#include <string.h>
+#include <fstream>
+#include <stdio.h>
+#include <cstring>
 
-using std::string;
+using namespace std;
 
-bool AbstractXMLParser::ParseFile(CMessageDisplay *pMsgs, const std::string &strFilename) {
-  FILE *Input;
-  if((Input = fopen(strFilename.c_str(), "r")) == (FILE *) 0) {
-    // could not open file
-    return false;
-  }
+bool AbstractParser::ParseFile(const string &strPath, bool bUser) {
+  std::ifstream in(strPath.c_str(), ios::binary);
+  bool res=Parse("file://"+strPath, in, bUser);
+  in.close();
+  return res;
+}
+
+AbstractXMLParser::AbstractXMLParser(CMessageDisplay *pMsgs) : AbstractParser(pMsgs) {
+}
 
+bool AbstractXMLParser::isUser() {
+  return m_bUser;
+}
+
+bool AbstractXMLParser::Parse(const std::string &strDesc, istream &in, bool bUser) {
+  if (!in.good()) return false;
+  
+  //we'll be re-entrant (i.e. allow nested calls), as it's not difficult here...
+  const bool bOldUser = m_bUser;
+  const string strOldDesc = m_strDesc;
+  m_bUser = bUser;
+  m_strDesc = strDesc;
+  
   XML_Parser Parser = XML_ParserCreate(NULL);
 
   // Members passed as callbacks must be static, so don't have a "this" pointer.
@@ -32,17 +50,22 @@ bool AbstractXMLParser::ParseFile(CMessageDisplay *pMsgs, const std::string &str
   char Buffer[1024];
   int Done;
   do {
-    size_t len = fread(Buffer, 1, sizeof(Buffer), Input);
+    in.read(Buffer, sizeof(Buffer));
+    size_t len = in.gcount();
     Done = len < sizeof(Buffer);
     if(XML_Parse(Parser, Buffer, len, Done) == XML_STATUS_ERROR) {
       bRes=false;
-      if (pMsgs) {
-        const char *msg=_("XML Error %s in file %s somewhere in block: %s");
+      if (m_pMsgs) {
         const XML_LChar *xmle=XML_ErrorString(XML_GetErrorCode(Parser)); //think XML_LChar==char, depends on preprocessor variables...
-        char *buf(new char[strlen(msg)+ strlen(xmle) + strFilename.length() + len + 3]);
-        //constructing a string here to get a null terminator. Can we put a null into Buffer instead?
-        sprintf(buf, msg, xmle, strFilename.c_str(), string(Buffer,len).c_str());
-        pMsgs->Message(buf,true);
+        
+        ///TRANSLATORS: the first string is the error message from the XML Parser;
+        /// the second is the URL of the file we're trying to read; the third
+        /// is a section excerpt from the file, containing the error.
+        const char *msg=_("XML Error %s in %s somewhere in block: %s");
+        //we can't use FormatMessage as we have too many substitutions...
+        char *buf(new char[strlen(msg) + strlen(xmle) + m_strDesc.length() + len]);
+        sprintf(buf, xmle, m_strDesc.c_str(), string(Buffer,len).c_str());
+        m_pMsgs->Message(buf, true);
         delete buf;
       }
       break;
@@ -50,7 +73,7 @@ bool AbstractXMLParser::ParseFile(CMessageDisplay *pMsgs, const std::string &str
   } while (!Done);
 
   XML_ParserFree(Parser);
-  fclose(Input);
+  m_bUser = bOldUser; m_strDesc = strOldDesc;
   return bRes;
 }
 
diff --git a/Src/DasherCore/AbstractXMLParser.h b/Src/DasherCore/AbstractXMLParser.h
index a02d0b4..70ced74 100644
--- a/Src/DasherCore/AbstractXMLParser.h
+++ b/Src/DasherCore/AbstractXMLParser.h
@@ -19,18 +19,46 @@
 
 #include <string>
 #include <expat.h>
+#include <iostream>
+
+class AbstractParser {
+public:
+  AbstractParser(CMessageDisplay *pMsgs) : m_pMsgs(pMsgs) { }
+  ///Utility method: constructs an ifstream to read from the specified file,
+  /// then calls Parse(string&,istream&,bool) with the description 'file://strPath'
+  virtual bool ParseFile(const std::string &strPath, bool bUser);
+  
+  /// \param strDesc string to display to user to identify the source of this data,
+  /// if there is an error. (Suggest: use a url, e.g. file://...)
+  /// \param bUser if True, the file is from a user location (editable), false if from a
+  /// system one. (Some subclasses treat the data differently according to which of these
+  /// it is from.)
+  virtual bool Parse(const std::string &strDesc, std::istream &in, bool bUser) = 0;
+  
+protected:
+  ///The MessageDisplay to use to inform the user. Subclasses should use this
+  /// too for any (e.g. semantic) errors they may detect.
+  CMessageDisplay * const m_pMsgs;
+};
 
 ///Basic wrapper over (Expat) XML Parser, handling file IO and wrapping C++
 /// virtual methods over C callbacks. Subclasses must implement methods to
 /// handle actual tags.
-class AbstractXMLParser {
+class AbstractXMLParser : public AbstractParser {
 public:
   ///Parse (the whole) file - done in chunks to avoid loading the whole thing into memory.
-  /// \param pInterface if non-null, any errors _besides_ file-not-found, will be passed
-  /// to the Message(,true) method to report to the user.
-  /// \return true if the file was opened+parsed OK; false if there was an error (e.g. FNF)
-  bool ParseFile(CMessageDisplay *pMsgs, const std::string &strFilename);
+  /// Any errors _besides_ file-not-found, will be passed to m_pMsgs as modal messages.
+  virtual bool Parse(const std::string &strDesc, std::istream &in, bool bUser);
 protected:
+  ///Create an AbstractXMLParser which will use the specified MessageDisplay to
+  /// inform the user of any errors.
+  AbstractXMLParser(CMessageDisplay *pMsgs);
+
+  ///Subclasses may call to get the description of the current file
+  const std::string &GetDesc();
+  ///Subclasses may call to determine if the current file is from a user location
+  bool isUser();
+  
   ///Subclass should override to handle a start tag
   virtual void XmlStartHandler(const XML_Char *name, const XML_Char **atts)=0;
   ///Subclass should override to handle an end tag
@@ -51,6 +79,8 @@ private:
   static void XML_StartElement(void *userData, const XML_Char * name, const XML_Char ** atts);
   static void XML_EndElement(void *userData, const XML_Char * name);
   static void XML_CharacterData(void *userData, const XML_Char * s, int len);
+  bool m_bUser;
+  std::string m_strDesc;
 };
 
 #endif
diff --git a/Src/DasherCore/Alphabet/AlphIO.cpp b/Src/DasherCore/Alphabet/AlphIO.cpp
index e92abf0..8a506f6 100644
--- a/Src/DasherCore/Alphabet/AlphIO.cpp
+++ b/Src/DasherCore/Alphabet/AlphIO.cpp
@@ -36,8 +36,7 @@ static char THIS_FILE[] = __FILE__;
 #endif
 #endif
 
-CAlphIO::CAlphIO(CMessageDisplay *pMsgs, const std::string &SystemLocation, const std::string &UserLocation, const std::vector<std::string> &Filenames)
-: LoadMutable(false), CData("") {
+CAlphIO::CAlphIO(CMessageDisplay *pMsgs) : AbstractXMLParser(pMsgs) {
   Alphabets["Default"]=CreateDefault();
 
   typedef pair < Opts::AlphabetTypes, std::string > AT;
@@ -62,20 +61,6 @@ CAlphIO::CAlphIO(CMessageDisplay *pMsgs, const std::string &SystemLocation, cons
     TtoS[Types[i].first] = Types[i].second;
   }
 
-  LoadMutable = false;
-  ParseFile(pMsgs, SystemLocation + "alphabet.xml");
-  if(Filenames.size() > 0) {
-    for(unsigned int i = 0; i < Filenames.size(); i++) {
-      ParseFile(pMsgs, SystemLocation + Filenames[i]);
-    }
-  }
-  LoadMutable = true;
-  ParseFile(pMsgs, UserLocation + "alphabet.xml");
-  if(Filenames.size() > 0) {
-    for(unsigned int i = 0; i < Filenames.size(); i++) {
-      ParseFile(pMsgs, UserLocation + Filenames[i]);
-    }
-  }
 }
 
 void CAlphIO::GetAlphabets(std::vector <std::string >*AlphabetList) const {
@@ -182,7 +167,7 @@ void CAlphIO::XmlStartHandler(const XML_Char *name, const XML_Char **atts) {
 
   if(strcmp(name, "alphabet") == 0) {
     InputInfo = new CAlphInfo();
-    InputInfo->Mutable = LoadMutable;
+    InputInfo->Mutable = isUser();
     ParagraphCharacter = NULL;
     SpaceCharacter = NULL;
     iGroupIdx = 0;
diff --git a/Src/DasherCore/Alphabet/AlphIO.h b/Src/DasherCore/Alphabet/AlphIO.h
index ce9f32d..529df9f 100644
--- a/Src/DasherCore/Alphabet/AlphIO.h
+++ b/Src/DasherCore/Alphabet/AlphIO.h
@@ -47,10 +47,14 @@ namespace Dasher {
 /// object per alphabet at this time, and stores them in a map from AlphID
 /// string until shutdown/destruction. (CAlphIO is a friend of CAlphInfo,
 /// so can create/manipulate instances.)
-class Dasher::CAlphIO : private AbstractXMLParser {
+class Dasher::CAlphIO : public AbstractXMLParser {
 public:
 
-  CAlphIO(CMessageDisplay *pMsgs, const std::string &SystemLocation, const std::string &UserLocation, const std::vector < std::string > &Filenames);
+  ///Create a new AlphIO. Initially, it will have only a 'default' alphabet
+  /// definition (English); further alphabets may be loaded in by calling the
+  /// Parse... methods inherited from Abstract[XML]Parser
+  CAlphIO(CMessageDisplay *pMsgs);
+  
   ~CAlphIO();
   void GetAlphabets(std::vector < std::string > *AlphabetList) const;
   std::string GetDefault();
@@ -67,7 +71,6 @@ private:
   // XML handling:
   /////////////////////////
 
-  bool LoadMutable;
   void ReadCharAtts(const XML_Char **atts, CAlphInfo::character &ch);
   // Alphabet types:
   std::map < std::string, Opts::AlphabetTypes > StoT;
diff --git a/Src/DasherCore/AlphabetManager.cpp b/Src/DasherCore/AlphabetManager.cpp
index 9f8d5e4..c52c7f6 100644
--- a/Src/DasherCore/AlphabetManager.cpp
+++ b/Src/DasherCore/AlphabetManager.cpp
@@ -152,24 +152,25 @@ CAlphabetManager::SGroupInfo *CAlphabetManager::copyGroups(CDasherScreen *pScree
 }
 
 CWordGeneratorBase *CAlphabetManager::GetGameWords() {
-  CFileWordGenerator *pGen = new CFileWordGenerator(m_pAlphabet, m_pAlphabetMap);
+  CFileWordGenerator *pGen = new CFileWordGenerator(m_pInterface, m_pAlphabet, m_pAlphabetMap);
+  pGen->setAcceptUser(true);
   if (!GetStringParameter(SP_GAME_TEXT_FILE).empty()) {
     const string &gtf(GetStringParameter(SP_GAME_TEXT_FILE));
-    if (pGen->open(gtf)) return pGen;
+    if (pGen->ParseFile(gtf,true)) return pGen;
     ///TRANSLATORS: the string "GameTextFile" is the name of a setting in gsettings
     /// (or equivalent), and should not be translated. The %s is the value of that
     /// setting (this message displayed only if the user has provided a value)
-    const char *msg=_("Note: GameTextFile setting specifies game sentences file '%s' but this does not exist");
-    char *buf(new char[strlen(msg)+gtf.length()]);
-    sprintf(buf,msg,gtf.c_str());
-    m_pInterface->Message(buf,false);
-    delete buf;
+    m_pInterface->FormatMessageWithString(_("Note: GameTextFile setting specifies game sentences file '%s' but this does not exist"),gtf.c_str());
   }
   if (!m_pAlphabet->GetGameModeFile().empty()) {
-    if (pGen->open(GetStringParameter(SP_USER_LOC) + m_pAlphabet->GetGameModeFile())) return pGen;
-    if (pGen->open(GetStringParameter(SP_SYSTEM_LOC) + m_pAlphabet->GetGameModeFile())) return pGen;
+    //TODO, try user dir first / give one or other priority?
+    // This will concatenate all - which doesn't seem too bad...?
+    m_pInterface->ScanFiles(pGen, m_pAlphabet->GetGameModeFile());
+    if (pGen->HasLines()) return pGen;
   }
-  if (pGen->open(GetStringParameter(SP_SYSTEM_LOC) + m_pAlphabet->GetTrainingFile())) return pGen;
+  pGen->setAcceptUser(false);
+  m_pInterface->ScanFiles(pGen, m_pAlphabet->GetTrainingFile());
+  if (pGen->HasLines()) return pGen;
   delete pGen;
   return NULL;
 }
diff --git a/Src/DasherCore/ColourIO.cpp b/Src/DasherCore/ColourIO.cpp
index 5df2232..b7d7b04 100644
--- a/Src/DasherCore/ColourIO.cpp
+++ b/Src/DasherCore/ColourIO.cpp
@@ -23,24 +23,8 @@ static char THIS_FILE[] = __FILE__;
 
 // TODO: Share information with AlphIO class?
 
-CColourIO::CColourIO(CMessageDisplay *pMsgs, const string &SystemLocation, const string &UserLocation, const vector<string> &Filenames)
-:BlankInfo(), LoadMutable(false), CData("") {
+CColourIO::CColourIO(CMessageDisplay *pMsgs) : AbstractXMLParser(pMsgs), BlankInfo() {
   CreateDefault();
-
-  LoadMutable = false;
-  ParseFile(pMsgs, SystemLocation + "colour.xml");
-  if(Filenames.size() > 0) {
-    for(unsigned int i = 0; i < Filenames.size(); i++) {
-      ParseFile(pMsgs, SystemLocation + Filenames[i]);
-    }
-  }
-  LoadMutable = true;
-  ParseFile(pMsgs, UserLocation + "colour.xml");
-  if(Filenames.size() > 0) {
-    for(unsigned int i = 0; i < Filenames.size(); i++) {
-      ParseFile(pMsgs, UserLocation + Filenames[i]);
-    }
-  }
 }
 
 void CColourIO::GetColours(std::vector <std::string >*ColourList) const {
@@ -816,7 +800,7 @@ void CColourIO::XmlStartHandler(const XML_Char *name, const XML_Char **atts) {
   if(strcmp(name, "palette") == 0) {
     ColourInfo NewInfo;
     InputInfo = NewInfo;
-    InputInfo.Mutable = LoadMutable;
+    InputInfo.Mutable = isUser();
     while(*atts != 0) {
       if(strcmp(*atts, "name") == 0) {
         InputInfo.ColourID = *(atts+1);
diff --git a/Src/DasherCore/ColourIO.h b/Src/DasherCore/ColourIO.h
index e707a4d..1f115ad 100644
--- a/Src/DasherCore/ColourIO.h
+++ b/Src/DasherCore/ColourIO.h
@@ -24,7 +24,9 @@ namespace Dasher {
 
 /// \defgroup Colours Colour scheme information
 /// @{
-class Dasher::CColourIO : private AbstractXMLParser {
+/// Class for reading in colour-scheme definitions, and storing all read schemes
+/// in a list.
+class Dasher::CColourIO : public AbstractXMLParser {
 public:
   // This structure completely describes the characters used in alphabet
   struct ColourInfo {
@@ -38,8 +40,11 @@ public:
     std::vector < int >Greens;
     std::vector < int >Blues;
   };
-
-  CColourIO(CMessageDisplay *pMsgs, const std::string &SystemLocation, const std::string &UserLocation, const std::vector < std::string > &Filenames);
+  
+  ///Construct a new ColourIO. It will have only a 'default' colour scheme;
+  /// further schemes may be loaded in by calling the Parse... methods inherited
+  /// from Abstract[XML]Parser.
+  CColourIO(CMessageDisplay *pMsgs);
   void GetColours(std::vector < std::string > *ColourList) const;
   const ColourInfo & GetInfo(const std::string & ColourID);
 private:
@@ -51,8 +56,6 @@ private:
   // XML handling:
   /////////////////////////
 
-  bool LoadMutable;
-
   // Data gathered
   std::string CData;            // Text gathered from when an elemnt starts to when it ends
   ColourInfo InputInfo;
diff --git a/Src/DasherCore/ControlManager.cpp b/Src/DasherCore/ControlManager.cpp
index 458a905..f75aabd 100644
--- a/Src/DasherCore/ControlManager.cpp
+++ b/Src/DasherCore/ControlManager.cpp
@@ -128,6 +128,8 @@ const vector<CControlBase::NodeTemplate *> &CControlParser::parsedNodes() {
   return m_vParsed;
 }
 
+///Template used for all node defns read in from XML - just
+/// execute a list of Actions.
 class XMLNodeTemplate : public CControlBase::NodeTemplate {
 public:
   XMLNodeTemplate(const string &label, int color) : NodeTemplate(label, color) {
@@ -146,92 +148,87 @@ public:
   vector<CControlBase::Action*> actions;
 };
 
-bool CControlParser::LoadFile(CMessageDisplay *pMsgs, const string &strFileName) {
-  ///Template used for all node defns read in from XML - just
-  /// execute a list of Actions.
-
-  class ParseHandler : public AbstractXMLParser {
-    typedef CControlBase::NodeTemplate NodeTemplate;
-  protected:
-    void XmlStartHandler(const XML_Char *name, const XML_Char **atts) {
-      vector<NodeTemplate *> &parent(nodeStack.empty() ? m_pMgr->m_vParsed : nodeStack.back()->successors);
-      if (strcmp(name,"node")==0) {
-        string label,nodeName; int color=-1;
-        while (*atts) {
-          if (strcmp(*atts,"name")==0) {
-            nodeName=*(atts+1);
-            DASHER_ASSERT(namedNodes.find(nodeName)==namedNodes.end());
-          } else if (strcmp(*atts,"label")==0) {
-            label = *(atts+1);
-          } else if (strcmp(*atts,"color")==0) {
-            color = atoi(*(atts+1));
-          }
-          atts+=2;
-        }
-        XMLNodeTemplate *n = new XMLNodeTemplate(label,color);
-        parent.push_back(n);
-        nodeStack.push_back(n);
-        if (nodeName!="")
-          namedNodes[nodeName]=n; //all refs resolved at end.
-      } else if (strcmp(name,"ref")==0) {
-        string target;
-        while (*atts) {
-          if (strcmp(*atts,"name")==0)
-            target=*(atts+1);
-          atts+=2;
-        }
-        map<string,NodeTemplate*>::iterator it=namedNodes.find(target);
-        if (it!=namedNodes.end())
-          parent.push_back(it->second);
-        else {
-          parent.push_back(NULL);
-          unresolvedRefs.push_back(pair<NodeTemplate**,string>(&(parent.back()),target));
-        }
-      } else if (strcmp(name,"alph")==0) {
-        parent.push_back(NULL);
-      } else if (NodeTemplate *n = m_pMgr->parseOther(name, atts)) {
-        parent.push_back(n);
-      } else if (CControlBase::Action *a=m_pMgr->parseAction(name, atts)) {
-        DASHER_ASSERT(!nodeStack.empty());
-        nodeStack.back()->actions.push_back(a);
-      }
-    }
+CControlParser::CControlParser(CMessageDisplay *pMsgs) : AbstractXMLParser(pMsgs) {
+}
+
+bool CControlParser::ParseFile(const string &strFileName, bool bUser) {
+  if (m_bUser) {
+    //have user files
+    if (!bUser) return true; //so ignore system!
+  } else {
+    //have system files (or none)
+    if (bUser) m_vParsed.clear(); //replace system with user
+    m_bUser = true;
+  }
+
+  namedNodes.clear();
+  unresolvedRefs.clear();
+  nodeStack.clear();
+  
+  if (!AbstractXMLParser::ParseFile(strFileName, bUser)) return false;
+  //resolve any forward references to nodes declared later
+  for (vector<pair<CControlBase::NodeTemplate**,string> >::iterator it=unresolvedRefs.begin(); it!=unresolvedRefs.end(); it++) {
+    map<string,CControlBase::NodeTemplate*>::iterator target = namedNodes.find(it->second);
+    if (target != namedNodes.end())
+      *(it->first) = target->second;
+  }
+  //somehow, need to clear out any refs that weren't resolved...???
+  return true;
+}
 
-    void XmlEndHandler(const XML_Char *szName) {
-      if (strcmp(szName,"node")==0) {
-        DASHER_ASSERT(!nodeStack.empty());
-        nodeStack.pop_back();
+void CControlParser::XmlStartHandler(const XML_Char *name, const XML_Char **atts) {
+  vector<CControlBase::NodeTemplate *> &parent(nodeStack.empty() ? m_vParsed : nodeStack.back()->successors);
+  if (strcmp(name,"node")==0) {
+    string label,nodeName; int color=-1;
+    while (*atts) {
+      if (strcmp(*atts,"name")==0) {
+        nodeName=*(atts+1);
+        DASHER_ASSERT(namedNodes.find(nodeName)==namedNodes.end());
+      } else if (strcmp(*atts,"label")==0) {
+        label = *(atts+1);
+      } else if (strcmp(*atts,"color")==0) {
+        color = atoi(*(atts+1));
       }
+      atts+=2;
     }
-
-  private:
-    ///Following only used in parsing...
-    map<string,NodeTemplate*> namedNodes;
-    vector<pair<NodeTemplate**,string> > unresolvedRefs;
-    vector<XMLNodeTemplate*> nodeStack;
-    CControlParser *m_pMgr;
-  public:
-    ParseHandler(CControlParser *pMgr) : m_pMgr(pMgr) {
+    XMLNodeTemplate *n = new XMLNodeTemplate(label,color);
+    parent.push_back(n);
+    nodeStack.push_back(n);
+    if (nodeName!="")
+      namedNodes[nodeName]=n; //all refs resolved at end.
+  } else if (strcmp(name,"ref")==0) {
+    string target;
+    while (*atts) {
+      if (strcmp(*atts,"name")==0)
+        target=*(atts+1);
+      atts+=2;
     }
-    void resolveRefs() {
-      //resolve any forward references to nodes declared later
-      for (vector<pair<NodeTemplate**,string> >::iterator it=unresolvedRefs.begin(); it!=unresolvedRefs.end(); it++) {
-        map<string,NodeTemplate*>::iterator target = namedNodes.find(it->second);
-        if (target != namedNodes.end())
-          *(it->first) = target->second;
-      }
-      //somehow, need to clear out any refs that weren't resolved...???
+    map<string,CControlBase::NodeTemplate*>::iterator it=namedNodes.find(target);
+    if (it!=namedNodes.end())
+      parent.push_back(it->second);
+    else {
+      parent.push_back(NULL);
+      unresolvedRefs.push_back(pair<CControlBase::NodeTemplate**,string>(&(parent.back()),target));
     }
-  };
+  } else if (strcmp(name,"alph")==0) {
+    parent.push_back(NULL);
+  } else if (CControlBase::NodeTemplate *n = parseOther(name, atts)) {
+    parent.push_back(n);
+  } else if (CControlBase::Action *a=parseAction(name, atts)) {
+    DASHER_ASSERT(!nodeStack.empty());
+    static_cast<XMLNodeTemplate*>(nodeStack.back())->actions.push_back(a);
+  }
+}
 
-  ParseHandler p(this);
-  if (!p.ParseFile(pMsgs, strFileName)) return false;
-  p.resolveRefs();
-  return true;
+void CControlParser::XmlEndHandler(const XML_Char *szName) {
+  if (strcmp(szName,"node")==0) {
+    DASHER_ASSERT(!nodeStack.empty());
+    nodeStack.pop_back();
+  }
 }
 
 CControlManager::CControlManager(CSettingsUser *pCreateFrom, CNodeCreationManager *pNCManager, CDasherInterfaceBase *pInterface)
-: CControlBase(pCreateFrom, pInterface, pNCManager), CSettingsObserver(pCreateFrom), m_pSpeech(NULL), m_pCopy(NULL) {
+: CControlParser(pInterface), CControlBase(pCreateFrom, pInterface, pNCManager), CSettingsObserver(pCreateFrom), m_pSpeech(NULL), m_pCopy(NULL) {
   //TODO, used to be able to change label+colour of root/pause/stop from controllabels.xml
   // (or, get the root node title "control" from the alphabet!)
   SetRootTemplate(new NodeTemplate("Control",8)); //default NodeTemplate does nothing
@@ -244,12 +241,7 @@ CControlManager::CControlManager(CSettingsUser *pCreateFrom, CNodeCreationManage
   m_pStop->successors.push_back(NULL);
   m_pStop->successors.push_back(GetRootTemplate());
 
-  //TODO, have a parameter to try first, and if that fails:
-  if(!LoadFile(m_pInterface, GetStringParameter(SP_USER_LOC) + "control.xml")) {
-    LoadFile(m_pInterface, GetStringParameter(SP_SYSTEM_LOC)+"control.xml");
-    //if that fails, we'll have no editing functions. Fine -
-    // doesn't seem vital enough to hardcode a fallback as well!
-  }
+  m_pInterface->ScanFiles(this, "control.xml"); //just look for the one
 
   updateActions();
 }
diff --git a/Src/DasherCore/ControlManager.h b/Src/DasherCore/ControlManager.h
index a07b58f..4968d9a 100644
--- a/Src/DasherCore/ControlManager.h
+++ b/Src/DasherCore/ControlManager.h
@@ -146,15 +146,20 @@ namespace Dasher {
   /// <alph/> tag, meaning one child of the node is to escape back to the alphabet. Subclasses
   /// may override parseAction to provide actions for the nodes to perform, also parseOther
   /// to link with NodeTemplates from other sources.
-  class CControlParser {
+  class CControlParser : public AbstractXMLParser {
+  public:
+    CControlParser(CMessageDisplay *pMsgs);
   protected:
-    ///Loads all node definitions from the specified filename, adding them to
-    /// any loaded from previous calls. (However, files processed independently:
+    ///Loads all node definitions from the specified filename. Note that
+    /// system files will not be loaded if user files are (and user files will
+    /// clear out any nodes from system ones). However, multiple system or multiple
+    /// user files, will be concatenated. (However, files are processed separately:
     /// e.g. names defined in one file will not be seen from another)
-    /// \param pMsgs Used to report errors via Message(,true) (i.e. modal)
     /// \param strFilename name+full-path of xml file to load
+    /// \param bUser true if from user-specific location (takes priority over system)
     /// \return true if the file was opened successfully; false if not.
-    bool LoadFile(CMessageDisplay *pMsgs, const std::string &strFilename);
+    bool ParseFile(const std::string &strFilename, bool bUser);
+    
     /// \return all node definitions that have been loaded by this CControlParser.
     const vector<CControlBase::NodeTemplate*> &parsedNodes();
     ///Subclasses may override to parse other nodes (besides "node", "ref" and "alph").
@@ -170,9 +175,18 @@ namespace Dasher {
       return NULL;
     };
     //TODO cleanup/deletion
+    void XmlStartHandler(const XML_Char *name, const XML_Char **atts);
+    void XmlEndHandler(const XML_Char *szName);
   private:
     ///all top-level parsed nodes
     vector<CControlBase::NodeTemplate *> m_vParsed;
+    ///whether parsed nodes were from user file or not
+    bool m_bUser;
+
+    ///Following only used as temporary variables during parsing...
+    map<string,CControlBase::NodeTemplate*> namedNodes;
+    vector<pair<CControlBase::NodeTemplate**,string> > unresolvedRefs;
+    vector<CControlBase::NodeTemplate*> nodeStack;
   };
 
   ///subclass which we actually construct! Parses editing node definitions from a file,
diff --git a/Src/DasherCore/DasherInterfaceBase.cpp b/Src/DasherCore/DasherInterfaceBase.cpp
index e665b63..529fa85 100644
--- a/Src/DasherCore/DasherInterfaceBase.cpp
+++ b/Src/DasherCore/DasherInterfaceBase.cpp
@@ -123,16 +123,12 @@ void CDasherInterfaceBase::Realize(unsigned long ulTime) {
   DASHER_ASSERT(m_DasherScreen ? m_pDasherView!=NULL : m_pDasherView==NULL);
 
   srand(ulTime);
-  
-  SetupPaths();
-
-  std::vector<std::string> vAlphabetFiles;
-  ScanAlphabetFiles(vAlphabetFiles);
-  m_AlphIO = new CAlphIO(this, GetStringParameter(SP_SYSTEM_LOC), GetStringParameter(SP_USER_LOC), vAlphabetFiles);
+ 
+  m_AlphIO = new CAlphIO(this);
+  ScanFiles(m_AlphIO, "alphabet*.xml");
 
-  std::vector<std::string> vColourFiles;
-  ScanColourFiles(vColourFiles);
-  m_ColourIO = new CColourIO(this, GetStringParameter(SP_SYSTEM_LOC), GetStringParameter(SP_USER_LOC), vColourFiles);
+  m_ColourIO = new CColourIO(this);
+  ScanFiles(m_ColourIO, "colour*.xml");
 
   ChangeColours();
 
diff --git a/Src/DasherCore/DasherInterfaceBase.h b/Src/DasherCore/DasherInterfaceBase.h
index 815b63f..50d56ef 100644
--- a/Src/DasherCore/DasherInterfaceBase.h
+++ b/Src/DasherCore/DasherInterfaceBase.h
@@ -359,6 +359,21 @@ public:
   ///
   virtual int GetFileSize(const std::string &strFileName) = 0;
 
+  
+  /// @name Platform dependent utility functions
+  /// These functions provide various platform dependent functions
+  /// required by the core. A derived class is created for each
+  /// supported platform which implements these.
+  // @{
+  
+  ///Look for files, matching a filename pattern, in whatever system and/or user
+  /// locations as may exist - e.g. on disk, in app package, on web, whatever.
+  /// TODO, can we add a default implementation that looks on the Dasher website?
+  /// \param pattern string matching just filename (not path), potentially
+  /// including '*'s (as per glob)
+  virtual void ScanFiles(AbstractParser *parser, const std::string &strPattern) = 0;
+    
+  // @}
 protected:
 
   /// @name Startup
@@ -426,34 +441,6 @@ protected:
   //The default expansion policy to use - an amortized policy depending on the LP_NODE_BUDGET parameter.
   CExpansionPolicy *m_defaultPolicy;
 
-  /// @name Platform dependent utility functions
-  /// These functions provide various platform dependent functions
-  /// required by the core. A derived class is created for each
-  /// supported platform which implements these.
-  // @{
-
-  ///
-  /// Initialise the SP_SYSTEM_LOC and SP_USER_LOC paths - the exact
-  /// method of doing this will be OS dependent
-  ///
-
-  virtual void SetupPaths() = 0;
-
-  ///
-  /// Produce a list of filenames for alphabet files
-  ///
-
-  virtual void ScanAlphabetFiles(std::vector<std::string> &vFileList) = 0;
-
-  ///
-  /// Produce a list of filenames for colour files
-  ///
-
-  virtual void ScanColourFiles(std::vector<std::string> &vFileList) = 0;
-
-  /// @}
-
-
   /// Provide a new CDasherInput input device object.
 
   void CreateInput();
diff --git a/Src/DasherCore/FileWordGenerator.cpp b/Src/DasherCore/FileWordGenerator.cpp
index 5c6c290..2d66831 100644
--- a/Src/DasherCore/FileWordGenerator.cpp
+++ b/Src/DasherCore/FileWordGenerator.cpp
@@ -2,11 +2,18 @@
 
 using namespace Dasher;
 
-CFileWordGenerator::CFileWordGenerator(const CAlphInfo *pAlph, const CAlphabetMap *pAlphMap)
-  : CWordGeneratorBase(pAlph,pAlphMap) {
+CFileWordGenerator::CFileWordGenerator(CMessageDisplay *pMsgs, const CAlphInfo *pAlph, const CAlphabetMap *pAlphMap)
+  : CWordGeneratorBase(pAlph,pAlphMap), AbstractParser(pMsgs) {
 }
 
-bool CFileWordGenerator::open(const std::string &sPath) {
+bool CFileWordGenerator::Parse(const std::string &strDesc, istream &in, bool bUser) {
+  //non-file streams not supported (yet)
+  DASHER_ASSERT(false);
+  return false;
+}
+
+bool CFileWordGenerator::ParseFile(const std::string &sPath, bool bUser) {
+  if (bUser && !m_bAcceptUser) return false;
   m_sFileHandle.close();
   m_vLineIndices.clear();
   m_sPath=sPath;
diff --git a/Src/DasherCore/FileWordGenerator.h b/Src/DasherCore/FileWordGenerator.h
index aaa49d5..b54d864 100644
--- a/Src/DasherCore/FileWordGenerator.h
+++ b/Src/DasherCore/FileWordGenerator.h
@@ -26,12 +26,16 @@ namespace Dasher {
  * the behavior is undefined as you may cause the file to be read in all
  * at once. 
  */
-class CFileWordGenerator : public CWordGeneratorBase {
+class CFileWordGenerator : public CWordGeneratorBase, public AbstractParser {
 public:
-  CFileWordGenerator(const CAlphInfo *pAlph, const CAlphabetMap *pAlphMap);
+  CFileWordGenerator(CMessageDisplay *pMsgs, const CAlphInfo *pAlph, const CAlphabetMap *pAlphMap);
 
+  ///Attempt to read from an arbitrary stream. Returns false, as we
+  /// only support reading game mode sentences from files.
+  bool Parse(const std::string &strDesc, std::istream &in, bool bUser);
+  
   ///Attempt to open the specified file. Return true for success, false for failure
-  bool open(const std::string &sPath);
+  bool ParseFile(const std::string &strFileName, bool bUser);
     
   virtual ~CFileWordGenerator() {
     m_sFileHandle.close();
@@ -42,22 +46,11 @@ public:
    * @throw  Throws an exception if the file cannot be read.
    */
  virtual std::string GetLine();
-
-
-  /**
-   * File path getter
-   * @return The path to the file this generator reads from.
-   */
-  std::string GetPath();
-
-  /**
-   * File name getter. Returns the file name and extension, without
-   * any slashes.
-   * @return The actual name of the file being read from
-   */
-  std::string GetFilename();
   
+  void setAcceptUser(bool bAcceptUser) {m_bAcceptUser = bAcceptUser;}
 
+  bool HasLines() {return !m_vLineIndices.empty();}
+  
 private:
 
 /* ---------------------------------------------------------------------
@@ -76,6 +69,8 @@ private:
   ifstream m_sFileHandle;
   
   std::vector<streampos> m_vLineIndices;
+  
+  bool m_bAcceptUser;
 };
 }
 
diff --git a/Src/DasherCore/Makefile.am b/Src/DasherCore/Makefile.am
index c4aff81..309393d 100644
--- a/Src/DasherCore/Makefile.am
+++ b/Src/DasherCore/Makefile.am
@@ -90,6 +90,7 @@ libdashercore_a_SOURCES = \
 		MemoryLeak.cpp \
 		MemoryLeak.h \
 		Messages.h \
+		Messages.cpp \
 		ModuleManager.cpp \
 		ModuleManager.h \
 		NodeCreationManager.cpp \
diff --git a/Src/DasherCore/Messages.cpp b/Src/DasherCore/Messages.cpp
new file mode 100644
index 0000000..c5e2e5c
--- /dev/null
+++ b/Src/DasherCore/Messages.cpp
@@ -0,0 +1,50 @@
+#include "Messages.h"
+#include <string.h>
+#include <vector>
+#include <stdarg.h>
+#include <stdio.h>
+
+using std::vector;
+
+void CMessageDisplay::FormatMessageWithString(const char *fmt, const char *str) {
+  char *buf(new char[strlen(fmt)+strlen(str)]);
+  sprintf(buf, fmt, str);
+  Message(buf, true);
+  delete buf;
+}
+
+  //The following implements a varargs version of the above,
+  // dynamically allocating enough storage for the formatted string
+  // using snprintf. However, this doesn't work on Solaris,
+  // hence commenting out.
+
+  //Note: vector is guaranteed to store elements contiguously.
+  // C++98 did not guarantee this, but this was corrected in a 2003
+  // technical corrigendum. As Bjarne Stroustrup says, 
+  // "this was always the intent and all implementations always did it that way"
+  /*vector<char> buf;
+  for (int len = strlen(fmt)+1024; ;) {
+    buf.resize(len);
+    va_list args;
+    va_start(args,fmt);
+    int res = vsnprintf(&buf[0], len, fmt, args);
+    va_end(args);
+    if (res>=0 && res<len) {
+      //ok, buf big enough, now contains string
+      Message(&buf[0], true);
+      return;
+    }
+    if (res<0) {
+      //on windows, returns -1 for "buffer not big enough" => double size & retry.
+      // However, on linux, -1 indicates "some other error".
+      // So make sure we don't infinite loop but instead break out somehow...
+      if (len*=2 > 1<<16) {
+        printf("Could not allocate big enough buffer, or other error, when trying to print:\n");
+        va_list args2;
+        va_start(args2,fmt);
+        vprintf(fmt,args2);
+        va_end(args2);
+        return; //exit loop + function, no call to Message()
+      }
+    } else len = res+1; //that identifies necessary size of buffer
+  }*/
diff --git a/Src/DasherCore/Messages.h b/Src/DasherCore/Messages.h
index 035fd9a..892a2aa 100644
--- a/Src/DasherCore/Messages.h
+++ b/Src/DasherCore/Messages.h
@@ -43,6 +43,10 @@ public:
   /// \param bInterrupt if true, text entry should be interrupted; if false, user should
   /// be able to continue writing uninterrupted.
   virtual void Message(const std::string &strText, bool bInterrupt)=0;
+  
+  ///Utility method for common case of displaying a modal message with a format
+  /// string containing a single %s.
+  void FormatMessageWithString(const char* fmt, const char* str);
 };
 
 /// @}
diff --git a/Src/DasherCore/NodeCreationManager.cpp b/Src/DasherCore/NodeCreationManager.cpp
index ddf72aa..51258b2 100644
--- a/Src/DasherCore/NodeCreationManager.cpp
+++ b/Src/DasherCore/NodeCreationManager.cpp
@@ -10,24 +10,33 @@
 
 using namespace Dasher;
 
-class ProgressNotifier : public CTrainer::ProgressIndicator {
+//Wraps the ParseFile of a provided Trainer, to setup progress notification
+// - and then passes self, as a ProgressIndicator, to the Trainer's ParseFile method.
+class ProgressNotifier : public AbstractParser, private CTrainer::ProgressIndicator {
 public:
   ProgressNotifier(CDasherInterfaceBase *pInterface, CTrainer *pTrainer)
-  : m_pInterface(pInterface), m_pTrainer(pTrainer) { }
+  : AbstractParser(pInterface), m_bSystem(false), m_bUser(false), m_pInterface(pInterface), m_pTrainer(pTrainer) { }
   void bytesRead(off_t n) {
     int iNewPercent = ((m_iStart + n)*100)/m_iStop;
     if (iNewPercent != m_iPercent) {
       m_pInterface->SetLockStatus(m_strDisplay, m_iPercent = iNewPercent);
     }
   }
-  bool run(const string &strDisplay, string strFile) {
-    m_pInterface->SetLockStatus(m_strDisplay=strDisplay, m_iPercent=0);
+  bool ParseFile(const string &strFilename, bool bUser) {
     m_iStart = 0;
-    m_iStop = m_pInterface->GetFileSize(strFile);
+    m_iStop = m_pInterface->GetFileSize(strFilename);
     if (m_iStop==0) return false;
-    m_pTrainer->LoadFile(strFile,this); //Hmmm. Error-reporting is only via Message()...?
+    return AbstractParser::ParseFile(strFilename, bUser);
+  }
+  bool Parse(const string &strUrl, istream &in, bool bUser) {
+    m_strDisplay = bUser ? _("Training on User Text") : _("Training on System Text");
+    m_pInterface->SetLockStatus(m_strDisplay, m_iPercent=0);
+    m_pTrainer->SetProgressIndicator(this);
+    if (!m_pTrainer->Parse(strUrl, in, bUser)) return false;
+    if (bUser) m_bUser=true; else m_bSystem=true;
     return true;
   }
+  bool m_bSystem, m_bUser;
 private:
   CDasherInterfaceBase *m_pInterface;
   CTrainer *m_pTrainer;
@@ -77,30 +86,18 @@ CNodeCreationManager::CNodeCreationManager(CSettingsUser *pCreateFrom,
     
   if (!pAlphInfo->GetTrainingFile().empty()) {
     ProgressNotifier pn(pInterface, m_pTrainer);
-    //1. Look for system training text...
-    bool bFound=pn.run(_("Training on System Text"), GetStringParameter(SP_SYSTEM_LOC) + pAlphInfo->GetTrainingFile());
-    //2. Now add in any user-provided individual training text...
-    if (!pn.run(_("Training on User Text"), GetStringParameter(SP_USER_LOC) + pAlphInfo->GetTrainingFile())) {
+    pInterface->ScanFiles(&pn,pAlphInfo->GetTrainingFile());
+    if (!pn.m_bUser) {
       ///TRANSLATORS: These 3 messages will be displayed when the user has just chosen a new alphabet. The %s parameter will be the name of the alphabet.
-      const char *msg = bFound ? _("No user training text found - if you have written in \"%s\" before, this means Dasher may not be learning from previous sessions")
+      const char *msg = pn.m_bSystem ? _("No user training text found - if you have written in \"%s\" before, this means Dasher may not be learning from previous sessions")
       : _("No training text (user or system) found for \"%s\". Dasher will still work but entry will be slower. We suggest downloading a training text file from the Dasher website, or constructing your own.");
-      char *buf(new char[strlen(msg)+pAlphInfo->GetID().length()]);
-      sprintf(buf,msg,pAlphInfo->GetID().c_str());
-      pInterface->Message(buf, true);
-      delete buf;
+      pInterface->FormatMessageWithString(msg, pAlphInfo->GetID().c_str());
     }
     //3. Finished, so unlock.
     m_pInterface->SetLockStatus("", -1);
+  }  else {
+    pInterface->FormatMessageWithString(_("\"%s\" does not specify training file. Dasher will work but entry will be slower. Check you have the latest version of the alphabet definition."), pAlphInfo->GetID().c_str());
   }
-#ifdef DEBUG
-  else {
-    const char *msg = _("\"%s\" does not specify training file. Dasher will work but entry will be slower. Check you have the latest version of the alphabet definition.");
-    char *buf(new char[strlen(msg) + pAlphInfo->GetID().length()]);
-    sprintf(buf, msg, pAlphInfo->GetID().c_str());
-    pInterface->Message(buf, true);
-    delete buf;
-  }
-#endif
 #ifdef DEBUG_LM_READWRITE
   {
     //test...
@@ -171,5 +168,6 @@ void CNodeCreationManager::AddExtras(CDasherNode *pParent) {
 void 
 CNodeCreationManager::ImportTrainingText(const std::string &strPath) {
   ProgressNotifier pn(m_pInterface, m_pTrainer);
-	pn.run("Training on New Text", strPath);
+  ifstream in(strPath.c_str(), ios::binary);
+	pn.ParseFile(strPath, true);
 }
diff --git a/Src/DasherCore/Parameters.cpp b/Src/DasherCore/Parameters.cpp
index bf81a98..67c3dd9 100644
--- a/Src/DasherCore/Parameters.cpp
+++ b/Src/DasherCore/Parameters.cpp
@@ -153,8 +153,6 @@ const sp_table stringparamtable[] = {
   {SP_ALPHABET_4, "Alphabet4", PERS, "", "Alphabet History 4"},
   {SP_COLOUR_ID, "ColourID", PERS, "", "ColourID"}, 
   {SP_DASHER_FONT, "DasherFont", PERS, "", "DasherFont"},
-  {SP_SYSTEM_LOC, "SystemLocation", !PERS, "sys_", "System Directory"},
-  {SP_USER_LOC, "UserLocation", !PERS, "usr_", "User Directory"},
   {SP_GAME_TEXT_FILE, "GameTextFile", PERS, "", "User-specified file with strings to practice writing"},
   {SP_SOCKET_INPUT_X_LABEL, "SocketInputXLabel", PERS, "x", "Label preceding X values for network input"},
   {SP_SOCKET_INPUT_Y_LABEL, "SocketInputYLabel", PERS, "y", "Label preceding Y values for network input"},
diff --git a/Src/DasherCore/Parameters.h b/Src/DasherCore/Parameters.h
index 2b77da3..e6d055b 100644
--- a/Src/DasherCore/Parameters.h
+++ b/Src/DasherCore/Parameters.h
@@ -68,7 +68,7 @@ enum {
 
 enum {
   SP_ALPHABET_ID = END_OF_LPS, SP_ALPHABET_1, SP_ALPHABET_2, SP_ALPHABET_3, SP_ALPHABET_4, 
-  SP_COLOUR_ID, SP_DASHER_FONT, SP_SYSTEM_LOC, SP_USER_LOC, SP_GAME_TEXT_FILE,
+  SP_COLOUR_ID, SP_DASHER_FONT, SP_GAME_TEXT_FILE,
   SP_SOCKET_INPUT_X_LABEL, SP_SOCKET_INPUT_Y_LABEL, SP_INPUT_FILTER, SP_INPUT_DEVICE,
   SP_BUTTON_0, SP_BUTTON_1, SP_BUTTON_2, SP_BUTTON_3, SP_BUTTON_4, SP_BUTTON_10, SP_JOYSTICK_DEVICE,
   END_OF_SPS
diff --git a/Src/DasherCore/Trainer.cpp b/Src/DasherCore/Trainer.cpp
index 1815c21..d0cdca4 100644
--- a/Src/DasherCore/Trainer.cpp
+++ b/Src/DasherCore/Trainer.cpp
@@ -8,6 +8,8 @@
 #include <sstream>
 #include <string>
 
+#include <iostream>
+
 using namespace Dasher;
 using namespace std;
 
@@ -22,18 +24,15 @@ static char THIS_FILE[] = __FILE__;
 #endif
 
 CTrainer::CTrainer(CMessageDisplay *pMsgs, CLanguageModel *pLanguageModel, const CAlphInfo *pInfo, const CAlphabetMap *pAlphabet)
-  : m_pMsgs(pMsgs), m_pAlphabet(pAlphabet), m_pLanguageModel(pLanguageModel), m_pInfo(pInfo) {
+  : AbstractParser(pMsgs), m_pAlphabet(pAlphabet), m_pLanguageModel(pLanguageModel), m_pInfo(pInfo), m_pProg(NULL) {
     vector<symbol> syms;
     pAlphabet->GetSymbols(syms,pInfo->GetContextEscapeChar());
     if (syms.size()==1)
       m_iCtxEsc = syms[0];
     else {      
       //no context switch commands will be executed!
-      const char *msg(_("Warning: faulty alphabet definition, escape sequence %s must be a single unicode character. This may worsen Dasher's text prediction."));
-      char *buf(new char[strlen(msg) + pInfo->GetContextEscapeChar().length() +1]);
-      sprintf(buf,msg,pInfo->GetContextEscapeChar().c_str());
-      pMsgs->Message(string(buf),true);
-      delete buf;
+      pMsgs->FormatMessageWithString(_("Warning: faulty alphabet definition, escape sequence %s must be a single unicode character. This may worsen Dasher's text prediction."),
+                                     pInfo->GetContextEscapeChar().c_str());
       m_iCtxEsc = -1;
     }
 }
@@ -95,64 +94,17 @@ private:
   CTrainer::ProgressIndicator *m_pProg;
 };
 
-void 
-Dasher::CTrainer::LoadFile(const std::string &strFileName, ProgressIndicator *pProg) {
-  if(strFileName == "")
-    return;
-  
-  FILE *pInputFile;
-  if((pInputFile = fopen(strFileName.c_str(), "r")) == (FILE *) 0)
-    return;
-  
-  char szTestBuffer[6];
-  
-  int iNumberRead = fread(szTestBuffer, 1, 5, pInputFile);
-  szTestBuffer[iNumberRead] = '\0';
-  
-  fclose(pInputFile);
+bool 
+Dasher::CTrainer::Parse(const string &strDesc, istream &in, bool bUser) {
+  if (in.fail()) {
+    m_pMsgs->FormatMessageWithString(_("Unable to open file \"%s\" for reading"),strDesc.c_str());
+    return false;
+  }
   
-  if(!strcmp(szTestBuffer, "<?xml")) {
-    //Invoke AbstractXMLParser method
-    m_bInSegment = false;
-    m_iLastBytes=0;
-    ParseFile(m_pMsgs, strFileName);
-  } else {
-    std::ifstream in(strFileName.c_str(), std::ios::binary);
-    if (in.fail()) {
-      const char *msg=_("Unable to open file \"%f\" for reading");
-      char *buf(new char[strlen(msg) + strFileName.length()+1]);
-      sprintf(buf, msg, strFileName.c_str());
-      m_pMsgs->Message(buf, true);
-      delete buf;
-      return;
-    }
-    ProgressStream syms(in,pProg,m_pMsgs);
-    Train(syms);
+  ProgressStream syms(in,m_pProg,m_pMsgs);
+  Train(syms);
   
-    in.close();
-  }
-}
-
-void CTrainer::XmlStartHandler(const XML_Char *szName, const XML_Char **pAtts) {
-  if(!strcmp(szName, "segment")) {
-    m_strCurrentText = "";
-    m_bInSegment = true;
-  }
-}
-
-void CTrainer::XmlEndHandler(const XML_Char *szName) {
-  if(!strcmp(szName, "segment")) {
-    std::istringstream in(m_strCurrentText);
-    ProgressStream syms(in, m_pProg, m_pMsgs, m_iLastBytes);
-    Train(syms);
-    m_iLastBytes = syms.m_iLastPos; //count that segment, ready for next
-    m_bInSegment = false;
-  }
-}
-
-void CTrainer::XmlCData(const XML_Char *szS, int iLen) {
-  if(m_bInSegment)
-    m_strCurrentText += std::string(szS, iLen);
+  return true;
 }
 
 CMandarinTrainer::CMandarinTrainer(CMessageDisplay *pMsgs, CPPMPYLanguageModel *pLanguageModel, const CAlphInfo *pInfo, const CAlphabetMap *pPYAlphabet, const CAlphabetMap *pCHAlphabet, const std::string &strDelim)
@@ -170,22 +122,12 @@ void CMandarinTrainer::Train(CAlphabetMap::SymbolStream &syms) {
       symbol Sympy = syms.next(m_pPYAlphabet);
       if (Sympy==-1) break; //EOF
       if (Sympy==0) {
-        const char *msg(_("Training file contains unknown source alphabet character %s"));
-        string prev = syms.peekBack();
-        char *buf(new char[strlen(msg) + prev.length()+1]);
-        sprintf(buf, msg, prev.c_str());
-        m_pMsgs->Message(buf,true);
-        delete buf;
+        m_pMsgs->FormatMessageWithString(_("Training file contains unknown source alphabet character %s"), syms.peekBack().c_str());
       }
       symbol Symchar = syms.next(m_pAlphabet);
       if (Symchar==-1) break; //EOF...ignore final Pinyin?
       if (Symchar==0) {
-        const char *msg=_("Training file contains unknown target alphabet character %s");
-        string prev = syms.peekBack();
-        char *buf(new char[strlen(msg) + prev.length()+1]);
-        sprintf(buf,msg,prev.c_str());
-        m_pMsgs->Message(buf,true);
-        delete buf;
+        m_pMsgs->FormatMessageWithString(_("Training file contains unknown target alphabet character %s"), syms.peekBack().c_str());
       }
       static_cast<CPPMPYLanguageModel *>(m_pLanguageModel)->LearnPYSymbol(trainContext, Sympy);
       m_pLanguageModel->LearnSymbol(trainContext, Symchar);
diff --git a/Src/DasherCore/Trainer.h b/Src/DasherCore/Trainer.h
index 340a207..cb3488e 100644
--- a/Src/DasherCore/Trainer.h
+++ b/Src/DasherCore/Trainer.h
@@ -6,7 +6,7 @@
 #include "AbstractXMLParser.h"
 
 namespace Dasher {
-  class CTrainer : private AbstractXMLParser {
+  class CTrainer : public AbstractParser {
             
   public:
     CTrainer(CMessageDisplay *pMsgs, CLanguageModel *pLanguageModel, const CAlphInfo *pInfo, const CAlphabetMap *pAlphabet);
@@ -16,13 +16,12 @@ namespace Dasher {
       virtual void bytesRead(off_t)=0;
     };
     
-    void LoadFile(const std::string &strFileName, ProgressIndicator *pProg=NULL);
+    void SetProgressIndicator(ProgressIndicator *pProg) {m_pProg = pProg;}
+
+    ///Parses a text file; bUser ignored.
+    bool Parse(const std::string &strDesc, std::istream &in, bool bUser);
   
   protected:
-    ///Override AbstractXMLParser methods to extract text in <segment>...</segment> pairs
-    void XmlStartHandler(const XML_Char *szName, const XML_Char **pAtts);
-    void XmlEndHandler(const XML_Char *szName);
-    void XmlCData(const XML_Char *szS, int iLen);
 
     virtual void Train(CAlphabetMap::SymbolStream &syms);
     
@@ -33,19 +32,12 @@ namespace Dasher {
     ///  false, if instead a double-escape-character (=encoding of that actual symbol) was read
     bool readEscape(CLanguageModel::Context &sContext, CAlphabetMap::SymbolStream &syms);
 
-    CMessageDisplay * const m_pMsgs;
     const CAlphabetMap * const m_pAlphabet;
     CLanguageModel * const m_pLanguageModel;
     const CAlphInfo * const m_pInfo;
     // symbol number in alphabet of the context-switch character (maybe 0 if not in alphabet!)
     int m_iCtxEsc;
   private:
-    //For dealing with XML CData:    
-    bool m_bInSegment;
-    std::string m_strCurrentText;
-    ///Number of bytes read up to and including end of _previous_ segment in XML.
-    off_t m_iLastBytes;
-    ///Store ProgressIndicator only when parsing XML
     ProgressIndicator *m_pProg;
   };
 	
diff --git a/Src/Gtk2/DasherControl.cpp b/Src/Gtk2/DasherControl.cpp
index 55703a2..2f387c9 100644
--- a/Src/Gtk2/DasherControl.cpp
+++ b/Src/Gtk2/DasherControl.cpp
@@ -11,6 +11,7 @@
 #include "../DasherCore/ModuleManager.h"
 #include "dasher_main.h"
 #include "../DasherCore/GameModule.h"
+#include "../Common/Globber.cpp"
 
 #include <fcntl.h>
 
@@ -80,6 +81,11 @@ CDasherControl::CDasherControl(GtkVBox *pVBox, GtkDasherControl *pDasherControl)
   g_signal_connect(m_pCanvas, "expose_event", G_CALLBACK(canvas_expose_event), this);
 #endif
 
+  char *home_dir = getenv("HOME");
+  char *user_data_dir = new char[strlen(home_dir) + 10];
+  sprintf(user_data_dir, "%s/.dasher/", home_dir);
+  m_user_data_dir = user_data_dir;
+
   m_pScreen = new CCanvas(m_pCanvas);
   ChangeScreen(m_pScreen);
 
@@ -116,82 +122,23 @@ void CDasherControl::CreateModules() {
 #endif
 }
 
-void CDasherControl::SetupPaths() {
-  char *home_dir;
-  char *user_data_dir;
-  const char *system_data_dir;
-
-  home_dir = getenv("HOME");
-  user_data_dir = new char[strlen(home_dir) + 10];
-  sprintf(user_data_dir, "%s/.dasher/", home_dir);
-
-  mkdir(user_data_dir, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
+void CDasherControl::ScanFiles(AbstractParser *parser, const std::string &strPattern) {
 
+  //System files.
   // PROGDATA is provided by the makefile
-  system_data_dir = PROGDATA "/";
-
-  SetStringParameter(SP_SYSTEM_LOC, system_data_dir);
-  SetStringParameter(SP_USER_LOC, user_data_dir);
-  delete[] user_data_dir;
-}
-
-void CDasherControl::ScanAlphabetFiles(std::vector<std::string> &vFileList) {
-  GDir *directory;
-  G_CONST_RETURN gchar *filename;
-  GPatternSpec *alphabetglob;
-  alphabetglob = g_pattern_spec_new("alphabet*xml");
-
-  directory = g_dir_open(GetStringParameter(SP_SYSTEM_LOC).c_str(), 0, NULL);
-
-  if(directory) {
-    while((filename = g_dir_read_name(directory))) {
-      if(g_pattern_match_string(alphabetglob, filename)) 
-	vFileList.push_back(filename);
-    }
-    g_dir_close(directory);
-  }
-
-  directory = g_dir_open(GetStringParameter(SP_USER_LOC).c_str(), 0, NULL);
-
-  if(directory) {
-    while((filename = g_dir_read_name(directory))) {
-      if(g_pattern_match_string(alphabetglob, filename))
-	vFileList.push_back(filename);
-    }
-    g_dir_close(directory);
-  }
+  string path(PROGDATA "/");
+  path += strPattern;
 
-  g_pattern_spec_free(alphabetglob);
-}
-
-void CDasherControl::ScanColourFiles(std::vector<std::string> &vFileList) {
-  GDir *directory;
-  G_CONST_RETURN gchar *filename;
-
-  GPatternSpec *colourglob;
-  colourglob = g_pattern_spec_new("colour*xml");
-
-  directory = g_dir_open(GetStringParameter(SP_SYSTEM_LOC).c_str(), 0, NULL);
-
-  if(directory) {
-    while((filename = g_dir_read_name(directory))) {
-      if(g_pattern_match_string(colourglob, filename))
-	vFileList.push_back(filename);
-    }
-    g_dir_close(directory);
-  }
-
-  directory = g_dir_open(GetStringParameter(SP_USER_LOC).c_str(), 0, NULL);
-
-  if(directory) {
-    while((filename = g_dir_read_name(directory))) {
-      if(g_pattern_match_string(colourglob, filename))
-	vFileList.push_back(filename);
-    }
-    g_dir_close(directory);
-  }
-
-  g_pattern_spec_free(colourglob);
+  //User files.  
+  mkdir(m_user_data_dir, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
+  string user_path(m_user_data_dir);
+  user_path += strPattern;
+  
+  const char *user[2], *sys[2];
+  user[0] = user_path.c_str(); sys[0] = path.c_str();
+  user[1] = sys[1] = NULL; //terminators
+  
+  globScan(parser, user, sys);
 }
 
 void CDasherControl::ClearAllContext() {
@@ -238,6 +185,8 @@ CDasherControl::~CDasherControl() {
     m_p1DMouseInput = NULL;
   }
 
+  delete[] m_user_data_dir;
+
 //   if(m_pKeyboardHelper) {
 //     delete m_pKeyboardHelper;
 //     m_pKeyboardHelper = 0;
@@ -471,7 +420,8 @@ void CDasherControl::WriteTrainFile(const std::string &filename, const std::stri
   if(strNewText.length() == 0)
     return;
 
-  std::string strFilename(GetStringParameter(SP_USER_LOC) + filename);
+  std::string strFilename(m_user_data_dir);
+  strFilename+=filename;
 
   int fd=open(strFilename.c_str(),O_CREAT|O_WRONLY|O_APPEND,S_IRUSR|S_IWUSR);
   write(fd,strNewText.c_str(),strNewText.length());
diff --git a/Src/Gtk2/DasherControl.h b/Src/Gtk2/DasherControl.h
index 9a57653..354052b 100644
--- a/Src/Gtk2/DasherControl.h
+++ b/Src/Gtk2/DasherControl.h
@@ -167,9 +167,7 @@ public:
 
   CGameModule *CreateGameModule(CDasherView *pView, CDasherModel *pModel);
 private:
-  virtual void ScanAlphabetFiles(std::vector<std::string> &vFileList);
-  virtual void ScanColourFiles(std::vector<std::string> &vFileList);
-  virtual void SetupPaths();
+  virtual void ScanFiles(AbstractParser *parser, const std::string &strPattern);
   virtual void CreateModules();
 
   GtkWidget *m_pVBox;
@@ -194,6 +192,9 @@ private:
 
   GtkDasherControl *m_pDasherControl;
 
+  //full path of user data directory, including trailing /
+  const char *m_user_data_dir;
+
   ///
   /// Keyboard helper class
   ///
diff --git a/Src/MacOSX/COSXDasherControl.h b/Src/MacOSX/COSXDasherControl.h
index 27ccc53..07e35b4 100644
--- a/Src/MacOSX/COSXDasherControl.h
+++ b/Src/MacOSX/COSXDasherControl.h
@@ -58,9 +58,7 @@ public:
   void SetEdit(id<DasherEdit> pEdit);
   CGameModule *CreateGameModule(CDasherView *pView, CDasherModel *pModel);
 private:
-  virtual void ScanAlphabetFiles(std::vector<std::string> &vFileList);
-  virtual void ScanColourFiles(std::vector<std::string> &vFileList);
-  virtual void SetupPaths();
+  virtual void ScanFiles(AbstractParser *parser, const std::string &strPattern);
   virtual void CreateModules();
   virtual bool SupportsSpeech();
   virtual void Speak(const std::string &strText, bool bInterrupt);
@@ -82,12 +80,12 @@ private:
   ///Just log (and call superclass)
   void editConvert(CDasherNode *pSource);
   void editProtect(CDasherNode *pSource);
-
+  
   DasherApp *dasherApp;   // objc counterpart
   id<DasherEdit> dasherEdit;  // current output - sends to other apps or textfield
   
   COSXMouseInput *m_pMouseInput;
   COSX1DMouseInput *m_p1DMouseInput;
 
-  
+  NSString * const userDir;
 };
diff --git a/Src/MacOSX/COSXDasherControl.mm b/Src/MacOSX/COSXDasherControl.mm
index 1011bb9..c3bf296 100644
--- a/Src/MacOSX/COSXDasherControl.mm
+++ b/Src/MacOSX/COSXDasherControl.mm
@@ -17,6 +17,7 @@
 #import "DasherEdit.h"
 #import "Event.h"
 #import "../Common/Common.h"
+#import "../Common/Globber.h"
 #import "GameModule.h"
 
 #import <iostream>
@@ -76,7 +77,8 @@ private:
 };
 
 COSXDasherControl::COSXDasherControl(DasherApp *aDasherApp)
-: CDashIntfScreenMsgs(new COSXSettingsStore()), dasherApp(aDasherApp), dasherEdit(nil) {
+: CDashIntfScreenMsgs(new COSXSettingsStore()), dasherApp(aDasherApp), dasherEdit(nil),
+  userDir([[NSString stringWithFormat:@"%@/Library/Application Support/Dasher/", NSHomeDirectory()] retain]) {
 }
 
 void COSXDasherControl::CreateModules() {
@@ -96,6 +98,7 @@ COSXDasherControl::~COSXDasherControl() {
   if(m_p1DMouseInput) {
     m_p1DMouseInput = NULL;
   }
+  [userDir release];
 }
 
 void COSXDasherControl::Realize2() {
@@ -103,61 +106,24 @@ void COSXDasherControl::Realize2() {
   [dasherApp startTimer];
 }
 
-void COSXDasherControl::SetupPaths() {
+void COSXDasherControl::ScanFiles(AbstractParser *parser, const string &strPattern) {
 
-  NSString *systemDir = [NSString stringWithFormat:@"%@/", [[NSBundle mainBundle] resourcePath]];
-  NSString *userDir = [NSString stringWithFormat:@"%@/Library/Application Support/Dasher/", NSHomeDirectory()];
+  string strPath(StdStringFromNSString([[NSBundle mainBundle] resourcePath])+"/"+strPattern);
+  const char *sys[2];
+  sys[0] = strPath.c_str();
+  sys[1] = NULL;
   
-  // if the userDir doesn't exist, create it, ready to receive stuff
-  if (![[NSFileManager defaultManager] fileExistsAtPath:userDir isDirectory:NULL]) {
-    (void)[[NSFileManager defaultManager] createDirectoryAtPath:userDir attributes:nil];
+  const char *user[2]; user[1] = NULL;
+  if ([[NSFileManager defaultManager] fileExistsAtPath:userDir isDirectory:NULL]) {
+    user[0] = (StdStringFromNSString(userDir)+strPattern).c_str();
+  } else {
+    // userDir doesn't exist => create it, ready to receive stuff
+    (void)[[NSFileManager defaultManager] createDirectoryAtPath:userDir withIntermediateDirectories:YES attributes:nil error:nil];
+    user[0] = 0;
   }
-    
-    // system resources are inside the .app, under the Resources directory
-  SetStringParameter(SP_SYSTEM_LOC, StdStringFromNSString(systemDir));
-  SetStringParameter(SP_USER_LOC, StdStringFromNSString(userDir));
-}
-
-void COSXDasherControl::ScanAlphabetFiles(std::vector<std::string> &vFileList) {
-  
-  NSDirectoryEnumerator *dirEnum;
-  NSString *file;
-
-  dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:NSStringFromStdString(GetStringParameter(SP_SYSTEM_LOC))];
-  while (file = [dirEnum nextObject]) {
-    if ([file hasSuffix:@".xml"] && [file hasPrefix:@"alphabet"]) {
-      vFileList.push_back(StdStringFromNSString(file));
-    }
-  }  
-  
-  dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:NSStringFromStdString(GetStringParameter(SP_USER_LOC))];
-  while (file = [dirEnum nextObject]) {
-    if ([file hasSuffix:@".xml"] && [file hasPrefix:@"alphabet"]) {
-      vFileList.push_back(StdStringFromNSString(file));
-    }
-  }  
+  globScan(parser, user, sys);
 }
 
-void COSXDasherControl::ScanColourFiles(std::vector<std::string> &vFileList) {
-  NSDirectoryEnumerator *dirEnum;
-  NSString *file;
-  
-  dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:NSStringFromStdString(GetStringParameter(SP_SYSTEM_LOC))];
-  while (file = [dirEnum nextObject]) {
-    if ([file hasSuffix:@".xml"] && [file hasPrefix:@"colour"]) {
-      vFileList.push_back(StdStringFromNSString(file));
-    }
-  }  
-  
-  dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:NSStringFromStdString(GetStringParameter(SP_USER_LOC))];
-  while (file = [dirEnum nextObject]) {
-    if ([file hasSuffix:@".xml"] && [file hasPrefix:@"colour"]) {
-      vFileList.push_back(StdStringFromNSString(file));
-    }
-  }  
-}
-
-
 void COSXDasherControl::goddamn(unsigned long iTime, bool bForceRedraw) {
   NewFrame(iTime, bForceRedraw);
 }
@@ -231,7 +197,7 @@ void COSXDasherControl::WriteTrainFile(const std::string &filename, const std::s
   if(strNewText.length() == 0)
     return;
   
-  std::string strFilename(GetStringParameter(SP_USER_LOC) + filename);
+  std::string strFilename(StdStringFromNSString(userDir) + filename);
   
   NSLog(@"Write train file: %s", strFilename.c_str());
   
diff --git a/Src/MacOSX/Dasher.xcodeproj/project.pbxproj b/Src/MacOSX/Dasher.xcodeproj/project.pbxproj
index f57a05e..4f95fde 100755
--- a/Src/MacOSX/Dasher.xcodeproj/project.pbxproj
+++ b/Src/MacOSX/Dasher.xcodeproj/project.pbxproj
@@ -392,6 +392,9 @@
 		33FC93390FEFA2C900A9F08D /* TwoPushDynamicFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33FC93370FEFA2C900A9F08D /* TwoPushDynamicFilter.cpp */; };
 		33FC933A0FEFA2C900A9F08D /* TwoPushDynamicFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 33FC93380FEFA2C900A9F08D /* TwoPushDynamicFilter.h */; };
 		33FC93430FEFA2FB00A9F08D /* FrameRate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33FC93420FEFA2FB00A9F08D /* FrameRate.cpp */; };
+		E7641875142A48AD0031FC91 /* Globber.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E7641874142A48AD0031FC91 /* Globber.cpp */; };
+		E7641878142A48C70031FC91 /* Globber.h in Headers */ = {isa = PBXBuildFile; fileRef = E7641877142A48C70031FC91 /* Globber.h */; };
+		E7C68E691430824D00440B5B /* Messages.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E7C68E681430824D00440B5B /* Messages.cpp */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
@@ -798,6 +801,9 @@
 		33FC93370FEFA2C900A9F08D /* TwoPushDynamicFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TwoPushDynamicFilter.cpp; sourceTree = "<group>"; };
 		33FC93380FEFA2C900A9F08D /* TwoPushDynamicFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TwoPushDynamicFilter.h; sourceTree = "<group>"; };
 		33FC93420FEFA2FB00A9F08D /* FrameRate.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FrameRate.cpp; sourceTree = "<group>"; };
+		E7641874142A48AD0031FC91 /* Globber.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Globber.cpp; path = ../Common/Globber.cpp; sourceTree = "<group>"; };
+		E7641877142A48C70031FC91 /* Globber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Globber.h; path = ../Common/Globber.h; sourceTree = "<group>"; };
+		E7C68E681430824D00440B5B /* Messages.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Messages.cpp; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -889,6 +895,7 @@
 		1948BDF40C226CFC001DFA32 /* DasherCore */ = {
 			isa = PBXGroup;
 			children = (
+				E7C68E681430824D00440B5B /* Messages.cpp */,
 				3344F0681341297F001FACAB /* UserLogBase.cpp */,
 				33CB6E7F125E2C7A002DB8AD /* WordGeneratorBase.cpp */,
 				33E756A31202D6180012A0E9 /* WordGeneratorBase.h */,
@@ -1256,6 +1263,8 @@
 				196874000C2BDC2E00D63879 /* OpenGLScreen.h */,
 				196874010C2BDC2E00D63879 /* OpenGLScreen.mm */,
 				19F8C7E50C858A2800276B4F /* I18n.h */,
+				E7641874142A48AD0031FC91 /* Globber.cpp */,
+				E7641877142A48C70031FC91 /* Globber.h */,
 			);
 			name = Common;
 			sourceTree = "<group>";
@@ -1467,6 +1476,7 @@
 				33FC1D2C13ACE7E7007642CD /* ScreenGameModule.h in Headers */,
 				33C3BDD213854CC000C768E0 /* DasherTextView.h in Headers */,
 				33DDB9E113B8AF360001C52D /* DynamicButtons.h in Headers */,
+				E7641878142A48C70031FC91 /* Globber.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1824,6 +1834,8 @@
 				33FC1D2B13ACE7E7007642CD /* ScreenGameModule.cpp in Sources */,
 				33C3BDD313854CC000C768E0 /* DasherTextView.mm in Sources */,
 				33DDB9E013B8AF360001C52D /* DynamicButtons.cpp in Sources */,
+				E7641875142A48AD0031FC91 /* Globber.cpp in Sources */,
+				E7C68E691430824D00440B5B /* Messages.cpp in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
diff --git a/Src/Win32/Dasher.cpp b/Src/Win32/Dasher.cpp
index c9d01a5..8106b40 100644
--- a/Src/Win32/Dasher.cpp
+++ b/Src/Win32/Dasher.cpp
@@ -214,68 +214,38 @@ void CDasher::ScanDirectory(const Tstring &strMask, std::vector<std::string> &vF
   }
 }
 
-void CDasher::ScanColourFiles(std::vector<std::string> &vFileList) {
-  Tstring Colours;
-
-  // TODO: Is it okay to have duplicate names in the array?
-  std::string strAppData2(GetStringParameter(SP_SYSTEM_LOC));
-  Tstring strAppData;
-
-  WinUTF8::UTF8string_to_wstring(strAppData2, strAppData);
-  
-  Colours = strAppData;
-  Colours += TEXT("colour*.xml");
-  ScanDirectory(Colours, vFileList); 
-
-  std::string strUserData2(GetStringParameter(SP_USER_LOC));
-  Tstring strUserData;
-
-  WinUTF8::UTF8string_to_wstring(strUserData2, strUserData);
-
-  Colours = strUserData;
-  Colours += TEXT("colour*.xml");
-  ScanDirectory(Colours, vFileList); 
-}
-
-void CDasher::ScanAlphabetFiles(std::vector<std::string> &vFileList) {
-  Tstring Alphabets;
-
-  // TODO: Is it okay to have duplicate names in the array?
-  std::string strAppData2(GetStringParameter(SP_SYSTEM_LOC));
-  Tstring strAppData;
-
-  WinUTF8::UTF8string_to_wstring(strAppData2, strAppData);
-  
-  Alphabets = strAppData;
-  Alphabets += TEXT("alphabet*.xml");
-  ScanDirectory(Alphabets, vFileList); 
-
-  std::string strUserData2(GetStringParameter(SP_USER_LOC));
-  Tstring strUserData;
-
-  WinUTF8::UTF8string_to_wstring(strUserData2, strUserData);
-
-  Alphabets = strUserData;
-  Alphabets += TEXT("alphabet*.xml");
-  ScanDirectory(Alphabets, vFileList); 
-}
-
-void CDasher::SetupPaths() {
+void CDasher::ScanFiles(AbstractParser *parser, const std::string &strPattern) {
   using namespace WinHelper;
   using namespace WinUTF8;
-
-  Tstring UserData, AppData;
-  std::string UserData2, AppData2;
-  GetUserDirectory(&UserData);
+  
+  Tstring pattern;
+  UTF8string_to_wstring(strPattern, pattern);
+  
+  std::vector<std::string> vFileList;
+  
+  Tstring AppData;
   GetAppDirectory(&AppData);
-  UserData += TEXT("dasher.rc\\");
   AppData += TEXT("system.rc\\");
-  CreateDirectory(UserData.c_str(), NULL);      // Try and create folders. Doesn't seem
   CreateDirectory(AppData.c_str(), NULL);       // to do any harm if they already exist.
-  wstring_to_UTF8string(UserData, UserData2);   // TODO: I don't know if special characters will work.
-  wstring_to_UTF8string(AppData, AppData2);     // ASCII-only filenames are safest. Being English doesn't help debug this...
-  SetStringParameter(SP_SYSTEM_LOC, AppData2);
-  SetStringParameter(SP_USER_LOC, UserData2);
+  string sysDir;
+  wstring_to_UTF8string(AppData,sysDir);
+  AppData += pattern;
+  ScanDirectory(AppData, vFileList);
+  for (vector<std::string>::iterator it=vFileList.begin(); it!=vFileList.end(); it++)
+    parser->ParseFile(sysDir + (*it),false);
+
+  vFileList.clear();
+  
+  Tstring UserData;
+  GetUserDirectory(&UserData);
+  UserData += TEXT("dasher.rc\\");
+  CreateDirectory(UserData.c_str(), NULL);      // Try and create folders. Doesn't seem
+  string userDir;
+  wstring_to_UTF8string(UserData,userDir);
+  UserData +=pattern;
+  ScanDirectory(UserData, vFileList); 
+  for (vector<std::string>::iterator it=vFileList.begin(); it!=vFileList.end(); it++)
+    parser->ParseFile(userDir + (*it),true);
 }
 
 int CDasher::GetFileSize(const std::string &strFileName) {
diff --git a/Src/Win32/Dasher.h b/Src/Win32/Dasher.h
index 7e51f33..05d51bf 100644
--- a/Src/Win32/Dasher.h
+++ b/Src/Win32/Dasher.h
@@ -65,9 +65,7 @@ public:
   virtual int GetFileSize(const std::string &strFileName);
 private:
 
-  virtual void ScanAlphabetFiles(std::vector<std::string> &vFileList);
-  virtual void ScanColourFiles(std::vector<std::string> &vFileList);
-  virtual void SetupPaths();
+  virtual void ScanFiles(AbstractParser *parser, const std::string &strPattern);
   virtual void CreateModules();
 
   void ScanDirectory(const Tstring &strMask, std::vector<std::string> &vFileList);
diff --git a/Src/iPhone/Classes/CDasherInterfaceBridge.h b/Src/iPhone/Classes/CDasherInterfaceBridge.h
index 064c70d..ef5e625 100644
--- a/Src/iPhone/Classes/CDasherInterfaceBridge.h
+++ b/Src/iPhone/Classes/CDasherInterfaceBridge.h
@@ -69,10 +69,8 @@ public:
   ///Override for asynchronous messages only...TODO?
   void Message(const string &strText, bool bInterrupt);
   Dasher::CGameModule *CreateGameModule(Dasher::CDasherView *pView,Dasher::CDasherModel *pModel);
+  void ScanFiles(AbstractParser *parser, const std::string &strPattern);
 private:
-  virtual void ScanAlphabetFiles(std::vector<std::string> &vFileList);
-  virtual void ScanColourFiles(std::vector<std::string> &vFileList);
-  virtual void SetupPaths();
   virtual void CreateModules();
   
   ///
@@ -81,6 +79,8 @@ private:
   
   void HandleEvent(int iParameter);
   
+  const NSString * const userPath;
+
   DasherAppDelegate *dasherApp;   // objc counterpart
   	
   Dasher::CIPhoneMouseInput *m_pMouseDevice;
diff --git a/Src/iPhone/Classes/CDasherInterfaceBridge.mm b/Src/iPhone/Classes/CDasherInterfaceBridge.mm
index 59ed81a..85405ff 100644
--- a/Src/iPhone/Classes/CDasherInterfaceBridge.mm
+++ b/Src/iPhone/Classes/CDasherInterfaceBridge.mm
@@ -20,6 +20,7 @@
 #import "TwoPushDynamicFilter.h"
 #import "EAGLView.h"
 #import "GameModule.h"
+#import "../Common/Globber.h"
 #import <iostream>
 #import <fcntl.h>
 
@@ -74,7 +75,9 @@ private:
   UIWebView *m_pWebView;
 };
 
-CDasherInterfaceBridge::CDasherInterfaceBridge(DasherAppDelegate *aDasherApp) : CDashIntfScreenMsgs(new COSXSettingsStore()), dasherApp(aDasherApp) {
+CDasherInterfaceBridge::CDasherInterfaceBridge(DasherAppDelegate *aDasherApp) : CDashIntfScreenMsgs(new COSXSettingsStore()),
+dasherApp(aDasherApp),
+userPath([[NSString stringWithFormat:@"%@/", [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]] retain]) {
 }
 
 void CDasherInterfaceBridge::CreateModules() {
@@ -108,6 +111,7 @@ CDasherInterfaceBridge::~CDasherInterfaceBridge() {
   delete m_pTwoFingerDevice;
   delete m_pUndoubledTouch;
   //(ACL)registered input filters should be automatically free'd by the module mgr?
+  [userPath release];
 }
 
 void CDasherInterfaceBridge::SetTiltAxes(Vec3 main, float off, Vec3 slow, float off2)
@@ -125,52 +129,35 @@ void CDasherInterfaceBridge::Realize() {
   [dasherApp glView].animating=YES;
 }
 
-void CDasherInterfaceBridge::SetupPaths() {
-  NSString *systemDir = [NSString stringWithFormat:@"%@/", [[NSBundle mainBundle] bundlePath]];
-  NSString *userDir = [NSString stringWithFormat:@"%@/", [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]];
+void CDasherInterfaceBridge::ScanFiles(AbstractParser *parser, const std::string &strPattern) {
 
-  SetStringParameter(SP_SYSTEM_LOC, StdStringFromNSString(systemDir));
-  SetStringParameter(SP_USER_LOC, StdStringFromNSString(userDir));
-}
-
-void CDasherInterfaceBridge::ScanAlphabetFiles(std::vector<std::string> &vFileList) {
+  string strPath(StdStringFromNSString([[NSBundle mainBundle] bundlePath])+"/"+strPattern);
+  const char *sys[2];
+  sys[0] = strPath.c_str();
+  sys[1] = NULL;
   
-  NSDirectoryEnumerator *dirEnum;
-  NSString *file;
+  const char *user[2]; user[1] = NULL;
+  if ([[NSFileManager defaultManager] fileExistsAtPath:userPath isDirectory:NULL]) {
+    user[0] = (StdStringFromNSString(userPath)+strPattern).c_str();
+  } else {
+    // userDir doesn't exist => create it, ready to receive stuff
+    (void)[[NSFileManager defaultManager] createDirectoryAtPath:userPath withIntermediateDirectories:YES attributes:nil error:nil];
+    user[0] = 0;
+  }
+  globScan(parser, user, sys);
 
-  dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:NSStringFromStdString(GetStringParameter(SP_SYSTEM_LOC))];
-  while (file = [dirEnum nextObject]) {
-    if ([file hasSuffix:@".xml"] && [file hasPrefix:@"alphabet"]) {
-      vFileList.push_back(StdStringFromNSString(file));
-    }
-  }  
-  
-  dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:NSStringFromStdString(GetStringParameter(SP_USER_LOC))];
-  while (file = [dirEnum nextObject]) {
-    if ([file hasSuffix:@".xml"] && [file hasPrefix:@"alphabet"]) {
-      vFileList.push_back(StdStringFromNSString(file));
-    }
-  }  
 }
 
-void CDasherInterfaceBridge::ScanColourFiles(std::vector<std::string> &vFileList) {
-  NSDirectoryEnumerator *dirEnum;
-  NSString *file;
-  
-  dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:NSStringFromStdString(GetStringParameter(SP_SYSTEM_LOC))];
-  while (file = [dirEnum nextObject]) {
-    if ([file hasSuffix:@".xml"] && [file hasPrefix:@"colour"]) {
-      vFileList.push_back(StdStringFromNSString(file));
-    }
-  }  
+/*void CDasherInterfaceBridge::ScanForFiles(AbstractFileParser *parser, const std::string &strName) {
+  NSFileManager *mgr = [NSFileManager defaultManager];
+  NSArray *names = [[mgr enumeratorAtPath:systemPath] allObjects];
+  if ([names containsObject:NSStringFromStdString(strName)])
+      parser->ParseFile(StdStringFromNSString(systemPath)+strName,false);
   
-  dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:NSStringFromStdString(GetStringParameter(SP_USER_LOC))];
-  while (file = [dirEnum nextObject]) {
-    if ([file hasSuffix:@".xml"] && [file hasPrefix:@"colour"]) {
-      vFileList.push_back(StdStringFromNSString(file));
-    }
-  }  
-}
+  names = [[mgr enumeratorAtPath:userPath] allObjects];
+  if ([names containsObject:NSStringFromStdString(strName)])
+    parser->ParseFile(StdStringFromNSString(userPath)+strName,true);
+}*/
 
 void CDasherInterfaceBridge::NewFrame(unsigned long iTime, bool bForceRedraw) {
   CDashIntfScreenMsgs::NewFrame(iTime, bForceRedraw);
@@ -277,7 +264,7 @@ void CDasherInterfaceBridge::WriteTrainFile(const std::string &filename,const st
   if(strNewText.length() == 0)
     return;
   
-  std::string strFilename(GetStringParameter(SP_USER_LOC) + filename);
+  std::string strFilename(StdStringFromNSString(userPath) + filename);
   
   NSLog(@"Write train file: %s", strFilename.c_str());
   
diff --git a/Src/iPhone/Dasher.xcodeproj/project.pbxproj b/Src/iPhone/Dasher.xcodeproj/project.pbxproj
index 6af3c32..f461384 100755
--- a/Src/iPhone/Dasher.xcodeproj/project.pbxproj
+++ b/Src/iPhone/Dasher.xcodeproj/project.pbxproj
@@ -216,6 +216,8 @@
 		3344FE660F71717C00506EAA /* XMLUtil.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3344FE130F71717C00506EAA /* XMLUtil.cpp */; };
 		334B1BF111232A8E007A6DFF /* ParametersController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 334B1BF011232A8E007A6DFF /* ParametersController.mm */; };
 		3354AF4811ADBAFD006CF570 /* Actions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3354AF4711ADBAFD006CF570 /* Actions.mm */; };
+		33557FC4142B484E002C600E /* Globber.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33557FC2142B484E002C600E /* Globber.cpp */; };
+		33558058142B7206002C600E /* Messages.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33558057142B7206002C600E /* Messages.cpp */; };
 		3360335813ABDF3700417DFE /* ScreenGameModule.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3360335713ABDF3700417DFE /* ScreenGameModule.cpp */; };
 		33627FBA0F7A82CF000C8818 /* training_albanian_SQ.txt in Resources */ = {isa = PBXBuildFile; fileRef = 33627F9A0F7A82CE000C8818 /* training_albanian_SQ.txt */; };
 		33627FBD0F7A82CF000C8818 /* training_bengali_BD.txt in Resources */ = {isa = PBXBuildFile; fileRef = 33627F9D0F7A82CE000C8818 /* training_bengali_BD.txt */; };
@@ -654,6 +656,9 @@
 		334B1C2011233B8B007A6DFF /* ModuleSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ModuleSettings.h; sourceTree = "<group>"; };
 		3354AF4611ADBAFD006CF570 /* Actions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Actions.h; sourceTree = "<group>"; };
 		3354AF4711ADBAFD006CF570 /* Actions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Actions.mm; sourceTree = "<group>"; };
+		33557FC2142B484E002C600E /* Globber.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Globber.cpp; sourceTree = "<group>"; };
+		33557FC3142B484E002C600E /* Globber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Globber.h; sourceTree = "<group>"; };
+		33558057142B7206002C600E /* Messages.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Messages.cpp; sourceTree = "<group>"; };
 		3360335613ABDF3700417DFE /* ScreenGameModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScreenGameModule.h; sourceTree = "<group>"; };
 		3360335713ABDF3700417DFE /* ScreenGameModule.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ScreenGameModule.cpp; sourceTree = "<group>"; };
 		33627F9A0F7A82CE000C8818 /* training_albanian_SQ.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = training_albanian_SQ.txt; sourceTree = "<group>"; };
@@ -1238,6 +1243,7 @@
 				3344FDDE0F71717C00506EAA /* MemoryLeak.cpp */,
 				3344FDDF0F71717C00506EAA /* MemoryLeak.h */,
 				33B342F313A8A8B2009AE0D5 /* Messages.h */,
+				33558057142B7206002C600E /* Messages.cpp */,
 				3344FDE00F71717C00506EAA /* ModuleManager.cpp */,
 				3344FDE10F71717C00506EAA /* ModuleManager.h */,
 				3344FDE20F71717C00506EAA /* NodeCreationManager.cpp */,
@@ -1343,6 +1349,8 @@
 		3344FE670F71718B00506EAA /* Common */ = {
 			isa = PBXGroup;
 			children = (
+				33557FC2142B484E002C600E /* Globber.cpp */,
+				33557FC3142B484E002C600E /* Globber.h */,
 				33EB49220F73E8B30048E7C2 /* OpenGLScreen.h */,
 				33EB49230F73E8B30048E7C2 /* OpenGLScreen.mm */,
 				334B1C2011233B8B007A6DFF /* ModuleSettings.h */,
@@ -1729,6 +1737,8 @@
 				33DFE416137AA32300F86CDE /* WordGeneratorBase.cpp in Sources */,
 				3360335813ABDF3700417DFE /* ScreenGameModule.cpp in Sources */,
 				33B6825013CA4CC300F0A952 /* DynamicButtons.cpp in Sources */,
+				33557FC4142B484E002C600E /* Globber.cpp in Sources */,
+				33558058142B7206002C600E /* Messages.cpp in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]