diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6efb5c2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..cf280d2 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +**xmltab-wlx** is a [Total Commander](https://www.ghisler.com/) plugin to view XML files. + +|[**Download the latest version**](https://github.com/little-brother/xmltab-wlx/releases/latest/download/xmltab.zip)| +|-------------------------------------------------------------------------------------------| + +![View](xmltab.png) + +### Features +* Mixed tree and grid view +* Column filters +* Sort data by column click +* Beautifier and highlighting +* Supports ANSI, UTF8 and UTF16 + +If you have any problems, comments or suggestions, check [Wiki](https://github.com/little-brother/xmltab-wlx/wiki), create [issue](https://github.com/little-brother/xmltab-wlx/issues) or just let me know lb.im@ya.ru. \ No newline at end of file diff --git a/main.c b/main.c new file mode 100644 index 0000000..ba58e7a --- /dev/null +++ b/main.c @@ -0,0 +1,1515 @@ +#define UNICODE +#define _UNICODE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xml.h" + +#define LVS_EX_AUTOSIZECOLUMNS 0x10000000 + +#define WMU_UPDATE_GRID WM_USER + 1 +#define WMU_UPDATE_RESULTSET WM_USER + 2 +#define WMU_UPDATE_FILTER_SIZE WM_USER + 3 +#define WMU_AUTO_COLUMN_SIZE WM_USER + 4 +#define WMU_RESET_CACHE WM_USER + 5 +#define WMU_SET_FONT WM_USER + 6 +#define WMU_UPDATE_TEXT WM_USER + 7 +#define WMU_UPDATE_HIGHLIGHT WM_USER + 8 + +#define IDC_MAIN 100 +#define IDC_TREE 101 +#define IDC_TAB 102 +#define IDC_GRID 103 +#define IDC_TEXT 104 +#define IDC_STATUSBAR 105 +#define IDC_HEADER_EDIT 1000 + +#define IDM_COPY_CELL 5000 +#define IDM_COPY_ROW 5001 +#define IDM_COPY_TEXT 5002 +#define IDM_SELECTALL 5003 +#define IDM_FORMAT 5004 + +#define IDH_EXIT 6000 +#define IDH_NEXT 6001 +#define IDH_PREV 6002 + +#define SB_UNUSED 0 +#define SB_CODEPAGE 1 +#define SB_MODE 2 +#define SB_ROW_COUNT 3 +#define SB_CURRENT_ROW 4 + +#define MAX_LENGTH 4096 +#define MAX_HIGHLIGHT_LENGTH 64000 +#define APP_NAME TEXT("xmltab") +#define LOADING TEXT("Loading...") + +#define CP_UTF16LE 1200 +#define CP_UTF16BE 1201 + +typedef struct { + int size; + DWORD PluginInterfaceVersionLow; + DWORD PluginInterfaceVersionHi; + char DefaultIniName[MAX_PATH]; +} ListDefaultParamStruct; + +static TCHAR iniPath[MAX_PATH] = {0}; + +LRESULT CALLBACK cbNewMain (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); +LRESULT CALLBACK cbNewFilterEdit(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); +LRESULT CALLBACK cbNewText(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); +HTREEITEM addNode(HWND hTreeWnd, HTREEITEM hParentItem, xml_element* val); +void highlightText(HWND hWnd, TCHAR* text); +char* formatXML(const char* data); +void setStoredValue(TCHAR* name, int value); +int getStoredValue(TCHAR* name, int defValue); +int CALLBACK cbEnumTabStopChildren (HWND hWnd, LPARAM lParam); +TCHAR* utf8to16(const char* in); +char* utf16to8(const TCHAR* in); +int detectCodePage(const unsigned char *data); +void setClipboardText(const TCHAR* text); +BOOL isNumber(const TCHAR* val); +BOOL isEmpty (const char* s); +BOOL isUtf8(const char * string); +HTREEITEM TreeView_AddItem (HWND hTreeWnd, TCHAR* caption, HTREEITEM parent, LPARAM lParam); +int TreeView_GetItemText(HWND hTreeWnd, HTREEITEM hItem, TCHAR* buf, int maxLen); +LPARAM TreeView_GetItemParam(HWND hTreeWnd, HTREEITEM hItem); +int TreeView_SetItemText(HWND hTreeWnd, HTREEITEM hItem, TCHAR* text); +int ListView_AddColumn(HWND hListWnd, TCHAR* colName); +int Header_GetItemText(HWND hWnd, int i, TCHAR* pszText, int cchTextMax); + +BOOL APIENTRY DllMain (HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { + return TRUE; +} + +void __stdcall ListGetDetectString(char* DetectString, int maxlen) { + snprintf(DetectString, maxlen, "MULTIMEDIA & ext=\"XML\""); +} + +void __stdcall ListSetDefaultParams(ListDefaultParamStruct* dps) { + if (iniPath[0] == 0) { + DWORD size = MultiByteToWideChar(CP_ACP, 0, dps->DefaultIniName, -1, NULL, 0); + MultiByteToWideChar(CP_ACP, 0, dps->DefaultIniName, -1, iniPath, size); + } +} + +HWND APIENTRY ListLoad (HWND hListerWnd, char* fileToLoad, int showFlags) { + DWORD size = MultiByteToWideChar(CP_ACP, 0, fileToLoad, -1, NULL, 0); + TCHAR* filepath = (TCHAR*)calloc (size, sizeof (TCHAR)); + MultiByteToWideChar(CP_ACP, 0, fileToLoad, -1, filepath, size); + + struct _stat st = {0}; + if (_tstat(filepath, &st) != 0 || st.st_size > getStoredValue(TEXT("max-file-size"), 100000000) || st.st_size < 4) + return 0; + + char* data = calloc(st.st_size + 1, sizeof(char)); + FILE *f = fopen(fileToLoad, "rb"); + fread(data, sizeof(char), st.st_size, f); + fclose(f); + + int cp = detectCodePage(data); + if (cp == CP_UTF16BE) { + for (int i = 0; i < st.st_size/2; i++) { + int c = data[2 * i]; + data[2 * i] = data[2 * i + 1]; + data[2 * i + 1] = c; + } + } + + if (cp == CP_UTF16LE || cp == CP_UTF16BE) { + char* data8 = utf16to8((TCHAR*)data); + free(data); + data = data8; + } + + if (cp == CP_ACP) { + DWORD len = MultiByteToWideChar(CP_ACP, 0, data, -1, NULL, 0); + TCHAR* data16 = (TCHAR*)calloc (len, sizeof (TCHAR)); + MultiByteToWideChar(CP_ACP, 0, data, -1, data16, len); + free(data); + char* data8 = utf16to8((TCHAR*)data16); + data = data8; + free(data16); + } + + struct xml_element *xml = xml_parse(data); + if (!xml) { + free(data); + return 0; + } + + INITCOMMONCONTROLSEX icex; + icex.dwSize = sizeof(icex); + icex.dwICC = ICC_LISTVIEW_CLASSES | ICC_TREEVIEW_CLASSES; + InitCommonControlsEx(&icex); + LoadLibrary(TEXT("msftedit.dll")); + + BOOL isStandalone = GetParent(hListerWnd) == HWND_DESKTOP; + HWND hMainWnd = CreateWindowEx(WS_EX_CONTROLPARENT, WC_STATIC, TEXT("xml-wlx"), WS_CHILD | WS_VISIBLE | (isStandalone ? SS_SUNKEN : 0), + 0, 0, 100, 100, hListerWnd, (HMENU)IDC_MAIN, GetModuleHandle(0), NULL); + + SetProp(hMainWnd, TEXT("WNDPROC"), (HANDLE)SetWindowLongPtr(hMainWnd, GWLP_WNDPROC, (LONG_PTR)&cbNewMain)); + SetProp(hMainWnd, TEXT("XML"), xml); + SetProp(hMainWnd, TEXT("DATA"), data); + SetProp(hMainWnd, TEXT("CACHE"), 0); + SetProp(hMainWnd, TEXT("RESULTSET"), 0); + SetProp(hMainWnd, TEXT("ROWCOUNT"), calloc(1, sizeof(int))); + SetProp(hMainWnd, TEXT("TOTALROWCOUNT"), calloc(1, sizeof(int))); + SetProp(hMainWnd, TEXT("ORDERBY"), calloc(1, sizeof(int))); + SetProp(hMainWnd, TEXT("COLNO"), calloc(1, sizeof(int))); + SetProp(hMainWnd, TEXT("SPLITTERWIDTH"), calloc(1, sizeof(int))); + SetProp(hMainWnd, TEXT("ISFORMAT"), calloc(1, sizeof(int))); + SetProp(hMainWnd, TEXT("FONT"), 0); + SetProp(hMainWnd, TEXT("FONTSIZE"), calloc(1, sizeof(int))); + SetProp(hMainWnd, TEXT("GRAYBRUSH"), CreateSolidBrush(GetSysColor(COLOR_BTNFACE))); + SetProp(hMainWnd, TEXT("MAXHIGHLIGHTLENGTH"), calloc(1, sizeof(int))); + + *(int*)GetProp(hMainWnd, TEXT("SPLITTERWIDTH")) = getStoredValue(TEXT("splitter-width"), 200); + *(int*)GetProp(hMainWnd, TEXT("FONTSIZE")) = getStoredValue(TEXT("font-size"), 16); + *(int*)GetProp(hMainWnd, TEXT("ISFORMAT")) = getStoredValue(TEXT("format"), 1); + *(int*)GetProp(hMainWnd, TEXT("MAXHIGHLIGHTLENGTH")) = getStoredValue(TEXT("max-highlight-length"), 64000); + + HWND hStatusWnd = CreateStatusWindow(WS_CHILD | WS_VISIBLE | (isStandalone ? SBARS_SIZEGRIP : 0), NULL, hMainWnd, IDC_STATUSBAR); + int sizes[6] = {90, 150, 200, 400, 500, -1}; + SendMessage(hStatusWnd, SB_SETPARTS, 6, (LPARAM)&sizes); + + HWND hTreeWnd = CreateWindow(WC_TREEVIEW, NULL, WS_VISIBLE | WS_CHILD | TVS_HASBUTTONS | TVS_HASLINES | TVS_SHOWSELALWAYS | WS_TABSTOP, + 0, 0, 100, 100, hMainWnd, (HMENU)IDC_TREE, GetModuleHandle(0), NULL); + + HWND hTabWnd = CreateWindow(WC_TABCONTROL, NULL, WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE | WS_TABSTOP, 100, 100, 100, 100, + hMainWnd, (HMENU)IDC_TAB, GetModuleHandle(0), NULL); + + TCITEM tci; + tci.mask = TCIF_TEXT | TCIF_IMAGE; + tci.iImage = -1; + tci.pszText = TEXT("Grid"); + tci.cchTextMax = 5; + TabCtrl_InsertItem(hTabWnd, 0, &tci); + + tci.pszText = TEXT("Text"); + tci.cchTextMax = 5; + TabCtrl_InsertItem(hTabWnd, 1, &tci); + + int tabNo = getStoredValue(TEXT("tab-no"), 0); + HWND hGridWnd = CreateWindow(WC_LISTVIEW, NULL, (tabNo == 0 ? WS_VISIBLE : 0) | WS_CHILD | LVS_REPORT | LVS_SHOWSELALWAYS | LVS_SINGLESEL | LVS_OWNERDATA | WS_TABSTOP, + 205, 0, 100, 100, hTabWnd, (HMENU)IDC_GRID, GetModuleHandle(0), NULL); + ListView_SetExtendedListViewStyle(hGridWnd, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_LABELTIP); + + HWND hHeader = ListView_GetHeader(hGridWnd); + LONG_PTR styles = GetWindowLongPtr(hHeader, GWL_STYLE); + SetWindowLongPtr(hHeader, GWL_STYLE, styles | HDS_FILTERBAR); + + HWND hTextWnd = CreateWindowEx(0, TEXT("RICHEDIT50W"), NULL, (tabNo == 1 ? WS_VISIBLE : 0) | WS_CHILD | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | WS_VSCROLL | WS_HSCROLL | WS_TABSTOP | ES_NOHIDESEL | ES_READONLY, + 0, 0, 100, 100, hTabWnd, (HMENU)IDC_TEXT, GetModuleHandle(0), NULL); + SetProp(hTextWnd, TEXT("WNDPROC"), (HANDLE)SetWindowLongPtr(hTextWnd, GWLP_WNDPROC, (LONG_PTR)cbNewText)); + TabCtrl_SetCurSel(hTabWnd, tabNo); + + HMENU hDataMenu = CreatePopupMenu(); + AppendMenu(hDataMenu, MF_STRING, IDM_COPY_CELL, TEXT("Copy cell")); + AppendMenu(hDataMenu, MF_STRING, IDM_COPY_ROW, TEXT("Copy row")); + SetProp(hMainWnd, TEXT("DATAMENU"), hDataMenu); + + HMENU hTextMenu = CreatePopupMenu(); + AppendMenu(hTextMenu, MF_STRING, IDM_COPY_TEXT, TEXT("Copy")); + AppendMenu(hTextMenu, MF_STRING, IDM_SELECTALL, TEXT("Select all")); + AppendMenu(hTextMenu, MF_STRING, 0, NULL); + AppendMenu(hTextMenu, MF_STRING | (*(int*)GetProp(hMainWnd, TEXT("ISFORMAT")) != 0 ? MF_CHECKED : 0), IDM_FORMAT, TEXT("Format")); + SetProp(hMainWnd, TEXT("TEXTMENU"), hTextMenu); + + SendMessage(hMainWnd, WMU_SET_FONT, 0, 0); + SendMessage(hMainWnd, WM_SIZE, 0, 0); + + SendMessage(hStatusWnd, SB_SETTEXT, SB_CODEPAGE, + (LPARAM)(cp == CP_UTF8 ? TEXT(" UTF-8") : cp == CP_UTF16LE ? TEXT(" UTF-16LE") : cp == CP_UTF16BE ? TEXT(" UTF-16BE") : TEXT(" ANSI"))); + + xml_element* node = xml->first_child; + while (node) { + HTREEITEM hItem = addNode(hTreeWnd, TVI_ROOT, node); + TreeView_Expand(hTreeWnd, hItem, TVE_EXPAND); + node = node->next; + } + + HTREEITEM hItem = TreeView_GetNextItem(hTreeWnd, TVI_ROOT, TVGN_CHILD); + TreeView_Select(hTreeWnd, hItem, TVGN_CARET); + + RegisterHotKey(hMainWnd, IDH_EXIT, 0, VK_ESCAPE); + RegisterHotKey(hMainWnd, IDH_NEXT, 0, VK_TAB); + RegisterHotKey(hMainWnd, IDH_PREV, MOD_CONTROL, VK_TAB); + SetFocus(hTreeWnd); + + return hMainWnd; +} + +void __stdcall ListCloseWindow(HWND hWnd) { + setStoredValue(TEXT("splitter-width"), *(int*)GetProp(hWnd, TEXT("SPLITTERWIDTH"))); + setStoredValue(TEXT("font-size"), *(int*)GetProp(hWnd, TEXT("FONTSIZE"))); + setStoredValue(TEXT("format"), *(int*)GetProp(hWnd, TEXT("ISFORMAT"))); + setStoredValue(TEXT("tab-no"), TabCtrl_GetCurSel(GetDlgItem(hWnd, IDC_TAB))); + + SendMessage(hWnd, WMU_RESET_CACHE, 0, 0); + xml_free((xml_element*)GetProp(hWnd, TEXT("XML"))); + free((char*)GetProp(hWnd, TEXT("DATA"))); + free((int*)GetProp(hWnd, TEXT("ROWCOUNT"))); + free((int*)GetProp(hWnd, TEXT("TOTALROWCOUNT"))); + free((int*)GetProp(hWnd, TEXT("ORDERBY"))); + free((int*)GetProp(hWnd, TEXT("COLNO"))); + free((int*)GetProp(hWnd, TEXT("SPLITTERWIDTH"))); + free((int*)GetProp(hWnd, TEXT("ISFORMAT"))); + free((int*)GetProp(hWnd, TEXT("MAXHIGHLIGHTLENGTH"))); + + DeleteFont(GetProp(hWnd, TEXT("FONT"))); + DeleteObject(GetProp(hWnd, TEXT("GRAYBRUSH"))); + DestroyMenu(GetProp(hWnd, TEXT("DATAMENU"))); + DestroyMenu(GetProp(hWnd, TEXT("TEXTMENU"))); + + RemoveProp(hWnd, TEXT("WNDPROC")); + RemoveProp(hWnd, TEXT("CACHE")); + RemoveProp(hWnd, TEXT("RESULTSET")); + RemoveProp(hWnd, TEXT("ROWCOUNT")); + RemoveProp(hWnd, TEXT("TOTALROWCOUNT")); + RemoveProp(hWnd, TEXT("ORDERBY")); + RemoveProp(hWnd, TEXT("COLNO")); + RemoveProp(hWnd, TEXT("XML")); + RemoveProp(hWnd, TEXT("DATA")); + RemoveProp(hWnd, TEXT("SPLITTERWIDTH")); + RemoveProp(hWnd, TEXT("ISFORMAT")); + RemoveProp(hWnd, TEXT("MAXHIGHLIGHTLENGTH")); + + RemoveProp(hWnd, TEXT("FONT")); + RemoveProp(hWnd, TEXT("GRAYBRUSH")); + RemoveProp(hWnd, TEXT("DATAMENU")); + RemoveProp(hWnd, TEXT("TEXTMENU")); + + DestroyWindow(hWnd); + return; +} + +LRESULT CALLBACK cbNewMain(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { + switch (msg) { + case WM_HOTKEY: { + WPARAM id = wParam; + if (id == IDH_EXIT) + SendMessage(GetParent(hWnd), WM_CLOSE, 0, 0); + + if (id == IDH_NEXT || id == IDH_PREV) { + HWND hFocus = GetFocus(); + HWND wnds[1000] = {0}; + EnumChildWindows(hWnd, (WNDENUMPROC)cbEnumTabStopChildren, (LPARAM)wnds); + + int no = 0; + while(wnds[no] && wnds[no] != hFocus) + no++; + + int cnt = no; + while(wnds[cnt]) + cnt++; + + BOOL isBackward = id == IDH_PREV; + no += isBackward ? -1 : 1; + SetFocus(wnds[no] && no >= 0 ? wnds[no] : (isBackward ? wnds[cnt - 1] : wnds[0])); + } + } + break; + + case WM_SIZE: { + HWND hStatusWnd = GetDlgItem(hWnd, IDC_STATUSBAR); + SendMessage(hStatusWnd, WM_SIZE, 0, 0); + RECT rc; + GetClientRect(hStatusWnd, &rc); + int statusH = rc.bottom; + + int splitterW = *(int*)GetProp(hWnd, TEXT("SPLITTERWIDTH")); + GetClientRect(hWnd, &rc); + HWND hTreeWnd = GetDlgItem(hWnd, IDC_TREE); + HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB); + HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID); + HWND hTextWnd = GetDlgItem(hTabWnd, IDC_TEXT); + SetWindowPos(hTreeWnd, 0, 0, 0, splitterW, rc.bottom - statusH, SWP_NOMOVE | SWP_NOZORDER); + SetWindowPos(hTabWnd, 0, splitterW + 5, 0, rc.right - splitterW - 5, rc.bottom - statusH, SWP_NOZORDER); + + RECT rc2; + GetClientRect(hTabWnd, &rc); + TabCtrl_GetItemRect(hTabWnd, 0, &rc2); + SetWindowPos(hTextWnd, 0, 2, rc2.bottom + 3, rc.right - rc.left - 5, rc.bottom - rc2.bottom - 7, SWP_NOZORDER); + SetWindowPos(hGridWnd, 0, 2, rc2.bottom + 3, rc.right - rc.left - 5, rc.bottom - rc2.bottom - 7, SWP_NOZORDER); + } + break; + + case WM_PAINT: { + PAINTSTRUCT ps = {0}; + HDC hDC = BeginPaint(hWnd, &ps); + + RECT rc; + GetClientRect(hWnd, &rc); + rc.left = *(int*)GetProp(hWnd, TEXT("SPLITTERWIDTH")); + rc.right = rc.left + 5; + FillRect(hDC, &rc, (HBRUSH)GetProp(hWnd, TEXT("GRAYBRUSH"))); + EndPaint(hWnd, &ps); + + return 0; + } + break; + + // https://groups.google.com/g/comp.os.ms-windows.programmer.win32/c/1XhCKATRXws + case WM_NCHITTEST: { + return 1; + } + break; + + case WM_LBUTTONDOWN: { + SetProp(hWnd, TEXT("ISMOUSEDOWN"), (HANDLE)1); + SetCapture(hWnd); + return 0; + } + break; + + case WM_LBUTTONUP: { + ReleaseCapture(); + RemoveProp(hWnd, TEXT("ISMOUSEDOWN")); + } + break; + + case WM_MOUSEMOVE: { + if (wParam != MK_LBUTTON || !GetProp(hWnd, TEXT("ISMOUSEDOWN"))) + return 0; + + DWORD x = GET_X_LPARAM(lParam); + if (x > 0 && x < 32000) + *(int*)GetProp(hWnd, TEXT("SPLITTERWIDTH")) = x; + SendMessage(hWnd, WM_SIZE, 0, 0); + } + break; + + case WM_MOUSEWHEEL: { + if (LOWORD(wParam) == MK_CONTROL) { + SendMessage(hWnd, WMU_SET_FONT, GET_WHEEL_DELTA_WPARAM(wParam) > 0 ? 1: -1, 0); + return 1; + } + } + break; + + case WM_CONTEXTMENU: { + POINT p = {LOWORD(lParam), HIWORD(lParam)}; + if (GetDlgCtrlID(WindowFromPoint(p)) == IDC_TEXT) + TrackPopupMenu(GetProp(hWnd, TEXT("TEXTMENU")), TPM_RIGHTBUTTON | TPM_TOPALIGN | TPM_LEFTALIGN, p.x, p.y, 0, hWnd, NULL); + } + break; + + case WM_COMMAND: { + WORD cmd = LOWORD(wParam); + if (cmd == IDM_COPY_CELL || cmd == IDM_COPY_ROW) { + HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB); + HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID); + HWND hHeader = ListView_GetHeader(hGridWnd); + int rowNo = ListView_GetNextItem(hGridWnd, -1, LVNI_SELECTED); + if (rowNo != -1) { + TCHAR*** cache = (TCHAR***)GetProp(hWnd, TEXT("CACHE")); + int* resultset = (int*)GetProp(hWnd, TEXT("RESULTSET")); + int rowCount = *(int*)GetProp(hWnd, TEXT("ROWCOUNT")); + if (!resultset || rowNo >= rowCount) + return 0; + + int colCount = Header_GetItemCount(hHeader); + + int startNo = cmd == IDM_COPY_CELL ? *(int*)GetProp(hWnd, TEXT("COLNO")) : 0; + int endNo = cmd == IDM_COPY_CELL ? startNo + 1 : colCount; + if (startNo > colCount || endNo > colCount) + return 0; + + int len = 0; + for (int colNo = startNo; colNo < endNo; colNo++) { + int _rowNo = resultset[rowNo]; + len += _tcslen(cache[_rowNo][colNo]) + 1 /* column delimiter: TAB */; + } + + TCHAR* buf = calloc(len + 1, sizeof(TCHAR)); + for (int colNo = startNo; colNo < endNo; colNo++) { + int _rowNo = resultset[rowNo]; + _tcscat(buf, cache[_rowNo][colNo]); + if (colNo != endNo - 1) + _tcscat(buf, TEXT("\t")); + } + + setClipboardText(buf); + free(buf); + } + } + + if (cmd == IDM_COPY_TEXT || cmd == IDM_SELECTALL) { + HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB); + HWND hTextWnd = GetDlgItem(hTabWnd, IDC_TEXT); + SendMessage(hTextWnd, cmd == IDM_COPY_TEXT ? WM_COPY : EM_SETSEL, 0, -1); + } + + if (cmd == IDM_FORMAT) { + HMENU hMenu = (HMENU)GetProp(hWnd, TEXT("TEXTMENU")); + int* pIsFormat = (int*)GetProp(hWnd, TEXT("ISFORMAT")); + *pIsFormat = (*pIsFormat + 1) % 2; + + MENUITEMINFO mii = {0}; + mii.cbSize = sizeof(MENUITEMINFO); + mii.fMask = MIIM_STATE; + mii.fState = *pIsFormat ? MFS_CHECKED : 0; + SetMenuItemInfo(hMenu, IDM_FORMAT, FALSE, &mii); + + SendMessage(hWnd, WMU_UPDATE_TEXT, 0, 0); + } + } + break; + + case WM_NOTIFY : { + NMHDR* pHdr = (LPNMHDR)lParam; + if (pHdr->idFrom == IDC_TAB && pHdr->code == TCN_SELCHANGE) { + HWND hTabWnd = pHdr->hwndFrom; + BOOL isText = TabCtrl_GetCurSel(hTabWnd) == 1; + ShowWindow(GetDlgItem(hTabWnd, IDC_GRID), isText ? SW_HIDE : SW_SHOW); + ShowWindow(GetDlgItem(hTabWnd, IDC_TEXT), isText ? SW_SHOW : SW_HIDE); + } + + if (pHdr->idFrom == IDC_TREE && pHdr->code == TVN_SELCHANGED) { + SendMessage(hWnd, WMU_UPDATE_GRID, 0, 0); + SendMessage(hWnd, WMU_UPDATE_TEXT, 0, 0); + } + + if (pHdr->idFrom == IDC_TREE && pHdr->code == TVN_ITEMEXPANDING) { + HWND hTreeWnd = pHdr->hwndFrom; + NMTREEVIEW* tv = (NMTREEVIEW*) lParam; + HTREEITEM hItem = tv->itemNew.hItem; + HTREEITEM hChild = TreeView_GetChild(hTreeWnd, hItem); + + if (hChild) { + TCHAR nodeName[256]; + TreeView_GetItemText(hTreeWnd, hChild, nodeName, 255); + if (_tcscmp(nodeName, LOADING) == 0) { + TreeView_DeleteItem(hTreeWnd, hChild); + + xml_element* node = (xml_element*)TreeView_GetItemParam(hTreeWnd, hItem); + xml_element* subnode = node != NULL ? node->first_child : 0; + while (subnode) { + addNode(hTreeWnd, hItem, subnode); + subnode = subnode->next; + } + } + } + } + + if (pHdr->idFrom == IDC_GRID && pHdr->code == LVN_GETDISPINFO) { + LV_DISPINFO* pDispInfo = (LV_DISPINFO*)lParam; + LV_ITEM* pItem = &(pDispInfo)->item; + + TCHAR*** cache = (TCHAR***)GetProp(hWnd, TEXT("CACHE")); + int* resultset = (int*)GetProp(hWnd, TEXT("RESULTSET")); + if(resultset && pItem->mask & LVIF_TEXT) { + int rowNo = resultset[pItem->iItem]; + pItem->pszText = cache[rowNo][pItem->iSubItem]; + } + } + + if (pHdr->idFrom == IDC_GRID && pHdr->code == LVN_COLUMNCLICK) { + NMLISTVIEW* pLV = (NMLISTVIEW*)lParam; + int colNo = pLV->iSubItem + 1; + int* pOrderBy = (int*)GetProp(hWnd, TEXT("ORDERBY")); + int orderBy = *pOrderBy; + *pOrderBy = colNo == orderBy || colNo == -orderBy ? -orderBy : colNo; + SendMessage(hWnd, WMU_UPDATE_RESULTSET, 0, 0); + } + + if (pHdr->idFrom == IDC_GRID && pHdr->code == (DWORD)NM_RCLICK) { + NMITEMACTIVATE* ia = (LPNMITEMACTIVATE) lParam; + POINT p; + GetCursorPos(&p); + *(int*)GetProp(hWnd, TEXT("COLNO")) = ia->iSubItem; + TrackPopupMenu(GetProp(hWnd, TEXT("DATAMENU")), TPM_RIGHTBUTTON | TPM_TOPALIGN | TPM_LEFTALIGN, p.x, p.y, 0, hWnd, NULL); + } + + if (pHdr->idFrom == IDC_GRID && pHdr->code == (DWORD)LVN_ITEMCHANGED) { + HWND hStatusWnd = GetDlgItem(hWnd, IDC_STATUSBAR); + + TCHAR buf[255] = {0}; + int pos = ListView_GetNextItem(pHdr->hwndFrom, -1, LVNI_SELECTED); + if (pos != -1) + _sntprintf(buf, 255, TEXT(" %i"), pos + 1); + SendMessage(hStatusWnd, SB_SETTEXT, SB_CURRENT_ROW, (LPARAM)buf); + } + + if (pHdr->idFrom == IDC_GRID && pHdr->code == (DWORD)LVN_KEYDOWN) { + NMLVKEYDOWN* kd = (LPNMLVKEYDOWN) lParam; + if (kd->wVKey == 0x43 && GetKeyState(VK_CONTROL)) // Ctrl + C + SendMessage(hWnd, WM_COMMAND, IDM_COPY_ROW, 0); + } + + if (pHdr->code == HDN_ITEMCHANGED && pHdr->hwndFrom == ListView_GetHeader(GetDlgItem(GetDlgItem(hWnd, IDC_TAB), IDC_GRID))) + SendMessage(hWnd, WMU_UPDATE_FILTER_SIZE, 0, 0); + } + break; + + case WMU_UPDATE_GRID: { + HWND hTreeWnd = GetDlgItem(hWnd, IDC_TREE); + HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB); + HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID); + HWND hStatusWnd = GetDlgItem(hWnd, IDC_STATUSBAR); + + HTREEITEM hItem = TreeView_GetSelection(hTreeWnd); + xml_element* node = (xml_element*)TreeView_GetItemParam(hTreeWnd, hItem); + + HWND hHeader = ListView_GetHeader(hGridWnd); + SendMessage(hWnd, WMU_RESET_CACHE, 0, 0); + *(int*)GetProp(hWnd, TEXT("ORDERBY")) = 0; + + int colCount = Header_GetItemCount(hHeader); + for (int colNo = 0; colNo < colCount; colNo++) + DestroyWindow(GetDlgItem(hHeader, IDC_HEADER_EDIT + colNo)); + + for (int colNo = 0; colNo < colCount; colNo++) + ListView_DeleteColumn(hGridWnd, colCount - colNo - 1); + + BOOL isTable = TRUE; + xml_element* template = 0; + int childCount = 0; + + xml_element* subnode = node->first_child; + char* tagName = 0; + while (isTable && subnode) { + childCount += subnode->key != 0; + if (tagName && subnode->key) + isTable = strcmp(subnode->key, tagName) == 0; + + if (!tagName && subnode->key && strlen(subnode->key) > 0) { + tagName = subnode->key; + template = subnode; + } + + subnode = subnode->next; + } + isTable = isTable && childCount > 1; + + if (isTable) { + xml_attribute* attr = template->first_attribute; + while (attr) { + char* colName8 = calloc(strlen(attr->key) + 2, sizeof(char)); + sprintf(colName8, "@%s", attr->key); + TCHAR* colName16 = utf8to16(colName8); + ListView_AddColumn(hGridWnd, colName16); + free(colName16); + free(colName8); + + attr = attr->next; + } + + subnode = template->first_child; + while (subnode) { + if (subnode->key && strlen(subnode->key) > 0) { + + TCHAR* colName16 = utf8to16(subnode->key); + ListView_AddColumn(hGridWnd, colName16); + free(colName16); + } + subnode = subnode->next; + } + + ListView_AddColumn(hGridWnd, TEXT("#CONTENT")); + } else { + ListView_AddColumn(hGridWnd, TEXT("Key")); + ListView_AddColumn(hGridWnd, TEXT("Value")); + } + + colCount = Header_GetItemCount(hHeader); + for (int colNo = 0; colNo < colCount; colNo++) { + // Use WS_BORDER to vertical text aligment + HWND hEdit = CreateWindowEx(WS_EX_TOPMOST, WC_EDIT, NULL, ES_CENTER | ES_AUTOHSCROLL | WS_VISIBLE | WS_CHILD | WS_TABSTOP | WS_BORDER, + 0, 0, 0, 0, hHeader, (HMENU)(INT_PTR)(IDC_HEADER_EDIT + colNo), GetModuleHandle(0), NULL); + SendMessage(hEdit, WM_SETFONT, (LPARAM)GetProp(hWnd, TEXT("FONT")), TRUE); + SetProp(hEdit, TEXT("WNDPROC"), (HANDLE)SetWindowLongPtr(hEdit, GWLP_WNDPROC, (LONG_PTR)cbNewFilterEdit)); + } + + + // Cache data + int* pTotalRowCount = (int*)GetProp(hWnd, TEXT("TOTALROWCOUNT")); + ListView_SetItemCount(hGridWnd, 0); + SetProp(hWnd, TEXT("CACHE"), 0); + + TCHAR*** cache = 0; + int rowCount = 0; + int rowNo = 0; + if (isTable) { + rowCount = node->child_count; + cache = calloc(rowCount, sizeof(TCHAR*)); + + xml_element* subnode = node->first_child; + while (subnode) { + if (!subnode->key) { + subnode = subnode->next; + continue; + } + + cache[rowNo] = (TCHAR**)calloc (colCount, sizeof (TCHAR*)); + int colNo = 0; + + xml_attribute* attr = template->first_attribute; + while (attr) { + xml_attribute* sa = xml_find_attribute(subnode, attr->key); + cache[rowNo][colNo] = utf8to16(sa ? sa->value : "N/A"); + + colNo++; + attr = attr->next; + } + + xml_element* tagNode = template->first_child; + while (tagNode) { + if (!tagNode->key) { + tagNode = tagNode->next; + continue; + } + + xml_element* sn = xml_find_element(subnode, tagNode->key); + cache[rowNo][colNo] = utf8to16(sn ? xml_content(sn) : "N/A"); + + colNo++; + tagNode = tagNode->next; + } + cache[rowNo][colNo] = utf8to16(subnode ? xml_content(subnode) : ""); + + rowNo++; + subnode = subnode->next; + } + } else { + rowCount = node->attribute_count + node->child_count + 1; + cache = calloc(rowCount, sizeof(TCHAR*)); + + xml_attribute* attr = node->first_attribute; + while (attr) { + cache[rowNo] = (TCHAR**)calloc (colCount, sizeof (TCHAR*)); + char* buf8 = calloc(strlen(attr->key) + 2, sizeof(char)); + sprintf(buf8, "@%s", attr->key); + cache[rowNo][0] = utf8to16(buf8); + free(buf8); + cache[rowNo][1] = utf8to16(attr->value); + + rowNo++; + attr = attr->next; + } + + xml_element* subnode = node->first_child; + while (subnode) { + if (!subnode->key && isEmpty(subnode->value)){ + subnode = subnode->next; + continue; + } + + cache[rowNo] = (TCHAR**)calloc (colCount, sizeof (TCHAR*)); + if (subnode->key) { + if (strncmp(subnode->key, "![CDATA[", 8) == 0) { + int len = strlen(subnode->key); + char* buf = calloc(len + 1, sizeof(char)); + sprintf(buf, "%.*s", len - strlen("![CDATA[]]>"), subnode->key + 8); + cache[rowNo][0] = utf8to16("[CDATA]"); + cache[rowNo][1] = utf8to16(buf); + free(buf); + } else if (strncmp(subnode->key, "!--", 3) == 0) { + int len = strlen(subnode->key); + char* buf = calloc(len + 1, sizeof(char)); + sprintf(buf, "%.*s", len - strlen("!----"), subnode->key + 3); + cache[rowNo][0] = utf8to16("[COMMENT]"); + cache[rowNo][1] = utf8to16(buf); + free(buf); + } else { + cache[rowNo][0] = utf8to16(subnode->key); + cache[rowNo][1] = utf8to16(xml_content(subnode)); + } + } else { + cache[rowNo][0] = utf8to16("[TEXT]"); + cache[rowNo][1] = utf8to16(subnode->value); + } + + rowNo++; + subnode = subnode->next; + } + } + + if (rowNo == 0) { + if (cache) + free(cache); + } else { + cache = realloc(cache, rowNo * sizeof(TCHAR*)); + SetProp(hWnd, TEXT("CACHE"), cache); + } + + SendMessage(hStatusWnd, SB_SETTEXT, SB_MODE, (LPARAM)(isTable ? TEXT(" TABLE") : TEXT(" SINGLE"))); + + *pTotalRowCount = rowNo; + SendMessage(hWnd, WMU_UPDATE_RESULTSET, 0, 0); + SendMessage(hWnd, WMU_AUTO_COLUMN_SIZE, 0, 0); + } + break; + + case WMU_UPDATE_RESULTSET: { + HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB); + HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID); + HWND hStatusWnd = GetDlgItem(hWnd, IDC_STATUSBAR); + HWND hHeader = ListView_GetHeader(hGridWnd); + + ListView_SetItemCount(hGridWnd, 0); + TCHAR*** cache = (TCHAR***)GetProp(hWnd, TEXT("CACHE")); + int* pTotalRowCount = (int*)GetProp(hWnd, TEXT("TOTALROWCOUNT")); + int* pRowCount = (int*)GetProp(hWnd, TEXT("ROWCOUNT")); + int* pOrderBy = (int*)GetProp(hWnd, TEXT("ORDERBY")); + int* resultset = (int*)GetProp(hWnd, TEXT("RESULTSET")); + if (resultset) + free(resultset); + + if (!cache) + return 1; + + if (*pTotalRowCount == 0) + return 1; + + int colCount = Header_GetItemCount(hHeader); + if (colCount == 0) + return 1; + + BOOL* bResultset = (BOOL*)calloc(*pTotalRowCount, sizeof(BOOL)); + for (int rowNo = 0; rowNo < *pTotalRowCount; rowNo++) + bResultset[rowNo] = TRUE; + + for (int colNo = 0; colNo < colCount; colNo++) { + HWND hEdit = GetDlgItem(hHeader, IDC_HEADER_EDIT + colNo); + TCHAR filter[MAX_LENGTH]; + GetWindowText(hEdit, filter, MAX_LENGTH); + int len = _tcslen(filter); + if (len == 0) + continue; + + for (int rowNo = 0; rowNo < *pTotalRowCount; rowNo++) { + if (!bResultset[rowNo]) + continue; + + TCHAR* value = cache[rowNo][colNo]; + if (len > 1 && (filter[0] == TEXT('<') || filter[0] == TEXT('>')) && isNumber(filter + 1)) { + TCHAR* end = 0; + double df = _tcstod(filter + 1, &end); + double dv = _tcstod(value, &end); + bResultset[rowNo] = (filter[0] == TEXT('<') && dv < df) || (filter[0] == TEXT('>') && dv > df); + } else { + bResultset[rowNo] = len == 1 ? _tcsstr(value, filter) != 0 : + filter[0] == TEXT('=') ? _tcscmp(value, filter + 1) == 0 : + filter[0] == TEXT('!') ? _tcsstr(value, filter + 1) == 0 : + filter[0] == TEXT('<') ? _tcscmp(value, filter + 1) < 0 : + filter[0] == TEXT('>') ? _tcscmp(value, filter + 1) > 0 : + _tcsstr(value, filter) != 0; + } + } + } + + int rowCount = 0; + resultset = (int*)calloc(*pTotalRowCount, sizeof(int)); + for (int rowNo = 0; rowNo < *pTotalRowCount; rowNo++) { + if (!bResultset[rowNo]) + continue; + + resultset[rowCount] = rowNo; + rowCount++; + } + free(bResultset); + + if (rowCount > 0) { + if (rowCount > *pTotalRowCount) + MessageBeep(0); + resultset = realloc(resultset, rowCount * sizeof(int)); + SetProp(hWnd, TEXT("RESULTSET"), (HANDLE)resultset); + int orderBy = *pOrderBy; + + if (orderBy) { + // Bubble-sort + for (int i = 0; i < rowCount; i++) { + for (int j = i + 1; j < rowCount; j++) { + int a = resultset[i]; + int b = resultset[j]; + + if(orderBy > 0 && _tcscmp(cache[a][orderBy - 1], cache[b][orderBy - 1]) > 0) { + resultset[i] = b; + resultset[j] = a; + } + + if(orderBy < 0 && _tcscmp(cache[a][-orderBy - 1], cache[b][-orderBy - 1]) < 0) { + resultset[i] = b; + resultset[j] = a; + } + } + } + } + } else { + SetProp(hWnd, TEXT("RESULTSET"), (HANDLE)0); + free(resultset); + } + + *pRowCount = rowCount; + ListView_SetItemCount(hGridWnd, rowCount); + InvalidateRect(hGridWnd, NULL, TRUE); + + TCHAR buf[255]; + _sntprintf(buf, 255, TEXT(" Rows: %i/%i"), rowCount, *pTotalRowCount); + SendMessage(hStatusWnd, SB_SETTEXT, SB_ROW_COUNT, (LPARAM)buf); + + PostMessage(hWnd, WMU_UPDATE_FILTER_SIZE, 0, 0); + } + break; + + case WMU_UPDATE_TEXT: { + HWND hTreeWnd = GetDlgItem(hWnd, IDC_TREE); + HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB); + HWND hTextWnd = GetDlgItem(hTabWnd, IDC_TEXT); + char* data = (char*)GetProp(hWnd, TEXT("DATA")); + + HTREEITEM hItem = TreeView_GetSelection(hTreeWnd); + xml_element* node = (xml_element*)TreeView_GetItemParam(hTreeWnd, hItem); + char* text8 = calloc(node->length + 1, sizeof(char)); + strncpy(text8, data + node->offset, node->length); + + if (*(int*)GetProp(hWnd, TEXT("ISFORMAT"))) { + char* ftext8 = formatXML(text8); + free(text8); + text8 = ftext8; + } + TCHAR* text16 = utf8to16(text8); + free(text8); + + LockWindowUpdate(hTextWnd); + SendMessage(hTextWnd, EM_EXLIMITTEXT, 0, _tcslen(text16) + 1); + SetWindowText(hTextWnd, text16); + LockWindowUpdate(0); + free(text16); + + SendMessage(hWnd, WMU_UPDATE_HIGHLIGHT, 0, 0); + } + break; + + case WMU_UPDATE_HIGHLIGHT: { + HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB); + HWND hTextWnd = GetDlgItem(hTabWnd, IDC_TEXT); + + GETTEXTLENGTHEX gtl = {GTL_NUMBYTES, 0}; + int len = SendMessage(hTextWnd, EM_GETTEXTLENGTHEX, (WPARAM)>l, 1200); + if (len < *(int*)GetProp(hWnd, TEXT("MAXHIGHLIGHTLENGTH"))) { + TCHAR* text = calloc(len + sizeof(TCHAR), sizeof(char)); + GETTEXTEX gt = {0}; + gt.cb = len + sizeof(TCHAR); + gt.flags = 0; + gt.codepage = 1200; + SendMessage(hTextWnd, EM_GETTEXTEX, (WPARAM)>, (LPARAM)text); + LockWindowUpdate(hTextWnd); + highlightText(hTextWnd, text); + free(text); + SendMessage(hTextWnd, EM_SETSEL, 0, 0); + LockWindowUpdate(0); + InvalidateRect(hTextWnd, NULL, TRUE); + } + } + break; + + case WMU_UPDATE_FILTER_SIZE: { + HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB); + HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID); + HWND hHeader = ListView_GetHeader(hGridWnd); + int colCount = Header_GetItemCount(hHeader); + SendMessage(hHeader, WM_SIZE, 0, 0); + for (int colNo = 0; colNo < colCount; colNo++) { + RECT rc; + Header_GetItemRect(hHeader, colNo, &rc); + int h2 = round((rc.bottom - rc.top) / 2); + SetWindowPos(GetDlgItem(hHeader, IDC_HEADER_EDIT + colNo), 0, rc.left - (colNo > 0), h2, rc.right - rc.left + 1, h2 + 1, SWP_NOZORDER); + } + } + break; + + case WMU_AUTO_COLUMN_SIZE: { + HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB); + HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID); + SendMessage(hGridWnd, WM_SETREDRAW, FALSE, 0); + HWND hHeader = ListView_GetHeader(hGridWnd); + int colCount = Header_GetItemCount(hHeader); + + for (int colNo = 0; colNo < colCount - 1; colNo++) + ListView_SetColumnWidth(hGridWnd, colNo, colNo < colCount - 1 ? LVSCW_AUTOSIZE_USEHEADER : LVSCW_AUTOSIZE); + + if (colCount == 1 && ListView_GetColumnWidth(hGridWnd, 0) < 100) + ListView_SetColumnWidth(hGridWnd, 0, 100); + + int maxWidth = getStoredValue(TEXT("max-column-width"), 300); + if (colCount > 1) { + for (int colNo = 0; colNo < colCount; colNo++) { + if (ListView_GetColumnWidth(hGridWnd, colNo) > maxWidth) + ListView_SetColumnWidth(hGridWnd, colNo, maxWidth); + } + } + + // Fix last column + if (colCount > 1) { + int colNo = colCount - 1; + ListView_SetColumnWidth(hGridWnd, colNo, LVSCW_AUTOSIZE); + TCHAR name16[MAX_LENGTH + 1]; + Header_GetItemText(hHeader, colNo, name16, MAX_LENGTH); + + SIZE s = {0}; + HDC hDC = GetDC(hHeader); + HFONT hOldFont = (HFONT)SelectObject(hDC, (HFONT)GetProp(hWnd, TEXT("FONT"))); + GetTextExtentPoint32(hDC, name16, _tcslen(name16), &s); + SelectObject(hDC, hOldFont); + ReleaseDC(hHeader, hDC); + + int w = s.cx + 12; + if (ListView_GetColumnWidth(hGridWnd, colNo) < w) + ListView_SetColumnWidth(hGridWnd, colNo, w); + + if (ListView_GetColumnWidth(hGridWnd, colNo) > maxWidth) + ListView_SetColumnWidth(hGridWnd, colNo, maxWidth); + } + + SendMessage(hGridWnd, WM_SETREDRAW, TRUE, 0); + InvalidateRect(hGridWnd, NULL, TRUE); + + PostMessage(hWnd, WMU_UPDATE_FILTER_SIZE, 0, 0); + } + break; + + case WMU_RESET_CACHE: { + HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB); + HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID); + TCHAR*** cache = (TCHAR***)GetProp(hWnd, TEXT("CACHE")); + int* pTotalRowCount = (int*)GetProp(hWnd, TEXT("TOTALROWCOUNT")); + + int colCount = Header_GetItemCount(ListView_GetHeader(hGridWnd)); + if (colCount > 0 && cache != 0) { + for (int rowNo = 0; rowNo < *pTotalRowCount; rowNo++) { + if (cache[rowNo]) { + for (int colNo = 0; colNo < colCount; colNo++) + if (cache[rowNo][colNo]) + free(cache[rowNo][colNo]); + + free(cache[rowNo]); + } + cache[rowNo] = 0; + } + free(cache); + } + + int* resultset = (int*)GetProp(hWnd, TEXT("RESULTSET")); + if (resultset) + free(resultset); + SetProp(hWnd, TEXT("RESULTSET"), 0); + + int* pRowCount = (int*)GetProp(hWnd, TEXT("ROWCOUNT")); + *pRowCount = 0; + + SetProp(hWnd, TEXT("CACHE"), 0); + *pTotalRowCount = 0; + } + break; + + // wParam - size delta + case WMU_SET_FONT: { + int* pFontSize = (int*)GetProp(hWnd, TEXT("FONTSIZE")); + if (*pFontSize + wParam < 10 || *pFontSize + wParam > 48) + return 0; + *pFontSize += wParam; + DeleteFont(GetProp(hWnd, TEXT("FONT"))); + + HFONT hFont = CreateFont (*pFontSize, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, TEXT("Arial")); + HWND hTreeWnd = GetDlgItem(hWnd, IDC_TREE); + HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB); + HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID); + HWND hTextWnd = GetDlgItem(hTabWnd, IDC_TEXT); + SendMessage(hTreeWnd, WM_SETFONT, (LPARAM)hFont, TRUE); + SendMessage(hGridWnd, WM_SETFONT, (LPARAM)hFont, TRUE); + SendMessage(hTextWnd, WM_SETFONT, (LPARAM)hFont, TRUE); + SendMessage(hTabWnd, WM_SETFONT, (LPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE); + + HWND hHeader = ListView_GetHeader(hGridWnd); + for (int colNo = 0; colNo < Header_GetItemCount(hHeader); colNo++) + SendMessage(GetDlgItem(hHeader, IDC_HEADER_EDIT + colNo), WM_SETFONT, (LPARAM)hFont, TRUE); + + SetProp(hWnd, TEXT("FONT"), hFont); + + PostMessage(hWnd, WMU_UPDATE_HIGHLIGHT, 0, 0); + PostMessage(hWnd, WMU_AUTO_COLUMN_SIZE, 0, 0); + } + break; + + } + return CallWindowProc((WNDPROC)GetProp(hWnd, TEXT("WNDPROC")), hWnd, msg, wParam, lParam); +} + +LRESULT CALLBACK cbNewFilterEdit(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { + WNDPROC cbDefault = (WNDPROC)GetProp(hWnd, TEXT("WNDPROC")); + + switch(msg){ + // Win10+ fix: draw an upper border + case WM_PAINT: { + cbDefault(hWnd, msg, wParam, lParam); + + RECT rc; + GetWindowRect(hWnd, &rc); + HDC hDC = GetWindowDC(hWnd); + HPEN hPen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_BTNFACE)); + HPEN oldPen = SelectObject(hDC, hPen); + MoveToEx(hDC, 1, 0, 0); + LineTo(hDC, rc.right - 1, 0); + SelectObject(hDC, oldPen); + DeleteObject(hPen); + ReleaseDC(hWnd, hDC); + + return 0; + } + break; + + // Prevent beep + case WM_CHAR: { + if (wParam == VK_RETURN || wParam == VK_ESCAPE || wParam == VK_TAB) + return 0; + } + break; + + case WM_KEYDOWN: { + if (wParam == VK_RETURN || wParam == VK_ESCAPE || wParam == VK_TAB) { + if (wParam == VK_RETURN) { + HWND hHeader = GetParent(hWnd); + HWND hGridWnd = GetParent(hHeader); + HWND hTabWnd = GetParent(hGridWnd); + HWND hMainWnd = GetParent(hTabWnd); + SendMessage(hMainWnd, WMU_UPDATE_RESULTSET, 0, 0); + } + + return 0; + } + } + break; + + case WM_DESTROY: { + RemoveProp(hWnd, TEXT("WNDPROC")); + } + break; + } + + return CallWindowProc(cbDefault, hWnd, msg, wParam, lParam); +} + +LRESULT CALLBACK cbNewText(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { + if (msg == EM_SETZOOM) + return 0; + + if (msg == WM_MOUSEWHEEL && LOWORD(wParam) == MK_CONTROL) { + HWND hTabWnd = GetParent(hWnd); + SendMessage(GetParent(hTabWnd), msg, wParam, lParam); + return 0; + } + + return CallWindowProc((WNDPROC)GetProp(hWnd, TEXT("WNDPROC")), hWnd, msg, wParam, lParam); +} + +HTREEITEM addNode(HWND hTreeWnd, HTREEITEM hParentItem, xml_element* node) { + if (!node) + return FALSE; + + HTREEITEM hItem = 0; + xml_element* subnode = node->first_child; + if (node->key) { + const char* value = node->value ? node->value : subnode && !subnode->key ? subnode->value : 0; + char* buf8 = calloc(strlen(node->key) + (value ? strlen(value) : 0) + 10, sizeof(char)); + if (node->key && strncmp(node->key, "![CDATA[", 8) == 0) { + sprintf(buf8, "#CDATA (%.2fKb)", strlen(node->key)/1024.0); + } else if (node->key && strncmp(node->key, "!--", 3) == 0) { + sprintf(buf8, "#COMMENT"); + } else if (!isEmpty(value)) { + sprintf(buf8, "%s = %s", node->key, value); + } else { + sprintf(buf8, "%s", node->key); + } + + TCHAR* name16 = utf8to16(buf8); + hItem = TreeView_AddItem(hTreeWnd, name16, hParentItem, (LPARAM)node); + free(name16); + free(buf8); + } + + BOOL hasSubnode = FALSE; + while (!hasSubnode && subnode) { + hasSubnode = subnode->key != NULL; + subnode = subnode->next; + } + if (hasSubnode) + TreeView_AddItem(hTreeWnd, LOADING, hItem, (LPARAM)subnode); + + return hItem; +} + +void highlightText (HWND hWnd, TCHAR* text) { + BOOL inTag = FALSE; + TCHAR q = 0; + BOOL isValue = FALSE; + + CHARFORMAT2 cf2 = {0}; + cf2.cbSize = sizeof(CHARFORMAT2) ; + cf2.dwMask = CFM_COLOR | CFM_BOLD; + + int len = _tcslen(text); + int pos = 0; + while (pos < len) { + int start = pos; + TCHAR c = text[pos]; + + cf2.crTextColor = RGB(0, 0, 0); + cf2.dwEffects = 0; + + if (c == TEXT('\'') || c == TEXT('"')) { + TCHAR* p = _tcschr(text + pos + 1, c); + if (p != NULL) { + pos = p - text; + cf2.crTextColor = RGB(0, 200, 0); + } + } else if (c == TEXT('<') && _tcsncmp(text + pos, TEXT("")); + if (p != NULL) { + pos = p - text + 2; + cf2.crTextColor = RGB(196, 196, 196); + } + } else if (c == TEXT('<') && _tcsncmp(text + pos, TEXT("")); + if (p != NULL) { + pos = p - text + 2; + cf2.crTextColor = RGB(126, 0, 0); + } + } else if (c == TEXT('<')) { + TCHAR* p = _tcspbrk(text + pos, TEXT(" \t\r\n>")); + if (p != NULL) { + pos = p - text; + cf2.crTextColor = RGB(0, 0, 128); + cf2.dwEffects = CFM_BOLD; + } + } else if (c == TEXT('>')) { + if (pos > 0 && text[pos - 1] == TEXT('/')) + start--; + + cf2.crTextColor = RGB(0, 0, 128); + cf2.dwEffects = CFM_BOLD; + } else if (pos > 0 && text[pos - 1] == TEXT('>')) { + TCHAR* p = _tcschr(text + pos + 1, TEXT('<')); + if (p != NULL) { + pos = p - text - 1; + cf2.crTextColor = RGB(255, 128, 0); + } + } + + SendMessage(hWnd, EM_SETSEL, start, pos + 1); + SendMessage(hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &cf2); + pos++; + } +} + +char* formatXML(const char* data) { + if (!data) + return 0; + + int len = strlen(data); + int bLen = len; + char* buf = calloc(bLen, sizeof(char)); + + BOOL inTag = FALSE; + BOOL isAttr = FALSE; + BOOL isValue = FALSE; + char quote = 0; + BOOL isOpenTag = FALSE; + + int bPos = 0; + int pos = 0; + int level = 0; + while (pos < len) { + char c = data[pos]; + char p = bPos > 0 ? buf[bPos - 1] : 0; + char n = data[pos + 1]; + + BOOL isSave = FALSE; + BOOL isSpace = FALSE; + BOOL isNun = strchr(" \t\r\n", c) != 0; + + if (1000 == 1000 && quote && c != quote) { + isSave = TRUE; + } else if (quote && c == quote) { + isSave = TRUE; + quote = 0; + } else if (c == '<') { + inTag = TRUE; + isSave = TRUE; + isValue = FALSE; + isOpenTag = n != '/'; + if (!isOpenTag) + level--; + } else if (inTag && c == '>') { + isSave = TRUE; + inTag = FALSE; + isAttr = FALSE; + isValue = FALSE; + if (p == '/' || p == '-') + level--; + quote = 0; + } else if (inTag && !isNun) { + isSave = TRUE; + isSpace = !isAttr && p != '<' && p != '/'; + isAttr = TRUE; + } else if (inTag && isNun) { + isAttr = FALSE; + } else if (!inTag && p == '>'){ + isValue = FALSE; + isSave = !isNun; + } else if (!inTag && !isValue && !isNun) { + isSave = TRUE; + isValue = TRUE; + } else if (!inTag && isValue && !isNun) { + isSave = TRUE; + } else if (!inTag && isValue && isNun) { + int n = strspn(data + pos, " \t\r\n"); + BOOL isEmptyTail = data[pos + n] == '<'; + if (isEmptyTail) { + isValue = FALSE; + } else { + isSave = TRUE; + } + } + + if (isSave && bPos > 0 && (c == '<' || p == '>')) { + buf[bPos] = '\n'; + bPos++; + + for (int l = 0; l < level; l++) { + buf[bPos] = '\t'; + bPos++; + } + } + + if (isSpace) { + buf[bPos] = ' '; + bPos++; + } + + if (isSave) { + buf[bPos] = data[pos]; + bPos++; + + if (bLen - bPos < 100) { + bLen += 32000; + buf = realloc(buf, bLen); + } + } + + if (c == '<' && isOpenTag) + level++; + + pos++; + } + buf[bPos] = 0; + + return buf; +} + +void setStoredValue(TCHAR* name, int value) { + TCHAR buf[128]; + _sntprintf(buf, 128, TEXT("%i"), value); + WritePrivateProfileString(APP_NAME, name, buf, iniPath); +} + +int getStoredValue(TCHAR* name, int defValue) { + TCHAR buf[128]; + return GetPrivateProfileString(APP_NAME, name, NULL, buf, 128, iniPath) ? _ttoi(buf) : defValue; +} + +int CALLBACK cbEnumTabStopChildren (HWND hWnd, LPARAM lParam) { + if (GetWindowLong(hWnd, GWL_STYLE) & WS_TABSTOP && IsWindowVisible(hWnd)) { + int no = 0; + HWND* wnds = (HWND*)lParam; + while (wnds[no]) + no++; + wnds[no] = hWnd; + } + + return TRUE; +} + +TCHAR* utf8to16(const char* in) { + TCHAR *out; + if (!in || strlen(in) == 0) { + out = (TCHAR*)calloc (1, sizeof (TCHAR)); + } else { + DWORD size = MultiByteToWideChar(CP_UTF8, 0, in, -1, NULL, 0); + out = (TCHAR*)calloc (size, sizeof (TCHAR)); + MultiByteToWideChar(CP_UTF8, 0, in, -1, out, size); + } + return out; +} + +char* utf16to8(const TCHAR* in) { + char* out; + if (!in || _tcslen(in) == 0) { + out = (char*)calloc (1, sizeof(char)); + } else { + int len = WideCharToMultiByte(CP_UTF8, 0, in, -1, NULL, 0, 0, 0); + out = (char*)calloc (len, sizeof(char)); + WideCharToMultiByte(CP_UTF8, 0, in, -1, out, len, 0, 0); + } + return out; +} + +// https://stackoverflow.com/a/25023604/6121703 +int detectCodePage(const unsigned char *data) { + return strncmp(data, "\xEF\xBB\xBF", 3) == 0 ? CP_UTF8 : // BOM + strncmp(data, "\xFE\xFF", 2) == 0 ? CP_UTF16BE : // BOM + strncmp(data, "\xFF\xFE", 2) == 0 ? CP_UTF16LE : // BOM + strncmp(data, "\x00\x3C", 2) == 0 ? CP_UTF16BE : // < + strncmp(data, "\x3C\x00", 2) == 0 ? CP_UTF16LE : // < + isUtf8(data) ? CP_UTF8 : + CP_ACP; +} + +void setClipboardText(const TCHAR* text) { + int len = (_tcslen(text) + 1) * sizeof(TCHAR); + HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, len); + memcpy(GlobalLock(hMem), text, len); + GlobalUnlock(hMem); + OpenClipboard(0); + EmptyClipboard(); + SetClipboardData(CF_UNICODETEXT, hMem); + CloseClipboard(); +} + +BOOL isNumber(const TCHAR* val) { + int len = _tcslen(val); + BOOL res = TRUE; + int pCount = 0; + for (int i = 0; res && i < len; i++) { + pCount += val[i] == TEXT('.'); + res = _istdigit(val[i]) || val[i] == TEXT('.'); + } + return res && pCount < 2; +} + +BOOL isEmpty (const char* s) { + BOOL res = TRUE; + for (int i = 0; s && res && i < strlen(s); i++) + res = !isgraph(s[i]); + + return res; +} + +// https://stackoverflow.com/a/1031773/6121703 +BOOL isUtf8(const char * string) { + if (!string) + return FALSE; + + const unsigned char * bytes = (const unsigned char *)string; + while (*bytes) { + if((bytes[0] == 0x09 || bytes[0] == 0x0A || bytes[0] == 0x0D || (0x20 <= bytes[0] && bytes[0] <= 0x7E))) { + bytes += 1; + continue; + } + + if (((0xC2 <= bytes[0] && bytes[0] <= 0xDF) && (0x80 <= bytes[1] && bytes[1] <= 0xBF))) { + bytes += 2; + continue; + } + + if ((bytes[0] == 0xE0 && (0xA0 <= bytes[1] && bytes[1] <= 0xBF) && (0x80 <= bytes[2] && bytes[2] <= 0xBF)) || + (((0xE1 <= bytes[0] && bytes[0] <= 0xEC) || bytes[0] == 0xEE || bytes[0] == 0xEF) && (0x80 <= bytes[1] && bytes[1] <= 0xBF) && (0x80 <= bytes[2] && bytes[2] <= 0xBF)) || + (bytes[0] == 0xED && (0x80 <= bytes[1] && bytes[1] <= 0x9F) && (0x80 <= bytes[2] && bytes[2] <= 0xBF)) + ) { + bytes += 3; + continue; + } + + if ((bytes[0] == 0xF0 && (0x90 <= bytes[1] && bytes[1] <= 0xBF) && (0x80 <= bytes[2] && bytes[2] <= 0xBF) && (0x80 <= bytes[3] && bytes[3] <= 0xBF)) || + ((0xF1 <= bytes[0] && bytes[0] <= 0xF3) && (0x80 <= bytes[1] && bytes[1] <= 0xBF) && (0x80 <= bytes[2] && bytes[2] <= 0xBF) && (0x80 <= bytes[3] && bytes[3] <= 0xBF)) || + (bytes[0] == 0xF4 && (0x80 <= bytes[1] && bytes[1] <= 0x8F) && (0x80 <= bytes[2] && bytes[2] <= 0xBF) && (0x80 <= bytes[3] && bytes[3] <= 0xBF)) + ) { + bytes += 4; + continue; + } + + return FALSE; + } + + return TRUE; +} + +HTREEITEM TreeView_AddItem (HWND hTreeWnd, TCHAR* caption, HTREEITEM parent, LPARAM lParam) { + TVITEM tvi = {0}; + TVINSERTSTRUCT tvins = {0}; + tvi.mask = TVIF_TEXT | TVIF_PARAM; + tvi.pszText = caption; + tvi.cchTextMax = _tcslen(caption) + 1; + tvi.lParam = lParam; + + tvins.item = tvi; + tvins.hInsertAfter = TVI_LAST; + tvins.hParent = parent; + return (HTREEITEM)SendMessage(hTreeWnd, TVM_INSERTITEM, 0, (LPARAM)(LPTVINSERTSTRUCT)&tvins); +}; + +int TreeView_GetItemText(HWND hTreeWnd, HTREEITEM hItem, TCHAR* buf, int maxLen) { + TV_ITEM tv = {0}; + tv.mask = TVIF_TEXT; + tv.hItem = hItem; + tv.cchTextMax = maxLen; + tv.pszText = buf; + return TreeView_GetItem(hTreeWnd, &tv); +} + +LPARAM TreeView_GetItemParam(HWND hTreeWnd, HTREEITEM hItem) { + TV_ITEM tv = {0}; + tv.mask = TVIF_PARAM; + tv.hItem = hItem; + + return TreeView_GetItem(hTreeWnd, &tv) ? tv.lParam : 0; +} + +int TreeView_SetItemText(HWND hTreeWnd, HTREEITEM hItem, TCHAR* text) { + TV_ITEM tv = {0}; + tv.mask = TVIF_TEXT; + tv.hItem = hItem; + tv.cchTextMax = _tcslen(text) + 1; + tv.pszText = text; + return TreeView_SetItem(hTreeWnd, &tv); +} + +int ListView_AddColumn(HWND hListWnd, TCHAR* colName) { + int colNo = Header_GetItemCount(ListView_GetHeader(hListWnd)); + LVCOLUMN lvc = {0}; + lvc.mask = LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; + lvc.iSubItem = colNo; + lvc.pszText = colName; + lvc.cchTextMax = _tcslen(colName) + 1; + lvc.cx = 100; + return ListView_InsertColumn(hListWnd, colNo, &lvc); +} + +int Header_GetItemText(HWND hWnd, int i, TCHAR* pszText, int cchTextMax) { + if (i < 0) + return FALSE; + + TCHAR* buf = calloc(cchTextMax + 1, sizeof(TCHAR)); + + HDITEM hdi = {0}; + hdi.mask = HDI_TEXT; + hdi.pszText = buf; + hdi.cchTextMax = cchTextMax; + int rc = Header_GetItem(hWnd, i, &hdi); + + _tcsncpy(pszText, buf, cchTextMax); + free(buf); + return rc; +} \ No newline at end of file diff --git a/xml.c b/xml.c new file mode 100644 index 0000000..2e81753 --- /dev/null +++ b/xml.c @@ -0,0 +1,818 @@ +#include +#include + +#include "xml.h" + +#define WHITESPACE " \t\r\n" + +#define TAG_ELEMENT_OPEN 1 +#define TAG_ELEMENT_CLOSE 2 +#define TAG_PI 3 +#define TAG_TYPE 4 +#define TAG_COMMENT 5 +#define TAG_CDATA 6 + +typedef struct xml_state { + /* the root element */ + struct xml_element *root; + + /* internal state variables */ + struct xml_element *current; + struct xml_tag_pattern *tag; + size_t length; + size_t cursor; + int empty; + const char* data; + const char *(*parser)(struct xml_state *, const char *); +} xml_state; + +struct xml_tag_pattern { + int type; + const char *open; + size_t open_len; + const char *close; +} xml_tag_patterns[] = { + {TAG_ELEMENT_OPEN, "<", 1, ">"}, + {TAG_ELEMENT_CLOSE, ""}, + {TAG_PI, ""}, + {TAG_TYPE, ""}, + {TAG_COMMENT, ""}, + {TAG_CDATA, ""}, + {0, 0, 0, 0} +}; + +/***************************************************************************** + * STRING OPERATIONS + ****************************************************************************/ + +/** + * Return length of quoted string respecting escaped characters + * + * @param s - first character after quote + * @param q - quote character + */ +static size_t xml_quotedspn(const char *s, char q) { + const char *first = s; + + while (*++s) { + if (*s == '\\') { + ++s; + continue; + } + + if (*s == q) { + return s - first; + } + } + + return 0; +} + +#ifdef WIN32 +/** + * Copies n bytes of given string + * + * @param s - string to copy + * @param n - number of bytes + */ +static char *strndup(const char *s, size_t n) { + char *r; + + if (!(r = calloc(n + 1, sizeof(char))) || + !memcpy(r, s, n)) { + return NULL; + } + + return r; +} +#endif + +/** + * Append string + * + * @param dest - address of string to append to + * @param dest_len - address of length of string + * @param src - string to append + * @param src_len - length of string to append + */ +static char *xml_string_append( + char **dest, + size_t *dest_len, + const char *src, + size_t src_len) { + char *n; + + if (src_len < 1) { + return *dest; + } + + if (!*dest) { + if ((*dest = strndup(src, src_len))) { + *dest_len += src_len; + } + } else if ((n = realloc(*dest, *dest_len + src_len + 1))) { + *dest = n; + n += *dest_len; + + *n = 0; + strncat(n, src, src_len); + + *dest_len += src_len; + } + + return *dest; +} + +/***************************************************************************** + * CREATING AND MODIFYING ELEMENTS + ****************************************************************************/ + +/** + * Add child to parent element + * + * @param p - parent element + * @param c - child element + */ +static void xml_element_add( + struct xml_element *p, + struct xml_element *c) { + c->parent = p; + + if (p->first_child) { + p->last_child->next = c; + p->last_child = c; + } else { + p->first_child = p->last_child = c; + } + + ++p->child_count; +} + +/** + * Create a new element + * + * @param parent - parent element + */ +static struct xml_element *xml_element_create(struct xml_element *parent, int offset) { + struct xml_element *e; + + if (!(e = calloc(1, sizeof(struct xml_element)))) { + return NULL; + } + e->offset = offset; + + if (parent) { + xml_element_add(parent, e); + } + + return e; +} + +/***************************************************************************** + * CREATING AND MODIFYING ATTRIBUTES + ****************************************************************************/ + +/** + * Add attribute to element + * + * @param e - element + * @param a - attribute + */ +static void xml_attribute_add( + struct xml_element *e, + struct xml_attribute *a) { + if (e->first_attribute) { + e->last_attribute->next = a; + e->last_attribute = a; + } else { + e->first_attribute = e->last_attribute = a; + } + + ++e->attribute_count; +} + +/** + * Create a new attribute + * + * @param parent - parent element + */ +static struct xml_attribute *xml_attribute_create( + struct xml_element *parent) { + struct xml_attribute *a; + + if (!(a = calloc(1, sizeof(struct xml_attribute)))) { + return NULL; + } + + if (parent) { + xml_attribute_add(parent, a); + } + + return a; +} + +/***************************************************************************** + * APPENDING KEY/VALUE + ****************************************************************************/ + +/** + * Append character data to current element + * + * @param st - state + * @param d - begin of data + * @param l - length of data + */ +static int xml_value_append(struct xml_state *st, const char *d, size_t l) { + int pos = d - st->data; + if (!st->length && + !(st->current = xml_element_create(st->current, pos))) { + return -1; + } + + if (!xml_string_append( + &st->current->value, + &st->length, + d, + l)) { + return -1; + } + + return 0; +} + +/** + * Append tag data to current element + * + * @param st - state + * @param d - begin of data + * @param l - length of data + */ +static int xml_key_append(struct xml_state *st, const char *d, size_t l) { + if (!st->current) { + return -1; + } + + /* ignore data for closing elements */ + if (st->tag->type == TAG_ELEMENT_CLOSE) { + return 0; + } + + /* append start of pattern for special tag types */ + if (!st->length && + st->tag->open_len > 1 && + !xml_string_append( + &st->current->key, + &st->length, + st->tag->open + 1, + st->tag->open_len - 1)) { + return -1; + } + + if (!xml_string_append( + &st->current->key, + &st->length, + d, + l)) { + return -1; + } + + return 0; +} + +/***************************************************************************** + * PARSING ATTRIBUTES + ****************************************************************************/ + +/** + * Parse attributes + * + * @param e - element + * @param from - first character after tag name + */ +static int xml_parse_attributes(struct xml_element *e, char *from) { + while (*from) { + struct xml_attribute *a; + size_t p; + char *key = NULL; + char *value = NULL; + size_t key_len = 0; + size_t value_len = 0; + + /* skip leading white space */ + from += strspn(from, WHITESPACE); + + /* search for first character that is not part of a name */ + p = strcspn(from, "="WHITESPACE); + + if (p < 1) { + break; + } + + key = from; + key_len = p; + + /* move after argument name */ + from += p; + + /* skip white space before next control character */ + from += strspn(from, WHITESPACE); + + if (*from == '=') { + char q = 0; + + /* move after '=' and skip leading white space */ + ++from; + from += strspn(from, WHITESPACE); + + if (!*from) { + break; + } else if (*from == '\'') { + p = xml_quotedspn(++from, (q = '\'')); + } else if (*from == '"') { + p = xml_quotedspn(++from, (q = '"')); + } else { + /* argument data is unqouted */ + p = strcspn(from, WHITESPACE); + } + + value = from; + value_len = p; + from += p; + + if (*from == q) { + ++from; + } + } + + if (!(a = xml_attribute_create(e))) { + return -1; + } + + if (key) { + *(key + key_len) = 0; + a->key = key; + } + + if (value) { + *(value + value_len) = 0; + a->value = value; + } + } + + return 0; +} + +/***************************************************************************** + * PARSING ELEMENTS + ****************************************************************************/ + +/* forward declarations */ +static const char *xml_parse_content(struct xml_state *, const char *); + +/** + * Close element + * + * @param st - state + */ +static void xml_close_element(struct xml_state *st, const char* d) { + st->current->length = d - st->data - st->current->offset; + st->current = st->current->parent; +} + +/** + * Close tag + * + * @param st - state + */ +static void xml_close_tag(struct xml_state *st) { + st->tag = NULL; + st->length = 0; + st->cursor = 0; + st->empty = 0; + st->parser = xml_parse_content; +} + +/** + * Parse end of tag name for empty element marker + * + * @param st - state + */ +static void xml_check_empty(struct xml_state *st) { + char *p = st->current->key + strlen(st->current->key) - 1; + + for (; p >= st->current->key; --p) { + /* ignore trailing white space; this isn't required + * by the spec but probably better to have */ + if (strchr(WHITESPACE, *p)) { + continue; + } + + if (*p == '/') { + *p = 0; + st->empty = 1; + } + + break; + } +} + +/** + * Parse tag name + * + * @param st - state + */ +static int xml_parse_tag_name(struct xml_state *st) { + char *p = st->current->key; + + xml_check_empty(st); + p += strcspn(p, WHITESPACE); + + if (!*p) { + /* key ends with the name */ + return 0; + } + + /* terminate name and skip further white space */ + *p++ = 0; + p += strspn(p, WHITESPACE); + + if (*p && xml_parse_attributes(st->current, p)) { + return -1; + } + + return 0; +} + +/** + * Parse tag until terminating pattern + * + * @param st - state + * @param d - XML data + */ +static const char *xml_parse_tag_body(struct xml_state *st, const char *d) { + while (*d) { + const char *m = NULL; + + if (!st->cursor) { + /* find first character of terminating pattern */ + m = strchr(d, st->tag->close[0]); + } else { + /* find next character of terminating pattern */ + for (;;) { + if (st->tag->close[st->cursor] == *d) { + m = d; + } + + /* break if next character does match or cursor + * is at the first character */ + if (m || !st->cursor) { + break; + } + + /* append leading part of pattern since it + * is data if d doesn't match */ + if (xml_key_append(st, st->tag->close, st->cursor)) { + return NULL; + } + + /* if cursor has already moved into a pattern + * but d doesn't match, d may still match the + * beginning of a pattern */ + st->cursor = 0; + } + + /* start over if no match was found and cursor + * is at first character of the pattern */ + if (!m && !st->cursor) { + continue; + } + } + + if (m) { + /* put data until this match into tag name */ + if (!st->cursor && xml_key_append(st, d, m - d)) { + return NULL; + } + + d = ++m; + ++st->cursor; + + /* terminating pattern complete */ + if (!st->tag->close[st->cursor]) { + /* append termination pattern for special tag types */ + if (st->cursor > 1 && + !xml_string_append( + &st->current->key, + &st->length, + st->tag->close, + st->cursor - 1)) { + return NULL; + } + + if (st->tag->type == TAG_ELEMENT_OPEN && + xml_parse_tag_name(st)) { + return NULL; + } + + if (st->tag->type != TAG_ELEMENT_OPEN || + st->empty) { + xml_close_element(st, d); + } + + xml_close_tag(st); + break; + } + } else { + /* append all the rest */ + size_t l = strlen(d); + + if (xml_key_append(st, d, l)) { + return NULL; + } + + /* move to end of data */ + d += l; + + break; + } + } + + return d; +} + +/** + * Parse beginning of a tag to determine its type (and terminating pattern) + * + * @param st - state + * @param d - XML data + */ +static const char *xml_parse_tag_opening(struct xml_state *st, + const char *d) { + for (; *d; ++d) { + struct xml_tag_pattern *p = xml_tag_patterns; + + /* check character against all opening patterns */ + for (;;) { + for (; p->type; ++p) { + if (p->open_len > st->cursor && + p->open[st->cursor] == *d) { + break; + } + } + + /* break if pattern is found or cursor is at + * first character */ + if (p->type || !st->cursor) { + break; + } + + /* if cursor has already moved into a pattern but + * d doesn't match, d may still match the beginning + * of a pattern */ + st->cursor = 0; + } + + /* if character doesn't match any pattern anymore + * take the latest matching pattern */ + if (!p->type) { + if (!st->tag) { + return NULL; + } + + if (st->length > 0) { + xml_close_element(st, d); + } + + st->length = 0; + st->cursor = 0; + st->parser = xml_parse_tag_body; + + /* create child element */ + int offset = d - st->data - st->tag->open_len; + if (st->tag->type != TAG_ELEMENT_CLOSE && + !(st->current = xml_element_create(st->current, offset))) { + return NULL; + } + + break; + } + + ++st->cursor; + st->tag = p; + } + + return d; +} + +/** + * Parse character data of active element + * + * @param st - state + * @param d - XML data + */ +static const char *xml_parse_content(struct xml_state *st, const char *d) { + const char *end = d + strcspn(d, "<"); + + if (end > d && xml_value_append(st, d, end - d)) { + return NULL; + } + + if (*end == '<') { + st->parser = xml_parse_tag_opening; + } + + return end; +} + +/** + * Parse (next) chunk of a XML document + * + * @param st - parsing status + * @param d - XML chunk + */ +int xml_parse_chunk(struct xml_state *st, const char *d) { + if (!d) { + return -1; + } + + if (!st->root) { + st->current = st->root = xml_element_create(NULL, 0); + } + + if (!st->parser) { + xml_close_tag(st); + } + + while (*d) { + if (!(d = st->parser(st, d))) { + return -1; + } + } + + return 0; +} + +/** + * Parse XML document + * + * @param data - XML string + */ +struct xml_element *xml_parse(const char *data) { + struct xml_state st; + + memset(&st, 0, sizeof(st)); + st.data = data; + + if (!xml_parse_chunk(&st, data) && st.root) { + return st.root; + } + + return NULL; +} + +/***************************************************************************** + * FREE MEMORY + ****************************************************************************/ + +/** + * Free XML element tree + * + * @param e - root element + */ +void xml_free(struct xml_element *e) { + struct xml_element *c, *n; + + if (!e) { + return; + } + + for (c = e->first_child; c; c = n) { + n = c->next; + xml_free(c); + } + + /* free attributes */ + { + struct xml_attribute *a, *na; + + for (a = e->first_attribute; a; a = na) { + na = a->next; + + /* don't free key/value because they're + * just pointers into e->key */ + free(a); + } + } + + free(e->key); + free(e->value); + free(e); +} + +/***************************************************************************** + * CHILD ELEMENT LOCATION + ****************************************************************************/ + +/** + * Returns child element by key + * + * @param e - first attribute + * @param key - attribute key + */ +struct xml_element *xml_find_element( + struct xml_element *e, + const char *key) { + xml_element* s = e ? e->first_child : NULL; + for (; s; s = s->next) { + if (!strcasecmp(s->key, key)) { + return s; + } + } + + return NULL; +} + +/***************************************************************************** + * ATTRIBUTE LOCATION + ****************************************************************************/ + +/** + * Returns attribute by key + * + * @param a - first attribute + * @param key - attribute key + */ +struct xml_attribute *xml_find_attribute( + struct xml_element *e, + const char *key) { + xml_attribute* a = e ? e->first_attribute : NULL; + for (; a; a = a->next) { + if (!strcasecmp(a->key, key)) { + return a; + } + } + + return NULL; +} + +/***************************************************************************** + * CONTENT CONCATENATION + ****************************************************************************/ + +/** + * Calculate size of all child values + * + * @param e - element + */ +static size_t xml_content_len(struct xml_element *e) { + size_t s = 0; + + for (e = e->first_child; e; e = e->next) { + if (e->value) { + s += strlen(e->value); + } else { + s += xml_content_len(e); + } + } + + return s; +} + +/** + * Copy elements into pre-calculated buffer + * + * @param e - element + * @param t - target + */ +static void xml_content_cpy(struct xml_element *e, char **t) { + for (e = e->first_child; e; e = e->next) { + if (e->value) { + strcpy(*t, e->value); + *t += strlen(e->value); + } else { + xml_content_cpy(e, t); + } + } +} + +/** + * Return concatenated content of element and all of its children + * + * @param e - element + */ +char *xml_content(struct xml_element *e) { + size_t l; + char *s; + char *t; + + if (!e || + (l = xml_content_len(e)) < 1 || + !(s = malloc(++l))) { + return NULL; + } + + t = s; + xml_content_cpy(e, &t); + + return s; +} diff --git a/xml.h b/xml.h new file mode 100644 index 0000000..36dbf45 --- /dev/null +++ b/xml.h @@ -0,0 +1,59 @@ +// Based on https://github.com/markusfisch/libxml +#ifndef _xml_h_ +#define _xml_h_ + +typedef struct xml_attribute { + /* Argument name */ + char *key; + + /* Argument value */ + char *value; + + /* Pointer to next argument. May be NULL. */ + struct xml_attribute *next; +} xml_attribute; + +typedef struct xml_element { + /* The tag name if this is a tag element or NULL if this + * element represents character data. */ + char *key; + + /* Character data segment of parent XML element or NULL if + * this is a tag element. "key" and "value" are exclusive + * because character data is treated as "nameless" child + * element. So each xml_element can have either a "key" + * or a "value" but not both. */ + char *value; + + /* Parent element. May be NULL. */ + struct xml_element *parent; + + /* First child element. May be NULL. */ + struct xml_element *first_child; + + /* Last child element. May be NULL. */ + struct xml_element *last_child; + + /* Pointer to next sibling. May be NULL. */ + struct xml_element *next; + + /* Added: child count, XML offset and XML length */ + int child_count; + int offset; + int length; + + /* First and last attribute. Both may be NULL. */ + xml_attribute *first_attribute, *last_attribute; + + int attribute_count; +} xml_element; + +struct xml_element *xml_parse(const char *); +void xml_free(struct xml_element *); + +struct xml_element *xml_find_element(struct xml_element*, const char*); +struct xml_attribute *xml_find_attribute(struct xml_element*, const char*); + +char *xml_content(struct xml_element *); + +#endif diff --git a/xmltab.png b/xmltab.png new file mode 100644 index 0000000..c4a922d Binary files /dev/null and b/xmltab.png differ