I apologize if this was asked before, but after searching for some time, I could not find any specific answer on this.
I have an ERD diagram in Visio 2010. It has around 15 tables or so. In order to have our DBAs create the database, I have to output each column to excel sheet with the data type, primary key, description.
My first attempt was to simply copy and paste the column definitions table from the shape properties, but this does not work (thanks Microsoft!). After trying a few other things, it turned out I would have to copy every cell manually for every table - time consuming.
I turned to C# and Visio Interop for help. I am able to export the column definitions now (they are in Text property of the shape), but I can not find the property which holds the name of the table.
Does anyone know which object holds this property, or if it is even accessible?
Thank you
In the end I solved it. I was unable to parse out the standard Visio drawing (.vsd) so I opted for Visio XML Drawing (.vdx). In the end, this worked for me:
Where path is the file path to the vxd drawing. I turned out that each shape definition in the page in XML drawing has 2 shapes of its own. The first shape holds the Entity name, the second holds the Entity Columns.
XDocument xdoc = XDocument.Load(path);
var elements = xdoc.Elements().Elements();
XName pageXName = XName.Get("Page","http://schemas.microsoft.com/visio/2003/core");
var pages = elements.Elements(pageXName);
foreach (XElement page in pages)
{
XName shapeXName = XName.Get("Shape","http://schemas.microsoft.com/visio/2003/core");
var shapes = from shape in page.Elements().Elements(shapeXName)
where shape.Attribute("Type").Value == "Group"
select shape;
foreach (XElement shape in shapes)
{
var shapeShapes = shape.Elements();
List<XElement> textShapes = shapeShapes.Elements(shapeXName).ToList();
XName textXName = XName.Get("Text","http://schemas.microsoft.com/visio/2003/core");
XName cpXName = XName.Get("Text", "http://schemas.microsoft.com/visio/2003/core");
string tableName = textShapes[0].Elements(textXName).SingleOrDefault().Value;
string columns = textShapes[1].Elements(textXName).SingleOrDefault().Value;
Debug.WriteLine("-------------" + tableName.TrimEnd('\n') + "---------------");
Debug.Write(columns);
Debug.WriteLine("----------------------------");
}
}
Related
Im trying to find a element in a page that posses a considerable number of the same element, the difference between the elements is one ID with numbers in the final of the name. for example:
The only one visible when the page is loaded: limit_arrown_btn_3201
But this number in the end of the ID changes when some action are aplicated in the system.Because of that I dont have a way to maping the elements to send the click event, because whent the element is changed the last numbers of ID is alterated, crashing my scripts...
I have tried to use CssSelector, but all another caracteristics like Names and Classes is exactly the same.
Did some one haved experienced the same situation and could help me?
Thanks a lot.
You can use partial id, contains *= or start with ^=
By.CssSelector([id*='limit_arrown_btn'])
At work, we run into this situation all the time. For us, this usually happens as a result of extJS dynamically renaming elements on us. To get around it, I use xpath and usually move up the DOM a few levels to find a unique element. Then, with the console open (Chrome is my browser of choice here), I click on and off the element that is dynamically changing. Chrome will highlight what attributes are changing when this happens. This way, you can see what does and does not change and tailor your xpath to match. For instance, we have an application that generates tables using randomly generated IDs. I built a utility class in which you pass in a WebElement that represents the whole table wrapper (header, body, footer, etc.) and a String value of the header column that you want to look at. From that, I get the dynamic ID for the column of the table: (Forgive the examples, as they are in Java and not C#)
public String getGridColumnFromTable(WebElement tableDivElement, String headerColumn) {
WebElement newElement = waitUntilElementIsClickable(tableDivElement);
WebElement contextContainer = null;
String headerXPath = ".//child::div[contains(#id,'headercontainer')]/child::div/child::div/child::div/child::div/span[.='" + headerColumn + "']";
WebElement headerGridColumn = newElement.findElement(By.xpath(headerXPath));
waitUntilElementIsClickable(headerGridColumn);
String contextContainerXpath = ".//parent::div/parent::div";
contextContainer = headerGridColumn.findElement(By.xpath(contextContainerXpath));
String gridColumnID = contextContainer.getAttribute("id");
waitForPostBack();
return gridColumnID;
}
Once I have that dynamic gridColumnID, I use that to tie all the other functions to it, like this:
public String getCellTextInTableByRowAndColumnName(WebElement tableDivElement, int rowNumber, String headerColumn) {
WebElement newElement = waitUntilElementIsClickable(tableDivElement);
String tableGridColumnId = getGridColumnFromTable(newElement, headerColumn);
return newElement.findElement(By.xpath(".//tbody/tr[contains(#data-recordindex, '" + rowNumber + "')]/td[contains(#class, '" + tableGridColumnId + "')]//div")).getText();
}
While this example may be a bit foreign to you, since your project is most likely very different, it does show how to work around the problem by traversing the DOM to find the element you're after.
If you have a few html snippets you could post, it would be most helpful in tailoring an answer.
I'm working on some code to manipulate bookmarks in a preexisting .DOTX template file. For this issue, some of the bookmarks are intended to point to another .DOTX file and insert it into the current document.
I'm having trouble finding a way to do this without some heavy manipulation and digging through each element in the 2nd template and creating a similar element in the current document.
Anyone have any ideas of a way to do this easily?
Turned out to be easier than I thought.
foreach (BookmarkStart bookmark in mainDoc.RootElement.Descendants<BookmarkStart>().Where(b => String.Equals(b.Name, bookmarkName)))
{
var parent = bookmark.Parent;
using (WordprocessingDocument newTemplate = WordprocessingDocument.Open(template2, false))
{
var newTemplateBody = newTemplate.MainDocumentPart.Document.Body;
foreach (var element in newTemplateBody.Elements().Reverse<OpenXmlElement>())
{
parent.InsertAfterSelf<OpenXmlElement>((OpenXmlElement)element.Clone());
}
}
}
I apparently was doing everything right, however I was inserting the template in a paragraph. The template is a table, which cannot be nested in a paragraph. This was what was actually breaking my document.
I'm beginning to program in C#. I can use Visual Studio Express 2012 for this purpose. I'm trying to create application that will import data from xml spreadsheet 2003 from specific column (but not specified number of entries in that column) and it will list text from each cell (all of them in that column).
I have read few topics about it, like this one:
http://social.msdn.microsoft.com/Forums/windowsapps/en-US/4fce4765-2d05-4a2b-8d0a-6219e87f3307/reading-excel-file-using-c-in-winrt-platform?forum=winappswithcsharp
but most of the answers are related with Visual Studio 2012 not the express version, thus I'm limited with libraries and extensions. Most of this solutions when I try to use them, don't work in my VS Express 2012 cause they are missing something.
This program is working for me and is returning value of one specific cell. How can I change it, so it will read every cell from that column, assign every value to a table (or maybe variable) so I can work with this content and maybe randomize order later?
namespace UnitTest
{
public class TestCode
{
//ReadExcelCellTest
public static void Main()
{
XDocument document = XDocument.Load(#"C:\Projekt2\File1.xml");
XNamespace workbookNameSpace = #"urn:schemas-microsoft-com:office:spreadsheet";
// Get worksheet
var query = from w in document.Elements(workbookNameSpace + "Workbook").Elements(workbookNameSpace + "Worksheet")
where w.Attribute(workbookNameSpace + "Name").Value.Equals("Sheet1")
select w;
List<XElement> foundWoksheets = query.ToList<XElement>();
if (foundWoksheets.Count() <= 0) { throw new ApplicationException("Worksheet Settings could not be found"); }
XElement worksheet = query.ToList<XElement>()[0];
// Get the row for "Seat"
query = from d in worksheet.Elements(workbookNameSpace + "Table").Elements(workbookNameSpace + "Row").Elements(workbookNameSpace + "Cell").Elements(workbookNameSpace + "Data")
where d.Value.Equals("StateID")
select d;
List<XElement> foundData = query.ToList<XElement>();
if (foundData.Count() <= 0) { throw new ApplicationException("Row 'StateID' could not be found"); }
XElement row = query.ToList<XElement>()[0].Parent.Parent;
// Get value cell of Etl_SPIImportLocation_ImportPath setting
XElement cell = row.Elements().ToList<XElement>()[1];
// Get the value "Leon"
string cellValue = cell.Elements(workbookNameSpace + "Data").ToList<XElement>()[0].Value;
Console.WriteLine(cellValue);
}
}
}
I believe that any version of Visual Studio would allow you to use open-source libraries:
http://closedxml.codeplex.com/
Link to doc: http://closedxml.codeplex.com/wikipage?title=Finding%20and%20extracting%20the%20data&referringTitle=Documentation
Reading Excel cell value should be much easier then.
How can I change it, so it will read every cell from that column,
assign every value to a table (or maybe variable) so I can work with
this content and maybe randomize order later?
I hope that closedxml will help you achieve this task.
Handling Excel sheets is not as easy as one might expect. For example: cell content is often just a reference to the real value stored in a dictionary (that's what the List<Dictionary<string, string>> is for in the code in the forum topic you linked). Also, other whacky things can happen, like receiving unexpected NULL cells at the end of a row.
I don't know how low level you want to keep your code. If there's a possibility that the functionality will evolve, you better look for some libraries. Take a look at Microsoft's own Open XML SDK: Open XML SDK 2.5. That provides support for all XML based office formats. There are two downsides: it's 12MB+ assembly, and the other is that this one is still not as high level as I expected. But you get some concepts like rows and columns.
The other alternatives are some non Microsoft libraries. There are numerous ones on CodePlex and other open source repos. Watch out to select something which is active and updated. Take a look at the issue section of the project, you'll see that there are usually many. You can see how they are handled. Many projects focus on Excel only, which is probably what you need, and will be smaller than a general OpenXML solution.
You can get paid product. Finally I ended up using SmartXLS, because it turned out our company had a license.
I'm currently writing a function to save and read data to/from and XML document through
LINQ. Currently I can write the document just fine, but if I go to add data to an existing item, it simply adds a new item. My goal is to create an address book type system (yes I know there's 1000 out there, it's just a learning project for myself) and I've tried ini and basic text but it seems that XML is the best way to go short of using a local DB like sql. Currently I have:
XDocument doc = XDocument.Load(#"C:\TextXML.xml");
var data = new XElement("Entry",
new XElement("Name", textBox1.Text),
new XElement("Address", richTextBox2.Text),
new XElement("Comments", richTextBox1.Text));
doc.Element("Contacts").Add(data);
doc.Save(#"C:\TextXML.xml");
I searched SO and can't seem to find how to append/replace.
Now this saves everything properly, even when I add to the document, but if I want to update an entry I'm not sure how to without creating a new "Entry" nor am have I gotten the knack of removing one. (I'm somewhat new to C# still and self-taught so pardon anything obvious I've overlooked.)
My second issues revolves around loading the information into textboxes.
I'm able to load a list of Entry names into a listbox, but when I go to open the information from that entry I'm not sure how to properly get the nested info.
With the example above I'd need something similar to the following:
XDocument doc = XDocument.Load(#"C:\TextXML.xml");
boxName.Text = The name from the SelectedItem of the list box.
boxAddress.Text = The address child of the element named above etc.
Each method I've tried I wind up with a null reference exception, which tells me I'm not pointing to the right thing, but I'm not sure how to get to those things properly.
I've also tried creating a string and var of the SelectedItem from the list box to help with the naming, and using ToString methods, but still can't figure it out.
For replacing values, there are several functions you can use in XElement:
Value (property with a public setter)
SetValue()
SetElementValue()
SetAttributeValue()
ReplaceWith()
ReplaceNodes()
For example, if you wanted to replace the value in Name:
data.Element("Name").SetValue("NewValue");
or
data.Element("Name").Value = "NewValue";
For loading, once you have the XElement node you desire, then it's as simple as doing
xelement.Value
Or if it's an attribute:
xelement.Attribute("AttributeName").Value
Using your code as an example:
boxName.Text = doc.Element("Entry").Element("Name").Value;
Edit to address comment:
If I'm reading your comment right, you're wanting to extract the Name/Address/etc. data from all the nodes within the Contacts main node?
If so, then you would probably want something like this:
boxName.Text = string.Join(",", doc.Elements("Entry").Select(x => x.Element("Name").Value));
This will give you a single string that has all the names in all Entries separated by a comma. Just change "Name" to "Address" to do the same for addresses.
I'd suggest doing a search for Linq to XML for finding more information about how to use this parsing.
While writing a custom import tool for a Tridion 2011 project using the core service I have come across an issue when trying to save a component.
The following code works fine when the field on the component has a value but when it does not I get an error.
Here's my code (error handling removed for brevity):
//component is a ComponentData object from Tridion
var doc = new XmlDocument();
doc.LoadXml(component.Content);
var namespaces = new XmlNamespaceManager(doc.NameTable);
namespaces.AddNamespace("ns", doc.DocumentElement.NamespaceURI);
//componentFromSpreadsheet has a dictionary of fields and values to update
foreach (var field in componentFromSpreadsheet.Fields)
{
XmlNode xmlNode = doc.SelectSingleNode("//ns:" + field.Key, namespaces);
if (xmlNode == null)
{
xmlNode = doc.CreateNode(XmlNodeType.Element, field.Key,
doc.DocumentElement.NamespaceURI);
doc.DocumentElement.AppendChild(xmlNode);
}
//Namespace any Html in the field
string fieldValue = HtmlTidy.Tidy(field.Value);
xmlNode.InnerXml = fieldValue;
}
component.Content = doc.OuterXml;
//This line throws a FaultException<CoreServiceException> with an
//XmlException from tridion
client.Save(component, null);
Here's the message from Tridion:
The element 'Content' in namespace
'uuid:09ed2feb-f7cb-4760-ba4c-b9ff4f45d025' has invalid child element
'summary' in namespace 'uuid:09ed2feb-f7cb-4760-ba4c-b9ff4f45d025'.
List of possible elements expected: 'related_links' in namespace
'uuid:09ed2feb-f7cb-4760-ba4c-b9ff4f45d025'
I know summary is a valid field for the schema of this component.
It seems like the schema is strict and cares about the order of the fields within the Xml. Is there any way around this or another approach?
Unfortunately you will have to add all mandatory fields in the correct order. The schema does indeed define the elements as an ordered sequence. You could try iterating the fields of the schema, and then selecting them from the spreadsheet rather than the approach you are currently using.
By default, the order is indeed important (uses xsd:sequence).
You could update the Schema to not care about the order (e.g. using xsd:all instead) but it will likely result in the Schema becoming an XSD Schema (so you would lose the ability to edit them using a GUI).
What you need to do is make sure you insert them in the right place.
So you need to loop through componentFromSpreadsheet in the right order - which most likely means you need a separate variable for the order, or you need to use different data type than Dictionary.