Select SVG paths of group by group id using lxml - python

I am having trouble selecting a particular set of paths using lxml. The SVG structure looks like this
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Created with matplotlib (http://matplotlib.org/) -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="288pt" version="1.1" viewBox="0 0 432 288" width="432pt">
<defs>
<style type="text/css">
*{stroke-linecap:butt;stroke-linejoin:round;}
</style>
</defs>
<g id="figure_1">
<g id="patch_1">
<path d=" M0 288 L432 288 L432 0 L0 0 z " style="fill:#ffffff;"/>
</g>
<g id="patch_2">
<path d=" M0 288 L432 288 L432 0 L0 0 z " style="fill:#ffffff;"/>
</g>
<g id="axes_1">
<g id="Poly3DCollection_1">
<path clip-path="url(#pe61355d493)" d=" M195.211 34.2225 L194.801 34.0894 L196.527 212.986 L196.909 212.999 z " style="fill:#0000ff;"/>
<path clip-path="url(#pe61355d493)" d=" M195.504 34.3231 L195.211 34.2225 L196.909 212.999 L197.184 213.022 z " style="fill:#0000ff;"/>
...
Its the paths listed at the bottom that I want to select and change their styles but I can't seem to get the syntax right and I fail to select the paths
ifilename = "myfig.svg"
with open( ifilename, 'r') as infile:
tree = etree.parse( infile )
elements = tree.findall(".//g[#id='Poly3DCollection_1'")
new_style = 'stroke-width:4px; stroke: linear-gradient(orange, darkblue)'
for child in elements:
child.attrib['style'] = new_style
mod_svg = 'myfigmod.svg'
tree.write(mod_svg)
EDIT
so this gets me the element I want in this instance but I would still like a specific way of getting this element
root = tree.getroot()
for child in root[1][2][0]:
child.attrib['style'] = new_style

There is no get_element_by_id in etree, so you have to use xpath, like you are doing to grab the element. I created your file and ran the code below and was able to change the style of the group.
element = tree.findall(".//{%s}g[#id='Poly3DCollection_1']" % SVG_NS)[0]
element.attrib["style"] = new_style

Related

Python SVG remove all tags with specific id attribute

I have an SVG image with Model and Group as ID attribute of tag g, like
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="1141.5200729370117" version="1.1" width="1726.9000701904297" id="temp" viewBox="0 0 1726.9000701904297 1141.5200729370117">
<defs/>
<g id="Model" class="Model A">
<g class="Group">
<g id="Group-1" class="Group A">
<g id="Line" fill="#000000" stroke="#000000" style="fill-opacity: 1; stroke-opacity: 1; stroke-width: 0.2;" class="Line External"><polygon points="118.00,21.56 1293.38,21.56 1277.38,37.56 133.97,37.56 "/> </g>
<g id="Box" fill="#FFFFFF" stroke="#FFFFFF" style="fill-opacity: 1; stroke-opacity: 1; stroke-width: 0.2;" class="Box External"><polygon points="118.00,21.56 1293.38,21.56 1277.38,37.56 133.97,37.56 "/> </g>
</g>
</g>
</g>
</svg>
I want to remove all g tags which have id other than Line. I read the SVG file using:
import xml.etree.ElementTree as ET
root = ET.parse('my file.svg')
SVG_NS = "http://www.w3.org/2000/svg"
Then I create a parent map
parent_map = {c:p for p in root.iter() for c in p}
And remove those other than Line
for node in root.findall('.//{%s}g' % SVG_NS):
name = node.get('id')
if "Line" in str(name):
print('n=', node)
else:
parent_map[node].remove(node)
How do I convert back the parent_map to an SVG and save it as a png file?

python: parsing certain values from svg file

I have a svg file like the following (en example)
<svg
<g class="displacy-arrow">
<path class="displacy-arc" id="arrow-ec55d4518d3c43e391ffce0b97c713ab-0-2" stroke-width="2px" d="M420,89.5 C420,2.0 575.0,2.0 575.0,89.5" fill="none" stroke="currentColor"/>
<text dy="1.25em" style="font-size: 0.8em; letter-spacing: 1px">
<textPath xlink:href="#arrow-ec55d4518d3c43e391ffce0b97c713ab-0-2" class="displacy-label" startOffset="50%" side="left" fill="currentColor" text-anchor="middle">pd</textPath>
</text>
<path class="displacy-arrowhead" d="M575.0,91.5 L583.0,79.5 567.0,79.5" fill="currentColor"/>
</g>
</svg>
I have tried to access the what is inside the 'textpath' node using the code below:
import xml.dom.minidom
doc = xml.dom.minidom.parse('my_file.svg')
name = doc.getElementsByTagName('textPath')
for t in name:
print([x.nodeValue for x in t.childNodes])
I would however like to get the other information included in the 'textpath', like the values for 'side' or 'fill', but I do not know how to access those.
just for future reference, I wrote a function based on the links that #Aswath has sent in the comments
from bs4 import BeautifulSoup
def extract_data_from_report3(filename):
soup = BeautifulSoup(open(filename), "html.parser")
for element in soup.find_all('textpath'):
print(element.get('side'))
extract_data_from_report3('my_file.svg')

PyQt: Manipulating svg files for QGraphicsSvgItem

My main objective is to change stroke width of a given svg file, but I see no inbuilt method to do so.
The width's are unfortunately hardcoded in the svg file, and my initial thought was to buffer the data, and edit it in memory before changing the rendered svg file. But that is just additional processing overhead.
Next, I looked into converting the svg to a dom - tree like structure, but then I might have to save it back again before loading it in the QGraphicsSvgItem, which would be a bizarre intermediate step for changing line thickness on the fly.
Here is an example svg file,
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="3.2421772mm"
height="6.1269817mm"
viewBox="0 0 3.2421772 6.1269817"
version="1.1"
id="svg12403"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="Bag.svg">
<defs
id="defs12397" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="-229.58734"
inkscape:cy="-219.85002"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1366"
inkscape:window-height="705"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1" />
<metadata
id="metadata12400">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-43.736054,-84.5377)">
<g
id="use26359"
transform="matrix(0.26458333,0,0,0.26458333,-3.9887501,73.928193)">
<desc
id="desc12960">Bag</desc>
<title
id="title12962">Bag</title>
<path
d="m 180.61365,49.4094 v 13.609494 h 11.78107 V 49.4094 C 192.27,46.875236 189.67,44.872517 186.50494,44.872517 c -3.16454,0 -5.76448,2.002719 -5.89129,4.536883 z m 2.94488,-9.07316 2.94641,4.536277 2.9449,-4.536277 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.66862381"
id="path12964"
inkscape:connector-curvature="0" />
<g
style="stroke-width:1.31504369"
id="g12968"
transform="matrix(0.507302,0,0,0.50958516,89.699529,-958.6518)">
<path
d="m 179.211,1978.199 v 26.707 h 23.223 v -26.707 c -0.246,-4.972 -5.371,-8.902 -11.61,-8.902 -6.238,0 -11.363,3.93 -11.613,8.902 z m 5.805,-17.804 5.808,8.902 5.805,-8.902 z m 5.808,0 v 8.902"
style="fill:none;stroke:#000000;stroke-width:0.93203717;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path12966"
inkscape:connector-curvature="0" />
</g>
</g>
</g>
</svg>
As you can see, there is a stroke width value in the style attribute for the path.
Is there a simple less obnoxious "hack" way of achieving this?

Regex replacement line by line in Python 3

I have written some code to try and do the following
Open SVG file I have already retrieved earlier in the Python code
Look through the file for a particular regex (r"(?:xlink:href\=\")(.*)(?:\?q=80\"/>)")
If the match is found, replace the text with a particular string e.g.
https://regex101.com/r/IECsHu/1
Then retrieve the JPG from the url which is matched (see above link to regex101.com)
However, this does not work and completely blanks the file out (so it is 0 bytes)
I think I must be very close to getting this working, but haven't managed it yet. Any guidance would be appreciated
pagenumber=1
directory_in_str='/home/somewhere/somedir/'
pathlist = Path(directory_in_str).glob('**/*.svg')
for path in pathlist:
#because path is object not string
path_in_str = str(path)
print(path_in_str)
with open(path_in_str, 'r+') as f:
for line in f:
myregex = r"(?:xlink:href\=\")(.*)(?:\?q=80\"\/\>)"
result = myregex.search(line)
if result:
#If a match is found, replace the text in the line
origfullimgfilename = result
formattedpagenumber = '{:0>3}'.format(pagenumber)
replfullimgfilename='page-'+str(formattedpagenumber)+'-img1.jpg'
line = re.sub(origfullimgfilename, replfullimgfilename, line.rstrip())
#Then retrieve the file! (origfullimgfilename)
try:
urllib.request.urlretrieve(origfullimgfilename+"?q=100", replfullimgfilename)
except urllib.error.HTTPError as e:
print("HTTP Error: "+str(e.code)+" - SVG URL: "+str(origfullimgfilename)+" - aborting\n")
break
pagenumber += 1
lines = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Page 1 -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" width="1198" height="1576" viewBox="0 0 1198 1576" version="1.1" style="display: block;margin-left: auto;margin-right: auto;">
<path d="M0,0 L0,1576 L1198,1576 L1198,0 Z " fill="#FFFFFF" stroke="none"/>
<image preserveAspectRatio="none" x="0" y="0" width="1199" height="1577" xlink:href="https://cdn-assets.somewhere.com/f929e7b4404d3e48918cdc0ecd4efbc9fa91dab5_2734/9c237c7e35efe88878f9e5f7a3555f7a379ed9ee9d95b491b6d0003fd80afc6b/9c68a28d6b5161f7e975a163ff093a81172916e23c778068c6bfefcfab195154.jpg?q=80"/>
<g fill="#112449">
<use xlink:href="#f0_2" transform="matrix(19.1,0,0,19.1,885.3,204.3)"/>
<use xlink:href="#f0_o" transform="matrix(19.1,0,0,19.1,910,204.3)"/>
<use xlink:href="#f0_t" transform="matrix(19.1,0,0,19.1,930.3,204.3)"/>
<use xlink:href="#f0_f" transform="matrix(19.1,0,0,19.1,949.6,204.3)"/>"""
import re
newlines = []
for line in lines.split('\n'):
myregex = re.compile(r"(?:xlink:href\=\")(.*)(?:\?q=80\"\/\>)")
result = myregex.search(line)
if result:
print(result)
#If a match is found, replace the text in the line
origfullimgfilename = result.group(1)
replfullimgfilename='page-##-img1.jpg'
line = re.sub(origfullimgfilename, replfullimgfilename, line)
newlines.append(line)
print( '\n'.join(newlines) )

Change attributes from defs elements for different use elements

I have a SVG with 2 circles. In fact, they use just a single "def" called s1. How can I change attributes from just one circle (use). For example I want to set a different class to the element s1 when using by an specific "use" element.
<svg viewBox = "0 0 1000 1000" version = "1.1">
<defs>
<!-- A circle of radius 200 -->
<circle id = "s1" cx = "200" cy = "200" r = "200" fill = "yellow" stroke = "black" stroke-width = "3"/>
</defs>
<use x = "100" y = "100" xlink:href = " #s1 "/>
<use x = "100" y = "650" xlink:href = " #s1 "/>
Thanks in advance.
You can't change element's specific atributes (cx,cy,r), but you can change all atributes on this list using <set ... >.
For example "opacity". Here is how to change it for your circles (hint: if you open this svg with opera or chrome, put cursor over third circle):
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg viewBox="0 0 1000 1000" version="1.1" height="1000px" width="1000px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" >
<g>
<rect y="0" width="1000" fill="blue" x="0" height="1000" />
<defs>
<circle id = "s1" cx = "200" cy = "200" r = "200" fill = "yellow" stroke = "black" stroke-width = "3"/>
</defs>
<use id = "one" x = "100" y = "100" xlink:href = "#s1"/>
<use id = "two" x = "100" y = "500" xlink:href = "#s1"/>
<use id = "three" x = "500" y = "100" xlink:href = "#s1">
<set attributeName="opacity" from="1" to="0.7" begin="mouseover" end="mouseout"/>
</use>
<set xlink:href="#two" attributeName="opacity" from="1" to="0.2" begin="three.mouseover" end="three.mouseout"/>
<set xlink:href="#one" attributeName="opacity" from="1" to="0.4"/>
</g>
</svg>
I hope this helps.

Categories