Find missing elements using XMLUnit - recursion

I have the following testXML
<root>
<a>test</a>
<b>bee</b>
<d/>
</root>
While the templateXML looks like this
<root>
<a/>
<b/>
<c/>
<d>
<e/>
</d>
</root>
I want XMLUnit to return the missing elements, in this case 'c' and 'e' are missing
/root[1]/c[1] is missing
/root[1]/d[1]/e[1] is missing
My code looks like this
public static ArrayList<Difference> testCompareToSkeletonXML(String xml, String template) throws Exception {
XMLUnit.setCompareUnmatched(false);
XMLUnit.setIgnoreAttributeOrder(true);
XMLUnit.setIgnoreComments(true);
XMLUnit.setIgnoreWhitespace(true);
XMLUnit.setIgnoreDiffBetweenTextAndCDATA(true);
DifferenceListener myDifferenceListener = new IgnoreTextAndAttributeValuesDifferenceListener();
DetailedDiff myDiff = new DetailedDiff(new Diff(template, xml));
// myDiff.overrideDifferenceListener(myDifferenceListener);
myDiff.overrideElementQualifier(new RecursiveElementNameAndTextQualifier());
ArrayList<Difference> returnList = new ArrayList<Difference>();
List<Object> allDifferences = myDiff.getAllDifferences();
for(Object obj: allDifferences){
Difference dif = (Difference) obj;
if(dif.getTestNodeDetail().getNode()== null){
//returnList.add(dif);
System.out.println(dif.getControlNodeDetail().getXpathLocation());
}
}
return returnList;
}
The output generated looks like this
/root[1]/a[1]
/root[1]/b[1]
/root[1]/c[1]
/root[1]/d[1]
Thanks for the help
--SD

You are telling XMLUnit to only match elements with each other if their element names and the nested text are the same. This is not the case for your a or b elements (they differ in nested text), that's why you see them in your list.
You see d rather than the nested e as the top level ds are different according to the rules of RecursiveElementNameAndTextQualifier. If you remove the line setting the RecursiveElementNameAndTextQualifier your list reduces to
/root[1]/c[1]
/root[1]/d[1]/e[1]
which is what you were expecting.

Related

Can not convert from object to list exception

List<myJavaClass> smallListnew = (List<myJavaClass>) i1.next();
Above line causing error says object can not be type casted to type List<myJavaClass>.
Below is some description code:
List<myJavaClass> i1=bigList.iterator();
Big list contains many small list in the following way:
//here unique list contains some Long values without the duplicates that were being compared with the refreshJobCountList.
Iterator<Long> i=uniqueRefJobId.iterator();
while (i.hasNext()) {
Long refreshJobID = i.next();
List<myJavaClass> smallList = new ArrayList<>();
for (myJavaClass details : refreshJobCountList) {
if (refreshJobID.equals(details.getRefreshJobId())) {
myJavaClass new_obj=new myJavaClass();
new_obj.setCount(details.getCount());
new_obj.setJobRunId(details.getJobRunId());
new_obj.setRefreshJobId(details.getRefreshJobId());
smallList.add(new_obj);
}
}
bigList.addAll(smallList);
}
Instead of typecasting the array. Add the elements of the small list to a bigger list. Use .allAll() to add the entire list.

Very complicated XQuery transformation

I have a very complex XQuery to write (at least by my standards).
Here is my input xml:
<testRequest>
<request1>
<Line>1</Line>
<action>addtoName</action>
</request1>
<request2>
<Line>2</Line>
<action>addtoSpace</action>
</request2>
<request3>
<Line>3<Line>
<action>addtospace</action>
</request3>
</testRequest>
In my output xml, the actions should be attached as attributes to the "request1" elements. So, based on the action element under a request1 element, the attribute for the request1 element should be one of the following:
if action = IgnoreCase(addtoName), the request1 element should be <request1 action=insertingname>
if action = IgnoreCase(addtoSpace), the request1 element should be <request1 action=updatingspace>
Not only this, but also, I need to add an attribute to the element, based on the action values underneath it.
So, I have to traverse each of the elements under a element and see if any of the elements are equal to "addtospace" if yes, then I need to get the corresponding values of the elements and make up the attribute for the element. From the above xml, my attribute for the element should be,
<testRequest lineFiller="Line Like 2_* AND Line Like 3_*>, where 2 and 3 are the respective line numbers.
and if there are no elements with element= addtoSpace, then the attribute for the element should be "changed".
So, in summary, my transformed xml should look like this:
<testRequest lineFiller="Line Like 2_* AND Line Like 3_*>
<request1 action=insertingname>
<Line>1</Line>
<action>addtoName</action>
</request1>
<request2 action=updatingspace>
<Line>2</Line>
<action>addtoSpace</action>
</request2>
<request3 action=updatingspace>
<Line>3<Line>
<action>addtospace</action>
</request3>
</testRequest>
Any help to accomplish this humungous task will be greatly appreciated.
thanks!!!
You should define functions to generate the attributes that you need to add to your elements.
For adding to the "request" element, this should work:
declare function local:getaction($x) {
if (lower-case($x/action) = "addtoname") then attribute action {"insertingspace"} else
if (lower-case($x/action) = "addtospace") then attribute action {"updatingspace"} else
()
};
The linefiller attribute can be created similarly:
declare function local:getfiller($x) {
attribute lineFiller {
if ($x/*[lower-case(action) = "addtospace"]) then
string-join(
for $r in $x/*[lower-case(action) = "addtospace"]
return concat("Line Like ",$r/Line,"_*")
, " AND ")
else "change"
}
};
Then to put it all together, fun a simple for loop over your original document, adding in the attributes where needed:
let $doc:=<<your original document>>
return
<testRequest>
{ local:getfiller($doc) }
{ for $r in $doc/* return
element { name($r) } {
local:getaction($r),
$r/*
}
}
</testRequest>
EDIT: enhanced getfiller function to return "change" if there are no actions

Removing last char from textfield without destroying textformat

i have a problem where i want to remove the last character from a textfield (including linebreaks) that has multiple textformats without removing the formats.
so far i have:
textfield.replaceText(textField.length-1,textField.length-1,'');
i guess this doesn't remove linebreaks, and is very slow, seems to destroy my textformats.
or:
textfield.text = textfield.text.slice(0,-1);
this is faster but removes all textformats as well.
It is a bit tedious, but you can use the htmlText-property of TextField, even though you are not formatting your text with StyleSheets: Flash will transform all your formatting information into HTML text internally, so even though you set textField.text, you can still get xml formatted text to work with:
textField.text = "A test.";
trace (textField.htmlText);
will actually return:
<P ALIGN="LEFT"><FONT FACE="Times Roman" SIZE="12" COLOR="#000000" LETTERSPACING="0" KERNING="0">A test.</FONT></P>
Text will always appear within <FONT> tags reflecting the changes you made using setTextFormat(). You can, therefore, iterate over the XML contained in this line, and remove only the last character in the last TextNode:
private function removeLastCharacter (textField:TextField) : void {
var xml:XML = new XML (textField.htmlText);
for ( var i : int = xml.children().length()-1; i >= 0; i-- ){
var node:XML = xml.children()[i];
if ( node.name() == "FONT") {
var tx:String = node.text()[0].toString();
node.setChildren (tx.substr (0, tx.length-1));
break;
}
}
textField.htmlText = xml;
trace (textField.text); // In the above example, output will be: "A test";
}
I hope I understand your problem correctly. If you keep your formatting in htmlText, I have one possible solution:
The idea is to keep the formatted text in an XML format, and modify the XML. XML will keep your formatting intact, you don't have to do string aerobatics to maintain them. The downsides are of course having to keep the formatting XML valid, and the extra variable.
Here's an example:
var tf:TextField = new TextField();
var t:XML = new XML("<html><p>lalala</p><font color='#ff0000'> lol</font></html>");
tf.htmlText = t.toXMLString();
t.font[0] = t.font[0].text().slice(0, -1);
tf.htmlText = t.toXMLString();
addChild(tf);

Linq2XML missing element

How do I modify the query below to properly handle the case where the "Summary" element is missing from one of the articles? Now when that happens I get an "Object reference not set to an instance of an object."
var articles = from article in xmlDoc.Descendants("Article")
select new {
articleId = article.Attribute("ID").Value,
heading = article.Element("Heading").Value,
summary = article.Element("Summary").Value,
contents = article.Element("Contents").Value,
cats = from cat in article.Elements("Categories")
select new {
category = cat.Element("Category").Value
}
};
The problem is that article.Element("Summary") returns null if the element is not found, so you get a NullReferenceException when you try to get the Value property.
To solve this, note that XElement also has an explicit conversion to string. This won't throw if the XElement is null - you will just get a null string reference.
So to solve your problem you can change this:
summary = article.Element("Summary").Value,
to this:
summary = (string)article.Element("Summary")

Filehelpers ExcelStorage.ExtractRecords fails when first cell is empty

When the first cell of an excel sheet to import using ExcelStorage.ExtractRecords is empty, the process fail. Ie. If the data starts at col 1, row 2, if the cell (2,1) has an empty value, the method fails.
Does anybody know how to work-around this? I've tried adding a FieldNullValue attribute to the mapping class with no luck.
Here is a sample project that show the code with problems
Hope somebody can help me or point in some direction.
Thank you!
It looks like you have stumbled upon an issue in FileHelpers.
What is happening is that the ExcelStorage.ExtractRecords method uses an empty cell check to see if it has reached the end of the sheet. This can be seen in the ExcelStorage.cs source code:
while (CellAsString(cRow, mStartColumn) != String.Empty)
{
try
{
recordNumber++;
Notify(mNotifyHandler, mProgressMode, recordNumber, -1);
colValues = RowValues(cRow, mStartColumn, RecordFieldCount);
object record = ValuesToRecord(colValues);
res.Add(record);
}
catch (Exception ex)
{
// Code removed for this example
}
}
So if the start column of any row is empty then it assumes that the file is done.
Some options to get around this:
Don't put any empty cells in the first column position.
Don't use excel as your file format -- convert to CSV first.
See if you can get a patch from the developer or patch the source yourself.
The first two are workarounds (and not really good ones). The third option might be the best but what is the end of file condition? Probably an entire row that is empty would be a good enough check (but even that might not work in all cases all of the time).
Thanks to the help of Tuzo, I could figure out a way of working this around.
I added a method to ExcelStorage class to change the while end condition. Instead of looking at the first cell for empty value, I look at all cells in the current row to be empty. If that's the case, return false to the while. This is the change to the while part of ExtractRecords:
while (!IsEof(cRow, mStartColumn, RecordFieldCount))
instead of
while (CellAsString(cRow, mStartColumn) != String.Empty)
IsEof is a method to check the whole row to be empty:
private bool IsEof(int row, int startCol, int numberOfCols)
{
bool isEmpty = true;
string cellValue = string.Empty;
for (int i = startCol; i <= numberOfCols; i++)
{
cellValue = CellAsString(row, i);
if (cellValue != string.Empty)
{
isEmpty = false;
break;
}
}
return isEmpty;
}
Of course if the user leaves an empty row between two data rows the rows after that one will not be processed, but I think is a good thing to keep working on this.
Thanks
I needed to be able to skip blank lines, so I've added the following code to the FileHelpers library. I've taken Sebastian's IsEof code and renamed the method to IsRowEmpty and changed the loop in ExtractRecords from ...
while (CellAsString(cRow, mStartColumn) != String.Empty)
to ...
while (!IsRowEmpty(cRow, mStartColumn, RecordFieldCount) || !IsRowEmpty(cRow+1, mStartColumn, RecordFieldCount))
I then changed this ...
colValues = RowValues(cRow, mStartColumn, RecordFieldCount);
object record = ValuesToRecord(colValues);
res.Add(record);
to this ...
bool addRow = true;
if (Attribute.GetCustomAttribute(RecordType, typeof(IgnoreEmptyLinesAttribute)) != null && IsRowEmpty(cRow, mStartColumn, RecordFieldCount))
{
addRow = false;
}
if (addRow)
{
colValues = RowValues(cRow, mStartColumn, RecordFieldCount);
object record = ValuesToRecord(colValues);
res.Add(record);
}
What this gives me is the ability to skip single empty rows. The file will be read until two successive empty rows are found

Resources