Sharepoint XSLT Dynamic filtering - sharepoint

I'm trying to create a dynamic row filter based on a variable. I have the following code:
<xsl:variable name="filter" select="contain(#Title, 'title1') or contain(#Title, 'title2')"/>
<xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row[string($filter)]" />
This unfortunately doesn't seem to work and I end up with all rows. I'm guessing the filter doesn't actually get applied, since I can copy and paste the output of the $filter variable, copy and paste it in the Row[] and it works as expected.
Anyone tried to do this before?
In case you're wondering the filter variable is actually created using a template that splits a string like:
title1 - title2 - title3
and returns a string like:
contain(#Title, 'title1') or contain(#Title, 'title2') or contain(#Title, 'title3')
Any help would be greatly appreciated!

You can't do what you seem to be attempting here. An XPath expression is atomical, you can't save parts of it and re-use them (apart from that it is contains(), not contain()).
You need something like this:
<xsl:variable name="Rows" select="
/dsQueryResponse/Rows/Row[
contains(#Title, 'title1') or contains(#Title, 'title2')
]
" />
Your "filter" does not work because if $filter is a string, then it is a string, nothing else. It does not get a magical meaning just because it looks like XPath. ;-)
This
<xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row[string($filter)]" />
evaluates to a non-empty string as the predicate. And any non-empty string evaluates to true, which makes the expression return every node there is.
If you want a dynamic filter based on an input string, then do this:
<xsl:variable name="filter" select="'|title1|title2|title3|'" />
<xsl:variable name="Rows" select="
/dsQueryResponse/Rows/Row[
contains(
$filter,
concat('|', #Title, '|')
)
]
" />
The use of delimiters also prevents "title11" from showing up if you look for "title1".
Make sure your filter always starts and ends with a delimiter, and use a delimiter that is reasonably unlikely to ever occur as a natural part of #Title. (For example, you could use 
. If your title cannot be multi-line this is pretty safe.)

Related

How do I address an item in a sequence when using tokenize()

I have an HTML that contains a style attribute with a list of values separated by semicolons:
<td style="border-width:1pt;border-color:#FFFFFF;border-style:solid">
I want to split this list:
<xsl:variable name="astyle"><xsl:value-of select="tokenize(#style, ';')"/></xsl:variable>
This gives me a sequence. Now I want to select the first item in the sequence.
<xsl:value-of select="$astyle[1]"/>
result:
"border-width:1pt border-color:#FFFFFF border-style:solid"
so that's the entire sequence instead of the first item in the sequence.
I can't find any documentation on this.
Edit: it seems my assumption is incorrect. Tokenize() contains a sequence of 1 item. I was expecting 3 items.
How can I split a string into items that can be addressed individually?
I want to do something like
<xsl:value-of select="$astyle[1]"/>
and get:
border-width:1pt
Use <xsl:variable select="tokenize(...)" name="astyle"/>, then the value of the variable is a sequence and $astyle[1] works.

Concatenate long strings from multiple records into one string

I have a situation where I need to concatenate long strings from multiple records in an Oracle database into a single string. These long strings are portions of a larger XML string, and my ultimate goal is to be able to convert this XML into something resembling query results and pull out specific values.
The data would look something like this, with the MSG_LINE_TEXT field being VARCHAR2(4000). So if the total message is less than 4000 characters, then there'd only be one record. In theory, there could be an infinite number of records for each message, although the highest I've seen so far is 14 records, which means I need to be able to handle strings that are at least 56000 characters long.
MESSAGE_ID MSG_LINE_NUMBER MSG_LINE_TEXT
---------- --------------- --------------------------------
17415414 1 Some XML snippet here
17415414 2 Some XML snippet here
17415414 3 Some XML snippet here
17415414 4 Some XML snippet here
The total XML for one MESSAGE_ID might look something like this. There could be many App_Advice_Error tags, although this specific example only contains one.
<tXML>
<Header>
<Source>MANH_prod_wmsweb</Source>
<Action_Type />
<Sequence_Number />
<Company_ID>1</Company_ID>
<Msg_Locale />
<Version />
<Internal_Reference_ID>17415414</Internal_Reference_ID>
<Internal_Date_Time_Stamp>2021-02-09 13:45:22</Internal_Date_Time_Stamp>
<External_Reference_ID />
<External_Date_Time_Stamp />
<User_ID>ESBUSER</User_ID>
<Message_Type>RESPONSE</Message_Type>
</Header>
<Response>
<Persistent_State>0</Persistent_State>
<Error_Type>2</Error_Type>
<Resp_Code>501</Resp_Code>
<Response_Details>
<Application_Advice>
<Shipper_ID />
<Imported_Object_Type>ASN</Imported_Object_Type>
<Response_Type>Error</Response_Type>
<Transaction_Date>2/9/21 13:45</Transaction_Date>
<Application_Ackg_Code>TE</Application_Ackg_Code>
<Business_Unit></Business_Unit>
<Tran_Set_Identifier_Code></Tran_Set_Identifier_Code>
<Transaction_Purpose_Code>11</Transaction_Purpose_Code>
<Imported_Message_Id></Imported_Message_Id>
<Imported_Object_Id>Reference Number Here</Imported_Object_Id>
<Additional_References>
<Additional_Reference_Info>
<Reference_Type>BusinessPartner</Reference_Type>
<Reference_ID></Reference_ID>
</Additional_Reference_Info>
</Additional_References>
<App_Advice_Errors>
<App_Advice_Error>
<App_Error_Text>Some error text here</App_Error_Text>
<Error_Message_Tokens>
<Error_Message_Token>Object that errored out</Error_Message_Token>
</Error_Message_Tokens>
<App_Err_Cond_Code>6100234</App_Err_Cond_Code>
</App_Advice_Error>
</App_Advice_Errors>
<Imported_Data></Imported_Data>
</Application_Advice>
</Response_Details>
</Response>
</tXML>
The values that I'm most interested in pulling out are the App_Err_Cond_Code, Error_Message_Token, and App_Error_Text tags. I had tried using something like this:
extractvalue(xmltype(msg_line_text), '//XPath of Tag')
This works beautifully for stuff where the entire XML is less than 4000 characters, i.e. the entire XML is stored in a single record. The problem comes when there are multiple records, because each individual snippet of XML isn't a valid XML string on its own, and so XMLTYPE throws an error, hence the reason I'm trying to concatenate them all into a single string, which I can then use with the above method.
I've tried a variety of ways to do this - LISTAGG, XMLAGG, SYS_CONNECT_BY_PATH, as well as writing a custom function something like this:
with
function get_messages(pTranLogID number) return string
is
xml varchar2;
begin
xml := '';
for msg in (
select r.msg_line_text
from tran_log_response_message r, tran_log t
where
t.message_id = r.message_id
and t.tran_log_id = pTranLogID
order by r.msg_line_number
)
loop
xml := xml || msg.msg_line_text;
end loop;
return 'test';
end;
select
tran_log_id, get_messages(tran_log_id)
from
tran_log
where
tran_log_id = '20633610';
/
The problem is that every one of these methods complained that the string was too long. Does anyone have any other ideas? Or maybe a better approach to this problem?
Thanks.

How can I use the Xpath function 'contains()' to return nothing if it's search param is blank or missing/false?

I'm trying to write an Xpath Statement (1.0) that can read info from a 'search' node and perform a search using it.
I was making some nice progress, but stumbled across an issue where if an attribute (used for a value in the search) is empty or doesn't exist, it fails.
Code Edited to simplify Example:
So Here is my sample XML:
<xml>
<files>
<file name="foo" description="" rating="4"/>
<file name="food" description="" rating="4"/>
<file name="foobar" description="" rating="3"/>
<file name="bar" description="" rating="3"/>
<file name="barter" description="" rating="3"/>
<file name="barterer" description="" rating="2"/>
</files>
<searches>
<search id="1">
<exclude>
<file term="foo"/>
</exclude>
</search>
</searches>
</xml>
And working XPATH:
//files/file[
not(contains(#name, //search[#id='1']/exclude/file/#term))
]
It works as expected...
However if the an expected attribute is missing or empty it will fail to work. I think because: contains(#attrib, "") matches everything for some-reason, therefore a not() will always match nothing if the attribute is "" or not present.
For Example, if I alter the exclude fragment of XML to this it fails:
<exclude>
<file term=""/>
</exclude>
with this too:
<exclude></exclude>
Is there a way to Check for an empty value and not perform the select? or is there perhaps a better way of structuring the Logic. Bare in mind I cannot use Conditionals or the other functions in Xpath2.0.
Why does the Xpath function contains() return everything if it search
param is blank or missing?
Because that is what the XPath specification says the contains() function should do:
If the value of $arg2 is the zero-length string, then the function
returns true.
You could adjust your XPath and simplify some of the conditions with the following:
//files/file[
(
(
not(//search[#id='1']/include/file/#term)
or
(
contains(#name, //search[#id='1']/include/file/#term)
or
contains(#description, //search[#id='1']/include/file/#term)
)
)
or
contains(#rating, //search[#id='1']/include/file/#rating)
)
and
(
(
not(//search[#id='1']/exclude/file/#term)
or
(
not(contains(#name, //search[#id='1']/exclude/file/#term))
and
not(contains(#description, //search[#id='1']/exclude/file/#term))
)
)
and
(
not(//search[#id='1']/exclude/file/#rating)
or
not(contains(#rating, //search[#id='1']/exclude/file/#rating))
)
)
]
Perhaps you want to say something like
//files/file[
not(contains(#name,
//search[#id='1']
/exclude/file/#term))
or not(
normalize-string(//search[#id='1']
/exclude/file/#term)
)
]
So I stumbled across the answer in a different post. Below is an example of it working.
Thanks for everyone's advise.
//files/file[
not(
contains(
#name,
concat( //search[#id='1']/exclude/file/#term,
substring('??', 1 + 2*
boolean( substring( //search[#id='1']/exclude/file/#term, 1 ) )
)
)
)
)
]
The bit where I placed "??" probably wants replacing with an invalid char or something. For each additional character used the 1+ needs to increment. For me I'm checking filenames so a questionmark seems a good idea. To be honest I probably won't be using this route, I was just eager to solve the problem after all this time.
Here is where I got the idea from:
How to give back constant if node does not exist in XPATH?

XSLT equation using SP list fields

I have a SP Dataview that I have converted to XSLT, so that I could add a header displaying a percentage (Complete). Before I converted the dvwp to xslt, I added two count headers- one on Complete, and another on LastName. They worked wonderfully- showing me the # of records and the # of records with a value in the complete field. However, when I converted the dv to xslt I realized that I lost my headers :(
So, I am adding them back in using xslt. Currently the XPath code for the equation that I have is <xsl:value-of select="count($Rows) div count($Rows)" />.
How do I get the total # of Yes values that are in my Complete field?
UPDATE1:
Found this http://www.endusersharepoint.com/STP/viewtopic.php?f=14&t=534 and tried it, however causes the following error- Failed setting processor stylesheet: 0x80004005: Argument 1 must return a node-set. -->count(/dsQueryResponse/Rows/Row='Y')<--
UPDATE2:
Complete is the name of a field w/i my XSLT dataset. The return type is either Y or blank. For grins I tried <xsl:value-of select="count(/xpath/to/parent/element[#Complete eq 'Y']) div count($Rows)" /> however I recieved the following error- Failed setting processor stylesheet: 0x80004005: Expected token ']' found 'NAME'.count((/xpath/to/parent/element[#Complete -->eq <--'Y']) div count($Rows) Am starting to think that there may be a problem w/ 'eq'.... Referencing my XML operators...
UPDATE3:
<xsl:value-of select="count(/xpath/to/parent/element[#Complete = 'Y']) div count($Rows)" />
Okay so it still says 0, but I think the reason why it's not showing the correct answer is b/c it is expecting to show an integer, and obviously the value being returned from the equation is going to be a decimal... Have been fiddling with the equation in XPath... here's what I've tried-
count(/xpath/to/parent/element[#Complete = 'Y']) div count($Rows)*100
(count(/xpath/to/parent/element[#Complete = 'Y']) div count($Rows))*100
100(count(/xpath/to/parent/element[#Complete = 'Y']) div count($Rows))
UPDATE4:
So I know my previous thought that the correct number not showing b/c it was a float is not correct, as all numbers in XPath and XSLT 1.0 are floats. Reference
UPDATE5:
Upon further investigation, I have found that the problem lies with the count(/xpath/to/parent/element[#Complete = 'Y']) part of my equation, as this is returning 0 instead of a value. [i know i have at least 3 'Y' vals in my Complete col]
UPDATE6:
<records*>
<record*>
<last_name></last_name>
<first_name></first_name>
<mi></mi>
<office_symbol></office_symbol>
<geo_location></geo_location>
<complete></complete>
<date_complete></date_complete>
<date_expires></date_expires>
<email></email>
<supervisor></supervior>
</record*>
</records*>
*i don't know what these nodes are called as my data is coming from a database and not an xml file, i just made up record/records
UPDATE7
Going back to my original question. I am still trying to find out the XPath equation to display the number of parents (record in the XML i posted above) where the complete node = Y.
UPDATE8
Ok. So I have edited and tested using http://www.w3schools.com/xsl/tryxslt.asp?xmlfile=cdcatalog&xsltfile=tryxsl_value-of. Working XSLT to count the # of Complete = Y is <xsl:value-of select="count(catalog/cd [complete = 'Y'])" /> so theen I put EXACTLY what works on W3schools into my SP Dataview and I get nothing... just an empty space. Why doesn't the code work in my SPDV?
If your "Complete" field is an element:
<xsl:value-of select="count(/xpath/to/complete/field/element[string(.) eq 'Yes])"/>
If your complete field is an attribute of an element:
<xsl:value-of select="count(/xpath/to/parent/element[#complete eq 'Yes'])"/>
Without knowing the structure of your XML I can't provide the specific XPATH required -- the predicate "[]" is what selects only the "Yes" values

XSLT: Define a param with & ampersand

I need to define a xsl:param with a string that contains & character for further processing.
For example:
<xsl:param name="greeting" as="xs:string">Hi & Bye</xsl:param>
Important Notice:
I am using a xslt converter component in a webservice tool. What I do, is that I just initialize the param and as the webservice is called the param is set to a value of a variable which was set in previous steps. So I have no control on the string, i.e meaning I can't use &
Any ideas how to do that?
Thanks in advance.
Encode it.
& is encoded as &:
<xsl:param name="greeting" as="xs:string">Hi & Bye</xsl:param>
See this document about XML Character entities.
Another option is to enclose such strings in CDATA sections:
<xsl:param name="greeting" as="xs:string"><![CDATA[Hi & Bye]]></xsl:param>
You could attempt to store the string as unparsed character data (CDATA). Marking the string in that case will notify an XML parser that the contained information should not be parsed:
<xsl:param name="greeting" as="xs:string"><![CDATA[ Hi & Bye ]]></xsl:param>

Resources