Inhalt eines Strings als Funktion ausführen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Rigoletto
User
Beiträge: 28
Registriert: Freitag 14. Februar 2014, 21:05

Hallo,

wenn ich in einer xml-Datei python code habe den ich ausführen möchte (mehrere Zeilen, also kein eval) wie mache ich das am besten.
Momentan fällt mir nur temp. speichern und importieren ein. Die erlaubten Befehle filtere ich per whitelist.

greetzs
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Rigoletto: egal ob Du filterst, oder nicht, es ist eine viel zu große Sicherheitslücke, Code einfache so aus externen Quellen auszuführen. Was willst Du eigentlich machen?
Rigoletto
User
Beiträge: 28
Registriert: Freitag 14. Februar 2014, 21:05

Ich habe für InkScape svg-Files ein kleines Packet geschrieben was mir Daten in Vorlagen füllt und Etiketten erstellt.
Nun muss ich noch mit bestimmten Bedingungen prüfen ob Felder gelöscht, Inhalt modifieziert, Bilder ausgeblendet oder ausgetauscht, etc werden müssen.
Das meiste könnte man einfach über eigene bereitgestellte Funktionen machen, aber halt nicht alles.
Wenn es nur für mich wäre würde ich die Funktionen direkt coden, will es aber public machen damit andere auch damit arbeiten oder es erweitern können.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Öh... ja und wieso muss dann der Python Code *in* der SVG-Datei drin stehen? Oder habe ich das falsch verstanden?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Rigoletto
User
Beiträge: 28
Registriert: Freitag 14. Februar 2014, 21:05

Hyperion hat geschrieben:Öh... ja und wieso muss dann der Python Code *in* der SVG-Datei drin stehen? Oder habe ich das falsch verstanden?
Muss er nicht, er könnte auch in einer extra Datei stehen die dann zum dem svg-File gehört. Aber ein File ist meiner Meinung nach praktischer.
Ggf. wenn du meinst das Code in das normale Modul zu packen, da gibt es zumindest bei uns zu viele Spezialfähle, Icons die in Abhängigkeit von Produkteigenschaften und Land auf Etikette gedruckt werden z. B.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Ich kapiere immer noch nicht, *was* Du eigentlich erreichen willst. Bevor wir hier ein XY-Problem lösen, solltest Du einfach mal das "Big Picture" beschreiben. Also was ist der eigentliche Anwendungsfall, was sind Einschränkungen und Bedingungen durch die Umwelt, also etwa Infrastruktur usw.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

@Rigoletto: Kann es sein das Du ein eigenes Template-System erfindest‽ Passt keins der vorhandenen? Falls ja, warum nicht?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

@BlackJack: Ach so... lol... jetzt ergibt das alles Sinn! :-)

@OP: Wenn dem so ist, wie BlackJack vermutet, dann nimm einfach eine exsistierende Template Engine. Ich würde zu Jinja greifen.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Rigoletto
User
Beiträge: 28
Registriert: Freitag 14. Februar 2014, 21:05

Hier ein Beispiel-Etikett das ich kurz anonymisiert habe:

Bild

Wenn das Etikett in Inkscape erstellt ist schreibt man an die Stellen wo Text ersetzt werden soll (benutze string.format()) einfach den Namen des Datenfeldes, z. B. {artikel_bezeichnung}, {dateiname_logo}, usw.
Diese svg-Datei lässt sich dann mit einer csv-Datei zu fertigen Etiketten-Dateien svg/pdf verarbeiten. Hab mir jetzt keine Template-Engine angeschaut, aber glaube das wäre mit Kanonen auf Spatzen geschossen, mit Elementree kann ich einfach den Inhalt und Attribute austauschen, einfügen und entfernen. Sind nur ein paar Zeilen Code fürs Barcode und Textfelder austauschen, Image fehlt mir noch:

Code: Alles auswählen

    def work(self):
        filter_ids = (('barcode', self.barcode), ('tspan', self.tspan))
        for self.labeldata in self.printdata:
            self.tree = copy.deepcopy(self.tmpl)
            self.iter_ = self.tree.iter()
            for elem in self.tree.iter():
                if 'id' in elem.attrib:
                    id = elem.attrib['id']
                    for filter in filter_ids:
                        if id.lower().startswith(filter[0]):
                            func = filter[1]
                            if func != None:
                                func(elem, self.labeldata)
            self.save(self.labeldata['Artikel-Nr'] + ".svg") #todo: replace fixed
     

    def tspan(self, elem, labeldata):
        oldtext = elem.text
        if oldtext != None:
            try:
                newtext = oldtext.format(**labeldata)
            except KeyError:
                newtext = oldtext
        else:
            newtext = oldtext
        elem.text = newtext
        

    def barcode(self, elem, labeldata):
        tag_only = elem.tag.split('}', 1)[1] #todo better solution for namespaces!?
        if tag_only == 'g':
            # remove old barcode
            for subelem in elem.findall(".//"):
                elem.remove(subelem)
            # insert new at same place
            barcode_data = labeldata[elem.attrib['id']]
            barcode_svg = BC.OLsvg()
            barcode_elem = barcode_svg.generateCode(barcode_data)
            for belem in barcode_elem:
                elem.append(belem)
Nun haben die verschieden Artikel Merkmale, die z. b. wie auf der rechten Seite zu sehen durch Icons dargestellt werden, Bodenheizung, Treppenstufen geeignet, usw.
Wenn ich jetzt nur mit einfachen Bedingungen arbeite könnte ich die Icons drucken oder nicht drucken.
Das würde aber unschön aussehen wenn z. b. Icon 1 und 6 gedruckt werden weil eine Lücke dazwischen ist.
Damit die aber immer untereinander stehen müsste eine Funktion schauen wieviele Icons habe ich und diese der Reihe nach in die Platzhalter füllen %icon_merkmal1%, %icon_merkmal2%, usw.

Oder prüfung ob das CE Kennzeichen gedruckt werden darf, wenn ja wird es gedruckt. Zusätzlich muss dann noch geschaut werden darf ich in dem CE Kennzeichen eine Brandklasse drucken.

Nur mir einfachen Bedingungen geht das leider nicht so einfach, oder?
Man könnte zwar die Daten vorher passend zurechtstricken,
aber ich lass Quelldaten gerne unberührt und für später plane ich einen odbc-Zugriff um die Daten direkt aus der Datenbank zu lesen.
Die will ich in der Datenbank nicht ändern.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Rigoletto: ein Template-System nimmt Dir die Arbeit ab, die Du hier angefangen hast, selbst zu programmieren. Ich bin mir noch nicht sicher, ob Du hier eine Klasse brauchst, der gezeigt Code zeigt aber, dass das nicht der Fall ist: die meisten Attribute die Du hier an self bindest, sollten lokale Variablen sein. Bei for-Schleifen machen Attribute nie Sinn, 'self.iter_' wird nirgends verwendet und 'self.tree' sollte wahrscheinlich auch direkt an "save" übergeben werden, was auch immer die Funktion anderes macht als "tree.write" aufzurufen.
tspan und barcode sind eigentlich keine Methoden. filter_ids ist eher etwas, das außerhalb einer Funktion definiert wird und wohl eher ein Wörterbuch ist. Deine Fehlerbehandlung ist ungewöhnlich. 'id.lower().startswith(...)' schreit danach, potentiell schwer lokalisierbare Fehler zu generieren. func dürfte nie None sein. Den KeyError würde man wohl eher sehen wollen, als ihn zu verschweigen. Wenn man oldtext und newtext einfach text nennt, könnte man sich drei Viertel der Zeilen sparen.
Alles in allem schreien die fehlenden Features danach, alles wegzuschmeißen und eine Template-Engine zu benutzen.
BlackJack

Da es um XML geht könnte man noch anmerken das es auch dafür speziell Template-Engines gibt, wie beispielsweise Genshi.
Rigoletto
User
Beiträge: 28
Registriert: Freitag 14. Februar 2014, 21:05

@Sirus, danke für die ausführliche Antwort.
Ich bewzweifle das ein Template-System hier praktikabel wäre. Der Template-Code müsste dann ja mitten zwischen den svg-Elementen eingetragen werden, das kann ich in Inkscape nicht. Dort kann ich aber für eine Gruppe von Elementen ein Attribut mit Python-Code eintragen, dieser Code könnte dann die Gruppe modifizieren.

Bild

Die svg-Dateien werden von unserem Marketing mit Inkscape erstellt und auch nicht mit einem Texteditor nachträglich bearbeitet. Das eintragen von einem einfachen Platzhalter ist ja für die Endandwender kein Problem, die Funktionen für die speziellen Sachen würde ich ja in deren Vorlagen eintragen. Auch kleine SVG-Files sind schon total unübersichtlich, da jinja nachzupflegen wäre schwierig und wenn man das Template ändert kann das ja total danebengehen.

Code: Alles auswählen

<?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="210mm"
   height="297mm"
   viewBox="0 0 744.09448819 1052.3622047"
   id="svg2"
   version="1.1"
   inkscape:version="0.91 r13725"
   sodipodi:docname="beispiel.svg">
  <sodipodi:namedview
     id="base"
     pagecolor="#ffffff"
     bordercolor="#666666"
     borderopacity="1.0"
     inkscape:pageopacity="0.0"
     inkscape:pageshadow="2"
     inkscape:zoom="0.83336326"
     inkscape:cx="238.85203"
     inkscape:cy="526.1811"
     inkscape:document-units="px"
     inkscape:current-layer="layer1"
     showgrid="false"
     inkscape:window-width="1920"
     inkscape:window-height="1028"
     inkscape:window-x="-8"
     inkscape:window-y="-8"
     inkscape:window-maximized="1" />
  <defs
     id="defs4" />
  <metadata
     id="metadata7">
    <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 />
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <g
     id="layer1"
     inkscape:groupmode="layer"
     inkscape:label="Ebene 1">
    <g
       python="insert code here"
       id="g4142">
      <text
         xml:space="preserve"
         style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
         x="63.597717"
         y="68.397545"
         id="text4136"
         sodipodi:linespacing="125%"><tspan
           sodipodi:role="line"
           id="tspan4138"
           x="63.597717"
           y="68.397545">Hello Inkscape</tspan></text>
    </g>
  </g>
</svg>
Ich pack generell alles in eine Klasse, sehe da keinen Nachteil!?
Mit lokal nicht lokal, da hatte ich ein Problem das der Type nach der übergabe nicht mehr stimmte, das muss ich mir nochmal anschauen, das self.iter_ ist noch ein überbleibsel davon.
Save wird später noch prüfen ob eine vorhandene generierte Datei nachträglich geändert wurde, die soll dann nicht einfach neu überschrieben werden.
Wiese sind das keine Methoden, sie sind doch speziell für das Objekt gedacht!? tspan könnte ich zwar generell nutzen, barcode jedoch nicht.
Ja, filter_ids verschieb ich. func ist auch noch ein Rest als ich noch key: None eingetragen hatte.
Den keyerror sehe ich dann an den erzeugten Etiketten, das der Platzhalter nicht ausgetauscht wurde. Finde das ok.
oldtext und newtext ist wirklich unschön, ändere ich auch ab.
BlackJack

@Rigoletto: Wenn Du generell alles in eine Klasse steckst dann schreibst Du also eigentlich Code aus Modulebene mit ``global``-Deklarationen, bloss halt als Klasse getarnt. Ich sehe da einen Nachteil. ;-)

Das sind semantisch keine Methoden weil sie das Objekt auf dem sie definiert sind überhaupt nicht benutzen. Methoden sind dazu da den Zustand des Objekts abzurufen (wenn das komplexer als einfache Werte ist) oder den Zustand des Objekts zu ändern. Das tun die genannten ”Methoden” nicht, also sind das nur verkappte Funktionen die in einer Klasse stecken.

Du siehst aber nur *das* etwas nicht ausgetauscht wurde wegen einem `KeyError`, hast dann Spass beim Suchen *was* das war.
Rigoletto
User
Beiträge: 28
Registriert: Freitag 14. Februar 2014, 21:05

BlackJack hat geschrieben:@Rigoletto: Wenn Du generell alles in eine Klasse steckst dann schreibst Du also eigentlich Code aus Modulebene mit ``global``-Deklarationen, bloss halt als Klasse getarnt. Ich sehe da einen Nachteil. ;-)

Das sind semantisch keine Methoden weil sie das Objekt auf dem sie definiert sind überhaupt nicht benutzen. Methoden sind dazu da den Zustand des Objekts abzurufen (wenn das komplexer als einfache Werte ist) oder den Zustand des Objekts zu ändern. Das tun die genannten ”Methoden” nicht, also sind das nur verkappte Funktionen die in einer Klasse stecken.

Du siehst aber nur *das* etwas nicht ausgetauscht wurde wegen einem `KeyError`, hast dann Spass beim Suchen *was* das war.
Ich kann die Funktion nicht an andere Stelle nutzen, seh da aber kein Nachteil das diese eh zu speziel für andere Sachen wäre.

Wieso, sie ändern meine svg-Daten, die in der Klasse stehen!?

Doch, ich seh in der erzeugten Vorlage das z. B. {Bezeichnung} nicht getauscht wurde, da immer noch {Bezeichnung} da steht, und nicht "SuperDupa Bezeichnung".


Aber können wir zu meiner eigentlichen Frage zurückommen :)
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Rigoletto: Das Argument hingt doch. Du willst doch nicht jedes Ergebnis anschauen und durchsuchen, ob irgendwo ein Platzhalter nicht ersetzt wurde. Ein klarer KeyError im Programm macht doch das Durchschauen überflüssig.

Natürlich ändern Funktionen irgendwelche Datenstrukturen. Aber deshalb muß man nicht alles gleich in eine Klasse stecken, dafür gibt es Funktionsparameter. So baust Du Dir nur undurchsichtige Abhängigkeiten aus quasi globalen Variablen zusammen.

Wenn Du mehr als nur Texte ändern willst, bleibt Dir wohl nichts anderes übrig, als gewisse Änderungen direkt im xml-Text vorzunehmen (Aus- und Einblenden von Elementen per "display", setzen von Positionen, etc.)
Rigoletto
User
Beiträge: 28
Registriert: Freitag 14. Februar 2014, 21:05

Sirius3 hat geschrieben:@Rigoletto: Das Argument hingt doch. Du willst doch nicht jedes Ergebnis anschauen und durchsuchen, ob irgendwo ein Platzhalter nicht ersetzt wurde. Ein klarer KeyError im Programm macht doch das Durchschauen überflüssig.

Natürlich ändern Funktionen irgendwelche Datenstrukturen. Aber deshalb muß man nicht alles gleich in eine Klasse stecken, dafür gibt es Funktionsparameter. So baust Du Dir nur undurchsichtige Abhängigkeiten aus quasi globalen Variablen zusammen.

Wenn Du mehr als nur Texte ändern willst, bleibt Dir wohl nichts anderes übrig, als gewisse Änderungen direkt im xml-Text vorzunehmen (Aus- und Einblenden von Elementen per "display", setzen von Positionen, etc.)
Nein, da die Daten aus einem cvs.DictReader kommen fehlt eine ganze Spalte und ist dann immer auf allen Etiketten zu "sehen". Sollten die Benutzer eine Meldung haben wollen bau ich die ein.

Ehrlich, vielleicht fehlt mir ja hier ja was an Wissen. Dann bitte mal ein praktischen Beispiel wo hier ein Nachteil liegt.

"direkt im xml-Text vorzunehmen"!?? Wieso, das kann ich alles per Code ändern oder was ist mit direkt gemeint? Ich kann alle Elemente löschen in dem ich ein Tag entferne, durch Attributänderung kann ich es verschieben oder aus- und einblenden, usw.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Rigoletto: es bleibt dabei: Fehler sollten dort angezeigt werden, wo sie entstehen und nicht versteck werden, in der Hoffnung, dass sie später irgendwanneinmal auftauchen. Wenn die Daten in der csv-Datei fehlerhaft sind, dann will ich das als Meldung auf dem Bildschirm haben.

Wenn ich das jetzt richtig lese, willst Du eigentlich nicht, dass in der xml-Datei steht, wie die Daten in das svg geschrieben werden, sondern Du willst alles in Python fest verdrahten. Das hast Du doch schon über Deine IDs gemacht. Wo gibt es da also noch Probleme?
Rigoletto
User
Beiträge: 28
Registriert: Freitag 14. Februar 2014, 21:05

Ich will das gleiche erreichen was passiert wenn ich mit Word einen Serienbrief drucke.
In meinem Fall habe ich eine Vorlage in diesem Fall die svg-Datei, und Daten die in einem dict stehen.
Diese werden zusammengeführt, mit dem einfachem {Platzhalter}. Der kann entweder Daten enthalten die ausgeben werden, z. b. eine Bezeichnung, einen Dateinamen, der dann eine Link zur einer Grafik ersetzt oder ein Attribut für ein Element, wie Farbe, Position usw., soweit kein Problem, das funktioniert in den Tests schon alles.
Nur für spezielle Formatierungen wie vorher erwähnt würde ich vorzugsweise in einen <g>-Tag Python Code eintragen wollen der dann die Elemente in der Gruppe speziel behandelt.

Die IDs werden von inkscape zwangsweise immer mit einer durchnummerieten ID versehen. Solange ich keine überschneidungen in den Anfangsbuchstaben habe wie z. B. bei font123, font-color423, font-size235, gibt es keine Problem. Wenn doch muss ich die Prüfung verbessern.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Rigoletto:
Um beim Word/Serienbrief-Vergleich zu bleiben - Inkscape hat eine Pluginschnittstelle, evtl. lässt sich da Python reinklemmen. Dann könntest Du die Etikettendaten per Dialog holen (z.B. CSV-Datei laden) und per Pluginskript als Serie drucken. Falls jede Vorlage spezielle Mappings braucht, die nur in Python gut darstellbar sind, kannst Du das in ein Element mit CDATA im SVG verpacken.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Rigoletto: genau das meinte ich doch. Sobald Du Platzhalter außerhalb von Text-Elementen plazierst (z.B. in style-Attributen) ist das svg nicht mehr richtig darstellbar, weil es halt ein Template und kein fertiges svg ist, also doch direktes editieren auf xml-Ebene. Und dann ist es sinnvoller, eine Template-Sprache zu nehmen, die unabhängig von Python-Code arbeitet, anstatt mit biegen und brechen zu versuchen, da Python-Code ins xml zu schreiben.
Antworten