2025-11-28 00:35:46 +09:00

371 lines
16 KiB
C++

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// This sample program reads lines from the console, parses them using the system
// schema, and displays the resulting condition trees. A real application might
// feed the condition trees to a graphical UI and/or translate them into a query
// language to be executed.
// To compile this sample to run also on Windows Vista, uncomment the following line.
// #define _WIN32_WINNT _WIN32_WINNT_VISTA
#include <stdio.h>
#include <propsys.h>
#include <propvarutil.h>
#include <shlwapi.h>
#include <structuredquery.h>
#pragma comment(lib, "propsys.lib")
#pragma comment(lib, "shlwapi.lib")
// Returns a label for a condition operation. Do not free the result.
#define MAP_ENTRY(x) {L#x, x}
PCWSTR GetOperationLabel(CONDITION_OPERATION op)
{
PCWSTR pszLabel = NULL;
static struct { PCWSTR pszLabel; CONDITION_OPERATION op; } const c_rgOperationLabels[] =
{
MAP_ENTRY(COP_IMPLICIT),
MAP_ENTRY(COP_EQUAL),
MAP_ENTRY(COP_NOTEQUAL),
MAP_ENTRY(COP_LESSTHAN),
MAP_ENTRY(COP_GREATERTHAN),
MAP_ENTRY(COP_LESSTHANOREQUAL),
MAP_ENTRY(COP_GREATERTHANOREQUAL),
MAP_ENTRY(COP_VALUE_STARTSWITH),
MAP_ENTRY(COP_VALUE_ENDSWITH),
MAP_ENTRY(COP_VALUE_CONTAINS),
MAP_ENTRY(COP_VALUE_NOTCONTAINS),
MAP_ENTRY(COP_DOSWILDCARDS),
MAP_ENTRY(COP_WORD_EQUAL),
MAP_ENTRY(COP_WORD_STARTSWITH),
MAP_ENTRY(COP_APPLICATION_SPECIFIC),
};
for (ULONG i = 0 ; !pszLabel && i < ARRAYSIZE(c_rgOperationLabels) ; ++i)
{
if (c_rgOperationLabels[i].op == op)
{
pszLabel = c_rgOperationLabels[i].pszLabel;
}
}
if (!pszLabel)
{
pszLabel = L"???";
}
return pszLabel;
}
// Display to stdout one condition tree, with indentation.
// Code that targets only Windows 7 and later can take advantage of ICondition2 and IObjectArray.
// Code that targets also earlier versions of Windows and Windows Search should use ICondition and IEnumUnknown.
// Note that the two functions display leaves for search terms with no explicit property somewhat differently.
#if _WIN32_WINNT >= _WIN32_WINNT_WIN7
HRESULT DisplayQuery(ICondition2* pc, int cIndentation)
{
CONDITION_TYPE ct;
HRESULT hr = pc->GetConditionType(&ct);
if (SUCCEEDED(hr))
{
switch (ct)
{
case CT_AND_CONDITION:
case CT_OR_CONDITION:
{
wprintf(L"%*s%s\n", 2 * cIndentation, L"", (ct == CT_AND_CONDITION ? L"AND" : L"OR"));
IObjectArray* poaSubs;
hr = pc->GetSubConditions(IID_PPV_ARGS(&poaSubs));
if (SUCCEEDED(hr))
{
UINT cSubs;
hr = poaSubs->GetCount(&cSubs);
for (UINT i = 0 ; SUCCEEDED(hr) && i < cSubs ; ++i)
{
ICondition2* pcSub;
hr = poaSubs->GetAt(i, IID_PPV_ARGS(&pcSub));
if (SUCCEEDED(hr))
{
DisplayQuery(pcSub, cIndentation + 1);
pcSub->Release();
}
}
poaSubs->Release();
}
}
break;
case CT_NOT_CONDITION:
{
wprintf(L"%*s%s\n", 2 * cIndentation, L"", L"NOT");
// ICondition::GetSubConditions can return the single subcondition of a negation node directly.
ICondition2* pcSub;
hr = pc->GetSubConditions(IID_PPV_ARGS(&pcSub));
if (SUCCEEDED(hr))
{
DisplayQuery(pcSub, cIndentation + 1);
pcSub->Release();
}
}
break;
case CT_LEAF_CONDITION:
{
PROPERTYKEY propkey;
CONDITION_OPERATION op;
PROPVARIANT propvar;
hr = pc->GetLeafConditionInfo(&propkey, &op, &propvar);
if (SUCCEEDED(hr))
{
IPropertyDescription* ppd;
hr = PSGetPropertyDescription(propkey, IID_PPV_ARGS(&ppd));
if (SUCCEEDED(hr))
{
PWSTR pszPropertyName;
hr = ppd->GetCanonicalName(&pszPropertyName);
if (SUCCEEDED(hr))
{
PROPVARIANT propvarString;
hr = PropVariantChangeType(&propvarString, propvar, PVCHF_ALPHABOOL, VT_LPWSTR); // Real applications should prefer PSFormatForDisplay but we want more "raw" values.
if (SUCCEEDED(hr))
{
PWSTR pszSemanticType;
hr = pc->GetValueType(&pszSemanticType);
if (SUCCEEDED(hr))
{
// The semantic type may be NULL; if so, do not display it at all.
if (!pszSemanticType)
{
hr = SHStrDup(L"", &pszSemanticType);
}
if (SUCCEEDED(hr))
{
wprintf(L"%*sLEAF %s %s %s %s\n", 2 * cIndentation, L"", pszPropertyName, GetOperationLabel(op), propvarString.pwszVal, pszSemanticType);
}
CoTaskMemFree(pszSemanticType);
}
PropVariantClear(&propvarString);
}
CoTaskMemFree(pszPropertyName);
}
ppd->Release();
}
PropVariantClear(&propvar);
}
}
break;
}
}
return hr;
}
#else // _WIN32_WINNT >= _WIN32_WINNT_WIN7
HRESULT DisplayQuery(ICondition* pc, int cIndentation)
{
CONDITION_TYPE ct;
HRESULT hr = pc->GetConditionType(&ct);
if (SUCCEEDED(hr))
{
switch (ct)
{
case CT_AND_CONDITION:
case CT_OR_CONDITION:
{
wprintf(L"%*s%s\n", 2 * cIndentation, L"", (ct == CT_AND_CONDITION ? L"AND" : L"OR"));
IEnumUnknown* peuSubs;
hr = pc->GetSubConditions(IID_PPV_ARGS(&peuSubs));
if (SUCCEEDED(hr))
{
IUnknown* punk;
while ((hr = peuSubs->Next(1, &punk, NULL)) == S_OK)
{
ICondition* pcSub;
hr = punk->QueryInterface(IID_PPV_ARGS(&pcSub));
if (SUCCEEDED(hr))
{
DisplayQuery(pcSub, cIndentation + 1);
pcSub->Release();
}
punk->Release();
}
peuSubs->Release();
}
}
break;
case CT_NOT_CONDITION:
{
wprintf(L"%*s%s\n", 2 * cIndentation, L"", L"NOT");
// ICondition::GetSubConditions can return the single subcondition of a negation node directly.
ICondition* pcSub;
hr = pc->GetSubConditions(IID_PPV_ARGS(&pcSub));
if (SUCCEEDED(hr))
{
DisplayQuery(pcSub, cIndentation + 1);
pcSub->Release();
}
}
break;
case CT_LEAF_CONDITION:
{
PWSTR pszPropertyName;
CONDITION_OPERATION op;
PROPVARIANT propvar;
hr = pc->GetComparisonInfo(&pszPropertyName, &op, &propvar);
if (SUCCEEDED(hr))
{
// The property name may be NULL or "*"; if NULL, display it as such.
if (!pszPropertyName)
{
hr = SHStrDup(L"(NULL)", &pszPropertyName);
}
PROPVARIANT propvarString;
hr = PropVariantChangeType(&propvarString, propvar, PVCHF_ALPHABOOL, VT_LPWSTR); // Real applications should prefer PSFormatForDisplay but we want more "raw" values.
if (SUCCEEDED(hr))
{
PWSTR pszSemanticType;
hr = pc->GetValueType(&pszSemanticType);
if (SUCCEEDED(hr))
{
// The semantic type may be NULL; if so, do not display it at all.
if (!pszSemanticType)
{
hr = SHStrDup(L"", &pszSemanticType);
}
if (SUCCEEDED(hr))
{
wprintf(L"%*sLEAF %s %s %s %s\n", 2 * cIndentation, L"", pszPropertyName, GetOperationLabel(op), propvarString.pwszVal, pszSemanticType);
}
CoTaskMemFree(pszSemanticType);
}
PropVariantClear(&propvarString);
}
CoTaskMemFree(pszPropertyName);
PropVariantClear(&propvar);
}
}
break;
}
}
return hr;
}
#endif // _WIN32_WINNT >= _WIN32_WINNT_WIN7
int wmain(int /* argc */, wchar_t* /* argv */ [])
{
wprintf(L"StructuredQuerySample\n");
wprintf(L"Please enter a query in Advanced Query Syntax (AQS), or an empty line to exit\n");
wprintf(L"the program. The program will parse the query, resolve the resulting condition\n");
wprintf(L"tree and display it in tree form. Some sample inputs to try:\n");
wprintf(L"from:bill modified:last week subject:(cats OR dogs)\n");
wprintf(L"name:(bubble NOT bath) author:~~george taken:<October 2007\n");
wprintf(L"kind:=folder has:attachment rating:****\n");
wprintf(L"flower readstatus:read size:42KB\n");
wprintf(L"System.IsShared:=FALSE exposuretime:>=0.125 received:5/25/2006 .. 7/17/2007\n");
wprintf(L"Note that these queries are for English as UI language. If your UI language is\n");
wprintf(L"different, you should use keywords from that language. Note though that\n");
wprintf(L"System.IsShared:=FALSE is an example of a query in \"canonical syntax\", which\n");
wprintf(L"will work for any UI language and should be used in programmatic use of AQS.\n");
wprintf(L"MSDN on AQS: http://msdn.microsoft.com/en-us/library/aa965711(VS.85).aspx)\n");
wprintf(L"Note also that any times in the condition trees are in UTC, not local time.\n");
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if (SUCCEEDED(hr))
{
// It is possible to CoCreateInstance a query parser directly using __uuidof(QueryParser) but by
// using a QueryParserManager we can get a query parser prepared for a certain UI language and catalog.
IQueryParserManager* pqpm;
hr = CoCreateInstance(__uuidof(QueryParserManager), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pqpm));
if (SUCCEEDED(hr))
{
// The language given to IQueryParserManager::CreateLoadedParser should be the user interface language of the
// application, which is often the same as the operating system's default UI language for this user. It is used for
// deciding what language to be used when interpreting keywords.
IQueryParser* pqp;
hr = pqpm->CreateLoadedParser(L"SystemIndex", GetUserDefaultUILanguage(), IID_PPV_ARGS(&pqp));
if (SUCCEEDED(hr))
{
// This sample turns off "natural query syntax", which is the more relaxed and human language like
// search notation, and also makes searches automatically look for words beginning with what has been
// specified (so an input of "from:bill" will search for anything from someone whose name begins with
// "bill".
hr = pqpm->InitializeOptions(FALSE, TRUE, pqp);
WCHAR szLine[1024];
while (SUCCEEDED(hr) && fgetws(szLine, ARRAYSIZE(szLine), stdin)[0] != L'\n')
{
// The actual work of parsing a query string is done here.
IQuerySolution* pqs;
hr = pqp->Parse(szLine, NULL, &pqs);
if (SUCCEEDED(hr))
{
// In this sample we do not bother distinguishing between various parse errors though we could.
// Note that there will be a solution even if there were parse errors; it just may not be what
// the user intended.
IEnumUnknown* peu;
hr = pqs->GetErrors(IID_PPV_ARGS(&peu));
if (SUCCEEDED(hr))
{
if (peu->Skip(1) == S_OK)
{
wprintf(L"Some part of the query string could not be parsed.\n");
}
ICondition* pc;
hr = pqs->GetQuery(&pc, NULL);
if (SUCCEEDED(hr))
{
// IQueryCondition::Resolve and IConditionFactory2::ResolveCondition turn any date/time references
// (relative, such as "today", and absolute, such as "5/7/2009") into absolute date/time references
// (in the UTC time zone), and also simplifies the result in various ways.
// Note that pc is unchanged and could be resolved against additional dates/times.
// Code that targets only Windows 7 and later can take advantage of IConditionFactory2::ResolveCondition.
// Code that targets also earlier versions of Windows and Windows Search should use IQueryCondition::Resolve.
SYSTEMTIME st;
GetLocalTime(&st);
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN7)
IConditionFactory2* pcf;
hr = pqs->QueryInterface(IID_PPV_ARGS(&pcf));
if (SUCCEEDED(hr))
{
ICondition2* pcResolved;
hr = pcf->ResolveCondition(pc, SQRO_DEFAULT, &st, IID_PPV_ARGS(&pcResolved));
if (SUCCEEDED(hr))
{
hr = DisplayQuery(pcResolved, 0);
pcResolved->Release();
}
pcf->Release();
}
#else // _WIN32_WINNT >= _WIN32_WINNT_WIN7
ICondition* pcResolved;
// With IConditionFactory::Resolve we must use SQRO_DONT_SPLIT_WORDS to behave like
// IConditionFactory2::ResolveCondition, we want to keep natural phrases unbroken.
hr = pqs->Resolve(pc, SQRO_DONT_SPLIT_WORDS, &st, &pcResolved);
if (SUCCEEDED(hr))
{
hr = DisplayQuery(pcResolved, 0);
pcResolved->Release();
}
#endif // _WIN32_WINNT >= _WIN32_WINNT_WIN7
pc->Release();
}
peu->Release();
}
pqs->Release();
}
}
pqp->Release();
}
pqpm->Release();
}
CoUninitialize();
}
return SUCCEEDED(hr) ? EXIT_SUCCESS : EXIT_FAILURE;
}