-
Posts
282 -
Joined
-
Last visited
-
Days Won
10
Content Type
Forums
Downloads
Forum Articles
Events
Everything posted by AspirinJunkie
-
There is a new version. This brings a significant performance increase. In my tests, _JSON_Parse() is now around 40–55% faster, and for _JSON_Generate(), the increase is around 15–35%. I didn't thought there was still such potential in the code. However, I still had a few ideas in mind that required quite a bit of restructuring, but I've now tackled them accordingly. In my opinion, it was worth it. In the process, I removed ByRef everywhere, so that direct value transfer is now possible for all functions (which was a request). I also added notes on how to handle .json files to the Readme.md (@WildByDesign: I hope that was what you meant?).
-
Yes, you can't know everything from the start. Either you assume that a certain functionality already exists - then you explicitly search for it. Or you implement the functionality yourself. You have done the latter and that shows that you have understood what is necessary and how to implement it. I don't care in principle. However, I think it takes me longer with Github to see that something is there. In addition, only problems and feature requests should really be posted there and not a general discussion about how to deal with the UDF. Ok - I just saw that the JSON-Test.au3 is still in German. I could adapt that at the same time. But I still don't quite know what is meant by "Write in the JSON file". How to create the JSON string is already shown in the example. Basically, all you need is a FileWrite and you're done. So perhaps I haven't quite understood what exactly needs to be added.
-
If you then want to write to the theme with _JSON_addChangeDelete() (which could make sense due to the nested structure), then you basically only need to determine the first free array index. Since you have an AutoIt data structure after _JSON_Parse(), you basically only need to make a Ubound to the themes array: #include "JSON.au3" ; your input JSON string Global $sJSONRAW = '{"workbench.startupEditor":"none","breadcrumbs.enabled":false,"window.controlsStyle":"custom","workbench.activityBar.location":"hidden","window.customTitleBarVisibility":"auto","window.titleBarStyle":"custom","workbench.colorCustomizations":{"editor.background":"#00000060","terminal.background":"#00000060"},"theme":"Existing Theme 1","themes":[{"name":"Existing Theme 1","tab":{"background":"#00000080","unfocusedBackground":"#00000000"},"tabRow":{"background":"#00000000","unfocusedBackground":"#00000000"},"window":{"applicationTheme":"light","useMica":true}},{"name":"Existing Theme 2","tab":{"background":"#00000080","unfocusedBackground":"#00000000"},"tabRow":{"background":"#00000000","unfocusedBackground":"#00000000"},"window":{"applicationTheme":"light","useMica":true}},{"name":"Existing Theme 3","tab":{"background":"#00000080","unfocusedBackground":"#00000000"},"tabRow":{"background":"#00000000","unfocusedBackground":"#00000000"},"window":{"applicationTheme":"light","useMica":true}},{"name":"Existing Theme 4","tab":{"background":"#00000080","unfocusedBackground":"#00000000"},"tabRow":{"background":"#00000000","unfocusedBackground":"#00000000"},"window":{"applicationTheme":"light","useMica":true}}]}' ; parse JSON string into nested AutoIt data structure: Global $vJSON = _JSON_Parse($sJSONRAW) If @error Then Exit MsgBox(0,"Erro", "Error " & @error & " during parsing the JSON string") ; print to console to check the structure of the input JSON string ConsoleWrite("------- Input --------- " & @CRLF & _JSON_Generate($vJSON) & @CRLF & @CRLF) ; determine the number of elements already in the array "themes" Global $iNewIdx = UBound($vJSON["themes"]) ; add the new theme at the first free index: _JSON_addChangeDelete($vJSON, "theme", "Default Theme Test") _JSON_addChangeDelete($vJSON, "themes[" & $iNewIdx & "].name", "Default Theme Test") _JSON_addChangeDelete($vJSON, "themes[" & $iNewIdx & "].tab.background", "#00000020") _JSON_addChangeDelete($vJSON, "themes[" & $iNewIdx & "].tab.unfocusedBackground", "#00000000") _JSON_addChangeDelete($vJSON, "themes[" & $iNewIdx & "].tabRow.background", "#00000000") _JSON_addChangeDelete($vJSON, "themes[" & $iNewIdx & "].tabRow.unfocusedBackground", "#00000000") _JSON_addChangeDelete($vJSON, "themes[" & $iNewIdx & "].window.applicationTheme", "dark") _JSON_addChangeDelete($vJSON, "themes[" & $iNewIdx & "].window.useMica", true) ; create JSON string out of the AutoIt datastructure and print it to the console ConsoleWrite("------- Output --------- " & @CRLF & _JSON_Generate($vJSON) & @CRLF & @CRLF) Exactly - after _JSON_Parse() the whole thing no longer has anything to do with JSON but you have completely normal AutoIt data structures. However, this data is heavily nested. In AutoIt you can easily read information from nested data structures. But changing them is extremely cumbersome. To simplify exactly this, there is the _JSON_addChangeDelete() which (contrary to its name) has nothing to do with JSON but takes care of nested AutoIt data structures. The "cleaner" way mentioned is therefore already the way that _JSON_addChangeDelete() takes, as it changes the data at AutoIt level - just with a simpler interface than building the nested array element manually. Btw:, here's a little trick: With structures as complex as your theme description, you don't have to add everything manually. If you already know the structure beforehand, then simply describe it directly in JSON, have the theme element created completely from it and add it as a whole. In other words, you can replace the entire _JSON_addChangeDelete() in the example with a single one (similar to the jq example above): _JSON_addChangeDelete($vJSON, "themes[" & $iNewIdx & "]", _JSON_Parse('{"name":"Default Theme Test","tab":{"background":"#00000020","unfocusedBackground":"#00000000"},"tabRow":{"background":"#00000000","unfocusedBackground":"#00000000"},"window":{"applicationTheme":"dark","useMica":true}}'))
-
Help with adding to JSON file
AspirinJunkie replied to WildByDesign's topic in AutoIt General Help and Support
All you have to do is write the generated string to a file and tell the output function to use UTF-8 as the encoding. You can specify the encoding manually with FileOpen(), but if you address FileWrite() directly with a file name, it will automatically write the passed string to the file encoded as UTF-8 (To overwrite, delete the file before FileWrite with FileDelete). The UDF cannot handle comments in JSON because they are not standard-compliant. You already get an error when parsing with _JSON_Parse(). Therefore, there are also no comments that can be adopted in any way, so that handling these files and the UDF is therefore not intended. Perhaps a few words for basic understanding: JSON only describes how data and its hierarchy can be represented as a string. It is therefore well suited as an exchange format between different systems, as the basic data types and structures used in JSON are found in the vast majority of programming languages. It is also easy to read by humans. When working with JSON, however, this means that you only have to deal with it twice: once to convert a JSON string into the data structure of your program (_JSON_Parse) and a second time to generate a JSON string from the data structures of your program (_JSON_Generate). Everything in between (changing/adding/deleting data etc.) basically has nothing to do with JSON but simply data manipulation within the data structures in your respective program. -
Help with adding to JSON file
AspirinJunkie replied to WildByDesign's topic in AutoIt General Help and Support
If by chance my UDF was included - I would implement it like this: #include "JSON.au3" ; your input JSON string Global $sJSONRAW = '{"workbench.startupEditor": "none","breadcrumbs.enabled": false,"window.controlsStyle": "custom","workbench.activityBar.location": "hidden"}' ; parse JSON string into nested AutoIt data structure: Global $vJSON = _JSON_Parse($sJSONRAW) If @error Then Exit MsgBox(0,"Erro", "Error " & @error & " during parsing the JSON string") ; print to console to check the structure of the input JSON string ConsoleWrite("------- Input --------- " & @CRLF & _JSON_Generate($vJSON) & @CRLF & @CRLF) ; add (or change if they already exists) the desired values ; for these values you can directly use the outer map because $vJSON is the outer map $vJSON["window.customTitleBarVisibility"] = "auto" $vJSON["window.titleBarStyle"] = "custom" ; for inner nested elements you can use _JSON_addChangeDelete() instead (points must be escaped) _JSON_addChangeDelete($vJSON, "workbench\.colorCustomizations.editor\.background", "#00000060") _JSON_addChangeDelete($vJSON, "workbench\.colorCustomizations.terminal\.background", "#00000060") ; create JSON string out of the AutoIt datastructure and print it to the console ConsoleWrite("------- Output --------- " & @CRLF & _JSON_Generate($vJSON) & @CRLF & @CRLF) -
Autocomplete selector for existing tags
AspirinJunkie replied to ioa747's topic in AutoIt Example Scripts
I might have a suggestion for an extension: Currently, the user must not make a single typing error for the suggestion to be displayed as such. To be more generous, you could use a fuzzy comparison. I have implemented this accordingly in your script and used >>this UDF<< for the fuzzy search. The script now looks like this: #include <GUIConstantsEx.au3> #include <EditConstants.au3> #include <WindowsConstants.au3> #include <Array.au3> #include <WinAPI.au3> #include <GuiListBox.au3> #include "FuzzyString.au3" ; Global Variables Global $g_hMainGUI Global $g_idEditInputTags Global $g_idListboxSuggestions Global $g_hListboxSuggestionsHWND Global $g_aAvailableTags Global $g_iCurrentSuggestionIndex = -1 ; Dummy control IDs for accelerators (Listbox Navigation Key) Global $g_idAccelUp, $g_idAccelDown, $g_idAccelEnter, $g_idAccelEscape ; Load all unique tags from database $g_aAvailableTags = _GetAllUniqueTags() If @error Then MsgBox(16, "Error", "Failed to load unique tags from database.") _CreateGUI() ; Create Main GUI Exit ;--------------------------------------------------------------------------------------- Func _GetAllUniqueTags() ; This function should retrieve your actual unique tags from a database or file. ; For demonstration, it reads words from a text file. Local $sString = StringLower(FileRead("C:\Program Files (x86)\AutoIt3\SciTE\SciTE Jump\License.txt")) ; matches sequences of letters that are at least 4 characters long Local $aWords = StringRegExp($sString, '[a-zA-Z]{4,}', 3) $aWords = _ArrayUnique($aWords, 0, 0, 1, 0) _ArraySort($aWords) Return $aWords EndFunc ;==>_GetAllUniqueTags ;--------------------------------------------------------------------------------------- Func _CreateGUI() $g_hMainGUI = GUICreate("Main GUI", 700, 300) GUICtrlCreateLabel("Title:", 10, 20, 50, 20) Local $idTitle = GUICtrlCreateInput("", 70, 20, 600, 25) GUICtrlCreateLabel("Tags:", 10, 60, 50, 20) $g_idEditInputTags = GUICtrlCreateInput("", 70, 60, 600, 25, $ES_AUTOVSCROLL) GUICtrlSetLimit($g_idEditInputTags, 250) Local $aPos = ControlGetPos($g_hMainGUI, "", $g_idEditInputTags) If @error Then Exit MsgBox(16, "Error", "Failed to get position of Tags input control. Exiting.") ; Suggestions Listbox $g_idListboxSuggestions = GUICtrlCreateList("", $aPos[0], $aPos[1] + $aPos[3], $aPos[2], 150, BitOR($LBS_NOTIFY, $WS_VSCROLL, $WS_BORDER, $WS_TABSTOP)) GUICtrlSetState($g_idListboxSuggestions, $GUI_HIDE) $g_hListboxSuggestionsHWND = GUICtrlGetHandle($g_idListboxSuggestions) GUIRegisterMsg($WM_COMMAND, "_WM_COMMAND_Handler") GUIRegisterMsg($WM_MOUSEWHEEL, "_WM_MOUSEWHEEL_Handler") $g_idAccelUp = GUICtrlCreateDummy() $g_idAccelDown = GUICtrlCreateDummy() $g_idAccelEnter = GUICtrlCreateDummy() $g_idAccelEscape = GUICtrlCreateDummy() Local $AccelKeys[4][2] = [ _ ["{UP}", $g_idAccelUp], _ ["{DOWN}", $g_idAccelDown], _ ["{ENTER}", $g_idAccelEnter], _ ["{ESCAPE}", $g_idAccelEscape] _ ] GUISetAccelerators($AccelKeys) Local $idExit = GUICtrlCreateButton("Exit", 300, 250, 100, 30) GUISetState(@SW_SHOW) Local $iMsg While 1 $iMsg = GUIGetMsg() Switch $iMsg Case $GUI_EVENT_CLOSE, $idExit ExitLoop Case $g_idAccelUp _ListboxNavigationKey($g_idAccelUp) Case $g_idAccelDown _ListboxNavigationKey($g_idAccelDown) Case $g_idAccelEnter _ListboxNavigationKey($g_idAccelEnter) Case $g_idAccelEscape _ListboxNavigationKey($g_idAccelEscape) EndSwitch WEnd GUIDelete($g_hMainGUI) EndFunc ;==>_CreateGUI ;--------------------------------------------------------------------------------------- Func _ApplySelectedTag($sSelectedTag) If $sSelectedTag = "" Then Return Local $sCurrentTags = GUICtrlRead($g_idEditInputTags) If @error Then Return Local $iLastCommaPos = StringInStr($sCurrentTags, ",", 0, -1) ; Find the last comma Local $sPrefixToKeep = "" If $iLastCommaPos > 0 Then $sPrefixToKeep = StringLeft($sCurrentTags, $iLastCommaPos) $sPrefixToKeep = StringStripWS($sPrefixToKeep, $STR_STRIPTRAILING) & " " EndIf Local $sNewTags = $sPrefixToKeep & $sSelectedTag & ", " GUICtrlSetData($g_idEditInputTags, $sNewTags) If @error Then Return _HideSuggestions() GUICtrlSetState($g_idEditInputTags, $GUI_FOCUS) If @error Then Return _WinAPI_PostMessage(GUICtrlGetHandle($g_idEditInputTags), $EM_SETSEL, StringLen($sNewTags), StringLen($sNewTags)) $g_iCurrentSuggestionIndex = -1 EndFunc ;==>_ApplySelectedTag ;--------------------------------------------------------------------------------------- Func _ShowSuggestions($sCurrentInput) Local $sTrimmedInput = StringStripWS($sCurrentInput, $STR_STRIPLEADING + $STR_STRIPTRAILING) If $sTrimmedInput = "" Then Return _HideSuggestions() Local $aInputParts = StringSplit($sTrimmedInput, ",", $STR_NOCOUNT) Local $sLastPart = "" If UBound($aInputParts) > 0 Then $sLastPart = StringStripWS($aInputParts[UBound($aInputParts) - 1], $STR_STRIPLEADING + $STR_STRIPTRAILING) Else $sLastPart = $sTrimmedInput EndIf If $sLastPart = "" Then Return _HideSuggestions() Local $aFilteredSuggestions = _FS_ArraySearchFuzzy($g_aAvailableTags, $sLastPart, 1, __LS_UntilLength) $aFilteredSuggestions = _ArrayExtract($aFilteredSuggestions, -1, -1, 0, 0) $iCount = UBound($aFilteredSuggestions) If $iCount > 0 Then GUICtrlSendMsg($g_idListboxSuggestions, $LB_RESETCONTENT, 0, 0) For $sSuggestion In $aFilteredSuggestions If $sSuggestion <> "" Then GUICtrlSendMsg($g_idListboxSuggestions, $LB_ADDSTRING, 0, $sSuggestion) EndIf Next GUICtrlSetState($g_idListboxSuggestions, $GUI_SHOW) GUICtrlSendMsg($g_idListboxSuggestions, $LB_SETCURSEL, 0, 0) GUICtrlSendMsg($g_idListboxSuggestions, $LB_SETTOPINDEX, 0, 0) $g_iCurrentSuggestionIndex = 0 Else _HideSuggestions() EndIf EndFunc ;==>_ShowSuggestions ;--------------------------------------------------------------------------------------- Func _HideSuggestions() GUICtrlSetState($g_idListboxSuggestions, $GUI_HIDE) GUICtrlSendMsg($g_idListboxSuggestions, $LB_RESETCONTENT, 0, 0) $g_iCurrentSuggestionIndex = -1 EndFunc ;==>_HideSuggestions ;--------------------------------------------------------------------------------------- Func _ListboxNavigationKey($idKey) If Not BitAND(GUICtrlGetState($g_idListboxSuggestions), $GUI_SHOW) Then Return Local $iCount = GUICtrlSendMsg($g_idListboxSuggestions, $LB_GETCOUNT, 0, 0) If $iCount = 0 Then Return Switch $idKey Case $g_idAccelUp If $g_iCurrentSuggestionIndex <= 0 Then $g_iCurrentSuggestionIndex = $iCount - 1 Else $g_iCurrentSuggestionIndex -= 1 EndIf GUICtrlSendMsg($g_idListboxSuggestions, $LB_SETCURSEL, $g_iCurrentSuggestionIndex, 0) GUICtrlSendMsg($g_idListboxSuggestions, $LB_SETTOPINDEX, $g_iCurrentSuggestionIndex, 0) Case $g_idAccelDown If $g_iCurrentSuggestionIndex >= $iCount - 1 Then $g_iCurrentSuggestionIndex = 0 Else $g_iCurrentSuggestionIndex += 1 EndIf GUICtrlSendMsg($g_idListboxSuggestions, $LB_SETCURSEL, $g_iCurrentSuggestionIndex, 0) GUICtrlSendMsg($g_idListboxSuggestions, $LB_SETTOPINDEX, $g_iCurrentSuggestionIndex, 0) Case $g_idAccelEnter Local $iIndex = GUICtrlSendMsg($g_idListboxSuggestions, $LB_GETCURSEL, 0, 0) If $iIndex <> $LB_ERR Then Local $sSelectedTag = _GUICtrlListBox_GetText($g_idListboxSuggestions, $iIndex) If $sSelectedTag <> "" Then _ApplySelectedTag($sSelectedTag) EndIf Else _HideSuggestions() EndIf $g_iCurrentSuggestionIndex = -1 Case $g_idAccelEscape _HideSuggestions() $g_iCurrentSuggestionIndex = -1 EndSwitch EndFunc ;==>_ListboxNavigationKey ;--------------------------------------------------------------------------------------- Func _WM_COMMAND_Handler($hWnd, $iMsg, $wParam, $lParam) Local $iControlID = _WinAPI_LoWord($wParam) Local $iNotificationCode = _WinAPI_HiWord($wParam) Switch $iMsg Case $WM_COMMAND Switch $iControlID Case $g_idEditInputTags Switch $iNotificationCode Case $EN_CHANGE Local $sCurrentInput = GUICtrlRead($g_idEditInputTags) _ShowSuggestions($sCurrentInput) Case $EN_KILLFOCUS Sleep(50) Local $hFocusedWindow = _WinAPI_GetFocus() Local $hListboxSuggestionsHandle = GUICtrlGetHandle($g_idListboxSuggestions) Local $hEditInputTagsHandle = GUICtrlGetHandle($g_idEditInputTags) If $hFocusedWindow <> $hListboxSuggestionsHandle And $hFocusedWindow <> $hEditInputTagsHandle Then _HideSuggestions() Local $sCurrentTags = GUICtrlRead($g_idEditInputTags) If StringRight($sCurrentTags, 2) = ", " Then GUICtrlSetData($g_idEditInputTags, StringTrimRight($sCurrentTags, 2)) ElseIf StringRight($sCurrentTags, 1) = "," Then GUICtrlSetData($g_idEditInputTags, StringTrimRight($sCurrentTags, 1)) EndIf EndIf EndSwitch Case $g_idListboxSuggestions Switch $iNotificationCode Case $LBN_SELCHANGE Local $iIndex = GUICtrlSendMsg($g_idListboxSuggestions, $LB_GETCURSEL, 0, 0) If $iIndex <> $LB_ERR Then Local $sSelectedTag = _GUICtrlListBox_GetText($g_idListboxSuggestions, $iIndex) If $sSelectedTag <> "" Then _ApplySelectedTag($sSelectedTag) EndIf EndIf Case $LBN_DBLCLK Local $iIndex = GUICtrlSendMsg($g_idListboxSuggestions, $LB_GETCURSEL, 0, 0) If $iIndex <> $LB_ERR Then Local $sSelectedTag = _GUICtrlListBox_GetText($g_idListboxSuggestions, $iIndex) If $sSelectedTag <> "" Then _ApplySelectedTag($sSelectedTag) EndIf EndIf EndSwitch EndSwitch EndSwitch Return $GUI_RUNDEFMSG EndFunc ;==>_WM_COMMAND_Handler ;--------------------------------------------------------------------------------------- Func _WM_MOUSEWHEEL_Handler($hWnd, $iMsg, $wParam, $lParam) ; Using GUIGetCursorInfo to find the control under the mouse Local $aCursorInfo = GUIGetCursorInfo($g_hMainGUI) ; Check if the mouse is over the suggestions ListBox and if the ListBox is visible If Not @error And IsArray($aCursorInfo) And UBound($aCursorInfo) >= 5 And $aCursorInfo[4] = $g_idListboxSuggestions And BitAND(GUICtrlGetState($g_idListboxSuggestions), $GUI_SHOW) Then Local $iDelta = _WinAPI_HiWord($wParam) ; How much the wheel turned (120 for a "click" up, -120 for a "click" down) Local $iNumLinesToScroll = 1 ; We scroll one line at a time with the wheel Local $iCurrentSelection = GUICtrlSendMsg($g_idListboxSuggestions, $LB_GETCURSEL, 0, 0) Local $iTotalItems = GUICtrlSendMsg($g_idListboxSuggestions, $LB_GETCOUNT, 0, 0) If $iTotalItems <= 0 Then Return 1 ; No items, nothing to scroll or select If $iDelta > 0 Then ; Scroll Up (wheel up) If $iCurrentSelection > 0 Then $g_iCurrentSuggestionIndex = $iCurrentSelection - $iNumLinesToScroll If $g_iCurrentSuggestionIndex < 0 Then $g_iCurrentSuggestionIndex = 0 Else $g_iCurrentSuggestionIndex = 0 ; Already at the top EndIf Else ; Scroll Down (wheel down) If $iCurrentSelection < $iTotalItems - 1 Then $g_iCurrentSuggestionIndex = $iCurrentSelection + $iNumLinesToScroll If $g_iCurrentSuggestionIndex >= $iTotalItems Then $g_iCurrentSuggestionIndex = $iTotalItems - 1 Else $g_iCurrentSuggestionIndex = $iTotalItems - 1 ; Already at the bottom EndIf EndIf ; We define the new option GUICtrlSendMsg($g_idListboxSuggestions, $LB_SETCURSEL, $g_iCurrentSuggestionIndex, 0) ; Also, make the selected element visible (if it isn't already) GUICtrlSendMsg($g_idListboxSuggestions, $LB_SETTOPINDEX, $g_iCurrentSuggestionIndex, 0) Return 1 ; Message has been handled EndIf Return $GUI_RUNDEFMSG ; Let AutoIt handle the message for other controls EndFunc ;==>_WM_MOUSEWHEEL_Handler ; comparison function for the fuzzy comparison, which only compares up to the same length and introduces limit values for short strings. Func __LS_UntilLength($sA, $sB, $iMax = Default) Local $iLenA = StringLen($sA) Local $iLenB = StringLen($sB) Local $iLenMin = $iLenA > $iLenB ? $iLenB : $iLenA Local $iLenShort = $iLenMin > 1 ? 2 : $iLenMin Local $aLS[2] ; short exit for performance boost If StringLeft($sA, $iLenShort) <> StringLeft($sB, $iLenShort) Then $aLS[0] = $iMax + 1 Return $aLS EndIf $aLS = _FS_Hamming(StringLeft($sA, $iLenMin), StringLeft($sB, $iLenMin), $iMax) If @error Then Return SetError(@error, @extended) If $aLS[0] > 0 And $aLS[0] > ($iLenMin - 2) Then $aLS[0] = $iMax + 1 Return $aLS EndFunc Enter something like "abose" and you'll see what I mean. This does not come from StringRegExp but from _ArrayUnique. And this behavior can be prevented directly when it is called via _ArrayUnique($aWords, 0, 0, 1, 0). -
Ok, so let's just say that the function is not “broken” after all, does what it is supposed to do and does it correctly without any known errors? It has always been the responsibility of the application code to use their respective dependencies correctly. Especially as non-public interfaces are used here, which were never intended to be used by the user. The _JSON_Generate() function is intended to format a string in a JSON-compliant manner as a user. Its behavior has not changed over time. A change would have no effect on the described functionality of the UDF, which is why there is no need for action from my point of view.
-
It may be obvious to you, but it still isn't to me. You have now written the function again but still haven't explained which input values cause it to fail. I have fed several test strings to it and all came back as expected. That's why I'm asking again: For which specific data does the function not return the desired result? About the byref/return story: In old versions, the function was designed to process the variable directly (a "procedure"). However, since you cannot include the function call in string definitions, this was later changed to a return the target value (a "function"). This has simplified the code that uses this function. The way in which the function must be used has of course changed: Before you had to make a single call and got the variable changed, now you have to use the return value instead. The ByRef was left in (for performance reasons at the time) to avoid additional copies. We had already covered the fact that these considerations are not fulfilled in reality in the discussion on _JSON_Parse(). This function is therefore also a candidate where the ByRef can be removed if the opportunity arises. But until then, this is simply irrelevant for the functionality of the function and has no influence. ByRef and Return are not incompatible. Edit: Ok, a little more thought: Could it be that it's not about the function being faulty, but that you just want a feature extension so that you can process variables directly and at the same time get them back as return values? Well of course you can discuss that but this function is not part of the user interface (it begins with 2 underscores) but an internal helper function for a specific purpose. So far it has fulfilled this purpose, so I have no compulsion to change it accordingly. If someone absolutely needs a function that processes a variable accordingly in-place, then this is a three-liner for him. But that would just be a feature request. In fact, the function was said to be “broken”. But I can't see that so far.
-
Well, I read that there should be problems and that the function should be broken, but so far I have not been able to discover anything concrete about what it is actually about. I couldn't find an example string where i could check whether it is correctly encoded in JSON. Therefore, it is not yet clear to me exactly what the problem should be. When I look at the supposed fix, it differs from __JSON_FormatString() in the handling of @CRLF. The fact that it only turns @CRLF into \n was a deliberate design decision at the time. For good platform compatibility, \n has basically established itself as the standard separator. This is not a bad thing, as basically every reasonable JSON decoder converts the line break sequence suitable for the respective system. It also makes for more compact and readable JSON strings. I would be somewhat surprised if an application explicitly insisted on \r\n for its JSON input. But yes - this approach of mine is of course open to discussion - there are of course also arguments against it. At least this choice was not made by accident. On the other hand, that doesn't seem to be the problem either, since (as I interpret the statements) an earlier version obviously doesn't seem to have the problem. But even then, the behavior regarding @CRLF was the same. I therefore suspect that it's more about the not so long ago introduced variant for small strings with the StringRegExpReplace(). Since I am very unhappy with this ugly cascade of StringReplace I have been looking for (more performant) alternatives and have found them at least for small strings by handling the replacements in a single StringRegExpReplace call (yes I know - the pattern comes directly from the RegEx hell...). It may well be that there are string constellations where this variant produces incorrect results. But: I have not yet seen a concrete example. Therefore, I need input data to see if there is a bug and if so, how it can be fixed. On the subject of the version numbers in the file: Yes, I was too careless because I also have a pure git view rather than manual numbers
-
Remarkably true - I have updated it.
-
Please help with Run PowerShell Command 'quotes"
AspirinJunkie replied to Trong's topic in AutoIt General Help and Support
Exactly - it is not escaping for Powershell code. But we're not at that level yet (as I said before: escaping with such nested interpreters is quite annoying...) Escaping per \" is not intended for powershell.exe but for the Windows API function CreateProcessA/W (which is the basis for the AutoIt function Run()). This function must ensure that the parameters are passed correctly to powershell.exe and the syntax is such that double quotation marks are escaped via \". A little clearer with the example: If you execute the following via Run (or from the command line): powershell.exe -Command "Write-Host \"This is a \"\"short\"\" test with multiple \"\"quotes\"\".\"" Then powershell.exe receives the following code as a parameter: Write-Host "This is a ""short"" test with multiple ""quotes""." And your double quotation marks for the powershell are correct again! This is exactly what the _prc_runPS() function does here. -
Please help with Run PowerShell Command 'quotes"
AspirinJunkie replied to Trong's topic in AutoIt General Help and Support
Yes, the escaping of the quotation marks within the commands can be quite annoying. So you have to escape the quotation marks in the command. This is a bit easier if you swap the double quotation marks with the single ones. Something like this: Local $command_r = 'PowerShell -Command "' & StringReplace($powerShellCommand, '"', '\"') & '"' Why you are using @ComSpec (i.e. the cmd.exe) although you want to call the powershell instead is not clear to me. I have written the following function myself. Maybe it will help you here: #include <WinAPIConv.au3> ; Get the list of running processes and filter for processes named "notepad" Local $commandToRun = "Get-Process | Where-Object {$_.ProcessName -eq 'notepad'} | Format-List Name, Id, MainWindowTitle" Local $powerShellResult = _prc_runPS($commandToRun) If @error Then MsgBox(16, "Error", "Could not get results from PowerShell.") Else If $powerShellResult Then MsgBox(64, "PowerShell Result", "Result returned from PowerShell: " & $powerShellResult) Else MsgBox(64, "Information", "No processes match the criteria.") EndIf EndIf ; Another example: Get the PowerShell version Local $versionCommand = "$PSVersionTable.PSVersion" Local $powerShellVersion = _prc_runPS($versionCommand) If Not @error Then MsgBox(64, "PowerShell Version", "PowerShell Version: " & $powerShellVersion) EndIf ; #FUNCTION# ====================================================================================== ; Name ..........: _prc_runPS() ; Description ...: Executes a Powershell command and returns its output as a string ; Syntax ........: _prc_runPS($sCmd, [$nFlags = 0x8, [$sWorkDir = '', [$sOptions = '-NoProfile -ExecutionPolicy Bypass', [$nTimeOut = 0, $bLoopRead = False]]]]]) ; Parameters ....: $sCmd - the powershell command to be executed as you would use it directly in the powershell ; you can also use line breaks ; $nFlags - [optional] flags that control the handling of the two streams stdout and stderr: ; 0x2: [Default] Return a string with the content of stdout ; 0x4: [Default] Return a string with the content of stderr ; 0x6: Return a array with the content of stdout in $Array[0] and stderr in $Array[1] ; 0x8: Return a string with the combined content of stdout and stderr ; $sWorkDir - [optional] the working directory like in Run() ; $sOptions - [String] additional parameters to be passed to powershell.exe ; $nTimeOut - [optional] the maximum time to wait for the process to be completed (see @error return) ; default = 0: infinite; every other number: wait time in seconds ; $bLoopRead - if true: stdout and stderr are read in a loop; if false: they are read in one go ; Return values .: Success: String with powershell output or if $nFlags = 0x6: array with cmdline outputs and set ; @extended = return code of the process ; Failure: "" and set @error to: ; | 1: error during run; @extended = @error of Run() ; | 2: ProcessWaitClose reaches timeout before completion ; | 3: content written in stderr - indicates error messages of the program (not a real error) ; Author ........: AspirinJunkie ; Modified ......: 2025-03-10 ; Related .......: _WinAPI_OemToChar() ; Example .......: Yes ; $x = _prc_runPS('$obj = New-Object -ComObject "Shell.Application"' & @CRLF & _ ; '$obj | Get-Member') ; ConsoleWrite($x & @CRLF) ; ================================================================================================= Func _prc_runPS($sCmd, $nFlags = 0x8, $sWorkDir = '', $sOptions = '-NoProfile -ExecutionPolicy Bypass', $nTimeOut = 0, $bLoopRead = False) ; handling Default keyword word for the parameters If IsKeyWord($nFlags) = 1 Then $nFlags = 0x8 If IsKeyWord($sWorkDir) = 1 Then $sWorkDir = "" If IsKeyWord($sOptions) = 1 Then $sOptions = '-NoProfile -ExecutionPolicy Bypass' ; format command as call parameter, passed to powershell.exe $sCmd = StringFormat('powershell.exe %s -Command "%s"', $sOptions, StringReplace($sCmd, '"', '\"',0,1)) ; start the cmd/process Local $iPID = Run($sCmd, $sWorkDir, @SW_Hide, $nFlags) If @error Then Return SetError(1, @error, "") Local $iExit, $sStdOut = "", $sStdErr = "" If $bLoopRead Then ; fill stdout and/or stderr over a loop Do If BitAND($nFlags, 0x4) Then $sStdErr &= StderrRead($iPID) $sStdOut &= StdoutRead($iPID) If @error Then ExitLoop Until 0 ; determine the exit code $iExit = ProcessWaitClose($iPID, 0) ElseIf ProcessWaitClose($iPID, $nTimeOut) = 0 Then ; wait until process ends Return SetError(2, 0, "") Else ; read out the process results in one go $iExit = @extended $sStdOut = StdoutRead($iPID) $sStdErr = BitAND($nFlags, 0x4) ? StderrRead($iPID) : "" EndIf ; return only stderr If $nFlags = 0x4 Then Return SetExtended($iExit, _WinAPI_OemToChar($sStdErr)) ; return array if stdout and stderr should be read separately ElseIf $nFlags = 0x6 Then Local $aRet[2] = [_WinAPI_OemToChar($sStdOut), _WinAPI_OemToChar($sStdErr)] Return SetError($sStdErr = "" ? 0 : 3, $iExit, $aRet) EndIf ; return a string Return SetExtended($iExit, _WinAPI_OemToChar($sStdOut)) EndFunc -
Have you changed anything in the script? Maybe so that $sScript is suddenly empty or something? Have you checked the result to see if this is really what the code produces? (an _ArrayDisplay() on $aActions for example) Or may I guess?: You are running your script as 32-bit instead of 64-bit? Then the results would be plausible, as nothing is being processed (you should be able to tell from the result). The results don't look very plausible (and I can't reproduce them either). The For-Loop alone to get the AutoIt array from the JsonC results is just 40% faster than the StringRegExp solution in my tests - there is not even a single Json-C code involved. This clashes with your results that the whole call should be ~230x faster than the StringRegExp solution. There was a small indexing error in the script, which I fixed. At the same time, I also added jq to my results above.
-
Certainly better performance for complex JSON documents and therefore great to have another tool in the toolbox of JSON problem solvers. 👍 I took the case of the individual array extraction from the meta.json to carry out the performance comparison mentioned above (test script in the appendix). This was my result: speed comparison.zip
-
That had performance reasons. With a classic ByRef, large strings have to be duplicated when they are transferred. As the function also works recursively, this would take place several times and would have a corresponding impact on performance. Important: “Would have”. In fact, AutoIt has quite obviously built in a kind of COW mechanism. This means that a copy is not created automatically, but only when the content is actually changed in the function. However, I do not know this with certainty but only deduce it from the performance behavior. The important point is: ByRef has no (more?) influence on the performance of the _JSON_Parse() function. So the recommendation would actually be to remove ByRef to enable direct string inputs. Which I have just done myself and adapted the repository accordingly.
-
The following UDF may also be of interest to you, as it expands on the idea of the Levenshtein distance: >>Fuzzy String UDF<<
-
Export|Import Registry Keys: Best Practices
AspirinJunkie replied to mr-es335's topic in AutoIt General Help and Support
If something doesn't do what you want it to do, the first thing you have to do: Debugging. In the simplest case, this means that you have passed values or variables output to find out their value at runtime to determine whether they have the expected value. In your case here, simply outputting the call string you have put together is all you need to do: ConsoleWrite("REGEDIT /S " & "D:\Scripts Working\GlobalRemoveAll\RegKeys\AddMe.reg" & @CRLF) ConsoleWrite('REGEDIT /S "D:\Scripts Working\GlobalRemoveAll\RegKeys\AddMe.reg"' & @CRLF) Then you will quickly see that the specific problem here is the spaces in the path. -
1. To extract only the version numbers without beta or anything else, you can use the following pattern (there are many alternative approaches for such a pattern, but this is based on your specific example): (?>\d+\.)+\d+\b 2. A normal _ArrayMax/_ArraySort etc. is not sufficient to determine the actual maximum including all secondary versions. The elements are strings and a classic AutoIt string comparison, on which _ArrayMax/_ArraySort is based, compares character by character. In this specific case, this would even work, but if you imagine that you now have the elements '135.0.3' and '135.0.20' in the list, then '135.0.3' would still be returned as the result. You have tried to get around this by setting the size comparison for _ArrayMax to “numeric”, but that doesn't help here either, as it means that the strings are first converted into a number. And that means that “135.0.2” simply becomes 135. So you either need a function that sorts the array with its own comparison function or returns the maximum, or you create a value for each value yourself that can be correctly compared by _ArrayMax. For the former, there are already UDFs such as >>this<<. The solution to your question would look like this: #include "ArrayPlus.au3" ; input string $sValue = "'133.0.3','133.0','133.0b7','133.0b8','133.0b9','134.0.1','134.0.2','134.0','134.0b1','134.0b3','134.0b5','134.0b9','135.0','135.0.1','135.0.2','135.0b1','135.0b3','135.0b5','135.0b9','136.0b1'" ; extract stable versions $aStables = StringRegExp($sValue, "(?>\d+\.)+\d+\b", 3) ; determine max version number $sMax = _ArrayGetMax($aStables, __ap_cb_comp_Natural) ; present result MsgBox(0,"max stable version", $sMax) 3. _ArraySort($sValue, 1) does nothing at all. As the name suggests, it sorts arrays. $sValue, on the other hand, is a string. (What @error also tells you, if it had been analyzed.)
-
Help with data processing and reporting.
AspirinJunkie replied to Trong's topic in AutoIt General Help and Support
Ok, with the information I have refined the script a little so that it may come closer to your requirements. You can certainly do the fine-tuning yourself. #include "TableData.au3" ; Transfer data to table type - customize attribute names in the header to your needs $mData = _td_fromCsv("Big_Data.txt", @TAB, 0, "Attrib1|Attrib2|Attrib3|Model|Attrib5|Attrib6|Process|Quantity|Produced|Unproduced|Attrib11|Attrib12") ; filter the data so that only elements whose process begins with "FATP_" are retained $mData = _td_filter($mData, 'StringRegExp($x.Process, "^FATP_.*")') ; [optional] sort by model and process for a better overview _td_sort($mData, "__extractModel($x.Model) & $x.Process ") ; display data _td_display($mData, "Big_Data - filtered dataset") ;group by model and process - the example here attempts to extract the model from the model column and concat it with the process to form a group string For $aGroupData In _td_groupBy($mData, "__extractModel($x.Model) & ' | ' & $x.Process") ; Go through the individual groups and process them. Here you could do your sums or whatever ; convert the datasets to key-value maps where the keys are the attributes for better handling (access the values by attribute name instead of index number) $aGroup = _td_toObjects($aGroupData) ; calculate the sum over the "Produced" attribute for the current group $fSum = _td_reduce($aGroupData, "+ $x.Produced", 0) ; print the model, the process and the sum of produced units ConsoleWrite(StringFormat("%-27s %-18s: %5d\n", __extractModel($aGroup[0].Model), $aGroup[0].Process, $fSum)) Next ; auxiliary function to extract the model name from the total string in the 4th column. You will certainly have to adapt this to your needs Func __extractModel($sString) Local $aSplit = StringSplit($sString, ",", 3) If UBound($aSplit) < 2 Then Return $sString Return StringRegExpReplace($aSplit[1], '\h*_.+$', '') EndFunc -
Help with data processing and reporting.
AspirinJunkie replied to Trong's topic in AutoIt General Help and Support
As I see it, you have to group and filter data, create totals for the groups, etc. This literally cries out for a job for SQLite. Once you have the data in an SQLite database, the groupings and totals are a piece of cake. To do this, however, you would first have to define a basic structure for this table in the database. In other words, which columns have which names and which data type. The import is then best done in milliseconds via _SQLite_SQLiteExe() in conjunction with the .import command and tabulator as column separator. Otherwise, it is not yet clear to me personally how you get from the input data to the table that you designate as desired output. The model is hidden somewhere as a substring in the 4th column. The rules for extracting it are not clear. And I also don't understand how these values of 140 are created from the initial values. No sum, average or anything else that you could derive from your example extract results in your desired values. Of course, you can also handle the import, filtering and grouping with pure AutoIt. But then I would recommend that you use ready-made UDFs to help you. Here is an example of how you could proceed with your data using the TableData UDF, for example: #include "TableData.au3" ; Transfer data to table type - customize attribute names in the header to your needs $mData = _td_fromCsv("Big_Data.txt", @TAB, 0, "Attrib1|Attrib2|Attrib3|Model|Attrib5|Attrib6|Process|Attrib8|Attrib9|Attrib10|Attrib11|Attrib12") ; display data _td_display($mData, "Big_Data - whole dataset") ; group by model and process - the example here attempts to extract the model from the model column and concat it with the process to form a group string $mGroups = _td_groupBy($mData, "__extractModel($x.Model) & ' | ' & $x.Process") ; Go through the individual groups and process them. Here you could do your sums or whatever For $sKey In MapKeys($mGroups) ; the current group data as table object $aGroupData = $mGroups[$sKey] ; calculate the sum over the 8th attribute for the current group $fSum = _td_reduce($aGroupData, "+ $x.Attrib8", 0) ConsoleWrite(StringFormat("% 70s: %6d\n", $sKey, $fSum)) ; display the current group data ;~ _td_display($aGroupData, $sKey) Next ; auxiliary function to extract the model name from the total string in the 4th column. You will certainly have to adapt this to your needs Func __extractModel($sString) Local $aSplit = StringSplit($sString, ",", 3) If UBound($aSplit) < 2 Then Return $sString Return $aSplit[1] EndFunc -
Get JSON data from random keys
AspirinJunkie replied to Tommy135's topic in AutoIt General Help and Support
You are using an old AutoIt version without map support. -
Exactly. I had changed this locally but overlooked this file when pushing. Is now fixed - thanks. I'm sure there will still be a few minor mistakes here and there. The thing is just really complex and extensive. The proper documentation of the individual functions alone took 2 weeks (that was a job for criminals!)
-
Linear Algebra UDF A library for linear algebra, developed in AutoIt. This library offers a variety of functions for performing calculations and operations in linear algebra. Idea A UDF for linear algebra in AutoIt. The aim of this is to be as thematically comprehensive, high-performance and easily accessible as possible. The widely used software library BLAS/LAPACK serves as the basis for the UDF. The user should be able to work as intuitively as possible and get by without any major administrative effort. A particular focus is on extensive functionalities for non-linear adjustment calculations. Structure The UDF is divided into 3 sub-UDFs: BLAS.au3, LAPACK.au3 and LinearAlgebra.au3. The low-level interfaces to the respective BLAS/LAPACK functionalities are implemented in the first two. LinearAlgebra.au3, which is intended as the primary interface for the end user, is built on this basis. The functions here offer simpler interfaces and access to more complex algorithms such as regressions and adjustment calculations. In addition to these 3 files, a DLL is also required which implements the BLAS/LAPACK interface. Install Download the 3 files BLAS.au3, LAPACK.au3 and LinearAlgebra.au3 (or clone the repository) from >>HERE<< Download a current BLAS/LAPACK DLL: Recommendation: OpenBLAS (theoretically, other BLAS/LAPACK implementations should also work - however, additional adaptations may then be necessary) Download the file OpenBLAS-x.x.xx-x64.zip from there and extract the file libopenblas.dll into the same folder as the LinearAlgebra.au3. The sample files in the subfolder /examples should now be executable . Features ---- vector/matrix creation ---- _la_fromArray - converts a AutoIt array or array define string into a matrix map _la_fromStruct - creates a matrix/vector map from a DllStruct as used here in the UDF _la_createVector - creates new empty vector _la_createMatrix - creates new empty matrix _la_createIdentity - create identity matrix/vector _la_duplicate - creates an independent copy of a matrix/vector map _la_fromFile - reads a matrix or a vector from a file created by _la_toFile() ---- extraction/transforming ---- _la_join - combines 2 matrices _la_transpose - transposes a matrix in-place or out-place and [optional] scaling _la_ReDim - changes the shape of a matrix by by changing the number of columns (also matrix <-> vector conversion) _la_getRow - extracts a row of a matrix as a vector _la_getColumn - extracts a column of a matrix as a vector _la_getDiag - extracts the diagonal of a matrix as a vector _la_getTriangle - extract upper or lower triangle part of a matrix _la_VectorToDiag - creates a diagonal matrix from a vector ---- data output ---- _la_display - displays a matrix/vector map, similar to _ArrayDisplay _la_toArray - converts a matrix/vector map into an AutoIt array _la_toFile - write a matrix/vector into a file ---- scalar operations ---- _la_rotate - applies a plane rotation to coordinate-pairs ---- matrix attributes ---- _la_isPositiveDefinite - checks whether a matrix is positive definite _la_isSymmetric - checks whether a matrix is symmetrical _la_rank - determines the rank of a matrix _la_determinant - calculate the determinant of a matrix _la_conditionNumber - determine the condition number of a matrix ---- unary operations ---- _la_inverse - calculates the inverse of a matrix _la_pseudoInverse - calculate the Moore-Penrose pseudo inverse of a matrix _la_sum - calculates the sum of the elements of a matrix, vector or parts thereof _la_asum - calculate the sum of the absolute(!) values of a matrix/vector _la_amin - finds the first element having the minimum absolute(!) value _la_amax - finds the first element having the maximum absolute(!) value _la_norm - calculate the euclidian norm of a vector _la_mean - calculate the mean of a vector or parts of a matrix ---- element wise operations ---- _la_sqrtElements - calculates the square root of each element of a matrix/vector _la_squareElements - calculates the square of each element of a matrix/vector _la_invElements - forms the reciprocal (1/x) for each element of the matrix/vector ---- addition subtraction ---- _la_sub - subtracts a matrix/vector B from matrix/vector A _la_add - calculate the sum of a matrix/vector/scalar mA and a matrix/vector/scalar mB ---- multiplication ---- _la_mul - calculates a multiplication between a matrix/vector/scalar A and a matrix/vector/scalar B _la_outerproduct - calculates the outer product ("tensor product") of two vectors _la_dot - calculate the "dot product"/"scalar product"/"inner product" of two vectors _la_scale - multiplies the elements of a matrix/vector by a scalar value _la_mulElementWise - calculates the element-wise ("Hadarmard") product between two matrices/vectors _la_cross - calculates the cross product between two 3-element vectors ---- factorization / decomposition ---- _la_LU - calculates the LU decomposition of a matrix _la_QR - calculates the QR decomposition of a matrix _la_SVD - calculates the singular value decomposition (SVD) of a matrix _la_cholesky - calculate the cholesky decomposition of a symmetric, positive definite matrix ( A --> L * Lᵀ or A --> U * Uᵀ ) ---- eigenvalues / eigenvectors ---- _la_eigen - computes for an N-by-N real matrix A, the eigenvalues and the left and/or right eigenvectors. ---- solve linear equation systems ---- _la_solve - computes the solution to a system of linear equations A * X = B ---- least squares solving ---- _la_lstsq - solves overdetermined or underdetermined [weighted] linear system ---- regression ---- _la_regression - calculates an n-dimensional linear or non-linear regression ---- adjustment ---- _la_adjustment - performs a least-squares adjustment calculation for a system of different [weighted] non-linear equations _la_adjustment_l1 - performs a adjustment calculation to L1 norm for a system of different [weighted] non-linear equations _la_adj_addObservation - adds an observation to the adjustment system ---- additional helper functions ---- _la_adj_showResult - formats the results of _la_adj more clearly and display them in a window Documentation The documentation for the individual functions is contained directly in the source code. Each function is provided with a description that explains its parameters and return values. In most cases, a short example is also included here. You will also find detailed explanations in the example files. To-Do Certain functions (e.g. _la_add(), _la_mul(), _la_solve(), ...) are currently implemented for the general case only. However, these would benefit accordingly if the specific functions for special matrix geometries (symmetric matrices, triangular matrices, band matrices, ...) were used in each case. The basic functions required for this are already implemented in BLAS.au3 and LAPACK.au3.
-
Another suggestion: Global $sTxt = _ '<a href="some-folder/2021-abc/somefile"> ' & @CRLF & _ '<a href="some-folder/2021-abc/somefile-123"> ' & @CRLF & _ '<a href="some-folder/2022-abc/1234"> ' & @CRLF & _ '<a href="some-folder/2022-abc/1234-somefile"> ' & @CRLF & _ '<a href="fake"> ' & @CRLF & _ '<a href="some-folder/2023-abc/123-somefile-456"> ' & @CRLF & _ '<a href="some-folder/another-folder/2024-abc/789-somefile-456">' & @CRLF $sProcessed = StringRegExpReplace($sTxt, '<a\b[^>]+href="\K[^"]+?\/(?=\d[^\/"]+")', '###') ConsoleWrite($sProcessed)