//+----------------------------------------------------------------------- // // Tabular Data Control Array // Copyright (C) Microsoft Corporation, 1996, 1997 // // File: TDCArr.cpp // // Contents: Implementation of the CTDCArr object. // This class forms the heart of the Tabular Data Control. // It provides the core 2D array of variant values, plus // a (possibly filtered/sorted) view of this data for // presentation through an ISimpleTabularData interface. // //------------------------------------------------------------------------ #include "stdafx.h" #include "wch.h" #include #include "TDC.h" #include #include "Notify.h" #include "TDCParse.h" #include "TDCArr.h" #include "TDCIds.h" //------------------------------------------------------------------------ // // Function: fWCHIsSpace() // // Synopsis: Indicates whether a WCHAR is considered a space character // // Arguments: wch Character to test // // Returns: TRUE/FALSE indicating whether the given character is // considered a space. // //------------------------------------------------------------------------ inline boolean fWCHIsSpace(WCHAR wch) { return (wch == L' ' || wch == L'\t'); } //------------------------------------------------------------------------ // // Function: fWCHEatTest() // // Synopsis: Advances a string pointer over a given test character. // // Arguments: ppwch Pointer to string to test // wch Match character // // Returns: TRUE indicating the character matched and the pointer has // been advanceed // FALSE indicating no match (character pointer left unchanged) // //------------------------------------------------------------------------ inline boolean fWCHEatTest(LPWCH *ppwch, WCHAR wch) { if (**ppwch != wch) return FALSE; (*ppwch)++; return TRUE; } //------------------------------------------------------------------------ // // Function: fWCHEatSpace() // // Synopsis: Advances a string pointer over white space. // // Arguments: ppwch String pointer. // // Returns: Nothing. // //------------------------------------------------------------------------ inline void fWCHEatSpace(LPWCH *ppwch) { while (fWCHIsSpace(**ppwch)) (*ppwch)++; } //------------------------------------------------------------------------ // // Method: CTDCArr() // // Synopsis: Class constructor. Due to the COM model, the // member function "Create" should be called to actually // initialise the STD data structures. // // Arguments: None. // //------------------------------------------------------------------------ CTDCArr::CTDCArr() : m_cRef(1) { m_pEventBroker = NULL; m_pSortList = NULL; m_bstrSortExpr = NULL; m_pFilterTree = NULL; m_bstrFilterExpr = NULL; } //------------------------------------------------------------------------ // // Member: Init() // // Synopsis: Initialises the internal data. // // Arguments: pEventBroker Object to delegate notifications to. // // Returns: S_OK upon success. // Error code upon failure. // //------------------------------------------------------------------------ STDMETHODIMP CTDCArr::Init(CEventBroker *pEventBroker, IMultiLanguage *pML) { HRESULT hr = S_OK; hr = m_arrparrCells.Init(0); if (SUCCEEDED(hr)) hr = m_arrparrFilter.Init(0); if (SUCCEEDED(hr)) hr = m_arrColInfo.Init(0); m_iFilterRows = CalcFilterRows(); m_iDataRows = CalcDataRows(); m_iCols = CalcCols(); m_fLastFilter = FALSE; m_fSortFilterDisrupted = FALSE; m_state = LS_UNINITIALISED; m_lcid = GetUserDefaultLCID(); m_lcidRead = m_lcid; _ASSERT(pEventBroker != NULL); m_pEventBroker = pEventBroker; m_pEventBroker->AddRef(); // better not be called with NULL m_pML = pML; m_pML->AddRef(); m_fAsync = FALSE; // assume non-asynch for error cases return hr; } //------------------------------------------------------------------------ // // Member: ~CTDCArr() // // Synopsis: Destructor for CTDCArr // //------------------------------------------------------------------------ CTDCArr::~CTDCArr() { for (LONG iRow = CalcDataRows(); iRow >= 0; iRow--) { m_arrparrCells[iRow]->Passivate(); delete m_arrparrCells[iRow]; } m_arrparrCells.Passivate(); m_arrparrFilter.Passivate(); m_arrColInfo.Passivate(); if (m_pSortList != NULL) delete m_pSortList; SysFreeString(m_bstrSortExpr); if (m_pFilterTree != NULL) delete m_pFilterTree; SysFreeString(m_bstrFilterExpr); if (m_pEventBroker) { m_pEventBroker->Release(); m_pEventBroker = NULL; } ClearInterface(&m_pML); } LONG CTDCArr::CalcDataRows() { return m_arrparrCells.GetSize() - 1; } LONG CTDCArr::CalcFilterRows() { return m_arrparrFilter.GetSize() - 1; } LONG CTDCArr::CalcCols() { return m_arrparrCells.GetSize() > 0 ? m_arrparrCells[0]->GetSize() : 0; } //------------------------------------------------------------------------ // // Member: GetRowCount() // // Synopsis: Retrieves the number of rows in the table. // // Arguments: pcRows pointer to number of rows (OUT) // // Returns: S_OK to indicate success. // //------------------------------------------------------------------------ STDMETHODIMP CTDCArr::getRowCount(DBROWCOUNT *pcRows) { *pcRows = m_iFilterRows; return S_OK; } //------------------------------------------------------------------------ // // Member: GetColumnCount() // // Synopsis: Retrieves the number of column in the table. // // Arguments: pcCols pointer to number of columns (OUT) // // Returns: S_OK to indicate success. // E_UNEXPECTED if the table has not been loaded yet. // //------------------------------------------------------------------------ STDMETHODIMP CTDCArr::getColumnCount(DB_LORDINAL *pcCols) { *pcCols = m_iCols; return S_OK; } //------------------------------------------------------------------------ // // Member: GetRWStatus() // // Synopsis: Gets the read/write status of a cell, row, column or the // entire array. Since this implementation of STD can never // set the read/write status of a cell anywhere, all data // cells are presumed to have the default access and all // column heading cells are presumed to be read-only. Therefore, // it is not necessary to keep track of this information in // individual cells, and this function need only return // the value OSPRW_DEFAULT. // // Arguments: iRow row index (-1 means all rows) // iCols column index (-1 means all columns) // prwStatus pointer to read/write status (OUT) // // Returns: S_OK if indices are correct (prwStatus set). // E_INVALIDARG if indices are out of bounds. // E_UNEXPECTED if the table has not been loaded yet. // //------------------------------------------------------------------------ STDMETHODIMP CTDCArr::getRWStatus(DBROWCOUNT iRow, DB_LORDINAL iCol, OSPRW *prwStatus) { HRESULT hr = S_OK; if ((fValidFilterRow(iRow) || iRow == -1) && (fValidCol(iCol) || iCol == -1)) { if (iRow == -1) { // Should return READONLY if there is only a label row, // but frameworks tend to get confused if they want to // later insert data. // // *prwStatus = m_iDataRows > 0 ? OSPRW_MIXED : OSPRW_READONLY; *prwStatus = OSPRW_MIXED; } else if (iRow == 0) *prwStatus = OSPRW_READONLY; else *prwStatus = OSPRW_DEFAULT; } else hr = E_INVALIDARG; return hr; } //------------------------------------------------------------------------ // // Member: GetVariant() // // Synopsis: Retrieves a variant value for a cell. // // Arguments: iRow row index // iCols column index // format output format // pVar pointer to storage for resulting value // // Returns: S_OK upon success (contents of pVar set). // E_INVALIDARG if indices are out of bounds. // E_UNEXPECTED if the table has not been loaded yet. // //------------------------------------------------------------------------ STDMETHODIMP CTDCArr::getVariant(DBROWCOUNT iRow, DB_LORDINAL iCol, OSPFORMAT format, VARIANT *pVar) { HRESULT hr = S_OK; if (fValidFilterCell(iRow, iCol)) { CTDCCell *pCell = GetFilterCell(iRow, iCol); if (format == OSPFORMAT_RAW) { // Copy the raw variant value // hr = VariantCopy(pVar, pCell); } else if (format == OSPFORMAT_FORMATTED || format == OSPFORMAT_HTML) { // Construct a BSTR value representing the cell // if (pCell->vt == VT_BOOL) { // For OLE DB spec compliance: // VariantChangeTypeEx converts booleans in BSTR "0", "-1". // This code yields BSTR "False", "True" instead. // VariantClear(pVar); pVar->vt = VT_BSTR; hr = VarBstrFromBool(pCell->boolVal, m_lcid, 0, &pVar->bstrVal); } else { hr = VariantChangeTypeEx(pVar, pCell, m_lcid, 0, VT_BSTR); } if (!SUCCEEDED(hr)) { VariantClear(pVar); pVar->vt = VT_BSTR; pVar->bstrVal = SysAllocString(L"#Error"); } } else hr = E_INVALIDARG; } else hr = E_INVALIDARG; return hr; } //------------------------------------------------------------------------ // // Member: SetVariant() // // Synopsis: Sets a cell's variant value from a given variant value. // The given variant type is coerced into the column's // underlying type. // // Arguments: iRow row index // iCols column index // format output format // Var value to be stored in the cell. // // Returns: S_OK upon success (contents of pVar set). // E_INVALIDARG if indices are out of bounds. // E_UNEXPECTED if the table has not been loaded yet. // //------------------------------------------------------------------------ STDMETHODIMP CTDCArr::setVariant(DBROWCOUNT iRow, DB_LORDINAL iCol, OSPFORMAT format, VARIANT Var) { HRESULT hr; if (fValidFilterCell(iRow, iCol)) { CTDCCell *pCell = GetFilterCell(iRow, iCol); CTDCColInfo *pColInfo = GetColInfo(iCol); if (format == OSPFORMAT_RAW || format == OSPFORMAT_FORMATTED || format == OSPFORMAT_HTML) { if (m_pEventBroker != NULL) { hr = m_pEventBroker->aboutToChangeCell(iRow, iCol); if (!SUCCEEDED(hr)) goto Cleanup; } if (Var.vt == pColInfo->vtType) hr = VariantCopy(pCell, &Var); else { // For OLE DB spec compliance: // VariantChangeTypeEx converts booleans in BSTR "0", "-1". // This code yields BSTR "False", "True" instead. // if (Var.vt == VT_BOOL && pColInfo->vtType==VT_BSTR) { VariantClear(pCell); pCell->vt = VT_BSTR; hr = VarBstrFromBool(Var.boolVal, m_lcid, 0, &pCell->bstrVal); } else hr = VariantChangeTypeEx(pCell, &Var, m_lcid, 0, pColInfo->vtType); } if (SUCCEEDED(hr) && m_pEventBroker != NULL) hr = m_pEventBroker->cellChanged(iRow, iCol); m_fSortFilterDisrupted = TRUE; } else hr = E_INVALIDARG; } else hr = E_INVALIDARG; Cleanup: return hr; } //------------------------------------------------------------------------ // // Member: GetLocale() // // Synopsis: Returns the locale of our data. // // Arguments: Returns a BSTR, representing an RFC1766 form string for our // locale. Note this may not necessarily match our LANGUAGE // param, if we had one, because teh string is canoncialized // by using MLang to convert it to an LCID and back to a string // again. // // Returns: S_OK to indicate success. // //------------------------------------------------------------------------ STDMETHODIMP CTDCArr::getLocale(BSTR *pbstrLocale) { return m_pML->GetRfc1766FromLcid(m_lcid, pbstrLocale); } //+----------------------------------------------------------------------- // // Member: DeleteRows // // Synopsis: Used to delete rows from the table. Bounds are checked // to make sure that the rows can all be deleted. Label row // cannot be deleted. // // Arguments: iRow first row to delete // cRows number of rows to delete // pcRowsDeleted actual number of rows deleted (OUT) // // Returns: S_OK upon success, i.e. all rows could be deleted // E_INVALIDARG if cRows < 0 or any rows to be deleted // are out of bounds // //------------------------------------------------------------------------ STDMETHODIMP CTDCArr::deleteRows(DBROWCOUNT iRow, DBROWCOUNT cRows, DBROWCOUNT *pcRowsDeleted) { HRESULT hr; *pcRowsDeleted = 0; if (fValidFilterRow(iRow) && iRow > 0 && cRows >= 0 && fValidFilterRow(iRow + cRows - 1)) { if (m_pEventBroker != NULL) { hr = m_pEventBroker->aboutToDeleteRows(iRow, cRows); if (!SUCCEEDED(hr)) goto Cleanup; } *pcRowsDeleted = cRows; hr = S_OK; if (cRows > 0) { // Delete the rows from the array // m_arrparrFilter.DeleteElems(iRow, cRows); m_iFilterRows = CalcFilterRows(); m_fSortFilterDisrupted = TRUE; // Notify the event-handler of the deletion // if (m_pEventBroker != NULL) hr = m_pEventBroker->deletedRows(iRow, cRows); } } else { hr = E_INVALIDARG; } Cleanup: return hr; } //+----------------------------------------------------------------------- // // Member: InsertRows() // // Synopsis: Allows for the insertion of new rows. This can either be // used to insert new rows between existing rows, or to // append new rows to the end of the table. Thus, to // insert new rows at the end of the table, a user would // specify the initial row as 1 greater than the current // row dimension. // Note that iRow is checked to ensure that it is within the // proper bounds (1..+1). // User cannot delete column heading row. // // Arguments: iRow rows will be inserted *before* row 'iRow' // cRows how many rows to insert // pcRowsInserted actual number of rows inserted (OUT) // // Returns: S_OK upon success, i.e. all rows could be inserted. // E_INVALIDARG if row is out of allowed bounds. // It is possible that fewer than the requested rows were // inserted. In this case, E_OUTOFMEMORY would be returned, // and the actual number of rows inserted would be set. // //------------------------------------------------------------------------ STDMETHODIMP CTDCArr::insertRows(DBROWCOUNT iRow, DBROWCOUNT cRows, DBROWCOUNT *pcRowsInserted) { HRESULT hr = S_OK; TSTDArray **pRows = NULL; LONG iTmpRow; // Verify that the insertion row is within range // if (iRow < 1 || iRow > m_iFilterRows + 1) { hr = E_INVALIDARG; goto Cleanup; } if (cRows <= 0) { if (cRows < 0) hr = E_INVALIDARG; goto Cleanup; } // Unless success is complete, assume 0 rows inserted. // *pcRowsInserted = 0; // Allocate a temporary array of rows // pRows = new TSTDArray* [cRows]; if (pRows == NULL) { hr = E_OUTOFMEMORY; goto Cleanup; } memset(pRows, '\0', sizeof(pRows[0]) * cRows); for (iTmpRow = 0; iTmpRow < cRows; iTmpRow++) { if ((pRows[iTmpRow] = new TSTDArray) == NULL) { hr = E_OUTOFMEMORY; goto CleanupTmpRows; } hr = pRows[iTmpRow]->InsertElems(0, m_iCols); if (!SUCCEEDED(hr)) goto CleanupTmpRows; } // Expand the Cell-Rows and Filter-Rows arrays to cope with the new rows. // _ASSERT(m_iFilterRows <= m_iDataRows); hr = m_arrparrCells.InsertElems(iRow, cRows); if (!SUCCEEDED(hr)) goto CleanupTmpRows; hr = m_arrparrFilter.InsertElems(iRow, cRows); if (!SUCCEEDED(hr)) { // Undo the previous allocation // m_arrparrCells.DeleteElems(iRow, cRows); goto CleanupTmpRows; } if (m_pEventBroker != NULL) { hr = m_pEventBroker->aboutToInsertRows(iRow, cRows); if (FAILED(hr)) goto CleanupTmpRows; } // Copy across the row pointers // for (iTmpRow = 0; iTmpRow < cRows; iTmpRow++) { m_arrparrCells[iRow + iTmpRow] = pRows[iTmpRow]; m_arrparrFilter[iRow + iTmpRow] = pRows[iTmpRow]; } // Return indicating success // *pcRowsInserted = cRows; m_iFilterRows = CalcFilterRows();; m_iDataRows = CalcDataRows(); // Fire events: if (*pcRowsInserted != 0) { m_fSortFilterDisrupted = TRUE; if (m_pEventBroker != NULL) hr = m_pEventBroker->insertedRows(iRow, cRows); } goto Cleanup; CleanupTmpRows: // Free the memory associated with the tmp rows. // for (iTmpRow = 0; iTmpRow < cRows; iTmpRow++) if (pRows[iTmpRow] != NULL) delete pRows[iTmpRow]; Cleanup: if (pRows != NULL) delete pRows; return hr; } // ;begin_internal //+----------------------------------------------------------------------- // // Member: DeleteColumns() // // Synopsis: Used to delete columns from the table. Bounds are checked // to make sure that the columns can all be deleted. Label // column cannot be deleted. // // Arguments: iCol first column to delete // cCols number of columns to delete // pcColsDeleted actual number of rows deleted (OUT) // // Returns: S_OK upon succes, i.e. all columns could be deleted // E_INVALIDARG if column is out of allowed bounds. // //------------------------------------------------------------------------ STDMETHODIMP CTDCArr::DeleteColumns(DB_LORDINAL iCol, DB_LORDINAL cCols, DB_LORDINAL *pcColsDeleted) { HRESULT hr; if (fValidCol(iCol) && iCol > 0 && cCols >= 0 && fValidCol(iCol + cCols - 1)) { *pcColsDeleted = cCols; hr = S_OK; if (cCols > 0) { for (LONG iRow = 0; iRow < m_iFilterRows; iRow++) { TSTDArray *pRow; pRow = m_arrparrCells[iRow]; pRow->DeleteElems(iCol - 1, cCols); } m_arrColInfo.DeleteElems(iCol - 1, cCols); m_iCols = CalcCols(); if (!m_fUseHeader) RenumberColumnHeadings(); m_fSortFilterDisrupted = TRUE; // Notify the event-handler of the deletion // #ifdef NEVER if (m_pEventBroker != NULL) hr = m_pEventBroker->DeletedCols(iCol, cCols); #endif } _ASSERT(m_arrColInfo.GetSize() == (ULONG) m_iCols); } else { hr = E_INVALIDARG; *pcColsDeleted = 0; } return hr; } //+----------------------------------------------------------------------- // // Member: InsertColumns() // // Synopsis: Allows for the insertion of new columns. This can either be // used to insert new columns between existing columns, or to // append new columns to the end of the table. Thus, to // insert new columns at the end of the table, a user would // specify the initial columns as 1 greater than the current // columns dimension. // Note that iColumn is checked to ensure that it is within the // proper bounds (1..+1). // // Arguments: iCol columns will be inserted *before* row 'iCol' // cCols how many columns to insert // pcColsInserted actual number of columns inserted (OUT) // // Returns: S_OK upon success, i.e. all columns could be inserted. // E_INVALIDARG if column is out of allowed bounds. // It is possible that fewer than the requested columns were // inserted. In this case, E_OUTOFMEMORY would be returned, // and the actual number of columns inserted would be set. // //------------------------------------------------------------------------ STDMETHODIMP CTDCArr::InsertColumns(DB_LORDINAL iCol, DB_LORDINAL cCols, DB_LORDINAL *pcColsInserted) { HRESULT hr = S_OK; LONG iTmpRow; // Verify that the insertion column is within range // if (iCol < 1 || iCol > m_iCols + 1) { hr = E_INVALIDARG; goto Cleanup; } if (cCols <= 0) { if (cCols < 0) hr = E_INVALIDARG; goto Cleanup; } // Unless success is complete, assume 0 columns inserted. // *pcColsInserted = 0; for (iTmpRow = 0; iTmpRow <= m_iDataRows; iTmpRow++) { hr = m_arrparrCells[iTmpRow]->InsertElems(iCol, cCols); if (!SUCCEEDED(hr)) { // Undo the changes we've done // while (--iTmpRow >= 0) m_arrparrCells[iTmpRow]->DeleteElems(iCol, cCols); goto Cleanup; } } // Return indicating success // *pcColsInserted = cCols; m_iCols = CalcCols(); // Fire events: if (*pcColsInserted != 0) { m_fSortFilterDisrupted = TRUE; #ifdef NEVER if (m_pEventBroker != NULL) hr = m_pEventBroker->InsertedCols(iCol, cCols); #endif } Cleanup: // If we're using automatically numbered column headings and some // columns were inserted, then renumber the columns. // if (*pcColsInserted > 0 && !m_fUseHeader) RenumberColumnHeadings(); return hr; } // ;end_internal //+----------------------------------------------------------------------- // // Member: Find() // // Synopsis: Searches for a row matching the specified criteria // // Arguments: iRowStart The starting row for the search // iCol The column being tested // vTest The value against which cells in the // test column are tested // findFlags Flags indicating whether to search up/down // and whether comparisons are case sensitive. // compType The comparison operator for matching (find a // cell =, >=, <=, >, <, <> the test value) // piRowFound The row with a matching cell [OUT] // // Returns: S_OK upon success, i.e. a row was found (piRowFound set). // E_FAIL upon failure, i.e. a row was not found. // E_INVALIDARG if starting row 'iRowStart' or test column 'iCol' // are out of bounds. // DISP_E_TYPEMISMATCH if the test value's type does not match // the test column's type. // //------------------------------------------------------------------------ STDMETHODIMP CTDCArr::find(DBROWCOUNT iRowStart, DB_LORDINAL iCol, VARIANT vTest, OSPFIND findFlags, OSPCOMP compType, DBROWCOUNT *piRowFound) { HRESULT hr = S_OK; boolean fUp = FALSE; boolean fCaseSensitive = FALSE; LONG iRow; *piRowFound = -1; // Validate arguments // if (iRowStart < 1 || !fValidFilterRow(iRowStart) || !fValidCol(iCol)) { hr = E_INVALIDARG; goto Cleanup; } if (GetColInfo(iCol)->vtType != vTest.vt) { // Search-value type does not match the underlying column type // Fail as per spec. // hr = DISP_E_TYPEMISMATCH; goto Cleanup; } if ((findFlags & OSPFIND_UP) != 0) fUp = TRUE; if ((findFlags & OSPFIND_CASESENSITIVE) != 0) fCaseSensitive = TRUE; for (iRow = iRowStart; fUp ? iRow > 0 : iRow <= m_iFilterRows; fUp ? iRow-- : iRow++) { int iCmp = VariantComp(GetFilterCell(iRow, iCol), &vTest, vTest.vt, fCaseSensitive); boolean fFound = FALSE; switch (compType) { case OSPCOMP_LT: fFound = iCmp < 0; break; case OSPCOMP_LE: fFound = iCmp <= 0; break; case OSPCOMP_GT: fFound = iCmp > 0; break; case OSPCOMP_GE: fFound = iCmp >= 0; break; case OSPCOMP_EQ: fFound = iCmp == 0; break; case OSPCOMP_NE: fFound = iCmp != 0; break; default: hr = E_INVALIDARG; goto Cleanup; } if (fFound) { *piRowFound = iRow; hr = S_OK; goto Cleanup; } } hr = E_FAIL; Cleanup: return hr; } //+----------------------------------------------------------------------- // // Member: addOLEDBSimpleProviderListener() // // Synopsis: Sets or clears a reference to the COM object which receives // notifications of cell changes, row/column insert/deletes etc. // // Arguments: pEvent Pointer to the COM object to receive // notifications, or NULL if no notifications // are to be sent. // // Returns: S_OK upon success. // Error code upon success. // //------------------------------------------------------------------------ STDMETHODIMP CTDCArr::addOLEDBSimpleProviderListener(OLEDBSimpleProviderListener *pSTDEvents) { HRESULT hr = S_OK; if (m_pEventBroker == NULL) hr = E_FAIL; else { hr = m_pEventBroker->SetSTDEvents(pSTDEvents); // If the event sink has been added, and we're already loaded, // then fire transferComplete, because we probably couldn't before. if (LS_LOADED==m_state) m_pEventBroker->STDLoadCompleted(); } return hr; } STDMETHODIMP CTDCArr::removeOLEDBSimpleProviderListener(OLEDBSimpleProviderListener *pSTDEvents) { HRESULT hr = S_OK; if (m_pEventBroker && pSTDEvents==m_pEventBroker->GetSTDEvents()) hr = m_pEventBroker->SetSTDEvents(NULL); return hr; } //+----------------------------------------------------------------------- // // Member: FindCol() // // Synopsis: Locate a column by name // // Arguments: pwchColName Name of column to locate // // Returns: +ve Column number upon success (column name matched) // 0 upon failure (no column name matched) // //------------------------------------------------------------------------ LONG CTDCArr::FindCol(LPWCH pwchColName) { LONG iCol = 0; if (pwchColName != NULL) { for (iCol = m_iCols; iCol > 0; iCol--) { CTDCCell *pCell = GetDataCell(0, iCol); _ASSERT(pCell->vt == VT_BSTR); if (wch_icmp(pwchColName, pCell->bstrVal) == 0) break; } } return iCol; } class SortElt { public: CTDCArr *pInstance; int iRow; TSTDArray * parrRow; }; //+----------------------------------------------------------------------- // // Function: CompareSort() // // Synopsis: Called by qsort() to order the rows of a table. // // Arguments: pElt1, pElt1 pointers to elements to be compared // // Returns: -1 if the first element is less than the second element // 0 if the first element equals the second element // +1 if the first element is greater than the second element // //------------------------------------------------------------------------ static int __cdecl CompareSort(const void *pElt1, const void *pElt2) { SortElt *pse1 = (SortElt *) pElt1; SortElt *pse2 = (SortElt *) pElt2; return pse1->pInstance->SortComp(pse1->iRow, pse2->iRow); } //+----------------------------------------------------------------------- // // Function: extract_num() // // Synopsis: Extracts the first non-negative number from the character // stream referenced by 'ppwch'. Updates 'ppwch' to point // to the character following the digits found. // // Arguments: ppwch Pointer to null-terminated WCHAR string // // Returns: Non-negative number extracted upon success (pointer updated) // -1 upon failure (no digits found; pointer moved to end-of-string // //+----------------------------------------------------------------------- static int extract_num(WCHAR **ppwch) { int retval = 0; boolean fFoundDigits = FALSE; if (*ppwch != NULL) { // Skip over leading non-digits // while ((**ppwch) != 0 && ((**ppwch) < L'0' || (**ppwch) > L'9')) (*ppwch)++; // Accumulate digits // fFoundDigits = *ppwch != 0; while ((**ppwch) >= L'0' && (**ppwch) <= L'9') retval = 10 * retval + *(*ppwch)++ - L'0'; } return fFoundDigits ? retval : -1; } //+----------------------------------------------------------------------- // // Member: CreateNumberedColumnHeadings() // // Synopsis: Allocates cells for numbered column headings. // // Arguments: None. // // Returns: S_OK upon success. // E_OUTOFMEMORY if there was insufficient memory to complete // the operation. // //------------------------------------------------------------------------ HRESULT CTDCArr::CreateNumberedColumnHeadings() { HRESULT hr = S_OK; LONG iCol; iCol = m_iCols; // Allocate a new row entry // hr = m_arrparrCells.InsertElems(0, 1); if (!SUCCEEDED(hr)) goto Cleanup; // Allocate a new row of cells // m_arrparrCells[0] = new TSTDArray; if (m_arrparrCells[0] == NULL) { hr = E_OUTOFMEMORY; goto Cleanup; } hr = m_arrparrCells[0]->InsertElems(0, iCol); Cleanup: return hr; } //+----------------------------------------------------------------------- // // Member: RenumberColumnHeadings() // // Synopsis: Set the automatic name "Column" for each column // // Arguments: None. // // Returns: Nothing. // //------------------------------------------------------------------------ void CTDCArr::RenumberColumnHeadings() { for (LONG iCol = m_iCols; iCol > 0; iCol--) { CTDCCell *pCell = GetDataCell(0, iCol); WCHAR awchLabel[20]; wch_cpy(awchLabel, L"Column"); _ltow(iCol, &awchLabel[6], 10); pCell->clear(); pCell->vt = VT_BSTR; pCell->bstrVal = SysAllocString(awchLabel); } } //+----------------------------------------------------------------------- // // Member: ParseColumnHeadings() // // Synopsis: Extracts type information (if present) from column // headings, removes leadning // // Arguments: None. // // Returns: S_OK upon success. // E_OUTOFMEMORY if there was insufficient memory to complete // the operation. // //------------------------------------------------------------------------ HRESULT CTDCArr::ParseColumnHeadings() { LPWCH pwchIntType = L"int"; LPWCH pwchFloatType = L"float"; LPWCH pwchStringType = L"string"; LPWCH pwchBooleanType = L"boolean"; LPWCH pwchDateType = L"date"; HRESULT hr = S_OK; LONG iCol; iCol = m_iCols; // Allocate space for column type info // hr = m_arrColInfo.InsertElems(0, iCol); if (!SUCCEEDED(hr)) goto Cleanup; for (; iCol > 0; iCol--) { // Column headings have the format: // [:[,]] // CTDCColInfo *pColInfo = GetColInfo(iCol); CTDCCell *pCell = GetDataCell(0, iCol); _ASSERT(pCell->vt == VT_BSTR); BSTR bstr; LPWCH pColon; bstr = pCell->bstrVal; pColInfo->vtType = VT_BSTR; // Default type for a column is BSTR pColon = wch_chr(bstr, L':'); if (pColon != NULL) { WCHAR *pwchFormat = NULL; LPWCH pSpace; *pColon++ = 0; pSpace = wch_chr(pColon, L' '); if (pSpace != NULL) { *pSpace++ = '\0'; pwchFormat = pSpace; } if (wch_icmp(pColon, pwchIntType) == 0) pColInfo->vtType = VT_I4; else if (wch_icmp(pColon, pwchFloatType) == 0) pColInfo->vtType = VT_R8; else if (wch_icmp(pColon, pwchStringType) == 0) pColInfo->vtType = VT_BSTR; else if (wch_icmp(pColon, pwchBooleanType) == 0) pColInfo->vtType = VT_BOOL; else if (wch_icmp(pColon, pwchDateType) == 0) { pColInfo->vtType = VT_DATE; TDCDateFmt fmt = TDCDF_NULL; if (pwchFormat != NULL) { int nPos = 0; int nDayPos = 0; int nMonPos = 0; int nYearPos= 0; // Convert the format string into an internal enum type // Find the relative positions of the letters 'D' 'M' 'Y' // for (; *pwchFormat != 0; nPos++, pwchFormat++) { switch (*pwchFormat) { case L'D': case L'd': nDayPos = nPos; break; case L'M': case L'm': nMonPos = nPos; break; case L'Y': case L'y': nYearPos = nPos; break; } } // Compare the relative positions to work out the format // if (nDayPos < nMonPos && nMonPos < nYearPos) fmt = TDCDF_DMY; else if (nMonPos < nDayPos && nDayPos < nYearPos) fmt = TDCDF_MDY; else if (nDayPos < nYearPos && nYearPos < nMonPos) fmt = TDCDF_DYM; else if (nMonPos < nYearPos && nYearPos < nDayPos) fmt = TDCDF_MYD; else if (nYearPos < nMonPos && nMonPos < nDayPos) fmt = TDCDF_YMD; else if (nYearPos < nDayPos && nDayPos < nMonPos) fmt = TDCDF_YDM; } pColInfo->datefmt = fmt; } } if (bstr != NULL) { // Remove leading/trailing spaces from column name // LPWCH pwch; LPWCH pwchDest = NULL; LPWCH pLastNonSpace = NULL; for (pwch = bstr; *pwch != 0; pwch++) { if (!fWCHIsSpace(*pwch)) { if (pwchDest == NULL) pwchDest = bstr; pLastNonSpace = pwchDest; } if (pwchDest != NULL) *pwchDest++ = *pwch; } if (pLastNonSpace == NULL) bstr[0] = 0; // all spaces! Make it null string. else pLastNonSpace[1] = 0; } // Copy the modified column header and free the original // pCell->bstrVal = SysAllocString(bstr); SysFreeString(bstr); if (pCell->bstrVal == NULL) { hr = E_OUTOFMEMORY; goto Cleanup; } } Cleanup: return hr; } //+----------------------------------------------------------------------- // // Member: VariantFromBSTR() // // Synopsis: Convert a BSTR value into a variant compatible with a // given column type. // // Arguments: pVar Pointer to resultant variant value // bstr Source BSTR // pColInfo Column information (type, format options etc). // lcid Locale # for locale-specific conversion. // // Returns: S_OK upon success (pVar set) // OLE_E_CANTCONVERT if the given BSTR is badly formatted // (e.g. contains an invalid date value for a date conversion). // E_OUTOFMEMORY if insufficient memory is available for // a conversion. // //------------------------------------------------------------------------ HRESULT CTDCArr::VariantFromBSTR(VARIANT *pVar, BSTR bstr, CTDCColInfo *pColInfo, LCID lcid) { HRESULT hr = E_FAIL; VariantInit(pVar); switch (pColInfo->vtType) { case VT_DATE: if (pColInfo->datefmt != TDCDF_NULL) { // Parse the date string according to specified format. // First, find the three numeric components in the date. // USHORT n1; USHORT n2; USHORT n3; WCHAR *pwch = bstr; SYSTEMTIME st; n1 = (USHORT)extract_num(&pwch); n2 = (USHORT)extract_num(&pwch); n3 = (USHORT)extract_num(&pwch); memset(&st, '\0', sizeof(st)); switch (pColInfo->datefmt) { case TDCDF_DMY: st.wDay = n1; st.wMonth = n2; st.wYear = n3; break; case TDCDF_MDY: st.wDay = n2; st.wMonth = n1; st.wYear = n3; break; case TDCDF_DYM: st.wDay = n1; st.wMonth = n3; st.wYear = n2; break; case TDCDF_MYD: st.wDay = n3; st.wMonth = n1; st.wYear = n2; break; case TDCDF_YMD: st.wDay = n3; st.wMonth = n2; st.wYear = n1; break; case TDCDF_YDM: st.wDay = n2; st.wMonth = n3; st.wYear = n1; break; } VariantClear(pVar); if (n1 >= 0 && n2 >= 0 && n3 >= 0 && SystemTimeToVariantTime(&st, &pVar->date)) { pVar->vt = VT_DATE; hr = S_OK; } else hr = OLE_E_CANTCONVERT; } else { // No date format specified - just use the default conversion // VARIANT vSrc; VariantInit(&vSrc); vSrc.vt = VT_BSTR; vSrc.bstrVal = bstr; hr = VariantChangeTypeEx(pVar, &vSrc, lcid, 0, pColInfo->vtType); } break; case VT_BOOL: case VT_I4: case VT_R8: default: // // Perform a standard conversion. // { VARIANT vSrc; VariantInit(&vSrc); vSrc.vt = VT_BSTR; vSrc.bstrVal = bstr; hr = VariantChangeTypeEx(pVar, &vSrc, lcid, 0, pColInfo->vtType); } break; case VT_BSTR: // // Duplicate the BSTR // pVar->bstrVal = SysAllocString(bstr); if (bstr != NULL && pVar->bstrVal == NULL) hr = E_OUTOFMEMORY; else { pVar->vt = VT_BSTR; hr = S_OK; } break; } return hr; } //+----------------------------------------------------------------------- // // Member: VariantComp() // // Synopsis: Compares two variant values. // // Arguments: pVar1 First variant value // pVar2 Second variant value // fCaseSensitive TRUE if string comparisons should be // case-sensitive, false if string comparisons // should be case-insensitive. Ignored if // non-string data types are being compared. // // Returns: -1 if // OLE_E_CANTCONVERT if the given BSTR is badly formatted // (e.g. contains an invalid date value for a date conversion). // E_OUTOFMEMORY if insufficient memory is available for // a conversion. // //------------------------------------------------------------------------ int CTDCArr::VariantComp(VARIANT *pVar1, VARIANT *pVar2, VARTYPE type, boolean fCaseSensitive) { int retval = 0; // NULLs are lexically less than anything else // if (pVar1->vt == VT_NULL) retval = pVar2->vt == VT_NULL ? 0 : -1; else if (pVar2->vt == VT_NULL) retval = 1; else if (pVar1->vt != type) { // Type-mismatches are lexically greater than anything else // retval = pVar2->vt == type ? 1 : 0; } else if (pVar2->vt != type) { // Type-mismatches are lexically greater than anything else // retval = -1; } else { switch (type) { case VT_I4: retval = pVar1->lVal < pVar2->lVal ? -1 : pVar1->lVal > pVar2->lVal ? 1 : 0; break; case VT_R8: retval = pVar1->dblVal < pVar2->dblVal ? -1 : pVar1->dblVal > pVar2->dblVal ? 1 : 0; break; case VT_BSTR: retval = fCaseSensitive ? wch_cmp(pVar1->bstrVal, pVar2->bstrVal) : wch_icmp(pVar1->bstrVal, pVar2->bstrVal); break; case VT_BOOL: retval = pVar1->boolVal ? (pVar2->boolVal ? 0 : 1) : (pVar2->boolVal ? -1 : 0); break; case VT_DATE: retval = pVar1->date < pVar2->date ? -1 : pVar1->date > pVar2->date ? 1 : 0; break; default: retval = 0; // Unrecognised types are all lexically equal break; } } return retval; } //+----------------------------------------------------------------------- // // Method: CreateSortList() // // Synopsis: Creates a list of sort criteria from a text description. // // Arguments: bstrSortCols ';'-separted list of column names, // optionally prefixed with '+' (default) // or '-' indicating ascending or descending // sort order respectively for that column. // // Returns: S_OK upon success. // E_OUTOFMEMORY if insufficient memory is available for // the construction of sort criteria. // // Side Effect: Saves created list in m_pSortList // //+----------------------------------------------------------------------- HRESULT CTDCArr::CreateSortList(BSTR bstrSortCols) { HRESULT hr = S_OK; if (m_pSortList != NULL) { delete m_pSortList; m_pSortList = NULL; } if (bstrSortCols != NULL) { WCHAR *pwchEnd = bstrSortCols; CTDCSortCriterion **pLast = &m_pSortList; while (*pwchEnd != 0) { WCHAR *pwchStart = pwchEnd; boolean fSortAscending = TRUE; // Discard leading white space and field-separators // while (*pwchStart == L';' || fWCHIsSpace(*pwchStart)) pwchStart++; // Strip off optional direction indicator + white space // if (*pwchStart == L'+' || *pwchStart == '-') { fSortAscending = *pwchStart++ == L'+'; while (fWCHIsSpace(*pwchStart)) pwchStart++; } // Find the field terminator. // Strip out trailing white spaces. // for (pwchEnd = pwchStart; *pwchEnd != 0 && *pwchEnd != L';';) pwchEnd++; while (pwchStart < pwchEnd && fWCHIsSpace(pwchEnd[-1])) pwchEnd--; // Ignore blank column names - this could be the result of // a leading or trailing ';'. // if (pwchStart >= pwchEnd) continue; // Find the column number from the column name // BSTR bstrColName = SysAllocStringLen(pwchStart, pwchEnd - pwchStart); if (bstrColName == NULL) { hr = E_OUTOFMEMORY; goto Cleanup; } LONG iCol = FindCol(bstrColName); SysFreeString(bstrColName); if (iCol > 0) { // Allocate a node for this criterion // _ASSERT(*pLast == NULL); *pLast = new CTDCSortCriterion; if (*pLast == NULL) { hr = E_OUTOFMEMORY; goto Cleanup; } (*pLast)->m_fSortAscending = fSortAscending; (*pLast)->m_iSortCol = iCol; pLast = &(*pLast)->m_pNext; } } } Cleanup: return hr; } //+----------------------------------------------------------------------- // // Method: SortComp() // // Synopsis: Compares two rows using the elememts in the columns specified // by the current sort criteria. // // Arguments: iRow1 Index of first row being compared // iRow2 Index of second row being compared // // Returns: -1 if first row should be sorted before the second row // 0 if rows are equal // 1 if first row should be sorted after the second row // //+----------------------------------------------------------------------- int CTDCArr::SortComp(LONG iRow1, LONG iRow2) { CTDCSortCriterion *pCriterion; int cmp = 0; for (pCriterion = m_pSortList; pCriterion != NULL && cmp == 0; pCriterion = pCriterion->m_pNext) { CTDCCell *pCell1 = GetFilterCell(iRow1, pCriterion->m_iSortCol); CTDCCell *pCell2 = GetFilterCell(iRow2, pCriterion->m_iSortCol); cmp = VariantComp(pCell1, pCell2, GetColInfo(pCriterion->m_iSortCol)->vtType, m_fCaseSensitive); if (!pCriterion->m_fSortAscending) cmp = -cmp; } return cmp; } //+----------------------------------------------------------------------- // // Method: FilterParseComplex() // // Synopsis: Takes the text of a filter query, parses it and creates // a tree of CTDCFilterNode representing the query. // // Arguments: phr: pointer to HRESULT value, set to indicate success/failure. // // ppwchQuery: This is a textual representation of a query. The // query language syntax is: // // Query ::== Complex // // Complex ::== Simple // ::== Simple '&' Simple ( '&' Simple ... ) // ::== Simple '|' Simple ( '|' Simple ... ) // // Simple ::== '(' Complex ')' // ::== Atom Relop Atom // // Relop ::== '=' | '>' | '>=' | '<' | '<=' | '<>' // // Atom ::== Bunch of characters up to a (, ), >, <, =, & or | // If it's recognisable as field name, then it's // treated as a field name. Otherwise it's treated // as a value. Quotes (") are processed, and force // the atom to be treated as a value. Escape // characters (\) are processed and allow the // use of special characters within a field name. // // Notes: // ----- // * The definition of 'Complex' expressly forbids mixing // logical ANDs and ORs ('&' and '|') unless parentheses // are used to clarify the query. Something like: // field1 > 2 & field3 = "lime" | field4 < 5 // is illegal, but: // (field1 > 2 & field3 = "lime") | field4 < 5 // is fine. // // * It is illegal to attempt a comparison of two columns // with different types. // // // Returns: Pointer to parsed Filter Node upon success (*phr set to S_OK) // NULL upon failure (*phr set to an appropriate error code) // //+----------------------------------------------------------------------- CTDCFilterNode *CTDCArr::FilterParseComplex(LPWCH *ppwchQuery, HRESULT *phr) { *phr = S_OK; CTDCFilterNode *retval; WCHAR wchBoolOp = 0; retval = FilterParseSimple(ppwchQuery, phr); // Stop if there's an error, or we encounter a terminating ')' or '\0' // while (retval != NULL && **ppwchQuery != L')' && **ppwchQuery != 0) { // Next character should be a matching logical connector ... // if (**ppwchQuery != L'&' && **ppwchQuery != L'|') { *phr = E_FAIL; break; } if (wchBoolOp == 0) wchBoolOp = **ppwchQuery; else if (wchBoolOp != **ppwchQuery) { *phr = E_FAIL; break; } (*ppwchQuery)++; CTDCFilterNode *pTmp = new CTDCFilterNode; if (pTmp == NULL) { *phr = E_OUTOFMEMORY; break; } pTmp->m_type = (wchBoolOp == L'&') ? CTDCFilterNode::NT_AND : CTDCFilterNode::NT_OR; pTmp->m_pLeft = retval; retval = pTmp; retval->m_pRight = FilterParseSimple(ppwchQuery, phr); if (retval->m_pRight == NULL) break; } if (!SUCCEEDED(*phr) && retval != NULL) { delete retval; retval = NULL; } return retval; } CTDCFilterNode *CTDCArr::FilterParseSimple(LPWCH *ppwch, HRESULT *phr) { *phr = S_OK; CTDCFilterNode *retval = NULL; fWCHEatSpace(ppwch); // Eat up white space if (fWCHEatTest(ppwch, L'(')) { retval = FilterParseComplex(ppwch, phr); if (retval != NULL) { if (fWCHEatTest(ppwch, L')')) fWCHEatSpace(ppwch); // Eat up white space else *phr = E_FAIL; } goto Cleanup; } retval = FilterParseAtom(ppwch, phr); if (retval == NULL) goto Cleanup; { CTDCFilterNode *pTmp = new CTDCFilterNode; if (pTmp == NULL) { *phr = E_OUTOFMEMORY; goto Cleanup; } pTmp->m_pLeft = retval; retval = pTmp; } retval->m_vt = retval->m_pLeft->m_vt; // Get the relational operator // if (fWCHEatTest(ppwch, L'=')) retval->m_type = CTDCFilterNode::NT_EQ; else if (fWCHEatTest(ppwch, L'>')) retval->m_type = fWCHEatTest(ppwch, L'=') ? CTDCFilterNode::NT_GE : CTDCFilterNode::NT_GT; else if (fWCHEatTest(ppwch, L'<')) retval->m_type = fWCHEatTest(ppwch, L'=') ? CTDCFilterNode::NT_LE : fWCHEatTest(ppwch, L'>') ? CTDCFilterNode::NT_NE : CTDCFilterNode::NT_LT; else { *phr = E_FAIL; goto Cleanup; } retval->m_pRight = FilterParseAtom(ppwch, phr); if (retval->m_pRight == NULL) goto Cleanup; if (retval->m_pLeft->m_iCol <= 0 && retval->m_pRight->m_iCol <= 0) { // At least one of the atoms being compared must be a column // // This condition means we don't have to test for comparison // of two wildcard values. // *phr = E_FAIL; goto Cleanup; } // Check type compatibility of atoms // if (retval->m_pRight->m_vt != retval->m_vt) { CTDCFilterNode *pSrc = retval->m_pRight; CTDCFilterNode *pTarg= retval->m_pLeft; if (retval->m_pLeft->m_iCol > 0) { if (retval->m_pRight->m_iCol > 0) { // Two columns of incompatible type - can't resolve // *phr = E_FAIL; goto Cleanup; } pSrc = retval->m_pLeft; pTarg = retval->m_pRight; } _ASSERT(pTarg->m_vt == VT_BSTR); _ASSERT(pTarg->m_iCol == 0); _ASSERT(pSrc->m_iCol > 0); CTDCColInfo *pColInfo = GetColInfo(pSrc->m_iCol); _ASSERT(pColInfo->vtType == pSrc->m_vt); VARIANT vtmp; VariantInit(&vtmp); *phr = VariantFromBSTR(&vtmp, pTarg->m_value.bstrVal, pColInfo, m_lcid); if (!SUCCEEDED(*phr)) goto Cleanup; VariantClear(&pTarg->m_value); pTarg->m_value = vtmp; pTarg->m_vt = pSrc->m_vt; retval->m_vt = pSrc->m_vt; } Cleanup: if (!SUCCEEDED(*phr) && retval != NULL) { delete retval; retval = NULL; } return retval; } CTDCFilterNode *CTDCArr::FilterParseAtom(LPOLESTR *ppwch, HRESULT *phr) { *phr = S_OK; CTDCFilterNode *retval = NULL; int nQuote = 0; boolean fDone = FALSE; LPOLESTR pwchDest; LPOLESTR pwchLastStrip; fWCHEatSpace(ppwch); // Eat up white space OLECHAR *pwchTmpBuf = new OLECHAR[wch_len(*ppwch) + 1]; if (pwchTmpBuf == NULL) { *phr = E_OUTOFMEMORY; goto Cleanup; } pwchDest = pwchTmpBuf; pwchLastStrip = pwchTmpBuf; while (**ppwch != 0 && !fDone) switch (**ppwch) { case _T('\\'): // Handle escape characters // if ((*pwchDest++ = *++(*ppwch)) != 0) { (*ppwch)++; pwchLastStrip = pwchDest; } break; case _T('"'): // Quotes // (*ppwch)++; pwchLastStrip = pwchDest; nQuote++; break; case _T('>'): case _T('<'): case _T('='): case _T('('): case _T(')'): case _T('&'): case _T('|'): fDone = ((nQuote & 1) == 0); if (fDone) break; default: *pwchDest++ = *(*ppwch)++; } // Strip off trailing white space // while (pwchDest > pwchLastStrip && fWCHIsSpace(pwchDest[-1])) pwchDest--; *pwchDest = 0; if ((pwchDest == pwchTmpBuf && nQuote == 0) || (nQuote & 1)) { // Empty string or mismatched quote // *phr = E_FAIL; goto Cleanup; } retval = new CTDCFilterNode; if (retval == NULL) { *phr = E_OUTOFMEMORY; goto Cleanup; } retval->m_type = CTDCFilterNode::NT_ATOM; retval->m_iCol = nQuote > 0 ? 0 : FindCol(pwchTmpBuf); if (retval->m_iCol == 0) { retval->m_vt = VT_BSTR; retval->m_value.vt = VT_BSTR; retval->m_value.bstrVal = SysAllocString(pwchTmpBuf); if (retval->m_value.bstrVal == NULL) { *phr = E_OUTOFMEMORY; goto Cleanup; } retval->m_fWildcard = wch_chr(retval->m_value.bstrVal, _T('*')) != NULL; } else { retval->m_vt = GetColInfo(retval->m_iCol)->vtType; retval->m_fWildcard = FALSE; } Cleanup: if (pwchTmpBuf != NULL) delete pwchTmpBuf; if (!SUCCEEDED(*phr) && retval != NULL) { delete retval; retval = NULL; } return retval; } //+----------------------------------------------------------------------- // // Method: EvalDataRow() // // Synopsis: Evaluates the given data row # against the filter query // represented by 'pNode'. // // Arguments: iRow The number of the row to evaluate. // pNode A filter query to the row against. // // Returns: TRUE if the given row satisfies the filter query. // FALSE otherwise. // //+----------------------------------------------------------------------- boolean CTDCArr::EvalDataRow(LONG iRow, CTDCFilterNode *pNode) { boolean retval = TRUE; VARIANT *pVar1; VARIANT *pVar2; _ASSERT(pNode != NULL); switch (pNode->m_type) { case CTDCFilterNode::NT_AND: retval = EvalDataRow(iRow, pNode->m_pLeft) && EvalDataRow(iRow, pNode->m_pRight); break; case CTDCFilterNode::NT_OR: retval = EvalDataRow(iRow, pNode->m_pLeft) || EvalDataRow(iRow, pNode->m_pRight); break; case CTDCFilterNode::NT_EQ: case CTDCFilterNode::NT_NE: case CTDCFilterNode::NT_LT: case CTDCFilterNode::NT_GT: case CTDCFilterNode::NT_LE: case CTDCFilterNode::NT_GE: pVar1 = &pNode->m_pLeft->m_value; pVar2 = &pNode->m_pRight->m_value; if (pNode->m_pLeft->m_iCol > 0) pVar1 = GetDataCell(iRow, pNode->m_pLeft->m_iCol); if (pNode->m_pRight->m_iCol > 0) pVar2 = GetDataCell(iRow, pNode->m_pRight->m_iCol); if ((pNode->m_pLeft->m_fWildcard || pNode->m_pRight->m_fWildcard) && (pNode->m_type == CTDCFilterNode::NT_EQ || pNode->m_type == CTDCFilterNode::NT_NE) && pVar1->vt == VT_BSTR && pVar2->vt == VT_BSTR) { // Wildcards are only meaningful in comparing strings // for equlaity / inequality // VARIANT *pText; VARIANT *pPattern; if (pNode->m_pLeft->m_fWildcard) { pPattern = pVar1; pText = pVar2; } else { pText = pVar1; pPattern = pVar2; } retval = wch_wildcardMatch(pText->bstrVal, pPattern->bstrVal, m_fCaseSensitive) ? (pNode->m_type == CTDCFilterNode::NT_EQ) : (pNode->m_type == CTDCFilterNode::NT_NE); } else { int cmp; cmp = VariantComp(pVar1, pVar2, pNode->m_vt, m_fCaseSensitive); switch (pNode->m_type) { case CTDCFilterNode::NT_LT: retval = cmp < 0; break; case CTDCFilterNode::NT_LE: retval = cmp <= 0; break; case CTDCFilterNode::NT_GT: retval = cmp > 0; break; case CTDCFilterNode::NT_GE: retval = cmp >= 0; break; case CTDCFilterNode::NT_EQ: retval = cmp == 0; break; case CTDCFilterNode::NT_NE: retval = cmp != 0; break; } } break; default: _ASSERT(FALSE); } return retval; } //+----------------------------------------------------------------------- // // Method: ApplySortFilterCriteria() // // Synopsis: Resets any filter and sorting criterion for the control to // the values specified. Initiates sort/filter operations // if appropriate. // // Arguments: None. // // Returns: S_OK upon success. // E_OUTOFMEMORY if there was insufficient memory to complete // the operation. // //+----------------------------------------------------------------------- STDMETHODIMP CTDCArr::ApplySortFilterCriteria() { HRESULT hr = S_OK; LONG iRow; if (!m_fSortFilterDisrupted || m_state == LS_UNINITIALISED || m_state == LS_LOADING_HEADER_UNAVAILABLE) { // No change, or can't do anything yet. // goto Cleanup; } // Discard the old parse trees // if (m_pSortList != NULL) delete m_pSortList; if (m_pFilterTree != NULL) delete m_pFilterTree; m_pSortList = NULL; m_pFilterTree = NULL; // Discard old filtered rows // if (m_arrparrFilter.GetSize() > 0) m_arrparrFilter.DeleteElems(0, m_arrparrFilter.GetSize()); // Create an array of filter rows from the data rows // hr = m_arrparrFilter.InsertElems(0, m_iDataRows + 1); if (!SUCCEEDED(hr)) goto Cleanup; for (iRow = 0; iRow <= m_iDataRows; iRow++) m_arrparrFilter[iRow] = m_arrparrCells[iRow]; m_iFilterRows = CalcFilterRows(); // Create the filter parse tree // if (m_bstrFilterExpr != NULL) { LPWCH pwchQuery = m_bstrFilterExpr; m_pFilterTree = FilterParseComplex(&pwchQuery, &hr); if (hr == E_FAIL || (m_pFilterTree != NULL && *pwchQuery != 0)) { // Parse failed or there were unparsed characters left over. // This gets treated as an 'include everything' filter. // if (m_pFilterTree != NULL) { delete m_pFilterTree; m_pFilterTree = NULL; } hr = S_OK; } } // Filter the rows // if (m_pFilterTree != NULL) { LONG iRowDest = 1; for (iRow = 1; iRow <= m_iFilterRows; iRow++) if (EvalDataRow(iRow, m_pFilterTree)) m_arrparrFilter[iRowDest++] = m_arrparrFilter[iRow]; if (iRowDest < iRow) m_arrparrFilter.DeleteElems(iRowDest, iRow - iRowDest); m_iFilterRows = CalcFilterRows(); } // Create the sort list // hr = CreateSortList(m_bstrSortExpr); if (!SUCCEEDED(hr)) goto Cleanup; // Sort the filtered rows // if (m_pSortList != NULL && m_iFilterRows > 0) { SortElt *pSortArr = new SortElt[m_iFilterRows + 1]; if (pSortArr == NULL) { hr = E_OUTOFMEMORY; goto Cleanup; } for (iRow = 0; iRow <= m_iFilterRows; iRow++) { pSortArr[iRow].pInstance = this; pSortArr[iRow].iRow = iRow; pSortArr[iRow].parrRow = m_arrparrFilter[iRow]; } qsort((void *)&pSortArr[1], m_iFilterRows, sizeof(pSortArr[0]), CompareSort); for (iRow = 0; iRow <= m_iFilterRows; iRow++) m_arrparrFilter[iRow] = pSortArr[iRow].parrRow; delete pSortArr; } m_fSortFilterDisrupted = FALSE; if (m_state == LS_LOADING_HEADER_AVAILABLE && m_iDataRows > 0) { // We've just parsed the sort/filter expressions - there // was no data to sort/filter, so dont register a change. } else { // Notify the event-broker of the changes // if (m_pEventBroker != NULL) hr = m_pEventBroker->STDDataSetChanged(); } Cleanup: return hr; } //+----------------------------------------------------------------------- // // Method: SetSortFilterCriteria() // // Synopsis: Resets any filter and sorting criterion for the control to // the values specified. Initiates sort/filter operations // if any the changes invalidate existing criteria. // // Arguments: bstrSortExpr List of columns for sorting ("" = no sorting) // bstrFilterExpr Expression for filtering ("" = no filtering) // // Returns: S_OK upon success. // E_OUTOFMEMORY if there was insufficient memory to complete // the operation. // //+----------------------------------------------------------------------- STDMETHODIMP CTDCArr::SetSortFilterCriteria(BSTR bstrSortExpr, BSTR bstrFilterExpr, boolean fCaseSensitive) { HRESULT hr = S_OK; // Check if we need to reparse the sort/filter criteria // if (wch_cmp(bstrSortExpr, m_bstrSortExpr) != 0 || wch_cmp(bstrFilterExpr, m_bstrFilterExpr) != 0 || fCaseSensitive != m_fCaseSensitive) { m_fSortFilterDisrupted = TRUE; } SysFreeString(m_bstrSortExpr); SysFreeString(m_bstrFilterExpr); m_bstrSortExpr = bstrSortExpr; m_bstrFilterExpr = bstrFilterExpr; m_fCaseSensitive = fCaseSensitive; // If not loaded, leave it to the load process to apply any changes // if (m_state == LS_LOADED) hr = ApplySortFilterCriteria(); return hr; } //+----------------------------------------------------------------------- // // Method: CTDCCStartDataLoad() // // Synopsis: Preparation for a load operation via FieldSink routines below // // Arguments: fUseHeader TRUE if the first line of fields should // be interpreted as column name/type info. // bstrSortExpr Sort expression for ordering rows // bstrFilterExpr Filter expression for including/excluding rows // lcidRead Locale ID to use for interpreting locale- // dependent data formats (date, number etc). // pBSC COM object performing the data transfer // fAppend Flag indicating whether the data should be // appended to any existing data. // // Returns: S_OK indicating success. // //+----------------------------------------------------------------------- STDMETHODIMP CTDCArr::StartDataLoad(boolean fUseHeader, BSTR bstrSortExpr, BSTR bstrFilterExpr, LCID lcidRead, CComObject > *pBSC, boolean fAppend, boolean fCaseSensitive) { HRESULT hr = S_OK; // If we're asked to append to existing data AND // - there isn't any OR // - the previous load didn't load a header row // then treat it as an initial load. // if (fAppend && m_state == LS_UNINITIALISED) fAppend = FALSE; if (fAppend) { if (m_state != LS_LOADED || m_iDataRows < 0) { hr = E_FAIL; goto Cleanup; } m_state = LS_LOADING_HEADER_AVAILABLE; } else { if (m_state != LS_UNINITIALISED || m_iDataRows != -1 || m_iFilterRows != -1 || m_iCols != 0) { hr = E_FAIL; goto Cleanup; } m_state = LS_LOADING_HEADER_UNAVAILABLE; m_fSortFilterDisrupted = TRUE; } SetSortFilterCriteria(bstrSortExpr, bstrFilterExpr, fCaseSensitive); m_fUseHeader = fUseHeader; m_fSkipRow = fAppend && fUseHeader; _ASSERT(m_iFilterRows == CalcFilterRows()); _ASSERT(m_iDataRows == CalcDataRows()); _ASSERT(m_iCols == CalcCols()); m_iCurrRow = m_iDataRows + 1; m_iCurrCol = 1; m_lcidRead = lcidRead; if (m_pEventBroker != NULL) hr = m_pEventBroker->STDLoadStarted(pBSC, fAppend); Cleanup: return hr; } ////////////////////////////////////////////////////////////////////////// // // CTDCFieldSink Methods - see comments in file TDCParse.h // --------------------- // ////////////////////////////////////////////////////////////////////////// //+----------------------------------------------------------------------- // // Method: AddField() // // Synopsis: Adds a data cell to the growing cell grid. // // Arguments: pwch Wide-char string holding data for the cell // dwSize # of significant bytes in 'pwch' // // Returns: S_OK indicating success. // E_OUTOFMEMORY if there was not enough memory to add the cell. // //+----------------------------------------------------------------------- STDMETHODIMP CTDCArr::AddField(LPWCH pwch, DWORD dwSize) { _ASSERT(m_state == LS_LOADING_HEADER_UNAVAILABLE || m_state == LS_LOADING_HEADER_AVAILABLE); HRESULT hr = S_OK; LONG nCols = 0; BSTR bstr = NULL; if (m_fSkipRow) goto Cleanup; #ifdef TDC_ATL_DEBUG_ADDFIELD ATLTRACE( _T("CTDCArr::AddField called: %d, %d\n"), m_iCurrRow, m_iCurrCol); #endif if (m_iCurrRow > m_iDataRows && m_iCurrCol == 1) { TSTDArray *pRow; // Need to insert a new row // _ASSERT(m_iCurrRow == m_iDataRows + 1); hr = m_arrparrCells.InsertElems(m_iCurrRow, 1); if (!SUCCEEDED(hr)) goto Cleanup; pRow = new TSTDArray; if (pRow == NULL) { hr = E_OUTOFMEMORY; goto Cleanup; } m_arrparrCells[m_iCurrRow] = pRow; if (m_iCurrRow > 0) { // We've already read at least one row, so we know how // many columns to insert for this row // hr = m_arrparrCells[m_iCurrRow]->InsertElems(0, m_iCols); if (!SUCCEEDED(hr)) goto Cleanup; } } if (m_iCurrRow == 0) { // This is the first row - we don't know how many columns there // will be, so just insert a single cell for this new element. // _ASSERT(m_iCurrCol == m_iCols + 1); hr = m_arrparrCells[m_iCurrRow]->InsertElems(m_iCurrCol - 1, 1); if (!SUCCEEDED(hr)) goto Cleanup; m_iCols++; } if (m_iCurrCol <= m_iCols) { CTDCCell *pCell = GetDataCell(m_iCurrRow, m_iCurrCol); pCell->clear(); pCell->vt = VT_BSTR; if (dwSize <= 0) pCell->bstrVal = NULL; else { pCell->bstrVal = SysAllocStringLen(pwch, dwSize); if (pCell->bstrVal == NULL) { hr = E_OUTOFMEMORY; goto Cleanup; } } if (m_iCurrRow > 0) { CTDCColInfo *pColInfo = GetColInfo(m_iCurrCol); if (pColInfo->vtType != VT_BSTR) { VARIANT v; HRESULT hr; hr = VariantFromBSTR(&v, pCell->bstrVal, pColInfo, m_lcidRead); if (SUCCEEDED(hr)) { hr = VariantCopy(pCell, &v); VariantClear(&v); } else { // Leave it as a BSTR // hr = S_OK; } } } } m_iCurrCol++; Cleanup: return hr; } // InsertionSortHelper - // returns -1 if the candidate row < current filter array row // 0 if the candidate row = current filter array row // 1 if the candidate row > current filter array row int CTDCArr::InsertionSortHelper(int iRow) { CTDCSortCriterion *pCriterion; int cmp = 0; for (pCriterion = m_pSortList; pCriterion != NULL; pCriterion = pCriterion->m_pNext) { CTDCCell *pCell1 = GetDataCell(m_iDataRows, pCriterion->m_iSortCol); CTDCCell *pCell2 = GetFilterCell(iRow, pCriterion->m_iSortCol); cmp = VariantComp(pCell1, pCell2, GetColInfo(pCriterion->m_iSortCol)->vtType, m_fCaseSensitive); if (!pCriterion->m_fSortAscending) cmp = -cmp; // if < or >, we don't have to look at any further criterion if (cmp) break; } return cmp; } //+----------------------------------------------------------------------- // // Method: EOLN() // // Synopsis: Closes of the current row in the growing cell grid, // handling column headings if it's the first row. // // Arguments: None. // // Returns: S_OK indicating success. // E_OUTOFMEMORY if insufficient memory is available for // a conversion. // //+----------------------------------------------------------------------- STDMETHODIMP CTDCArr::EOLN() { ATLTRACE(_T("CTDCArr::EOLN called, row: %d\n"), m_iCurrRow); HRESULT hr = S_OK; _ASSERT(m_state == LS_LOADING_HEADER_UNAVAILABLE || m_state == LS_LOADING_HEADER_AVAILABLE); if (m_fSkipRow) { // Appending to existing data; skip over the first (header) line m_fSkipRow = FALSE; goto Cleanup; } if (m_iCurrRow == 0) { // The first row has been inserted - if m_fUseHeader indicates // that the first row contains header information then parse // it; otherwise, create some numbered column headings. // if (!m_fUseHeader) { hr = CreateNumberedColumnHeadings(); if (!SUCCEEDED(hr)) goto Cleanup; // An extra row has been inserted - update the insertion // row index for later insertion of new elements // m_iCurrRow++; // Initialise each column heading as "Column" // RenumberColumnHeadings(); } m_iDataRows++; m_iFilterRows++; _ASSERT(m_iDataRows == 0); _ASSERT(m_iFilterRows == 0); ParseColumnHeadings(); m_state = LS_LOADING_HEADER_AVAILABLE; // Insert the hedaer row into the list of filtered rows // hr = m_arrparrFilter.InsertElems(0, 1); if (!SUCCEEDED(hr)) goto Cleanup; m_arrparrFilter[0] = m_arrparrCells[0]; // Notify the event handler that the headers have been loaded // if (m_pEventBroker != NULL) { hr = m_pEventBroker->STDLoadedHeader(); OutputDebugStringX(_T("TDCCtl: header loaded\n")); if (!SUCCEEDED(hr)) goto Cleanup; } } if (m_iCurrRow > 0) { // Convert uninitialised cells into their column's type. // LONG iCol; for (iCol = m_iCurrCol; iCol < m_iCols; iCol++) { CTDCCell *pCell = GetDataCell(m_iCurrRow, iCol); // This uninitialised VARIANT is assumed to be the result // of specifying too few cells in a row. // _ASSERT(pCell->vt == VT_EMPTY); pCell->vt = VT_BSTR; pCell->bstrVal = NULL; CTDCColInfo *pColInfo = GetColInfo(iCol); if (pColInfo->vtType != VT_BSTR) { VARIANT v; HRESULT tmp_hr; tmp_hr = VariantFromBSTR(&v, pCell->bstrVal, pColInfo, m_lcidRead); if (SUCCEEDED(tmp_hr)) { hr = VariantCopy(pCell, &v); VariantClear(&v); if (!SUCCEEDED(hr)) goto Cleanup; } else { // Leave the cell as a BSTR // } } } m_iDataRows++; } m_iCurrCol = 1; m_iCurrRow++; if (m_fSortFilterDisrupted) { // This will have the side-effect of incorporating any new data rows // hr = ApplySortFilterCriteria(); if (!SUCCEEDED(hr)) goto Cleanup; } else if (m_iDataRows > 0 && (m_pFilterTree == NULL || EvalDataRow(m_iDataRows, m_pFilterTree))) { // The new row passed the filter criteria. // Insert the new row into the filtered list // LONG iRowInsertedAt = m_iFilterRows + 1; // at the correct insertion point according to the current // sort criteria, if there is one. We only need to do the search // if this is not the first row, and if the candidate row is less // than the last row. if (m_pSortList != NULL && m_iFilterRows != 0 && InsertionSortHelper(m_iFilterRows) < 0) { // not at end, do traditional binary search. LONG lLow = 1; // we don't use element zero! LONG lHigh = m_iFilterRows + 1; LONG lMid; while (lLow < lHigh) { lMid = (lLow + lHigh) / 2; // Note that InsertionSortHelper automatically flips the comparison // if m_fAscending flag is off. if (InsertionSortHelper(lMid) <= 0) { lHigh = lMid; } else { lLow = lMid + 1; } } iRowInsertedAt = lLow; } hr = m_arrparrFilter.InsertElems(iRowInsertedAt, 1); if (!SUCCEEDED(hr)) goto Cleanup; m_arrparrFilter[iRowInsertedAt] = m_arrparrCells[m_iDataRows]; ++m_iFilterRows; // Notify event handler of row insertion // if (m_pEventBroker != NULL) hr = m_pEventBroker->rowsAvailable(iRowInsertedAt, 1); } Cleanup: return hr; } //+----------------------------------------------------------------------- // // Method: EOF() // // Synopsis: Indicates no more cells will be added to the cell grid. // The column-heading cells are added unless it was indicated // that cell headings should be taken from the data read. // The cells in each column are converted to that column's // specified data type. // // Arguments: None. // // Returns: S_OK indicating success. // //+----------------------------------------------------------------------- STDMETHODIMP CTDCArr::EOF() { OutputDebugStringX(_T("CTDArr::EOF() called\n")); _ASSERT(m_state == LS_LOADING_HEADER_UNAVAILABLE || m_state == LS_LOADING_HEADER_AVAILABLE); HRESULT hr = S_OK; if (m_iCurrCol > 1) EOLN(); m_state = LS_LOADED; m_iFilterRows = CalcFilterRows(); _ASSERT(m_iDataRows == CalcDataRows()); _ASSERT(m_iCols == CalcCols()); if (m_fSortFilterDisrupted) { hr = ApplySortFilterCriteria(); if (!SUCCEEDED(hr)) goto Cleanup; } if (m_pEventBroker != NULL) hr = m_pEventBroker->STDLoadCompleted(); Cleanup: return hr; } // GetEstimatedRows.. // We should really see if URLMon has a means of giving a byte count on the file // we're downloading. For now though.. STDMETHODIMP CTDCArr::getEstimatedRows(DBROWCOUNT *pcRows) { *pcRows = m_iFilterRows; if (m_state 0) m_arrparrFilter.DeleteElems(0, m_arrparrFilter.GetSize()); if (m_arrparrCells.GetSize() > 0) m_arrparrCells.DeleteElems(0, m_arrparrCells.GetSize()); m_state = LS_UNINITIALISED; m_iFilterRows = CalcFilterRows(); m_iDataRows = CalcDataRows(); m_iCols = CalcCols(); // If we stop the load before the header was parsed, we won't // have a dataset, but we still need to fire datasetchanged, // to let our customer know the query failed. if (m_pEventBroker != NULL) hr = m_pEventBroker->STDDataSetChanged(); // // fall through to LOADING_HEADER_AVAILABLE! // case LS_LOADING_HEADER_AVAILABLE: m_state = LS_LOADED; // mark us as finished now // LoadStopped will abort any transfer in progress, and fire // transferComplete with the OSPXFER_ABORT flag. if (m_pEventBroker != NULL) hr = m_pEventBroker->STDLoadStopped(); break; } return hr; } ////////////////////////////////////////////////////////////////////////// // // Implementation of IUnknown COM interface. // ----------------------------------------- // ////////////////////////////////////////////////////////////////////////// //+----------------------------------------------------------------------- // // Method: QueryInterface() // // Synopsis: Implements part of the standard IUnknown COM interface. // (Returns a pointer to this COM object) // // Arguments: riid GUID to recognise // ppv Pointer to this COM object [OUT] // // Returns: S_OK upon success. // E_NOINTERFACE if queried for an unrecognised interface. // //+----------------------------------------------------------------------- STDMETHODIMP CTDCArr::QueryInterface (REFIID riid, LPVOID * ppv) { HRESULT hr; _ASSERTE(ppv != NULL); // This is the non-delegating IUnknown implementation if (riid == IID_IUnknown || riid == IID_OLEDBSimpleProvider) { *ppv = this; ((LPUNKNOWN)*ppv)->AddRef(); hr = S_OK; } else { *ppv = NULL; hr = E_NOINTERFACE; } #ifdef _ATL_DEBUG_QI AtlDumpIID(riid, _T("CTDCArr"), hr); #endif return hr; } //+----------------------------------------------------------------------- // // Method: AddRef() // // Synopsis: Implements part of the standard IUnknown COM interface. // (Adds a reference to this COM object) // // Arguments: None // // Returns: Number of references to this COM object. // //+----------------------------------------------------------------------- STDMETHODIMP_(ULONG) CTDCArr::AddRef () { return ++m_cRef; } //+----------------------------------------------------------------------- // // Method: Release() // // Synopsis: Implements part of the standard IUnknown COM interface. // (Removes a reference to this COM object) // // Arguments: None // // Returns: Number of remaining references to this COM object. // 0 if the COM object is no longer referenced. // //+----------------------------------------------------------------------- STDMETHODIMP_(ULONG) CTDCArr::Release () { ULONG retval; m_cRef -= 1; retval = m_cRef; if (!m_cRef) { m_cRef = 0xffff; //MM: Use this 'flag' for debug? delete this; } return retval; }