Julius Kavay · Aug 28, 2017 go to post

my quick test says there are two chars, CR and LF, on an AIX and on Ubuntu

USER>s str=##class(%GlobalCharacterStream).%New()
USER>d str.WriteLine("Test")
USER>d str.Rewind()
USER>zzdump str.Read(100)
0000: 54 65 73 74 0D 0A                                       Test..
USER>
​USER>w $zv
Cache for UNIX (IBM AIX for System Power System-64) 2017.1 (Build 792U) Mon Mar 20 2017 19:19:37 EDT
USER>

-----------------

USER>s str=##class(%GlobalCharacterStream).%New()
USER>d str.WriteLine("Test")
USER>d str.Rewind()
USER>zzdump str.Read(100)
0000: 54 65 73 74 0D 0A                                       Test..
USER>
​USER>w $zv
Cache for UNIX (Ubuntu Server LTS for x86-64) 2017.1 (Build 792U) Mon Mar 20 2017 19:22:31 EDT
USER>

Maybe, your problem has nothing to do with Cache?

Regards,

Julius

Julius Kavay · Aug 28, 2017 go to post

Cache dies not like an object property as a left side argument,

but you can solve the problem much simpler:

set context.NewID=$tr($j(context.PatientID,10)," ",0)

By the way, if your solution would work, the result would be longer then 10 chars

for example, if context.PatientID=123 then you

would get: "000000123000"

instead of: "0000000123"

Regards,

Julius

Julius Kavay · Aug 1, 2018 go to post

If you want to compare collections, then you have to know what do you compare.
Let look on this examples:

1) Hobbies
   old: music, sport
   new: sport, music
   
   One could say, there is no difference (if all hobbies are equally preferred) 
   
2) Work instructions
   old: format_disk, create_backup
   new: create_backup, format_disk
   
   In this example, if the work is done in a wrong sequence, the data are lost.

In other words, if you compare collections, you have to take in account the
importance or unimportance of sequencies, which means, the compare function
needs a third input argument

compare(old, new, relyOnSequence, ...)

By the way, your test() method has his very special "point of view" of lists:

set old1=$lb("blue","red","green"),    new1=$lb("green","red","blue")
set null=$lb("", "", "")

do ##class(...).test(old, new, .add, .del) write add=nul, del=nul --> 11
do ##class(...).test(old, old, .add, .del) write add=nul, del=nul --> 11
do ##class(...).test(nul, nul, .add, .del) write add=nul, del=nul --> 11

Is this an expected behavior?
 

Julius Kavay · Jan 17, 2019 go to post

"like a centipede with a wooden leg: 99 times tic and 1 toc the stream is then truncated and still requires extra coding"

That's the whole point! If we turncate the stream to $$$MaxStringLength, no matter where (in a calculated property or in a GETter method), just to present this (string)value to a variable, a function or whatever, then we can instantly stick to a string.

By the way, it's possible to store $$$MaxStringLength bytes in a local or global variable. Properties of classes are stored in globals as $listelements, so the maxlength for a single string property depends on things like count and types of other properties in the class and not least, does this class extends %Persistent or extends another class(es) which extends %Persistent - in this case we need some extra bytes for those (extended) class names too.

Julius Kavay · Jan 17, 2019 go to post

What do you mean with "from Code I am getting"? From which Code? ["\"KY\",\"TN\",\"AL\",\"GA\""] seems to be the same as the terminal output, except the quote chars are escaped...

Julius Kavay · Jul 31, 2019 go to post

We can save some more bytes:

f i=1:1:100 w $p(i_",Fizz,Buzz,FizzBuzz",",",i#5=0*2+'(i#3)+1),!

"640K ought to be enough for anybody." Oh, I meant, 64 bytes should be enough.

Julius Kavay · Jul 31, 2019 go to post

It depends on, what are the requirements... Usually we want to occupy as few (RAM) bytes as possible. Yes, it uses the fewest characters but far more bytes as Robert's solution.

Julius Kavay · Aug 1, 2019 go to post

No, it works and all installations but yes, it needs 8 Bit  characters.

A simple command like

write "abcd"

writes out four 8-Bit characters, independent of your installation (8-Bit or Unicode)

On Evgeny Shvarov's PC, a command like

write "Физз"

would write out four 16Bit characters, in sum 2*4 = 8 characters, each with 8 Bits

Julius Kavay · Aug 16, 2019 go to post

HELP: what are the markdown characters?

In the above answer, I put some words between angle braces, now all they are lost! OK, I try once again with an apostrophe.

  1. set stat=conn.Connect("odbcname", "username" ,"password") "username" is a MySQL user.

  2. If the content of the variable "variableX" is an alphanumeric value then put a single quote char (') around the value. Beforehand remove (or replace) all single quotes (with something else) from "variableX".

Julius Kavay · Aug 19, 2019 go to post

Some hints/questions:

  • did you installed the correct driver? I mean, do have Cache/Iris and the
    driver the same architecture (32 vs. 64 bit)?

  • I'm not an MySQL expert (I do not even have an MySQL DB), so I ask, is there any problem with the character size (1 byte vs. 2 bytes/unicode), if this applies?

  • with a DB-Tool, like WinSQL, try to make an SELECT (as suggested by Eduard Lebedyuk) statement. What is the error message?

Julius Kavay · Aug 19, 2019 go to post

I can see it right now, do you use

  • conn.Prepare(...) or
  • conn.PrepareW(...) ?

This should be coordinated with your MySQL installation.

Julius Kavay · Aug 19, 2019 go to post

Of course, the same goes for the other methods, which have a wide (...W) variant

Julius Kavay · Aug 26, 2019 go to post

Yes, you have right, thank you for the hint.  One never should add an alternate function without testing it!

The correct form is:

ClassMethod CountQ(node) As %Integer{end=node  if $data(@node)#10 set sum=1 else set sum=0 }  while 1 set node=$query(@node) quit:node=""||($name(@node,$qlength(end))'=end) if $increment(sum) }  quit sum}
Julius Kavay · Sep 5, 2019 go to post

With time measurements keep in mind:

- usually, you are not alone on a Cache server
  There are many other processes, some of them belongs to Cache other to the OS
  
- the time resolution (whatever you use: $now(), $zh) is also limited

- it depends also on the time, how long your mesurement runs (you are not alone!)
 

This is my short testroutine:

Times(iter=1E3,count=4) ; show times

    w ?3,"count   num+1   1+num   =$i()    $i()",!
    w ?15,"times in microseconds",!
    w $tr($j("",40)," ",-1),!
    
    f i=1:1:count d time(iter) s iter=iter*10
    q
    
time(iter)
{
    s f=1E6/iter // factor for "one operation in microseconds"
    
    w $j(iter,8)
    s num=0,t=$zh f i=1:1:iter { s num=num+1 } d t($zh-t*f)
    s num=0,t=$zh f i=1:1:iter { s num=1+num } d t($zh-t*f)
    
    s num=0,t=$zh f i=1:1:iter { s num=$i(num) } d t($zh-t*f)
    s num=0,t=$zh f i=1:1:iter { i $i(num) } d t($zh-t*f)
    w !
}

t(t)
{
    w $j(t,8,3)
}


and this is the output


USER>d ^Times(1,8)
   count   num+1   1+num   =$i()    $i()
               times in microseconds
----------------------------------------
       1   2.000   1.000   2.000   1.000
      10   0.100   0.100   0.100   0.200
     100   0.030   0.030   0.080   0.080
    1000   0.044   0.042   0.088   0.090
   10000   0.028   0.028   0.075   0.077
  100000   0.027   0.027   0.064   0.050
 1000000   0.018   0.014   0.031   0.032
10000000   0.011   0.011   0.031   0.032

USER>d ^Times(1,8)
   count   num+1   1+num   =$i()    $i()
               times in microseconds
----------------------------------------
       1   4.000   0.000   2.000   1.000
      10   0.100   0.100   0.100   0.100
     100   0.040   0.030   0.080   0.580
    1000   0.044   0.041   0.088   0.088
   10000   0.028   0.028   0.075   0.077
  100000   0.027   0.027   0.073   0.076
 1000000   0.027   0.021   0.032   0.032
10000000   0.011   0.011   0.031   0.032

USER>d ^Times(1,8)
   count   num+1   1+num   =$i()    $i()
               times in microseconds
----------------------------------------
       1   3.000   1.000   2.000   1.000
      10   0.100   0.000   0.100   0.100
     100   0.040   0.030   0.080   0.590
    1000   0.045   0.041   0.088   0.090
   10000   0.028   0.028   0.075   0.077
  100000   0.027   0.027   0.073   0.075
 1000000   0.015   0.012   0.031   0.032
10000000   0.011   0.011   0.031   0.032

USER>

USER>

USER>d ^Times(1,8)
   count   num+1   1+num   =$i()    $i()
               times in microseconds
----------------------------------------
       1   3.000   0.000   3.000   1.000
      10   0.100   0.000   0.100   0.100
     100   0.030   0.030   0.080   0.630
    1000   0.046   0.042   0.088   0.090
   10000   0.028   0.028   0.075   0.077
  100000   0.027   0.027   0.073   0.075
 1000000   0.014   0.012   0.032   0.032
10000000   0.011   0.011   0.031   0.032

USER>

I consider time measurements only as a rough approximations

Julius Kavay · Sep 6, 2019 go to post

Do you really think it makes a difference if my routine contains "set xx=xx+1" instead of "set xx=1+xx"?

If yes, try the following:

Times2 ; execution time measurement

  s num=0,t=$zh f i=1:1:1E6 { s num=num+1 } w $j($zh-t,8,6),!
  s num=0,t=$zh f i=1:1:1E6 { s num=num+1 } w $j($zh-t,8,6),!
  q

my output values are

USER>d ^Times2
0.047048
0.038218

USER>d ^Times2
0.034727
0.035160

USER>d ^Times2
0.044252
0.036175

USER>d ^Times2
0.045639
0.035366

Both loops are exactly the same! And now, please explain why the times are partly more than 20% different?

Julius Kavay · Sep 9, 2019 go to post

If you "donate" your XML-File a version info and an extra root-node:

<?xml version="1.0" encoding="UTF-8" ?>
<Data>
  <Details>
   ...
  </Details>
</Data>

and use, for example, the %XML.Textreader class (see belov).

Then with few lines of code the job is done:

XML ; XML to CSV
#define ROOT "Data"
#define DEL    ";"        ##; your CSV-delimiter, $c(9) or ";" or ...
#;
   set inpFile="c:\temp\example.xml"
   set outFile="c:\temp\example.csv"
   if ##class(%XML.TextReader).ParseFile(inpFile, .rdr) {
      if rdr.Read(), rdr.NodeType="element", rdr.Name=$$$ROOT {
         open outFile:"nw":1
         if $t {
            use outFile
            while rdr.Read() {
               if rdr.NodeType="element",rdr.Name="Details" {
                  set line=""
               } elseif rdr.NodeType="chars" {
                 set line=line_$lb(rdr.Value)
               } elseif rdr.NodeType="endelement",rdr.Name="Details" {
                 w $lts(line,$$$DEL),!
               } elseif rdr.NodeType="endelement",rdr.Name=$$$ROOT {
                  close outFile
                  quit
               }
            }
         } else { w "file open problem",! }
      } else { w "XML root-element problem",! }
   } else { w "XML structure problem",! }

Julius Kavay · Sep 10, 2019 go to post

The intention behind my post was, to give you one idea (of many other possibilities), how to convert XML to CSV. A empty (chars) element is just one of some other unhandled cases (missing tags, other tags inside of COLx tag, etc.).

If you need some speed and your XML-File obeys following constraints:

- the file is a correct XML-File
- contains only the 'Data', 'Details' and 'ColX' tags
- no selfclosing tags, like <sometag/>

then you could try the QAD-way (quick-and-dirty) of conversion.

Again, below an example routine (without excessive testing).
All ISC people and ordinary programer, please look the other way ;-))

Convert() Public
{
   set len=32000 // chunk size, a safe value is:
                 // 32767 - longestColumnValue - tagSizes   set fi="c:\temp\example-t.xml" // inp file (xml)   set fo="c:\temp\example-t.csv" // output file (csv)   open fi:"ru":1 open:$t fo:"nw":1   if '$t close fi quit   set xml=$$inp(fi,len) // first xml-chunk   set i=0, p=0   while 1 {      set i=$locate(xml,"\<\/?\w+\>",i,j,v) // next (opening or closing) tag      if i {                                            // let see, what we got          if v="<Details>" set row="", p=-1 // start a new rowelseif v="</Details>" out(fo,row) p=0 // complete, write outelseif p,v["<Col" p=j, o=$zstrip(v,"*AP") // new column, keep startelseif p,v["</Col" {$li(row,o)=$$val($e(xml,p,i-1)) // get value         }                                   // everything else is don't care         set i=jelse {         set tmp=$$inp(fi,len) // next xml-chunk         if tmp="" quit                                   // done         // remove processed data, add new one         if p>0 set xml=$e(xml,p,*)_tmp,p=1,i=0 else xml=$e(xml,i,*)_tmp,i=0 }      }   }   close fi   close fo
}

val(val){   quit $zstrip(val,"<>w") // add handling of charcter entities like &lt; etc.}out(fo,row){   use fo
   write $listtostring(row,";",1),! // delimiter!}inp(fn,len){   use fn
   try read xml#len catch xml="" // in case, $zeof-mode is off   quit xml}

The above converter reads a simple test XML-file with two million 'ColX' items in 5 seconds and creates a CSV file with 100000 rows and 20 columns (in each row).

Instead of file you can also use an stream.

Julius Kavay · Sep 14, 2019 go to post

By the way, he above works for unicode chars too, unless your pattern match table is outdated ;-))
Another solution is to use an loop and RegEx:


set str="abcd(123)/,op(56)*&^%$987ABC", i=0
set remove="[:punct:]|[:symbol:]"
set keep = "()"

while $locate(str,remove,i,i,c) { set:keep'[c $e(str,i-1)=" " }

Julius Kavay · Jan 15, 2020 go to post

In the class definition one can create  persistent-, serial-, registered-, abstract- and data-classes.

All of the above classes are contained in ##class(%Dictionary.PackageDefinition).GetPackageList(). Do you have a example for an class, which is not contained in the above method?

Or there is just a misunderstanding?

Julius Kavay · Jan 15, 2020 go to post

Ok, I started my Studio (2018.1.1) --> New Class --> ABC.Try --> Finish.

Then removed the Extends... and I left over with

Class ABC.Try
{

}

Then saved (but no compile) and my test in a terminal:

USER>s aa=##class(%Dictionary.PackageDefinition).GetPackageList()

USER>w aa.Find("ABC")
523
USER>

Julius Kavay · Sep 5, 2017 go to post
Class your.class
{
Property Colors As list Of %String;


ClassMethod toJson(list) As %String [ SqlProc ]
{
    s json=""
    f i=1:1:$ll(list) {
        s:i>1 json=json_"," s item=$lg(list,i)
        if '$ld(list,i) { s json=json_"null" }
        elseif item="" { s json=json_"""""" }
        elseif $lv(item) { s json=json_..toJson(item) }
        elseif $num(item,".")=item { s json=json_$fn(item,"N") }
        else {
            f c="\","/","""",$c(8),$c(9),$c(10),$c(12),$c(13) s item=$replace(item,c,"\"_$tr(c,$c(8,9,10,12,13),"btnfr"))
            f  q:'$locate(item,"[:cntrl:]",0,j,v)  s $e(item,j-1)="\u"_$e($zh($a(v)+65536),2,5)
            s json=json_""""_item_""""
        }
    }
    
    q "["_json_"]"
}
}

select your.class_toJson(Colors) from your.class

gives you the expected result . If you prefer  a direct  use of globals, then use

write ##class(your.class).toJson($lg(^your.global(theOID),theSlotNumber))

Julius Kavay · Oct 30, 2017 go to post

I think, your best bet is:

write $zstrip($zstrip(text,"*c"),"<=>w")

because you want to remoe ALL (i.e.: *) control chars but only SOME (i.e.: <=>) whitespaces.

You could try something like:

set chars=$c(0,1,2,3,....31, 32 /* blank */, ...<maybe other control chars, above 128>)

write $tr(text,chars)

hth

Julius Kavay · Jan 7, 2018 go to post

The  short answer ist:

a) look for Mnemonics 

b) write /cup(line, column)

c) use ?tab

For example:

 write #, "Hello", !, /cup(3,25),"Let's go....", !, "And now", ?20, "we are in column 20", !

Julius Kavay · Jul 5, 2018 go to post

On-the-fly and untested:

set del=<your_csv_delimiter> // <;>, <tab>, <whatever...>

 while'stream.AtEnd {
        set line=stream.ReadLine()
        write line,! // this is csv

       set json=[]

     for i=1:1:$l(line,del) do json.%Push($p(line,del,i))

    write json.%ToJSON(),!  // or whatever other action you need...
}

HTH

Julius

Julius Kavay · Aug 7, 2018 go to post

You are comparing apples wih oranges!

The line  (your case 1):

set dat=$lg(^TestD(id))    //dat=$lb("a","b","c","d","e")

sets dat to the FIRST list item (if present, or to "", if the list item is NOT present) of ^TestD(id)  but ONLY if ^TestD(id) is defined AND it is a Cache list.

In elsecase, you will get an <UNDEF> if ^TestD(id) does not exists or an <LIST> error, if ^TestD(id) exists but the content is not a list!

The line (your case 2):

 set dat=$g(^TEST2(id))   //dat = "a#b#c#d#e"

sets dat to the content of ^TEST2(id) , if  it exists or to "", if there is no ^TEST2(id)

Julius Kavay · Sep 14, 2018 go to post

You can do it either this way:

set OrgTypeId = $piece(line, ",", 2) // assuming, item 2 is an OID of ZenCrm.OrgType if ##class(ZenCrm.OrgType).ExistsId(OrgTypeId) { set rec.OrgType = ##class(ZenCrm.OrgType).%OpenId(OrgTypeId,0) }

or this way: do rec.OrgTypeSetObjectId($piece(line, ",", 2))

Julius Kavay · Sep 20, 2018 go to post

Hello Kevin,

if I anderstand you correctly, several your Cache systems (development, life, ...) will store their files on a third server (possibly on a file server) and you want to put the files from each (source) Cache (instance) into a different (target) folder, where the folder name being the "name" of the source Cache system - am I correct?

If yes, I think, your best choice ist:

...\hostNameOfCache\instanceNameofCache...

or, as John Murray suggested

...\GUIDofCacheInstance...

or just a fixed string like

...\kevinNotebook(development)...

You can put (John's) GUID or my "fixed string" in a (possibly system) Global like ^%Zconfig("path")=...

Why? Things like MAC- or IP-Addresses can change more often then you think. Especially IP-Addresses will change, hence are there DNS servers.

On the other hand, it's uncommon to change host- or instance names (of Cache or of whatever installation).

Your IP-Address preference has an other downside too. Many servers have more then one IP-Address (and if they do not have one today, maybe tomorrow!).

If you persist to take the systems IP-Address, here is, what you wanted:

set iplist = $system.INetInfo.GetListOfConfiguredInterfaces(0) for i = 1:1:$listlength(iplist) zw $list(iplist,i)

Regards and have a nice day Julius