XPath not working correctly with XFA - c#

I have a dynamic PDF form that has a DropDownList. I'm using iTextSharp to try and modify the values inside the PDF before sending it out to the client. Here's what I'm trying, as per an answer in this question:
PdfReader reader = new PdfReader(myPdfPath);
XmlDocument xdoc = reader.AcroFields.Xfa.DomDocument;
XmlNode dropdown = xdoc.SelectSingleNode("/*/template/subform[#name='form1']/subform[#name='form2']/field[#name='DropDownList1']");
But no matter what XPath expression I use (ex: //subform or field[#name='DropDownList1']), SelectSingleNode always returns null, and SelectNodes returns an empty list.
Am I doing something wrong here? If there is a better way of doing this, I'd love to know.
Here's some the xml (I want the field node):
<?xml version="1.0" encoding="UTF-8"?>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/" timeStamp="2013-01-18T13:22:31Z" uuid="3c6141a4-56b4-4f6e-8a2d-4519050f1c69">
<template xmlns="http://www.xfa.org/schema/xfa-template/3.0/">
<?formServer defaultPDFRenderFormat acrobat9.1dynamic?>
<subform name="form1" layout="tb" locale="en_US" restoreState="auto">
<pageSet>
<pageArea name="Page1" id="Page1">
<contentArea x="0.25in" y="0.25in" w="576pt" h="756pt"/>
<medium stock="default" short="612pt" long="792pt"/> <?templateDesigner expand 0?>
</pageArea>
<?templateDesigner expand 0?>
</pageSet>
<subform w="576pt" h="756pt" name="form2">
<field name="DropDownList1" y="22.225mm" x="6.35mm" w="62mm" h="9mm">
<ui>
<choiceList>
<border>
<edge stroke="lowered"/>
</border>
<margin/>
</choiceList>
</ui>
<font typeface="Myriad Pro"/>
<margin topInset="1mm" bottomInset="1mm" leftInset="1mm" rightInset="1mm"/>
<para vAlign="middle"/>
<caption reserve="25mm">
<para vAlign="middle"/>
<value>
<text>Drop-down List</text>
</value>
</caption>
<items save="1">
<text>Item 1</text>
<text>Item 2</text>
<text>Item 3</text>
</items>
</field>
</subform>
<proto/>
<desc>
<text name="version">10.0.2.20120224.1.869952.867557</text>
</desc>
<?templateDesigner expand 1?>
<?renderCache.subset "Myriad Pro" 0 0 ISO-8859-1 4 112 28 0001000E001200130023002400250027002D0033003500420044004500460049004A004C004D004F00500051005300540055005600580059 -12BCDFLRTacdehiklnoprstuwx?>
</subform>
<?templateDesigner DefaultPreviewDynamic 1?>
<?templateDesigner DefaultRunAt client?>
<?templateDesigner Grid show:1, snap:1, units:0, color:ff8080, origin:(0,0), interval:(125000,125000)?>
<?templateDesigner DefaultCaptionFontSettings face:Myriad Pro;size:10;weight:normal;style:normal?>
<?templateDesigner DefaultValueFontSettings face:Myriad Pro;size:10;weight:normal;style:normal?>
<?templateDesigner DefaultLanguage JavaScript?>
<?acrobat JavaScript strictScoping?>
<?templateDesigner WidowOrphanControl 0?>
<?templateDesigner SaveTaggedPDF 1?>
<?templateDesigner SavePDFWithEmbeddedFonts 1?>
<?templateDesigner FormTargetVersion 30?>
<?templateDesigner Rulers horizontal:1, vertical:1, guidelines:1, crosshairs:0?>
<?templateDesigner Zoom 92?>
</template>
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
<annots/>
</xfdf>
<datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/"></datasets>
</xdp:xdp>

The reason your XPaths don't work is that most of the XML is in the namespace http://www.xfa.org/schema/xfa-template/3.0/.
In cases like these, the appropriate practice is to create an XmlNamespaceManager and declare the namespaces, then use them in your XPath. Please see this post for an explanation.
PdfReader reader = new PdfReader(myPdfPath);
XmlDocument xdoc = reader.AcroFields.Xfa.DomDocument;
XmlNamespaceManager nsm = new XmlNamespaceManager(xdoc.NameTable);
nsm.AddNamespace("xfa", "http://www.xfa.org/schema/xfa-template/3.0/");
XmlNode dropdown =
xdoc.SelectSingleNode("/*/xfa:template/xfa:subform[#name='form1']/xfa:subform[#name='form2']/xfa:field[#name='DropDownList1']",
nsm);
I also don't see a subform element with the name "form2". Was that a piece you just didn't include in your XML sample?

Related

Change XML element value in c#

My C# code:
XDocument doc = XDocument.Load(filename);
IEnumerable<XElement> collection =
doc.Elements("BCIRequest").Elements("Card").Elements("SelectedPIN");
My XML document:
<?xml version="1.0" encoding="utf-8"?>
<BCIRequest Version="2.0"
xmlns="urn:xxxxxx:bci:request">
<Header>
<SenderCode>XX99</SenderCode>
<SenderID>9999</SenderID>
<SequenceNumber>123</SequenceNumber>
<CardGroupCount>2</CardGroupCount>
<CardCount>4</CardCount>
<BlockCount>2</BlockCount>
</Header>
<!--card groups (must precede cards and blocks)-->
<CardGroup RequestID="1">
<CustomerNumber>XX01234567</CustomerNumber>
<CardGroupName Emboss="true">GROUP ONE</CardGroupName>
</CardGroup>
<CardGroup RequestID="2"
RequestRef="87416CB7-DAEF-483A-BD08-1A885531D958">
<CustomerNumber>XX12345678</CustomerNumber>
<CardGroupName Emboss="false">GROUP TWO</CardGroupName>
</CardGroup>
<Card RequestID="3">
<CustomerNumber>XX01234567</CustomerNumber>
<DriverCard>
<Driver Emboss="true">MARGE SIMPSON</Driver>
</DriverCard>
<CardTypeID>10</CardTypeID>
<PurchaseCategoryID>11</PurchaseCategoryID>
<Reissue>false</Reissue>
<GeneratedPIN/>
<OdoPrompt>false</OdoPrompt>
<CRNPrompt>false</CRNPrompt>
</Card>
<Card RequestID="4">
<CustomerNumber>XX12345678</CustomerNumber>
<VehicleCard>
<VRN Emboss="true">KYI 830</VRN>
</VehicleCard>
<CardTypeID>10</CardTypeID>
<PurchaseCategoryID>11</PurchaseCategoryID>
<Reissue>false</Reissue>
<SelectedPIN>0123</SelectedPIN>
<OdoPrompt>false</OdoPrompt>
<CRNPrompt>false</CRNPrompt>
</Card>
<Card RequestID="5">
<CustomerNumber>XX01234567</CustomerNumber>
<BearerCard>
<Bearer Emboss="true">OPEN XXXXXX</Bearer>
</BearerCard>
<CardTypeID>10</CardTypeID>
<PurchaseCategoryID>11</PurchaseCategoryID>
<Reissue>false</Reissue>
<FleetPIN/>
<OdoPrompt>false</OdoPrompt>
<CRNPrompt>false</CRNPrompt>
</Card>
<Block RequestID="6">
<CustomerNumber>XX01234567</CustomerNumber>
<PAN>7002999999999999991</PAN>
</Block>
<Card RequestID="7"
RequestRef="956EA6C5-7D7E-4622-94D0-38CAD9FCC8DF">
<CustomerNumber>XX01234567</CustomerNumber>
<DriverCard>
<Driver Emboss="true">HOMER SIMPSON</Driver>
<VRN Emboss="true">795 DVI</VRN>
</DriverCard>
<EmbossText>SPRINGFIELD POWER</EmbossText>
<CardTypeID>10</CardTypeID>
<TokenTypeID>20</TokenTypeID>
<PurchaseCategoryID>30</PurchaseCategoryID>
<ExpiryDate>2018-12</ExpiryDate>
<Reissue>true</Reissue>
<SelectedPIN>0123</SelectedPIN>
<OdoPrompt>true</OdoPrompt>
<CRNPrompt>true</CRNPrompt>
<!--address with optional fields specified-->
<CardDeliveryAddress OneTimeUse="false">
<ContactName>M xxxx</ContactName>
<ContactTitle>Mr</ContactTitle>
<CompanyName>Sxxxx</CompanyName>
<Line1>Sector 22-F</Line1>
<Line2>Springfield Power Plant</Line2>
<Line3>xxx Road</Line3>
<City>xxxx</City>
<Zipcode>xxxx</Zipcode>
<CountryCode>xxx</CountryCode>
</CardDeliveryAddress>
<!--address with only required fields-->
<PINDeliveryAddress OneTimeUse="true">
<Line1>xxxx</Line1>
<City>xxx</City>
<Zipcode>xxxx</Zipcode>
<CountryCode>xxxx</CountryCode>
</PINDeliveryAddress>
<Limits>
<Value Transaction="unlimited" Daily="200" Weekly="unlimited" Monthly="400"/>
<Volume Transaction="100" Daily="unlimited" Weekly="unlimited" Monthly="unlimited"/>
<Transactions Daily="unlimited" Weekly="unlimited" Monthly="unlimited"/>
<Day Monday="true" Tuesday="true" Wednesday="true" Thursday="true" Friday="true" Saturday="false" Sunday="false"/>
<Time Start="unlimited" End="17:00:00"/>
</Limits>
<Products>
<FuelProductRestrictionID>40</FuelProductRestrictionID>
<NonFuelProductRestrictionID>51</NonFuelProductRestrictionID>
<NonFuelProductRestrictionID>52</NonFuelProductRestrictionID>
<NonFuelProductRestrictionID>53</NonFuelProductRestrictionID>
<NonFuelProductRestrictionID>54</NonFuelProductRestrictionID>
<NonFuelProductRestrictionID>55</NonFuelProductRestrictionID>
</Products>
</Card>
<Block RequestID="8"
RequestRef="69A3E44D-DC10-4BEE-9249-1FC3C651BA0E">
<CustomerNumber>xxxxx</CustomerNumber>
<PAN>xxxxxx</PAN>
</Block>
</BCIRequest>
I need to update the element value in the above values. The old value is:
<SelectedPIN>0123</SelectedPIN>
And the new value should be:
<SelectedPIN EncryptedPIN="TKDS" FormNumber="000793906306">****</SelectedPIN>
Can anyone can help me on this?
If I selected the BCIRequest element, it's returning a null value. I've tried many solutions but unable to get one working on this XML file.
There many ways an Xml can be be modified, I prefer XDocument
XDocument doc = XDocument.Parse(input);
foreach (var element in doc.Descendants("SelectedPIN")) // filter if you want single element, in example I modifed for all elements.
{
element.Add(new XAttribute("EncryptedPIN", "TKDS"));
element.Add(new XAttribute("FormNumber", "000793906306"));
element.Value = "xxxxx"; //new value
}
and finally you can save the document using
doc.Save();
Take a look at this Demo
The root node (BCIRequest) contains a namespace so you need to include that into your query. Something like this should work:
XNamespace ns = "urn:xxxxxx:bci:request";
IEnumerable<XElement> collection = doc.Elements(ns + "BCIRequest").Elements(ns + "Card").Elements(ns + "SelectedPIN");

How to get child node by number from XML file

Am trying to make sorting of data in my XML by attribute "times" and show it in TreeView
I have XML file like
<main>
<data url="http://www.r.com/" data="13.10.2013 20:16:33" times="6" />
<data url="https://www.google.com/" data="13.10.2013 20:16:14" times="5" />
<data url="http://ya.com/" data="13.10.2013 19:21:15" times="26" />
</main>
what i want is to sort all nodes by attribute "times". If i can get any of node (1, 2 or 3), that i can take attribute and compare it with first ono - so can make some sorting. but i can't get required element.
so the question - how can i get any nodes from XML file, if i know only it's serial number or how can i sort XML file by some attribute?
Found, if i have id - i can use simething like
XmlDocument myXml = new XmlDocument();
myXml.Load(myfile);
myXml.GetElementById(`here put id`).GetAttribute("required attribute")
but i havent any id.
EDIT:
<main>
<data url="http://ya.com/" data="13.10.2013 19:21:15" times="**26**" />
<data url="http://www.r.com/" data="13.10.2013 20:16:33" times="**6**" />
<data url="https://www.google.com/" data="13.10.2013 20:16:14" times="**5**" />
</main>
Am new to Linq->Xml, So I have no idea about efficiency in this but it seems to work.
XDocument xdoc = XDocument.Parse(xml);
var ordered = xdoc.Descendants("data")
.OrderByDescending(x => int.Parse(x.Attribute("times").Value))
.ToArray();
xdoc.Root.RemoveAll();
xdoc.Root.Add(ordered);
Since you want to show data in TreeView,you need to first get all the data from xml and then sort it by times
XDocument doc=XDocument.Load(url);
//extract all data from xml
var data=doc.Descendants("data")
.Select(x=>
new
{
url=x.Attribute("url").Value,
data=x.Attribute("data").Value,
times=int.Parse(x.Attribute("times").Value)
}
);
foreach(var datum in data.OrderByDescending(x=>x.times))
{
datum.url;
datum.data;
datum.times;
}

How to grab children from xml file as a string

I am looking for an occurance of a certain tag in my xml file. If i find an occurance then i want to get its immediate children tags (not their child tags)
Is this possible? If so what do i need to look in to ?
Thanks
<Footballer>
<Player>
<Number />
<Team>
<Division />
<Position />
</Team>
<Country />
<Birthdate />
</Player>
</Footballer>
if player was the input for example then the tags Number, Team, Country Birthdate would be returned
You can try to use linq to xml:
var doc = XDocument.Load(xmlFilePath);
List<string> urlList = doc.Descendants("yourparent");
.Select(x => insert value you want to select).ToList();

Load selective XML data into Dataset using C#

Appreciate some pointers on how can I use XPath on XML file to extract only some data and load it into a dataset.
ds.ReadXml(fsReadXml);
will load the entire xml into dataset but my requirement is to load only particular node and values to dataset.
Sample xml data:
<data cobdate="5 Jul 2011" DBStatus="">
<view>BOTH</view>
<show_acctnbr>true</show_acctnbr>
<summary>
<headings sum="Summary" real_per="Realized this period" real_trd="Profit/loss in trading currency" real_select="Profit/loss in selected currency" short_term="Short Term Profit/Loss" long_term="Long Term Profit/Loss" />
<account number="A123456" curr_code="USD" curr_desc="US Dollars" tradecurrvalue="123,123.00" selectcurrvalue="123,123.00" managed="NO" />
<account number="P123456" curr_code="USD" curr_desc="US Dollars" tradecurrvalue="0.00" selectcurrvalue="0.00" managed="NO" />
</summary>
<detail>
<headings dateaq="Date acquired" datesld="Date sold" desc="Description" sec_nbr="Security number " qty="Quantity" cost="Cost basis" />
<account number="A123456" currency="US Dollars">
<item datesold="29 Apr 11" sec_nbr="1234" description="SOME VALUE(USD)" quantity="8,000" proceeds="123,123.0" />
<item datesold="29 Apr 11" sec_nbr="4567" description="SOME VALUE(USD)" quantity="9,000" proceeds="123,123.0" />
</account>
<account number="P123456" currency="US Dollars">
<item datesold="29 Apr 11" sec_nbr="1234" description="SOME VALUE(USD)" quantity="8,000" proceeds="123,123.0" />
<item datesold="29 Apr 11" sec_nbr="4567" description="SOME VALUE(USD)" quantity="9,000" proceeds="123,123.00" />
</account>
</detail>
</data>
In this example data, I just need to load accounts from <summary> node and if possible only number, tradecurrvalue and selectcurrvalue attributes. I using C# and 3.5.
With XDocument:
var doc = XDocument.Load(fileName);
var lst = doc
.Descendants("summary") // don't care where summary is
.Elements("account ") // direct child of <summary>
.Select(x => new
{
number = x.Attribute("number").Value,
...
});
foreach(var account in lst)
{
.... // add to DataSet
}
can also use XmlNodeList and XmlNode, light weight compared to others
foreach (XmlNode node in nodes)
{
dt.Rows.Add(node["Name"].InnerText.ToString(),
node["NoOfDay"].InnerText.ToString(),
node["dateColumn"].InnerText.ToString()
);
}
Reference Link
Evaluate this XPath expression in your code:
/*/summary/account/#*
[contains('|number|tradecurrvalue|selectcurrvalue|',
concat('|',name(),'|')
)
]
This selects any attribute (of any account element that is a child of any summary element that is a child of the top element in the XML document), named "number", "tradecurrvalue" or "selectcurrvalue"
It is very easy to enlarge the list of possible attribute names you want selected -- just include them in the pipe-delimited name list.

C# XPath Not Finding Anything

I'm trying to use XPath to select the items which have a facet with Location values, but currently my attempts even to just select all items fail: The system happily reports that it found 0 items, then returns (instead the nodes should be processed by a foreach loop). I'd appreciate help either making my original query or just getting XPath to work at all.
XML
<?xml version="1.0" encoding="UTF-8" ?>
<Collection Name="My Collection" SchemaVersion="1.0" xmlns="http://schemas.microsoft.com/collection/metadata/2009" xmlns:p="http://schemas.microsoft.com/livelabs/pivot/collection/2009" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FacetCategories>
<FacetCategory Name="Current Address" Type="Location"/>
<FacetCategory Name="Previous Addresses" Type="Location" />
</FacetCategories>
<Items>
<Item Id="1" Name="John Doe">
<Facets>
<Facet Name="Current Address">
<Location Value="101 America Rd, A Dorm Rm 000, Chapel Hill, NC 27514" />
</Facet>
<Facet Name="Previous Addresses">
<Location Value="123 Anywhere Ln, Darien, CT 06820" />
<Location Value="000 Foobar Rd, Cary, NC 27519" />
</Facet>
</Facets>
</Item>
</Items>
</Collection>
C#
public void countItems(string fileName)
{
XmlDocument document = new XmlDocument();
document.Load(fileName);
XmlNode root = document.DocumentElement;
XmlNodeList xnl = root.SelectNodes("//Item");
Console.WriteLine(String.Format("Found {0} items" , xnl.Count));
}
There's more to the method than this, but since this is all that gets run I'm assuming the problem lies here. Calling root.ChildNodes accurately returns FacetCategories and Items, so I am completely at a loss.
Thanks for your help!
Your root element has a namespace. You'll need to add a namespace resolver and prefix the elements in your query.
This article explains the solution. I've modified your code so that it gets 1 result.
public void countItems(string fileName)
{
XmlDocument document = new XmlDocument();
document.Load(fileName);
XmlNode root = document.DocumentElement;
// create ns manager
XmlNamespaceManager xmlnsManager = new XmlNamespaceManager(document.NameTable);
xmlnsManager.AddNamespace("def", "http://schemas.microsoft.com/collection/metadata/2009");
// use ns manager
XmlNodeList xnl = root.SelectNodes("//def:Item", xmlnsManager);
Response.Write(String.Format("Found {0} items" , xnl.Count));
}
Because you have an XML namespace on your root node, there is no such thing as "Item" in your XML document, only "[namespace]:Item", so when searching for a node with XPath, you need to specify the namespace.
If you don't like that, you can use the local-name() function to match all elements whose local name (the name part other than the prefix) is the value you're looking for. It's a bit ugly syntax, but it works.
XmlNodeList xnl = root.SelectNodes("//*[local-name()='Item']");

Categories