dave-howard / vsdx Goto Github PK
View Code? Open in Web Editor NEWvsdx - A python library for processing .vsdx files
License: BSD 3-Clause "New" or "Revised" License
vsdx - A python library for processing .vsdx files
License: BSD 3-Clause "New" or "Revised" License
Congrats for the library.
I was excited to modify my vsdx from python, but I found this roadblock:
My vsdx file has shapes displaying text fields linked to data properties.
I can modify the data properties using the vsdx library but, when I open the vsdx file, I find that the data from the data properties have not been refreshed in the text field, showing still the old data.
Is there a way to trigger a recalculation of the text fields for the whole vsdx file? Or to update the text fields at the same time as the data properties?
Thanks
I kindly ask for help in finding a workaround for the following error. Thank you.
test8_simple_connector.vsdx
Traceback (most recent call last):
c2 = Connect.create(page=new_page, from_shape=new_shape2, to_shape=new_shape3) # ERROR
rels = page.rels_xml.getroot()
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'getroot'
import shutil
from vsdx import VisioFile, Connect
src_file = 'test8_simple_connector.vsdx'
copy_src = shutil.copy(src_file, 'tmp_src.vsdx')
with VisioFile(copy_src) as vis:
src_page = vis.pages[0]
block_shape = src_page.child_shapes[0]
new_page = vis.add_page('new page')
# 1
# adding new_shape1, new_shape2 and connecting them
new_shape1 = block_shape.copy(new_page)
new_shape1.text = 'new shape 1'
new_shape2 = block_shape.copy(new_page)
new_shape2.text = 'new shape 2'
c1 = Connect.create(page=new_page, from_shape=new_shape1, to_shape=new_shape2)
vis.save_vsdx('after 1.vsdx')
# 2
# adding new_shape3 and connecting it to new_shape2
new_shape3 = block_shape.copy(new_page)
new_shape3.text = 'new shape 3'
c2 = Connect.create(page=new_page, from_shape=new_shape2, to_shape=new_shape3) # ERROR
vis.save_vsdx('test.vsdx')
Is it possible to use this to save matplotlib figures in vsdx format? I'm trying to convert some of my existing matplotlib graphics to vsdx and it's kind of hard to transfer and rechart all the data, so I'm wondering if there's a quicker way.
the function is_master_shape(self) as part of the Shape class is not working correctly, the issues seems to be with the function
def is_master_page(self):
return type(self.vis.masters_xml)==ET.Element and self.vis.masters_xml.find(f'{namespace}Master[@id="{self.page_id}"]/{namespace}PageSheet') is not None
in the Page class: it uses the Page ID to test wether a Master exists with the same ID.
I don't think that a page ID and a master ID share the same ID "space" and are unique between them. I am working on a VSDX file in which a Page and a Master have the same ID value.
Hello,
When I try to save a vsdx (with vis.save_vsdx("vsdx_file_name_here") I edited with a python script, I have this error :
Traceback (most recent call last):
File "C:\Spyder\Python\Python310\lib\xml\etree\ElementTree.py", line 762, in _get_writer
write = file_or_filename.write
AttributeError: 'str' object has no attribute 'write'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Users\qbernard\Desktop\PythonTestsScripts\VisioUpdater\script\main.py", line 357, in <module>
vis.save_vsdx('vsdxfile.vsdx')
File "C:\Spyder\Python\Python310\lib\site-packages\vsdx\vsdxfile.py", line 1015, in save_vsdx
xml_to_file(self.pages_xml_rels, f'{self.directory}/visio/pages/_rels/pages.xml.rels')
File "C:\Spyder\Python\Python310\lib\site-packages\vsdx\vsdxfile.py", line 49, in xml_to_file
xml.write(filename, xml_declaration=True, method='xml', encoding='UTF-8')
File "C:\Spyder\Python\Python310\lib\xml\etree\ElementTree.py", line 732, in write
with _get_writer(file_or_filename, enc_lower) as write:
File "C:\Spyder\Python\Python310\lib\contextlib.py", line 135, in __enter__
return next(self.gen)
File "C:\Spyder\Python\Python310\lib\xml\etree\ElementTree.py", line 768, in _get_writer
file = open(file_or_filename, "w", encoding=encoding,
FileNotFoundError: [Errno 2] No such file or directory: 'C:\\Users\\qbernard\\Desktop\\PythonTestsScripts\\VisioUpdater\\script\\MVS_BASELINE20220927/visio/pages/_rels/pages.xml.rels'
Right now I am not sure why the "file is not found", since when I open my vsdx as a zip, in /MVS_BASELINE20220927/vision/pages/_rels, i find the pages.xml.rels. I was guessing it is because of the \ and / in the path, but I prefer to ask here (maybe I did a stupid error and I did not see it...)
my main section of code :
if __name__ == '__main__' :
"""
"""
pathVis = 'C:/Users/qbernard/Desktop/PythonTestsScripts/VisioUpdater/script/MVS_BASELINE20220927.vsdx'
vis,pathVis = openVis(pathVis = pathVis)
extractToDatabase(vis = vis, pathVis = pathVis)
dataset = restruct(database)
dataset = keySave(dataset)
dataset_001 = AdvQuery(dataset, "ID", ["VAR"])
dataset_001 = AdvQuery(dataset_001, "Nom ADL1", ["Nom ADL1"]) # Testing Purposes
batchRewrite(dataset_001, "Nom ADL1", "Nom_ADL1") # Testing Purposes
print("DEBUG : Importing to Visio")
importDatabaseToVis(database, vis = vis, pathVis = pathVis)
pathVis02 = vsdxSaveAs() # this open a save as window, we overwrite the vsdx file.
vis.save_vsdx('pathVis02')
Any guess where this error can come from ?
Yours,
Quentin
To implement Element.find() and Element.findall() to simplify the codebase.
when there are many shapes and many masters then all structure is parsed multiple time
and it takes minutes to load such a file
Hi,
I'm currently trying to extract some location information for each shape in a visio file. However, I noticed that sometimes these attributes are not mapped to the Shape class and they aren't in the page's xml either.
Is it normal for certain attributes to not appear in the XML at all? For example, I'm trying to detect when a shape is rotated by 90 degrees. The visio file has some default 'System' shapes and I want to get the rotated shape created by that default quick shape in addition to any other that are at an angle. That angle attribute is not shown anywhere though.
I am trying to do a project and I have more than a few questions. I am trying to create a Visio diagram from a spec and want to automate that process.
The main issue is that I have is that when I resize a shape, the outline is not resized.
Hi @dave-howard, first to all, I would like to say thanks for this library, it became very helpful in our daily tasks.
We are trying to difference between shapes that has the same master name, as we can see, the only way to do that is to use the BaseID from Masters XML Part but the library doesn't expose that attribute, could be posible to update it with that new attribute?
Thanks for your great work.
Kind regards,
Juan.
if a shape has master_page_ID, which cannot be found in the current vsdx file, the shapes' text method tries to access its master's text without actually checking if it has an accessable master.
Breaks here:
Line 563 in ff82f8f
Easiest fix is probably to rather check is master_shape is None.
I have not yet found out why this happens, but it seems to be happening when pasting nested shapes in from other files.
The API has evolved a bit organically and as such is not currently very consistent.
Most methods today are not marked as private (not underscore) but many are not really aimed at end users of the package.
If we think about how each object is likely to be used we are likely to be able to a inituitive experience for user/developers.
For example - being consistent with how a user finds, moves, copies, removes a Shape or a Page
Ideally we should have a well defined API before we move to v1.0.0 release so we can avoiding breaking changes later.
I would like to be able to have a tag like {% move_to_page <string> %}
where is the page_name
Maybe needs some thought about the definition above, possibly add a {% move_to_page_index <int> %}
also
Where this tag exists in shape or group shape text, the shape should be moved to page identified by page name /index
Same logic for copy methods which would leave the original shape in place
the ability to find a shape by it's data properties, either property name, label, or value would have advantages over:
Shape Data would be a good way to identify shapes that require some action performed on them.
methods may look like:
the first has the advantage of not having to look beyond the property row, the value requires finding cell element, but this should be a very minimal overhead
each of the methods may be supplimented with a page.find_shapes_by_xxx that returns a list of matching shapes
Hello!
after copy_page and save_vsdx new file do not open in Visio: internal Visio error #3400
i use pytest test.py::test_copy_page
and error occurs even with test files
I have an issue, that if i try to delete several pages at once, the deleted pages do not match my selection:
for eg.:
for file in visios:
with VisioFile(file) as vis:
for page in vis.pages:
if not 'LAN' in page.name and not 'Background' in page.name:
vis.remove_page_by_index(page.index_num)
vis.save_vsdx(file)
and then then there are still pages, not containing LAN or Background in the file name, present.
Especially the first page (by appearance and not by index_num) never gets deleted.
ah and by the way:
Is there something like a shape.remove() method?
I am maybe missing something but it looks like the library does not parse the Section and Row elements in pages and maybe other types of elements.
As far as I can tell, only Shapes and Cells are currently supported.
EDIT: I was in fact missing a lot of things, looking at the code, it looks like this issue does not exist as everything is read in the shape object
Hello,
First of all, thank you for creating this package. You've potentially saved me from having to write vba macros for automating Visio processes for my current job.
That being said, I seem to be having trouble accurately moving and resizing shapes on a page using the "height", "width", "x" and "y" attributes.
Here is a sample of what I'm experiencing:
`with VisioFile('test.vsdx') as vis:
page = vis.get_page_by_name('box')
box = [x for x in page.all_shapes if x.shape_name == 'box'][0]
text = [x for x in page.all_shapes if x.shape_name == 'text'][0]
box.x = 2.5
box.y = 2
box.height = 1
box.width = 3
text.text = "NOW A RECTANGLE"
text.x = 5.5
text.y = 4
text.height = 0.5
text.width = 5
vis.save_vsdx('test_new.vsdx')`
On the second image, I'm expecting the center of the blue rectangle to be updated to (2.5, 2), but instead it appears to be centered somewhere around (2, 0.5).
Is there another way to achieve this?
I'm using Microsoft Visio Professional 2016
Optimize the Code for read only scenarios where only necessary properties are parsed.
For example, a decent sized VSDX file with Shapes parsing along with Geometry, Control will take around 9 secs.
By opting out these params it reduces to 1.3 secs.
Hello,
below is my code
from vsdx import VisioFile,Shape,vsdxfile,PagePosition,Page
with VisioFile('test1.vsdx') as vis:
page1=vis.add_page(name="NewlyAdded")
page1.width=100 # Getting error for this two lines
page1.height=200
page = vis.pages[0]
new_page=vis.copy_page(page,index=PagePosition.AFTER,name="Demo")
new_page.width=100 # Getting error for this two lines also
new_page.height=200
vis.save_vsdx('copy_of_diagram11.vsdx') # save to a new file```
I am getting below error
C:\Users\abc\AppData\Local\Programs\Python\Python312\Lib\site-packages\vsdx\pages.py", line 123, in width
self._pagesheet_xml.find(f'{namespace}Cell[@N="PageWidth"]').attrib['V'] = str(value)
^^^^^^^^^^^^^^^^^^^
File "C:\Users\abc\AppData\Local\Programs\Python\Python312\Lib\site-packages\vsdx\pages.py", line 113, in _pagesheet_xml
ps = self.vis.masters_xml.find(f'{namespace}Master[@ID="{self.page_id}"]/{namespace}PageSheet')
^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'list' object has no attribute 'find'
@dave-howard Can you please check and solve this bug.
@dave-howard
I have been looking for a way to connect two shapes together by using the glue feature on Visio. I was wondering if there is anyway to do that in your library. I saw the connection option and when I try doing that to a bunch of shapes it creates a bunch of triangles. Any suggestions?
Thank you,
Andrew
From what I gathered, VisioFile.page_xml_by_file_path is not exactly being used anywhere except for VisioFile.set_page_max_id(), where a for loop can replace the functionality.
I did a quick test and removed the references to it, and things seemed to continue working as expected.
Is there a use case that I am missing? Or may I open a PR?
I think that if it is not in use, removing it will remove something that a contributor would need to keep in mind or worry about.
I am trying to connect 2 shape objects , but I did not find any way to connect two shapes with a dynamic connector object.
Can you please introduce this feature soon.
Hi Dave,
Is my understanding from the shapes.py code correctly that you currently do not support the reading of multiple geometry sections defined in a single shape record?
I have come across several examples of such cases.
Geert
thanks for the 3 test vsdx and the well written test file.
May I know how to get the connection information from vsdx?
Hi,
I am quite new to this library.
I do have the task to export the attributes of any shape from a Visio file. For now I do not have found any function within class vsdx.VisioFile.Shape that would allow looping over the attributes.
My code is as follows:
from vsdx import VisioFile
with VisioFile('Test_Prozess_neu.vsdx') as vis:
# open first page
page = vis.get_page(0)
# Read all shapes
shapes = page.shapes[0].sub_shapes()
# Loop over all available shapes
for shape in shapes:
print(shape.text)
print(shape.ID)
# Now I would normally execute the function for exporting all attributes..._
Do I do something wrong or does this function simply not exist (yet)?
Thanks for some support,
Frank
Hi,
How do we change the value of a shape data and save it to a new file?
The following code does not save the new value of the shape data to the new file.
with VisioFile('diagram.vsdx') as vis:
page = vis.pages[0]
code_a_shape = page.find_shape_by_property_label_value('test', 'testvalue')
code_a_shape.data_properties['test'].value = '00'
vis.save_vsdx('shape_changed.vsdx')
For deduplication shapes can be inherited and then overridden at the page level.
Take the following example file:
test4_lines.zip
2 of the shapes are declared (text) in master1 and master2 while 2 are in page1.
I've been looking at how an add_page method would need to be implemented. I wrote a proof of concept and pushed it to my fork, but I believe there's still some decisions to be taken before a PR is opened, and also I don't want to conflict with work related to master pages.
Till now the process seems to be:
I think the major decision to be taken is how to go about adding the entry to pages.xml.
Currently I'm hard-coding the PageSheet element with default values. Another route is to have a template VSDX file in the package and copy+paste the element from that template (this is similar to how python-docx creates new files).
There are a couple of minor decisions as well:
Further enhancements:
Hi,
I am trying to create software architecture diagrams from inventory data. Since it is the main goal to have editable documents, Visio is a suitable solution. it would be great to use vsdx as the library.
What I was not able to find yet, is there a way to create new diagrams from scratch?
I found the description of altering existing files, but was not able to find a description of creating a completely new file, with new shapes.
Hello, I ran into problems with displaying a tree in vsdx.
When opening a file,
firstly: links are displayed incorrectly until you move some figure or select auto-fit on the designer tab.
Secondly: the shapes go beyond the dimensions of the template page, this is also corrected when you click the autofit button on the design tab.
After pressing autosize button, i got good result:
Please tell me how to fix this? How can I achieve this through python? I tried to do it through pure xml but all attempts failed. Also I can't use vsdm :-(
Hi Dave,
The change implemented in #55 added a new bug in my case. In the .csv file, I have a case where the value_cell = None
. This raises an error in DataProperty.__init__(...)
due to it checking value_cell.text
in an if statement.
I suggest adding the following or something similar:
elif value_cell is None:
value = None
So the start of __init__
becomes:
def __init__(self, *, xml: Element, shape: Shape):
"""init a DataProperty from a property xml element in a Shape object"""
name = xml.attrib.get('N')
# get Cell element for each property of DataProperty
label_cell = xml.find(f'{namespace}Cell[@N="Label"]')
value_cell = xml.find(f'{namespace}Cell[@N="Value"]')
value = None
if isinstance(value_cell, Element) and value_cell.attrib.get('V') is not None:
value = value_cell.attrib.get('V') # populate value from V attribute
elif value_cell is None:
value = None
elif value_cell.text:
value = value_cell.text # populate value from element inner text
This might, however introduce new errors I haven't tough of.
Best regards,
Johan Christian Jenssen
Hi,
I want to make a full fledged visio file with several features. Many of the functionalities are not available in this package and there is not any other package to use this. Can you suggest me on this? I have attached the sample file for reference.
STOK-X-BS01 SLD (unlocked).zip
Hi!
I like to open a file with an absolute path location.
from vsdx import VisioFile
filename = PROJECT_PATH+'/FILE.vsdx'
with VisioFile(filename) as vis:
shape = vis.pages[0].find_shape_by_text('Hello World')
shape.text.splitlines()[0]
pass
I get then the following Error:
OSError: [Errno 22] Invalid argument: './D:/PROJECT_PATH/FILE/visio/pages/_rels/pages.xml.rels'
OSError Traceback (most recent call last)
<ipython-input-13-0745db2bd44b> in <module>
9 print(filename)
10 print(directory)
---> 11 with VisioFile(filename) as vis:
12 # with VisioFile(filename):
13 # shape = vis.pages[0].find_shape_by_text('Hello World')
~\AppData\Roaming\Python\Python39\site-packages\vsdx\__init__.py in __init__(self, filename, debug)
67 self.pages = list() # type: List[VisioFile.Page] # list of Page objects, populated by open_vsdx_file()
68 self.master_pages = list() # type: List[VisioFile.Page] # list of Page objects, populated by open_vsdx_file()
---> 69 self.open_vsdx_file()
70
71 def __enter__(self):
~\AppData\Roaming\Python\Python39\site-packages\vsdx\__init__.py in open_vsdx_file(self)
87
88 # load each page file into an ElementTree object
---> 89 self.load_pages()
90 self.load_master_pages()
91
~\AppData\Roaming\Python\Python39\site-packages\vsdx\__init__.py in load_pages(self)
100
101 rel_filename = rel_dir + 'pages.xml.rels'
--> 102 rels = file_to_xml(rel_filename).getroot() # rels contains page filenames
103 self.pages_xml_rels = file_to_xml(rel_filename) # store pages.xml.rels so pages can be added or removed
104 if self.debug:
~\AppData\Roaming\Python\Python39\site-packages\vsdx\__init__.py in file_to_xml(filename)
1398 """Import a file as an ElementTree"""
1399 try:
-> 1400 tree = ET.parse(filename)
1401 return tree
1402 except FileNotFoundError:
C:\Program Files\Python39\lib\xml\etree\ElementTree.py in parse(source, parser)
1227 """
1228 tree = ElementTree()
-> 1229 tree.parse(source, parser)
1230 return tree
1231
C:\Program Files\Python39\lib\xml\etree\ElementTree.py in parse(self, source, parser)
567 close_source = False
568 if not hasattr(source, "read"):
--> 569 source = open(source, "rb")
570 close_source = True
571 try:
Can you help me? :-)
Have a nice day and thank you for this great library!
Hello Dave,
I noticed that the data_properties method does not return everything in the shape properties.
I also found why. Sometimes there is no cell.attrib["V"] but there is a cell.text, like this :
<Section N='Property'>
<Row N='Row_2'>
<Cell N='Value' V='Placeholder' U='STR'/>
<Cell N='LangID' V='en-IN'/>
</Row>
<Row N='Row_3'>
<Cell N='Value' V='Placeholder' U='STR'/>
<Cell N='LangID' V='en-IN'/>
</Row>
<Row N='Row_5'>
<Cell N='Value' V='Placeholder' U='STR'/>
</Row>
<Row N='Row_6'>
<Cell N='Value' U='STR'>Placeholder but not in Value
</Cell>
<Cell N='LangID' V='en-IN'/>
</Row>
</Section>
This exception is not taken in account in your code :
def __init__(self, *, xml: Element, shape: Shape):
"""Represents a single Data Property item associated with a Shape object"""
name = xml.attrib.get('N')
# get Cell element for each property of DataProperty
label_cell = xml.find(f'{namespace}Cell[@N="Label"]')
value_cell = xml.find(f'{namespace}Cell[@N="Value"]')
value = value_cell.attrib.get('V') if type(value_cell) is Element else None
The else should be replaced by (in my opinion) :
elif value_cell.text :
value = value_cell.text
else :
value = None
(or maybe something a bit cleaner)
Regards,
Quentin
Hey,
I was playing around with this library (very nice) and I noticed that when I found an object with find_by_text and find_by_data_label they did not show as being equal. I have a million guesses as to why, but figured I would point it out in case if y'all thought it would be a good idea to overwrite the ==
assignment operator.
Code that showed the result shown below, I had a single object with 'Start2' as the text on it an a data property with label: 'One', Name: 'Two', Value: 'Three'
with VisioFile('Test2.vsdx') as vis:
page = vis.get_page(0)
shape = page.find_shape_by_text('Start2')
props = shape.data_properties
for property_label in props.keys():
prop = props[property_label]
print(f"prop: lbl:'{prop.label}' name:'{prop.name}': val:'{prop.value}'")
# print(props)
shape2 = page.find_shape_by_property_label('One')
props2 = shape2.data_properties
for property_label in props2.keys():
prop = props2[property_label]
print(f"prop: lbl:'{prop.label}' name:'{prop.name}': val:'{prop.value}'")
print(shape2 == shape)
Sorry, I probably missed how to do this, but how do you set the fill color of a shape?
Hi @dave-howard,
I need to access the master page info of a Shape in order to access to their attributes (name, master_base_id, etc..) (AWS-complex-stencils-shapes.zip), as I can see on the code, this master shape is retrieve from here:
But then it access the child_shape[0] for retrieve it as master_shape (I don't understand why), in this part I lost track of the page master attributes that I need (name, master_base_id, etc...)
As you can see, the child_shape[0].text attribute is not informed, then when trying to access shape.text, the attribute self.master_shape.text will be empty.
I need to access to the value (AWS Step Functions workflow) in the only shape of the vsdx file, should it be updated how to retrieve the master page info in order to do that?
Thank for your help.
Cordially,
Juan.
Hi, here you check the Name
attribute of a shape against a known value:
Line 490 in 6e98f01
Though, in my German version of Visio it will contain a German translation, see:
<Shape ID="144" NameU="Dynamic connector" Name="Dynamischer Verbinder" Type="Shape" Master="5">...</Shape>
You might want to use the NameU
attribute instead.
This is Visio Standard 2016 if that matters.
The media vsdx file is open with the Media()
call, but never closed in the create()
function. This leaves the unzipped vsdx file lying about, and possibly causes some issues if create()
is called multiple times.
I think it's only a matter of calling media._media_vsdx.close_vsdx()
Hi Mr. Howard,
could you please post a small example of replacing some text in a .vsdx-file?
For example, replacing the text "dog" in a square with "cat".
Best regards
GC
Hi, first of all, thank you for this library, it would be nice to have a - save to pdf option.
thanks
Hello, thank you for this project!
Any tips on how copy_shape() method can be used?
HI,
can we convert the vsd to SVG or create vsd file and export it to the SVG file.
I love using draw io very much,
however to export the graph,
the only support format that could preserve some information is vsdx,
but there are so few vsdx python libraries out there.
Hello!
I try to use 5 loops on one page, but have an error
jinja2.exceptions.TemplateSyntaxError: Unexpected end of template. Jinja was looking for the following tags: 'endfor' or 'else'. The innermost block that needs to be closed is 'for'.
Only one loop statement is work
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.