Ms Windows Resource Lint Header

Part of MsWindowsResourceLint. Save this as rcLint.h:

      //  this program parses RC files (as VC++ outputs them)
      //  and reports on CUA style issues

#pragma warning(disable: 4786) #include <string> #include <map> #include <assert.h> #include <iomanip> #include <vector> #include <sstream> #include <iostream>

using std::cout; using std::endl; using std::ostream; using std::string; typedef std::map< string, string > strings_t; typedef std::vector<string> spec_t; typedef string::size_type size_type;

class Source { public: Source(string const & rc = ""): m_rc(rc), m_bot(0), m_eot(0) {}

void setResource(string const & rc) { m_rc = rc; } size_type getBOT() { return m_bot; } string const & getPriorToken() { return m_priorToken; } string const & getCurrentToken() { return m_currentToken; }

string const & pullNextToken() { m_priorToken = m_currentToken; extractNextToken(); return m_currentToken; }

size_type getLineNumber(size_type at) { size_type lineNumber = 1;

for(size_type idx(0); idx < at; ++idx) if ('\n' == m_rc[idx]) ++lineNumber;

return lineNumber; }

string getLine(size_type at) { size_type bol = m_rc.rfind('\n', at); if (string::npos == bol) bol = 0; else ++bol; size_type eol = m_rc.find('\n', at); if (string::npos == eol) eol = m_rc.length(); else ++eol; return m_rc.substr(bol, eol - bol); }

private:

string const & extractNextToken() { char static const delims[] = " \t\n,";

m_bot = m_rc.find_first_not_of(delims, m_eot);

if (string::npos == m_bot) m_currentToken = ""; else if (m_rc[m_bot] == '"') m_currentToken = parseString(); else if (m_rc.substr(m_bot, 2) == "//") { if (skipUntil("\n")) return extractNextToken(); } else if (m_rc.substr(m_bot, 2) == "/*") { if (skipUntil("*/")) return extractNextToken(); } /* else if (m_rc.substr(m_bot, 1) == "#") { string line = getLine(m_bot); size_type at(0); while(isspace(line[at]) && at < line.size()) ++at;

if ('#' == line[at]) { m_eot = m_bot + 1; if (skipUntil("\n")) return extractNextToken(); } }*/ else { m_eot = m_rc.find_first_of(" \n,/", m_bot); m_currentToken = m_rc.substr(m_bot, m_eot - m_bot); }

if ('#' == m_currentToken[0]) { // assert(m_rc.substr(m_bot, 1) == "#"); string line = getLine(m_bot); size_type at(0); while(isspace(line[at]) && at < line.size()) ++at;

if ('#' == line[at]) { --m_eot; if (skipUntil("\n")) return extractNextToken(); } }

return m_currentToken; }

bool skipUntil(char const * delimiter) { m_eot = m_rc.find(delimiter, m_eot + 1);

if (string::npos == m_eot) { m_currentToken = ""; return false; } m_eot += strlen(delimiter); return true; }

char parseStringChar() { if (m_rc[m_eot] == '\\') { m_eot += 1; char escapee(m_rc[m_eot++]);

switch (escapee) { case 'n' : return '\n'; case 'r' : return '\r'; case 't' : return '\t'; case '0' : return '\0'; case '\\': return '\\'; case 'a' : return '\a'; default : // TODO \x, \v \b, \f if (isdigit(escapee)) { string slug = m_rc.substr(m_eot - 1, 3); return char(strtol(slug.c_str(), NULL, 8)); } else //assert(false); return escapee; } } else if (m_rc[m_eot] == '"' && m_rc[m_eot+1] == '"') m_eot++;

return m_rc[m_eot++]; }

string parseString() { m_eot = m_bot + 1; string z;

while ( m_eot < m_rc.length() && ( m_rc[m_eot] != '"' || m_rc[m_eot + 1] == '"' ) ) z += parseStringChar();

if (m_eot < m_rc.length()) m_eot += 1;

return z; }

string m_rc; size_type m_bot; size_type m_eot; string m_priorToken; string m_currentToken; };

class Resource;

class ResourceHandle? { public: // don't read this it's just a sharing smart pointer... ResourceHandle?(Resource *p = NULL): m_pInt(new int(p != NULL)), m_p(p) {} Resource *get() { return m_p; } Resource *operator->() { return m_p; } ResourceHandle?(ResourceHandle? const & rh): m_pInt(rh.m_pInt), m_p(rh.m_p) { ++(*m_pInt); } ~ResourceHandle?(); private: Resource * m_p; int * m_pInt; };

class ComplaintDepartment? { public: virtual string getLine(size_type at) = 0; virtual size_type getLineNumber(size_type at) = 0; virtual void complain(string const & description, size_type bot, string evidence = "") = 0; virtual string getString(string const & id) = 0; virtual Resource * getAccelerators(string const & menuID) = 0; }; // DependencyInversionPrinciple

class Resource { public: void setBeginningOfText(size_type bot) { m_bot = bot; } string getID() { return getSpec(0); } virtual Resource * clone() { return new Resource(*this); } virtual Resource * get(string /*id*/) { return NULL; } virtual Resource * get(int /*idx*/) { return NULL; } virtual Resource * get_(int /*idx*/) { return NULL; } virtual void parse(Source & /*aSource*/) { } virtual string getLabel() { return getSpec(getLabelIndex()); } virtual void LintOne?(ComplaintDepartment? & ) {} virtual void LintAll?(ComplaintDepartment? & ) {} size_type getBeginningOfText() { return m_bot; } int getSpecCount() const { return m_spec.size(); } string const & getSpec(int x) const { return m_spec[x]; } void addSpec(string const & token) { m_spec.push_back(token); } virtual bool weBePromptLabel() { return false; }

char getLabelHotkey() { string label = getLabel(); size_type at = label.find('&');

if ( string::npos != at && (at + 2) < label.length() && '&' != label[at + 1] ) return label[at + 1];

return '\0'; }

virtual void printTree(ostream & out, int depth = 1) { out << std::setw(depth) << " " << getID() << endl; }

void LintLabelPrompt?(ComplaintDepartment? & aCD) // TODO oaoo? { char hotKey = this->getLabelHotkey(); if (!hotKey) aCD.complain("Missing & in Menu Label", m_bot); // TODO make m_bot private }

private: virtual int getLabelIndex() { return 1; } spec_t m_spec; size_type m_bot; };

class // TODO or Accelerator Control: public Resource { public:

bool weBePromptLabel() { return "LTEXT" == getSpec(1) && ':' == *getLabel().rbegin(); }

private: virtual int getLabelIndex() { return 2; } Resource * clone() { return new Control(*this); }

bool weBeButton() { return "CONTROL" == getSpec(1) && "Button" == getSpec(4); }

bool weBePushButton() { return ( "PUSHBUTTON" == getSpec(1) || "DEFPUSHBUTTON" == getSpec(1) ) && "IDOK" != getSpec(3) && "IDCANCEL" != getSpec(3); }

virtual void LintOne?(ComplaintDepartment? & aCD) {

// TODO put quotes back on strings

if ( weBeButton() || weBePromptLabel() || weBePushButton() ) { // cout << getLabel() << endl; if (!getLabelHotkey()) aCD.complain("Button missing hotkey", getBeginningOfText());

} }

};

string toString(int i) { std::stringstream z; z << i; return z.str(); }

class ResourceCollection?: public Resource { public:

void add(Resource & nu) { m_Resources[nu.getID()] = nu.clone(); }

void printTree(ostream & out, int depth = 1) { Resource::printTree(out, depth);

for( Resources_t::iterator it = m_Resources.begin(); it != m_Resources.end(); ++it ) it->second->printTree(out, depth + 5); } // TODO this merged with LintAll?() == Visitor

int getCount() { return m_Resources.size(); }

Resource * get(string id) { Resources_t::iterator it = m_Resources.find(id); if (it == m_Resources.end()) return NULL; return it->second.operator->(); }

Resource * get(int idx) { Resources_t::iterator it = m_Resources.begin(); while (it != m_Resources.end() && idx--) ++it; return it != m_Resources.end()? it->second.operator->(): NULL; }

Resource * get_(int idx) { return get(toString(idx));/* Resources_t::iterator it = m_Resources.begin(); while (it != m_Resources.end() && idx--) ++it; return it != m_Resources.end()? it->second.operator->(): NULL;*/ }

virtual string parseName(Source & /*aSource*/) { return ""; }

void parseResource(ResourceCollection? & aSink, Source & aSource) { setBeginningOfText(aSource.getBOT()); addSpec(parseName(aSource)); string begin;

do { begin = aSource.pullNextToken(); if (begin == "") return; } while("BEGIN" != begin);

aSource.pullNextToken();

for(;;) { string token = aSource.getCurrentToken(); if ("" == token || "END" == token) break; parse(aSource); } aSink.add(*this); }

void LintAll?(ComplaintDepartment? & aCD) { for( Resources_t::iterator it = m_Resources.begin(); it != m_Resources.end(); ++it ) { it->second->LintOne?(aCD); it->second->LintAll?(aCD); } }

void LintDupedLabelHotKeys?(ComplaintDepartment? & aCD, string type) { Resource * pControl = NULL;

for ( int idx(0); pControl = get(idx); ++idx ) if (char hotKey = pControl->getLabelHotkey()) { string evidence; Resource * pControl2 = NULL;

for ( int idx2(idx + 1); pControl2 = get(idx2); ++idx2 ) // TODO OAOO { char hotKey2 = pControl2->getLabelHotkey();

if (hotKey == hotKey2) evidence += aCD.getLine(pControl2->getBeginningOfText()); } if ("" != evidence) aCD.complain( "Duplicated " + type + " hotkey", pControl->getBeginningOfText(), evidence ); } }

virtual bool isFirstTokenOfNextItem(string const & token) { return false; }

void initResource(Resource & aResource, Source & aSource) { aResource.addSpec(toString(getCount())); aResource.addSpec(aSource.getCurrentToken()); aResource.setBeginningOfText(aSource.getBOT()); string token = aSource.pullNextToken();

while (token != "END") { if ( isFirstTokenOfNextItem(token) ) break;

aResource.addSpec(token); token = aSource.pullNextToken(); } add(aResource); }

private: typedef std::map< string, ResourceHandle? > Resources_t; Resources_t m_Resources; };

ResourceHandle?::~ResourceHandle?() // permits derived Resource objects to occupy std::map<> { --(*m_pInt); if(!m_pInt) { delete m_p; delete m_pInt; } }

class MenuHierarchy?: public ResourceCollection? { public: void setMenuID(string const & nu) { m_MenuID = nu; } string const & getMenuID() { return m_MenuID; } virtual void addMenuItem(Source & aSource); void parse(Source & aSource);

private: string m_MenuID; };

class // also string in STRINGTABLE MenuItem?: public MenuHierarchy? { public:

private: Resource * clone() { return new MenuItem?(*this); }

void complainAbout(ComplaintDepartment? & aCD, char const * description, Resource & aResource) { aCD.complain ( description, getBeginningOfText(), aCD.getLine(aResource.getBeginningOfText()) ); }

void LintAccelerator?(ComplaintDepartment? & aCD, Resource & aControl) { if (getID() == aControl.getSpec(2)) // TODO make getLabel get the label { size_type at = getLabel().find('\t');

if (string::npos == at) complainAbout(aCD, "Missing accelerator prompt in Menu Label", aControl); else { string slice = getLabel().substr(at); string keyCap = slice.substr(slice.length() - 1, 1);

if (keyCap != aControl.getSpec(1)) complainAbout(aCD, "Wrong accelerator prompt in Menu Label", aControl); } } }

void LintAccelerators?(ComplaintDepartment? & aCD, Resource & anAccelerator) { for(int idx(0);;++idx) { Resource * pControl = anAccelerator.get(idx); if (!pControl) break; LintAccelerator?(aCD, *pControl); } }

void LintOne?(ComplaintDepartment? & aCD) { LintLabelPrompt?(aCD);

string help = aCD.getString(getID());

if ("" == help) aCD.complain("Missing StringTable? help for MenuItem?", this->getBeginningOfText());

Resource * p = aCD.getAccelerators(getMenuID()); if (p) LintAccelerators?(aCD, *p); }

};

class Popup: public MenuHierarchy? { public:

Resource *clone() { return new Popup(*this); }

string parseName(Source & aSource) { return aSource.pullNextToken(); }

private:

void LintOne?(ComplaintDepartment? & aCD) { LintLabelPrompt?(aCD); LintDupedLabelHotKeys?(aCD, "MenuItem?"); }

private: virtual int getLabelIndex() { return 0; }

};

void MenuHierarchy?::addMenuItem(Source & aSource) { string label = aSource.pullNextToken(); assert(label.size()); if ("SEPARATOR" == label) { aSource.pullNextToken(); return; } size_type bot = aSource.getBOT(); // TODO getBeginningOfText() string id = aSource.pullNextToken(); MenuItem? anItem; anItem.setBeginningOfText(bot); anItem.addSpec(id); anItem.addSpec(label); anItem.setMenuID(getMenuID()); string token;

for (;;) { token = aSource.pullNextToken();

if ( "END" == token || "POPUP" == token || "MENUITEM" == token) break;

anItem.addSpec(token); } add(anItem); }

void MenuHierarchy?::parse(Source & aSource) { string token = aSource.getCurrentToken();

if ("MENUITEM" == token) addMenuItem(aSource); else if ("POPUP" == token) { Popup aPopup; aPopup.setMenuID(getMenuID()); aPopup.parseResource(*this, aSource); token = aSource.pullNextToken(); } else assert(false); // syntax error in your resource code! Look at token and m_bot }

class Menu: public MenuHierarchy? { public:

Resource *clone() { return new Menu(*this); }

string parseName(Source & aSource) { string token = aSource.getPriorToken(); // TODO commonalize this setMenuID(token); return token; }

};

class StringTable?: public ResourceCollection? { public: StringTable?(int idx): m_idx(toString(idx)) {}

private: string parseName(Source & aSource) { return m_idx; }

void parse(Source & aSource) { string token = aSource.getCurrentToken(); Resource anEntry; anEntry.setBeginningOfText(aSource.getBOT()); anEntry.addSpec(token); anEntry.addSpec(aSource.pullNextToken()); add(anEntry); aSource.pullNextToken(); }

Resource *clone() { return new StringTable?(*this); } string m_idx; };

class Accelerators: public ResourceCollection? { Resource *clone() { return new Accelerators(*this); }

string parseName(Source & aSource) { return aSource.getPriorToken() + " ACCELERATORS"; }

bool isFirstTokenOfNextItem(string const & token) { return (1 == token.length() && "|" != token) || "VK_" == token.substr(0, 3); }

void parse(Source & aSource) { Control aCtrl; initResource(aCtrl, aSource); }

};

class Dialog: public ResourceCollection? { public:

Resource *clone() { return new Dialog(*this); }

string parseName(Source & aSource) // get prior name class?? TODO { return aSource.getPriorToken(); }

void LintPromptLabel?(ComplaintDepartment? & aCD, Resource & aResource, int idx) {

if (Resource * pNext = get(toString(idx + 1))) { bool found (false);

for(int x(0); x < pNext->getSpecCount(); ++x) { if ("WS_TABSTOP" == pNext->getSpec(x)) { found = true; break; } } if (!found) aCD.complain( "Prompt before control without tabstop", aResource.getBeginningOfText() ); } else { aCD.complain( "Prompt at end of control list", aResource.getBeginningOfText() ); }

}

void LintOne?(ComplaintDepartment? & aCD) { LintDupedLabelHotKeys?(aCD, "Control");

Resource * pResource = NULL;

for ( int idx(0); pResource = get(toString(idx)); ++idx ) if (pResource->weBePromptLabel()) LintPromptLabel?(aCD, *pResource, idx); }

bool isFirstTokenOfNextItem(string const & token) { return token == "LTEXT" || token == "RTEXT" || token == "DEFPUSHBUTTON" || token == "PUSHBUTTON" || token == "AUTO3STATE" || token == "AUTOCHECKBOX" || token == "AUTORADIOBUTTON" || token == "CHECKBOX" || token == "COMBOBOX" || token == "CONTROL" || token == "CTEXT" || token == "EDITTEXT" || token == "GROUPBOX" || token == "ICON" || token == "LISTBOX" || token == "PUSHBOX" || token == "RADIOBUTTON" || token == "SCROLLBAR" || token == "STATE3"; }

void parse(Source & aSource) { Control aCtrl; initResource(aCtrl, aSource); }

};

class ResourceFile? : public ResourceCollection?, public ComplaintDepartment? { public:

Resource *clone() { return NULL; } // the buck stops here ResourceFile?(): m_pComplaints(NULL) {}

virtual size_type getLineNumber(size_type at) { return m_Source.getLineNumber(at); }

virtual string getLine(size_type at) { return m_Source.getLine(at); }

string getString(string const & id) { int idx(0);

for(;;) { Resource * p = get(toString(idx++)); if (!p) return ""; p = p->get(id); if (p) return p->getLabel(); } }

void callLint(ostream & complaints) { m_pComplaints = &complaints; LintAll?(*this); m_pComplaints = NULL; }

Resource * getAccelerators(string const & menuID) { return get(menuID + " ACCELERATORS"); }

void complain(string const & description, size_type bot, string evidence = "") { size_type lineNumber(getLineNumber(bot)); *m_pComplaints << getID() << '(' << lineNumber << "): " << description << "\n"; *m_pComplaints << getLine(bot); if ("" != evidence) *m_pComplaints << evidence; *m_pComplaints << "\n"; }

void parseFile( string const & rc, string const & fileName = "" ) { m_Source.setResource(rc); setBeginningOfText(m_Source.getBOT()); addSpec(fileName); int idx(0);

for(;;) { string token = m_Source.pullNextToken(); if ("" == token) return;

if ("MENU" == token) { Menu().parseResource(*this, m_Source); } else if ("DIALOG" == token || "DIALOGEX" == token) { Dialog().parseResource(*this, m_Source); } else if ("ACCELERATORS" == token) { Accelerators().parseResource(*this, m_Source); } else if ("STRINGTABLE" == token) { StringTable?(idx++).parseResource(*this, m_Source); } else { // std::cerr << "Unrecognized: " << token << endl; } } }

// TODO everyone deals with END the same way.

private: Source m_Source; ostream * m_pComplaints; };


CategoryLint


EditText of this page (last edited April 19, 2011) or FindPage with title or text search