QlikView "field not found" workaround for adding a new field - field

I am trying to get my script to reload in QlikView without success. I have added a new field (named Litres) to my most recently generated QVD files. However, my older QVD files do not have this Litres field which therefore causes the script to error out with the "Field not found" error.
I am trying to do the following:
Search for all the fields in the QVD
If the Litres field exists, then load the data into QlikView.
If not, create the field with a value of zero and continue.
The above should not result in an error.
My current script is below:
/* get all sales csvs */
sales:
load 2014 as Year
AutoGenerate 0;
set FilePath = ..\..\SourceData\qv-sales*.csv;
for each File in filelist('$(FilePath)')
/* load qvd file if it is newer than csv file */
temptable1:
first 1 LOAD
*
FROM $(File)
(txt, codepage is 1252, embedded labels, delimiter is ',', msq);
IF FieldNumber('Litres' , 'temptable1') <> null then
SET "Litres";
SET "Litres LY" ;
SET dyncode2 = P-Value;
SET dyncode3 = O-P-Value;
SET dyncode4 = P-Value LY;
SET dyncode5 = O-P-Value LY;
SET dyncode6 = P-Qty;
SET dyncode7 = P-O-Qty;
SET dyncode8 = P-Qty LY;
SET dyncode9 = P-O-Qty LY;
ENDIF;
DROP TABLE temptable1;
let qvdFile = replace('$(File)','csv','qvd');
if (QvdCreateTime('$(qvdFile)') >= FileTime('$(File)')) then
/* load qvd file if it is newer than csv file */
sales:
Concatenate (sales) load
"GP",
"O-GP",
"Litres",
"Litres LY",
"GP LY",
"O-GP LY",
$(dyncode2) as "P-Value",
$(dyncode3) as "O-P-Value",
$(dyncode4) as "P-Value LY",
$(dyncode5) as "O-P-Value LY",
$(dyncode6) as "P-Qty",
$(dyncode7) as "P-O-Qty",
$(dyncode8) as "P-Qty LY",
$(dyncode9) as "P-O-Qty LY" from $(qvdFile) (qvd);
else
/* create temp store key1 & key2 are manually handled synthetic keys */
/* Dummy field is to prevent autoconcatenation problems. */
temptable:
noconcatenate Load
"GP",
"O-GP",
"Litres" ,
"Litres LY",
"GP LY",
"O-GP LY",
$(dyncode2) as "P-Value",
$(dyncode3) as "O-P-Value",
$(dyncode4) as "P-Value LY",
$(dyncode5) as "O-P-Value LY",
$(dyncode6) as "P-Qty",
$(dyncode7) as "P-O-Qty",
$(dyncode8) as "P-Qty LY",
$(dyncode9) as "P-O-Qty LY",
autonumber(1) as dummy1
FROM $(File) (ansi, txt, delimiter is ',', embedded labels);
/* create qvd file from temp table */
if (ScriptErrorCount = 0) then
Store temptable into $(qvdFile);
endif
sales:
concatenate("sales") load
"Litres",
"Litres LY",
"GP LY",
"O-GP LY"
"P-Value",
"O-P-Value",
"P-Value LY",
"O-P-Value LY",
"P-Qty",
"P-O-Qty",
"P-Qty LY",
"P-O-Qty LY"
resident temptable;
/* drop temp table */
DROP TABLE temptable;
endif
next File
Thanks in advance!

You can use the FieldNumber function to determine whether a field exists in a table. For example FieldNumber('MyField', 'MyTable') returns the position of MyField within MyTable. If MyField does not exist in MyTable the function returns zero.
You can adapt this to your advantage by loading all fields from your QVD into a temporary table and then checking to see if this table contains the field. If it does, you can continue loading. If not, you can simply set the field to zero.
I have adapted your script and inserted an IF for this purpose:
/* get all sales csvs */
sales:
load 2014 as Year
AutoGenerate 0;
set FilePath = ..\..\SourceData\qv-sales*.csv;
for each File in filelist('$(FilePath)')
/* load qvd file if it is newer than csv file */
temptable1:
first 1 LOAD
*
FROM $(File)
(txt, codepage is 1252, embedded labels, delimiter is ',', msq);
IF FieldNumber('Litres' , 'temptable1') <> null then
SET "Litres";
SET "Litres LY" ;
SET dyncode2 = P-Value;
SET dyncode3 = O-P-Value;
SET dyncode4 = P-Value LY;
SET dyncode5 = O-P-Value LY;
SET dyncode6 = P-Qty;
SET dyncode7 = P-O-Qty;
SET dyncode8 = P-Qty LY;
SET dyncode9 = P-O-Qty LY;
ENDIF
DROP TABLE temptable1;
let qvdFile = replace('$(File)','csv','qvd');
if (QvdCreateTime('$(qvdFile)') >= FileTime('$(File)')) then
/* load qvd file if it is newer than csv file */
fieldcheck:
FIRST 1
NOCONCATENATE
LOAD
*
FROM $(qvdFile) (qvd);
if FieldNumber('Litres','fieldcheck') = 0 then
drop table fieldcheck;
sales:
Concatenate (sales) load
"GP",
"O-GP",
0 as "Litres",
0 as "Litres LY",
"GP LY",
"O-GP LY",
$(dyncode2) as "P-Value",
$(dyncode3) as "O-P-Value",
$(dyncode4) as "P-Value LY",
$(dyncode5) as "O-P-Value LY",
$(dyncode6) as "P-Qty",
$(dyncode7) as "P-O-Qty",
$(dyncode8) as "P-Qty LY",
$(dyncode9) as "P-O-Qty LY" from $(qvdFile) (qvd);
else
drop table fieldcheck;
sales:
Concatenate (sales) load
"GP",
"O-GP",
"Litres",
"Litres LY",
"GP LY",
"O-GP LY",
$(dyncode2) as "P-Value",
$(dyncode3) as "O-P-Value",
$(dyncode4) as "P-Value LY",
$(dyncode5) as "O-P-Value LY",
$(dyncode6) as "P-Qty",
$(dyncode7) as "P-O-Qty",
$(dyncode8) as "P-Qty LY",
$(dyncode9) as "P-O-Qty LY" from $(qvdFile) (qvd);
endif
else
/* create temp store key1 & key2 are manually handled synthetic keys */
/* Dummy field is to prevent autoconcatenation problems. */
temptable:
noconcatenate Load
"GP",
"O-GP",
"Litres" ,
"Litres LY",
"GP LY",
"O-GP LY",
$(dyncode2) as "P-Value",
$(dyncode3) as "O-P-Value",
$(dyncode4) as "P-Value LY",
$(dyncode5) as "O-P-Value LY",
$(dyncode6) as "P-Qty",
$(dyncode7) as "P-O-Qty",
$(dyncode8) as "P-Qty LY",
$(dyncode9) as "P-O-Qty LY",
autonumber(1) as dummy1
FROM $(File) (ansi, txt, delimiter is ',', embedded labels);
/* create qvd file from temp table */
if (ScriptErrorCount = 0) then
Store temptable into $(qvdFile);
endif
sales:
concatenate("sales") load
"Litres",
"Litres LY",
"GP LY",
"O-GP LY"
"P-Value",
"O-P-Value",
"P-Value LY",
"O-P-Value LY",
"P-Qty",
"P-O-Qty",
"P-Qty LY",
"P-O-Qty LY"
resident temptable;
/* drop temp table */
DROP TABLE temptable;
endif
next File

If my understanding is correct you need to regenerate your QVDs because you added a new field which doesn't exists in the previous ones, so you recreate them from the source (txt) and store them.
A simpler way to achieve this is to take advantage of the fact that when you do a forced concatenate with a table the missing fields will be generated with null, which we can use with the function alt to transform the null into 0.
so taking your example:
/* get all sales csvs */
sales:
load 2014 as Year
AutoGenerate 0;
set FilePath = ..\..\SourceData\qv-sales*.csv;
for each File in filelist('$(FilePath)')
let qvdFile = replace('$(File)','csv','qvd');
//this generate the field Litres, exists or not
tmp: noconcatenate load null() as Litres autogenerate(0);
if (QvdCreateTime('$(qvdFile)') >= FileTime('$(File)')) then
/* load qvd file if it is newer than csv file */
concatenate(tmp) LOAD * FROM $(qvdFile) (qvd);
else
concatenate(tmp) LOAD * FROM $(File)
(txt, codepage is 1252, embedded labels, delimiter is ',', msq);
endif;
tmp_sales:
noconcatenate
load
alt(Litres,0) as Litres,
"Litres LY",
"GP LY",
"O-GP LY"
"P-Value",
"O-P-Value",
"P-Value LY",
"O-P-Value LY",
"P-Qty",
"P-O-Qty",
"P-Qty LY",
"P-O-Qty LY"
resident tmp;
drop table tmp;
//if it was newer that the csv we store it
if (QvdCreateTime('$(qvdFile)') >= FileTime('$(File)')) then
Store tmp_sales into $(qvdFile);
endif
concatenate(sales) load * resident tmp_sales;
drop table tmp_sales;
next File

Related

stored procedure failing with "Arithmetic overflow error converting varchar to data type numeric." error even though I cast all fields explicitly

I've spent an inordinate amount of time on getting this one stored proc to work, it's basically putting results from the query into an html format that is getting emailed. The query that the proc collects data from works fine and returns results as expected however when the output is attempted to be put into the table for the email it fails with the arithmetic overflow error.
DECLARE #sCompanyName varchar(50) = 'ITT Discrepancy'
DECLARE #sEmailTo varchar(128) = ''
DECLARE #sMailProfile varchar(128) = 'Database Notification Profile'
-- Other declarations
DECLARE #sEMailSubject varchar(100) = #sCompanyName + convert(varchar(10),Convert(date,getdate()))
DECLARE #sMessageBody varchar(max) = '', #sITT varchar(max) = '', #iMailItemID int = 0
-- Exit if there are no records to process
IF NOT EXISTS(select ORDDOCID, ITEMNMBR, TRNSFQTY, case when ct is null then 0 else ct end as 'SerialsScanned', TRNSFQTY-ct as 'Diff' from
(select ORDDOCID, ITEMNMBR,TRNSFQTY from ZTEST.dbo.SVC00701 where TRNSFLOC = 'TS-PF' and STATUS in (3,4)) as gp
left join
(select OrderNumber, ItemNumber, COUNT(serial) as 'ct' from ManualScan.dbo.SoldItems where TranType = 'ITT' group by OrderNumber, ItemNumber) as srl on gp.ORDDOCID = srl.OrderNumber and gp.ITEMNMBR = srl.ItemNumber
where TRNSFQTY-(case when ct is null then 0 else ct end)>0)
RETURN
-- Set the header
SET #sMessageBody = '<html><head><style type="text/css">
.style1
{
width: 100%;
border-style: solid;
border-width: 1px;
}
</style>
</head>
<p style="font-size: large; color: #CC3300;">
' + #sCompanyName + '
</p>'
-- Set the Items Received
DECLARE #sITTnum varchar(31), #sItemNum varchar(31), #sITTQty numeric(19,5), #sSrlQty numeric(18,0), #sDiff numeric(18,0)
DECLARE cITT CURSOR FOR
select ORDDOCID, ITEMNMBR, cast(TRNSFQTY as numeric(18,0)), cast(case when ct is null then 0 else ct end as numeric(18,0)) as 'SerialsScanned', cast(TRNSFQTY as numeric(18,0))-cast(case when ct is null then 0 else ct end as numeric(18,0)) as 'Diff'
from
(select ORDDOCID, ITEMNMBR,cast(TRNSFQTY as numeric(18,0)) as 'TRNSFQTY' from ZTEST.dbo.SVC00701 where TRNSFLOC = 'TS-PF' and STATUS in (3,4)) as gp
left join
(select OrderNumber, ItemNumber, cast(case when COUNT(serial) is null then 0 else COUNT(serial) end as numeric(18,0)) as 'ct' from ManualScan.dbo.SoldItems where TranType = 'ITT' group by OrderNumber, ItemNumber) as srl on gp.ORDDOCID = srl.OrderNumber and gp.ITEMNMBR = srl.ItemNumber
where TRNSFQTY-(case when ct is null then 0 else ct end)>0
OPEN cITT
FETCH NEXT FROM cITT INTO #sITTnum, #sItemNum, #sITTQty, #sSrlQty, #sDiff
WHILE ##FETCH_STATUS = 0
BEGIN
`it says error occurs at this line...`
SELECT #sITT = #sITT + '<tr><td style="border-style: solid; border-width: thin">' + #sITTnum +
'</td><td style="border-style: solid; border-width: thin">' + #sItemNum +
`but if I comment out from these lines it works fine so something in these values/variables`
'</td><td style="border-style: solid; border-width: thin">' + #sITTQty +
'</td><td style="border-style: solid; border-width: thin">' + #sSrlQty +
'</td><td style="border-style: solid; border-width: thin">' + #sDiff +
'</td></tr>'
FETCH NEXT FROM cITT INTO #sITTnum, #sItemNum, #sITTQty, #sSrlQty, #sDiff
END
CLOSE cITT
DEALLOCATE cITT
IF #sITT <> ''
SET #sITT = '<table class="style2"
style="font-family: "Times New Roman", Times, serif; border-style: solid; border-width:thin">
<tr>
<td style="border-style: solid; border-width: thin" >
<b>ITT Num</b></td>
<td style="border-style: solid; border-width: thin" >
<b>Item Number</b></td>
<td style="border-style: solid; border-width: thin">
<b>ITT Quantity</b></td>
<td style="border-style: solid; border-width: thin">
<b>Scanned Quantity</b></td>
<td style="border-style: solid; border-width: thin">
<b>Difference</b></td>
</tr>' + #sITT + '</table></body></html>'
SET #sMessageBody = #sMessageBody + #sITT
EXEC msdb..sp_send_dbmail
#profile_name = #sMailProfile,
#body_format = 'HTML',
#recipients = #sEmailTo,
#subject = #sEMailSubject,
#body = #sMessageBody,
#mailitem_id = #iMailItemID OUTPUT
These are the results of the query:
ORDDOCID ITEMNMBR (No column name) SerialsScanned Diff
T100742 APP-MU8X2LL/A 100 0 100
Any guidance would be extremely appreciated.
In the failing line you essentially have:
declare #sITTQty numeric(19,5); -- = ...
declare #sITT varchar(max); -- = ...
set #sITT = #sITT
+ '</td><td style="border-style: solid; border-width: thin">'
+ #sITTQty
-- + ...
So you are trying to add a numeric to a varchar. This attempts to promote the varchar data to numeric, and that is why you are getting the error.
Add an explicit cast to #sITTQty
declare #sITTQty numeric(19,5);
declare #sITT varchar(max);
set #sITT = #sITT
+ '</td><td style="border-style: solid; border-width: thin">'
+ cast(#sITTQty as varchar(19))
-- + ...
Edit: You will have to do the same with #sDiff and #sSrlQty

Rails query join to internal table count

I have two tables:
Player --> has_many FantasyStarts
FantasyStarts --> belongs_to Player
In english, I am trying to find all SeasonStats from the last year and week that were the first entry for that player in the whole table (ie who started last week and it was their first 'start' ever recorded in the table). I have constructed a bare sql query that works great, but I am trying to improve my rails query skills:
FantasyStart.includes(:player).find_by_sql("with count_table as (select player_id, count(*) as num_starts from fantasy_starts where position != 'BN' group by player_id) select * from fantasy_starts join count_table on fantasy_starts.player_id = count_table.player_id where week = 13 and year = 2019 and count_table.num_starts = 1 and position != 'BN' ")
I have also figure out a 'rails way' of creating the map of counts I would join to:
FantasyStart.where.not(position: 'BN').group(:player_id).count
which yields:
=> {184=>2, 2562721=>5, 2540215=>12, 100004=>57, 100001=>26, 100006=>62, 2505785=>5, 2561029=>1, 2541316=>1, 2558954=>1, 2552408=>12, 2532820=>60, 2507999=>25, 2506194=>16, 2505600=>18, 2532977=>6, 2507164=>75, 2495441=>4, 100022=>23, 2543704=>2, 2532807=>1, 81288=>1, 2550658=>4, 2506386=>15, 2560809=>11, 2533349=>4, 2560735=>3, 2557976=>19, 2556521=>15,....
hypothetically i could use the map above to filter the main query and only include player_ids that have a count of 1 in that map.
any good ideas for a next step?
Answering to your answer;
That's because you're chaining pluck to your "nested" query, that way ActiveRecord isn't able to perform a subquery, and instead of that it makes another query and use the result of that, which is an array to use with an IN clause.
Try instead removing the pluck, leaving just select:
FantasyStart.where(week: 13, year: 2019).where.not(position: 'BN').where(player_id: FantasyStart.where.not(position: 'BN').group(:player_id).having('count(*) = 1').select(:player_id))
It should generate something like this:
SELECT fantasy_starts.*
FROM fantasy_starts
WHERE fantasy_starts.week = $1
AND fantasy_starts.year = $2
AND fantasy_starts.position != $3
AND fantasy_starts.player_id IN (
SELECT fantasy_starts.player_id
FROM fantasy_starts
WHERE fantasy_starts.position != $4
GROUP BY fantasy_starts.player_id
HAVING (count(*) = 1)
) [[week, 13], [year, 2019], [position, BN], [position, BN]]
It seems you could shorten the query by removing the != from the subquery:
FantasyStart
.where.not(position: 'BN')
.where(player_id: FantasyStart
.group(:player_id)
.having('count(*) = 1')
.select(:player_id), week: 13, year: 2019)
hey i think i made progress curious what one of you more experience folk might think:
FantasyStart.where(week: 13, year: 2019).where.not(position: 'BN').where(player_id: FantasyStart.select(:player_id).where.not(position: 'BN').group(:player_id).having('count(*) = 1').pluck(:player_id))
this generates:
(8.4ms) SELECT "fantasy_starts"."player_id" FROM "fantasy_starts" WHERE "fantasy_starts"."position" != $1 GROUP BY "fantasy_starts"."player_id" HAVING (count(*) = 1) [["position", "BN"]]
FantasyStart Load (2.4ms) SELECT "fantasy_starts".* FROM "fantasy_starts" WHERE "fantasy_starts"."week" = $1 AND "fantasy_starts"."year" = $2 AND "fantasy_starts"."position" != $3 AND "fantasy_starts"."player_id" IN ($4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $40, $41, $42, $43, $44, $45, $46, $47, $48, $49, $50, $51, $52, $53, $54, $55, $56, $57, $58, $59, $60, $61, $62, $63, $64, $65, $66, $67, $68, $69, $70, $71, $72, $73, $74, $75, $76, $77, $78, $79, $80, $81, $82, $83, $84, $85, $86, $87, $88, $89, $90, $91, $92, $93, $94, $95, $96, $97, $98, $99, $100, $101, $102, $103, $104, $105, $106, $107, $108, $109, $110, $111, $112, $113, $114, $115, $116, $117, $118, $119) LIMIT $120
this is still two queries so i know it could be better...

NSIS. Post russian text with inetc plugin

I'm writing custom uninstaller feedback window. There are radiobutton with default reasons and textfield, where users write their own uninstall reason. I replace default reason with string constant and post it without any problems. If i send english text from textfield, it works. But not the russian. Server send error 500 and can't encode my data. How to pass data on russian in right way?
I'm build Unicode installer. Server expect UTF-8 data.
$reason_edit_data - text from textfield
$CB_#_State - state of # radiobutton (checked/unchecked)
; **REPLACE DEFAULT REASON WITH STRING CONSTANT**
StrCpy $reason "NO_DEFAULT_REASON"
${If} $CB_1_State == ${BST_CHECKED}
StrCpy $reason "CANT_FIGURE_OUT"
${EndIf}
${If} $CB_2_State == ${BST_CHECKED}
StrCpy $reason "FAILED_WITH_PLUGINS"
${EndIf}
${If} $CB_3_State == ${BST_CHECKED}
StrCpy $reason "ERRORS_IN_APP"
${EndIf}
${If} $CB_4_State == ${BST_CHECKED}
StrCpy $reason "SUB_USE_WEB"
${EndIf}
${If} $CB_5_State == ${BST_CHECKED}
StrCpy $reason "REINSTALL_APP"
${EndIf}
; **COPY TEXTFIELD DATA TO VARIABLE. PROBLEM WITH COMMENT**
${If} $reason_edit_data == ""
StrCpy $comment "NO_EXTRA_REASON"
${Else}
StrCpy $comment "$reason_edit_data"
${EndIf}
; **POST DATA. HEADER JUST FOR EXAMPLE**
inetc::post '{ "reason": "$reason", "comment": "$comment" }' \
/TOSTACKCONV \
/SILENT \
/HEADER
"https://api.example.com/api/uninstallations" \
/END
Pop $0
MessageBox MB_OK "status: $0"
INetC is limited to UTF-16LE and the active Ansi codepage. A /UTF8DATA switch could probably be added but in the meantime you have to put the post data in a file:
!include LogicLib.nsh
Function WriteStringAsUTF8ToFile
Exch $1 ; File path
Exch
Exch $2 ; NSIS string & File handle
Push $0
Push $3 ; UTF-8 string buffer
System::Call 'KERNEL32::WideCharToMultiByte(i65001,i0,wr2,i-1,p0,i0,p0,p0)i.r0'
${If} $0 = 0
fail_simple:
StrCpy $1 ""
${Else}
System::Call '*(&i$0)p.r3'
${IfThen} $3 P= 0 ${|} Goto fail_simple ${|}
System::Call 'KERNEL32::WideCharToMultiByte(i65001,i0,wr2,i-1,pr3,ir0,p0,p0)'
IntOp $0 $0 - 1 ; Don't write the terminator
FileOpen $2 $1 w
${IfThen} $2 == "" ${|} Goto fail ${|}
System::Call 'KERNEL32::WriteFile(pr2,pr3,ir0,*i,p0)i.r0'
${IfThen} $0 = 0 ${|} StrCpy $1 "" ${|}
FileClose $2
Goto done
fail:
StrCpy $1 ""
done:
System::Free $3
${EndIf}
Pop $3
Pop $0
Pop $2
Exch $1
FunctionEnd
Section
InitPluginsDir
Push "${U+2115}SIS" ; String to write as UTF-8
Push "$PluginsDir\data.txt"
Call WriteStringAsUTF8ToFile
Pop $0
${If} $0 == ""
MessageBox mb_ok "Error: Unable to write post data to file!"
${Else}
inetc::post $0 /FILE /TOSTACK "http://example.com/post" "" /END
Pop $1 ; "OK" on success
Pop $2
MessageBox mb_ok "Result: $1$\nResponse: $2"
${EndIf}
SectionEnd

FindFirst returns folders in English - Delphi

Good morning ... I am listing all files in a directory ... But I am facing the following problem ... When folders are coming All in English ... But the operating system is Ministry of Defence for the Portuguese ... How to list it according to the operating system language:
Ex: Program Files (EN) -> Arquivos de Programa (PT)
if FindFirst (directory + '*. *', faAnyFile, search_rec) = 0 then
       begin
         repeat
           Form4.ListView1.Items.Add.Caption: = search_rec.Name;
             lista.Add (search_rec.Name);
         Until FindNext (search_rec) <> 0;
         FindClose (search_rec);
       end;
Ok Portuguese:
SHGetFileInfo(PChar(strPath + SearchRec.Name), 0, FileInfo,
SizeOf(FileInfo), SHGFI_DISPLAYNAME);
Listitem.Caption := FileInfo.szDisplayName;

Create Byte array from NSMutableArray

I want to create a Byte Array like this one;
Byte UUID[] = {0xEB, 0xEF, 0xD0, 0x83, 0x70, 0xA2, 0x47, 0xC8, 0x98, 0x37, 0xE7, 0xB5, 0x63, 0x4D, 0xF5, 0x24};
But the problem here I am facing is, I need to fill all the elements in the above array programatically from a NSMutableArray that holds the values as below;
(
0xEB,
0xEF,
0xD0,
0x83,
0x70,
0xA2,
0x47,
0xC8,
0x98,
0x37,
0xE7,
0xB5,
0x63,
0x4D,
0xF5,
0x24
)
I have tried with the integer values of each index but it is showing '/0' in the Byte Array.
If anyone have any information regarding this please share.
Thanks
Assuming that you have an array of strings "0xEB", "0xEF", ..., the following should work:
NSArray *array = #[#"0xEB", #"0xEF", #"0xD0", #"0x83", #"0x70", #"0xA2", #"0x47", #"0xC8", #"0x98", #"0x37", #"0xE7", #"0xB5", #"0x63", #"0x4D", #"0xF5", #"0x24"];
Byte UUID[16];
for (int i = 0; i < 16; i++) {
UUID[i] = strtoul([array[i] UTF8String], NULL, 16);
}
This works even if the strings do not have the "0x" prefix:
NSArray *array = #[#"EB", #"EF", ...]
because strtoul(string, ..., 16) reads a string with or without "0x" prefix
in base 16, and converts it to an integer.

Resources