Changing ImageData using Open XML SDK - c#

I use Open XML SDK to manipulate OOXML spreadsheets.
I need to change the data in a VmlDrawingsPart, specifically the ImageData. ImageData have an o:relid property which is the reference to the image/file being controlled by the ImageData. I want to replace this reference, so the ImageData points to another source image/file.
I have a worksheetPart, old imagePart and the id to the new imagePart. From this, I need to find the ImageData, which is saved in xl/drawings/vmldrawing1.vml and then use Descendants to find ImageData and change it, but this is where I have trouble. I cannot find the right entry for this. How can I reach ImageData to change the reference?
string id = Get_RelationshipId(imagePart);
ImageData imageData = worksheetPart.VmlDrawingParts.First().RootElement.Descendants<ImageData>()
.Where(p => p.RelId == id)
.Select(p => p)
.Single();
imageData.RelId = Get_RelationshipId(new_ImagePart);
UPDATE
The above code give me this exception:
System.NullReferenceException: 'Object reference not set to an instance of an object.'
DocumentFormat.OpenXml.Packaging.OpenXmlPart.RootElement.get returned null.
Here's the actual content of the vmlDrawing1.vml file, where you can find o:relid="rId2" and I want to change this relationship value to point to another relationship:
<xml xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel">
<o:shapelayout v:ext="edit">
<o:idmap v:ext="edit" data="1"/>
</o:shapelayout><v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75"
o:preferrelative="t" path="m#4#5l#4#11#9#11#9#5xe" filled="f" stroked="f">
<v:stroke joinstyle="miter"/>
<v:formulas>
<v:f eqn="if lineDrawn pixelLineWidth 0"/>
<v:f eqn="sum #0 1 0"/>
<v:f eqn="sum 0 0 #1"/>
<v:f eqn="prod #2 1 2"/>
<v:f eqn="prod #3 21600 pixelWidth"/>
<v:f eqn="prod #3 21600 pixelHeight"/>
<v:f eqn="sum #0 0 1"/>
<v:f eqn="prod #6 1 2"/>
<v:f eqn="prod #7 21600 pixelWidth"/>
<v:f eqn="sum #8 21600 0"/>
<v:f eqn="prod #7 21600 pixelHeight"/>
<v:f eqn="sum #10 21600 0"/>
</v:formulas>
<v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"/>
<o:lock v:ext="edit" aspectratio="t"/>
</v:shapetype><v:shape id="_x0000_s1025" type="#_x0000_t75" style='position:absolute;
margin-left:2in;margin-top:165pt;width:144.75pt;height:15.75pt;z-index:1'
filled="t" fillcolor="window [65]" stroked="t" strokecolor="windowText [64]"
o:insetmode="auto">
<v:fill color2="window [65]"/>
<v:imagedata o:relid="rId1" o:title=""/>
<x:ClientData ObjectType="Pict">
<x:SizeWithCells/>
<x:Anchor>
3, 0, 11, 0, 6, 1, 12, 1</x:Anchor>
<x:CF>Pict</x:CF>
<x:AutoPict/>
</x:ClientData>
</v:shape><v:shape id="_x0000_s1026" type="#_x0000_t75" style='position:absolute;
margin-left:670.5pt;margin-top:97.5pt;width:207.75pt;height:42.75pt;
z-index:2' filled="t" fillcolor="window [65]" stroked="t" strokecolor="windowText [64]"
o:insetmode="auto">
<v:fill color2="window [65]"/>
<v:imagedata o:relid="rId2" o:title=""/>
<x:ClientData ObjectType="Pict">
<x:SizeWithCells/>
<x:Anchor>
13, 62, 6, 10, 18, 19, 9, 7</x:Anchor>
<x:CF>Pict</x:CF>
<x:AutoPict/>
</x:ClientData>
</v:shape><v:shape id="_x0000_s1027" type="#_x0000_t75" style='position:absolute;
margin-left:145.5pt;margin-top:244.5pt;width:144.75pt;height:15pt;z-index:3'
filled="t" fillcolor="window [65]" stroked="t" strokecolor="windowText [64]"
o:insetmode="auto">
<v:fill color2="window [65]"/>
<v:imagedata o:relid="rId3" o:title=""/>
<x:ClientData ObjectType="Pict">
<x:SizeWithCells/>
<x:Anchor>
3, 2, 16, 6, 6, 3, 17, 6</x:Anchor>
<x:CF>Pict</x:CF>
<x:AutoPict/>
<x:DDE/>
<x:Camera/>
</x:ClientData>
</v:shape></xml>
I know I can get this code to wrok somehow, because I got the same concept working for changing Blip image controls on ordinarily embedded images by using this code:
// Change relationships of image
string id = Get_RelationshipId(imagePart);
Blip blip = worksheetPart.DrawingsPart.WorksheetDrawing.Descendants<DocumentFormat.OpenXml.Drawing.Spreadsheet.Picture>()
.Where(p => p.BlipFill.Blip.Embed == id)
.Select(p => p.BlipFill.Blip)
.Single();
blip.Embed = Get_RelationshipId(new_ImagePart);

I am attempting a different approach which runs through the XML searching for the corresponding attribute. I can find the attribute now and change it. However, I don't think this is an ideal approach, so I welcome better answers.
Here's the code I got working:
// Change relationships of image
string id = Get_RelationshipId(imagePart);
XDocument xElement = worksheetPart.VmlDrawingParts.First().GetXDocument();
IEnumerable<XElement> descendants = xElement.FirstNode.Document.Descendants();
foreach (XElement descendant in descendants)
{
if (descendant.Name == "{urn:schemas-microsoft-com:vml}imagedata")
{
IEnumerable<XAttribute> attributes = descendant.Attributes();
foreach (XAttribute attribute in attributes)
{
if (attribute.Name == "{urn:schemas-microsoft-com:office:office}relid")
{
if (attribute.Value == id)
{
attribute.Value = Get_RelationshipId(new_ImagePart);
worksheetPart.VmlDrawingParts.First().SaveXDocument();
}
}
}
}
}

Related

Inserting a new element after an existing element in an SVG (XML)

I want to insert an element after an existing element. For example, in this simple XML file, I want to insert an element after the "defs" element.
<?xml version="1.0" standalone="no"?>
<svg viewBox="0 0 790 790" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" stroke-linecap="round" stroke-linejoin="round" fill-rule="evenodd" xml:space="preserve" >
<defs >
<clipPath id="clipId0" >
<path d="M0,790 790,790 790,0 0,0 z" />
</clipPath>
</defs>
****I WANT TO INSERT MY NEW ELEMENT HERE
<g clip-path="url(#clipId0)" fill="none" stroke="rgb(0,0,0)" stroke-width="0.5" />
<g clip-path="url(#clipId0)" fill="rgb(0,255,0)" stroke="rgb(0,255,0)" stroke-width="0" >
<text transform="matrix(12.0023 0 -0 12.0023 130.206 546.583)" font-family="Arial,'sans-serif'" font-size="1.39636" >WORK REGION</text>
</g>
</svg>
Here is my attempted c# code:
public void AddBlackBackground(string xOrig, string yOrig, string xExt, string yExt )
{
XElement xE = new XElement("rect");
xE.Add(new XAttribute("x", xOrig));
xE.Add(new XAttribute("y", xOrig));
xE.Add(new XAttribute("width", xOrig));
xE.Add(new XAttribute("height", xOrig));
xE.Add(new XAttribute("style", "fill:black"));
XElement root = _XML_Doc.Root.Element("svg");
XElement defs = root.Element("defs");
defs.AddAfterSelf(xE);
}
The XElements root and defs are null when I run the code, so naturally the line defs.AddAfterSelf(xE); throws an Object reference not set error.
Any guidance is appreciated.
Try this with namespace
var ns = _XML_Doc.Root.Name.Namespace;
XElement root = _XML_Doc.Element(ns +"svg");
XElement defs = root.Element(ns + "defs");
defs.AddAfterSelf(xE);
Or to ignore namespace
_XML_Doc.Root
.DescendantsAndSelf()
.Elements()
.Single(d => d.Name.LocalName == "defs")
.AddAfterSelf(xE);

C# XML LINQ searching

I have a XML file like this:
<SoftwareComponent schemaVersion="1.0" packageID="Y75WC" releaseID="Y75WC" hashMD5="a190fdfa292276288df38507ea551a3b" path="FOLDER04650736M/1/OptiPlex_3050_1.7.9.exe" dateTime="2017-12-05T05:34:30+00:00" releaseDate="décembre 05, 2017" vendorVersion="1.7.9" dellVersion="1.7.9" packageType="LWXP" identifier="532f5a9e-c087-4499-b40c-cf7921ee06d3" rebootRequired="true">
<Name>
<Display lang="en"><![CDATA[Dell OptiPlex 3050 System BIOS,1.7.9]]></Display>
</Name>
<ComponentType value="BIOS">
<Display lang="en"><![CDATA[BIOS]]></Display>
</ComponentType>
<Description>
<Display lang="en"><![CDATA[This package provides the Dell System BIOS update and is supported on Dell OptiPlex 3050 Tower, OptiPlex 3050 Small Form Factor and OptiPlex 3050 Micro for Windows Operation System.]]></Display>
</Description>
<LUCategory value="NONE">
<Display lang="en"><![CDATA[None]]></Display>
</LUCategory>
<Category value="BI">
<Display lang="en"><![CDATA[FlashBIOS Updates]]></Display>
</Category>
<SupportedDevices>
<Device componentID="159" embedded="0">
<Display lang="en"><![CDATA[OptiPlex 3050 System BIOS]]></Display>
</Device>
</SupportedDevices>
<SupportedSystems>
<Brand key="1" prefix="OP">
<Display lang="en"><![CDATA[Optiplex]]></Display>
<Model systemID="07A3">
<Display lang="en"><![CDATA[3050]]></Display>
</Model>
</Brand>
</SupportedSystems>
<ImportantInfo URL="http://www.dell.com/support/home/us/en/19/Drivers/DriversDetails?driverId=Y75WC" />
<Criticality value="2">
<Display lang="en"><![CDATA[Urgent-Dell highly recommends applying this update as soon as possible. The update contains changes to improve the reliability and availability of your Dell system.]]></Display>
</Criticality>
There are multiple SoftwareComponent Elements inside.
I tried to get some attributes of SoftwareComponent ( dellVersion, hashMD5) based on descendants Elements ( ComponentType value, SupportedSystems->Device->Display value, Criticality value) but all my tests were not good.
See my actual code, I can get all the value in the XML file only:
XDocument doc = XDocument.Load("catalog.xml");
var els = from el in doc.Root.Elements("SoftwareComponent")
select new
{
dellVersion = (string)el.Attribute("dellVersion"),
hashMD5 = (string)el.Attribute("hashMD5"),
path = (string)el.Attribute("path"),
};
foreach (var el in els)
{
Console.WriteLine("dell BIOS: {0}, MD5: {1}, path: {2}", el.dellVersion, el.hashMD5, el.path);
}
Somebody can show me how to proceed please ?
Thanks
First of all, your XML document is missing a </SoftwareComponent> end tag. Maybe you didn't copy the contents OK here.
Then, SoftwareComponent is actually the root in your document, so you would need code like:
XDocument doc = XDocument.Load("catalog.xml");
var el = new
{
dellVersion = (string)doc.Root.Attribute("dellVersion"),
hashMD5 = (string)doc.Root.Attribute("hashMD5"),
path = (string)doc.Root.Attribute("path"),
};
Console.WriteLine("dell BIOS: {0}, MD5: {1}, path: {2}", el.dellVersion, el.hashMD5, el.path);
Your code would work OK as-is if the XML would have the format:
<Root>
<SoftwareComponent schemaVersion="1.0" packageID="Y75WC" releaseID="Y75WC" hashMD5="a190fdfa292276288df38507ea551a3b" path="FOLDER04650736M/1/OptiPlex_3050_1.7.9.exe" dateTime="2017-12-05T05:34:30+00:00" releaseDate="décembre 05, 2017" vendorVersion="1.7.9" dellVersion="1.7.9" packageType="LWXP" identifier="532f5a9e-c087-4499-b40c-cf7921ee06d3" rebootRequired="true">
</SoftwareComponent>
</Root>
XML documents can only have one root node, so you can't have multiple SoftwareComponent as root as you seem to imply.
If you want to get, for example, ComponentType value, you can do:
componentTypeValue = (string)el.Descendants("ComponentType").FirstOrDefault().Attribute("value")
I would actually change the query into a foreach and check that FirstOrDefault result for null.

Extract string value from md-card

I want to extract the string value (Email is verified successfully!) from the md-card using Selenium.
<md-card ng-if="$ctrl.isMyCompany && $ctrl.showVerified" class="ng-scope _md">
<md-card-content class="tradingPartnerVerification-warn ng-binding layout-align-start-center" layout-align="start center">
<md-icon class="myCompany-verifiedEmailIcon" md-svg-icon="check-circle" role="img" aria-label="check-circle">
<svg xmlns="http://www.w3.org/2000/svg" fit="" height="100%" width="100%" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" focusable="false">
<g id="check-circle">
<path d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M11,16.5L18,9.5L16.59,8.09L11,13.67L7.91,10.59L6.5,12L11,16.5Z"></path>
</g>
</svg>
</md-icon>
Email is verified successfully!
</md-card-content>
</md-card>
I'm using .Net with NUnit framework. My code is below.
string ActualEmailVerifiedText = diver.FindElement(By.XPath("//md-card-content[#class='tradingPartnerVerification-warn ng-binding layout-align-start-center']")).Text;
I think it is possible that some class is being added in md-card-content in runtime (assuming you are misspeling driver only here)
Try this:
string ActualEmailVerifiedText = driver.FindElement(
By.XPath("//md-card-content[contains(#class,'tradingPartnerVerification-warn')]")
).getAttribute('textContent');
To extract the string Email is verified successfully! you need to induce WebDriverWait for the text to render within the HTML as follows :
wait = new WebDriverWait(driver, TimeSpan.FromSeconds(30));
string myElement = wait.Until(ExpectedConditions.TextToBePresentInElementLocated(By.XPath("//md-card[#class='ng-scope _md']/md-card-content[#class='tradingPartnerVerification-warn ng-binding layout-align-start-center']"),"Email is verified successfully!"));
Console.WriteLine(myElement.Text);

Query to return entities that match two link criterias with Left Outer Join

I have a query that should return "A" entities while having links to entity "B" and "C" and these two links have "Left Outer Join" operator so that I can receive "A" entities either from one link or another.
After execution it returns me only one record which is common for two of the link criterias. If I remove link to entity "B" then I get expected records that match the link criteria. Same happens when I remove link to entity "C" while the link to entity "B" is present.
So, I assume that my query works only with one link, but it doesn't work as expected when there are two links and I don't understand why.
Actually I was following this MSDN example.
Here's my data:
_____________
| A |
-------------
| a_id |
| 1 | - entity with this id is the only record in the results
| 2 | - entity with this id should appear in the results, but it is not
| 3 | - entity with this id should appear in the results, but it is not
_________________________________
| B |
---------------------------------
| b_one_id | b_two_id |
| 1 | 1 | - matches link criteria with alias "connectionB"
| 2 | 1 | - matches link criteria with alias "connectionB"
| 1 | 444 | - doesn't match link criteria, should not appear
_________________________________
| C |
---------------------------------
| c_one_id | c_two_id |
| 1 | 2 | - matches link criteria with alias "connectionC"
| 3 | 2 | - matches link criteria with alias "connectionC"
| 1 | 555 | - doesn't match link criteria, should not appear
My query expression:
QueryExpression query = new QueryExpression
{
EntityName = A.EntityLogicalName,
ColumnSet = new ColumnSet
{
AllColumns = false,
Columns =
{
"a_id",
"a_name"
}
}
};
query.Distinct = true;
query.AddLink(B.EntityLogicalName, "a_id", "b_one_id", JoinOperator.LeftOuter);
query.LinkEntities[0].EntityAlias = "connectionB";
query.LinkEntities[0].LinkCriteria.AddCondition("b_two_id", ConditionOperator.Equal, 1);
query.AddLink(C.EntityLogicalName, "a_id", "c_one_id", JoinOperator.LeftOuter);
query.LinkEntities[0].EntityAlias = "connectionC";
query.LinkEntities[0].LinkCriteria.AddCondition("c_two_id", ConditionOperator.Equal, 2);
// doesn't work as expected
query.Criteria.AddFilter(LogicalOperator.Or);
query.Criteria.Filters[0].AddCondition("connectionB", "b_one_id", ConditionOperator.NotNull);
query.Criteria.Filters[0].AddCondition("connectionC", "c_one_id", ConditionOperator.NotNull);
// doesn't work as expected either
query.Criteria.AddCondition("a_id", ConditionOperator.NotNull);
The query expression gets transformed into following Fetch XML query:
<fetch distinct="true" no-lock="false" mapping="logical">
<entity name="A">
<attribute name="a_id" />
<attribute name="a_name" />
<filter type="and">
<filter type="or">
<!-- doesn't work as expected -->
<condition attribute="b_one_id" operator="not-null" entityname="connectionB" />
<condition attribute="c_one_id" operator="not-null" entityname="connectionC" />
<!-- doesn't work as expected either -->
<condition attribute="a_id" operator="not-null" entityname="A" />
</filter>
</filter>
<link-entity name="B" to="a_id" from="b_one_id" link-type="outer" alias="connectionB">
<filter type="and">
<condition attribute="b_two_id" operator="eq" value="1" />
</filter>
</link-entity>
<link-entity name="C" to="a_id" from="c_one_id" link-type="outer" alias="connectionC">
<filter type="and">
<condition attribute="c_two_id" operator="eq" value="2" />
</filter>
</link-entity>
</entity>
</fetch>
I hope There is some programming mistake in Your Code.
Look at Your Code Part
query.AddLink(B.EntityLogicalName, "a_id", "b_one_id", JoinOperator.LeftOuter);
query.LinkEntities[0].EntityAlias = "connectionB";
query.LinkEntities[0].LinkCriteria.AddCondition("b_two_id", ConditionOperator.Equal, 1);
query.AddLink(C.EntityLogicalName, "a_id", "c_one_id", JoinOperator.LeftOuter);
query.LinkEntities[0].EntityAlias = "connectionC";
query.LinkEntities[0].LinkCriteria.AddCondition("c_two_id", ConditionOperator.Equal, 2);
You are adding query.AddLink() and again Adding some other Link to query
but Using the same Link ie query.LinkEntities[0] for Alias and LinkCriteria.
May be correcting this mistake can solve your Problem.

Listing atributes from sub-elements

I'm new to C# but I'm attempting to load data from a xml file that's like this...
<?xml version="1.0" encoding="utf-8" ?>
<adventures>
<adventure_path Name ="Adventure Path 1">
<adventure Name ="Adventure 1">
<senario Name ="Senario 1">
<location Name="Location 1" Players="1"/>
</senario>
<adventure Name ="Adventure 2">
<senario Name ="Senario 2">
<location Name="Location 2" Players="1"/>
</senario>
</adventure>
</adventure_path>
<adventure_path Name ="Adventure Path 2">
<adventure Name ="Adventure 3">
<senario Name ="Senario 3">
<location Name="Location 3" Players="1"/>
</senario>
<adventure Name ="Adventure 4">
<senario Name ="Senario 4">
<location Name="Location 4" Players="1"/>
</senario>
</adventure>
</adventure_path>
</adventures>
What I'm trying to do with this is load the name attributes to an itemlistbox of the adventure element (the "adventure 1", "adventure 2", etc.) within the selected adventure_path element in an itemlistbox. I got the selection part working and the loading to the list working. What's not working is loading all the adventures...
Basically what happens is ListBox1 loads the adventure paths all fine and dandy, I select one of the said adventure paths, and ListBox2 load the first adventure... and that's it. It wont load adventure 2, 3, or 4. So here's the code that should load all the adventures-
private void lst_Adventure_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
string selectedItem = lst_Adventure.SelectedItem.ToString();
lst_Adventures.Items.Clear();
XDocument doc = new XDocument();
bool gotStatsXml = false;
try
{
doc = XDocument.Load("D:\\WpfApplication1\\WpfApplication1\\Adventures.xml");
gotStatsXml = true;
}
catch
{
gotStatsXml = false;
}
XElement selectedElement = doc.Descendants().Where(x => (string)x.Attribute("Name") == selectedItem).FirstOrDefault();
foreach (var docs in selectedElement.Descendants("adventure")) ;
{
XElement elements = selectedElement.Element("adventure");
string AdventuresPathName = elements.Attribute("Name").Value;
lst_Adventures.Items.Add(AdventuresPathName);
}
}
You have two problems:
You don't check if selectedElement is null before accessing its descendants
You are adding first adventure element of selectedElement on each loop (take a look - instead of using docs element (which is adventure) you are loading Element("adventure") from selectedElement)
Instead you should use
if (selectedElement != null)
{
foreach (var docs in selectedElement.Elements("adventure"))
{
string AdventuresPathName = (string)docs.Attribute("Name");
lst_Adventures.Items.Add(AdventuresPathName);
}
}
Or, simply get all adventure names:
List<string> adventureNames =
doc.Root.Elements("adventure_path")
.Where(ap => (string)ap.Attribute("Name") == selectedItem)
.Elements("adventure")
.Select(a => (string)a.Attribute("Name"))
.ToList();
Or with XPath
var xpath = String.Format("//adventure_path[#Name='{0}']/adventure", selectedItem);
List<string> adventureNames = doc.XPathSelectElements(xpath)
.Select(a => (string)a.Attribute("Name"))
.ToList();
you are missing the value property x.Attribute("Name").Value
XElement selectedElement = doc.Descendants().Where(x => x.Attribute("Name").Value == selectedItem).FirstOrDefault();

Categories

Resources