Ms Windows Resource Lint Source
      Part of MsWindowsResourceLint. Save as rcLint.cpp, and create a project file for this:
        //  this program parses RC files (as VC++ outputs them)
        //  and reports on CUA style issues
      
      
        #include "rcLint.h"
        #include "test.h"
        #include 
      
      
        using std::stringstream;
        using std::ofstream;
        bool TestCase::all_tests_passed(true);
        TestCase::TestCases_t TestCase::cases;
      
      
        ////////////////////////////////////////////////////
        ///   tests on the token source
      
      
        TEST_(TestCase, pullNextToken)
        {
      
      
        Source aSource("a b\nc\n  d");
      
      
        string 
        token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("a", token);
        token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("b", token);
        token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("c", token);
        token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("d", token);
        token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("" , token);  //  EOF!
      
      
        }
      
      
        TEST_(TestCase, pullNextToken_comma)
        {
      
      
        Source aSource("a , b\nc, \n  d");
      
      
        string 
        token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("a", token);
        token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("b", token);
        token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("c", token);
        token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("d", token);
        token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("", token);  //  EOF!
      
      
        }
      
      
        struct
        TestTokens:  TestCase
        {
      
      
        void
        test_a_b_d(string input)
        {
        Source aSource(input);
        string 
        token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("a", token);
        token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("b", token);
        //    token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("c", token);
        token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("d", token);
        token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("", token);  //  EOF!
        }
      
      
        };
      
      
        TEST_(TestTokens, elideComments)
        {   
        test_a_b_d("a b\n //c\n  d");
        test_a_b_d("a b\n//c \n  d");
        test_a_b_d("a b\n // c \"neither\" \n  d");
        test_a_b_d("a b\n // c \"neither\" \n  d//");
        test_a_b_d("//\na b\n // c \"neither\" \n  d//");
        test_a_b_d("//c\na b\n // c \"neither\" \n  d//");
        test_a_b_d("// c\na b\n // c \"neither\" \n  d//");
        test_a_b_d("//c \na b\n // c \"neither\" \n  d//");
        test_a_b_d("// \na b\n // c \"neither\" \n  d//");
        test_a_b_d(" // \na b\n // c \"neither\" \n  d//");
        }
      
      
        TEST_(TestTokens, elideStreamComments)
        {   
        test_a_b_d("a b\n /*c*/\n  d");
        test_a_b_d("a b\n/*c*/ \n  d");
        test_a_b_d("a b\n /* c \"neither\" */\n  d");
        test_a_b_d("a b\n /* c \"neither\" \n */ d//");
        test_a_b_d("//\na b\n /* c \"neither\" */ \n  d/**/");
        test_a_b_d("//c\na b\n // c \"neither\" \n  d/* */");
        test_a_b_d("/* c\n*/a b\n // c \"neither\" \n  d//");
        test_a_b_d("//c \na b\n // c \"neither\" \n  d//");
        test_a_b_d("// \na b\n // c \"neither\" \n  d//");
        test_a_b_d(" // \na b\n // c \"neither\" \n  d//");
        }
      
      
        TEST_(TestTokens, elidePreprocessorStatements)
        {
        test_a_b_d("a b\n #c\n  d");
        test_a_b_d("a b\n#c \n  d");
        test_a_b_d("a b\n # c \"neither\" \n  d");
        test_a_b_d("a b\n #\n  d");
        test_a_b_d("a b\n#\n  d");
        Source aSource("a b # \n  d");
        string 
        token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("a", token);
        token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("b", token);
        token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("#", token);
        token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("d", token);
        token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("", token);  //  EOF!
        }
      
      
        TEST_(TestCase, pullNextTokenString)
        {
      
      
        Source aSource("a b\n\"c\\n  d\"");
      
      
        string token = aSource.pullNextToken();
        CPPUNIT_ASSERT_EQUAL("a", token);
      
      
        token = aSource.pullNextToken();
        CPPUNIT_ASSERT_EQUAL("b", token);
      
      
        token = aSource.pullNextToken();
        string expect = "c\n  d";
        CPPUNIT_ASSERT_EQUAL(expect, token);
      
      
        token = aSource.pullNextToken();
        CPPUNIT_ASSERT_EQUAL("", token);  //  EOF!
      
      
        }
      
      
        TEST_(TestCase, pullNextTokenWithComma)
        {
      
      
        Source aSource("a b\n\"c  d\",e,f, g");
      
      
        string token = aSource.pullNextToken();
        CPPUNIT_ASSERT_EQUAL("a", token);
      
      
        token = aSource.pullNextToken();
        CPPUNIT_ASSERT_EQUAL("b", token);
      
      
        token = aSource.pullNextToken();
        string expect = "c  d";
        CPPUNIT_ASSERT_EQUAL(expect, token);
      
      
        token = aSource.pullNextToken();
        CPPUNIT_ASSERT_EQUAL("e", token);
      
      
        token = aSource.pullNextToken();
        CPPUNIT_ASSERT_EQUAL("f", token);
      
      
        token = aSource.pullNextToken();
        CPPUNIT_ASSERT_EQUAL("g", token);
      
      
        token = aSource.pullNextToken();
        CPPUNIT_ASSERT_EQUAL("", token);  //  EOF!
      
      
        }
      
      
        ////////////////////////////////////////////////////
        ///   tests on the parser & object model
      
      
        TEST_(TestCase, StringTable)
        {
      
      
        string rc = "STRINGTABLE PRELOAD MOVEABLE DISCARDABLE\n"
        "BEGIN  //  this is a comment \n"
        "    IDS_OBVIOUS1  \"I like food\"\n"
        "    IDS_OBVIOUS2  \"Food is good\"\n"
        "    IDS_OBVIOUS3  \"\"\"Eat good food\"\"\"\n"  //  <-- escaped strings
        " // IDS_DONT_PARSE_ME  \"Don't parse me\"\n"  //  <-- escaped strings
        "END\n";
      
      
        ResourceFile aResourceFile;
        aResourceFile.parseFile(rc);
      
      
        CPPUNIT_ASSERT_EQUAL("I like food", aResourceFile.getString("IDS_OBVIOUS1"));
        CPPUNIT_ASSERT_EQUAL("Food is good", aResourceFile.getString("IDS_OBVIOUS2"));
        string expect = "\"Eat good food\"";
        CPPUNIT_ASSERT_EQUAL(expect, aResourceFile.getString("IDS_OBVIOUS3"));
        CPPUNIT_ASSERT_EQUAL("", aResourceFile.getString("IDS_NOT_OBVIOUS"));
      
      
        }
      
      
        TEST_(TestCase, Dialog)
        {
      
      
        string rc = "IDD_ABOUTBOX DIALOG DISCARDABLE  0, 0, 217, 55\n"
        "STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU\n"
        "CAPTION \"About HostApp\"\n"
        "FONT 8, \"MS Sans Serif\"\n"
        "BEGIN\n"
        "    ICON            IDR_MAINFRAME,IDC_STATIC,11,17,20,20\n"
        "    LTEXT           \"Pyramus && Thisby\",IDC_STATIC,40,10,119,8,SS_NOPREFIX\n"
        "    LTEXT           \"Copyright (C) 2098\",IDC_STATIC,40,25,119,8\n"
        "    DEFPUSHBUTTON   \"Okay\",IDOK,178,7,32,14,WS_GROUP\n"
        "END\n";
      
      
        ResourceFile aResourceFile;
        aResourceFile.parseFile(rc);
        Resource * pDlg = aResourceFile.get("IDD_ABOUTBOX");
        CPPUNIT_ASSERT_EQUAL("IDD_ABOUTBOX",      pDlg->getID());
        CPPUNIT_ASSERT_EQUAL(8,                   pDlg->get("0")->getSpecCount());
        CPPUNIT_ASSERT_EQUAL("ICON",              pDlg->get("0")->getSpec(1));
        CPPUNIT_ASSERT_EQUAL("Pyramus && Thisby", pDlg->get("1")->getSpec(2));
        CPPUNIT_ASSERT_EQUAL("Pyramus && Thisby", pDlg->get("1")->getLabel());
        CPPUNIT_ASSERT_EQUAL("IDC_STATIC",        pDlg->get("2")->getSpec(3));
        CPPUNIT_ASSERT_EQUAL("178",               pDlg->get("3")->getSpec(4));
        CPPUNIT_ASSERT_EQUAL(NULL,                pDlg->get("4"));
      
      
        }
      
      
        TEST_(TestCase, AccelTable)
        {
      
      
        string rc = "IDPLATYPUS ACCELERATORS LOADONCALL MOVEABLE DISCARDABLE \n"
        "BEGIN\n"
        "    VK_INSERT, IDM_EDITCOPY,   VIRTKEY, CONTROL\n"
        "    VK_INSERT, IDM_EDITPASTE,  VIRTKEY, SHIFT\n"
        "    VK_INSERT, IDM_EDITPASTE,  VIRTKEY, SHIFT | CONTROL\n"
        "    VK_INSERT, IDM_EDITPASTE,  VIRTKEY, SHIFT | CONTROL | ALT\n"
        "    VK_BACK,   IDM_EDITUNDO,   VIRTKEY, ALT\n"
        "    VK_F5,     IDM_EDITTIME,   VIRTKEY\n"
        "    \"A\",     IDM_SEARCHNEXT, VIRTKEY, CONTROL\n"
        "    \"B\",     IDM_SEARCHPREV, VIRTKEY\n"
        "END\n";
      
      
        //  TODO  can the control items pipe? Can they be in any order?
      
      
        ResourceFile aResourceFile;
        aResourceFile.parseFile(rc);
        Resource * anAccelTable = aResourceFile.get("IDPLATYPUS ACCELERATORS");
        assert(anAccelTable);
        CPPUNIT_ASSERT_EQUAL(5,               anAccelTable->get("0")->getSpecCount());
        CPPUNIT_ASSERT_EQUAL("VK_INSERT",     anAccelTable->get("0")->getSpec(1));
        CPPUNIT_ASSERT_EQUAL("IDM_EDITPASTE", anAccelTable->get("1")->getSpec(2));
        CPPUNIT_ASSERT_EQUAL("VIRTKEY",       anAccelTable->get("2")->getSpec(3));
        CPPUNIT_ASSERT_EQUAL(9,               anAccelTable->get("3")->getSpecCount());
        CPPUNIT_ASSERT_EQUAL("SHIFT",         anAccelTable->get("3")->getSpec(4));
        CPPUNIT_ASSERT_EQUAL("|",             anAccelTable->get("3")->getSpec(5));
        CPPUNIT_ASSERT_EQUAL(5,               anAccelTable->get("4")->getSpecCount());
        CPPUNIT_ASSERT_EQUAL("ALT",           anAccelTable->get("4")->getSpec(4));
      
      
        }
      
      
        TEST_(TestCase, Menu)
        {
      
      
        string rc = "IDPLATYPUS MENU LOADONCALL MOVEABLE DISCARDABLE\n"
        "BEGIN\n"
        "    POPUP \"&File\"\n"
        "    BEGIN\n"
        "        MENUITEM \"&New\",          IDM_FILENEW\n"
        "        MENUITEM \"&Open...\",      IDM_FILEOPEN, GRAYED\n"
        "  //      MENUITEM \"&Kozmik...\",      IDM_KOZMIK\n"
        "    END\n"
        "END\n";
      
      
        ResourceFile aResourceFile;
        aResourceFile.parseFile(rc);
      
      
        Resource & aMenu = *aResourceFile.get("IDPLATYPUS");
        CPPUNIT_ASSERT_EQUAL("IDPLATYPUS", aMenu.getID());
      
      
        string popupID = "&File";
        Resource * pPopup = aMenu.get(popupID);
        CPPUNIT_ASSERT_EQUAL(popupID, pPopup->getID());
      
      
        CPPUNIT_ASSERT_EQUAL("&New",     pPopup->get("IDM_FILENEW" )->getLabel());
        CPPUNIT_ASSERT_EQUAL("&Open...", pPopup->get("IDM_FILEOPEN")->getLabel());
      
      
        }
      
      
        TEST_(TestCase, SubMenu)
        {
      
      
        string rc = "IDPLATYPUS MENU LOADONCALL MOVEABLE DISCARDABLE\n"
        "BEGIN\n"
        "    POPUP \"&File\"\n"
        "    BEGIN\n"
        "        MENUITEM \"&New\",               IDM_FILENEW\n"
        "        MENUITEM \"&Open...\",           IDM_FILEOPEN\n"
        "        POPUP \"&Menu\"\n"
        "        BEGIN\n"
        "            MENUITEM \"&Frog\",          IDM_FROG\n"
        "            MENUITEM \"&Soup\",          IDM_SOUP\n"
        "        END\n"
        "    END\n"
        "END\n";
      
      
        ResourceFile aResourceFile;
        aResourceFile.parseFile(rc);
      
      
        Resource & aMenu = *aResourceFile.get("IDPLATYPUS");
        CPPUNIT_ASSERT_EQUAL("IDPLATYPUS", aMenu.getID());
      
      
        string popupID = "&File";
        Resource * pPopup = aMenu.get(popupID);
        CPPUNIT_ASSERT_EQUAL(popupID, pPopup->getID());
      
      
        CPPUNIT_ASSERT_EQUAL("&New",       pPopup->get("IDM_FILENEW" )->getLabel());
        CPPUNIT_ASSERT_EQUAL("&Open...",   pPopup->get("IDM_FILEOPEN")->getLabel());
      
      
        Resource & bPopup = *pPopup->get("&Menu");
      
      
        CPPUNIT_ASSERT_EQUAL("&Frog",      bPopup.get("IDM_FROG")->getLabel());
        CPPUNIT_ASSERT_EQUAL("&Soup",      bPopup.get("IDM_SOUP")->getLabel());
        CPPUNIT_ASSERT_EQUAL("IDPLATYPUS", dynamic_cast
      
        }
      
      
        struct
        TestResourceFile:  public TestCase
        {
      
      
        ResourceFile m_aResourceFile;
      
      
        void
        setUp()
        {
        m_aResourceFile.parseFile("NIBELUNG_MENU MENU DISCARDABLE \n"
        "BEGIN\n"
        "    POPUP \"&Brunhilde\"\n"
        "    BEGIN\n"
        "        MENUITEM SEPARATOR\n"
        "    END\n"
        "END\n"
        "\n"
        "STRINGTABLE PRELOAD MOVEABLE DISCARDABLE\n"
        "BEGIN\n"
        "    IDS_SNOW        \"Snow\"\n"
        "    IDS_ANGEL       \"Angel\\n\"\n"
        "    IDS_COMMITTEE   \"Committee\"\n"
        "END\n");
        }
      
      
        };
      
      
        TEST_(TestResourceFile, MenuWithOnlySeparator)
        {
        CPPUNIT_ASSERT_EQUAL("Snow"     , m_aResourceFile.getString("IDS_SNOW"     ));
        CPPUNIT_ASSERT_EQUAL("Angel\n"  , m_aResourceFile.getString("IDS_ANGEL"    ));
        CPPUNIT_ASSERT_EQUAL("Committee", m_aResourceFile.getString("IDS_COMMITTEE"));
        }
      
      
        TEST_(TestResourceFile, getLine)
        {
      
      
        //NIBELUNG_MENU MENU DISCARDABLE nBEGINn    POPUP "&Brunhilde"n    BEGINn        MENUITEM SEPARATORn    ENDnENDnnSTRINGTABLE PRELOAD MOVEABLE DISCARDABLEnBEGINn    IDS_SNOW        "Snow"n    IDS_ANGEL       "Angel\n"n    IDS_COMMITTEE   "Committee"nENDn
      
      
        string expect = "    POPUP \"&Brunhilde\"\n";
        CPPUNIT_ASSERT_EQUAL(expect,                         m_aResourceFile.getLine(43));
        CPPUNIT_ASSERT_EQUAL("        MENUITEM SEPARATOR\n", m_aResourceFile.getLine(80));
        CPPUNIT_ASSERT_EQUAL(3, m_aResourceFile.getLineNumber(43));
        CPPUNIT_ASSERT_EQUAL(5, m_aResourceFile.getLineNumber(80));
        }
      
      
        ////////////////////////////////////////////////////
        ///   tests for lint system
      
      
        //  TODO  &&, & on end
        //  TODO  "spike sample" fixture for the linter - proselytize
      
      
        struct
        TestLint:  TestCase
        {
        ResourceFile m_aResourceFile;
      
      
        void
        findOneError(string const & rc, string const & expect)
        {
        m_aResourceFile.parseFile(rc, "fileName.rc");
        stringstream complaints;
        m_aResourceFile.callLint(complaints);
        CPPUNIT_ASSERT_EQUAL(expect, complaints.str());
        }
      
      
        };
      
      
        TEST_(TestLint, PromptAtEndOfControlList)
        {
      
      
        string spike = "    LTEXT           \"&Address:\",IDC_STATIC,7,7,30,8\n";
      
      
        string rc = "IDD_CALL DIALOG DISCARDABLE  0, 0, 242, 79\n"
        "BEGIN\n"
        + spike +
        "END\n";
      
      
        findOneError( rc, "fileName.rc(3): Prompt at end of control list\n"
        + spike +
        "\n" );
      
      
        }
      
      
        TEST_(TestLint, PromptBeforeControlWithoutTabstop)
        {
      
      
        string spike = "    LTEXT           \"&Address:\",IDC_STATIC,7,7,30,8\n";
      
      
        string rc = "IDD_CALL DIALOG DISCARDABLE  0, 0, 242, 79\n"
        "BEGIN\n"
        + spike +
        "    EDITTEXT        IDE_ADDR,39,7,125,12,ES_AUTOHSCROLL\n"
        "END\n";
      
      
        findOneError( rc, "fileName.rc(3): Prompt before control without tabstop\n"
        + spike +
        "\n" );
      
      
        }
      
      
        TEST_(TestLint, PromptMissingHotkey)
        {
      
      
        string spike = "    LTEXT           \"Address:\",IDC_STATIC,7,7,30,8\n";
      
      
        string rc = "IDD_CALL DIALOG DISCARDABLE  0, 0, 242, 79\n"
        "BEGIN\n"
        + spike +
        "    EDITTEXT        IDE_ADDR,39,7,125,12,ES_AUTOHSCROLL | WS_TABSTOP\n"
        "    LTEXT           \"&Call Type:\",IDC_STATIC,185,45,32,8\n"
        "    COMBOBOX        IDL_CALL_TYPE,183,58,52,47,CBS_DROPDOWN | WS_VSCROLL | \n"
        "                    WS_TABSTOP\n"
        "    DEFPUSHBUTTON   \"OK\",IDOK,182,7,50,14\n"
        "    PUSHBUTTON      \"Cancel\",IDCANCEL,182,24,50,14\n"
        "END\n";
      
      
        findOneError( rc, "fileName.rc(3): Button missing hotkey\n"
        + spike +
        "\n" );
      
      
        }
      
      
        TEST_(TestLint, DoubleAmpersandsAreNotHotkeys)
        {
      
      
        string spike = "    CONTROL         \"Application && Sharing\",IDC_NMCH_AS,\"Button\",\n";
        string rc = "IDD_CONFERENCE DIALOG DISCARDABLE  0, 0, 237, 119\n"
        "BEGIN\n"
        "    CONTROL         \"&File Transfer\",IDC_NMCH_FT,\"Button\",BS_AUTOCHECKBOX | \n"
        "                    WS_TABSTOP,71,66,76,10\n"
        + spike +
        "                    BS_AUTOCHECKBOX | WS_TABSTOP,71,81,76,10\n"
        "END\n";
      
      
        findOneError( rc, "fileName.rc(5): Button missing hotkey\n"
        + spike +
        "\n" );
      
      
        }
      
      
        TEST_(TestLint, TrailingAmpersandsAreNotHotkeys)
        {
      
      
        string spike = "    CONTROL         \"Application Sharing &\",IDC_NMCH_AS,\"Button\",\n";
        string rc = "IDD_CONFERENCE DIALOG DISCARDABLE  0, 0, 237, 119\n"
        "BEGIN\n"
        "    CONTROL         \"&File Transfer\",IDC_NMCH_FT,\"Button\",BS_AUTOCHECKBOX | \n"
        "                    WS_TABSTOP,71,66,76,10\n"
        + spike +
        "                    BS_AUTOCHECKBOX | WS_TABSTOP,71,81,76,10\n"
        "END\n";
      
      
        findOneError( rc, "fileName.rc(5): Button missing hotkey\n"
        + spike +
        "\n" );
      
      
        }
      
      
        TEST_(TestLint, ButtonMissingHotkey)
        {
      
      
        string spike = "    CONTROL         \"Application Sharing\",IDC_NMCH_AS,\"Button\",\n";
        string rc = "IDD_CONFERENCE DIALOG DISCARDABLE  0, 0, 237, 119\n"
        "BEGIN\n"
        "    CONTROL         \"&File Transfer\",IDC_NMCH_FT,\"Button\",BS_AUTOCHECKBOX | \n"
        "                    WS_TABSTOP,71,66,76,10\n"
        + spike +
        "                    BS_AUTOCHECKBOX | WS_TABSTOP,71,81,76,10\n"
        "END\n";
      
      
        findOneError( rc, "fileName.rc(5): Button missing hotkey\n"
        + spike +
        "\n" );
      
      
        }
      
      
        TEST_(TestLint, PushbuttonMissingHotkey)
        {
      
      
        string spike = "    PUSHBUTTON      \"Browse\",IDB_BROWSE,120,41,50,14\n";
        string rc = "IDD_SENDFILE DIALOG DISCARDABLE  0, 0, 177, 69\n"
        "BEGIN\n"
        "    DEFPUSHBUTTON   \"OK\",IDOK,119,4,50,14\n"
        "    PUSHBUTTON      \"Cancel\",IDCANCEL,120,24,50,14\n"
        + spike +
        "END\n";
      
      
        findOneError( rc, "fileName.rc(5): Button missing hotkey\n"
        + spike +
        "\n" );
      
      
        }
      
      
        TEST_(TestLint, DefPushbuttonMissingHotkey)
        {
      
      
        string spike = "    DEFPUSHBUTTON      \"Browse\",IDB_BROWSE,120,41,50,14\n";
        string rc = "IDD_SENDFILE DIALOG DISCARDABLE  0, 0, 177, 69\n"
        "BEGIN\n"
        "    PUSHBUTTON   \"OK\",IDOK,119,4,50,14\n"
        "    PUSHBUTTON   \"Cancel\",IDCANCEL,120,24,50,14\n"
        + spike +
        "END\n";
      
      
        findOneError( rc, "fileName.rc(5): Button missing hotkey\n"
        + spike +
        "\n" );
      
      
        }
      
      
        TEST_(TestLint, DuplicatedControlHotkey)
        {
      
      
        string spike1 = "    GROUPBOX        \"&Multisample\",IDC_STATIC,5,101,200,28\n";
        string spike2 = "    LTEXT           \"&Multisample Type:\",IDC_STATIC,22,113,62,10,\n";
      
      
        string rc = "IDD_SELECTDEVICE DIALOG DISCARDABLE  0, 0, 267, 138\n"
        "STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU\n"
        "CAPTION \"Select Device\"\n"
        "FONT 8, \"MS Shell Dlg\"\n"
        "BEGIN\n"
        "    GROUPBOX        \"Rendering device\",IDC_STATIC,5,5,200,45\n"
        "    LTEXT           \"&Adapter:\",IDC_STATIC,22,17,65,10,SS_CENTERIMAGE\n"
        "    COMBOBOX        IDC_ADAPTER_COMBO,90,15,105,100,CBS_DROPDOWNLIST | \n"
        "                    WS_VSCROLL | WS_TABSTOP\n"
        "    LTEXT           \"&Device:\",IDC_STATIC,22,32,65,10,SS_CENTERIMAGE\n"
        "    COMBOBOX        IDC_DEVICE_COMBO,90,30,105,100,CBS_DROPDOWNLIST | \n"
        "                    WS_VSCROLL | WS_TABSTOP\n"
        "    GROUPBOX        \"Rendering mode\",IDC_STATIC,5,52,200,45\n"
        "    CONTROL         \"Use desktop &window\",IDC_WINDOW,\"Button\",\n"
        "                    BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,10,62,85,15\n"
        "    CONTROL         \"&Fullscreen mode:\",IDC_FULLSCREEN,\"Button\",\n"
        "                    BS_AUTORADIOBUTTON,10,77,75,15\n"
        "    COMBOBOX        IDC_FULLSCREENMODES_COMBO,90,77,105,204,CBS_DROPDOWNLIST | \n"
        "                    WS_VSCROLL | WS_GROUP | WS_TABSTOP\n"
        + spike1
        + spike2 +
        "                    SS_CENTERIMAGE\n"
        "    COMBOBOX        IDC_MULTISAMPLE_COMBO,90,111,105,100,CBS_DROPDOWNLIST | \n"
        "                    WS_VSCROLL | WS_TABSTOP\n"
        "    DEFPUSHBUTTON   \"OK\",IDOK,210,10,50,14\n"
        "    PUSHBUTTON      \"Cancel\",IDCANCEL,210,30,50,14\n"
        "END\n";
      
      
        findOneError( rc, "fileName.rc(21): Duplicated Control hotkey\n"
        + spike2
        + spike1+
        "\n" );
      
      
        //  TODO  force get(int) to return items in line order
      
      
        }
      
      
        TEST_(TestLint, DuplicatedMenuItemHotkey)
        {
      
      
        string spike1 = "        MENUITEM \"Kriem&hild\\tShift+K\",  ID_KRIEMHILD\n";
        string spike2 = "        MENUITEM \"Gunt&her\\tCtrl+G\",     ID_GUNTHER\n";
        string rc = "IDPLATYPUS MENU DISCARDABLE \n"
        "BEGIN\n"
        "    POPUP \"&Brunhilde\"\n"
        "    BEGIN\n"
        + spike1
        + spike2 +
        "    END\n"
        "END\n"
        "IDPLATYPUS ACCELERATORS LOADONCALL MOVEABLE DISCARDABLE \n"
        "BEGIN\n"
        "    \"K\", ID_KRIEMHILD,  VIRTKEY, SHIFT\n"
        "    \"G\", ID_GUNTHER,    VIRTKEY, CONTROL\n"
        "END\n"
        "STRINGTABLE PRELOAD MOVEABLE DISCARDABLE\n"
        "BEGIN\n"
        "    ID_GUNTHER      \"Snow\"\n"
        "    ID_KRIEMHILD    \"Angel\\n\"\n"
        "    IDS_COMMITTEE   \"Committee\"\n"
        "END\n";
      
      
        findOneError( rc, "fileName.rc(6): Duplicated MenuItem hotkey\n"
        + spike2
        + spike1 +
        "\n" );
      
      
        }
      
      
        TEST_(TestLint, MissingAccelerator)
        {
      
      
        string spike1 = "    POPUP \"Brunhilde\"\n";
        string spike2 = "        MENUITEM \"Gunther\",       ID_GUNTHER\n";
        string rc = "NIBELUNG_MENU MENU DISCARDABLE \n"
        "BEGIN\n"
        + spike1 + 
        "    BEGIN\n"
        "        MENUITEM \"&Kriemhild\",\n  ID_KRIEMHILD\n"
        + spike2 +
        "    END\n"
        "END\n"
        "STRINGTABLE PRELOAD MOVEABLE DISCARDABLE\n"
        "BEGIN\n"
        "    ID_GUNTHER      \"Snow\"\n"
        "    ID_KRIEMHILD    \"Angel\\n\"\n"
        "    IDS_COMMITTEE   \"Committee\"\n"
        "END\n";
      
      
        findOneError( rc, "fileName.rc(3): Missing & in Menu Label\n"
        + spike1 +
        "\n"
        "fileName.rc(7): Missing & in Menu Label\n"
        + spike2 +
        "\n" );
      
      
        }
      
      
        TEST_(TestLint, MenuItemMissesShortcut)
        {
      
      
        string spike1 = "        MENUITEM \"Gunth&er\",       ID_GUNTHER\n";
        string spike2 = "    \"G\", ID_GUNTHER,   VIRTKEY, CONTROL\n";
        string rc = "IDPLATYPUS MENU DISCARDABLE \n"
        "BEGIN\n"
        "    POPUP \"&Brunhilde\"\n"
        "    BEGIN\n"
        "        MENUITEM \"&Kriemhild\\tShift+K\",\n  ID_KRIEMHILD\n"
        + spike1 +
        "    END\n"
        "END\n"
        "IDPLATYPUS ACCELERATORS LOADONCALL MOVEABLE DISCARDABLE \n"
        "BEGIN\n"
        + spike2 +
        "    \"K\", ID_KRIEMHILD,  VIRTKEY, SHIFT\n"
        "END\n"
        "STRINGTABLE PRELOAD MOVEABLE DISCARDABLE\n"
        "BEGIN\n"
        "    ID_GUNTHER      \"Snow\"\n"
        "    ID_KRIEMHILD    \"Angel\\n\"\n"
        "    IDS_COMMITTEE   \"Committee\"\n"
        "END\n";
      
      
        findOneError( rc, "fileName.rc(7): Missing accelerator prompt in Menu Label\n"
        + spike1
        + spike2 +
        "\n" );
      
      
        }
      
      
        TEST_(TestLint, MenuItemShortcutWrong)
        {
      
      
        string spike1 = "        MENUITEM \"&Kriemhild\\tShift+H\",  ID_KRIEMHILD\n";
        string spike2 = "    \"K\", ID_KRIEMHILD,  VIRTKEY, SHIFT\n";
        string rc = "IDPLATYPUS MENU DISCARDABLE \n"
        "BEGIN\n"
        "    POPUP \"&Brunhilde\"\n"
        "    BEGIN\n"
        + spike1 +
        "        MENUITEM \"Gunth&er\\tCtrl+G\",       ID_GUNTHER\n"
        "    END\n"
        "END\n"
        "IDPLATYPUS ACCELERATORS LOADONCALL MOVEABLE DISCARDABLE \n"
        "BEGIN\n"
        + spike2 +
        "    \"G\", ID_GUNTHER,   VIRTKEY, CONTROL\n"
        "END\n"
        "STRINGTABLE PRELOAD MOVEABLE DISCARDABLE\n"
        "BEGIN\n"
        "    ID_GUNTHER      \"Snow\"\n"
        "    ID_KRIEMHILD    \"Angel\\n\"\n"
        "    IDS_COMMITTEE   \"Committee\"\n"
        "END\n";
      
      
        //  TODO  that line number should be 5?
      
      
        findOneError( rc, "fileName.rc(5): Wrong accelerator prompt in Menu Label\n"
        + spike1
        + spike2 +
        "\n" );
      
      
        }
      
      
        TEST_(TestLint, MissingStringTableHelpForMenuItem)
        {
      
      
        string spike = "        MENUITEM \"&Kriemhild\\tShift+K\",  ID_KRIEMHILD\n";
      
      
        string rc = "IDPLATYPUS MENU DISCARDABLE \n"
        "BEGIN\n"
        "    POPUP \"&Brunhilde\"\n"
        "    BEGIN\n"
        + spike +
        "        MENUITEM \"Gunth&er\\tCtrl+G\",       ID_GUNTHER\n"
        "    END\n"
        "END\n"
        "IDPLATYPUS ACCELERATORS LOADONCALL MOVEABLE DISCARDABLE \n"
        "BEGIN\n"
        "    \"K\", ID_KRIEMHILD,  VIRTKEY, SHIFT\n"
        "    \"G\", ID_GUNTHER,    VIRTKEY, CONTROL\n"
        "END\n"
        "STRINGTABLE PRELOAD MOVEABLE DISCARDABLE\n"
        "BEGIN\n"
        "    ID_GUNTHER      \"Snow\"\n"
        "    IDS_ANGEL       \"Angel\\n\"\n"
        "    IDS_COMMITTEE   \"Committee\"\n"
        "END\n";
      
      
        findOneError( rc, "fileName.rc(5): Missing StringTable help for MenuItem\n"
        + spike +
        "\n" );
      
      
        }
      
      
        ////////////////////////////////////////////////////
        ///   tests for miscellany
      
      
        TEST_(TestCase, printTree)
        {
      
      
        string rc = "NIBELUNG_MENU MENU\n"
        "BEGIN\n"
        "    POPUP \"Brunhilde\", GRAYED\n"
        "    BEGIN\n"
        "        MENUITEM \"&Kriemhild\",\n  ID_KRIEMHILD\n"
        "        MENUITEM \"Gunther\",       ID_GUNTHER\n"
        "    END\n"
        "END\n";
      
      
        ResourceFile aResourceFile;
        aResourceFile.parseFile(rc, "fileName.rc");
        stringstream tree;
      
      
        aResourceFile.printTree(tree, 1);
      
      
        string expect = " fileName.rc\n"
        "      NIBELUNG_MENU\n"
        "           Brunhilde\n"
        "                ID_GUNTHER\n"
        "                ID_KRIEMHILD\n";
      
      
        CPPUNIT_ASSERT_EQUAL(expect, tree.str());
      
      
        }
      
      
        TEST_(TestCase, callLint)
        {
      
      
        string rc =
        "IDR_MENU    MENU MOVEABLE DISCARDABLE\n"
        "BEGIN\n"
        "     POPUP \"&File\"\n"
        "      BEGIN\n"
        "\t"   "MENUITEM \"&Close\",    IDM_FILECLOSE\n"
        "       MENUITEM SEPARATOR\n"
        "      END\n"
        "    END\n";
      
      
        ResourceFile aResourceFile;
        aResourceFile.parseFile(rc, "fileName.rc");
        //    aResourceFile.printTree(cout);
        stringstream out;
        aResourceFile.callLint(out);
      
      
        }
      
      
        TEST_(TestCase, LeGrandWazoo)
        {
      
      
        string rc = "IDD_ABOUTBOX DIALOG DISCARDABLE  0, 0, 217, 55\n"
        "STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU\n"
        "CAPTION \"About HostApp\"\n"
        "FONT 8, \"MS Sans Serif\"\n"
        "BEGIN\n"
        "    ICON            IDR_MAINFRAME,IDC_STATIC,11,17,20,20\n"
        "    LTEXT           \"Pyramus && Thisby\",IDC_STATIC,40,10,119,8,SS_NOPREFIX\n"
        "    LTEXT           \"Copyright (C) 2098\",IDC_STATIC,40,25,119,8\n"
        "    DEFPUSHBUTTON   \"Okay\",IDOK,178,7,32,14,WS_GROUP\n"
        "END\n"
        "\n"
        "\n"
        "NIBELUNG_MENU MENU DISCARDABLE \n"
        "BEGIN\n"
        "    POPUP \"&Flying\"\n"
        "    BEGIN\n"
        "        MENUITEM \"&Valkyrie\",    ID_VALKYRIE, grayed\n"
        "        MENUITEM \"E&xit\",        ID_FILE_EXIT\n"
        "    END\n"
        "    POPUP \"&Rheingold\"\n"
        "    BEGIN\n"
        "        MENUITEM \"&Play\",        ID_RHEINGOLD_PLAY\n"
        "        MENUITEM \"Walhalla\",     ID_RHEINGOLD_WALHALLA\n"
        "        MENUITEM SEPARATOR\n"
        "        POPUP \"&Alberich\"\n"
        "        BEGIN\n"
        "            MENUITEM \"&Volsung\", ID_VOLSUNG\n"
        "            MENUITEM \"&Hunland\", ID_HUNLAND\n"
        "        END\n"
        "        POPUP \"Wotan\"\n"
        "        BEGIN\n"
        "            MENUITEM \"&Rhein\",   ID_RHEIN\n"
        "        END\n"
        "        MENUITEM \"&Title Menu\",  ID_RHEINGOLD_TITLEMENU\n"
        "    END\n"
        "    POPUP \"&Brunhilde\"\n"
        "    BEGIN\n"
        "        POPUP \"&Siegfried\"\n"
        "        BEGIN\n"
        "            MENUITEM \"&Kriemhild\",\n  ID_KRIEMHILD\n"
        "       //     MENUITEM \"&Miscreant\",  ID_MISCREANT\n"
        "            MENUITEM \"&Gunther\",      ID_GUNTHER\n"
        "\n"
        "        END\n"
        "        MENUITEM SEPARATOR\n"
        "    END\n"
        "    POPUP \"&Help\"\n"
        "    BEGIN\n"
        "        MENUITEM \"&About Nibelung\",  ID_ABOUT\n"
        "        MENUITEM \"Gotterdammerung\",  ID_GOTTERDAMMERUNG\n"
        "    END\n"
        "END\n"
        "\n"
        "STRINGTABLE PRELOAD MOVEABLE DISCARDABLE\n"
        "BEGIN\n"
        "    IDS_SNOW        \"Snow\"\n"
        "    IDS_ANGEL       \"Angel\\n\"\n"
        "    IDS_COMMITTEE   \"Committee\"\n"
        "END\n"
        "\n"
        "IDPLATYPUS MENU LOADONCALL MOVEABLE DISCARDABLE\n"
        "BEGIN\n"
        "    POPUP \"&File\"\n"
        "    BEGIN\n"
        "        MENUITEM \"&New\",        IDM_FILENEW\n"
        "        MENUITEM \"&Open...\",    IDM_FILEOPEN\n"
        "        POPUP \"&Menu\"\n"
        "        BEGIN\n"
        "            MENUITEM \"&Frog\",   IDM_FROG\n"
        "            MENUITEM \"&Soup\",   IDM_SOUP\n"
        "        END\n"
        "    END\n"
        "END\n"
        "\n"
        "STRINGTABLE PRELOAD MOVEABLE DISCARDABLE\n"
        "BEGIN\n"
        "    IDS_OBVIOUS1     \"I like food\"\n"
        "    IDS_OBVIOUS2     \"Food is good\"\n"
        "    IDS_OBVIOUS3     \"\"\"Eat good food\"\"\"\n"  //  <-- escaped strings
        "END\n";
      
      
        ResourceFile aResourceFile;
        aResourceFile.parseFile(rc);
      
      
        CPPUNIT_ASSERT_EQUAL("IDD_ABOUTBOX", aResourceFile.get("IDD_ABOUTBOX")->getID());
      
      
        Resource & aMenu = *aResourceFile.get("IDPLATYPUS");
        CPPUNIT_ASSERT_EQUAL("IDPLATYPUS", aMenu.getID());
      
      
        string     popupID = "&File";
        Resource * pPopup  = aMenu.get(popupID);
        CPPUNIT_ASSERT_EQUAL(popupID, pPopup->getID());
      
      
        CPPUNIT_ASSERT_EQUAL("&New",     pPopup->get("IDM_FILENEW" )->getLabel());
        CPPUNIT_ASSERT_EQUAL("&Open...", pPopup->get("IDM_FILEOPEN")->getLabel());
      
      
        Resource & bPopup = *pPopup->get("&Menu");
      
      
        CPPUNIT_ASSERT_EQUAL("&Frog", bPopup.get("IDM_FROG")->getLabel());    
        CPPUNIT_ASSERT_EQUAL("&Soup", bPopup.get("IDM_SOUP")->getLabel());
      
      
        Resource & nibelungMenu = *aResourceFile.get("NIBELUNG_MENU");
        CPPUNIT_ASSERT_EQUAL("NIBELUNG_MENU", nibelungMenu.getID());
      
      
        pPopup = nibelungMenu.get("&Flying");
        CPPUNIT_ASSERT_EQUAL("&Valkyrie", pPopup->get("ID_VALKYRIE" )->getLabel());
        CPPUNIT_ASSERT_EQUAL("E&xit",     pPopup->get("ID_FILE_EXIT")->getLabel());
      
      
        pPopup = nibelungMenu.get("&Rheingold");
        CPPUNIT_ASSERT_EQUAL("&Play"   , pPopup->get("ID_RHEINGOLD_PLAY"    )->getLabel());
        CPPUNIT_ASSERT_EQUAL("Walhalla", pPopup->get("ID_RHEINGOLD_WALHALLA")->getLabel());
      
      
        Resource * zPopup = pPopup->get("&Alberich");
        CPPUNIT_ASSERT_EQUAL("&Volsung", zPopup->get("ID_VOLSUNG")->getLabel());
        CPPUNIT_ASSERT_EQUAL("&Hunland", zPopup->get("ID_HUNLAND")->getLabel());
        zPopup = pPopup->get("Wotan");
        CPPUNIT_ASSERT_EQUAL("&Rhein", zPopup->get("ID_RHEIN")->getLabel());
        CPPUNIT_ASSERT_EQUAL("&Title Menu", pPopup->get("ID_RHEINGOLD_TITLEMENU")->getLabel());
      
      
        pPopup = nibelungMenu.get("&Brunhilde");
        zPopup = pPopup->get("&Siegfried");
        CPPUNIT_ASSERT_EQUAL("&Kriemhild", zPopup->get("ID_KRIEMHILD")->getLabel());
        CPPUNIT_ASSERT_EQUAL("&Gunther"  , zPopup->get("ID_GUNTHER"  )->getLabel());
      
      
        pPopup = nibelungMenu.get("&Help");
        CPPUNIT_ASSERT_EQUAL("&About Nibelung", pPopup->get("ID_ABOUT")->getLabel());
        CPPUNIT_ASSERT_EQUAL("Gotterdammerung", pPopup->get("ID_GOTTERDAMMERUNG")->getLabel());
      
      
        CPPUNIT_ASSERT_EQUAL("Snow"         , aResourceFile.getString("IDS_SNOW"     ));
        CPPUNIT_ASSERT_EQUAL("Angel\n"      , aResourceFile.getString("IDS_ANGEL"    ));
        CPPUNIT_ASSERT_EQUAL("Committee"    , aResourceFile.getString("IDS_COMMITTEE"));
        CPPUNIT_ASSERT_EQUAL("I like food"  , aResourceFile.getString("IDS_OBVIOUS1"));
        CPPUNIT_ASSERT_EQUAL("Food is good" , aResourceFile.getString("IDS_OBVIOUS2"));
        string expect = "\"Eat good food\"";
        CPPUNIT_ASSERT_EQUAL(expect, aResourceFile.getString("IDS_OBVIOUS3"));
        expect = "";
        CPPUNIT_ASSERT_EQUAL(expect, aResourceFile.getString("IDS_NOT_OBVIOUS"));
      
      
        }
      
      
        std::string
        fileToString(string const & name)
        {
        std::ifstream in(name.c_str());
        std::ostringstream oss;
        oss << in.rdbuf();
        return oss.str();
        } 
      
      
        void
        rcLint(ostream & out, string fileName)
        {
        string rc = fileToString(fileName);
        ResourceFile aResourceFile;
        aResourceFile.parseFile(rc, fileName);
        //    aResourceFile.printTree(cout);
        aResourceFile.callLint(out);
        }
      
      
        int
        main(int argc, char **argv)
        {
        if (argc == 2)
        {
        rcLint(cout, argv[1]);
        return 0;
        }
        else
        {
        bool worked = TestCase::runTests();
        cout << (worked?  "All tests passed":  "Test(s) failed") << endl;
        return ! worked;
        }
        }