Xpath query for event filtering - xpath

I have the current filtering logic to define events that I want to source
<QueryList>
<Query Id="0" Path="Security">
<Select Path="Security">
*[System[(EventID=4624 or EventID=4625)]]
and
*[EventData[Data[#Name='TargetUserName'] != 'ANONYMOUS LOGON']]
and
*[substring([EventData[Data[#Name='TargetUserName']]],2,1) != '-']
</Select>
</Query>
</QueryList>
But THe part "*[substring([EventData[Data[#Name='TargetUserName']]],2,1) != '-']" is leading to error as it is not being parsed.
I want to discard certain target usernames which start with 'L-' and 'D-' and 'C:/'.
Please suggest proper solutions

I am guessing, based on how you wrote your third XPath expression, that you have a structure like this:
...
<Any-Element>
<EventData>L-username
<Data Name='TargetUserName'>xxx</Data>
</EventData>
</Any-Element>
...
Then you could use this expression to obtain what you want:
*[substring(EventData[Data[#Name='TargetUserName']], 2, 1) = '-']
But I suspect this is not the case, since your second expression compares the contents of the <Data> element (and not the contents of <EventData>, and you didn't mention it was failing. So probably the xxxstring above is where your username is. If that is the case, you should compare the contents of Data, and not EventData:
*[EventData[substring(Data[#Name='TargetUserName'], 2, 1) = '-']]

I found that if you change the query to pull application or system logs that you do not get the same error. There seems to be a bug with the Security logs. Another post suggests that reducing the character length to under 150 seems to resolve the issue.
https://social.technet.microsoft.com/Forums/windowsserver/en-US/cda721cc-6479-4b04-9fdd-9ccbec86b159/possible-bug-collecting-security-events-using-event-subscriptions-and-windows-2008-r2?forum=winserverManagement
Not sure if this solves your problem as you may need more than 150 characters.

Related

XPath greater than operator?

I'm trying to select all event log entries beyond a certain date. So far I think I got equals, but I don't know how to change this to greater than the specified date... so close yet so far!
<QueryList>
<Query Id="0" Path="Application">
<Select Path="Application">*[System[TimeCreated[#SystemTime='2013-01-01T12:21:25.0000000']]]</Select>
</Query>
</QueryList>
I found the best way to create a XPath query for EventLog. See here on how to create a custom view. After you create the custom view, with whatever filter you want, simply click on the XML, and voila, it shows you the XPath query that it constructed itself!
The next challenge was the formating of the date. I used this: "yyyy-MM-ddThh:mm:ss:fffZ"
I also think you cannot create a filter that says, shows me everything after this date. So I simply recreated a range between the date I wanted and the current date.
For completeness, here is the filter that I created (who dreams up specs for this?)
<QueryList>
<Query Id="0" Path="Application">
<Select Path="Application">*[System[(Level=1 or Level 2 or Level=3) and TimeCreated[#SystemTime>='2013-01-01T12:00:00:000Z' and #SystemTime<='2013-02-13T05:30:34:948Z']]]</Select>
</Query>
</QueryList>
Use something like this:
*[System[TimeCreated[
number(translate(substring-before(#SystemTime, 'T'), '-', '')) > 20130101]]]
If you need to consider the entire string, then strip everything unnecessary to the comparison:
*[System[TimeCreated[
number(translate(#SystemTime, '-T:.', '')) > 201301011221250000000]]]

XPath and and or syntax, any shorter way to write this Xpath

I'm filtering a big file that contains types of shoes for children, man as wel as woman.
Now I want to filter out certain types of woman shoes, the following xpath works but there is a xpath length limitation with the program I'm using. So I'm wondering if there a shorter / more efficient way to construct this xpath
/Products/Product[contains(CategoryPath/ProductCategoryPath,'Halbschuhe') and contains(CategoryPath/ProductCategoryPath,'Damen') or contains(CategoryPath/ProductCategoryPath,'Sneaker') and contains(CategoryPath/ProductCategoryPath,'Damen') or contains(CategoryPath/ProductCategoryPath,'Ballerinas') and contains(CategoryPath/ProductCategoryPath,'Damen')]
Edit: Added requested file sample
<Products>
<!-- snip -->
<Product ProgramID="4875" ArticleNumber="GO1-f05-0001-12">
<CategoryPath>
<ProductCategoryID>34857489</ProductCategoryID>
<ProductCategoryPath>Damen > Sale > Schuhe > Sneaker > Sneaker Low</ProductCategoryPath>
<AffilinetProductCategoryPath>Kleidung & Accessoires?</AffilinetProductCategoryPath>
</CategoryPath>
<Price>
<DisplayPrice>40.95 EUR</DisplayPrice>
<Price>40.95</Price>
</Price>
</Product>
<!-- snip -->
</Products>
If you had XPath 2.0 available, you should try the matches() function or even tokenize() as suggested by Ranon in his great answer.
With XPath 1.0, one way to shorten the expression could be this:
/Products/Product[
CategoryPath/ProductCategoryPath[
contains(., 'Damen')
and ( contains(., 'Halbschuhe')
or contains(., 'Sneaker')
or contains(., 'Ballerinas') )] ]
A convenient oneliner for easier copy-paste:
/Products/Product[CategoryPath/ProductCategoryPath[contains(.,'Damen') and (contains(.,'Halbschuhe') or contains(.,'Sneaker') or contains(.,'Ballerinas'))]]
I tried to preserve your expression exactly how it was, none of the changes should change the behaviour in any way.
There are some even shorter solutions that would have to take assumptions about the XML structure etc., but those could be broken in some hidden way we can't see without the full context, so we're not going that way.
If your XPath engine supports XPath 2.0, it can be done in an even more convenient (and probably efficient) way:
//Product[
CategoryPath/ProductCategoryPath[
tokenize(., '\s') = ('Halbschuhe', 'Sneaker', 'Ballerinas') and contains(., 'Damen')
]
]
fn:tokenize($string, $token) splits a string on a regex (here using whitespace, you also could provide a space only). = compares on a set based semantics, so if any of the strings on the left side equal any of the strings on the right side, it returns true.

Select element with a changing Id string using XPath

I have a textarea control with an Id that goes something like this:
<textarea id="NewTextArea~~51887~~1" rows="2"/>
And the xpath that has worked before has been
//textarea[#id, "NewTextArea~~51887~~1"]
But now the '51887' portion of the id is become diverse (changing every time) so I need to select the NewtextArea~~*~~1 element without actually specifying the number. Is there a way I can wildcard part of the string so that it will match a particular pattern? I tried using starts-with and ends-with but couldn't get it to work:
//textarea[starts-with(#id, 'NewTextArea~~') and ends-with(#name, '~~1')]
Bare in mind there are other fields with the difference being the number on the end.
Any advice or guidance would be greatly appreciated :)
I tried using starts-with and ends-with but couldn't get it to work:
//textarea[starts-with(#id, 'NewTextArea~~') and ends-with(#name, '~~1')]
ends-with() is available as a standard function only in XPath 2.0 and you seem to be using XPath 1.0.
Use:
//textarea
[starts-with(#id, 'NewTextArea~~')
and
substring(#id, string-length(#id) - 2) = '~~1'
]
Explanation:
See the answer to this question, for how to implement ends-with() in XPath 1.0:
https://stackoverflow.com/a/405507/36305

Find attribute names that start with a certain pattern

I am looking to find all attributes of an element that match a certain pattern.
So for an element
<element s2="1" name="aaaa" id="1" />
<element s3="1" name="aaaa" id="2" />
I would like to be able to find all attributes that start with 's' (returning the value of s1 for the first element and s3 for the value of the second element).
If this is outside of xpath's ability please let me know.
Use:
element/#*[starts-with(name(), 's')]
This XPath expression selects all atribute nodes whose name starts with the string 's' and that are attributes of elements named element that are children of the current node.
starts-with() is a standard function in XPath 1.0
element/#*[substring(name(), 1,1) = "s"]
will match any attribute that starts with 's'.
The function starts-with() might look better than using substring()
I've tested the given answers from both #Dimitre-Novatchev and #Ledhund, using lxml.html module in Python.
Both element/#*[starts-with(name(), 's')] and element/#*[substring(name(), 1,1) = "s"] return only the values of s2 and s3. You won't be able to know which value belong to which attribute.
I think in practice I would be more interested in finding the elements themselves that contain the attributes of names starting with specific characters rather than just their values.
To achieve that is very simple, just add /.. at the end,
element/#*[starts-with(name(), "s")]/..
or
element/#*[starts-with(name(), "s")]/parent::*
or
element/#*[starts-with(name(), "s")]/parent::node()
None from above worked for me.
So I did not some changes and it worked for me. :)
/*:UserCustomField[starts-with(#name, 'purchaseDate')]

XPath concat multiple nodes

I'm not very familiar with xpath. But I was working with xpath expressions and setting them in a database. Actually it's just the BAM tool for biztalk.
Anyway, I have an xml which could look like:
<File>
<Element1>element1<Element1>
<Element2>element2<Element2>
<Element3>
<SubElement>sub1</SubElement>
<SubElement>sub2</SubElement>
<SubElement>sub3</SubElement>
<Element3>
</File>
I was wondering if there is a way to use an xpath expression of getting all the SubElements concatted? At the moment, I am using:
/*[local-name()='File']/*[local-name()='Element3']/*[local-name()='SubElement']
This works if it only has one index. But apparently my xml sometimes has more nodes, so it gives NULL. I could just use
/*[local-name()='File']/*[local-name()='Element3']/*[local-name()='SubElement'][0]
but I need all the nodes. Is there a way to do this?
Thanks a lot!
Edit: I changed the XML, I was wrong, it's different, it should look like this:
<item>
<element1>el1</element1>
<element2>el2</element2>
<element3>el3</element3>
<element4>
<subEl1>subel1a</subEl1>
<subEl2>subel2a</subEl2>
</element4>
<element4>
<subEl1>subel1b</subEl1>
<subEl2>subel2b</subEl2>
</element4>
</item>
And I need to have a one line code to get a result like: "subel2a subel2b";
I need the one line because I set this xpath expression as an xml attribute (not my choice, it's specified). I tried string-join but it's not really working.
string-join(/file/Element3/SubElement, ',')
/File/Element3/SubElement will match all of the SubElement elements in your sample XML. What are you using to evaluate it?
If your evaluation method is subject to the "first node rule", then it will only match the first one. If you are using a method that returns a nodeset, then it will return all of them.
You can get all SubElements by using:
//SubElement
But this won't keep them grouped together how you want. You will want to do a query for all elements that contain a SubElement (basically do a search for the parent of any SubElements).
//parent::SubElement
Once you have that, you could (depending on your programming language) loop through the parents and concatenate the SubElements.

Resources