go to post Steven Hobbs · May 23, 2022 One other quick comment: Note that WRITE ##class(DC.Test).IsString( ) // -> 1 The IsString function takes a missing argument and assumes it is a string value. Using an undefined variable as an argument to $LISTBUILD generates an "undefined" $LIST element and does not generate an <UNDEFINED> signal.
go to post Steven Hobbs · May 23, 2022 I should mention that the numeric comparisons $A($LB(val),2)<3 and $A($LB(val,2)>3 are not quite legal ObjectScript since the internal specification of $LIST string representation is subject to extension in the future. In previous Caché and IRIS releases the $LIST code type byte for a string could be 1 or 2. In the future, $LISTBUILD will support compressing string values and those compressed $LIST string elements will use $LIST code type values greater than 3. It is legal to use such undocumented $LIST representation techniques when debugging new code on a particular Caché/IRIS release. However, depending on such internal representations in a production application should be avoided since the internal representations can change in the future. Example: some programmers have used the string equality operation, ls1=ls2, to test if two $LIST strings contain the same values. They should have used the $LISTSAME(ls1,ls2) function call. This use of string equality on $LIST strings has resulted in broken applications in the past when extensions were added to the internal specification of $LIST strings.
go to post Steven Hobbs · May 23, 2022 The original ANSI M (also known as ANSI MUMPS) standard did not distinguish between a canonical numeric string versus a number. Many early implementations of this standard used strings of numeric characters as the internal representation of a number and did arithmetic on character strings instead of converting the numeric string to a binary representation and then using the hardware binary arithmetic instructions. The result of a numeric operation would always be a character string using the canonical numeric representation. In such an implementation there was no way to tell the difference between the character string "123" and the literal 123 since "123" was the canonical numeric string representation for that value. For performance reasons, InterSystems ObjectScript has several different internal representations it can use for the number 123. It can use the three character strintg "123"; it can use the 32-bit binary integer 123; it can use one of several representations of 123 in ObjectScripts' decimal floating point representation which has a 64-bit binary integer significand combined with an 8-bit power-of-10 exponent. Writing 12300E-2 will usually be represented with the decimal floating-point representation and not with the integer repersentation. The command WRITE $LISTSAME($LISTBUILD("123"),$LISTBUILD(12300E-2)) will write 1 because the string "123" and the numeric literal 12300E-2 are the same value. However WRITE $LISTBUILD("123")=$LISTBUILD(12300E-2)) will write 0 because the $LISTBUILD will will generate different string encodings in this situation even though the values are considered to be identical. Although $LISTBUILD could convert different internal representations into identical $LIST strings, it is faster just to place the internal representation into the generated binary string. This is why you must use $LISTSAME when you want to check if two $LIST strings contain the same value. There are a very few functions built into ObjectScript that have a behavior that depends on the internal representation of the argument. All of these function were inherited when InterSystems extended ObjectScript to have a compatible feature with the language implemented by other vendors who produced extended M/MUMPS systems. The most common function is $ZHEX(x) which turns a hex string to a decimal number and which turns a decimal number into a string. Thus, WRITE $ZHEX("10") will write number 16 while WRITE $ZHEX(10) will write the string "A". If you want to convert variable 'x' from an integer to hex then you need to write $ZHEX(+x) to make sure 'x' is using an internal numeric representation. If you want to convert variable 'x' from hex to an integer then you need to write $ZHEX(x_"") to make sure 'x' is using the string internal representation. One additional note: The literal 123.0 will use an internal numeric representation so WRITE "123.0"=123.0 will WRITE 0 since a string equality comparison operator will be done between the literal string "123.0" and the canonical numeric string "123". However, WRITE +"123.0"=123.0 will WRITE 1 because the unary plus operator converts the first operand to a numeric representation and then the string equality operator converts both numeric operands into their canonical numeric string representations making the string equality operation be the same as "123"="123".
go to post Steven Hobbs · May 18, 2022 The %Library.DynamicAbstractObject, %Library.DynamicArray and %Library.DynamicObject classes with the %ToJSON and %FromJSON methods first appeared in Caché 2016.2. I believe that 2016.2 also included JSON constructor syntax in ObjectScript. I.e., you could write SET jsonvar={"number":1.2, "string":"abcd",null,true} as an executable ObjectScript statement. Any JSON constant array or object is legal. I am not sure when JSON constructors involving ObjectScript expressions was allowed. E.g. SET jsonvar={"number":(1+.2+variable1), "string":("abcd"_variable2),null,false}. Newer versions of Caché have more JSON support and IRIS has even more support, including the %JSON.Adapter class. Inheriting the %JSON.Adapter class makes it possible to use the %JSONImport/%JSONExport methods which can read/writeJSON representation into/from ordinary class definitions.
go to post Steven Hobbs · May 18, 2022 In particular, an object is garbage collected when there are no ways to access that object using oref values starting from an oref-type value stored in a local variable. When an oref is converted to an integer (or to a string) then that does not count as an object reference. Store that integer/string in a PPG and then do KILLs (or otherwise make changes) of oref-type values referring to an object then that object will be deleted despite the fact a PPG (or any other variable) contains an integer/string reference to the object.
go to post Steven Hobbs · Apr 21, 2022 Sorry about that. My browser only showed this part of the URL: https://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?... I did not notice ?... which indicated a longer URL than what I was really looking at. The shorter URL I thought I was looking at gives the Class Reference page of the InterSystems documentation web site. The SubclassOf query in ##class(%Dictionary.ClassDefinitionQuery) executed by a SQL SELECT command on a particular instance does provide a way to look at the subclasses of a class on that instance. The query provides an even better way for executing code in program that needs to do a run-time search for information that is machine readable. The local Class Reference web page supported by a particular Caché instance is probably an easier way to find a human readable display of the subclasses. After clicking on a SubClasses Summary links then you probably want to again click on other links (especially the nested SubClasses Summary links.)
go to post Steven Hobbs · Apr 20, 2022 The class reference at docs.intersystems.com will tell you about InterSystems supplied classes but it will not tell you about user written classes that are extended from a system supplied abstract classes nor about a user class extended from other user classes. Look at the following with your browser: http://localhost:57772/csp/documatic/%25CSP.Documatic.cls , where localhost is the system name on which you are running Caché and 57772 is the WebServer port of your Caché instance. (If you were running IRIS your WebServer port would look something like 52773.) Once you are on the Class Reference web page then navigate to find the particular class in which you are interested. Look at the SubClasses Summary section to find inherited classes. You can click on the an inherited class name to navigate to the Class Reference information of that class.
go to post Steven Hobbs · Apr 19, 2022 Leon has not made a return comment and his original question would be ambiguous to some people. I often call the " character, the 'double quote' character while I often call the ' character the 'single quote' character. Other names I might use for the " character are 'quotation character' or 'ditto mark'. Other names for the ' character would be 'apostrophe' or 'apostrophe quotation character'. [[Notice I am using apostrophe quotation characters in this comment.]] I might describe "" characters as 'doubled quotation characters' or 'doubled double quotes'. Since Leon asked to 'remove double quotes (") from a string' my first impression would be to remove all " characters from the string. I.e., every $C(32) should be removed. If Leon wanted to only remove 'doubled quotation characters' then he probably would have written 'remove double quotes ("") from a string'.
go to post Steven Hobbs · Apr 11, 2022 If your long strings are coming from JSON representation then Set DynObj=##class(%DynamicObject).%FromJSON(...) will create a %Library.DynamicObject or %Library.DynamicArray object in memory containing the JSON array/object elements where the sizes are limited only by the amount of virtual memory the platform will allocate to your process. A string element of an object/array can have many gigabytes of characters (virtual memory permitting) and you can get the value of such a huge string element in the form of an in-memory, read-only %Stream doing: Set StreamVal=DynObj.%Get(key,,"stream") in cases where DynObj.%Get(key) would get a <MAXSTRING>. The StreamVal (class %Stream.DynamicBinary or %Stream.DynamicCharacter) is a read-only, random-access %Stream and it shares the same buffer space as the 'key' element of the DynObj (class %Library.DynamicObject) so the in-memory %Stream does not need additional virtual memory. You can then create a persistent object from a class in the %Steam package (%Stream.GlobalBinary, %Stream.GlobalCharacter, %Stream.FileBinary, %Stream.FileCharacter, or some other appropriate class.) You can then use the CopyFrom method to populate the persistent %Stream from the read-only, in-memory %Stream.DynamicBinary/Character.
go to post Steven Hobbs · Apr 4, 2022 Consider the following: USER>set num1=1,str1="""1""",num2=2,str2="""2""",num10=10,str10="""10""",abc="abc" USER>set (x(num1),x(str1),x(num2),x(str2),x(num10),x(str10),x(abc))="" USER>set i="" do { set i=$order(x(i)) q:i="" write i,! } while 1 1210"1""10""2"abc The WRITE command treats its expression arguments as ObjectScript string expressions so any argument expression with a numeric value is converted to a string value containing characters. Note that variables str1 and str2 are strings containing 3 characters starting with one double-quote character, ". The str1 and str2 values sort among other strings where the first character is a double-quote character. When you print these subscript strings, the subscripts from variables num1 and num2 print as one character strings, the subscript from variable num10 prints as a two character string, the subscripts from variables sub1, sub2 and abc print as three character strings and the subscript from variable str10 prints as a 4 character string. If you use ZWRITE instead of WRITE then ZWRITE will add extra quote marks, will add $C(int) expression syntax and will add other things so that the textual representation that is printed does not contain any unprintable characters and you can see trailing white space.
go to post Steven Hobbs · Apr 4, 2022 I wasn't advocating changing a system collation or changing a namespace collation. I only recommended using ##class(%Library.GlobalEdit) to change the subscript collation of a newly created individual global variable, leaving all other globals unchanged. You can see the collations loaded into an instance by executing 'DO ^|"%SYS"|COLLATE' . I believe in this case the user wants to use built-in collation 133, which should be the version of collation 5 that sorts only strings and does not sort numbers. It looks like 133 is now considered to be a "legacy collation" as I have problems finding it in the on-line documentation.
go to post Steven Hobbs · Apr 4, 2022 You might consider looking at the ##class(%GlobalEdit).Create(...) method. It has a 'Collation' argument which allows you to change the collation from the namespace default. Just choose a collation that does *NOT* sort canonical numeric strings in front of non-numeric strings. I don't recommend ever changing the default collation of a namespace as many utility routines depend on canonical numeric strings sorting in numeric order and not sorting in string order. Those utilities may not work in such a namespace.
go to post Steven Hobbs · Jan 28, 2022 The %GlobalCharacterStream class is deprecated (although I believe it will continue working.) You should consider replacing it with the %Stream.GlobalCharacter class.
go to post Steven Hobbs · Jan 4, 2022 You might consider using the %Library.DynamicObject and %Library.DynamicArray classes which are built into ObjectScript. ObjectScript supports JSON constructors for %DynamicObject, {...}, and for %DynamicArray, [...]. a %DynamicObject/Array can contain both JSON values and ObjectScript values. There is also a %FromJSON(x) which reads JSON objects/arrays when x is a %Stream or a FileNme or an ObjectScript %String containing JSON syntax. Here is an example from a recent IRIS release: USER>zload foo USER>zprintfoo() { set DynObj = { "notanumber":"28001", "aboolean":true, "anumber":12345, "adecimal":1.2, "adate":"01/01/2023", "adate2":"01-01-2023", "anull":null, "anull2":null, "anull3":null } write DynObj.%ToJSON(),!,! set DynObj.Bool1 = 1 ; Set without type do DynObj.%Set("Bool2",1,"boolean") ; Set with type set DynObj.old1 = "abc" ; Set without type do DynObj.%Set("new1","abc","null") ; Set with type set DynObj.old2 = "" ; Set without type do DynObj.%Set("new2","","null") ; Set with type write DynObj.%ToJSON()} USER>do ^foo(){"notanumber":"28001","aboolean":true,"anumber":12345,"adecimal":1.2,"adate":"01/01/2023","adate2":"01-01-2023","anull":null,"anull2":null,"anull3":null} {"notanumber":"28001","aboolean":true,"anumber":12345,"adecimal":1.2,"adate":"01/01/2023","adate2":"01-01-2023","anull":null,"anull2":null,"anull3":null,"Bool1":1,"Bool2":true,"old1":"abc","new1":"abc","old2":"","new2":null} Your can use ordinary ObjectScript assignment to assign ObjectScript values to entries in a %DynamicObject/Array. You can use the %Set(key,value,type) method to assign ObjectScript values into an object with an optional type parameter controlling which JSON value to which to convert the ObjectScript value. I.e., type "boolean" converts ObjectScript numeric expressions into JSON false/true values and type "null" converts an ObjectScript %String into a JSON string *except* that the empty string is converted to the JSON null value. (Note: some older implementations of %Set with type "null" only accepted "" as the value while new implementations accept any string expression as the value.) The JSON constructor operators in ObjectScript, besides accepting JSON literals as values, can also accept an ObjectScript expression enclosed in round parentheses. E.g., SET DynObj2 = { "Pi":3.14159, "2Pi":(2*$ZPI) }
go to post Steven Hobbs · Dec 8, 2021 When asking about a 'ROUTINE' you may be asking about the difference between a 'Routine', 'Procedure', 'Subroutine', 'Function', 'Label', 'Class' and 'Method' A 'Routine' is a source code file with name like 'myroutine.mac'. Source code can also be a 'Method' which is found in a 'Class' file with a name like 'myclass.cls" A Routine file can contain Procedures, Subroutines, Functions and Labels. See https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCOS_usercode#GCOS_usercode_overview for some InterSystems documentation. You can call a subroutine with the statement 'DO MySubroutine^myroutine(arg)'; You can call a function with the expression '$$MyFunction^myroutine(arg)'; You can call the procedure 'MyProcedure^myroutine(arg)' using either the syntax for a subroutine call or function call depending on whether you need the value returned by the procedure; and, You can Goto the source code following a label with the statement 'GO MyLabel^myroutine'. If you reference a subroutine/function/procedure/label inside a Routine file (e.g. myroutine.mac) and that subroutine/function/procedure/label access (e.g.$$MyFunction^myroutine(arg)) is referencing a name defined in the same Routine file then you do not have to specify the ^Routine name in the call syntax (e.g. $$MyFunction(arg)). The local variables used inside a subroutine or function are 'public' variables and those named variables are shared with all other subroutines and functions. You can use the NEW command inside a subroutine or function to temporarily create new public variables without modifying previous instances of public variables with the same names. The local variables used inside a procedure are private variables. The private variables are only available inside the procedure. Those names do not conflict with variables used by any caller of the procedure. Those names are not available in any code called by the procedure although it is possible to pass either the private variable or the value of the private variable as an argument when calling out of a procedure. The declaration of a procedure can optionally include a list of public variables. Names in public list reference the public variable when they are accessed by code in the procedure. You can use the NEW command with an argument that is a public variable within a procedure. A label defined inside a procedure is also private and it cannot be referenced by any GO command outside that procedure. Methods defined in a class file default to being a procedure with private variables and private labels. However, it is possible to specify that a method is a subroutine or function. Also, a method procedure declaration can optionally include a list of global variables.
go to post Steven Hobbs · Dec 1, 2021 The %GlobalBinaryStream class is deprecated and newly written code should instead use %Stream.GlobalBinary. Of course, existing code that is not needing modification can continue using %Library.GlobalBinaryStream..
go to post Steven Hobbs · Nov 7, 2021 “AlertText” is not the name of a JSON object field but is instead the value of a JSON array element. Assuming you have done your %FromJSON into variable ‘data’ then data.%Get(“params”).%Get(0) will evaluate to the string value “AlertText”.
go to post Steven Hobbs · Oct 27, 2021 As mentioned in another Developer Community Question, a recent version of IRIS would allow you to evaluate object.%Get("pdfKeyName",,"stream") which would return to you an %Stream object containing the JSON string in question as raw characters. Also, %Get in IRIS can support object.%Get("pdfKeyName",,"stream<base64") which would do Base64 decoding as it creates the %Stream. However, you said you need to stick with an older version of Caché/Ensemble which predates these %Get features. It is possible to convert the long JSON string component to a %Stream but it will take some parsing passes. (1) First use SET json1Obj=[ ].%FromJSON(FileOrStream) to create json1OBJ containing all the elements of the original JSON coming from a File or Stream. (2) If your pdf JSON string is nested in json1OBJ then select down to the closest containing %DynamicObject containing your pdf JSON string, I.e. Set json2Obj=json1Obj.level1.level2 if your original JSON looks like {"level1":{"level2":{"pdfKeyName":"Very long JSON string containing pdf", ...}, ...}, ...} (3) Create a new %Stream containing the JSON representation of that closest containing %DynamicObject. I.e., SET TempStream=##class(%Stream.TmpBinary).%New() DO json2Obj.%ToJSON(TempStream) (4) Read out buffers from TempStream looking for "pdfKeyName":" . Note that this 14-character string could span a buffer boundary. (5) Continue reading additional buffers until you find a "-characer not preceded by a \-character; Or until you find a "-character preceded by an even number of \-characters. (6) Take characters read in step (5) and pass them through $ZCVT(chars,"I","JSON",handle) to convert JSON string escape characters to binary characters and then write them to a %Stream.FileBinary (or some other Binary %Stream of your choosing.) (7) If your JSON "pdfKeyName" element contains Base64 encoding then you will also need to use $SYSTEM.Encryption.Base64Decode(string). Unfortunately $SYSTEM.Encryption.Base64Decode(string), unlike $ZCVT(chars,"I",trantable,handle), does not include a 'handle' argument to support the buffer boundary cases in those situations where you have broken a very large string into smaller buffers. Therefore you should remove the whitespace characters from 'string' before calling $SYSTEM.Encryption.Base64Decode(string) and you must make sure the argument 'string' has a length which is a multiple of 4 so that a group of 4 data characters cannot cross a buffer boundary. Again the more complete support in IRIS can do steps (2) through (7) without worrying about buffer boundaries by executing Set BinaryStreamTmp = json1Obj.level1.level2.%Get("pdfKeyName",,"stream<base64") and you can then copy the BinaryStreamTmp contents to wherever you want to resulting .pdf file.
go to post Steven Hobbs · Oct 26, 2021 The %DynamicAbstractObject classes first appeared in version 2016.2 so JSON support in software versions 2017.x will not have many of the more recent new features. If possible, you really should upgrade to more recent InterSystems versions so you can use more modern functionality.
go to post Steven Hobbs · Oct 26, 2021 The latest version of IRIS will have improved %DynamicObject (and the %DynamicArray) class objects. They will support a method call like obj.%Get(key,,”stream”) which will return a %Stream.DynamicCharacter oref and this %Stream can contain a very large number of characters. This can be copied into a %Stream.GlobalCharacter or a %Stream.FileCharacter if you want to save those characters in a persistent object. This new form of %Get will also be able to include encoding/decoding using Base64 representation. Similar extensions have been added to the %Set method.