Python: specifying the namespace in an lxml.etree path - python

I'm trying to figure out how to access a specific element by id in an SVG file. I was using the python library of lxml to parse through the file, but it always comes up empty. Here is the python script I used to access the element:
#!/usr/bin/env python
from lxml import etree
XHTML_NAMESPACE = "http://www.w3.org/2000/svg"
XHTML = "{%s}" % XHTML_NAMESPACE
NSMAP = {None : XHTML_NAMESPACE}
root = etree.parse("temp.svg")
textid = "text1274"
path = ".//text[#id='" + textid + "']/title"
name = root.findtext(path=path, namespaces=NSMAP)
print name
The result is always an empty string ('None'), but no error. It believes it found what I was looking for, but what I wanted was the element text (which should have been "Wei, 771 - 661BCE."). Here is the incriminating SVG file:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
xml:space="preserve"
viewBox="0 0 54001 32400"
id="svg2"
inkscape:version="0.91 r"
sodipodi:docname="china700BC.svg"><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1366"
inkscape:window-height="692"
id="namedview2468"
showgrid="false"
inkscape:zoom="0.016419753"
inkscape:cx="17689.896"
inkscape:cy="17739.986"
inkscape:window-x="0"
inkscape:window-y="24"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<defs
id="defs4">
<filter
id="blur2">
<feGaussianBlur
id="feGaussianBlur7"
result="blur"
stdDeviation="2"
in="SourceGraphic" />
</filter>
<filter
id="blur4">
<feGaussianBlur
id="feGaussianBlur10"
result="blur"
stdDeviation="4"
in="SourceGraphic" />
</filter>
<filter
id="blur8">
<feGaussianBlur
id="feGaussianBlur13"
result="blur"
stdDeviation="8"
in="SourceGraphic" />
</filter>
<filter
id="blur16">
<feGaussianBlur
id="feGaussianBlur16"
result="blur"
stdDeviation="16"
in="SourceGraphic" />
</filter>
<filter
id="blur32">
<feGaussianBlur
id="feGaussianBlur19"
result="blur"
stdDeviation="32"
in="SourceGraphic" />
</filter>
<filter
id="blur64">
<feGaussianBlur
id="feGaussianBlur22"
result="blur"
stdDeviation="64"
in="SourceGraphic" />
</filter>
</defs>
>
<g
stroke-linecap="round"
stroke-linejoin="round"
stroke-miterlimit="7"
stroke-width="14"
fill="none"
filter="url(#blur2)"
id="fntr">
<ellipse
id="ellipse381"
fill="white"
stroke="white"
ry="1"
rx="1"
cy="0"
cx="0" />
<ellipse
id="ellipse383"
fill="white"
stroke="white"
ry="1"
rx="1"
cy="32400"
cx="54001" />
<ellipse
fill="#FEBADE"
ry="1"
rx="1"
cy="24759"
cx="48948"
id="295286-dummy" />
</g>
<g
text-anchor="middle"
id="regn">
</g>
<g
text-anchor="middle"
id="cultr">
</g>
<g
text-anchor="middle"
id="peopl">
</g>
<g
font-style="italic"
text-anchor="middle"
id="tribe">
</g>
<text
id="text455"
x="30542.088"
y="16248.173"
font-size="20"
style="font-weight:normal;font-size:233.01080322px;text-anchor:middle"><title
id="title457">Chen.</title>Chen</text>
<text
id="text1274"
x="28689.652"
y="12753.011"
font-size="28"
style="font-weight:normal;font-size:326.21511841px;text-anchor:middle"><title
id="title1276">Wei, 771 - 661BCE.</title>Wei</text>
<script
id="script2466">
function LoadHandler(event)
{
new Title(event.getTarget().getOwnerDocument(), 810);
}
</script>
</svg>
I discovered that I can eliminate the error by deleting the eighth line, beginning with "xmlns=..." (which is the namespace declaration). However, due to the nature of where I obtained this file I cannot permanently remove this line (and probably shouldn't). Is there some way (such as properly specifying the namespace) I can get the expected output without having to edit the XML at all?
Thanks a ton

Mapping default namespace to None prefix didn't work for me either. You can, however, map it to a normal string prefix and use that prefix in the xpath, the rest of your codes are working without any change :
from lxml import etree
XHTML_NAMESPACE = "http://www.w3.org/2000/svg"
XHTML = "{%s}" % XHTML_NAMESPACE
NSMAP = {'d' : XHTML_NAMESPACE} # map default namespace to prefix 'd:'
root = etree.parse("temp.svg")
textid = "text1274"
path = ".//d:text[#id='" + textid + "']/d:title" # use registered prefix in xpath
name = root.findtext(path=path, namespaces=NSMAP)
print name

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?

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?

Increasing Version in xml file using python script

/* Python Script */
import xml.etree.ElementTree as ET
tree = ET.parse('config.xml')
root = tree.getroot()
updateData = open('config.xml','w+')
print('Root Data is ',root.tag)
print('Root Attribute ',root.attrib)
old_version = root.attrib.values()[0]
print('Old_Version is ',old_version)
def increment_ver(old_version):
old_version = old_version.split('.')
old_version[2] = str(int(old_version[2]) + 1)
print('Old_Version 2 ',old_version[2])
return '.'.join(old_version)
new_Version = increment_ver(old_version);
print('New_version :',new_Version,root.attrib['version'])
root.attrib['version'] = new_Version
print(root.attrib)
tree.write(updateData)
updateData.close()
/* Original Config xml file */
<?xml version='1.0' encoding='utf-8'?>
<widget id="io.ionic.starter" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>aman</name>
<description>An awesome Ionic/Cordova app.</description>
<author email="hi#ionicframework.com" href="http://ionicframework.com/">Ionic Framework Team</author>
<content src="index.html" />
<access origin="*" />
<allow-intent href="http://*/*" />
<allow-intent href="https://*/*" />
<allow-intent href="tel:*" />
<allow-intent href="sms:*" />
<allow-intent href="mailto:*" />
<allow-intent href="geo:*" />
<preference name="ScrollEnabled" value="false" />
/* New Config.xml file */
<ns0:widget xmlns:ns0="http://www.w3.org/ns/widgets" xmlns:ns1="http://schemas.android.com/apk/res/android" id="io.ionic.starter" version="0.0.2">
<ns0:name>aman</ns0:name>
<ns0:description>An awesome Ionic/Cordova app.</ns0:description>
<ns0:author email="hi#ionicframework.com" href="http://ionicframework.com/">Ionic Framework Team</ns0:author>
<ns0:content src="index.html" />
<ns0:access origin="*" />
<ns0:allow-intent href="http://*/*" />
<ns0:allow-intent href="https://*/*" />
<ns0:allow-intent href="tel:*" />
<ns0:allow-intent href="sms:*" />
<ns0:allow-intent href="mailto:*" />
Once the script gets executed the version number is increased by 1 which i was trying to achieve. But, ns0 tag is added throughout the file and the header XML info tag gets removed [].
Please let me know what i have done wrong.
Your script slightly modified:
import xml.etree.ElementTree as ET
ET.register_namespace('', 'http://www.w3.org/ns/widgets')
tree = ET.parse('config.xml')
# (...) no changes in this part of code.
tree.write(f, xml_declaration=True, encoding="utf-8")
updateData.close()
The result:
<?xml version='1.0' encoding='utf-8'?>
<widget xmlns="http://www.w3.org/ns/widgets" id="io.ionic.starter" version="0.0.2">
<name>aman</name>
<description>An awesome Ionic/Cordova app.</description>
<author email="hi#ionicframework.com" href="http://ionicframework.com/">Ionic Framework Team</author>
<content src="index.html" />
<access origin="*" />
<allow-intent href="http://*/*" />
<allow-intent href="https://*/*" />
<allow-intent href="tel:*" />
<allow-intent href="sms:*" />
<allow-intent href="mailto:*" />
<allow-intent href="geo:*" />
<preference name="ScrollEnabled" value="false" />
</widget>
One of the namespace declarations has been dropped because it was not used in the XML body.
If you want to preserve namespaces use lxml library. In this case, your code would look like this (notice no ET.register_namespace):
import lxml.etree as ET
tree = ET.parse('config.xml')
root = tree.getroot()
updateData = open('config.xml','w+')
# (...) no changes in this part of code.
tree.write(f, xml_declaration=True, encoding="utf-8")
updateData.close()
In this case the output:
<?xml version='1.0' encoding='UTF-8'?>
<widget xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0" id="io.ionic.starter" version="0.0.2">
<name>aman</name>
<description>An awesome Ionic/Cordova app.</description>
<author email="hi#ionicframework.com" href="http://ionicframework.com/">Ionic Framework Team</author>
<content src="index.html"/>
<access origin="*"/>
<allow-intent href="http://*/*"/>
<allow-intent href="https://*/*"/>
<allow-intent href="tel:*"/>
<allow-intent href="sms:*"/>
<allow-intent href="mailto:*"/>
<allow-intent href="geo:*"/>
<preference name="ScrollEnabled" value="false"/>
</widget>

How can I preserve whitespaces with python 2.7 lxml?

I have a huge xml file (thousands of lines) and I need to change some attribute parameters.
Xml looks like this:
<person id="name" name="pers_name">
<group id="Common">
<emotion id="smile">
<texture texture="smile" x="-131" y="-17" />
<effect name="name1" x="51" y="438" />
<effect name="name2" x="61" y="419" />
<effect name="name3" x="55" y="312" />
</emotion>
</group>
</person>
After I did it and wrote it with tree.write(path, encoding='utf-8', xml_declaration=True) I lost whitespaces before closing tag.
How can I preserve it?
<person id="name" name="pers_name">
<group id="Common">
<emotion id="smile">
<texture texture="smile" x="-131" y="-17"/>
<effect name="name1" x="51" y="438"/>
<effect name="name2" x="61" y="419"/>
<effect name="name3" x="55" y="312"/>
</emotion>
</group>
</person>
Code
from lxml import etree
# Offsets
x_offset = -10
y_offset = -20
tree = etree.parse(path)
XML = tree.getroot()
for effect in XML.iter('effect'):
texture_offset_x = int(effect.get('texture_offset_x')) + x_offset
texture_offset_y = int(effect.get('texture_offset_y')) + y_offset
effect.set('texture_offset_x', str(texture_offset_x))
effect.set('texture_offset_y', str(texture_offset_y))
tree.write(path, encoding='utf-8', xml_declaration=True)

SVG file. remove element

I'm trying to remove element with id "area_3". I used something like:
for node in tree.xpath('//ellipse'):
node.getparent().remove(node)
SVG example:
<svg width="600" height="600" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Created with Method Draw - http://github.com/duopixel/Method-Draw/ -->
<g>
<title>Layer 1</title>
<image id="svg_1" y="0" x="0"/>
<image stroke="null" xlink:href="tehplan.jpg" id="svg_5" height="587.777769" width="585.333339" y="0.578137" x="20.083334"/>
<ellipse ry="19" rx="18" id="area_2" cy="172" cx="189" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke="#000000" fill="#ffffff"/>
<ellipse id="area_3" ry="19" rx="18" cy="161" cx="275" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke="#000000" fill="#ffffff"/>
</g>
</svg>
Try this:
from lxml import etree
tree = etree.parse(open("so.svg"))
to_remove = tree.xpath("/svg:svg/svg:g/svg:ellipse[#id=\"area_3\"]",
namespaces={"svg": "http://www.w3.org/2000/svg"})[0]
g = to_remove.getparent()
g.remove(to_remove)
with open("so.out.svg", "wb") as o:
o.write(etree.tostring(tree, pretty_print=True))
Output:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="600" height="600">
<!-- Created with Method Draw - http://github.com/duopixel/Method-Draw/ -->
<g>
<title>Layer 1</title>
<image id="svg_1" y="0" x="0"/>
<image stroke="null" xlink:href="tehplan.jpg" id="svg_5" height="587.777769" width="585.333339" y="0.578137" x="20.083334"/>
<ellipse ry="19" rx="18" id="area_2" cy="172" cx="189" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke="#000000" fill="#ffffff"/>
</g>
</svg>

Categories