From dedc06573f952e0f8c92edba2aa2f87c39691647 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 13 Oct 2026 18:48:19 +0200 Subject: [PATCH 1/9] TBMANLIB_Xml: moved to 1.0.0.1; its XPath engine documented (mXPath, SelectNodes, SelectSingleNode, the XPath* methods) with its defects, white space dropped by the parser, pretty printing rewritten; still Experimental --- WIP.md | 2 +- builder/page-baseline.json | 2 +- .../Contributed/TBMANLIB_Xml/cXml.md | 291 ++++++++- .../Contributed/TBMANLIB_Xml/cXmlNode.md | 165 +++++- .../Contributed/TBMANLIB_Xml/index.md | 48 +- .../Contributed/TBMANLIB_Xml/mXPath.md | 559 ++++++++++++++++++ .../Contributed/TBMANLIB_Xml/mXml.md | 2 +- docs/Reference/Experimental.md | 2 +- .../contributed/TBMANLIB_Xml/api.json | 156 ++++- .../contributed-TBMANLIB_Xml/Settings | 2 +- 10 files changed, 1183 insertions(+), 46 deletions(-) create mode 100644 docs/Reference/Contributed/TBMANLIB_Xml/mXPath.md diff --git a/WIP.md b/WIP.md index 515e3458..3e26be81 100644 --- a/WIP.md +++ b/WIP.md @@ -34,7 +34,7 @@ sibling. History goes nowhere: git keeps it. Reference documentation is **complete** for all thirteen packages: VBA, VBRUN, VB, WebView2, Assert, CustomControls, CEF, WinEventLogLib, WinNamedPipesLib, WinServicesLib, tbIDE, WinNativeCommonCtls and AppGlobalClassObject. It is adapted from primary sources: Microsoft VBA-Docs (CC-BY-4.0) for the runtime library, `.twin` source for the twinBASIC-specific packages. The CEF and WebView2 packages also carry a tutorial set. -Twenty-six **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerLib, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsJson, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. The two console packages carry a NOTE (owner, 2026-10-12) that the samples harness cannot yet capture a console, so their console claims follow the source. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. VBComDlg's `exclude_from_docs` names `CommonDialogBase`, a module whose public procedures only its class calls. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modImpExp` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Xml, tbRegExp, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib, BigNumberLib and WinReg do not compile on BETA 1005 (each documented from its source alone, its samples compile-only; WinReg's methods were also run in a copy of its source with its four failing calls passed `ByVal`, and ReadString, GetRegistryValueType and the first ConvertValueToString give wrong results there), xCalendar has defects that can hang a program, TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_ConsoleComponents cannot take a cConsole of the published TBMANLIB_ConsoleCore (it embeds a private copy of it), TBMANLIB_ConsoleCore reads keys wrongly, TBMANLIB_Diagnostic cannot create its Dictionary or make a text report and misplaces List items, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text, and tbRegExp, with **Global**, reports a match of no characters twice when the search skipped characters to reach it (a defect of the vba-regex engine it is made from, confirmed by a second agent). TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph; tsPwGen's names its five test modules and its six internal modules, and tsJson's its test and internal modules likewise, which their landing pages describe in a paragraph each. See *Where things live* for how a package moves between the two lists. +Twenty-six **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerLib, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsJson, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. The two console packages carry a NOTE (owner, 2026-10-12) that the samples harness cannot yet capture a console, so their console claims follow the source. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. VBComDlg's `exclude_from_docs` names `CommonDialogBase`, a module whose public procedures only its class calls. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modImpExp` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Xml, tbRegExp, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib, BigNumberLib and WinReg do not compile on BETA 1005 (each documented from its source alone, its samples compile-only; WinReg's methods were also run in a copy of its source with its four failing calls passed `ByVal`, and ReadString, GetRegistryValueType and the first ConvertValueToString give wrong results there), xCalendar has defects that can hang a program, TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_ConsoleComponents cannot take a cConsole of the published TBMANLIB_ConsoleCore (it embeds a private copy of it), TBMANLIB_ConsoleCore reads keys wrongly, TBMANLIB_Diagnostic cannot create its Dictionary or make a text report and misplaces List items, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text (and its XPath engine reads the step `..` and `and` with `or` wrongly), and tbRegExp, with **Global**, reports a match of no characters twice when the search skipped characters to reach it (a defect of the vba-regex engine it is made from, confirmed by a second agent). TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph, and the five types of its XPath engine (`XPathTokenType`, `XPathAxisType`, `XPathOperator`, `XPathStep`, `XPathPredicate`), which no public member takes or returns and which the mXPath page mentions in a paragraph; tsPwGen's names its five test modules and its six internal modules, and tsJson's its test and internal modules likewise, which their landing pages describe in a paragraph each. See *Where things live* for how a package moves between the two lists. The rest of this file is the maintenance guide for updating existing pages or adding new ones: package surface notes, page conventions, and the integrity check. diff --git a/builder/page-baseline.json b/builder/page-baseline.json index 3cfe7aa0..cc2fc503 100644 --- a/builder/page-baseline.json +++ b/builder/page-baseline.json @@ -1,5 +1,5 @@ { "src": "docs", - "pages": 1255, + "pages": 1256, "staticFiles": 1009 } diff --git a/docs/Reference/Contributed/TBMANLIB_Xml/cXml.md b/docs/Reference/Contributed/TBMANLIB_Xml/cXml.md index c53ba948..895cf3be 100644 --- a/docs/Reference/Contributed/TBMANLIB_Xml/cXml.md +++ b/docs/Reference/Contributed/TBMANLIB_Xml/cXml.md @@ -8,7 +8,7 @@ has_toc: false # cXml class {: .no_toc } -A **cXml** holds one XML document. It reads the document from a string or a file, or starts a new one with a root element. It finds, reads and changes elements and attributes by a path such as `library/book`, and writes the document back as a string or a file. The elements, text and comments of the document are [**cXmlNode**](cXmlNode) objects, which the [**Document**](#document) and [**RootElement**](#rootelement) properties and the search methods return. +A **cXml** holds one XML document. It reads the document from a string or a file, or starts a new one with a root element. It finds, reads and changes elements and attributes by a path such as `library/book`, and writes the document back as a string or a file. The [**XPath**](#xpath) methods find nodes by an XPath expression, in the subset that the [**mXPath**](mXPath) module reads. The elements, text and comments of the document are [**cXmlNode**](cXmlNode) objects, which the [**Document**](#document) and [**RootElement**](#rootelement) properties and the search methods return. An object is created with `Dim x As New cXml`. A new object holds no document. [**Parse**](#parse), [**LoadFromFile**](#loadfromfile), [**CreateNew**](#createnew) or the [**XmlString**](#xmlstring) property gives it one, and until then the members that take a path find nothing. @@ -31,7 +31,7 @@ Debug.Print x.ToXmlString(False) ``` > [!WARNING] -> In version 1.0.0.0, the parser reads some valid XML wrongly and raises no error: a self-closing element, `&` and other entities in text, a tag that uses both kinds of quote or has a line break between its attributes, and a processing instruction. A comment, a CDATA section or a DOCTYPE declaration in the wrong place makes **Parse** loop forever. [Defects in version 1.0.0.0](#defects) lists each case. +> In version 1.0.0.1, the parser reads some valid XML wrongly and raises no error: a self-closing element, `&` and other entities in text, a tag that uses both kinds of quote or has a line break between its attributes, and a processing instruction. A comment, a CDATA section or a DOCTYPE declaration in the wrong place makes **Parse** loop forever. [Defects in version 1.0.0.1](#defects) lists each case. * TOC {:toc} @@ -39,13 +39,13 @@ Debug.Print x.ToXmlString(False) ## Paths {: #paths } -The members that take an *xpath* parameter find an element by a path: [**SelectElement**](#selectelement), [**GetElementText**](#getelementtext), [**SetElementText**](#setelementtext), [**GetAttribute**](#getattribute), [**SetAttribute**](#setattribute), [**RemoveAttribute**](#removeattribute) and [**RemoveElement**](#removeelement), and [**AddElement**](#addelement) for its *parentXPath*. The name of the parameter is misleading: the syntax is a small one of the package's own and is not XPath. +The members that take an *xpath* parameter find an element by a path: [**SelectElement**](#selectelement), [**GetElementText**](#getelementtext), [**SetElementText**](#setelementtext), [**GetAttribute**](#getattribute), [**SetAttribute**](#setattribute), [**RemoveAttribute**](#removeattribute) and [**RemoveElement**](#removeelement), and [**AddElement**](#addelement) for its *parentXPath*. The name of the parameter is misleading: the syntax is a small one of the package's own and is not XPath. The [**XPath**](#xpath) methods read XPath expressions. - A path is a list of names separated by `/`, such as `library/book`. The search starts at the document node, so the first name is the name of the root element: `library/book` finds a book, and `book` finds nothing. - Each name selects the first child, of the node found so far, whose name equals it. Letter case is ignored. Only direct children count, and when several children have the name, the first one is taken. - White space around a name is removed, and an empty name is ignored. `/library//book` and ` library / book ` both mean `library/book`. - A name is compared as a whole, so a namespace prefix is part of it: `ns:item` is the name of an element written ``. The package does no namespace processing. -- Nothing else has a meaning. A position (`book[2]`), an attribute (`@id`), `*`, `.` and `..` are names that no element has, so the path selects nothing. `//book` does not search at any depth: the empty name is ignored, and the path means `book`. [**SelectElements**](#selectelements) searches at any depth, and the **ChildNodes** of a node reach the other children. +- Nothing else has a meaning. A position (`book[2]`), an attribute (`@id`), `*`, `.` and `..` are names that no element has, so the path selects nothing. `//book` does not search at any depth: the empty name is ignored, and the path means `book`. [**SelectElements**](#selectelements) searches at any depth, and the **ChildNodes** of a node reach the other children. The [**XPath**](#xpath) methods read positions, attribute tests, `*`, `.` and `//`. - The nodes that are not elements have names too, `#text`, `#comment` and `#cdata-section`, and a path can select them: `library/#comment` is the first comment directly inside the root element. - A path with no name, an empty string or `/`, selects nothing, and the Debug Console shows `SelectElement failed: Subscript out of range`. @@ -87,14 +87,14 @@ Debug.Print children(2).GetAttribute("id") ## Reporting failures {: #reporting-failures } -A member that cannot do its work returns a value that says so, and does not stop the program. [**Parse**](#parse), [**LoadFromFile**](#loadfromfile), [**SaveToFile**](#savetofile), [**Validate**](#validate) and [**RemoveElement**](#removeelement) return **False**. [**SelectElement**](#selectelement) and [**AddElement**](#addelement) return **Nothing**. [**GetElementText**](#getelementtext) and [**GetAttribute**](#getattribute) return an empty string, and [**SelectElements**](#selectelements) an empty collection. A method that returns nothing, such as [**SetAttribute**](#setattribute), does nothing. +A member that cannot do its work returns a value that says so, and does not stop the program. [**Parse**](#parse), [**LoadFromFile**](#loadfromfile), [**SaveToFile**](#savetofile), [**Validate**](#validate) and [**RemoveElement**](#removeelement) return **False**. [**SelectElement**](#selectelement), [**AddElement**](#addelement) and [**XPathSingle**](#xpathsingle) return **Nothing**. [**GetElementText**](#getelementtext), [**GetAttribute**](#getattribute), [**XPathText**](#xpathtext) and [**XPathAttribute**](#xpathattribute) return an empty string, and [**SelectElements**](#selectelements), [**XPath**](#xpath) and [**XPathFromRoot**](#xpathfromroot) an empty collection. A method that returns nothing, such as [**SetAttribute**](#setattribute), does nothing. When an error causes the failure, the member also: - writes a line to the Debug Console that names the member and gives the error's description, such as `Parse failed: XML string cannot be empty` or `SelectElement failed: Document not initialized`. In a built executable, **Debug.Print** writes nothing (see [**Debug**](../../../Modules/Debug)), so the line appears in the IDE only; - leaves the error in the **Err** object. After the call returns, **Err.Number**, **Err.Source** and **Err.Description** describe it. A program reads them straight after the call, because the next call of most members clears them, and calls `Err.Clear` before the call so that an earlier error does not remain in them. -The errors that occur are [**ERR_EMPTY_DOCUMENT**](mXml#err_empty_document), which [**Parse**](#parse) raises for an empty string and [**SelectElement**](#selectelement) for a document that has not been read or created, and the errors of the Visual Basic runtime that the code meets while it works: 5 (*Invalid procedure call or argument*) for a tag cut off by the end of the text, 9 (*Subscript out of range*) for a path with no name, and 91 (*Object variable or With block variable not set*) in [**RemoveElement**](#removeelement) for a path that selects nothing. The other error constants of [**mXml**](mXml#constants) are not used. +The errors that occur are [**ERR_EMPTY_DOCUMENT**](mXml#err_empty_document), which [**Parse**](#parse) raises for an empty string and [**SelectElement**](#selectelement) for a document that has not been read or created, and the errors of the Visual Basic runtime that the code meets while it works: 5 (*Invalid procedure call or argument*) for a tag cut off by the end of the text, 9 (*Subscript out of range*) for a path with no name, and 91 (*Object variable or With block variable not set*) in [**RemoveElement**](#removeelement) for a path that selects nothing. The XPath members meet error 6 (*Overflow*) for a position too large for a **Long**, and write `XPath query failed:`, `XPathSingle query failed:` or `XPathFromRoot query failed:` and the description. The other error constants of [**mXml**](mXml#constants) are not used. The members that let an error reach the program are [**XmlString**](#xmlstring), [**ToXmlString**](#toxmlstring) and [**Merge**](#merge). @@ -131,7 +131,7 @@ Syntax: *object*.**Document** **As cXmlNode** *object* : *required* An object expression that evaluates to a **cXml**. -Read-only. The node has the **NodeType** **Document** and the **Name** `#document`. Its **ChildNodes** hold the root element and any comments and text outside it, in document order, including text that is only white space. A new object holds an empty document node. [**Parse**](#parse), [**LoadFromFile**](#loadfromfile), [**CreateNew**](#createnew) and [**XmlString**](#xmlstring) replace it. +Read-only. The node has the **NodeType** **Document** and the **Name** `#document`. Its **ChildNodes** hold the root element and any comments and text outside it, in document order. Text that is only spaces is not kept, and text that has a line break or a tab in it is. A new object holds an empty document node. [**Parse**](#parse), [**LoadFromFile**](#loadfromfile), [**CreateNew**](#createnew) and [**XmlString**](#xmlstring) replace it. ```tb check_build check_run Dim x As New cXml @@ -431,7 +431,7 @@ Syntax: *object*.**GetElementText** ( *xpath* ) **As String** *xpath* : *required* A **String**, the [path](#paths) of the element. -Returns the text of the element: the values of all the text nodes and CDATA sections inside it, at any depth, one after the other in document order with nothing between them. Comments add nothing. Text that is only white space is included, so the text of an element in an indented document holds the line breaks and the indentation. Entities in the text are returned as written; see [Entities](#defect-entities). +Returns the text of the element: the values of all the text nodes and CDATA sections inside it, at any depth, one after the other in document order with nothing between them. Comments add nothing. White space that has a line break or a tab in it is included, so the text of an element in an indented document holds the line breaks and the indentation; text that is only spaces is not in the document ([White space](#defect-whitespace)). Entities in the text are returned as written; see [Entities](#defect-entities). Returns an empty string when the path selects nothing, which a program cannot tell from an element with no text. @@ -540,7 +540,7 @@ Syntax: *object*.**Parse** ( *xml* ) **As Boolean** Replaces the document with the one in the text, sets [**RootElement**](#rootelement) and [**XmlString**](#xmlstring), and returns **True**. The `version` and `encoding` of an XML declaration, written with double quotes, set [**Version**](#version) and [**Encoding**](#encoding). -The parser builds these nodes: elements with their attributes, text, comments and CDATA sections. Text that is only white space is kept, in the document and inside elements. An end tag closes the innermost open element whatever its name is, and an element still open at the end of the text is closed by it. Processing instructions and the declaration are not kept. +The parser builds these nodes: elements with their attributes, text, comments and CDATA sections. Text that is only spaces is left out, in the document and inside elements; text that has a line break or a tab in it is kept ([White space](#defect-whitespace)). An end tag closes the innermost open element whatever its name is, and an element still open at the end of the text is closed by it. Processing instructions and the declaration are not kept. Returns **False**, and writes `Parse failed:` and the description to the Debug Console, for an empty string and for a tag that the end of the text cuts off, such as `<` or ``, written from [**Version**](#version) and [**Encoding**](#encoding), with no line break after it. +: *optional* A **Boolean**. **True** (the default) starts the text with a declaration such as ``, written from [**Version**](#version) and [**Encoding**](#encoding). A line break follows the declaration when *prettyPrint* is **True**, and nothing follows it otherwise. *prettyPrint* -: *optional* A **Boolean**. **False** (the default) writes the document compactly: nothing is added between the nodes. **True** puts each element, comment, CDATA section and text on a line of its own, ends every line with a carriage return and a line feed, and leaves out the text nodes that are only white space. See [Pretty printing](#defect-pretty) for what the indentation looks like. +: *optional* A **Boolean**. **False** (the default) writes the document compactly: nothing is added between the nodes. **True** puts each element, comment, CDATA section and text on a line of its own, indents each line by one *indent* for each level of nesting, and ends every line with a carriage return and a line feed, the last one included. A text node that is only spaces is left out. See [Pretty printing](#defect-pretty) for reading the text back. *indent* -: *optional* A **String**, the indentation for each level of nesting. The default is two spaces. Only the first character of the string is used. +: *optional* A **String**, the indentation for each level of nesting, written once for each level. The default is two spaces. An empty string writes no indentation. The text holds elements, text, comments and CDATA sections. An element with no child is written as ``, and an element with children as `...`. The characters `&`, `<`, `>`, `"` and `'` are replaced by `&`, `<`, `>`, `"` and `'` in text and in the values of attributes. Comments and CDATA sections are written as they are. The attributes of an element are written in the order of its attribute list. A document that holds nothing gives the declaration alone, or an empty string without one. -> [!NOTE] -> An empty *indent* with *prettyPrint* **True** raises error 5, *Invalid procedure call or argument*, as soon as the document has an element. - ```tb check_build check_run Dim x As New cXml x.CreateNew "r" @@ -848,6 +875,29 @@ Debug.Print x.ToXmlString(False) ' x < y ``` +With *prettyPrint*, the nodes are on lines of their own. The sample shows the spaces as dots and the line breaks as bars: + +```tb check_build check_run +Dim x As New cXml +x.CreateNew "r" +x.AddElement "r", "a" +x.SetElementText "r/a", "t" +x.AddElement "r", "b" +x.AddElement "r/b", "c" + +Dim s As String +s = x.ToXmlString(True, True) +Debug.Print Replace(Replace(s, " ", "."), vbCrLf, "|") +s = x.ToXmlString(False, True, "--") +Debug.Print Replace(Replace(s, " ", "."), vbCrLf, "|") +s = x.ToXmlString(False, True, "") +Debug.Print Replace(Replace(s, " ", "."), vbCrLf, "|") +' Output: +' ||..|....t|..|..|....|..|| +' |--|----t|--|--|----|--|| +' ||t|||||| +``` + ### Validate {: .no_toc } @@ -881,10 +931,171 @@ Debug.Print x.Validate() ' True ``` -## Defects in version 1.0.0.0 +### XPath +{: .no_toc } + +Returns the nodes that an XPath expression selects. + +Syntax: *object*.**XPath** ( *path* ) **As Collection** + +*object* +: *required* An object expression that evaluates to a **cXml**. + +*path* +: *required* A **String**, an XPath expression; see the [syntax](mXPath#syntax) that [**mXPath**](mXPath) reads. + +Evaluates the expression with [**SelectNodes**](mXPath#selectnodes), starting at the document node, so `/library/book` and `library/book` both select the books of a root element named `library`. Returns a new **Collection** of [**cXmlNode**](cXmlNode) objects, numbered from 1, with each node in it once. The collection is empty, and is never **Nothing**, when nothing is selected, when *path* is empty, and when [**Initialized**](#initialized) is **False**. When the evaluation raises an error, the method writes `XPath query failed:` and the description to the Debug Console, leaves the error in **Err**, and returns an empty collection. + +The engine reads a part of XPath 1.0 and reads some expressions wrongly; see [Defects](mXPath#defects). + +```tb check_build check_run +Dim x As New cXml +x.Parse "DuneEmmaUlysses" + +Dim n As cXmlNode +For Each n In x.XPath("/library/book[@lang='en']") + Debug.Print n.GetAttribute("id") & " " & n.Text +Next +Debug.Print x.XPath("//book").Count +Debug.Print x.XPath("/library/magazine").Count +' Output: +' 1 Dune +' 3 Ulysses +' 3 +' 0 +``` + +### XPathAttribute +{: .no_toc } + +Returns an attribute of the first node that an XPath expression selects. + +Syntax: *object*.**XPathAttribute** ( *xpath* , *attrName* ) **As String** + +*object* +: *required* An object expression that evaluates to a **cXml**. + +*xpath* +: *required* A **String**, an XPath expression that selects the element. + +*attrName* +: *required* A **String**, the name of the attribute. Letter case is ignored. + +Returns the value of the attribute, as [**GetAttribute**](cXmlNode#getattribute) of the first node that [**XPath**](#xpath) selects does. Returns an empty string when the expression selects nothing or the node has no such attribute, which a program cannot tell from an attribute whose value is empty. The expression is evaluated by [**XPathSingle**](#xpathsingle), which writes the line for an error. + +```tb check_build check_run +Dim x As New cXml +x.Parse "DuneEmma" + +Debug.Print x.XPathAttribute("//book[@id='2']", "lang") +Debug.Print x.XPathAttribute("//book[1]", "LANG") +Debug.Print "[" & x.XPathAttribute("//book", "missing") & "]" +Debug.Print "[" & x.XPathAttribute("//magazine", "lang") & "]" +' Output: +' fr +' en +' [] +' [] +``` + +### XPathFromRoot +{: .no_toc } + +Returns the nodes that an XPath expression selects, starting at the root element. + +Syntax: *object*.**XPathFromRoot** ( *xpath* ) **As Collection** + +*object* +: *required* An object expression that evaluates to a **cXml**. + +*xpath* +: *required* A **String**, an XPath expression. + +Works as [**XPath**](#xpath) does, with the [**RootElement**](#rootelement) as the starting node and not the document node. The first step selects among the children of the root element, so `book` selects the books, `/library/book` selects nothing when the root element is `library`, and `.` selects the root element. Returns an empty collection when the object holds no root element, and writes `XPathFromRoot query failed:` and the description for an error. + +```tb check_build check_run +Dim x As New cXml +x.Parse "" + +Debug.Print x.XPathFromRoot("book").Count +Debug.Print x.XPathFromRoot("/library/book").Count +Debug.Print x.XPathFromRoot(".").Item(1).Name +Debug.Print x.XPathFromRoot("//book").Count +Debug.Print x.XPath("/library/book").Count +' Output: +' 2 +' 0 +' library +' 2 +' 2 +``` + +### XPathSingle +{: .no_toc } + +Returns the first node that an XPath expression selects. + +Syntax: *object*.**XPathSingle** ( *xpath* ) **As** [**cXmlNode**](cXmlNode) + +*object* +: *required* An object expression that evaluates to a **cXml**. + +*xpath* +: *required* A **String**, an XPath expression. + +Returns the first node of what [**XPath**](#xpath) returns, or **Nothing** when that is empty or [**Initialized**](#initialized) is **False**. When the evaluation raises an error, it writes `XPathSingle query failed:` and the description to the Debug Console, leaves the error in **Err**, and returns **Nothing**. + +```tb check_build check_run +Dim x As New cXml +x.Parse "DuneEmma" + +Dim n As cXmlNode +Set n = x.XPathSingle("//book[@id='2']") +Debug.Print n.Text +Debug.Print (x.XPathSingle("//magazine") Is Nothing) +Debug.Print x.XPathSingle("//book").GetAttribute("id") +' Output: +' Emma +' True +' 1 +``` + +### XPathText +{: .no_toc } + +Returns the text of the first node that an XPath expression selects. + +Syntax: *object*.**XPathText** ( *xpath* ) **As String** + +*object* +: *required* An object expression that evaluates to a **cXml**. + +*xpath* +: *required* A **String**, an XPath expression that selects the node. + +Returns the text of the node as [**GetElementText**](#getelementtext) does: the values of all the text nodes and CDATA sections inside it, at any depth, one after the other in document order. When the expression selects a text node or a CDATA section, the result is its value. Returns an empty string when the expression selects nothing, which a program cannot tell from a node with no text. The expression is evaluated by [**XPathSingle**](#xpathsingle), which writes the line for an error. + +The [**XPathText**](cXmlNode#xpathtext) of a node is different: it returns only the text directly inside the node it selects. + +```tb check_build check_run +Dim x As New cXml +x.Parse "xyz" + +Debug.Print x.XPathText("/a") +Debug.Print x.XPathText("/a/b") +Debug.Print x.XPathText("//b/text()") +Debug.Print "[" & x.XPathText("/zzz") & "]" +' Output: +' xyz +' y +' y +' [] +``` + +## Defects in version 1.0.0.1 {: #defects } -Each of these sections describes a case in which the package gives a wrong result or never returns. The samples show the result of the version named here. +Each of these sections describes a case in which the package gives a wrong result or never returns. The samples show the result of the version named here. The defects of the XPath engine are on the page of the [**mXPath**](mXPath#defects) module. ### Self-closing elements {: #defect-self-closing .no_toc } @@ -1022,18 +1233,47 @@ Debug.Print "[" & x.GetElementText("a") & "]" ' [] ``` +### White space +{: #defect-whitespace .no_toc } + +> [!WARNING] +> **Parse** leaves out a text node that holds only spaces, and keeps one that has a line break or a tab in it. A space between two elements is lost: `

a b

` is read as `

ab

`, and its text is `ab`. An element that holds only spaces is read as an empty element. The white space of an indented document is kept, because it holds line breaks. + +```tb check_build check_run +Dim x As New cXml +x.Parse "

a b

" +Debug.Print x.ToXmlString(False) +Debug.Print x.GetElementText("p") + +x.Parse " " +Debug.Print x.ToXmlString(False) + +x.Parse " 1 " +Debug.Print x.RootElement.ChildNodes.Count + +x.Parse "" & vbCrLf & "1" & vbCrLf & "" +Debug.Print x.RootElement.ChildNodes.Count +' Output: +'

ab

+' ab +' +' 1 +' 3 +``` + +Text set with [**SetElementText**](#setelementtext) is kept whatever it holds, spaces only included. + ### Pretty printing {: #defect-pretty .no_toc } > [!WARNING] -> **ToXmlString** with *prettyPrint* **True** indents one level with the first character of *indent* alone, so the default of two spaces indents by one space, and `vbTab & vbTab` by one tab. It also puts the text of an element on lines of its own, so the text of the element in the written document is the text with line breaks and indentation around it. Reading it back with [**GetElementText**](#getelementtext) returns them. The text changes each time the document is written and read. +> A document written with *prettyPrint* **True** does not read back as the document that was written. The line breaks and the indentation around the text of an element are text to the parser, which keeps white space that has a line break in it. [**GetElementText**](#getelementtext) of the element returns them, and writing the document again with *prettyPrint* puts them on lines of their own, so the text changes each time the document is written and read. ```tb check_build check_run Dim x As New cXml x.CreateNew "r" x.AddElement "r", "a" x.SetElementText "r/a", "t" -x.AddElement "r", "empty" Dim s As String s = x.ToXmlString(False, True) @@ -1042,9 +1282,13 @@ Debug.Print Replace(Replace(s, " ", "."), vbCrLf, "|") Dim y As New cXml y.Parse s Debug.Print Replace(Replace(y.GetElementText("r/a"), " ", "."), vbCrLf, "|") + +s = y.ToXmlString(False, True) +Debug.Print Replace(Replace(s, " ", "."), vbCrLf, "|") ' Output: -' |.|..t|.|.|| -' |..t|. +' |..|....t|..|| +' |....t|.. +' |..|..|..|....|....t|..|..|..||||| ``` ### Clone, Merge and the root element @@ -1143,4 +1387,5 @@ A file with fewer than three bytes cannot be loaded, and an empty file gives **F - [cXmlNode](cXmlNode) class -- the nodes of the document - [mXml](mXml) module -- the node types, the error numbers and the UTF-8 functions +- [mXPath](mXPath) module -- the XPath engine behind the **XPath** methods - [TBMANLIB_Xml](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB_Xml/cXmlNode.md b/docs/Reference/Contributed/TBMANLIB_Xml/cXmlNode.md index eac000cd..7ac3df07 100644 --- a/docs/Reference/Contributed/TBMANLIB_Xml/cXmlNode.md +++ b/docs/Reference/Contributed/TBMANLIB_Xml/cXmlNode.md @@ -8,7 +8,7 @@ has_toc: false # cXmlNode class {: .no_toc } -A **cXmlNode** is one node of an XML document held by a [**cXml**](cXml) object: the document itself, an element, a piece of text, a CDATA section or a comment. It has six public fields and five methods. The fields hold the node's name, kind and value, its attributes, its child nodes and its parent; the methods read and change the attributes and find child nodes by name. +A **cXmlNode** is one node of an XML document held by a [**cXml**](cXml) object: the document itself, an element, a piece of text, a CDATA section or a comment. It has six public fields, a property that returns the node's text, and nine methods. The fields hold the node's name, kind and value, its attributes, its child nodes and its parent; the methods read and change the attributes, find child nodes by name, and find nodes by an XPath expression. A **cXml** object creates the nodes when it parses text or when [**AddElement**](cXml#addelement) adds an element, and returns them from [**SelectElement**](cXml#selectelement), [**SelectElements**](cXml#selectelements), [**RootElement**](cXml#rootelement) and [**Document**](cXml#document). A program can also create a node with `New cXmlNode` and add it to a node's **ChildNodes**. @@ -112,7 +112,7 @@ Syntax: *node*.**ChildNodes** **As Collection** *node* : *required* An object expression that evaluates to a **cXmlNode**. -A **Collection** of **cXmlNode** objects, numbered from 1. It holds elements, text, CDATA sections and comments, including text that is only white space. The collection is empty for a text node, a CDATA section and a comment. [**GetChildNode**](#getchildnode) and [**GetChildNodesByName**](#getchildnodesbyname) search the collection by name. +A **Collection** of **cXmlNode** objects, numbered from 1. It holds elements, text, CDATA sections and comments. The parser adds no text node for text that is only spaces; see [White space](cXml#defect-whitespace). The collection is empty for a text node, a CDATA section and a comment. [**GetChildNode**](#getchildnode) and [**GetChildNodesByName**](#getchildnodesbyname) search the collection by name. To add a node by hand, set its **NodeType**, **Name** and **Value**, set its [**Parent**](#parent), and add it to the collection. The writer chooses what to write by **NodeType**, not by **Name**. @@ -184,6 +184,35 @@ Syntax: *node*.**Value** [ **=** *value* ] **As String** A **String**. It is empty for the document node and for elements. For a text node it is the text as it stood in the XML, with any entity left undecoded (see [Entities](cXml#defect-entities)); [**ToXmlString**](cXml#toxmlstring) escapes it again when it writes the node. +## Properties + +### Text +{: .no_toc } + +Returns the text directly inside the node. + +Syntax: *node*.**Text** **As String** + +*node* +: *required* An object expression that evaluates to a **cXmlNode**. + +Read-only. Returns the values of the text nodes and CDATA sections among the node's [**ChildNodes**](#childnodes), one after the other, with nothing between them. Child elements and comments add nothing, and neither does the text inside a child element, which [**GetElementText**](cXml#getelementtext) of a **cXml** includes. A node that has no text node or CDATA section among its children, a text node itself among them, gives an empty string. + +```tb check_build check_run +Dim x As New cXml +x.Parse "xyz" + +Debug.Print x.RootElement.Text +Debug.Print x.GetElementText("a") +Debug.Print x.RootElement.GetChildNode("b").Text +Debug.Print "[" & x.RootElement.ChildNodes(1).Text & "]" +' Output: +' xcz +' xcyz +' y +' [] +``` + ## Methods ### New @@ -356,8 +385,140 @@ Next ' A = 3 ``` +### XPath +{: .no_toc } + +Returns the nodes that an XPath expression selects, starting at this node. + +Syntax: *node*.**XPath** ( *path* ) **As Collection** + +*node* +: *required* An object expression that evaluates to a **cXmlNode**. + +*path* +: *required* A **String**, an XPath expression; see the [syntax](mXPath#syntax) that [**mXPath**](mXPath) reads. + +Evaluates the expression with [**SelectNodes**](mXPath#selectnodes), with this node as the starting node: `title` selects the child elements named `title`, and `.//title` the descendants. A `/` at the start of the expression is ignored, so the expression does not start at the document, and `//title` searches below this node only. Returns a new **Collection** of **cXmlNode** objects, numbered from 1, with each node in it once, and an empty one when nothing is selected. When the evaluation raises an error, the method writes `Node.XPath query failed:` and the description to the Debug Console, leaves the error in **Err**, and returns an empty collection. The engine reads a part of XPath 1.0 and reads some expressions wrongly; see [Defects](mXPath#defects). + +```tb check_build check_run +Dim x As New cXml +x.Parse "DuneEmmaEmma II" + +Dim b As cXmlNode +Set b = x.XPathSingle("/catalog/book[@id='2']") + +Debug.Print b.XPath("title").Count +Debug.Print b.XPath("title[2]").Item(1).Text +Debug.Print b.XPath(".//title").Count +Debug.Print b.XPath("//title").Count +Debug.Print b.XPath("/catalog/book").Count +Debug.Print b.XPath("parent::*/book").Count +' Output: +' 2 +' Emma II +' 2 +' 2 +' 0 +' 2 +``` + +### XPathAttribute +{: .no_toc } + +Returns an attribute of the first node that an XPath expression selects, starting at this node. + +Syntax: *node*.**XPathAttribute** ( *xpath* , *attrName* ) **As String** + +*node* +: *required* An object expression that evaluates to a **cXmlNode**. + +*xpath* +: *required* A **String**, an XPath expression that selects the element. + +*attrName* +: *required* A **String**, the name of the attribute. Letter case is ignored. + +Returns the value of the attribute, as [**GetAttribute**](#getattribute) of the first node that [**XPath**](#xpath) selects does. Returns an empty string when the expression selects nothing or the node has no such attribute, which a program cannot tell from an attribute whose value is empty. The expression is evaluated by [**XPathSingle**](#xpathsingle). + +```tb check_build check_run +Dim x As New cXml +x.Parse "DuneEmma" + +Dim b As cXmlNode +Set b = x.XPathSingle("/catalog/book[@id='2']") +Debug.Print b.XPathAttribute("title", "lang") +Debug.Print "[" & b.XPathAttribute("author", "lang") & "]" +' Output: +' fr +' [] +``` + +### XPathSingle +{: .no_toc } + +Returns the first node that an XPath expression selects, starting at this node. + +Syntax: *node*.**XPathSingle** ( *xpath* ) **As cXmlNode** + +*node* +: *required* An object expression that evaluates to a **cXmlNode**. + +*xpath* +: *required* A **String**, an XPath expression. + +Returns the first node of what [**XPath**](#xpath) returns, or **Nothing** when that is empty. When the evaluation raises an error, it writes `Node.XPathSingle query failed:` and the description to the Debug Console, leaves the error in **Err**, and returns **Nothing**. + +```tb check_build check_run +Dim x As New cXml +x.Parse "Dune" + +Dim t As cXmlNode +Set t = x.RootElement.XPathSingle("book/title") +Debug.Print t.Text +Debug.Print t.Parent.GetAttribute("id") +Debug.Print (x.RootElement.XPathSingle("magazine") Is Nothing) +' Output: +' Dune +' 1 +' True +``` + +### XPathText +{: .no_toc } + +Returns the text of the first node that an XPath expression selects, starting at this node. + +Syntax: *node*.**XPathText** ( *xpath* ) **As String** + +*node* +: *required* An object expression that evaluates to a **cXmlNode**. + +*xpath* +: *required* A **String**, an XPath expression that selects the node. + +Returns the [**Text**](#text) of the first node that [**XPath**](#xpath) selects: the text directly inside it, and not the text inside its child elements. A text node and a CDATA section have no children, so when the expression selects one, the result is an empty string; its **Value** holds the text. Returns an empty string too when the expression selects nothing. The expression is evaluated by [**XPathSingle**](#xpathsingle). + +> [!NOTE] +> The [**XPathText**](cXml#xpathtext) of a **cXml** returns the text at any depth, and returns the value of a text node. For `xyz`, the **cXml** gives `xyz` for `/a`, and the root element gives `xz` for `.`. + +```tb check_build check_run +Dim x As New cXml +x.Parse "xyz" + +Debug.Print x.XPathText("/a") +Debug.Print x.RootElement.XPathText(".") +Debug.Print x.RootElement.XPathText("b") +Debug.Print "[" & x.RootElement.XPathText("b/text()") & "]" +' Output: +' xyz +' xz +' y +' [] +``` + ## See Also - [cXml](cXml) class -- holds the document and finds nodes by path - [mXml](mXml) module -- the node types and the error numbers +- [mXPath](mXPath) module -- the XPath engine behind the **XPath** methods - [TBMANLIB_Xml](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB_Xml/index.md b/docs/Reference/Contributed/TBMANLIB_Xml/index.md index b45cc0f9..ab567f54 100644 --- a/docs/Reference/Contributed/TBMANLIB_Xml/index.md +++ b/docs/Reference/Contributed/TBMANLIB_Xml/index.md @@ -2,18 +2,23 @@ title: TBMANLIB_Xml Package parent: Experimental Packages has_toc: false -indexed_from: 1.0.0.0 +indexed_from: 1.0.0.1 exclude_from_docs: - mXmlTests + - XPathTokenType + - XPathAxisType + - XPathOperator + - XPathStep + - XPathPredicate permalink: /tB/Packages/Contributed/TBMANLIB_Xml/ --- # TBMANLIB_Xml Package {: .no_toc } -**Author:** woeoio. **Version:** 1.0.0.0 +**Author:** woeoio. **Version:** 1.0.0.1 -The **TBMANLIB_Xml** contributed package reads, changes and writes XML documents. It has the shape of a small DOM. A [**cXml**](cXml) object holds a document as a tree of [**cXmlNode**](cXmlNode) objects, finds elements by a path such as `library/book`, and reads and writes the document as a string or as a UTF-8 file. The [**mXml**](mXml) module holds the node types, the error numbers and two functions that convert between strings and UTF-8 bytes. The package is written in twinBASIC and calls two functions of the Windows API for the conversion. This section documents version 1.0.0.0, published under the MIT licence. +The **TBMANLIB_Xml** contributed package reads, changes and writes XML documents. It has the shape of a small DOM. A [**cXml**](cXml) object holds a document as a tree of [**cXmlNode**](cXmlNode) objects, finds elements by a path such as `library/book` or by an XPath expression, and reads and writes the document as a string or as a UTF-8 file. The [**mXml**](mXml) module holds the node types, the error numbers and two functions that convert between strings and UTF-8 bytes, and the [**mXPath**](mXPath) module evaluates the XPath expressions. The package is written in twinBASIC and calls two functions of the Windows API for the conversion. This section documents version 1.0.0.1, published under the MIT licence. ```tb check_build check_run Dim x As New cXml @@ -37,23 +42,27 @@ Debug.Print y.ToXmlString(False) ``` > [!WARNING] -> The parser of version 1.0.0.0 reads some valid XML wrongly and raises no error, and for some text it never returns. [Defects in version 1.0.0.0](cXml#defects) lists the cases: self-closing elements, entities in text, tags with both kinds of quote or with line breaks, processing instructions, and comments, CDATA sections and DOCTYPE declarations in some positions. +> The parser of version 1.0.0.1 reads some valid XML wrongly and raises no error, and for some text it never returns. [Defects in version 1.0.0.1](cXml#defects) lists the cases: self-closing elements, entities in text, tags with both kinds of quote or with line breaks, processing instructions, and comments, CDATA sections and DOCTYPE declarations in some positions. + +> [!WARNING] +> The XPath engine reads some valid expressions wrongly and raises no error: the step `..`, axes such as `ancestor::`, positions after `//`, and the operators `and` and `or`. [Defects in version 1.0.0.1](mXPath#defects) lists the cases. ## What the package reads and writes A document is read from a string by [**Parse**](cXml#parse), from a file by [**LoadFromFile**](cXml#loadfromfile), or started with [**CreateNew**](cXml#createnew). The package supports: - elements and their attributes, with attribute values in double or single quotes; -- text, comments and CDATA sections, each held as a node. Text that is only white space is kept; +- text, comments and CDATA sections, each held as a node. Text that is only spaces is left out, and text that has a line break or a tab in it is kept; - the `version` and `encoding` of the XML declaration, which are read, kept and written; - reading and setting an element's text and attributes, adding and removing elements, copying a document ([**Clone**](cXml#clone)) and appending one to another ([**Merge**](cXml#merge)); -- writing the document compactly or with line breaks, with `&`, `<`, `>`, `"` and `'` escaped; +- finding nodes by a path of names ([**SelectElement**](cXml#selectelement)) or by an XPath expression ([**XPath**](cXml#xpath)), in the part of XPath 1.0 that [**mXPath**](mXPath) reads; +- writing the document compactly or with line breaks and indentation, with `&`, `<`, `>`, `"` and `'` escaped; - files in UTF-8. A byte order mark is accepted when a file is read and always written. -The package does not support, in version 1.0.0.0: +The package does not support, in version 1.0.0.1: - DTDs and schemas. A DOCTYPE declaration is not read, and nothing is validated against a DTD or a schema. [**Validate**](cXml#validate) only checks that elements and attributes have names; -- XPath. A path is a list of element names separated by `/`, with no positions, attributes or wildcards; see [Paths](cXml#paths); +- most of XPath. The [**XPath**](cXml#xpath) methods read a part of XPath 1.0, described on the page of the [**mXPath**](mXPath) module: there is no union, no attribute step, no ancestor or sibling axis, and few functions. The other members that take a path read a list of element names separated by `/`, with no positions, attributes or wildcards; see [Paths](cXml#paths); - namespaces. A prefix is part of an element's or attribute's name, and no namespace is resolved; - entities defined in a DTD, and character references such as `A`. The five predefined entities are decoded in attribute values and not in text; - processing instructions, which are skipped, and the `standalone` pseudo-attribute of the declaration; @@ -64,11 +73,11 @@ The [**ParseOption**](mXml#parseoption) enumeration, with members such as **Pres ## Failures -No member of the package ends a program with an error of its own. A member that cannot do its work returns **False**, **Nothing**, an empty string or an empty collection, and in the IDE writes a line that names the member to the Debug Console, for example `Parse failed: XML string cannot be empty`. The error stays in the **Err** object after the call. The error numbers are constants of [**mXml**](mXml#constants), and only [**ERR_EMPTY_DOCUMENT**](mXml#err_empty_document) is used. [Reporting failures](cXml#reporting-failures) has the details. +No member of the package ends a program with an error of its own. A member that cannot do its work returns **False**, **Nothing**, an empty string or an empty collection, and in the IDE writes a line that names the member to the Debug Console, for example `Parse failed: XML string cannot be empty`. The error stays in the **Err** object after the call. The error numbers are constants of [**mXml**](mXml#constants), and only [**ERR_EMPTY_DOCUMENT**](mXml#err_empty_document) is used. [Reporting failures](cXml#reporting-failures) has the details. The two functions of [**mXPath**](mXPath#selectnodes) are the exception: they handle no error, and the error of an evaluation reaches the program that called them. -## Classes and module +## Classes and modules -- [cXml](cXml) -- holds an XML document, reads it from a string or a file, finds, changes and adds elements by path, and writes it as a string or a file +- [cXml](cXml) -- holds an XML document, reads it from a string or a file, finds, changes and adds elements by path, finds nodes by an XPath expression, and writes it as a string or a file - [Document](cXml#document) -- returns the document node - [Encoding](cXml#encoding) -- returns or sets the encoding name of the declaration - [Initialized](cXml#initialized) -- returns whether the object holds a document @@ -93,12 +102,18 @@ No member of the package ends a program with an error of its own. A member that - [SetElementText](cXml#setelementtext) -- replaces the text of an element - [ToXmlString](cXml#toxmlstring) -- returns the document as text - [Validate](cXml#validate) -- checks that every element and attribute has a name + - [XPath](cXml#xpath) -- returns the nodes that an XPath expression selects + - [XPathAttribute](cXml#xpathattribute) -- returns an attribute of the first node that an XPath expression selects + - [XPathFromRoot](cXml#xpathfromroot) -- returns the nodes that an XPath expression selects, starting at the root element + - [XPathSingle](cXml#xpathsingle) -- returns the first node that an XPath expression selects + - [XPathText](cXml#xpathtext) -- returns the text of the first node that an XPath expression selects - [cXmlNode](cXmlNode) -- one node of a document: an element, text, a CDATA section, a comment or the document itself - [Attributes](cXmlNode#attributes) -- holds the attributes as a list of names and values - [ChildNodes](cXmlNode#childnodes) -- holds the nodes directly inside the node - [Name](cXmlNode#name) -- returns or sets the name - [NodeType](cXmlNode#nodetype) -- returns or sets the kind of node - [Parent](cXmlNode#parent) -- returns the node that holds this one + - [Text](cXmlNode#text) -- returns the text directly inside the node - [Value](cXmlNode#value) -- returns or sets the text of a text node, a CDATA section or a comment - [New](cXmlNode#new) -- creates a node - [GetAttribute](cXmlNode#getattribute) -- returns the value of an attribute @@ -106,6 +121,10 @@ No member of the package ends a program with an error of its own. A member that - [GetChildNodesByName](cXmlNode#getchildnodesbyname) -- returns all the children with a given name - [RemoveAttribute](cXmlNode#removeattribute) -- removes an attribute - [SetAttribute](cXmlNode#setattribute) -- sets the value of an attribute + - [XPath](cXmlNode#xpath) -- returns the nodes that an XPath expression selects, starting at the node + - [XPathAttribute](cXmlNode#xpathattribute) -- returns an attribute of the first node that an XPath expression selects + - [XPathSingle](cXmlNode#xpathsingle) -- returns the first node that an XPath expression selects + - [XPathText](cXmlNode#xpathtext) -- returns the text directly inside the first node that an XPath expression selects - [mXml](mXml) -- the node types, the parse options, the error numbers, the defaults and the UTF-8 conversion functions - [XmlNodeType](mXml#xmlnodetype) -- the kinds of node - [ParseOption](mXml#parseoption) -- four flags that no member reads @@ -113,12 +132,15 @@ No member of the package ends a program with an error of its own. A member that - [DEFAULT_XML_VERSION](mXml#default_xml_version), [DEFAULT_ENCODING](mXml#default_encoding) and [DEFAULT_INDENT](mXml#default_indent) -- the defaults - [StringToUTF8Bytes](mXml#stringtoutf8bytes) -- converts a string to UTF-8 bytes - [UTF8BytesToString](mXml#utf8bytestostring) -- converts UTF-8 bytes to a string +- [mXPath](mXPath) -- the XPath engine behind the **XPath** methods + - [SelectNodes](mXPath#selectnodes) -- returns the nodes that an XPath expression selects + - [SelectSingleNode](mXPath#selectsinglenode) -- returns the first node that an XPath expression selects ## The test module -The package also contains **mXmlTests**, a module of unit tests for **cXml**, which has no page of its own here. **mXmlTests.RunAllTests** runs 86 checks and writes a line for each to the Debug Console: `[PASS]` or `[FAIL]` and the check's message, grouped under the name of the test. It ends with a summary of the totals, the pass rate and a closing line; in version 1.0.0.0 all 86 pass. Each test is also a public **Sub** of the module that can be run alone. +The package also contains **mXmlTests**, a module of unit tests for **cXml**, which has no page of its own here. **mXmlTests.RunAllTests** runs 119 checks and writes a line for each to the Debug Console: `[PASS]` or `[FAIL]` and the check's message, grouped under the name of the test. It ends with a summary of the totals, the pass rate and a closing line; in version 1.0.0.1 all 119 pass. Each test is also a public **Sub** of the module that can be run alone. -The checks compare one value at a time and do not exercise the cases listed under [Defects in version 1.0.0.0](cXml#defects): the test of self-closing tags tests that the output contains `/>`, and the namespace test tests that **Parse** returns **True**. The file tests write `test_output.xml` in the current folder, and leave it there. +The checks compare one value at a time and do not exercise the cases listed under [Defects in version 1.0.0.1](cXml#defects): the test of self-closing tags tests that the output contains `/>`, and the namespace test tests that **Parse** returns **True**. The eight XPath tests test the forms that [**mXPath**](mXPath) reads, and none of the cases listed under its [defects](mXPath#defects). The file tests write `test_output.xml` and `test_output_pretty.xml` in the current folder, and leave them there. ## See Also diff --git a/docs/Reference/Contributed/TBMANLIB_Xml/mXPath.md b/docs/Reference/Contributed/TBMANLIB_Xml/mXPath.md new file mode 100644 index 00000000..7e7299a3 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Xml/mXPath.md @@ -0,0 +1,559 @@ +--- +title: mXPath +parent: TBMANLIB_Xml Package +permalink: /tB/Packages/Contributed/TBMANLIB_Xml/mXPath +has_toc: false +--- + +# mXPath module +{: .no_toc } + +The **mXPath** module is the XPath engine of the package. It finds the nodes of a document that an XPath expression selects. The **XPath** methods of [**cXml**](cXml#xpath) and [**cXmlNode**](cXmlNode#xpath) call it, and a program can call its two functions, [**SelectNodes**](#selectnodes) and [**SelectSingleNode**](#selectsinglenode), with any node of a document as the starting point. A program reaches them with the module's name in front (`mXPath.SelectNodes`) or by their own names. + +The engine reads a part of XPath 1.0: steps that go to children, descendants, the parent and the node itself, a few node tests, and predicates that test a position, an attribute, the text of a node or a child. It does not refuse an expression it cannot read. Some expressions select nothing, and some are read as other expressions and select the wrong nodes; [Defects in version 1.0.0.1](#defects) lists them. + +```tb check_build check_run +Dim x As New cXml +x.Parse "" & _ + "Dune" & _ + "Emma" & _ + "Cosmos" & _ + "" + +Dim n As cXmlNode +For Each n In mXPath.SelectNodes(x.Document, "//book[@cat='fiction'][@price>10]") + Debug.Print n.GetAttribute("id") & " " & n.XPathText("title") +Next +Debug.Print x.XPathText("/catalog/book[last()]/title") +Debug.Print x.XPathAttribute("//book[@id='2']", "cat") +' Output: +' 1 Dune +' Cosmos +' fiction +``` + +> [!WARNING] +> Several expressions that are valid XPath are read wrongly and raise no error: the parent step `..` selects nothing, `ancestor::a` is read as `child::a`, `//book[1]` selects one node where XPath selects one for each parent, and `and`, `or` and the comparison operators are found by plain searches of the text. [Defects in version 1.0.0.1](#defects) has each case. + +* TOC +{:toc} + +## Syntax +{: #syntax } + +An expression is a list of steps separated by `/`, and each step can have predicates in square brackets. White space around a step is ignored, and so is a `/` at the end. + +### Where a path starts +{: .no_toc } + +The functions take the node to start from. [**XPath**](cXml#xpath) of a [**cXml**](cXml) starts at the document node, [**XPathFromRoot**](cXml#xpathfromroot) at the root element, and the **XPath** of a [**cXmlNode**](cXmlNode#xpath) at that node. The first step selects from the starting node, and each later step from every node the step before selected. + +A `/` at the start of an expression does not make it start at the document: it is ignored. Starting at the document node, `/catalog` and `catalog` are the same expression and select the root element. Starting at any other node, `/catalog` selects a child of that node named `catalog`, and `//title` searches below that node only. No expression reaches a node outside the subtree of the starting node, except through the **parent** axis. + +```tb check_build check_run +Dim x As New cXml +x.Parse "DuneEmma" + +Dim book As cXmlNode +Set book = x.XPathSingle("/catalog/book[@id='2']") + +Debug.Print x.XPath("catalog/book").Count +Debug.Print x.XPath("/catalog/book").Count +Debug.Print x.XPath("//title").Count +Debug.Print book.XPath("//title").Count +Debug.Print book.XPath("/title").Count +Debug.Print book.XPath("/catalog/book").Count +Debug.Print x.XPathFromRoot("book").Count +Debug.Print x.XPathFromRoot("/catalog/book").Count +' Output: +' 2 +' 2 +' 2 +' 1 +' 1 +' 0 +' 2 +' 0 +``` + +### Steps +{: .no_toc } + +A step selects nodes by their **Name** and kind. Letter case is ignored in names, so `/CATALOG/Book` selects the same nodes as `/catalog/book`. + +| Step | Selects | +|:-----|:--------| +| `name` | the children whose **Name** is *name*. The names of the nodes that are not elements, `#text`, `#comment` and `#cdata-section`, work too | +| `prefix:name` | the children whose whole name is `prefix:name`. The engine does no namespace processing | +| `*` | all the child elements | +| `node()` | all the children, of every kind | +| `text()` | the children that are text nodes. CDATA sections are not text nodes here | +| `comment()` | the children that are comments | +| `.` | the node itself | +| `//` between two steps | the next step looks at descendants at any depth, and not only children | +| `//` at the start | the same, from the starting node, which it does not include | +| `child::`, `descendant::`, `parent::`, `self::` before a step | the step goes to the children, the descendants, the parent or the node itself. These are the axes the engine knows, and the name of an axis is not case sensitive. `parent::*` selects the parent when it is an element, and `parent::node()` selects it whatever it is | + +Each node is in the result once, however many routes reach it, and a path that selects nothing gives an empty collection. + +```tb check_build check_run +Dim x As New cXml +x.Parse "text" + +Dim q As Variant +Dim n As cXmlNode +Dim names As String +For Each q In Array("/lib/shelf", "/LIB/Shelf", "//book", "/lib//book", "/lib/shelf/*", "/lib/shelf/node()", "/lib/shelf/comment()", "//book/text()", "/lib/shelf/#comment", "/lib/shelf/book/parent::shelf", "//book/parent::*", "/lib/./shelf/self::shelf", "//shelf/child::book", "/lib/descendant::book", "/lib/self::shelf") + names = "" + For Each n In x.XPath(q) + names = names & " " & n.Name & ":" & n.GetAttribute("id") & n.Value + Next + Debug.Print q & " ->" & names +Next +' Output: +' /lib/shelf -> shelf:s1 shelf:s2 +' /LIB/Shelf -> shelf:s1 shelf:s2 +' //book -> book:a book:b book:c +' /lib//book -> book:a book:b book:c +' /lib/shelf/* -> book:a book:b book:c +' /lib/shelf/node() -> book:a #comment:c book:b book:c +' /lib/shelf/comment() -> #comment:c +' //book/text() -> #text:text +' /lib/shelf/#comment -> #comment:c +' /lib/shelf/book/parent::shelf -> shelf:s1 shelf:s2 +' //book/parent::* -> shelf:s1 shelf:s2 +' /lib/./shelf/self::shelf -> shelf:s1 shelf:s2 +' //shelf/child::book -> book:a book:b book:c +' /lib/descendant::book -> book:a book:b book:c +' /lib/self::shelf -> +``` + +### Predicates +{: .no_toc } + +A predicate in square brackets keeps some of the nodes that its step selected. A step can have several, and each one works on the nodes the one before kept: `book[@cat='fiction'][2]` is the second of the fiction books. White space around a predicate and around an operator is ignored. + +| Predicate | Keeps | +|:----------|:------| +| `[3]` | the third node. A position counts, from 1, the nodes that the step selected from one node of the step before; a number out of range keeps nothing | +| `[last()]` | the last node | +| `[position()=3]` | the third node. No space is allowed between `position()` and `=` | +| `[@id]` | the nodes that have the attribute, even when its value is empty | +| `[@id='1']`, `[@id="1"]`, `[@id=1]` | the nodes whose attribute equals the text. The quotes can be left out | +| `[@id!='1']` | the nodes whose attribute differs from the text | +| `[@price>10]`, `[@price<10]`, `[@price>=10]`, `[@price<=10]` | the nodes whose attribute is greater than, less than, at least or at most the value | +| `[text()]` | the nodes that have text of their own | +| `[text()='Dune']` | the nodes whose own text equals the text | +| `[name]` | the nodes that have a child named *name*, of any kind | +| `[contains(@id, 'a')]` | the nodes where the first argument contains the second | +| `[starts-with(@id, 'a')]` | the nodes where the first argument begins with the second | +| `[not(@id)]` | the nodes that the predicate inside does not keep | +| `[@a='1' and @b='2']`, `[@a='1' or @b='2']` | the nodes that satisfy both parts, or at least one. The words need a space on each side and can be written in any letter case | + +The names of the functions are not case sensitive, and the opening parenthesis must follow the name directly. The first argument of **contains** and **starts-with** is `@name` for an attribute, `text()` or `.` for the text of the node, or a quoted string. Any other first argument is taken as the text itself, not evaluated. The second argument is a quoted string, or text that is used as it is. The two arguments are separated at the first comma that is not inside quotes, so a second argument can hold commas. + +**Text** here means the text nodes and CDATA sections directly inside the node, one after the other. Child elements do not count and nothing is trimmed, so the text of an element in an indented document includes the line breaks and the indentation around it. + +**Comparing** attributes follows these rules: + +- `=` and `!=` compare text, and letter case does not matter. `@price=8` selects an attribute whose value is `8`, and `@price=8.0` does not. +- `<`, `>`, `<=` and `>=` compare numbers when both sides read as numbers, and text otherwise, again without regard to letter case. +- A name is looked up without regard to letter case, as everywhere in the package. +- An attribute that is not there compares as an empty string; see [Comparing attributes](#defect-attributes). + +```tb check_build check_run +Dim x As New cXml +x.Parse "" & _ + "DuneHerbert" & _ + "EmmaAusten" & _ + "CosmosSagan" & _ + "" + +Dim q As Variant +Dim n As cXmlNode +Dim ids As String +For Each q In Array("//book[2]", "//book[last()]", "//book[position()=1]", "//book[@id]", "//book[@cat='FICTION']", "//book[@id=3]", "//book[@cat!='fiction']", "//book[@price>10]", "//book[@price<=8]", "//book[@price>'10']", "//book[@price=8.0]", "//book[@cat='fiction'][2]", "//book[title]", "//title[text()='Dune']", "//author[contains(text(), 'a')]", "//book[starts-with(@cat, 'sci')]", "//book[not(@cat='fiction')]", "//book[@cat='fiction' and @price>10]", "//book[@id='1' or @id='3']", "//book[ @id = '2' ]") + ids = "" + For Each n In x.XPath(q) + ids = ids & " " & n.GetAttribute("id") & n.Text + Next + Debug.Print q & " ->" & ids +Next +' Output: +' //book[2] -> 2 +' //book[last()] -> 3 +' //book[position()=1] -> 1 +' //book[@id] -> 1 2 3 +' //book[@cat='FICTION'] -> 1 2 +' //book[@id=3] -> 3 +' //book[@cat!='fiction'] -> 3 +' //book[@price>10] -> 1 3 +' //book[@price<=8] -> 2 +' //book[@price>'10'] -> 1 3 +' //book[@price=8.0] -> +' //book[@cat='fiction'][2] -> 2 +' //book[title] -> 1 2 3 +' //title[text()='Dune'] -> Dune +' //author[contains(text(), 'a')] -> Austen Sagan +' //book[starts-with(@cat, 'sci')] -> 3 +' //book[not(@cat='fiction')] -> 3 +' //book[@cat='fiction' and @price>10] -> 1 +' //book[@id='1' or @id='3'] -> 1 3 +' //book[ @id = '2' ] -> 2 +``` + +### What the engine does not read +{: .no_toc } + +These parts of XPath select nothing, and raise no error. + +- A step for an attribute, `@id`, as in `/catalog/book/@id` and `//@id`, and the **attribute** axis. [**XPathAttribute**](cXml#xpathattribute) and the **GetAttribute** methods read attributes. +- The union operator `|`, parentheses used for grouping, arithmetic, and comparisons between two paths or two attributes. +- Functions other than `contains`, `starts-with`, `not`, `text`, `last` and `position`, such as `count`, `name`, `local-name`, `string-length` and `normalize-space`, and `position()` or `last()` in any form other than those in the table. Such a predicate is read as the name of a child, which no element has. +- A comparison with a child or with `.`, as in `book[title='Dune']` and `title[.='Dune']`; see [Text and child comparisons](#defect-text). + +Other expressions are not refused either, and are read as something different from what XPath 1.0 says. They are listed under [Defects in version 1.0.0.1](#defects). + +```tb check_build check_run +Dim x As New cXml +x.Parse "DuneEmma" + +Dim q As Variant +For Each q In Array("/catalog/book/@id", "//@id", "/catalog/book/attribute::id", "//title | //book", "//book[(@id='1')]", "//book[@id=@id]", "//book[count(title)=1]", "//book[last()-1]", "//book[position()>1]", "//book[title='Dune']", "//title[.='Dune']") + Debug.Print q & " -> " & x.XPath(q).Count +Next +' Output: +' /catalog/book/@id -> 0 +' //@id -> 0 +' /catalog/book/attribute::id -> 0 +' //title | //book -> 0 +' //book[(@id='1')] -> 0 +' //book[@id=@id] -> 0 +' //book[count(title)=1] -> 0 +' //book[last()-1] -> 0 +' //book[position()>1] -> 0 +' //book[title='Dune'] -> 0 +' //title[.='Dune'] -> 0 +``` + +## Functions + +### SelectNodes +{: .no_toc } + +Returns the nodes that an XPath expression selects. + +Syntax: **SelectNodes** ( *contextNode* , *xpath* ) **As Collection** + +*contextNode* +: *required* A [**cXmlNode**](cXmlNode), the node the expression starts from. It can be a node of any kind, and the document node of a [**cXml**](cXml) is [**Document**](cXml#document). + +*xpath* +: *required* A **String**, the expression; see [Syntax](#syntax). + +Returns a new **Collection** of **cXmlNode** objects, numbered from 1, in the order the steps found them. Each node is in it once. The collection is empty, and is not **Nothing**, when the expression selects nothing, when *xpath* is an empty string, when *contextNode* is **Nothing**, and when the expression has no step, such as `/` or `//`. + +The function handles no error. A program that calls it directly meets the errors that the evaluation raises: error 6 (*Overflow*) for a position too large for a **Long**, such as `[99999999999]`. The **XPath** methods of [**cXml**](cXml#xpath) and [**cXmlNode**](cXmlNode#xpath) catch it, write a line that begins `XPath query failed:` to the Debug Console, and return an empty collection. + +> [!NOTE] +> The time grows with the square of the number of nodes the query visits, because each node found is compared with all the ones found before. Finding 2,000 elements with `//item` takes about a second, and each doubling of the count multiplies the time by about four. [**SelectElements**](cXml#selectelements) has no such cost. + +```tb check_build check_run +Dim x As New cXml +x.Parse "" + +Dim found As Collection +Set found = mXPath.SelectNodes(x.RootElement, "b") +Debug.Print found.Count & " " & found(2).GetAttribute("id") +Debug.Print mXPath.SelectNodes(Nothing, "b").Count +Debug.Print mXPath.SelectNodes(x.Document, "").Count + +On Error Resume Next +Set found = mXPath.SelectNodes(x.Document, "//b[99999999999]") +Debug.Print Err.Number & " " & Err.Description +On Error GoTo 0 +Debug.Print x.XPath("//b[99999999999]").Count +' Output: +' 2 2 +' 0 +' 0 +' 6 Overflow +' XPath query failed: Overflow +' 0 +``` + +### SelectSingleNode +{: .no_toc } + +Returns the first node that an XPath expression selects. + +Syntax: **SelectSingleNode** ( *contextNode* , *xpath* ) **As** [**cXmlNode**](cXmlNode) + +*contextNode* +: *required* A **cXmlNode**, the node the expression starts from. + +*xpath* +: *required* A **String**, the expression. + +Returns the first node of what [**SelectNodes**](#selectnodes) returns, or **Nothing** when that is empty. It raises the errors of **SelectNodes** too. + +```tb check_build check_run +Dim x As New cXml +x.Parse "" + +Dim n As cXmlNode +Set n = mXPath.SelectSingleNode(x.Document, "//b[@id='2']") +Debug.Print n.GetAttribute("id") +Debug.Print (mXPath.SelectSingleNode(x.Document, "/zzz") Is Nothing) +Debug.Print (mXPath.SelectSingleNode(Nothing, "/a") Is Nothing) +' Output: +' 2 +' True +' True +``` + +## Defects in version 1.0.0.1 +{: #defects } + +Each of these sections describes an expression that the engine reads differently from XPath 1.0 and that raises no error. The samples show the result of the version named here. + +### The parent step +{: #defect-parent .no_toc } + +> [!WARNING] +> The step `..` selects nothing. The `parent::` axis works, so `parent::*` or `parent::book` takes its place. + +```tb check_build check_run +Dim x As New cXml +x.Parse "Dune" + +Debug.Print x.XPath("/catalog/book/title/..").Count +Debug.Print x.XPath("/catalog/book/title/parent::*").Count +Debug.Print x.XPath("/catalog/book/title/parent::book").Count +' Output: +' 0 +' 1 +' 1 +``` + +### Axes +{: #defect-axes .no_toc } + +> [!WARNING] +> An axis other than **child**, **descendant**, **parent**, **self** and **attribute** is not refused. The engine drops the axis and reads the rest of the step as a child step, so `ancestor::a` selects a child named `a`, where XPath selects an ancestor, and `following-sibling::b` selects a child named `b`. `descendant-or-self::` is read as `child::`. + +```tb check_build check_run +Dim x As New cXml +x.Parse "" +Debug.Print x.XPathAttribute("/a/b/ancestor::a", "id") +Debug.Print x.XPathAttribute("/a/b/parent::a", "id") + +x.Parse "" +Debug.Print x.XPathAttribute("/r/a/following-sibling::b", "id") +' Output: +' inner +' outer +' child +``` + +### Positions +{: #defect-positions .no_toc } + +> [!WARNING] +> A position counts the nodes that the step selected from one node of the step before. After `//`, those are all the descendants at any depth, so `//book[1]` is the first book in the whole document, where XPath gives the first book of each parent. A step written with `/` counts the children of each parent, as XPath does. Positions in any form other than `[n]`, `[last()]` and `[position()=n]` select nothing: `[last()-1]`, `[position()>1]`, `[position() = 2]` with spaces, and `[position()=last()]`. A position that is not a whole number is rounded to one, so `[1.5]` is the second node. + +```tb check_build check_run +Dim x As New cXml +x.Parse "" + +Dim q As Variant +Dim n As cXmlNode +Dim ids As String +For Each q In Array("/lib/shelf/book[1]", "//book[1]", "/lib//book[1]", "//shelf/book[last()]", "//book[last()]", "//shelf/book[last()-1]", "//shelf/book[position() = 1]") + ids = "" + For Each n In x.XPath(q) + ids = ids & " " & n.GetAttribute("id") + Next + Debug.Print q & " ->" & ids +Next +' Output: +' /lib/shelf/book[1] -> a c +' //book[1] -> a +' /lib//book[1] -> a +' //shelf/book[last()] -> b d +' //book[last()] -> d +' //shelf/book[last()-1] -> +' //shelf/book[position() = 1] -> +``` + +### Logical operators +{: #defect-logic .no_toc } + +> [!WARNING] +> `and` and `or` are found by a search for the first ` and ` and the first ` or ` in the text of the predicate, and the search does not skip quoted strings or parentheses. The predicate is cut at the first ` and ` if there is one, and the two halves are read separately, so `or` binds tighter than `and`: `a and b or c` is read as `a and (b or c)`, and `a or b and c` as `(a or b) and c`. XPath binds `and` tighter. A string that holds ` and ` or ` or `, such as `@t='salt and pepper'`, cuts the string in two, and the predicate selects nothing. Parentheses are not read as grouping: `not(@x and @y)` and `(@x='1') and (@y='2')` select nothing. + +```tb check_build check_run +Dim x As New cXml +x.Parse "" + +Dim q As Variant +Dim n As cXmlNode +Dim ids As String +For Each q In Array("//i[@x='1' and @y='2']", "//i[@x='1' or @z='3']", "//i[@x='1' and @y='2' or @z='3']", "//i[@z='3' or @x='1' and @y='2']", "//i[not(@x and @y)]", "//i[(@x='1') and (@y='2')]") + ids = "" + For Each n In x.XPath(q) + ids = ids & " " & n.GetAttribute("id") + Next + Debug.Print q & " ->" & ids +Next + +x.Parse "" +Debug.Print x.XPath("//i[@t='salt and pepper']").Count +Debug.Print x.XPath("//i[contains(@t, 'and')]").Count +' Output: +' //i[@x='1' and @y='2'] -> 1 +' //i[@x='1' or @z='3'] -> 1 2 3 +' //i[@x='1' and @y='2' or @z='3'] -> 1 +' //i[@z='3' or @x='1' and @y='2'] -> 1 +' //i[not(@x and @y)] -> +' //i[(@x='1') and (@y='2')] -> +' 0 +' 1 +``` + +XPath 1.0 selects `1 2` for the third and fourth queries, `2 3` for the fifth and `1` for the sixth. + +### Comparing attributes +{: #defect-attributes .no_toc } + +> [!WARNING] +> An attribute that a node does not have compares as an empty string, so `@p<10`, `@p!='x'` and `@p=''` keep the nodes that lack `p`; in XPath 1.0 a comparison with a missing attribute is false. A comparison operator is found by a search of the whole predicate, quoted strings included. A quoted string that holds `>`, `<` or `!=` therefore moves the split, and the attribute is looked up under a name such as `t='a`, which no node has, so it compares as an empty string: `@t='ab`. + +```tb check_build check_run +Dim x As New cXml +x.Parse "" + +Dim q As Variant +Dim n As cXmlNode +Dim ids As String +For Each q In Array("//i[@p]", "//i[@p<10]", "//i[@p>10]", "//i[@p!='x']", "//i[@p='']") + ids = "" + For Each n In x.XPath(q) + ids = ids & " " & n.GetAttribute("id") + Next + Debug.Print q & " ->" & ids +Next + +x.Parse "" +Debug.Print x.XPath("//i[@t='a=b']").Count +Debug.Print x.XPath("//i[@t='a>b']").Count +Debug.Print x.XPath("//i[@t='a 1 3 4 +' //i[@p<10] -> 1 2 4 +' //i[@p>10] -> 3 +' //i[@p!='x'] -> 1 2 3 4 +' //i[@p=''] -> 2 4 +' 1 +' 0 +' 2 +``` + +### Text and child comparisons +{: #defect-text .no_toc } + +> [!WARNING] +> A predicate that starts with `text()` is read as a test of the node's own text. Its operator is taken to be the first `=` in it, whatever comes before, so `text()!='Dune'` selects the nodes whose text equals `Dune`, and an operator without `=` (`text()>'Z'`) is ignored, so the predicate keeps every node that has text. A comparison with a child (`book[title='Dune']`) or with the node (`title[.='Dune']`) selects nothing. In `contains` and `starts-with`, a first argument that is the name of a child is taken as the text of the name: `contains(title, 'i')` asks whether the word `title` has an `i` in it, and is true for every node. + +```tb check_build check_run +Dim x As New cXml +x.Parse "DuneEmma" + +Dim q As Variant +Dim n As cXmlNode +Dim ids As String +For Each q In Array("//title[text()='Dune']", "//title[text()!='Dune']", "//title[text()>'Z']", "//book[title='Dune']", "//title[.='Dune']", "//book[contains(title, 'i')]") + ids = "" + For Each n In x.XPath(q) + ids = ids & " " & n.Name & ":" & n.GetAttribute("id") & n.Text + Next + Debug.Print q & " ->" & ids +Next +' Output: +' //title[text()='Dune'] -> title:Dune +' //title[text()!='Dune'] -> title:Dune +' //title[text()>'Z'] -> title:Dune title:Emma +' //book[title='Dune'] -> +' //title[.='Dune'] -> +' //book[contains(title, 'i')] -> book:1 book:2 +``` + +### A bracket that is not closed +{: #defect-brackets .no_toc } + +> [!WARNING] +> A `[` with no `]` after it is dropped together with what follows it, and the step is read without the predicate. `//b[@id='1'` selects every `b`. + +```tb check_build check_run +Dim x As New cXml +x.Parse "" +Debug.Print x.XPath("//b[@id='1']").Count +Debug.Print x.XPath("//b[@id='1'").Count +Debug.Print x.XPath("//b[").Count +' Output: +' 1 +' 2 +' 2 +``` + +### Slashes +{: #defect-slashes .no_toc } + +> [!WARNING] +> An empty step, as in `///`, ends the path, and the steps after it are not read. `/catalog///book` is read as `/catalog` and selects the root element. + +```tb check_build check_run +Dim x As New cXml +x.Parse "" +Debug.Print x.XPath("/catalog///book").Count & " " & x.XPath("/catalog///book").Item(1).Name +Debug.Print x.XPath("/catalog/book/").Count +' Output: +' 1 catalog +' 1 +``` + +### Defects of the parser +{: #defect-parser .no_toc } + +The expressions read the nodes that the parser made, so the [defects of the parser](cXml#defects) reach them. A self-closing element `` is a node named `/a` that no step can name, and the elements after `` are its children. Entities in text are not decoded, so a comparison with `text()` has to use the text as it stands in the XML, `Tom & Jerry`. The value of an attribute is decoded, and a comparison with it uses `Tom & Jerry`. + +```tb check_build check_run +Dim x As New cXml +x.Parse "t" +Debug.Print x.XPath("/r/*").Count +Debug.Print x.XPath("/r/c").Count +Debug.Print x.XPath("/r/b/c").Count + +x.Parse "Tom & Jerry" +Debug.Print x.XPath("//t[text()='Tom & Jerry']").Count +Debug.Print x.XPath("//t[text()='Tom & Jerry']").Count +Debug.Print x.XPath("//u[@v='Tom & Jerry']").Count +' Output: +' 2 +' 0 +' 1 +' 0 +' 1 +' 1 +``` + +## The types of the engine + +The module also declares five types that only the engine uses: the enumerations **XPathTokenType**, **XPathAxisType** and **XPathOperator**, and the types **XPathStep** and **XPathPredicate**. No public member of the package takes or returns one of them, and this reference does not describe them. + +## See Also + +- [cXml](cXml) class -- the **XPath** methods of a document +- [cXmlNode](cXmlNode) class -- the **XPath** methods of a node, and **Text** +- [mXml](mXml) module -- the node types and the error numbers +- [TBMANLIB_Xml](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB_Xml/mXml.md b/docs/Reference/Contributed/TBMANLIB_Xml/mXml.md index 126a6fee..80825c7a 100644 --- a/docs/Reference/Contributed/TBMANLIB_Xml/mXml.md +++ b/docs/Reference/Contributed/TBMANLIB_Xml/mXml.md @@ -58,7 +58,7 @@ Four flags whose names describe options of a parser. | **IgnoreProcessingInstructions** | 4 | | **ValidateOnParse** | 8 | -Nothing in version 1.0.0.0 of the package reads a **ParseOption**: [**Parse**](cXml#parse) and the other members that read XML take no such parameter. They always keep text that is only white space, always create nodes for comments and always skip processing instructions, and no member checks the text while it reads it. +Nothing in version 1.0.0.1 of the package reads a **ParseOption**: [**Parse**](cXml#parse) and the other members that read XML take no such parameter. They always leave out text that is only spaces, always keep text that has a line break in it, always create nodes for comments and always skip processing instructions, and no member checks the text while it reads it. ## Constants diff --git a/docs/Reference/Experimental.md b/docs/Reference/Experimental.md index 9048aa94..d6fe19b2 100644 --- a/docs/Reference/Experimental.md +++ b/docs/Reference/Experimental.md @@ -19,7 +19,7 @@ These packages are published through TWINSERV, as the [Contributed Packages](../ - [**TBMANLIB_ConsoleComponents**](../Contributed/TBMANLIB_ConsoleComponents/) -- text-interface components for console programs: progress bars, charts, tables and panels. Version 0.0.0.0 compiles, and its components cannot use a console object of the published TBMANLIB_ConsoleCore. - [**TBMANLIB_ConsoleCore**](../Contributed/TBMANLIB_ConsoleCore/) -- a console library with colours, UTF-8 and a full-screen mode. Version 1.0.0.1 compiles, and it reads keys wrongly and has several helpers that give wrong results. - [**TBMANLIB_Diagnostic**](../Contributed/TBMANLIB_Diagnostic/) -- a diagnostic report of the computer and its processes, as text, JSON or HTML. Version 0.0.0.0 compiles, and its text report, its **Dictionary** class and several of its list and file helpers fail or give wrong results. -- [**TBMANLIB_Xml**](../Contributed/TBMANLIB_Xml/) -- XML documents parsed, edited and written, in twinBASIC code. Version 1.0.0.0 compiles, and its parser misreads some common XML, such as self-closing elements and entities in text, and does not return for some malformed text. +- [**TBMANLIB_Xml**](../Contributed/TBMANLIB_Xml/) -- XML documents parsed, edited and written, in twinBASIC code. Version 1.0.0.1 compiles, and its parser misreads some common XML, such as self-closing elements and entities in text, and does not return for some malformed text. Its XPath engine reads some valid expressions as others, such as the step `..` and `and` with `or`. - [**tbRegExp**](../Contributed/tbRegExp/) -- regular expressions, in place of VBScript's **RegExp** object. Version 1.4.0.0 compiles, and with **Global** set to **True** it reports a match of no characters twice when the search skipped characters to reach it, so **Execute**, **Replace** and **Split** give wrong results for patterns such as `$`, `\b` and a lookahead. - [**WinReg**](../Contributed/WinReg/) -- registry keys and values, read, written, deleted and enumerated. Version 1.0.1.1 does not compile in twinBASIC BETA 1005, and three of its methods give wrong results: **ReadString** returns the text followed by NUL characters, **GetRegistryValueType** returns **REG_NONE** for a value that holds data, and one overload of **ConvertValueToString** finds no value. - [**xCalendar**](../Contributed/xCalendar/) -- a calendar control, in place of the Calendar control of `MSCAL.OCX`. Version 1.0.7.0 compiles, and has defects, among them some that can stop a program from responding. diff --git a/indexer/snapshots/contributed/TBMANLIB_Xml/api.json b/indexer/snapshots/contributed/TBMANLIB_Xml/api.json index fc24f605..f98f93f7 100644 --- a/indexer/snapshots/contributed/TBMANLIB_Xml/api.json +++ b/indexer/snapshots/contributed/TBMANLIB_Xml/api.json @@ -2,7 +2,7 @@ "format": 3, "package": "TBMANLIB_Xml", "project": "TBMANLIB_Xml", - "version": "1.0.0.0", + "version": "1.0.0.1", "types": [ { "name": "cXml", @@ -43,7 +43,7 @@ {"name":"RemoveAttribute","kind":"sub","vis":"public","signature":"Public Sub RemoveAttribute(ByVal xpath As String, ByVal attrName As String)"}, {"name":"RemoveElement","kind":"function","vis":"public","signature":"Public Function RemoveElement(ByVal xpath As String) As Boolean","returnType":"Boolean"}, {"name":"RootElement","kind":"property","vis":"public","signature":"Public Property Get RootElement() As cXmlNode","returnType":"cXmlNode","accessor":"Get"}, - {"name":"SaveToFile","kind":"function","vis":"public","signature":"Public Function SaveToFile(ByVal filePath As String, Optional ByVal withDeclaration As Boolean = True) As Boolean","returnType":"Boolean"}, + {"name":"SaveToFile","kind":"function","vis":"public","signature":"Public Function SaveToFile(ByVal filePath As String, Optional ByVal withDeclaration As Boolean = True, Optional ByVal prettyPrint As Boolean = False, Optional ByVal indent As String = \" \") As Boolean","returnType":"Boolean"}, {"name":"SelectElement","kind":"function","vis":"public","signature":"Public Function SelectElement(ByVal xpath As String) As cXmlNode","returnType":"cXmlNode"}, {"name":"SelectElements","kind":"function","vis":"public","signature":"Public Function SelectElements(ByVal tagName As String) As Collection","returnType":"Collection"}, {"name":"SerializeNodeCompact","kind":"sub","vis":"private","signature":"Private Sub SerializeNodeCompact(ByRef node As cXmlNode, ByRef sb As Collection)"}, @@ -61,6 +61,11 @@ {"name":"ValidateNode","kind":"function","vis":"private","signature":"Private Function ValidateNode(ByRef node As cXmlNode) As Boolean","returnType":"Boolean"}, {"name":"Version","kind":"property","vis":"public","signature":"Public Property Get Version() As String","returnType":"String","accessor":"Get"}, {"name":"Version","kind":"property","vis":"public","signature":"Public Property Let Version(ByVal value As String)","accessor":"Let"}, + {"name":"XPath","kind":"function","vis":"public","signature":"Public Function XPath(ByVal path As String) As Collection","returnType":"Collection"}, + {"name":"XPathAttribute","kind":"function","vis":"public","signature":"Public Function XPathAttribute(ByVal xpath As String, ByVal attrName As String) As String","returnType":"String"}, + {"name":"XPathFromRoot","kind":"function","vis":"public","signature":"Public Function XPathFromRoot(ByVal xpath As String) As Collection","returnType":"Collection"}, + {"name":"XPathSingle","kind":"function","vis":"public","signature":"Public Function XPathSingle(ByVal xpath As String) As cXmlNode","returnType":"cXmlNode"}, + {"name":"XPathText","kind":"function","vis":"public","signature":"Public Function XPathText(ByVal xpath As String) As String","returnType":"String"}, {"name":"XmlString","kind":"property","vis":"public","signature":"Public Property Get XmlString() As String","returnType":"String","accessor":"Get"}, {"name":"XmlString","kind":"property","vis":"public","signature":"Public Property Let XmlString(ByVal value As String)","accessor":"Let"}, {"name":"m_Document","kind":"field","vis":"private","signature":"Private m_Document As cXmlNode"}, @@ -92,7 +97,144 @@ {"name":"Parent","kind":"field","vis":"public","signature":"Public Parent As cXmlNode"}, {"name":"RemoveAttribute","kind":"sub","vis":"public","signature":"Public Sub RemoveAttribute(ByVal attrName As String)"}, {"name":"SetAttribute","kind":"sub","vis":"public","signature":"Public Sub SetAttribute(ByVal attrName As String, ByVal attrValue As String)"}, - {"name":"Value","kind":"field","vis":"public","signature":"Public Value As String"} + {"name":"Text","kind":"property","vis":"public","signature":"Public Property Get Text() As String","returnType":"String","accessor":"Get"}, + {"name":"Value","kind":"field","vis":"public","signature":"Public Value As String"}, + {"name":"XPath","kind":"function","vis":"public","signature":"Public Function XPath(ByVal path As String) As Collection","returnType":"Collection"}, + {"name":"XPathAttribute","kind":"function","vis":"public","signature":"Public Function XPathAttribute(ByVal xpath As String, ByVal attrName As String) As String","returnType":"String"}, + {"name":"XPathSingle","kind":"function","vis":"public","signature":"Public Function XPathSingle(ByVal xpath As String) As cXmlNode","returnType":"cXmlNode"}, + {"name":"XPathText","kind":"function","vis":"public","signature":"Public Function XPathText(ByVal xpath As String) As String","returnType":"String"} + ] + }, + { + "name": "mXPath", + "kind": "module", + "vis": "public", + "file": "Src/mXPath.twin", + "signature": "Public Module mXPath", + "members": [ + {"name":"ApplyPredicates","kind":"function","vis":"private","signature":"Private Function ApplyPredicates(ByRef candidates As Collection, ByRef step As XPathStep) As Collection","returnType":"Collection"}, + {"name":"ContainsNode","kind":"function","vis":"private","signature":"Private Function ContainsNode(ByRef coll As Collection, ByRef node As cXmlNode) As Boolean","returnType":"Boolean"}, + {"name":"EvaluateAttributePredicate","kind":"function","vis":"private","signature":"Private Function EvaluateAttributePredicate(ByRef node As cXmlNode, ByVal attrExpr As String) As Boolean","returnType":"Boolean"}, + {"name":"EvaluateContainsFunction","kind":"function","vis":"private","signature":"Private Function EvaluateContainsFunction(ByRef node As cXmlNode, ByVal predicate As String) As Boolean","returnType":"Boolean"}, + {"name":"EvaluatePredicate","kind":"sub","vis":"private","signature":"Private Sub EvaluatePredicate(ByRef candidates As Collection, ByVal predicate As String, ByRef result As Collection)"}, + {"name":"EvaluatePredicateForNode","kind":"function","vis":"private","signature":"Private Function EvaluatePredicateForNode(ByRef node As cXmlNode, ByVal predicate As String, ByVal position As Long, ByVal total As Long) As Boolean","returnType":"Boolean"}, + {"name":"EvaluateStartsWithFunction","kind":"function","vis":"private","signature":"Private Function EvaluateStartsWithFunction(ByRef node As cXmlNode, ByVal predicate As String) As Boolean","returnType":"Boolean"}, + {"name":"EvaluateStep","kind":"sub","vis":"private","signature":"Private Sub EvaluateStep(ByRef step As XPathStep, ByRef currentNodes As Collection, ByRef resultNodes As Collection)"}, + {"name":"EvaluateTextPredicate","kind":"function","vis":"private","signature":"Private Function EvaluateTextPredicate(ByRef node As cXmlNode, ByVal predicate As String) As Boolean","returnType":"Boolean"}, + {"name":"ExtractFunctionArg","kind":"function","vis":"private","signature":"Private Function ExtractFunctionArg(ByVal funcCall As String) As String","returnType":"String"}, + {"name":"ExtractQuotedValue","kind":"function","vis":"private","signature":"Private Function ExtractQuotedValue(ByVal str As String) As String","returnType":"String"}, + {"name":"FindCommaOutsideQuotes","kind":"function","vis":"private","signature":"Private Function FindCommaOutsideQuotes(ByVal str As String) As Long","returnType":"Long"}, + {"name":"FindMatchingBracket","kind":"function","vis":"private","signature":"Private Function FindMatchingBracket(ByVal str As String, ByVal openPos As Long) As Long","returnType":"Long"}, + {"name":"FindStepEnd","kind":"function","vis":"private","signature":"Private Function FindStepEnd(ByVal xpath As String, ByVal startPos As Long) As Long","returnType":"Long"}, + {"name":"GetChildCandidates","kind":"sub","vis":"private","signature":"Private Sub GetChildCandidates(ByRef parentNode As cXmlNode, ByVal nodeTest As String, ByRef candidates As Collection)"}, + {"name":"GetDescendantCandidates","kind":"sub","vis":"private","signature":"Private Sub GetDescendantCandidates(ByRef parentNode As cXmlNode, ByVal nodeTest As String, ByRef candidates As Collection)"}, + {"name":"GetNodeTextContent","kind":"function","vis":"private","signature":"Private Function GetNodeTextContent(ByRef node As cXmlNode) As String","returnType":"String"}, + {"name":"HasAttribute","kind":"function","vis":"private","signature":"Private Function HasAttribute(ByRef node As cXmlNode, ByVal attrName As String) As Boolean","returnType":"Boolean"}, + {"name":"InStrCaseInsensitive","kind":"function","vis":"private","signature":"Private Function InStrCaseInsensitive(ByVal str As String, ByVal search As String) As Long","returnType":"Long"}, + {"name":"MatchesNodeTest","kind":"function","vis":"private","signature":"Private Function MatchesNodeTest(ByRef node As cXmlNode, ByVal nodeTest As String) As Boolean","returnType":"Boolean"}, + {"name":"ParsePredicates","kind":"sub","vis":"private","signature":"Private Sub ParsePredicates(ByVal stepStr As String, ByVal startPos As Long, ByRef step As XPathStep)"}, + {"name":"ParseStep","kind":"function","vis":"private","signature":"Private Function ParseStep(ByVal stepStr As String, ByRef step As XPathStep, ByVal isDescendant As Boolean) As Boolean","returnType":"Boolean"}, + {"name":"ParseXPath","kind":"function","vis":"private","signature":"Private Function ParseXPath(ByVal xpath As String, ByRef steps() As XPathStep) As Long","returnType":"Long"}, + {"name":"SelectNodes","kind":"function","vis":"public","signature":"Public Function SelectNodes(ByRef contextNode As cXmlNode, ByVal xpath As String) As Collection","returnType":"Collection"}, + {"name":"SelectSingleNode","kind":"function","vis":"public","signature":"Public Function SelectSingleNode(ByRef contextNode As cXmlNode, ByVal xpath As String) As cXmlNode","returnType":"cXmlNode"} + ] + }, + { + "name": "XPathAxisType", + "kind": "enum", + "in": "mXPath", + "vis": "public", + "file": "Src/mXPath.twin", + "signature": "Public Enum XPathAxisType", + "values": [ + {"name":"AxisChild","value":"0"}, + {"name":"AxisDescendant","value":"1"}, + {"name":"AxisParent","value":"2"}, + {"name":"AxisSelf","value":"3"}, + {"name":"AxisAttribute","value":"4"} + ] + }, + { + "name": "XPathOperator", + "kind": "enum", + "in": "mXPath", + "vis": "public", + "file": "Src/mXPath.twin", + "signature": "Public Enum XPathOperator", + "values": [ + {"name":"OpNone","value":"0"}, + {"name":"OpEquals","value":"1"}, + {"name":"OpNotEquals","value":"2"}, + {"name":"OpLessThan","value":"3"}, + {"name":"OpGreaterThan","value":"4"}, + {"name":"OpLessOrEqual","value":"5"}, + {"name":"OpGreaterOrEqual","value":"6"}, + {"name":"OpExists","value":"7"} + ] + }, + { + "name": "XPathPredicate", + "kind": "type", + "in": "mXPath", + "vis": "public", + "file": "Src/mXPath.twin", + "signature": "Public Type XPathPredicate", + "fields": [ + {"name":"PredicateType","vis":"public","signature":"PredicateType As Long"}, + {"name":"AttributeName","vis":"public","signature":"AttributeName As String"}, + {"name":"Operator","vis":"public","signature":"Operator As XPathOperator"}, + {"name":"CompareValue","vis":"public","signature":"CompareValue As String"}, + {"name":"IndexValue","vis":"public","signature":"IndexValue As Long"}, + {"name":"FunctionName","vis":"public","signature":"FunctionName As String"} + ], + "members": [] + }, + { + "name": "XPathStep", + "kind": "type", + "in": "mXPath", + "vis": "public", + "file": "Src/mXPath.twin", + "signature": "Public Type XPathStep", + "fields": [ + {"name":"Axis","vis":"public","signature":"Axis As XPathAxisType"}, + {"name":"NodeTest","vis":"public","signature":"NodeTest As String"}, + {"name":"Predicates","vis":"public","signature":"Predicates() As String"}, + {"name":"PredicateCount","vis":"public","signature":"PredicateCount As Long"} + ], + "members": [] + }, + { + "name": "XPathTokenType", + "kind": "enum", + "in": "mXPath", + "vis": "public", + "file": "Src/mXPath.twin", + "signature": "Public Enum XPathTokenType", + "values": [ + {"name":"TokenNone","value":"0"}, + {"name":"TokenSlash","value":"1"}, + {"name":"TokenDoubleSlash","value":"2"}, + {"name":"TokenDot","value":"3"}, + {"name":"TokenDoubleDot","value":"4"}, + {"name":"TokenStar","value":"5"}, + {"name":"TokenAt","value":"6"}, + {"name":"TokenLeftBracket","value":"7"}, + {"name":"TokenRightBracket","value":"8"}, + {"name":"TokenLeftParen","value":"9"}, + {"name":"TokenRightParen","value":"10"}, + {"name":"TokenEquals","value":"11"}, + {"name":"TokenNotEquals","value":"12"}, + {"name":"TokenLessThan","value":"13"}, + {"name":"TokenGreaterThan","value":"14"}, + {"name":"TokenLessOrEqual","value":"15"}, + {"name":"TokenGreaterOrEqual","value":"16"}, + {"name":"TokenName","value":"17"}, + {"name":"TokenNumber","value":"18"}, + {"name":"TokenString","value":"19"}, + {"name":"TokenFunction","value":"20"}, + {"name":"TokenAnd","value":"21"}, + {"name":"TokenOr","value":"22"} ] }, { @@ -195,6 +337,14 @@ {"name":"TestToStringPretty","kind":"sub","vis":"public","signature":"Public Sub TestToStringPretty()"}, {"name":"TestToStringWithDeclaration","kind":"sub","vis":"public","signature":"Public Sub TestToStringWithDeclaration()"}, {"name":"TestValidate","kind":"sub","vis":"public","signature":"Public Sub TestValidate()"}, + {"name":"TestXPathAttribute","kind":"sub","vis":"public","signature":"Public Sub TestXPathAttribute()"}, + {"name":"TestXPathBasic","kind":"sub","vis":"public","signature":"Public Sub TestXPathBasic()"}, + {"name":"TestXPathDescendant","kind":"sub","vis":"public","signature":"Public Sub TestXPathDescendant()"}, + {"name":"TestXPathFunctions","kind":"sub","vis":"public","signature":"Public Sub TestXPathFunctions()"}, + {"name":"TestXPathIndex","kind":"sub","vis":"public","signature":"Public Sub TestXPathIndex()"}, + {"name":"TestXPathLogical","kind":"sub","vis":"public","signature":"Public Sub TestXPathLogical()"}, + {"name":"TestXPathNodeMethod","kind":"sub","vis":"public","signature":"Public Sub TestXPathNodeMethod()"}, + {"name":"TestXPathWildcard","kind":"sub","vis":"public","signature":"Public Sub TestXPathWildcard()"}, {"name":"m_TestsFailed","kind":"field","vis":"private","signature":"Private m_TestsFailed As Long"}, {"name":"m_TestsPassed","kind":"field","vis":"private","signature":"Private m_TestsPassed As Long"}, {"name":"m_TestsRun","kind":"field","vis":"private","signature":"Private m_TestsRun As Long"} diff --git a/test/example-projects/contributed-TBMANLIB_Xml/Settings b/test/example-projects/contributed-TBMANLIB_Xml/Settings index 141e59f8..81fe0b7c 100644 --- a/test/example-projects/contributed-TBMANLIB_Xml/Settings +++ b/test/example-projects/contributed-TBMANLIB_Xml/Settings @@ -55,7 +55,7 @@ "path32": "/Packages/TBMANLIB_Xml", "path64": "/Packages/TBMANLIB_Xml", "symbolId": "TBMANLIB_Xml", - "versionBuild": "0", + "versionBuild": "1", "versionMajor": "1", "versionMinor": "0", "versionRevision": "0" From 47f7cac5646a3be5b8c4351dca64c9592f9f5016 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 13 Oct 2026 18:50:36 +0200 Subject: [PATCH 2/9] tbFileSysTools: moved to 1.8.0.0; IsFileLocked, FileContainsText, FindInTextFiles, File.ContainsText and File.IsLocked; encoding detection, error numbers, junctions, Item and GetDrive brought up to date --- .../Contributed/tbFileSysTools/Drives.md | 12 +- .../Contributed/tbFileSysTools/File.md | 58 +++- .../tbFileSysTools/FileSystemObject.md | 47 ++- .../tbFileSysTools/FileSystemTools.md | 282 +++++++++++++---- .../Contributed/tbFileSysTools/Files.md | 4 +- .../Contributed/tbFileSysTools/Folder.md | 4 +- .../Contributed/tbFileSysTools/Folders.md | 2 +- .../Contributed/tbFileSysTools/index.md | 65 ++-- .../contributed/tbFileSysTools/api.json | 293 ++++++++++++++++-- .../contributed-tbFileSysTools/Settings | 4 +- 10 files changed, 642 insertions(+), 129 deletions(-) diff --git a/docs/Reference/Contributed/tbFileSysTools/Drives.md b/docs/Reference/Contributed/tbFileSysTools/Drives.md index f23c0a58..96682097 100644 --- a/docs/Reference/Contributed/tbFileSysTools/Drives.md +++ b/docs/Reference/Contributed/tbFileSysTools/Drives.md @@ -32,13 +32,15 @@ Debug.Print ds(1).Path = ds.Item(1).Path ' True ```tb check_build check_run Dim ds As Drives, d As Drive Set ds = GetDrives() +Debug.Print ds("c:").Path ' C: +Debug.Print ds("C:\").Path ' C: On Error Resume Next -Set d = ds("C:") +Set d = ds("1") Debug.Print Err.Number ' 5 Err.Clear Set d = ds(0) -Debug.Print Err.Number ' 9 +Debug.Print Err.Number ' 5 Err.Clear On Error GoTo 0 ``` @@ -60,11 +62,11 @@ One drive of the collection. This is the default member. Syntax: *object*.**Item** ( *letterOrIndex* ) **As Drive** *letterOrIndex* -: *required* A **Variant**: the drive letter alone, such as `"C"`, without a colon, or the position of the drive, counted from 1. +: *required* A **Variant**: a drive letter such as `"C"`, or the position of the drive, counted from 1. A string is read by its first character, so `"C"`, `"c"`, `"C:"` and `"C:\"` all name the drive C. -The drives are in the order of their letters, and the letter is not case-sensitive. A letter that is not in the collection raises error 5, and so does `"C:"`, a letter with a colon. A position below 1 or above [**Count**](#count) raises error 9 (*Subscript out of range*). +The drives are in the order of their letters, and the letter is not case-sensitive. A letter that is not in the collection, and a position below 1 or above [**Count**](#count), raise error 5 (*Invalid procedure call or argument*), with the message *No drive 'x' in the collection*. -The Scripting Runtime accepts `"C:"`, raises error 68 (*Device unavailable*) for a letter that is not in use, and raises error 5 for a position. +The Scripting Runtime raises error 68 (*Device unavailable*) for a letter that is not in use, and error 5 for a position. ### NewEnum {: .no_toc } diff --git a/docs/Reference/Contributed/tbFileSysTools/File.md b/docs/Reference/Contributed/tbFileSysTools/File.md index 73a6fec5..a0580ef3 100644 --- a/docs/Reference/Contributed/tbFileSysTools/File.md +++ b/docs/Reference/Contributed/tbFileSysTools/File.md @@ -8,7 +8,7 @@ has_toc: false # File class {: .no_toc } -A **File** represents one file on disk: its name, size, dates and attributes, and operations to copy, move, rename and delete it, to open it as a [**TextStream**](TextStream) and to read its content. It has the members of the **File** of the Scripting Runtime, and adds [**Encoding**](#encoding), [**LineEnding**](#lineending), [**Normalize**](#normalize), [**Refresh**](#refresh), [**HasAttribute**](#hasattribute), [**SetAttribute**](#setattribute), [**ToString**](#tostring), [**ToStream**](#tostream), [**ExtensionName**](#extensionname) and [**Version**](#version). +A **File** represents one file on disk: its name, size, dates and attributes, and operations to copy, move, rename and delete it, to open it as a [**TextStream**](TextStream) and to read its content. It has the members of the **File** of the Scripting Runtime, and adds [**ContainsText**](#containstext), [**Encoding**](#encoding), [**IsLocked**](#islocked), [**LineEnding**](#lineending), [**Normalize**](#normalize), [**Refresh**](#refresh), [**HasAttribute**](#hasattribute), [**SetAttribute**](#setattribute), [**ToString**](#tostring), [**ToStream**](#tostream), [**ExtensionName**](#extensionname) and [**Version**](#version). A **File** is not created with **New**. [**GetFile**](FileSystemTools#getfile) returns one, and so does the [**Files**](Files) collection of a [**Folder**](Folder#files). @@ -46,8 +46,8 @@ A **File** keeps only the path of the file. Each member that reads a property lo The price is that every read costs a call to Windows. Read a value once and keep it, rather than reading it in a loop. These cost more than the rest: -- [**Encoding**](#encoding) reads the first 64 KB of the file. -- [**LineEnding**](#lineending) reads the whole file. +- [**Encoding**](#encoding) reads the first 32 KB of the file. +- [**ContainsText**](#containstext) and [**LineEnding**](#lineending) read the whole file. - [**Version**](#version) reads the version resource of the file. - [**Type**](#type) asks the Windows shell. @@ -107,12 +107,9 @@ Returns the [**Drive**](Drive) of the path, for a network path the share. The text encoding of the file. -Syntax: *object*.**Encoding** [ ( *sampleBytes* ) ] **As TextEncoding** +Syntax: *object*.**Encoding** **As TextEncoding** -*sampleBytes* -: *optional* A **Long**, the number of bytes at the start of the file to examine. Default: 65536. - -The same as [**GetFileEncoding**](FileSystemTools#getfileencoding): a [**TextEncoding**](FileSystemTools#textencoding) member, negative when the file starts with a BOM, and **encUnknown** for an empty or binary file. The file is read on each call. +The same as [**GetFileEncoding**](FileSystemTools#getfileencoding): a [**TextEncoding**](FileSystemTools#textencoding) member, negative when the file starts with a BOM, and **encUnknown** for an empty or binary file. The first 32 KB of the file are read on each call. ### ExtensionName {: .no_toc } @@ -221,6 +218,36 @@ The same as [**GetFileVersion**](FileSystemTools#getfileversion): an empty strin ## Methods +### ContainsText +{: .no_toc } + +Whether the file holds a piece of text. + +Syntax: *object*.**ContainsText** ( *searchText* [, *ignoreCase* ] ) **As Boolean** + +*searchText* +: *required* A **String**, the text to look for. It must not be empty. Every character stands for itself: `*` and `?` are not wildcards. + +*ignoreCase* +: *optional* A **Boolean**. **True** (default) ignores the letter case of the text. **False** compares the characters exactly. + +The same as [**FileContainsText**](FileSystemTools#filecontainstext) for the path of the file: **True** when the text of the file, read in the encoding that [**Encoding**](#encoding) finds, holds *searchText*. **False** for an empty file and for a file that looks like binary data. Raises error 5 when *searchText* is empty. + +```tb check_build check_run +Dim work As String, f As File +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work +StringToTextFile "Hello World", BuildPath(work, "a.txt") +Set f = GetFile(BuildPath(work, "a.txt")) + +Debug.Print f.ContainsText("world") ' True +Debug.Print f.ContainsText("world", False) ' False +Debug.Print f.IsLocked ' False +Debug.Print f.Encoding = encUtf8 ' True + +DeleteFolder work +``` + ### Copy {: .no_toc } @@ -248,6 +275,15 @@ Syntax: *object*.**Delete** ( [ *force* ] ) See [**DeleteFile**](FileSystemTools#deletefile). The object cannot be used afterwards: its members raise error 53. +### IsLocked +{: .no_toc } + +Whether the file is in use. + +Syntax: *object*.**IsLocked** **As Boolean** + +The same as [**IsFileLocked**](FileSystemTools#isfilelocked) for the path of the file: **True** when the file cannot be opened for reading and writing with exclusive access at this moment, because a program has it open or because it is read-only. The answer holds for the instant of the call only. Raises error 53 when the file does not exist. + ### Move {: .no_toc } @@ -265,9 +301,9 @@ See [**MoveFile**](FileSystemTools#movefile) for the rules and errors. The objec Rewrites a text file with a chosen encoding and line ending. -Syntax: *object*.**Normalize** ( [ *toEncoding* [, *toLineEnding* [, *fromEncoding* [, *toAttributes* [, *skipIfUnchanged* ] ] ] ] ] ) **As Boolean** +Syntax: *object*.**Normalize** ( [ *toEncoding* [, *toLineEnding* [, *fromEncoding* [, *toAttributes* ] ] ] ] ) **As Boolean** -The arguments, their defaults and the result are those of [**NormalizeTextFile**](FileSystemTools#normalizetextfile), where *toLineEnding* is called *toNewLine*: **encUtf8**, **nlWindows**, **encAutoDetect**, **faNoChange** and **True**. Returns **True** when the file was changed. The object reads the file again afterwards. +The arguments, their defaults and the result are those of [**NormalizeTextFile**](FileSystemTools#normalizetextfile), where *toLineEnding* is called *toNewLine*: **encUtf8**, **nlWindows**, **encAutoDetect** and **faNoChange**. Returns **True** when the file was changed. The object reads the file again afterwards. ### OpenAsTextStream {: .no_toc } @@ -325,7 +361,7 @@ Syntax: *object*.**SetAttribute** ( *attr*, *state* ) : *required* A **Boolean**. **True** sets the attribute and **False** clears it. The other attributes stay as they are. > [!WARNING] -> In version 1.1.1.0, **SetAttribute** sets nothing, and raises no error, on a file that has no attribute at all, because [**Attributes**](#attributes) then reports **faNormal**, and **faNormal** wins over the attribute being set. A new file has the archive attribute, so it is a file from which the program cleared every attribute that this applies to. To give such a file an attribute, assign [**Attributes**](#attributes). The same method of a [**Folder**](Folder#setattribute) is not affected. +> In version 1.8.0.0, **SetAttribute** sets nothing, and raises no error, on a file that has no attribute at all, because [**Attributes**](#attributes) then reports **faNormal**, and **faNormal** wins over the attribute being set. A new file has the archive attribute, so it is a file from which the program cleared every attribute that this applies to. To give such a file an attribute, assign [**Attributes**](#attributes). The same method of a [**Folder**](Folder#setattribute) is not affected. ```tb check_build check_run Dim work As String, f As File diff --git a/docs/Reference/Contributed/tbFileSysTools/FileSystemObject.md b/docs/Reference/Contributed/tbFileSysTools/FileSystemObject.md index 83333048..f7148bae 100644 --- a/docs/Reference/Contributed/tbFileSysTools/FileSystemObject.md +++ b/docs/Reference/Contributed/tbFileSysTools/FileSystemObject.md @@ -86,7 +86,7 @@ Syntax: *object*.**DeleteFile** ( *fileSpec* [, *force* [, *ignoreMissing* ] ] ) Syntax: *object*.**DeleteFolder** ( *folderSpec* [, *force* [, *ignoreMissing* ] ] ) -*folderSpec* is *folderPath* in the module. See [**DeleteFolder**](FileSystemTools#deletefolder), including its warning about links. +*folderSpec* is *folderPath* in the module. See [**DeleteFolder**](FileSystemTools#deletefolder), including its note about links. ### GetFilePaths {: .no_toc } @@ -135,7 +135,7 @@ See [**CleanFileName**](FileSystemTools#cleanfilename). ### GetAbsolutePathName {: .no_toc } -Syntax: *object*.**GetAbsolutePathName** ( *path* [, *baseDirectory* [, *useLongPathPrefix* ] ] ) **As String** +Syntax: *object*.**GetAbsolutePathName** ( *path* [, *baseDirectory* ] ) **As String** See [**GetAbsolutePathName**](FileSystemTools#getabsolutepathname). @@ -239,7 +239,7 @@ See [**SetCurrentDir**](FileSystemTools#setcurrentdir). ### GetFileEncoding {: .no_toc } -Syntax: *object*.**GetFileEncoding** ( *filePath* [, *sampleBytes* ] ) **As TextEncoding** +Syntax: *object*.**GetFileEncoding** ( *filePath* ) **As TextEncoding** See [**GetFileEncoding**](FileSystemTools#getfileencoding). @@ -264,6 +264,13 @@ Syntax: *object*.**GetFileVersion** ( *fileName* [, *source* ] ) **As String** *fileName* is *filePath* in the module. See [**GetFileVersion**](FileSystemTools#getfileversion). +### IsFileLocked +{: .no_toc } + +Syntax: *object*.**IsFileLocked** ( *filePath* ) **As Boolean** + +See [**IsFileLocked**](FileSystemTools#isfilelocked). + ## Objects ### Drives @@ -313,7 +320,7 @@ See [**MergeTextFiles**](FileSystemTools#mergetextfiles). ### NormalizeTextFile {: .no_toc } -Syntax: *object*.**NormalizeTextFile** ( *filePath* [, *toEncoding* [, *toNewLine* [, *fromEncoding* [, *toAttributes* [, *skipIfUnchanged* ] ] ] ] ] ) **As Boolean** +Syntax: *object*.**NormalizeTextFile** ( *filePath* [, *toEncoding* [, *toNewLine* [, *fromEncoding* [, *toAttributes* ] ] ] ] ) **As Boolean** See [**NormalizeTextFile**](FileSystemTools#normalizetextfile). @@ -352,6 +359,38 @@ Syntax: *object*.**WriteStream** ( *stream*(), *filePath* ) See [**WriteStream**](FileSystemTools#writestream). +## Searching text + +```tb check_build check_run +Dim fso As New FileSystemObject +Dim work As String, hits As Collection +work = fso.BuildPath(Environ$("TEMP"), fso.GetTempName("fst", "")) +fso.CreateFolder work +fso.StringToTextFile "find me", fso.BuildPath(work, "a.txt") +fso.StringToTextFile "other", fso.BuildPath(work, "b.txt") + +Debug.Print fso.FileContainsText(fso.BuildPath(work, "a.txt"), "FIND") ' True +Set hits = fso.FindInTextFiles(work, "find") +Debug.Print hits.Count ' 1 +Debug.Print fso.IsFileLocked(fso.BuildPath(work, "a.txt")) ' False + +fso.DeleteFolder work +``` + +### FileContainsText +{: .no_toc } + +Syntax: *object*.**FileContainsText** ( *filePath*, *searchText* [, *ignoreCase* ] ) **As Boolean** + +See [**FileContainsText**](FileSystemTools#filecontainstext). + +### FindInTextFiles +{: .no_toc } + +Syntax: *object*.**FindInTextFiles** ( *folderPath*, *searchText* [, *filePattern* [, *recursive* [, *ignoreCase* [, *fileSizeCapInMb* ] ] ] ] ) **As Collection** + +See [**FindInTextFiles**](FileSystemTools#findintextfiles). + ## Text streams ### CreateTextFile diff --git a/docs/Reference/Contributed/tbFileSysTools/FileSystemTools.md b/docs/Reference/Contributed/tbFileSysTools/FileSystemTools.md index fe3f182a..af7100a2 100644 --- a/docs/Reference/Contributed/tbFileSysTools/FileSystemTools.md +++ b/docs/Reference/Contributed/tbFileSysTools/FileSystemTools.md @@ -33,9 +33,10 @@ The procedures, by purpose: - Existence: [DriveExists](#driveexists), [FileExists](#fileexists), [FolderExists](#folderexists) - Files and folders: [CopyFile](#copyfile), [CopyFolder](#copyfolder), [CreateFolder](#createfolder), [DeleteFile](#deletefile), [DeleteFolder](#deletefolder), [GetFilePaths](#getfilepaths), [MoveFile](#movefile), [MoveFolder](#movefolder), [Rename](#rename) - Current directory and special folders: [GetCurrentDir](#getcurrentdir), [GetSpecialFolder](#getspecialfolder), [SetCurrentDir](#setcurrentdir) -- File information: [GetFileEncoding](#getfileencoding), [GetFileLineEnding](#getfilelineending), [GetFileType](#getfiletype), [GetFileVersion](#getfileversion) +- File information: [GetFileEncoding](#getfileencoding), [GetFileLineEnding](#getfilelineending), [GetFileType](#getfiletype), [GetFileVersion](#getfileversion), [IsFileLocked](#isfilelocked) - Objects: [GetDrive](#getdrive), [GetDrives](#getdrives), [GetFile](#getfile), [GetFolder](#getfolder) - Whole files: [ArrayToTextFile](#arraytotextfile), [MergeTextFiles](#mergetextfiles), [NormalizeTextFile](#normalizetextfile), [ReadStream](#readstream), [StringToTextFile](#stringtotextfile), [TextFileToArray](#textfiletoarray), [TextFileToString](#textfiletostring), [WriteStream](#writestream) +- Searching text: [FileContainsText](#filecontainstext), [FindInTextFiles](#findintextfiles) - Text streams: [CreateTextFile](#createtextfile), [GetStandardStream](#getstandardstream), [OpenTextFile](#opentextfile) The enumerations that the procedures take are described [at the end of the page](#enumerations): [TextEncoding](#textencoding), [NewlineType](#newlinetype), [IOMode](#iomode), [SpecialFolderConst](#specialfolderconst), [FileAttribute](#fileattribute), [FileVersionSource](#fileversionsource) and [StandardStreamTypes](#standardstreamtypes). @@ -77,7 +78,7 @@ DeleteFolder work **Wildcards.** [**CopyFile**](#copyfile), [**CopyFolder**](#copyfolder), [**DeleteFile**](#deletefile), [**DeleteFolder**](#deletefolder), [**MoveFile**](#movefile) and [**MoveFolder**](#movefolder) accept `*` and `?` in the last component of the path that names what to act on. A wildcard in a destination is error 5. A pattern that matches nothing is an error, as described for each procedure. A failure part-way through a wildcard operation leaves the earlier matches changed; the sample under [**DeleteFile**](#deletefile) shows it. > [!WARNING] -> Windows matches a pattern against the 8.3 short name of a file as well as against its name. On a volume that creates short names, `*.txt` also matches `a.txtx`, whose short name ends in `.TXT`. In version 1.1.1.0 [**GetFilePaths**](#getfilepaths) lists such a file for `*.txt`, and [**DeleteFile**](#deletefile) deletes it. The Scripting Runtime does the same. +> Windows matches a pattern against the 8.3 short name of a file as well as against its name. On a volume that creates short names, `*.txt` also matches `a.txtx`, whose short name ends in `.TXT`. [**CopyFile**](#copyfile), [**DeleteFile**](#deletefile) and [**MoveFile**](#movefile) act on such a file, and the procedures for folders match the short names of folders in the same way. The Scripting Runtime does the same. [**GetFilePaths**](#getfilepaths) does not list such a file: it tests each name against the pattern again. ```tb check_build check_run Dim work As String @@ -88,23 +89,30 @@ StringToTextFile "2", BuildPath(work, "a.txtx") ' The 8.3 name of a.txtx is different from its name, and it ends in .TXT. Debug.Print GetFile(BuildPath(work, "a.txtx")).ShortName <> "a.txtx" ' True -Debug.Print GetFilePaths(work, "*.txt").Count ' 2 +Debug.Print GetFilePaths(work, "*.txt").Count ' 1 DeleteFile BuildPath(work, "*.txt") Debug.Print GetFilePaths(work).Count ' 0 DeleteFolder work ``` -**Errors.** A procedure raises a run-time error with the number VB6 uses for the same failure. The error numbers, and the Windows failures that map to them, are in [Errors](./#errors), which also lists the errors that version 1.1.1.0 raises where the Scripting Runtime raises another. **Err.Source** is the name of the operation, such as `CopyFile`, and **Err.Description** names the path; the errors of [**OpenTextFile**](#opentextfile) and its relatives have an empty **Err.Source** and only the standard text of the number. +**Errors.** A procedure raises a run-time error with the number VB6 uses for the same failure. The error numbers, and the Windows failures that map to them, are in [Errors](./#errors), which also lists a few errors whose numbers are not the expected ones. **Err.Source** is the name of the operation, such as `CopyFile`, and **Err.Description** names the path; the errors of [**OpenTextFile**](#opentextfile) and its relatives have an empty **Err.Source** and only the standard text of the number. **Encodings.** A text procedure takes a [**TextEncoding**](#textencoding) member. Its value is a Windows code page; a negative value is the same code page with a byte-order mark (BOM). On reading, **encAutoDetect** means: find the encoding from the first bytes of the file. On writing, **encAutoDetect** means **encUtf8**. A BOM that the file holds always decides the encoding on reading, whatever the argument says. ### Junctions and symbolic links {: .no_toc } -A directory junction or symbolic link is a folder that stands for another folder. In version 1.1.1.0, [**CopyFolder**](#copyfolder), [**DeleteFolder**](#deletefolder) and [**GetFilePaths**](#getfilepaths) with *recursive* enter one like an ordinary folder. [**Folder.Size**](Folder#size) does not: it skips the link, so each file is counted once, where it is stored. The Scripting Runtime enters a link when it adds up the size, so it counts a target twice when the target is in the same tree. +A directory junction or symbolic link is a folder that stands for another folder. [**CopyFolder**](#copyfolder), [**DeleteFolder**](#deletefolder), [**GetFilePaths**](#getfilepaths) with *recursive*, [**FindInTextFiles**](#findintextfiles) with *recursive* and [**Folder.Size**](Folder#size) do not enter one: -The sample builds a tree with a link to one of its own folders, and a second tree with a link to a folder outside it. **DeleteFolder** of the second tree deletes the file in the outside folder. +- **CopyFolder** leaves the link out of the copy. +- **DeleteFolder** removes the link and leaves the folder it stands for, with its files, as it was. +- **GetFilePaths** lists no file below the link. +- **Size** counts each file once, where it is stored. + +The link is still a member of the [**SubFolders**](Folder#subfolders) of its folder. The Scripting Runtime enters a link in all of these. It counts a target twice when the target is in the same tree, copies the files of the target into the copy under the name of the link, and deletes the files of the target. + +The sample builds a tree with a link to one of its own folders, and a second tree with a link to a folder outside it. **DeleteFolder** of the second tree leaves the file in the outside folder. ```tb check_build check_run Dim work As String, tree As String, sh As Object @@ -118,10 +126,11 @@ sh.Run "cmd /c mklink /J """ & BuildPath(tree, "link") & """ """ & BuildPath(tre Debug.Print GetFolder(tree).Size ' 1500 Debug.Print CreateObject("Scripting.FileSystemObject").GetFolder(tree).Size ' 2500 -Debug.Print GetFilePaths(tree, , , , True).Count ' 3 +Debug.Print GetFilePaths(tree, , , , True).Count ' 2 +Debug.Print GetFolder(tree).SubFolders.Count ' 2 CopyFolder tree, BuildPath(work, "copy") -Debug.Print FileExists(BuildPath(work, "copy\link\file2.txt")) ' True -sh.Run "cmd /c rmdir """ & BuildPath(tree, "link") & """", 0, True +Debug.Print FolderExists(BuildPath(work, "copy\link")) ' False +Debug.Print FileExists(BuildPath(work, "copy\sub\file2.txt")) ' True Dim target As String, tree2 As String target = BuildPath(work, "target") @@ -133,7 +142,7 @@ sh.Run "cmd /c mklink /J """ & BuildPath(tree2, "link") & """ """ & target & """ DeleteFolder tree2, True Debug.Print FolderExists(tree2) ' False Debug.Print FolderExists(target) ' True -Debug.Print FileExists(BuildPath(target, "keep.txt")) ' False +Debug.Print FileExists(BuildPath(target, "keep.txt")) ' True DeleteFolder work ``` @@ -197,7 +206,7 @@ Debug.Print CleanFileName(".gitignore") ' .gitignore Returns the full path for a path specification. -Syntax: **GetAbsolutePathName** ( *path* [, *baseDirectory* [, *useLongPathPrefix* ] ] ) **As String** +Syntax: **GetAbsolutePathName** ( *path* [, *baseDirectory* ] ) **As String** *path* : *required* A **String** holding an absolute or relative path. The path does not have to exist. @@ -205,22 +214,23 @@ Syntax: **GetAbsolutePathName** ( *path* [, *baseDirectory* [, *useLongPathPrefi *baseDirectory* : *optional* A **String** holding the folder against which a relative *path* is resolved. Default: an empty string, which means the current directory. An absolute *path* ignores it. A *path* of the form `C:name` (a drive without a backslash) uses it only when *baseDirectory* is on the same drive. -*useLongPathPrefix* -: *optional* A **Boolean**. When **True**, the result starts with `\\?\`, or with `\\?\UNC\` for a network path. Default: **False**. - -Returns the path with `.` and `..` resolved and forward slashes turned into backslashes. The work is done on the text, and the disk is not read. A backslash at the end is removed, except for a drive root, which keeps it and gets an uppercase drive letter: `c:\` becomes `C:\`. The rest of the path keeps the letter case it was given. +Returns the path with `.` and `..` resolved and forward slashes turned into backslashes. The work is done on the text, and the disk is not read. A backslash at the end is removed, except for a drive root, which keeps it and gets an uppercase drive letter: `c:\` becomes `C:\`. The rest of the path keeps the letter case it was given. A path that starts with two backslashes names a network share, and `..` cannot climb above the share: `\\srv\sh\..\x` is `\\srv\sh\x`. The network is not contacted. Raises error 52 when Windows cannot form a full path from the text. An empty string is such a case: `GetAbsolutePathName("")` raises error 52, where the Scripting Runtime returns the current directory. `GetAbsolutePathName(".")` returns the current directory in both. ```tb check_build check_run Debug.Print GetAbsolutePathName("a\..\b", "C:\base") ' C:\base\b +Debug.Print GetAbsolutePathName("..\x", "C:\base\sub") ' C:\base\x Debug.Print GetAbsolutePathName("\x", "D:\base\deep") ' D:\x Debug.Print GetAbsolutePathName("C:name", "C:\base") ' C:\base\name Debug.Print GetAbsolutePathName("c:\") ' C:\ Debug.Print GetAbsolutePathName("C:\Temp\") ' C:\Temp Debug.Print GetAbsolutePathName("C:/a/./b/../c") ' C:\a\c -Debug.Print GetAbsolutePathName("C:\x", , True) ' \\?\C:\x -Debug.Print GetAbsolutePathName("\\srv\sh\f", , True) ' \\?\UNC\srv\sh\f +Debug.Print GetAbsolutePathName("C:\a\..\..") ' C:\ +Debug.Print GetAbsolutePathName("\\srv\sh\f\..\g") ' \\srv\sh\g +Debug.Print GetAbsolutePathName("\\srv\sh\..\..\x") ' \\srv\sh\x +Debug.Print GetAbsolutePathName("..\x", "\\srv\sh\dir") ' \\srv\sh\x +Debug.Print GetAbsolutePathName("C:\x", "D:\base") ' C:\x ``` ### GetBaseName @@ -336,7 +346,7 @@ Both paths are first made absolute with [**GetAbsolutePathName**](#getabsolutepa Returns the absolute *targetPath* when there is no relative path, for example when the two paths are on different drives, and when only *targetPath* is longer than 260 characters. > [!WARNING] -> In version 1.1.1.0, when *basePath* is 260 characters or longer, the result can be a relative path that leads to the wrong place, and no error is raised. For a target `x\y.txt` below such a base, the result starts with `..\` and the last name of the base, instead of being `x\y.txt`. A base path of 258 characters or fewer gives the right result. +> In version 1.8.0.0, when *basePath* is 260 characters or longer, the result can be a relative path that leads to the wrong place, and no error is raised. For a target `x\y.txt` below such a base, the result starts with `..\` and the last name of the base, instead of being `x\y.txt`. A base path of 258 characters or fewer gives the right result. ```tb check_build check_run Debug.Print GetRelativePath("C:\Projects\App", "C:\Projects\Lib\core.txt") ' ..\Lib\core.txt @@ -435,6 +445,9 @@ Errors: - 76 when the folder of *destination* does not exist. - 58 when the destination file exists and *overwrite* is **False**. - 70 when Windows refuses: a destination file that is read-only, and a *source* that is a folder. +- 52 when the name of the destination file holds a character that Windows does not allow, or is a reserved device name such as `nul`. + +Copying a file onto itself does nothing and raises no error when *overwrite* is **True**, and is error 58 when it is **False**. The Scripting Runtime raises the same numbers for a missing source, an existing destination with *overwrite* **False**, and a read-only destination. @@ -481,8 +494,8 @@ Errors: - 5 when *destination* is the source or lies inside it, which would copy the folder into itself. - 58 when a file exists and *overwrite* is **False**. -> [!WARNING] -> In version 1.1.1.0, a directory junction or symbolic link inside the source is copied as if it were an ordinary folder, so its target is copied under the name of the link. See [Junctions and symbolic links](#junctions-and-symbolic-links). +> [!NOTE] +> A directory junction or symbolic link inside the source is left out of the copy: the copy has no folder of that name, and nothing from the folder the link stands for. The Scripting Runtime copies the files of the target into the copy under the name of the link. See [Junctions and symbolic links](#junctions-and-symbolic-links). ### CreateFolder {: .no_toc } @@ -504,6 +517,7 @@ Errors: - 58 when a folder, or a file, with that path exists. - 76 when the parent does not exist and *createIntermediates* is **False**. - 52 when the last component holds a character that is not allowed in a name, the wildcards `*` and `?` among them. +- 75 when the last component is a reserved device name such as `con` or `nul` (the message ends with the Windows code 87). ```tb check_build check_run Dim work As String @@ -587,8 +601,8 @@ The contents are deleted first, and an item that cannot be deleted is skipped wi Errors: 76 when no folder matches or a file is named; 75 when the folder is not empty because something in it could not be deleted, such as a read-only file when *force* is **False** (the message ends with the Windows code 145). The folder and the file stay. The Scripting Runtime raises error 70 in that case. -> [!WARNING] -> In version 1.1.1.0, a directory junction or symbolic link inside the folder is entered like an ordinary folder, and **DeleteFolder** deletes the files in its target, not only the link. A tree that holds a link to a folder outside it loses the contents of that folder. The Scripting Runtime does the same. Remove such links before calling **DeleteFolder**. See [Junctions and symbolic links](#junctions-and-symbolic-links). +> [!NOTE] +> A directory junction or symbolic link inside the folder is removed, and the folder it stands for keeps its files. The Scripting Runtime deletes the files in the target as well, so a tree that holds a link to a folder outside it loses the contents of that folder there. See [Junctions and symbolic links](#junctions-and-symbolic-links). ### GetFilePaths {: .no_toc } @@ -614,7 +628,7 @@ Syntax: **GetFilePaths** ( *folderPath* [, *pattern* [, *includeHidden* [, *incl Returns a **Collection** of **String** values, the full path of each file, starting at item 1. Folders are not in it. The order is the one in which Windows lists the folder. An empty result is an empty **Collection**. Hidden and system subfolders are searched only when *includeHidden* and *includeSystem* allow them. -Raises error 76 when *folderPath* is not a folder, and error 70 when a folder cannot be read. A junction or symbolic link in the tree is entered like a folder, so the files below its target are listed again under the link; see [Junctions and symbolic links](#junctions-and-symbolic-links). +Raises error 76 when *folderPath* is not a folder, and error 70 when a folder cannot be read. A junction or symbolic link in the tree is not entered, so no file below it is listed; see [Junctions and symbolic links](#junctions-and-symbolic-links). The pattern is tested against the name of each file and not against its 8.3 short name, which Windows would also match; see the warning on wildcards under [Conventions](#conventions). Unlike [**Folder.Files**](Folder#files), the result leaves out hidden and system files unless they are asked for. @@ -655,7 +669,9 @@ Returns the full path of the moved file. With a wildcard in *source*, it returns A file is never replaced: when the destination file exists, error 58 is raised. A destination that is an existing folder gets the file with or without a backslash at the end. The Scripting Runtime raises error 58 when the folder has no backslash. -Errors: 53 when a wildcard matches no file, or matches only folders; 76 when the destination folder of a wildcard move does not exist; 58 when the destination file exists. +Moving a file onto itself does nothing and returns its path, except that error 70 is raised when the file is in use. A change of letter case alone is also a move onto itself, so it changes nothing; use [**Rename**](#rename) for it. + +Errors: 53 when a wildcard matches no file, or matches only folders; 76 when the destination folder of a wildcard move does not exist; 58 when the destination file exists; 52 when the name of the destination holds a character that Windows does not allow; 75 when it is a reserved device name such as `con` (the message ends with the Windows code 87). > [!NOTE] > A *source* without a wildcard that names a folder is moved too, with no error: `MoveFile "C:\old", "C:\new"` renames the folder. The Scripting Runtime raises error 53. @@ -696,7 +712,7 @@ Returns the full path of the renamed item. *newName* cannot move the item to another folder. A change of letter case alone is allowed. -Errors: 53 when *source* does not exist (for a folder as well as a file); 58 when *newName* is the name of another item in that folder; 5 for a wildcard, and for a *newName* that leads to another folder. +Errors: 53 when *source* does not exist (for a folder as well as a file); 58 when *newName* is the name of another item in that folder; 52 when *newName* holds a character that Windows does not allow; 5 for a wildcard, for a *newName* that leads to another folder, and for a reserved device name such as `con`. ```tb check_build check_run Dim work As String @@ -733,7 +749,7 @@ Syntax: **GetSpecialFolder** ( *specialFolder* ) **As String** *specialFolder* : *required* A [**SpecialFolderConst**](#specialfolderconst) member. -Returns the path without a trailing backslash. **sfTemporaryFolder** (and its FSO name **TemporaryFolder**) returns the value of the `TEMP` environment variable; every other member is asked from Windows by its known-folder identifier. Raises error 5 for a value that is not a member. A program built for 32 bits gets the `Program Files (x86)` folder for both **sfProgramFiles** and **sfProgramFilesX86**, and the `System32` path for **sfSystemFolder**; **sfSystemX86Folder** is `SysWOW64`. The letter case of the result can differ from that of the Scripting Runtime: `C:\WINDOWS` against `C:\Windows`. +Returns the path without a trailing backslash. **sfTemporaryFolder** (and its FSO name **TemporaryFolder**) returns the value of the `TEMP` environment variable; every other member is asked from Windows by its known-folder identifier. On Windows XP, which has no such identifiers, the older CSIDL numbers are used, and **sfDownloads** and **sfPublicDownloads**, which have none, raise error 5. Raises error 5 for a value that is not a member. A program built for 32 bits gets the `Program Files (x86)` folder for both **sfProgramFiles** and **sfProgramFilesX86**, and the `System32` path for **sfSystemFolder**; **sfSystemX86Folder** is `SysWOW64`. The letter case of the result can differ from that of the Scripting Runtime: `C:\WINDOWS` against `C:\Windows`. > [!NOTE] > Scripting.FileSystemObject returns a **Folder** object from **GetSpecialFolder**. This procedure returns the path as a **String**. Use [**GetFolder**](#getfolder) on the result to get the object. @@ -765,25 +781,58 @@ Errors: 53 when the folder does not exist, 76 when the path names a file, and 75 Returns the text encoding of a file. -Syntax: **GetFileEncoding** ( *filePath* [, *sampleBytes* ] ) **As TextEncoding** +Syntax: **GetFileEncoding** ( *filePath* ) **As TextEncoding** *filePath* : *required* A **String** naming the file. -*sampleBytes* -: *optional* A **Long**, the number of bytes at the start of the file to examine. Default: 65536. - -Returns a [**TextEncoding**](#textencoding) member. A negative value means that the file starts with a BOM. A file that is empty, or looks like binary data, returns **encUnknown**. The decision is taken in this order: +Returns a [**TextEncoding**](#textencoding) member. A negative value means that the file starts with a BOM. A file that is empty, or looks like binary data, returns **encUnknown**. Only the first 32,768 bytes of the file, the sample, are examined, and the decision is taken in this order: 1. A BOM at the start: UTF-8, UTF-16, UTF-32 (little or big endian), GB18030 and UTF-7 each have one. -2. A pattern of zero bytes that points to UTF-16 or UTF-32. -3. UTF-8, when the first 8,192 bytes are valid UTF-8. Plain ASCII is valid UTF-8, so it is reported as **encUtf8**, and so is ASCII text that holds a zero byte. -4. Binary data, when the sample is not valid UTF-8 and holds a zero byte, or more than 2% control characters other than tab, line feed, carriage return and form feed. The second test is made only for a sample of 64 bytes or more. -5. Otherwise, **encSystemAnsi**. +2. The signature of a known binary format at the start of a file of 16 bytes or more: **encUnknown**. The formats are OLE compound documents (`.doc`, `.xls`, `.ppt`, `.msi`), SQLite databases, ZIP archives and the formats built on them (`.docx`, `.xlsx`, `.jar`), PDF, JPEG, PNG, GIF, TIFF, BMP, MP4 and the related QuickTime and HEIC files, RIFF files (AVI, WAV, WebP), GZIP, 7-Zip, RAR, ELF, icons and cursors, ASF (`.wmv`, `.wma`), MPEG-1 program streams, and twinBASIC projects and packages (`.twinproj`, `.twinpack`). The bytes are tested, and not the extension. A text file that begins with the same letters, such as `%PDF` or `RIFF`, gets the same answer. +3. UTF-8, when the sample is valid UTF-8 and the first 256 bytes do not look like binary data (see below). Plain ASCII is valid UTF-8, so it is reported as **encUtf8**. A multi-byte sequence that the end of the sample cuts in two is left out of the test. +4. UTF-32, when the length of the file is a multiple of 4 and, in the first 1,024 bytes, the units of four bytes whose two upper bytes are zero (little endian) outnumber those whose two lower bytes are zero (big endian), or the reverse, by more than 70% of all the units. +5. UTF-16, when the length of the file is even, the sample looks like binary data, and read as UTF-16 it looks like text: no more than 1 code unit in 10,000 is an unpaired surrogate, a control character other than whitespace, or a noncharacter. The byte order is big endian when the second bytes of the units vary more than the first bytes, and little endian otherwise. +6. Binary data: **encUnknown**, when the sample looks like binary data. +7. Otherwise, **encSystemAnsi**. -The result is an estimate, as every guess about the encoding of a text file is. A file in UTF-16, UTF-32 or UTF-8, with or without a BOM, is found right; so is a Latin-1 file when the system code page is Latin-1. Text in another code page without a BOM is not: GB18030 text with an accented letter is reported as **encSystemAnsi**, and UTF-7 or Big5 text that is pure ASCII as **encUtf8**. A sample that ends in the first byte of a multi-byte sequence is judged without that byte, so a Latin-1 file whose only accented letter is its last byte is reported as **encUtf8**. Pass the encoding to the procedure that reads the file when it is known. +A sample looks like binary data when it holds a zero byte, or more than 2% control characters other than tab, line feed, carriage return and form feed. The second test is made only for a sample of 64 bytes or more, and a zero byte that is the last byte of the file does not count. + +The result is an estimate, as every guess about the encoding of a text file is. A file in UTF-16, UTF-32 or UTF-8, with or without a BOM, is found right; so is a Latin-1 file when the system code page is Latin-1. A short file gives little evidence: UTF-16 text with no zero byte, such as eight Japanese characters with no line ending, is reported as **encSystemAnsi**, and a few bytes of binary data can be reported as UTF-16. Text in another code page without a BOM is not found: GB18030 text with an accented letter is reported as **encSystemAnsi**, and UTF-7 or Big5 text that is pure ASCII as **encUtf8**. A sample that ends in the first byte of a multi-byte sequence is judged without that byte, so a Latin-1 file whose only accented letter is its last byte is reported as **encUtf8**, and so is a Latin-1 file whose accented letter comes after the first 32,768 bytes. Pass the encoding to the procedure that reads the file when it is known. + +Raises error 53 when the file does not exist; see [Errors](./#errors). + +```tb check_build check_run +Dim work As String, path As String, b() As Byte +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work +path = BuildPath(work, "t.txt") + +StringToTextFile "", path +Debug.Print GetFileEncoding(path) = encUnknown ' True +StringToTextFile "Hello", path +Debug.Print GetFileEncoding(path) = encUtf8 ' True +StringToTextFile "Hello", path, encUtf16Be +Debug.Print GetFileEncoding(path) = encUtf16Be ' True +StringToTextFile "Hello", path, encUtf16Bom +Debug.Print GetFileEncoding(path) = encUtf16Bom ' True +StringToTextFile "Caf" & ChrW$(233) & " au lait", path, encLatin1 +Debug.Print GetFileEncoding(path) = encSystemAnsi ' True + +' A zero byte in the first 256 bytes marks binary data; a later one does not. +StringToTextFile String$(100, "a") & vbNullChar & String$(100, "a"), path +Debug.Print GetFileEncoding(path) = encUnknown ' True +StringToTextFile String$(300, "a") & vbNullChar & String$(300, "a"), path +Debug.Print GetFileEncoding(path) = encUtf8 ' True + +' The signature of a PNG file. +ReDim b(0 To 15) +b(0) = &H89: b(1) = &H50: b(2) = &H4E: b(3) = &H47: b(4) = &HD: b(5) = &HA: b(6) = &H1A: b(7) = &HA +WriteStream b, path +Debug.Print GetFileEncoding(path) = encUnknown ' True -Raises error 5 when the file does not exist; see [Errors](./#errors). +DeleteFolder work +``` ### GetFileLineEnding {: .no_toc } @@ -836,6 +885,46 @@ Debug.Print Len(GetFileVersion(kernel)) > 0 ' True Debug.Print Len(GetFileVersion(kernel, fvFileString)) > 0 ' True ``` +### IsFileLocked +{: .no_toc } + +Returns whether a file is in use. + +Syntax: **IsFileLocked** ( *filePath* ) **As Boolean** + +*filePath* +: *required* A **String** naming the file. + +Returns **True** when the file cannot be opened for reading and writing with exclusive access at this moment. That is so when a program has the file open, in any mode, for reading as well as for writing, and the program can be the one that calls **IsFileLocked**: an open [**TextStream**](TextStream) is such a case. It is so also for a read-only file, which cannot be opened for writing, although nothing has it open. A hidden attribute does not make a file locked. The answer holds for the instant of the call only, because another program can open or close the file immediately afterwards. + +Raises error 53 when the file does not exist, also when *filePath* names a folder or is empty. + +```tb check_build check_run +Dim work As String, path As String, fn As Long +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work +path = BuildPath(work, "data.txt") +StringToTextFile "x", path + +Debug.Print IsFileLocked(path) ' False +fn = FreeFile() +Open path For Binary Access Read Write Lock Read Write As #fn +Debug.Print IsFileLocked(path) ' True +Close #fn +Debug.Print IsFileLocked(path) ' False + +Dim ts As TextStream +Set ts = OpenTextFile(path) ' open for reading +Debug.Print IsFileLocked(path) ' True +ts.Close + +SetAttr path, vbReadOnly +Debug.Print IsFileLocked(path) ' True +SetAttr path, vbNormal + +DeleteFolder work +``` + ## Objects ### GetDrive @@ -846,9 +935,9 @@ Returns a [**Drive**](Drive) object. Syntax: **GetDrive** ( *drivePath* ) **As Drive** *drivePath* -: *required* A **String** that starts with a drive letter (`C`, `C:`, `C:\`, or a longer path on that drive), or the path of a network share (`\\server\share`, or a longer path). +: *required* A **String** that is a drive letter (`C`, `C:` or `C:\`), or the path of a network share (`\\server\share`, or a longer path). -Returns the drive, with its letter in uppercase. A path on a drive is reduced to the drive. Raises error 68 when the string is of none of those forms, and when a lettered drive does not exist. A network share is not checked until a property of the **Drive** needs it. +Returns the drive, with its letter in uppercase. A longer path on a lettered drive, such as `C:\Windows`, is not a drive, and raises error 5, as it does with the Scripting Runtime; [**GetDriveName**](#getdrivename) reduces such a path to its drive first. A path on a network share is reduced to the share. Raises error 5 when the string is of none of those forms, and error 68 when a lettered drive does not exist. A network share is not checked until a property of the **Drive** needs it. ### GetDrives {: .no_toc } @@ -906,7 +995,7 @@ Syntax: **ArrayToTextFile** ( *lines*(), *filePath* [, *encoding* [, *lineEnding The file is written as [**WriteStream**](#writestream) writes it, read-only files included; see the warning there. [**TextFileToArray**](#textfiletoarray) reads the result back into the same array. An array that is not dimensioned writes an empty file. -Errors: 52 when the last component of *filePath* holds `<`, `>`, `:`, `"`, `|`, `?`, `*` or a control character, and in version 1.1.1.0 a character above U+7FFF (see [Errors](./#errors)); the errors of [**WriteStream**](#writestream). +Errors: 52 when the last component of *filePath* holds `<`, `>`, `:`, `"`, `|`, `?`, `*` or a control character, or is a reserved device name such as `con` (see [**CleanFileName**](#cleanfilename)); the errors of [**WriteStream**](#writestream). ### MergeTextFiles {: .no_toc } @@ -939,7 +1028,7 @@ Every line is written with a line ending, the last one too, so the result ends w When the result is one of the inputs, the new text is written to a file beside it and swapped in once it is complete, and the input stays as it was after an error. When it is a separate file and an error occurs, the part that was written stays. -Errors: 5 when the first file does not exist or either file is binary; 53 when the second file does not exist; 52 for an output name with a character above U+7FFF (see [Errors](./#errors)). +Errors: 5 when either file is binary; 53 when either file does not exist. ```tb check_build check_run Dim work As String @@ -964,7 +1053,7 @@ DeleteFolder work Rewrites a text file in place with a chosen encoding and line ending. -Syntax: **NormalizeTextFile** ( *filePath* [, *toEncoding* [, *toNewLine* [, *fromEncoding* [, *toAttributes* [, *skipIfUnchanged* ] ] ] ] ] ) **As Boolean** +Syntax: **NormalizeTextFile** ( *filePath* [, *toEncoding* [, *toNewLine* [, *fromEncoding* [, *toAttributes* ] ] ] ] ) **As Boolean** *filePath* : *required* A **String** naming the file. @@ -981,12 +1070,9 @@ Syntax: **NormalizeTextFile** ( *filePath* [, *toEncoding* [, *toNewLine* [, *fr *toAttributes* : *optional* A [**FileAttribute**](#fileattribute) combination to give the file. Only the settable attributes count. Default: **faNoChange**, which keeps the attributes of the file. -*skipIfUnchanged* -: *optional* A **Boolean**. **True** (default) leaves the file alone when the text would be written back with exactly the bytes it has. **False** always writes it. - -Returns **True** when the file was changed, a change of attributes alone included, and **False** when it was left as it was. A second call with the same arguments returns **False**. +The file is left alone when the text would be written back with exactly the bytes it has. Returns **True** when the file was changed, a change of attributes alone included, and **False** when it was left as it was. A second call with the same arguments returns **False**. -A binary file is refused with error 5; this includes a file that *fromEncoding* names when it is not UTF-16 or UTF-32 and holds binary data. An empty file stays empty, or becomes a BOM alone when *toEncoding* has one. The new file is written beside the old one and swapped in, as [**WriteStream**](#writestream) does. +A binary file is refused with error 5; this includes a file that *fromEncoding* names when it is not UTF-16 or UTF-32 and holds binary data. An empty file stays empty, or becomes a BOM alone when *toEncoding* has one. The new file is written beside the old one and swapped in, as [**WriteStream**](#writestream) does. Raises error 53 when the file does not exist. ```tb check_build check_run Dim work As String @@ -1040,7 +1126,7 @@ Syntax: **ReadStream** ( *filePath*, *stream*() [, *maxBytesToRead* ] ) An empty file, and a *maxBytesToRead* of 0, leave *stream* as it was: an array that already has elements keeps them, and one that has none stays without. Test the array with [**IsArrayInitialized**](../../../Modules/Information/IsArrayInitialized) before indexing it. -Raises error 7 when the whole file is longer than 2,147,483,647 bytes; a head of any size can be read from a larger file. Raises error 5 (*file not found!*) when the file does not exist; see [Errors](./#errors). +Raises error 7 when the whole file is longer than 2,147,483,647 bytes; a head of any size can be read from a larger file. Raises error 53 when the file does not exist. A file that is held open with exclusive access raises an unspecified error, number -2147467259; see [Errors](./#errors). ### StringToTextFile {: .no_toc } @@ -1065,7 +1151,7 @@ An empty *text* creates an empty file, or one that holds only the BOM. The file For a code page with a limited character set, a character that the code page does not have is replaced by the substitute character of that code page, without an error: the Greek letter pi written with **encLatin1** becomes the byte 63, a question mark. An unpaired surrogate in *text* raises error 5 when the encoding is UTF-8. -Errors: 52 when the last component of *filePath* holds `<`, `>`, `:`, `"`, `|`, `?`, `*` or a control character, and in version 1.1.1.0 a character above U+7FFF (see [Errors](./#errors)); the errors of [**WriteStream**](#writestream). +Errors: 52 when the last component of *filePath* holds `<`, `>`, `:`, `"`, `|`, `?`, `*` or a control character, or is a reserved device name such as `con` (see [**CleanFileName**](#cleanfilename)); the errors of [**WriteStream**](#writestream). ```tb check_build check_run Dim work As String, path As String, b() As Byte @@ -1104,7 +1190,7 @@ Syntax: **TextFileToArray** ( *filePath* [, *encoding* ] ) **As String()** Returns a **String** array, dimensioned from 0, with one element per line. CR LF, LF and CR all end a line, also in one file. The line endings are not in the elements. A line ending at the end of the file gives a last, empty element. An empty file returns an array that is not dimensioned; test it with [**IsArrayInitialized**](../../../Modules/Information/IsArrayInitialized). A BOM is removed. -Raises error 5 for a binary file when the encoding is detected, and the error of [**GetFileEncoding**](#getfileencoding) for a missing file. +Raises error 5 for a binary file when the encoding is detected, and error 53 for a missing file. ```tb check_build check_run Dim work As String @@ -1142,7 +1228,7 @@ Syntax: **TextFileToString** ( *filePath* [, *encoding* [, *outLineEnding* ] ] ) Returns the text, without the BOM. An empty file returns an empty string. Bytes that are not valid in the encoding are replaced by a substitute character, without an error. -Raises error 5 for a file that looks binary when the encoding is detected (an explicit encoding is not checked), error 7 for a file of more than 2 GB, and the error of [**GetFileEncoding**](#getfileencoding) for a missing file. +Raises error 5 for a file that looks binary when the encoding is detected (an explicit encoding is not checked), error 7 for a file of more than 2 GB, and error 53 for a missing file. ### WriteStream {: .no_toc } @@ -1160,9 +1246,9 @@ Syntax: **WriteStream** ( *stream*(), *filePath* ) The bytes are first written to a new file in the same folder, `.~XXXXXXXX.tmp`, which is then swapped in for the target. A failure part-way therefore leaves the old file whole. The folder must allow a new file to be created in it. > [!WARNING] -> In version 1.1.1.0, **WriteStream**, [**StringToTextFile**](#stringtotextfile) and [**ArrayToTextFile**](#arraytotextfile) replace a read-only file without an error, and the new file is not read-only. The attributes of the old file are lost. [**CreateTextFile**](#createtextfile) and the Scripting Runtime's **CreateTextFile** raise error 70 for the same file. +> In version 1.8.0.0, **WriteStream**, [**StringToTextFile**](#stringtotextfile) and [**ArrayToTextFile**](#arraytotextfile) replace a read-only file without an error, and the new file is not read-only. The attributes of the old file are lost. [**CreateTextFile**](#createtextfile) and the Scripting Runtime's **CreateTextFile** raise error 70 for the same file. -Errors: an unspecified error (the number -2147467259) when the folder does not exist, and error 70 when Windows refuses; see [Errors](./#errors) for the other Windows failures. +Errors: 76 when the folder does not exist, and 70 when Windows refuses, such as for a file that is in use. A failure to write the new file raises the error of the runtime, such as 61 for a full disk; see [Errors](./#errors) for the other Windows failures. ```tb check_build check_run Dim work As String, path As String, ts As TextStream, b() As Byte, lines() As String @@ -1193,13 +1279,99 @@ Set ts = CreateTextFile(path) Debug.Print Err.Number ' 70 Err.Clear WriteStream b, BuildPath(work, "missing\x.bin") -Debug.Print Err.Number ' -2147467259 +Debug.Print Err.Number ' 76 Err.Clear On Error GoTo 0 DeleteFolder work, True ``` +## Searching text + +### FileContainsText +{: .no_toc } + +Returns whether a text file contains a piece of text. + +Syntax: **FileContainsText** ( *filePath*, *searchText* [, *ignoreCase* ] ) **As Boolean** + +*filePath* +: *required* A **String** naming the file. + +*searchText* +: *required* A **String**, the text to look for. It must not be empty. Every character stands for itself: `*` and `?` are not wildcards. + +*ignoreCase* +: *optional* A **Boolean**. **True** (default) ignores the letter case of the text, accented letters included. **False** compares the characters exactly. + +Reads the whole file, in the encoding that [**GetFileEncoding**](#getfileencoding) finds, and returns **True** when its text holds *searchText*. The file is searched as one string, so *searchText* can span a line ending, written as the file has it: `vbCrLf` for a file with CR LF. Returns **False** for an empty file and for a file that looks like binary data. + +Errors: 5 when *searchText* is empty; 53 when the file does not exist; 7 for a file of more than 2 GB; the errors of [**ReadStream**](#readstream) for a file that cannot be read. + +### FindInTextFiles +{: .no_toc } + +Returns the paths of the text files in a folder that contain a piece of text. + +Syntax: **FindInTextFiles** ( *folderPath*, *searchText* [, *filePattern* [, *recursive* [, *ignoreCase* [, *fileSizeCapInMb* ] ] ] ] ) **As Collection** + +*folderPath* +: *required* A **String** naming the folder to search. + +*searchText* +: *required* A **String**, the text to look for. It must not be empty. Every character stands for itself: `*` and `?` are not wildcards. + +*filePattern* +: *optional* A **String**, a file name that can hold the wildcards `*` and `?`, as the *pattern* of [**GetFilePaths**](#getfilepaths). Default: `*`. The pattern applies to the files and not to the folders. + +*recursive* +: *optional* A **Boolean**. **True** also searches the subfolders, to any depth. Default: **False**. + +*ignoreCase* +: *optional* A **Boolean**. **True** (default) ignores the letter case of the text, accented letters included. **False** compares the characters exactly. + +*fileSizeCapInMb* +: *optional* A **Long**. A file larger than this many megabytes (1 MB is 1,048,576 bytes) is skipped, so 0 skips every file that is not empty. Default: -1, and any negative value, which set no limit. A file of more than 2 GB is always skipped. + +Returns a **Collection** of **String** values, the full path of each file whose text holds *searchText*, starting at item 1, in the order in which Windows lists the folder. An empty result is an empty **Collection**. + +The files are those that [**GetFilePaths**](#getfilepaths) returns for *filePattern*: hidden and system files are not searched, and a junction or symbolic link is not entered. Each file is tested as [**FileContainsText**](#filecontainstext) tests it, in the encoding that [**GetFileEncoding**](#getfileencoding) finds. An empty file and a file that looks like binary data do not match. A file that cannot be read, because it is held open with exclusive access or access to it is denied, is skipped without an error. + +Errors: 5 when *searchText* is empty; 76 when *folderPath* is not a folder. + +```tb check_build check_run +Dim work As String, hits As Collection, p As Variant +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder BuildPath(work, "sub"), True +StringToTextFile "Alpha and beta", BuildPath(work, "a.txt") +StringToTextFile "gamma", BuildPath(work, "b.txt") +StringToTextFile "alpha", BuildPath(work, "c.log") +StringToTextFile "ALPHA", BuildPath(work, "sub\d.txt") + +Debug.Print FileContainsText(BuildPath(work, "a.txt"), "BETA") +Debug.Print FileContainsText(BuildPath(work, "a.txt"), "BETA", False) + +Set hits = FindInTextFiles(work, "alpha") +Debug.Print hits.Count +For Each p In hits + Debug.Print GetFileName(CStr(p)) +Next +Debug.Print FindInTextFiles(work, "alpha", "*.txt", True).Count +Debug.Print FindInTextFiles(work, "alpha", , True, False).Count +Debug.Print FindInTextFiles(work, "zzz").Count +' Output: +' True +' False +' 2 +' a.txt +' c.log +' 2 +' 1 +' 0 + +DeleteFolder work +``` + ## Text streams ### CreateTextFile @@ -1221,7 +1393,7 @@ Syntax: **CreateTextFile** ( *filePath* [, *overwrite* [, *encoding* [, *lineEnd *lineEnding* : *optional* A [**NewlineType**](#newlinetype) member that [**WriteLine**](TextStream#writeline) and [**WriteBlankLines**](TextStream#writeblanklines) use. Default: **nlWindows**. Anything that is not **nlUnix** or **nlMacintosh** is CR LF. -The file is written in place, with no swap file, and an existing read-only file raises error 70. The errors are those of [**OpenTextFile**](#opentextfile) for **ForWriting**, and error 52 for a name that has `<`, `>`, `"`, `|`, `?`, `*` or a control character, and in version 1.1.1.0 a character above U+7FFF (see [Errors](./#errors)). +The file is written in place, with no swap file, and an existing read-only file raises error 70. The errors are those of [**OpenTextFile**](#opentextfile) for **ForWriting**, and error 52 for a name that has `<`, `>`, `"`, `|`, `?`, `*` or a control character, or is a reserved device name such as `con` (see [**CleanFileName**](#cleanfilename)). A colon in the name is allowed: it addresses an alternate data stream of an NTFS file. > [!IMPORTANT] > The third argument of **Scripting.FileSystemObject.CreateTextFile** is a **Boolean** *Unicode*, and this procedure's third argument is an *encoding*. A **Boolean** converts to a number: **True** is -1, which is **encAutoDetect**, and on writing that means UTF-8, not UTF-16. **False** is 0, **encSystemAnsi**. Code ported from the Scripting Runtime that passes **True** needs **encUtf16** or **encUtf16Bom** here. diff --git a/docs/Reference/Contributed/tbFileSysTools/Files.md b/docs/Reference/Contributed/tbFileSysTools/Files.md index b1f8d343..b326171f 100644 --- a/docs/Reference/Contributed/tbFileSysTools/Files.md +++ b/docs/Reference/Contributed/tbFileSysTools/Files.md @@ -50,7 +50,7 @@ Set f = fs("missing.txt") Debug.Print Err.Number ' 5 Err.Clear Set f = fs(2) -Debug.Print Err.Number ' 9 +Debug.Print Err.Number ' 5 Err.Clear On Error GoTo 0 @@ -76,7 +76,7 @@ Syntax: *object*.**Item** ( *nameOrIndex* ) **As File** *nameOrIndex* : *required* A **Variant**: the name of the file with its extension, such as `"readme.txt"`, in any letter case, or the position of the file, counted from 1. -The files are in the order in which Windows lists the folder. A name that is not in the collection raises error 5, and a position below 1 or above [**Count**](#count) raises error 9 (*Subscript out of range*). +The files are in the order in which Windows lists the folder. A name that is not in the collection, and a position below 1 or above [**Count**](#count), raise error 5 (*Invalid procedure call or argument*), with the message *No item 'x' in the collection*. The Scripting Runtime takes a name only: it raises error 53 (*File not found*) for a name that is not in the collection, and error 5 for a position. diff --git a/docs/Reference/Contributed/tbFileSysTools/Folder.md b/docs/Reference/Contributed/tbFileSysTools/Folder.md index 8ab6f0a3..a05c0eaa 100644 --- a/docs/Reference/Contributed/tbFileSysTools/Folder.md +++ b/docs/Reference/Contributed/tbFileSysTools/Folder.md @@ -106,7 +106,7 @@ Syntax: *object*.**Files** **As Files** Returns a [**Files**](Files) collection of the files directly in the folder. Hidden and system files are in it, which differs from [**GetFilePaths**](FileSystemTools#getfilepaths). The folder is read when the property is read, and the collection does not change afterwards. Raises error 70 when the folder cannot be read, as the Scripting Runtime does. > [!NOTE] -> In version 1.1.1.0, **Files**, [**SubFolders**](#subfolders) and [**Size**](#size) raise error 76 (*Path not found*) for a folder whose path has 258 characters or more, although the folder exists and its [**Name**](#name) can be read. The same members work for a folder with 256 characters. +> In version 1.8.0.0, **Files**, [**SubFolders**](#subfolders) and [**Size**](#size) raise error 76 (*Path not found*) for a folder whose path has 258 characters or more, although the folder exists and its [**Name**](#name) can be read. The same members work for a folder with 256 characters. ```tb check_build check_run Dim work As String, deep As String, n As Long @@ -270,7 +270,7 @@ Syntax: *object*.**Delete** ( [ *force* ] ) *force* : *optional* A **Boolean**. **True** clears the read-only, hidden and system attributes as the contents are deleted. Default: **False**. -See [**DeleteFolder**](FileSystemTools#deletefolder), including its warning about links. The object cannot be used afterwards: its members raise error 76. +See [**DeleteFolder**](FileSystemTools#deletefolder), including its note about links. The object cannot be used afterwards: its members raise error 76. ### Move {: .no_toc } diff --git a/docs/Reference/Contributed/tbFileSysTools/Folders.md b/docs/Reference/Contributed/tbFileSysTools/Folders.md index 49e61431..b641e3db 100644 --- a/docs/Reference/Contributed/tbFileSysTools/Folders.md +++ b/docs/Reference/Contributed/tbFileSysTools/Folders.md @@ -52,7 +52,7 @@ Syntax: *object*.**Item** ( *nameOrIndex* ) **As Folder** *nameOrIndex* : *required* A **Variant**: the name of the folder, such as `"bin"`, in any letter case, or the position of the folder, counted from 1. -The folders are in the order in which Windows lists the folder that holds them. A name that is not in the collection raises error 5, and a position below 1 or above [**Count**](#count) raises error 9 (*Subscript out of range*). +The folders are in the order in which Windows lists the folder that holds them. A name that is not in the collection, and a position below 1 or above [**Count**](#count), raise error 5 (*Invalid procedure call or argument*), with the message *No item 'x' in the collection*. The Scripting Runtime takes a name only: it raises error 76 (*Path not found*) for a name that is not in the collection, and error 5 for a position. It also has an **Add** method, which creates a folder; this collection has none, and [**CreateFolder**](FileSystemTools#createfolder) does that work. diff --git a/docs/Reference/Contributed/tbFileSysTools/index.md b/docs/Reference/Contributed/tbFileSysTools/index.md index f70f14a4..32fc6c79 100644 --- a/docs/Reference/Contributed/tbFileSysTools/index.md +++ b/docs/Reference/Contributed/tbFileSysTools/index.md @@ -6,14 +6,14 @@ exclude_from_docs: - FSTShared - TextCodec - WinAPI -indexed_from: 1.1.1.0 +indexed_from: 1.8.0.0 permalink: /tB/Packages/Contributed/tbFileSysTools/ --- # tbFileSysTools Package {: .no_toc } -**Author:** GCuser99. **Version:** 1.1.1.0. **On GitHub:** [GCuser99/tbFileSysTools](https://github.com/GCuser99/tbFileSysTools), in the folder *Package*. +**Author:** GCuser99. **Version:** 1.8.0.0. **On GitHub:** [GCuser99/tbFileSysTools](https://github.com/GCuser99/tbFileSysTools), in the folder *Package*. The **tbFileSysTools** contributed package, listed in the Available Packages dialog as *File System Tools - Modern Replacement for FSO*, reads and changes files, folders and drives, as the **FileSystemObject** of the Scripting Runtime does, and is written in twinBASIC. It reads and writes text files in any Windows code page, with detection of the encoding and handling of byte-order marks (BOMs), detects and converts line endings, streams files of any size, and works with paths longer than 260 characters. @@ -48,8 +48,7 @@ ts.Close DeleteFolder work ``` -> [!NOTE] -> This section documents version 1.1.1.0, the version on the package server, published under the MIT licence. The GitHub repository has moved on: its README describes version 1.8.0.0, and its change log lists changes from 1.2.0.0 on. Some members that the README names, such as **FindInTextFiles**, **FileContainsText** and **IsFileLocked**, do not exist in 1.1.1.0. The change log also records that later versions removed the *useLongPathPrefix* argument of **GetAbsolutePathName** and the *skipIfUnchanged* argument of **NormalizeTextFile**, which this version has, and that they changed some of the behaviour described on these pages. +The package is published under the MIT licence. The public types of the package are the module and the eight classes on these pages, and the enumerations that the module declares. Three private modules, **FSTShared** (shared file code and the error mapping), **TextCodec** (encoding, decoding and detection) and **WinAPI** (Windows declarations), are not part of the interface and are not described. @@ -61,8 +60,9 @@ A call that works with the Scripting Runtime's **FileSystemObject** keeps its na - Whole files: [**StringToTextFile**](FileSystemTools#stringtotextfile), [**TextFileToString**](FileSystemTools#textfiletostring), [**ArrayToTextFile**](FileSystemTools#arraytotextfile), [**TextFileToArray**](FileSystemTools#textfiletoarray), [**ReadStream**](FileSystemTools#readstream), [**WriteStream**](FileSystemTools#writestream), [**NormalizeTextFile**](FileSystemTools#normalizetextfile) and [**MergeTextFiles**](FileSystemTools#mergetextfiles). - Detection: [**GetFileEncoding**](FileSystemTools#getfileencoding) and [**GetFileLineEnding**](FileSystemTools#getfilelineending). +- Searching and locks: [**FindInTextFiles**](FileSystemTools#findintextfiles), [**FileContainsText**](FileSystemTools#filecontainstext) and [**IsFileLocked**](FileSystemTools#isfilelocked). - Paths and directories: [**CleanFileName**](FileSystemTools#cleanfilename), [**GetRelativePath**](FileSystemTools#getrelativepath), [**GetFilePaths**](FileSystemTools#getfilepaths), [**GetFileType**](FileSystemTools#getfiletype), [**GetCurrentDir**](FileSystemTools#getcurrentdir), [**SetCurrentDir**](FileSystemTools#setcurrentdir) and [**Rename**](FileSystemTools#rename). -- On [**File**](File): **Encoding**, **ExtensionName**, **HasAttribute**, **LineEnding**, **Normalize**, **Refresh**, **Rename**, **SetAttribute**, **ToStream**, **ToString** and **Version**. On [**Folder**](Folder): **HasAttribute**, **Refresh**, **Rename** and **SetAttribute**. On [**Drive**](Drive): **SerialNumberHex** and **UnsignedSerialNumber**. On [**TextStream**](TextStream): **Encoding**, **Flush** and **IsStreaming**. +- On [**File**](File): **ContainsText**, **Encoding**, **ExtensionName**, **HasAttribute**, **IsLocked**, **LineEnding**, **Normalize**, **Refresh**, **Rename**, **SetAttribute**, **ToStream**, **ToString** and **Version**. On [**Folder**](Folder): **HasAttribute**, **Refresh**, **Rename** and **SetAttribute**. On [**Drive**](Drive): **SerialNumberHex** and **UnsignedSerialNumber**. On [**TextStream**](TextStream): **Encoding**, **Flush** and **IsStreaming**. - [**Files**](Files), [**Folders**](Folders) and [**Drives**](Drives) return an item by position as well as by name. - Enumerations for the encodings, line endings and 25 special folders. @@ -76,21 +76,20 @@ A call that works with the Scripting Runtime's **FileSystemObject** keeps its na - [**GetSpecialFolder**](FileSystemTools#getspecialfolder) returns the path as a **String**, where the Scripting Runtime returns a **Folder**. It accepts 25 folders, and not only the Windows, System and Temporary folders. - [**FileAttribute**](FileSystemTools#fileattribute) has no *Volume* (8) and no *Alias* (1024). The label of a volume is [**Drive.VolumeName**](Drive#volumename), and an alias is **faReparsePoint**. [**File**](File#setattribute) and [**Folder**](Folder#setattribute) add **SetAttribute** and **HasAttribute**, which change and test one attribute. -- [**Folder.Size**](Folder#size) does not enter directory junctions and symbolic links. The Scripting Runtime does, and counts the target of a link twice when it is in the same tree: 2,500 bytes there for a folder that holds 1,500. +- [**Folder.Size**](Folder#size), [**CopyFolder**](FileSystemTools#copyfolder), [**DeleteFolder**](FileSystemTools#deletefolder) and [**GetFilePaths**](FileSystemTools#getfilepaths) with *recursive* do not enter directory junctions and symbolic links. The Scripting Runtime does, and counts the target of a link twice when it is in the same tree: 2,500 bytes there for a folder that holds 1,500. Its **CopyFolder** copies the files of the target into the copy, and its **DeleteFolder** deletes them. - [**BuildPath**](FileSystemTools#buildpath) and [**GetParentFolderName**](FileSystemTools#getparentfoldername) turn `/` into `\`. `BuildPath("C:\a", "b/c")` returns `C:\a\b\c` and the Scripting Runtime `C:\a\b/c`; `GetParentFolderName("C:/a/b.txt")` returns `C:\a` and the Scripting Runtime `C:/a`. - [**GetAbsolutePathName**](FileSystemTools#getabsolutepathname) raises error 52 for an empty string, where the Scripting Runtime returns the current directory. -- [**GetDrive**](FileSystemTools#getdrive) accepts a full path and reduces it to its drive: `GetDrive("C:\Windows")` is `C:`. The Scripting Runtime raises error 5. - [**CopyFile**](FileSystemTools#copyfile), [**CopyFolder**](FileSystemTools#copyfolder), [**MoveFile**](FileSystemTools#movefile) and [**MoveFolder**](FileSystemTools#movefolder) put the source inside an existing destination folder, whether or not the destination ends in a backslash. The Scripting Runtime does so only with a backslash. Without one it raises error 70 for **CopyFile**, 58 for **MoveFile** and **MoveFolder**, and **CopyFolder** copies the contents of the source straight into the folder. - [**MoveFile**](FileSystemTools#movefile) moves a folder when *source* names one. The Scripting Runtime raises error 53. - [**DeleteFolder**](FileSystemTools#deletefolder) of a folder that holds a read-only file raises error 75 without *force*; the Scripting Runtime raises 70. - [**GetFileVersion**](FileSystemTools#getfileversion) returns an empty string for a file that does not exist. The Scripting Runtime raises an error. - At the end of a stream, [**ReadAll**](TextStream#readall) returns an empty string and [**Skip**](TextStream#skip) does nothing, where the Scripting Runtime raises error 62. A closed stream raises 52 here and 91 there. -- [**Files.Item**](Files#item), [**Folders.Item**](Folders#item) and [**Drives.Item**](Drives#item) take a position as well as a name. A name that is not in the collection raises error 5; the Scripting Runtime raises 53, 76 and 68 for the three. `Drives("C:")` raises error 5, and the Scripting Runtime accepts it. +- [**Files.Item**](Files#item), [**Folders.Item**](Folders#item) and [**Drives.Item**](Drives#item) take a position as well as a name. A name or a position that is not in the collection raises error 5; the Scripting Runtime raises 53, 76 and 68 for a name that is not in the three. - [**GetTempName**](FileSystemTools#gettempname) returns eight random letters and digits and `.tmp`. The Scripting Runtime returns `rad` and five hexadecimal digits. - A path longer than 260 characters works in most procedures. The Scripting Runtime does not accept it: **FileExists** and **FolderExists** return **False** for it, **GetFile** raises error 53 and **OpenTextFile** raises 76. - [**StringToTextFile**](FileSystemTools#stringtotextfile), [**ArrayToTextFile**](FileSystemTools#arraytotextfile) and [**WriteStream**](FileSystemTools#writestream) replace a read-only file without an error. [**CreateTextFile**](FileSystemTools#createtextfile) raises error 70 for it, as the Scripting Runtime's does. -**The same.** For these failures the two raise the same error numbers: a missing file (53) or folder (76) from **GetFile**, **GetFolder**, **CopyFile**, **DeleteFile**, **DeleteFolder** and **OpenTextFile**; an existing folder or destination (58) from **CreateFolder**, **CopyFile**, **MoveFile** and **CreateTextFile**; a missing parent folder (76) from **CreateFolder**; a drive that does not exist (68) from **GetDrive**; a read-only destination or file (70) from **CopyFile**, **DeleteFile** and **CreateTextFile**; an unreadable folder (70) from **Files**, **SubFolders** and **Size**; the wrong direction of a stream (54) and a read at the end (62) from **TextStream**. Both enter a junction or symbolic link when they delete a folder, and both match a pattern against the 8.3 name of a file too. +**The same.** For these failures the two raise the same error numbers: a missing file (53) or folder (76) from **GetFile**, **GetFolder**, **CopyFile**, **DeleteFile**, **DeleteFolder** and **OpenTextFile**; an existing folder or destination (58) from **CreateFolder**, **CopyFile**, **MoveFile** and **CreateTextFile**; a missing parent folder (76) from **CreateFolder**; a drive that does not exist (68) and a path that is longer than a drive (5) from **GetDrive**; a read-only destination or file (70) from **CopyFile**, **DeleteFile** and **CreateTextFile**; an unreadable folder (70) from **Files**, **SubFolders** and **Size**; the wrong direction of a stream (54) and a read at the end (62) from **TextStream**. Both match a pattern against the 8.3 name of a file in **CopyFile**, **DeleteFile** and **MoveFile**, and both accept `C:` as the item of **Drives**. ## Using the package with the Scripting Runtime @@ -111,10 +110,9 @@ The other ways out are to remove the reference to the Scripting Runtime, or to l The procedures raise the error numbers that VB6 uses for the same failures. **Err.Source** names the operation, such as `CopyFile`, and **Err.Description** names the path. The errors of [**OpenTextFile**](FileSystemTools#opentextfile), [**CreateTextFile**](FileSystemTools#createtextfile) and the other procedures that open a stream have an empty **Err.Source** and only the standard text of their number, without the path. Each entry says which numbers it raises. -- 5 -- *Invalid procedure call or argument*: a wildcard in a destination, a folder copied into itself, a text procedure given a binary file, a value that is not a member of an enumeration, a name that is not in a collection, and the *file not found!* of [**ReadStream**](FileSystemTools#readstream). +- 5 -- *Invalid procedure call or argument*: a wildcard in a destination, a folder copied into itself, a text procedure given a binary file, a value that is not a member of an enumeration, a name or a position that is not in a collection, a string that is not a drive, and an empty search text. - 7 -- *Out of memory*: a whole file that is too large for one array or **String** (more than 2 GB). -- 9 -- *Subscript out of range*: a position outside a collection. -- 52 -- *Bad file name or number*: a name with a character that Windows does not allow, and a closed [**TextStream**](TextStream). +- 52 -- *Bad file name or number*: a name with a character that Windows does not allow, or a reserved device name such as `con`, and a closed [**TextStream**](TextStream). - 53 -- *File not found*. - 54 -- *Bad file mode*: a read on a stream open for writing, or the reverse. - 58 -- *File already exists*. The text says *File* also when a folder is in the way. @@ -128,39 +126,45 @@ The procedures raise the error numbers that VB6 uses for the same failures. **Er Windows reports a failure with its own code, and the package maps it to one of those numbers: *file not found* (2) to 53, *path not found* (3) and *bad directory name* (267) to 76, *file exists* (80 and 183) to 58, *access denied* and the sharing violations (5, 32 and 33) to 70, and *invalid name* (123) to 52. > [!NOTE] -> Version 1.1.1.0 raises some errors that the Scripting Runtime does not raise, or other numbers. The sample shows the first three. +> A few failures raise a number that is not the one the same failure raises elsewhere in the package. The sample shows the first two. > -> - A file name with a character above U+7FFF, such as the Chinese character U+8A9E, the Hangul letter U+AC00 or an emoji, raises error 52 in [**StringToTextFile**](FileSystemTools#stringtotextfile), [**ArrayToTextFile**](FileSystemTools#arraytotextfile), [**CreateTextFile**](FileSystemTools#createtextfile), [**OpenTextFile**](FileSystemTools#opentextfile) with *create* **True**, the output file of [**MergeTextFiles**](FileSystemTools#mergetextfiles) and [**Folder.CreateTextFile**](Folder#createtextfile). The Scripting Runtime accepts the names, and so do [**WriteStream**](FileSystemTools#writestream) and [**CreateFolder**](FileSystemTools#createfolder). -> - A missing file raises error 5 and not 53 in [**TextFileToString**](FileSystemTools#textfiletostring), [**TextFileToArray**](FileSystemTools#textfiletoarray), [**GetFileEncoding**](FileSystemTools#getfileencoding), [**GetFileLineEnding**](FileSystemTools#getfilelineending), [**NormalizeTextFile**](FileSystemTools#normalizetextfile), [**ReadStream**](FileSystemTools#readstream) and the first file of [**MergeTextFiles**](FileSystemTools#mergetextfiles). [**OpenTextFile**](FileSystemTools#opentextfile) and [**GetFile**](FileSystemTools#getfile) raise 53. -> - A folder that does not exist raises the unspecified error -2147467259, not 76, in [**StringToTextFile**](FileSystemTools#stringtotextfile), [**ArrayToTextFile**](FileSystemTools#arraytotextfile) and [**WriteStream**](FileSystemTools#writestream). [**CreateTextFile**](FileSystemTools#createtextfile) and [**OpenTextFile**](FileSystemTools#opentextfile) raise 76. +> - A file that is held open with exclusive access raises the unspecified error -2147467259 when it is read, by [**TextFileToString**](FileSystemTools#textfiletostring), [**GetFileEncoding**](FileSystemTools#getfileencoding), [**ReadStream**](FileSystemTools#readstream) and the other procedures that read a whole file. Writing to it raises 70. +> - A reserved device name, such as `con` or `nul`, raises 52 in the procedures that write a file ([**StringToTextFile**](FileSystemTools#stringtotextfile), [**ArrayToTextFile**](FileSystemTools#arraytotextfile), [**CreateTextFile**](FileSystemTools#createtextfile), [**OpenTextFile**](FileSystemTools#opentextfile) with *create* **True**, [**Folder.CreateTextFile**](Folder#createtextfile)) and in [**CopyFile**](FileSystemTools#copyfile). It raises 75 in [**CreateFolder**](FileSystemTools#createfolder) and [**MoveFile**](FileSystemTools#movefile), and 5 in [**Rename**](FileSystemTools#rename). > - [**Folder.Files**](Folder#files), [**SubFolders**](Folder#subfolders) and [**Size**](Folder#size) raise error 76 for a folder whose path has 258 characters or more. ```tb check_build check_run -Dim work As String, s As String, ts As TextStream +Dim work As String, p As String, s As String, fn As Long work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) CreateFolder work +p = BuildPath(work, "a.txt") +StringToTextFile "x", p On Error Resume Next -StringToTextFile "x", BuildPath(work, ChrW$(&H8A9E&) & ".txt") -Debug.Print Err.Number ' 52 +fn = FreeFile() +Open p For Binary Access Read Write Lock Read Write As #fn +s = TextFileToString(p) +Debug.Print Err.Number ' -2147467259 Err.Clear -StringToTextFile "x", BuildPath(work, ChrW$(&H65E5&) & ".txt") -Debug.Print Err.Number ' 0 -s = TextFileToString(BuildPath(work, "missing.txt")) -Debug.Print Err.Number ' 5 +StringToTextFile "y", p +Debug.Print Err.Number ' 70 Err.Clear -Set ts = OpenTextFile(BuildPath(work, "missing.txt")) -Debug.Print Err.Number ' 53 +Close #fn + +StringToTextFile "x", BuildPath(work, "con") +Debug.Print Err.Number ' 52 Err.Clear -StringToTextFile "x", BuildPath(work, "nofolder\x.txt") -Debug.Print Err.Number ' -2147467259 +CreateFolder BuildPath(work, "con") +Debug.Print Err.Number ' 75 +Err.Clear +Rename p, "con" +Debug.Print Err.Number ' 5 Err.Clear On Error GoTo 0 DeleteFolder work ``` -Test with [**FileExists**](FileSystemTools#fileexists) first when the number of a missing-file error matters. +In the procedures that read or write the text or the bytes of a file, a file that does not exist raises error 53, and a folder that does not exist raises 76. ## Classes and modules @@ -175,7 +179,9 @@ Test with [**FileExists**](FileSystemTools#fileexists) first when the number of - [DeleteFile](FileSystemTools#deletefile) -- deletes one or more files - [DeleteFolder](FileSystemTools#deletefolder) -- deletes a folder and everything in it - [DriveExists](FileSystemTools#driveexists) -- returns whether a drive exists + - [FileContainsText](FileSystemTools#filecontainstext) -- returns whether a text file contains a piece of text - [FileExists](FileSystemTools#fileexists) -- returns whether a file exists + - [FindInTextFiles](FileSystemTools#findintextfiles) -- returns the paths of the text files in a folder that contain a piece of text - [FolderExists](FileSystemTools#folderexists) -- returns whether a folder exists - [GetAbsolutePathName](FileSystemTools#getabsolutepathname) -- returns the full path for a path specification - [GetBaseName](FileSystemTools#getbasename) -- returns the last component of a path without its extension @@ -197,6 +203,7 @@ Test with [**FileExists**](FileSystemTools#fileexists) first when the number of - [GetSpecialFolder](FileSystemTools#getspecialfolder) -- returns the path of a well-known Windows folder - [GetStandardStream](FileSystemTools#getstandardstream) -- returns a TextStream for standard input, output or error - [GetTempName](FileSystemTools#gettempname) -- returns a random name for a temporary file or folder + - [IsFileLocked](FileSystemTools#isfilelocked) -- returns whether a file is in use - [MergeTextFiles](FileSystemTools#mergetextfiles) -- joins two text files into one - [MoveFile](FileSystemTools#movefile) -- moves one or more files - [MoveFolder](FileSystemTools#movefolder) -- moves one or more folders @@ -229,7 +236,7 @@ Test with [**FileExists**](FileSystemTools#fileexists) first when the number of - [Drives](Drives) -- the collection of the drives - [File](File) -- a file - [Attributes](File#attributes), [DateCreated](File#datecreated), [DateLastAccessed](File#datelastaccessed), [DateLastModified](File#datelastmodified), [Drive](File#drive), [Encoding](File#encoding), [ExtensionName](File#extensionname), [HasAttribute](File#hasattribute), [LineEnding](File#lineending), [Name](File#name), [ParentFolder](File#parentfolder), [Path](File#path), [ShortName](File#shortname), [ShortPath](File#shortpath), [Size](File#size), [Type](File#type), [Version](File#version) -- the properties - - [Copy](File#copy), [Delete](File#delete), [Move](File#move), [Normalize](File#normalize), [OpenAsTextStream](File#openastextstream), [Refresh](File#refresh), [Rename](File#rename), [SetAttribute](File#setattribute), [ToStream](File#tostream), [ToString](File#tostring) -- the methods + - [ContainsText](File#containstext), [Copy](File#copy), [Delete](File#delete), [IsLocked](File#islocked), [Move](File#move), [Normalize](File#normalize), [OpenAsTextStream](File#openastextstream), [Refresh](File#refresh), [Rename](File#rename), [SetAttribute](File#setattribute), [ToStream](File#tostream), [ToString](File#tostring) -- the methods - [Files](Files) -- the collection of the files of a folder - [Folder](Folder) -- a folder - [Attributes](Folder#attributes), [DateCreated](Folder#datecreated), [DateLastAccessed](Folder#datelastaccessed), [DateLastModified](Folder#datelastmodified), [Drive](Folder#drive), [Files](Folder#files), [HasAttribute](Folder#hasattribute), [IsRootFolder](Folder#isrootfolder), [Name](Folder#name), [ParentFolder](Folder#parentfolder), [Path](Folder#path), [ShortName](Folder#shortname), [ShortPath](Folder#shortpath), [Size](Folder#size), [SubFolders](Folder#subfolders), [Type](Folder#type) -- the properties diff --git a/indexer/snapshots/contributed/tbFileSysTools/api.json b/indexer/snapshots/contributed/tbFileSysTools/api.json index 8422896f..d470845f 100644 --- a/indexer/snapshots/contributed/tbFileSysTools/api.json +++ b/indexer/snapshots/contributed/tbFileSysTools/api.json @@ -2,7 +2,7 @@ "format": 3, "package": "tbFileSysTools", "project": "tbFileSysTools", - "version": "1.1.1.0", + "version": "1.8.0.0", "types": [ { "name": "Drive", @@ -88,18 +88,30 @@ {"name":"EnsureTrailingBackslash","kind":"function","vis":"public","signature":"Public Function EnsureTrailingBackslash(ByVal s As String) As String","returnType":"String"}, {"name":"FileTimeToDate","kind":"function","vis":"public","signature":"Public Function FileTimeToDate(ByRef ft As FILETIME) As Date","returnType":"Date"}, {"name":"FindDataFileName","kind":"function","vis":"public","signature":"Public Function FindDataFileName(ByRef ffd As WIN32_FIND_DATAW) As String","returnType":"String"}, + {"name":"FindFirstOne","kind":"function","vis":"public","signature":"Public Function FindFirstOne(ByVal searchSpec As String, ByRef ffd As WIN32_FIND_DATAW, Optional ByRef lastErr As Long) As LongPtr","returnType":"LongPtr"}, {"name":"GetShortPath","kind":"function","vis":"public","signature":"Public Function GetShortPath(ByVal path As String) As String","returnType":"String"}, + {"name":"GuardLeafName","kind":"sub","vis":"public","signature":"Public Sub GuardLeafName(ByVal path As String, ByVal opName As String, ByVal allowColon As Boolean)"}, {"name":"IncludeByAttr","kind":"function","vis":"public","signature":"Public Function IncludeByAttr(ByVal attr As FILE_ATTRIBUTES, ByVal includeHidden As Boolean, ByVal includeSystem As Boolean) As Boolean","returnType":"Boolean"}, + {"name":"InvariantUpper","kind":"function","vis":"public","signature":"Public Function InvariantUpper(ByVal s As String) As String","returnType":"String"}, {"name":"IsEmptySearch","kind":"function","vis":"public","signature":"Public Function IsEmptySearch(ByVal errCode As Long, ByVal sourceName As String, ByVal context As String) As Boolean","returnType":"Boolean"}, + {"name":"IsReparsePoint","kind":"function","vis":"public","signature":"Public Function IsReparsePoint(ByVal path As String) As Boolean","returnType":"Boolean"}, + {"name":"IsReservedDeviceName","kind":"function","vis":"public","signature":"Public Function IsReservedDeviceName(ByVal name As String) As Boolean","returnType":"Boolean"}, {"name":"IsUNC","kind":"function","vis":"private","signature":"Private Function IsUNC(ByVal p As String) As Boolean","returnType":"Boolean"}, + {"name":"MatchesLongName","kind":"function","vis":"public","signature":"Public Function MatchesLongName(ByVal longName As String, ByVal pattern As String) As Boolean","returnType":"Boolean"}, {"name":"Max","kind":"function","vis":"public","signature":"Public Function Max(ParamArray numberList() As Variant) As Variant","returnType":"Variant"}, {"name":"Min","kind":"function","vis":"public","signature":"Public Function Min(ParamArray numberList() As Variant) As Variant","returnType":"Variant"}, + {"name":"OSVersion","kind":"function","vis":"public","signature":"Public Function OSVersion() As Double","returnType":"Double"}, + {"name":"PathEquals","kind":"function","vis":"public","signature":"Public Function PathEquals(ByVal a As String, ByVal b As String) As Boolean","returnType":"Boolean"}, + {"name":"PathIsUnderOrEqual","kind":"function","vis":"public","signature":"Public Function PathIsUnderOrEqual(ByVal childPath As String, ByVal parentPath As String) As Boolean","returnType":"Boolean"}, {"name":"RaiseFromLastError","kind":"sub","vis":"public","signature":"Public Sub RaiseFromLastError(ByVal errCode As Long, ByVal sourceName As String, ByVal context As String, Optional ByVal pathIsDrive As Boolean = False)"}, {"name":"SETTABLE_ATTRS","kind":"const","vis":"public","signature":"Public Const SETTABLE_ATTRS As Long = FileAttribute.faReadOnly Or FileAttribute.faHidden Or FileAttribute.faSystem Or FileAttribute.faArchive Or FileAttribute.faTemporary Or FileAttribute.faNotContentIndexed Or FileAttribute.faNormal"}, {"name":"SanitizeAttrs","kind":"function","vis":"public","signature":"Public Function SanitizeAttrs(ByVal value As FileAttribute) As FileAttribute","returnType":"FileAttribute"}, {"name":"SetPathAttributes","kind":"function","vis":"public","signature":"Public Function SetPathAttributes(ByVal path As String, ByVal attrs As Long) As Long","returnType":"Long"}, {"name":"SizeFromHL","kind":"function","vis":"public","signature":"Public Function SizeFromHL(ByVal high As Long, ByVal low As Long) As LongLong","returnType":"LongLong"}, - {"name":"StatPath","kind":"function","vis":"public","signature":"Public Function StatPath(ByVal path As String, ByRef attrs As Long, ByRef size As LongLong, ByRef created As Date, ByRef modified As Date, ByRef accessed As Date, ByRef leafName As String) As Boolean","returnType":"Boolean"} + {"name":"StatPath","kind":"function","vis":"public","signature":"Public Function StatPath(ByVal path As String, ByRef attrs As Long, ByRef size As LongLong, ByRef created As Date, ByRef modified As Date, ByRef accessed As Date, ByRef leafName As String) As Boolean","returnType":"Boolean"}, + {"name":"WINDOWS_7","kind":"const","vis":"public","signature":"Public Const WINDOWS_7 As Double = 6.1"}, + {"name":"WINDOWS_VISTA","kind":"const","vis":"public","signature":"Public Const WINDOWS_VISTA As Double = 6.0"}, + {"name":"mOSVersion","kind":"field","vis":"private","signature":"Private mOSVersion As Double"} ] }, { @@ -113,22 +125,24 @@ "members": [ {"name":"Attributes","kind":"property","vis":"public","signature":"Public Property Get Attributes() As FileAttribute","returnType":"FileAttribute","accessor":"Get","description":"Gets or sets the file's attributes on disk (ReadOnly/Hidden/System/Archive); combine with Or. NoChange is ignored — use Normal to clear all attributes."}, {"name":"Attributes","kind":"property","vis":"public","signature":"Public Property Let Attributes(ByVal value As FileAttribute)","accessor":"Let"}, + {"name":"ContainsText","kind":"function","vis":"public","signature":"Public Function ContainsText(ByVal searchText As String, Optional ByVal ignoreCase As Boolean = True) As Boolean","returnType":"Boolean","description":"Returns True if the given text is found in this text file. Encoding is auto-detected; returns False for a binary or unknown-encoding file."}, {"name":"Copy","kind":"sub","vis":"public","signature":"Public Sub Copy(ByVal destination As String, Optional ByVal overwrite As Boolean = True)","description":"Copies the file to the destination (overwrites by default)."}, {"name":"DateCreated","kind":"property","vis":"public","signature":"Public Property Get DateCreated() As Date","returnType":"Date","accessor":"Get","description":"Returns the date and time when a specified file was created."}, {"name":"DateLastAccessed","kind":"property","vis":"public","signature":"Public Property Get DateLastAccessed() As Date","returnType":"Date","accessor":"Get","description":"Returns the date and time when a specified file was last accessed."}, {"name":"DateLastModified","kind":"property","vis":"public","signature":"Public Property Get DateLastModified() As Date","returnType":"Date","accessor":"Get","description":"Returns the date and time when a specified file was last modified."}, {"name":"Delete","kind":"sub","vis":"public","signature":"Public Sub Delete(Optional ByVal force As Boolean = False)","description":"Deletes the file."}, {"name":"Drive","kind":"property","vis":"public","signature":"Public Property Get Drive() As Drive","returnType":"Drive","accessor":"Get","description":"Returns the drive letter of the drive where a specified file or folder resides."}, - {"name":"Encoding","kind":"property","vis":"public","signature":"Public Property Get Encoding(Optional ByVal sampleBytes As Long = 65536&) As TextEncoding","returnType":"TextEncoding","accessor":"Get","description":"Detects a file's text encoding from a head sample: BOM sniff, then UTF-16/32 and UTF-8 heuristics. Returns a signed TextEncoding (negative = BOM present); encUnknown for an empty/unreadable file."}, + {"name":"Encoding","kind":"property","vis":"public","signature":"Public Property Get Encoding() As TextEncoding","returnType":"TextEncoding","accessor":"Get","description":"Detects a file's text encoding from a head sample: BOM sniff, then UTF-16/32 and UTF-8 heuristics. Returns a signed TextEncoding (negative = BOM present); encUnknown for an empty/unreadable file."}, {"name":"EnsureCurrent","kind":"sub","vis":"private","signature":"Private Sub EnsureCurrent()"}, {"name":"ExtensionName","kind":"property","vis":"public","signature":"Public Property Get ExtensionName() As String","returnType":"String","accessor":"Get","description":"Returns a string containing the extension name."}, {"name":"HasAttribute","kind":"property","vis":"public","signature":"Public Property Get HasAttribute(ByVal attr As FileAttribute) As Boolean","returnType":"Boolean","accessor":"Get","description":"True if the specified attribute is set."}, {"name":"Internal_Init","kind":"sub","vis":"friend","signature":"Friend Sub Internal_Init(ByVal path As String, ByVal attrs As Long, ByVal size As LongLong, ByVal created As Date, ByVal modified As Date, ByVal accessed As Date)"}, + {"name":"IsLocked","kind":"function","vis":"public","signature":"Public Function IsLocked() As Boolean","returnType":"Boolean","description":"Returns True if the file is currently locked by another process - that is, if it cannot be opened for exclusive read/write access right now. The answer is only valid for the instant of the call. Raises if the file does not exist."}, {"name":"LineEnding","kind":"property","vis":"public","signature":"Public Property Get LineEnding() As NewlineType","returnType":"NewlineType","accessor":"Get","description":"The file's line-ending style on disk (nlWindows / nlUnix / nlMacintosh / nlMixed / nlUnknown). Reads file content on each access."}, {"name":"Move","kind":"sub","vis":"public","signature":"Public Sub Move(ByVal destination As String)","description":"Moves the file, then re-points this object at the new location."}, {"name":"Name","kind":"property","vis":"public","signature":"Public Property Get Name() As String","returnType":"String","accessor":"Get","description":"Gets/Sets the name of the file. If setting, The new name must be a bare name, or a path that resolves to the same folder as the source — it cannot move the item to a different folder. Wildcards are not permitted, and the destination must not already exist."}, {"name":"Name","kind":"property","vis":"public","signature":"Public Property Let Name(ByVal newName As String)","accessor":"Let"}, - {"name":"Normalize","kind":"function","vis":"public","signature":"Public Function Normalize(Optional ByVal toEncoding As TextEncoding = encUtf8, Optional ByVal toLineEnding As NewlineType = nlWindows, Optional ByVal fromEncoding As TextEncoding = encAutoDetect, Optional ByVal toAttributes As FileAttribute = faNoChange, Optional ByVal skipIfUnchanged As Boolean = True) As Boolean","returnType":"Boolean","description":"Rewrites this file in place: target encoding + newline style (source auto-detected), attributes preserved by default. Returns True if modified."}, + {"name":"Normalize","kind":"function","vis":"public","signature":"Public Function Normalize(Optional ByVal toEncoding As TextEncoding = encUtf8, Optional ByVal toLineEnding As NewlineType = nlWindows, Optional ByVal fromEncoding As TextEncoding = encAutoDetect, Optional ByVal toAttributes As FileAttribute = faNoChange) As Boolean","returnType":"Boolean","description":"Rewrites this file in place: target encoding + newline style (source auto-detected), attributes preserved by default. Returns True if modified."}, {"name":"OpenAsTextStream","kind":"function","vis":"public","signature":"Public Function OpenAsTextStream(Optional ByVal ioMode As IOMode = ForReading, Optional ByVal encoding As TextEncoding = encAutoDetect, Optional ByVal writeModeLineEnding As NewlineType = nlWindows) As TextStream","returnType":"TextStream","description":"Opens the file and returns a streaming TextStream."}, {"name":"ParentFolder","kind":"property","vis":"public","signature":"Public Property Get ParentFolder() As Folder","returnType":"Folder","accessor":"Get","description":"Returns the folder object for the parent of the specified file."}, {"name":"Path","kind":"property","vis":"public","signature":"Public Property Get Path() As String","returnType":"String","accessor":"Get","attrs":[{"name":"DefaultMember"}],"description":"Returns the path for a specified file."}, @@ -170,16 +184,18 @@ {"name":"DeleteFolder","kind":"sub","vis":"public","signature":"Public Sub DeleteFolder(ByVal folderSpec As String, Optional ByVal force As Boolean = False, Optional ByVal ignoreMissing As Boolean = False)","description":"Deletes a folder and its contents (wildcards supported). force clears read-only attributes as it goes. ignoreMissing (default False) suppresses the path-not-found error when the folder or wildcard pattern matches nothing; it does not suppress other failures such as the path being a file or the folder being locked or non-empty."}, {"name":"DriveExists","kind":"function","vis":"public","signature":"Public Function DriveExists(ByVal driveSpec As String) As Boolean","returnType":"Boolean","description":"Returns True if a specified drive exists; False if it does not."}, {"name":"Drives","kind":"function","vis":"public","signature":"Public Function Drives() As Drives","returnType":"Drives","description":"Returns a Drives collection of all logical drives present on the system."}, + {"name":"FileContainsText","kind":"function","vis":"public","signature":"Public Function FileContainsText(ByVal filePath As String, ByVal searchText As String, Optional ByVal ignoreCase As Boolean = True) As Boolean","returnType":"Boolean","description":"Returns True if the given text is found in the file's decoded contents. Encoding is auto-detected; returns False for a binary or unknown-encoding file. Raises 5 on an empty search string."}, {"name":"FileExists","kind":"function","vis":"public","signature":"Public Function FileExists(ByVal fileSpec As String) As Boolean","returnType":"Boolean","description":"Returns True if a specified file exists; False if it does not."}, + {"name":"FindInTextFiles","kind":"function","vis":"public","signature":"Public Function FindInTextFiles( ByVal folderPath As String, ByVal searchText As String, Optional ByVal filePattern As String = \"*\", Optional ByVal recursive As Boolean = False, Optional ByVal ignoreCase As Boolean = True, Optional ByVal fileSizeCapInMb As Long = -1) As Collection","returnType":"Collection","description":"Returns the paths of text files under a folder whose decoded contents contain the given text. Encoding is auto-detected per file; binary or unknown-encoding files are skipped."}, {"name":"FolderExists","kind":"function","vis":"public","signature":"Public Function FolderExists(ByVal folderSpec As String) As Boolean","returnType":"Boolean","description":"Returns True if a specified folder exists; False if it does not."}, - {"name":"GetAbsolutePathName","kind":"function","vis":"public","signature":"Public Function GetAbsolutePathName(ByVal path As String, Optional ByVal baseDirectory As String = \"\", Optional ByVal useLongPathPrefix As Boolean = False) As String","returnType":"String","description":"Returns a complete and unambiguous path from a provided path specification."}, + {"name":"GetAbsolutePathName","kind":"function","vis":"public","signature":"Public Function GetAbsolutePathName(ByVal path As String, Optional ByVal baseDirectory As String = \"\") As String","returnType":"String","description":"Returns a complete and unambiguous path from a path specification, resolving from the current directory or baseDirectory if supplied. The input path need not exist."}, {"name":"GetBaseName","kind":"function","vis":"public","signature":"Public Function GetBaseName(ByVal path As String) As String","returnType":"String","description":"Returns a string containing the base name of the last component, less any file extension, in a path."}, {"name":"GetCurrentDir","kind":"function","vis":"public","signature":"Public Function GetCurrentDir() As String","returnType":"String","description":"Returns the current directory."}, {"name":"GetDrive","kind":"function","vis":"public","signature":"Public Function GetDrive(ByVal driveSpec As String) As Drive","returnType":"Drive","description":"Returns a Drive object for a drive spec: \"C\", \"C:\", \"C:\\\", a UNC share, or any path on the drive."}, {"name":"GetDriveName","kind":"function","vis":"public","signature":"Public Function GetDriveName(ByVal path As String) As String","returnType":"String","description":"Returns a string containing the name of the drive for a specified path."}, {"name":"GetExtensionName","kind":"function","vis":"public","signature":"Public Function GetExtensionName(ByVal path As String) As String","returnType":"String","description":"Returns a string containing the extension name for the last component in a path."}, {"name":"GetFile","kind":"function","vis":"public","signature":"Public Function GetFile(ByVal filePath As String) As File","returnType":"File","description":"Returns a File object for an existing file."}, - {"name":"GetFileEncoding","kind":"function","vis":"public","signature":"Public Function GetFileEncoding(ByVal filePath As String, Optional ByVal sampleBytes As Long = 65536) As TextEncoding","returnType":"TextEncoding","description":"Detects a file's text encoding from a head sample (default 64 KB): BOM sniff first, then UTF-16/32 and UTF-8 heuristics, else system ANSI. Returns a signed TextEncoding (negative = BOM present), or encUnknown if the file is empty or looks binary. Raises if the file cannot be read."}, + {"name":"GetFileEncoding","kind":"function","vis":"public","signature":"Public Function GetFileEncoding(ByVal filePath As String) As TextEncoding","returnType":"TextEncoding","description":"Detects a file's text encoding from a head sample (default 64 KB): BOM sniff first, then UTF-16/32 and UTF-8 heuristics, else system ANSI. Returns a signed TextEncoding (negative = BOM present), or encUnknown if the file is empty or looks binary. Raises if the file cannot be read."}, {"name":"GetFileLineEnding","kind":"function","vis":"public","signature":"Public Function GetFileLineEnding(ByVal filePath As String, Optional ByVal encoding As TextEncoding = encAutoDetect) As NewlineType","returnType":"NewlineType","description":"Reads a text file and reports its line-ending style: nlWindows / nlUnix / nlMacintosh for a consistent file, nlMixed if it contains more than one, or nlUnknown if it has none. Reads the whole file, so the result (including mixed) is exact."}, {"name":"GetFileName","kind":"function","vis":"public","signature":"Public Function GetFileName(ByVal path As String) As String","returnType":"String","description":"Returns the last component of a specified path that is not part of the drive specification."}, {"name":"GetFilePaths","kind":"function","vis":"public","signature":"Public Function GetFilePaths(ByVal folderPath As String, Optional ByVal pattern As String = \"*\", Optional ByVal includeHidden As Boolean = False, Optional ByVal includeSystem As Boolean = False, Optional ByVal recursive As Boolean = False) As Collection","returnType":"Collection","description":"Returns a Collection of full file paths in the specified folder. Supports a wildcard pattern, optional recursion into subfolders, inclusion of hidden/system files."}, @@ -191,10 +207,11 @@ {"name":"GetSpecialFolder","kind":"function","vis":"public","signature":"Public Function GetSpecialFolder(ByVal specialFolder As SpecialFolderConst) As String","returnType":"String","description":"Returns the absolute path to the requested known folder. The returned path does NOT include a trailing backslash."}, {"name":"GetStandardStream","kind":"function","vis":"public","signature":"Public Function GetStandardStream(ByVal standardStreamType As StandardStreamTypes, Optional ByVal unicode As Boolean = False) As TextStream","returnType":"TextStream","description":"Returns a TextStream over one of the process's standard streams. unicode:=True gives UTF-16LE (no BOM); default is the system ANSI code page. Raises if no handle is available (e.g. no console)."}, {"name":"GetTempName","kind":"function","vis":"public","signature":"Public Function GetTempName(Optional ByVal prefix As String, Optional ByVal extension As String = \".tmp\", Optional ByVal inFolder As String = \"\") As String","returnType":"String","description":"Returns a randomly generated temporary file or folder name. If inFolder is supplied, the returned name is one that does not already exist in that folder at the time of the call."}, + {"name":"IsFileLocked","kind":"function","vis":"public","signature":"Public Function IsFileLocked(ByVal filePath As String) As Boolean","returnType":"Boolean","description":"Returns True if the file is currently locked by another process - that is, if it cannot be opened for exclusive read/write access right now. The answer is only valid for the instant of the call. Raises if the file does not exist."}, {"name":"MergeTextFiles","kind":"sub","vis":"public","signature":"Public Sub MergeTextFiles(ByVal firstFilePath As String, ByVal secondFilePath As String, Optional ByVal outputFilePath As String, Optional ByVal insertBlankLineBetween As Boolean = False, Optional ByVal firstFileEncoding As TextEncoding = encAutoDetect, Optional ByVal secondFileEncoding As TextEncoding = encAutoDetect)","description":"Merges two text files in the order specified. If outputFilePath is omitted, the first file is overwritten in place. The second file's line endings are converted to match the first file's. Streams line by line, so file size is not limited."}, {"name":"MoveFile","kind":"sub","vis":"public","signature":"Public Sub MoveFile(ByVal source As String, ByVal destination As String)","description":"Moves one or more files from one location to another."}, {"name":"MoveFolder","kind":"sub","vis":"public","signature":"Public Sub MoveFolder(ByVal source As String, ByVal destination As String)","description":"Moves a folder from one location to another."}, - {"name":"NormalizeTextFile","kind":"function","vis":"public","signature":"Public Function NormalizeTextFile(ByVal filePath As String, Optional ByVal toEncoding As TextEncoding = encUtf8, Optional ByVal toNewLine As NewlineType = nlWindows, Optional ByVal fromEncoding As TextEncoding = encAutoDetect, Optional ByVal toAttributes As FileAttribute = faNoChange, Optional ByVal skipIfUnchanged As Boolean = True) As Boolean","returnType":"Boolean","description":"Rewrites a text file in place in the target encoding and newline style (source auto-detected). toAttributes enforces a target attribute set (settable bits only); NoChange (the default) preserves the file's current attributes. Skips the write when content is already in target form. Refuses binary files. Returns True if the file was modified."}, + {"name":"NormalizeTextFile","kind":"function","vis":"public","signature":"Public Function NormalizeTextFile(ByVal filePath As String, Optional ByVal toEncoding As TextEncoding = encUtf8, Optional ByVal toNewLine As NewlineType = nlWindows, Optional ByVal fromEncoding As TextEncoding = encAutoDetect, Optional ByVal toAttributes As FileAttribute = faNoChange) As Boolean","returnType":"Boolean","description":"Rewrites a text file in place in the target encoding and newline style (source auto-detected). toAttributes enforces a target attribute set (settable bits only); NoChange (the default) preserves the file's current attributes. Skips the write when content is already in target form. Refuses binary files. Returns True if the file was modified."}, {"name":"OpenTextFile","kind":"function","vis":"public","signature":"Public Function OpenTextFile(ByVal fileName As String, Optional ByVal ioMode As IOMode = ForReading, Optional ByVal create As Boolean = False, Optional ByVal encoding As TextEncoding = encAutoDetect, Optional ByVal writeModeLineEnding As NewlineType = nlWindows) As TextStream","returnType":"TextStream","description":"Opens a text file and returns a streaming TextStream. For reading, omit encoding (encAutoDetect) to detect it from a head sample. For writing/appending, encoding selects the output encoding (default UTF-8, no BOM). Encodings whose chunk boundaries cannot be managed (UTF-7, unclassifiable code pages) transparently fall back to a buffered path."}, {"name":"ReadStream","kind":"sub","vis":"public","signature":"Public Sub ReadStream(ByVal filePath As String, ByRef stream() As Byte, Optional ByVal maxBytesToRead As Long = -1)","description":"Reads the file specified into a byte array."}, {"name":"Rename","kind":"sub","vis":"public","signature":"Public Sub Rename(ByVal source As String, ByVal newName As String)","description":"Renames a file or folder in place. The new name must be a bare name, or a path that resolves to the same folder as the source — it cannot move the item to a different folder. Wildcards are not permitted, and the destination must not already exist."}, @@ -239,9 +256,11 @@ {"name":"EnsureParentExists","kind":"sub","vis":"private","signature":"Private Sub EnsureParentExists(ByVal path As String)"}, {"name":"EnumerateLevel","kind":"sub","vis":"private","signature":"Private Sub EnumerateLevel(ByVal folder As String, ByVal pattern As String, ByVal includeHidden As Boolean, ByVal includeSystem As Boolean, ByRef result As Collection)"}, {"name":"EnumerateSubFolders","kind":"function","vis":"private","signature":"Private Function EnumerateSubFolders(ByVal folder As String, ByVal includeHidden As Boolean, ByVal includeSystem As Boolean) As Collection","returnType":"Collection"}, + {"name":"FileContainsText","kind":"function","vis":"public","signature":"Public Function FileContainsText(ByVal filePath As String, ByVal searchText As String, Optional ByVal ignoreCase As Boolean = True) As Boolean","returnType":"Boolean","description":"Returns True if the given text is found in the file's decoded contents. Encoding is auto-detected; returns False for a binary or unknown-encoding file. Raises 5 on an empty search string."}, {"name":"FileExists","kind":"function","vis":"public","signature":"Public Function FileExists(ByVal filePath As String) As Boolean","returnType":"Boolean","description":"Returns True if a specified file exists; False if it does not."}, + {"name":"FindInTextFiles","kind":"function","vis":"public","signature":"Public Function FindInTextFiles( ByVal folderPath As String, ByVal searchText As String, Optional ByVal filePattern As String = \"*\", Optional ByVal recursive As Boolean = False, Optional ByVal ignoreCase As Boolean = True, Optional ByVal fileSizeCapInMb As Long = -1) As Collection","returnType":"Collection","description":"Returns the paths of text files under a folder whose decoded contents contain the given text. Wildcard charcters in 'containsText' interpreted literally. Encoding is auto-detected per file; binary or unknown-encoding files are skipped."}, {"name":"FolderExists","kind":"function","vis":"public","signature":"Public Function FolderExists(ByVal folderPath As String) As Boolean","returnType":"Boolean","description":"Returns True if a specified folder exists; False if it does not."}, - {"name":"GetAbsolutePathName","kind":"function","vis":"public","signature":"Public Function GetAbsolutePathName(ByVal path As String, Optional ByVal baseDirectory As String = \"\", Optional ByVal useLongPathPrefix As Boolean = False) As String","returnType":"String","description":"Returns a complete and unambiguous path from a provided path specification."}, + {"name":"GetAbsolutePathName","kind":"function","vis":"public","signature":"Public Function GetAbsolutePathName(ByVal path As String, Optional ByVal baseDirectory As String = \"\") As String","returnType":"String","description":"Returns a complete and unambiguous path from a path specification, resolving from the current directory or baseDirectory if supplied. The input path need not exist."}, {"name":"GetBaseName","kind":"function","vis":"public","signature":"Public Function GetBaseName(ByVal path As String) As String","returnType":"String","description":"Returns a string containing the base name of the last component, less any file extension, in a path."}, {"name":"GetCurrentDir","kind":"function","vis":"public","signature":"Public Function GetCurrentDir() As String","returnType":"String","description":"Returns the current directory."}, {"name":"GetDrive","kind":"function","vis":"public","signature":"Public Function GetDrive(ByVal drivePath As String) As Drive","returnType":"Drive","description":"Returns a Drive object for a drive spec: \"C\", \"C:\", \"C:\\\", a UNC share, or any path on the drive."}, @@ -249,7 +268,7 @@ {"name":"GetDrives","kind":"function","vis":"public","signature":"Public Function GetDrives() As Drives","returnType":"Drives","description":"Returns a Drives collection of all logical drives present on the system."}, {"name":"GetExtensionName","kind":"function","vis":"public","signature":"Public Function GetExtensionName(ByVal path As String) As String","returnType":"String","description":"Returns a string containing the extension name for the last component in a path."}, {"name":"GetFile","kind":"function","vis":"public","signature":"Public Function GetFile(ByVal filePath As String) As File","returnType":"File","description":"Returns a File object for an existing file."}, - {"name":"GetFileEncoding","kind":"function","vis":"public","signature":"Public Function GetFileEncoding(ByVal filePath As String, Optional ByVal sampleBytes As Long = 65536) As TextEncoding","returnType":"TextEncoding","description":"Detects a file's text encoding from a head sample (default 64 KB): BOM sniff first, then UTF-16/32 and UTF-8 heuristics, else system ANSI. Returns a signed TextEncoding (negative = BOM present), or encUnknown if the file is empty or looks binary. Raises if the file cannot be read."}, + {"name":"GetFileEncoding","kind":"function","vis":"public","signature":"Public Function GetFileEncoding(ByVal filePath As String) As TextEncoding","returnType":"TextEncoding","description":"Detects a file's text encoding from a head sample (default 64 KB): BOM sniff first, then UTF-16/32 and UTF-8 heuristics, else system ANSI. Returns a signed TextEncoding (negative = BOM present), or encUnknown if the file is empty or looks binary. Raises if the file cannot be read."}, {"name":"GetFileLineEnding","kind":"function","vis":"public","signature":"Public Function GetFileLineEnding(ByVal filePath As String, Optional ByVal encoding As TextEncoding = encAutoDetect) As NewlineType","returnType":"NewlineType","description":"Reads a text file and reports its line-ending style: nlWindows / nlUnix / nlMacintosh for a consistent file, nlMixed if it contains more than one, or nlUnknown if it has none. Reads the whole file, so the result (including mixed) is exact."}, {"name":"GetFileName","kind":"function","vis":"public","signature":"Public Function GetFileName(ByVal path As String) As String","returnType":"String","description":"Returns the last component of a specified path that is not part of the drive specification."}, {"name":"GetFilePaths","kind":"function","vis":"public","signature":"Public Function GetFilePaths(ByVal folderPath As String, Optional ByVal pattern As String = \"*\", Optional ByVal includeHidden As Boolean = False, Optional ByVal includeSystem As Boolean = False, Optional ByVal recursive As Boolean = False) As Collection","returnType":"Collection","description":"Returns a Collection of full file paths in the specified folder. Supports a wildcard pattern, optional recursion into subfolders, inclusion of hidden/system files."}, @@ -259,14 +278,15 @@ {"name":"GetParentFolderName","kind":"function","vis":"public","signature":"Public Function GetParentFolderName(ByVal path As String) As String","returnType":"String","description":"Returns a string containing the name of the parent folder of the last component in a specified path."}, {"name":"GetRelativePath","kind":"function","vis":"public","signature":"Public Function GetRelativePath(ByVal basePath As String, ByVal targetPath As String, Optional ByVal basePathIsFolder As Boolean = True, Optional ByVal trimLeadingDotSlash As Boolean = True) As String","returnType":"String","description":"Returns a relative path from a base path to a target path. The base path is treated as a folder by default. Relative inputs are resolved against the current directory. Returns the absolute target if no relative path exists (e.g. different drives)."}, {"name":"GetSpecialFolder","kind":"function","vis":"public","signature":"Public Function GetSpecialFolder(ByVal specialFolder As SpecialFolderConst) As String","returnType":"String","description":"Returns the absolute path to the requested known folder. The returned path does NOT include a trailing backslash."}, + {"name":"GetSpecialFolderCSIDL","kind":"function","vis":"private","signature":"Private Function GetSpecialFolderCSIDL(ByVal specialFolder As SpecialFolderConst) As String","returnType":"String"}, + {"name":"GetSpecialFolderKnown","kind":"function","vis":"private","signature":"Private Function GetSpecialFolderKnown(ByVal specialFolder As SpecialFolderConst) As String","returnType":"String"}, {"name":"GetStandardStream","kind":"function","vis":"public","signature":"Public Function GetStandardStream(ByVal standardStreamType As StandardStreamTypes, Optional ByVal unicode As Boolean = False) As TextStream","returnType":"TextStream","description":"Returns a TextStream over one of the process's standard streams. unicode:=True gives UTF-16LE (no BOM); default is the system ANSI code page. Raises if no handle is available (e.g. no console)."}, {"name":"GetTempName","kind":"function","vis":"public","signature":"Public Function GetTempName(Optional ByVal prefix As String, Optional ByVal extension As String = \".tmp\", Optional ByVal inFolder As String = \"\") As String","returnType":"String","description":"Returns a randomly generated temporary file or folder name. If inFolder is supplied, the returned name is one that does not already exist in that folder at the time of the call."}, - {"name":"GuardWriteName","kind":"sub","vis":"private","signature":"Private Sub GuardWriteName(ByVal path As String, ByVal opName As String)"}, {"name":"GuidFromString","kind":"function","vis":"private","signature":"Private Function GuidFromString(ByVal s As String) As UUID","returnType":"UUID"}, {"name":"HasWildcards","kind":"function","vis":"private","signature":"Private Function HasWildcards(ByVal s As String) As Boolean","returnType":"Boolean"}, {"name":"IsDriveRelative","kind":"function","vis":"private","signature":"Private Function IsDriveRelative(ByVal p As String) As Boolean","returnType":"Boolean"}, + {"name":"IsFileLocked","kind":"function","vis":"public","signature":"Public Function IsFileLocked(ByVal filePath As String) As Boolean","returnType":"Boolean","description":"Returns True if the file is currently locked by another process - that is, if it cannot be opened for exclusive read/write access right now. The answer is only valid for the instant of the call. Raises if the file does not exist."}, {"name":"IsFolderPathOrExistingFolder","kind":"function","vis":"private","signature":"Private Function IsFolderPathOrExistingFolder(ByVal path As String) As Boolean","returnType":"Boolean"}, - {"name":"IsReservedDeviceName","kind":"function","vis":"private","signature":"Private Function IsReservedDeviceName(ByVal name As String) As Boolean","returnType":"Boolean"}, {"name":"IsUncRoot","kind":"function","vis":"private","signature":"Private Function IsUncRoot(ByVal s As String) As Boolean","returnType":"Boolean"}, {"name":"KFID_CommonStartup_STR","kind":"const","vis":"private","signature":"Private Const KFID_CommonStartup_STR As String = \"{82A5EA35-D9CD-47C5-9629-E15D2F714E6E}\""}, {"name":"KFID_Desktop_STR","kind":"const","vis":"private","signature":"Private Const KFID_Desktop_STR As String = \"{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}\""}, @@ -299,7 +319,7 @@ {"name":"MoveOneFile","kind":"function","vis":"private","signature":"Private Function MoveOneFile(ByVal source As String, ByVal destination As String) As String","returnType":"String"}, {"name":"MoveOneFolder","kind":"function","vis":"private","signature":"Private Function MoveOneFolder(ByVal source As String, ByVal destination As String) As String","returnType":"String"}, {"name":"MoveWildcard","kind":"function","vis":"private","signature":"Private Function MoveWildcard(ByVal sourcePattern As String, ByVal destination As String, ByVal wantFolders As Boolean) As String","returnType":"String"}, - {"name":"NormalizeTextFile","kind":"function","vis":"public","signature":"Public Function NormalizeTextFile(ByVal filePath As String, Optional ByVal toEncoding As TextEncoding = encUtf8, Optional ByVal toNewLine As NewlineType = nlWindows, Optional ByVal fromEncoding As TextEncoding = encAutoDetect, Optional ByVal toAttributes As FileAttribute = faNoChange, Optional ByVal skipIfUnchanged As Boolean = True) As Boolean","returnType":"Boolean","description":"Rewrites a text file in place in the target encoding and newline style (source auto-detected). toAttributes enforces a target attribute set (settable bits only); NoChange (the default) preserves the file's current attributes. Skips the write when content is already in target form. Refuses binary files. Returns True if the file was modified."}, + {"name":"NormalizeTextFile","kind":"function","vis":"public","signature":"Public Function NormalizeTextFile(ByVal filePath As String, Optional ByVal toEncoding As TextEncoding = encUtf8, Optional ByVal toNewLine As NewlineType = nlWindows, Optional ByVal fromEncoding As TextEncoding = encAutoDetect, Optional ByVal toAttributes As FileAttribute = faNoChange) As Boolean","returnType":"Boolean","description":"Rewrites a text file in place in the target encoding and newline style (source auto-detected). toAttributes enforces a target attribute set (settable bits only); NoChange (the default) preserves the file's current attributes. Skips the write when content is already in target form. Refuses binary files. Returns True if the file was modified."}, {"name":"OpenTextFile","kind":"function","vis":"public","signature":"Public Function OpenTextFile(ByVal filePath As String, Optional ByVal ioMode As IOMode = ForReading, Optional ByVal create As Boolean = False, Optional ByVal encoding As TextEncoding = encAutoDetect, Optional ByVal writeModeLineEnding As NewlineType = nlWindows) As TextStream","returnType":"TextStream","description":"Opens a text file and returns a streaming TextStream. For reading, omit encoding (encAutoDetect) to detect it from a head sample. For writing/appending, encoding selects the output encoding (default UTF-8, no BOM). Encodings whose chunk boundaries cannot be managed (UTF-7, unclassifiable code pages) transparently fall back to a buffered path."}, {"name":"PathExists","kind":"function","vis":"private","signature":"Private Function PathExists(ByVal path As String) As Boolean","returnType":"Boolean"}, {"name":"PeekWord","kind":"function","vis":"private","signature":"Private Function PeekWord(ByVal p As LongPtr) As Integer","returnType":"Integer"}, @@ -308,6 +328,7 @@ {"name":"ReadStringFileInfo","kind":"function","vis":"private","signature":"Private Function ReadStringFileInfo(ByRef buffer() As Byte, ByVal fieldName As String) As String","returnType":"String"}, {"name":"Rename","kind":"function","vis":"public","signature":"Public Function Rename(ByVal source As String, ByVal newName As String) As String","returnType":"String","description":"Renames a file or folder in place and returns the resolved absolute path of the renamed item. The new name may be a bare name or a path resolving to the same folder as the source (case-only changes are permitted); it cannot move the item to a different folder. Wildcards are not permitted, and the destination must not already exist."}, {"name":"ResolveAttrForRelative","kind":"function","vis":"private","signature":"Private Function ResolveAttrForRelative(ByVal path As String) As FILE_ATTRIBUTES","returnType":"FILE_ATTRIBUTES"}, + {"name":"SearchOneFile","kind":"sub","vis":"private","signature":"Private Sub SearchOneFile( ByVal path As String, ByVal containsText As String, ByVal ignoreCase As Boolean, ByVal maxWholeRead As LongLong, ByVal userCap As LongLong, ByRef results As Collection)"}, {"name":"SetCurrentDir","kind":"sub","vis":"public","signature":"Public Sub SetCurrentDir(ByVal folderPath As String)","description":"Sets the current directory."}, {"name":"StringToTextFile","kind":"sub","vis":"public","signature":"Public Sub StringToTextFile(ByVal text As String, ByVal filePath As String, Optional ByVal encoding As TextEncoding = encUtf8, Optional ByVal outLineEnding As NewlineType = nlNoChange)","description":"Writes a string to a file in the specified encoding (a BOM is emitted only for BOM-bearing members such as encUtf8Bom). Overwrites any existing file."}, {"name":"TextFileToArray","kind":"function","vis":"public","signature":"Public Function TextFileToArray(ByVal filePath As String, Optional ByVal encoding As TextEncoding = encAutoDetect) As String()","returnType":"String()","description":"Reads a text file into an array of lines (mixed CR/LF/CRLF handled). Encoding auto-detected by default. A trailing newline yields a final empty element. An empty file returns an unallocated array."}, @@ -583,19 +604,24 @@ {"name":"CountUTF32UnitsFromUTF16","kind":"function","vis":"private","signature":"Private Function CountUTF32UnitsFromUTF16(utf16Bytes() As Byte) As Long","returnType":"Long"}, {"name":"Decode","kind":"function","vis":"public","signature":"Public Function Decode(ByRef stream() As Byte, ByVal enc As TextEncoding, Optional ByVal outLineEnding As NewlineType = nlNoChange) As String","returnType":"String"}, {"name":"DecodeBytes","kind":"function","vis":"public","signature":"Public Function DecodeBytes(ByRef stream() As Byte, ByVal codePage As Long, Optional ByVal outLineEnding As NewlineType = nlNoChange) As String","returnType":"String"}, - {"name":"Detect","kind":"function","vis":"public","signature":"Public Function Detect(ByRef stream() As Byte, Optional ByVal seed As TextEncoding = encAutoDetect) As TextEncoding","returnType":"TextEncoding"}, + {"name":"Detect","kind":"function","vis":"public","signature":"Public Function Detect(ByRef stream() As Byte, Optional ByVal seed As TextEncoding = encAutoDetect, Optional ByVal trueLength As Long = -1) As TextEncoding","returnType":"TextEncoding"}, {"name":"DetectNewlineType","kind":"function","vis":"public","signature":"Public Function DetectNewlineType(ByVal text As String) As NewlineType","returnType":"NewlineType"}, - {"name":"DetectUTF1632","kind":"function","vis":"private","signature":"Private Function DetectUTF1632(stream() As Byte, Optional ByVal maxBytesToScan As Long = 2000, Optional ByVal maxNullsToFind As Long = 100) As TextEncoding","returnType":"TextEncoding"}, + {"name":"DetectUTF32","kind":"function","vis":"private","signature":"Private Function DetectUTF32(bytes() As Byte, Optional ByVal maxBytesToScan As Long = 1024) As TextEncoding","returnType":"TextEncoding"}, {"name":"Encode","kind":"function","vis":"public","signature":"Public Function Encode(ByVal text As String, ByVal enc As TextEncoding) As Byte()","returnType":"Byte()"}, {"name":"EncodeBytes","kind":"function","vis":"public","signature":"Public Function EncodeBytes(ByVal text As String, ByVal codePage As Long) As Byte()","returnType":"Byte()"}, + {"name":"EntropyOf","kind":"function","vis":"private","signature":"Private Function EntropyOf(counts() As Long, ByVal total As Long) As Double","returnType":"Double"}, + {"name":"EntropySkew","kind":"function","vis":"private","signature":"Private Function EntropySkew(b() As Byte, Optional ByVal maxBytesToScan As Long = 1024) As Long","returnType":"Long"}, {"name":"GetNewLine","kind":"function","vis":"public","signature":"Public Function GetNewLine(ByVal nlType As NewlineType) As String","returnType":"String"}, {"name":"HALF_BASE","kind":"const","vis":"private","signature":"Private Const HALF_BASE As Long = &H10000"}, {"name":"HasBom","kind":"function","vis":"private","signature":"Private Function HasBom(ByVal enc As TextEncoding) As Boolean","returnType":"Boolean"}, - {"name":"IsValidUTF8","kind":"function","vis":"private","signature":"Private Function IsValidUTF8(stream() As Byte, Optional ByVal maxBytesToScan As Long = 8192) As Boolean","returnType":"Boolean"}, + {"name":"IsPlausibleBmpUnit","kind":"function","vis":"private","signature":"Private Function IsPlausibleBmpUnit(ByVal cu As Long) As Boolean","returnType":"Boolean"}, + {"name":"IsValidUTF8","kind":"function","vis":"private","signature":"Private Function IsValidUTF8(stream() As Byte, Optional ByVal maxBytesToScan As Long = 32768) As Boolean","returnType":"Boolean"}, {"name":"IsWideText","kind":"function","vis":"public","signature":"Public Function IsWideText(ByVal enc As TextEncoding) As Boolean","returnType":"Boolean"}, - {"name":"LooksLikeBinary","kind":"function","vis":"public","signature":"Public Function LooksLikeBinary(ByRef raw() As Byte, Optional ByVal maxBytesToScan As Long = 8192) As Boolean","returnType":"Boolean"}, + {"name":"LooksLikeBinary","kind":"function","vis":"public","signature":"Public Function LooksLikeBinary(ByRef raw() As Byte, Optional ByVal maxBytesToScan As Long = 32768, Optional ByVal trueLength As Long = -1) As Boolean","returnType":"Boolean"}, + {"name":"LooksLikeText","kind":"function","vis":"private","signature":"Private Function LooksLikeText(bytes() As Byte, ByVal skewPos As Boolean, Optional ByVal maxBytesToScan As Long = 32768) As Boolean","returnType":"Boolean"}, {"name":"SafeCountB","kind":"function","vis":"public","signature":"Public Function SafeCountB(ByRef b() As Byte) As Long","returnType":"Long"}, {"name":"SliceBytes","kind":"function","vis":"public","signature":"Public Function SliceBytes(ByRef b() As Byte, ByVal startIdx As Long, ByVal endIdx As Long) As Byte()","returnType":"Byte()"}, + {"name":"SniffBinary","kind":"function","vis":"private","signature":"Private Function SniffBinary(ByRef stream() As Byte) As Boolean","returnType":"Boolean"}, {"name":"SniffBom","kind":"function","vis":"private","signature":"Private Function SniffBom(ByRef stream() As Byte) As TextEncoding","returnType":"TextEncoding"}, {"name":"SwapByteOrder16","kind":"sub","vis":"private","signature":"Private Sub SwapByteOrder16(ByRef stream() As Byte)"}, {"name":"SwapByteOrder32","kind":"sub","vis":"private","signature":"Private Sub SwapByteOrder32(ByRef stream() As Byte)"}, @@ -603,7 +629,8 @@ {"name":"UNI_SUR_HIGH_END","kind":"const","vis":"private","signature":"Private Const UNI_SUR_HIGH_END As Long = &HDBFF&"}, {"name":"UNI_SUR_HIGH_START","kind":"const","vis":"private","signature":"Private Const UNI_SUR_HIGH_START As Long = &HD800&"}, {"name":"UNI_SUR_LOW_END","kind":"const","vis":"private","signature":"Private Const UNI_SUR_LOW_END As Long = &HDFFF&"}, - {"name":"UNI_SUR_LOW_START","kind":"const","vis":"private","signature":"Private Const UNI_SUR_LOW_START As Long = &HDC00&"} + {"name":"UNI_SUR_LOW_START","kind":"const","vis":"private","signature":"Private Const UNI_SUR_LOW_START As Long = &HDC00&"}, + {"name":"UnitAt","kind":"function","vis":"private","signature":"Private Function UnitAt(bytes() As Byte, ByVal i As Long, ByVal skewPos As Boolean) As Long","returnType":"Long"} ] }, { @@ -641,7 +668,6 @@ {"name":"FlushWriteChunk","kind":"sub","vis":"private","signature":"Private Sub FlushWriteChunk(ByVal flushAll As Boolean)"}, {"name":"GENERIC_WRITE","kind":"const","vis":"private","signature":"Private Const GENERIC_WRITE As Long = &H40000000"}, {"name":"Gb18030PrefixLen","kind":"function","vis":"private","signature":"Private Function Gb18030PrefixLen(ByRef b() As Byte, ByVal total As Long) As Long","returnType":"Long"}, - {"name":"GuardCreateName","kind":"sub","vis":"private","signature":"Private Sub GuardCreateName(ByVal path As String, ByVal opName As String)"}, {"name":"Init","kind":"sub","vis":"friend","signature":"Friend Sub Init(ByVal filePath As String, ByVal ioMode As IOMode, ByVal create As Boolean, ByVal format As TextEncoding, ByVal lineEnding As NewlineType)"}, {"name":"InitAppending","kind":"sub","vis":"private","signature":"Private Sub InitAppending(ByVal format As TextEncoding, ByVal create As Boolean)"}, {"name":"InitReading","kind":"sub","vis":"private","signature":"Private Sub InitReading(ByVal format As TextEncoding)"}, @@ -769,6 +795,7 @@ {"name":"FileTimeToLocalFileTime","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function FileTimeToLocalFileTime Lib \"kernel32\" (lpFileTime As FILETIME, lpLocalFileTime As FILETIME) As BOOL","returnType":"BOOL"}, {"name":"FileTimeToSystemTime","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function FileTimeToSystemTime Lib \"kernel32\" (lpFileTime As FILETIME, lpSystemTime As SYSTEMTIME) As BOOL","returnType":"BOOL"}, {"name":"FindClose","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function FindClose Lib \"kernel32\" (ByVal hFindFile As LongPtr) As Long","returnType":"Long"}, + {"name":"FindFirstFileExW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function FindFirstFileExW Lib \"kernel32\" (ByVal lpFileName As LongPtr, ByVal fInfoLevelId As FINDEX_INFO_LEVELS, ByRef lpFindFileData As WIN32_FIND_DATAW, ByVal fSearchOp As FINDEX_SEARCH_OPS, ByVal lpSearchFilter As LongPtr, ByVal dwAdditionalFlags As FINDEX_EXTRA_FLAGS) As LongPtr","returnType":"LongPtr"}, {"name":"FindFirstFileW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function FindFirstFileW Lib \"kernel32\" (ByVal lpFileName As LongPtr, ByRef lpFindFileData As WIN32_FIND_DATAW) As LongPtr","returnType":"LongPtr"}, {"name":"FindNextFileW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function FindNextFileW Lib \"kernel32\" (ByVal hFindFile As LongPtr, ByRef lpFindFileData As WIN32_FIND_DATAW) As Long","returnType":"Long"}, {"name":"GetACP","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetACP Lib \"kernel32\" () As Long","returnType":"Long"}, @@ -785,20 +812,28 @@ {"name":"GetLogicalDrives","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetLogicalDrives Lib \"kernel32\" () As Long","returnType":"Long"}, {"name":"GetShortPathNameW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetShortPathNameW Lib \"kernel32\" (ByVal lpszLongPath As LongPtr, ByVal lpszShortPath As LongPtr, ByVal cchBuffer As Long) As Long","returnType":"Long"}, {"name":"GetStdHandle","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetStdHandle Lib \"kernel32\" (ByVal nStdHandle As STD_HANDLES) As LongPtr","returnType":"LongPtr"}, + {"name":"GetThreadLocale","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetThreadLocale Lib \"kernel32\" () As Long","returnType":"Long"}, {"name":"GetVolumeInformationW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetVolumeInformationW Lib \"kernel32\" (ByVal lpRootPathName As LongPtr, ByVal lpVolumeNameBuffer As LongPtr, ByVal nVolumeNameSize As Long, ByRef lpVolumeSerialNumber As Long, ByRef lpMaximumComponentLength As Long, ByRef lpFileSystemFlags As Long, ByVal lpFileSystemNameBuffer As LongPtr, ByVal nFileSystemNameSize As Long) As Long","returnType":"Long"}, {"name":"INVALID_HANDLE_VALUE","kind":"const","vis":"public","signature":"Public Const INVALID_HANDLE_VALUE As LongPtr = -1"}, + {"name":"LCMapStringEx","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function LCMapStringEx Lib \"kernel32\" (ByVal lpLocaleName As String, [TypeHint(LCMAP_FLAGS, COMPARE_FLAGS)] ByVal dwMapFlags As Long, ByVal lpSrcStr As String, ByVal cchSrc As Long, ByVal lpDestStr As LongPtr, ByVal cchDest As Long, lpVersionInformation As NLSVERSIONINFO, Optional ByVal lpReserved As LongPtr, Optional ByVal sortHandle As LongPtr) As Long","returnType":"Long"}, + {"name":"LCMapStringW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function LCMapStringW Lib \"kernel32\" (ByVal Locale As DefaultLocales, [TypeHint(LCMAP_FLAGS, COMPARE_FLAGS)] ByVal dwMapFlags As Long, ByVal lpSrcStr As LongPtr, ByVal cchSrc As Long, ByVal lpDestStr As LongPtr, ByVal cchDest As Long) As Long","returnType":"Long"}, + {"name":"LOCALE_NAME_INVARIANT","kind":"const","vis":"public","signature":"Public Const LOCALE_NAME_INVARIANT As String = \"\""}, {"name":"LPWSTRtoStr","kind":"function","vis":"public","signature":"Public Function LPWSTRtoStr(lPtr As LongPtr, Optional ByVal fFree As Boolean = True) As String","returnType":"String"}, {"name":"MAX_PATH","kind":"const","vis":"public","signature":"Public Const MAX_PATH As Long = 260"}, {"name":"MoveFileExW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function MoveFileExW Lib \"kernel32\" (ByVal lpExistingFileName As LongPtr, ByVal lpNewFileName As LongPtr, ByVal dwFlags As MoveFileExFlags) As BOOL","returnType":"BOOL"}, {"name":"MultiByteToWideChar","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function MultiByteToWideChar Lib \"kernel32\" (ByVal CodePage As Long, ByVal dwFlags As MBWC_Flags, ByVal lpMultiByteStr As LongPtr, ByVal cchMultiByte As Long, ByVal lpWideCharStr As LongPtr, ByVal cchWideChar As Long) As Long","returnType":"Long"}, + {"name":"PathMatchSpecW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function PathMatchSpecW Lib \"shlwapi\" (ByVal pszFile As LongPtr, ByVal pszSpec As LongPtr) As BOOL","returnType":"BOOL"}, {"name":"PathRelativePathToW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function PathRelativePathToW Lib \"shlwapi\" (ByVal pszPath As LongPtr, ByVal pszFrom As LongPtr, ByVal dwAttrFrom As FILE_ATTRIBUTES, ByVal pszTo As LongPtr, ByVal dwAttrTo As FILE_ATTRIBUTES) As BOOL","returnType":"BOOL"}, {"name":"ReadFile","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function ReadFile Lib \"kernel32\" (ByVal hFile As LongPtr, lpBuffer As Any, ByVal nNumberOfBytesToRead As Long, lpNumberOfBytesRead As Long, lpOverlapped As OVERLAPPED) As BOOL","returnType":"BOOL"}, {"name":"RemoveDirectoryW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function RemoveDirectoryW Lib \"kernel32\" (ByVal lpPathName As LongPtr) As BOOL","returnType":"BOOL"}, + {"name":"RtlGetNtVersionNumbers","kind":"sub","vis":"public","declare":true,"signature":"Public Declare PtrSafe Sub RtlGetNtVersionNumbers Lib \"ntdll\" (Major As Long, Minor As Long, Build As Long)","attrs":[{"name":"UseGetLastError","args":"False"}]}, {"name":"SHGetFileInfoW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function SHGetFileInfoW Lib \"shell32\" (ByVal pszPath As Any, ByVal dwFileAttributes As Long, psfi As SHFILEINFOW, ByVal cbFileInfo As Long, ByVal uFlags As SHGFI_flags) As LongPtr","returnType":"LongPtr"}, + {"name":"SHGetFolderPathW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function SHGetFolderPathW Lib \"shell32\" (ByVal hwnd As LongPtr, ByVal csidl As CSIDLs, ByVal hToken As LongPtr, ByVal dwFlags As SHGFP_TYPE, ByVal pszPath As LongPtr) As Long","returnType":"Long"}, {"name":"SHGetKnownFolderPath","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function SHGetKnownFolderPath Lib \"shell32\" (rfid As UUID, ByVal dwFlags As KNOWN_FOLDER_FLAG, ByVal hToken As LongPtr, ppszPath As LongPtr) As Long","returnType":"Long"}, {"name":"SetCurrentDirectoryW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function SetCurrentDirectoryW Lib \"kernel32\" (ByVal lpPathName As LongPtr) As BOOL","returnType":"BOOL"}, {"name":"SetFileAttributesW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function SetFileAttributesW Lib \"kernel32\" (ByVal lpFileName As LongPtr, ByVal dwFileAttributes As FILE_ATTRIBUTES) As BOOL","returnType":"BOOL"}, {"name":"SetLastError","kind":"sub","vis":"public","declare":true,"signature":"Public Declare PtrSafe Sub SetLastError Lib \"kernel32\" (ByVal dwErrCode As Long)"}, + {"name":"SetThreadLocale","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function SetThreadLocale Lib \"kernel32\" (ByVal Locale As DefaultLocales) As BOOL","returnType":"BOOL"}, {"name":"SetVolumeLabelW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function SetVolumeLabelW Lib \"kernel32\" (ByVal lpRootPathName As LongPtr, ByVal lpVolumeName As LongPtr) As Long","returnType":"Long"}, {"name":"SysReAllocStringW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function SysReAllocStringW Lib \"oleaut32\" Alias \"SysReAllocString\" (ByVal pBSTR As LongPtr, Optional ByVal pszStrPtr As LongPtr) As Long","returnType":"Long"}, {"name":"VerQueryValueW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function VerQueryValueW Lib \"version.dll\" (ByRef pBlock As Any, ByVal lpSubBlock As LongPtr, ByRef lplpBuffer As LongPtr, ByRef puLen As Long) As BOOL","returnType":"BOOL"}, @@ -820,6 +855,30 @@ {"name":"CTRUE"} ] }, + { + "name": "COMPARE_FLAGS", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum COMPARE_FLAGS", + "values": [ + {"name":"SORT_STRINGSORT","value":"&H1000&"}, + {"name":"SORT_DIGITSASNUMBERS","value":"&H8"}, + {"name":"LINGUISTIC_IGNORECASE","value":"&H10&"}, + {"name":"LINGUISTIC_IGNOREDIACRITIC","value":"&H20&"}, + {"name":"NORM_IGNORECASE","value":"&H1&"}, + {"name":"NORM_IGNORENONSPACE","value":"&H2&"}, + {"name":"NORM_IGNORESYMBOLS","value":"&H4&"}, + {"name":"NORM_IGNOREKANATYPE","value":"&H10000"}, + {"name":"NORM_IGNOREKANATYPE_ALT","value":"&H40"}, + {"name":"NORM_IGNOREWIDTH","value":"&H20000"}, + {"name":"NORM_IGNOREWIDTH_ALT","value":"&H08"}, + {"name":"NORM_IGNOREKASHIDA","value":"&H00040000"}, + {"name":"NORM_LINGUISTIC_CASING","value":"&H8000000"}, + {"name":"NORM_STOP_ON_NULL","value":"&H10000000"} + ] + }, { "name": "CPINFO", "kind": "type", @@ -835,6 +894,78 @@ ], "members": [] }, + { + "name": "CSIDLs", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum CSIDLs", + "values": [ + {"name":"CSIDL_DESKTOP","value":"&H0000"}, + {"name":"CSIDL_INTERNET","value":"&H0001"}, + {"name":"CSIDL_PROGRAMS","value":"&H0002"}, + {"name":"CSIDL_CONTROLS","value":"&H0003"}, + {"name":"CSIDL_PRINTERS","value":"&H0004"}, + {"name":"CSIDL_PERSONAL","value":"&H0005"}, + {"name":"CSIDL_FAVORITES","value":"&H0006"}, + {"name":"CSIDL_STARTUP","value":"&H0007"}, + {"name":"CSIDL_RECENT","value":"&H0008"}, + {"name":"CSIDL_SENDTO","value":"&H0009"}, + {"name":"CSIDL_BITBUCKET","value":"&H000a"}, + {"name":"CSIDL_STARTMENU","value":"&H000b"}, + {"name":"CSIDL_MYDOCUMENTS","value":"&H000c"}, + {"name":"CSIDL_MYMUSIC","value":"&H000d"}, + {"name":"CSIDL_MYVIDEO","value":"&H000e"}, + {"name":"CSIDL_DESKTOPDIRECTORY","value":"&H0010"}, + {"name":"CSIDL_DRIVES","value":"&H0011"}, + {"name":"CSIDL_NETWORK","value":"&H0012"}, + {"name":"CSIDL_NETHOOD","value":"&H0013"}, + {"name":"CSIDL_FONTS","value":"&H0014"}, + {"name":"CSIDL_TEMPLATES","value":"&H0015"}, + {"name":"CSIDL_COMMON_STARTMENU","value":"&H0016"}, + {"name":"CSIDL_COMMON_PROGRAMS","value":"&H0017"}, + {"name":"CSIDL_COMMON_STARTUP","value":"&H0018"}, + {"name":"CSIDL_COMMON_DESKTOPDIRECTORY","value":"&H0019"}, + {"name":"CSIDL_APPDATA","value":"&H001a"}, + {"name":"CSIDL_PRINTHOOD","value":"&H001b"}, + {"name":"CSIDL_LOCAL_APPDATA","value":"&H001c"}, + {"name":"CSIDL_ALTSTARTUP","value":"&H001d"}, + {"name":"CSIDL_COMMON_ALTSTARTUP","value":"&H001e"}, + {"name":"CSIDL_COMMON_FAVORITES","value":"&H001f"}, + {"name":"CSIDL_INTERNET_CACHE","value":"&H0020"}, + {"name":"CSIDL_COOKIES","value":"&H0021"}, + {"name":"CSIDL_HISTORY","value":"&H0022"}, + {"name":"CSIDL_COMMON_APPDATA","value":"&H0023"}, + {"name":"CSIDL_WINDOWS","value":"&H0024"}, + {"name":"CSIDL_SYSTEM","value":"&H0025"}, + {"name":"CSIDL_PROGRAM_FILES","value":"&H0026"}, + {"name":"CSIDL_MYPICTURES","value":"&H0027"}, + {"name":"CSIDL_PROFILE","value":"&H0028"}, + {"name":"CSIDL_SYSTEMX86","value":"&H0029"}, + {"name":"CSIDL_PROGRAM_FILESX86","value":"&H002a"}, + {"name":"CSIDL_PROGRAM_FILES_COMMON","value":"&H002b"}, + {"name":"CSIDL_PROGRAM_FILES_COMMONX86","value":"&H002c"}, + {"name":"CSIDL_COMMON_TEMPLATES","value":"&H002d"}, + {"name":"CSIDL_COMMON_DOCUMENTS","value":"&H002e"}, + {"name":"CSIDL_COMMON_ADMINTOOLS","value":"&H002f"}, + {"name":"CSIDL_ADMINTOOLS","value":"&H0030"}, + {"name":"CSIDL_CONNECTIONS","value":"&H0031"}, + {"name":"CSIDL_COMMON_MUSIC","value":"&H0035"}, + {"name":"CSIDL_COMMON_PICTURES","value":"&H0036"}, + {"name":"CSIDL_COMMON_VIDEO","value":"&H0037"}, + {"name":"CSIDL_RESOURCES","value":"&H0038"}, + {"name":"CSIDL_RESOURCES_LOCALIZED","value":"&H0039"}, + {"name":"CSIDL_COMMON_OEM_LINKS","value":"&H003a"}, + {"name":"CSIDL_CDBURN_AREA","value":"&H003b"}, + {"name":"CSIDL_COMPUTERSNEARME","value":"&H003d"}, + {"name":"CSIDL_FLAG_PER_USER_INIT","value":"&H0800"}, + {"name":"CSIDL_FLAG_NO_ALIAS","value":"&H1000"}, + {"name":"CSIDL_FLAG_CREATE","value":"&H8000&"}, + {"name":"CSIDL_FLAG_DONT_VERIFY","value":"&H4000"}, + {"name":"CSIDL_FLAG_MASK","value":"&HFF00&"} + ] + }, { "name": "CopyFileFlags", "kind": "enum", @@ -876,6 +1007,22 @@ {"name":"DROPEFFECT_SCROLL","value":"&H80000000"} ] }, + { + "name": "DefaultLocales", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum DefaultLocales", + "values": [ + {"name":"LOCALE_CUSTOM_DEFAULT","value":"&H0c00"}, + {"name":"LOCALE_CUSTOM_UI_DEFAULT","value":"&H1400"}, + {"name":"LOCALE_CUSTOM_UNSPECIFIED","value":"&H1000"}, + {"name":"LOCALE_INVARIANT","value":"&H007f"}, + {"name":"LOCALE_USER_DEFAULT","value":"&H0400"}, + {"name":"LOCALE_SYSTEM_DEFAULT","value":"&H0800"} + ] + }, { "name": "FILETIME", "kind": "type", @@ -924,6 +1071,45 @@ {"name":"FILE_ATTRIBUTE_STRICTLY_SEQUENTIAL","value":"&H20000000"} ] }, + { + "name": "FINDEX_EXTRA_FLAGS", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum FINDEX_EXTRA_FLAGS", + "values": [ + {"name":"FIND_FIRST_EX_CASE_SENSITIVE","value":"1"}, + {"name":"FIND_FIRST_EX_LARGE_FETCH","value":"2"}, + {"name":"FIND_FIRST_EX_ON_DISK_ENTRIES_ONLY","value":"4"} + ] + }, + { + "name": "FINDEX_INFO_LEVELS", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum FINDEX_INFO_LEVELS", + "values": [ + {"name":"FindExInfoStandard"}, + {"name":"FindExInfoBasic"}, + {"name":"FindExInfoMaxInfoLevel"} + ] + }, + { + "name": "FINDEX_SEARCH_OPS", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum FINDEX_SEARCH_OPS", + "values": [ + {"name":"FindExSearchNameMatch"}, + {"name":"FindExSearchLimitToDirectories"}, + {"name":"FindExSearchLimitToDevices"} + ] + }, { "name": "FileShareMode", "kind": "enum", @@ -975,6 +1161,31 @@ {"name":"KF_FLAG_ALIAS_ONLY","value":"&H80000000"} ] }, + { + "name": "LCMAP_FLAGS", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum LCMAP_FLAGS", + "values": [ + {"name":"LCMAP_LOWERCASE","value":"&H00000100"}, + {"name":"LCMAP_UPPERCASE","value":"&H00000200"}, + {"name":"LCMAP_TITLECASE","value":"&H00000300"}, + {"name":"LCMAP_SORTKEY","value":"&H00000400"}, + {"name":"LCMAP_BYTEREV","value":"&H00000800"}, + {"name":"LCMAP_HIRAGANA","value":"&H00100000"}, + {"name":"LCMAP_KATAKANA","value":"&H00200000"}, + {"name":"LCMAP_HALFWIDTH","value":"&H00400000"}, + {"name":"LCMAP_FULLWIDTH","value":"&H00800000"}, + {"name":"LCMAP_LINGUISTIC_CASING","value":"&H01000000"}, + {"name":"LCMAP_SIMPLIFIED_CHINESE","value":"&H02000000"}, + {"name":"LCMAP_TRADITIONAL_CHINESE","value":"&H04000000"}, + {"name":"LCMAP_SORTHANDLE","value":"&H20000000"}, + {"name":"LCMAP_HASH","value":"&H00040000"}, + {"name":"LCMAP_IGNOREDBCS","value":"&H80000000"} + ] + }, { "name": "MBWC_Flags", "kind": "enum", @@ -1001,6 +1212,40 @@ {"name":"MOVEFILE_COPY_ALLOWED","value":"&H00000002"} ] }, + { + "name": "NLSVERSIONINFO", + "kind": "type", + "in": "WinAPI", + "vis": "public", + "public": false, + "file": "WinAPI.twin", + "signature": "Public Type NLSVERSIONINFO", + "description": "For Windows 8 and newer. Windows 7 and below: Use NLSVERSIONINFO_WIN7", + "fields": [ + {"name":"dwNLSVersionInfoSize","vis":"public","signature":"dwNLSVersionInfoSize As Long"}, + {"name":"dwNLSVersion","vis":"public","signature":"dwNLSVersion As Long"}, + {"name":"dwDefinedVersion","vis":"public","signature":"dwDefinedVersion As Long"}, + {"name":"dwEffectiveId","vis":"public","signature":"dwEffectiveId As Long"}, + {"name":"guidCustomVersion","vis":"public","signature":"guidCustomVersion As UUID"} + ], + "members": [] + }, + { + "name": "NLSVERSIONINFO_WIN7", + "kind": "type", + "in": "WinAPI", + "vis": "public", + "public": false, + "file": "WinAPI.twin", + "signature": "Public Type NLSVERSIONINFO_WIN7", + "description": "For Windows 7 and below only. For Windows 8 and newer, use NLSVERSIONINFO or NLSVERSIONINFOEX", + "fields": [ + {"name":"dwNLSVersionInfoSize","vis":"public","signature":"dwNLSVersionInfoSize As Long"}, + {"name":"dwNLSVersion","vis":"public","signature":"dwNLSVersion As Long"}, + {"name":"dwDefinedVersion","vis":"public","signature":"dwDefinedVersion As Long"} + ], + "members": [] + }, { "name": "OVERLAPPED", "kind": "type", @@ -1211,6 +1456,18 @@ {"name":"SHGFI_ATTR_SPECIFIED","value":"&H20000"} ] }, + { + "name": "SHGFP_TYPE", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum SHGFP_TYPE", + "values": [ + {"name":"SHGFP_TYPE_CURRENT","value":"0"}, + {"name":"SHGFP_TYPE_DEFAULT","value":"1"} + ] + }, { "name": "STD_HANDLES", "kind": "enum", diff --git a/test/example-projects/contributed-tbFileSysTools/Settings b/test/example-projects/contributed-tbFileSysTools/Settings index be608964..67f6e45e 100644 --- a/test/example-projects/contributed-tbFileSysTools/Settings +++ b/test/example-projects/contributed-tbFileSysTools/Settings @@ -57,8 +57,8 @@ "symbolId": "tbFileSysTools", "versionBuild": "0", "versionMajor": "1", - "versionMinor": "1", - "versionRevision": "1" + "versionMinor": "8", + "versionRevision": "0" } ], "project.settingsVersion": 1, From 199c2cad4a29564fbded1b823bc721bef0b4fa07 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 13 Oct 2026 18:56:33 +0200 Subject: [PATCH 3/9] xCalendar: moved to 1.0.17.0; the hang on an impossible date, the repaint loop and the unscaled lists are fixed; four new minor defects; probe scenarios 12w and 17; still Experimental --- WIP.md | 2 +- docs/Reference/Contributed/xCalendar/index.md | 79 ++++--- docs/Reference/Experimental.md | 2 +- .../snapshots/contributed/xCalendar/api.json | 13 +- .../contributed-xCalendar/Settings | 2 +- test/package-probes/xCalendar/README.md | 42 +++- test/package-probes/xCalendar/src/Probe.twin | 211 ++++++++++++++++-- 7 files changed, 274 insertions(+), 77 deletions(-) diff --git a/WIP.md b/WIP.md index 3e26be81..bcaf9969 100644 --- a/WIP.md +++ b/WIP.md @@ -34,7 +34,7 @@ sibling. History goes nowhere: git keeps it. Reference documentation is **complete** for all thirteen packages: VBA, VBRUN, VB, WebView2, Assert, CustomControls, CEF, WinEventLogLib, WinNamedPipesLib, WinServicesLib, tbIDE, WinNativeCommonCtls and AppGlobalClassObject. It is adapted from primary sources: Microsoft VBA-Docs (CC-BY-4.0) for the runtime library, `.twin` source for the twinBASIC-specific packages. The CEF and WebView2 packages also carry a tutorial set. -Twenty-six **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerLib, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsJson, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. The two console packages carry a NOTE (owner, 2026-10-12) that the samples harness cannot yet capture a console, so their console claims follow the source. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. VBComDlg's `exclude_from_docs` names `CommonDialogBase`, a module whose public procedures only its class calls. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modImpExp` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Xml, tbRegExp, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib, BigNumberLib and WinReg do not compile on BETA 1005 (each documented from its source alone, its samples compile-only; WinReg's methods were also run in a copy of its source with its four failing calls passed `ByVal`, and ReadString, GetRegistryValueType and the first ConvertValueToString give wrong results there), xCalendar has defects that can hang a program, TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_ConsoleComponents cannot take a cConsole of the published TBMANLIB_ConsoleCore (it embeds a private copy of it), TBMANLIB_ConsoleCore reads keys wrongly, TBMANLIB_Diagnostic cannot create its Dictionary or make a text report and misplaces List items, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text (and its XPath engine reads the step `..` and `and` with `or` wrongly), and tbRegExp, with **Global**, reports a match of no characters twice when the search skipped characters to reach it (a defect of the vba-regex engine it is made from, confirmed by a second agent). TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph, and the five types of its XPath engine (`XPathTokenType`, `XPathAxisType`, `XPathOperator`, `XPathStep`, `XPathPredicate`), which no public member takes or returns and which the mXPath page mentions in a paragraph; tsPwGen's names its five test modules and its six internal modules, and tsJson's its test and internal modules likewise, which their landing pages describe in a paragraph each. See *Where things live* for how a package moves between the two lists. +Twenty-six **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerLib, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsJson, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. The two console packages carry a NOTE (owner, 2026-10-12) that the samples harness cannot yet capture a console, so their console claims follow the source. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. VBComDlg's `exclude_from_docs` names `CommonDialogBase`, a module whose public procedures only its class calls. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modImpExp` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Xml, tbRegExp, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib, BigNumberLib and WinReg do not compile on BETA 1005 (each documented from its source alone, its samples compile-only; WinReg's methods were also run in a copy of its source with its four failing calls passed `ByVal`, and ReadString, GetRegistryValueType and the first ConvertValueToString give wrong results there), xCalendar has a grid that keeps the old month's dates when Value is set to another month, and text colour properties that have no effect, TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_ConsoleComponents cannot take a cConsole of the published TBMANLIB_ConsoleCore (it embeds a private copy of it), TBMANLIB_ConsoleCore reads keys wrongly, TBMANLIB_Diagnostic cannot create its Dictionary or make a text report and misplaces List items, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text (and its XPath engine reads the step `..` and `and` with `or` wrongly), and tbRegExp, with **Global**, reports a match of no characters twice when the search skipped characters to reach it (a defect of the vba-regex engine it is made from, confirmed by a second agent). TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph, and the five types of its XPath engine (`XPathTokenType`, `XPathAxisType`, `XPathOperator`, `XPathStep`, `XPathPredicate`), which no public member takes or returns and which the mXPath page mentions in a paragraph; tsPwGen's names its five test modules and its six internal modules, and tsJson's its test and internal modules likewise, which their landing pages describe in a paragraph each. See *Where things live* for how a package moves between the two lists. The rest of this file is the maintenance guide for updating existing pages or adding new ones: package surface notes, page conventions, and the integrity check. diff --git a/docs/Reference/Contributed/xCalendar/index.md b/docs/Reference/Contributed/xCalendar/index.md index 20eaf662..879855dc 100644 --- a/docs/Reference/Contributed/xCalendar/index.md +++ b/docs/Reference/Contributed/xCalendar/index.md @@ -2,20 +2,20 @@ title: xCalendar Package parent: Experimental Packages has_toc: false -indexed_from: 1.0.7.0 +indexed_from: 1.0.17.0 permalink: /tB/Packages/Contributed/xCalendar/ --- # xCalendar Package {: .no_toc } -**Author:** sokinkeso. **Version:** 1.0.7.0 +**Author:** sokinkeso. **Version:** 1.0.17.0 The **xCalendar** contributed package provides a calendar control: a grid of the days of one month, in which the user selects a date by clicking a day, by pressing a key, or by choosing a month or a year from a list. It is meant as a replacement for the Calendar control of `MSCAL.OCX`. -The package has one public class, **xCalendar**, a user control that draws itself with GDI calls. The class has 27 properties, 10 methods and 12 events, and four enumerations belong to it. This section documents version 1.0.7.0, published under the MIT licence. The package describes itself as "Calendar MSCAL replacement (Experimental, 32-bit and 64-bit)", and its 32-bit and 64-bit builds behave the same. +The package has one public class, **xCalendar**, a user control that draws itself with GDI calls. The class has 27 properties, 10 methods and 12 events, and four enumerations belong to it. This section documents version 1.0.17.0, published under the MIT licence. The package describes itself as "Calendar MSCAL replacement (Experimental, 32-bit and 64-bit)", and its 32-bit and 64-bit builds behave the same. -The package has defects: choosing a month from the month list can hang the program, and the colour properties of the text have no effect. [Known problems in version 1.0.7.0](#known-problems-in-version-1070) lists them. +The package has defects: setting **Value** to a date in another month leaves the grid showing the old month, and the colour properties of the text have no effect. [Known problems in version 1.0.17.0](#known-problems-in-version-10170) lists them. ```tb check_build Private Sub Form_Load() @@ -78,16 +78,14 @@ The start values of the properties are set when the control is placed on a form. The control is drawn in up to three areas, one above the other: the title area, the row of day names and the grid of days. It draws into a hidden picture box and copies the result to its own surface. The control can have any size: the seven columns share the width equally, and the six rows share the height that the other areas leave. -The sizes inside the control, such as the margins and the two lists, are in device pixels and are not scaled with the DPI of the display. At 144 DPI a control that is 256 pixels wide at design time is 384 pixels wide, and its lists and margins keep their pixel sizes. +The two lists are scaled with the DPI of the display, by the [**DpiScale**](../../VB/UserControl#dpiscale) of the control: the sizes given for them below are those at 96 DPI. The other sizes inside the control, the margins, the frame and the lines, are in device pixels and are not scaled. At 144 DPI a control that is 256 pixels wide at design time is 384 pixels wide, and its margins keep their pixel sizes. ### Title area -The title area is present when **ShowTitle** or **ShowDateSelectors** is **True**, and has no height when both are **False**. Its height is the height of the title text in **TitleFont**, plus **TitleMargin** above and below. Its background is **BackColor**. It holds two things. +The title area is present when **ShowTitle** or **ShowDateSelectors** is **True**, and has no height when both are **False**. Its height is the height of one line of text in **TitleFont**, plus **TitleMargin** above and below. The control works the height out when it is created and when its size changes, and again when **TitleFont** is assigned with **Set** or **TitleMargin** or **ShowDateSelectors** is set. Its background is **BackColor**. It holds two things. -- **The title** is the name of the shown month and the year, such as `October 2026`, in **TitleFont**, centred in the space to the left of the date selectors. **MonthLength** decides whether the month is written in full or as its first three letters. It is drawn only when **ShowTitle** is **True**. The month name comes from the [**MonthName**](../../../Modules/Strings/MonthName) function, so it is in the language of the system. -- **The date selectors** are two drop-down lists at the right edge, drawn only when **ShowDateSelectors** is **True**. The month list is 80 pixels wide and holds the names of the twelve months. The year list is 50 pixels wide and holds the years 1900 to 2100. Both are 21 pixels high and centred in the height of the title area. The year list ends 5 pixels from the right edge of the control, and the month list ends 5 pixels before the year list. Choosing an entry changes the shown month or year, keeps the selected day of the month, and raises **NewMonth** or **NewYear**. - -The title is centred in the width of the control less 140 pixels, the space that the two lists take, whether or not the lists are shown. At 144 DPI the lists are too small for their text: the year list shows `20` for 2026 and the month list `Octobe` for October. [Known problems](#known-problems-in-version-1070) describes both. +- **The title** is the name of the shown month and the year, such as `October 2026`, in **TitleFont**, centred in the space to the left of the month list, or in the whole width of the control when the lists are not shown. A title wider than that space is cut off at both ends: see [Known problems](#known-problems-in-version-10170). **MonthLength** decides whether the month is written in full or as its first three letters. It is drawn only when **ShowTitle** is **True**. The month name comes from the [**MonthName**](../../../Modules/Strings/MonthName) function, so it is in the language of the system. +- **The date selectors** are two drop-down lists at the right edge, drawn only when **ShowDateSelectors** is **True**. At 96 DPI the month list is 80 pixels wide and holds the names of the twelve months, and the year list is 50 pixels wide and holds the years 1900 to 2100. Both are 21 pixels high, placed so that a list of that height is centred in the height of the title area. The year list ends 5 pixels from the right edge of the control, and the month list ends 5 pixels before the year list. At 144 DPI each of these sizes is 1.5 times as large: the lists are 120 and 75 pixels wide, and the year list ends 8 pixels from the right edge. The height of a drop-down list is fixed by its font, so at 144 DPI each list is 28 pixels high, not the 31 that the control asks for, and sits 2 pixels above the centre of the title area. When the control is resized the lists keep their distance from the right edge, and the title is centred again in the space to their left. Choosing an entry changes the shown month or year, keeps the selected day of the month, and raises **NewMonth** or **NewYear**. ### Row of day names @@ -97,7 +95,7 @@ The row is present when **ShowDays** is **True**. It has seven cells of the same The grid has six rows of seven cells, 42 days in all, whatever the month. The first cell holds the latest date, on or before the first day of the shown month, that falls on the weekday **FirstDay** names. A month therefore starts in the first row, and the cells before its first day hold the last days of the month before. Each cell shows the day of the month at its top left corner, in **GridFont**. The cell of the selected date, **Value**, is filled with a fixed warm grey (`&H82868E`) and has a black line along its left and top edges. -All the text of the control is drawn in the same near-black, whatever the colour properties say: the days of the month before and the month after are not grey, and the title is not in **TitleFontColor**. See [Known problems](#known-problems-in-version-1070). +All the text of the control is drawn in the same near-black, whatever the colour properties say: the days of the month before and the month after are not grey, and the title is not in **TitleFontColor**. See [Known problems](#known-problems-in-version-10170). **ShowVerticalGrid** and **ShowHorizontalGrid** switch the lines between the cells on and off, and **GridCellEffect** and **GridLinesColor** decide how the lines look. A black frame of one pixel always surrounds the row of day names and the grid, whichever lines are on. @@ -105,7 +103,7 @@ A cell is selected only when its date is equal to **Value**. A **Value** with a ### Disabled state -When **Enabled** is **False**, the two lists are disabled and drawn in grey, and the row of day names has the background `&HEEEEEE` in place of **DayBackColor**. With **Flat** lines, the grid lines are `&H5B5B5B` in place of **GridLinesColor**. **BackColor** is the same as for an enabled control, and the text keeps its colour. +When **Enabled** is set to **False** by the program, the two lists are disabled and drawn in grey, and the row of day names has the background `&HEEEEEE` in place of **DayBackColor**. A control that loads from a form with **Enabled** already **False** draws its lists as an enabled control does: see [Known problems](#known-problems-in-version-10170). With **Flat** lines, the grid lines are `&H5B5B5B` in place of **GridLinesColor**. **BackColor** is the same as for an enabled control, and the text keeps its colour. A disabled control does not receive the mouse or the keyboard from the user, and the focus cannot be given to it. Code can still change its date, for example with **NextDay** or **Value**. @@ -115,11 +113,11 @@ A disabled control does not receive the mouse or the keyboard from the user, and Pressing the left mouse button on a cell of the grid selects the date of that cell. The control raises **BeforeUpdate**, which can cancel the selection, then moves the selection and redraws, and raises **AfterUpdate**. A day of the month before or the month after is selected in the same way, and the control then shows its month; no **NewMonth** or **NewYear** event is raised for that change of month. A click with the right button, and a click on the title area or the row of day names, select nothing: they raise **MouseUp** and **Click** only. -A press near the right edge or the bottom edge of the grid can select a date that the grid does not show: see [Known problems](#known-problems-in-version-1070). +A press near the right edge or the bottom edge of the grid can select a date that the grid does not show: see [Known problems](#known-problems-in-version-10170). ### With the keyboard -The control handles these keys while the keyboard focus is in the control. A click on the control gives it the focus, and Tab does not: see [Known problems](#known-problems-in-version-1070). +The control handles these keys while the keyboard focus is in the control. A click on the control gives it the focus, and Tab does not: see [Known problems](#known-problems-in-version-10170). | Key | Effect | Method it calls | |:----|:-------|:----------------| @@ -155,13 +153,11 @@ Setting **Value**, **Day**, **Month** or **Year**, and calling the methods that **MouseDown** is declared but never raised. -## Known problems in version 1.0.7.0 +## Known problems in version 1.0.17.0 > [!WARNING] -> Version 1.0.7.0 of the package stops the program, or gives a wrong result without an error, in these cases. +> Version 1.0.17.0 of the package gives a wrong result without an error in these cases. > -> - **A month or a year chosen from a list can hang the program.** Choosing a month in which the selected day does not exist, such as February when the selected day is the 31st, or the year 2025 when the selected date is February 29, 2024, makes the program run at full processor load and never return. The same dates set from code raise run-time error 13 instead. -> - **The control asks to be painted again after every paint while the date selectors are shown**, a few hundred times a second, so a program that calls **DoEvents** does not come back from it, and the processor is busy. With **ShowDateSelectors** **False** it stops. > - **The colour properties of the text have no effect.** **DayFontColor**, **GridFontColor** and **TitleFontColor** are stored and returned, but the day names, the day numbers and the title are always drawn in the same near-black (`&H4`), and so are the days of the month before and the month after, which the control means to draw in grey. The disabled state has no effect on the text colour either. > - **Setting Value to a date in another month, or setting FirstDay, does not place the dates of the grid again.** After **Value** is set, the title and the two lists show the new month, but the grid still shows the dates of the month before, with no cell selected. After **FirstDay** is set, the day names move to the new order and the dates do not, so the names and the dates disagree. **Refresh** does not correct either. The dates are placed again when the selection is next moved, by a click on a cell, by **Today** or by one of the **Next** and **Previous** methods. > - **A press near the right edge or the bottom edge can select a date that the grid does not show.** In a control 255 pixels wide, a cell is 36.43 pixels, which the control takes as 36, and a press at 252 to 254 pixels from the left selects the date of a column after the last, the first date of the next row. In a control 292 pixels high, a press at 290 or 291 pixels from the top selects a date a row below the grid. @@ -170,14 +166,17 @@ Setting **Value**, **Day**, **Month** or **Year**, and calling the methods that > - **A year outside 1900 to 2100 is not shown in the year list.** Setting **Year** or **Value** to 1899 or earlier leaves the year list blank, and to 2101 or later leaves it as it was. No error is raised, and **Value** and the title show the year. > [!NOTE] -> Version 1.0.7.0 of the package has these other problems. Each raises an error, or concerns how the control looks or is reached. +> Version 1.0.17.0 of the package has these other problems. Each raises an error, or concerns how the control looks or is reached. > +> - **A month or a year chosen from a list can open the run-time error box.** Choosing a month in which the selected day does not exist, such as February when the selected day is the 31st, or the year 2025 when the selected date is February 29, 2024, raises run-time error 13, *Type mismatch*, in the control's own code, where the program cannot handle it: the program shows its error box, and goes on when the box is closed. The list then shows the new month or year, and **Month** or **Year** returns it, but **Value** and **Day** keep the old date, the grid is not drawn again, and **NewMonth** or **NewYear** is not raised. The same dates set from code raise run-time error 13 to the caller. > - **Setting ValueIsNull raises run-time error 438**, *Object doesn't support this property or method*, to **True** or to **False**. The value is stored all the same, and nothing else in the control uses it. > - **A day, month or year that makes an impossible date raises run-time error 13**, *Type mismatch*: **Day** 0 or 32, or 31 in a month of 30 days, **Month** 13 or 0, **Month** 2 when **Day** is 31, **Year** 2025 when the selected date is February 29, 2024. The property then returns the value assigned and **Value** keeps its old date, until the next valid assignment. After a **Month** of 13, **Refresh** raises an error (-2147352567, *Exception occurred.*) until **Month** is set to a valid value. > - **Tab never moves the keyboard focus to the control**, so a user who does not click on it cannot reach the arrow keys. A click on the control, and a choice in either list, give it the focus. > - **MouseDown is never raised.** -> - **The title is centred in the width of the control less 140 pixels**, also when the date selectors are not shown, so it stands left of the centre. -> - **The two lists are 80 and 50 pixels wide at any DPI.** At 144 DPI the month list shows `Octobe` for October, and the year list `20` for 2026. +> - **A title wider than the space to the left of the month list is cut off at both ends.** At 144 DPI, with the control at its default size (384 pixels wide) and the default **TitleFont**, that space is 174 pixels wide, and `October 2026` does not fit in it. A wider control, a smaller **TitleFont**, a short month name (**MonthLength** **ShortLength**: `Oct 2026` fits) or **ShowDateSelectors** **False** gives the title room. +> - **A control that loads from a form with Enabled False shows its two lists as enabled.** Setting **Enabled** to **False** in code draws them in grey, but a control saved with **Enabled** **False** draws them as for an enabled control. The row of day names is drawn as disabled in both cases. +> - **Setting ShowTitle to True does not bring the title back when both ShowTitle and ShowDateSelectors were False.** The title area has no height then, and keeps none: the title is not drawn, and the row of day names stays at the top. Setting **ShowDateSelectors** to **True**, setting **TitleMargin**, or changing the size of the control gives the title area its height again. +> - **The height of the title area does not follow a change made to the title font object.** After `xCalendar1.TitleFont.Size = 24`, the title is drawn at the new size, but the title area keeps its old height and the lists keep their places. Assigning the object to **TitleFont** with **Set** again, or setting **TitleMargin**, fits the area to the font. The row of day names and the day numbers follow a change to **DayFont** or **GridFont** at the next paint. ## Properties @@ -193,7 +192,7 @@ The background colour of the title area and of the cells of the grid, apart from The day of the month of the selected date, from 1 to 31. **Integer**. Default: the day of the month on which the control was placed on the form. -Setting **Day** selects that day of the shown month and year, and redraws the control. A day that the shown month does not have, such as 31 in June, or a value below 1 or above 31, raises run-time error 13, *Type mismatch*. **Day** then returns the value assigned, and **Value** is unchanged. See [Known problems](#known-problems-in-version-1070). +Setting **Day** selects that day of the shown month and year, and redraws the control. A day that the shown month does not have, such as 31 in June, or a value below 1 or above 31, raises run-time error 13, *Type mismatch*. **Day** then returns the value assigned, and **Value** is unchanged. See [Known problems](#known-problems-in-version-10170). ### DayBackColor {: .no_toc } @@ -210,7 +209,7 @@ The control keeps the object that is assigned, not a copy of it. Assigning a fon ### DayFontColor {: .no_toc } -The colour of the day names. **OLE_COLOR**. Default: black. The value is stored, and it has no effect on the colour of the text: see [Known problems](#known-problems-in-version-1070). +The colour of the day names. **OLE_COLOR**. Default: black. The value is stored, and it has no effect on the colour of the text: see [Known problems](#known-problems-in-version-10170). ### DayLength {: .no_toc } @@ -234,7 +233,7 @@ Whether the control responds to the user. **Boolean**. Default: **True**. A disa The weekday of the first column of the grid. A [**WeekDaysEnum**](#weekdaysenum) member. Default: **Monday**. -Changing **FirstDay** at run time moves the day names at once, but the dates of the grid are placed again only when the selection next moves, so the names and the dates disagree until then. **Refresh** does not correct it. See [Known problems](#known-problems-in-version-1070). A value outside 0 to 7 raises run-time error 5, *Invalid procedure call or argument*, and **FirstDay** returns the value assigned. +Changing **FirstDay** at run time moves the day names at once, but the dates of the grid are placed again only when the selection next moves, so the names and the dates disagree until then. **Refresh** does not correct it. See [Known problems](#known-problems-in-version-10170). A value outside 0 to 7 raises run-time error 5, *Invalid procedure call or argument*, and **FirstDay** returns the value assigned. ### GridCellEffect {: .no_toc } @@ -251,19 +250,19 @@ The font of the day numbers in the grid. A **Font** object. Default: Arial, 8 po ### GridFontColor {: .no_toc } -The colour of the day numbers. **OLE_COLOR**. Default: dark blue (`&HA00000`, blue 160). The value is stored, and it has no effect on the colour of the text: see [Known problems](#known-problems-in-version-1070). +The colour of the day numbers. **OLE_COLOR**. Default: dark blue (`&HA00000`, blue 160). The value is stored, and it has no effect on the colour of the text: see [Known problems](#known-problems-in-version-10170). ### GridLinesColor {: .no_toc } -The colour of the lines of the grid when **GridCellEffect** is **Flat**. **OLE_COLOR**. Default: `&H80000010`, the system colour [**vbButtonShadow**](../../VBRUN/Constants/SystemColorConstants). The default is drawn as near-black, and not as the system colour: see [Known problems](#known-problems-in-version-1070). +The colour of the lines of the grid when **GridCellEffect** is **Flat**. **OLE_COLOR**. Default: `&H80000010`, the system colour [**vbButtonShadow**](../../VBRUN/Constants/SystemColorConstants). The default is drawn as near-black, and not as the system colour: see [Known problems](#known-problems-in-version-10170). ### Month {: .no_toc } The month of the selected date, from 1 to 12. **Integer**. Default: the month in which the control was placed on the form. -Setting **Month** selects the same day of that month in the shown year, shows the month, and updates the month list. A month number outside 1 to 12, or a month that lacks the selected day (**Day** is 31 and the month has 30 days), raises run-time error 13, *Type mismatch*. **Month** then returns the value assigned, and **Value** and the month list are unchanged. See [Known problems](#known-problems-in-version-1070). +Setting **Month** selects the same day of that month in the shown year, shows the month, and updates the month list. A month number outside 1 to 12, or a month that lacks the selected day (**Day** is 31 and the month has 30 days), raises run-time error 13, *Type mismatch*. **Month** then returns the value assigned, and **Value** and the month list are unchanged. See [Known problems](#known-problems-in-version-10170). ### MonthLength {: .no_toc } @@ -277,7 +276,7 @@ A value other than **ShortLength** shows the whole name. Whether the month list and the year list are shown. **Boolean**. Default: **True**. -While the lists are shown the control is painted again continuously, and the title is centred in the width less 140 pixels whether or not they are shown. See [Known problems](#known-problems-in-version-1070). +Setting the property moves the lists to their places and works out the height of the title area again. The title is centred in the space to the left of the lists, or in the whole width when they are not shown. With **ShowTitle** **False** as well, the title area has no height: see [Known problems](#known-problems-in-version-10170). ### ShowDays {: .no_toc } @@ -292,7 +291,7 @@ Whether the horizontal lines between the rows of the grid are drawn. **Boolean** ### ShowTitle {: .no_toc } -Whether the title, the month and the year as text, is drawn. **Boolean**. Default: **True**. With **ShowTitle** **False** and **ShowDateSelectors** **True**, the title area keeps its height and shows only the two lists. +Whether the title, the month and the year as text, is drawn. **Boolean**. Default: **True**. With **ShowTitle** **False** and **ShowDateSelectors** **True**, the title area keeps its height and shows only the two lists. With both **False** the title area has no height, and setting **ShowTitle** to **True** does not give it one: see [Known problems](#known-problems-in-version-10170). ### ShowVerticalGrid {: .no_toc } @@ -302,17 +301,17 @@ Whether the vertical lines between the columns of the grid are drawn. **Boolean* ### TitleFont {: .no_toc } -The font of the title. A **Font** object. Default: Arial, 16 points. The remarks for [**DayFont**](#dayfont) apply. +The font of the title. A **Font** object. Default: Arial, 16 points. The remarks for [**DayFont**](#dayfont) apply, with one exception: the height of the title area, and the places of the two lists in it, follow the font only when it is assigned with **Set**, not when a property of the object changes. See [Known problems](#known-problems-in-version-10170). ### TitleFontColor {: .no_toc } -The colour of the title. **OLE_COLOR**. Default: orange (`&H80FF&`, red 255, green 128, blue 0). The value is stored, and it has no effect on the colour of the text: see [Known problems](#known-problems-in-version-1070). +The colour of the title. **OLE_COLOR**. Default: orange (`&H80FF&`, red 255, green 128, blue 0). The value is stored, and it has no effect on the colour of the text: see [Known problems](#known-problems-in-version-10170). ### TitleMargin {: .no_toc } -The space above and below the title, in device pixels. **Integer**. Default: `5`. The height of the title area is the height of the title text plus twice this value. At 144 DPI, raising the value from 5 to 15 moves the top edge of the row of day names down by 20 pixels, twice the change. +The space above and below the title, in device pixels. **Integer**. Default: `5`. The height of the title area is the height of one line of the title font plus twice this value, and the two lists are placed in the height again when the value is set. At 144 DPI, raising the value from 5 to 15 moves the top edge of the row of day names down by 20 pixels, twice the change. ### Value {: .no_toc } @@ -321,28 +320,26 @@ The selected date. A **Date**. Default: the date on which the control was placed Setting **Value** changes **Year**, **Month** and **Day** to the parts of the date, updates the two lists, and redraws the control. The time of day of the date is kept, and a **Value** with a time of day selects no cell. Setting **Value** raises no event. A String that is a date is converted; a String that is not a date raises run-time error 13, *Type mismatch*, and **Null** raises error 94. -When the new date is in a month other than the shown one, the title and the lists show the new month, and the grid keeps the dates of the month it showed before, with no cell selected, until the selection next moves. See [Known problems](#known-problems-in-version-1070). +When the new date is in a month other than the shown one, the title and the lists show the new month, and the grid keeps the dates of the month it showed before, with no cell selected, until the selection next moves. See [Known problems](#known-problems-in-version-10170). ### ValueIsNull {: .no_toc } **Boolean**. Default: **False**. -Setting the property raises run-time error 438, *Object doesn't support this property or method*. The value is stored all the same, and nothing else in the control uses it: it has no effect on **Value** or on how the control is drawn. See [Known problems](#known-problems-in-version-1070). +Setting the property raises run-time error 438, *Object doesn't support this property or method*. The value is stored all the same, and nothing else in the control uses it: it has no effect on **Value** or on how the control is drawn. See [Known problems](#known-problems-in-version-10170). ### ValueStr {: .no_toc } The selected date as a **String**. Reading it returns **Value** as **CStr** converts it: the short date of the regional settings, such as `3/15/2026` with US regional settings, followed by the time, such as `2:30:00 PM`, only when **Value** has a time of day. Setting it converts the string with **CDate** and assigns the result to **Value**. A string that **CDate** cannot read, an empty string among them, raises run-time error 13, *Type mismatch*. -The source marks the property as temporary: it is there until the property page can edit a date. - ### Year {: .no_toc } The year of the selected date. **Integer**. Default: the year in which the control was placed on the form. -Setting **Year** selects the same day and month in that year, shows the year, and updates the year list, which holds 1900 to 2100. A year that lacks the selected day (**Day** is 29, **Month** is 2, and the year is not a leap year) raises run-time error 13, *Type mismatch*; **Year** then returns the value assigned, and **Value** is unchanged. A year outside 1900 to 2100 raises no error, but it is not in the year list. See [Known problems](#known-problems-in-version-1070). +Setting **Year** selects the same day and month in that year, shows the year, and updates the year list, which holds 1900 to 2100. A year that lacks the selected day (**Day** is 29, **Month** is 2, and the year is not a leap year) raises run-time error 13, *Type mismatch*; **Year** then returns the value assigned, and **Value** is unchanged. A year outside 1900 to 2100 raises no error, but it is not in the year list. See [Known problems](#known-problems-in-version-10170). ## Methods @@ -394,7 +391,7 @@ Selects the same day one year before the selected one. The remarks for [**NextYe ### Refresh {: .no_toc } -Draws the control again. It does not place the dates of the grid again, and so does not correct a grid that disagrees with the day names or with **Value** (see [Known problems](#known-problems-in-version-1070)). +Draws the control again. It does not place the dates of the grid again, and so does not correct a grid that disagrees with the day names or with **Value** (see [Known problems](#known-problems-in-version-10170)). ### Today {: .no_toc } @@ -494,7 +491,7 @@ The parameters are those of [**KeyDown**](#keydown). ### MouseDown {: .no_toc } -Declared, and never raised. A handler for it is never called. See [Known problems](#known-problems-in-version-1070). +Declared, and never raised. A handler for it is never called. See [Known problems](#known-problems-in-version-10170). Syntax: *object*\_**MouseDown**( *Button* **As Integer**, *Shift* **As Integer**, *X* **As Single**, *Y* **As Single** ) @@ -530,7 +527,7 @@ Raised after the user chooses a month in the month list, once the selection has Syntax: *object*\_**NewMonth**( ) -A change of month by any other means, such as a click on a day of another month, a key, a method or a property, does not raise it. Choosing a month in which the selected day does not exist can hang the program: see [Known problems](#known-problems-in-version-1070). +A change of month by any other means, such as a click on a day of another month, a key, a method or a property, does not raise it. Choosing a month in which the selected day does not exist raises run-time error 13 in the control and does not raise this event: see [Known problems](#known-problems-in-version-10170). ### NewYear {: .no_toc } @@ -539,7 +536,7 @@ Raised after the user chooses a year in the year list. Syntax: *object*\_**NewYear**( ) -A change of year by any other means does not raise it. Choosing a year in which the selected day does not exist, February 29 in a year that is not a leap year, can hang the program: see [Known problems](#known-problems-in-version-1070). +A change of year by any other means does not raise it. Choosing a year in which the selected day does not exist, February 29 in a year that is not a leap year, raises run-time error 13 in the control and does not raise this event: see [Known problems](#known-problems-in-version-10170). ## Enumerations diff --git a/docs/Reference/Experimental.md b/docs/Reference/Experimental.md index d6fe19b2..910808b1 100644 --- a/docs/Reference/Experimental.md +++ b/docs/Reference/Experimental.md @@ -22,4 +22,4 @@ These packages are published through TWINSERV, as the [Contributed Packages](../ - [**TBMANLIB_Xml**](../Contributed/TBMANLIB_Xml/) -- XML documents parsed, edited and written, in twinBASIC code. Version 1.0.0.1 compiles, and its parser misreads some common XML, such as self-closing elements and entities in text, and does not return for some malformed text. Its XPath engine reads some valid expressions as others, such as the step `..` and `and` with `or`. - [**tbRegExp**](../Contributed/tbRegExp/) -- regular expressions, in place of VBScript's **RegExp** object. Version 1.4.0.0 compiles, and with **Global** set to **True** it reports a match of no characters twice when the search skipped characters to reach it, so **Execute**, **Replace** and **Split** give wrong results for patterns such as `$`, `\b` and a lookahead. - [**WinReg**](../Contributed/WinReg/) -- registry keys and values, read, written, deleted and enumerated. Version 1.0.1.1 does not compile in twinBASIC BETA 1005, and three of its methods give wrong results: **ReadString** returns the text followed by NUL characters, **GetRegistryValueType** returns **REG_NONE** for a value that holds data, and one overload of **ConvertValueToString** finds no value. -- [**xCalendar**](../Contributed/xCalendar/) -- a calendar control, in place of the Calendar control of `MSCAL.OCX`. Version 1.0.7.0 compiles, and has defects, among them some that can stop a program from responding. +- [**xCalendar**](../Contributed/xCalendar/) -- a calendar control, in place of the Calendar control of `MSCAL.OCX`. Version 1.0.17.0 compiles, and has defects, among them a grid that keeps the dates of the old month when **Value** is set to another month, and colour properties of the text that have no effect. diff --git a/indexer/snapshots/contributed/xCalendar/api.json b/indexer/snapshots/contributed/xCalendar/api.json index 918b74c1..aa68e14f 100644 --- a/indexer/snapshots/contributed/xCalendar/api.json +++ b/indexer/snapshots/contributed/xCalendar/api.json @@ -2,7 +2,7 @@ "format": 3, "package": "xCalendar", "project": "xCalendar", - "version": "1.0.7.0", + "version": "1.0.17.0", "types": [ { "name": "xCalendar", @@ -57,6 +57,7 @@ {"name":"DrawTextW","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function DrawTextW Lib \"user32\" (ByVal hdc As LongPtr, ByVal lpStr As LongPtr, ByVal nCount As Long, lpRect As RECT, ByVal wFormat As Long) As Long","returnType":"Long"}, {"name":"Enabled","kind":"property","vis":"public","signature":"Public Property Get Enabled() As Boolean","returnType":"Boolean","accessor":"Get","description":"Gets the Enabled state of the control"}, {"name":"Enabled","kind":"property","vis":"public","signature":"Public Property Let Enabled(ByVal New_Enabled As Boolean)","accessor":"Let","description":"Sets the Enabled state of the control"}, + {"name":"FillComboBoxes","kind":"sub","vis":"private","signature":"Private Sub FillComboBoxes()"}, {"name":"FillRect","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function FillRect Lib \"user32\" (ByVal hDC As LongPtr, lpRect As RECT, ByVal hBrush As LongPtr) As Long","returnType":"Long"}, {"name":"FirstDate","kind":"field","vis":"private","signature":"Private FirstDate As Date"}, {"name":"FirstDay","kind":"property","vis":"public","signature":"Public Property Get FirstDay() As WeekDaysEnum","returnType":"WeekDaysEnum","accessor":"Get","description":"Gets the first day of the week"}, @@ -90,6 +91,7 @@ {"name":"NextMonth","kind":"sub","vis":"public","signature":"Public Sub NextMonth()","description":"Moves the selection one month in the future"}, {"name":"NextWeek","kind":"sub","vis":"public","signature":"Public Sub NextWeek()","description":"Moves the selection one week in the future"}, {"name":"NextYear","kind":"sub","vis":"public","signature":"Public Sub NextYear()","description":"Moves the selection one year in the future"}, + {"name":"PositionComboboxes","kind":"sub","vis":"private","signature":"Private Sub PositionComboboxes()"}, {"name":"PreviousDay","kind":"sub","vis":"public","signature":"Public Sub PreviousDay()","description":"Selects the previous day"}, {"name":"PreviousMonth","kind":"sub","vis":"public","signature":"Public Sub PreviousMonth()","description":"Moves the selection one month in the past"}, {"name":"PreviousWeek","kind":"sub","vis":"public","signature":"Public Sub PreviousWeek()","description":"Moves the selection one week in the past"}, @@ -98,7 +100,6 @@ {"name":"Refresh","kind":"sub","vis":"public","signature":"Public Sub Refresh()","description":"Forces a complete repaint of the control"}, {"name":"SelectObject","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function SelectObject Lib \"gdi32\" (ByVal hDC As LongPtr, ByVal hObject As LongPtr) As LongPtr","returnType":"LongPtr"}, {"name":"SetCtrlState","kind":"sub","vis":"private","signature":"Private Sub SetCtrlState(ByVal NewState As CtrlStateEnum, Optional RedrawCtrl As Boolean = True)"}, - {"name":"SetupComboBoxes","kind":"sub","vis":"private","signature":"Private Sub SetupComboBoxes()"}, {"name":"ShowDateSelectors","kind":"property","vis":"public","signature":"Public Property Get ShowDateSelectors() As Boolean","returnType":"Boolean","accessor":"Get","description":"Gets the visibility of the month and year selectors"}, {"name":"ShowDateSelectors","kind":"property","vis":"public","signature":"Public Property Let ShowDateSelectors(ByVal New_ShowDateSelectors As Boolean)","accessor":"Let","description":"Sets the visibility of the month and year selectors"}, {"name":"ShowDays","kind":"property","vis":"public","signature":"Public Property Get ShowDays() As Boolean","returnType":"Boolean","accessor":"Get","description":"Gets the visibility of the day names area"}, @@ -115,13 +116,13 @@ {"name":"TitleFontColor","kind":"property","vis":"public","signature":"Public Property Let TitleFontColor(ByVal New_TitleFontColor As OLE_COLOR)","accessor":"Let","description":"Sets the color of the title font"}, {"name":"TitleHeight","kind":"field","vis":"private","signature":"Private TitleHeight As Long"}, {"name":"TitleMargin","kind":"property","vis":"public","signature":"Public Property Get TitleMargin() As Integer","returnType":"Integer","accessor":"Get","description":"Gets the title area top and bottom margin"}, - {"name":"TitleMargin","kind":"property","vis":"public","signature":"Public Property Let TitleMargin(ByVal New_TitleMargin As Integer)","accessor":"Let","description":"Sets the title area top and bottom margin"}, + {"name":"TitleMargin","kind":"property","vis":"public","signature":"Public Property Let TitleMargin(ByVal New_TitleMargin As Integer)","accessor":"Let","description":"Gets the title area top and bottom margin"}, + {"name":"TitleWidth","kind":"field","vis":"private","signature":"Private TitleWidth As long"}, {"name":"Today","kind":"sub","vis":"public","signature":"Public Sub Today()","description":"Moves the selection to today"}, {"name":"UserControl_Click","kind":"sub","vis":"private","signature":"Private Sub UserControl_Click()"}, {"name":"UserControl_DblClick","kind":"sub","vis":"private","signature":"Private Sub UserControl_DblClick()"}, {"name":"UserControl_GotFocus","kind":"sub","vis":"private","signature":"Private Sub UserControl_GotFocus()"}, {"name":"UserControl_InitProperties","kind":"sub","vis":"private","signature":"Private Sub UserControl_InitProperties()"}, - {"name":"UserControl_KeyDown","kind":"sub","vis":"private","signature":"Private Sub UserControl_KeyDown(KeyCode As Integer, Shift As Integer)"}, {"name":"UserControl_KeyPress","kind":"sub","vis":"private","signature":"Private Sub UserControl_KeyPress(KeyAscii As Integer)"}, {"name":"UserControl_KeyUp","kind":"sub","vis":"private","signature":"Private Sub UserControl_KeyUp(KeyCode As Integer, Shift As Integer)"}, {"name":"UserControl_LostFocus","kind":"sub","vis":"private","signature":"Private Sub UserControl_LostFocus()"}, @@ -136,8 +137,8 @@ {"name":"Value","kind":"property","vis":"public","signature":"Public Property Let Value(ByVal New_Value As Date)","accessor":"Let","description":"Sets the calendar value"}, {"name":"ValueIsNull","kind":"property","vis":"public","signature":"Public Property Get ValueIsNull() As Boolean","returnType":"Boolean","accessor":"Get"}, {"name":"ValueIsNull","kind":"property","vis":"public","signature":"Public Property Let ValueIsNull(ByVal New_ValueIsNull As Boolean)","accessor":"Let"}, - {"name":"ValueStr","kind":"property","vis":"public","signature":"Public Property Get ValueStr() As String","returnType":"String","accessor":"Get","description":"Gets the calendar value as string"}, - {"name":"ValueStr","kind":"property","vis":"public","signature":"Public Property Let ValueStr(ByVal NewValueStr As String)","accessor":"Let","description":"Sets the calendar value as string"}, + {"name":"ValueStr","kind":"property","vis":"public","signature":"Public Property Get ValueStr() As String","returnType":"String","accessor":"Get"}, + {"name":"ValueStr","kind":"property","vis":"public","signature":"Public Property Let ValueStr(ByVal NewValueStr As String)","accessor":"Let"}, {"name":"Year","kind":"property","vis":"public","signature":"Public Property Get Year() As Integer","returnType":"Integer","accessor":"Get","description":"Gets the year of the selected date"}, {"name":"Year","kind":"property","vis":"public","signature":"Public Property Let Year(ByVal New_Year As Integer)","accessor":"Let","description":"Sets the year of the selected date"}, {"name":"cmbMonth_Click","kind":"sub","vis":"private","signature":"Private Sub cmbMonth_Click()"}, diff --git a/test/example-projects/contributed-xCalendar/Settings b/test/example-projects/contributed-xCalendar/Settings index bd5cc6b7..188c7252 100644 --- a/test/example-projects/contributed-xCalendar/Settings +++ b/test/example-projects/contributed-xCalendar/Settings @@ -58,7 +58,7 @@ "versionBuild": "0", "versionMajor": "1", "versionMinor": "0", - "versionRevision": "7" + "versionRevision": "17" } ], "project.settingsVersion": 1, diff --git a/test/package-probes/xCalendar/README.md b/test/package-probes/xCalendar/README.md index 88a1e8ef..cc3d26ef 100644 --- a/test/package-probes/xCalendar/README.md +++ b/test/package-probes/xCalendar/README.md @@ -3,9 +3,13 @@ What the contributed package xCalendar's **xCalendar** control does at run time: a form in a built exe on a private desktop holds one control, the exe operates it from inside and writes every event and every value it reads to a file. The page `docs/Reference/Contributed/xCalendar/index.md` states -what this probe printed, on package 1.0.7.0 and BETA 1005, in 32-bit and 64-bit builds (the two give +what this probe printed, on package 1.0.17.0 and BETA 1005, in 32-bit and 64-bit builds (the two give the same output, pixel positions included). Run it again when the section's `indexed_from` moves to a -new version of the package. +new version of the package. The page was written from 1.0.7.0 first; 1.0.17.0 answers differently in +scenarios 12x, 12y, 12z and 16 (no hang, no repaint loop), in the lists' size and place (scaled with +the DPI), in the title's place, in the height of the title area (scenarios 14, p18 and 17), and in the +lists of a control that loads with Enabled False (scenario 1 on the form `designer.mjs` writes); +every other scenario printed the same on both. It is outside every gate and CI: it needs Windows, a twinBASIC install (the newest `twinBASIC_IDE_BETA_` on the Desktop, as `tbrun` finds it) and the package, which `stage.mjs` @@ -20,16 +24,25 @@ node test/package-probes/xCalendar/probe.mjs 1 2,3 4,5 6,7,8 9 10 11,12 13 14 node test/package-probes/xCalendar/probe.mjs --arch win64 --port 9911 1 2,3 4,5 6,7,8 9 10 11,12 13 14 node test/package-probes/xCalendar/probe.mjs --shots 15 15b node test/package-probes/xCalendar/probe.mjs p2,p3 p5,p7 p9 p10 p11 p12 p13 p14 p18 p19 p20,p21 p24,p25 p29 p33 p22x -node test/package-probes/xCalendar/probe.mjs 12x 12y 12z # each ends at the timeout: see below +node test/package-probes/xCalendar/probe.mjs 16 17 +node test/package-probes/xCalendar/probe.mjs 12x 12y 12z 12w # each opens the run-time error box, except 12w: see below +node test/package-probes/xCalendar/designer.mjs --edit Enabled=False --out .tbform +node test/package-probes/xCalendar/probe.mjs --form .tbform 1 # a control that loads disabled ``` Each argument is one run of the exe; the scenario ids are listed at the top of [`src/Probe.twin`](src/Probe.twin), the numbers of the questions they answer. A build takes about ten seconds and a run 3 to 20 s (the picture runs, 2 s a picture, the longest); every run is ended after 33 -s. Exit 1 means a run crashed, opened a box or reached the timeout, which is the answer for `12x`, +s. Exit 1 means a run crashed, opened a box or reached the timeout. The box is the answer for `12x`, `12y` and `12z`: choosing a month or a year from a list that makes an impossible date (31 January to -February, 29 February 2024 to 2025) leaves the exe in a loop at full processor load, which -`run-exe.mjs` shows by sampling its processor time at the timeout. +February, 29 February 2024 to 2025) raises run-time error 13 in the control's own handler, and the +runtime opens its error box; the runner closes it (BM_CLICK on OK) and the run goes on, so these +runs end with exit 1 and the box listed. In 1.0.7.0 the same keys left the exe in a loop at full +processor load until the timeout, which `run-exe.mjs` shows by sampling its processor time. +`12z` and `12w` post the key to the queue: the list then takes the key as two steps (`12w`, on a date +every month has, goes June, July, August, with two `NewMonth`), where a key sent with `SendMessage` +(scenario 12, `12x`) is one step. The package has no code for a list's keys, so that is the lists' +own doing, and it is why `12z` ends in March, not February. Ports 9910 to 9940 are this probe's; `--port` keys the IDE's DevTools port, the work folders (`%TEMP%\xcal-probe\tree-`) and the private desktop's name, so two runs with different ports can @@ -66,17 +79,22 @@ run at once. focus, the key-up dispatched after the key-down, through `DoEvents` (`p9`). The runtime's loop makes the `KeyPress` and `KeyUp` events and the `Shift` argument (a modifier is held with `SetKeyboardState`, which `GetKeyState` reads). **A key sent with `SendMessage` straight to the picture box raises - `KeyDown` only** (scenario 10): the loop is part of what a key press does. **`DoEvents` does not - return while the date selectors are visible**, because the control asks for a repaint after every - paint (scenario 16); the probe's own `Pump` stops after three `WM_PAINT`, and the scenarios that need - `DoEvents` hide the selectors first. + `KeyDown` only** (scenario 10): the loop is part of what a key press does. **In 1.0.7.0 `DoEvents` + did not return while the date selectors were visible**, because the control asked for a repaint after + every paint; in 1.0.17.0 it returns at once (scenario 16). The probe's own `Pump` still stops after + three `WM_PAINT`, and the scenarios that use `DoEvents` still hide the selectors first, as they were + written for 1.0.7.0. - Pixels are read with `GetPixel` from the control's window DC, after it has painted. - A real mouse or keyboard cannot be used: the exe runs on a private desktop, not the input desktop. Windows sends no input to a disabled window, so what a click on a disabled control does is only the control's own code, which scenario 11 shows. - The sizes are device pixels at the private desktop's DPI, 144 (150%) on the machine the probe was - written on: the control's date selectors are positioned in unscaled pixels (a list 50 wide), so at - that DPI the year list shows "20" of "2026". + written on. The control scales its two lists with `UserControl.DpiScale` (the year list is 75 wide + there, the month list 120); its margins and lines it does not scale. 1.0.7.0 did not scale the + lists either, so at that DPI its year list showed "20" of "2026". Only 144 DPI was run: that the + factor is `DpiScale` comes from the package's source. +- Pixels under a child window (a list, a picture box) read as -1 (`&HFFFFFFFF`) from the control's + DC, so a scan for the title's pixels stops at the month list's left edge (`ListsLeft`). ## Rules diff --git a/test/package-probes/xCalendar/src/Probe.twin b/test/package-probes/xCalendar/src/Probe.twin index 10dc6828..0a0fc03b 100644 --- a/test/package-probes/xCalendar/src/Probe.twin +++ b/test/package-probes/xCalendar/src/Probe.twin @@ -9,8 +9,8 @@ ' ' Input is simulated by sending window messages (Win.twin): mouse messages to the control's window, ' key messages to the window that has the keyboard focus. The message loop is run by Pump, a -' bounded loop: the control asks for a repaint again after every paint while its selectors are -' visible (scenario 16), and DoEvents would not return. +' bounded loop: in package 1.0.7.0 the control asked for a repaint again after every paint while +' its selectors were visible and DoEvents did not return (scenario 16); 1.0.17.0 does not. ' ' The IDE-side [RunAfterBuild] Sub only prints: the form is shown only in the exe. ' @@ -27,12 +27,21 @@ ' 10 keys, with and without Shift and Ctrl, and sent to the wrong window ' 11 Enabled = False ' 12 the Month and Year selectors (two combo boxes) -' 12x a selector change that makes an impossible date (Jan 31 to February); never returns +' 12x a selector change that makes an impossible date (Jan 31 to February); opens the +' run-time error box (1.0.7.0: never returned) ' 12y the same for the year list (Feb 29 2024 to 2025) +' 12z 12x again, with the key posted to the queue and dispatched by the probe's loop +' 12w a key posted to the month list on a date every month has (June 15): one step? ' 13 FirstDay, DayLength, MonthLength, GridCellEffect and the margins, out of range ' 14 the colours and fonts set from code; Show* flags and the layout they give ' 15 pictures of the form window (the run-exe.mjs runner takes them); 15b more of them -' 16 the message loop: how many WM_PAINT messages the control keeps asking for +' 16 the message loop: how many WM_PAINT messages the control keeps asking for, and +' whether DoEvents returns with the selectors shown +' 17 what package 1.0.17.0 changed: the lists' size and place at the DPI of the display and +' when the control is resized, the title area's height (TitleFont, TitleMargin, ShowTitle +' and ShowDateSelectors), and the title's place +' (A control that loads with Enabled False is shown by scenario 1 on a form that +' designer.mjs --edit Enabled=False wrote: probe.mjs --form FILE 1) ' ' The scenarios below answer the page author's numbered questions (xcal-probe-questions.md), and ' read the control's pixels with GetPixel from its window DC: @@ -90,7 +99,7 @@ Module Probe Debug.Print "hook ran in the IDE; the form runs only in the exe" End Sub - Private Const ScenarioIds As String = ",1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,15b,16,p2,p3,p5,p7,p9,p10,p11,p12,p13,p14,p18,p19,p20,p21,p24,p25,p29,p33,12x,12y,12z,p22x," + Private Const ScenarioIds As String = ",1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,15b,16,17,p2,p3,p5,p7,p9,p10,p11,p12,p13,p14,p18,p19,p20,p21,p24,p25,p29,p33,12x,12y,12z,12w,p22x," Public Sub Main() Dim sel As String = "," & Replace(Environ$("PROBE"), " ", "") & "," @@ -147,11 +156,13 @@ Module Probe Case "12x": S12x Case "12y": S12y Case "12z": S12z + Case "12w": S12w Case "13": S13 Case "14": S14 Case "15": S15 Case "15b": S15b Case "16": S16 + Case "17": S17 Case "p2": P2 Case "p3": P3 Case "p5": P5 @@ -333,8 +344,8 @@ EH: ' ---- mouse ---- ' A mouse message to the control window: sent, or (gReal) posted to the queue and dispatched - ' by DoEvents, the runtime's own loop, as the system delivers a real mouse's. gReal needs the - ' selectors hidden, or DoEvents does not return (scenario 16). + ' by DoEvents, the runtime's own loop, as the system delivers a real mouse's. With package + ' 1.0.7.0 gReal needed the selectors hidden, or DoEvents did not return (scenario 16). Private Sub Msg(ByVal m As Long, ByVal w As LongPtr, ByVal l As LongPtr) If gReal Then Win.PostMessageW CalH, m, w, l @@ -1055,7 +1066,9 @@ EH: ' A selector change that makes an impossible date: January 31 to February. The control builds ' the date from year, month and the old day, in its own event handler, so the error is raised - ' inside the control with no caller to take it. Run on its own: it may end the run. + ' inside the control with no caller to take it. In 1.0.7.0 the run never came back from the key + ' and was ended by the runner's timeout; in 1.0.17.0 the runtime opens its run-time error box, + ' which the runner closes, and the run goes on. Run on its own. Private Sub S12x() On Error Resume Next Cal.Value = DateSerial(2026, 1, 31) @@ -1075,12 +1088,25 @@ EH: Pump Act "key Down posted to the month combo, on 2026-01-31" Win.PostKey MonthH, Win.VK_DOWN + Pump 12 + Say " returned from the loop" + St "after" + End Sub + + ' The control of 12z on a date every month has: one Down key posted to the queue is one step + ' of the month list (the control of 12z took two after the error). + Private Sub S12w() + On Error Resume Next + Cal.Value = DateSerial(2026, 6, 15) Pump + Act "key Down posted to the month combo, on 2026-06-15" + Win.PostKey MonthH, Win.VK_DOWN + Pump 12 Say " returned from the loop" St "after" End Sub - ' The same for the year list: 2024-02-29 to 2025. Run on its own: it may end the run. + ' The same for the year list: 2024-02-29 to 2025. Run on its own. Private Sub S12y() On Error Resume Next Cal.Value = DateSerial(2024, 2, 29) @@ -1324,6 +1350,10 @@ EH: Private Sub S16() On Error Resume Next Storm "as loaded", 1500 + ' With 1.0.7.0 this DoEvents does not return, and the 30 s watchdog ends the process. + Dim t0 As Long = GetTickCount + DoEvents + Say " DoEvents with the selectors shown returned after " & (GetTickCount - t0) & " ms" Cal.ShowDateSelectors = False Storm "ShowDateSelectors = False", 1000 Cal.ShowTitle = False @@ -1339,6 +1369,146 @@ EH: Chk "S16" End Sub + '========================================================================================= + ' 17: what package 1.0.17.0 changed: the lists and the title area + '========================================================================================= + + ' Where both lists are, in the control's client area. + Private Sub Lists(ByVal what As String) + Dim rc As RECT + Dim rm As RECT + Dim ry As RECT + Win.GetWindowRect CalH, rc + Win.GetWindowRect MonthH, rm + Win.GetWindowRect YearH, ry + Say " lists[" & what & "]: control " & CalW & "x" & CalHt & "; month list x=" & (rm.Left - rc.Left) & ".." & (rm.Right - rc.Left) & " y=" & (rm.Top - rc.Top) & ".." & (rm.Bottom - rc.Top) & "; year list x=" & (ry.Left - rc.Left) & ".." & (ry.Right - rc.Left) & " y=" & (ry.Top - rc.Top) & ".." & (ry.Bottom - rc.Top) & "; space right of the year list " & (CalW - (ry.Right - rc.Left)) & "; visible " & Win.IsWindowVisible(MonthH) & "/" & Win.IsWindowVisible(YearH) + End Sub + + ' The header (title area + day names) and the first pixel row of the day names' band. + Private Sub Header(ByVal what As String) + Pump + Calibrate + Say " header[" & what & "]: " & gHeader & " px; the day names' band starts at y=" & FirstY(3, Cal.DayBackColor, 0, CalHt - 1) + End Sub + + ' The control's window put to a new size, its top left corner kept. + Private Sub ResizeControl(ByVal w As Long, ByVal h As Long) + Dim rc As RECT + Dim p As Win.POINT + Win.GetWindowRect CalH, rc + p.x = rc.Left + p.y = rc.Top + Win.ScreenToClient Form1.hWnd, p + Win.MoveWindow CalH, p.x, p.y, w, h, 1 + Pump + Win.GetClientRect CalH, rc + CalW = rc.Right + CalHt = rc.Bottom + End Sub + + Private Sub S17() + On Error Resume Next + Dim f As New StdFont + Say "dpi of the control window: " & Win.GetDpiForWindow(CalH) + Header "as loaded" + Lists "as loaded" + TitleSpan "ShowDateSelectors = True", ListsLeft() - 2 + Cal.Value = DateSerial(2026, 10, 13) + Cal.Month = 10 + TitleSpan "October 2026", ListsLeft() - 2 + Cal.MonthLength = 0 + TitleSpan "Oct 2026 (MonthLength = ShortLength)", ListsLeft() - 2 + Cal.MonthLength = 1 + Cal.Value = DateSerial(2026, 6, 15) + Cal.Month = 6 + Say "-- the control's window resized with MoveWindow, its top left corner kept" + ResizeControl 300, 288 + Lists "300 x 288" + TitleSpan "ShowDateSelectors = True", ListsLeft() - 2 + ResizeControl 384, 250 + Lists "384 x 250" + ResizeControl 384, 288 + Lists "384 x 288 again" + Say "-- TitleMargin" + Cal.TitleMargin = 15 + Header "TitleMargin = 15" + Lists "TitleMargin = 15" + Cal.TitleMargin = 5 + Header "TitleMargin = 5" + Say "-- TitleFont: Set to a 24 pt font, changed in place to 10 pt, then Set again" + f.Name = "Arial" + f.Size = 24 + Set Cal.TitleFont = f + Chk "Set TitleFont" + Header "24 pt set" + Lists "24 pt set" + TitleSpan "24 pt set", ListsLeft() - 2 + f.Size = 10 + Header "f.Size = 10 in place" + Lists "f.Size = 10 in place" + TitleSpan "f.Size = 10 in place, no Refresh", ListsLeft() - 2 + Set Cal.TitleFont = f + Header "Set TitleFont = f again" + Lists "Set TitleFont = f again" + TitleSpan "Set TitleFont = f again", ListsLeft() - 2 + f.Size = 16 + Set Cal.TitleFont = f + Header "16 pt set, as at the start" + Say "-- GridFont changed in place, no Refresh: is the grid drawn again?" + Dim g As New StdFont + Dim x0 As Long + Dim y0 As Long + Dim x1 As Long + Dim y1 As Long + g.Name = "Arial" + g.Size = 8 + Set Cal.GridFont = g + Pump + Calibrate + BBox 2, gHeader + 2, CLng(gColW) - 2, CLng(gHeader + gRowH) - 2, Cal.BackColor, x0, y0, x1, y1 + Say " g.Size = 8 set: digits of the first cell span " & (x1 - x0 + 1) & " x " & (y1 - y0 + 1) & " pixels" + g.Size = 20 + Pump + Calibrate + BBox 2, gHeader + 2, CLng(gColW) - 2, CLng(gHeader + gRowH) - 2, Cal.BackColor, x0, y0, x1, y1 + Say " g.Size = 20 in place, a pump, no Refresh: digits of the first cell span " & (x1 - x0 + 1) & " x " & (y1 - y0 + 1) & " pixels" + g.Size = 8 + Set Cal.GridFont = g + Pump + Say "-- ShowTitle and ShowDateSelectors" + Cal.ShowTitle = False + Header "ShowTitle = False, lists shown" + Cal.ShowDateSelectors = False + Header "ShowTitle = False, ShowDateSelectors = False" + Cal.ShowTitle = True + Header "ShowTitle = True again, lists hidden" + Cal.ShowDateSelectors = True + Header "ShowDateSelectors = True" + Lists "ShowDateSelectors = True" + Say "-- again, ended by TitleMargin in place of ShowDateSelectors" + Cal.ShowTitle = False + Cal.ShowDateSelectors = False + Header "both False" + Cal.ShowTitle = True + Header "ShowTitle = True again, lists hidden" + Cal.TitleMargin = 5 + Header "TitleMargin = 5 (the value it has)" + Cal.ShowDateSelectors = True + Header "ShowDateSelectors = True" + Say "-- and a third time, ended by a change of the control's size" + Cal.ShowTitle = False + Cal.ShowDateSelectors = False + Header "both False" + Cal.ShowTitle = True + Header "ShowTitle = True again, lists hidden" + ResizeControl 380, 288 + Header "the control 380 wide" + ResizeControl 384, 288 + Cal.ShowDateSelectors = True + Header "ShowDateSelectors = True" + Chk "S17" + End Sub + '========================================================================================= ' Pixels: read from the control's window DC with GetPixel (a COLORREF, 0x00BBGGRR, the same ' number as an OLE_COLOR property), after the control has painted. @@ -1518,6 +1688,17 @@ EH: ' p2: the title with the selectors hidden (Q2) '========================================================================================= + ' The left edge of the month list, in the control's client area: the title is drawn to its left. + ' (A pixel under a list or a picture box reads as -1 from the control's DC, so a box that + ' reaches the lists finds "text" there.) + Private Function ListsLeft() As Long + Dim rc As RECT + Dim rm As RECT + Win.GetWindowRect CalH, rc + Win.GetWindowRect MonthH, rm + ListsLeft = rm.Left - rc.Left + End Function + Private Sub TitleSpan(ByVal what As String, ByVal xmax As Long) Pump Calibrate @@ -1527,12 +1708,12 @@ EH: Dim x1 As Long Dim y1 As Long BBox 1, 2, xmax, top - 3, Cal.BackColor, x0, y0, x1, y1 - Say " title[" & what & "]: day-names band starts at y=" & top & "; title text pixels span x=" & x0 & ".." & x1 & " (centre " & ((x0 + x1) / 2) & "), y=" & y0 & ".." & y1 & "; control width " & CalW & ": W/2 = " & (CalW / 2) & ", (W - 140)/2 = " & ((CalW - 140) / 2) + Say " title[" & what & "]: day-names band starts at y=" & top & "; title text pixels in x=1.." & xmax & " span x=" & x0 & ".." & x1 & " (centre " & ((x0 + x1) / 2) & "), y=" & y0 & ".." & y1 & "; control width " & CalW & ": W/2 = " & (CalW / 2) & "; the month list starts at x=" & ListsLeft() & ", half of it " & (ListsLeft() / 2) End Sub Private Sub P2() On Error Resume Next - TitleSpan "ShowDateSelectors = True", CalW - 145 + TitleSpan "ShowDateSelectors = True", ListsLeft() - 2 Cal.ShowDateSelectors = False TitleSpan "ShowDateSelectors = False", CalW - 1 Shot "title_noselectors" @@ -1587,7 +1768,7 @@ EH: Private Sub TextColours(ByVal what As String) Pump Calibrate - Say " " & what & ": TitleFontColor=" & Hx(Cal.TitleFontColor) & ", the colours in the title text's box: " & TopColours(40, 8, 200, 36, Cal.BackColor) + Say " " & what & ": TitleFontColor=" & Hx(Cal.TitleFontColor) & ", the colours in the title text's box: " & TopColours(8, 8, ListsLeft() - 4, 36, Cal.BackColor) Say " " & what & ": DayFontColor=" & Hx(Cal.DayFontColor) & ", the colours in the first day name's box: " & TopColours(4, 52, 50, gHeader - 4, Cal.DayBackColor) Say " " & what & ": GridFontColor=" & Hx(Cal.GridFontColor) & ", the colours in the first cell's box: " & TopColours(4, gHeader + 3, 50, gHeader + 22, Cal.BackColor) End Sub @@ -2000,7 +2181,7 @@ EH: Dim x1 As Long Dim y1 As Long Dim h0 As Long - Say "-- selectors visible (the control repaints all the time)" + Say "-- selectors visible" Pump Calibrate h0 = gHeader @@ -2011,7 +2192,7 @@ EH: Say " header (title + day names) before: " & h0 & "; after f.Size = 24 on f = Cal.TitleFont (no Set) and a pump: " & gHeader f.Size = 16 Pump - Say "-- selectors hidden (nothing repaints until something asks)" + Say "-- selectors hidden" Cal.ShowDateSelectors = False Pump Calibrate @@ -2262,7 +2443,7 @@ EH: End Sub '========================================================================================= - ' p22x: Month = 13 and then a repaint (Q22). Run on its own: it may end the run. + ' p22x: Month = 13 and then a repaint (Q22). Run on its own. '========================================================================================= Private Sub P22x() From dbe6134b873d01e9aa1ca36c12a5b2f83c4f7941 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 13 Oct 2026 18:57:55 +0200 Subject: [PATCH 4/9] BigNumberLib: moved to 1.0.2.0, which compiles; arithmetic checked against BigInt; still Experimental for IsNegative, the negative operands of GCD and LCM, the negative zero of AssignFromLong and the modular inverse that does not return --- WIP.ExamplesBuild.md | 2 +- WIP.md | 2 +- .../Contributed/BigNumberLib/BigNumber.md | 220 +++++++++++++++--- .../Contributed/BigNumberLib/index.md | 48 +++- docs/Reference/Experimental.md | 2 +- .../contributed/BigNumberLib/api.json | 2 +- .../contributed-BigNumberLib/Settings | 6 +- 7 files changed, 236 insertions(+), 46 deletions(-) diff --git a/WIP.ExamplesBuild.md b/WIP.ExamplesBuild.md index 25ad6e1a..e257faec 100644 --- a/WIP.ExamplesBuild.md +++ b/WIP.ExamplesBuild.md @@ -335,7 +335,7 @@ the repository's to commit: four of the nine ship no licence file, and WinReg em until `contributed-WinReg` referenced WinDevLib (at the version WinReg embeds, 8.8.0.507) and the Scripting Runtime itself. The WinReg landing page tells the reader the same. - **An error inside an embedded package is the package's, and fails nothing** - (`splitPackageFaults`). BigNumberLib 0.0.0.0, WinReg 1.0.1.1 and ArrayListLib 1.4.0.134 do not compile on BETA + (`splitPackageFaults`). WinReg 1.0.1.1 and ArrayListLib 1.4.0.134 do not compile on BETA 1005: each passes a pointer to a `ByRef` structure parameter without `ByVal` (TB5001). The template-alone build finds those rows; the run prints them as a note naming the package rather than as a template FAIL, every other template row still fails, and the samples are diff --git a/WIP.md b/WIP.md index bcaf9969..453bf43e 100644 --- a/WIP.md +++ b/WIP.md @@ -34,7 +34,7 @@ sibling. History goes nowhere: git keeps it. Reference documentation is **complete** for all thirteen packages: VBA, VBRUN, VB, WebView2, Assert, CustomControls, CEF, WinEventLogLib, WinNamedPipesLib, WinServicesLib, tbIDE, WinNativeCommonCtls and AppGlobalClassObject. It is adapted from primary sources: Microsoft VBA-Docs (CC-BY-4.0) for the runtime library, `.twin` source for the twinBASIC-specific packages. The CEF and WebView2 packages also carry a tutorial set. -Twenty-six **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerLib, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsJson, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. The two console packages carry a NOTE (owner, 2026-10-12) that the samples harness cannot yet capture a console, so their console claims follow the source. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. VBComDlg's `exclude_from_docs` names `CommonDialogBase`, a module whose public procedures only its class calls. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modImpExp` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Xml, tbRegExp, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib, BigNumberLib and WinReg do not compile on BETA 1005 (each documented from its source alone, its samples compile-only; WinReg's methods were also run in a copy of its source with its four failing calls passed `ByVal`, and ReadString, GetRegistryValueType and the first ConvertValueToString give wrong results there), xCalendar has a grid that keeps the old month's dates when Value is set to another month, and text colour properties that have no effect, TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_ConsoleComponents cannot take a cConsole of the published TBMANLIB_ConsoleCore (it embeds a private copy of it), TBMANLIB_ConsoleCore reads keys wrongly, TBMANLIB_Diagnostic cannot create its Dictionary or make a text report and misplaces List items, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text (and its XPath engine reads the step `..` and `and` with `or` wrongly), and tbRegExp, with **Global**, reports a match of no characters twice when the search skipped characters to reach it (a defect of the vba-regex engine it is made from, confirmed by a second agent). TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph, and the five types of its XPath engine (`XPathTokenType`, `XPathAxisType`, `XPathOperator`, `XPathStep`, `XPathPredicate`), which no public member takes or returns and which the mXPath page mentions in a paragraph; tsPwGen's names its five test modules and its six internal modules, and tsJson's its test and internal modules likewise, which their landing pages describe in a paragraph each. See *Where things live* for how a package moves between the two lists. +Twenty-six **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerLib, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsJson, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. The two console packages carry a NOTE (owner, 2026-10-12) that the samples harness cannot yet capture a console, so their console claims follow the source. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. VBComDlg's `exclude_from_docs` names `CommonDialogBase`, a module whose public procedures only its class calls. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modImpExp` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Xml, tbRegExp, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib and WinReg do not compile on BETA 1005 (each documented from its source alone, its samples compile-only; WinReg's methods were also run in a copy of its source with its four failing calls passed `ByVal`, and ReadString, GetRegistryValueType and the first ConvertValueToString give wrong results there), xCalendar has a grid that keeps the old month's dates when Value is set to another month, and text colour properties that have no effect, BigNumberLib 1.0.2.0 compiles, but loses the minus sign after `IsNegative = True` and in a negative operand of the greatest common divisor and the least common multiple, makes a zero from `AssignFromLong` negative, and does not return from the modular inverse of zero (all four confirmed by a second agent), TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_ConsoleComponents cannot take a cConsole of the published TBMANLIB_ConsoleCore (it embeds a private copy of it), TBMANLIB_ConsoleCore reads keys wrongly, TBMANLIB_Diagnostic cannot create its Dictionary or make a text report and misplaces List items, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text (and its XPath engine reads the step `..` and `and` with `or` wrongly), and tbRegExp, with **Global**, reports a match of no characters twice when the search skipped characters to reach it (a defect of the vba-regex engine it is made from, confirmed by a second agent). TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph, and the five types of its XPath engine (`XPathTokenType`, `XPathAxisType`, `XPathOperator`, `XPathStep`, `XPathPredicate`), which no public member takes or returns and which the mXPath page mentions in a paragraph; tsPwGen's names its five test modules and its six internal modules, and tsJson's its test and internal modules likewise, which their landing pages describe in a paragraph each. See *Where things live* for how a package moves between the two lists. The rest of this file is the maintenance guide for updating existing pages or adding new ones: package surface notes, page conventions, and the integrity check. diff --git a/docs/Reference/Contributed/BigNumberLib/BigNumber.md b/docs/Reference/Contributed/BigNumberLib/BigNumber.md index d8b946e8..16a99e31 100644 --- a/docs/Reference/Contributed/BigNumberLib/BigNumber.md +++ b/docs/Reference/Contributed/BigNumberLib/BigNumber.md @@ -10,7 +10,7 @@ has_toc: false A **BigNumber** represents an arbitrary-precision signed integer backed by the TomsFastMath (libtfm) C library, supporting numbers up to 4096 bits. A multiplication accepts operands of up to 2048 bits each. The default property is [**ValueString**](#valuestring). -```tb check_build +```tb check_build check_run Dim a As New BigNumber Dim b As New BigNumber Dim result As New BigNumber @@ -18,11 +18,14 @@ Dim result As New BigNumber a.ValueString = "123456789012345678901234567890" b.ValueString = "987654321098765432109876543210" result.AssignAddition a, b -Debug.Print result.ValueString +Debug.Print result.ValueString ' 1111111110111111111011111111100 ``` > [!NOTE] -> This class requires twinBASIC BETA 434 or newer. Overflow errors are not trapped. String-to-value conversions silently stop at the first invalid character --- for example, setting `ValueHex = "ABC123he"` stores `&HABC123` without raising an error. +> This class requires twinBASIC BETA 434 or newer. String-to-value conversions silently stop at the first invalid character --- for example, setting `ValueHex = "ABC123he"` stores `&HABC123` without raising an error. + +> [!WARNING] +> Overflow is not trapped. A result that needs more bits than the library holds loses its high bits, and a string with more digits than fit gives a wrong value. No error is raised in either case. The library holds a little more than the 4096 bits the class supports, 4352 bits in a 32-bit program and 4608 in a 64-bit one, so a result a few bits over 4096 may still be right. * TOC {:toc} @@ -37,7 +40,7 @@ Returns or sets the value as a decimal string representation. **Default property Syntax: *object*.**ValueString** [ = *value* ] *value* -: A **String** containing a decimal integer, optionally preceded by a minus sign. Conversion stops at the first character that is not a valid decimal digit. +: A **String** containing a decimal integer, optionally preceded by a minus sign. Conversion stops at the first character that is not a valid decimal digit, so a leading plus sign or space gives 0. A string with no digits gives 0. The getter returns the decimal digits, preceded by a minus sign for a negative value. @@ -51,7 +54,19 @@ Syntax: *object*.**ValueHex** [ = *value* ] *value* : A **String** containing a hexadecimal integer (digits `0`--`9` and `A`--`F`, case-insensitive), optionally preceded by a minus sign. Conversion stops at the first character that is not a valid hex digit. -The getter returns upper-case digits without leading zeros, preceded by a minus sign for a negative value. +The getter returns upper-case digits without leading zeros, preceded by a minus sign for a negative value. A prefix such as `0x` or `&H` is not accepted: conversion stops at the `x` or the `&`. + +```tb check_build check_run +Dim n As New BigNumber +n.ValueHex = "ABC123he" +Debug.Print n.ValueHex ' ABC123 +Debug.Print n.ValueString ' 11256099 + +n.ValueString = "-255" +Debug.Print n.ValueHex ' -FF +Debug.Print n.ValueOctal ' -377 +Debug.Print n.ValueBinary ' -11111111 +``` ### ValueOctal {: .no_toc } @@ -85,7 +100,20 @@ Returns or sets whether the value is negative. Syntax: *object*.**IsNegative** [ = *value* ] *value* -: A **Boolean**. Setting **True** marks the number as negative; setting **False** marks it as positive. +: A **Boolean**. Setting **False** makes the number non-negative. Setting **True** is meant to make it negative, but see the warning. + +The getter returns **True** for a negative number, and **False** for a positive number and for zero, except the negative zero that [**AssignFromLong**](#assignfromlong) describes. + +> [!WARNING] +> Setting **IsNegative** to **True** does not make the number negative. The property stores -1, the value of **True**, where the library expects 1, so the number is in a state that the library does not handle: [**ValueString**](#valuestring) and the other conversions show no minus sign, **IsNegative** still returns **True**, and arithmetic with the number gives wrong results. To make a number negative, set **ValueString** to a string that starts with a minus sign, or use [**AssignNegation**](#assignnegation). [**AssignGreatestCommonDivisor**](#assigngreatestcommondivisor) and [**AssignLeastCommonMultiple**](#assignleastcommonmultiple) set this state in a negative operand. + +```tb check_build check_run +Dim n As New BigNumber +n.ValueString = "5" +n.IsNegative = True +Debug.Print n.IsNegative ' True +Debug.Print n.ValueString ' 5 +``` ## Methods @@ -141,6 +169,24 @@ Syntax: *object*.**AssignDivision** *a*, *b* *b* : *required* A **BigNumber** holding the divisor. Must not be zero. +The error has the description "invalid operation", and this instance keeps its value when it is raised. + +```tb check_build check_run +Dim a As New BigNumber +Dim b As New BigNumber +Dim q As New BigNumber +a.ValueString = "-7" +b.ValueString = "2" +q.AssignDivision a, b +Debug.Print q.ValueString ' -3 + +b.ValueString = "0" +On Error Resume Next +q.AssignDivision a, b +Debug.Print Err.Number & " " & Err.Description ' 5 invalid operation +Debug.Print q.ValueString ' -3 +``` + ### AssignModulus {: .no_toc } @@ -154,6 +200,24 @@ Syntax: *object*.**AssignModulus** *a*, *b* *b* : *required* A **BigNumber** holding the divisor. Must not be zero. +> [!WARNING] +> When *b* is negative and the result is zero, the result is a negative zero: [**IsNegative**](#isnegative) returns **True** and [**IsEqualTo**](#isequalto) does not find it equal to a zero read from a string. [**AssignFromLong**](#assignfromlong) describes it. + +```tb check_build check_run +Dim a As New BigNumber +Dim b As New BigNumber +Dim r As New BigNumber +a.ValueString = "-7" +b.ValueString = "2" +r.AssignModulus a, b +Debug.Print r.ValueString ' 1 + +a.ValueString = "7" +b.ValueString = "-2" +r.AssignModulus a, b +Debug.Print r.ValueString ' -1 +``` + ### AssignSquared {: .no_toc } @@ -164,10 +228,13 @@ Syntax: *object*.**AssignSquared** *a* *a* : *required* A **BigNumber** holding the value to square. +> [!WARNING] +> The square of zero is a negative zero, as [**AssignFromLong**](#assignfromlong) describes. + ### AssignAdditionModulus {: .no_toc } -Sets this instance to `(a + b) Mod mod_c`. +Sets this instance to `(a + b) Mod mod_c`. Raises run-time error 5 if *mod_c* is zero. Syntax: *object*.**AssignAdditionModulus** *a*, *b*, *mod_c* @@ -178,12 +245,30 @@ Syntax: *object*.**AssignAdditionModulus** *a*, *b*, *mod_c* : *required* A **BigNumber** holding the second addend. *mod_c* -: *required* A **BigNumber** holding the modulus. +: *required* A **BigNumber** holding the modulus. It should be positive. + +This section describes all four methods that end in **Modulus**. Each takes the remainder as [**AssignModulus**](#assignmodulus) does: for a positive modulus the result is in the range 0 up to one less than the modulus, whatever the signs of the operands. With a negative modulus the result has the sign of the modulus, except that a result of zero is the modulus itself, so `(3 + 4) Mod -7` is -7. + +```tb check_build check_run +Dim a As New BigNumber +Dim b As New BigNumber +Dim m As New BigNumber +Dim r As New BigNumber +a.ValueString = "-3" +b.ValueString = "4" +m.ValueString = "7" +r.AssignAdditionModulus a, b, m +Debug.Print r.ValueString ' 1 +r.AssignSubtractionModulus a, b, m +Debug.Print r.ValueString ' 0 +r.AssignMultiplicationModulus a, b, m +Debug.Print r.ValueString ' 2 +``` ### AssignSubtractionModulus {: .no_toc } -Sets this instance to `(a - b) Mod mod_c`. +Sets this instance to `(a - b) Mod mod_c`. Raises run-time error 5 if *mod_c* is zero. [**AssignAdditionModulus**](#assignadditionmodulus) describes the sign of the result. Syntax: *object*.**AssignSubtractionModulus** *a*, *b*, *mod_c* @@ -194,12 +279,12 @@ Syntax: *object*.**AssignSubtractionModulus** *a*, *b*, *mod_c* : *required* A **BigNumber** holding the subtrahend. *mod_c* -: *required* A **BigNumber** holding the modulus. +: *required* A **BigNumber** holding the modulus. It should be positive. ### AssignMultiplicationModulus {: .no_toc } -Sets this instance to `(a * b) Mod mod_c`. +Sets this instance to `(a * b) Mod mod_c`. Raises run-time error 5 if *mod_c* is zero. [**AssignAdditionModulus**](#assignadditionmodulus) describes the sign of the result. Syntax: *object*.**AssignMultiplicationModulus** *a*, *b*, *mod_c* @@ -210,12 +295,12 @@ Syntax: *object*.**AssignMultiplicationModulus** *a*, *b*, *mod_c* : *required* A **BigNumber** holding the second factor. *mod_c* -: *required* A **BigNumber** holding the modulus. +: *required* A **BigNumber** holding the modulus. It should be positive. ### AssignSquaredModulus {: .no_toc } -Sets this instance to `(a * a) Mod mod_b`. +Sets this instance to `(a * a) Mod mod_b`. Raises run-time error 5 if *mod_b* is zero. [**AssignAdditionModulus**](#assignadditionmodulus) describes the sign of the result. Syntax: *object*.**AssignSquaredModulus** *a*, *mod_b* @@ -223,12 +308,12 @@ Syntax: *object*.**AssignSquaredModulus** *a*, *mod_b* : *required* A **BigNumber** holding the value to square. *mod_b* -: *required* A **BigNumber** holding the modulus. +: *required* A **BigNumber** holding the modulus. It should be positive. ### AssignModularMultiplicativeInverse {: .no_toc } -Sets this instance to the modular multiplicative inverse of *a* modulo *mod_b* --- the value *x* such that `(a * x) Mod mod_b = 1`. Raises run-time error 5 if the inverse does not exist. +Sets this instance to the modular multiplicative inverse of *a* modulo *mod_b* --- the value *x* such that `(a * x) Mod mod_b = 1`. Raises run-time error 5 if the inverse does not exist, which is the case when *a* and *mod_b* have a common factor and when *a* is a multiple of *mod_b*. For a negative *a* the result is negative: the inverse of -3 modulo 7 is -5, because `(-3 * -5) Mod 7` is 1. Syntax: *object*.**AssignModularMultiplicativeInverse** *a*, *mod_b* @@ -236,12 +321,31 @@ Syntax: *object*.**AssignModularMultiplicativeInverse** *a*, *mod_b* : *required* A **BigNumber** holding the value to invert. *mod_b* -: *required* A **BigNumber** holding the modulus. +: *required* A **BigNumber** holding the modulus. It must be positive. + +> [!WARNING] +> The call does not return, and the program stops responding, when *mod_b* is odd and either *a* is zero or *mod_b* is negative. A zero *a* with an even *mod_b*, and any *a* with a negative even *mod_b*, raise error 5. + +```tb check_build check_run +Dim a As New BigNumber +Dim m As New BigNumber +Dim x As New BigNumber +a.ValueString = "3" +m.ValueString = "11" +x.AssignModularMultiplicativeInverse a, m +Debug.Print x.ValueString ' 4 + +a.ValueString = "6" +m.ValueString = "9" +On Error Resume Next +x.AssignModularMultiplicativeInverse a, m +Debug.Print Err.Number & " " & Err.Description ' 5 invalid operation +``` ### AssignGreatestCommonDivisor {: .no_toc } -Sets this instance to the greatest common divisor of *a* and *b*. The signs of *a* and *b* are ignored; the result is always non-negative. +Sets this instance to the greatest common divisor of *a* and *b*. The signs of *a* and *b* are ignored; the result is always non-negative. The greatest common divisor of zero and *b* is the absolute value of *b*, and that of two zeros is zero. Syntax: *object*.**AssignGreatestCommonDivisor** *a*, *b* @@ -251,10 +355,24 @@ Syntax: *object*.**AssignGreatestCommonDivisor** *a*, *b* *b* : *required* A **BigNumber** holding the second value. +> [!WARNING] +> A negative *a* or *b* is changed by the call. The method clears the sign of each operand and puts it back with [**IsNegative**](#isnegative), which leaves the operand in the state that the warning there describes: it shows no minus sign, and arithmetic with it gives wrong results. + +```tb check_build check_run +Dim a As New BigNumber +Dim b As New BigNumber +Dim g As New BigNumber +a.ValueString = "-12" +b.ValueString = "18" +g.AssignGreatestCommonDivisor a, b +Debug.Print g.ValueString ' 6 +Debug.Print a.ValueString ' 12 +``` + ### AssignLeastCommonMultiple {: .no_toc } -Sets this instance to the least common multiple of *a* and *b*. The signs of *a* and *b* are ignored. +Sets this instance to the least common multiple of *a* and *b*. The signs of *a* and *b* are ignored; the result is non-negative. The least common multiple of zero and any value is zero. Syntax: *object*.**AssignLeastCommonMultiple** *a*, *b* @@ -264,6 +382,9 @@ Syntax: *object*.**AssignLeastCommonMultiple** *a*, *b* *b* : *required* A **BigNumber** holding the second value. +> [!WARNING] +> A negative *a* or *b* is changed by the call, as [**AssignGreatestCommonDivisor**](#assigngreatestcommondivisor) describes. + ### AssignShiftRight {: .no_toc } @@ -275,7 +396,18 @@ Syntax: *object*.**AssignShiftRight** *a*, *shiftAmount_b* : *required* A **BigNumber** holding the value to shift. *shiftAmount_b* -: *required* A **Long** specifying the number of bit positions to shift right. +: *required* A **Long** specifying the number of bit positions to shift right. It must not be negative: a negative amount leaves the value unchanged. + +```tb check_build check_run +Dim a As New BigNumber +Dim r As New BigNumber +a.ValueString = "-7" +r.AssignShiftRight a, 1 +Debug.Print r.ValueString ' -3 +a.ValueString = "-1" +r.AssignShiftRight a, 1 +Debug.Print r.ValueString ' 0 +``` ### AssignShiftLeft {: .no_toc } @@ -288,7 +420,15 @@ Syntax: *object*.**AssignShiftLeft** *a*, *shiftAmount_b* : *required* A **BigNumber** holding the value to shift. *shiftAmount_b* -: *required* A **Long** specifying the number of bit positions to shift left. +: *required* A **Long** specifying the number of bit positions to shift left. It must not be negative: a negative amount gives a wrong result, not a shift to the right. + +```tb check_build check_run +Dim a As New BigNumber +Dim r As New BigNumber +a.ValueString = "5" +r.AssignShiftLeft a, 100 +Debug.Print r.ValueString ' 6338253001141147007483516026880 +``` ### AssignNegation {: .no_toc } @@ -324,6 +464,20 @@ Syntax: *object*.**AssignFromLong** *a* *a* : *required* A **Long** holding the value to assign. Negative values (including `&H80000000`) are handled correctly. +> [!WARNING] +> Assigning 0 gives a negative zero: [**IsNegative**](#isnegative) returns **True**, [**IsLessThan**](#islessthan) finds the number less than a zero read from the string `"0"`, and [**IsEqualTo**](#isequalto) does not find it equal. Its text is still `0`, and adding it to another number or subtracting it gives the right result. The same negative zero comes from [**AssignSquared**](#assignsquared) with a zero argument, and from [**AssignModulus**](#assignmodulus) when the divisor is negative and the result is zero. To get a true zero, set [**ValueString**](#valuestring) to `"0"`, or set **IsNegative** to **False** after the assignment. + +```tb check_build check_run +Dim z As New BigNumber +Dim s As New BigNumber +z.AssignFromLong 0 +s.ValueString = "0" +Debug.Print z.ValueString ' 0 +Debug.Print z.IsNegative ' True +Debug.Print z.IsEqualTo(s) ' False +Debug.Print z.IsLessThan(s) ' True +``` + ### HandleError {: .no_toc } @@ -389,23 +543,21 @@ Syntax: *object*.**IsGreaterThanOrEqualTo**( *b* ) This example computes the factorial of 50 using **BigNumber** arithmetic. -```tb check_build -Public Function Factorial50() As String - Dim result As New BigNumber - Dim factor As New BigNumber - Dim temp As New BigNumber +```tb check_build check_run +Dim result As New BigNumber +Dim factor As New BigNumber +Dim temp As New BigNumber - result.AssignFromLong 1 +result.AssignFromLong 1 - Dim i As Long - For i = 2 To 50 - factor.AssignFromLong i - temp.AssignMultiplication result, factor - result.ValueString = temp.ValueString ' copy temp into result - Next i +Dim i As Long +For i = 2 To 50 + factor.AssignFromLong i + temp.AssignMultiplication result, factor + result.ValueString = temp.ValueString ' copy temp into result +Next i - Factorial50 = result.ValueString -End Function +Debug.Print result.ValueString ' 30414093201713378043612608166064768844377641568960512000000000000 ``` ## See Also diff --git a/docs/Reference/Contributed/BigNumberLib/index.md b/docs/Reference/Contributed/BigNumberLib/index.md index 0897d9c5..a897b725 100644 --- a/docs/Reference/Contributed/BigNumberLib/index.md +++ b/docs/Reference/Contributed/BigNumberLib/index.md @@ -2,20 +2,58 @@ title: BigNumberLib Package parent: Experimental Packages has_toc: false -indexed_from: 0.0.0.0 +indexed_from: 1.0.2.0 +exclude_from_docs: + - Absolute + - AddModulus + - Addition + - BigNumberTests + - Comparisons + - Divide + - GreatestCommonDivisor + - LeastCommonMultiple + - ModularMultiplicativeInverse + - Modulus + - Multiply + - MultiplyModulus + - Negate + - ShiftLeft + - ShiftRight + - Square + - SquareModulus + - Subtract + - SubtractModulus + - ValueBinary + - ValueHex + - ValueOctal + - ValueString + - fp_int permalink: /tB/Packages/Contributed/BigNumberLib/ --- # BigNumberLib Package {: .no_toc } -**Author:** WaynePhillipsEA. **Version:** 0.0.0.0 +**Author:** WaynePhillipsEA. **Version:** 1.0.2.0. **On GitHub:** [libtom/tomsfastmath](https://github.com/libtom/tomsfastmath), the C library that the package wraps. -The **BigNumberLib** contributed package provides arbitrary-precision signed integer arithmetic for twinBASIC. +The **BigNumberLib** contributed package provides arbitrary-precision signed integer arithmetic for twinBASIC. Its one class, [**BigNumber**](BigNumber), holds an integer of up to 4096 bits. The arithmetic is done by version 0.13.1 of the TomsFastMath (libtfm) C library, which the package links as a static library, one build for 32-bit programs and one for 64-bit programs. -> [!NOTE] -> Version 0.0.0.0 does not compile in twinBASIC BETA 1005, so a project that references it does not build. **BigNumber** passes `vbNullPtr` to the `ByRef` *remainder* structure parameter of `fp_div` and `fp_div_2d`, where a null pointer for a structure has to be passed as `ByVal vbNullPtr` (error TB5001). +A number is set and read as a string in decimal, hexadecimal, octal or binary, or is set from a **Long**. The class adds, subtracts, multiplies, divides and squares; takes a remainder, also of a sum, a difference, a product or a square; finds a modular inverse, a greatest common divisor and a least common multiple; shifts; and compares. + +> [!WARNING] +> Some members give wrong results, or do not return. Each is described under its member on the [**BigNumber**](BigNumber) page: +> +> - Setting [**IsNegative**](BigNumber#isnegative) to **True** does not make a number negative. [**AssignGreatestCommonDivisor**](BigNumber#assigngreatestcommondivisor) and [**AssignLeastCommonMultiple**](BigNumber#assignleastcommonmultiple) leave a negative operand in the same state, with its minus sign gone. +> - [**AssignFromLong**](BigNumber#assignfromlong) with 0 gives a zero that is flagged negative, which [**IsEqualTo**](BigNumber#isequalto) does not find equal to a zero read from a string. [**AssignSquared**](BigNumber#assignsquared) of zero and [**AssignModulus**](BigNumber#assignmodulus) with a negative divisor can give the same zero. +> - [**AssignModularMultiplicativeInverse**](BigNumber#assignmodularmultiplicativeinverse) does not return when the modulus is odd and the value is zero or the modulus is negative. +> - Overflow is not trapped: a result of more than the library holds, a little over 4096 bits, loses its high bits, with no error. ## Classes - [BigNumber](BigNumber) -- arbitrary-precision signed integer backed by the TomsFastMath (libtfm) C library, supporting numbers up to 4096 bits. + +## Test code + +The package also contains **BigNumberTests**, a module with one **Sub**, **RunAllTests**, which runs the **RunTests** Sub of 22 test modules, one for each operation and each string conversion (**Addition**, **Divide**, **Modulus**, **ShiftLeft**, **ValueHex**, **Comparisons** and so on). Together they make 777 checks with **Debug.Assert**, which a built program skips. Run in the IDE, version 1.0.2.0 passes all of them. These modules have no pages of their own here. + +**BigNumber** also declares a structure type, **fp_int**, which holds the number for the library, and a program does not use it. diff --git a/docs/Reference/Experimental.md b/docs/Reference/Experimental.md index 910808b1..db42c987 100644 --- a/docs/Reference/Experimental.md +++ b/docs/Reference/Experimental.md @@ -13,7 +13,7 @@ Community-authored packages from TWINSERV that are not yet ready for use in a pr These packages are published through TWINSERV, as the [Contributed Packages](../Contributed/) are, and are documented in the same way. A package is listed here when its newest version does not compile in the current twinBASIC, or when it has defects that a program would meet. Each section says which version it documents and what does not work yet. A package moves to the Contributed Packages once a version of it works, and its pages keep their addresses. - [**ArrayListLib**](../Contributed/ArrayListLib/) -- a list class in place of .NET's `ArrayList`. Version 1.4.0.134 does not compile in twinBASIC BETA 1005. -- [**BigNumberLib**](../Contributed/BigNumberLib/) -- arbitrary-precision signed integer arithmetic. Version 0.0.0.0 does not compile in twinBASIC BETA 1005. +- [**BigNumberLib**](../Contributed/BigNumberLib/) -- arbitrary-precision signed integer arithmetic. Version 1.0.2.0 compiles, and has several members that give wrong results or do not return: setting **IsNegative** to **True**, a negative operand of the greatest common divisor and the least common multiple, a zero from **AssignFromLong**, and the modular inverse of zero. - [**TBMANLIB**](../Contributed/TBMANLIB/) -- a toolkit of helpers: JSON, logs, INI files, strings, bytes, files and dictionaries. Version 1.0.0.5 compiles for 32-bit programs, crashes the compiler in a 64-bit build, and has several members that fail or give wrong results. - [**TBMANLIB_Aes**](../Contributed/TBMANLIB_Aes/) -- AES encryption of text and byte arrays. Version 1.0.0.4 compiles, and its cipher modes are not the ones their names say: the default mode, CBC, runs ECB, and the output does not match the standard test vectors. - [**TBMANLIB_ConsoleComponents**](../Contributed/TBMANLIB_ConsoleComponents/) -- text-interface components for console programs: progress bars, charts, tables and panels. Version 0.0.0.0 compiles, and its components cannot use a console object of the published TBMANLIB_ConsoleCore. diff --git a/indexer/snapshots/contributed/BigNumberLib/api.json b/indexer/snapshots/contributed/BigNumberLib/api.json index b314dd1b..3312c63d 100644 --- a/indexer/snapshots/contributed/BigNumberLib/api.json +++ b/indexer/snapshots/contributed/BigNumberLib/api.json @@ -2,7 +2,7 @@ "format": 3, "package": "BigNumberLib", "project": "BigNumberLib", - "version": "0.0.0.0", + "version": "1.0.2.0", "types": [ { "name": "Absolute", diff --git a/test/example-projects/contributed-BigNumberLib/Settings b/test/example-projects/contributed-BigNumberLib/Settings index 8287aadf..d31bf383 100644 --- a/test/example-projects/contributed-BigNumberLib/Settings +++ b/test/example-projects/contributed-BigNumberLib/Settings @@ -51,14 +51,14 @@ "autoCtlExt": true, "id": "{C8320E09-7273-4873-9646-DD1155EC9500}", "licence": "LibTom (Pubic Domain)", - "name": "[IMPORTED] BigNumberLib (Experimental)", + "name": "[IMPORTED] BigNumberLib (Experimental) TomsFastMath Static Library", "path32": "/Packages/BigNumberLib", "path64": "/Packages/BigNumberLib", "symbolId": "BigNumberLib", "versionBuild": "0", - "versionMajor": "0", + "versionMajor": "1", "versionMinor": "0", - "versionRevision": "0" + "versionRevision": "2" } ], "project.settingsVersion": 1, From 382ecae19d70fc923d9a2111d08f97b46cf07959 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 13 Oct 2026 19:18:28 +0200 Subject: [PATCH 5/9] TBMANLIB_Diagnostic: moved to 1.0.0.1, whose text report works (an INI-style layout); the other defects remain, still Experimental --- WIP.md | 2 +- .../TBMANLIB_Diagnostic/Collectors.md | 6 +- .../TBMANLIB_Diagnostic/Dictionary.md | 4 +- .../TBMANLIB_Diagnostic/FileHelper.md | 10 +-- .../TBMANLIB_Diagnostic/FormatHelper.md | 2 +- .../TBMANLIB_Diagnostic/FormatterFactory.md | 4 +- .../TBMANLIB_Diagnostic/Formatters.md | 85 ++++++++++++++----- .../Contributed/TBMANLIB_Diagnostic/List.md | 2 +- .../Contributed/TBMANLIB_Diagnostic/index.md | 21 +++-- docs/Reference/Experimental.md | 2 +- .../contributed/TBMANLIB_Diagnostic/api.json | 10 +-- .../contributed-TBMANLIB_Diagnostic/Settings | 4 +- 12 files changed, 96 insertions(+), 56 deletions(-) diff --git a/WIP.md b/WIP.md index 453bf43e..c119a607 100644 --- a/WIP.md +++ b/WIP.md @@ -34,7 +34,7 @@ sibling. History goes nowhere: git keeps it. Reference documentation is **complete** for all thirteen packages: VBA, VBRUN, VB, WebView2, Assert, CustomControls, CEF, WinEventLogLib, WinNamedPipesLib, WinServicesLib, tbIDE, WinNativeCommonCtls and AppGlobalClassObject. It is adapted from primary sources: Microsoft VBA-Docs (CC-BY-4.0) for the runtime library, `.twin` source for the twinBASIC-specific packages. The CEF and WebView2 packages also carry a tutorial set. -Twenty-six **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerLib, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsJson, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. The two console packages carry a NOTE (owner, 2026-10-12) that the samples harness cannot yet capture a console, so their console claims follow the source. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. VBComDlg's `exclude_from_docs` names `CommonDialogBase`, a module whose public procedures only its class calls. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modImpExp` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Xml, tbRegExp, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib and WinReg do not compile on BETA 1005 (each documented from its source alone, its samples compile-only; WinReg's methods were also run in a copy of its source with its four failing calls passed `ByVal`, and ReadString, GetRegistryValueType and the first ConvertValueToString give wrong results there), xCalendar has a grid that keeps the old month's dates when Value is set to another month, and text colour properties that have no effect, BigNumberLib 1.0.2.0 compiles, but loses the minus sign after `IsNegative = True` and in a negative operand of the greatest common divisor and the least common multiple, makes a zero from `AssignFromLong` negative, and does not return from the modular inverse of zero (all four confirmed by a second agent), TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_ConsoleComponents cannot take a cConsole of the published TBMANLIB_ConsoleCore (it embeds a private copy of it), TBMANLIB_ConsoleCore reads keys wrongly, TBMANLIB_Diagnostic cannot create its Dictionary or make a text report and misplaces List items, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text (and its XPath engine reads the step `..` and `and` with `or` wrongly), and tbRegExp, with **Global**, reports a match of no characters twice when the search skipped characters to reach it (a defect of the vba-regex engine it is made from, confirmed by a second agent). TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph, and the five types of its XPath engine (`XPathTokenType`, `XPathAxisType`, `XPathOperator`, `XPathStep`, `XPathPredicate`), which no public member takes or returns and which the mXPath page mentions in a paragraph; tsPwGen's names its five test modules and its six internal modules, and tsJson's its test and internal modules likewise, which their landing pages describe in a paragraph each. See *Where things live* for how a package moves between the two lists. +Twenty-six **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerLib, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsJson, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. The two console packages carry a NOTE (owner, 2026-10-12) that the samples harness cannot yet capture a console, so their console claims follow the source. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. VBComDlg's `exclude_from_docs` names `CommonDialogBase`, a module whose public procedures only its class calls. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modImpExp` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Xml, tbRegExp, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib and WinReg do not compile on BETA 1005 (each documented from its source alone, its samples compile-only; WinReg's methods were also run in a copy of its source with its four failing calls passed `ByVal`, and ReadString, GetRegistryValueType and the first ConvertValueToString give wrong results there), xCalendar has a grid that keeps the old month's dates when Value is set to another month, and text colour properties that have no effect, BigNumberLib 1.0.2.0 compiles, but loses the minus sign after `IsNegative = True` and in a negative operand of the greatest common divisor and the least common multiple, makes a zero from `AssignFromLong` negative, and does not return from the modular inverse of zero (all four confirmed by a second agent), TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_ConsoleComponents cannot take a cConsole of the published TBMANLIB_ConsoleCore (it embeds a private copy of it), TBMANLIB_ConsoleCore reads keys wrongly, TBMANLIB_Diagnostic cannot create its Dictionary and misplaces List items, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text (and its XPath engine reads the step `..` and `and` with `or` wrongly), and tbRegExp, with **Global**, reports a match of no characters twice when the search skipped characters to reach it (a defect of the vba-regex engine it is made from, confirmed by a second agent). TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph, and the five types of its XPath engine (`XPathTokenType`, `XPathAxisType`, `XPathOperator`, `XPathStep`, `XPathPredicate`), which no public member takes or returns and which the mXPath page mentions in a paragraph; tsPwGen's names its five test modules and its six internal modules, and tsJson's its test and internal modules likewise, which their landing pages describe in a paragraph each. See *Where things live* for how a package moves between the two lists. The rest of this file is the maintenance guide for updating existing pages or adding new ones: package surface notes, page conventions, and the integrity check. diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/Collectors.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/Collectors.md index 02f2c1a7..8ce74bff 100644 --- a/docs/Reference/Contributed/TBMANLIB_Diagnostic/Collectors.md +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/Collectors.md @@ -180,7 +180,7 @@ Category `CPU Info`; mode **Static**; no summary. **Description**: `CPU architec | `Processor Count` | The number of logical processors that `GetNativeSystemInfo` reports. | | `Processor Level` | The `wProcessorLevel` number, a decimal number whose meaning depends on the architecture. | | `Processor Revision` | `0x` and the `wProcessorRevision` number in hexadecimal. | -| `Page Size` | The size of a memory page, written by [**FormatBytes**](FormatHelper#formatbytes) with no decimals. The text is `4. KB`, with a decimal separator after the number: see [Known problems](.#known-problems-in-version-0000). | +| `Page Size` | The size of a memory page, written by [**FormatBytes**](FormatHelper#formatbytes) with no decimals. The text is `4. KB`, with a decimal separator after the number: see [Known problems](.#known-problems-in-version-1001). | | `Processor Name` | The text of the registry value `ProcessorNameString`, without leading and trailing spaces. Left out when it is empty or cannot be read. | | `Frequency` | The registry value `~MHz` and ` MHz`, such as `3600 MHz`. Left out when it cannot be read. | @@ -208,7 +208,7 @@ Category `Locale`; mode **Static**; no summary. **Description**: `Regional setti | `Timezone` | `UTC`, the sign, the hours and, when it is not a whole hour, a colon and two digits of minutes: `UTC+0`, `UTC+1`, `UTC-5`, `UTC+5:30`. | | `Timezone Name` | The name of the time zone in standard time. Left out when empty. | -`Timezone` is built from the **Bias** field of the Windows time-zone record, which is the offset of standard time. The **DaylightBias** field is not read, and neither is the value that tells whether daylight saving time is in effect, so the offset does not change in summer, and `Timezone Name` is the standard-time name all year. On a computer in Central European Summer Time, where the clock is at UTC+2, the items are `Timezone` = `UTC+1` and `Timezone Name` = `W. Europe Standard Time`. See [Known problems](.#known-problems-in-version-0000). +`Timezone` is built from the **Bias** field of the Windows time-zone record, which is the offset of standard time. The **DaylightBias** field is not read, and neither is the value that tells whether daylight saving time is in effect, so the offset does not change in summer, and `Timezone Name` is the standard-time name all year. On a computer in Central European Summer Time, where the clock is at UTC+2, the items are `Timezone` = `UTC+1` and `Timezone Name` = `W. Europe Standard Time`. See [Known problems](.#known-problems-in-version-1001). **Windows functions.** All of `kernel32.dll`: `GetUserDefaultLCID`, `GetLocaleInfoW` with the four type constants for the ISO language and country codes and the English language and country names, `GetACP`, `GetOEMCP` and `GetTimeZoneInformation`. @@ -443,7 +443,7 @@ The keys of the next adapter are `Adapter 1` and `IPAddress1`, and so on. The ke **Windows functions.** `WSAStartup`, `gethostname` and `WSACleanup` of `ws2_32.dll` for the host name; `GetAdaptersInfo` of `iphlpapi.dll`, which is called twice, once for the size of the buffer and once for the data; `RtlMoveMemory` of `kernel32.dll` to copy the records. The host name and the descriptions are converted from the ANSI code page. Only IPv4 addresses are reported, because `GetAdaptersInfo` returns no others. -The collector reads the adapters one after the other in steps of 640 bytes, a fixed number that its source calls approximate. It tests the pointer that links the records of the list only for 0 and does not follow it. The size of an `IP_ADAPTER_INFO` record depends on the size of a pointer. In a 32-bit program the records are 640 bytes apart, and the collector lists every adapter. In a 64-bit program `LenB` of the package's `IP_ADAPTER_INFO` type is 696 and the records are 704 bytes apart, so the second step does not reach the second record: on a computer with several adapters the full result lists `Adapter 0` only, and the summary may have no `Primary IP`. See [Known problems](.#known-problems-in-version-0000). +The collector reads the adapters one after the other in steps of 640 bytes, a fixed number that its source calls approximate. It tests the pointer that links the records of the list only for 0 and does not follow it. The size of an `IP_ADAPTER_INFO` record depends on the size of a pointer. In a 32-bit program the records are 640 bytes apart, and the collector lists every adapter. In a 64-bit program `LenB` of the package's `IP_ADAPTER_INFO` type is 696 and the records are 704 bytes apart, so the second step does not reach the second record: on a computer with several adapters the full result lists `Adapter 0` only, and the summary may have no `Primary IP`. See [Known problems](.#known-problems-in-version-1001). **When the information is missing.** The host name is left out when Winsock cannot start or `gethostname` fails. When there are no adapters, or `GetAdaptersInfo` fails, the full result has no adapter items and the summary has no `Primary IP`. The collector has no error result. diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/Dictionary.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/Dictionary.md index 629ed893..0690b531 100644 --- a/docs/Reference/Contributed/TBMANLIB_Diagnostic/Dictionary.md +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/Dictionary.md @@ -10,7 +10,7 @@ has_toc: false A collection of values that are found by a key. It is a generic class, **Dictionary(Of TKey, TValue)**, and is not the **Dictionary** of the Scripting Runtime. It keeps the keys in one [**List**](List) and the values in another, in the order in which they were added, and finds a key by looking at the keys one after the other, so it is meant for a few dozen items and not a large number. Nothing else in the package uses it. -**In version 0.0.0.0 a dictionary cannot be created.** The constructor assigns the two lists to its fields without **Set**, and `New Dictionary(Of TKey, TValue)` raises run-time error 91, *Object variable or With block variable not set*. The variable keeps the value **Nothing**, so every member fails as well. See [Known problems](.#known-problems-in-version-0000). +**In version 1.0.0.1 a dictionary cannot be created.** The constructor assigns the two lists to its fields without **Set**, and `New Dictionary(Of TKey, TValue)` raises run-time error 91, *Object variable or With block variable not set*. The variable keeps the value **Nothing**, so every member fails as well. See [Known problems](.#known-problems-in-version-1001). ```tb check_build check_run Dim ages As Dictionary(Of String, Long) @@ -113,7 +113,7 @@ Creates an empty dictionary. Syntax: **New** **Dictionary(Of TKey, TValue)** -In version 0.0.0.0 it raises run-time error 91: see the start of this page. +In version 1.0.0.1 it raises run-time error 91: see the start of this page. ### Remove {: .no_toc } diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/FileHelper.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/FileHelper.md index a3a240cb..7a13c51c 100644 --- a/docs/Reference/Contributed/TBMANLIB_Diagnostic/FileHelper.md +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/FileHelper.md @@ -10,7 +10,7 @@ has_toc: false Functions that write, append to and read a text file, test whether a file or folder exists, and take paths apart. A program uses them to save a report that a [formatter](Formatters) made. They call the file statements of the runtime library (**Open**, **Put**, **Get**, **Kill**, **MkDir**, **GetAttr**), and need no other library. -The functions are called with the name of the module, as in `FileHelper.WriteFile(report, path)`. Three of them, **WriteFile**, **AppendFile** and **ReadFile**, return a **Variant** and never raise an error. **WriteFile** and **AppendFile** return **True**, and when something fails, a **String** that describes the failure; a program tells the two apart with **VarType**. **ReadFile** returns a **String** in both cases, the content of the file or the description of the failure, and the description starts with the text `Error `. A file that does not exist is not a failure for **ReadFile** and **GetFileSize**: they create it. See [Known problems](.#known-problems-in-version-0000). +The functions are called with the name of the module, as in `FileHelper.WriteFile(report, path)`. Three of them, **WriteFile**, **AppendFile** and **ReadFile**, return a **Variant** and never raise an error. **WriteFile** and **AppendFile** return **True**, and when something fails, a **String** that describes the failure; a program tells the two apart with **VarType**. **ReadFile** returns a **String** in both cases, the content of the file or the description of the failure, and the description starts with the text `Error `. A file that does not exist is not a failure for **ReadFile** and **GetFileSize**: they create it. See [Known problems](.#known-problems-in-version-1001). ```tb check_build check_run Dim folder As String = Environ$("TEMP") & "\diag-filehelper" @@ -212,7 +212,7 @@ Syntax: **FileHelper.GetFileSize** ( *filePath* ) **As LongLong** *filePath* : *required* A **String**, the path of the file, absolute or relative to **App.Path**. -Opens the file in binary mode and returns the result of **LOF**. Returns -1 when an error occurs, such as a folder that does not exist. The function opens the file with `For Binary`, and that mode creates a file that does not exist, so a missing file in a folder that exists is not an error: it is created with no content and the function returns 0. See [Known problems](.#known-problems-in-version-0000). +Opens the file in binary mode and returns the result of **LOF**. Returns -1 when an error occurs, such as a folder that does not exist. The function opens the file with `For Binary`, and that mode creates a file that does not exist, so a missing file in a folder that exists is not an error: it is created with no content and the function returns 0. See [Known problems](.#known-problems-in-version-1001). ```tb check_build check_run Dim path As String = Environ$("TEMP") & "\diag-missing.bin" @@ -265,7 +265,7 @@ Syntax: **FileHelper.NormalizePath** ( *path* ) **As String** Changes every slash to a backslash and every run of backslashes to one. It then goes through the parts between the backslashes: it removes the spaces at both ends of a part, drops an empty part and a part `.`, and lets a part `..` remove the part before it. A `..` with nothing before it is dropped. The parts are joined with a backslash, and the result has no backslash at the start or at the end. -Because an empty first part is dropped, a path from the root, such as `\a\b`, loses its first backslash, and so does a UNC path: `\\server\share\f.txt` becomes `server\share\f.txt`, which is a relative path. [**GetFullPath**](#getfullpath) treats such a path as absolute and does not add **App.Path**, so a file in a UNC path is opened as the relative path `server\share\f.txt`. See [Known problems](.#known-problems-in-version-0000). +Because an empty first part is dropped, a path from the root, such as `\a\b`, loses its first backslash, and so does a UNC path: `\\server\share\f.txt` becomes `server\share\f.txt`, which is a relative path. [**GetFullPath**](#getfullpath) treats such a path as absolute and does not add **App.Path**, so a file in a UNC path is opened as the relative path `server\share\f.txt`. See [Known problems](.#known-problems-in-version-1001). ```tb check_build check_run Debug.Print FileHelper.NormalizePath("C:/a//b/./c/../d.txt") @@ -299,7 +299,7 @@ Syntax: **FileHelper.ReadFile** ( *filePath* ) **As Variant** *filePath* : *required* A **String**, the path of the file, absolute or relative to **App.Path**. -Returns the content of the file as a **String**, and an empty string for an empty file. When an error occurs, such as a folder that does not exist, it returns an error text in the form that [**AppendFile**](#appendfile) describes. A file that does not exist is not an error: it is created with no content, as for [**GetFileSize**](#getfilesize), and the function returns an empty string, so a missing file and an empty file look the same. See [Known problems](.#known-problems-in-version-0000). +Returns the content of the file as a **String**, and an empty string for an empty file. When an error occurs, such as a folder that does not exist, it returns an error text in the form that [**AppendFile**](#appendfile) describes. A file that does not exist is not an error: it is created with no content, as for [**GetFileSize**](#getfilesize), and the function returns an empty string, so a missing file and an empty file look the same. See [Known problems](.#known-problems-in-version-1001). The function reads the bytes of the file into a string as characters, and does not decode UTF-8 or remove a byte-order mark. A file that [**WriteFile**](#writefile) wrote therefore starts with the three characters of its byte-order mark, and a character above 127 is several characters. @@ -340,7 +340,7 @@ Syntax: **FileHelper.WriteFile** ( *content*, *filePath* ) **As Variant** Creates the file, and the folder that holds it when only that last folder is missing (see [**EnsureDirectoryExists**](#ensuredirectoryexists)). It writes the byte-order mark and then the text in UTF-8, from the start of the file. Returns **True**, or an error text as [**AppendFile**](#appendfile) describes. -**WriteFile** opens the file in binary mode and does not shorten it. When the file exists and is longer than the new content, the bytes after the new content stay in the file. See [Known problems](.#known-problems-in-version-0000). +**WriteFile** opens the file in binary mode and does not shorten it. When the file exists and is longer than the new content, the bytes after the new content stay in the file. See [Known problems](.#known-problems-in-version-1001). ```tb check_build check_run Dim path As String = Environ$("TEMP") & "\diag-writefile.txt" diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/FormatHelper.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/FormatHelper.md index 929f35c9..3ba266d4 100644 --- a/docs/Reference/Contributed/TBMANLIB_Diagnostic/FormatHelper.md +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/FormatHelper.md @@ -99,7 +99,7 @@ Syntax: **FormatHelper.FormatBytes** ( *bytes* [ , *decimals* ] ) **As String** The units are 1024 times larger one after the other, and are written `KB`, `MB`, `GB` and `TB`. The function picks the largest unit that is at most the size, divides, and writes the number with *decimals* digits, a space and the unit: `1.5 KB`. A size below 1024 is written in whole bytes as `512 B`, whatever *decimals* is. A size of 1024 TB or more is shown in TB. The number is made with the **Format** function and a pattern of zeros, so the digits are rounded. A negative size is written in bytes. -With *decimals* set to 0 the pattern is `0.`, and the decimal separator stays in the text: `4. KB` and not `4 KB`. The **Page Size** item of [**CPUInfoCollector**](Collectors#cpuinfocollector) is written this way. See [Known problems](.#known-problems-in-version-0000). +With *decimals* set to 0 the pattern is `0.`, and the decimal separator stays in the text: `4. KB` and not `4 KB`. The **Page Size** item of [**CPUInfoCollector**](Collectors#cpuinfocollector) is written this way. See [Known problems](.#known-problems-in-version-1001). ```tb check_build check_run Debug.Print FormatHelper.FormatBytes(4096, 0) diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/FormatterFactory.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/FormatterFactory.md index 65100108..779eebc1 100644 --- a/docs/Reference/Contributed/TBMANLIB_Diagnostic/FormatterFactory.md +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/FormatterFactory.md @@ -24,7 +24,7 @@ Next ' xml -> Text .txt ``` -The text formatter, which `text`, `txt` and every name that is not known give, raises run-time error 5 when it begins a report in version 0.0.0.0: see [TextFormatter](Formatters#textformatter). The names `json` and `html` work. +The text formatter is the one that `text`, `txt` and every name that is not known give, such as `xml` in the sample. The function raises no error for an unknown name, and the report is then a text report: see [TextFormatter](Formatters#textformatter). * TOC {:toc} @@ -41,7 +41,7 @@ Syntax: **FormatterFactory.CreateFormatter** ( *formatName* ) **As** [**IOutputF *formatName* : *required* A **String**. The letter case does not matter. `text` and `txt` give a [**TextFormatter**](Formatters#textformatter), `json` a [**JsonFormatter**](Formatters#jsonformatter), and `html` and `htm` an [**HtmlFormatter**](Formatters#htmlformatter). -Returns a new formatter each time. Any other name, including an empty string, gives a **TextFormatter** without an error. In version 0.0.0.0 that formatter fails later, when a report begins (see [TextFormatter](Formatters#textformatter)), so a name that is not `json` or `html` makes no report. +Returns a new formatter each time. Any other name, including an empty string, gives a **TextFormatter** without an error. ### CreateFormatterFromArg {: .no_toc } diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/Formatters.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/Formatters.md index 0eb5a684..2aab64f4 100644 --- a/docs/Reference/Contributed/TBMANLIB_Diagnostic/Formatters.md +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/Formatters.md @@ -8,11 +8,11 @@ has_toc: false # Formatters {: .no_toc } -The package has three formatters. Each one turns a list of diagnostic results into one string, in a format for reading (text), for programs (JSON) or for a browser (HTML). The text formatter fails in version 0.0.0.0, so a report is made with the JSON or the HTML formatter. They implement [**IOutputFormatter**](IOutputFormatter), so [**FormatterFactory.FormatResults**](FormatterFactory#formatresults) runs them in the same way, and [**FormatterFactory.CreateFormatter**](FormatterFactory#createformatter) creates one from a name. +The package has three formatters. Each one turns a list of diagnostic results into one string, in a format for reading (text), for programs (JSON) or for a browser (HTML). They implement [**IOutputFormatter**](IOutputFormatter), so [**FormatterFactory.FormatResults**](FormatterFactory#formatresults) runs them in the same way, and [**FormatterFactory.CreateFormatter**](FormatterFactory#createformatter) creates one from a name. | Class | Name | Extension | Output | |:------|:-----|:----------|:-------| -| [**TextFormatter**](#textformatter) | `Text` | `.txt` | Meant to be a report in lines of 50 characters, with a box round the title and a frame round each group. In version 0.0.0.0 it raises error 5 and makes no report. | +| [**TextFormatter**](#textformatter) | `Text` | `.txt` | A report in the layout of an INI file: a title line, a line `[group]` for each group and a line `key = value` for each item. It has no time and no escaping. | | [**JsonFormatter**](#jsonformatter) | `JSON` | `.json` | One JSON object, with an object for each group, and every value a string. | | [**HtmlFormatter**](#htmlformatter) | `HTML` | `.html` | A complete HTML document with its style sheet, and a table for each group. | @@ -22,6 +22,7 @@ The formatters have these properties in common: - The line breaks are **vbCrLf**. - One object can make any number of reports, because **BeginFormat** discards what the last report left in it. - Each report names the tool as `Twinbasic Diagnostic Tool v1.0`, or, in JSON, as `"toolVersion": "1.0"`. The text is fixed in the code, and is not the version of the package. +- The JSON and HTML reports give the time at which **BeginFormat** was called. The text report does not. - The classes declare the members of the interface as **Public**, so a variable of the class type reaches them by the long names, such as `IOutputFormatter_FormatItem`. ```tb check_build check_run @@ -57,33 +58,79 @@ Next i ## TextFormatter {: .no_toc } -Meant to write a report as text, in lines that are 50 characters wide, with a double-line box round the title and a frame round each group, drawn with the box-drawing characters of Unicode. **FormatterName** is `Text` and **FileExtension** is `.txt`. +Writes a report as plain text, in the layout of an INI file. **FormatterName** is `Text` and **FileExtension** is `.txt`. -**In version 0.0.0.0 the formatter cannot make a report.** [**BeginFormat**](IOutputFormatter#beginformat) raises run-time error 5, *Invalid procedure call or argument*. The class builds the box with the **Chr$** function and the code points 9556 (`╔`), 9552 (`═`) and others above 255, and [**Chr$**](../../../Modules/Strings/Chr) accepts only 0 to 255. **ChrW$** accepts the code points. The error is the same in a project that sets `runtime.useUnicodeStandardLibrary` to **False**, in a 32-bit program and in a 64-bit program. See [Known problems](.#known-problems-in-version-0000). +```text +# Twinbasic Diagnostic Tool v1.0 + +[CPU Info] +Processor Count = 8 +Path = C:\Tools "x" + +[Memory] +Memory Load = 54% +``` + +- The first line is the title, `# Twinbasic Diagnostic Tool v1.0`, and an empty line follows it. +- Each group is a line with its name in square brackets, and then a line for each item: the key, a space, an equals sign, a space and the value. +- A group that has items is followed by an empty line. A group with no items has no empty line after it, and its header line is followed at once by the next one. +- The report has no time and no closing line. +- [**FormatResults**](FormatterFactory#formatresults) passes a failed result as one item with the key `Error`, so its group has the line `Error = ` and the message. +- Nothing is escaped. A key or a value that holds ` = ` or a line break is written as it is, and then a reader cannot always split the text back into keys and values. ```tb check_build check_run Dim f As IOutputFormatter Set f = New TextFormatter -On Error Resume Next f.BeginFormat -Debug.Print Err.Number & ": " & Err.Description -Err.Clear - -Dim s As String -s = Chr$(9556) -Debug.Print Err.Number -Err.Clear -s = ChrW$(9556) -Debug.Print Err.Number & " " & AscW(s) +f.FormatCategory "CPU Info" +f.FormatItem "Processor Count", "8" +f.FormatItem "Path", "C:\Tools ""x""" +f.EndCategory +f.FormatCategory "Empty" +f.EndCategory +f.FormatCategory "Memory" +f.FormatItem "Memory Load", "54%" +f.EndCategory + +Dim lines() As String +Dim i As Long +lines = Split(f.EndFormat(), vbCrLf) +For i = 0 To UBound(lines) - 1 + Debug.Print "|" & lines(i) & "|" +Next i ' Output: -' 5: Invalid procedure call or argument -' 5 -' 0 9556 +' |# Twinbasic Diagnostic Tool v1.0| +' || +' |[CPU Info]| +' |Processor Count = 8| +' |Path = C:\Tools "x"| +' || +' |[Empty]| +' |[Memory]| +' |Memory Load = 54%| +' || ``` -Because [**FormatterFactory.CreateFormatter**](FormatterFactory#createformatter) gives a **TextFormatter** for `text`, for `txt` and for every name it does not know, a report made with one of those names fails in the same way. Use `json` or `html`. +[**FormatterFactory.CreateFormatter**](FormatterFactory#createformatter) gives a **TextFormatter** for `text`, for `txt` and for every name it does not know. A list of results is formatted with [**FormatResults**](FormatterFactory#formatresults): + +```tb check_build check_run +Dim results As New List(Of DiagnosticResult(Of KeyValuePair(Of String, String))) +Dim r As DiagnosticResult(Of KeyValuePair(Of String, String)) + +Set r = New DiagnosticResult(Of KeyValuePair(Of String, String))("Uptime") +r.AddItem New KeyValuePair(Of String, String)("Total Days", "3") +results.Add r + +Set r = New DiagnosticResult(Of KeyValuePair(Of String, String))("Memory") +r.SetError "Failed to get memory status" +results.Add r -The source builds the report from a title in a box, one block for each group (a header line, a line for each item as `│`, two spaces, the key, a colon, a space and the value, and a closing line), and a last line `Generated: ` and the time as `yyyy-mm-dd hh:nn:ss`. A program that wants a text report can write its own formatter: see [**IOutputFormatter**](IOutputFormatter). +Dim text As String +text = FormatterFactory.FormatResults(results, FormatterFactory.CreateFormatter("text")) +Debug.Print Replace(text, vbCrLf, "|") +' Output: +' # Twinbasic Diagnostic Tool v1.0||[Uptime]|Total Days = 3||[Memory]|Error = Failed to get memory status|| +``` ## JsonFormatter {: .no_toc } diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/List.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/List.md index 87b10196..a178cb76 100644 --- a/docs/Reference/Contributed/TBMANLIB_Diagnostic/List.md +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/List.md @@ -28,7 +28,7 @@ Debug.Print names.Contains("Bob") **Item** is a function, so an item is read as `names.Item(0)`. The class has no default member and no enumerator: there is no `names(0)`, and the items are visited with an index loop from 0 to **Count** - 1. -**Insert**, and **SetItem** with the index 0, put the item in the wrong place in version 0.0.0.0, and **SetItem** can lose an item. Both pass the position as the fourth argument of **Collection.Add**, which is *after*, where the code needs *before*. [**Insert**](#insert) puts the new item one place too late, and [**SetItem**](#setitem) with the index 0 puts it in the second place. See [Known problems](.#known-problems-in-version-0000). +**Insert**, and **SetItem** with the index 0, put the item in the wrong place in version 1.0.0.1, and **SetItem** can lose an item. Both pass the position as the fourth argument of **Collection.Add**, which is *after*, where the code needs *before*. [**Insert**](#insert) puts the new item one place too late, and [**SetItem**](#setitem) with the index 0 puts it in the second place. See [Known problems](.#known-problems-in-version-1001). ```tb check_build check_run Dim text As String diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/index.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/index.md index f8a3dc4d..738bb7f9 100644 --- a/docs/Reference/Contributed/TBMANLIB_Diagnostic/index.md +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/index.md @@ -7,22 +7,22 @@ exclude_from_docs: - WinAPIStructures - DelegatesModule - RefreshModeModule -indexed_from: 0.0.0.0 +indexed_from: 1.0.0.1 permalink: /tB/Packages/Contributed/TBMANLIB_Diagnostic/ --- # TBMANLIB_Diagnostic Package {: .no_toc } -**Author:** woeoio. **Version:** 0.0.0.0 +**Author:** woeoio. **Version:** 1.0.0.1 The **TBMANLIB_Diagnostic** contributed package makes a diagnostic report of a Windows computer and of the processes that run on it, and writes the report as text, JSON or HTML. The package has three parts. A [**DiagnosticEngine**](DiagnosticEngine) runs *collectors*, classes that each find one kind of information (the operating system, the processor and its load, the memory, the disks, the network, the running processes, the regional settings, the environment variables, the installed runtimes and the uptime), and each returns a [**DiagnosticResult**](DiagnosticResult), a list of pairs of text. A *formatter* turns the results into one string. [**FileHelper**](FileHelper) can save the string as a UTF-8 file. The package is written in twinBASIC and calls the Windows API directly. It uses no other library, and none of its collectors asks for administrator rights. The package is the reusable part of woeoio's entry for [twinBASIC Monthly Challenge #1](../../../../Challenges/1), *Self-Contained Diagnostic Tool*. The entry is a console program that shows the values on a live screen, or writes a report when it is started with an argument such as `/json`. That program is not in the package; [**FormatterFactory.CreateFormatterFromArg**](FormatterFactory#createformatterfromarg) reads such an argument. -This section documents version 0.0.0.0, published on TWINSERV on 1 February 2026 with no licence. The package compiles in BETA 1005, as a 32-bit and as a 64-bit program. The repository [woeoio/tbman](https://github.com/woeoio/tbman), the toolkit of the same author whose package is [**TBMANLIB**](../TBMANLIB/), does not hold the code of this package. +This section documents version 1.0.0.1, published on TWINSERV on 3 February 2026 under the MIT licence. The package compiles in BETA 1005, as a 32-bit and as a 64-bit program. The repository [woeoio/tbman](https://github.com/woeoio/tbman), the toolkit of the same author whose package is [**TBMANLIB**](../TBMANLIB/), does not hold the code of this package. -The package has defects: the text formatter and the **Dictionary** class cannot be used, and several members give a wrong result without an error. [Known problems in version 0.0.0.0](#known-problems-in-version-0000) lists them. The sample below uses the parts that work. +The package has defects: the **Dictionary** class cannot be used, and several members give a wrong result without an error. [Known problems in version 1.0.0.1](#known-problems-in-version-1001) lists them. The sample below uses the parts that work. ```tb check_build check_run Dim engine As New DiagnosticEngine @@ -57,7 +57,7 @@ FileHelper.DeleteFile path 1. **Register collectors.** A new [**DiagnosticEngine**](DiagnosticEngine) has none. A program registers an object of each collector class that it wants with [**RegisterCollector**](DiagnosticEngine#registercollector). The order of registration is the order of the report. The [eleven collectors](Collectors) of the package are classes with names such as **OSInfoCollector** and **MemoryInfoCollector**, and a program can write its own, see [**IDiagnosticCollector**](IDiagnosticCollector). 2. **Run them.** [**RunAll**](DiagnosticEngine#runall) runs every collector and returns a list of results. [**RunCategory**](DiagnosticEngine#runcategory) runs one by its name, and [**RunByRefreshMode**](DiagnosticEngine#runbyrefreshmode) runs those that change at one rate. The engine raises events and calls callbacks while it works, so a program can show the progress or cancel. -3. **Format.** [**FormatterFactory.CreateFormatter**](FormatterFactory#createformatter) makes a [formatter](Formatters) from the name `text`, `json` or `html`, and [**FormatResults**](FormatterFactory#formatresults) returns the report as a string. A formatter only builds the string. In version 0.0.0.0 the text formatter fails, so a report is made from `json` or `html`. +3. **Format.** [**FormatterFactory.CreateFormatter**](FormatterFactory#createformatter) makes a [formatter](Formatters) from the name `text`, `json` or `html`, and [**FormatResults**](FormatterFactory#formatresults) returns the report as a string. A formatter only builds the string. 4. **Save.** [**FileHelper.WriteFile**](FileHelper#writefile) writes the string, and the formatter's **FileExtension** property gives the extension for the file name. A result can also be read directly, without a formatter. Each [item](KeyValuePair) has a key and a value, both text: the key is the label of the line, such as `Total Physical`, and the value is its text, such as `15.9 GB`. @@ -102,7 +102,7 @@ The package declares a generic class named **Dictionary**, and the Scripting Run | `Dim c As New Scripting.Dictionary` | the class of the Scripting Runtime | the class of the Scripting Runtime | | `Dim d As New TBMANLIB_Diagnostic.Dictionary(Of String, Long)` | the class of the package | the class of the package | -A project that wants the Scripting Runtime's class writes `Scripting.Dictionary`, which does not depend on the order. The package's class cannot be created in version 0.0.0.0 (see [**Dictionary**](Dictionary)), so a project has no reason to write its name. A project with only the package, which has no `Scripting` library, shows the first row of the table: +A project that wants the Scripting Runtime's class writes `Scripting.Dictionary`, which does not depend on the order. The package's class cannot be created in version 1.0.0.1 (see [**Dictionary**](Dictionary)), so a project has no reason to write its name. A project with only the package, which has no `Scripting` library, shows the first row of the table: ```tb check_build expect-error=TB5136 Dim a As New Dictionary @@ -124,10 +124,10 @@ The package has four modules whose contents a program has no need for: - **DelegatesModule** declares the four delegate types. Three are the types of the callbacks of the engine, see [Delegates](DiagnosticEngine#delegates), and the fourth is [**FormatterFactoryDelegate**](FormatterFactory#formatterfactorydelegate). - **RefreshModeModule** holds only the enumeration [**RefreshMode**](RefreshMode). -## Known problems in version 0.0.0.0 +## Known problems in version 1.0.0.1 > [!WARNING] -> Version 0.0.0.0 of the package gives a wrong result without an error, or loses data, in these cases. +> Version 1.0.0.1 of the package gives a wrong result without an error, or loses data, in these cases. > > - **[List.Insert](List#insert) puts the new item one place too late**, and **[List.SetItem](List#setitem) with the index 0 puts it in the second place.** `SetItem` with the index 0 on a list of one item removes the item and then raises error 5, so the list is empty afterwards. > - **[FileHelper.WriteFile](FileHelper#writefile) does not shorten an existing file.** The bytes after the new content stay: writing `ab` over a file of 13 bytes leaves a file of 13 bytes. @@ -140,10 +140,9 @@ The package has four modules whose contents a program has no need for: > - **[NetworkInfoCollector](Collectors#networkinfocollector) lists the first adapter only in a 64-bit program.** It reads the adapters in steps of 640 bytes, and the records are 704 bytes apart in a 64-bit program. The summary may then have no `Primary IP`. A 32-bit program lists every adapter. > [!NOTE] -> Version 0.0.0.0 has these other problems. Each raises an error. +> Version 1.0.0.1 has these other problems. Each raises an error. > > - **A [Dictionary](Dictionary) cannot be created.** `New Dictionary(Of TKey, TValue)` raises run-time error 91, *Object variable or With block variable not set*. -> - **The [TextFormatter](Formatters#textformatter) cannot make a report.** Its **BeginFormat** raises error 5, *Invalid procedure call or argument*, because it builds the box with **Chr$** and code points above 255. [**FormatterFactory.CreateFormatter**](FormatterFactory#createformatter) gives this formatter for `text`, for `txt` and for every name it does not know. The names `json` and `html` work. > - **[List.Contains](List#contains), [IndexOf](List#indexof) and [Remove](List#remove) raise error 438**, *Object doesn't support this property or method*, for a list of objects of a class that has no default member. > - **[List.Insert](List#insert) with the index 0 raises error 5 on an empty list.** > - **[WriteFile](FileHelper#writefile) and [AppendFile](FileHelper#appendfile) make only the last folder of a path.** For a path whose parent folder is missing they return the error text `Error -2147467259: Unspecified error` and the path. The **Open** statement raises error 76, *Path not found*, for it in VB6. @@ -175,7 +174,7 @@ Two behaviours follow from the code and can look like defects. [**CPULoadCollect - [FormatHelper](FormatHelper) -- formats sizes, durations, percentages, dates and text, and escapes text for JSON and HTML - [FileHelper](FileHelper) -- writes, appends to and reads text files, tests for files and folders, and takes paths apart - [List](List) -- a list with a 0-based index -- [Dictionary](Dictionary) -- values found by a key; it cannot be created in version 0.0.0.0 +- [Dictionary](Dictionary) -- values found by a key; it cannot be created in version 1.0.0.1 - [StringBuilder](StringBuilder) -- builds a long string from parts ## See Also diff --git a/docs/Reference/Experimental.md b/docs/Reference/Experimental.md index db42c987..a6742df8 100644 --- a/docs/Reference/Experimental.md +++ b/docs/Reference/Experimental.md @@ -18,7 +18,7 @@ These packages are published through TWINSERV, as the [Contributed Packages](../ - [**TBMANLIB_Aes**](../Contributed/TBMANLIB_Aes/) -- AES encryption of text and byte arrays. Version 1.0.0.4 compiles, and its cipher modes are not the ones their names say: the default mode, CBC, runs ECB, and the output does not match the standard test vectors. - [**TBMANLIB_ConsoleComponents**](../Contributed/TBMANLIB_ConsoleComponents/) -- text-interface components for console programs: progress bars, charts, tables and panels. Version 0.0.0.0 compiles, and its components cannot use a console object of the published TBMANLIB_ConsoleCore. - [**TBMANLIB_ConsoleCore**](../Contributed/TBMANLIB_ConsoleCore/) -- a console library with colours, UTF-8 and a full-screen mode. Version 1.0.0.1 compiles, and it reads keys wrongly and has several helpers that give wrong results. -- [**TBMANLIB_Diagnostic**](../Contributed/TBMANLIB_Diagnostic/) -- a diagnostic report of the computer and its processes, as text, JSON or HTML. Version 0.0.0.0 compiles, and its text report, its **Dictionary** class and several of its list and file helpers fail or give wrong results. +- [**TBMANLIB_Diagnostic**](../Contributed/TBMANLIB_Diagnostic/) -- a diagnostic report of the computer and its processes, as text, JSON or HTML. Version 1.0.0.1 compiles. Its **Dictionary** class cannot be created, and several of its list and file helpers and two of its collectors fail or give wrong results. - [**TBMANLIB_Xml**](../Contributed/TBMANLIB_Xml/) -- XML documents parsed, edited and written, in twinBASIC code. Version 1.0.0.1 compiles, and its parser misreads some common XML, such as self-closing elements and entities in text, and does not return for some malformed text. Its XPath engine reads some valid expressions as others, such as the step `..` and `and` with `or`. - [**tbRegExp**](../Contributed/tbRegExp/) -- regular expressions, in place of VBScript's **RegExp** object. Version 1.4.0.0 compiles, and with **Global** set to **True** it reports a match of no characters twice when the search skipped characters to reach it, so **Execute**, **Replace** and **Split** give wrong results for patterns such as `$`, `\b` and a lookahead. - [**WinReg**](../Contributed/WinReg/) -- registry keys and values, read, written, deleted and enumerated. Version 1.0.1.1 does not compile in twinBASIC BETA 1005, and three of its methods give wrong results: **ReadString** returns the text followed by NUL characters, **GetRegistryValueType** returns **REG_NONE** for a value that holds data, and one overload of **ConvertValueToString** finds no value. diff --git a/indexer/snapshots/contributed/TBMANLIB_Diagnostic/api.json b/indexer/snapshots/contributed/TBMANLIB_Diagnostic/api.json index d64a7a84..199eb03e 100644 --- a/indexer/snapshots/contributed/TBMANLIB_Diagnostic/api.json +++ b/indexer/snapshots/contributed/TBMANLIB_Diagnostic/api.json @@ -2,7 +2,7 @@ "format": 3, "package": "TBMANLIB_Diagnostic", "project": "TBMANLIB_Diagnostic", - "version": "0.0.0.0", + "version": "1.0.0.1", "types": [ { "name": "CPUInfoCollector", @@ -561,13 +561,6 @@ "implements": ["IOutputFormatter"], "attrs": [{"name":"ClassId","args":"\"20A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C6\""},{"name":"InterfaceId","args":"\"20A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C7\""}], "members": [ - {"name":"BOX_WIDTH","kind":"const","vis":"private","signature":"Private Const BOX_WIDTH As Long = 50"}, - {"name":"GetBottomBorder","kind":"function","vis":"private","signature":"Private Function GetBottomBorder() As String","returnType":"String"}, - {"name":"GetCategoryFooter","kind":"function","vis":"private","signature":"Private Function GetCategoryFooter() As String","returnType":"String"}, - {"name":"GetCategoryHeader","kind":"function","vis":"private","signature":"Private Function GetCategoryHeader(ByVal categoryName As String) As String","returnType":"String"}, - {"name":"GetCenteredLine","kind":"function","vis":"private","signature":"Private Function GetCenteredLine(ByVal text As String) As String","returnType":"String"}, - {"name":"GetItemLine","kind":"function","vis":"private","signature":"Private Function GetItemLine(ByVal key As String, ByVal value As String) As String","returnType":"String"}, - {"name":"GetTopBorder","kind":"function","vis":"private","signature":"Private Function GetTopBorder() As String","returnType":"String"}, {"name":"IOutputFormatter_BeginFormat","kind":"sub","vis":"public","signature":"Public Sub IOutputFormatter_BeginFormat()"}, {"name":"IOutputFormatter_EndCategory","kind":"sub","vis":"public","signature":"Public Sub IOutputFormatter_EndCategory()"}, {"name":"IOutputFormatter_EndFormat","kind":"function","vis":"public","signature":"Public Function IOutputFormatter_EndFormat() As String","returnType":"String"}, @@ -577,6 +570,7 @@ {"name":"IOutputFormatter_FormatterName","kind":"property","vis":"public","signature":"Public Property Get IOutputFormatter_FormatterName() As String","returnType":"String","accessor":"Get"}, {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New()"}, {"name":"m_Builder","kind":"field","vis":"private","signature":"Private m_Builder As StringBuilder"}, + {"name":"m_HasItems","kind":"field","vis":"private","signature":"Private m_HasItems As Boolean"}, {"name":"m_InCategory","kind":"field","vis":"private","signature":"Private m_InCategory As Boolean"} ] }, diff --git a/test/example-projects/contributed-TBMANLIB_Diagnostic/Settings b/test/example-projects/contributed-TBMANLIB_Diagnostic/Settings index 584dded2..46324b4a 100644 --- a/test/example-projects/contributed-TBMANLIB_Diagnostic/Settings +++ b/test/example-projects/contributed-TBMANLIB_Diagnostic/Settings @@ -55,8 +55,8 @@ "path32": "/Packages/TBMANLIB_Diagnostic", "path64": "/Packages/TBMANLIB_Diagnostic", "symbolId": "TBMANLIB_Diagnostic", - "versionBuild": "0", - "versionMajor": "0", + "versionBuild": "1", + "versionMajor": "1", "versionMinor": "0", "versionRevision": "0" } From 7e2e1d3b065bb3c5d383808ac53908990bd42c17 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 13 Oct 2026 19:37:48 +0200 Subject: [PATCH 6/9] TBMANLIB_ConsoleComponents: moved to 1.0.0.1, which references the published ConsoleCore but still embeds a copy whose types differ from it; the working arrangement (the sources in the program's project) documented; still Experimental --- WIP.md | 2 +- .../TBMANLIB_ConsoleComponents/Helpers.md | 6 +++-- .../cConsoleLayout.md | 2 +- .../cConsolePanel.md | 2 +- .../TBMANLIB_ConsoleComponents/cDataGrid.md | 2 +- .../TBMANLIB_ConsoleComponents/index.md | 24 +++++++++++-------- docs/Reference/Experimental.md | 2 +- .../TBMANLIB_ConsoleComponents/api.json | 2 +- .../Settings | 8 +++---- 9 files changed, 28 insertions(+), 22 deletions(-) diff --git a/WIP.md b/WIP.md index c119a607..acb0ae57 100644 --- a/WIP.md +++ b/WIP.md @@ -34,7 +34,7 @@ sibling. History goes nowhere: git keeps it. Reference documentation is **complete** for all thirteen packages: VBA, VBRUN, VB, WebView2, Assert, CustomControls, CEF, WinEventLogLib, WinNamedPipesLib, WinServicesLib, tbIDE, WinNativeCommonCtls and AppGlobalClassObject. It is adapted from primary sources: Microsoft VBA-Docs (CC-BY-4.0) for the runtime library, `.twin` source for the twinBASIC-specific packages. The CEF and WebView2 packages also carry a tutorial set. -Twenty-six **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerLib, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsJson, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. The two console packages carry a NOTE (owner, 2026-10-12) that the samples harness cannot yet capture a console, so their console claims follow the source. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. VBComDlg's `exclude_from_docs` names `CommonDialogBase`, a module whose public procedures only its class calls. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modImpExp` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Xml, tbRegExp, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib and WinReg do not compile on BETA 1005 (each documented from its source alone, its samples compile-only; WinReg's methods were also run in a copy of its source with its four failing calls passed `ByVal`, and ReadString, GetRegistryValueType and the first ConvertValueToString give wrong results there), xCalendar has a grid that keeps the old month's dates when Value is set to another month, and text colour properties that have no effect, BigNumberLib 1.0.2.0 compiles, but loses the minus sign after `IsNegative = True` and in a negative operand of the greatest common divisor and the least common multiple, makes a zero from `AssignFromLong` negative, and does not return from the modular inverse of zero (all four confirmed by a second agent), TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_ConsoleComponents cannot take a cConsole of the published TBMANLIB_ConsoleCore (it embeds a private copy of it), TBMANLIB_ConsoleCore reads keys wrongly, TBMANLIB_Diagnostic cannot create its Dictionary and misplaces List items, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text (and its XPath engine reads the step `..` and `and` with `or` wrongly), and tbRegExp, with **Global**, reports a match of no characters twice when the search skipped characters to reach it (a defect of the vba-regex engine it is made from, confirmed by a second agent). TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph, and the five types of its XPath engine (`XPathTokenType`, `XPathAxisType`, `XPathOperator`, `XPathStep`, `XPathPredicate`), which no public member takes or returns and which the mXPath page mentions in a paragraph; tsPwGen's names its five test modules and its six internal modules, and tsJson's its test and internal modules likewise, which their landing pages describe in a paragraph each. See *Where things live* for how a package moves between the two lists. +Twenty-six **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerLib, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsJson, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. The two console packages carry a NOTE (owner, 2026-10-12) that the samples harness cannot yet capture a console, so their console claims follow the source. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. VBComDlg's `exclude_from_docs` names `CommonDialogBase`, a module whose public procedures only its class calls. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modImpExp` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Xml, tbRegExp, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib and WinReg do not compile on BETA 1005 (each documented from its source alone, its samples compile-only; WinReg's methods were also run in a copy of its source with its four failing calls passed `ByVal`, and ReadString, GetRegistryValueType and the first ConvertValueToString give wrong results there), xCalendar has a grid that keeps the old month's dates when Value is set to another month, and text colour properties that have no effect, BigNumberLib 1.0.2.0 compiles, but loses the minus sign after `IsNegative = True` and in a negative operand of the greatest common divisor and the least common multiple, makes a zero from `AssignFromLong` negative, and does not return from the modular inverse of zero (all four confirmed by a second agent), TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_ConsoleComponents cannot take a cConsole of the published TBMANLIB_ConsoleCore (it embeds a copy of it, the same package at 1.0.0.1, whose types the compiler treats as different; checked in a project made by the IDE's own import, in either order), TBMANLIB_ConsoleCore reads keys wrongly, TBMANLIB_Diagnostic cannot create its Dictionary and misplaces List items, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text (and its XPath engine reads the step `..` and `and` with `or` wrongly), and tbRegExp, with **Global**, reports a match of no characters twice when the search skipped characters to reach it (a defect of the vba-regex engine it is made from, confirmed by a second agent). TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph, and the five types of its XPath engine (`XPathTokenType`, `XPathAxisType`, `XPathOperator`, `XPathStep`, `XPathPredicate`), which no public member takes or returns and which the mXPath page mentions in a paragraph; tsPwGen's names its five test modules and its six internal modules, and tsJson's its test and internal modules likewise, which their landing pages describe in a paragraph each. See *Where things live* for how a package moves between the two lists. The rest of this file is the maintenance guide for updating existing pages or adding new ones: package surface notes, page conventions, and the integrity check. diff --git a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/Helpers.md b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/Helpers.md index 592df230..7d5aba8a 100644 --- a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/Helpers.md +++ b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/Helpers.md @@ -35,7 +35,7 @@ The four numbers are **ConsoleColor** values: 10 is **Green**, 14 is **Yellow** Every colour parameter and result is a [**ConsoleColor**](../TBMANLIB_ConsoleCore/Types#consolecolor), and every function is called on an object, as in `helper.DimColor(...)`. The numbers that give the choice of a colour are in each entry; they are fixed in the code. In the syntax lines of this section, *object* is a **cColorHelper**. -Known problems in version 0.0.0.0: [**BrightenColor**](#brightencolor) returns 16 for **DarkGray**, [**DimColor**](#dimcolor) returns **DarkGray** for every dark colour, [**IntensityColor**](#intensitycolor) ignores its *baseColor*, and [**GradientColor**](#gradientcolor) chooses among three colours and does not blend. +Known problems in version 1.0.0.1: [**BrightenColor**](#brightencolor) returns 16 for **DarkGray**, [**DimColor**](#dimcolor) returns **DarkGray** for every dark colour, [**IntensityColor**](#intensitycolor) ignores its *baseColor*, and [**GradientColor**](#gradientcolor) chooses among three colours and does not blend. ### BrightenColor {: .no_toc } @@ -214,6 +214,8 @@ Computes positions and [**ConsoleRect**](../TBMANLIB_ConsoleCore/Types#consolere The members that give several rectangles or numbers return them in a **Variant** that holds an array, with indexes from 0, in the order the entry names. The rectangles of one result do not overlap and have no gap between them, except where the entry says so. A division by a count of 0 raises error 11, *Division by zero*. +A program that includes the source files of the components (see [The two packages](./#the-two-packages)) reads an array of rectangles by assigning the **Variant** to an array variable of the type: with `Dim rects() As ConsoleRect`, the statement `rects = helper.Grid(2, 2)` gives `rects(0)` to `rects(3)`. An element cannot be taken out of the **Variant** itself: `r = parts(0)`, with `r` a **ConsoleRect**, is refused (TB5028, *Unable to convert type 'Variant' to type 'ConsoleRect'*), and `parts(0).Left` raises error -2147467259, *Unspecified error*. An array of **Long**, as **SpacedPositions** gives, is read element by element from the **Variant**, as the sample below does. + ```tb check_build Dim con As New cConsole Dim layout As cLayoutHelper = New cLayoutHelper(con) @@ -232,7 +234,7 @@ Debug.Print columns(0) & " " & columns(1) & " " & columns(2) The output is that of a console of 80 columns by 25 rows, which is also the size that a program without a console gets. > [!NOTE] -> The members that give a **ConsoleRect** or a **ConsolePosition** can be called, but a program that references the published package TBMANLIB_ConsoleCore cannot read the result: the compiler refuses the members of the value and its assignment to a variable of the type. See [The two packages](./#the-two-packages). The members that give a **Long** or an array of **Long** are not affected. +> The members that give a **ConsoleRect** or a **ConsolePosition** can be called, but a program that references the packages TBMANLIB_ConsoleCore and TBMANLIB_ConsoleComponents cannot read the result: the compiler refuses the members of the value and its assignment to a variable of the type. See [The two packages](./#the-two-packages). The members that give a **Long** or an array of **Long** are not affected. ### New {: .no_toc } diff --git a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsoleLayout.md b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsoleLayout.md index 7dedb878..cfa9addd 100644 --- a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsoleLayout.md +++ b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsoleLayout.md @@ -31,7 +31,7 @@ Debug.Print layout.PanelCount ' 3 ``` -The root panel covers columns 0 to 39 and rows 0 to 11, so its content area is columns 2 to 37 and rows 2 to 9. **list** takes columns 2 to 10 and **detail** columns 12 to 37, both in rows 2 to 9; the sample prints the size of the content area inside the border and padding of each. A program that references only the published package TBMANLIB_ConsoleCore cannot read the **Rect** of a panel; see [The two packages](./#the-two-packages). +The root panel covers columns 0 to 39 and rows 0 to 11, so its content area is columns 2 to 37 and rows 2 to 9. **list** takes columns 2 to 10 and **detail** columns 12 to 37, both in rows 2 to 9; the sample prints the size of the content area inside the border and padding of each. A program that references the packages TBMANLIB_ConsoleCore and TBMANLIB_ConsoleComponents cannot read the **Rect** of a panel; see [The two packages](./#the-two-packages). * TOC {:toc} diff --git a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsolePanel.md b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsolePanel.md index 0898e254..05310026 100644 --- a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsolePanel.md +++ b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsolePanel.md @@ -72,7 +72,7 @@ The number of cells between the border and the content area, on every side. **Lo ### Rect {: .no_toc } -The rectangle of the panel. A [**ConsoleRect**](../TBMANLIB_ConsoleCore/Types#consolerect), with inclusive edges. The constructor, [**Move**](#move) and [**Resize**](#resize) set it. A program that references the published package TBMANLIB_ConsoleCore cannot read its fields: see [The two packages](./#the-two-packages). [**ContentWidth**](#contentwidth) and [**ContentHeight**](#contentheight) give sizes as **Long** values. +The rectangle of the panel. A [**ConsoleRect**](../TBMANLIB_ConsoleCore/Types#consolerect), with inclusive edges. The constructor, [**Move**](#move) and [**Resize**](#resize) set it. A program that references the packages TBMANLIB_ConsoleCore and TBMANLIB_ConsoleComponents cannot read its fields: see [The two packages](./#the-two-packages). [**ContentWidth**](#contentwidth) and [**ContentHeight**](#contentheight) give sizes as **Long** values. ### Title {: .no_toc } diff --git a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cDataGrid.md b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cDataGrid.md index 78e465e3..c3f7e461 100644 --- a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cDataGrid.md +++ b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cDataGrid.md @@ -10,7 +10,7 @@ has_toc: false Draws a table of text into the screen buffer, with a header row, columns of fixed widths, aligned cells, coloured rows and one selected row. -A **cDataGrid** keeps the [**Buffer**](../TBMANLIB_ConsoleCore/cConsole#buffer) of the console it was made with. The program gives it the rows of text, the widths and alignments of the columns and the colours of the rows with the four `Set` members, and then calls [**Render**](#render), which draws the whole table at a position. The grid holds no scroll position and does not read keys: a program that lets the user choose a row calls [**MoveUp**](#moveup) and [**MoveDown**](#movedown) from its own key handler and draws the table again. +A **cDataGrid** keeps the [**Buffer**](../TBMANLIB_ConsoleCore/cConsole#buffer) of the console it was made with. The program gives it the rows of text, the widths and alignments of the columns and the colours of the rows with the four `Set` members, and then calls [**Render**](#render), which draws the whole table at a position. The grid holds no scroll position and does not read keys: a program that lets the user choose a row calls [**MoveUp**](#moveup) and [**MoveDown**](#movedown) from its own key handler and draws the table again. The key handler is that of [**cConsole**](../TBMANLIB_ConsoleCore/cConsole#onkeypress), which [reports virtual-key codes](../TBMANLIB_ConsoleCore/#defects-in-version-1001), most of which differ from the members of **ConsoleKey**: a program compares them with constants such as `vbKeyUp`. ```tb check_build Dim con As New cConsole diff --git a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/index.md b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/index.md index 3d00e4b7..5dfc3f48 100644 --- a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/index.md +++ b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/index.md @@ -2,16 +2,16 @@ title: TBMANLIB_ConsoleComponents Package parent: Experimental Packages has_toc: false -indexed_from: 0.0.0.0 +indexed_from: 1.0.0.1 permalink: /tB/Packages/Contributed/TBMANLIB_ConsoleComponents/ --- # TBMANLIB_ConsoleComponents Package {: .no_toc } -**Author:** woeoio. **Version:** 0.0.0.0 +**Author:** woeoio. **Version:** 1.0.0.1 -The **TBMANLIB_ConsoleComponents** contributed package is a set of text-interface components for console programs. It has progress bars ([**cProgressBar**](cProgressBar)), small line charts ([**cSparkline**](cSparkline)), bar charts ([**cVBar**](cVBar)), a table ([**cDataGrid**](cDataGrid)), bordered panels ([**cConsolePanel**](cConsolePanel)), a manager that places panels in a grid, a sidebar or a header and footer ([**cConsoleLayout**](cConsoleLayout)), colour themes ([**cConsoleTheme**](cConsoleTheme)), and helpers for colours and positions ([**cColorHelper**, **cLayoutHelper**](Helpers)). The components draw into the screen buffer of the full-screen mode of [**cConsole**](../TBMANLIB_ConsoleCore/cConsole), in the package [TBMANLIB_ConsoleCore](../TBMANLIB_ConsoleCore/); the program then writes the buffer to the console with [**Refresh**](../TBMANLIB_ConsoleCore/cConsole#refresh). This section documents version 0.0.0.0, published on TWINSERV on 1 February 2026 with no licence declared. The package describes itself as "TBMAN: Text UI (TUI) component library base on ConsoleCore". +The **TBMANLIB_ConsoleComponents** contributed package is a set of text-interface components for console programs. It has progress bars ([**cProgressBar**](cProgressBar)), small line charts ([**cSparkline**](cSparkline)), bar charts ([**cVBar**](cVBar)), a table ([**cDataGrid**](cDataGrid)), bordered panels ([**cConsolePanel**](cConsolePanel)), a manager that places panels in a grid, a sidebar or a header and footer ([**cConsoleLayout**](cConsoleLayout)), colour themes ([**cConsoleTheme**](cConsoleTheme)), and helpers for colours and positions ([**cColorHelper**, **cLayoutHelper**](Helpers)). The components draw into the screen buffer of the full-screen mode of [**cConsole**](../TBMANLIB_ConsoleCore/cConsole), in the package [TBMANLIB_ConsoleCore](../TBMANLIB_ConsoleCore/); the program then writes the buffer to the console with [**Refresh**](../TBMANLIB_ConsoleCore/cConsole#refresh). This section documents version 1.0.0.1, published on TWINSERV on 3 February 2026 under the MIT licence. The package describes itself as "TBMAN: Text UI (TUI) component library base on ConsoleCore". > [!NOTE] > The tools that check this documentation's samples run a sample and compare what it prints with **Debug.Print**. They cannot yet read what a program writes to a console. So the samples on these pages are compiled, and what the components draw on the console has not been checked by running them. Statements about what a component draws follow the package's source, and will be checked once the tools can capture a console. @@ -36,7 +36,7 @@ con.Restore ``` > [!WARNING] -> In version 0.0.0.0 the components that take a **cConsole** cannot be used with the published package [TBMANLIB_ConsoleCore](../TBMANLIB_ConsoleCore/). The package compiles, but the constructors of [**cProgressBar**](cProgressBar), [**cSparkline**](cSparkline), [**cVBar**](cVBar), [**cDataGrid**](cDataGrid), [**cConsoleLayout**](cConsoleLayout) and [**cLayoutHelper**](Helpers), and the members of [**cConsolePanel**](cConsolePanel) that take a **cConsoleBuffer**, raise error -2147467262, *No such interface supported*, when they are given an object of the published ConsoleCore, and a program cannot read the **ConsoleRect** and **ConsolePosition** values that the components return. [The two packages](#the-two-packages) has the details. Only [**cConsoleTheme**](cConsoleTheme), the module **mConsoleThemeFactory**, [**cColorHelper**](Helpers#ccolorhelper-class) and the geometry of **cConsolePanel** work. The outputs that the samples on these pages show are those of the code of the two packages compiled into one project, and not of a project that references the two packages. +> In version 1.0.0.1 the components that take a **cConsole** cannot be used with the published package [TBMANLIB_ConsoleCore](../TBMANLIB_ConsoleCore/). The package compiles, but the constructors of [**cProgressBar**](cProgressBar), [**cSparkline**](cSparkline), [**cVBar**](cVBar), [**cDataGrid**](cDataGrid), [**cConsoleLayout**](cConsoleLayout) and [**cLayoutHelper**](Helpers), and the members of [**cConsolePanel**](cConsolePanel) that take a **cConsoleBuffer**, raise error -2147467262, *No such interface supported*, when they are given an object of the published ConsoleCore, and a program cannot read the **ConsoleRect** and **ConsolePosition** values that the components return. The package carries a copy of ConsoleCore of its own, and the compiler (BETA 1005) treats the types of that copy as different from the types of the published package. [The two packages](#the-two-packages) has the details, and the one arrangement in which the components work. Only [**cConsoleTheme**](cConsoleTheme), the module **mConsoleThemeFactory**, [**cColorHelper**](Helpers#ccolorhelper-class) and the geometry of **cConsolePanel** work with the package as it is. The outputs that the samples on these pages show are those of the source files of the components compiled into a project that references ConsoleCore, and not of a project that references the two packages. * TOC {:toc} @@ -47,18 +47,20 @@ con.Restore Like ConsoleCore, the package is meant for a console application; see [Which project it needs](../TBMANLIB_ConsoleCore/cConsole#which-project-it-needs). -A component names types of ConsoleCore in its constructor and its parameters: **cConsole**, **cConsoleBuffer**, **ConsoleColor**, **ConsoleRect** and others. The references of a package are not visible to the project that uses it, so a program that creates a component, or names one of these types, references **TBMANLIB_ConsoleCore** as well as this package. Both are added as [Importing a package from TWINSERV](../../../../Features/Packages/Importing-TWINSERV) describes. TWINSERV lists both packages at version 0.0.0.0. +A component names types of ConsoleCore in its constructor and its parameters: **cConsole**, **cConsoleBuffer**, **ConsoleColor**, **ConsoleRect** and others. The references of a package are not visible to the project that uses it, so a program that creates a component, or names one of these types, references **TBMANLIB_ConsoleCore** as well as this package. Both are added as [Importing a package from TWINSERV](../../../../Features/Packages/Importing-TWINSERV) describes. TWINSERV lists both packages at version 1.0.0.1. -The project file of this package lists ConsoleCore as an embedded reference: a copy of version 1.0.0.0, named *New project*, with the identifier `{EAEAEAEA-EAEA-EAEA-EAEA-EAEAEAEAEA00}`. The published package is version 0.0.0.0, named TBMANLIB_ConsoleCore, with an identifier of its own. The six source files of the embedded copy are the same as those of the published package; its project data differs in the identifier, the name and the description, and it does not declare its package visibility as public. +The project file of this package lists ConsoleCore as an embedded reference: version 1.0.0.1, with the identifier `{A0CCFE06-651A-40A4-84D9-0CA4200A8224}` of the published package, and the package file holds that copy in its own `Packages` folder. The six source files of the copy are the same as those of the published package. A project that imports this package gets the copy inside it, at `/Packages/TBMANLIB_ConsoleComponents/Packages/TBMANLIB_ConsoleCore`, and a program that imports ConsoleCore as well gets a second copy, at `/Packages/TBMANLIB_ConsoleCore`. That is so in either order of import: the program's copy does not replace the one inside the package. -The components are compiled against the embedded copy, whose types are not those of the published package, although they have the same names. A project that references both therefore meets two sets of types, and this is the result: +The components are compiled against the copy inside the package. The compiler treats its types as different from those of the copy that the program references, although they have the same names and the same code. A project that references both therefore meets two sets of types, and this is the result: - **Objects.** Passing a **cConsole** or a **cConsoleBuffer** of the published package to a component compiles. At run time the call raises error -2147467262, *No such interface supported*: the constructors of **cProgressBar**, **cSparkline**, **cVBar**, **cDataGrid**, **cConsoleLayout** and **cLayoutHelper** do, and so do **Render** and **ClearContent** of **cConsolePanel**. -- **Structures.** A **ConsoleRect** or **ConsolePosition** that a component returns, such as the **Rect** field of **cConsolePanel**, or the result of **ContentRect**, **CenterRect**, **Center** or **Grid**, has the type of the embedded copy. The compiler refuses to read a member of it (TB5027, *[ConsoleRect] does not contain 'Left', or it is inaccessible in this context*) and to assign it to a **ConsoleRect** variable or pass it to **ConsoleRect_Contains** (TB5028, *Unable to convert type 'ConsoleRect' to type 'ConsoleRect'*). +- **Structures.** A **ConsoleRect** or **ConsolePosition** that a component returns, such as the **Rect** field of **cConsolePanel**, or the result of **ContentRect**, **CenterRect**, **Center** or **Grid**, has the type of the copy inside the package. The compiler refuses to read a member of it (TB5027, *[ConsoleRect] does not contain 'Left', or it is inaccessible in this context*), to assign it to a **ConsoleRect** variable (TB5028, *Unable to convert type 'ConsoleRect' to type 'ConsoleRect'*) and to pass it to **ConsoleRect_Contains** (TB5001, *Validation of call to 'ConsoleRect_Contains' failed. Argument for 'rect': cannot coerce type 'ConsoleRect' to 'ConsoleRect'*). - **Enumerations.** A **ConsoleColor** is a number, so the colour members work: **cConsoleTheme**, **mConsoleThemeFactory** and **cColorHelper** give and take colours, and **cConsolePanel** keeps its style fields. - **Nested enumerations.** The enumerations of the components, such as **PanelBorderStyle**, are written without the class: `PanelBorderStyle.Double`. The form `cConsolePanel.PanelBorderStyle.Double` is refused (TB5079, *Unrecognized symbol 'cConsolePanel'*). -A program that references only this package cannot create a **cConsole**, and a component cannot be created without one. The members that this documentation describes for the components are the members of the package's code; the sample outputs show what that code prints when it is compiled together with the code of ConsoleCore. +A program that references only this package cannot create a **cConsole**, and a component cannot be created without one. + +The components work when their source files are part of the program's own project and the project references ConsoleCore and not this package: there is then one set of types, and a **cConsole** reaches every component. The files are the `.twin` files of the `Components` folder in the sources of this package, which is published under the MIT licence. The members that this documentation describes for the components are the members of that code, and the sample outputs show what it prints in such a project. ### How a component works @@ -75,7 +77,9 @@ The components do not clear the area they draw in, except where a page says so, Positions are columns and rows of the buffer, counted from 0 at the top left, in that order. A component draws on a black background unless a parameter sets another; the defaults of each member are in its entry. The bars, borders and charts use characters such as U+2588 and U+2500, so the font of the console has to contain them. -## Defects in version 0.0.0.0 +No component reads a key or a mouse event. A program that reacts to them uses [**ReadKey**](../TBMANLIB_ConsoleCore/cConsole#readkey) and the handlers of **cConsole**, and meets the defects that [ConsoleCore lists](../TBMANLIB_ConsoleCore/#defects-in-version-1001): **ReadKey** never returns in a program that has no console input, and most key codes of **ConsoleKey** are not the virtual-key codes that **ReadKey** and the key handler return. + +## Defects in version 1.0.0.1 - [**cSparkline**](cSparkline#braille) with the Braille character set draws a chart taller than 4 dots with its character rows in reverse order, and draws nothing for a value equal to the minimum when the height is a multiple of 4. - [**cDataGrid**](cDataGrid) raises error 9 in **Render** unless all four of its arrays are set; [**SetRowColors**](cDataGrid#setrowcolors) is indexed like the data array, header included; [**ScrollTo**](cDataGrid#scrollto) only selects a row and scrolls nothing; its properties can be set and not read. diff --git a/docs/Reference/Experimental.md b/docs/Reference/Experimental.md index a6742df8..5bc70eeb 100644 --- a/docs/Reference/Experimental.md +++ b/docs/Reference/Experimental.md @@ -16,7 +16,7 @@ These packages are published through TWINSERV, as the [Contributed Packages](../ - [**BigNumberLib**](../Contributed/BigNumberLib/) -- arbitrary-precision signed integer arithmetic. Version 1.0.2.0 compiles, and has several members that give wrong results or do not return: setting **IsNegative** to **True**, a negative operand of the greatest common divisor and the least common multiple, a zero from **AssignFromLong**, and the modular inverse of zero. - [**TBMANLIB**](../Contributed/TBMANLIB/) -- a toolkit of helpers: JSON, logs, INI files, strings, bytes, files and dictionaries. Version 1.0.0.5 compiles for 32-bit programs, crashes the compiler in a 64-bit build, and has several members that fail or give wrong results. - [**TBMANLIB_Aes**](../Contributed/TBMANLIB_Aes/) -- AES encryption of text and byte arrays. Version 1.0.0.4 compiles, and its cipher modes are not the ones their names say: the default mode, CBC, runs ECB, and the output does not match the standard test vectors. -- [**TBMANLIB_ConsoleComponents**](../Contributed/TBMANLIB_ConsoleComponents/) -- text-interface components for console programs: progress bars, charts, tables and panels. Version 0.0.0.0 compiles, and its components cannot use a console object of the published TBMANLIB_ConsoleCore. +- [**TBMANLIB_ConsoleComponents**](../Contributed/TBMANLIB_ConsoleComponents/) -- text-interface components for console programs: progress bars, charts, tables and panels. Version 1.0.0.1 compiles, and its components cannot use a console object of the published TBMANLIB_ConsoleCore, since the package carries a copy of that package whose types the compiler treats as different. - [**TBMANLIB_ConsoleCore**](../Contributed/TBMANLIB_ConsoleCore/) -- a console library with colours, UTF-8 and a full-screen mode. Version 1.0.0.1 compiles, and it reads keys wrongly and has several helpers that give wrong results. - [**TBMANLIB_Diagnostic**](../Contributed/TBMANLIB_Diagnostic/) -- a diagnostic report of the computer and its processes, as text, JSON or HTML. Version 1.0.0.1 compiles. Its **Dictionary** class cannot be created, and several of its list and file helpers and two of its collectors fail or give wrong results. - [**TBMANLIB_Xml**](../Contributed/TBMANLIB_Xml/) -- XML documents parsed, edited and written, in twinBASIC code. Version 1.0.0.1 compiles, and its parser misreads some common XML, such as self-closing elements and entities in text, and does not return for some malformed text. Its XPath engine reads some valid expressions as others, such as the step `..` and `and` with `or`. diff --git a/indexer/snapshots/contributed/TBMANLIB_ConsoleComponents/api.json b/indexer/snapshots/contributed/TBMANLIB_ConsoleComponents/api.json index d0c38e4e..bb93530b 100644 --- a/indexer/snapshots/contributed/TBMANLIB_ConsoleComponents/api.json +++ b/indexer/snapshots/contributed/TBMANLIB_ConsoleComponents/api.json @@ -2,7 +2,7 @@ "format": 3, "package": "TBMANLIB_ConsoleComponents", "project": "TBMANLIB_ConsoleComponents", - "version": "0.0.0.0", + "version": "1.0.0.1", "types": [ { "name": "cColorHelper", diff --git a/test/example-projects/contributed-TBMANLIB_ConsoleComponents/Settings b/test/example-projects/contributed-TBMANLIB_ConsoleComponents/Settings index 88f567b5..227b88e3 100644 --- a/test/example-projects/contributed-TBMANLIB_ConsoleComponents/Settings +++ b/test/example-projects/contributed-TBMANLIB_ConsoleComponents/Settings @@ -55,8 +55,8 @@ "path32": "/Packages/TBMANLIB_ConsoleComponents", "path64": "/Packages/TBMANLIB_ConsoleComponents", "symbolId": "TBMANLIB_ConsoleComponents", - "versionBuild": "0", - "versionMajor": "0", + "versionBuild": "1", + "versionMajor": "1", "versionMinor": "0", "versionRevision": "0" }, @@ -68,8 +68,8 @@ "path32": "/Packages/TBMANLIB_ConsoleCore", "path64": "/Packages/TBMANLIB_ConsoleCore", "symbolId": "TBMANLIB_ConsoleCore", - "versionBuild": "0", - "versionMajor": "0", + "versionBuild": "1", + "versionMajor": "1", "versionMinor": "0", "versionRevision": "0" } From b091f9ea2d937ea343f66136fb5ac6dc8b45e95c Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 13 Oct 2026 19:43:03 +0200 Subject: [PATCH 7/9] OLEGuids: moved to 1.0.10.0; IDispatch renamed IDispatchUnrestricted with OLEDISPPARAMS, IOleInPlaceObjectWindowless and its VB variant, the changed signatures; WARNING: IEnumVARIANTUnrestricted extends IDispatch, so its methods crash on a real enumerator (confirmed by a second agent); stays Contributed --- WIP.ExamplesBuild.md | 2 +- builder/page-baseline.json | 2 +- .../Contributed/OLEGuids/IDispatch.md | 76 --------- .../OLEGuids/IDispatchUnrestricted.md | 144 ++++++++++++++++++ .../OLEGuids/IEnumVARIANTUnrestricted.md | 11 +- .../Contributed/OLEGuids/IObjectSafety.md | 2 +- .../Contributed/OLEGuids/IOleControl.md | 4 +- .../Contributed/OLEGuids/IOleControlSite.md | 6 +- .../Contributed/OLEGuids/IOleControlVB.md | 1 + .../OLEGuids/IOleInPlaceActiveObjectVB.md | 32 +++- .../Contributed/OLEGuids/IOleInPlaceObject.md | 1 + .../OLEGuids/IOleInPlaceObjectWindowless.md | 61 ++++++++ .../OLEGuids/IOleInPlaceObjectWindowlessVB.md | 67 ++++++++ .../Contributed/OLEGuids/IOleInPlaceSite.md | 2 +- .../Contributed/OLEGuids/IOleWindow.md | 12 +- .../OLEGuids/IPerPropertyBrowsing.md | 10 +- .../OLEGuids/IPerPropertyBrowsingVB.md | 9 +- .../OLEGuids/IUnknownUnrestricted.md | 19 ++- .../Contributed/OLEGuids/OLECLSID.md | 6 +- .../Contributed/OLEGuids/OLEDISPPARAMS.md | 90 +++++++++++ docs/Reference/Contributed/OLEGuids/index.md | 25 +-- .../snapshots/contributed/OLEGuids/api.json | 80 +++++++--- .../contributed-OLEGuids/Settings | 2 +- 23 files changed, 527 insertions(+), 137 deletions(-) delete mode 100644 docs/Reference/Contributed/OLEGuids/IDispatch.md create mode 100644 docs/Reference/Contributed/OLEGuids/IDispatchUnrestricted.md create mode 100644 docs/Reference/Contributed/OLEGuids/IOleInPlaceObjectWindowless.md create mode 100644 docs/Reference/Contributed/OLEGuids/IOleInPlaceObjectWindowlessVB.md create mode 100644 docs/Reference/Contributed/OLEGuids/OLEDISPPARAMS.md diff --git a/WIP.ExamplesBuild.md b/WIP.ExamplesBuild.md index e257faec..02871998 100644 --- a/WIP.ExamplesBuild.md +++ b/WIP.ExamplesBuild.md @@ -13,7 +13,7 @@ compiles](docs/Documentation/Authoring.md) in Authoring.md. ## State Of 1,530 `tb` fences in 749 pages, **1,370 are marked `check_build`** and compile, and 159 -are `inert` with a recorded reason (107 of them OLEGuids' method signatures). +are `inert` with a recorded reason (111 of them OLEGuids' method signatures). **None is undecided**, so the backlog the census measures is empty. No fence is an `excerpt`. The run takes about 110 s. 88 of the marked samples are also `check_run`. diff --git a/builder/page-baseline.json b/builder/page-baseline.json index cc2fc503..3d6ab60f 100644 --- a/builder/page-baseline.json +++ b/builder/page-baseline.json @@ -1,5 +1,5 @@ { "src": "docs", - "pages": 1256, + "pages": 1259, "staticFiles": 1009 } diff --git a/docs/Reference/Contributed/OLEGuids/IDispatch.md b/docs/Reference/Contributed/OLEGuids/IDispatch.md deleted file mode 100644 index 4c4b6e3e..00000000 --- a/docs/Reference/Contributed/OLEGuids/IDispatch.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -title: IDispatch -parent: OLEGuids Package -permalink: /tB/Packages/Contributed/OLEGuids/IDispatch -has_toc: false ---- - -# IDispatch interface -{: .no_toc } - -Finds and calls the members of an automation object by name or by dispatch identifier. - -**IDispatch** has the interface identifier `00020400-0000-0000-C000-000000000046` and extends **stdole.IUnknown**. It is declared `[OleAutomation(False)]`. Microsoft Learn describes the Windows interface: [IDispatch](https://learn.microsoft.com/en-us/windows/win32/api/oaidl/nn-oaidl-idispatch). - -The declaration is complete: it has all four methods of the Windows interface. Each is a **Sub** that hides the HRESULT: a failure code raises a run-time error, and a success code other than `S_OK` is read with [**Err.LastHresult**](../../../Modules/ErrObject/LastHresult). The structure parameters of **Invoke** are **stdole**'s **DISPPARAMS** and **EXCEPINFO**; the interface identifiers are the package's [OLECLSID](OLECLSID). - -> [!NOTE] -> A variable declared `As IDispatch` has the type of **stdole**, not this one, in a project that references **stdole**. A call through it is late-bound by name, and the four methods are not reached. Write `OLEGuids.IDispatch` to get this declaration. [IOleControlSite.GetExtendedControl](IOleControlSite#getextendedcontrol) returns it. - -The [IDispatch](../../../../Reference/COM-Interfaces/IDispatch) page in the COM Interfaces section gives the contract of the four methods, what a class that implements the interface must do, and how the **stdole** declaration behaves. The parameters there are the same as here, except that a project declares its own structure for the interface identifier where this package supplies [OLECLSID](OLECLSID). - -## Methods - -### GetTypeInfoCount - -```tb inert=signature -Sub GetTypeInfoCount(ByRef pctInfo As Long) -``` - -Says whether the object can describe itself with type information. *pctInfo* receives 1 when the object has type information and 0 when it has none. - -### GetTypeInfo - -```tb inert=signature -Sub GetTypeInfo(ByVal itinfo As Long, ByVal lcid As Long, ByRef pptinfo As LongPtr) -``` - -Returns the object's type information, as an `ITypeInfo*` in *pptinfo*. *itinfo* is the index of the type information: 0 selects the type information of the object's own implementation of **IDispatch**. *lcid* is the locale of the information. - -### GetIDsOfNames - -```tb inert=signature -Sub GetIDsOfNames(ByRef riid As OLEGuids.OLECLSID, ByRef rgszNames As LongPtr, ByVal cNames As Long, ByVal lcid As Long, ByRef rgDispID As Long) -``` - -Converts the names of a member and its parameters into dispatch identifiers. *riid* is reserved: pass an [OLECLSID](OLECLSID) holding only zeros. *rgszNames* is the first element of an array of *cNames* pointers to Unicode strings, such as `StrPtr(name)` in a **LongPtr** variable, and *rgDispID* receives one identifier for each name. A name that the object does not have raises a run-time error. - -### Invoke - -```tb inert=signature -Sub Invoke(ByVal DispIDMember As Long, ByRef riid As OLEGuids.OLECLSID, ByVal lcid As Long, ByVal wFlags As Integer, ByRef pdispparams As stdole.DISPPARAMS, ByRef pvarResult As Variant, ByRef pexcepinfo As stdole.EXCEPINFO, ByRef puArgErr As Long) -``` - -Calls the member with the dispatch identifier *DispIDMember*. *riid* is reserved and holds only zeros. *wFlags* says what kind of access it is: 1 calls a method (`DISPATCH_METHOD`), 2 reads a property (`DISPATCH_PROPERTYGET`), 4 assigns a property (`DISPATCH_PROPERTYPUT`) and 8 sets an object property (`DISPATCH_PROPERTYPUTREF`). *pdispparams* holds the arguments, *pvarResult* receives the result, *pexcepinfo* receives the details of an error the member raised, and *puArgErr* receives the index of the argument that was wrong. - -## Example - -This function finds the dispatch identifier of a member of an object by name. The *riid* argument is an **OLECLSID** that is not filled in, so it holds only zeros. - -```tb check_build -Private Function DispIdOf(ByVal Obj As Object, ByVal MemberName As String) As Long - Dim d As OLEGuids.IDispatch = Obj - Dim iid As OLECLSID - Dim namePtr As LongPtr = StrPtr(MemberName) - Dim id As Long - d.GetIDsOfNames iid, namePtr, 1, 0, id - Return id -End Function -``` - -## See Also - -- [IDispatch](../../../../Reference/COM-Interfaces/IDispatch) interface -- the contract, in the COM Interfaces section -- [OLECLSID](OLECLSID) type -- a class or interface identifier -- [IOleControlSite](IOleControlSite) interface -- its **GetExtendedControl** returns this interface -- [IEnumVARIANTUnrestricted](IEnumVARIANTUnrestricted) interface -- extends **stdole.IDispatch** diff --git a/docs/Reference/Contributed/OLEGuids/IDispatchUnrestricted.md b/docs/Reference/Contributed/OLEGuids/IDispatchUnrestricted.md new file mode 100644 index 00000000..116248b4 --- /dev/null +++ b/docs/Reference/Contributed/OLEGuids/IDispatchUnrestricted.md @@ -0,0 +1,144 @@ +--- +title: IDispatchUnrestricted +parent: OLEGuids Package +permalink: /tB/Packages/Contributed/OLEGuids/IDispatchUnrestricted +redirect_from: +- /tB/Packages/Contributed/OLEGuids/IDispatch +has_toc: false +--- + +# IDispatchUnrestricted interface +{: .no_toc } + +Finds and calls the members of an automation object by name or by dispatch identifier, through four methods that a program can call. + +**IDispatchUnrestricted** has the interface identifier `00020400-0000-0000-C000-000000000046`, which is the identifier of **IDispatch**, and extends **stdole.IUnknown**. It is declared `[OleAutomation(False)]`. Microsoft Learn describes the Windows interface: [IDispatch](https://learn.microsoft.com/en-us/windows/win32/api/oaidl/nn-oaidl-idispatch). + +The declaration is complete: it has all four methods of the Windows interface. Each is a **Sub** that hides the HRESULT: a failure code raises a run-time error, and a success code other than `S_OK` is read with [**Err.LastHresult**](../../../Modules/ErrObject/LastHresult). The interface identifiers are the package's [OLECLSID](OLECLSID). **Invoke** takes its three structure parameters as pointers (**LongPtr**): the address of an [OLEDISPPARAMS](OLEDISPPARAMS), of a **Variant** and of an `EXCEPINFO` structure, from [**VarPtr**](../../../Modules/Information/VarPtr), or 0 where the parameter is optional. + +> [!NOTE] +> The package has no declaration named **IDispatch**. A variable declared `As IDispatch` has the type of **stdole**, and a call through it is late-bound by name, so the four methods are not reached. To reach them, assign the object to an **IDispatchUnrestricted** variable. The assignment asks the object for the **IDispatch** interface, and it accepts an **Object** variable and a **stdole.IDispatch** variable alike. [IOleControlSite.GetExtendedControl](IOleControlSite#getextendedcontrol) returns a **stdole.IDispatch**. + +```tb check_build +Private Function Describable(ByVal Site As IOleControlSite) As Boolean + Dim d As IDispatchUnrestricted = Site.GetExtendedControl() + Dim count As Long + d.GetTypeInfoCount count + Return count > 0 +End Function +``` + +The [IDispatch](../../../../Reference/COM-Interfaces/IDispatch) page in the COM Interfaces section gives the contract of the four methods, what a class that implements the interface must do, and how the **stdole** declaration behaves. The parameters there are the same as here, except that a project's own declaration of the interface gives **Invoke** structure parameters of types where this one has pointers. + +## Methods + +### GetTypeInfoCount + +```tb inert=signature +Sub GetTypeInfoCount(ByRef pctInfo As Long) +``` + +Says whether the object can describe itself with type information. *pctInfo* receives 1 when the object has type information and 0 when it has none. An object of a twinBASIC class has it. + +### GetTypeInfo + +```tb inert=signature +Sub GetTypeInfo(ByVal itinfo As Long, ByVal lcid As Long, ByRef pptinfo As LongPtr) +``` + +Returns the object's type information, as an `ITypeInfo*` in *pptinfo*. *itinfo* is the index of the type information: 0 selects the type information of the object's own implementation of **IDispatch**. *lcid* is the locale of the information. An object of a twinBASIC class has the one description, and any other index raises run-time error -2147352565, which is `DISP_E_BADINDEX` (`&H8002000B`). + +### GetIDsOfNames + +```tb inert=signature +Sub GetIDsOfNames(ByRef riid As OLEGuids.OLECLSID, ByRef rgszNames As LongPtr, ByVal cNames As Long, ByVal lcid As Long, ByRef rgDispId As Long) +``` + +Converts the names of a member and its parameters into dispatch identifiers. *riid* is reserved: pass an [OLECLSID](OLECLSID) holding only zeros. *rgszNames* is the first element of an array of *cNames* pointers to Unicode strings, such as `StrPtr(name)` in a **LongPtr** variable, and *rgDispId* receives one identifier for each name. + +A name that the object does not have raises run-time error 438, *Object doesn't support this property or method*. **Err.LastHresult** then holds `DISP_E_UNKNOWNNAME` (`&H80020006`), and *rgDispId* holds -1 (`DISPID_UNKNOWN`). + +```tb check_run +Dim d As IDispatchUnrestricted = New Calculator +Dim iid As OLECLSID +Dim namePtr As LongPtr = StrPtr("Add") +Dim id As Long + +On Error Resume Next +d.GetIDsOfNames iid, namePtr, 1, 0, id +Debug.Print Err.Number ' 438 +Debug.Print Hex(Err.LastHresult) ' 80020006 +Debug.Print id ' -1 +``` + +### Invoke + +```tb inert=signature +Sub Invoke(ByVal DispId As Long, ByRef riid As OLEGuids.OLECLSID, ByVal lcid As Long, ByVal wFlags As Integer, ByVal lpdispparams As LongPtr, ByVal lpvarResult As LongPtr, ByVal lpexcepinfo As LongPtr, ByRef puArgErr As Long) +``` + +Calls the member with the dispatch identifier *DispId*. *riid* is reserved and holds only zeros. *lcid* is the locale for interpreting the arguments. *wFlags* says what kind of access it is: 1 calls a method (`DISPATCH_METHOD`), 2 reads a property (`DISPATCH_PROPERTYGET`), 4 assigns a property (`DISPATCH_PROPERTYPUT`) and 8 sets an object property (`DISPATCH_PROPERTYPUTREF`). + +*lpdispparams* is the address of an [OLEDISPPARAMS](OLEDISPPARAMS) that holds the arguments. *lpvarResult* is the address of a **Variant** that receives the result, or 0 when the result is not wanted. *lpexcepinfo* is the address of an `EXCEPINFO` structure that receives the details of an error the member raised, or 0. *puArgErr* receives the index of the argument that was wrong. + +The package declares no `EXCEPINFO` type, and the **stdole** type cannot be used: some of its members are of unsigned types, which twinBASIC does not support, as VB6 does not, so a variable of it does not compile (TB5106). A project that wants the details declares a structure of the same layout with signed members, as the [IDispatch](../../../../Reference/COM-Interfaces/IDispatch) page does. + +A member that raises an error is reported in one of two ways. With *lpexcepinfo* set to 0, the member's own error is raised: an `Err.Raise 5, , "boom"` in the member raises error 5 with the description *boom*. With *lpexcepinfo* the address of a structure, the call raises error -2147352567, which is `DISP_E_EXCEPTION` (`&H80020009`), and the structure holds the error: *scode* is the Windows code of the member's error (`&H800A0005` for error 5), and *bstrSource* and *bstrDescription* are its source and description. + +An identifier that the object has no member for raises error 438. **Err.LastHresult** holds `DISP_E_MEMBERNOTFOUND` (`&H80020003`). + +## Example + +This function finds the dispatch identifier of a member of an object by name. The *riid* argument is an **OLECLSID** that is not filled in, so it holds only zeros. + +```tb check_build +Private Function DispIdOf(ByVal Obj As Object, ByVal MemberName As String) As Long + Dim d As IDispatchUnrestricted = Obj + Dim iid As OLECLSID + Dim namePtr As LongPtr = StrPtr(MemberName) + Dim id As Long + d.GetIDsOfNames iid, namePtr, 1, 0, id + Return id +End Function +``` + +The next procedure calls a method of a class by its name. The class has a method `Subtract(a, b)` that returns *a* minus *b*. + +```tb hidden +Class Calculator + Public Function Subtract(ByVal a As Long, ByVal b As Long) As Long + Return a - b + End Function +End Class +``` + +The arguments are an array of **Variant** values in **reverse** order: the last argument of the call is element 0. The call `Subtract(10, 3)` puts 3 in element 0 and 10 in element 1. + +```tb check_run +Dim d As IDispatchUnrestricted = New Calculator + +Dim iid As OLECLSID +Dim namePtr As LongPtr = StrPtr("Subtract") +Dim id As Long +d.GetIDsOfNames iid, namePtr, 1, 0, id + +Dim args(0 To 1) As Variant +args(0) = 3 +args(1) = 10 +Dim params As OLEDISPPARAMS +params.rgvarg = VarPtr(args(0)) +params.cArgs = 2 + +Dim result As Variant +Dim argErr As Long +d.Invoke id, iid, 0, 1, VarPtr(params), VarPtr(result), 0, argErr +Debug.Print result ' 7 +``` + +## See Also + +- [IDispatch](../../../../Reference/COM-Interfaces/IDispatch) interface -- the contract, in the COM Interfaces section +- [OLEDISPPARAMS](OLEDISPPARAMS) type -- the arguments of **Invoke** +- [OLECLSID](OLECLSID) type -- a class or interface identifier +- [IOleControlSite](IOleControlSite) interface -- its **GetExtendedControl** returns a **stdole.IDispatch** +- [IUnknownUnrestricted](IUnknownUnrestricted) interface -- the three methods of **IUnknown**, callable diff --git a/docs/Reference/Contributed/OLEGuids/IEnumVARIANTUnrestricted.md b/docs/Reference/Contributed/OLEGuids/IEnumVARIANTUnrestricted.md index eac41547..09556c32 100644 --- a/docs/Reference/Contributed/OLEGuids/IEnumVARIANTUnrestricted.md +++ b/docs/Reference/Contributed/OLEGuids/IEnumVARIANTUnrestricted.md @@ -14,10 +14,13 @@ Enumerates a sequence of **Variant** values, with counts declared as **Long** va ## Declaration -The enumerator declared in **stdole** cannot be implemented from twinBASIC, because its counts are unsigned 32-bit integers, which twinBASIC has no type for. This declaration uses **Long** counts instead. Its *cElt* parameters are **Long**, and so is *pcEltFetched*, which is passed by value. +The enumerator declared in **stdole** cannot be implemented from twinBASIC, because its counts are unsigned 32-bit integers, which twinBASIC has no type for. This declaration uses **Long** counts instead. Its *cElt* parameters are **Long**, and *lpcEltFetched* is the address of a **Long**, as a **LongPtr**. The declaration extends **stdole.IDispatch**. The Windows **IEnumVARIANT** extends **IUnknown**. +> [!WARNING] +> Because the declaration extends **stdole.IDispatch**, its four methods come after the seven of **IDispatch** in the method table, where the table of an **IEnumVARIANT** has them straight after the three of **IUnknown**. So in version 1.0.10.0 every method called through this interface on a real enumerator, such as the one a **Collection** returns, calls a slot past the end of its table: **Next**, **Skip** and **Clone** end the program with an access violation, and **Reset** raises a run-time error or does nothing. **For Each** over an object whose enumerator is a class that implements this interface ends the program the same way, without calling any of the class's methods. A program that calls or writes an enumerator declares its own copy of the interface, which extends **stdole.IUnknown**, as the [IEnumVARIANT](../../../../Reference/COM-Interfaces/IEnumVARIANT) page in the COM Interfaces section shows. + The declaration is complete: it has all four methods of the Windows interface. Each is a **Sub**, so the HRESULT is hidden: a failure code raises a run-time error, and a success code such as `S_FALSE` is read with [**Err.LastHresult**](../../../Modules/ErrObject/LastHresult). The [IEnumVARIANT](../../../../Reference/COM-Interfaces/IEnumVARIANT) page in the COM Interfaces section gives the contract of the four methods, how **For Each** uses them, and a project's own declaration of the interface. ## Methods @@ -25,10 +28,10 @@ The declaration is complete: it has all four methods of the Windows interface. E ### Next ```tb inert=signature -Sub Next(ByVal cElt As Long, ByRef rgVar As Variant, ByVal pcEltFetched As Long) +Sub Next(ByVal cElt As Long, ByRef rgVar As Variant, ByVal lpcEltFetched As LongPtr) ``` -Returns the next *cElt* items of the sequence, or as many as are left. *rgVar* is the first element of the caller's array of **Variant** values, which receives the items. *pcEltFetched* is the number of items fetched, as a pointer in the Windows method. It is a 32-bit **Long** here, so it can hold a pointer only in 32-bit code; a caller that does not need the count passes 0, which is a null pointer. A call that returns fewer items than asked for returns `S_FALSE`. +Returns the next *cElt* items of the sequence, or as many as are left. *rgVar* is the first element of the caller's array of **Variant** values, which receives the items. *lpcEltFetched* is the address of a **Long** that receives the number of items fetched, such as `VarPtr(fetched)`. A caller that does not need the count passes 0, a null pointer, which the Windows contract allows only when *cElt* is 1. A call that returns fewer items than asked for returns `S_FALSE`. ### Skip @@ -57,4 +60,4 @@ Returns, in *ppEnum*, a copy of the enumerator that starts at the same position. ## See Also - [IEnumVARIANT](../../../../Reference/COM-Interfaces/IEnumVARIANT) interface -- the contract, in the COM Interfaces section -- [IDispatch](IDispatch) interface -- finds and calls members of an automation object +- [IDispatchUnrestricted](IDispatchUnrestricted) interface -- finds and calls members of an automation object by name or dispatch identifier diff --git a/docs/Reference/Contributed/OLEGuids/IObjectSafety.md b/docs/Reference/Contributed/OLEGuids/IObjectSafety.md index 97ccf05b..af7d49b8 100644 --- a/docs/Reference/Contributed/OLEGuids/IObjectSafety.md +++ b/docs/Reference/Contributed/OLEGuids/IObjectSafety.md @@ -76,4 +76,4 @@ End Class ## See Also - [OLECLSID](OLECLSID) type -- a class or interface identifier -- [IDispatch](IDispatch) interface -- finds and calls members of an automation object +- [IDispatchUnrestricted](IDispatchUnrestricted) interface -- finds and calls members of an automation object by name or dispatch identifier diff --git a/docs/Reference/Contributed/OLEGuids/IOleControl.md b/docs/Reference/Contributed/OLEGuids/IOleControl.md index 9ce32ea3..0538b160 100644 --- a/docs/Reference/Contributed/OLEGuids/IOleControl.md +++ b/docs/Reference/Contributed/OLEGuids/IOleControl.md @@ -37,10 +37,10 @@ Tells the control that the user pressed one of the mnemonic keys it listed in ** ### OnAmbientPropertyChange ```tb inert=signature -Sub OnAmbientPropertyChange(ByVal DispID As Long) +Sub OnAmbientPropertyChange(ByVal DispId As Long) ``` -Tells the control that an ambient property of the container changed. *DispID* is the dispatch identifier of the property, or -1 (`DISPID_UNKNOWN`) when more than one property changed. +Tells the control that an ambient property of the container changed. *DispId* is the dispatch identifier of the property, or -1 (`DISPID_UNKNOWN`) when more than one property changed. ### FreezeEvents diff --git a/docs/Reference/Contributed/OLEGuids/IOleControlSite.md b/docs/Reference/Contributed/OLEGuids/IOleControlSite.md index 4783917c..dbd9224a 100644 --- a/docs/Reference/Contributed/OLEGuids/IOleControlSite.md +++ b/docs/Reference/Contributed/OLEGuids/IOleControlSite.md @@ -35,10 +35,10 @@ Asks the container to keep the control active in place, when *fLock* is not 0, o ### GetExtendedControl ```tb inert=signature -Function GetExtendedControl() As OLEGuids.IDispatch +Function GetExtendedControl() As stdole.IDispatch ``` -Returns the container's extended control: the object that holds the properties and methods the container maintains for the control, and that the control could not reach otherwise. The result is the package's [IDispatch](IDispatch). The Windows method returns it through an output parameter. +Returns the container's extended control: the object that holds the properties and methods the container maintains for the control, and that the control could not reach otherwise. The result is a **stdole.IDispatch**, so a call through it is late-bound by name. To call the four methods of **IDispatch** on it, assign it to an [IDispatchUnrestricted](IDispatchUnrestricted) variable. The Windows method returns it through an output parameter. ### TransformCoords @@ -79,7 +79,7 @@ Asks the container to show the control's property pages itself, so that they can ## See Also - [IOleControl](IOleControl) interface -- the control's side -- [IDispatch](IDispatch) interface -- finds and calls members of an automation object +- [IDispatchUnrestricted](IDispatchUnrestricted) interface -- finds and calls members of an automation object by name or dispatch identifier - [OLECONTROLINFO](OLECONTROLINFO) type -- the keyboard mnemonic table of a control - [OLEACCELMSG](OLEACCELMSG) type -- a window message - [OLEPOINT](OLEPOINT) type -- a point diff --git a/docs/Reference/Contributed/OLEGuids/IOleControlVB.md b/docs/Reference/Contributed/OLEGuids/IOleControlVB.md index f6d725b9..5cefe03d 100644 --- a/docs/Reference/Contributed/OLEGuids/IOleControlVB.md +++ b/docs/Reference/Contributed/OLEGuids/IOleControlVB.md @@ -41,5 +41,6 @@ Tells the control that the user pressed one of its mnemonic keys. The Windows me - [IOleControl](IOleControl) interface -- the Windows interface this one corresponds to - [IOleInPlaceActiveObjectVB](IOleInPlaceActiveObjectVB) interface -- Visual Basic-style **TranslateAccelerator** - [IPerPropertyBrowsingVB](IPerPropertyBrowsingVB) interface -- Visual Basic-style versions of three **IPerPropertyBrowsing** methods +- [IOleInPlaceObjectWindowlessVB](IOleInPlaceObjectWindowlessVB) interface -- Visual Basic-style **OnWindowMessage** - [OLECONTROLINFO](OLECONTROLINFO) type -- the keyboard mnemonic table of a control - [OLEACCELMSG](OLEACCELMSG) type -- a window message diff --git a/docs/Reference/Contributed/OLEGuids/IOleInPlaceActiveObjectVB.md b/docs/Reference/Contributed/OLEGuids/IOleInPlaceActiveObjectVB.md index e4c49ac9..3ab07e27 100644 --- a/docs/Reference/Contributed/OLEGuids/IOleInPlaceActiveObjectVB.md +++ b/docs/Reference/Contributed/OLEGuids/IOleInPlaceActiveObjectVB.md @@ -19,16 +19,46 @@ It exists because [**IOleInPlaceActiveObject**](IOleInPlaceActiveObject) has `[P ### TranslateAccelerator ```tb inert=signature -Sub TranslateAccelerator(Handled As Boolean, RetVal As Long, ByVal hWnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr, ByVal Shift As Long) +Sub TranslateAccelerator(ByRef Handled As Boolean, ByRef RetVal As Long, ByVal hWnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr, ByVal Shift As Long) ``` Lets the object process an accelerator keystroke. The Windows method receives a pointer to a `MSG` structure; this one receives the fields of the message as separate parameters: the window handle *hWnd*, the message number *wMsg*, and *wParam* and *lParam*. *Shift* is passed in addition to them. *Handled* and *RetVal* are passed by reference, and the implementation sets them. *Handled* is a flag that an implementation which dealt with the keystroke sets to **True**. *RetVal* corresponds to the HRESULT that the Windows method returns: `S_OK` (0) when the object used the keystroke and `S_FALSE` (1) when it did not. +## Example + +This class uses the F2 key (virtual-key code `&H71`) and reports that it did, with `S_OK` as the result. It leaves every other keystroke alone. + +```tb check_build projname=og-activeobject slot=file +Class AcceleratorHandler + Implements IOleInPlaceActiveObjectVB + + Private Sub IOleInPlaceActiveObjectVB_TranslateAccelerator(ByRef Handled As Boolean, ByRef RetVal As Long, ByVal hWnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr, ByVal Shift As Long) Implements IOleInPlaceActiveObjectVB.TranslateAccelerator + If wMsg = &H100 And wParam = &H71 Then + Handled = True + RetVal = 0 + End If + End Sub +End Class +``` + +A key-down message for F2 (`WM_KEYDOWN`, `&H100`) is handled: + +```tb check_run projname=og-activeobject +Dim a As IOleInPlaceActiveObjectVB = New AcceleratorHandler +Dim handled As Boolean +Dim result As Long = 1 + +a.TranslateAccelerator handled, result, 0, &H100, &H71, 0, 0 +Debug.Print handled ' True +Debug.Print result ' 0 +``` + ## See Also - [IOleInPlaceActiveObject](IOleInPlaceActiveObject) interface -- the Windows interface this one corresponds to - [IOleControlVB](IOleControlVB) interface -- Visual Basic-style **GetControlInfo** and **OnMnemonic** - [IPerPropertyBrowsingVB](IPerPropertyBrowsingVB) interface -- Visual Basic-style versions of three **IPerPropertyBrowsing** methods +- [IOleInPlaceObjectWindowlessVB](IOleInPlaceObjectWindowlessVB) interface -- Visual Basic-style **OnWindowMessage** - [OLEACCELMSG](OLEACCELMSG) type -- a window message diff --git a/docs/Reference/Contributed/OLEGuids/IOleInPlaceObject.md b/docs/Reference/Contributed/OLEGuids/IOleInPlaceObject.md index 662ae7b8..3b795736 100644 --- a/docs/Reference/Contributed/OLEGuids/IOleInPlaceObject.md +++ b/docs/Reference/Contributed/OLEGuids/IOleInPlaceObject.md @@ -64,6 +64,7 @@ End Sub ## See Also - [IOleWindow](IOleWindow) interface -- the base interface +- [IOleInPlaceObjectWindowless](IOleInPlaceObjectWindowless) interface -- extends this interface for an object that has no window - [IOleInPlaceSite](IOleInPlaceSite) interface -- the container-side site for in-place activation - [IOleInPlaceActiveObject](IOleInPlaceActiveObject) interface -- messages, activation and border changes for an in-place active object - [OLERECT](OLERECT) type -- a rectangle diff --git a/docs/Reference/Contributed/OLEGuids/IOleInPlaceObjectWindowless.md b/docs/Reference/Contributed/OLEGuids/IOleInPlaceObjectWindowless.md new file mode 100644 index 00000000..f8ccfbbb --- /dev/null +++ b/docs/Reference/Contributed/OLEGuids/IOleInPlaceObjectWindowless.md @@ -0,0 +1,61 @@ +--- +title: IOleInPlaceObjectWindowless +parent: OLEGuids Package +permalink: /tB/Packages/Contributed/OLEGuids/IOleInPlaceObjectWindowless +has_toc: false +--- + +# IOleInPlaceObjectWindowless interface +{: .no_toc } + +Lets a container send window messages to an OLE object that is active in place and has no window of its own, and ask the object for its drop target. + +**IOleInPlaceObjectWindowless** has the interface identifier `1C2056CC-5EF4-101B-8BC8-00AA003E3B29` and extends [**IOleInPlaceObject**](IOleInPlaceObject), so it also has the methods of that interface and of [**IOleWindow**](IOleWindow). It is declared `[OleAutomation(False)]`. Microsoft Learn describes the Windows interface: [IOleInPlaceObjectWindowless](https://learn.microsoft.com/en-us/windows/win32/api/ocidl/nn-ocidl-ioleinplaceobjectwindowless). + +A small object, such as a control, does not need a window. The container receives the window messages for it and passes them on with **OnWindowMessage**, and the object takes part in drag and drop through the target that **GetDropTarget** returns. + +The declaration is complete: it has both methods of the Windows interface. Both are marked `[PreserveSig]` and return the raw HRESULT as a **Long**, so a class cannot implement this interface; it is for calling. [IOleInPlaceObjectWindowlessVB](IOleInPlaceObjectWindowlessVB) declares **OnWindowMessage** in a form a class can implement. + +## Methods + +### OnWindowMessage + +```tb inert=signature +[PreserveSig] +Function OnWindowMessage(ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr, ByRef Result As LongPtr) As Long +``` + +Sends a window message from the container to the object. *wMsg* is the message identifier, and *wParam* and *lParam* are the parameters of the message, as the container received them. A position in *wParam* or *lParam* is in client coordinates of the container's window. *Result* receives the result code of the message. + +The result of the call is the raw HRESULT: `S_OK` (0) when the object processed the message, and `S_FALSE` (1) when it did not. The container then processes the message itself or passes it to the default window procedure. + +The container sends the mouse messages to the object that has captured the mouse, or else to the object under the pointer, and the keyboard messages to the object that has the focus. + +### GetDropTarget + +```tb inert=signature +[PreserveSig] +Function GetDropTarget(ByRef lppDropTarget As LongPtr) As Long +``` + +Returns the object's `IDropTarget` interface in *lppDropTarget*. A windowless object cannot register a drop target, because it has no window to register, so the container gets the interface here instead. The package does not declare `IDropTarget`, so the pointer is a **LongPtr**. The container gives the pointer back with **Release**, and may keep it for later drag operations until then. + +The result is the raw HRESULT: `S_OK` (0) on success, and `E_NOTIMPL` (`&H80004001`) when the object does not support drag and drop. + +## Example + +This function sends a message to a windowless object and returns whether the object processed it. The result code of the message is not used. + +```tb check_build +Private Function Deliver(ByVal Obj As IOleInPlaceObjectWindowless, ByVal Msg As Long, ByVal WParam As LongPtr, ByVal LParam As LongPtr) As Boolean + Dim result As LongPtr + Return Obj.OnWindowMessage(Msg, WParam, LParam, result) = 0 +End Function +``` + +## See Also + +- [IOleInPlaceObjectWindowlessVB](IOleInPlaceObjectWindowlessVB) interface -- Visual Basic-style **OnWindowMessage** +- [IOleInPlaceObject](IOleInPlaceObject) interface -- the base interface +- [IOleWindow](IOleWindow) interface -- returns the window handle of an OLE object +- [IOleInPlaceSite](IOleInPlaceSite) interface -- the container-side site for in-place activation diff --git a/docs/Reference/Contributed/OLEGuids/IOleInPlaceObjectWindowlessVB.md b/docs/Reference/Contributed/OLEGuids/IOleInPlaceObjectWindowlessVB.md new file mode 100644 index 00000000..fadd9d50 --- /dev/null +++ b/docs/Reference/Contributed/OLEGuids/IOleInPlaceObjectWindowlessVB.md @@ -0,0 +1,67 @@ +--- +title: IOleInPlaceObjectWindowlessVB +parent: OLEGuids Package +permalink: /tB/Packages/Contributed/OLEGuids/IOleInPlaceObjectWindowlessVB +has_toc: false +--- + +# IOleInPlaceObjectWindowlessVB interface +{: .no_toc } + +A Visual Basic-style version of **IOleInPlaceObjectWindowless.OnWindowMessage**, which a class can implement. + +**IOleInPlaceObjectWindowlessVB** is not a Windows interface. It has the interface identifier `061D3A2F-A213-4016-9756-B89140604819` and extends **stdole.IDispatch**. It is not declared `[OleAutomation(False)]`, as the package's Windows interfaces are. Its one method has the dispatch identifier 1 (`[DispId(1)]`). + +It exists because [**IOleInPlaceObjectWindowless**](IOleInPlaceObjectWindowless) has `[PreserveSig]` members, which a twinBASIC class cannot implement. This interface has one **Sub** that corresponds to the **OnWindowMessage** method of that interface, with signature types that Visual Basic code uses. **GetDropTarget** has no counterpart. + +## Methods + +### OnWindowMessage + +```tb inert=signature +[DispId(1)] +Sub OnWindowMessage(ByRef Handled As Boolean, ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr, ByRef Result As LongPtr) +``` + +Receives a window message that a container sends to an object that is active in place and has no window. *wMsg* is the message identifier, and *wParam* and *lParam* are the parameters of the message. + +*Handled* and *Result* are passed by reference, and the implementation sets them. *Handled* is a flag that an implementation which processed the message sets to **True**. *Result* corresponds to the result code that the Windows method returns through its last parameter. + +## Example + +This class processes the left mouse button message, `WM_LBUTTONDOWN` (`&H201`), and leaves every other message to its container. + +```tb check_build projname=og-windowless slot=file +Class ClickHandler + Implements IOleInPlaceObjectWindowlessVB + + Private Sub IOleInPlaceObjectWindowlessVB_OnWindowMessage(ByRef Handled As Boolean, ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr, ByRef Result As LongPtr) Implements IOleInPlaceObjectWindowlessVB.OnWindowMessage + If wMsg = &H201 Then + Handled = True + Result = 0 + End If + End Sub +End Class +``` + +A container passes it the messages it receives: + +```tb check_run projname=og-windowless +Dim w As IOleInPlaceObjectWindowlessVB = New ClickHandler +Dim handled As Boolean +Dim result As LongPtr + +w.OnWindowMessage handled, &H201, 0, 0, result +Debug.Print handled ' True + +handled = False +w.OnWindowMessage handled, &H200, 0, 0, result +Debug.Print handled ' False +``` + +## See Also + +- [IOleInPlaceObjectWindowless](IOleInPlaceObjectWindowless) interface -- the Windows interface this one corresponds to +- [IOleControlVB](IOleControlVB) interface -- Visual Basic-style **GetControlInfo** and **OnMnemonic** +- [IOleInPlaceActiveObjectVB](IOleInPlaceActiveObjectVB) interface -- Visual Basic-style **TranslateAccelerator** +- [IPerPropertyBrowsingVB](IPerPropertyBrowsingVB) interface -- Visual Basic-style versions of three **IPerPropertyBrowsing** methods diff --git a/docs/Reference/Contributed/OLEGuids/IOleInPlaceSite.md b/docs/Reference/Contributed/OLEGuids/IOleInPlaceSite.md index c2eaf2ab..8c1810e2 100644 --- a/docs/Reference/Contributed/OLEGuids/IOleInPlaceSite.md +++ b/docs/Reference/Contributed/OLEGuids/IOleInPlaceSite.md @@ -54,7 +54,7 @@ Returns what the object needs to run in place. *ppFrame* receives the container' ### Scroll ```tb inert=signature -Sub Scroll(ByVal scrollExtant As Currency) +Sub Scroll(ByVal ScrollExtant As Currency) ``` Asks the container to scroll the object by a number of pixels. The Windows method takes a `SIZE` by value, the distances in the x and y directions. This declaration passes the same eight bytes as a **Currency**. After the scroll, the container gives the object the new clipping rectangle with [**IOleInPlaceObject.SetObjectRects**](IOleInPlaceObject#setobjectrects). diff --git a/docs/Reference/Contributed/OLEGuids/IOleWindow.md b/docs/Reference/Contributed/OLEGuids/IOleWindow.md index 9cbd80ae..3b6618b2 100644 --- a/docs/Reference/Contributed/OLEGuids/IOleWindow.md +++ b/docs/Reference/Contributed/OLEGuids/IOleWindow.md @@ -36,7 +36,7 @@ Turns context-sensitive help mode on, when *fEnterMode* is not 0, or off, when i This class reports one window handle, which it is given when it is created. Its **ContextSensitiveHelp** does nothing, because the class has no help mode. -```tb check_build slot=file +```tb check_build projname=og-olewindow slot=file Class WindowSite Implements IOleWindow @@ -55,6 +55,16 @@ Class WindowSite End Class ``` +A caller asks the object for its window through the interface: + +```tb check_run projname=og-olewindow +Dim site As New WindowSite +site.Init &H1234 + +Dim w As IOleWindow = site +Debug.Print Hex(w.GetWindow()) ' 1234 +``` + ## See Also - [IOleInPlaceObject](IOleInPlaceObject) interface -- activates and deactivates an object in place diff --git a/docs/Reference/Contributed/OLEGuids/IPerPropertyBrowsing.md b/docs/Reference/Contributed/OLEGuids/IPerPropertyBrowsing.md index 35e5482f..a6fbb147 100644 --- a/docs/Reference/Contributed/OLEGuids/IPerPropertyBrowsing.md +++ b/docs/Reference/Contributed/OLEGuids/IPerPropertyBrowsing.md @@ -14,7 +14,7 @@ Lets a property browser ask an object for the display string, the property page The declaration is complete: it has all four methods of the Windows interface, and every one is marked `[PreserveSig]`. Each returns the raw HRESULT as a **Long**; `E_NOTIMPL` (`&H80004001`) means that the object does not provide that kind of information. A class cannot implement this interface; it is for calling. [IPerPropertyBrowsingVB](IPerPropertyBrowsingVB) declares three of the methods in a form a class can implement. -Each property is named by its dispatch identifier, *DispID*. +Each property is named by its dispatch identifier, *DispId*. ## Methods @@ -22,7 +22,7 @@ Each property is named by its dispatch identifier, *DispID*. ```tb inert=signature [PreserveSig] -Function GetDisplayString(ByVal DispID As Long, ByRef lpDisplayName As LongPtr) As Long +Function GetDisplayString(ByVal DispId As Long, ByRef lpDisplayName As LongPtr) As Long ``` Returns a text string that describes a property, for a property browser to display. *lpDisplayName* receives a pointer to a `BSTR` that the object allocates. The caller frees it with `SysFreeString`. @@ -31,7 +31,7 @@ Returns a text string that describes a property, for a property browser to displ ```tb inert=signature [PreserveSig] -Function MapPropertyToPage(ByVal DispID As Long, ByRef pCLSID As OLEGuids.OLECLSID) As Long +Function MapPropertyToPage(ByVal DispId As Long, ByRef pCLSID As OLEGuids.OLECLSID) As Long ``` Returns the class identifier of the property page on which the property can be edited, in *pCLSID*. When the method fails, *pCLSID* is set to the null identifier. @@ -40,7 +40,7 @@ Returns the class identifier of the property page on which the property can be e ```tb inert=signature [PreserveSig] -Function GetPredefinedStrings(ByVal DispID As Long, ByRef pCaStringsOut As OLEGuids.OLECALPOLESTR, ByRef pCaCookiesOut As OLEGuids.OLECADWORD) As Long +Function GetPredefinedStrings(ByVal DispId As Long, ByRef pCaStringsOut As OLEGuids.OLECALPOLESTR, ByRef pCaCookiesOut As OLEGuids.OLECADWORD) As Long ``` Returns the names of the values a property can take, for a drop-down list. *pCaStringsOut* receives a counted array of string pointers, and *pCaCookiesOut* a counted array of cookies. Each string has a cookie in the same position. The object allocates the strings and both arrays, and the caller frees them with `CoTaskMemFree`. A cookie is passed to **GetPredefinedValue** to get the value the string names. @@ -49,7 +49,7 @@ Returns the names of the values a property can take, for a drop-down list. *pCaS ```tb inert=signature [PreserveSig] -Function GetPredefinedValue(ByVal DispID As Long, ByVal dwCookie As Long, ByRef pVarOut As Variant) As Long +Function GetPredefinedValue(ByVal DispId As Long, ByVal dwCookie As Long, ByRef pVarOut As Variant) As Long ``` Returns, in *pVarOut*, the value that corresponds to *dwCookie*, a cookie that **GetPredefinedStrings** returned. diff --git a/docs/Reference/Contributed/OLEGuids/IPerPropertyBrowsingVB.md b/docs/Reference/Contributed/OLEGuids/IPerPropertyBrowsingVB.md index 97aea4c6..5e98d667 100644 --- a/docs/Reference/Contributed/OLEGuids/IPerPropertyBrowsingVB.md +++ b/docs/Reference/Contributed/OLEGuids/IPerPropertyBrowsingVB.md @@ -14,7 +14,7 @@ Visual Basic-style versions of three **IPerPropertyBrowsing** methods, which a c It exists because [**IPerPropertyBrowsing**](IPerPropertyBrowsing) has `[PreserveSig]` members, which a twinBASIC class cannot implement. Each method here is a **Sub** that corresponds to one of that interface's methods, and uses Visual Basic types in place of pointers: a **String** for a `BSTR*`, and arrays of **String** and **Long** for the counted arrays. **MapPropertyToPage** has no counterpart. -*Handled* is the first parameter of every method. It is passed by reference, and it is a flag that an implementation which dealt with the call sets to **True**. *DispID* is the dispatch identifier of the property. +*Handled* is the first parameter of every method. It is passed by reference, and it is a flag that an implementation which dealt with the call sets to **True**. *DispId* is the dispatch identifier of the property. ## Methods @@ -22,7 +22,7 @@ It exists because [**IPerPropertyBrowsing**](IPerPropertyBrowsing) has `[Preserv ```tb inert=signature [DispId(1)] -Sub GetDisplayString(ByRef Handled As Boolean, ByVal DispID As Long, ByRef DisplayName As String) +Sub GetDisplayString(ByRef Handled As Boolean, ByVal DispId As Long, ByRef DisplayName As String) ``` Returns a text string that describes the property, for a property browser to display, in *DisplayName*. Corresponds to [**IPerPropertyBrowsing.GetDisplayString**](IPerPropertyBrowsing#getdisplaystring). @@ -31,7 +31,7 @@ Returns a text string that describes the property, for a property browser to dis ```tb inert=signature [DispId(2)] -Sub GetPredefinedStrings(ByRef Handled As Boolean, ByVal DispID As Long, ByRef StringsOut As String(), ByRef CookiesOut As Long()) +Sub GetPredefinedStrings(ByRef Handled As Boolean, ByVal DispId As Long, ByRef StringsOut As String(), ByRef CookiesOut As Long()) ``` Returns the names of the values the property can take, in *StringsOut*, and a cookie for each in *CookiesOut*, at the same position. Corresponds to [**IPerPropertyBrowsing.GetPredefinedStrings**](IPerPropertyBrowsing#getpredefinedstrings). @@ -40,7 +40,7 @@ Returns the names of the values the property can take, in *StringsOut*, and a co ```tb inert=signature [DispId(3)] -Sub GetPredefinedValue(ByRef Handled As Boolean, ByVal DispID As Long, ByVal Cookie As Long, ByRef Value As Variant) +Sub GetPredefinedValue(ByRef Handled As Boolean, ByVal DispId As Long, ByVal Cookie As Long, ByRef Value As Variant) ``` Returns, in *Value*, the value that corresponds to *Cookie*, a cookie that **GetPredefinedStrings** returned. Corresponds to [**IPerPropertyBrowsing.GetPredefinedValue**](IPerPropertyBrowsing#getpredefinedvalue). @@ -50,3 +50,4 @@ Returns, in *Value*, the value that corresponds to *Cookie*, a cookie that **Get - [IPerPropertyBrowsing](IPerPropertyBrowsing) interface -- the Windows interface this one corresponds to - [IOleControlVB](IOleControlVB) interface -- Visual Basic-style **GetControlInfo** and **OnMnemonic** - [IOleInPlaceActiveObjectVB](IOleInPlaceActiveObjectVB) interface -- Visual Basic-style **TranslateAccelerator** +- [IOleInPlaceObjectWindowlessVB](IOleInPlaceObjectWindowlessVB) interface -- Visual Basic-style **OnWindowMessage** diff --git a/docs/Reference/Contributed/OLEGuids/IUnknownUnrestricted.md b/docs/Reference/Contributed/OLEGuids/IUnknownUnrestricted.md index 0b4e7769..38b8ffb5 100644 --- a/docs/Reference/Contributed/OLEGuids/IUnknownUnrestricted.md +++ b/docs/Reference/Contributed/OLEGuids/IUnknownUnrestricted.md @@ -22,13 +22,28 @@ See [IUnknown](../../../../Reference/COM-Interfaces/IUnknown) for the contract o ```tb inert=signature [PreserveSig] -Function QueryInterface(ByVal priid As LongPtr, ppvObject As LongPtr) As Long +Function QueryInterface(ByVal priid As LongPtr, ByRef ppvObject As LongPtr) As Long ``` -Asks the object for another interface. *priid* is the address of the interface identifier requested, such as the address of an [OLECLSID](OLECLSID) variable. *ppvObject* is passed by reference and receives the interface pointer, or 0 when the object does not support the interface. The result is the HRESULT: 0 (`S_OK`) on success, `&H80004002` (`E_NOINTERFACE`) when the interface is not supported. +Asks the object for another interface. *priid* is the address of the interface identifier requested, such as the address of an [OLECLSID](OLECLSID) variable. *ppvObject* is passed by reference and receives the interface pointer. The result is the HRESULT: 0 (`S_OK`) on success, `&H80004002` (`E_NOINTERFACE`) when the interface is not supported. A pointer that **QueryInterface** returns comes with a reference, which the caller gives back with **Release**. +> [!NOTE] +> When the interface is not supported, the COM contract sets *ppvObject* to 0. An object of a twinBASIC class leaves the variable as it was (BETA 1005). Set the variable to 0 before the call, and test the result rather than the pointer. + +```tb check_run +Dim u As IUnknownUnrestricted = New Collection + +Dim iid As OLECLSID ' an interface identifier that the object does not have +iid.Data1 = &H12345 + +Dim p As LongPtr = 0 +Dim hr As Long = u.QueryInterface(VarPtr(iid), p) +Debug.Print Hex(hr) ' 80004002 +Debug.Print p ' 0 +``` + ### AddRef ```tb inert=signature diff --git a/docs/Reference/Contributed/OLEGuids/OLECLSID.md b/docs/Reference/Contributed/OLEGuids/OLECLSID.md index 7cebf727..025a4198 100644 --- a/docs/Reference/Contributed/OLEGuids/OLECLSID.md +++ b/docs/Reference/Contributed/OLEGuids/OLECLSID.md @@ -37,11 +37,11 @@ End Type For the identifier `00020400-0000-0000-C000-000000000046`, *Data1* is `&H00020400`, *Data2* and *Data3* are 0, and *Data4* holds `&HC0`, 0, 0, 0, 0, 0, 0 and `&H46`. -An **OLECLSID** variable that is not filled in holds only zeros, which is the null identifier `IID_NULL` that [**IDispatch.GetIDsOfNames**](IDispatch#getidsofnames) and [**IDispatch.Invoke**](IDispatch#invoke) ask for in their *riid* parameter. +An **OLECLSID** variable that is not filled in holds only zeros, which is the null identifier `IID_NULL` that [**IDispatchUnrestricted.GetIDsOfNames**](IDispatchUnrestricted#getidsofnames) and [**IDispatchUnrestricted.Invoke**](IDispatchUnrestricted#invoke) ask for in their *riid* parameter. ## Where it is used -- [**IDispatch.GetIDsOfNames**](IDispatch#getidsofnames) and [**IDispatch.Invoke**](IDispatch#invoke) -- *riid*, by reference. +- [**IDispatchUnrestricted.GetIDsOfNames**](IDispatchUnrestricted#getidsofnames) and [**IDispatchUnrestricted.Invoke**](IDispatchUnrestricted#invoke) -- *riid*, by reference. - [**IObjectSafety.GetInterfaceSafetyOptions**](IObjectSafety#getinterfacesafetyoptions) and [**SetInterfaceSafetyOptions**](IObjectSafety#setinterfacesafetyoptions) -- *riid*, by reference. - [**IPerPropertyBrowsing.MapPropertyToPage**](IPerPropertyBrowsing#mappropertytopage) -- *pCLSID*, by reference. - [**IRichEditOle.ConvertObject**](IRichEditOle#convertobject) -- *pCLSIDNew*, and [**IRichEditOle.ActivateAs**](IRichEditOle#activateas) -- *pCLSID* and *pCLSIDAs*, by reference. @@ -68,6 +68,6 @@ End Function ## See Also -- [IDispatch](IDispatch) interface -- finds and calls members of an automation object +- [IDispatchUnrestricted](IDispatchUnrestricted) interface -- finds and calls members of an automation object by name or dispatch identifier - [IObjectSafety](IObjectSafety) interface -- reports and sets an object's safety options - [OLECAUUID](OLECAUUID) type -- a counted array of GUIDs diff --git a/docs/Reference/Contributed/OLEGuids/OLEDISPPARAMS.md b/docs/Reference/Contributed/OLEGuids/OLEDISPPARAMS.md new file mode 100644 index 00000000..a224497e --- /dev/null +++ b/docs/Reference/Contributed/OLEGuids/OLEDISPPARAMS.md @@ -0,0 +1,90 @@ +--- +title: OLEDISPPARAMS +parent: OLEGuids Package +permalink: /tB/Packages/Contributed/OLEGuids/OLEDISPPARAMS +has_toc: false +--- + +# OLEDISPPARAMS type +{: .no_toc } + +The arguments of a call through **IDispatch.Invoke**, with the layout of the Windows `DISPPARAMS` structure. + +**OLEDISPPARAMS** is a user-defined type declared in the package's `OLEGuids` module, so both `OLEDISPPARAMS` and `OLEGuids.OLEDISPPARAMS` name it. It mirrors the Windows structure [DISPPARAMS](https://learn.microsoft.com/en-us/windows/win32/api/oaidl/ns-oaidl-dispparams), which holds the addresses and the counts of the arguments and of the named arguments of a call. + +```tb inert=signature +Public Type OLEDISPPARAMS + rgvarg As LongPtr + rgdispidNamedArgs As LongPtr + cArgs As Long + cNamedArgs As Long +End Type +``` + +## Fields + +*rgvarg* +: A **LongPtr** holding the address of an array of **Variant** values, which are the arguments. They are in **reverse** order: element 0 is the last argument of the call and the highest element is the first. The named arguments come first in the array, so they are the last of the call's own order. + +*rgdispidNamedArgs* +: A **LongPtr** holding the address of an array of **Long** values, which are the dispatch identifiers of the named arguments, in the order of the first *cNamedArgs* elements of *rgvarg*. It is 0 when there are no named arguments. + +*cArgs* +: A **Long** holding the number of elements in *rgvarg*, the named arguments included. + +*cNamedArgs* +: A **Long** holding the number of elements in *rgdispidNamedArgs*. + +A variable that is not filled in holds only zeros, which is a call with no arguments. + +An assignment to a property passes the new value as the only argument, named with the reserved identifier `DISPID_PROPERTYPUT` (-3): *cArgs* and *cNamedArgs* are 1, and *rgdispidNamedArgs* is the address of a **Long** holding -3. The [IDispatch](../../../../Reference/COM-Interfaces/IDispatch#the-dispparams-structure) page in the COM Interfaces section describes the structure further. + +## Where it is used + +[**IDispatchUnrestricted.Invoke**](IDispatchUnrestricted#invoke) takes the address of an **OLEDISPPARAMS** variable, from [**VarPtr**](../../../Modules/Information/VarPtr), as *lpdispparams*. No method of the package takes the structure by name. + +## Example + +This procedure assigns 25 to the `Level` property of a **Gauge** object by its dispatch identifier, through **Invoke** with the access flag 4 (`DISPATCH_PROPERTYPUT`). + +```tb hidden +Class Gauge + Private m_level As Long + + Public Property Get Level() As Long + Return m_level + End Property + + Public Property Let Level(ByVal NewValue As Long) + m_level = NewValue + End Property +End Class +``` + +```tb check_run +Dim g As New Gauge +Dim d As IDispatchUnrestricted = g + +Dim iid As OLECLSID +Dim namePtr As LongPtr = StrPtr("Level") +Dim id As Long +d.GetIDsOfNames iid, namePtr, 1, 0, id + +Dim newValue(0 To 0) As Variant +newValue(0) = 25 +Dim putId As Long = -3 ' DISPID_PROPERTYPUT +Dim params As OLEDISPPARAMS +params.rgvarg = VarPtr(newValue(0)) +params.rgdispidNamedArgs = VarPtr(putId) +params.cArgs = 1 +params.cNamedArgs = 1 + +Dim argErr As Long +d.Invoke id, iid, 0, 4, VarPtr(params), 0, 0, argErr +Debug.Print g.Level ' 25 +``` + +## See Also + +- [IDispatchUnrestricted](IDispatchUnrestricted) interface -- finds and calls members of an automation object by name or dispatch identifier +- [OLECLSID](OLECLSID) type -- a class or interface identifier diff --git a/docs/Reference/Contributed/OLEGuids/index.md b/docs/Reference/Contributed/OLEGuids/index.md index 56956b08..6271e4e8 100644 --- a/docs/Reference/Contributed/OLEGuids/index.md +++ b/docs/Reference/Contributed/OLEGuids/index.md @@ -2,18 +2,18 @@ title: OLEGuids Package parent: Contributed Packages has_toc: false -indexed_from: 1.0.2.0 +indexed_from: 1.0.10.0 permalink: /tB/Packages/Contributed/OLEGuids/ --- # OLEGuids Package {: .no_toc } -**Author:** Krool. **Version:** 1.0.2.0. **On GitHub:** [Kr00l/VBCCR](https://github.com/Kr00l/VBCCR/tree/master/twinBASIC%20Package/OLEGuids), in the folder *twinBASIC Package/OLEGuids*. +**Author:** Krool. **Version:** 1.0.10.0. **On GitHub:** [Kr00l/VBCCR](https://github.com/Kr00l/VBCCR/tree/master/twinBASIC%20Package/OLEGuids), in the folder *twinBASIC Package/OLEGuids*. -A twinBASIC package that declares 23 OLE and COM interfaces and 10 structures in twinBASIC source, so that a 32-bit or 64-bit project can call or implement them without a type library. +A twinBASIC package that declares 25 OLE and COM interfaces and 11 structures in twinBASIC source, so that a 32-bit or 64-bit project can call or implement them without a type library. -The package is a twinBASIC implementation of `OLEGuids.tlb`, the type library of OLE interface and type definitions that Krool's VBCCR and VBFLXGRD controls (`VBCCRxx.OCX` and `VBFLXGRDxx.OCX`) use. It replaces `OLEGuids.tlb` in projects that build 64-bit programs, and works in 32-bit projects as well. This section documents version 1.0.2.0, published under the MIT licence. +The package is a twinBASIC implementation of `OLEGuids.tlb`, the type library of OLE interface and type definitions that Krool's VBCCR and VBFLXGRD controls (`VBCCRxx.OCX` and `VBFLXGRDxx.OCX`) use. It replaces `OLEGuids.tlb` in projects that build 64-bit programs, and works in 32-bit projects as well. This section documents version 1.0.10.0, published under the MIT licence. ## Using the package @@ -22,7 +22,7 @@ Add the package in Project → References (Ctrl-T) → Available Packages. The i - **Call it.** Declare a variable of the interface type and assign an object to it. The assignment asks the object for that interface, and a call through the variable runs the object's method. - **Implement it.** Name it in an [**Implements**](../../../Core/Implements) statement in a class, and write each method. A class cannot implement an interface that has a `[PreserveSig]` member (see [How the declarations differ from Windows](#how-the-declarations-differ-from-windows)). -A name can be written bare (`IOleWindow`, `OLERECT`) or qualified with the package name (`OLEGuids.IOleWindow`, `OLEGuids.OLERECT`). The ten structures are declared in a module named `OLEGuids`, which is why the qualified form works for them; the module has no page of its own. +A name can be written bare (`IOleWindow`, `OLERECT`) or qualified with the package name (`OLEGuids.IOleWindow`, `OLEGuids.OLERECT`). The eleven structures are declared in a module named `OLEGuids`, which is why the qualified form works for them; the module has no page of its own. ```tb check_build Private Function InPlaceWindow(ByVal Obj As Object) As LongPtr @@ -32,11 +32,11 @@ End Function ``` > [!NOTE] -> In a project that references **stdole**, as every default project does, the bare name `IDispatch` is **stdole**'s declaration. A call through it is late-bound by name, and its four methods are not reached (see [IDispatch](../../../../Reference/COM-Interfaces/IDispatch#the-stdole-declaration)). The package's own declaration needs the qualified name, **OLEGuids.IDispatch**. +> The package has no declaration named **IDispatch**. In a project that references **stdole**, as every default project does, the name is **stdole**'s declaration. A call through it is late-bound by name, and its four methods are not reached (see [IDispatch](../../../../Reference/COM-Interfaces/IDispatch#the-stdole-declaration)). The package's [IDispatchUnrestricted](IDispatchUnrestricted) declares the four methods, and an object is assigned to a variable of that type like any other interface. ```tb check_build Private Function HasTypeInfo(ByVal Obj As Object) As Boolean - Dim d As OLEGuids.IDispatch = Obj + Dim d As IDispatchUnrestricted = Obj Dim count As Long d.GetTypeInfoCount count Return count > 0 @@ -48,17 +48,17 @@ End Function The interfaces that exist in Windows have their original interface identifiers, so an object written in another language can be called through them. The declarations differ from the C headers in these ways: - **The HRESULT is hidden.** A **Sub**, and a **Function** that is not marked `[PreserveSig]`, returns nothing of the Windows method's HRESULT. A failure code raises a run-time error, and a success code other than `S_OK` is read with [**Err.LastHresult**](../../../Modules/ErrObject/LastHresult). Where the Windows method has an output parameter for its result, the **Function**'s result takes its place. -- **`[PreserveSig]` members return the raw HRESULT** as a **Long**. A twinBASIC class cannot implement a member marked [**[PreserveSig]**](../../../Core/Attributes#preservesig): the compiler reports TB5004, *unable to match this implementation to its interface member*. The interfaces with such a member are for calling, not for implementing: [IUnknownUnrestricted](IUnknownUnrestricted), [IOleObject](IOleObject), [IOleControl](IOleControl), [IOleControlSite](IOleControlSite), [IOleInPlaceActiveObject](IOleInPlaceActiveObject), [IOleInPlaceSite](IOleInPlaceSite), [IPerPropertyBrowsing](IPerPropertyBrowsing) and [IRichEditOle](IRichEditOle). A class sets [**Err.ReturnHResult**](../../../Modules/ErrObject/ReturnHResult) to return a specific HRESULT from a **Sub** it implements. +- **`[PreserveSig]` members return the raw HRESULT** as a **Long**. A twinBASIC class cannot implement a member marked [**[PreserveSig]**](../../../Core/Attributes#preservesig): the compiler reports TB5004, *unable to match this implementation to its interface member*. The interfaces with such a member are for calling, not for implementing: [IUnknownUnrestricted](IUnknownUnrestricted), [IOleObject](IOleObject), [IOleInPlaceObjectWindowless](IOleInPlaceObjectWindowless), [IOleControl](IOleControl), [IOleControlSite](IOleControlSite), [IOleInPlaceActiveObject](IOleInPlaceActiveObject), [IOleInPlaceSite](IOleInPlaceSite), [IPerPropertyBrowsing](IPerPropertyBrowsing) and [IRichEditOle](IRichEditOle). A class sets [**Err.ReturnHResult**](../../../Modules/ErrObject/ReturnHResult) to return a specific HRESULT from a **Sub** it implements. - **Pointers are `LongPtr`.** A parameter that is a pointer to a structure or to an interface the package does not declare (a `RECT*`, a `MSG*`, an `IMoniker*`) is a **LongPtr**. Pass the address of a variable with [**VarPtr**](../../../Modules/Information/VarPtr), or `StrPtr` for a string. - **Some declarations are partial.** [IOleObject](IOleObject) declares the first nine of the 21 methods of the Windows interface. The four stub interfaces --- [IOleClientSite](IOleClientSite), [IOleInPlaceFrame](IOleInPlaceFrame), [IStorage](IStorage) and [IDataObject](IDataObject) --- declare no methods; their description says to use the tbShellLib package for the full version. A stub is useful for naming the type of a parameter or a variable, not for calling. -- **Five declarations are variants of a Windows interface.** [IUnknownUnrestricted](IUnknownUnrestricted) has no base interface, which makes the three methods of **IUnknown** callable. [IEnumVARIANTUnrestricted](IEnumVARIANTUnrestricted) declares the **IEnumVARIANT** enumerator with **Long** counts. [IOleInPlaceActiveObjectVB](IOleInPlaceActiveObjectVB), [IOleControlVB](IOleControlVB) and [IPerPropertyBrowsingVB](IPerPropertyBrowsingVB) are not Windows interfaces: they have Visual Basic-style signatures that a class can implement. +- **Seven declarations are variants of a Windows interface.** [IUnknownUnrestricted](IUnknownUnrestricted) has no base interface, which makes the three methods of **IUnknown** callable. [IDispatchUnrestricted](IDispatchUnrestricted) declares the four methods of **IDispatch**, which the **stdole** declaration does not reach. [IEnumVARIANTUnrestricted](IEnumVARIANTUnrestricted) declares the **IEnumVARIANT** enumerator with **Long** counts. [IOleInPlaceActiveObjectVB](IOleInPlaceActiveObjectVB), [IOleControlVB](IOleControlVB), [IPerPropertyBrowsingVB](IPerPropertyBrowsingVB) and [IOleInPlaceObjectWindowlessVB](IOleInPlaceObjectWindowlessVB) are not Windows interfaces: they have Visual Basic-style signatures that a class can implement. -Every interface except the three `VB` variants is declared `[OleAutomation(False)]`, so it is not marked as an Automation interface. The contract of each method is that of the Windows interface, which Microsoft Learn documents; each interface page links to it. +Every interface except the four `VB` variants is declared `[OleAutomation(False)]`, so it is not marked as an Automation interface. The contract of each method is that of the Windows interface, which Microsoft Learn documents; each interface page links to it. ## Interfaces - [IDataObject](IDataObject) -- stub for the interface that transfers data between applications -- [IDispatch](IDispatch) -- finds and calls members of an automation object by name or dispatch identifier +- [IDispatchUnrestricted](IDispatchUnrestricted) -- finds and calls members of an automation object by name or dispatch identifier - [IEnumVARIANTUnrestricted](IEnumVARIANTUnrestricted) -- enumerator of **Variant** values with **Long** counts - [IObjectSafety](IObjectSafety) -- reports and sets an object's safety options for untrusted callers and data - [IOleClientSite](IOleClientSite) -- stub for the container-side site of an embedded object @@ -69,6 +69,8 @@ Every interface except the three `VB` variants is declared `[OleAutomation(False - [IOleInPlaceActiveObjectVB](IOleInPlaceActiveObjectVB) -- Visual Basic-style **TranslateAccelerator** - [IOleInPlaceFrame](IOleInPlaceFrame) -- stub for the container's frame window in in-place activation - [IOleInPlaceObject](IOleInPlaceObject) -- activates and deactivates an object in place +- [IOleInPlaceObjectWindowless](IOleInPlaceObjectWindowless) -- window messages and drop target of an object that is active in place and has no window +- [IOleInPlaceObjectWindowlessVB](IOleInPlaceObjectWindowlessVB) -- Visual Basic-style **OnWindowMessage** - [IOleInPlaceSite](IOleInPlaceSite) -- the container-side site for in-place activation - [IOleInPlaceUIWindow](IOleInPlaceUIWindow) -- border space in a frame or document window - [IOleObject](IOleObject) -- connects an embedded object to its site and performs its verbs @@ -89,6 +91,7 @@ Every interface except the three `VB` variants is declared `[OleAutomation(False - [OLECAUUID](OLECAUUID) -- a counted array of GUIDs - [OLECLSID](OLECLSID) -- a class or interface identifier, with the layout of the Windows **GUID** structure - [OLECONTROLINFO](OLECONTROLINFO) -- the keyboard mnemonic table of a control +- [OLEDISPPARAMS](OLEDISPPARAMS) -- the arguments of an **IDispatch.Invoke** call, with the layout of the Windows **DISPPARAMS** structure - [OLEINPLACEFRAMEINFO](OLEINPLACEFRAMEINFO) -- accelerator information of an in-place frame - [OLEPOINT](OLEPOINT) -- a point, with the layout of the Windows **POINT** structure - [OLERECT](OLERECT) -- a rectangle, with the layout of the Windows **RECT** structure diff --git a/indexer/snapshots/contributed/OLEGuids/api.json b/indexer/snapshots/contributed/OLEGuids/api.json index bb880a17..4bd5befe 100644 --- a/indexer/snapshots/contributed/OLEGuids/api.json +++ b/indexer/snapshots/contributed/OLEGuids/api.json @@ -2,7 +2,7 @@ "format": 3, "package": "OLEGuids", "project": "OLEGuids", - "version": "1.0.2.0", + "version": "1.0.10.0", "types": [ { "name": "IDataObject", @@ -16,18 +16,18 @@ "members": [] }, { - "name": "IDispatch", + "name": "IDispatchUnrestricted", "kind": "interface", "vis": "public", "file": "OLEGuids.twin", - "signature": "Interface IDispatch Extends stdole.IUnknown", + "signature": "Interface IDispatchUnrestricted Extends stdole.IUnknown", "extends": "stdole.IUnknown", "attrs": [{"name":"InterfaceId","args":"\"00020400-0000-0000-C000-000000000046\""},{"name":"OleAutomation","args":"False"}], "members": [ - {"name":"GetIDsOfNames","kind":"sub","vis":"public","signature":"Sub GetIDsOfNames(ByRef riid As OLEGuids.OLECLSID, ByRef rgszNames As LongPtr, ByVal cNames As Long, ByVal lcid As Long, ByRef rgDispID As Long)"}, + {"name":"GetIDsOfNames","kind":"sub","vis":"public","signature":"Sub GetIDsOfNames(ByRef riid As OLEGuids.OLECLSID, ByRef rgszNames As LongPtr, ByVal cNames As Long, ByVal lcid As Long, ByRef rgDispId As Long)"}, {"name":"GetTypeInfo","kind":"sub","vis":"public","signature":"Sub GetTypeInfo(ByVal itinfo As Long, ByVal lcid As Long, ByRef pptinfo As LongPtr)"}, {"name":"GetTypeInfoCount","kind":"sub","vis":"public","signature":"Sub GetTypeInfoCount(ByRef pctInfo As Long)"}, - {"name":"Invoke","kind":"sub","vis":"public","signature":"Sub Invoke(ByVal DispIDMember As Long, ByRef riid As OLEGuids.OLECLSID, ByVal lcid As Long, ByVal wFlags As Integer, ByRef pdispparams As stdole.DISPPARAMS, ByRef pvarResult As Variant, ByRef pexcepinfo As stdole.EXCEPINFO, ByRef puArgErr As Long)"} + {"name":"Invoke","kind":"sub","vis":"public","signature":"Sub Invoke(ByVal DispId As Long, ByRef riid As OLEGuids.OLECLSID, ByVal lcid As Long, ByVal wFlags As Integer, ByVal lpdispparams As LongPtr, ByVal lpvarResult As LongPtr, ByVal lpexcepinfo As LongPtr, ByRef puArgErr As Long)"} ] }, { @@ -40,7 +40,7 @@ "attrs": [{"name":"InterfaceId","args":"\"00020404-0000-0000-C000-000000000046\""},{"name":"OleAutomation","args":"False"}], "members": [ {"name":"Clone","kind":"sub","vis":"public","signature":"Sub Clone(ByRef ppEnum As stdole.IEnumVARIANT)"}, - {"name":"Next","kind":"sub","vis":"public","signature":"Sub Next(ByVal cElt As Long, ByRef rgVar As Variant, ByVal pcEltFetched As Long)"}, + {"name":"Next","kind":"sub","vis":"public","signature":"Sub Next(ByVal cElt As Long, ByRef rgVar As Variant, ByVal lpcEltFetched As LongPtr)"}, {"name":"Reset","kind":"sub","vis":"public","signature":"Sub Reset()"}, {"name":"Skip","kind":"sub","vis":"public","signature":"Sub Skip(ByVal cElt As Long)"} ] @@ -80,7 +80,7 @@ "members": [ {"name":"FreezeEvents","kind":"sub","vis":"public","signature":"Sub FreezeEvents(ByVal bFreeze As Long)"}, {"name":"GetControlInfo","kind":"function","vis":"public","signature":"Function GetControlInfo(ByRef pCI As OLECONTROLINFO) As Long","returnType":"Long","attrs":[{"name":"PreserveSig"}]}, - {"name":"OnAmbientPropertyChange","kind":"sub","vis":"public","signature":"Sub OnAmbientPropertyChange(ByVal DispID As Long)"}, + {"name":"OnAmbientPropertyChange","kind":"sub","vis":"public","signature":"Sub OnAmbientPropertyChange(ByVal DispId As Long)"}, {"name":"OnMnemonic","kind":"function","vis":"public","signature":"Function OnMnemonic(ByRef pMsg As OLEACCELMSG) As Long","returnType":"Long","attrs":[{"name":"PreserveSig"}]} ] }, @@ -93,7 +93,7 @@ "extends": "stdole.IUnknown", "attrs": [{"name":"InterfaceId","args":"\"B196B289-BAB4-101A-B69C-00AA00341D07\""},{"name":"OleAutomation","args":"False"}], "members": [ - {"name":"GetExtendedControl","kind":"function","vis":"public","signature":"Function GetExtendedControl() As OLEGuids.IDispatch","returnType":"OLEGuids.IDispatch"}, + {"name":"GetExtendedControl","kind":"function","vis":"public","signature":"Function GetExtendedControl() As stdole.IDispatch","returnType":"stdole.IDispatch"}, {"name":"LockInPlaceActive","kind":"sub","vis":"public","signature":"Sub LockInPlaceActive(ByVal fLock As Long)"}, {"name":"OnControlInfoChanged","kind":"sub","vis":"public","signature":"Sub OnControlInfoChanged()"}, {"name":"OnFocus","kind":"sub","vis":"public","signature":"Sub OnFocus(ByVal fGotFocus As Long)"}, @@ -142,7 +142,7 @@ "extends": "stdole.IDispatch", "attrs": [{"name":"InterfaceId","args":"\"38584260-0CFB-45E7-8FBB-5D20B311F5B8\""}], "members": [ - {"name":"TranslateAccelerator","kind":"sub","vis":"public","signature":"Sub TranslateAccelerator(Handled As Boolean, RetVal As Long, ByVal hWnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr, ByVal Shift As Long)"} + {"name":"TranslateAccelerator","kind":"sub","vis":"public","signature":"Sub TranslateAccelerator(ByRef Handled As Boolean, ByRef RetVal As Long, ByVal hWnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr, ByVal Shift As Long)"} ] }, { @@ -171,6 +171,31 @@ {"name":"UIDeactivate","kind":"sub","vis":"public","signature":"Sub UIDeactivate()"} ] }, + { + "name": "IOleInPlaceObjectWindowless", + "kind": "interface", + "vis": "public", + "file": "OLEGuids.twin", + "signature": "Interface IOleInPlaceObjectWindowless Extends OLEGuids.IOleInPlaceObject", + "extends": "OLEGuids.IOleInPlaceObject", + "attrs": [{"name":"InterfaceId","args":"\"1C2056CC-5EF4-101B-8BC8-00AA003E3B29\""},{"name":"OleAutomation","args":"False"}], + "members": [ + {"name":"GetDropTarget","kind":"function","vis":"public","signature":"Function GetDropTarget(ByRef lppDropTarget As LongPtr) As Long","returnType":"Long","attrs":[{"name":"PreserveSig"}]}, + {"name":"OnWindowMessage","kind":"function","vis":"public","signature":"Function OnWindowMessage(ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr, ByRef Result As LongPtr) As Long","returnType":"Long","attrs":[{"name":"PreserveSig"}]} + ] + }, + { + "name": "IOleInPlaceObjectWindowlessVB", + "kind": "interface", + "vis": "public", + "file": "OLEGuids.twin", + "signature": "Interface IOleInPlaceObjectWindowlessVB Extends stdole.IDispatch", + "extends": "stdole.IDispatch", + "attrs": [{"name":"InterfaceId","args":"\"061D3A2F-A213-4016-9756-B89140604819\""}], + "members": [ + {"name":"OnWindowMessage","kind":"sub","vis":"public","signature":"Sub OnWindowMessage(ByRef Handled As Boolean, ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr, ByRef Result As LongPtr)","attrs":[{"name":"DispId","args":"1"}]} + ] + }, { "name": "IOleInPlaceSite", "kind": "interface", @@ -189,7 +214,7 @@ {"name":"OnPosRectChange","kind":"sub","vis":"public","signature":"Sub OnPosRectChange(ByVal lprcPosRect As LongPtr)"}, {"name":"OnUIActivate","kind":"sub","vis":"public","signature":"Sub OnUIActivate()"}, {"name":"OnUIDeactivate","kind":"sub","vis":"public","signature":"Sub OnUIDeactivate(ByVal fUndoable As Long)"}, - {"name":"Scroll","kind":"sub","vis":"public","signature":"Sub Scroll(ByVal scrollExtant As Currency)"} + {"name":"Scroll","kind":"sub","vis":"public","signature":"Sub Scroll(ByVal ScrollExtant As Currency)"} ] }, { @@ -232,8 +257,8 @@ "kind": "interface", "vis": "public", "file": "OLEGuids.twin", - "signature": "Interface IOleWindow Extends IUnknown", - "extends": "IUnknown", + "signature": "Interface IOleWindow Extends stdole.IUnknown", + "extends": "stdole.IUnknown", "attrs": [{"name":"InterfaceId","args":"\"00000114-0000-0000-C000-000000000046\""},{"name":"OleAutomation","args":"False"}], "members": [ {"name":"ContextSensitiveHelp","kind":"sub","vis":"public","signature":"Sub ContextSensitiveHelp(ByVal fEnterMode As Long)"}, @@ -249,10 +274,10 @@ "extends": "stdole.IUnknown", "attrs": [{"name":"InterfaceId","args":"\"376BD3AA-3845-101B-84ED-08002B2EC713\""},{"name":"OleAutomation","args":"False"}], "members": [ - {"name":"GetDisplayString","kind":"function","vis":"public","signature":"Function GetDisplayString(ByVal DispID As Long, ByRef lpDisplayName As LongPtr) As Long","returnType":"Long","attrs":[{"name":"PreserveSig"}]}, - {"name":"GetPredefinedStrings","kind":"function","vis":"public","signature":"Function GetPredefinedStrings(ByVal DispID As Long, ByRef pCaStringsOut As OLEGuids.OLECALPOLESTR, ByRef pCaCookiesOut As OLEGuids.OLECADWORD) As Long","returnType":"Long","attrs":[{"name":"PreserveSig"}]}, - {"name":"GetPredefinedValue","kind":"function","vis":"public","signature":"Function GetPredefinedValue(ByVal DispID As Long, ByVal dwCookie As Long, ByRef pVarOut As Variant) As Long","returnType":"Long","attrs":[{"name":"PreserveSig"}]}, - {"name":"MapPropertyToPage","kind":"function","vis":"public","signature":"Function MapPropertyToPage(ByVal DispID As Long, ByRef pCLSID As OLEGuids.OLECLSID) As Long","returnType":"Long","attrs":[{"name":"PreserveSig"}]} + {"name":"GetDisplayString","kind":"function","vis":"public","signature":"Function GetDisplayString(ByVal DispId As Long, ByRef lpDisplayName As LongPtr) As Long","returnType":"Long","attrs":[{"name":"PreserveSig"}]}, + {"name":"GetPredefinedStrings","kind":"function","vis":"public","signature":"Function GetPredefinedStrings(ByVal DispId As Long, ByRef pCaStringsOut As OLEGuids.OLECALPOLESTR, ByRef pCaCookiesOut As OLEGuids.OLECADWORD) As Long","returnType":"Long","attrs":[{"name":"PreserveSig"}]}, + {"name":"GetPredefinedValue","kind":"function","vis":"public","signature":"Function GetPredefinedValue(ByVal DispId As Long, ByVal dwCookie As Long, ByRef pVarOut As Variant) As Long","returnType":"Long","attrs":[{"name":"PreserveSig"}]}, + {"name":"MapPropertyToPage","kind":"function","vis":"public","signature":"Function MapPropertyToPage(ByVal DispId As Long, ByRef pCLSID As OLEGuids.OLECLSID) As Long","returnType":"Long","attrs":[{"name":"PreserveSig"}]} ] }, { @@ -264,9 +289,9 @@ "extends": "stdole.IDispatch", "attrs": [{"name":"InterfaceId","args":"\"D5D3BBE3-DB60-4522-AF5B-D767FE736DDB\""}], "members": [ - {"name":"GetDisplayString","kind":"sub","vis":"public","signature":"Sub GetDisplayString(ByRef Handled As Boolean, ByVal DispID As Long, ByRef DisplayName As String)","attrs":[{"name":"DispId","args":"1"}]}, - {"name":"GetPredefinedStrings","kind":"sub","vis":"public","signature":"Sub GetPredefinedStrings(ByRef Handled As Boolean, ByVal DispID As Long, ByRef StringsOut As String(), ByRef CookiesOut As Long())","attrs":[{"name":"DispId","args":"2"}]}, - {"name":"GetPredefinedValue","kind":"sub","vis":"public","signature":"Sub GetPredefinedValue(ByRef Handled As Boolean, ByVal DispID As Long, ByVal Cookie As Long, ByRef Value As Variant)","attrs":[{"name":"DispId","args":"3"}]} + {"name":"GetDisplayString","kind":"sub","vis":"public","signature":"Sub GetDisplayString(ByRef Handled As Boolean, ByVal DispId As Long, ByRef DisplayName As String)","attrs":[{"name":"DispId","args":"1"}]}, + {"name":"GetPredefinedStrings","kind":"sub","vis":"public","signature":"Sub GetPredefinedStrings(ByRef Handled As Boolean, ByVal DispId As Long, ByRef StringsOut As String(), ByRef CookiesOut As Long())","attrs":[{"name":"DispId","args":"2"}]}, + {"name":"GetPredefinedValue","kind":"sub","vis":"public","signature":"Sub GetPredefinedValue(ByRef Handled As Boolean, ByVal DispId As Long, ByVal Cookie As Long, ByRef Value As Variant)","attrs":[{"name":"DispId","args":"3"}]} ] }, { @@ -350,7 +375,7 @@ "attrs": [{"name":"InterfaceId","args":"\"00000000-0000-0000-C000-000000000046\""},{"name":"OleAutomation","args":"False"}], "members": [ {"name":"AddRef","kind":"function","vis":"public","signature":"Function AddRef() As Long","returnType":"Long","attrs":[{"name":"PreserveSig"}]}, - {"name":"QueryInterface","kind":"function","vis":"public","signature":"Function QueryInterface(ByVal priid As LongPtr, ppvObject As LongPtr) As Long","returnType":"Long","attrs":[{"name":"PreserveSig"}]}, + {"name":"QueryInterface","kind":"function","vis":"public","signature":"Function QueryInterface(ByVal priid As LongPtr, ByRef ppvObject As LongPtr) As Long","returnType":"Long","attrs":[{"name":"PreserveSig"}]}, {"name":"Release","kind":"function","vis":"public","signature":"Function Release() As Long","returnType":"Long","attrs":[{"name":"PreserveSig"}]} ] }, @@ -448,6 +473,21 @@ ], "members": [] }, + { + "name": "OLEDISPPARAMS", + "kind": "type", + "in": "OLEGuids", + "vis": "public", + "file": "OLEGuids.twin", + "signature": "Public Type OLEDISPPARAMS", + "fields": [ + {"name":"rgvarg","vis":"public","signature":"rgvarg As LongPtr"}, + {"name":"rgdispidNamedArgs","vis":"public","signature":"rgdispidNamedArgs As LongPtr"}, + {"name":"cArgs","vis":"public","signature":"cArgs As Long"}, + {"name":"cNamedArgs","vis":"public","signature":"cNamedArgs As Long"} + ], + "members": [] + }, { "name": "OLEINPLACEFRAMEINFO", "kind": "type", diff --git a/test/example-projects/contributed-OLEGuids/Settings b/test/example-projects/contributed-OLEGuids/Settings index d94e2c77..b968310d 100644 --- a/test/example-projects/contributed-OLEGuids/Settings +++ b/test/example-projects/contributed-OLEGuids/Settings @@ -58,7 +58,7 @@ "versionBuild": "0", "versionMajor": "1", "versionMinor": "0", - "versionRevision": "2" + "versionRevision": "10" } ], "project.settingsVersion": 1, From 27efb7639e2ab2d754eadf5291df31d6ec610a9c Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 13 Oct 2026 19:57:25 +0200 Subject: [PATCH 8/9] Queued: QueryInterface on an object of a class the project compiles leaves the out pointer as it was on E_NOINTERFACE (BETA 1005); NOTEs on IUnknown and OLEGuids' IUnknownUnrestricted --- BUGS-TO-REPORT.md | 67 +++++++++ ...queryinterface-leaves-out-pointer.twinproj | Bin 0 -> 9260 bytes .../repro.json | 15 ++ .../src/Settings | 61 +++++++++ .../src/Sources/Form1.tbform | 84 ++++++++++++ .../src/Sources/Form1.twin | 7 + .../src/Sources/Startup.twin | 128 ++++++++++++++++++ .../vb6/Module1.bas | 89 ++++++++++++ .../vb6/Probe.vbp | 8 ++ .../vb6/Source.cls | 16 +++ docs/Reference/COM-Interfaces/IUnknown.md | 7 +- .../OLEGuids/IUnknownUnrestricted.md | 2 +- 12 files changed, 482 insertions(+), 2 deletions(-) create mode 100644 bugs/queryinterface-leaves-out-pointer/queryinterface-leaves-out-pointer.twinproj create mode 100644 bugs/queryinterface-leaves-out-pointer/repro.json create mode 100644 bugs/queryinterface-leaves-out-pointer/src/Settings create mode 100644 bugs/queryinterface-leaves-out-pointer/src/Sources/Form1.tbform create mode 100644 bugs/queryinterface-leaves-out-pointer/src/Sources/Form1.twin create mode 100644 bugs/queryinterface-leaves-out-pointer/src/Sources/Startup.twin create mode 100644 bugs/queryinterface-leaves-out-pointer/vb6/Module1.bas create mode 100644 bugs/queryinterface-leaves-out-pointer/vb6/Probe.vbp create mode 100644 bugs/queryinterface-leaves-out-pointer/vb6/Source.cls diff --git a/BUGS-TO-REPORT.md b/BUGS-TO-REPORT.md index e1d20e41..b231e561 100644 --- a/BUGS-TO-REPORT.md +++ b/BUGS-TO-REPORT.md @@ -4415,3 +4415,70 @@ The page throws `TypeError: Cannot read properties of undefined (reading '0')` i Severity: low. Nothing is lost; the empty designer is misleading. + +--- + +## `QueryInterface` of an object of a twinBASIC class returns E_NOINTERFACE and leaves its out pointer as it was, where COM sets it to NULL + +**Describe the bug** +`IUnknown::QueryInterface`, called through slot 0 of the vtable of an object of a class that twinBASIC compiles (a class of the project, a form, a control), asks for an interface the object does not have and returns `E_NOINTERFACE` (`&H80004002`), but leaves the out pointer, `*ppvObject`, as it was. The COM contract is that the method sets `*ppvObject` to NULL when it fails. A variable set to 12345 before the call is still 12345 after it. Observed in the DEBUG CONSOLE, by calling slot 0 through an interface that the project declares, and again with `DispCallFunc` on the object's own pointer. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `queryinterface-leaves-out-pointer.twinproj` (attached as `queryinterface-leaves-out-pointer.zip`). Its module `Startup` asks seven objects for an interface that none of them has, and sets the out variable to 12345 before each call. +2. Press F5. Each object is asked twice: through `IRawUnknown`, an interface of the project that has `IUnknown`'s interface identifier and `Extends Nothing`, so that its first method is slot 0 and `[PreserveSig]` returns the raw HRESULT; and with `DispCallFunc`, calling slot 0 of the pointer that `ObjPtr` gives for the `Object` variable. +3. See the DEBUG CONSOLE. For `Plain`, `Implementer` (a class that implements an interface), `Form1` and `CommandButton` (a control added to the form with `Controls.Add`), both calls return `E_NOINTERFACE` and leave `out=12345`. Here is the line for `Plain`, and the line for the `Collection`, which sets the variable to 0 as COM requires: + +``` +Plain: missing interface: hr=80004002 out=12345; with DispCallFunc: hr=80004002 out=12345; IUnknown: hr=0 out is a pointer=True; IDispatch: hr=0 out is a pointer=True +Collection: missing interface: hr=80004002 out=0; with DispCallFunc: hr=80004002 out=0; IUnknown: hr=0 out is a pointer=True; IDispatch: hr=0 out is a pointer=True +``` + +The narrowed reproduction is that project cut down to one object and one call: +```vb +[InterfaceId("00000000-0000-0000-C000-000000000046")] +Private Interface IMin Extends Nothing + [PreserveSig] + Function QueryInterface(ByVal priid As LongPtr, ByRef ppvObject As LongPtr) As Long +End Interface + +Private Class Plain + Public Value As Long +End Class + +Module Startup + + Private Type Guid128 + Data1 As Long + Data2 As Integer + Data3 As Integer + Data4(0 To 7) As Byte + End Type + + Public Sub Main() + Dim u As IMin = New Plain + Dim iid As Guid128 ' an interface that Plain does not have + iid.Data1 = &H12345 + Dim p As LongPtr = 12345 + Dim hr As Long = u.QueryInterface(VarPtr(iid), p) + Debug.Print Hex$(hr), p ' prints 80004002 and 12345; COM requires 0 + End Sub + +End Module +``` + +**Expected behavior** +`*ppvObject` is 0 after every call that fails, so that every line of the output shows `out=0` in place of `out=12345`, as the line for the `Collection` does. The Windows documentation of `IUnknown::QueryInterface` says that `*ppvObject` is set to NULL when the object does not support the interface. VB6 does it for its own classes: its `Class` instance, `Collection`, `Err` and a `Scripting.Dictionary` all return `E_NOINTERFACE` and set the variable to 0 (the VB6 project is attached as `queryinterface-leaves-out-pointer-vb6.zip`; it calls slot 0 with `DispCallFunc`, as VB6 cannot declare the interface). + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +Which objects: the pointer is left as it was for an object of a class that twinBASIC compiles: a plain class, a class that implements an interface of the project, a form, the VB package's `CommandButton`, and (in another project, not attached) the `WebView2` control on a form. It is set to 0, correctly, for the objects of the runtime library: `Collection` and `Err` in the attached project, and `App`, `Screen` and a form's `Controls` in the other. A `Scripting.Dictionary` from the Scripting Runtime library sets it to 0 too. + +What does not reproduce it: asking for an interface the object has. `IUnknown` and `IDispatch` return `S_OK` and write a new pointer, for every object above (the `IUnknown` and `IDispatch` columns of the output). The result is the same through the project's own interface and through `DispCallFunc`, so it is not a side effect of the declaration, and the same in a 32-bit and a 64-bit run. + +Severity: low. The failure is reported, by the HRESULT, so a caller that tests it is not affected. A caller that tests the pointer instead, as a C or C++ client may, sees the value it passed in. When the variable is reused after an earlier successful call, that value is a pointer the caller has released. + + diff --git a/bugs/queryinterface-leaves-out-pointer/queryinterface-leaves-out-pointer.twinproj b/bugs/queryinterface-leaves-out-pointer/queryinterface-leaves-out-pointer.twinproj new file mode 100644 index 0000000000000000000000000000000000000000..ce4c8af7b1b80802945737ec5c64b831c15f4fd5 GIT binary patch literal 9260 zcmcgyS#KN35!UYJk`g@yc?*KZz_2JlBzWm^q5wQ?Vs%BN{j&Qw=dS5?>5)%@+>fBDaEwtly@we_cjhb!U5Je=AS ze$MSBkFJvV+LtgOWS^hm@!PH6;CrwL{4nOuXch2tF%Ioe@F+vv+WINIe7m)a?+X!4 zxa-;;_mct+kps>z@O^Dh9_(3>#Lw06Hhyk+}#kif{yRSq3;rFahNF1G_37^&O^K4<-&I~`Hml~uDPcO(NKPA z-xXR68u=iccuxFER(c+><>V>%h3gujsuGDHIfDJ+2#SgVJLF!hmh6M?w(k<1Or-=I z#I`FYFTj`fLUKOjGoQ2fgNzCkMI$F_VNe*xp?Ki&JWO6t$+MAD7_Ix>gZ^FN5v@E& zAvdCx>$T=_^Q3Xq&_cq1x;zpykB0*i#Ckez*MU;4maF)b@lY+F(wBi(`_=lhV*Q;R zbvu}p?%F#pX7d=3=82;Jj=OSu`R91h1zbS~6J-aA3as-wwHRJfP8t^{6m2c5ZC+nWpfb32J+xp>MmfGOK*F+hyHXn^oG%oE=F zJmIa)LwMjtVJ`@zBN3#7&vp6KPF!tL7ygnUSLeNM z=m(jyG$7O0RbWp;8c6l62+f*vmfQ(2E@XmIpP@z`-wV1?7n4hWu-9QW{ z@B(j!buwGLA^RBldT%p0;qp|SCx=IhF$pdhf`nvzjUGi1DW2sB>>#xn2{0Q{s~sDf z2#5o3WJeEUn?hJ>2*cP8IdODK4K-z2Y9f+O9TPiRa(5iA9Qe&KYk)N-L@7meIcV0cix8fc+A6m9@w@bY?ofx?34l`qr<$iga&fY z()I7}T|2@?I^-XA_`qqG8;8e@QzR;_gZl{u2ZVAADORle38qDPXVV&)xRI3GdTKOU z-DbaWQa(C5JuTO2oqoA}T~JUmKLZDV4(WnP&WUo*bC?9}YPqNgk*<*hm3UpU?O&5* zgZJKp=RbOH-mLLnid;WtRg%;8w3vGlTgDXl_76xN?vOrcI4$EKvK^D$yE}V&5_5bW zF%QJ%DDe(h$PtRXi1qF-uLhT+-c7&N>9Nx5cPGeSNM!YWP;{6p3qqyiBxZps3$ZK{ zF%f^XqXVr_1-} z=h&k?Hp0##*11eXY+*Z`L3xwAoV1a((O<+z+OleA~jzNboJwGFDj4oSQcuU`1az|NKYIy^vWI>zqqs)pP z%N_cXK>5`?Qf77Ka*C1*n*wU7Pjx4m+jS_Ga88y|WrG|0QQHgaeU7>3MC{UsGQ8RD z4#S5mG~(fs55)`=2t*PR-8J7RwO6;c3mppKuvWy*eQ$OhhX<^^y5Un61j{R_j@;P3 zdH(lnwHyajO8>!(8>9$3;`u4$PO0qVUmFyHFmGfZCS%r37K>GBU%BGOxyog)U0Zlc zjO%2KDwIJP3C{$~xpKCynu{Zl%NHnbd8jC)fQSup{1|SDF9pBX?6DU!Dsn`TqopyY z5KeY?kjxX8GaXGx8??D*4LXjow~26S^%FHhqg^|;Yne<0q7Q-V1SSQ|j1*?1@x_s5 zsmew^JC?iJUd4zpG>Q17_1Agosz}LTRYQ9U)}aSFi(K%9AFdcqkIX{W@wnTlTdd^@ zgcj>2cB!~iI2J3h`%D~Vv54HU@T994(X`!?J*xxZhJkbL&EUJ39fhP;H4@iEmW0= z7WvuIdBfRKu9HsHP94sIr5(-;e%{{+j?*^k3>v+~x!(5Toc|WJg%Tx-*|2vx8eI0y zbM)95tJNFL!_*j*chTNoz~#s_N$d34V=BTRd{0%@Kt@pM>t)`^rnRC6W=a3G4%j7s zWI10ZvT{6iFjO@PD+kGFo%^<9(l^bekDM=4I%ewBu154&$6qXvM|5$iaVn;0t0451 ztlKsKMKntTYF17M?>%<|3kA(kkrO6fEEb%(vg5Ks@`ul*Re>rIkOp`t(`$-F#Q|kr zY-Z$pNKtEYxKx!>vq7l{^`!=^VfI4OBEHw)DE9?wU~g9G;y43BcDMp!C*(+5YI950 zMkK9*G}I}=#uv$2unjszo!evXl8Mo2-&X!=4#5mq81I;I-Mk<}K;E#V*$h^c_;rRkzt@KxT#Zg5mwQKmCZwxYuF z8GHS%;3{TyBxIB0UoeoX7ul03B)(Q)JZH;ibR`Ff=KJv0*Y7&juizlq6HfDJT}Pls zk)v|(@f53N>V8BSwQ-dFl$I(lqMhCwB(s=@ z1`8-jrC6_*5Hb-@OL~)0pa*M4NyjiEpv)K~lwrSSI9wTv6Xxjh2CJP}>@|DGKffx? z!+ktj%uiwo?0`mz=w}QD8`%cSXN>CHHD=)r#4LWm=myTo7WZUPZ0de}+`eI~T|Q1`6$EH6Mi%mNrJpQSl^yJ{VR~kzw586f zl%4|E&4E18;mrEt0H!mO3IA#7wZU-psRiVWFXoEBV6gElgBC~*Iakw|s7}tkL>;B- z+nU7C6;Nj25-Bd==<=$F6U1QwNk^3;63*bn(u+kD7Zh_zmTZoMgRwK?tdJ>ZeZ-qE zGG?<-y#RcMCIZU#S_UV%mk>p0f>gbi|QmrzWs=4%Rtkn4X9@9ryDq>Nx@hJ)bItIwPz-Cc0!97+q zP29DUII|r`HV}v#;{#LznV4dZ>Slwn4MlxW?60z`5Me3HT~a&f5}?N-U6iQPhqhym z>ljolu)tiAl&K|mO4GSPO~790_W+MLQMHkTh^9&vOPv(ZGI^wy%$g!H)#;EyT2#kc z(=VeN!6AC=D88b67w{(@F6^FmceX8DiA=>T3FY~(B0N-CBBoZTr7sB3<|aJdp{dwG zFcPu4X_GlH&9Lhip2d0N1dY#YTi%9Qub$MglHQ;1?%FhVclYY)TD2Cb8Tx$x>Zui? zT^b`jqSOCedG&;=F)q6_5lUm+1?BmY^vUmtbsXwF2N#d5_vc;_&Eb)%vDHn`4ToG# z0y}!MZbWGC*wiJ{4L4uqX_)YaULTzvbbAlVqpHH>Jk{P)y}d_S!BW2t-8s{%qYhn> zMgEy?gh@4n=(Y}VWdAuYt7!}OI!BF@=3)7y)~%PDhrQGC=~4HjTx-|somQ{jYITm+ zwB+fl!2e|DeA$)neGbso5p;?0f}hki$!blEkds$gw=yZdIlNg@*I5wY|vvZCjaT-X}8&G9@kIQ9+EmE9C@kj8|qvB+-8vG(9zXb>KB8NVbCb> zBMk3)aNfIN*R9U?t+%~TIqNR7+ctVhIN8QxEL>dEF}(yU(UCpcH{>#n=OId;DTI0y z5v-k*Jo+%Wls)URVZfl}m3rGYP^~!`6ENS{fl;W|^E%k*xv2gdT<_BEfKCJHdh-@^ zYNz#nyK!1RYIa)XX7{vRZa0sQ%dNx1YQ5h)>GeCE|H~HOUl45O`eXF9RL@_<9HW^3 zk>;`LTe+xg#_9-c*8FjsQ7dZw;@@tDatCb4Cz_e&7KBBascw}|Vb>((Y@BXN6Dn|! z6UeqGsZ$qBvYVnk+M!1Y4;V5ISv(AVdA)}#H?jf?J#Lxf%sZ|beOy6dLJIMwe(p6# r@*}PRlq*}hi&VkY`g>1I@COl-c+l3wKY8d@m4fB&4!Q= RLYKSe1P literal 0 HcmV?d00001 diff --git a/bugs/queryinterface-leaves-out-pointer/repro.json b/bugs/queryinterface-leaves-out-pointer/repro.json new file mode 100644 index 00000000..6a928ab6 --- /dev/null +++ b/bugs/queryinterface-leaves-out-pointer/repro.json @@ -0,0 +1,15 @@ +{ + "mode": "run", + "expect": { + "exit": 0, + "output": [ + "^Plain: missing interface: hr=80004002 out=12345; with DispCallFunc: hr=80004002 out=12345; IUnknown: hr=0 out is a pointer=True; IDispatch: hr=0 out is a pointer=True$", + "^Implementer: missing interface: hr=80004002 out=12345; with DispCallFunc: hr=80004002 out=12345;", + "^Form1: missing interface: hr=80004002 out=12345; with DispCallFunc: hr=80004002 out=12345;", + "^CommandButton: missing interface: hr=80004002 out=12345; with DispCallFunc: hr=80004002 out=12345;", + "^Collection: missing interface: hr=80004002 out=0; with DispCallFunc: hr=80004002 out=0;", + "^Err: missing interface: hr=80004002 out=0; with DispCallFunc: hr=80004002 out=0;", + "^Scripting\\.Dictionary: missing interface: hr=80004002 out=0; with DispCallFunc: hr=80004002 out=0;" + ] + } +} diff --git a/bugs/queryinterface-leaves-out-pointer/src/Settings b/bugs/queryinterface-leaves-out-pointer/src/Settings new file mode 100644 index 00000000..082d011e --- /dev/null +++ b/bugs/queryinterface-leaves-out-pointer/src/Settings @@ -0,0 +1,61 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "QueryinterfaceLeavesOutPointer", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Reproduces: QueryInterface on a twinBASIC object returns E_NOINTERFACE and leaves its out pointer as it was, where COM sets it to NULL", + "project.exportPathIsV2": true, + "project.id": "{3EC63845-81D2-45E9-96D8-1B22CAE2AAC7}", + "project.name": "QueryinterfaceLeavesOutPointer", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/bugs/queryinterface-leaves-out-pointer/src/Sources/Form1.tbform b/bugs/queryinterface-leaves-out-pointer/src/Sources/Form1.tbform new file mode 100644 index 00000000..6bb7db39 --- /dev/null +++ b/bugs/queryinterface-leaves-out-pointer/src/Sources/Form1.tbform @@ -0,0 +1,84 @@ +[ + { + "AlwaysShowKeyboardCues": false, + "Appearance": "vbAppear3d", + "AutoRedraw": false, + "BackColor": 13223074, + "BorderStyle": "vbSizable", + "Caption": "Form1", + "ClipControls": true, + "ControlBox": true, + "DrawMode": "vbCopyPen", + "DrawStyle": "vbSolid", + "DrawWidth": 1, + "Enabled": true, + "FillColor": 0, + "FillStyle": "vbFSTransparent", + "FontBold": false, + "FontItalic": false, + "FontName": "Segoe UI", + "FontSize": 8, + "FontStrikethru": false, + "FontTransparent": true, + "FontUnderline": false, + "ForeColor": -2147483630, + "FormDesignerId": "{7B247400-0000-4000-9000-7B2474000F02}", + "HasDC": true, + "Height": 480, + "HelpContextID": 0, + "Icon": "", + "KeyPreview": false, + "Left": 0, + "LinkMode": "vbLinkNone", + "LinkTopic": null, + "MDIChild": false, + "MaxButton": true, + "MaxHeight": 0, + "MaxWidth": 0, + "MinButton": true, + "MinHeight": 0, + "MinWidth": 0, + "MouseIcon": "", + "MousePointer": "vbDefault", + "Moveable": true, + "Name": "Form1", + "NegotiateMenus": false, + "OLEDropMode": "vbOLEDropNone", + "Opacity": 100, + "Palette": "", + "PaletteMode": "vbPaletteModeHalftone", + "Picture": "", + "PictureDpiScaling": false, + "RightToLeft": false, + "ScaleHeight": 8560, + "ScaleLeft": 0, + "ScaleMode": "vbTwips", + "ScaleTop": 0, + "ScaleWidth": 11760, + "ShowInTaskbar": true, + "StartUpPosition": "vbStartUpScreen", + "TabFocusAutoSelect": false, + "Tag": null, + "Top": 0, + "TopMost": false, + "TransparencyKey": -1, + "Visible": true, + "WhatsThisButton": false, + "WhatsThisHelp": false, + "Width": 640, + "WindowState": "vbNormal", + "__IDEOptions": { + "alignToGrid": false, + "gridHeight": 10, + "gridWidth": 10, + "lockedControls": [], + "multiColoredGrabbers": true, + "showGrid": true, + "showOutlines": false + }, + "__lastUpdateMarker": 3573907440, + "_children": [], + "_className": "Form", + "_clsid": "{33AD4F38-6699-11CF-B70C-00AA0060D393}" + } +] diff --git a/bugs/queryinterface-leaves-out-pointer/src/Sources/Form1.twin b/bugs/queryinterface-leaves-out-pointer/src/Sources/Form1.twin new file mode 100644 index 00000000..b413eebe --- /dev/null +++ b/bugs/queryinterface-leaves-out-pointer/src/Sources/Form1.twin @@ -0,0 +1,7 @@ +' An empty form: one of the objects whose QueryInterface the project asks. + +[FormDesignerId("7B247400-0000-4000-9000-7B2474000F02")] +[PredeclaredId] +Class Form1 + +End Class diff --git a/bugs/queryinterface-leaves-out-pointer/src/Sources/Startup.twin b/bugs/queryinterface-leaves-out-pointer/src/Sources/Startup.twin new file mode 100644 index 00000000..f5ccae13 --- /dev/null +++ b/bugs/queryinterface-leaves-out-pointer/src/Sources/Startup.twin @@ -0,0 +1,128 @@ +' IUnknown::QueryInterface (slot 0 of an object's vtable), asked for an interface that the +' object does not have, returns E_NOINTERFACE (&H80004002) and leaves the out pointer as it +' was, for an object of a class that twinBASIC compiles. COM requires it to be set to NULL. +' The call is made twice: through this project's own interface, which declares slot 0, +' and with DispCallFunc on the object's vtable, with no interface of the project's involved. + +[InterfaceId("00000000-0000-0000-C000-000000000046")] +[OleAutomation(False)] +Private Interface IRawUnknown Extends Nothing + [PreserveSig] + Function QueryInterface(ByVal priid As LongPtr, ByRef ppvObject As LongPtr) As Long + [PreserveSig] + Function AddRef() As Long + [PreserveSig] + Function Release() As Long +End Interface + +Private Interface IThing + Sub Dummy() +End Interface + +Private Class Plain + Public Value As Long +End Class + +Private Class Implementer + Implements IThing + Private Sub IThing_Dummy() Implements IThing.Dummy + End Sub +End Class + +Module Startup + + Private Type Guid128 + Data1 As Long + Data2 As Integer + Data3 As Integer + Data4(0 To 7) As Byte + End Type + + Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _ + ByVal Destination As LongPtr, ByVal Source As LongPtr, ByVal Length As LongPtr) + Private Declare PtrSafe Function DispCallFunc Lib "oleaut32" ( _ + ByVal pvInstance As LongPtr, ByVal oVft As LongPtr, ByVal cc As Long, ByVal vtReturn As Integer, _ + ByVal cActuals As Long, ByRef prgvt As Integer, ByRef prgpvarg As LongPtr, ByRef pvargResult As Variant) As Long + + Private Const SENTINEL As LongPtr = 12345 + + Public Sub Main() + ' Objects of classes that twinBASIC compiles. + Ask "Plain", New Plain + Ask "Implementer", New Implementer + Load Form1 + Ask "Form1", Form1 + Ask "CommandButton", Form1.Controls.Add("VB.CommandButton", "Command1") + Unload Form1 + ' Objects of the runtime library, and an object of another COM library, for comparison. + Ask "Collection", New Collection + Ask "Err", Err + Ask "Scripting.Dictionary", CreateObject("Scripting.Dictionary") + End Sub + + Private Sub Ask(ByVal Label As String, ByVal Obj As Object) + Dim u As IRawUnknown = Obj ' asks for IUnknown + + Dim missing As Guid128 ' an interface identifier that no object here has + missing.Data1 = &H12345 + + Dim iidUnknown As Guid128 ' IUnknown: {00000000-0000-0000-C000-000000000046} + iidUnknown.Data4(0) = &HC0 + iidUnknown.Data4(7) = &H46 + + Dim iidDispatch As Guid128 ' IDispatch: {00020400-0000-0000-C000-000000000046} + iidDispatch.Data1 = &H20400 + iidDispatch.Data4(0) = &HC0 + iidDispatch.Data4(7) = &H46 + + Dim p As LongPtr = SENTINEL + Dim hr As Long = u.QueryInterface(VarPtr(missing), p) + Dim line As String = Label & ": missing interface: hr=" & Hex$(hr) & " out=" & CStr(p) + + p = SENTINEL + hr = CallQueryInterface(ObjPtr(Obj), VarPtr(missing), p) ' the pointer of the Object variable + line &= "; with DispCallFunc: hr=" & Hex$(hr) & " out=" & CStr(p) + + p = SENTINEL + hr = u.QueryInterface(VarPtr(iidUnknown), p) + line &= "; IUnknown: hr=" & Hex$(hr) & " out is a pointer=" & CStr(p <> SENTINEL And p <> 0) + GiveBack p + + p = SENTINEL + hr = u.QueryInterface(VarPtr(iidDispatch), p) + line &= "; IDispatch: hr=" & Hex$(hr) & " out is a pointer=" & CStr(p <> SENTINEL And p <> 0) + GiveBack p + + Debug.Print line + End Sub + + ' Slot 0 of the vtable of pObj, called with DispCallFunc. + Private Function CallQueryInterface(ByVal pObj As LongPtr, ByVal pIid As LongPtr, ByRef Out As LongPtr) As Long + Dim vals(0 To 1) As Variant + Dim vts(0 To 1) As Integer + Dim ptrs(0 To 1) As LongPtr + Dim ret As Variant + vals(0) = pIid + vals(1) = VarPtr(Out) + Dim i As Long + For i = 0 To 1 + vts(i) = VarType(vals(i)) + ptrs(i) = VarPtr(vals(i)) + Next + ret = 0& + Dim rc As Long = DispCallFunc(pObj, 0, 4, 3, 2, vts(0), ptrs(0), ret) + If rc <> 0 Then + CallQueryInterface = rc + Else + CallQueryInterface = CLng(ret) + End If + End Function + + ' Releases the reference that a successful QueryInterface added. + Private Sub GiveBack(ByVal p As LongPtr) + If p = 0 Or p = SENTINEL Then Exit Sub + Dim holder As IRawUnknown + CopyMemory VarPtr(holder), VarPtr(p), LenB(p) ' the variable now owns the reference + End Sub + +End Module diff --git a/bugs/queryinterface-leaves-out-pointer/vb6/Module1.bas b/bugs/queryinterface-leaves-out-pointer/vb6/Module1.bas new file mode 100644 index 00000000..466e4877 --- /dev/null +++ b/bugs/queryinterface-leaves-out-pointer/vb6/Module1.bas @@ -0,0 +1,89 @@ +Attribute VB_Name = "Module1" +Option Explicit + +' IUnknown::QueryInterface (slot 0 of an object's vtable), asked for an interface that the +' object does not have, called with DispCallFunc on the vtable. The output is preset to 12345, +' so that a call which leaves it alone shows. The twinBASIC side is in src/. +' Build and run it with: node scripts/bug_repro.mjs vb6 queryinterface-leaves-out-pointer + +Private Declare Function DispCallFunc Lib "oleaut32.dll" (ByVal pvInstance As Long, ByVal oVft As Long, ByVal cc As Long, ByVal vtReturn As Integer, ByVal cActuals As Long, prgvt As Integer, prgpvarg As Long, pvargResult As Variant) As Long + +Private Const CC_STDCALL As Long = 4 +Private Const VT_I4 As Integer = 3 +Private Const SENTINEL As Long = 12345 + +Sub Main() + On Error GoTo Fail + Open App.Path & "\out.txt" For Output As #9 + Ask "Class (Source.cls)", New Source + Ask "Collection", New Collection + Ask "Err", Err + Ask "Scripting.Dictionary", CreateObject("Scripting.Dictionary") + Close #9 + Exit Sub +Fail: + On Error Resume Next + Print #9, "unhandled error " & Err.Number & ": " & Err.Description + Close #9 +End Sub + +Private Sub Ask(ByVal Label As String, ByVal Obj As Object) + Dim missing(0 To 15) As Byte ' {00012345-0000-0000-0000-000000000000}: no object here has it + Dim iidUnknown(0 To 15) As Byte ' {00000000-0000-0000-C000-000000000046} + Dim iidDispatch(0 To 15) As Byte ' {00020400-0000-0000-C000-000000000046} + Dim p As Long + Dim hr As Long + Dim s As String + + missing(0) = &H45: missing(1) = &H23: missing(2) = &H1 + iidUnknown(8) = &HC0: iidUnknown(15) = &H46 + iidDispatch(1) = &H4: iidDispatch(2) = &H2: iidDispatch(8) = &HC0: iidDispatch(15) = &H46 + + p = SENTINEL + hr = QueryInterface(ObjPtr(Obj), VarPtr(missing(0)), p) ' the pointer of the Object variable + s = Label & ": missing interface: hr=" & Hex$(hr) & " out=" & CStr(p) + + p = SENTINEL + hr = QueryInterface(ObjPtr(Obj), VarPtr(iidUnknown(0)), p) + s = s & "; IUnknown: hr=" & Hex$(hr) & " out is a pointer=" & CStr(p <> SENTINEL And p <> 0) + GiveBack p + + p = SENTINEL + hr = QueryInterface(ObjPtr(Obj), VarPtr(iidDispatch(0)), p) + s = s & "; IDispatch: hr=" & Hex$(hr) & " out is a pointer=" & CStr(p <> SENTINEL And p <> 0) + GiveBack p + + Print #9, s +End Sub + +' Slot 0 of the vtable of pObj, called with DispCallFunc; returns its HRESULT. +Private Function QueryInterface(ByVal pObj As Long, ByVal pIid As Long, ByRef pOut As Long) As Long + Dim vals(0 To 1) As Variant + Dim vts(0 To 1) As Integer + Dim ptrs(0 To 1) As Long + Dim vRet As Variant + Dim hr As Long + + vals(0) = pIid + vals(1) = VarPtr(pOut) + vts(0) = VT_I4: vts(1) = VT_I4 + ptrs(0) = VarPtr(vals(0)): ptrs(1) = VarPtr(vals(1)) + vRet = CLng(0) + hr = DispCallFunc(pObj, 0, CC_STDCALL, VT_I4, 2, vts(0), ptrs(0), vRet) + If hr <> 0 Then + QueryInterface = hr + Else + QueryInterface = CLng(vRet) + End If +End Function + +' Releases the reference that a successful QueryInterface added: slot 2. +Private Sub GiveBack(ByVal p As Long) + Dim vts(0 To 0) As Integer + Dim ptrs(0 To 0) As Long + Dim vRet As Variant + + If p = 0 Or p = SENTINEL Then Exit Sub + vRet = CLng(0) + DispCallFunc p, 2 * 4, CC_STDCALL, VT_I4, 0, vts(0), ptrs(0), vRet +End Sub diff --git a/bugs/queryinterface-leaves-out-pointer/vb6/Probe.vbp b/bugs/queryinterface-leaves-out-pointer/vb6/Probe.vbp new file mode 100644 index 00000000..1e85b094 --- /dev/null +++ b/bugs/queryinterface-leaves-out-pointer/vb6/Probe.vbp @@ -0,0 +1,8 @@ +Type=Exe +Module=Module1; Module1.bas +Class=Source; Source.cls +Startup="Sub Main" +ExeName32="Probe.exe" +Name="Probe" +CompilationType=0 +OptimizationType=0 diff --git a/bugs/queryinterface-leaves-out-pointer/vb6/Source.cls b/bugs/queryinterface-leaves-out-pointer/vb6/Source.cls new file mode 100644 index 00000000..c12fdd80 --- /dev/null +++ b/bugs/queryinterface-leaves-out-pointer/vb6/Source.cls @@ -0,0 +1,16 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True + Persistable = 0 'NotPersistable + DataBindingBehavior = 0 'vbNone + DataSourceBehavior = 0 'vbNone + MTSTransactionMode = 0 'NotAnMTSObject +END +Attribute VB_Name = "Source" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = True +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = True +Option Explicit + +Public Value As Long diff --git a/docs/Reference/COM-Interfaces/IUnknown.md b/docs/Reference/COM-Interfaces/IUnknown.md index 9d16f57a..1dc8fa70 100644 --- a/docs/Reference/COM-Interfaces/IUnknown.md +++ b/docs/Reference/COM-Interfaces/IUnknown.md @@ -39,7 +39,12 @@ Syntax: *object*.**QueryInterface** *riid*, *ppvObject* *ppvObject* : *required* Receives the interface pointer, or a null pointer when the object does not support the interface. -Returns `S_OK` and a pointer with one reference added for the caller, or `E_NOINTERFACE` (`&H80004002`) and a null pointer. Three rules govern the answer: +Returns `S_OK` and a pointer with one reference added for the caller, or `E_NOINTERFACE` (`&H80004002`) and a null pointer. + +> [!NOTE] +> In BETA 1005, an object of a class that the project compiles --- a class, a form, a control --- returns `E_NOINTERFACE` for an interface it does not support but leaves *ppvObject* as it was, instead of setting it to a null pointer. The runtime's own objects, such as a **Collection**, set it to a null pointer, as every object of VB6 does. A caller tests the result, not the pointer. + +Three rules govern the answer: - **Identity.** Asking any interface of an object for **IUnknown** always returns the same pointer value. A caller finds out whether two interface pointers belong to one object by asking each for **IUnknown** and comparing the results. - **A fixed set.** If the object answers `S_OK` for an interface once, it never answers `E_NOINTERFACE` for it later, and the reverse. diff --git a/docs/Reference/Contributed/OLEGuids/IUnknownUnrestricted.md b/docs/Reference/Contributed/OLEGuids/IUnknownUnrestricted.md index 38b8ffb5..836354a7 100644 --- a/docs/Reference/Contributed/OLEGuids/IUnknownUnrestricted.md +++ b/docs/Reference/Contributed/OLEGuids/IUnknownUnrestricted.md @@ -30,7 +30,7 @@ Asks the object for another interface. *priid* is the address of the interface i A pointer that **QueryInterface** returns comes with a reference, which the caller gives back with **Release**. > [!NOTE] -> When the interface is not supported, the COM contract sets *ppvObject* to 0. An object of a twinBASIC class leaves the variable as it was (BETA 1005). Set the variable to 0 before the call, and test the result rather than the pointer. +> When the interface is not supported, the COM contract sets *ppvObject* to 0. In BETA 1005, an object of a class that the project compiles --- a class, a form, a control --- returns `E_NOINTERFACE` and leaves the variable as it was. The runtime's own objects, such as a **Collection**, set it to 0, as every object of VB6 does. Set the variable to 0 before the call, and test the result rather than the pointer. ```tb check_run Dim u As IUnknownUnrestricted = New Collection From ed8c507971ac255218d54b42db7cb6b698006b38 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 13 Oct 2026 20:04:58 +0200 Subject: [PATCH 9/9] ArrayListLib: moved to 1.6.151.1, still not compiling on BETA 1005 (TB5001 in VSwap and LSetByVal); BaseIndex, ToString and the optional-argument constructor; run in a corrected copy, with WARNINGs on For Each over an empty list, ToString, Reverse, GetEnumerator and Insert; still Experimental --- WIP.ExamplesBuild.md | 2 +- WIP.md | 2 +- .../Contributed/ArrayListLib/ArrayList.md | 225 ++++++++++++++---- .../Contributed/ArrayListLib/Comparer.md | 6 +- .../Contributed/ArrayListLib/Enumerator.md | 19 +- .../Contributed/ArrayListLib/Interfaces.md | 53 +++-- .../Contributed/ArrayListLib/ListRange.md | 66 +++-- .../Contributed/ArrayListLib/index.md | 42 ++-- docs/Reference/Experimental.md | 2 +- .../contributed/ArrayListLib/api.json | 81 ++++--- .../contributed-ArrayListLib/Settings | 6 +- 11 files changed, 361 insertions(+), 143 deletions(-) diff --git a/WIP.ExamplesBuild.md b/WIP.ExamplesBuild.md index 02871998..d8534142 100644 --- a/WIP.ExamplesBuild.md +++ b/WIP.ExamplesBuild.md @@ -335,7 +335,7 @@ the repository's to commit: four of the nine ship no licence file, and WinReg em until `contributed-WinReg` referenced WinDevLib (at the version WinReg embeds, 8.8.0.507) and the Scripting Runtime itself. The WinReg landing page tells the reader the same. - **An error inside an embedded package is the package's, and fails nothing** - (`splitPackageFaults`). WinReg 1.0.1.1 and ArrayListLib 1.4.0.134 do not compile on BETA + (`splitPackageFaults`). WinReg 1.0.1.1 and ArrayListLib 1.6.151.1 do not compile on BETA 1005: each passes a pointer to a `ByRef` structure parameter without `ByVal` (TB5001). The template-alone build finds those rows; the run prints them as a note naming the package rather than as a template FAIL, every other template row still fails, and the samples are diff --git a/WIP.md b/WIP.md index acb0ae57..9a36c2a0 100644 --- a/WIP.md +++ b/WIP.md @@ -34,7 +34,7 @@ sibling. History goes nowhere: git keeps it. Reference documentation is **complete** for all thirteen packages: VBA, VBRUN, VB, WebView2, Assert, CustomControls, CEF, WinEventLogLib, WinNamedPipesLib, WinServicesLib, tbIDE, WinNativeCommonCtls and AppGlobalClassObject. It is adapted from primary sources: Microsoft VBA-Docs (CC-BY-4.0) for the runtime library, `.twin` source for the twinBASIC-specific packages. The CEF and WebView2 packages also carry a tutorial set. -Twenty-six **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerLib, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsJson, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. The two console packages carry a NOTE (owner, 2026-10-12) that the samples harness cannot yet capture a console, so their console claims follow the source. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. VBComDlg's `exclude_from_docs` names `CommonDialogBase`, a module whose public procedures only its class calls. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modImpExp` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Xml, tbRegExp, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib and WinReg do not compile on BETA 1005 (each documented from its source alone, its samples compile-only; WinReg's methods were also run in a copy of its source with its four failing calls passed `ByVal`, and ReadString, GetRegistryValueType and the first ConvertValueToString give wrong results there), xCalendar has a grid that keeps the old month's dates when Value is set to another month, and text colour properties that have no effect, BigNumberLib 1.0.2.0 compiles, but loses the minus sign after `IsNegative = True` and in a negative operand of the greatest common divisor and the least common multiple, makes a zero from `AssignFromLong` negative, and does not return from the modular inverse of zero (all four confirmed by a second agent), TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_ConsoleComponents cannot take a cConsole of the published TBMANLIB_ConsoleCore (it embeds a copy of it, the same package at 1.0.0.1, whose types the compiler treats as different; checked in a project made by the IDE's own import, in either order), TBMANLIB_ConsoleCore reads keys wrongly, TBMANLIB_Diagnostic cannot create its Dictionary and misplaces List items, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text (and its XPath engine reads the step `..` and `and` with `or` wrongly), and tbRegExp, with **Global**, reports a match of no characters twice when the search skipped characters to reach it (a defect of the vba-regex engine it is made from, confirmed by a second agent). TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph, and the five types of its XPath engine (`XPathTokenType`, `XPathAxisType`, `XPathOperator`, `XPathStep`, `XPathPredicate`), which no public member takes or returns and which the mXPath page mentions in a paragraph; tsPwGen's names its five test modules and its six internal modules, and tsJson's its test and internal modules likewise, which their landing pages describe in a paragraph each. See *Where things live* for how a package moves between the two lists. +Twenty-six **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerLib, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsJson, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. The two console packages carry a NOTE (owner, 2026-10-12) that the samples harness cannot yet capture a console, so their console claims follow the source. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. VBComDlg's `exclude_from_docs` names `CommonDialogBase`, a module whose public procedures only its class calls. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modImpExp` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Xml, tbRegExp, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib and WinReg do not compile on BETA 1005 (each documented from its source and a copy of it that builds, its samples compile-only; ArrayListLib 1.6.151.1 was run in a copy with the arguments of its three `VSwap` calls passed `ByVal` and `LSetByVal` taking a `LongPtr`, where a For Each over a list with no items fails, ToString of such a list never returns, Reverse keeps the middle pair of an even run in place and a second GetEnumerator over a list ignores its arguments; WinReg's methods were also run in a copy of its source with its four failing calls passed `ByVal`, and ReadString, GetRegistryValueType and the first ConvertValueToString give wrong results there), xCalendar has a grid that keeps the old month's dates when Value is set to another month, and text colour properties that have no effect, BigNumberLib 1.0.2.0 compiles, but loses the minus sign after `IsNegative = True` and in a negative operand of the greatest common divisor and the least common multiple, makes a zero from `AssignFromLong` negative, and does not return from the modular inverse of zero (all four confirmed by a second agent), TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_ConsoleComponents cannot take a cConsole of the published TBMANLIB_ConsoleCore (it embeds a copy of it, the same package at 1.0.0.1, whose types the compiler treats as different; checked in a project made by the IDE's own import, in either order), TBMANLIB_ConsoleCore reads keys wrongly, TBMANLIB_Diagnostic cannot create its Dictionary and misplaces List items, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text (and its XPath engine reads the step `..` and `and` with `or` wrongly), and tbRegExp, with **Global**, reports a match of no characters twice when the search skipped characters to reach it (a defect of the vba-regex engine it is made from, confirmed by a second agent). TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph, and the five types of its XPath engine (`XPathTokenType`, `XPathAxisType`, `XPathOperator`, `XPathStep`, `XPathPredicate`), which no public member takes or returns and which the mXPath page mentions in a paragraph; tsPwGen's names its five test modules and its six internal modules, and tsJson's its test and internal modules likewise, which their landing pages describe in a paragraph each. See *Where things live* for how a package moves between the two lists. The rest of this file is the maintenance guide for updating existing pages or adding new ones: package surface notes, page conventions, and the integrity check. diff --git a/docs/Reference/Contributed/ArrayListLib/ArrayList.md b/docs/Reference/Contributed/ArrayListLib/ArrayList.md index 021db659..ce87a7da 100644 --- a/docs/Reference/Contributed/ArrayListLib/ArrayList.md +++ b/docs/Reference/Contributed/ArrayListLib/ArrayList.md @@ -10,7 +10,7 @@ has_toc: false An **ArrayList** is a list of **Variant** values that grows as items are added. It has the members of the .NET class `System.Collections.ArrayList` which a COM client can call, and it is written in twinBASIC, so it needs no .NET. -Items are numbered from 0. An item can be any value: a number, a string, an array or an object, and an object is stored as a reference. A list is created with **New**, as the class is not predeclared. [**Item**](#item) is the default member, so `list(0)` is the first item, and **For Each** goes through the items in order. +Items are numbered from [**BaseIndex**](#baseindex), which is 0 unless the list is made with another number or the property is assigned. An item can be any value: a number, a string, an array or an object, and an object is stored as a reference. A list is created with [**New**](#new), as the class is not predeclared. [**Item**](#item) is the default member, so `list(0)` is the first item of a list with the base 0, and **For Each** goes through the items in order. ```tb check_build Dim list As New ArrayList @@ -27,7 +27,7 @@ Debug.Print list.Count Debug.Print list.IndexOf("fig") ``` -**ArrayList** implements [**IArrayList**](Interfaces#iarraylist), which extends [**IListRange**](Interfaces#ilistrange), [**IList**](Interfaces#ilist), [**ICollection**](Interfaces#icollection) and [**IEnumerable**](Interfaces#ienumerable) in turn, and also implements [**ICloneable**](Interfaces#icloneable). A variable can be declared with the class or with any of these interfaces; an interface offers a smaller set of members. The class has one member that no interface has: assigning to [**Count**](#count). +**ArrayList** implements [**IArrayList**](Interfaces#iarraylist), which extends [**IListRange**](Interfaces#ilistrange), [**IList**](Interfaces#ilist), [**ICollection**](Interfaces#icollection) and [**IEnumerable**](Interfaces#ienumerable) in turn, and also implements [**ICloneable**](Interfaces#icloneable). A variable can be declared with the class or with any of these interfaces; an interface offers a smaller set of members. The class has one member that no interface has: assigning to [**Count**](#count). A **For Each** over a variable declared with an interface type raises an error: see [Known problems](./#known-problems). * TOC {:toc} @@ -41,7 +41,7 @@ Debug.Print list.IndexOf("fig") - A number matches a number of the same type. An **Integer** 1 does not match a **Long** 1, and neither matches a **Double** 1. - A string matches a string with the same characters. The letter case counts. - An object matches the same object, not an object that merely looks alike. -- **Null** matches **Null**. +- **Null** matches **Null**, and **Empty** matches **Empty**. ```tb check_build Dim list As New ArrayList @@ -55,7 +55,7 @@ Debug.Print list.IndexOf("1") ## Capacity and growth -The list keeps its items in an internal array. A new list has room for 2 items, and [**Capacity**](#capacity) returns the number of items the array holds. When an item is added to a full list, the capacity becomes twice the old capacity plus 1: 2, 5, 11, 23 and so on. [**Clear**](#clear) returns the capacity to 2, and [**Clone**](#clone) gives its copy a capacity equal to the number of items. Assigning to **Capacity** sets it exactly. +The list keeps its items in an internal array. A new list has room for 2 items, or for the number that [**New**](#new) was given, and [**Capacity**](#capacity) returns the number of items the array holds. When an item is added to a full list, the capacity becomes twice the old capacity plus 1: 2, 5, 11, 23 and so on. [**Clear**](#clear) returns the capacity to 2, and [**Clone**](#clone) gives its copy a capacity equal to the number of items. Assigning to **Capacity** sets it exactly. ```tb check_build Dim list As New ArrayList @@ -71,6 +71,27 @@ list.Clear Debug.Print list.Capacity ``` +## Index base + +The first item of a list has the number [**BaseIndex**](#baseindex), which is 0 for a list made with `New ArrayList`. A list made with `New ArrayList(, 1)` numbers its items from 1, as a Visual Basic **Collection** does. Every member that takes or returns the number of an item counts from the base: [**Item**](#item), [**Add**](#add), [**Insert**](#insert), [**IndexOf**](#indexof), [**LastIndexOf**](#lastindexof), [**RemoveAt**](#removeat), [**GetRange**](#getrange), [**BinarySearch**](#binarysearch) and the others that take an *Index*. [**ToArray**](#toarray) returns an array with the same lower bound. A [**ListRange**](ListRange) has the base of its list when it is made. + +Three things are counted from 0 in any case: the *Index* of [**GetEnumerator**](#getenumerator) and the positions of the [**Enumerator**](Enumerator) it returns, the array that [**Items**](#items) returns, and the *Index* of [**CopyTo**](#copyto) when the target is an array, which is an index of that array. + +```tb check_build +Dim list As ArrayList = New ArrayList(, 1) +list.Add "first" +list.Add "second" +list.Add "third" +Debug.Print list(1) +Debug.Print list.IndexOf("third") +Debug.Print list.BaseIndex +list.BaseIndex = 10 +Debug.Print list(10) +Dim a() As Variant +a = list.ToArray() +Debug.Print LBound(a) +``` + ## Enumerating **For Each** goes through the items from the first to the last, with a **Variant** as its variable. [**GetEnumerator**](#getenumerator) is the member that **For Each** calls; a program can call it with arguments to go through a part of the list, backwards, or in steps. [**Items**](#items), a hidden member, returns the items as an array. @@ -87,6 +108,9 @@ Next The enumerator reads the array of the list in place, and nothing checks that the list is unchanged while **For Each** runs. The documentation of the .NET class says that its enumerator is invalidated by a change to the list and raises an **InvalidOperationException**. A program should not add or remove items inside the loop. +> [!WARNING] +> A **For Each** over a list with no items raises error -2147467259, *Unspecified error*, and one that runs under `On Error Resume Next` never ends. A program has to test **Count** first. A second call of **GetEnumerator** on the same unchanged list, after the first loop ran to the end, ignores its *Index*, *GetCount* and *GetStep* arguments and gives the first loop's part again; a **For Each** with no arguments then gives that part too. See [Known problems](./#known-problems). + ## Ranges [**GetRange**](#getrange) returns a [**ListRange**](ListRange), a view of a run of items, not a copy. A change made through the view changes the list. A change made to the list invalidates the view, and its next use raises an error. See [ListRange](ListRange). @@ -103,19 +127,63 @@ The enumerator reads the array of the list in place, and nothing checks that the | 13 | Type mismatch | [**CopyTo**](#copyto), [**InsertRange**](#insertrange), [**AddRange**](#addrange) and [**SetRange**](#setrange) with something that is neither an array nor an object | | 6 | Overflow | [**CopyTo**](#copyto) with an array that is too small | | 438 | Object doesn't support this property or method | [**InsertRange**](#insertrange), [**AddRange**](#addrange) and [**SetRange**](#setrange) with an object that has no **Count** property | +| -2147467259 | Unspecified error | [**New**](#new) and [**Capacity**](#capacity) with a capacity of 0 or less; [**Clone**](#clone) of a list with no items; **For Each** over a list with no items | -The numbers are raised with the standard text of the run time. +Apart from the last row, the numbers are raised with the standard text of the run time. ## Differences from the .NET class - **Count.** The .NET property is read only. Here it can be assigned. -- **Sort.** Without a comparer, strings are compared by their characters, so an upper-case letter comes before a lower-case one. +- **BaseIndex.** The .NET class always numbers its items from 0. Here [**BaseIndex**](#baseindex) sets the number of the first item. +- **ToString.** The .NET class inherits **ToString** from **Object**, which returns the name of the type. Here [**ToString**](#tostring) writes the items as text. +- **Sort.** Without a comparer, strings are compared by their characters, so an upper-case letter comes before a lower-case one. The .NET class compares them as the current culture does, where `apple` comes before `Apple`. Items of different kinds, such as numbers and strings, make the .NET method raise an error; here they are put in the order of the `<` operator. +- **Capacity.** A new .NET list has room for no items, and doubles its room from 4 items on; **Clear** keeps it. Here a new list has room for 2, the room becomes twice the old room plus 1, and **Clear** returns it to 2. +- **Empty lists.** A **For Each** over a .NET list with no items makes no pass, and **Clone** of one works. Here both raise an error. - **Checks.** [**Insert**](#insert), [**InsertRange**](#insertrange), [**Sort**](#sort) and [**BinarySearch**](#binarysearch) do not check their index and count against the list, and the assignment to [**Capacity**](#capacity) does not check its value against **Count**. The documentation of the .NET class lists an **ArgumentOutOfRangeException** for an **Insert** index outside the list and for a **Capacity** below **Count**. - **Changes during a loop.** See [Enumerating](#enumerating). - **Parameters.** The count parameters are named *GetCount*, not *count*, and a named argument has to use that name. +## Constructors + +### New +{: .no_toc } + +Creates a list. + +Syntax: **New ArrayList** [ ( [ *Capacity* ] [ , *BaseIndex* ] ) ] + +*Capacity* +: *optional* A **Long**: the number of items the list has room for. The default is 2. A value of 0 or less raises error -2147467259 (*Unspecified error*). + +*BaseIndex* +: *optional* A **Long**: the number of the first item. The default is 0. + +**New** with arguments is a part of an expression, so the variable is declared first. `Dim list As New ArrayList(10)` is a syntax error; `Dim list As ArrayList = New ArrayList(10)` and `Set list = New ArrayList(10)` work. The arguments can be named or left out, as in `New ArrayList(, 1)`. + +```tb check_build +Dim a As ArrayList = New ArrayList(100) +Dim b As ArrayList = New ArrayList(, 1) +Dim c As ArrayList = New ArrayList(Capacity:=10, BaseIndex:=1) +Set a = New ArrayList(5) +Debug.Print a.Capacity & " " & b.BaseIndex & " " & c.Capacity +``` + ## Properties +### BaseIndex +{: .no_toc } + +The number of the first item. **Long**, read/write. + +Syntax: *object*.**BaseIndex** [ **=** *value* ] + +*value* +: A **Long**. The default is 0. + +Assigning a value renumbers the items: the first item has the number *value*, and the others follow. The items do not move. The assignment counts as a change of the list, so a [**ListRange**](ListRange) made before it is invalid afterwards. See [Index base](#index-base). + +[**IndexOf**](#indexof) and [**LastIndexOf**](#lastindexof) return -1 for an item they do not find. With a **BaseIndex** below 0, -1 can also be the number of an item, and [**Remove**](#remove) acts on it. See [Known problems](./#known-problems). + ### Capacity {: .no_toc } @@ -124,11 +192,11 @@ The number of items the list has room for. **Long**, read/write. Syntax: *object*.**Capacity** [ **=** *value* ] *value* -: A **Long**, at least [**Count**](#count). +: A **Long**, at least [**Count**](#count). A value of 0 or less raises error -2147467259. -A new list has a capacity of 2. The list grows by itself when an item is added to a full list, so setting the capacity is needed only to avoid repeated growth when the final size is known. The value is used exactly. +A new list has a capacity of 2, or the number that [**New**](#new) was given. The list grows by itself when an item is added to a full list, so setting the capacity is needed only to avoid repeated growth when the final size is known. The value is used exactly. -The assignment does not check *value* against [**Count**](#count). +The assignment does not check *value* against [**Count**](#count). With a value below **Count**, the items past the capacity cannot be read, and error 9 is raised for them. The assignment does not change the version number of the list, so an enumerator that a previous **For Each** left behind does not notice the move of the items; see [Known problems](./#known-problems). ### Count {: .no_toc } @@ -163,7 +231,7 @@ The item at a position. **Variant**, read/write, and the default member. Syntax: [ **Set** ] *object*.**Item** ( *index* ) [ **=** *value* ] *index* -: A **Long** from 0 to **Count** - 1. A value outside the list raises error 9. +: A **Long** from [**BaseIndex**](#baseindex) to **BaseIndex** + **Count** - 1. A value outside the list raises error 9. *value* : The new item. Use **Set** to store an object reference. @@ -193,7 +261,7 @@ Syntax: *object*.**Add** ( *value* ) **As Long** *value* : *required* The item: any **Variant**, an object included. -Returns the index of the new item, which is the value of [**Count**](#count) before the call. +Returns the index of the new item, which is [**BaseIndex**](#baseindex) plus the value of [**Count**](#count) before the call. ```tb check_build Dim list As New ArrayList @@ -233,7 +301,7 @@ Searches a sorted run of items and returns the index of a value. Syntax: *object*.**BinarySearch** ( *Index*, *GetCount*, *Value* [ , *Comparer* ] ) **As Long** *Index* -: *required* A **Long**: the index of the first item to search. +: *required* A **Long**: the index of the first item to search, counted from [**BaseIndex**](#baseindex). *GetCount* : *required* A **Long**: the number of items to search. @@ -244,7 +312,7 @@ Syntax: *object*.**BinarySearch** ( *Index*, *GetCount*, *Value* [ , *Comparer* *Comparer* : *optional* An object that implements [**IComparer**](Interfaces#icomparer). Without it, the items are compared with the `<` operator, and equality with `=`. -Returns the index of a matching item. When there is none, the result is negative: **Not** *result* is the index of the first item that is larger than *Value*, or **Count** if there is none. When several items match, the result is the index of any one of them. +Returns the index of a matching item. When there is none, the result is negative: **Not** *result* is the index of the first item that is larger than *Value*, or **BaseIndex** + **Count** if there is none. When several items match, the result is the index of any one of them. The items from *Index* must be sorted in the order that *Comparer*, or the `<` operator, gives. The method does not check *Index* and *GetCount* against the list. @@ -273,7 +341,7 @@ Returns a copy of the list. Syntax: *object*.**Clone** ( ) **As Variant** -Returns a **Variant** that holds a new **ArrayList** with the same items. The copy is shallow: an object among the items is shared with the copy, not duplicated. **Clone** is the member of [**ICloneable**](Interfaces#icloneable). +Returns a **Variant** that holds a new **ArrayList** with the same items and the same [**BaseIndex**](#baseindex). The copy is shallow: an object among the items is shared with the copy, not duplicated. **Clone** is the member of [**ICloneable**](Interfaces#icloneable), which **ArrayList** implements; the interface **IArrayList** does not have it, so a variable of that type needs an **ICloneable** variable to call it. ```tb check_build Dim a As New ArrayList @@ -285,6 +353,9 @@ Debug.Print a.Count Debug.Print b.Count ``` +> [!NOTE] +> **Clone** of a list with no items raises error -2147467259, *Unspecified error*. + ### Contains {: .no_toc } @@ -306,7 +377,7 @@ Syntax: *object*.**CopyTo** ( *Target*, *Index* ) : *required* An array of any type, or an object that implements [**IListRange**](Interfaces#ilistrange), such as an **ArrayList**. The items are assigned one by one to the elements of an array of another type than **Variant**. *Index* -: *required* A **Long**: the index in *Target* of the first item copied. +: *required* A **Long**: the index in *Target* of the first item copied. For a list it is counted from the **BaseIndex** of *Target*. The array is filled in place; it keeps its size. When *Target* is a list, its items from *Index* are replaced and its count does not change, as for [**SetRange**](#setrange). @@ -329,7 +400,7 @@ Returns the enumerator that **For Each** uses, optionally for a part of the list Syntax: *object*.**GetEnumerator** ( [ *Index* ] [ , *GetCount* ] [ , *GetStep* ] [ , *ThisEnumerator* ] ) **As stdole.IUnknown** *Index* -: *optional* A **Long**: the index of the first item. The default is 0. +: *optional* A **Long**: the index of the first item, counted from 0 whatever the [**BaseIndex**](#baseindex) of the list is. The default is 0. *GetCount* : *optional* The number of items in the part to go through. The default is all the items from *Index*. @@ -338,22 +409,29 @@ Syntax: *object*.**GetEnumerator** ( [ *Index* ] [ , *GetCount* ] [ , *GetStep* : *optional* A **Long**: how far the enumerator moves for each item. The default is 1. A negative value goes backwards, starting at the last item of the part. *ThisEnumerator* -: *optional* An [**IEnumerator**](Interfaces#ienumerator) variable that receives the enumerator, so that the loop can change its position, step or size. See [**Enumerator**](Enumerator). +: *optional* An [**IEnumerator**](Interfaces#ienumerator) variable that receives the enumerator, so that the loop can change its position, step or size. The positions of that enumerator are counted from 0 as well. See [**Enumerator**](Enumerator). -**For Each** calls this method without arguments. Calling it with arguments inside the loop header goes through part of the list. The method is marked [**[Enumerator]**](../../../Core/Attributes#enumerator). The result is an [**Enumerator**](Enumerator) object. +**For Each** calls this method without arguments. Calling it with arguments inside the loop header goes through part of the list. The method is marked [**[Enumerator]**](../../../Core/Attributes#enumerator). The result is an [**Enumerator**](Enumerator) object, or nothing when *GetCount* is 0 or less, which is the case for every call of a list with no items. The method does not check *GetCount* against the number of items. +The list keeps two enumerators and returns the first one whenever it is not in use, binding it again only when the list has changed. + +> [!WARNING] +> After a **For Each** over the list ran to the end, and while the list is unchanged, a call with other values of *Index*, *GetCount* and *GetStep* gives the enumerator of the first call. A **For Each** over the list with no arguments then goes through the part of the first loop, not through all the items. A loop that was left with **Exit For** does not do this, as it leaves the first enumerator in use. A third loop nested inside two others, over the same list, gives wrong items. Assigning to [**Capacity**](#capacity) does not count as a change of the list, so the next loop can read memory that the items have left. The loops in the example below are over lists of their own for this reason. + ```tb check_build -Dim list As New ArrayList -list.AddRange Array("a", "b", "c", "d", "e") +Dim backwards As New ArrayList +backwards.AddRange Array("a", "b", "c", "d", "e") +Dim everyOther As New ArrayList +everyOther.AddRange Array("a", "b", "c", "d", "e") Dim v As Variant -For Each v In list.GetEnumerator(, , -1) +For Each v In backwards.GetEnumerator(, , -1) Debug.Print v Next Debug.Print "--" -For Each v In list.GetEnumerator(0, , 2) +For Each v In everyOther.GetEnumerator(0, , 2) Debug.Print v Next ``` @@ -366,14 +444,14 @@ Returns a view of a run of items. Syntax: *object*.**GetRange** ( *Index*, *GetCount* ) **As IListRange** *Index* -: *required* A **Long**: the index of the first item of the run. +: *required* A **Long**: the index of the first item of the run, counted from [**BaseIndex**](#baseindex). *GetCount* : *required* A **Long**: the number of items in the run. -Returns a [**ListRange**](ListRange), as an [**IListRange**](Interfaces#ilistrange). The view shares the items of the list, so changing an item in the view changes the list. Error 9 is raised when the run does not lie in the list. +Returns a [**ListRange**](ListRange), as an [**IListRange**](Interfaces#ilistrange). The view shares the items of the list, so changing an item in the view changes the list. The view has the **BaseIndex** that the list has now. Error 9 is raised when the run does not lie in the list. A change made through the view can leave it reading released memory: see [A range after the list grows](ListRange#a-range-after-the-list-grows). -The type **IListRange** has no **Reverse**, **LastIndexOf**, **Capacity**, **Clone** or **Items**; the type **IArrayList** has them. +The type **IListRange** has no **Reverse**, **LastIndexOf**, **Capacity**, **BaseIndex** or **Items**; the type **IArrayList** has them. Neither of them has **Clone**, which belongs to **ICloneable**. ```tb check_build Dim list As New ArrayList @@ -401,12 +479,12 @@ Syntax: *object*.**IndexOf** ( *Value* [ , *Index* [ , *GetCount* ] ] ) **As Lon : *required* The value to look for. See [Items and equality](#items-and-equality). *Index* -: *optional* A **Long**: the index where the search starts. The default is 0. +: *optional* A **Long**: the index where the search starts. The default is [**BaseIndex**](#baseindex). The parameter is declared as a **Variant**, so that a call can leave it out and give *GetCount*. *GetCount* : *optional* The number of items to search. The default is all the items from *Index*. -Returns -1 when no item matches. Error 9 is raised when *Index* is above **Count**, *GetCount* is negative, or the run does not fit in the list. +Returns -1 when no item matches. Error 9 is raised when *Index* is above **BaseIndex** + **Count**, *GetCount* is negative, or the run does not fit in the list. ### Insert {: .no_toc } @@ -416,18 +494,21 @@ Inserts an item at a position and moves the items from there on up by one. Syntax: *object*.**Insert** ( *Index*, *Value* ) *Index* -: *required* A **Long** from 0 to **Count**. With **Count**, the item is added at the end. +: *required* A **Long** from [**BaseIndex**](#baseindex) to **BaseIndex** + **Count**. With **BaseIndex** + **Count**, the item is added at the end. *Value* : *required* The item. -*Index* is not checked against **Count**. The method moves **Count** - *Index* items up to make room, so the number is negative for a value above **Count**. See [Known problems](./#known-problems). +*Index* is not checked against the list. The method moves **Count** + **BaseIndex** - *Index* items up to make room, so the number is negative for a value above the end, and the program can crash. See [Known problems](./#known-problems). + +> [!WARNING] +> **Insert** at **BaseIndex**, the first position of a list that has items, raises error 9 unless the list has room for 31 more items than it holds (its [**Capacity**](#capacity) is at least **Count** + 31). Give [**New**](#new) a capacity, or assign **Capacity**, to avoid the error. ```tb check_build Dim list As New ArrayList list.AddRange Array("a", "c") list.Insert 1, "b" -list.Insert 0, "start" +list.Insert list.Count, "d" Dim v As Variant For Each v In list Debug.Print v @@ -442,7 +523,7 @@ Inserts the items of an array or of another collection at a position. Syntax: *object*.**InsertRange** ( *Index*, *Target* ) *Index* -: *required* A **Long** from 0 to **Count**. The items are inserted in front of the item that is at *Index* now. As for [**Insert**](#insert), the value is not checked. +: *required* A **Long** from [**BaseIndex**](#baseindex) to **BaseIndex** + **Count**. The items are inserted in front of the item that is at *Index* now. As for [**Insert**](#insert), the value is not checked. An *Index* of **BaseIndex** with a *Target* of exactly one item raises error 9, as [**Insert**](#insert) does, unless the list has room for 31 more items than it holds; a *Target* of two or more items does not. *Target* : *required* The items to insert: an array or an object, as described below. @@ -476,12 +557,12 @@ Syntax: *object*.**LastIndexOf** ( *Value* [ , *Index* [ , *GetCount* ] ] ) **As : *required* The value to look for. *Index* -: *optional* The index where the search starts, going towards the start of the list. The default is the last item. +: *optional* The index where the search starts, going towards the start of the list. The default is the last item. The parameter is declared as a **Variant**, so that a call can leave it out and give *GetCount*. *GetCount* : *optional* The number of items to search, counted back from *Index*. The default is all the items from *Index* down to the first. -Returns -1 when no item matches. Error 9 is raised when *Index* is **Count** or more, or *GetCount* is more than *Index* + 1. +Returns -1 when no item matches. Error 9 is raised when *Index* is **BaseIndex** + **Count** or more, or *GetCount* is more than *Index* - **BaseIndex** + 1. ```tb check_build Dim list As New ArrayList @@ -503,6 +584,9 @@ Syntax: *object*.**Remove** ( *Value* ) Nothing happens, and no error is raised, when no item matches. +> [!WARNING] +> With a [**BaseIndex**](#baseindex) below 0, **Remove** of a value that is not in the list removes the item at index -1, or raises error 9 when the list has no such item. The method takes the -1 that [**IndexOf**](#indexof) returns for a missing value as the index of an item. + ### RemoveAt {: .no_toc } @@ -511,7 +595,7 @@ Removes the item at a position and moves the later items down by one. Syntax: *object*.**RemoveAt** ( *Index* ) *Index* -: *required* A **Long** from 0 to **Count** - 1. A value outside the list raises error 9. +: *required* A **Long** from [**BaseIndex**](#baseindex) to **BaseIndex** + **Count** - 1. A value outside the list raises error 9. ### RemoveRange {: .no_toc } @@ -521,7 +605,7 @@ Removes a run of items. Syntax: *object*.**RemoveRange** ( *Index*, *GetCount* ) *Index* -: *required* A **Long**: the index of the first item to remove. +: *required* A **Long**: the index of the first item to remove, counted from [**BaseIndex**](#baseindex). *GetCount* : *required* A **Long**: the number of items to remove. @@ -536,14 +620,17 @@ Reverses the order of the items, or of a run of them. Syntax: *object*.**Reverse** ( [ *Index* ] [ , *GetCount* ] ) *Index* -: *optional* A **Long**: the index of the first item of the run. The default is 0. +: *optional* A **Long**: the index of the first item of the run. The default is [**BaseIndex**](#baseindex). The parameter is declared as a **Variant**, so that a call can leave it out and give *GetCount*. *GetCount* : *optional* The number of items in the run. The default is all the items from *Index*. Error 9 is raised when the run does not lie in the list. -The loop runs for *i* from 0 up to and including half of *GetCount*, rounded down, and swaps item *Index* + *i* with item *Index* + *GetCount* - 1 - *i*. For an even *GetCount* the last pass swaps the middle pair a second time, so those two items keep their order; for a *GetCount* of 0 the loop still runs once. See [Known problems](./#known-problems). +The loop runs for *i* from 0 up to and including half of *GetCount*, rounded down, and swaps item *Index* + *i* with item *Index* + *GetCount* - 1 - *i*. For an even *GetCount* the last pass swaps the middle pair a second time, so those two items keep their order; for a *GetCount* of 0 the loop still runs once. + +> [!WARNING] +> A run with an even number of items is not reversed correctly: its two middle items stay where they are, so `a b c d` becomes `d b c a` and a run of two items does not change at all. A run of an odd number of items is reversed correctly. With a *GetCount* of 0, and so for a list with no items, the single pass swaps the item at *Index* with the one before it; for a list with no items that writes outside the array that holds the items and damages the memory, and a later growth of the list failed. See [Known problems](./#known-problems). ```tb check_build Dim list As New ArrayList @@ -563,7 +650,7 @@ Replaces the items from a position with the items of an array or of another coll Syntax: *object*.**SetRange** ( *Index*, *Target* ) *Index* -: *required* A **Long**: the index of the first item to replace. +: *required* A **Long**: the index of the first item to replace, counted from [**BaseIndex**](#baseindex). *Target* : *required* An array, or an object. The kinds are those of [**InsertRange**](#insertrange). @@ -588,7 +675,7 @@ Sorts the items, or a run of them. Syntax: *object*.**Sort** ( [ *Index* ] [ , *GetCount* ] [ , *Comparer* ] ) *Index* -: *optional* A **Long**: the index of the first item of the run. The default is 0. +: *optional* A **Long**: the index of the first item of the run. The default is [**BaseIndex**](#baseindex). The parameter is declared as a **Variant**, so that a call can leave it out and give *GetCount*. *GetCount* : *optional* The number of items in the run. The default is all the items from *Index*. @@ -596,9 +683,9 @@ Syntax: *object*.**Sort** ( [ *Index* ] [ , *GetCount* ] [ , *Comparer* ] ) *Comparer* : *optional* An object that implements [**IComparer**](Interfaces#icomparer). Without it, the items are compared with the `<` operator. -The order is ascending. To sort in the other direction, give a comparer that reverses its result, or call [**Reverse**](#reverse) afterwards. The method does not check *Index* and *GetCount* against the list. +The order is ascending. To sort in the other direction, give a comparer that reverses its result, or call [**Reverse**](#reverse) afterwards, which keeps the two middle items of an even number in place. The method does not check *Index* and *GetCount* against the list: a run that reaches past the last item takes in places that hold nothing, which are sorted in front of the items, and moves items out of the list. -Items of different kinds, such as numbers and strings, are put in the order that the `<` operator gives for **Variant** values. +Items of different kinds, such as numbers and strings, are put in the order that the `<` operator gives for **Variant** values. A **Null** or an **Empty** among the items does not take a place in that order, and the result is not sorted. ```tb check_build projname=arraylist-sort slot=file Class LengthComparer @@ -628,7 +715,7 @@ Returns the items as an array. Syntax: *object*.**ToArray** ( ) **As Variant()** -Returns a new array of **Variant**, with the lower bound 0. The array is a copy; an object among the items is shared with it. A list with no items gives an array with no elements, with the upper bound -1. +Returns a new array of **Variant**, with the lower bound [**BaseIndex**](#baseindex). The array is a copy; an object among the items is shared with it. A list with no items gives an array with no elements, with the bounds 0 and -1, whatever **BaseIndex** is. ```tb check_build Dim list As New ArrayList @@ -641,6 +728,45 @@ a = list.ToArray() Debug.Print UBound(a) ``` +### ToString +{: .no_toc } + +Returns the items as text, in the notation of a JSON array. + +Syntax: *object*.**ToString** ( [ *TextQualifier* ] [ , *UseNullAsEmpty* ] ) **As String** + +*TextQualifier* +: *optional* A **String** written before and after each string and each date. Where it occurs in a string, it is written twice. The default is a double quote, `"`. An empty string writes strings and dates without any mark. + +*UseNullAsEmpty* +: *optional* A **Boolean**. When **True**, which is the default, an **Empty** item is written as `null`. When **False**, it is written as nothing. + +Returns the items between square brackets, separated by a comma and a space. A value is written as follows: + +| Value | Text | +|---|---| +| a number | the number as **Str** writes it, without the leading space, with a point as the decimal separator | +| a **Boolean** | `True` or `False` | +| a **String** | the characters between two *TextQualifier* | +| a **Date** | the date and time in UTC, in the form `yyyy-mm-ddThh:mm:ss.000Z`, between two *TextQualifier*. The offset of the time zone is the one the system has now, not the one the date had | +| **Null** | `null` | +| **Empty** | `null`, or nothing with *UseNullAsEmpty* **False** | +| an array, a list or any other object that **For Each** can go through | its items, written in the same way between square brackets | + +The text is not valid JSON where a value is written as `True` or `False`, or where *TextQualifier* is not a double quote. The numbering of the items does not change the text. + +Through a variable of type **IListRange** or **IArrayList** both arguments are required. + +```tb check_build +Dim list As New ArrayList +list.AddRange Array(1, 2.5, "text", "say ""hi""", Null, True) +Debug.Print list.ToString() +Debug.Print list.ToString("'") +``` + +> [!WARNING] +> **ToString** never returns for a list with no items, for a nested list with no items, and for an item that is an object that **For Each** cannot go through, such as an instance of a class of the program. See [Known problems](./#known-problems). + ## Hidden members These members do not appear in IntelliSense. They can be called all the same. COM has no overloaded methods, so the members with a number in their name, from **BinarySearch_2** to **Sort_2**, stand for overloads of the .NET class; the README of the package gives this reason. The others are properties that a list has to offer for compatibility, and the two that serve the package itself, **Items** and **Version**. @@ -648,14 +774,14 @@ These members do not appear in IntelliSense. They can be called all the same. CO ### BinarySearch_2 {: .no_toc } -Searches the whole list with the `<` operator. The same as **BinarySearch** (0, **Count**, *Value*). +Searches the whole list with the `<` operator. The same as **BinarySearch** (**BaseIndex**, **Count**, *Value*). Syntax: *object*.**BinarySearch_2** ( *Value* ) **As Long** ### BinarySearch_3 {: .no_toc } -Searches the whole list with a comparer. The same as **BinarySearch** (0, **Count**, *Value*, *Comparer*). +Searches the whole list with a comparer. The same as **BinarySearch** (**BaseIndex**, **Count**, *Value*, *Comparer*). Syntax: *object*.**BinarySearch_3** ( *Value*, *Comparer* ) **As Long** @@ -695,7 +821,7 @@ Returns the items as an array that shares the memory of the list. Syntax: *object*.**Items** ( ) **As Variant()** -The array is not a copy, and its elements are not counted as references to the objects they hold. It is meant for one use only, as the subject of **For Each**: +The array is not a copy, and its elements are not counted as references to the objects they hold. Its lower bound is 0, whatever [**BaseIndex**](#baseindex) is. It is meant for one use only, as the subject of **For Each**: ```tb check_build Dim list As New ArrayList @@ -715,7 +841,10 @@ The same as **LastIndexOf** with the value and the index. Syntax: *object*.**LastIndexOf_2** ( *Value*, *Index* ) **As Long** -Searches from *Index* back to the first item. +Searches from *Index* back to the first item. It calls **LastIndexOf** with a *GetCount* of *Index* + 1, which is the number of items only when [**BaseIndex**](#baseindex) is 0. + +> [!NOTE] +> With a **BaseIndex** above 0, **LastIndexOf_2** raises error 9, and with a **BaseIndex** below 0 it searches only *Index* + 1 items, counted back from *Index*, and so can miss an item. ### LastIndexOf_3 {: .no_toc } @@ -727,7 +856,7 @@ Syntax: *object*.**LastIndexOf_3** ( *Value*, *Index*, *GetCount* ) **As Long** ### Sort_2 {: .no_toc } -Sorts the whole list with a comparer. The same as **Sort** 0, , *Comparer*. +Sorts the whole list with a comparer. The same as **Sort** **BaseIndex**, , *Comparer*. Syntax: *object*.**Sort_2** ( *Comparer* ) @@ -739,7 +868,7 @@ Always **Nothing**. **Variant**, read-only. The .NET class returns an object her ### Version {: .no_toc } -A number that changes each time the list changes. **Long**, read-only. A [**ListRange**](ListRange) compares it with the value it saw when it was created, to find out whether the list has changed since. It changes for the assignment to **Item** and **Count**, and for **Add**, **AddRange**, **Clear**, **Insert**, **InsertRange**, **Remove**, **RemoveAt**, **RemoveRange**, **Reverse**, **SetRange** and **Sort**. +A number that changes each time the list changes. **Long**, read-only. A [**ListRange**](ListRange) compares it with the value it saw when it was created, to find out whether the list has changed since. It changes for the assignment to **Item**, **Count** and **BaseIndex**, and for **Add**, **AddRange**, **Clear**, **Insert**, **InsertRange**, **Remove**, **RemoveAt**, **RemoveRange**, **Reverse**, **SetRange** and **Sort**. The assignment to **Capacity** does not change it, although it can move the items. ## See Also diff --git a/docs/Reference/Contributed/ArrayListLib/Comparer.md b/docs/Reference/Contributed/ArrayListLib/Comparer.md index 3177d9fd..b5758cc8 100644 --- a/docs/Reference/Contributed/ArrayListLib/Comparer.md +++ b/docs/Reference/Contributed/ArrayListLib/Comparer.md @@ -25,11 +25,11 @@ Debug.Print Comparer.Compare(#1/2/2026#, #1/1/2026#) **Compare** takes the first rule that applies: -1. It subtracts the second value from the first and takes the sign of the difference ([**Sgn**](../../../Modules/Math/Sgn)). This is the comparison of numbers and of dates. -2. When the subtraction raises an error, two values that are not objects are compared with the `<` and `>` operators. The comparison of strings is by their characters, so an upper-case letter is smaller than a lower-case one. +1. It subtracts the second value from the first and takes the sign of the difference ([**Sgn**](../../../Modules/Math/Sgn)). This is the comparison of numbers and of dates. Two strings that both look like numbers are compared as numbers too, so `"10"` is greater than `"9"`, and a **Boolean** counts as its number, so **True** (-1) is smaller than **False** (0). +2. When the subtraction raises an error, two values that are not objects are compared with the `<` and `>` operators. The comparison of strings is by their characters, so an upper-case letter is smaller than a lower-case one. A text is greater than a number, and a **Null** is equal to every value. 3. When one of the two values is an array, the first element of the array stands for it: **Compare** compares `X(0)` with `Y(0)`, or with the other value when it is not an array. 4. When the first value is an object, the result is that of its **CompareTo** method called with the second value. The result is returned as the object gives it, not reduced to -1, 0 or 1. -5. When the comparison of rule 2, 3 or 4 raises an error, the result is 0: the two values count as equal. This is the result for an object with no **CompareTo**, and for two values that cannot be compared with each other. +5. An error raised by the comparison of rule 2, 3 or 4 reaches the caller of **Compare**, and a **Sort** or **BinarySearch** that called it stops with the same error. Errors come from an object with no **CompareTo** (error 438), from a value compared with such an object, from an array with no elements (error 9) and from a **CompareTo** that raises one. ## Writing a comparer diff --git a/docs/Reference/Contributed/ArrayListLib/Enumerator.md b/docs/Reference/Contributed/ArrayListLib/Enumerator.md index dffb0e72..d05899c6 100644 --- a/docs/Reference/Contributed/ArrayListLib/Enumerator.md +++ b/docs/Reference/Contributed/ArrayListLib/Enumerator.md @@ -43,7 +43,7 @@ For Each v In list.GetEnumerator(, , , e) Next ``` -**Next** adds the step to the position before it reads an item, except for the first item, where the step to add is 0. **Reset** sets the position but not that step. After an item has been returned, the step is the enumerator's own, so the first **Next** after a **Reset** moves past the first item. See [Known problems](./#known-problems). +**Next** adds the step to the position before it reads an item, except for the first item, where the step to add is 0. **Reset** and [**Bind**](#bind) set the position to the first item of the part and make the step to add 0, so the first **Next** after either of them returns the first item. Nothing checks that the array is unchanged during the loop. See [Enumerating](ArrayList#enumerating). @@ -71,7 +71,7 @@ Syntax: **New Enumerator** [ ( *TargetArray* [ , *Index* [ , *GetCount* [ , *Get *ThisEnumerator* : *optional* An [**IEnumerator**](Interfaces#ienumerator) variable that receives the new enumerator. -Without arguments, the enumerator has no array; call [**Bind**](#bind) to give it one. With arguments, it is bound by **Bind** at once. +Without arguments, the enumerator has no array; call [**Bind**](#bind) to give it one. Until then its [**CurrentSize**](#currentsize) and [**CurrentStep**](#currentstep) are 0, and **Next** reports the end of the part at once. With arguments, it is bound by **Bind** at once. ## Properties @@ -109,6 +109,15 @@ Syntax: *object*.**CurrentStep** [ **=** *value* ] A step of 0 returns the same item for ever, so a loop with it does not end. +### IsAvailable +{: .no_toc } + +Whether the enumerator is free for a new loop. **Boolean**, read-only. + +Syntax: *object*.**IsAvailable** + +The value is **True** for a new enumerator. It becomes **False** when [**Reset**](#reset) is called, which **For Each** does at the start of a loop, and **True** again when a call of **Next** reaches the end of the part. A loop left with **Exit For** leaves it **False**. The property belongs to the class **Enumerator**; the interface **IEnumerator** does not have it. [**GetEnumerator**](ArrayList#getenumerator) of a list uses it to find out whether the enumerator it kept can serve another loop. + ## Methods ### Bind @@ -130,7 +139,7 @@ Syntax: *object*.**Bind** ( *TargetArray*, *Index*, *GetCount*, *GetStep* ) *GetStep* : *required* A **Long**: how far the position moves for each item. -The enumerator reads the memory of the array in place, so the array must not be resized or erased while the enumerator is in use. For a negative *GetStep*, the position is set to the last item of the part. For a positive one, **Bind** leaves the position and the step that **Next** adds as they are. +The enumerator reads the memory of the array in place, so the array must not be resized or erased while the enumerator is in use. The position is set to the first item of the part, or to the last one for a negative *GetStep*, and the step that **Next** adds becomes 0, so the next **Next** returns that item. **Bind** does not change [**IsAvailable**](#isavailable). ### GetEnumerator {: .no_toc } @@ -182,7 +191,7 @@ Returns the next item. Syntax: *object*.**Next** ( *celt*, *rgvar*, *pceltFetched* ) *celt* -: *required* A **Long**, which has to be 1. Any other value fails with the error code `E_INVALIDARG` (&H80070057). +: *required* A **Long**, which has to be 1. Any other value fails with the error code `E_INVALIDARG` (&H80070057), which a program that calls the member through an **IEnumerator** variable sees as error 5. *rgvar* : *required* A **Variant** that receives the item. An object is stored as a reference. @@ -199,7 +208,7 @@ Goes back to the start of the part. Syntax: *object*.**Reset** ( ) -The position becomes 0, or the last item of the part for a negative step. **For Each** calls **Reset** before the first **Next**. The step that **Next** adds is not cleared: see [Position, step and size](#position-step-and-size). +The position becomes 0, or the last item of the part for a negative step, and the step that **Next** adds becomes 0, so the first **Next** after it returns the first item. **For Each** calls **Reset** before the first **Next**. [**IsAvailable**](#isavailable) becomes **False**. ### Skip {: .no_toc } diff --git a/docs/Reference/Contributed/ArrayListLib/Interfaces.md b/docs/Reference/Contributed/ArrayListLib/Interfaces.md index 76519d8a..2f326c04 100644 --- a/docs/Reference/Contributed/ArrayListLib/Interfaces.md +++ b/docs/Reference/Contributed/ArrayListLib/Interfaces.md @@ -15,13 +15,17 @@ IEnumerable ICollection IList IListRange - IArrayList (also implements ICloneable) + IArrayList +ICloneable IEnumVARIANT IEnumerator IComparer ``` -[**ArrayList**](ArrayList) and [**ListRange**](ListRange) implement **IArrayList**, so a variable of any of these types can hold a list. A function that only reads items can take an **IList**; a function that sorts or takes parts can take an **IListRange**. [**Enumerator**](Enumerator) implements **IEnumerator**, and [**Comparer**](Comparer) implements **IComparer**. +[**ArrayList**](ArrayList) and [**ListRange**](ListRange) implement **IArrayList** and **ICloneable**, so a variable of any of these types can hold a list. A function that only reads items can take an **IList**; a function that sorts or takes parts can take an **IListRange**. [**Enumerator**](Enumerator) implements **IEnumerator**, and [**Comparer**](Comparer) implements **IComparer**. + +> [!WARNING] +> A **For Each** over a variable declared as **IList**, **ICollection**, **IEnumerable**, **IListRange** or **IArrayList** raises error -2147352567, *Exception occurred.* A call of **GetEnumerator** through such a variable with fewer than all four arguments does not compile. A variable declared as **ArrayList**, **ListRange**, **Object** or **Variant** can be used in a **For Each**; a function that takes an interface can loop over the items with an index. See [Known problems](./#known-problems). ```tb check_build Dim items As IList = New ArrayList @@ -29,9 +33,9 @@ items.Add "a" items.Add "b" Debug.Print items.Count Debug.Print items.Item(1) -Dim v As Variant -For Each v In items - Debug.Print v +Dim i As Long +For i = 0 To items.Count - 1 + Debug.Print items.Item(i) Next ``` @@ -51,7 +55,7 @@ Returns the enumerator that **For Each** uses. Syntax: *object*.**GetEnumerator** ( [ *Index* ] [ , *GetCount* ] [ , *GetStep* ] [ , *ThisEnumerator* ] ) **As stdole.IUnknown** -The member is marked [**[Enumerator]**](../../../Core/Attributes#enumerator). The parameters are those of [**GetEnumerator**](ArrayList#getenumerator) of **ArrayList**; here *ThisEnumerator* is declared **As Any**, an untyped reference. +The member is marked [**[Enumerator]**](../../../Core/Attributes#enumerator). The parameters are those of [**GetEnumerator**](ArrayList#getenumerator) of **ArrayList**; here *ThisEnumerator* is declared **As Any**, an untyped reference. Called through a variable of an interface type, the member does not compile with fewer than all four arguments (TB5050), and a **For Each** that calls it raises error -2147352567. ## ICollection @@ -264,9 +268,33 @@ Syntax: *object*.**ToArray** ( ) **As Variant()** See [**ToArray**](ArrayList#toarray). +### ToString +{: #ilistrange-tostring .no_toc } + +Returns the items as text. + +Syntax: *object*.**ToString** ( *TextQualifier*, *UseNullAsEmpty* ) **As String** + +*TextQualifier* +: *required* A **String**. + +*UseNullAsEmpty* +: *required* A **Boolean**. + +See [**ToString**](ArrayList#tostring). Through the interface both arguments are required; the classes declare them optional. + ## IArrayList -Extends **IListRange** and implements **ICloneable**. The interface of a whole list. Interface identifier `1043D433-27BB-40F7-9255-50F4724FAB94`. +Extends **IListRange**. The interface of a whole list. It does not have **Clone**, which belongs to [**ICloneable**](#icloneable). Interface identifier `1043D433-27BB-40F7-9255-50F4724FAB94`. + +### BaseIndex +{: #iarraylist-baseindex .no_toc } + +The number of the first item. **Long**, read/write. + +Syntax: *object*.**BaseIndex** [ **=** *value* ] + +See [**BaseIndex**](ArrayList#baseindex). ### Capacity {: #iarraylist-capacity .no_toc } @@ -277,15 +305,6 @@ Syntax: *object*.**Capacity** [ **=** *value* ] See [**Capacity**](ArrayList#capacity). -### Clone -{: #iarraylist-clone .no_toc } - -Returns a copy of the list. - -Syntax: *object*.**Clone** ( ) **As Variant** - -The member of [**ICloneable**](#icloneable) again; see [**Clone**](ArrayList#clone). - ### CloneTo {: #iarraylist-cloneto .no_toc } @@ -333,7 +352,7 @@ See [**Version**](ArrayList#version). ## ICloneable -An object that can make a copy of itself. **IArrayList** implements it. Interface identifier `8ABD76D4-42F5-4920-927A-BC766E00CE46`. +An object that can make a copy of itself. [**ArrayList**](ArrayList#clone) and [**ListRange**](ListRange#clone) implement it. The interface **IArrayList** does not have **Clone**, so a variable of that type has to be assigned to an **ICloneable** variable to copy the list. Interface identifier `8ABD76D4-42F5-4920-927A-BC766E00CE46`. ### Clone {: #icloneable-clone .no_toc } diff --git a/docs/Reference/Contributed/ArrayListLib/ListRange.md b/docs/Reference/Contributed/ArrayListLib/ListRange.md index 5dba13c0..c5e74ea5 100644 --- a/docs/Reference/Contributed/ArrayListLib/ListRange.md +++ b/docs/Reference/Contributed/ArrayListLib/ListRange.md @@ -8,9 +8,9 @@ has_toc: false # ListRange class {: .no_toc } -A **ListRange** is a view of a run of items in an [**ArrayList**](ArrayList). It shows those items under indexes that start at 0, and it does not copy them: a change made through the view changes the list. [**GetRange**](ArrayList#getrange) of an **ArrayList** (or of another **ListRange**) returns one. +A **ListRange** is a view of a run of items in an [**ArrayList**](ArrayList). It shows those items under indexes that start at its [**BaseIndex**](#baseindex), which it takes from its list when it is made, and it does not copy them: a change made through the view changes the list. [**GetRange**](ArrayList#getrange) of an **ArrayList** (or of another **ListRange**) returns one. -A range is made by **GetRange**; the class is marked as not creatable. **GetRange** returns the range as an [**IListRange**](Interfaces#ilistrange), which has no **Reverse**, **LastIndexOf**, **Capacity**, **Clone** or **Items**. A range implements [**IArrayList**](Interfaces#iarraylist) and the class **ArrayList** as well; the type **IArrayList** has all the members of a range. +A range is made by **GetRange**; the class is marked as not creatable. **GetRange** returns the range as an [**IListRange**](Interfaces#ilistrange), which has no **Reverse**, **LastIndexOf**, **Capacity**, **BaseIndex** or **Items**. A range implements [**IArrayList**](Interfaces#iarraylist), [**ICloneable**](Interfaces#icloneable) and the class **ArrayList** as well; the type **IArrayList** has all the members of a range except **Clone**. ```tb check_build Dim list As New ArrayList @@ -33,16 +33,32 @@ Next ## When a range stops being valid -A range keeps the version number that the list had when the range was made. Each call of the range first compares that number with the list's. A change that the range makes itself keeps the numbers equal, with the two exceptions below. Any other change to the list --- [**Add**](ArrayList#add), [**Insert**](ArrayList#insert), [**RemoveAt**](ArrayList#removeat), [**Sort**](ArrayList#sort) or an assignment to an item, for example --- makes them differ, and the next call of the range raises error -2146233079 (`vbObjectError + 988425`) with the text *This ArrayList range is no longer valid.* Make a new range with **GetRange** after such a change. +A range keeps the version number that the list had when the range was made. Each call of the range first compares that number with the list's. A change that the range makes itself keeps the numbers equal, with the two exceptions below. Any other change to the list --- [**Add**](ArrayList#add), [**Insert**](ArrayList#insert), [**RemoveAt**](ArrayList#removeat), [**Sort**](ArrayList#sort), an assignment to an item or an assignment to [**BaseIndex**](ArrayList#baseindex), for example --- makes them differ, and the next call of the range raises error -2146233079 (`vbObjectError + 988425`) with the text *This ArrayList range is no longer valid.* Make a new range with **GetRange** after such a change. The number is the value that the documentation of the .NET class **InvalidOperationException** gives for its HRESULT, `COR_E_INVALIDOPERATION` (`&H80131509`). Two changes made through the range itself make it invalid, because they count differently in the range and in the list. An assignment to an item (`r.Item(0) = x`, or with **Set**) adds 1 to the range's version number and nothing to the list's. [**SetRange**](#setrange) of a range, with a list or a range as its argument, adds 1 to the range's number and 2 to the list's. See [Known problems](./#known-problems). -A change made through one range adds to the version number of the list, so every other range of the same list is invalid afterwards. +A change made through one range adds to the version number of the list, so every other range of the same list is invalid afterwards. A range made by **GetRange** of another range compares its number with the number of that range, not with the list's: a change made through the outer range makes the inner one invalid, a change made through the inner one leaves the outer one valid, and a change made to the list directly invalidates the outer range and leaves the inner one valid. + +## A range after the list grows + +A range reads the items from the address they had when the range was made. A change made through the range that makes the list enlarge its array, such as [**Add**](#add), [**AddRange**](#addrange), [**Insert**](#insert) or [**InsertRange**](#insertrange) on a list with no room, can move the array. The range then goes on reading the old array, which has been released, and does not notice: the range and the list keep the same version number. + +> [!WARNING] +> After such a change the items that the range shows can be the old ones, or whatever now occupies that memory. A range of two items of a list with room for five, after three calls of **Add** through the range, showed `a b c d` and an empty item, where the list held `a b X Y Z c d`. Make the list large enough first, by giving [**New**](ArrayList#new) a capacity or assigning [**Capacity**](ArrayList#capacity), or make a new range with **GetRange** after the change. ## Properties +### BaseIndex +{: .no_toc } + +The number of the first item of the range. **Long**, read/write. + +Syntax: *object*.**BaseIndex** [ **=** *value* ] + +A range has the **BaseIndex** that its list or outer range has when the range is made. An assignment changes the numbering of this range only; the list and the other ranges keep theirs, and the range stays valid. See [**BaseIndex**](ArrayList#baseindex) of **ArrayList**. + ### Capacity {: .no_toc } @@ -69,7 +85,7 @@ The item at a position in the range. **Variant**, read/write, and the default me Syntax: [ **Set** ] *object*.**Item** ( *index* ) [ **=** *value* ] *index* -: A **Long** from 0 to **Count** - 1 of the range. A value outside the range raises error 9. +: A **Long** from [**BaseIndex**](#baseindex) to **BaseIndex** + **Count** - 1 of the range. A value outside the range raises error 9. See [**Item**](ArrayList#item) of **ArrayList**. An assignment makes the range invalid, as described above. @@ -96,7 +112,7 @@ The items are inserted in the list after the last item of the range, and the ran ### BinarySearch {: .no_toc } -Searches a sorted run of the range and returns the index of a value, counted from the start of the range. +Searches a sorted run of the range and returns the index of a value, counted from the [**BaseIndex**](#baseindex) of the range. Syntax: *object*.**BinarySearch** ( *Index*, *GetCount*, *Value* [ , *Comparer* ] ) **As Long** @@ -118,7 +134,8 @@ Returns a copy of the items of the range. Syntax: *object*.**Clone** ( ) **As Variant** -Returns a **Variant** that holds a new **ArrayList** with the items of the range, as [**Clone**](ArrayList#clone) of an **ArrayList** does. +Returns a **Variant** that holds a new **ArrayList** with the items of the range and the **BaseIndex** of the range, as [**Clone**](ArrayList#clone) of an **ArrayList** does. **Clone** is the member of [**ICloneable**](Interfaces#icloneable); the interface **IArrayList** does not have it. + ### Contains {: .no_toc } @@ -144,7 +161,7 @@ Returns the enumerator that **For Each** uses over the items of the range. Syntax: *object*.**GetEnumerator** ( [ *Index* ] [ , *GetCount* ] [ , *GetStep* ] [ , *ThisEnumerator* ] ) **As stdole.IUnknown** -As [**GetEnumerator**](ArrayList#getenumerator), with *Index* counted from the start of the range. +As [**GetEnumerator**](ArrayList#getenumerator), with *Index* counted from the first item of the range, which is 0 whatever the **BaseIndex** of the range is. The range keeps two enumerators in the same way as a list, with the same effects: see the warning there. A range with no items has no enumerator, and a **For Each** over it raises an error. ### GetRange {: .no_toc } @@ -153,7 +170,7 @@ Returns a view of a run of items of the range. Syntax: *object*.**GetRange** ( *Index*, *GetCount* ) **As IListRange** -*Index* is counted from the start of the range. Error 9 is raised when the run does not lie in the range. A range of a range is a view of the same items of the list; a change through either makes the other invalid. +*Index* is counted from the **BaseIndex** of the range. Error 9 is raised when the run does not lie in the range. A range of a range is a view of the same items of the list. See [When a range stops being valid](#when-a-range-stops-being-valid) for which changes make which range invalid. ### IndexOf {: .no_toc } @@ -162,7 +179,7 @@ Returns the index in the range of the first item that matches a value, or -1. Syntax: *object*.**IndexOf** ( *Value* [ , *Index* [ , *GetCount* ] ] ) **As Long** -As [**IndexOf**](ArrayList#indexof), with *Index* and the result counted from the start of the range. Error 9 is raised when *Index* is outside the range or the run does not fit in it. +As [**IndexOf**](ArrayList#indexof), with *Index* and the result counted from the **BaseIndex** of the range. The default of *Index* is that **BaseIndex**. Error 9 is raised when *Index* is outside the range or the run does not fit in it. ### Insert {: .no_toc } @@ -171,7 +188,7 @@ Inserts an item at a position of the range. Syntax: *object*.**Insert** ( *Index*, *Value* ) -*Index* is counted from the start of the range, and the item is inserted in the list at that position. The range grows by one. *Index* is not checked against the range. +*Index* is counted from the **BaseIndex** of the range, and the item is inserted in the list at that position. The range grows by one. *Index* is not checked against the range. The call is passed on to the list, so [**Insert**](ArrayList#insert) of the list applies, with its failure at the first position of a list that has little room, and the range can be left reading released memory: see [A range after the list grows](#a-range-after-the-list-grows). ### InsertRange {: .no_toc } @@ -187,9 +204,9 @@ As [**InsertRange**](ArrayList#insertrange), with *Index* counted from the start Returns the index in the range of the last item that matches a value, or -1. -Syntax: *object*.**LastIndexOf** ( *Value*, *Index*, *GetCount* ) **As Long** +Syntax: *object*.**LastIndexOf** ( *Value* [ , *Index* [ , *GetCount* ] ] ) **As Long** -As [**LastIndexOf**](ArrayList#lastindexof), with *Index* and the result counted from the start of the range. On a range, *Index* and *GetCount* are declared without **Optional**. +As [**LastIndexOf**](ArrayList#lastindexof), with *Index* and the result counted from the **BaseIndex** of the range. The default of *Index* is the last item of the range. ### Remove {: .no_toc } @@ -198,7 +215,7 @@ Removes the first item of the range that matches a value. Syntax: *object*.**Remove** ( *Value* ) -As [**Remove**](ArrayList#remove). The item is removed from the list, and the range shrinks by one. +As [**Remove**](ArrayList#remove), including its failure with a **BaseIndex** below 0. The item is removed from the list, and the range shrinks by one. ### RemoveAt {: .no_toc } @@ -223,9 +240,9 @@ Syntax: *object*.**RemoveRange** ( *Index*, *GetCount* ) Reverses the order of the items of the range, or of a run of them. -Syntax: *object*.**Reverse** ( *Index*, *GetCount* ) +Syntax: *object*.**Reverse** ( [ *Index* ] [ , *GetCount* ] ) -As [**Reverse**](ArrayList#reverse), with *Index* counted from the start of the range, with the same loop. On a range, both arguments are declared without **Optional**. +As [**Reverse**](ArrayList#reverse), with *Index* counted from the **BaseIndex** of the range, and with the same loop. A run with an even number of items keeps its two middle items in place: see the warning there. ### SetRange {: .no_toc } @@ -252,7 +269,16 @@ Returns the items of the range as an array. Syntax: *object*.**ToArray** ( ) **As Variant()** -As [**ToArray**](ArrayList#toarray), for the items of the range. +As [**ToArray**](ArrayList#toarray), for the items of the range. The lower bound of the array is the **BaseIndex** of the range. + +### ToString +{: .no_toc } + +Returns the items of the range as text. + +Syntax: *object*.**ToString** ( [ *TextQualifier* ] [ , *UseNullAsEmpty* ] ) **As String** + +As [**ToString**](ArrayList#tostring), for the items of the range. Through a variable of type **IListRange** or **IArrayList** both arguments are required. It never returns for a range with no items. ## Hidden members @@ -267,12 +293,12 @@ Syntax: *object*.**Bind** ( *TargetList* [ , *Index* [ , *GetCount* ] ] ) **As L : *required* An [**IArrayList**](Interfaces#iarraylist): the list or range to look at. *Index* -: *optional* A **Long**: the index in *TargetList* of the first item. The default is 0. +: *optional* A **Long**: the index in *TargetList* of the first item, counted from the **BaseIndex** of *TargetList*. The default is that **BaseIndex**. The parameter is declared as a **Variant**, so that a call can leave it out and give *GetCount*. *GetCount* : *optional* The number of items. The default is all the items from *Index*. -Returns the range itself. **GetRange** calls **Bind**; a program has no reason to. +Returns the range itself, and gives it the **BaseIndex** of *TargetList*. **GetRange** calls **Bind**; a program has no reason to. ### IndexOf_2, IndexOf_3, LastIndexOf_2, LastIndexOf_3, BinarySearch_2, BinarySearch_3, Sort_2 {: .no_toc } @@ -291,7 +317,7 @@ Returns the items of the range as an array that shares their memory. Syntax: *object*.**Items** ( ) **As Variant()** -As [**Items**](ArrayList#items): use the result only as the subject of **For Each**. +As [**Items**](ArrayList#items): use the result only as the subject of **For Each**. Its lower bound is 0. ### SyncRoot {: .no_toc } diff --git a/docs/Reference/Contributed/ArrayListLib/index.md b/docs/Reference/Contributed/ArrayListLib/index.md index db6a5aa9..4a286e66 100644 --- a/docs/Reference/Contributed/ArrayListLib/index.md +++ b/docs/Reference/Contributed/ArrayListLib/index.md @@ -3,23 +3,27 @@ title: ArrayListLib Package parent: Experimental Packages has_toc: false exclude_from_docs: + - CommonModule - LibMemory - LibMemoryEx -indexed_from: 1.4.0.134 +indexed_from: 1.6.151.1 permalink: /tB/Packages/Contributed/ArrayListLib/ --- # ArrayListLib Package {: .no_toc } -**Author:** R.Beltran. **Version:** 1.4.0.134. **On GitHub:** [Theadd/ArrayList](https://github.com/Theadd/ArrayList), in the folder *ArrayListLib*. +**Author:** R.Beltran. **Version:** 1.6.151.1. **On GitHub:** [Theadd/ArrayList](https://github.com/Theadd/ArrayList), in the folder *ArrayListLib*. The **ArrayListLib** contributed package is a list class for twinBASIC, in place of the `ArrayList` class of .NET's `mscorlib` library. A VBA or VB6 program that needs a list which grows as items are added can use that class through `CreateObject("System.Collections.ArrayList")`. [**ArrayList**](ArrayList) has the members of that class which a COM client can call, under the same names, and needs no .NET Framework. -This section documents version 1.4.0.134, which is the version on TWINSERV, published under the Unlicense. The repository is now at version 1.6, which has members that this section does not describe, such as a **BaseIndex** property and a **ToString** method; the sources of version 1.4 are in the history of the repository. +This section documents version 1.6.151.1, which is the version on TWINSERV, published under the Unlicense. > [!NOTE] -> Version 1.4.0.134 does not compile in twinBASIC BETA 1005, so a project that references it does not build. **ArrayList** passes the addresses of two items, as **Long** values, to its private procedure **VSwap**, which declares both parameters as `ByRef` structures, in **Reverse** and in the two quicksort procedures that **Sort** calls (error TB5001, in `ArrayList.twin` at lines 236, 519 and 536). +> Version 1.6.151.1 does not compile in twinBASIC BETA 1005, so a project that references it does not build (error TB5001, in a 32-bit and in a 64-bit build). **ArrayList** passes the addresses of two items, as **Long** values, to its private procedure **VSwap**, which declares both parameters as `ByRef` structures, in **Reverse** (`ArrayList.twin`, line 297) and in the two quicksort procedures that **Sort** calls (lines 585 and 602). **MemMoveBSTR**, a private procedure of the module **LibMemoryEx**, passes an address to **LSetByVal** in the same way (`Modules/LibMemoryEx.twin`, line 104), and **LSetByVal** declares its parameter as a `ByRef` structure that holds a string pointer. A pointer passed for a structure has to be passed `ByVal`. The behaviour described on these pages is that of the package with the two arguments of each **VSwap** call passed `ByVal`, and with **LSetByVal** taking the address as a **LongPtr** and putting it in a structure of its own. That copy builds in 32-bit and in 64-bit programs, and both give the same results. + +> [!WARNING] +> Several members give wrong results, damage memory or never return. A **For Each** over a list with no items raises an error, and one that runs under `On Error Resume Next` never ends. A call to **GetEnumerator** after a **For Each** over the same list ran to the end ignores its arguments. **Reverse** keeps the two middle items of a run with an even number of items in place, and overwrites memory when the run has no items. **ToString** never returns for a list with no items. **Insert** at the first position fails unless the list has room for 31 more items. **Remove** with a negative **BaseIndex** removes the wrong item. A range reads released memory after a change made through it makes the list grow. See [Known problems](#known-problems). ```tb check_build Dim scores As New ArrayList @@ -38,10 +42,10 @@ Debug.Print "average " & total / scores.Count ## How it differs from the .NET class -- **Creation.** A list is created with **New**, as in `Dim list As New ArrayList`, in place of `CreateObject`. +- **Creation.** A list is created with **New**, as in `Dim list As New ArrayList`, in place of `CreateObject`. **New** takes an optional capacity and an optional index base, which needs a variable declared first: `Dim list As ArrayList = New ArrayList(100, 1)`. See [New](ArrayList#new). - **Members left out.** The package has none of the static members of the .NET class (**Adapter**, **FixedSize**, **ReadOnly**, **Repeat** and **Synchronized**), nor its **TrimToSize** method, and **ToArray** has no *Type* argument. - **Overloads.** The README of the package says that COM has no overloaded methods, and that the hidden members with a number in their name, such as `IndexOf_2`, stand for the overloads of the .NET class. See [Hidden members](ArrayList#hidden-members). -- **Additions.** [**Count**](ArrayList#count) can be assigned, [**GetEnumerator**](ArrayList#getenumerator) takes a start, a count and a step so that **For Each** can go through part of a list or backwards, and the methods that take a collection also take any array. +- **Additions.** [**BaseIndex**](ArrayList#baseindex) makes the first item number 1, or any other number, in place of 0. [**Count**](ArrayList#count) can be assigned, [**GetEnumerator**](ArrayList#getenumerator) takes a start, a count and a step so that **For Each** can go through part of a list or backwards, and the methods that take a collection also take any array. [**ToString**](ArrayList#tostring) writes the items as text, in the notation of a JSON array. - **Errors.** An index outside the list raises error 9, *Subscript out of range*, and an argument of the wrong kind error 13 or 6. The documentation of the .NET class lists an **ArgumentOutOfRangeException** for an index outside the list. - **Changes during a loop.** A **For Each** over a list that changes while the loop runs is not detected. The documentation of the .NET class says that its enumerator is invalidated by such a change. See [Enumerating](ArrayList#enumerating). @@ -50,6 +54,7 @@ The page [ArrayList](ArrayList#differences-from-the-net-class) lists the remaini ## Classes and interfaces - [ArrayList](ArrayList) -- the list: items added, inserted, found, removed, sorted and enumerated + - [BaseIndex](ArrayList#baseindex) -- returns or sets the number of the first item - [Capacity](ArrayList#capacity) -- returns or sets the number of items the list has room for - [Count](ArrayList#count) -- returns or sets the number of items - [Item](ArrayList#item) -- returns or sets one item (the default member) @@ -58,6 +63,7 @@ The page [ArrayList](ArrayList#differences-from-the-net-class) lists the remaini - [Contains](ArrayList#contains), [IndexOf](ArrayList#indexof), [LastIndexOf](ArrayList#lastindexof), [BinarySearch](ArrayList#binarysearch) -- find items - [Sort](ArrayList#sort), [Reverse](ArrayList#reverse) -- reorder items - [GetRange](ArrayList#getrange), [SetRange](ArrayList#setrange), [CopyTo](ArrayList#copyto), [ToArray](ArrayList#toarray), [Clone](ArrayList#clone) -- parts and copies + - [ToString](ArrayList#tostring) -- the items as text - [GetEnumerator](ArrayList#getenumerator) -- the enumerator that **For Each** uses - [ListRange](ListRange) -- a view of a run of items in an ArrayList, returned by **GetRange** - [Enumerator](Enumerator) -- the enumerator of **For Each**, with a start, a count and a step @@ -66,25 +72,31 @@ The page [ArrayList](ArrayList#differences-from-the-net-class) lists the remaini ## Name clashes -The package declares the names **ArrayList**, **Comparer**, **Enumerator**, **ListRange**, **ICloneable**, **ICollection**, **IComparer**, **IEnumVARIANT**, **IEnumerable**, **IEnumerator**, **IList**, **IListRange** and **IArrayList**, and its two memory modules declare more (see below). A name that another library or the project declares as well can be written with the name of the package in front, as in `ArrayListLib.ArrayList`. +The package declares the names **ArrayList**, **Comparer**, **Enumerator**, **ListRange**, **ICloneable**, **ICollection**, **IComparer**, **IEnumVARIANT**, **IEnumerable**, **IEnumerator**, **IList**, **IListRange** and **IArrayList**, and its three modules declare more (see below). A name that another library or the project declares as well can be written with the name of the package in front, as in `ArrayListLib.ArrayList`. ## Known problems -These faults follow from the code of version 1.4.0.134. The package cannot be built in BETA 1005 (see the note above), so none of them has been run. +These faults are those of the package as described in the note above. The ones marked as wrong results, memory damage or a hang are the ones a program is likely to meet. -- **Reverse.** The loop runs for *i* from 0 up to and including half of the count, rounded down, and swaps the items at *i* and at the count - 1 - *i* from the start of the run. For an even count the last pass swaps the middle pair a second time, so those two items keep their order. For a run of no items the loop still runs once. +- **For Each over a list with no items.** [**GetEnumerator**](ArrayList#getenumerator) returns nothing for a list or a range with no items, and the **For Each** raises error -2147467259 (`E_FAIL`, *Unspecified error*). Under `On Error Resume Next` the statement is repeated without end. The same holds for a list that **Clear** or the removal of its last item has emptied. +- **For Each over a variable of an interface type.** A **For Each** over a variable declared as **IList**, **ICollection**, **IEnumerable**, **IListRange** or **IArrayList** raises error -2147352567, *Exception occurred.* A variable declared as **ArrayList**, **ListRange**, **Object** or **Variant** works. A call of **GetEnumerator** through an interface variable with fewer than all four arguments does not compile (TB5050). +- **GetEnumerator reuses its enumerators.** Each list keeps two enumerators and gives out the first one when it is not in use. The first one is in use from the start of a **For Each** until the loop ran to the end; leaving the loop with **Exit For** keeps it in use. When the list has not changed since the enumerator was bound, the arguments of **GetEnumerator** (*Index*, *GetCount* and *GetStep*) are ignored, so a second loop over the same list gives the part, the count and the direction of the first. A third loop nested inside two others reuses the second enumerator while it is in use, and gives wrong items. Assigning [**Capacity**](ArrayList#capacity) can move the items to another place in memory without changing the list's version number, so the next loop over the list can read the memory the items left. +- **Reverse.** The loop runs for *i* from 0 up to and including half of the count, rounded down, and swaps the items at *i* and at the count - 1 - *i* from the start of the run. For an even count the last pass swaps the middle pair a second time, so those two items keep their order: a run of two items is not reversed at all. For a run of no items the loop still runs once, and swaps the item at the start of the run with the item-sized block before it (16 bytes in a 32-bit program, 24 in a 64-bit one). With the run at the start of the list, that block is the header of the memory block that holds the items. The next growth of the list raised error -2147024882 (*Not enough memory resources are available*) in a 32-bit program, and ended a 64-bit program with the exception 0xC0000374 (damage to the heap). +- **ToString.** The method goes through the items with **For Each** under `On Error Resume Next`. It never returns for a list with no items, for a nested list with no items, and for an item that is an object with no enumerator, such as an instance of a class of the program. +- **Insert at the first position.** [**Insert**](ArrayList#insert) at **BaseIndex** on a list that has items needs room for 31 more items than the list holds. Without it, the method enlarges the array with `ReDim Preserve` and a lower bound of -30, which twinBASIC refuses, and raises error 9. A list with room for 31 more items does not fail. +- **Insert and InsertRange.** *Index* is not checked against **Count**, and the number of items the method moves is **Count** - *Index*. An *Index* above **Count** + **BaseIndex** crashed the program in **Insert** with an access violation. - **CopyTo.** An array is refused with error 6 when `UBound(Target) - Index` is less than **Count**, so an array with exactly **Count** elements from *Index* is refused. The same check is in **ListRange**. -- **Insert and InsertRange.** *Index* is not checked against **Count**, and the number of items the method moves is **Count** - *Index*. -- **Sort and BinarySearch.** On an **ArrayList**, *Index* and *GetCount* are not checked against the list. A **ListRange** checks them. -- **Capacity.** The assignment does not check its value against **Count**. +- **Sort and BinarySearch.** On an **ArrayList**, *Index* and *GetCount* are not checked against the list: a run that reaches past **Count** but not past the capacity takes in the unused places, and **Sort** then moves items out of the list. A **ListRange** checks them. +- **Capacity.** The assignment does not check its value against **Count**. A value below **Count** keeps **Count** as it was, and an item at an index past the capacity raises error 9. A value of 0 or less raises error -2147467259. The same error comes from `New ArrayList(0)` and from **Clone** of a list with no items. - **Count.** A smaller value ends the list without clearing the items cut off. A larger value within the capacity makes those items part of the list again. -- **ListRange.** An assignment to an item through a range, and **SetRange** of a range with a list as its argument, make the range invalid, because they add to the version number of the range and of the list by different amounts. **SetRange** of a range checks the number of items against the list and not the range. -- **Enumerator.** **Reset** does not clear the step that **Next** adds, so the first **Next** after it moves past the first item once an item has been returned. **Clone** starts at the beginning of the part and not at the position of the original. +- **BaseIndex below 0.** [**IndexOf**](ArrayList#indexof) returns -1 for an item that is not in the list, and -1 is an index of such a list. **Remove** of a value that is not in the list removes the item at index -1, or raises error 9 when there is none. The hidden member **LastIndexOf_2** searches too few items with a negative **BaseIndex**, and raises error 9 with a positive one. +- **ListRange.** A change made through a range that makes the list enlarge its array leaves the range reading the old array, which has been released, so that it shows items the list no longer has in those places. An assignment to an item through a range, and **SetRange** of a range with a list as its argument, make the range invalid, because they add to the version number of the range and of the list by different amounts. **SetRange** of a range checks the number of items against the list and not the range. +- **Enumerator.** **Clone** starts at the beginning of the part and not at the position of the original. - **For Each.** A change to the list during the loop is not detected. ## Memory modules -The package includes two modules, **LibMemory** and **LibMemoryEx**, which the list classes use to read and write memory directly: the array descriptor type **SAFEARRAY_1D** and its constants, the procedures **VarPtrArr** and **ArrPtr** that return the address of an array, and **ZeroMemory**, **MemMoveEx**, **VariantArrayClone** and **ReassignArrayTo**, which clear, move, copy and hand over blocks of memory. **LibMemory** is the *VBA-MemoryTools* code of Ion Cristian Buse, under the MIT licence. A project that references the package can call them, but this section does not document them. +The package includes three modules. **LibMemory** and **LibMemoryEx** are used by the list classes to read and write memory directly: the array descriptor type **SAFEARRAY_1D** and its constants, the procedures **VarPtrArr** and **ArrPtr** that return the address of an array, and **ZeroMemory**, **MemMoveEx**, **VariantArrayClone** and **ReassignArrayTo**, which clear, move and copy blocks of memory and give an array to another variable. **LibMemory** is the *VBA-MemoryTools* code of Ion Cristian Buse, under the MIT licence. **CommonModule** holds **Stringify**, a hidden function that [**ToString**](ArrayList#tostring) calls for each value. A project that references the package can call them, but this section does not document them. ## See Also diff --git a/docs/Reference/Experimental.md b/docs/Reference/Experimental.md index 5bc70eeb..49e7742d 100644 --- a/docs/Reference/Experimental.md +++ b/docs/Reference/Experimental.md @@ -12,7 +12,7 @@ Community-authored packages from TWINSERV that are not yet ready for use in a pr These packages are published through TWINSERV, as the [Contributed Packages](../Contributed/) are, and are documented in the same way. A package is listed here when its newest version does not compile in the current twinBASIC, or when it has defects that a program would meet. Each section says which version it documents and what does not work yet. A package moves to the Contributed Packages once a version of it works, and its pages keep their addresses. -- [**ArrayListLib**](../Contributed/ArrayListLib/) -- a list class in place of .NET's `ArrayList`. Version 1.4.0.134 does not compile in twinBASIC BETA 1005. +- [**ArrayListLib**](../Contributed/ArrayListLib/) -- a list class in place of .NET's `ArrayList`. Version 1.6.151.1 does not compile in twinBASIC BETA 1005, and in a copy that does, a **For Each** over a list with no items fails, **ToString** of such a list never returns, **Reverse** leaves the two middle items of an even run in place, and a second **GetEnumerator** over the same list ignores its arguments. - [**BigNumberLib**](../Contributed/BigNumberLib/) -- arbitrary-precision signed integer arithmetic. Version 1.0.2.0 compiles, and has several members that give wrong results or do not return: setting **IsNegative** to **True**, a negative operand of the greatest common divisor and the least common multiple, a zero from **AssignFromLong**, and the modular inverse of zero. - [**TBMANLIB**](../Contributed/TBMANLIB/) -- a toolkit of helpers: JSON, logs, INI files, strings, bytes, files and dictionaries. Version 1.0.0.5 compiles for 32-bit programs, crashes the compiler in a 64-bit build, and has several members that fail or give wrong results. - [**TBMANLIB_Aes**](../Contributed/TBMANLIB_Aes/) -- AES encryption of text and byte arrays. Version 1.0.0.4 compiles, and its cipher modes are not the ones their names say: the default mode, CBC, runs ECB, and the output does not match the standard test vectors. diff --git a/indexer/snapshots/contributed/ArrayListLib/api.json b/indexer/snapshots/contributed/ArrayListLib/api.json index d317f9aa..40442ec5 100644 --- a/indexer/snapshots/contributed/ArrayListLib/api.json +++ b/indexer/snapshots/contributed/ArrayListLib/api.json @@ -2,7 +2,7 @@ "format": 3, "package": "ArrayListLib", "project": "ArrayListLib", - "version": "1.4.0.134", + "version": "1.6.151.1", "types": [ { "name": "ArrayList", @@ -10,12 +10,14 @@ "vis": "public", "file": "ArrayList.twin", "signature": "Public Class ArrayList", - "implements": ["IArrayList"], + "implements": ["IArrayList","ICloneable"], "attrs": [{"name":"ClassId","args":"\"E88D5510-C8E5-433D-8DA5-712D8586E0C9\""},{"name":"COMCreatable","args":"True"}], "members": [ {"name":"Add","kind":"function","vis":"public","signature":"Public Function Add(Value As Variant) As Long Implements IArrayList.Add","returnType":"Long","description":"Adds an item to the list. The return value is the position the new element was inserted in."}, {"name":"AddRange","kind":"sub","vis":"public","signature":"Public Sub AddRange(Target As Variant) Implements IArrayList.AddRange"}, {"name":"Assign","kind":"sub","vis":"private","signature":"Private Sub Assign(ByRef Target As Variant, ByRef Value As Variant)","attrs":[{"name":"ArrayBoundsChecks","args":"False"},{"name":"IntegerOverflowChecks","args":"False"}]}, + {"name":"BaseIndex","kind":"property","vis":"public","signature":"Public Property Get BaseIndex() As Long Implements IArrayList.BaseIndex","returnType":"Long","accessor":"Get"}, + {"name":"BaseIndex","kind":"property","vis":"public","signature":"Public Property Let BaseIndex(Value As Long) Implements IArrayList.BaseIndex","accessor":"Let"}, {"name":"BinarySearch","kind":"function","vis":"public","signature":"Public Function BinarySearch(ByVal Index As Long, ByVal GetCount As Long, Value As Variant, Optional ByRef Comparer As IComparer = Nothing) As Long Implements IArrayList.BinarySearch","returnType":"Long","attrs":[{"name":"ArrayBoundsChecks","args":"False"},{"name":"IntegerOverflowChecks","args":"False"}],"description":"Searches a section of a sorted list. Returns the index of the given value in the list. If not found, returns a negative integer. Use the bitwise operator (Not) to get the index of the first element larger than this one, if any."}, {"name":"BinarySearch_2","kind":"function","vis":"public","signature":"Public Function BinarySearch_2(ByRef Value As Variant) As Long","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, {"name":"BinarySearch_3","kind":"function","vis":"public","signature":"Public Function BinarySearch_3(ByRef Value As Variant, ByRef Comparer As IComparer) As Long","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, @@ -25,17 +27,17 @@ {"name":"Capacity","kind":"property","vis":"public","signature":"Public Property Let Capacity(Value As Long) Implements IArrayList.Capacity","accessor":"Let"}, {"name":"Class_Terminate","kind":"sub","vis":"private","signature":"Private Sub Class_Terminate()"}, {"name":"Clear","kind":"sub","vis":"public","signature":"Public Sub Clear() Implements IArrayList.Clear","description":"Removes all items from the list."}, - {"name":"Clone","kind":"function","vis":"public","signature":"Public Function Clone() As Variant Implements IArrayList.Clone","returnType":"Variant","description":"Creates a shallow copy of this ArrayList."}, + {"name":"Clone","kind":"function","vis":"public","signature":"Public Function Clone() As Variant Implements ICloneable.Clone","returnType":"Variant","description":"Creates a shallow copy of this ArrayList."}, {"name":"CloneTo","kind":"sub","vis":"friend","signature":"Friend Sub CloneTo(ByVal Target As IArrayList, Source As IArrayList, ByVal Index As Long, ByVal GetCount As Long) Implements IArrayList.CloneTo"}, {"name":"Contains","kind":"function","vis":"public","signature":"Public Function Contains(Value As Variant) As Boolean Implements IArrayList.Contains","returnType":"Boolean","description":"Returns whether the list contains a particular item."}, {"name":"CopyTo","kind":"sub","vis":"public","signature":"Public Sub CopyTo(Target As Variant, Index As Long) Implements IArrayList.CopyTo","description":"Copies this ArrayList to another array at specified index, the other array must be of a compatible array type but not necessarily the same type. It also accepts other lists implementing IListRange as target."}, {"name":"Count","kind":"property","vis":"public","signature":"Public Property Get Count() As Long Implements IArrayList.Count","returnType":"Long","accessor":"Get"}, {"name":"Count","kind":"property","vis":"public","signature":"Public Property Let Count(ByVal Value As Long)","accessor":"Let"}, {"name":"GetAddressOfItemAt","kind":"function","vis":"friend","signature":"Friend Function GetAddressOfItemAt(Index As Long) As LongPtr Implements IArrayList.GetAddressOfItemAt","returnType":"LongPtr"}, - {"name":"GetEnumerator","kind":"function","vis":"public","signature":"Public Function GetEnumerator(Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant, Optional ByVal GetStep As Long = 1, Optional ByRef ThisEnumerator As IEnumerator) As stdole.IUnknown Implements IArrayList.GetEnumerator","returnType":"stdole.IUnknown","attrs":[{"name":"Enumerator"}]}, + {"name":"GetEnumerator","kind":"function","vis":"public","signature":"Public Function GetEnumerator(Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant, Optional ByVal GetStep As Long = 1, Optional ByRef ThisEnumerator As IEnumerator) As stdole.IUnknown Implements IArrayList.GetEnumerator","returnType":"stdole.IUnknown","attrs":[{"name":"Enumerator"}],"description":"When using this enumerator for more advanced usage other than regular For Each usage, Index is always 0-based regardless of the current BaseIndex value in this ArrayList."}, {"name":"GetRange","kind":"function","vis":"public","signature":"Public Function GetRange(ByVal Index As Long, ByVal GetCount As Long) As IListRange Implements IArrayList.GetRange","returnType":"IListRange"}, {"name":"GrowCapacity","kind":"sub","vis":"private","signature":"Private Sub GrowCapacity(ByVal Increment As Long)"}, - {"name":"IndexOf","kind":"function","vis":"public","signature":"Public Function IndexOf(Value As Variant, Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant) As Long Implements IArrayList.IndexOf","returnType":"Long","description":"Returns the index of a particular item. Returns -1 if the item isn't in the list."}, + {"name":"IndexOf","kind":"function","vis":"public","signature":"Public Function IndexOf(Value As Variant, Optional ByVal Index As Variant, Optional ByVal GetCount As Variant) As Long Implements IArrayList.IndexOf","returnType":"Long","description":"Returns the index of a particular item. Returns -1 if the item isn't in the list."}, {"name":"IndexOf_2","kind":"function","vis":"public","signature":"Public Function IndexOf_2(ByRef Value As Variant, ByVal Index As Long, ByVal GetCount As Long) As Long","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, {"name":"IndexOf_3","kind":"function","vis":"public","signature":"Public Function IndexOf_3(ByRef Value As Variant) As Long","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, {"name":"Insert","kind":"sub","vis":"public","signature":"Public Sub Insert(ByVal Index As Long, Value As Variant) Implements IArrayList.Insert","description":"Inserts value into the list at position Index. Index must be non-negative and less than or equal to the number of elements in the list. If Index equals the number of items in the list, then value is appended to the end."}, @@ -52,23 +54,25 @@ {"name":"LastIndexOf","kind":"function","vis":"public","signature":"Public Function LastIndexOf(ByRef Value As Variant, Optional ByVal Index As Variant, Optional ByVal GetCount As Variant) As Long Implements IArrayList.LastIndexOf","returnType":"Long","description":"Returns the last index of a particular item. Returns -1 if the item isn't in the list."}, {"name":"LastIndexOf_2","kind":"function","vis":"public","signature":"Public Function LastIndexOf_2(ByRef Value As Variant, ByVal Index As Long) As Long","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, {"name":"LastIndexOf_3","kind":"function","vis":"public","signature":"Public Function LastIndexOf_3(ByRef Value As Variant, ByVal Index As Long, ByVal GetCount As Long) As Long","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, - {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New()"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(Optional ByVal Capacity As Long = 2, Optional ByVal BaseIndex As Long = 0)"}, {"name":"QuickSort","kind":"sub","vis":"private","signature":"Private Sub QuickSort(l As Long, r As Long, ByRef Comparer As IComparer)"}, {"name":"QuickSortV2","kind":"sub","vis":"private","signature":"Private Sub QuickSortV2(l As Long, r As Long)"}, {"name":"Remove","kind":"sub","vis":"public","signature":"Public Sub Remove(Value As Variant) Implements IArrayList.Remove","description":"Removes an item from the list."}, {"name":"RemoveAt","kind":"sub","vis":"public","signature":"Public Sub RemoveAt(ByVal Index As Long) Implements IArrayList.RemoveAt","description":"Removes the item at Index position."}, - {"name":"RemoveRange","kind":"sub","vis":"public","signature":"Public Sub RemoveRange(ByVal Index As Long, ByVal GetCount As Long) Implements IArrayList.RemoveRange"}, + {"name":"RemoveRange","kind":"sub","vis":"public","signature":"Public Sub RemoveRange(ByVal Index As Long, ByVal GetCount As Long) Implements IArrayList.RemoveRange","description":"Removes a specified range of elements from the ArrayList, starting from a specified index. \n### Parameters\n`Index` (Type: Long): The starting index of the range to be removed. \n`GetCount` (Type: Long): The number of elements to be removed starting from the index. \n### Usage Example\n```vb\n ' Example Usage\n Dim myArrayList As New ArrayList\n ' (Assuming elements are present in the list)\n myArrayList.RemoveRange 2, 3 ' Removes 3 elements starting from the 3rd index.\n```\n### Notes\n- If the index is less than 0, if the count is less than 0, or if the count exceeds the available range to be removed, an error with code 9 is raised. \n- The elements within the range to be removed are set to `Empty`. \n- Following removal, the elements after the specified range are shifted to fill the removed space. \n- Memory is zeroed for the elements at the end of the ArrayList after the removal. \n- The internal version of the ArrayList is updated after removal."}, {"name":"ReserveSpaceForInsert","kind":"sub","vis":"private","signature":"Private Sub ReserveSpaceForInsert(ByVal Index As Long, ByVal GetCount As Long)"}, {"name":"ReserveSpaceForInsertOfOneAtZero","kind":"sub","vis":"private","signature":"Private Sub ReserveSpaceForInsertOfOneAtZero()"}, - {"name":"Reverse","kind":"sub","vis":"public","signature":"Public Sub Reverse(Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant) Implements IArrayList.Reverse"}, + {"name":"Reverse","kind":"sub","vis":"public","signature":"Public Sub Reverse(Optional ByVal Index As Variant, Optional ByVal GetCount As Variant) Implements IArrayList.Reverse"}, {"name":"SetRange","kind":"sub","vis":"public","signature":"Public Sub SetRange(ByVal Index As Long, Target As Variant) Implements IArrayList.SetRange"}, - {"name":"Sort","kind":"sub","vis":"public","signature":"Public Sub Sort(Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant, Optional Comparer As IComparer = Nothing) Implements IArrayList.Sort","attrs":[{"name":"ArrayBoundsChecks","args":"False"},{"name":"IntegerOverflowChecks","args":"False"}]}, + {"name":"Sort","kind":"sub","vis":"public","signature":"Public Sub Sort(Optional ByVal Index As Variant, Optional ByVal GetCount As Variant, Optional Comparer As IComparer = Nothing) Implements IArrayList.Sort","attrs":[{"name":"ArrayBoundsChecks","args":"False"},{"name":"IntegerOverflowChecks","args":"False"}]}, {"name":"Sort_2","kind":"sub","vis":"public","signature":"Public Sub Sort_2(ByRef Comparer As IComparer)","hidden":true,"attrs":[{"name":"Hidden"}]}, {"name":"Swap","kind":"sub","vis":"private","signature":"Private Sub Swap(ByRef A As Variant, ByRef B As Variant)","attrs":[{"name":"ArrayBoundsChecks","args":"False"},{"name":"IntegerOverflowChecks","args":"False"}]}, {"name":"SyncRoot","kind":"property","vis":"public","signature":"Public Property Get SyncRoot() As Variant Implements IArrayList.SyncRoot","returnType":"Variant","accessor":"Get","hidden":true,"attrs":[{"name":"Hidden"}]}, {"name":"ToArray","kind":"function","vis":"public","signature":"Public Function ToArray() As Variant() Implements IArrayList.ToArray","returnType":"Variant()"}, + {"name":"ToString","kind":"function","vis":"public","signature":"Public Function ToString(Optional TextQualifier As String = \"\"\"\", Optional ByVal UseNullAsEmpty As Boolean = True) As String Implements IArrayList.ToString","returnType":"String"}, {"name":"VSwap","kind":"sub","vis":"private","signature":"Private Sub VSwap(ByRef A As UDTVariantB, ByRef B As UDTVariantB)"}, {"name":"Version","kind":"property","vis":"public","signature":"Public Property Get Version() As Long Implements IArrayList.Version","returnType":"Long","accessor":"Get","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"pBaseIndex","kind":"field","vis":"private","signature":"Private pBaseIndex As Long"}, {"name":"pIndex","kind":"field","vis":"private","signature":"Private pIndex As Long"}, {"name":"pItems","kind":"field","vis":"private","signature":"Private pItems() As Variant"}, {"name":"pVersion","kind":"field","vis":"private","signature":"Private pVersion As Long"} @@ -100,6 +104,18 @@ ], "members": [] }, + { + "name": "CommonModule", + "kind": "module", + "vis": "public", + "file": "Modules/CommonModule.twin", + "signature": "Module CommonModule", + "members": [ + {"name":"CListRange","kind":"function","vis":"private","signature":"Private Function CListRange(Value As Variant) As ListRange","returnType":"ListRange"}, + {"name":"Stringify","kind":"function","vis":"public","signature":"Public Function Stringify(Value As Variant, TextQualifier As String, ByVal UseNullAsEmpty As Boolean) As String","returnType":"String","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"ToUTC","kind":"function","vis":"private","signature":"Private Function ToUTC(Value As Date) As Date","returnType":"Date"} + ] + }, { "name": "Comparer", "kind": "class", @@ -135,6 +151,8 @@ {"name":"CurrentStep","kind":"property","vis":"public","signature":"Public Property Let CurrentStep(Value As Long) Implements IEnumerator.CurrentStep","accessor":"Let"}, {"name":"E_INVALIDARGS","kind":"const","vis":"private","signature":"Private Const E_INVALIDARGS As Long = &H80070057"}, {"name":"GetEnumerator","kind":"function","vis":"public","signature":"Public Function GetEnumerator() As stdole.IUnknown","returnType":"stdole.IUnknown","attrs":[{"name":"Enumerator"}]}, + {"name":"IsAvailable","kind":"property","vis":"public","signature":"Public Property Get IsAvailable() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"IsEnumerating","kind":"field","vis":"private","signature":"Private IsEnumerating As Boolean"}, {"name":"Items","kind":"field","vis":"private","signature":"Private Items() As Variant"}, {"name":"MaxValue","kind":"field","vis":"private","signature":"Private MaxValue As Long"}, {"name":"NStep","kind":"field","vis":"private","signature":"Private NStep As Long"}, @@ -154,16 +172,16 @@ "file": "IArrayList.twin", "signature": "Public Interface IArrayList Extends IListRange", "extends": "IListRange", - "implements": ["ICloneable"], "attrs": [{"name":"InterfaceId","args":"\"1043D433-27BB-40F7-9255-50F4724FAB94\""}], "members": [ + {"name":"BaseIndex","kind":"property","vis":"public","signature":"Property Get BaseIndex() As Long","returnType":"Long","accessor":"Get"}, + {"name":"BaseIndex","kind":"property","vis":"public","signature":"Property Let BaseIndex(Value As Long)","accessor":"Let"}, {"name":"Capacity","kind":"property","vis":"public","signature":"Property Get Capacity() As Long","returnType":"Long","accessor":"Get"}, {"name":"Capacity","kind":"property","vis":"public","signature":"Property Let Capacity(Value As Long)","accessor":"Let"}, - {"name":"Clone","kind":"function","vis":"public","signature":"Function Clone() As Variant Implements ICloneable.Clone","returnType":"Variant"}, {"name":"CloneTo","kind":"sub","vis":"public","signature":"Sub CloneTo(ByVal Target As IArrayList, ByRef Source As IArrayList, ByVal Index As Long, ByVal GetCount As Long)","hidden":true,"attrs":[{"name":"Hidden"}]}, {"name":"Items","kind":"function","vis":"public","signature":"Function Items() As Variant()","returnType":"Variant()","hidden":true,"attrs":[{"name":"Hidden"}],"description":"CAUTION: Limit usage only in For Each In .Items calls, do NOT assign the return value of .Items to another variable unless you know what you're doing."}, {"name":"LastIndexOf","kind":"function","vis":"public","signature":"Function LastIndexOf(ByRef Value As Variant, Optional ByVal Index As Variant, Optional ByVal GetCount As Variant) As Long","returnType":"Long"}, - {"name":"Reverse","kind":"sub","vis":"public","signature":"Sub Reverse(Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant)"}, + {"name":"Reverse","kind":"sub","vis":"public","signature":"Sub Reverse(Optional ByVal Index As Variant, Optional ByVal GetCount As Variant)"}, {"name":"Version","kind":"property","vis":"public","signature":"Property Get Version() As Long","returnType":"Long","accessor":"Get","hidden":true,"attrs":[{"name":"Hidden"}]} ] }, @@ -262,7 +280,7 @@ {"name":"Add","kind":"function","vis":"public","signature":"Function Add(Value As Variant) As Long","returnType":"Long","description":"Adds an item to the list. The return value is the position the new element was inserted in."}, {"name":"Clear","kind":"sub","vis":"public","signature":"Sub Clear()","description":"Removes all items from the list."}, {"name":"Contains","kind":"function","vis":"public","signature":"Function Contains(Value As Variant) As Boolean","returnType":"Boolean","description":"Returns whether the list contains a particular item."}, - {"name":"IndexOf","kind":"function","vis":"public","signature":"Function IndexOf(Value As Variant, Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant) As Long","returnType":"Long","description":"Returns the index of a particular item. Returns -1 if the item isn't in the list."}, + {"name":"IndexOf","kind":"function","vis":"public","signature":"Function IndexOf(Value As Variant, Optional ByVal Index As Variant, Optional ByVal GetCount As Variant) As Long","returnType":"Long","description":"Returns the index of a particular item. Returns -1 if the item isn't in the list."}, {"name":"Insert","kind":"sub","vis":"public","signature":"Sub Insert(ByVal Index As Long, Value As Variant)","description":"Inserts value into the list at position Index. Index must be non-negative and less than or equal to the number of elements in the list. If Index equals the number of items in the list, then value is appended to the end."}, {"name":"IsFixedSize","kind":"property","vis":"public","signature":"Property Get IsFixedSize() As Boolean","returnType":"Boolean","accessor":"Get"}, {"name":"IsReadOnly","kind":"property","vis":"public","signature":"Property Get IsReadOnly() As Boolean","returnType":"Boolean","accessor":"Get"}, @@ -289,15 +307,16 @@ {"name":"InsertRange","kind":"sub","vis":"public","signature":"Sub InsertRange(ByVal Index As Long, Target As Variant)"}, {"name":"RemoveRange","kind":"sub","vis":"public","signature":"Sub RemoveRange(ByVal Index As Long, ByVal GetCount As Long)","description":"Removes a range of elements from the list."}, {"name":"SetRange","kind":"sub","vis":"public","signature":"Sub SetRange(ByVal Index As Long, Target As Variant)"}, - {"name":"Sort","kind":"sub","vis":"public","signature":"Sub Sort(Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant, Optional ByRef Comparer As IComparer = Nothing)","description":"Sorts the elements in a section of this list. The sort compares the elements to each other using the given IComparer interface."}, - {"name":"ToArray","kind":"function","vis":"public","signature":"Function ToArray() As Variant()","returnType":"Variant()"} + {"name":"Sort","kind":"sub","vis":"public","signature":"Sub Sort(Optional ByVal Index As Variant, Optional ByVal GetCount As Variant, Optional ByRef Comparer As IComparer = Nothing)","description":"Sorts the elements in a section of this list. The sort compares the elements to each other using the given IComparer interface."}, + {"name":"ToArray","kind":"function","vis":"public","signature":"Function ToArray() As Variant()","returnType":"Variant()"}, + {"name":"ToString","kind":"function","vis":"public","signature":"Function ToString(TextQualifier As String, ByVal UseNullAsEmpty As Boolean) As String","returnType":"String"} ] }, { "name": "LibMemory", "kind": "module", "vis": "public", - "file": "LibMemory.twin", + "file": "Modules/LibMemory.twin", "signature": "Module LibMemory", "members": [ {"name":"ArrPtr","kind":"function","vis":"public","signature":"Public Function ArrPtr(ByRef arr As Variant) As LongPtr","returnType":"LongPtr"}, @@ -313,7 +332,7 @@ "kind": "type", "in": "LibMemory", "vis": "public", - "file": "LibMemory.twin", + "file": "Modules/LibMemory.twin", "signature": "Public Type SAFEARRAYBOUND", "fields": [ {"name":"cElements","vis":"public","signature":"cElements As Long"}, @@ -326,7 +345,7 @@ "kind": "type", "in": "LibMemory", "vis": "public", - "file": "LibMemory.twin", + "file": "Modules/LibMemory.twin", "signature": "Public Type SAFEARRAY_1D", "fields": [ {"name":"cDims","vis":"public","signature":"cDims As Integer"}, @@ -344,7 +363,7 @@ "name": "LibMemoryEx", "kind": "module", "vis": "public", - "file": "LibMemoryEx.twin", + "file": "Modules/LibMemoryEx.twin", "signature": "Public Module LibMemoryEx", "members": [ {"name":"FADF_AUTO","kind":"const","vis":"public","signature":"Public Const FADF_AUTO As Long = &H1"}, @@ -371,7 +390,7 @@ "kind": "type", "in": "LibMemoryEx", "vis": "public", - "file": "LibMemoryEx.twin", + "file": "Modules/LibMemoryEx.twin", "signature": "Type UDTStringPointer", "fields": [ {"name":"Value","vis":"public","signature":"Value As String"} @@ -384,33 +403,35 @@ "vis": "public", "file": "ListRange.twin", "signature": "Public Class ListRange", - "implements": ["IArrayList","ArrayList"], + "implements": ["IArrayList","ICloneable","ArrayList"], "attrs": [{"name":"ClassId","args":"\"0A102E9C-9E68-411A-A4B8-E6128FC07721\""},{"name":"COMCreatable","args":"False"}], "members": [ {"name":"Add","kind":"function","vis":"public","signature":"Public Function Add(Value As Variant) As Long Implements ArrayList.Add, IArrayList.Add","returnType":"Long"}, {"name":"AddRange","kind":"sub","vis":"public","signature":"Public Sub AddRange(Target As Variant) Implements ArrayList.AddRange, IArrayList.AddRange"}, {"name":"ArrayList_Count","kind":"property","vis":"private","signature":"Private Property Let ArrayList_Count(ByVal Value As Long) Implements ArrayList.Count","accessor":"Let"}, {"name":"Assign","kind":"sub","vis":"private","signature":"Private Sub Assign(ByRef Target As Variant, ByRef Value As Variant)","attrs":[{"name":"ArrayBoundsChecks","args":"False"},{"name":"IntegerOverflowChecks","args":"False"}]}, + {"name":"BaseIndex","kind":"property","vis":"public","signature":"Public Property Get BaseIndex() As Long Implements ArrayList.BaseIndex, IArrayList.BaseIndex","returnType":"Long","accessor":"Get"}, + {"name":"BaseIndex","kind":"property","vis":"public","signature":"Public Property Let BaseIndex(Value As Long) Implements ArrayList.BaseIndex, IArrayList.BaseIndex","accessor":"Let"}, {"name":"BinarySearch","kind":"function","vis":"public","signature":"Public Function BinarySearch(ByVal Index As Long, ByVal GetCount As Long, Value As Variant, Optional ByRef Comparer As IComparer = Nothing) As Long Implements ArrayList.BinarySearch, IArrayList.BinarySearch","returnType":"Long"}, {"name":"BinarySearch_2","kind":"function","vis":"public","signature":"Public Function BinarySearch_2(ByRef Value As Variant) As Long Implements ArrayList.BinarySearch_2","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, {"name":"BinarySearch_3","kind":"function","vis":"public","signature":"Public Function BinarySearch_3(ByRef Value As Variant, ByRef Comparer As IComparer) As Long Implements ArrayList.BinarySearch_3","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, - {"name":"Bind","kind":"function","vis":"public","signature":"Public Function Bind(TargetList As IArrayList, Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant) As ListRange","returnType":"ListRange","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"Bind","kind":"function","vis":"public","signature":"Public Function Bind(TargetList As IArrayList, Optional ByVal Index As Variant, Optional ByVal GetCount As Variant) As ListRange","returnType":"ListRange","hidden":true,"attrs":[{"name":"Hidden"}]}, {"name":"C2IArrayList","kind":"function","vis":"private","signature":"Private Function C2IArrayList(Value As Variant) As IArrayList","returnType":"IArrayList"}, {"name":"C2IListRange","kind":"function","vis":"private","signature":"Private Function C2IListRange(Value As Variant) As IListRange","returnType":"IListRange"}, {"name":"Capacity","kind":"property","vis":"public","signature":"Public Property Get Capacity() As Long Implements ArrayList.Capacity, IArrayList.Capacity","returnType":"Long","accessor":"Get"}, {"name":"Capacity","kind":"property","vis":"public","signature":"Public Property Let Capacity(Value As Long) Implements ArrayList.Capacity, IArrayList.Capacity","accessor":"Let"}, {"name":"Class_Terminate","kind":"sub","vis":"private","signature":"Private Sub Class_Terminate()"}, {"name":"Clear","kind":"sub","vis":"public","signature":"Public Sub Clear() Implements ArrayList.Clear, IArrayList.Clear"}, - {"name":"Clone","kind":"function","vis":"public","signature":"Public Function Clone() As Variant Implements ArrayList.Clone, IArrayList.Clone","returnType":"Variant"}, + {"name":"Clone","kind":"function","vis":"public","signature":"Public Function Clone() As Variant Implements ArrayList.Clone, ICloneable.Clone","returnType":"Variant"}, {"name":"CloneTo","kind":"sub","vis":"private","signature":"Private Sub CloneTo(ByVal Target As IArrayList, Source As IArrayList, ByVal Index As Long, ByVal GetCount As Long) Implements ArrayList.CloneTo, IArrayList.CloneTo"}, {"name":"Contains","kind":"function","vis":"public","signature":"Public Function Contains(Value As Variant) As Boolean Implements ArrayList.Contains, IArrayList.Contains","returnType":"Boolean"}, {"name":"CopyTo","kind":"sub","vis":"public","signature":"Public Sub CopyTo(Target As Variant, Index As Long) Implements ArrayList.CopyTo, IArrayList.CopyTo"}, {"name":"Count","kind":"property","vis":"public","signature":"Public Property Get Count() As Long Implements ArrayList.Count, IArrayList.Count","returnType":"Long","accessor":"Get"}, {"name":"Count","kind":"property","vis":"private","signature":"Private Property Let Count(ByVal Value As Long)","accessor":"Let"}, {"name":"GetAddressOfItemAt","kind":"function","vis":"private","signature":"Private Function GetAddressOfItemAt(Index As Long) As LongPtr Implements ArrayList.GetAddressOfItemAt, IArrayList.GetAddressOfItemAt","returnType":"LongPtr"}, - {"name":"GetEnumerator","kind":"function","vis":"public","signature":"Public Function GetEnumerator(Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant, Optional ByVal GetStep As Long = 1, Optional ByRef ThisEnumerator As IEnumerator) As stdole.IUnknown Implements ArrayList.GetEnumerator, IArrayList.GetEnumerator","returnType":"stdole.IUnknown","attrs":[{"name":"Enumerator"}]}, + {"name":"GetEnumerator","kind":"function","vis":"public","signature":"Public Function GetEnumerator(Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant, Optional ByVal GetStep As Long = 1, Optional ByRef ThisEnumerator As IEnumerator) As stdole.IUnknown Implements ArrayList.GetEnumerator, IArrayList.GetEnumerator","returnType":"stdole.IUnknown","attrs":[{"name":"Enumerator"}],"description":"When using this enumerator for more advanced usage other than regular For Each usage, Index is always 0-based regardless of the current BaseIndex value in this ArrayList."}, {"name":"GetRange","kind":"function","vis":"public","signature":"Public Function GetRange(ByVal Index As Long, ByVal GetCount As Long) As IListRange Implements ArrayList.GetRange, IArrayList.GetRange","returnType":"IListRange"}, - {"name":"IndexOf","kind":"function","vis":"public","signature":"Public Function IndexOf(Value As Variant, Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant) As Long Implements ArrayList.IndexOf, IArrayList.IndexOf","returnType":"Long"}, + {"name":"IndexOf","kind":"function","vis":"public","signature":"Public Function IndexOf(Value As Variant, Optional ByVal Index As Variant, Optional ByVal GetCount As Variant) As Long Implements ArrayList.IndexOf, IArrayList.IndexOf","returnType":"Long"}, {"name":"IndexOf_2","kind":"function","vis":"public","signature":"Public Function IndexOf_2(ByRef Value As Variant, ByVal Index As Long, ByVal GetCount As Long) As Long Implements ArrayList.IndexOf_2","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, {"name":"IndexOf_3","kind":"function","vis":"public","signature":"Public Function IndexOf_3(ByRef Value As Variant) As Long Implements ArrayList.IndexOf_3","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, {"name":"Insert","kind":"sub","vis":"public","signature":"Public Sub Insert(ByVal Index As Long, Value As Variant) Implements ArrayList.Insert, IArrayList.Insert"}, @@ -423,24 +444,26 @@ {"name":"Item","kind":"property","vis":"public","signature":"Public Property Let Item(ByVal Index As Long, Value As Variant) Implements ArrayList.Item, IArrayList.Item","accessor":"Let"}, {"name":"Item","kind":"property","vis":"public","signature":"Public Property Set Item(ByVal Index As Long, Value As Variant) Implements ArrayList.Item, IArrayList.Item","accessor":"Set"}, {"name":"Items","kind":"function","vis":"public","signature":"Public Function Items() As Variant() Implements ArrayList.Items, IArrayList.Items","returnType":"Variant()","hidden":true,"attrs":[{"name":"Hidden"}],"description":"CAUTION: Limit usage only in For Each In .Items calls, do NOT assign the return value of .Items to another variable unless you know what you're doing."}, - {"name":"LastIndexOf","kind":"function","vis":"public","signature":"Public Function LastIndexOf(Value As Variant, ByVal Index As Variant, ByVal GetCount As Variant) As Long Implements ArrayList.LastIndexOf, IArrayList.LastIndexOf","returnType":"Long"}, + {"name":"LastIndexOf","kind":"function","vis":"public","signature":"Public Function LastIndexOf(Value As Variant, Optional ByVal Index As Variant, Optional ByVal GetCount As Variant) As Long Implements ArrayList.LastIndexOf, IArrayList.LastIndexOf","returnType":"Long"}, {"name":"LastIndexOf_2","kind":"function","vis":"public","signature":"Public Function LastIndexOf_2(ByRef Value As Variant, ByVal Index As Long) As Long Implements ArrayList.LastIndexOf_2","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, {"name":"LastIndexOf_3","kind":"function","vis":"public","signature":"Public Function LastIndexOf_3(ByRef Value As Variant, ByVal Index As Long, ByVal GetCount As Long) As Long Implements ArrayList.LastIndexOf_3","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, - {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New() Implements ArrayList.New"}, + {"name":"New","kind":"sub","vis":"private","signature":"Private Sub New(Optional ByVal Capacity As Long = 2, Optional ByVal BaseIndex As Long = 0) Implements ArrayList.New"}, {"name":"Remove","kind":"sub","vis":"public","signature":"Public Sub Remove(Value As Variant) Implements ArrayList.Remove, IArrayList.Remove"}, {"name":"RemoveAt","kind":"sub","vis":"public","signature":"Public Sub RemoveAt(ByVal Index As Long) Implements ArrayList.RemoveAt, IArrayList.RemoveAt"}, {"name":"RemoveRange","kind":"sub","vis":"public","signature":"Public Sub RemoveRange(ByVal Index As Long, ByVal GetCount As Long) Implements ArrayList.RemoveRange, IArrayList.RemoveRange"}, - {"name":"Reverse","kind":"sub","vis":"public","signature":"Public Sub Reverse(ByVal Index As Long, ByVal GetCount As Variant) Implements ArrayList.Reverse, IArrayList.Reverse"}, + {"name":"Reverse","kind":"sub","vis":"public","signature":"Public Sub Reverse(Optional ByVal Index As Variant, Optional ByVal GetCount As Variant) Implements ArrayList.Reverse, IArrayList.Reverse"}, {"name":"SetRange","kind":"sub","vis":"public","signature":"Public Sub SetRange(ByVal Index As Long, Target As Variant) Implements ArrayList.SetRange, IArrayList.SetRange"}, - {"name":"Sort","kind":"sub","vis":"public","signature":"Public Sub Sort(Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant, Optional Comparer As IComparer = Nothing) Implements ArrayList.Sort, IArrayList.Sort"}, + {"name":"Sort","kind":"sub","vis":"public","signature":"Public Sub Sort(Optional ByVal Index As Variant, Optional ByVal GetCount As Variant, Optional Comparer As IComparer = Nothing) Implements ArrayList.Sort, IArrayList.Sort"}, {"name":"Sort_2","kind":"sub","vis":"public","signature":"Public Sub Sort_2(ByRef Comparer As IComparer) Implements ArrayList.Sort_2","hidden":true,"attrs":[{"name":"Hidden"}]}, {"name":"SyncRoot","kind":"property","vis":"public","signature":"Public Property Get SyncRoot() As Variant Implements ArrayList.SyncRoot, IArrayList.SyncRoot","returnType":"Variant","accessor":"Get","hidden":true,"attrs":[{"name":"Hidden"}]}, {"name":"ToArray","kind":"function","vis":"public","signature":"Public Function ToArray() As Variant() Implements ArrayList.ToArray, IArrayList.ToArray","returnType":"Variant()"}, + {"name":"ToString","kind":"function","vis":"public","signature":"Public Function ToString(Optional TextQualifier As String = \"\"\"\", Optional ByVal UseNullAsEmpty As Boolean = True) As String Implements ArrayList.ToString, IArrayList.ToString","returnType":"String"}, {"name":"Version","kind":"property","vis":"public","signature":"Public Property Get Version() As Long Implements ArrayList.Version, IArrayList.Version","returnType":"Long","accessor":"Get","hidden":true,"attrs":[{"name":"Hidden"}]}, {"name":"pBaseIndex","kind":"field","vis":"private","signature":"Private pBaseIndex As Long"}, {"name":"pIndex","kind":"field","vis":"private","signature":"Private pIndex As Long"}, {"name":"pItems","kind":"field","vis":"private","signature":"Private pItems() As Variant"}, {"name":"pList","kind":"field","vis":"private","signature":"Private pList As IArrayList"}, + {"name":"pListIndex","kind":"field","vis":"private","signature":"Private pListIndex As Long"}, {"name":"pVersion","kind":"field","vis":"private","signature":"Private pVersion As Long"} ] } diff --git a/test/example-projects/contributed-ArrayListLib/Settings b/test/example-projects/contributed-ArrayListLib/Settings index 9fb889ac..71e88566 100644 --- a/test/example-projects/contributed-ArrayListLib/Settings +++ b/test/example-projects/contributed-ArrayListLib/Settings @@ -55,10 +55,10 @@ "path32": "/Packages/ArrayListLib", "path64": "/Packages/ArrayListLib", "symbolId": "ArrayListLib", - "versionBuild": "134", + "versionBuild": "1", "versionMajor": "1", - "versionMinor": "4", - "versionRevision": "0" + "versionMinor": "6", + "versionRevision": "151" } ], "project.settingsVersion": 1,