Freemarker: Get element from array by index - freemarker

I have this code:
<#local slots = time_utils.get_slots(objectArray) />
<#local days = time_utils.get_short_days(objectArray) />
<#local index = 0 />
<#list days as day>
<#list slots as slot>
<#if time_utils.is_slot_available(objectArray[index], slot, day)> bla bla </#if>
<#local index = index + 1 />
</#list>
</#list>
The function:
<#function is_slot_available date slot short_date>
<#local hour_of_date = '${date.startsAt?string["HH"]}' />
<#local day_of_date = '${date.startsAt?string["dd"]}' />
<#if (hour_of_date == '${slot[6..7]}') && (day_of_date == '${short_date[short_date?length-5..short_date?length-4]}')>
<#return true />
</#if>
<#return false />
</#function>
When I run this code, I have the error:
Error executing macro: is_slot_available
required parameter: date is not specified.
I don't get this error when I replace index by 0 or any number in objectArray[index] in the function call.
So what's the rignt way to do this?
Thanks!

Thanks to #ddekany:
I guess the value of index increases until it goes out of range, or
runs into an array item that stores null.
That was the problem.

Related

How to get the values of several xml tags and pass them to the url as a string using Freemarker

<?xml version="1.0" encoding="UTF-8"?>
<tns:Map xmlns:tns="urn://xxx" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<tns:type>2</tns:type>
<tns:code>CAT</tns:code>
<tns:code>DOG</tns:code>
</tns:Map>
it needs to be like this - http://test.com/test/CAT,DOG
i tried like this(and other options):
<#ftl ns_prefixes={"ns1":"urn://xxx", "ns2":"urn://xxx"}>
<#assign type = body["ns1:Request"]["ns1:Content"]["ns2:Map"]["ns2:type"]>
<#if type?number == 1>
http://test.com/somePath
<#elseif type?number == 2>
<#list body["ns1:Request"]["ns1:Content"]["ns2:Map"] as codeList>
<#assign codes = codeList["ns2:code"]>
http://test.com/test/${codes}
</#list>
</#if>
but it throws an syntax error.
And with this code I can get only the first element, but how to display all? using http://test.com/test/${codes?join(", ")} doesn't help:
<#ftl ns_prefixes={"ns1":"urn://xxx", "ns2":"urn://xxx"}>
<#assign type = body["ns1:Request"]["ns1:Content"]["ns2:Map"]["ns2:type"]>
<#assign codes = body["ns1:Request"]["ns1:Content"]["ns2:Map"]["ns2:code"]?sequence>
<#if type?number == 1>
http://test.com/somePath
<#elseif type?number == 2>
http://test.com/test/${codes[0]}
</#if>
how to write list tns:code to variable?
solution:
<#ftl ns_prefixes={"ns1":"urn://xxx", "ns2":"urn://xxx"}>
<#assign type = body["ns1:Request"]["ns1:Content"]["ns2:Map"]["ns2:type"]>
<#if type?number == 1>
http://test.com/somePath
<#elseif type?number == 2>
<#assign codes = body["ns1:Request"]["ns1:Content"]["ns2:Map"]["ns2:code"]>
http://test.com/test/${codes?join(",")}
</#if>

How to properly group records when executing a <#list>

New guy here. I have been building an advanced form in NetSuite (uses Freemarker) to display invoice data. Everything looks and works great, however, I want to group the invoice line items by location. I am using a simple <#list> loop to pull the line item records. I currently display the location on each line item.
Code (formats/styles removed for simplicity):
<table>
<#list record.item as item>
<tr>
<td> ${item.location} </td>
<td> ${item.description} </td>
<td> ${item.quantity} </td>
<td> ${item.rate} </td>
<td> ${item.amount} </td>
</tr>
</#list>
</table>
Current Output example:
Location A Des 1 1 $100 $100
Location B Des 1 1 $100 $100
Location C Des 1 1 $100 $100
Location A Des 2 1 $100 $100
Location B Des 2 1 $100 $100
Location C Des 2 1 $100 $100
Location A Des 3 1 $100 $100
Location C Des 3 1 $100 $100
Desired Output Example:
Location A
Des 1 1 $100 $100
Des 2 1 $100 $100
Des 3 1 $100 $100
Location B
Des 1 1 $100 $100
Des 2 1 $100 $100
Location C
Des 1 1 $100 $100
Des 2 1 $100 $100
Des 3 1 $100 $100
I have tried to nest a second <#list> but it did not work correctly. Any suggestions or pointers would be helpful to push me in the correct direction.
Thank you!
FreeMarker expects such grouping to be done by whatever sets up the variables, which is in this case NetSuite. (However, I think this could be seen as purely a presentation concern, and so maybe FreeMarker should handle this in the future.) If NetSuite indeed won't group the data for you, then you have to do it in FreeMarker, which will be a bit awkward, as it's not a real programming language... but here it is.
Define a macro like this:
<#macro listGroups items groupField>
<#if items?size == 0><#return></#if>
<#local sortedItems = items?sort_by(groupField)>
<#local groupStart = 0>
<#list sortedItems as item>
<#if !item?is_first && item[groupField] != lastItem[groupField]>
<#local groupEnd = item?index>
<#nested lastItem[groupField], sortedItems[groupStart ..< groupEnd]>
<#local groupStart = groupEnd>
</#if>
<#local lastItem = item>
</#list>
<#local groupEnd = sortedItems?size>
<#nested lastItem[groupField], sortedItems[groupStart ..< groupEnd]>
</#macro>
You can use this macro later like this:
<#listGroups record.item "location"; groupName, groupItems>
<p>${groupName}</p>
<table>
<#list groupItems as groupItem>
<tr>
<td>${groupItem.location}</td>
<td>${groupItem.description}</td>
<td>${groupItem.quantity}</td>
<td>${groupItem.rate}</td>
<td>${groupItem.amount}</td>
</tr>
</#list>
</table>
</#listGroups>
Note that groupName and groupItems in <#listGroups ...> are just arbitrary loop variable names that you specify, and they need not match the variable names used inside the #macro definition.
Update:
If you need to group by a composite key, you can nest calls of the above. Let's say you have these records coming from the data-model (here defined in FreeMarker, so it's easy to try):
<#assign records = [
{"a": 1, "b": "c1", "c": 1, "d": 11},
{"a": 1, "b": "c1", "c": 2, "d": 12},
{"a": 1, "b": "c2", "c": 3, "d": 13},
{"a": 1, "b": "c3", "c": 4, "d": 14},
{"a": 2, "b": "c3", "c": 5, "d": 15},
{"a": 2, "b": "c3", "c": 5, "d": 15}
]>
You need to group by a and b, like 1, c1 is a group, and 1, c2 is another. Then you can do this:
<#listGroups records "a"; a, level1GroupItems>
<#listGroups level1GroupItems "b"; b, groupItems>
<p>${a}, ${b}:</p>
<table>
<#list groupItems as groupItem>
<tr>
<td>${groupItem.c}</td>
<td>${groupItem.d}</td>
</tr>
</#list>
</table>
</#listGroups>
</#listGroups>
Another use-case is when your records are already sorted by the composite group key. In that case there's a slightly more efficient solution:
<#macro listGroupsOfSorted items groupFields...>
<#if items?size == 0><#return></#if>
<#local groupStart = 0>
<#list items as item>
<#if !item?is_first && !subvariablesEqual(item, lastItem, groupFields)>
<#local groupEnd = item?index>
<#nested items[groupStart ..< groupEnd]>
<#local groupStart = groupEnd>
</#if>
<#local lastItem = item>
</#list>
<#local groupEnd = items?size>
<#nested items[groupStart ..< groupEnd]>
</#macro>
<#function subvariablesEqual(obj1, obj2, subVars)>
<#list subVars as subVar>
<#if obj1[subVar] != obj2[subVar]>
<#return false>
</#if>
</#list>
<#return true>
</#function>
and then in your normal template:
<#-- Note: records must be already sorted! -->
<#listGroupsOfSorted records "a" "b"; groupItems>
<p>${groupItems[0].a}, ${groupItems[0].b}:</p>
<table>
<#list groupItems as groupItem>
<tr>
<td>${groupItem.c}</td>
<td>${groupItem.d}</td>
</tr>
</#list>
</table>
</#listGroupsOfSorted>

In NetSuite Adanced PDF Template, How Can I Convert a String Value to Numeric Value

When I loop through my "record.item" sequence, I try to record and print the item's line number.
It seems that item.line is a string, not a number. For Example:
<#list record.item as item>
<#assign curLineNumber = item.line/>
<#assign nextLineNumber = curLineNumber + 1/>
<span>Curent Line #: [${curLineNumber}] --- Next Line #: [${nextLineNumber}]<br/></span>
</#list>
This prints out
Curent Line #: [1] --- Next Line #: [11]
Curent Line #: [2] --- Next Line #: [21]
Curent Line #: [3] --- Next Line #: [31]
.....
It looks like that item.line always gives me string value of line number, instead of numeric value.
Does anyone know how to solve this issue? Thanks
Try casting to number like this:
<#assign nextLineNumber = curLineNumber?number + 1/>

What data to expect in license-maven-plugin third party license FreeMarker template?

I'm using the Mojo License Maven plugin to generate a list of third party licenses. According to the documentation I could use a custom FreeMarker template to format the file using the fileTemplate parameter, but there is no documentation on the data that will be passed to the FreeMarker template. There is a link on the examples page to existsing templates, but that link is broken.
After some searching I found the template on GitHub:
...
<#-- To render the third-party file.
Available context :
- dependencyMap a collection of Map.Entry with
key are dependencies (as a MavenProject) (from the maven project)
values are licenses of each dependency (array of string)
- licenseMap a collection of Map.Entry with
key are licenses of each dependency (array of string)
values are all dependencies using this license
-->
<#function licenseFormat licenses>
<#assign result = ""/>
<#list licenses as license>
<#assign result = result + " (" +license + ")"/>
</#list>
<#return result>
</#function>
<#function artifactFormat p>
<#if p.name?index_of('Unnamed') > -1>
<#return p.artifactId + " (" + p.groupId + ":" + p.artifactId + ":" + p.version + " - " + (p.url!"no url defined") + ")">
<#else>
<#return p.name + " (" + p.groupId + ":" + p.artifactId + ":" + p.version + " - " + (p.url!"no url defined") + ")">
</#if>
</#function>
<#if dependencyMap?size == 0>
The project has no dependencies.
<#else>
Lists of ${dependencyMap?size} third-party dependencies.
<#list dependencyMap as e>
<#assign project = e.getKey()/>
<#assign licenses = e.getValue()/>
${licenseFormat(licenses)} ${artifactFormat(project)}
</#list>
</#if>
It uses this LicenseMap

Parsing with ruby

I am new to ruby and I have a school project were I am parsing a xml file and need to get data after certain tags. I can only use core ruby. No gems
pFile = File.open("myfile.mzML", "r")
regmsLvl = "ms level\" value=\""
pFile.each_line { |line|
scn = line.scan(/#{regmsLvl}(\d)/)
#what I want to do but doesn't work
if scn == 1
puts("Got it!")
end
#what I have to do to compare if == 1
if scn != nil
scn.each do |val|
if val[0].to_i == 1
puts("Got it!")
end
end
end
}
# a sample line that I am parsing is:
<cvParam cvRef="MS" accession="MS:1000511" name="ms level" value="1" />
This seems silly.
line.scans out put makes scn a 2d array. How can I just have it be a string that gets overridden each pass. Or how should I change this whole thing. Any suggestions are appreciated.
puts(scn) prints out the 1 but if I do scn == 1 or scn.to_i == 1 it never gets into the if. I have tried scn.pop and scn.pop.pop
I have added a section to show what I am trying to do now.
I need to check the ms level: if 1 then get scan start time and then the binary. This is the code that I am now working with.
xmlfile = File.new("afile.mzML")
xmldoc = Document.new(xmlfile)
root = xmldoc.root
puts "Root element : " + root.attributes["xmlns"]
xmldoc.elements.each("mzML/run/spectrumList/spectrum/cvParam"){
|e| if e.attributes["value"].to_i ==1
# Now I need to get start time: #
["mzML/run/spectrumList/spectrum/cvParam/scanList/scan/value"]
# and then
["mzML/run/spectrumList/spectrum/cvParam/binaryDataArrayList/binaryDataArray/binary"]
end
}
<run id="ru_0" defaultInstrumentConfigurationRef="ic_0" sampleRef="sa_0" defaultSourceFileRef="sf_ru_0">
<spectrumList count="3310" defaultDataProcessingRef="dp_sp_0">
<spectrum id="scan=8839" index="0" defaultArrayLength="171" dataProcessingRef="dp_sp_0">
<cvParam cvRef="MS" accession="MS:1000525" name="spectrum representation" />
<cvParam cvRef="MS" accession="MS:1000511" name="ms level" value="1" />
<cvParam cvRef="MS" accession="MS:1000294" name="mass spectrum" />
<cvParam cvRef="MS" accession="MS:1000130" name="positive scan" />
<scanList count="1">
<cvParam cvRef="MS" accession="MS:1000795" name="no combination" />
<scan>
<cvParam cvRef="MS" accession="MS:1000016" name="scan start time" value="5429.47" unitAccession="UO:0000010" unitName="second" unitCvRef="UO" />
</scan>
</scanList>
<binaryDataArrayList count="2">
<binaryDataArray encodedLength="1824">
<cvParam cvRef="MS" accession="MS:1000514" name="m/z array" unitAccession="MS:1000040" unitName="m/z" unitCvRef="MS" />
<cvParam cvRef="MS" accession="MS:1000523" name="64-bit float" />
<cvParam cvRef="MS" accession="MS:1000576" name="no compression" />
<binary>AAAAQBCdgkAAAACAP6KCQAAAAAA8pIJAAAAAYAWlgkAAAABgQ6aCQAAAAGCzp4JAAAAAQEaogkAAAACgDKqCQAAAAEAgqoJAAAAAwEOqgkAAAABAWKqCQAAAAGBErIJAAAAAIOetgkAAAABAMLCCQAAAAGDlsYJAAAAA4DeygkAAAACAw7SCQAAAACBauIJAAAAAwFC6gkAAAACAYb6CQAAAAIDnwYJAAAAAwDjHgkAAAAAATMyCQAAAAADnzIJAAAAAAArOgkAAAACgTc6CQAAAAKBqzoJAAAAAQJLPgkAAAACAVNCCQAAAAAAK0oJAAAAAIF7SgkAAAADABNSCQAAAAKAx1YJAAAAAYHXXgkAAAAAg3teCQAAAAOAf2oJAAAAAICbcgkAAAAAAx92CQAAAAKA03oJAAAAAIBXigkAAAABAO+KCQAAAAKCr5YJAAAAAYMnlgkAAAADgK+aCQAAAAKDq6YJAAAAAAC3qgkAAAACgNe6CQAAAAMCA74JAAAAAANL0gkAAAAAAUfiCQAAAAOCt+YJAAAAA4O75gkAAAACAPPqCQAAAAGBq/oJAAAAAwEQCg0AAAABAKAqDQAAAAAAoDoNAAAAA4G0Og0AAAADAZhKDQAAAACCBEoNAAAAAwIQWg0AAAABAjheDQAAAAMA+GoNAAAAAQIYag0AAAAAA7RyDQAAAAEB9HYNAAAAAwIseg0AAAADgbyKDQAAAAAAPJINAAAAAgEUlg0AAAACgYCaDQAAAAOBfKoNAAAAA4DAug0AAAADAZi+DQAAAAAA0MINAAAAAoFMwg0AAAAAgMjKDQAAAACA2NINAAAAAgDk2g0AAAAAg+DyDQAAAAOAfPoNAAAAAAKU/g0AAAAAgQUKDQAAAAKBVQoNAAAAAYNRHg0AAAAAgf0qDQAAAAICZSoNAAAAAIDFQg0AAAAAgM1KDQAAAAEBjUoNAAAAAoGNUg0AAAAAAZ1aDQAAAAABqWINAAAAAYHhZg0AAAACAfl2DQAAAAEAcXoNAAAAAICpfg0AAAADgw2GDQAAAAACmZ4NAAAAAQDRog0AAAABAiWqDQAAAAAAibYNAAAAAQHpug0AAAABAEnKDQAAAAABCcoNAAAAAoHxyg0AAAACgGXaDQAAAAMBDdoNAAAAAgJR2g0AAAAAgHHqDQAAAAEBGeoNAAAAAIHh6g0AAAABAl3qDQAAAAKCkfYNAAAAAYE5+g0AAAAAAm36DQAAAAEDigYNAAAAAQGWCg0AAAABAjYKDQAAAACClgoNAAAAA4ESGg0AAAABgYIaDQAAAAMDSh4NAAAAAYCqIg0AAAADAT4qDQAAAAACCioNAAAAAwJmOg0AAAABAnZKDQAAAAKDJlINAAAAAgHGWg0AAAABgl5eDQAAAAEB4mINAAAAA4B2eg0AAAADgKKCDQAAAAGAvooNAAAAAwJakg0AAAABAUaiDQAAAAGBgqoNAAAAAIBatg0AAAADAxa6DQAAAAKCosoNAAAAAICy6g0AAAAAAbrqDQAAAAACRuoNAAAAAAMa/g0AAAACgOsCDQAAAAABzwoNAAAAAIOTCg0AAAACADcWDQAAAAGB4xoNAAAAAQOfGg0AAAAAAvceDQAAAAEBZyoNAAAAA4OnKg0AAAAAgMs6DQAAAAOC/z4NAAAAAYInUg0AAAABgftaDQAAAAODC1oNAAAAAwJXXg0AAAAAAgdiDQAAAAKA/2oNAAAAAoILag0AAAABghtyDQAAAAGCm3INAAAAAAO7cg0AAAACgr9+DQAAAAGCY4oNAAAAAgDbkg0AAAABAN+WDQAAAAKBU5oNA</binary>
</binaryDataArray>
I think you were pretty close. Assuming you can use that REXML library (which looks like it's part of the core ruby library) you should be able to do this
require 'rexml/document'
xmlfile = File.new("afile.mzML")
xmldoc = REXML::Document.new(xmlfile)
root = xmldoc.root
start_time = nil
binary = nil
# get the ms level
ms_level = root.elements["spectrumList/spectrum/cvParam[#name='ms level']"].attributes["value"].to_i
if ms_level == 1
# get the scan start time
start_time = root.elements["spectrumList/spectrum/scanList/scan/cvParam[#name='scan start time']"].attributes["value"]
# get the binary
binary = root.elements["spectrumList/spectrum/binaryDataArrayList/binaryDataArray/binary"].text
end
p start_time # => "5429.47"
p binary # => that crazy long binary
This REXML tutorial is helpful: http://www.germane-software.com/software/rexml/docs/tutorial.html
Note, I made a few assumptions, like the elements would always exist, the ms level was always an int, the file structure is always the same. Those assumptions may not be true in your situation but this should be a start.

Resources