combine multiple fields with the same name using regexp - regex

I have multiple fields with the same name "item" inside json object, how can I combine their values under one field (as an array) using regex
sample input:
{"item":"A","item":"B","item":"C"}
expected output:
"item": ["A","B","C"]
note that we could have more than 3 items on above sample
I have tried {"item":"(.*?)","item":"(.*?)","item":"(.*?)"} but here I'm limited with 3 items, I need something to work for any number of items.
Thanks

The conversion you want can be performed quite easily in XSLT 2.0 or higher using:
<xsl:text>"item": [</xsl:text>
<xsl:value-of select="for $i in tokenize(translate($input, '{}', ''), ',') return substring-after($i, ':')" separator=","/>
<xsl:text>]</xsl:text>
Demo: https://xsltfiddle.liberty-development.net/eiorv1e
Of course, if your ultimate goal is to parse the input in order to use its values, then it's not necessary to put it back as JSON.

Related

How to use contains() with a set of strings in XSLT

I have the following XML snippet:
<figure customer="ABC DEF">
<image customer="ABC"/>
<image customer="XYZ"/>
</figure>
I'd like to check if the figure element's customer attribute contains the customer attributes of the image elements.
<xsl:if test="contains(#customer, image/#customer)">
...
</xsl:if>
I get an error saying:
a sequence of more than one item is not allowed as the second argument of contains
It's important to note that I cannot tell the values of the customer attributes in advance, thus using xsl:choose is not an option here.
Is it possible to solve this without using xsl:for-each?
In XSLT 2.0 you can use:
test="image/#customer/contains(../../#customer, .) = true()"
and you will get a true() result if any of them are true. Actually, that leads me to suggest:
test="some $cust in image/#customer satisfies contains(#customer, $cust)"
but that won't address the situation where the customer string is a subset of another customer string.
Therefore, perhaps this is best:
test="tokenize(#customer,'\s+') = image/#customer"
... as that will do a string-by-string comparison and give you true() if any of the tokenized values of the figure attribute is equal to one of the image attributes.

Filtering set of rows with XSLT

i have troubles filtering with XSLT, basically i have an xml, where i would like to get only those items where the Due Date falls into interval of 3 months ... i've written the following:
<xsl:variable name="TaskRows" select="/dsQueryResponse/Tasks/Rows/Row[#DueDate > $IsoBeginQuartDate AND #DueDate < $IsoEndQuartDate]"/>
But I get an error: "Failed seting processor stylesheet: expected token ']' found 'NAME' ...
IsoDates are calculated and formated dates in ISO format ...
Any idea, how to do it, or i can't use "AND" when filtering?
PS: i'm using XSLT 1.0
There is case-sensitivity at work here! Try using 'and' instead of 'AND'.
Unfortunately in XSLT 1.0, you can't compare dates to see if a date is between two values.
What you could do is something like this:
<!-- Make sure to format this variables as YYYYMMDD -->
<xsl:variable name="$IsoBeginQuartDate">20130101</xsl:variable>
<xsl:variable name="$IsoBeginQuartDate">20131231</xsl:variable>
<!-- Make sure #DueDate also has the format YYYYMMDD, in this example I assume the DueDate has format YYYY-MM-DD -->
<xsl:variable name="TaskRows" select="/dsQueryResponse/Tasks/Rows/Row[translate(#DueDate, '-', '') > $IsoBeginQuartDate and translate(#DueDate, '-', '') < $IsoEndQuartDate]"/>
You now get errors, because > and < or not supported for strings or dates.

Manage flags in xslt?

All,
i am searching in a list of fields those who has the type clob and i am writing it separed by a comma like this [field1, field2, ... fieldn]
my problem is how to identify the first matched field to write it without comma ( i can't use position() because the first field matched can be the first of the list or the last of the list)
I want to make this algorithm in xslt,
variable is_first = TRUE;
if(is_first) {
do smthng;
isfirst = False;
}
Actually it is not possible to make something like this in xslt since variable are immutable. There probably could be workarounds but you have to specify your need in more details.
edit:
If your input is string with values separated by commas...
<xsl:variable name="inputString" select="'field1,field2,field3a,field4,field3b'" />
... you could use tokenize() functions...
<xsl:variable name="tokenized" select="tokenize($inputString, ',')" />
... and then select items corresponding to your condition
<!-- Select item corresponding to condition (e.g. it contains 3). Take first one if there are several such items -->
<xsl:value-of select="$tokenized[contains(., '3')][1]" />
Edit2:
You can use separator attribute of xsl:value-of (xslt 2.0) for output of delimited values.
Assuming following variable
<xsl:variable name="list">
<item>first</item>
<item>second</item>
<item>third</item>
</xsl:variable>
this <xsl:value-of select="$list/item" separator="," /> makes desired output first,second,third
You need to write this using functional code rather than procedural code. It's not possible to do the conversion without seeing the context (it's much easier to work from the problem rather than from the solution in a lower-level language).
But the most common equivalent in XSLT would take the form
<xsl:for-each select=".....">
<xsl:if test="position() = 1"><!-- first time code --></xsl:if>
....
</xsl:for-each>

Using <xsl:for-each> for incremental element names

I have an XML that is converted from a java map. So all the map keys are converted into node names. The XML structure is as below
<map>
<firstName>AAA</firstName>
<firstName1>BBB</firstName1>
<firstName2>CCC</firstName2>
<firstName3>DDD</firstName3>
</map>
I am trying to write a for-each loop to extract data from this XML to create an output XML. I have tried most of the options available such as name(), local-name(), contains(), etc but couldn't come up with something that worked. What are the options available since the incremental node name can go upto count 100 or more. Any inputs in coding the loop would be of great help. I am using XSLT 1.0.
There are many ways to select the children of the top element (map):
/*/*
This selects all elements that are children of the top element of the XML document.
/*/*[starts-with(name(), 'firstName')]
This selects all top element's children-elements, whose name starts with the string 'firstName'.
/*/*[starts-with(name(), 'firstName')
and floor(substring-after(name(), 'firstName')) = substring-after(name(), 'firstName')) ]
This selects all top element's children-elements, whose name starts with the string 'firstName' and the remaining substring after this is an integer.
/*/*[starts-with(name(), 'firstName')
and translate(name(), '0123456789', '') = 'firstName')) ]
This selects all top element's children-elements, whose name starts with the string 'firstName' and the remaining substring after this contains only digits.
Finally, in XPath 2.0 (XSLT 2.0) one can use regular expressions:
/*/*[matches(name(), '^firstName\d+$')]
This will select all the first level elements and their information, which you can then use as you wish:
<xsl:for-each select="/*/*">
<xsl:value-of select="local-name()"/>
<xsl:value-of select="."/>
</xsl:for-each>

XSLT 1.0 trying to convert node to lowercase to be compared with string

I have a param named $SearchRecipe which holds a string that i'm always passing a lowercase string to.
I have an XML file which i'm accessing using my xslt and example of some data from the xml file :
<ARecipe>
<RecipeNo>117</RecipeNo>
<ItemName>Veggie Sausages with Beans and Chips</ItemName>
<RecipeInfo>
<Ingredients>Linda Mcartney Sausages(2 per serving), Beans (400g per serving), Chips(Handful), Random Chillis (up to you how much)</Ingredients>
<Instructions>Put on fat fryer, insert chips and sausages. Put on wok, insert beans and add some random chillis etc. Heat beans and remove cooked Sausages and Chips. Place sausages and chips in tissue to remove excess oil. Place in a plate and serve warm.</Instructions>
<RecipeType>Main</RecipeType>
<Vegetarian>No</Vegetarian>
</RecipeInfo>
</ARecipe>
I'm running a query to go through all the ItemName nodes and compare it to my string which works fine. For example if I put V in the string it matches the ItemName in this (ARecipe) and this gets displayed in my xslt output. However if I pass v as a value then it won't bring this particular node (ARecipe).
I'm using these lines to go through the xml file :
<xsl:variable name="matchedRecipes"
select="ARecipe[
($SearchType = 'Start' and starts-with($Test, $SearchRecipe)) or
($SearchType = 'Contains' and contains(ItemName, $SearchRecipe)) or
($SearchType = 'RecipeType' and
contains(RecipeInfo/RecipeType, $SearchRecipe))
]" />
<xsl:if test="$SearchType = 'Start' or $SearchType = 'Contains'">
<xsl:apply-templates select="$matchedRecipes">
What I have tried so far is this :
<xsl:variable name="Test">
<xsl:value-of select="translate('ARecipe/ItemName',$ucletters,$lcletters)"/>
</xsl:variable>
I'm a novice in most languages etc, but I would be able to do this kind of thing in C# fairly easily, i'm having an absolute nightmate with xslt. Also when I wrote this request for help, I had already been doing this work for a few hours and the last hour I've spent trying all sorts to get this lowercase issue to resolve. So if I've not asked properly, I would like to appologize in advance.
If you would like to see the larger picture I have uploaded my code here :
http://pastebin.com/w8AsiQRg
You'll need to do something like this:
<xsl:variable name="SearchLc" select="translate($SearchRecipe, $ucletters, $lcletters)" />
<xsl:variable name="matchedRecipes"
select="ARecipe[
($SearchType = 'Start' and
starts-with(translate(ItemName, $ucletters, $lcletters), $SearchLc)) or
($SearchType = 'Contains' and
contains(translate(ItemName, $ucletters, $lcletters), $SearchLc)) or
($SearchType = 'RecipeType' and
contains(translate(RecipeInfo/RecipeType, $ucletters, $lcletters),
$SearchRecipe))
]" />
You can get the lowercase version of the search text ahead of time, but then you'll need to do the lower-casing of the individual items inside the selection XPath.